diff --git a/bin/lazy.sh b/bin/lazy.sh index eb0603701..372799288 100755 --- a/bin/lazy.sh +++ b/bin/lazy.sh @@ -5,7 +5,7 @@ # Optional parameter is the username for signing edits node ./tiddlywiki.js \ - editions/server \ + editions/tw5.com-server \ --verbose \ --server 8080 $:/core/save/lazy-images text/plain text/html $1 $2\ || exit 1 diff --git a/bin/readme.md b/bin/readme.md index c611d2b7b..28064f02d 100644 --- a/bin/readme.md +++ b/bin/readme.md @@ -1,3 +1,3 @@ -
The TiddlyWiki5 repository contains several scripts in the bin
folder that you can use to automate common tasks, or as a useful starting point for your own scripts. See Scripts for building tiddlywiki.com for details of the scripts used to build and release http://tiddlywiki.com/.
All the scripts expect to be run from the root folder of the repository.
serve
: serves tw5.com./bin/serve.sh -h
+Script Files
The TiddlyWiki5 repository contains several scripts in the bin
folder that you can use to automate common tasks, or as a useful starting point for your own scripts. See Scripts for building tiddlywiki.com for details of the scripts used to build and release http://tiddlywiki.com/.
All the scripts expect to be run from the root folder of the repository.
serve
: serves tw5.com
./bin/serve.sh -h
./bin/serve.sh [edition dir] [username] [password] [host] [port]
Or:
./bin/serve.cmd -h
./bin/serve.cmd [edition dir] [username] [password] [host] [port]
This script starts TiddlyWiki5 running as an HTTP server, defaulting to the content from the tw5.com-server
edition. By default, the Node.js serves on port 8080. If the optional username
parameter is provided, it is used for signing edits. If the password
is provided then HTTP basic authentication is used. Run the script with the -h
parameter to see online help.
To experiment with this configuration, run the script and then visit http://127.0.0.1:8080
in a browser.
Changes made in the browser propagate to the server over HTTP (use the browser developer console to see these requests). The server then syncs changes to the file system (and logs each change to the screen).
test
: build and run tests
This script runs the test
edition of TiddlyWiki on the server to perform the server-side tests and to build test.html
for running the tests in the browser.
lazy
: serves tw5.com with lazily loaded images
./bin/lazy.sh <username> [<password>]
Or:
./bin/lazy.cmd <username> [<password>]
This script serves the tw5.com-server
edition content with LazyLoading applied to images.
2bld
: builds TiddlyWiki 2.6.5
This script builds TiddlyWiki 2.6.5 from the original source and then displays the differences between them (diff
is used for *nix, fc
for Windows).
\ No newline at end of file
diff --git a/boot/boot.js b/boot/boot.js
index 9d47c9ba8..e5f6c775e 100644
--- a/boot/boot.js
+++ b/boot/boot.js
@@ -136,7 +136,7 @@ $tw.utils.error = function(err) {
heading = dm("h1",{text: errHeading}),
prompt = dm("div",{text: promptMsg, "class": "tc-error-prompt"}),
message = dm("div",{text: err}),
- button = dm("button",{text: "close"}),
+ button = dm("button",{text: ( $tw.language == undefined ? "close" : $tw.language.getString("Buttons/Close/Caption") )}),
form = dm("form",{children: [heading,prompt,message,button], "class": "tc-error-form"});
document.body.insertBefore(form,document.body.firstChild);
form.addEventListener("submit",function(event) {
@@ -251,15 +251,20 @@ $tw.utils.parseDate = function(value) {
// Stringify an array of tiddler titles into a list string
$tw.utils.stringifyList = function(value) {
- var result = [];
- for(var t=0; tContributing to TiddlyWiki5We welcome contributions to the code and documentation of TiddlyWiki in several ways:
- ReportingBugs
- Helping to improve our documentation
- Contributing to the code via GitHub
- See http://tiddlywiki.com/dev for more details
There are other ways to help TiddlyWiki too.
Contributor License Agreement
Like other OpenSource projects, TiddlyWiki5 needs a signed contributor license agreement from individual contributors. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the UnaMesa Association (the legal entity that owns TiddlyWiki on behalf of the community).
- For individuals use: licenses/CLA-individual
- For entities use: licenses/CLA-entity
How to sign the CLA
Create a GitHub pull request to add your name to cla-individual.md
or cla-entity.md
, with the date in the format (YYYY/MM/DD).
step by step
- Navigate to licenses/CLA-individual or licenses/CLA-entity according to whether you are signing as an individual or representative of an organisation
- Click the edit button at the top-right corner (clicking this button will fork the project so you can edit the file)
- Add your name at the bottom
- eg:
Jeremy Ruston, @Jermolene, 2011/11/22
- Below the edit box for the CLA text you should see a box labelled Propose file change
- Enter a brief title to explain the change (eg, "Signing the CLA")
- Click the green button labelled Propose file change
- On the following screen, click the green button labelled Create pull request
The CLA documents used for this project were created using Harmony Project Templates. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity".
Remarks
+
Contributing to TiddlyWiki5
We welcome contributions to the code and documentation of TiddlyWiki in several ways:
- ReportingBugs
- Helping to improve our documentation
- Contributing to the code via GitHub
- See http://tiddlywiki.com/dev for more details
There are other ways to help TiddlyWiki too.
Contributor License Agreement
Like other OpenSource projects, TiddlyWiki5 needs a signed contributor license agreement from individual contributors. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the UnaMesa Association (the legal entity that owns TiddlyWiki on behalf of the community).
- For individuals use: licenses/CLA-individual
- For entities use: licenses/CLA-entity
How to sign the CLA
Create a GitHub pull request to add your name to cla-individual.md
or cla-entity.md
, with the date in the format (YYYY/MM/DD).
step by step
- Navigate to licenses/CLA-individual or licenses/CLA-entity according to whether you are signing as an individual or representative of an organisation
- Click the "edit" button at the top-right corner (clicking this button will fork the project so you can edit the file)
- Add your name at the bottom
- eg:
Jeremy Ruston, @Jermolene, 2011/11/22
- Below the edit box for the CLA text you should see a box labelled Propose file change
- Enter a brief title to explain the change (eg, "Signing the CLA")
- Click the green button labelled Propose file change
- On the following screen, click the green button labelled Create pull request
The CLA documents used for this project were created using Harmony Project Templates. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity".
Remarks
----—
- When not owning the copyright in the entire work of authorship**
In this case, please clearly state so, since otherwise we assume that you are the legal copyright holder of the contributed work! Please provide links and additional information that clarify under which license the rest of the code is distributed.
This file was automatically generated by TiddlyWiki5
\ No newline at end of file
diff --git a/core/images/advanced-search-button.tid b/core/images/advanced-search-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/auto-height.tid b/core/images/auto-height.tid
new file mode 100755
index 000000000..e65b9c5b6
--- /dev/null
+++ b/core/images/auto-height.tid
@@ -0,0 +1,6 @@
+title: $:/core/images/auto-height
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/blank.tid b/core/images/blank.tid
old mode 100644
new mode 100755
diff --git a/core/images/bold.tid b/core/images/bold.tid
new file mode 100755
index 000000000..0e5d9d8d6
--- /dev/null
+++ b/core/images/bold.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/bold
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/cancel-button.tid b/core/images/cancel-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/chevron-down.tid b/core/images/chevron-down.tid
old mode 100644
new mode 100755
diff --git a/core/images/chevron-left.tid b/core/images/chevron-left.tid
old mode 100644
new mode 100755
diff --git a/core/images/chevron-right.tid b/core/images/chevron-right.tid
old mode 100644
new mode 100755
diff --git a/core/images/chevron-up.tid b/core/images/chevron-up.tid
old mode 100644
new mode 100755
diff --git a/core/images/clone-button.tid b/core/images/clone-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/close-all-button.tid b/core/images/close-all-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/close-button.tid b/core/images/close-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/close-others-button.tid b/core/images/close-others-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/copy-clipboard.tid b/core/images/copy-clipboard.tid
new file mode 100644
index 000000000..583808bd4
--- /dev/null
+++ b/core/images/copy-clipboard.tid
@@ -0,0 +1,15 @@
+title: $:/core/images/copy-clipboard
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/delete-button.tid b/core/images/delete-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/done-button.tid b/core/images/done-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/down-arrow.tid b/core/images/down-arrow.tid
old mode 100644
new mode 100755
diff --git a/core/images/download-button.tid b/core/images/download-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/edit-button.tid b/core/images/edit-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/erase.tid b/core/images/erase.tid
new file mode 100755
index 000000000..ba18f5bd0
--- /dev/null
+++ b/core/images/erase.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/erase
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/excise.tid b/core/images/excise.tid
new file mode 100755
index 000000000..2360cb5a7
--- /dev/null
+++ b/core/images/excise.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/excise
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/export-button.tid b/core/images/export-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/file.tid b/core/images/file.tid
old mode 100644
new mode 100755
index bc91d4208..d66b19e50
--- a/core/images/file.tid
+++ b/core/images/file.tid
@@ -4,11 +4,11 @@ tags: $:/tags/Image
\ No newline at end of file
diff --git a/core/images/fixed-height.tid b/core/images/fixed-height.tid
new file mode 100755
index 000000000..3b53256ab
--- /dev/null
+++ b/core/images/fixed-height.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/fixed-height
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/fold-all-button.tid b/core/images/fold-all-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/fold-button.tid b/core/images/fold-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/fold-others-button.tid b/core/images/fold-others-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/folder.tid b/core/images/folder.tid
old mode 100644
new mode 100755
diff --git a/core/images/full-screen-button.tid b/core/images/full-screen-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/github.tid b/core/images/github.tid
old mode 100644
new mode 100755
diff --git a/core/images/globe.tid b/core/images/globe.tid
old mode 100644
new mode 100755
diff --git a/core/images/heading-1.tid b/core/images/heading-1.tid
new file mode 100755
index 000000000..94d57dee9
--- /dev/null
+++ b/core/images/heading-1.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/heading-1
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/heading-2.tid b/core/images/heading-2.tid
new file mode 100755
index 000000000..65b2e3750
--- /dev/null
+++ b/core/images/heading-2.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/heading-2
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/heading-3.tid b/core/images/heading-3.tid
new file mode 100755
index 000000000..6899440a7
--- /dev/null
+++ b/core/images/heading-3.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/heading-3
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/heading-4.tid b/core/images/heading-4.tid
new file mode 100755
index 000000000..c30a44692
--- /dev/null
+++ b/core/images/heading-4.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/heading-4
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/heading-5.tid b/core/images/heading-5.tid
new file mode 100755
index 000000000..8e0a7fdc8
--- /dev/null
+++ b/core/images/heading-5.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/heading-5
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/heading-6.tid b/core/images/heading-6.tid
new file mode 100755
index 000000000..93f7bcfd7
--- /dev/null
+++ b/core/images/heading-6.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/heading-6
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/help.tid b/core/images/help.tid
old mode 100644
new mode 100755
diff --git a/core/images/home-button.tid b/core/images/home-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/import-button.tid b/core/images/import-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/info-button.tid b/core/images/info-button.tid
old mode 100644
new mode 100755
index 2d9c2cb5c..263fe9056
--- a/core/images/info-button.tid
+++ b/core/images/info-button.tid
@@ -9,5 +9,4 @@ tags: $:/tags/Image
-
\ No newline at end of file
diff --git a/core/images/italic.tid b/core/images/italic.tid
new file mode 100755
index 000000000..f7c46b55c
--- /dev/null
+++ b/core/images/italic.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/italic
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/left-arrow.tid b/core/images/left-arrow.tid
old mode 100644
new mode 100755
diff --git a/core/images/line-width.tid b/core/images/line-width.tid
new file mode 100755
index 000000000..1e8854c4e
--- /dev/null
+++ b/core/images/line-width.tid
@@ -0,0 +1,9 @@
+title: $:/core/images/line-width
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/link.tid b/core/images/link.tid
new file mode 100644
index 000000000..1e094a9b0
--- /dev/null
+++ b/core/images/link.tid
@@ -0,0 +1,9 @@
+title: $:/core/images/link
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/list-bullet.tid b/core/images/list-bullet.tid
new file mode 100755
index 000000000..7951ad229
--- /dev/null
+++ b/core/images/list-bullet.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/list-bullet
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/list-number.tid b/core/images/list-number.tid
new file mode 100755
index 000000000..768f16d25
--- /dev/null
+++ b/core/images/list-number.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/list-number
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/locked-padlock.tid b/core/images/locked-padlock.tid
old mode 100644
new mode 100755
diff --git a/core/images/mail.tid b/core/images/mail.tid
old mode 100644
new mode 100755
diff --git a/core/images/menu-button.tid b/core/images/menu-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/mono-block.tid b/core/images/mono-block.tid
new file mode 100755
index 000000000..1675e3854
--- /dev/null
+++ b/core/images/mono-block.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/mono-block
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/mono-line.tid b/core/images/mono-line.tid
new file mode 100755
index 000000000..81d0faf3f
--- /dev/null
+++ b/core/images/mono-line.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/mono-line
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/new-button.tid b/core/images/new-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/new-here-button.tid b/core/images/new-here-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/new-image-button.tid b/core/images/new-image-button.tid
new file mode 100755
index 000000000..5109bf98d
--- /dev/null
+++ b/core/images/new-image-button.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/new-image-button
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/new-journal-button.tid b/core/images/new-journal-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/opacity.tid b/core/images/opacity.tid
new file mode 100755
index 000000000..e9bb732e1
--- /dev/null
+++ b/core/images/opacity.tid
@@ -0,0 +1,10 @@
+title: $:/core/images/opacity
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/open-window.tid b/core/images/open-window.tid
old mode 100644
new mode 100755
diff --git a/core/images/options-button.tid b/core/images/options-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/paint.tid b/core/images/paint.tid
new file mode 100755
index 000000000..c6dcab834
--- /dev/null
+++ b/core/images/paint.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/paint
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/palette.tid b/core/images/palette.tid
old mode 100644
new mode 100755
diff --git a/core/images/permalink-button.tid b/core/images/permalink-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/permaview-button.tid b/core/images/permaview-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/picture.tid b/core/images/picture.tid
new file mode 100755
index 000000000..71d2bf1a6
--- /dev/null
+++ b/core/images/picture.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/picture
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/plugin-generic-language.tid b/core/images/plugin-generic-language.tid
old mode 100644
new mode 100755
diff --git a/core/images/plugin-generic-plugin.tid b/core/images/plugin-generic-plugin.tid
old mode 100644
new mode 100755
diff --git a/core/images/plugin-generic-theme.tid b/core/images/plugin-generic-theme.tid
old mode 100644
new mode 100755
diff --git a/core/images/preview-closed.tid b/core/images/preview-closed.tid
new file mode 100755
index 000000000..1ca8f24ba
--- /dev/null
+++ b/core/images/preview-closed.tid
@@ -0,0 +1,15 @@
+title: $:/core/images/preview-closed
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/preview-open.tid b/core/images/preview-open.tid
new file mode 100755
index 000000000..17f2319e1
--- /dev/null
+++ b/core/images/preview-open.tid
@@ -0,0 +1,9 @@
+title: $:/core/images/preview-open
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/print-button.tid b/core/images/print-button.tid
new file mode 100644
index 000000000..5e7c1d8e0
--- /dev/null
+++ b/core/images/print-button.tid
@@ -0,0 +1,12 @@
+title: $:/core/images/print-button
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/quote.tid b/core/images/quote.tid
new file mode 100755
index 000000000..882b1debc
--- /dev/null
+++ b/core/images/quote.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/quote
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/refresh-button.tid b/core/images/refresh-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/right-arrow.tid b/core/images/right-arrow.tid
old mode 100644
new mode 100755
diff --git a/core/images/save-button.tid b/core/images/save-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/size.tid b/core/images/size.tid
new file mode 100755
index 000000000..4822dbb50
--- /dev/null
+++ b/core/images/size.tid
@@ -0,0 +1,6 @@
+title: $:/core/images/size
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/spiral.tid b/core/images/spiral.tid
old mode 100644
new mode 100755
diff --git a/core/images/stamp.tid b/core/images/stamp.tid
new file mode 100755
index 000000000..ab8949c1d
--- /dev/null
+++ b/core/images/stamp.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/stamp
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/star-filled.tid b/core/images/star-filled.tid
old mode 100644
new mode 100755
diff --git a/core/images/storyview-classic.tid b/core/images/storyview-classic.tid
old mode 100644
new mode 100755
diff --git a/core/images/storyview-pop.tid b/core/images/storyview-pop.tid
old mode 100644
new mode 100755
diff --git a/core/images/storyview-zoomin.tid b/core/images/storyview-zoomin.tid
old mode 100644
new mode 100755
diff --git a/core/images/strikethrough.tid b/core/images/strikethrough.tid
new file mode 100755
index 000000000..80898c870
--- /dev/null
+++ b/core/images/strikethrough.tid
@@ -0,0 +1,9 @@
+title: $:/core/images/strikethrough
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/subscript.tid b/core/images/subscript.tid
new file mode 100755
index 000000000..a56aa2f1d
--- /dev/null
+++ b/core/images/subscript.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/subscript
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/superscript.tid b/core/images/superscript.tid
new file mode 100755
index 000000000..ca48636e0
--- /dev/null
+++ b/core/images/superscript.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/superscript
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/tag-button.tid b/core/images/tag-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/theme-button.tid b/core/images/theme-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/tip.tid b/core/images/tip.tid
old mode 100644
new mode 100755
diff --git a/core/images/twitter.tid b/core/images/twitter.tid
old mode 100644
new mode 100755
diff --git a/core/images/underline.tid b/core/images/underline.tid
new file mode 100755
index 000000000..22465748b
--- /dev/null
+++ b/core/images/underline.tid
@@ -0,0 +1,8 @@
+title: $:/core/images/underline
+tags: $:/tags/Image
+
+
\ No newline at end of file
diff --git a/core/images/unfold-all-button.tid b/core/images/unfold-all-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/unfold-button.tid b/core/images/unfold-button.tid
old mode 100644
new mode 100755
diff --git a/core/images/unlocked-padlock.tid b/core/images/unlocked-padlock.tid
old mode 100644
new mode 100755
diff --git a/core/images/up-arrow.tid b/core/images/up-arrow.tid
old mode 100644
new mode 100755
diff --git a/core/images/video.tid b/core/images/video.tid
old mode 100644
new mode 100755
diff --git a/core/images/warning.tid b/core/images/warning.tid
old mode 100644
new mode 100755
diff --git a/core/language/en-GB/Buttons.multids b/core/language/en-GB/Buttons.multids
index 1c1c23c50..412e35f8f 100644
--- a/core/language/en-GB/Buttons.multids
+++ b/core/language/en-GB/Buttons.multids
@@ -62,6 +62,10 @@ NewJournal/Caption: new journal
NewJournal/Hint: Create a new journal tiddler
NewJournalHere/Caption: new journal here
NewJournalHere/Hint: Create a new journal tiddler tagged with this one
+NewImage/Caption: new image
+NewImage/Hint: Create a new image tiddler
+NewMarkdown/Caption: new Markdown tiddler
+NewMarkdown/Hint: Create a new Markdown tiddler
NewTiddler/Caption: new tiddler
NewTiddler/Hint: Create a new tiddler
OpenWindow/Caption: open in new window
@@ -72,6 +76,8 @@ Permalink/Caption: permalink
Permalink/Hint: Set browser address bar to a direct link to this tiddler
Permaview/Caption: permaview
Permaview/Hint: Set browser address bar to a direct link to all the tiddlers in this story
+Print/Caption: print page
+Print/Hint: Print the current page
Refresh/Caption: refresh
Refresh/Hint: Perform a full refresh of the wiki
Save/Caption: ok
@@ -88,3 +94,78 @@ TagManager/Caption: tag manager
TagManager/Hint: Open tag manager
Theme/Caption: theme
Theme/Hint: Choose the display theme
+Bold/Caption: bold
+Bold/Hint: Apply bold formatting to selection
+Clear/Caption: clear
+Clear/Hint: Clear image to solid colour
+EditorHeight/Caption: editor height
+EditorHeight/Caption/Auto: Automatically adjust height to fit content
+EditorHeight/Caption/Fixed: Fixed height:
+EditorHeight/Hint: Choose the height of the text editor
+Excise/Caption: excise
+Excise/Caption/Excise: Perform excision
+Excise/Caption/MacroName: Macro name:
+Excise/Caption/NewTitle: Title of new tiddler:
+Excise/Caption/Replace: Replace excised text with:
+Excise/Caption/Replace/Macro: macro
+Excise/Caption/Replace/Link: link
+Excise/Caption/Replace/Transclusion: transclusion
+Excise/Caption/Tag: Tag new tiddler with the title of this tiddler
+Excise/Caption/TiddlerExists: Warning: tiddler already exists
+Excise/Hint: Excise the selected text into a new tiddler
+Heading1/Caption: heading 1
+Heading1/Hint: Apply heading level 1 formatting to lines containing selection
+Heading2/Caption: heading 2
+Heading2/Hint: Apply heading level 2 formatting to lines containing selection
+Heading3/Caption: heading 3
+Heading3/Hint: Apply heading level 3 formatting to lines containing selection
+Heading4/Caption: heading 4
+Heading4/Hint: Apply heading level 4 formatting to lines containing selection
+Heading5/Caption: heading 5
+Heading5/Hint: Apply heading level 5 formatting to lines containing selection
+Heading6/Caption: heading 6
+Heading6/Hint: Apply heading level 6 formatting to lines containing selection
+Italic/Caption: italic
+Italic/Hint: Apply italic formatting to selection
+LineWidth/Caption: line width
+LineWidth/Hint: Set line width for painting
+Link/Caption: link
+Link/Hint: Create wikitext link
+ListBullet/Caption: bulleted list
+ListBullet/Hint: Apply bulleted list formatting to lines containing selection
+ListNumber/Caption: numbered list
+ListNumber/Hint: Apply numbered list formatting to lines containing selection
+MonoBlock/Caption: monospaced block
+MonoBlock/Hint: Apply monospaced block formatting to lines containing selection
+MonoLine/Caption: monospaced
+MonoLine/Hint: Apply monospaced character formatting to selection
+Opacity/Caption: opacity
+Opacity/Hint: Set painting opacity
+Paint/Caption: paint colour
+Paint/Hint: Set painting colour
+Picture/Caption: picture
+Picture/Hint: Insert picture
+Preview/Caption: preview
+Preview/Hint: Show preview pane
+PreviewType/Caption: preview type
+PreviewType/Hint: Choose preview type
+Quote/Caption: quote
+Quote/Hint: Apply quoted text formatting to lines containing selection
+Size/Caption: image size
+Size/Caption/Height: Height:
+Size/Caption/Resize: Resize image
+Size/Caption/Width: Width:
+Size/Hint: Set image size
+Stamp/Caption: stamp
+Stamp/Caption/New: Add your own
+Stamp/Hint: Insert a preconfigured snippet of text
+Stamp/New/Title: Name as shown in menu
+Stamp/New/Text: Text of snippet. (Remember to add a descriptive title in the caption field).
+Strikethrough/Caption: strikethrough
+Strikethrough/Hint: Apply strikethrough formatting to selection
+Subscript/Caption: subscript
+Subscript/Hint: Apply subscript formatting to selection
+Superscript/Caption: superscript
+Superscript/Hint: Apply superscript formatting to selection
+Underline/Caption: underline
+Underline/Hint: Apply underline formatting to selection
diff --git a/core/language/en-GB/ControlPanel.multids b/core/language/en-GB/ControlPanel.multids
index abd4e9ae7..5204d62aa 100644
--- a/core/language/en-GB/ControlPanel.multids
+++ b/core/language/en-GB/ControlPanel.multids
@@ -27,6 +27,19 @@ EditorTypes/Hint: These tiddlers determine which editor is used to edit specific
EditorTypes/Type/Caption: Type
Info/Caption: Info
Info/Hint: Information about this TiddlyWiki
+KeyboardShortcuts/Add/Prompt: Type shortcut here
+KeyboardShortcuts/Add/Caption: add shortcut
+KeyboardShortcuts/Caption: Keyboard Shortcuts
+KeyboardShortcuts/Hint: Manage keyboard shortcut assignments
+KeyboardShortcuts/NoShortcuts/Caption: No keyboard shortcuts assigned
+KeyboardShortcuts/Remove/Hint: remove keyboard shortcut
+KeyboardShortcuts/Platform/All: All platforms
+KeyboardShortcuts/Platform/Mac: Macintosh platform only
+KeyboardShortcuts/Platform/NonMac: Non-Macintosh platforms only
+KeyboardShortcuts/Platform/Linux: Linux platform only
+KeyboardShortcuts/Platform/NonLinux: Non-Linux platforms only
+KeyboardShortcuts/Platform/Windows: Windows platform only
+KeyboardShortcuts/Platform/NonWindows: Non-Windows platforms only
LoadedModules/Caption: Loaded Modules
LoadedModules/Hint: These are the currently loaded tiddler modules linked to their source tiddlers. Any italicised modules lack a source tiddler, typically because they were setup during the boot process.
Palette/Caption: Palette
@@ -39,12 +52,13 @@ Palette/HideEditor/Caption: hide editor
Palette/Prompt: Current palette:
Palette/ShowEditor/Caption: show editor
Parsing/Caption: Parsing
-Parsing/Hint: Here you can globally disable individual wiki parser rules. Take care as disabling some parser rules can prevent ~TiddlyWiki functioning correctly (you can restore normal operation with [[safe mode|http://tiddlywiki.com/#SafeMode]] )
+Parsing/Hint: Here you can globally disable/enable wiki parser rules. For changes to take effect, save and reload your wiki. Disabling certain parser rules can prevent <$text text="TiddlyWiki"/> from functioning correctly. Use [[safe mode|http://tiddlywiki.com/#SafeMode]] to restore normal operation.
Parsing/Block/Caption: Block Parse Rules
Parsing/Inline/Caption: Inline Parse Rules
Parsing/Pragma/Caption: Pragma Parse Rules
-Plugins/Add/Hint: Install plugins from the official library
Plugins/Add/Caption: Get more plugins
+Plugins/Add/Hint: Install plugins from the official library
+Plugins/AlreadyInstalled/Hint: This plugin is already installed at version <$text text=<>/>
Plugins/Caption: Plugins
Plugins/Disable/Caption: disable
Plugins/Disable/Hint: Disable this plugin when reloading page
@@ -52,14 +66,17 @@ Plugins/Disabled/Status: (disabled)
Plugins/Empty/Hint: None
Plugins/Enable/Caption: enable
Plugins/Enable/Hint: Enable this plugin when reloading page
-Plugins/Install: install
+Plugins/Install/Caption: install
Plugins/Installed/Hint: Currently installed plugins:
Plugins/Languages/Caption: Languages
Plugins/Languages/Hint: Language pack plugins
+Plugins/NoInfoFound/Hint: No ''"<$text text=<>/>"'' found
+Plugins/NoInformation/Hint: No information provided
+Plugins/NotInstalled/Hint: This plugin is not currently installed
Plugins/OpenPluginLibrary: open plugin library
Plugins/Plugins/Caption: Plugins
Plugins/Plugins/Hint: Plugins
-Plugins/Reinstall: reinstall
+Plugins/Reinstall/Caption: reinstall
Plugins/Themes/Caption: Themes
Plugins/Themes/Hint: Theme plugins
Saving/Caption: Saving
@@ -83,6 +100,9 @@ Settings/CamelCase/Caption: Camel Case Wiki Links
Settings/CamelCase/Hint: You can globally disable automatic linking of ~CamelCase phrases. Requires reload to take effect
Settings/CamelCase/Description: Enable automatic ~CamelCase linking
Settings/Caption: Settings
+Settings/EditorToolbar/Caption: Editor Toolbar
+Settings/EditorToolbar/Hint: Enable or disable the editor toolbar:
+Settings/EditorToolbar/Description: Show editor toolbar
Settings/Hint: These settings let you customise the behaviour of TiddlyWiki.
Settings/NavigationAddressBar/Caption: Navigation Address Bar
Settings/NavigationAddressBar/Hint: Behaviour of the browser address bar when navigating to a tiddler:
@@ -118,6 +138,9 @@ Settings/TitleLinks/Caption: Tiddler Titles
Settings/TitleLinks/Hint: Optionally display tiddler titles as links
Settings/TitleLinks/No/Description: Do not display tiddler titles as links
Settings/TitleLinks/Yes/Description: Display tiddler titles as links
+Settings/MissingLinks/Caption: Wiki Links
+Settings/MissingLinks/Hint: Choose whether to link to tiddlers that do not exist yet
+Settings/MissingLinks/Description: Enable links to missing tiddlers
StoryView/Caption: Story View
StoryView/Prompt: Current view:
Theme/Caption: Theme
@@ -130,6 +153,8 @@ Toolbars/EditToolbar/Hint: Choose which buttons are displayed for tiddlers in ed
Toolbars/Hint: Select which toolbar buttons are displayed
Toolbars/PageControls/Caption: Page Toolbar
Toolbars/PageControls/Hint: Choose which buttons are displayed on the main page toolbar
+Toolbars/EditorToolbar/Caption: Editor Toolbar
+Toolbars/EditorToolbar/Hint: Choose which buttons are displayed in the editor toolbar. Note that some buttons will only appear when editing tiddlers of a certain type
Toolbars/ViewToolbar/Caption: View Toolbar
Toolbars/ViewToolbar/Hint: Choose which buttons are displayed for tiddlers in view mode
Tools/Download/Full/Caption: Download full wiki
diff --git a/core/language/en-GB/Docs/ModuleTypes.multids b/core/language/en-GB/Docs/ModuleTypes.multids
index 7d666e309..b58c85ad8 100644
--- a/core/language/en-GB/Docs/ModuleTypes.multids
+++ b/core/language/en-GB/Docs/ModuleTypes.multids
@@ -1,16 +1,21 @@
title: $:/language/Docs/ModuleTypes/
+allfilteroperator: A sub-operator for the ''all'' filter operator.
animation: Animations that may be used with the RevealWidget.
+bitmapeditoroperation: A bitmap editor toolbar operation.
command: Commands that can be executed under Node.js.
config: Data to be inserted into `$tw.config`.
filteroperator: Individual filter operator methods.
global: Global data to be inserted into `$tw`.
+info: Publishes system information via the [[$:/temp/info-plugin]] pseudo-plugin.
isfilteroperator: Operands for the ''is'' filter operator.
+library: Generic module type for general purpose JavaScript modules.
macro: JavaScript macro definitions.
parser: Parsers for different content types.
saver: Savers handle different methods for saving files from the browser.
startup: Startup functions.
storyview: Story views customise the animation and behaviour of list widgets.
+texteditoroperation: A text editor toolbar operation.
tiddlerdeserializer: Converts different content types into tiddlers.
tiddlerfield: Defines the behaviour of an individual tiddler field.
tiddlermethod: Adds methods to the `$tw.Tiddler` prototype.
diff --git a/core/language/en-GB/EditTemplate.multids b/core/language/en-GB/EditTemplate.multids
index 2125495e2..8d8801af9 100644
--- a/core/language/en-GB/EditTemplate.multids
+++ b/core/language/en-GB/EditTemplate.multids
@@ -1,10 +1,8 @@
title: $:/language/EditTemplate/
Body/External/Hint: This is an external tiddler stored outside of the main TiddlyWiki file. You can edit the tags and fields but cannot directly edit the content itself
-Body/Hint: Use [[wiki text|http://tiddlywiki.com/static/WikiText.html]] to add formatting, images, and dynamic features
Body/Placeholder: Type the text for this tiddler
-Body/Preview/Button/Hide: hide preview
-Body/Preview/Button/Show: show preview
+Body/Preview/Type/Output: output
Field/Remove/Caption: remove field
Field/Remove/Hint: Remove field
Fields/Add/Button: add
@@ -19,6 +17,7 @@ Tags/Add/Button: add
Tags/Add/Placeholder: tag name
Tags/Dropdown/Caption: tag list
Tags/Dropdown/Hint: Show tag list
+Title/BadCharacterWarning: Warning: avoid using any of the characters <> in tiddler titles
Type/Dropdown/Caption: content type list
Type/Dropdown/Hint: Show content type list
Type/Delete/Caption: delete content type
diff --git a/core/language/en-GB/GettingStarted.tid b/core/language/en-GB/GettingStarted.tid
index 2be983442..5911f04d7 100644
--- a/core/language/en-GB/GettingStarted.tid
+++ b/core/language/en-GB/GettingStarted.tid
@@ -11,7 +11,7 @@ Before you start storing important information in ~TiddlyWiki it is important to
|<$link to="$:/SiteTitle"><>$link> |<$edit-text tiddler="$:/SiteTitle" default="" tag="input"/> |
|<$link to="$:/SiteSubtitle"><>$link> |<$edit-text tiddler="$:/SiteSubtitle" default="" tag="input"/> |
-|<$link to="$:/DefaultTiddlers"><>$link> |<>
<$edit-text tag="textarea" tiddler="$:/DefaultTiddlers"/>
//<>// |
+|<$link to="$:/DefaultTiddlers"><>$link> |<>
<$edit tag="textarea" tiddler="$:/DefaultTiddlers"/>
//<>// |
See the [[control panel|$:/ControlPanel]] for more options.
diff --git a/core/language/en-GB/Import.multids b/core/language/en-GB/Import.multids
index 9475d6a2c..697d3b510 100644
--- a/core/language/en-GB/Import.multids
+++ b/core/language/en-GB/Import.multids
@@ -1,6 +1,6 @@
title: $:/language/Import/
-Imported: The following tiddlers were imported:
+Imported/Hint: The following tiddlers were imported:
Listing/Cancel/Caption: Cancel
Listing/Hint: These tiddlers are ready to import:
Listing/Import/Caption: Import
diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids
index d953eb01f..c21b68326 100644
--- a/core/language/en-GB/Misc.multids
+++ b/core/language/en-GB/Misc.multids
@@ -5,10 +5,12 @@ 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 http://tiddlywiki.com/static/Upgrading.html for more details.
ClassicWarning/Upgrade/Caption: upgrade
CloseAll/Button: close all
+ColourPicker/Recent: Recent:
ConfirmCancelTiddler: Do you wish to discard changes to the tiddler "<$text text=<>/>"?
ConfirmDeleteTiddler: Do you wish to delete the tiddler "<$text text=<>/>"?
ConfirmOverwriteTiddler: Do you wish to overwrite the tiddler "<$text text=<>/>"?
ConfirmEditShadowTiddler: You are about to edit a ShadowTiddler. Any changes will override the default system making future upgrades non-trivial. Are you sure you want to edit "<$text text=<>/>"?
+Count: count
DefaultNewTiddlerTitle: New Tiddler
DropMessage: Drop here (or use the 'Escape' key to cancel)
Encryption/Cancel: Cancel
@@ -19,11 +21,23 @@ Encryption/Password: Password
Encryption/RepeatPassword: Repeat password
Encryption/PasswordNoMatch: Passwords do not match
Encryption/SetPassword: Set password
+Error/Caption: Error
+Error/Filter: Filter error
+Error/FilterSyntax: Syntax error in filter expression
+Error/IsFilterOperator: Filter Error: Unknown operand for the 'is' filter operator
+Error/LoadingPluginLibrary: Error loading plugin library
+Error/RecursiveTransclusion: Recursive transclusion error in transclude widget
+Error/RetrievingSkinny: Error retrieving skinny tiddler list
+Error/SavingToTWEdit: Error saving to TWEdit
+Error/WhileSaving: Error while saving
+Error/XMLHttpRequest: XMLHttpRequest error code
InternalJavaScriptError/Title: Internal JavaScript Error
InternalJavaScriptError/Hint: Well, this is embarrassing. It is recommended that you restart TiddlyWiki by refreshing your browser
InvalidFieldName: Illegal characters in field name "<$text text=<>/>". Fields can only contain lowercase letters, digits and the characters underscore (`_`), hyphen (`-`) and period (`.`)
LazyLoadingWarning: Loading external text from ''<$text text={{!!_canonical_uri}}/>''
If this message doesn't disappear you may be using a browser that doesn't support external text in this configuration. See http://tiddlywiki.com/#ExternalText
+LoginToTiddlySpace: Login to TiddlySpace
MissingTiddler/Hint: Missing tiddler "<$text text=<>/>" - click {{$:/core/images/edit-button}} to create
+No: No
OfficialPluginLibrary: Official ~TiddlyWiki Plugin Library
OfficialPluginLibrary/Hint: The official ~TiddlyWiki plugin library at tiddlywiki.com. Plugins, themes and language packs are maintained by the core team.
PluginReloadWarning: Please save {{$:/core/ui/Buttons/save-wiki}} and reload {{$:/core/ui/Buttons/refresh}} to allow changes to plugins to take effect
@@ -36,3 +50,4 @@ TagManager/Info/Heading: Info
TagManager/Tag/Heading: Tag
Tiddler/DateFormat: DDth MMM YYYY at hh12:0mmam
UnsavedChangesWarning: You have unsaved changes in TiddlyWiki
+Yes: Yes
diff --git a/core/language/en-GB/Search.multids b/core/language/en-GB/Search.multids
index fd6eadc7e..f6b207000 100644
--- a/core/language/en-GB/Search.multids
+++ b/core/language/en-GB/Search.multids
@@ -8,6 +8,7 @@ Matches: //<> matches //
Matches/All: All matches:
Matches/Title: Title matches:
Search: Search
+Search/TooShort: Search text too short
Shadows/Caption: Shadows
Shadows/Hint: Search for shadow tiddlers
Shadows/Matches: //<> matches //
diff --git a/core/language/en-GB/Snippets/ListByTag.tid b/core/language/en-GB/Snippets/ListByTag.tid
new file mode 100644
index 000000000..e8f9cef49
--- /dev/null
+++ b/core/language/en-GB/Snippets/ListByTag.tid
@@ -0,0 +1,5 @@
+title: $:/language/Snippets/ListByTag
+tags: $:/tags/TextEditor/Snippet
+caption: List of tiddlers by tag
+
+<>
diff --git a/core/language/en-GB/Snippets/MacroDefinition.tid b/core/language/en-GB/Snippets/MacroDefinition.tid
new file mode 100644
index 000000000..3829ac903
--- /dev/null
+++ b/core/language/en-GB/Snippets/MacroDefinition.tid
@@ -0,0 +1,7 @@
+title: $:/language/Snippets/MacroDefinition
+tags: $:/tags/TextEditor/Snippet
+caption: Macro definition
+
+\define macroName(param1:"default value",param2)
+Text of the macro
+\end
diff --git a/core/language/en-GB/Snippets/Table 4x3.tid b/core/language/en-GB/Snippets/Table 4x3.tid
new file mode 100644
index 000000000..99f64a68d
--- /dev/null
+++ b/core/language/en-GB/Snippets/Table 4x3.tid
@@ -0,0 +1,8 @@
+title: $:/language/Snippets/Table4x3
+tags: $:/tags/TextEditor/Snippet
+caption: Table with 4 columns by 3 rows
+
+|! |!Alpha |!Beta |!Gamma |!Delta |
+|!One | | | | |
+|!Two | | | | |
+|!Three | | | | |
diff --git a/core/language/en-GB/Snippets/TableOfContents.tid b/core/language/en-GB/Snippets/TableOfContents.tid
new file mode 100644
index 000000000..40fe4aafb
--- /dev/null
+++ b/core/language/en-GB/Snippets/TableOfContents.tid
@@ -0,0 +1,9 @@
+title: $:/language/Snippets/TableOfContents
+tags: $:/tags/TextEditor/Snippet
+caption: Table of Contents
+
+
+
+<>
+
+
\ No newline at end of file
diff --git a/core/language/en-GB/ThemeTweaks.multids b/core/language/en-GB/ThemeTweaks.multids
new file mode 100644
index 000000000..694d56b13
--- /dev/null
+++ b/core/language/en-GB/ThemeTweaks.multids
@@ -0,0 +1,41 @@
+title: $:/language/ThemeTweaks/
+
+ThemeTweaks: Theme Tweaks
+ThemeTweaks/Hint: You can tweak certain aspects of the ''Vanilla'' theme.
+Options: Options
+Options/SidebarLayout: Sidebar layout
+Options/SidebarLayout/Fixed-Fluid: Fixed story, fluid sidebar
+Options/SidebarLayout/Fluid-Fixed: Fluid story, fixed sidebar
+Options/StickyTitles: Sticky titles
+Options/StickyTitles/Hint: Causes tiddler titles to "stick" to the top of the browser window. Caution: Does not work at all with Chrome, and causes some layout issues in Firefox
+Options/CodeWrapping: Wrap long lines in code blocks
+Settings: Settings
+Settings/FontFamily: Font family
+Settings/CodeFontFamily: Code font family
+Settings/BackgroundImage: Page background image
+Settings/BackgroundImageAttachment: Page background image attachment
+Settings/BackgroundImageAttachment/Scroll: Scroll with tiddlers
+Settings/BackgroundImageAttachment/Fixed: Fixed to window
+Settings/BackgroundImageSize: Page background image size
+Settings/BackgroundImageSize/Auto: Auto
+Settings/BackgroundImageSize/Cover: Cover
+Settings/BackgroundImageSize/Contain: Contain
+Metrics: Sizes
+Metrics/FontSize: Font size
+Metrics/LineHeight: Line height
+Metrics/BodyFontSize: Font size for tiddler body
+Metrics/BodyLineHeight: Line height for tiddler body
+Metrics/StoryLeft: Story left position
+Metrics/StoryLeft/Hint: how far the left margin of the story river
(tiddler area) is from the left of the page
+Metrics/StoryTop: Story top position
+Metrics/StoryTop/Hint: how far the top margin of the story river
is from the top of the page
+Metrics/StoryRight: Story right
+Metrics/StoryRight/Hint: how far the left margin of the sidebar
is from the left of the page
+Metrics/StoryWidth: Story width
+Metrics/StoryWidth/Hint: the overall width of the story river
+Metrics/TiddlerWidth: Tiddler width
+Metrics/TiddlerWidth/Hint: within the story river
+Metrics/SidebarBreakpoint: Sidebar breakpoint
+Metrics/SidebarBreakpoint/Hint: the minimum page width at which the story
river and sidebar will appear side by side
+Metrics/SidebarWidth: Sidebar width
+Metrics/SidebarWidth/Hint: the width of the sidebar in fluid-fixed layout
diff --git a/core/modules/commands/savetiddler.js b/core/modules/commands/savetiddler.js
index b6c6ac376..efc484ec7 100644
--- a/core/modules/commands/savetiddler.js
+++ b/core/modules/commands/savetiddler.js
@@ -32,13 +32,17 @@ Command.prototype.execute = function() {
path = require("path"),
title = this.params[0],
filename = path.resolve(this.commander.outputPath,this.params[1]),
- tiddler = this.commander.wiki.getTiddler(title),
- type = tiddler.fields.type || "text/vnd.tiddlywiki",
- contentTypeInfo = $tw.config.contentTypeInfo[type] || {encoding: "utf8"};
- $tw.utils.createFileDirectories(filename);
- fs.writeFile(filename,tiddler.fields.text,contentTypeInfo.encoding,function(err) {
- self.callback(err);
- });
+ tiddler = this.commander.wiki.getTiddler(title);
+ if(tiddler) {
+ var type = tiddler.fields.type || "text/vnd.tiddlywiki",
+ contentTypeInfo = $tw.config.contentTypeInfo[type] || {encoding: "utf8"};
+ $tw.utils.createFileDirectories(filename);
+ fs.writeFile(filename,tiddler.fields.text,contentTypeInfo.encoding,function(err) {
+ self.callback(err);
+ });
+ } else {
+ return "Missing tiddler: " + title;
+ }
return null;
};
diff --git a/core/modules/deserializers.js b/core/modules/deserializers.js
index 00815f831..16f5a373f 100644
--- a/core/modules/deserializers.js
+++ b/core/modules/deserializers.js
@@ -72,8 +72,16 @@ exports["application/x-tiddler-html-div"] = function(text,fields) {
};
exports["application/json"] = function(text,fields) {
- var incoming = JSON.parse(text),
+ var incoming,
results = [];
+ try {
+ incoming = JSON.parse(text);
+ } catch(e) {
+ incoming = [{
+ title: "JSON error: " + e,
+ text: ""
+ }]
+ }
if($tw.utils.isArray(incoming)) {
for(var t=0; t 0);
+ };
+
+ /*
+ Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
+ */
+ EditTextWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes();
+ // Completely rerender if any of our attributes have changed
+ if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.placeholder || changedAttributes.size || changedAttributes.autoHeight || changedAttributes.minHeight || changedAttributes.focusPopup || changedAttributes.rows || changedTiddlers[HEIGHT_MODE_TITLE] || changedTiddlers[ENABLE_TOOLBAR_TITLE]) {
+ this.refreshSelf();
+ return true;
+ } else if(changedTiddlers[this.editTitle]) {
+ var editInfo = this.getEditInfo();
+ this.updateEditor(editInfo.value,editInfo.type);
+ }
+ this.engine.fixHeight();
+ if(this.editShowToolbar) {
+ return this.refreshChildren(changedTiddlers);
+ } else {
+ return false;
+ }
+ };
+
+ /*
+ Update the editor with new text. This method is separate from updateEditorDomNode()
+ so that subclasses can override updateEditor() and still use updateEditorDomNode()
+ */
+ EditTextWidget.prototype.updateEditor = function(text,type) {
+ this.updateEditorDomNode(text,type);
+ };
+
+ /*
+ Update the editor dom node with new text
+ */
+ EditTextWidget.prototype.updateEditorDomNode = function(text,type) {
+ this.engine.setText(text,type);
+ };
+
+ /*
+ Save changes back to the tiddler store
+ */
+ EditTextWidget.prototype.saveChanges = function(text) {
+ var editInfo = this.getEditInfo();
+ if(text !== editInfo.value) {
+ editInfo.update(text);
+ }
+ };
+
+ /*
+ Handle a dom "keydown" event, which we'll bubble up to our container for the keyboard widgets benefit
+ */
+ EditTextWidget.prototype.handleKeydownEvent = function(event) {
+ // Check for a keyboard shortcut
+ if(this.toolbarNode) {
+ var shortcutElements = this.toolbarNode.querySelectorAll("[data-tw-keyboard-shortcut]");
+ for(var index=0; index 0 && newHeight > 0 && !(newWidth === this.currCanvas.width && newHeight === this.currCanvas.height)) {
+ this.changeCanvasSize(newWidth,newHeight);
+ }
+ // Update the input controls
+ this.refreshToolbar();
+ // Save the image into the tiddler
+ this.saveChanges();
+};
+
+})();
diff --git a/core/modules/editor/operations/text/excise.js b/core/modules/editor/operations/text/excise.js
new file mode 100644
index 000000000..ced771719
--- /dev/null
+++ b/core/modules/editor/operations/text/excise.js
@@ -0,0 +1,49 @@
+/*\
+title: $:/core/modules/editor/operations/text/excise.js
+type: application/javascript
+module-type: texteditoroperation
+
+Text editor operation to excise the selection to a new tiddler
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["excise"] = function(event,operation) {
+ var editTiddler = this.wiki.getTiddler(this.editTitle),
+ editTiddlerTitle = this.editTitle;
+ if(editTiddler && editTiddler.fields["draft.of"]) {
+ editTiddlerTitle = editTiddler.fields["draft.of"];
+ }
+ var excisionTitle = event.paramObject.title || this.wiki.generateNewTitle("New Excision");
+ this.wiki.addTiddler(new $tw.Tiddler(
+ this.wiki.getCreationFields(),
+ this.wiki.getModificationFields(),
+ {
+ title: excisionTitle,
+ text: operation.selection,
+ tags: event.paramObject.tagnew === "yes" ? [editTiddlerTitle] : []
+ }
+ ));
+ operation.replacement = excisionTitle;
+ switch(event.paramObject.type || "transclude") {
+ case "transclude":
+ operation.replacement = "{{" + operation.replacement+ "}}";
+ break;
+ case "link":
+ operation.replacement = "[[" + operation.replacement+ "]]";
+ break;
+ case "macro":
+ operation.replacement = "<<" + (event.paramObject.macro || "translink") + " \"\"\"" + operation.replacement + "\"\"\">>";
+ break;
+ }
+ operation.cutStart = operation.selStart;
+ operation.cutEnd = operation.selEnd;
+ operation.newSelStart = operation.selStart;
+ operation.newSelEnd = operation.selStart + operation.replacement.length;
+};
+
+})();
diff --git a/core/modules/editor/operations/text/make-link.js b/core/modules/editor/operations/text/make-link.js
new file mode 100644
index 000000000..e8caf21c5
--- /dev/null
+++ b/core/modules/editor/operations/text/make-link.js
@@ -0,0 +1,29 @@
+/*\
+title: $:/core/modules/editor/operations/text/make-link.js
+type: application/javascript
+module-type: texteditoroperation
+
+Text editor operation to make a link
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["make-link"] = function(event,operation) {
+ if(operation.selection) {
+ operation.replacement = "[[" + operation.selection + "|" + event.paramObject.text + "]]";
+ operation.cutStart = operation.selStart;
+ operation.cutEnd = operation.selEnd;
+ } else {
+ operation.replacement = "[[" + event.paramObject.text + "]]";
+ operation.cutStart = operation.selStart;
+ operation.cutEnd = operation.selEnd;
+ }
+ operation.newSelStart = operation.selStart + operation.replacement.length;
+ operation.newSelEnd = operation.newSelStart;
+};
+
+})();
diff --git a/core/modules/editor/operations/text/prefix-lines.js b/core/modules/editor/operations/text/prefix-lines.js
new file mode 100644
index 000000000..ad67232fc
--- /dev/null
+++ b/core/modules/editor/operations/text/prefix-lines.js
@@ -0,0 +1,54 @@
+/*\
+title: $:/core/modules/editor/operations/text/prefix-lines.js
+type: application/javascript
+module-type: texteditoroperation
+
+Text editor operation to add a prefix to the selected lines
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["prefix-lines"] = function(event,operation) {
+ // Cut just past the preceding line break, or the start of the text
+ operation.cutStart = $tw.utils.findPrecedingLineBreak(operation.text,operation.selStart);
+ // Cut to just past the following line break, or to the end of the text
+ operation.cutEnd = $tw.utils.findFollowingLineBreak(operation.text,operation.selEnd);
+ // Compose the required prefix
+ var prefix = $tw.utils.repeat(event.paramObject.character,event.paramObject.count);
+ // Process each line
+ var lines = operation.text.substring(operation.cutStart,operation.cutEnd).split(/\r?\n/mg);
+ $tw.utils.each(lines,function(line,index) {
+ // Remove and count any existing prefix characters
+ var count = 0;
+ while(line.charAt(0) === event.paramObject.character) {
+ line = line.substring(1);
+ count++;
+ }
+ // Remove any whitespace
+ while(line.charAt(0) === " ") {
+ line = line.substring(1);
+ }
+ // We're done if we removed the exact required prefix, otherwise add it
+ if(count !== event.paramObject.count) {
+ // Apply the prefix
+ line = prefix + " " + line;
+ }
+ // Save the modified line
+ lines[index] = line;
+ });
+ // Stitch the replacement text together and set the selection
+ operation.replacement = lines.join("\n");
+ if(lines.length === 1) {
+ operation.newSelStart = operation.cutStart + operation.replacement.length;
+ operation.newSelEnd = operation.newSelStart;
+ } else {
+ operation.newSelStart = operation.cutStart;
+ operation.newSelEnd = operation.newSelStart + operation.replacement.length;
+ }
+};
+
+})();
diff --git a/core/modules/editor/operations/text/replace-all.js b/core/modules/editor/operations/text/replace-all.js
new file mode 100644
index 000000000..fc1541935
--- /dev/null
+++ b/core/modules/editor/operations/text/replace-all.js
@@ -0,0 +1,23 @@
+/*\
+title: $:/core/modules/editor/operations/text/replace-all.js
+type: application/javascript
+module-type: texteditoroperation
+
+Text editor operation to replace the entire text
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["replace-all"] = function(event,operation) {
+ operation.cutStart = 0;
+ operation.cutEnd = operation.text.length;
+ operation.replacement = event.paramObject.text;
+ operation.newSelStart = 0;
+ operation.newSelEnd = operation.replacement.length;
+};
+
+})();
diff --git a/core/modules/editor/operations/text/replace-selection.js b/core/modules/editor/operations/text/replace-selection.js
new file mode 100644
index 000000000..740a41fb1
--- /dev/null
+++ b/core/modules/editor/operations/text/replace-selection.js
@@ -0,0 +1,23 @@
+/*\
+title: $:/core/modules/editor/operations/text/replace-selection.js
+type: application/javascript
+module-type: texteditoroperation
+
+Text editor operation to replace the selection
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["replace-selection"] = function(event,operation) {
+ operation.replacement = event.paramObject.text;
+ operation.cutStart = operation.selStart;
+ operation.cutEnd = operation.selEnd;
+ operation.newSelStart = operation.selStart;
+ operation.newSelEnd = operation.selStart + operation.replacement.length;
+};
+
+})();
diff --git a/core/modules/editor/operations/text/wrap-lines.js b/core/modules/editor/operations/text/wrap-lines.js
new file mode 100644
index 000000000..521811f50
--- /dev/null
+++ b/core/modules/editor/operations/text/wrap-lines.js
@@ -0,0 +1,28 @@
+/*\
+title: $:/core/modules/editor/operations/text/wrap-lines.js
+type: application/javascript
+module-type: texteditoroperation
+
+Text editor operation to wrap the selected lines with a prefix and suffix
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["wrap-lines"] = function(event,operation) {
+ // Cut just past the preceding line break, or the start of the text
+ operation.cutStart = $tw.utils.findPrecedingLineBreak(operation.text,operation.selStart);
+ // Cut to just past the following line break, or to the end of the text
+ operation.cutEnd = $tw.utils.findFollowingLineBreak(operation.text,operation.selEnd);
+ // Add the prefix and suffix
+ operation.replacement = event.paramObject.prefix + "\n" +
+ operation.text.substring(operation.cutStart,operation.cutEnd) + "\n" +
+ event.paramObject.suffix + "\n";
+ operation.newSelStart = operation.cutStart + event.paramObject.prefix.length + 1;
+ operation.newSelEnd = operation.newSelStart + (operation.cutEnd - operation.cutStart);
+};
+
+})();
diff --git a/core/modules/editor/operations/text/wrap-selection.js b/core/modules/editor/operations/text/wrap-selection.js
new file mode 100644
index 000000000..30e41e841
--- /dev/null
+++ b/core/modules/editor/operations/text/wrap-selection.js
@@ -0,0 +1,52 @@
+/*\
+title: $:/core/modules/editor/operations/text/wrap-selection.js
+type: application/javascript
+module-type: texteditoroperation
+
+Text editor operation to wrap the selection with the specified prefix and suffix
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["wrap-selection"] = function(event,operation) {
+ if(operation.selStart === operation.selEnd) {
+ // No selection; check if we're within the prefix/suffix
+ if(operation.text.substring(operation.selStart - event.paramObject.prefix.length,operation.selStart + event.paramObject.suffix.length) === event.paramObject.prefix + event.paramObject.suffix) {
+ // Remove the prefix and suffix unless they comprise the entire text
+ if(operation.selStart > event.paramObject.prefix.length || (operation.selEnd + event.paramObject.suffix.length) < operation.text.length ) {
+ operation.cutStart = operation.selStart - event.paramObject.prefix.length;
+ operation.cutEnd = operation.selEnd + event.paramObject.suffix.length;
+ operation.replacement = "";
+ operation.newSelStart = operation.cutStart;
+ operation.newSelEnd = operation.newSelStart;
+ }
+ } else {
+ // Wrap the cursor instead
+ operation.cutStart = operation.selStart;
+ operation.cutEnd = operation.selEnd;
+ operation.replacement = event.paramObject.prefix + event.paramObject.suffix;
+ operation.newSelStart = operation.selStart + event.paramObject.prefix.length;
+ operation.newSelEnd = operation.newSelStart;
+ }
+ } else if(operation.text.substring(operation.selStart,operation.selStart + event.paramObject.prefix.length) === event.paramObject.prefix && operation.text.substring(operation.selEnd - event.paramObject.suffix.length,operation.selEnd) === event.paramObject.suffix) {
+ // Prefix and suffix are already present, so remove them
+ operation.cutStart = operation.selStart;
+ operation.cutEnd = operation.selEnd;
+ operation.replacement = operation.selection.substring(event.paramObject.prefix.length,operation.selection.length - event.paramObject.suffix.length);
+ operation.newSelStart = operation.selStart;
+ operation.newSelEnd = operation.selStart + operation.replacement.length;
+ } else {
+ // Add the prefix and suffix
+ operation.cutStart = operation.selStart;
+ operation.cutEnd = operation.selEnd;
+ operation.replacement = event.paramObject.prefix + operation.selection + event.paramObject.suffix;
+ operation.newSelStart = operation.selStart;
+ operation.newSelEnd = operation.selStart + operation.replacement.length;
+ }
+};
+
+})();
diff --git a/core/modules/filters.js b/core/modules/filters.js
index 8fbcac587..76046b828 100644
--- a/core/modules/filters.js
+++ b/core/modules/filters.js
@@ -20,7 +20,7 @@ Parses an operation (i.e. a run) within a filter string
Returns the new start position, after the parsed operation
*/
function parseFilterOperation(operators,filterString,p) {
- var operator, operand, bracketPos, curlyBracketPos;
+ var nextBracketPos, operator;
// Skip the starting square bracket
if(filterString.charAt(p++) !== "[") {
throw "Missing [ in filter expression";
@@ -33,14 +33,14 @@ function parseFilterOperation(operators,filterString,p) {
operator.prefix = filterString.charAt(p++);
}
// Get the operator name
- var nextBracketPos = filterString.substring(p).search(/[\[\{<\/]/);
+ nextBracketPos = filterString.substring(p).search(/[\[\{<\/]/);
if(nextBracketPos === -1) {
throw "Missing [ in filter expression";
}
nextBracketPos += p;
var bracket = filterString.charAt(nextBracketPos);
operator.operator = filterString.substring(p,nextBracketPos);
-
+
// Any suffix?
var colon = operator.operator.indexOf(':');
if(colon > -1) {
@@ -79,7 +79,7 @@ console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand
}
break;
}
-
+
if(nextBracketPos === -1) {
throw "Missing closing bracket in filter expression";
}
@@ -87,7 +87,7 @@ console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand
operator.operand = filterString.substring(p,nextBracketPos);
}
p = nextBracketPos + 1;
-
+
// Push this operator
operators.push(operator);
} while(filterString.charAt(p) !== "]");
@@ -121,7 +121,7 @@ exports.parseFilter = function(filterString) {
operandRegExp.lastIndex = p;
match = operandRegExp.exec(filterString);
if(!match || match.index !== p) {
- throw "Syntax error in filter expression";
+ throw $tw.language.getString("Error/FilterSyntax");
}
var operation = {
prefix: "",
@@ -171,7 +171,7 @@ exports.compileFilter = function(filterString) {
filterParseTree = this.parseFilter(filterString);
} catch(e) {
return function(source,widget) {
- return ["Filter error: " + e];
+ return [$tw.language.getString("Error/Filter") + ": " + e];
};
}
// Get the hashmap of filter operator functions
diff --git a/core/modules/filters/days.js b/core/modules/filters/days.js
index 39cd375f7..318d72567 100644
--- a/core/modules/filters/days.js
+++ b/core/modules/filters/days.js
@@ -27,6 +27,7 @@ exports.days = function(source,operator,options) {
};
if(operator.prefix === "!") {
+ targetTimeStamp = targetTimeStamp - 1000*60*60*24*dayIntervalSign;
source(function(tiddler,title) {
if(tiddler && tiddler.fields[fieldName]) {
if(!isWithinDays($tw.utils.parseDate(tiddler.fields[fieldName]))) {
diff --git a/core/modules/filters/encodings.js b/core/modules/filters/encodings.js
new file mode 100644
index 000000000..24c44aaa3
--- /dev/null
+++ b/core/modules/filters/encodings.js
@@ -0,0 +1,83 @@
+/*\
+title: $:/core/modules/filters/decodeuricomponent.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator for applying decodeURIComponent() to each item.
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter functions
+*/
+
+exports.decodeuricomponent = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push(decodeURIComponent(title));
+ });
+ return results;
+};
+
+exports.encodeuricomponent = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push(encodeURIComponent(title));
+ });
+ return results;
+};
+
+exports.decodeuri = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push(decodeURI(title));
+ });
+ return results;
+};
+
+exports.encodeuri = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push(encodeURI(title));
+ });
+ return results;
+};
+
+exports.decodehtml = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push($tw.utils.htmlDecode(title));
+ });
+ return results;
+};
+
+exports.encodehtml = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push($tw.utils.htmlEncode(title));
+ });
+ return results;
+};
+
+exports.stringify = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push($tw.utils.stringify(title));
+ });
+ return results;
+};
+
+exports.escaperegexp = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ results.push($tw.utils.escapeRegExp(title));
+ });
+ return results;
+};
+
+})();
diff --git a/core/modules/filters/has.js b/core/modules/filters/has.js
index 9a82f3111..7a1738782 100644
--- a/core/modules/filters/has.js
+++ b/core/modules/filters/has.js
@@ -25,7 +25,7 @@ exports.has = function(source,operator,options) {
});
} else {
source(function(tiddler,title) {
- if(tiddler && $tw.utils.hop(tiddler.fields,operator.operand) && tiddler.fields[operator.operand] !== "") {
+ if(tiddler && $tw.utils.hop(tiddler.fields,operator.operand) && !(tiddler.fields[operator.operand] === "" || tiddler.fields[operator.operand].length === 0)) {
results.push(title);
}
});
diff --git a/core/modules/filters/is.js b/core/modules/filters/is.js
index d27d00907..0db243044 100644
--- a/core/modules/filters/is.js
+++ b/core/modules/filters/is.js
@@ -32,7 +32,7 @@ exports.is = function(source,operator,options) {
if(isFilterOperator) {
return isFilterOperator(source,operator.prefix,options);
} else {
- return ["Filter Error: Unknown operand for the 'is' filter operator"];
+ return [$tw.language.getString("Error/IsFilterOperator")];
}
};
diff --git a/core/modules/filters/minlength.js b/core/modules/filters/minlength.js
new file mode 100644
index 000000000..d4e679bef
--- /dev/null
+++ b/core/modules/filters/minlength.js
@@ -0,0 +1,29 @@
+/*\
+title: $:/core/modules/filters/minlength.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator for filtering out titles that don't meet the minimum length in the operand
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter function
+*/
+exports.minlength = function(source,operator,options) {
+ var results = [],
+ minLength = parseInt(operator.operand || "",10) || 0;
+ source(function(tiddler,title) {
+ if(title.length >= minLength) {
+ results.push(title);
+ }
+ });
+ return results;
+};
+
+})();
diff --git a/core/modules/filters/wikiparserrules.js b/core/modules/filters/wikiparserrules.js
index ee57113c8..213298515 100644
--- a/core/modules/filters/wikiparserrules.js
+++ b/core/modules/filters/wikiparserrules.js
@@ -16,10 +16,11 @@ Filter operator for returning the names of the wiki parser rules in this wiki
Export our filter function
*/
exports.wikiparserrules = function(source,operator,options) {
- var results = [];
+ var results = [],
+ operand = operator.operand;
$tw.utils.each($tw.modules.types.wikirule,function(mod) {
var exp = mod.exports;
- if(exp.types[operator.operand]) {
+ if(!operand || exp.types[operand]) {
results.push(exp.name);
}
});
diff --git a/core/modules/info/platform.js b/core/modules/info/platform.js
index 9f6097f74..c30f5b83c 100644
--- a/core/modules/info/platform.js
+++ b/core/modules/info/platform.js
@@ -18,6 +18,21 @@ exports.getInfoTiddlerFields = function() {
// Basics
infoTiddlerFields.push({title: "$:/info/browser", text: mapBoolean(!!$tw.browser)});
infoTiddlerFields.push({title: "$:/info/node", text: mapBoolean(!!$tw.node)});
+ // Document location
+ if($tw.browser) {
+ var setLocationProperty = function(name,value) {
+ infoTiddlerFields.push({title: "$:/info/url/" + name, text: value});
+ },
+ location = document.location;
+ setLocationProperty("full", (location.toString()).split("#")[0]);
+ setLocationProperty("host", location.host);
+ setLocationProperty("hostname", location.hostname);
+ setLocationProperty("protocol", location.protocol);
+ setLocationProperty("port", location.port);
+ setLocationProperty("pathname", location.pathname);
+ setLocationProperty("search", location.search);
+ setLocationProperty("origin", location.origin);
+ }
return infoTiddlerFields;
};
diff --git a/core/modules/keyboard.js b/core/modules/keyboard.js
new file mode 100644
index 000000000..bfa76d7e2
--- /dev/null
+++ b/core/modules/keyboard.js
@@ -0,0 +1,279 @@
+/*\
+title: $:/core/modules/keyboard.js
+type: application/javascript
+module-type: global
+
+Keyboard handling utilities
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var namedKeys = {
+ "cancel": 3,
+ "help": 6,
+ "backspace": 8,
+ "tab": 9,
+ "clear": 12,
+ "return": 13,
+ "enter": 13,
+ "pause": 19,
+ "escape": 27,
+ "space": 32,
+ "page_up": 33,
+ "page_down": 34,
+ "end": 35,
+ "home": 36,
+ "left": 37,
+ "up": 38,
+ "right": 39,
+ "down": 40,
+ "printscreen": 44,
+ "insert": 45,
+ "delete": 46,
+ "0": 48,
+ "1": 49,
+ "2": 50,
+ "3": 51,
+ "4": 52,
+ "5": 53,
+ "6": 54,
+ "7": 55,
+ "8": 56,
+ "9": 57,
+ "firefoxsemicolon": 59,
+ "firefoxequals": 61,
+ "a": 65,
+ "b": 66,
+ "c": 67,
+ "d": 68,
+ "e": 69,
+ "f": 70,
+ "g": 71,
+ "h": 72,
+ "i": 73,
+ "j": 74,
+ "k": 75,
+ "l": 76,
+ "m": 77,
+ "n": 78,
+ "o": 79,
+ "p": 80,
+ "q": 81,
+ "r": 82,
+ "s": 83,
+ "t": 84,
+ "u": 85,
+ "v": 86,
+ "w": 87,
+ "x": 88,
+ "y": 89,
+ "z": 90,
+ "numpad0": 96,
+ "numpad1": 97,
+ "numpad2": 98,
+ "numpad3": 99,
+ "numpad4": 100,
+ "numpad5": 101,
+ "numpad6": 102,
+ "numpad7": 103,
+ "numpad8": 104,
+ "numpad9": 105,
+ "multiply": 106,
+ "add": 107,
+ "separator": 108,
+ "subtract": 109,
+ "decimal": 110,
+ "divide": 111,
+ "f1": 112,
+ "f2": 113,
+ "f3": 114,
+ "f4": 115,
+ "f5": 116,
+ "f6": 117,
+ "f7": 118,
+ "f8": 119,
+ "f9": 120,
+ "f10": 121,
+ "f11": 122,
+ "f12": 123,
+ "f13": 124,
+ "f14": 125,
+ "f15": 126,
+ "f16": 127,
+ "f17": 128,
+ "f18": 129,
+ "f19": 130,
+ "f20": 131,
+ "f21": 132,
+ "f22": 133,
+ "f23": 134,
+ "f24": 135,
+ "firefoxminus": 173,
+ "semicolon": 186,
+ "equals": 187,
+ "comma": 188,
+ "dash": 189,
+ "period": 190,
+ "slash": 191,
+ "backquote": 192,
+ "openbracket": 219,
+ "backslash": 220,
+ "closebracket": 221,
+ "quote": 222
+};
+
+function KeyboardManager(options) {
+ var self = this;
+ options = options || "";
+ // Save the named key hashmap
+ this.namedKeys = namedKeys;
+ // Create a reverse mapping of code to keyname
+ this.keyNames = [];
+ $tw.utils.each(namedKeys,function(keyCode,name) {
+ self.keyNames[keyCode] = name.substr(0,1).toUpperCase() + name.substr(1);
+ });
+ // Save the platform-specific name of the "meta" key
+ this.metaKeyName = $tw.platform.isMac ? "cmd-" : "win-";
+}
+
+/*
+Return an array of keycodes for the modifier keys ctrl, shift, alt, meta
+*/
+KeyboardManager.prototype.getModifierKeys = function() {
+ return [
+ 16, // Shift
+ 17, // Ctrl
+ 18, // Alt
+ 20, // CAPS LOCK
+ 91, // Meta (left)
+ 93, // Meta (right)
+ 224 // Meta (Firefox)
+ ]
+};
+
+/*
+Parses a key descriptor into the structure:
+{
+ keyCode: numeric keycode
+ shiftKey: boolean
+ altKey: boolean
+ ctrlKey: boolean
+ metaKey: boolean
+}
+Key descriptors have the following format:
+ ctrl+enter
+ ctrl+shift+alt+A
+*/
+KeyboardManager.prototype.parseKeyDescriptor = function(keyDescriptor) {
+ var components = keyDescriptor.split(/\+|\-/),
+ info = {
+ keyCode: 0,
+ shiftKey: false,
+ altKey: false,
+ ctrlKey: false,
+ metaKey: false
+ };
+ for(var t=0; t 0) {
+ shortcutArray.sort(function(a,b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ })
+ return prefix + shortcutArray.join(separator) + suffix;
+ } else {
+ return "";
+ }
+};
+
+})();
diff --git a/core/modules/macros/makedatauri.js b/core/modules/macros/makedatauri.js
index 4b578f466..a7474bdf0 100644
--- a/core/modules/macros/makedatauri.js
+++ b/core/modules/macros/makedatauri.js
@@ -3,7 +3,7 @@ title: $:/core/modules/macros/makedatauri.js
type: application/javascript
module-type: macro
-Macro to convert the content of a tiddler to a data URI
+Macro to convert a string of text to a data URI
<>
diff --git a/core/modules/parsers/htmlparser.js b/core/modules/parsers/htmlparser.js
index b2528e961..39b0c21df 100644
--- a/core/modules/parsers/htmlparser.js
+++ b/core/modules/parsers/htmlparser.js
@@ -24,7 +24,7 @@ var HtmlParser = function(type,text,options) {
tag: "iframe",
attributes: {
src: {type: "string", value: src},
- sandbox: {type: "string", value: "sandbox"}
+ sandbox: {type: "string", value: ""}
}
}];
};
diff --git a/core/modules/parsers/parseutils.js b/core/modules/parsers/parseutils.js
index 97c994050..0d74355f7 100644
--- a/core/modules/parsers/parseutils.js
+++ b/core/modules/parsers/parseutils.js
@@ -218,6 +218,7 @@ exports.parseAttribute = function(source,pos) {
// Define our regexps
var reAttributeName = /([^\/\s>"'=]+)/g,
reUnquotedAttribute = /([^\/\s<>"'=]+)/g,
+ reFilteredValue = /\{\{\{(.+?)\}\}\}/g,
reIndirectValue = /\{\{([^\}]+)\}\}/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
@@ -243,29 +244,37 @@ exports.parseAttribute = function(source,pos) {
node.type = "string";
node.value = stringLiteral.value;
} else {
- // Look for an indirect value
- var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
- if(indirectValue) {
- pos = indirectValue.end;
- node.type = "indirect";
- node.textReference = indirectValue.match[1];
+ // Look for a filtered value
+ var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
+ if(filteredValue) {
+ pos = filteredValue.end;
+ node.type = "filtered";
+ node.filter = filteredValue.match[1];
} else {
- // 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];
+ // Look for an indirect value
+ var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
+ if(indirectValue) {
+ pos = indirectValue.end;
+ node.type = "indirect";
+ node.textReference = indirectValue.match[1];
} else {
- // Look for a macro invocation value
- var macroInvocation = $tw.utils.parseMacroInvocation(source,pos);
- if(macroInvocation) {
- pos = macroInvocation.end;
- node.type = "macro";
- node.value = macroInvocation;
- } else {
+ // Look for a unquoted value
+ var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
+ if(unquotedValue) {
+ pos = unquotedValue.end;
node.type = "string";
- node.value = "true";
+ node.value = unquotedValue.match[1];
+ } else {
+ // Look for a macro invocation value
+ var macroInvocation = $tw.utils.parseMacroInvocation(source,pos);
+ if(macroInvocation) {
+ pos = macroInvocation.end;
+ node.type = "macro";
+ node.value = macroInvocation;
+ } else {
+ node.type = "string";
+ node.value = "true";
+ }
}
}
}
diff --git a/core/modules/parsers/wikiparser/rules/extlink.js b/core/modules/parsers/wikiparser/rules/extlink.js
index 539d0f11b..07ddbfb88 100644
--- a/core/modules/parsers/wikiparser/rules/extlink.js
+++ b/core/modules/parsers/wikiparser/rules/extlink.js
@@ -26,7 +26,7 @@ exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /~?(?:file|http|https|mailto|ftp|irc|news|data|skype):[^\s<>{}\[\]`|'"\\^~]+(?:\/|\b)/mg;
+ this.matchRegExp = /~?(?:file|http|https|mailto|ftp|irc|news|data|skype):[^\s<>{}\[\]`|"\\^]+(?:\/|\b)/mg;
};
exports.parse = function() {
@@ -42,7 +42,8 @@ exports.parse = function() {
attributes: {
href: {type: "string", value: this.match[0]},
"class": {type: "string", value: "tc-tiddlylink-external"},
- target: {type: "string", value: "_blank"}
+ target: {type: "string", value: "_blank"},
+ rel: {type: "string", value: "noopener noreferrer"}
},
children: [{
type: "text", text: this.match[0]
diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js
index 71cdfd5dc..dd1ea4540 100644
--- a/core/modules/parsers/wikiparser/rules/html.js
+++ b/core/modules/parsers/wikiparser/rules/html.js
@@ -101,6 +101,10 @@ exports.parseTag = function(source,pos,options) {
node.type = node.tag.substr(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) === ">") ) {
+ return null;
+ }
// Process attributes
var attribute = $tw.utils.parseAttribute(source,pos);
while(attribute) {
diff --git a/core/modules/parsers/wikiparser/rules/macrodef.js b/core/modules/parsers/wikiparser/rules/macrodef.js
index 1a0f34e01..daf854cab 100644
--- a/core/modules/parsers/wikiparser/rules/macrodef.js
+++ b/core/modules/parsers/wikiparser/rules/macrodef.js
@@ -61,7 +61,7 @@ exports.parse = function() {
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
} else {
// Otherwise, the end of the definition is marked by the end of the line
- reEnd = /(\r?\n)/mg;
+ reEnd = /($|\r?\n)/mg;
// Move past any whitespace
this.parser.pos = $tw.utils.skipWhiteSpace(this.parser.source,this.parser.pos);
}
diff --git a/core/modules/parsers/wikiparser/rules/prettyextlink.js b/core/modules/parsers/wikiparser/rules/prettyextlink.js
index 45bcbbf43..19b4f725b 100644
--- a/core/modules/parsers/wikiparser/rules/prettyextlink.js
+++ b/core/modules/parsers/wikiparser/rules/prettyextlink.js
@@ -106,6 +106,7 @@ exports.parseLink = function(source,pos) {
}
node.attributes.href = {type: "string", value: URL};
node.attributes.target = {type: "string", value: "_blank"};
+ node.attributes.rel = {type: "string", value: "noopener noreferrer"};
// Update the end position
node.end = closePos + 2;
return node;
diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js
index 33841f116..56a2850a3 100644
--- a/core/modules/parsers/wikiparser/rules/prettylink.js
+++ b/core/modules/parsers/wikiparser/rules/prettylink.js
@@ -40,7 +40,8 @@ exports.parse = function() {
attributes: {
href: {type: "string", value: link},
"class": {type: "string", value: "tc-tiddlylink-external"},
- target: {type: "string", value: "_blank"}
+ target: {type: "string", value: "_blank"},
+ rel: {type: "string", value: "noopener noreferrer"}
},
children: [{
type: "text", text: text
diff --git a/core/modules/parsers/wikiparser/rules/syslink.js b/core/modules/parsers/wikiparser/rules/syslink.js
index 441fe2aa5..6eb2cdcd4 100644
--- a/core/modules/parsers/wikiparser/rules/syslink.js
+++ b/core/modules/parsers/wikiparser/rules/syslink.js
@@ -18,7 +18,12 @@ exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /~?\$:\/[a-zA-Z0-9/.\-_]+/mg;
+ this.matchRegExp = new RegExp(
+ "~?\\$:\\/[" +
+ $tw.config.textPrimitives.anyLetter.substr(1,$tw.config.textPrimitives.anyLetter.length - 2) +
+ "\/._-]+",
+ "mg"
+ );
};
exports.parse = function() {
diff --git a/core/modules/parsers/wikiparser/wikiparser.js b/core/modules/parsers/wikiparser/wikiparser.js
index 736e2dbdf..2a55399dd 100644
--- a/core/modules/parsers/wikiparser/wikiparser.js
+++ b/core/modules/parsers/wikiparser/wikiparser.js
@@ -16,6 +16,7 @@ Attributes are stored as hashmaps of the following objects:
{type: "string", value: } - literal string
{type: "indirect", textReference: } - indirect through a text reference
+ {type: "macro", macro: } - indirect through a macro invocation
\*/
(function(){
diff --git a/core/modules/pluginswitcher.js b/core/modules/pluginswitcher.js
index 9c05d375f..ab82be933 100644
--- a/core/modules/pluginswitcher.js
+++ b/core/modules/pluginswitcher.js
@@ -18,12 +18,14 @@ wiki: wiki store to be used
pluginType: type of plugin to be switched
controllerTitle: title of tiddler used to control switching of this resource
defaultPlugins: array of default plugins to be used if nominated plugin isn't found
+onSwitch: callback when plugin is switched (single parameter is array of plugin titles)
*/
function PluginSwitcher(options) {
this.wiki = options.wiki;
this.pluginType = options.pluginType;
this.controllerTitle = options.controllerTitle;
this.defaultPlugins = options.defaultPlugins || [];
+ this.onSwitch = options.onSwitch;
// Switch to the current plugin
this.switchPlugins();
// Listen for changes to the selected plugin
@@ -64,6 +66,10 @@ PluginSwitcher.prototype.switchPlugins = function() {
var registeredTiddlers = $tw.wiki.registerPluginTiddlers(this.pluginType,plugins);
// Unpack the current theme tiddlers
$tw.wiki.unpackPluginTiddlers();
+ // Call the switch handler
+ if(this.onSwitch) {
+ this.onSwitch(plugins);
+ }
};
exports.PluginSwitcher = PluginSwitcher;
diff --git a/core/modules/saver-handler.js b/core/modules/saver-handler.js
index 78e0598a6..4c938f7af 100644
--- a/core/modules/saver-handler.js
+++ b/core/modules/saver-handler.js
@@ -151,7 +151,7 @@ SaverHandler.prototype.saveWiki = function(options) {
text = this.wiki.renderTiddler(downloadType,template,options),
callback = function(err) {
if(err) {
- alert("Error while saving:\n\n" + err);
+ alert($tw.language.getString("Error/WhileSaving") + ":\n\n" + err);
} else {
// Clear the task queue if we're saving (rather than downloading)
if(method !== "download") {
diff --git a/core/modules/savers/download.js b/core/modules/savers/download.js
index 8d1436f29..ea7c62f58 100644
--- a/core/modules/savers/download.js
+++ b/core/modules/savers/download.js
@@ -34,6 +34,7 @@ DownloadSaver.prototype.save = function(text,method,callback,options) {
// Set up the link
var link = document.createElement("a");
link.setAttribute("target","_blank");
+ link.setAttribute("rel","noopener noreferrer");
if(Blob !== undefined) {
var blob = new Blob([text], {type: "text/html"});
link.setAttribute("href", URL.createObjectURL(blob));
diff --git a/core/modules/savers/put.js b/core/modules/savers/put.js
new file mode 100644
index 000000000..39f1ca18c
--- /dev/null
+++ b/core/modules/savers/put.js
@@ -0,0 +1,80 @@
+/*\
+title: $:/core/modules/savers/put.js
+type: application/javascript
+module-type: saver
+
+Saves wiki by performing a PUT request to the server
+
+Works with any server which accepts a PUT request
+to the current URL, such as a WebDAV server.
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Select the appropriate saver module and set it up
+*/
+var PutSaver = function(wiki) {
+ this.wiki = wiki;
+ var self = this;
+ // Async server probe. Until probe finishes, save will fail fast
+ // See also https://github.com/Jermolene/TiddlyWiki5/issues/2276
+ var req = new XMLHttpRequest();
+ req.open("OPTIONS",encodeURI(document.location.protocol + "//" + document.location.hostname + ":" + document.location.port + document.location.pathname));
+ req.onload = function() {
+ // Check DAV header http://www.webdav.org/specs/rfc2518.html#rfc.section.9.1
+ self.serverAcceptsPuts = (this.status === 200 && !!this.getResponseHeader('dav'));
+ };
+ req.send();
+};
+
+PutSaver.prototype.save = function(text,method,callback) {
+ if (!this.serverAcceptsPuts) {
+ return false;
+ }
+ var req = new XMLHttpRequest();
+ // TODO: store/check ETags if supported by server, to protect against overwrites
+ // Prompt: Do you want to save over this? Y/N
+ // Merging would be ideal, and may be possible using future generic merge flow
+ req.onload = function() {
+ if (this.status === 200 || this.status === 201) {
+ callback(null); // success
+ }
+ else {
+ callback(this.responseText); // fail
+ }
+ };
+ req.open("PUT", encodeURI(window.location.href));
+ req.setRequestHeader("Content-Type", "text/html;charset=UTF-8");
+ req.send(text);
+ return true;
+};
+
+/*
+Information about this saver
+*/
+PutSaver.prototype.info = {
+ name: "put",
+ priority: 2000,
+ capabilities: ["save", "autosave"]
+};
+
+/*
+Static method that returns true if this saver is capable of working
+*/
+exports.canSave = function(wiki) {
+ return /^https?:/.test(location.protocol);
+};
+
+/*
+Create an instance of this saver
+*/
+exports.create = function(wiki) {
+ return new PutSaver(wiki);
+};
+
+})();
diff --git a/core/modules/savers/twedit.js b/core/modules/savers/twedit.js
index 33d748f67..6907a542c 100644
--- a/core/modules/savers/twedit.js
+++ b/core/modules/savers/twedit.js
@@ -39,7 +39,7 @@ TWEditSaver.prototype.save = function(text,method,callback) {
// Error handler
var errorHandler = function(event) {
// Error
- callback("Error saving to TWEdit: " + event.target.error.code);
+ callback($tw.language.getString("Error/SavingToTWEdit") + ": " + event.target.error.code);
};
// Get the file system
window.requestFileSystem(LocalFileSystem.PERSISTENT,0,function(fileSystem) {
diff --git a/core/modules/savers/upload.js b/core/modules/savers/upload.js
index 897496b6a..bcf4b9d54 100644
--- a/core/modules/savers/upload.js
+++ b/core/modules/savers/upload.js
@@ -54,7 +54,7 @@ UploadSaver.prototype.save = function(text,method,callback) {
// Do the HTTP post
var http = new XMLHttpRequest();
http.open("POST",url,true,username,password);
- http.setRequestHeader("Content-Type","multipart/form-data; ;charset=UTF-8; boundary=" + boundary);
+ http.setRequestHeader("Content-Type","multipart/form-data; charset=UTF-8; boundary=" + boundary);
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
if(http.responseText.substr(0,4) === "0 - ") {
@@ -67,7 +67,7 @@ UploadSaver.prototype.save = function(text,method,callback) {
try {
http.send(data);
} catch(ex) {
- return callback("Error:" + ex);
+ return callback($tw.language.getString("Error/Caption") + ":" + ex);
}
$tw.notifier.display("$:/language/Notifications/Save/Starting");
return true;
diff --git a/core/modules/startup/browser-messaging.js b/core/modules/startup/browser-messaging.js
index 481449c74..c38ba7b85 100644
--- a/core/modules/startup/browser-messaging.js
+++ b/core/modules/startup/browser-messaging.js
@@ -78,7 +78,7 @@ exports.startup = function() {
if(url) {
loadIFrame(url,function(err,iframeInfo) {
if(err) {
- alert("Error loading plugin library: " + url);
+ alert($tw.language.getString("Error/LoadingPluginLibrary") + ": " + url);
} else {
iframeInfo.domNode.contentWindow.postMessage({
verb: "GET",
@@ -100,7 +100,7 @@ exports.startup = function() {
if(url && title) {
loadIFrame(url,function(err,iframeInfo) {
if(err) {
- alert("Error loading plugin library: " + url);
+ alert($tw.language.getString("Error/LoadingPluginLibrary") + ": " + url);
} else {
iframeInfo.domNode.contentWindow.postMessage({
verb: "GET",
diff --git a/core/modules/startup/rootwidget.js b/core/modules/startup/rootwidget.js
index fdc6c738e..2b5111aff 100644
--- a/core/modules/startup/rootwidget.js
+++ b/core/modules/startup/rootwidget.js
@@ -28,7 +28,7 @@ exports.startup = function() {
// Install the notification mechanism
$tw.notifier = new $tw.utils.Notifier($tw.wiki);
$tw.rootWidget.addEventListener("tm-notify",function(event) {
- $tw.notifier.display(event.param);
+ $tw.notifier.display(event.param,{variables: event.paramObject});
});
// Install the scroller
$tw.pageScroller = new $tw.utils.PageScroller();
diff --git a/core/modules/startup/startup.js b/core/modules/startup/startup.js
index 08f9641e3..56d780159 100755
--- a/core/modules/startup/startup.js
+++ b/core/modules/startup/startup.js
@@ -24,9 +24,34 @@ var widget = require("$:/core/modules/widgets/widget.js");
exports.startup = function() {
var modules,n,m,f;
+ // Minimal browser detection
if($tw.browser) {
$tw.browser.isIE = (/msie|trident/i.test(navigator.userAgent));
+ $tw.browser.isFirefox = !!document.mozFullScreenEnabled;
}
+ // Platform detection
+ $tw.platform = {};
+ if($tw.browser) {
+ $tw.platform.isMac = /Mac/.test(navigator.platform);
+ $tw.platform.isWindows = /win/i.test(navigator.platform);
+ $tw.platform.isLinux = /Linux/i.test(navigator.appVersion);
+ } else {
+ switch(require("os").platform()) {
+ case "darwin":
+ $tw.platform.isMac = true;
+ break;
+ case "win32":
+ $tw.platform.isWindows = true;
+ break;
+ case "freebsd":
+ $tw.platform.isLinux = true;
+ break;
+ case "linux":
+ $tw.platform.isLinux = true;
+ break;
+ }
+ }
+ // 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");
@@ -38,7 +63,17 @@ exports.startup = function() {
controllerTitle: "$:/language",
defaultPlugins: [
"$:/languages/en-US"
- ]
+ ],
+ onSwitch: function(plugins) {
+ if($tw.browser) {
+ var pluginTiddler = $tw.wiki.getTiddler(plugins[0]);
+ if(pluginTiddler) {
+ document.documentElement.setAttribute("dir",pluginTiddler.getFieldString("text-direction") || "auto");
+ } else {
+ document.documentElement.removeAttribute("dir");
+ }
+ }
+ }
});
// Kick off the theme manager
$tw.themeManager = new $tw.PluginSwitcher({
@@ -50,6 +85,8 @@ exports.startup = function() {
"$:/themes/tiddlywiki/vanilla"
]
});
+ // Kick off the keyboard manager
+ $tw.keyboardManager = new $tw.KeyboardManager();
// Clear outstanding tiddler store change events to avoid an unnecessary refresh cycle at startup
$tw.wiki.clearTiddlerEventQueue();
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
diff --git a/core/modules/startup/story.js b/core/modules/startup/story.js
index 987bee216..8ef57076f 100644
--- a/core/modules/startup/story.js
+++ b/core/modules/startup/story.js
@@ -53,6 +53,10 @@ exports.startup = function() {
$tw.rootWidget.addEventListener("tm-browser-refresh",function(event) {
window.location.reload(true);
});
+ // Listen for the tm-print message
+ $tw.rootWidget.addEventListener("tm-print",function(event) {
+ (event.event.view || window).print();
+ });
// Listen for the tm-home message
$tw.rootWidget.addEventListener("tm-home",function(event) {
window.location.hash = "";
diff --git a/core/modules/startup/windows.js b/core/modules/startup/windows.js
index 9ce34f842..69966f346 100644
--- a/core/modules/startup/windows.js
+++ b/core/modules/startup/windows.js
@@ -49,7 +49,10 @@ exports.startup = function() {
$tw.wiki.removeEventListener("change",refreshHandler);
},false);
// Set up the styles
- var styleWidgetNode = $tw.wiki.makeTranscludeWidget("$:/core/ui/PageStylesheet",{document: $tw.fakeDocument, variables: variables}),
+ 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");
diff --git a/core/modules/syncer.js b/core/modules/syncer.js
index 223998e1c..cb1191f16 100644
--- a/core/modules/syncer.js
+++ b/core/modules/syncer.js
@@ -96,11 +96,26 @@ Syncer.prototype.readTiddlerInfo = function() {
self.tiddlerInfo[title] = {
revision: tiddler.fields.revision,
adaptorInfo: self.syncadaptor && self.syncadaptor.getTiddlerInfo(tiddler),
- changeCount: self.wiki.getChangeCount(title)
+ changeCount: self.wiki.getChangeCount(title),
+ hasBeenLazyLoaded: false
};
});
};
+/*
+Create an tiddlerInfo structure if it doesn't already exist
+*/
+Syncer.prototype.createTiddlerInfo = function(title) {
+ if(!$tw.utils.hop(this.tiddlerInfo,title)) {
+ this.tiddlerInfo[title] = {
+ revision: null,
+ adaptorInfo: {},
+ changeCount: -1,
+ hasBeenLazyLoaded: false
+ };
+ }
+};
+
/*
Checks whether the wiki is dirty (ie the window shouldn't be closed)
*/
@@ -120,7 +135,7 @@ Syncer.prototype.updateDirtyStatus = function() {
/*
Save an incoming tiddler in the store, and updates the associated tiddlerInfo
*/
-Syncer.prototype.storeTiddler = function(tiddlerFields) {
+Syncer.prototype.storeTiddler = function(tiddlerFields,hasBeenLazyLoaded) {
// Save the tiddler
var tiddler = new $tw.Tiddler(this.wiki.getTiddler(tiddlerFields.title),tiddlerFields);
this.wiki.addTiddler(tiddler);
@@ -128,7 +143,8 @@ Syncer.prototype.storeTiddler = function(tiddlerFields) {
this.tiddlerInfo[tiddlerFields.title] = {
revision: tiddlerFields.revision,
adaptorInfo: this.syncadaptor.getTiddlerInfo(tiddler),
- changeCount: this.wiki.getChangeCount(tiddlerFields.title)
+ changeCount: this.wiki.getChangeCount(tiddlerFields.title),
+ hasBeenLazyLoaded: hasBeenLazyLoaded !== undefined ? hasBeenLazyLoaded : true
};
};
@@ -180,7 +196,7 @@ Syncer.prototype.syncFromServer = function() {
},self.pollTimerInterval);
// Check for errors
if(err) {
- self.logger.alert("Error retrieving skinny tiddler list:",err);
+ self.logger.alert($tw.language.getString("Error/RetrievingSkinny") + ":",err);
return;
}
// Process each incoming tiddler
@@ -202,7 +218,7 @@ Syncer.prototype.syncFromServer = function() {
});
} else {
// Load the skinny version of the tiddler
- self.storeTiddler(tiddlerFields);
+ self.storeTiddler(tiddlerFields,false);
}
}
}
@@ -238,11 +254,17 @@ Syncer.prototype.syncToServer = function(changes) {
Lazily load a skinny tiddler if we can
*/
Syncer.prototype.handleLazyLoadEvent = function(title) {
- // Queue up a sync task to load this tiddler
- this.enqueueSyncTask({
- type: "load",
- title: title
- });
+ // Don't lazy load the same tiddler twice
+ var info = this.tiddlerInfo[title];
+ if(!info || !info.hasBeenLazyLoaded) {
+ this.createTiddlerInfo(title);
+ this.tiddlerInfo[title].hasBeenLazyLoaded = true;
+ // Queue up a sync task to load this tiddler
+ this.enqueueSyncTask({
+ type: "load",
+ title: title
+ });
+ }
};
/*
@@ -253,7 +275,7 @@ Syncer.prototype.handleLoginEvent = function() {
this.getStatus(function(err,isLoggedIn,username) {
if(!isLoggedIn) {
$tw.passwordPrompt.createPrompt({
- serviceName: "Login to TiddlySpace",
+ serviceName: $tw.language.getString("LoginToTiddlySpace"),
callback: function(data) {
self.login(data.username,data.password,function(err,isLoggedIn) {
self.syncFromServer();
@@ -324,13 +346,7 @@ Syncer.prototype.enqueueSyncTask = function(task) {
task.queueTime = now;
task.lastModificationTime = now;
// Fill in some tiddlerInfo if the tiddler is one we haven't seen before
- if(!$tw.utils.hop(this.tiddlerInfo,task.title)) {
- this.tiddlerInfo[task.title] = {
- revision: null,
- adaptorInfo: {},
- changeCount: -1
- };
- }
+ this.createTiddlerInfo(task.title);
// Bail if this is a save and the tiddler is already at the changeCount that the server has
if(task.type === "save" && this.wiki.getChangeCount(task.title) <= this.tiddlerInfo[task.title].changeCount) {
return;
@@ -387,8 +403,8 @@ Process the task queue, performing the next task if appropriate
*/
Syncer.prototype.processTaskQueue = function() {
var self = this;
- // Only process a task if we're not already performing a task. If we are already performing a task then we'll dispatch the next one when it completes
- if(this.numTasksInProgress() === 0) {
+ // Only process a task if the sync adaptor is fully initialised and we're not already performing a task. If we are already performing a task then we'll dispatch the next one when it completes
+ if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress() === 0) {
// Choose the next task to perform
var task = this.chooseNextTask();
// Perform the task if we had one
@@ -483,7 +499,7 @@ Syncer.prototype.dispatchTask = function(task,callback) {
}
// Store the tiddler
if(tiddlerFields) {
- self.storeTiddler(tiddlerFields);
+ self.storeTiddler(tiddlerFields,true);
}
// Invoke the callback
callback(null);
diff --git a/core/modules/utils/dom/dom.js b/core/modules/utils/dom/dom.js
index 35f556262..118498bc3 100644
--- a/core/modules/utils/dom/dom.js
+++ b/core/modules/utils/dom/dom.js
@@ -60,6 +60,20 @@ exports.toggleClass = function(el,className,status) {
}
};
+/*
+Get the first parent element that has scrollbars or use the body as fallback.
+*/
+exports.getScrollContainer = function(el) {
+ var doc = el.ownerDocument;
+ while(el.parentNode) {
+ el = el.parentNode;
+ if(el.scrollTop) {
+ return el;
+ }
+ }
+ return doc.body;
+};
+
/*
Get the scroll position of the viewport
Returns:
@@ -76,6 +90,31 @@ exports.getScrollPosition = function() {
}
};
+/*
+Adjust the height of a textarea to fit its content, preserving scroll position, and return the height
+*/
+exports.resizeTextAreaToFit = function(domNode,minHeight) {
+ // Get the scroll container and register the current scroll position
+ var container = $tw.utils.getScrollContainer(domNode),
+ scrollTop = container.scrollTop;
+ // Measure the specified minimum height
+ domNode.style.height = minHeight;
+ var measuredHeight = domNode.offsetHeight;
+ // Set its height to auto so that it snaps to the correct height
+ domNode.style.height = "auto";
+ // Calculate the revised height
+ var newHeight = Math.max(domNode.scrollHeight + domNode.offsetHeight - domNode.clientHeight,measuredHeight);
+ // Only try to change the height if it has changed
+ if(newHeight !== domNode.offsetHeight) {
+ domNode.style.height = newHeight + "px";
+ // Make sure that the dimensions of the textarea are recalculated
+ $tw.utils.forceLayout(domNode);
+ // Set the container to the position we registered at the beginning
+ container.scrollTop = scrollTop;
+ }
+ return newHeight;
+};
+
/*
Gets the bounding rectangle of an element in absolute page coordinates
*/
@@ -164,5 +203,32 @@ exports.addEventListeners = function(domNode,events) {
});
};
+/*
+Get the computed styles applied to an element as an array of strings of individual CSS properties
+*/
+exports.getComputedStyles = function(domNode) {
+ var textAreaStyles = window.getComputedStyle(domNode,null),
+ styleDefs = [],
+ name;
+ for(var t=0; t 0) {
+ return callback(null);
+ }
+ fs.rmdir(dirpath,function(err) {
+ if(err) {
+ return callback(err);
+ }
+ self.deleteEmptyDirs(path.dirname(dirpath),callback);
+ });
+ });
+};
+
})();
diff --git a/core/modules/utils/pluginmaker.js b/core/modules/utils/pluginmaker.js
index 559f5aada..1fc1a3986 100644
--- a/core/modules/utils/pluginmaker.js
+++ b/core/modules/utils/pluginmaker.js
@@ -28,7 +28,7 @@ exports.repackPlugin = function(title,additionalTiddlers,excludeTiddlers) {
try {
jsonPluginTiddler = JSON.parse(pluginTiddler.fields.text);
} catch(e) {
- throw "Cannot parse plugin tiddler " + title + "\nError: " + e;
+ throw "Cannot parse plugin tiddler " + title + "\n" + $tw.language.getString("Error/Caption") + ": " + e;
}
// Get the list of tiddlers
var tiddlers = Object.keys(jsonPluginTiddler.tiddlers);
diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js
index 90015609d..94c08f329 100644
--- a/core/modules/utils/utils.js
+++ b/core/modules/utils/utils.js
@@ -17,7 +17,27 @@ Display a warning, in colour if we're on a terminal
*/
exports.warning = function(text) {
console.log($tw.node ? "\x1b[1;33m" + text + "\x1b[0m" : text);
-}
+};
+
+/*
+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
+*/
+exports.replaceString = function(text,search,replace) {
+ return text.replace(search,function() {
+ return replace;
+ });
+};
+
+/*
+Repeats a string
+*/
+exports.repeat = function(str,count) {
+ var result = "";
+ for(var t=0;t{}\[\]`|'"\\^~]+(?:\/|\b)/i;
+ var externalRegExp = /^(?:file|http|https|mailto|ftp|irc|news|data|skype):[^\s<>{}\[\]`|"\\^]+(?:\/|\b)/i;
return externalRegExp.test(to);
};
@@ -654,4 +708,20 @@ exports.sign = Math.sign || function(x) {
return x > 0 ? 1 : -1;
};
+/*
+IE does not have an endsWith function
+*/
+exports.strEndsWith = function(str,ending,position) {
+ if(str.endsWith) {
+ return str.endsWith(ending,position);
+ } else {
+ if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > str.length) {
+ position = str.length;
+ }
+ position -= ending.length;
+ var lastIndex = str.indexOf(ending, position);
+ return lastIndex !== -1 && lastIndex === position;
+ }
+};
+
})();
diff --git a/core/modules/widgets/action-createtiddler.js b/core/modules/widgets/action-createtiddler.js
new file mode 100644
index 000000000..01010b940
--- /dev/null
+++ b/core/modules/widgets/action-createtiddler.js
@@ -0,0 +1,81 @@
+/*\
+title: $:/core/modules/widgets/action-createtiddler.js
+type: application/javascript
+module-type: widget
+
+Action widget to create a new tiddler with a unique name and specified fields.
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var Widget = require("$:/core/modules/widgets/widget.js").widget;
+
+var CreateTiddlerWidget = function(parseTreeNode,options) {
+ this.initialise(parseTreeNode,options);
+};
+
+/*
+Inherit from the base widget class
+*/
+CreateTiddlerWidget.prototype = new Widget();
+
+/*
+Render this widget into the DOM
+*/
+CreateTiddlerWidget.prototype.render = function(parent,nextSibling) {
+ this.computeAttributes();
+ this.execute();
+};
+
+/*
+Compute the internal state of the widget
+*/
+CreateTiddlerWidget.prototype.execute = function() {
+ this.actionBaseTitle = this.getAttribute("$basetitle");
+ this.actionSaveTitle = this.getAttribute("$savetitle");
+ this.actionTimestamp = this.getAttribute("$timestamp","yes") === "yes";
+};
+
+/*
+Refresh the widget by ensuring our attributes are up to date
+*/
+CreateTiddlerWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes();
+ if($tw.utils.count(changedAttributes) > 0) {
+ this.refreshSelf();
+ return true;
+ }
+ return this.refreshChildren(changedTiddlers);
+};
+
+/*
+Invoke the action associated with this widget
+*/
+CreateTiddlerWidget.prototype.invokeAction = function(triggeringWidget,event) {
+ var title = this.wiki.generateNewTitle(this.actionBaseTitle),
+ fields = {},
+ creationFields,
+ modificationFields;
+ $tw.utils.each(this.attributes,function(attribute,name) {
+ if(name.charAt(0) !== "$") {
+ fields[name] = attribute;
+ }
+ });
+ if(this.actionTimestamp) {
+ creationFields = this.wiki.getCreationFields();
+ modificationFields = this.wiki.getModificationFields();
+ }
+ var tiddler = this.wiki.addTiddler(new $tw.Tiddler(creationFields,fields,modificationFields,{title: title}));
+ if(this.actionSaveTitle) {
+ this.wiki.setTextReference(this.actionSaveTitle,title,this.getVariable("currentTiddler"));
+ }
+ return true; // Action was invoked
+};
+
+exports["action-createtiddler"] = CreateTiddlerWidget;
+
+})();
diff --git a/core/modules/widgets/action-deletefield.js b/core/modules/widgets/action-deletefield.js
index 8e9080f91..93b45b40a 100644
--- a/core/modules/widgets/action-deletefield.js
+++ b/core/modules/widgets/action-deletefield.js
@@ -67,7 +67,7 @@ DeleteFieldWidget.prototype.invokeAction = function(triggeringWidget,event) {
removeFields[name] = undefined;
}
});
- this.wiki.addTiddler(new $tw.Tiddler(this.wiki.getModificationFields(),tiddler,removeFields,this.wiki.getCreationFields()));
+ this.wiki.addTiddler(new $tw.Tiddler(this.wiki.getCreationFields(),tiddler,removeFields,this.wiki.getModificationFields()));
}
return true; // Action was invoked
};
diff --git a/core/modules/widgets/action-sendmessage.js b/core/modules/widgets/action-sendmessage.js
index 2f1b9cae4..c53314b2d 100644
--- a/core/modules/widgets/action-sendmessage.js
+++ b/core/modules/widgets/action-sendmessage.js
@@ -78,7 +78,8 @@ SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
param: param,
paramObject: paramObject,
tiddlerTitle: this.getVariable("currentTiddler"),
- navigateFromTitle: this.getVariable("storyTiddler")
+ navigateFromTitle: this.getVariable("storyTiddler"),
+ event: event
});
return true; // Action was invoked
};
diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js
index da4ba5a01..07c0c5ac1 100644
--- a/core/modules/widgets/button.js
+++ b/core/modules/widgets/button.js
@@ -86,6 +86,9 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
self.setTiddler();
handled = true;
}
+ if(self.actions) {
+ self.invokeActionString(self.actions,self,event);
+ }
if(handled) {
event.preventDefault();
event.stopPropagation();
@@ -128,12 +131,13 @@ ButtonWidget.prototype.navigateTo = function(event) {
navigateFromNode: this,
navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height
},
- navigateSuppressNavigation: event.metaKey || event.ctrlKey || (event.button === 1)
+ navigateSuppressNavigation: event.metaKey || event.ctrlKey || (event.button === 1),
+ event: event
});
};
ButtonWidget.prototype.dispatchMessage = function(event) {
- this.dispatchEvent({type: this.message, param: this.param, tiddlerTitle: this.getVariable("currentTiddler")});
+ this.dispatchEvent({type: this.message, param: this.param, tiddlerTitle: this.getVariable("currentTiddler"), event: event});
};
ButtonWidget.prototype.triggerPopup = function(event) {
@@ -153,6 +157,7 @@ Compute the internal state of the widget
*/
ButtonWidget.prototype.execute = function() {
// Get attributes
+ this.actions = this.getAttribute("actions");
this.to = this.getAttribute("to");
this.message = this.getAttribute("message");
this.param = this.getAttribute("param");
diff --git a/core/modules/widgets/edit-bitmap.js b/core/modules/widgets/edit-bitmap.js
index aaac6c696..f094d9464 100644
--- a/core/modules/widgets/edit-bitmap.js
+++ b/core/modules/widgets/edit-bitmap.js
@@ -13,16 +13,22 @@ Edit-bitmap widget
"use strict";
// Default image sizes
-var DEFAULT_IMAGE_WIDTH = 300,
- DEFAULT_IMAGE_HEIGHT = 185;
+var DEFAULT_IMAGE_WIDTH = 600,
+ DEFAULT_IMAGE_HEIGHT = 370;
// Configuration tiddlers
var LINE_WIDTH_TITLE = "$:/config/BitmapEditor/LineWidth",
- LINE_COLOUR_TITLE = "$:/config/BitmapEditor/Colour";
+ LINE_COLOUR_TITLE = "$:/config/BitmapEditor/Colour",
+ LINE_OPACITY_TITLE = "$:/config/BitmapEditor/Opacity";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var EditBitmapWidget = function(parseTreeNode,options) {
+ // Initialise the editor operations if they've not been done already
+ if(!this.editorOperations) {
+ EditBitmapWidget.prototype.editorOperations = {};
+ $tw.modules.applyMethods("bitmapeditoroperation",this.editorOperations);
+ }
this.initialise(parseTreeNode,options);
};
@@ -42,7 +48,12 @@ EditBitmapWidget.prototype.render = function(parent,nextSibling) {
this.computeAttributes();
// Execute our logic
this.execute();
- // Create our element
+ // Create the wrapper for the toolbar and render its content
+ this.toolbarNode = this.document.createElement("div");
+ this.toolbarNode.className = "tc-editor-toolbar";
+ parent.insertBefore(this.toolbarNode,nextSibling);
+ this.domNodes.push(this.toolbarNode);
+ // Create the on-screen canvas
this.canvasDomNode = $tw.utils.domMaker("canvas",{
document: this.document,
"class":"tc-edit-bitmapeditor",
@@ -60,29 +71,33 @@ EditBitmapWidget.prototype.render = function(parent,nextSibling) {
name: "mouseup", handlerObject: this, handlerMethod: "handleMouseUpEvent"
}]
});
- this.widthDomNode = $tw.utils.domMaker("input",{
- document: this.document,
- "class":"tc-edit-bitmapeditor-width",
- eventListeners: [{
- name: "change", handlerObject: this, handlerMethod: "handleWidthChangeEvent"
- }]
- });
- this.heightDomNode = $tw.utils.domMaker("input",{
- document: this.document,
- "class":"tc-edit-bitmapeditor-height",
- eventListeners: [{
- name: "change", handlerObject: this, handlerMethod: "handleHeightChangeEvent"
- }]
- });
- // Insert the elements into the DOM
+ // Set the width and height variables
+ this.setVariable("tv-bitmap-editor-width",this.canvasDomNode.width + "px");
+ this.setVariable("tv-bitmap-editor-height",this.canvasDomNode.height + "px");
+ // Render toolbar child widgets
+ this.renderChildren(this.toolbarNode,null);
+ // // Insert the elements into the DOM
parent.insertBefore(this.canvasDomNode,nextSibling);
- parent.insertBefore(this.widthDomNode,nextSibling);
- parent.insertBefore(this.heightDomNode,nextSibling);
- this.domNodes.push(this.canvasDomNode,this.widthDomNode,this.heightDomNode);
+ this.domNodes.push(this.canvasDomNode);
// Load the image into the canvas
if($tw.browser) {
this.loadCanvas();
}
+ // Add widget message listeners
+ this.addEventListeners([
+ {type: "tm-edit-bitmap-operation", handler: "handleEditBitmapOperationMessage"}
+ ]);
+};
+
+/*
+Handle an edit bitmap operation message from the toolbar
+*/
+EditBitmapWidget.prototype.handleEditBitmapOperationMessage = function(event) {
+ // Invoke the handler
+ var handler = this.editorOperations[event.param];
+ if(handler) {
+ handler.call(this,event);
+ }
};
/*
@@ -91,13 +106,28 @@ Compute the internal state of the widget
EditBitmapWidget.prototype.execute = function() {
// Get our parameters
this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
+ // Make the child widgets
+ this.makeChildWidgets();
};
/*
-Note that the bitmap editor intentionally doesn't try to refresh itself because it would be confusing to have the image changing spontaneously while editting it
+Just refresh the toolbar
*/
EditBitmapWidget.prototype.refresh = function(changedTiddlers) {
- return false;
+ return this.refreshChildren(changedTiddlers);
+};
+
+/*
+Set the bitmap size variables and refresh the toolbar
+*/
+EditBitmapWidget.prototype.refreshToolbar = function() {
+ // Set the width and height variables
+ this.setVariable("tv-bitmap-editor-width",this.canvasDomNode.width + "px");
+ this.setVariable("tv-bitmap-editor-height",this.canvasDomNode.height + "px");
+ // Refresh each of our child widgets
+ $tw.utils.each(this.children,function(childWidget) {
+ childWidget.refreshSelf();
+ });
};
EditBitmapWidget.prototype.loadCanvas = function() {
@@ -112,7 +142,7 @@ EditBitmapWidget.prototype.loadCanvas = function() {
self.currCanvas = self.document.createElement("canvas");
self.initCanvas(self.currCanvas,currImage.width,currImage.height,currImage);
// Set the width and height input boxes
- self.updateSize();
+ self.refreshToolbar();
};
currImage.onerror = function() {
// Set the on-screen canvas size and clear it
@@ -121,7 +151,7 @@ EditBitmapWidget.prototype.loadCanvas = function() {
self.currCanvas = self.document.createElement("canvas");
self.initCanvas(self.currCanvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
// Set the width and height input boxes
- self.updateSize();
+ self.refreshToolbar();
};
// Get the current bitmap into an image object
currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
@@ -139,14 +169,6 @@ EditBitmapWidget.prototype.initCanvas = function(canvas,width,height,image) {
}
};
-/*
-** Update the input boxes with the actual size of the canvas
-*/
-EditBitmapWidget.prototype.updateSize = function() {
- this.widthDomNode.value = this.currCanvas.width;
- this.heightDomNode.value = this.currCanvas.height;
-};
-
/*
** Change the size of the canvas, preserving the current image
*/
@@ -167,28 +189,6 @@ EditBitmapWidget.prototype.changeCanvasSize = function(newWidth,newHeight) {
ctx.drawImage(this.currCanvas,0,0);
};
-EditBitmapWidget.prototype.handleWidthChangeEvent = function(event) {
- // Get the new width
- var newWidth = parseInt(this.widthDomNode.value,10);
- // Update if necessary
- if(newWidth > 0 && newWidth !== this.currCanvas.width) {
- this.changeCanvasSize(newWidth,this.currCanvas.height);
- }
- // Update the input controls
- this.updateSize();
-};
-
-EditBitmapWidget.prototype.handleHeightChangeEvent = function(event) {
- // Get the new width
- var newHeight = parseInt(this.heightDomNode.value,10);
- // Update if necessary
- if(newHeight > 0 && newHeight !== this.currCanvas.height) {
- this.changeCanvasSize(this.currCanvas.width,newHeight);
- }
- // Update the input controls
- this.updateSize();
-};
-
EditBitmapWidget.prototype.handleTouchStartEvent = function(event) {
this.brushDown = true;
this.strokeStart(event.touches[0].clientX,event.touches[0].clientY);
@@ -264,8 +264,9 @@ EditBitmapWidget.prototype.strokeMove = function(x,y) {
// Redraw the previous image
ctx.drawImage(this.currCanvas,0,0);
// Render the stroke
+ ctx.globalAlpha = parseFloat(this.wiki.getTiddlerText(LINE_OPACITY_TITLE,"1.0"));
ctx.strokeStyle = this.wiki.getTiddlerText(LINE_COLOUR_TITLE,"#ff0");
- ctx.lineWidth = parseInt(this.wiki.getTiddlerText(LINE_WIDTH_TITLE,"3"),10);
+ ctx.lineWidth = parseFloat(this.wiki.getTiddlerText(LINE_WIDTH_TITLE,"3"));
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
@@ -292,7 +293,7 @@ EditBitmapWidget.prototype.saveChanges = function() {
var tiddler = this.wiki.getTiddler(this.editTitle);
if(tiddler) {
// data URIs look like "data:;base64,"
- var dataURL = this.canvasDomNode.toDataURL(tiddler.fields.type,1.0),
+ var dataURL = this.canvasDomNode.toDataURL(tiddler.fields.type),
posColon = dataURL.indexOf(":"),
posSemiColon = dataURL.indexOf(";"),
posComma = dataURL.indexOf(","),
diff --git a/core/modules/widgets/edit-shortcut.js b/core/modules/widgets/edit-shortcut.js
new file mode 100644
index 000000000..78f34839c
--- /dev/null
+++ b/core/modules/widgets/edit-shortcut.js
@@ -0,0 +1,139 @@
+/*\
+title: $:/core/modules/widgets/edit-shortcut.js
+type: application/javascript
+module-type: widget
+
+Widget to display an editable keyboard shortcut
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var Widget = require("$:/core/modules/widgets/widget.js").widget;
+
+var EditShortcutWidget = function(parseTreeNode,options) {
+ this.initialise(parseTreeNode,options);
+};
+
+/*
+Inherit from the base widget class
+*/
+EditShortcutWidget.prototype = new Widget();
+
+/*
+Render this widget into the DOM
+*/
+EditShortcutWidget.prototype.render = function(parent,nextSibling) {
+ this.parentDomNode = parent;
+ this.computeAttributes();
+ this.execute();
+ this.inputNode = this.document.createElement("input");
+ // Assign classes
+ if(this.shortcutClass) {
+ this.inputNode.className = this.shortcutClass;
+ }
+ // Assign other attributes
+ if(this.shortcutStyle) {
+ this.inputNode.setAttribute("style",this.shortcutStyle);
+ }
+ if(this.shortcutTooltip) {
+ this.inputNode.setAttribute("title",this.shortcutTooltip);
+ }
+ if(this.shortcutPlaceholder) {
+ this.inputNode.setAttribute("placeholder",this.shortcutPlaceholder);
+ }
+ if(this.shortcutAriaLabel) {
+ this.inputNode.setAttribute("aria-label",this.shortcutAriaLabel);
+ }
+ // Assign the current shortcut
+ this.updateInputNode();
+ // Add event handlers
+ $tw.utils.addEventListeners(this.inputNode,[
+ {name: "keydown", handlerObject: this, handlerMethod: "handleKeydownEvent"}
+ ]);
+ // Link into the DOM
+ parent.insertBefore(this.inputNode,nextSibling);
+ this.domNodes.push(this.inputNode);
+};
+
+/*
+Compute the internal state of the widget
+*/
+EditShortcutWidget.prototype.execute = function() {
+ this.shortcutTiddler = this.getAttribute("tiddler");
+ this.shortcutField = this.getAttribute("field");
+ this.shortcutIndex = this.getAttribute("index");
+ this.shortcutPlaceholder = this.getAttribute("placeholder");
+ this.shortcutDefault = this.getAttribute("default","");
+ this.shortcutClass = this.getAttribute("class");
+ this.shortcutStyle = this.getAttribute("style");
+ this.shortcutTooltip = this.getAttribute("tooltip");
+ this.shortcutAriaLabel = this.getAttribute("aria-label");
+};
+
+/*
+Update the value of the input node
+*/
+EditShortcutWidget.prototype.updateInputNode = function() {
+ if(this.shortcutField) {
+ var tiddler = this.wiki.getTiddler(this.shortcutTiddler);
+ if(tiddler && $tw.utils.hop(tiddler.fields,this.shortcutField)) {
+ this.inputNode.value = tiddler.getFieldString(this.shortcutField);
+ } else {
+ this.inputNode.value = this.shortcutDefault;
+ }
+ } else if(this.shortcutIndex) {
+ this.inputNode.value = this.wiki.extractTiddlerDataItem(this.shortcutTiddler,this.shortcutIndex,this.shortcutDefault);
+ } else {
+ this.inputNode.value = this.wiki.getTiddlerText(this.shortcutTiddler,this.shortcutDefault);
+ }
+};
+
+/*
+Handle a dom "keydown" event
+*/
+EditShortcutWidget.prototype.handleKeydownEvent = function(event) {
+ // Ignore shift, ctrl, meta, alt
+ if(event.keyCode && $tw.keyboardManager.getModifierKeys().indexOf(event.keyCode) === -1) {
+ // Get the shortcut text representation
+ var value = $tw.keyboardManager.getPrintableShortcuts([{
+ ctrlKey: event.ctrlKey,
+ shiftKey: event.shiftKey,
+ altKey: event.altKey,
+ metaKey: event.metaKey,
+ keyCode: event.keyCode
+ }]);
+ if(value.length > 0) {
+ this.wiki.setText(this.shortcutTiddler,this.shortcutField,this.shortcutIndex,value[0]);
+ }
+ // Ignore the keydown if it was already handled
+ event.preventDefault();
+ event.stopPropagation();
+ return true;
+ } else {
+ return false;
+ }
+};
+
+/*
+Selectively refreshes the widget if needed. Returns true if the widget needed re-rendering
+*/
+EditShortcutWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes();
+ if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.placeholder || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.style || changedAttributes.tooltip || changedAttributes["aria-label"]) {
+ this.refreshSelf();
+ return true;
+ } else if(changedTiddlers[this.shortcutTiddler]) {
+ this.updateInputNode();
+ return true;
+ } else {
+ return false;
+ }
+};
+
+exports["edit-shortcut"] = EditShortcutWidget;
+
+})();
diff --git a/core/modules/widgets/edit-text.js b/core/modules/widgets/edit-text.js
index c0588f93c..bc7b77490 100644
--- a/core/modules/widgets/edit-text.js
+++ b/core/modules/widgets/edit-text.js
@@ -12,280 +12,10 @@ Edit-text widget
/*global $tw: false */
"use strict";
-var DEFAULT_MIN_TEXT_AREA_HEIGHT = "100px"; // Minimum height of textareas in pixels
+var editTextWidgetFactory = require("$:/core/modules/editor/factory.js").editTextWidgetFactory,
+ FramedEngine = require("$:/core/modules/editor/engines/framed.js").FramedEngine,
+ SimpleEngine = require("$:/core/modules/editor/engines/simple.js").SimpleEngine;
-var Widget = require("$:/core/modules/widgets/widget.js").widget;
-
-var EditTextWidget = function(parseTreeNode,options) {
- this.initialise(parseTreeNode,options);
-};
-
-/*
-Inherit from the base widget class
-*/
-EditTextWidget.prototype = new Widget();
-
-/*
-Render this widget into the DOM
-*/
-EditTextWidget.prototype.render = function(parent,nextSibling) {
- var self = this;
- // Save the parent dom node
- this.parentDomNode = parent;
- // Compute our attributes
- this.computeAttributes();
- // Execute our logic
- this.execute();
- // Create our element
- var editInfo = this.getEditInfo(),
- tag = this.editTag;
- if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) {
- tag = "input";
- }
- var domNode = this.document.createElement(tag);
- if(this.editType) {
- domNode.setAttribute("type",this.editType);
- }
- if(editInfo.value === "" && this.editPlaceholder) {
- domNode.setAttribute("placeholder",this.editPlaceholder);
- }
- if(this.editSize) {
- domNode.setAttribute("size",this.editSize);
- }
- if(this.editRows) {
- domNode.setAttribute("rows",this.editRows);
- }
- // Assign classes
- if(this.editClass) {
- domNode.className = this.editClass;
- }
- // Set the text
- if(this.editTag === "textarea") {
- domNode.appendChild(this.document.createTextNode(editInfo.value));
- } else {
- domNode.value = editInfo.value;
- }
- // Add an input event handler
- $tw.utils.addEventListeners(domNode,[
- {name: "focus", handlerObject: this, handlerMethod: "handleFocusEvent"},
- {name: "input", handlerObject: this, handlerMethod: "handleInputEvent"}
- ]);
- // Insert the element into the DOM
- parent.insertBefore(domNode,nextSibling);
- this.domNodes.push(domNode);
- if(this.postRender) {
- this.postRender();
- }
- // Fix height
- this.fixHeight();
- // Focus field
- if(this.editFocus === "true") {
- if(domNode.focus && domNode.select) {
- domNode.focus();
- domNode.select();
- }
- }
-};
-
-/*
-Get the tiddler being edited and current value
-*/
-EditTextWidget.prototype.getEditInfo = function() {
- // Get the edit value
- var self = this,
- value,
- update;
- if(this.editIndex) {
- value = this.wiki.extractTiddlerDataItem(this.editTitle,this.editIndex,this.editDefault);
- update = function(value) {
- var data = self.wiki.getTiddlerData(self.editTitle,{});
- if(data[self.editIndex] !== value) {
- data[self.editIndex] = value;
- self.wiki.setTiddlerData(self.editTitle,data);
- }
- };
- } else {
- // Get the current tiddler and the field name
- var tiddler = this.wiki.getTiddler(this.editTitle);
- if(tiddler) {
- // If we've got a tiddler, the value to display is the field string value
- value = tiddler.getFieldString(this.editField);
- } else {
- // Otherwise, we need to construct a default value for the editor
- switch(this.editField) {
- case "text":
- value = "Type the text for the tiddler '" + this.editTitle + "'";
- break;
- case "title":
- value = this.editTitle;
- break;
- default:
- value = "";
- break;
- }
- if(this.editDefault !== undefined) {
- value = this.editDefault;
- }
- }
- update = function(value) {
- var tiddler = self.wiki.getTiddler(self.editTitle),
- updateFields = {
- title: self.editTitle
- };
- updateFields[self.editField] = value;
- self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getCreationFields(),tiddler,updateFields,self.wiki.getModificationFields()));
- };
- }
- return {value: value, update: update};
-};
-
-/*
-Compute the internal state of the widget
-*/
-EditTextWidget.prototype.execute = function() {
- // Get our parameters
- this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
- this.editField = this.getAttribute("field","text");
- this.editIndex = this.getAttribute("index");
- this.editDefault = this.getAttribute("default");
- this.editClass = this.getAttribute("class");
- this.editPlaceholder = this.getAttribute("placeholder");
- this.editSize = this.getAttribute("size");
- this.editRows = this.getAttribute("rows");
- this.editAutoHeight = this.getAttribute("autoHeight","yes") === "yes";
- this.editMinHeight = this.getAttribute("minHeight",DEFAULT_MIN_TEXT_AREA_HEIGHT);
- this.editFocusPopup = this.getAttribute("focusPopup");
- this.editFocus = this.getAttribute("focus");
- // Get the editor element tag and type
- var tag,type;
- if(this.editField === "text") {
- tag = "textarea";
- } else {
- tag = "input";
- var fieldModule = $tw.Tiddler.fieldModules[this.editField];
- if(fieldModule && fieldModule.editTag) {
- tag = fieldModule.editTag;
- }
- if(fieldModule && fieldModule.editType) {
- type = fieldModule.editType;
- }
- type = type || "text";
- }
- // Get the rest of our parameters
- this.editTag = this.getAttribute("tag",tag);
- this.editType = this.getAttribute("type",type);
-};
-
-/*
-Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
-*/
-EditTextWidget.prototype.refresh = function(changedTiddlers) {
- var changedAttributes = this.computeAttributes();
- // Completely rerender if any of our attributes have changed
- if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.placeholder || changedAttributes.size || changedAttributes.autoHeight || changedAttributes.minHeight || changedAttributes.focusPopup || changedAttributes.rows) {
- this.refreshSelf();
- return true;
- } else if(changedTiddlers[this.editTitle]) {
- this.updateEditor(this.getEditInfo().value);
- return true;
- }
- // Fix the height anyway in case there has been a reflow
- this.fixHeight();
- return false;
-};
-
-/*
-Update the editor with new text. This method is separate from updateEditorDomNode()
-so that subclasses can override updateEditor() and still use updateEditorDomNode()
-*/
-EditTextWidget.prototype.updateEditor = function(text) {
- this.updateEditorDomNode(text);
-};
-
-/*
-Update the editor dom node with new text
-*/
-EditTextWidget.prototype.updateEditorDomNode = function(text) {
- // Replace the edit value if the tiddler we're editing has changed
- var domNode = this.domNodes[0];
- if(!domNode.isTiddlyWikiFakeDom) {
- if(this.document.activeElement !== domNode) {
- domNode.value = text;
- }
- // Fix the height if needed
- this.fixHeight();
- }
-};
-
-/*
-Get the first parent element that has scrollbars or use the body as fallback.
-*/
-EditTextWidget.prototype.getScrollContainer = function(el) {
- while(el.parentNode) {
- el = el.parentNode;
- if(el.scrollTop) {
- return el;
- }
- }
- return this.document.body;
-};
-
-/*
-Fix the height of textareas to fit their content
-*/
-EditTextWidget.prototype.fixHeight = function() {
- var domNode = this.domNodes[0];
- if(this.editAutoHeight && domNode && !domNode.isTiddlyWikiFakeDom && this.editTag === "textarea") {
- // Resize the textarea to fit its content, preserving scroll position
- // Get the scroll container and register the current scroll position
- var container = this.getScrollContainer(domNode),
- scrollTop = container.scrollTop;
- // Measure the specified minimum height
- domNode.style.height = this.editMinHeight;
- var minHeight = domNode.offsetHeight;
- // Set its height to auto so that it snaps to the correct height
- domNode.style.height = "auto";
- // Calculate the revised height
- var newHeight = Math.max(domNode.scrollHeight + domNode.offsetHeight - domNode.clientHeight,minHeight);
- // Only try to change the height if it has changed
- if(newHeight !== domNode.offsetHeight) {
- domNode.style.height = newHeight + "px";
- // Make sure that the dimensions of the textarea are recalculated
- $tw.utils.forceLayout(domNode);
- // Set the container to the position we registered at the beginning
- container.scrollTop = scrollTop;
- }
- }
-};
-
-/*
-Handle a dom "input" event
-*/
-EditTextWidget.prototype.handleInputEvent = function(event) {
- this.saveChanges(this.domNodes[0].value);
- this.fixHeight();
- return true;
-};
-
-EditTextWidget.prototype.handleFocusEvent = function(event) {
- if(this.editFocusPopup) {
- $tw.popup.triggerPopup({
- domNode: this.domNodes[0],
- title: this.editFocusPopup,
- wiki: this.wiki,
- force: true
- });
- }
- return true;
-};
-
-EditTextWidget.prototype.saveChanges = function(text) {
- var editInfo = this.getEditInfo();
- if(text !== editInfo.value) {
- editInfo.update(text);
- }
-};
-
-exports["edit-text"] = EditTextWidget;
+exports["edit-text"] = editTextWidgetFactory(FramedEngine,SimpleEngine);
})();
diff --git a/core/modules/widgets/edit.js b/core/modules/widgets/edit.js
index 38b676e57..ebcc775ca 100644
--- a/core/modules/widgets/edit.js
+++ b/core/modules/widgets/edit.js
@@ -57,7 +57,8 @@ EditWidget.prototype.execute = function() {
index: {type: "string", value: this.editIndex},
"class": {type: "string", value: this.editClass},
"placeholder": {type: "string", value: this.editPlaceholder}
- }
+ },
+ children: this.parseTreeNode.children
}]);
};
diff --git a/core/modules/widgets/entity.js b/core/modules/widgets/entity.js
index 7e2b9dc66..5c885baea 100755
--- a/core/modules/widgets/entity.js
+++ b/core/modules/widgets/entity.js
@@ -29,7 +29,8 @@ Render this widget into the DOM
EntityWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.execute();
- var textNode = this.document.createTextNode($tw.utils.entityDecode(this.parseTreeNode.entity));
+ var entityString = this.getAttribute("entity",this.parseTreeNode.entity || ""),
+ textNode = this.document.createTextNode($tw.utils.entityDecode(entityString));
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
};
@@ -44,7 +45,13 @@ EntityWidget.prototype.execute = function() {
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
EntityWidget.prototype.refresh = function(changedTiddlers) {
- return false;
+ var changedAttributes = this.computeAttributes();
+ if(changedAttributes.entity) {
+ this.refreshSelf();
+ return true;
+ } else {
+ return false;
+ }
};
exports.entity = EntityWidget;
diff --git a/core/modules/widgets/fields.js b/core/modules/widgets/fields.js
index 511b6f7d5..d75a3b6e9 100755
--- a/core/modules/widgets/fields.js
+++ b/core/modules/widgets/fields.js
@@ -75,9 +75,9 @@ FieldsWidget.prototype.execute = function() {
value = reMatch[1];
}
}
- row = row.replace("$name$",fieldName);
- row = row.replace("$value$",value);
- row = row.replace("$encoded_value$",$tw.utils.htmlEncode(value));
+ row = $tw.utils.replaceString(row,"$name$",fieldName);
+ row = $tw.utils.replaceString(row,"$value$",value);
+ row = $tw.utils.replaceString(row,"$encoded_value$",$tw.utils.htmlEncode(value));
text.push(row);
}
}
diff --git a/core/modules/widgets/image.js b/core/modules/widgets/image.js
index 50c0703b1..4ecafbd2b 100644
--- a/core/modules/widgets/image.js
+++ b/core/modules/widgets/image.js
@@ -87,6 +87,9 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
src = _canonical_uri;
break;
}
+ } else {
+ // Just trigger loading of the tiddler
+ this.wiki.getTiddlerText(this.imageSource);
}
}
}
diff --git a/core/modules/widgets/keyboard.js b/core/modules/widgets/keyboard.js
index df32cc5b8..8d2c105a7 100644
--- a/core/modules/widgets/keyboard.js
+++ b/core/modules/widgets/keyboard.js
@@ -41,8 +41,11 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) {
domNode.className = classes.join(" ");
// Add a keyboard event handler
domNode.addEventListener("keydown",function (event) {
- if($tw.utils.checkKeyDescriptor(event,self.keyInfo)) {
- self.invokeActions(this,event);
+ if($tw.keyboardManager.checkKeyDescriptors(event,self.keyInfoArray)) {
+ self.invokeActions(self,event);
+ if(self.actions) {
+ self.invokeActionString(self.actions,self,event);
+ }
self.dispatchMessage(event);
event.preventDefault();
event.stopPropagation();
@@ -65,10 +68,11 @@ Compute the internal state of the widget
*/
KeyboardWidget.prototype.execute = function() {
// Get attributes
+ this.actions = this.getAttribute("actions");
this.message = this.getAttribute("message");
this.param = this.getAttribute("param");
this.key = this.getAttribute("key");
- this.keyInfo = $tw.utils.parseKeyDescriptor(this.key);
+ this.keyInfoArray = $tw.keyboardManager.parseKeyDescriptors(this.key);
this["class"] = this.getAttribute("class");
// Make child widgets
this.makeChildWidgets();
diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js
index c749ff945..e4596c72c 100755
--- a/core/modules/widgets/link.js
+++ b/core/modules/widgets/link.js
@@ -13,6 +13,7 @@ Link widget
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
+var MISSING_LINK_CONFIG_TITLE = "$:/config/MissingLinks";
var LinkWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
@@ -35,9 +36,10 @@ LinkWidget.prototype.render = function(parent,nextSibling) {
this.execute();
// Get the value of the tv-wikilinks configuration macro
var wikiLinksMacro = this.getVariable("tv-wikilinks"),
- useWikiLinks = wikiLinksMacro ? (wikiLinksMacro.trim() !== "no") : true;
+ useWikiLinks = wikiLinksMacro ? (wikiLinksMacro.trim() !== "no") : true,
+ missingLinksEnabled = !(this.hideMissingLinks && this.isMissing && !this.isShadow);
// Render the link if required
- if(useWikiLinks) {
+ if(useWikiLinks && missingLinksEnabled) {
this.renderLink(parent,nextSibling);
} else {
// Just insert the link text
@@ -80,8 +82,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
// Set an href
var wikiLinkTemplateMacro = this.getVariable("tv-wikilink-template"),
wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$",
- wikiLinkText = wikiLinkTemplate.replace("$uri_encoded$",encodeURIComponent(this.to));
- wikiLinkText = wikiLinkText.replace("$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
+ wikiLinkText = $tw.utils.replaceString(wikiLinkTemplate,"$uri_encoded$",encodeURIComponent(this.to));
+ wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
wikiLinkText = this.getVariable("tv-get-export-link",{params: [{name: "to",value: this.to}],defaultValue: wikiLinkText});
if(tag === "a") {
domNode.setAttribute("href",wikiLinkText);
@@ -216,6 +218,7 @@ LinkWidget.prototype.execute = function() {
// Determine the link characteristics
this.isMissing = !this.wiki.tiddlerExists(this.to);
this.isShadow = this.wiki.isShadowTiddler(this.to);
+ this.hideMissingLinks = ($tw.wiki.getTiddlerText(MISSING_LINK_CONFIG_TITLE,"yes") === "no");
// Make the child widgets
this.makeChildWidgets();
};
@@ -225,7 +228,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
LinkWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
- if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip) {
+ if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip || changedTiddlers[MISSING_LINK_CONFIG_TITLE]) {
this.refreshSelf();
return true;
}
diff --git a/core/modules/widgets/linkcatcher.js b/core/modules/widgets/linkcatcher.js
index 51738d6d8..d279140c0 100644
--- a/core/modules/widgets/linkcatcher.js
+++ b/core/modules/widgets/linkcatcher.js
@@ -45,6 +45,7 @@ LinkCatcherWidget.prototype.execute = function() {
this.catchMessage = this.getAttribute("message");
this.catchSet = this.getAttribute("set");
this.catchSetTo = this.getAttribute("setTo");
+ this.catchActions = this.getAttribute("actions");
// Construct the child widgets
this.makeChildWidgets();
};
@@ -80,6 +81,9 @@ LinkCatcherWidget.prototype.handleNavigateEvent = function(event) {
var tiddler = this.wiki.getTiddler(this.catchSet);
this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: this.catchSet, text: this.catchSetTo}));
}
+ if(this.catchActions) {
+ this.invokeActionString(this.catchActions,this);
+ }
return false;
};
diff --git a/core/modules/widgets/list.js b/core/modules/widgets/list.js
index 4c5b265f2..f4981df33 100755
--- a/core/modules/widgets/list.js
+++ b/core/modules/widgets/list.js
@@ -43,6 +43,9 @@ ListWidget.prototype.render = function(parent,nextSibling) {
this.renderChildren(parent,nextSibling);
// Construct the storyview
var StoryView = this.storyViews[this.storyViewName];
+ if(this.storyViewName && !StoryView) {
+ StoryView = this.storyViews["classic"];
+ }
if(StoryView && !this.document.isTiddlyWikiFakeDom) {
this.storyview = new StoryView(this);
} else {
diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js
index b6aab02f4..fa54a80d3 100755
--- a/core/modules/widgets/navigator.js
+++ b/core/modules/widgets/navigator.js
@@ -73,7 +73,7 @@ NavigatorWidget.prototype.refresh = function(changedTiddlers) {
this.refreshSelf();
return true;
} else {
- return this.refreshChildren(changedTiddlers);
+ return this.refreshChildren(changedTiddlers);
}
};
@@ -355,6 +355,9 @@ NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) {
if(isRename) {
this.wiki.deleteTiddler(draftOf);
}
+ // #2381 always remove new title & old
+ this.removeTitleFromStory(storyList,draftTitle);
+ this.removeTitleFromStory(storyList,draftOf);
if(!event.paramObject || event.paramObject.suppressNavigation !== "yes") {
// Replace the draft in the story with the original
this.replaceFirstTitleInStory(storyList,title,draftTitle);
@@ -451,7 +454,7 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
// Merge the tags
var mergedTags = [];
if(existingTiddler && existingTiddler.fields.tags) {
- $tw.utils.pushTop(mergedTags,existingTiddler.fields.tags)
+ $tw.utils.pushTop(mergedTags,existingTiddler.fields.tags);
}
if(additionalFields && additionalFields.tags) {
// Merge tags
@@ -492,7 +495,6 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
// Import JSON tiddlers into a pending import tiddler
NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
- var self = this;
// Get the tiddlers
var tiddlers = [];
try {
@@ -544,7 +546,7 @@ NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
history.push(IMPORT_TITLE);
// Save the updated story and history
this.saveStoryList(storyList);
- this.addToHistory(history);
+ this.addToHistory(history);
}
return false;
};
@@ -556,7 +558,7 @@ NavigatorWidget.prototype.handlePerformImportEvent = function(event) {
importData = this.wiki.getTiddlerDataCached(event.param,{tiddlers: {}}),
importReport = [];
// Add the tiddlers to the store
- importReport.push($tw.language.getString("Import/Imported") + "\n");
+ importReport.push($tw.language.getString("Import/Imported/Hint") + "\n");
$tw.utils.each(importData.tiddlers,function(tiddlerFields) {
var title = tiddlerFields.title;
if(title && importTiddler && importTiddler.fields["selection-" + title] !== "unchecked") {
@@ -577,8 +579,7 @@ NavigatorWidget.prototype.handlePerformImportEvent = function(event) {
};
NavigatorWidget.prototype.handleFoldTiddlerEvent = function(event) {
- var self = this,
- paramObject = event.paramObject || {};
+ var paramObject = event.paramObject || {};
if(paramObject.foldedState) {
var foldedState = this.wiki.getTiddlerText(paramObject.foldedState,"show") === "show" ? "hide" : "show";
this.wiki.setText(paramObject.foldedState,"text",null,foldedState);
@@ -613,8 +614,7 @@ NavigatorWidget.prototype.handleUnfoldAllTiddlersEvent = function(event) {
};
NavigatorWidget.prototype.handleRenameTiddlerEvent = function(event) {
- var self = this,
- paramObject = event.paramObject || {},
+ var paramObject = event.paramObject || {},
from = paramObject.from || event.tiddlerTitle,
to = paramObject.to;
$tw.wiki.renameTiddler(from,to);
diff --git a/core/modules/widgets/select.js b/core/modules/widgets/select.js
index b0d7385cb..4d1111730 100644
--- a/core/modules/widgets/select.js
+++ b/core/modules/widgets/select.js
@@ -51,6 +51,7 @@ SelectWidget.prototype.render = function(parent,nextSibling) {
Handle a change event
*/
SelectWidget.prototype.handleChangeEvent = function(event) {
+ // Get the new value and assign it to the tiddler
if(this.selectMultiple == false) {
var value = this.getSelectDomNode().value;
} else {
@@ -58,6 +59,10 @@ SelectWidget.prototype.handleChangeEvent = function(event) {
value = $tw.utils.stringifyList(value);
}
this.wiki.setText(this.selectTitle,this.selectField,this.selectIndex,value);
+ // Trigger actions
+ if(this.selectActions) {
+ this.invokeActionString(this.selectActions,this,event);
+ }
};
/*
@@ -132,6 +137,7 @@ Compute the internal state of the widget
*/
SelectWidget.prototype.execute = function() {
// Get our parameters
+ this.selectActions = this.getAttribute("actions");
this.selectTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.selectField = this.getAttribute("field","text");
this.selectIndex = this.getAttribute("index");
diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js
index f75fa2c98..76844b62d 100755
--- a/core/modules/widgets/setvariable.js
+++ b/core/modules/widgets/setvariable.js
@@ -40,6 +40,7 @@ SetWidget.prototype.execute = function() {
// Get our parameters
this.setName = this.getAttribute("name","currentTiddler");
this.setFilter = this.getAttribute("filter");
+ this.setSelect = this.getAttribute("select");
this.setValue = this.getAttribute("value");
this.setEmptyValue = this.getAttribute("emptyValue");
// Set context variable
@@ -56,7 +57,15 @@ SetWidget.prototype.getValue = function() {
if(this.setFilter) {
var results = this.wiki.filterTiddlers(this.setFilter,this);
if(!this.setValue) {
- value = $tw.utils.stringifyList(results);
+ var select;
+ if(this.setSelect) {
+ select = parseInt(this.setSelect,10);
+ }
+ if(select !== undefined) {
+ value = results[select] || "";
+ } else {
+ value = $tw.utils.stringifyList(results);
+ }
}
if(results.length === 0 && this.setEmptyValue !== undefined) {
value = this.setEmptyValue;
@@ -72,7 +81,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
SetWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
- if(changedAttributes.name || changedAttributes.filter || changedAttributes.value || changedAttributes.emptyValue ||
+ if(changedAttributes.name || changedAttributes.filter || changedAttributes.select ||changedAttributes.value || changedAttributes.emptyValue ||
(this.setFilter && this.getValue() != this.variables[this.setName].value)) {
this.refreshSelf();
return true;
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 34a912851..7af61fc8e 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -68,7 +68,7 @@ TranscludeWidget.prototype.execute = function() {
parseTreeNodes = [{type: "element", tag: "span", attributes: {
"class": {type: "string", value: "tc-error"}
}, children: [
- {type: "text", text: "Recursive transclusion error in transclude widget"}
+ {type: "text", text: $tw.language.getString("Error/RecursiveTransclusion")}
]}];
}
}
diff --git a/core/modules/widgets/view.js b/core/modules/widgets/view.js
index 5e1fa9a01..bbe9106be 100755
--- a/core/modules/widgets/view.js
+++ b/core/modules/widgets/view.js
@@ -55,6 +55,9 @@ ViewWidget.prototype.execute = function() {
case "htmlwikified":
this.text = this.getValueAsHtmlWikified();
break;
+ case "plainwikified":
+ this.text = this.getValueAsPlainWikified();
+ break;
case "htmlencodedplainwikified":
this.text = this.getValueAsHtmlEncodedPlainWikified();
break;
@@ -135,6 +138,10 @@ ViewWidget.prototype.getValueAsHtmlWikified = function() {
return this.wiki.renderText("text/html","text/vnd.tiddlywiki",this.getValueAsText(),{parentWidget: this});
};
+ViewWidget.prototype.getValueAsPlainWikified = function() {
+ return this.wiki.renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText(),{parentWidget: this});
+};
+
ViewWidget.prototype.getValueAsHtmlEncodedPlainWikified = function() {
return $tw.utils.htmlEncode(this.wiki.renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText(),{parentWidget: this}));
};
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 679433071..f4905d433 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -125,7 +125,7 @@ Widget.prototype.substituteVariableParameters = function(text,formalParams,actua
// If we've still not got a value, use the default, if any
paramValue = paramValue || paramInfo["default"] || "";
// Replace any instances of this parameter
- text = text.replace(new RegExp("\\$" + $tw.utils.escapeRegExp(paramInfo.name) + "\\$","mg"),paramValue);
+ text = $tw.utils.replaceString(text,new RegExp("\\$" + $tw.utils.escapeRegExp(paramInfo.name) + "\\$","mg"),paramValue);
}
}
return text;
@@ -222,7 +222,9 @@ Widget.prototype.computeAttributes = function() {
self = this,
value;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
- if(attribute.type === "indirect") {
+ if(attribute.type === "filtered") {
+ value = self.wiki.filterTiddlers(attribute.filter,self)[0] || "";
+ } else if(attribute.type === "indirect") {
value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("currentTiddler"));
} else if(attribute.type === "macro") {
value = self.getVariable(attribute.value.name,{params: attribute.value.params});
@@ -493,8 +495,11 @@ Widget.prototype.invokeActions = function(triggeringWidget,event) {
for(var t=0; t 0) {
+ resultNode.attributes = {};
+ $tw.utils.each(widgetNode.attributes,function(attr,attrName) {
+ resultNode.attributes[attrName] = widgetNode.getAttribute(attrName);
+ });
+ }
+ if(Object.keys(widgetNode.children || {}).length > 0) {
+ resultNode.children = [];
+ $tw.utils.each(widgetNode.children,function(widgetChildNode) {
+ var node = {};
+ resultNode.children.push(node);
+ copyNode(widgetChildNode,node);
+ });
+ }
+ },
+ results = {};
+ copyNode(this.wikifyWidgetNode,results);
+ return results;
+};
+
+/*
+Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
+*/
+WikifyWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes();
+ // Refresh ourselves entirely if any of our attributes have changed
+ if(changedAttributes.name || changedAttributes.text || changedAttributes.type || changedAttributes.mode || changedAttributes.output) {
+ this.refreshSelf();
+ return true;
+ } else {
+ // Refresh the widget tree
+ if(this.wikifyWidgetNode.refresh(changedTiddlers)) {
+ // Check if there was any change
+ var result = this.getResult();
+ if(result !== this.wikifyResult) {
+ // If so, save the change
+ this.wikifyResult = result;
+ this.setVariable(this.wikifyName,this.wikifyResult);
+ // Refresh each of our child widgets
+ $tw.utils.each(this.children,function(childWidget) {
+ childWidget.refreshSelf();
+ });
+ return true;
+ }
+ }
+ // Just refresh the children
+ return this.refreshChildren(changedTiddlers);
+ }
+};
+
+exports.wikify = WikifyWidget;
+
+})();
diff --git a/core/modules/wiki.js b/core/modules/wiki.js
index 64abe9e89..63e31303c 100755
--- a/core/modules/wiki.js
+++ b/core/modules/wiki.js
@@ -328,7 +328,7 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
var result =
isNaN(x) && !isNaN(y) ? (isDescending ? -1 : 1) :
!isNaN(x) && isNaN(y) ? (isDescending ? 1 : -1) :
- (isDescending ? y - x : x - y);
+ (isDescending ? y - x : x - y);
return result;
};
if(sortField !== "title") {
@@ -784,6 +784,7 @@ Options include:
_canonical_uri: optional string of the canonical URI of this content
*/
exports.parseText = function(type,text,options) {
+ text = text || "";
options = options || {};
// Select a parser
var Parser = $tw.Wiki.parsers[type];
@@ -903,31 +904,54 @@ options: as for wiki.makeWidget() plus:
options.field: optional field to transclude (defaults to "text")
options.mode: transclusion mode "inline" or "block"
options.children: optional array of children for the transclude widget
+options.importVariables: optional importvariables filter string for macros to be included
+options.importPageMacros: optional boolean; if true, equivalent to passing "[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]" to options.importVariables
*/
exports.makeTranscludeWidget = function(title,options) {
options = options || {};
- var parseTree = {tree: [{
+ var parseTreeDiv = {tree: [{
type: "element",
tag: "div",
- children: [{
- type: "transclude",
- attributes: {
- tiddler: {
- name: "tiddler",
- type: "string",
- value: title}},
- isBlock: !options.parseAsInline}]}
- ]};
+ children: []}]},
+ parseTreeImportVariables = {
+ type: "importvariables",
+ attributes: {
+ filter: {
+ name: "filter",
+ type: "string"
+ }
+ },
+ isBlock: false,
+ children: []},
+ parseTreeTransclude = {
+ type: "transclude",
+ attributes: {
+ tiddler: {
+ name: "tiddler",
+ type: "string",
+ value: title}},
+ isBlock: !options.parseAsInline};
+ if(options.importVariables || options.importPageMacros) {
+ if(options.importVariables) {
+ parseTreeImportVariables.attributes.filter.value = options.importVariables;
+ } else if(options.importPageMacros) {
+ parseTreeImportVariables.attributes.filter.value = "[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]";
+ }
+ parseTreeDiv.tree[0].children.push(parseTreeImportVariables);
+ parseTreeImportVariables.children.push(parseTreeTransclude);
+ } else {
+ parseTreeDiv.tree[0].children.push(parseTreeTransclude);
+ }
if(options.field) {
- parseTree.tree[0].children[0].attributes.field = {type: "string", value: options.field};
+ parseTreeTransclude.attributes.field = {type: "string", value: options.field};
}
if(options.mode) {
- parseTree.tree[0].children[0].attributes.mode = {type: "string", value: options.mode};
+ parseTreeTransclude.attributes.mode = {type: "string", value: options.mode};
}
if(options.children) {
- parseTree.tree[0].children[0].children = options.children;
+ parseTreeTransclude.children = options.children;
}
- return $tw.wiki.makeWidget(parseTree,options);
+ return $tw.wiki.makeWidget(parseTreeDiv,options);
};
/*
@@ -1117,29 +1141,24 @@ exports.readFile = function(file,callback) {
var reader = new FileReader();
// Onload
reader.onload = function(event) {
- // Deserialise the file contents
var text = event.target.result,
tiddlerFields = {title: file.name || "Untitled", type: type};
- // Are we binary?
if(isBinary) {
- // The base64 section starts after the first comma in the data URI
var commaPos = text.indexOf(",");
if(commaPos !== -1) {
- tiddlerFields.text = text.substr(commaPos+1);
- callback([tiddlerFields]);
+ text = text.substr(commaPos + 1);
}
+ }
+ // Check whether this is an encrypted TiddlyWiki file
+ var encryptedJson = $tw.utils.extractEncryptedStoreArea(text);
+ if(encryptedJson) {
+ // If so, attempt to decrypt it with the current password
+ $tw.utils.decryptStoreAreaInteractive(encryptedJson,function(tiddlers) {
+ callback(tiddlers);
+ });
} else {
- // Check whether this is an encrypted TiddlyWiki file
- var encryptedJson = $tw.utils.extractEncryptedStoreArea(text);
- if(encryptedJson) {
- // If so, attempt to decrypt it with the current password
- $tw.utils.decryptStoreAreaInteractive(encryptedJson,function(tiddlers) {
- callback(tiddlers);
- });
- } else {
- // Otherwise, just try to deserialise any tiddlers in the file
- callback(self.deserializeTiddlers(type,text,tiddlerFields));
- }
+ // Otherwise, just try to deserialise any tiddlers in the file
+ callback(self.deserializeTiddlers(type,text,tiddlerFields));
}
};
// Kick off the read
@@ -1164,7 +1183,9 @@ exports.findDraft = function(targetTitle) {
}
/*
-Check whether the specified draft tiddler has been modified
+Check whether the specified draft tiddler has been modified.
+If the original tiddler doesn't exist, create a vanilla tiddler variable,
+to check if additional fields have been added.
*/
exports.isDraftModified = function(title) {
var tiddler = this.getTiddler(title);
@@ -1172,11 +1193,9 @@ exports.isDraftModified = function(title) {
return false;
}
var ignoredFields = ["created", "modified", "title", "draft.title", "draft.of"],
- origTiddler = this.getTiddler(tiddler.fields["draft.of"]);
- if(!origTiddler) {
- return tiddler.fields.text !== "";
- }
- return tiddler.fields["draft.title"] !== tiddler.fields["draft.of"] || !tiddler.isEqual(origTiddler,ignoredFields);
+ origTiddler = this.getTiddler(tiddler.fields["draft.of"]) || new $tw.Tiddler({text:"", tags:[]}),
+ titleModified = tiddler.fields["draft.title"] !== tiddler.fields["draft.of"];
+ return titleModified || !tiddler.isEqual(origTiddler,ignoredFields);
};
/*
@@ -1218,3 +1237,4 @@ exports.invokeUpgraders = function(titles,tiddlers) {
};
})();
+
diff --git a/core/templates/raw-static-tiddler.tid b/core/templates/raw-static-tiddler.tid
new file mode 100644
index 000000000..0531a391c
--- /dev/null
+++ b/core/templates/raw-static-tiddler.tid
@@ -0,0 +1,7 @@
+title: $:/core/templates/raw-static-tiddler
+
+<$view field="text" format="plainwikified" />
\ No newline at end of file
diff --git a/core/templates/static.area.tid b/core/templates/static.area.tid
index a7fbdf7bc..d235ab4ed 100644
--- a/core/templates/static.area.tid
+++ b/core/templates/static.area.tid
@@ -1,6 +1,7 @@
title: $:/core/templates/static.area
<$reveal type="nomatch" state="$:/isEncrypted" text="yes">
+{{{ [all[shadows+tiddlers]tag[$:/tags/RawStaticContent]!has[draft.of]] ||$:/core/templates/raw-static-tiddler}}}
{{$:/core/templates/static.content||$:/core/templates/html-tiddler}}
$reveal>
<$reveal type="match" state="$:/isEncrypted" text="yes">
diff --git a/core/templates/static.template.html.tid b/core/templates/static.template.html.tid
index 27a2e324d..5da5fb752 100644
--- a/core/templates/static.template.html.tid
+++ b/core/templates/static.template.html.tid
@@ -15,6 +15,7 @@ type: text/vnd.tiddlywiki-html
+
{{$:/core/wiki/title}}
diff --git a/core/templates/static.tiddler.html.tid b/core/templates/static.tiddler.html.tid
index bcc008660..6fb56b6cd 100644
--- a/core/templates/static.tiddler.html.tid
+++ b/core/templates/static.tiddler.html.tid
@@ -13,6 +13,7 @@ title: $:/core/templates/static.tiddler.html
+
diff --git a/core/templates/tiddlywiki5.html.tid b/core/templates/tiddlywiki5.html.tid
index 3f8701f18..160d5040b 100644
--- a/core/templates/tiddlywiki5.html.tid
+++ b/core/templates/tiddlywiki5.html.tid
@@ -12,6 +12,7 @@ title: $:/core/templates/tiddlywiki5.html
+
diff --git a/core/ui/AboveStory/tw2-plugin-check.tid b/core/ui/AboveStory/tw2-plugin-check.tid
index cce2deb9c..6c3687ad2 100644
--- a/core/ui/AboveStory/tw2-plugin-check.tid
+++ b/core/ui/AboveStory/tw2-plugin-check.tid
@@ -10,7 +10,7 @@ tags: $:/tags/AboveStory
-<$list filter="[all[system+tiddlers]tag[systemConfig]limit[1]]">
+<$list filter="[all[system+tiddlers]tag[systemConfig]]">
-
diff --git a/core/ui/AdvancedSearch/Filter.tid b/core/ui/AdvancedSearch/Filter.tid
index 93179bbaa..f6f13a863 100644
--- a/core/ui/AdvancedSearch/Filter.tid
+++ b/core/ui/AdvancedSearch/Filter.tid
@@ -3,35 +3,13 @@ tags: $:/tags/AdvancedSearch
caption: {{$:/language/Search/Filter/Caption}}
\define lingo-base() $:/language/Search/
-<$linkcatcher to="$:/temp/advancedsearch">
-
<
>
<$edit-text tiddler="$:/temp/advancedsearch" type="search" tag="input"/>
-<$button popup=<> class="tc-btn-invisible">
-{{$:/core/images/down-arrow}}
-$button>
-<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
-<$button class="tc-btn-invisible">
-<$action-setfield $tiddler="$:/temp/advancedsearch" $field="text" $value=""/>
-{{$:/core/images/close-button}}
-$button>
-<$macrocall $name="exportButton" exportFilter={{$:/temp/advancedsearch}} lingoBase="$:/language/Buttons/ExportTiddlers/"/>
-$reveal>
+<$list filter="[all[shadows+tiddlers]tag[$:/tags/AdvancedSearch/FilterButton]!has[draft.of]]"><$transclude/>$list>
-
-<$reveal state=<> type="nomatch" text="" default="">
-
-<$list filter="[all[shadows+tiddlers]tag[$:/tags/Filter]]"><$link to={{!!filter}}><$transclude field="description"/>$link>
-$list>
-
-$reveal>
-
-
-$linkcatcher>
-
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
<$set name="resultCount" value="""<$count filter={{$:/temp/advancedsearch}}/>""">
diff --git a/core/ui/AdvancedSearch/FilterButtons/clear.tid b/core/ui/AdvancedSearch/FilterButtons/clear.tid
new file mode 100644
index 000000000..3dd22e03d
--- /dev/null
+++ b/core/ui/AdvancedSearch/FilterButtons/clear.tid
@@ -0,0 +1,9 @@
+title: $:/core/ui/AdvancedSearch/Filter/FilterButtons/clear
+tags: $:/tags/AdvancedSearch/FilterButton
+
+<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
+<$button class="tc-btn-invisible">
+<$action-setfield $tiddler="$:/temp/advancedsearch" $field="text" $value=""/>
+{{$:/core/images/close-button}}
+$button>
+$reveal>
diff --git a/core/ui/AdvancedSearch/FilterButtons/delete.tid b/core/ui/AdvancedSearch/FilterButtons/delete.tid
new file mode 100644
index 000000000..fea48a667
--- /dev/null
+++ b/core/ui/AdvancedSearch/FilterButtons/delete.tid
@@ -0,0 +1,26 @@
+title: $:/core/ui/AdvancedSearch/Filter/FilterButtons/delete
+tags: $:/tags/AdvancedSearch/FilterButton
+
+<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
+<$button popup=<> class="tc-btn-invisible">
+{{$:/core/images/delete-button}}
+$button>
+$reveal>
+
+<$reveal state=<> type="popup" position="belowleft" animate="yes">
+
+
+
+<$set name="resultCount" value="""<$count filter={{$:/temp/advancedsearch}}/>""">
+Are you sure you wish to delete <> tiddler(s)?
+$set>
+
+
+<$button class="tc-btn">
+<$action-deletetiddler $filter={{$:/temp/advancedsearch}}/>
+Delete these tiddlers
+$button>
+
+
+
+$reveal>
diff --git a/core/ui/AdvancedSearch/FilterButtons/dropdown.tid b/core/ui/AdvancedSearch/FilterButtons/dropdown.tid
new file mode 100644
index 000000000..ee1c5eb37
--- /dev/null
+++ b/core/ui/AdvancedSearch/FilterButtons/dropdown.tid
@@ -0,0 +1,19 @@
+title: $:/core/ui/AdvancedSearch/Filter/FilterButtons/dropdown
+tags: $:/tags/AdvancedSearch/FilterButton
+
+
+<$button popup=<> class="tc-btn-invisible">
+{{$:/core/images/down-arrow}}
+$button>
+
+
+<$reveal state=<> type="popup" position="belowleft" animate="yes">
+<$linkcatcher to="$:/temp/advancedsearch">
+
+
+<$list filter="[all[shadows+tiddlers]tag[$:/tags/Filter]]"><$link to={{!!filter}}><$transclude field="description"/>$link>
+$list>
+
+
+$linkcatcher>
+$reveal>
diff --git a/core/ui/AdvancedSearch/FilterButtons/export.tid b/core/ui/AdvancedSearch/FilterButtons/export.tid
new file mode 100644
index 000000000..ec9ea91ac
--- /dev/null
+++ b/core/ui/AdvancedSearch/FilterButtons/export.tid
@@ -0,0 +1,6 @@
+title: $:/core/ui/AdvancedSearch/Filter/FilterButtons/export
+tags: $:/tags/AdvancedSearch/FilterButton
+
+<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
+<$macrocall $name="exportButton" exportFilter={{$:/temp/advancedsearch}} lingoBase="$:/language/Buttons/ExportTiddlers/"/>
+$reveal>
diff --git a/core/ui/AdvancedSearch/Shadows.tid b/core/ui/AdvancedSearch/Shadows.tid
index cd4a6cd38..b2455bfbb 100644
--- a/core/ui/AdvancedSearch/Shadows.tid
+++ b/core/ui/AdvancedSearch/Shadows.tid
@@ -21,6 +21,8 @@ caption: {{$:/language/Search/Shadows/Caption}}
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
+<$list filter="[{$:/temp/advancedsearch}minlength{$:/config/Search/MinLength}limit[1]]" emptyMessage="""{{$:/language/Search/Search/TooShort}}""" variable="listItem">
+
<$set name="resultCount" value="""<$count filter="[all[shadows]search{$:/temp/advancedsearch}] -[[$:/temp/advancedsearch]]"/>""">
@@ -33,6 +35,8 @@ caption: {{$:/language/Search/Shadows/Caption}}
$set>
+$list>
+
$reveal>
<$reveal state="$:/temp/advancedsearch" type="match" text="">
diff --git a/core/ui/AdvancedSearch/Standard.tid b/core/ui/AdvancedSearch/Standard.tid
index 174e6b24a..2342cb5b7 100644
--- a/core/ui/AdvancedSearch/Standard.tid
+++ b/core/ui/AdvancedSearch/Standard.tid
@@ -20,6 +20,7 @@ caption: {{$:/language/Search/Standard/Caption}}
$linkcatcher>
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
+<$list filter="[{$:/temp/advancedsearch}minlength{$:/config/Search/MinLength}limit[1]]" emptyMessage="""{{$:/language/Search/Search/TooShort}}""" variable="listItem">
<$set name="searchTiddler" value="$:/temp/advancedsearch">
<$list filter="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]butfirst[]limit[1]]" emptyMessage="""
<$list filter="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]]">
@@ -29,4 +30,5 @@ caption: {{$:/language/Search/Standard/Caption}}
<$macrocall $name="tabs" tabsList="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]]" default={{$:/config/SearchResults/Default}}/>
$list>
$set>
+$list>
$reveal>
diff --git a/core/ui/AdvancedSearch/System.tid b/core/ui/AdvancedSearch/System.tid
index 19cdfa86e..6de9d0786 100644
--- a/core/ui/AdvancedSearch/System.tid
+++ b/core/ui/AdvancedSearch/System.tid
@@ -21,6 +21,8 @@ caption: {{$:/language/Search/System/Caption}}
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
+<$list filter="[{$:/temp/advancedsearch}minlength{$:/config/Search/MinLength}limit[1]]" emptyMessage="""{{$:/language/Search/Search/TooShort}}""" variable="listItem">
+
<$set name="resultCount" value="""<$count filter="[is[system]search{$:/temp/advancedsearch}] -[[$:/temp/advancedsearch]]"/>""">
@@ -33,6 +35,8 @@ caption: {{$:/language/Search/System/Caption}}
$set>
+$list>
+
$reveal>
<$reveal state="$:/temp/advancedsearch" type="match" text="">
diff --git a/core/ui/AlertTemplate.tid b/core/ui/AlertTemplate.tid
index 87ddbd376..bcfc3c3fa 100644
--- a/core/ui/AlertTemplate.tid
+++ b/core/ui/AlertTemplate.tid
@@ -5,7 +5,7 @@ title: $:/core/ui/AlertTemplate
<$button class="tc-btn-invisible"><$action-deletetiddler $tiddler=<>/>{{$:/core/images/delete-button}}$button>
-<$view field="component"/> - <$view field="modified" format="date" template="0hh:0mm:0ss DD MM YYYY"/> <$reveal type="nomatch" state="!!count" text="">(count: <$view field="count"/>)$reveal>
+<$view field="component"/> - <$view field="modified" format="date" template="0hh:0mm:0ss DD MM YYYY"/> <$reveal type="nomatch" state="!!count" text="">({{$:/language/Count}}: <$view field="count"/>)$reveal>
diff --git a/core/ui/Components/plugin-info.tid b/core/ui/Components/plugin-info.tid
new file mode 100644
index 000000000..a5fbf141a
--- /dev/null
+++ b/core/ui/Components/plugin-info.tid
@@ -0,0 +1,95 @@
+title: $:/core/ui/Components/plugin-info
+
+\define lingo-base() $:/language/ControlPanel/Plugins/
+
+\define popup-state-macro()
+$(qualified-state)$-$(currentTiddler)$
+\end
+
+\define tabs-state-macro()
+$(popup-state)$-$(pluginInfoType)$
+\end
+
+\define plugin-icon-title()
+$(currentTiddler)$/icon
+\end
+
+\define plugin-disable-title()
+$:/config/Plugins/Disabled/$(currentTiddler)$
+\end
+
+\define plugin-table-body(type,disabledMessage,default-popup-state)
+
+<$transclude tiddler=<> subtiddler=<>>
+<$transclude tiddler="$:/core/images/plugin-generic-$type$"/>
+$transclude>
+
+
+
+''<$view field="description"><$view field="title"/>$view>'' $disabledMessage$
+
+
+<$view field="title"/>
+
+
+<$view field="version"/>
+
+
+\end
+
+\define plugin-info(type,default-popup-state)
+<$set name="popup-state" value=<>>
+<$reveal type="nomatch" state=<> text="yes">
+<$link to={{!!title}} class="tc-plugin-info">
+<>
+$link>
+$reveal>
+<$reveal type="match" state=<> text="yes">
+<$link to={{!!title}} class="tc-plugin-info tc-plugin-info-disabled">
+< ">>
+$link>
+$reveal>
+<$reveal type="match" text="yes" state=<> default="""$default-popup-state$""">
+
+
+<$list filter="[all[current]] -[[$:/core]]">
+
+<$reveal type="nomatch" state=<> text="yes">
+<$button set=<> setTo="yes" tooltip={{$:/language/ControlPanel/Plugins/Disable/Hint}} aria-label={{$:/language/ControlPanel/Plugins/Disable/Caption}}>
+<>
+$button>
+$reveal>
+<$reveal type="match" state=<> text="yes">
+<$button set=<> setTo="no" tooltip={{$:/language/ControlPanel/Plugins/Enable/Hint}} aria-label={{$:/language/ControlPanel/Plugins/Enable/Caption}}>
+<>
+$button>
+$reveal>
+
+$list>
+<$reveal type="nomatch" text="" state="!!list">
+<$set name="tabsList" filter="[list[]] contents">
+<$macrocall $name="tabs" state=<> tabsList=<> default="readme" template="$:/core/ui/PluginInfo"/>
+$set>
+$reveal>
+<$reveal type="match" text="" state="!!list">
+<>
+$reveal>
+
+
+$reveal>
+$set>
+\end
+
+<$macrocall $name="plugin-info" type=<> default-popup-state=<>/>
diff --git a/core/ui/ControlPanel/Basics.tid b/core/ui/ControlPanel/Basics.tid
index 714954a35..52ff077c3 100644
--- a/core/ui/ControlPanel/Basics.tid
+++ b/core/ui/ControlPanel/Basics.tid
@@ -19,7 +19,7 @@ caption: {{$:/language/ControlPanel/Basics/Caption}}
|<$link to="$:/SiteSubtitle"><>$link> |<$edit-text tiddler="$:/SiteSubtitle" default="" tag="input"/> |
|<$link to="$:/status/UserName"><>$link> |<$edit-text tiddler="$:/status/UserName" default="" tag="input"/> |
|<$link to="$:/config/AnimationDuration"><>$link> |<$edit-text tiddler="$:/config/AnimationDuration" default="" tag="input"/> |
-|<$link to="$:/DefaultTiddlers"><>$link> |<>
<$edit-text tag="textarea" tiddler="$:/DefaultTiddlers"/>
//<>// |
+|<$link to="$:/DefaultTiddlers"><>$link> |<>
<$edit tag="textarea" tiddler="$:/DefaultTiddlers" class="tc-edit-texteditor"/>
//<>// |
|<$link to="$:/config/NewJournal/Title"><>$link> |<$edit-text tiddler="$:/config/NewJournal/Title" default="" tag="input"/> |
|<$link to="$:/config/NewJournal/Tags"><>$link> |<$edit-text tiddler="$:/config/NewJournal/Tags" default="" tag="input"/> |
|<> |{{$:/snippets/minilanguageswitcher}} |
diff --git a/core/ui/ControlPanel/KeyboardShortcuts.tid b/core/ui/ControlPanel/KeyboardShortcuts.tid
new file mode 100644
index 000000000..64301a4ac
--- /dev/null
+++ b/core/ui/ControlPanel/KeyboardShortcuts.tid
@@ -0,0 +1,140 @@
+title: $:/core/ui/ControlPanel/KeyboardShortcuts
+tags: $:/tags/ControlPanel
+caption: {{$:/language/ControlPanel/KeyboardShortcuts/Caption}}
+
+\define lingo-base() $:/language/ControlPanel/KeyboardShortcuts/
+
+\define new-shortcut(title)
+
+<$edit-shortcut tiddler="$title$" placeholder={{$:/language/ControlPanel/KeyboardShortcuts/Add/Prompt}} style="width:auto;"/> <$button>
+<>
+<$action-listops
+ $tiddler="$(shortcutTitle)$"
+ $field="text"
+ $subfilter="[{$title$}]"
+/>
+<$action-deletetiddler
+ $tiddler="$title$"
+/>
+$button>
+
+\end
+
+\define shortcut-list-item(caption)
+
+
+
+<>
+
+
+
+<$button popup=<> class="tc-btn-invisible">
+{{$:/core/images/edit-button}}
+$button>
+<$macrocall $name="displayshortcuts" $output="text/html" shortcuts={{$(shortcutTitle)$}} prefix="" separator=" " suffix=""/>
+
+<$reveal state=<> type="popup" position="below" animate="yes">
+
+
+<$list filter="[list[$(shortcutTitle)$!!text]sort[title]]" variable="shortcut" emptyMessage="""
+
+//<>//
+
+""">
+
+<$button class="tc-btn-invisible" tooltip=<>>
+<$action-listops
+ $tiddler="$(shortcutTitle)$"
+ $field="text"
+ $subfilter="+[remove]"
+/>
+×
+$button>
+
+<$macrocall $name="displayshortcuts" $output="text/html" shortcuts=<>/>
+
+
+$list>
+
+<$macrocall $name="new-shortcut" title=<>/>
+
+
+$reveal>
+
+
+\end
+
+\define shortcut-list(caption,prefix)
+
+<$list filter="[all[tiddlers+shadows][$prefix$$(shortcutName)$]]" variable="shortcutTitle">
+<>
+$list>
+
+\end
+
+\define shortcut-editor()
+<>
+<>
+<>
+<>
+<>
+<>
+<>
+\end
+
+\define shortcut-preview()
+<$macrocall $name="displayshortcuts" $output="text/html" shortcuts={{$(shortcutPrefix)$$(shortcutName)$}} prefix="" separator=" " suffix=""/>
+\end
+
+\define shortcut-item-inner()
+
+
+<$reveal type="nomatch" state=<> text="open">
+<$button class="tc-btn-invisible">
+<$action-setfield
+ $tiddler=<>
+ $value="open"
+/>
+{{$:/core/images/right-arrow}}
+$button>
+$reveal>
+<$reveal type="match" state=<> text="open">
+<$button class="tc-btn-invisible">
+<$action-setfield
+ $tiddler=<>
+ $value="close"
+/>
+{{$:/core/images/down-arrow}}
+$button>
+$reveal>
+''<$text text=<>/>''
+
+
+<$transclude tiddler="$:/config/ShortcutInfo/$(shortcutName)$"/>
+
+
+<$list filter="$:/config/shortcuts/ $:/config/shortcuts-mac/ $:/config/shortcuts-not-mac/ $:/config/shortcuts-linux/ $:/config/shortcuts-not-linux/ $:/config/shortcuts-windows/ $:/config/shortcuts-not-windows/" variable="shortcutPrefix">
+<>
+$list>
+
+
+<$set name="dropdownState" value={{$(dropdownStateTitle)$}}>
+<$list filter="[prefix[open]]" variable="listItem">
+<>
+$list>
+$set>
+\end
+
+\define shortcut-item()
+<$set name="dropdownStateTitle" value=<>>
+<>
+$set>
+\end
+
+
+
+<$list filter="[all[shadows+tiddlers]removeprefix[$:/config/ShortcutInfo/]]" variable="shortcutName">
+<>
+$list>
+
+
diff --git a/core/ui/ControlPanel/Modals/AddPlugins.tid b/core/ui/ControlPanel/Modals/AddPlugins.tid
index d05c41767..76305e4ed 100644
--- a/core/ui/ControlPanel/Modals/AddPlugins.tid
+++ b/core/ui/ControlPanel/Modals/AddPlugins.tid
@@ -1,13 +1,11 @@
title: $:/core/ui/ControlPanel/Modals/AddPlugins
subtitle: {{$:/core/images/download-button}} {{$:/language/ControlPanel/Plugins/Add/Caption}}
-\define lingo-base() $:/language/ControlPanel/Plugins/
-
\define install-plugin-button()
<$button>
<$action-sendmessage $message="tm-load-plugin-from-library" url={{!!url}} title={{$(assetInfo)$!!original-title}}/>
-<$list filter="[get[original-title]get[version]]" variable="installedVersion" emptyMessage="""{{$:/language/ControlPanel/Plugins/Install}}""">
-{{$:/language/ControlPanel/Plugins/Reinstall}}
+<$list filter="[get[original-title]get[version]]" variable="installedVersion" emptyMessage="""{{$:/language/ControlPanel/Plugins/Install/Caption}}""">
+{{$:/language/ControlPanel/Plugins/Reinstall/Caption}}
$list>
$button>
\end
@@ -48,9 +46,9 @@ $:/state/add-plugin-info/$(connectionTiddler)$/$(assetInfo)$
<$reveal type="match" text="yes" state=<>>
-
-<$checkbox tiddler="""$:/config/WikiParserRules/$typeCap$/$(currentTiddler)$""" field="text" checked="enable" unchecked="disable" default="enable"> ''<$text text=<
>/>'': $checkbox>
-
+\define toggle(Type)
+<$checkbox
+tiddler="""$:/config/WikiParserRules/$Type$/$(rule)$"""
+field="text"
+checked="enable"
+unchecked="disable"
+default="enable">
+<>
+$checkbox>
\end
-\define parsing-outer(typeLower,typeCap)
-
-<$list filter="[wikiparserrules[$typeLower$]]">
-<>
+\define rules(type,Type)
+<$list filter="[wikiparserrules[$type$]]" variable="rule">
+- <
>
$list>
-
\end
<>
-! <>
-
-<>
-
-! <>
-
-<>
-
-! <>
-
-<>
+
+- <
>
+<>
+- <
>
+<>
+- <
>
+<>
+
\ No newline at end of file
diff --git a/core/ui/ControlPanel/Plugins.tid b/core/ui/ControlPanel/Plugins.tid
index d91dead26..49ff58e5e 100644
--- a/core/ui/ControlPanel/Plugins.tid
+++ b/core/ui/ControlPanel/Plugins.tid
@@ -4,95 +4,11 @@ caption: {{$:/language/ControlPanel/Plugins/Caption}}
\define lingo-base() $:/language/ControlPanel/Plugins/
-\define popup-state-macro()
-$(qualified-state)$-$(currentTiddler)$
-\end
-
-\define tabs-state-macro()
-$(popup-state)$-$(pluginInfoType)$
-\end
-
-\define plugin-icon-title()
-$(currentTiddler)$/icon
-\end
-
-\define plugin-disable-title()
-$:/config/Plugins/Disabled/$(currentTiddler)$
-\end
-
-\define plugin-table-body(type,disabledMessage)
-
-
-<$transclude tiddler=<> subtiddler=<>>
-<$transclude tiddler="$:/core/images/plugin-generic-$type$"/>
-$transclude>
-
-
-
-''<$view field="description"><$view field="title"/>$view>'' $disabledMessage$
-
-
-<$view field="title"/>
-
-
-<$view field="version"/>
-
-
-\end
-
\define plugin-table(type)
+<$set name="plugin-type" value="""$type$""">
<$set name="qualified-state" value=<>>
-<$list filter="[!has[draft.of]plugin-type[$type$]sort[description]]" emptyMessage=<>>
-<$set name="popup-state" value=<>>
-<$reveal type="nomatch" state=<> text="yes">
-<$link to={{!!title}} class="tc-plugin-info">
-<>
-$link>
-$reveal>
-<$reveal type="match" state=<> text="yes">
-<$link to={{!!title}} class="tc-plugin-info tc-plugin-info-disabled">
-< ">>
-$link>
-$reveal>
-<$reveal type="match" text="yes" state=<>>
-
-
-<$list filter="[all[current]] -[[$:/core]]">
-
-<$reveal type="nomatch" state=<> text="yes">
-<$button set=<> setTo="yes" tooltip={{$:/language/ControlPanel/Plugins/Disable/Hint}} aria-label={{$:/language/ControlPanel/Plugins/Disable/Caption}}>
-<>
-$button>
-$reveal>
-<$reveal type="match" state=<> text="yes">
-<$button set=<> setTo="no" tooltip={{$:/language/ControlPanel/Plugins/Enable/Hint}} aria-label={{$:/language/ControlPanel/Plugins/Enable/Caption}}>
-<>
-$button>
-$reveal>
-
-$list>
-<$reveal type="nomatch" text="" state="!!list">
-<$macrocall $name="tabs" state=<> tabsList={{!!list}} default="readme" template="$:/core/ui/PluginInfo"/>
-$reveal>
-<$reveal type="match" text="" state="!!list">
-No information provided
-$reveal>
-
-
-$reveal>
+<$list filter="[!has[draft.of]plugin-type[$type$]sort[description]]" emptyMessage=<> template="$:/core/ui/Components/plugin-info"/>
$set>
-$list>
$set>
\end
diff --git a/core/ui/ControlPanel/Settings/EditorToolbar.tid b/core/ui/ControlPanel/Settings/EditorToolbar.tid
new file mode 100644
index 000000000..aa142bf62
--- /dev/null
+++ b/core/ui/ControlPanel/Settings/EditorToolbar.tid
@@ -0,0 +1,9 @@
+title: $:/core/ui/ControlPanel/Settings/EditorToolbar
+tags: $:/tags/ControlPanel/Settings
+caption: {{$:/language/ControlPanel/Settings/EditorToolbar/Caption}}
+
+\define lingo-base() $:/language/ControlPanel/Settings/EditorToolbar/
+<>
+
+<$checkbox tiddler="$:/config/TextEditor/EnableToolbar" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/TextEditor/EnableToolbar"><>$link> $checkbox>
+
diff --git a/core/ui/ControlPanel/Settings/MissingLinks.tid b/core/ui/ControlPanel/Settings/MissingLinks.tid
new file mode 100644
index 000000000..4a7ba5f2e
--- /dev/null
+++ b/core/ui/ControlPanel/Settings/MissingLinks.tid
@@ -0,0 +1,9 @@
+title: $:/core/ui/ControlPanel/Settings/MissingLinks
+tags: $:/tags/ControlPanel/Settings
+caption: {{$:/language/ControlPanel/Settings/MissingLinks/Caption}}
+
+\define lingo-base() $:/language/ControlPanel/Settings/MissingLinks/
+<>
+
+<$checkbox tiddler="$:/config/MissingLinks" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/MissingLinks"><>$link> $checkbox>
+
diff --git a/core/ui/ControlPanel/Toolbars/EditorToolbar.tid b/core/ui/ControlPanel/Toolbars/EditorToolbar.tid
new file mode 100644
index 000000000..ce700fa98
--- /dev/null
+++ b/core/ui/ControlPanel/Toolbars/EditorToolbar.tid
@@ -0,0 +1,21 @@
+title: $:/core/ui/ControlPanel/Toolbars/EditorToolbar
+tags: $:/tags/ControlPanel/Toolbars
+caption: {{$:/language/ControlPanel/Toolbars/EditorToolbar/Caption}}
+
+\define lingo-base() $:/language/TiddlerInfo/
+
+\define config-title()
+$:/config/EditorToolbarButtons/Visibility/$(listItem)$
+\end
+
+\define toolbar-button()
+<$checkbox tiddler=<> field="text" checked="show" unchecked="hide" default="show"> <$transclude tiddler={{$(listItem)$!!icon}}/> <$transclude tiddler=<> field="caption"/> -- <$transclude tiddler=<> field="description"/> $checkbox>
+\end
+
+{{$:/language/ControlPanel/Toolbars/EditorToolbar/Hint}}
+
+<$list filter="[all[shadows+tiddlers]tag[$:/tags/EditorToolbar]!has[draft.of]]" variable="listItem">
+
+<>
+
+$list>
diff --git a/core/ui/EditTemplate.tid b/core/ui/EditTemplate.tid
index 10c364681..184172214 100644
--- a/core/ui/EditTemplate.tid
+++ b/core/ui/EditTemplate.tid
@@ -1,16 +1,26 @@
title: $:/core/ui/EditTemplate
+\define actions()
+<$action-sendmessage $message="tm-add-tag" $param={{$:/temp/NewTagName}}/>
+<$action-deletetiddler $tiddler="$:/temp/NewTagName"/>
+<$action-sendmessage $message="tm-add-field" $name={{$:/temp/newfieldname}} $value={{$:/temp/newfieldvalue}}/>
+<$action-deletetiddler $tiddler="$:/temp/newfieldname"/>
+<$action-deletetiddler $tiddler="$:/temp/newfieldvalue"/>
+<$action-sendmessage $message="tm-save-tiddler"/>
+\end
\define frame-classes()
tc-tiddler-frame tc-tiddler-edit-frame $(missingTiddlerClass)$ $(shadowTiddlerClass)$ $(systemTiddlerClass)$
\end
>>
+<$fieldmangler>
<$set name="storyTiddler" value=<>>
-<$keyboard key={{$:/config/shortcuts/cancel-edit-tiddler}} message="tm-cancel-tiddler">
-<$keyboard key={{$:/config/shortcuts/save-tiddler}} message="tm-save-tiddler">
+<$keyboard key="((cancel-edit-tiddler))" message="tm-cancel-tiddler">
+<$keyboard key="((save-tiddler))" actions=<>>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/EditTemplate]!has[draft.of]]" variable="listItem">
<$transclude tiddler=<>/>
$list>
$keyboard>
$keyboard>
$set>
+$fieldmangler>
diff --git a/core/ui/EditTemplate/Preview/output.tid b/core/ui/EditTemplate/Preview/output.tid
new file mode 100644
index 000000000..fa330b8c0
--- /dev/null
+++ b/core/ui/EditTemplate/Preview/output.tid
@@ -0,0 +1,9 @@
+title: $:/core/ui/EditTemplate/body/preview/output
+tags: $:/tags/EditPreview
+caption: {{$:/language/EditTemplate/Body/Preview/Type/Output}}
+
+<$set name="tv-tiddler-preview" value="yes">
+
+<$transclude />
+
+$set>
diff --git a/core/ui/EditTemplate/body-editor.tid b/core/ui/EditTemplate/body-editor.tid
new file mode 100644
index 000000000..3de3beec9
--- /dev/null
+++ b/core/ui/EditTemplate/body-editor.tid
@@ -0,0 +1,30 @@
+title: $:/core/ui/EditTemplate/body/editor
+
+<$edit
+
+ field="text"
+ class="tc-edit-texteditor"
+ placeholder={{$:/language/EditTemplate/Body/Placeholder}}
+
+><$set
+
+ name="targetTiddler"
+ value=<>
+
+><$list
+
+ filter="[all[shadows+tiddlers]tag[$:/tags/EditorToolbar]!has[draft.of]]"
+
+><$reveal
+
+ type="nomatch"
+ state=<>
+ text="hide"
+ class="tc-text-editor-toolbar-item-wrapper"
+
+><$transclude
+
+ tiddler="$:/core/ui/EditTemplate/body/toolbar/button"
+ mode="inline"
+
+/>$reveal>$list>$set>$edit>
diff --git a/core/ui/EditTemplate/body-toolbar-button.tid b/core/ui/EditTemplate/body-toolbar-button.tid
new file mode 100644
index 000000000..4b273c2f0
--- /dev/null
+++ b/core/ui/EditTemplate/body-toolbar-button.tid
@@ -0,0 +1,107 @@
+title: $:/core/ui/EditTemplate/body/toolbar/button
+
+\define toolbar-button-icon()
+<$list
+
+ filter="[all[current]!has[custom-icon]]"
+ variable="no-custom-icon"
+
+><$transclude
+
+ tiddler={{!!icon}}
+
+/>$list>
+\end
+
+\define toolbar-button-tooltip()
+{{!!description}}<$macrocall $name="displayshortcuts" $output="text/plain" shortcuts={{!!shortcuts}} prefix="` - [" separator="] [" suffix="]`"/>
+\end
+
+\define toolbar-button()
+<$list
+
+ filter={{!!condition}}
+ variable="list-condition"
+
+><$wikify
+
+ name="tooltip-text"
+ text=<>
+ mode="inline"
+ output="text"
+
+><$list
+
+ filter="[all[current]!has[dropdown]]"
+ variable="no-dropdown"
+
+><$button
+
+ class="tc-btn-invisible $(buttonClasses)$"
+ tooltip=<>
+
+><><$transclude
+
+ tiddler=<>
+ field="text"
+
+/>$button>$list><$list
+
+ filter="[all[current]has[dropdown]]"
+ variable="dropdown"
+
+><$set
+
+ name="dropdown-state"
+ value=<>
+
+><$button
+
+ popup=<>
+ class="tc-popup-keep tc-btn-invisible $(buttonClasses)$"
+ selectedClass="tc-selected"
+ tooltip=<>
+
+><><$transclude
+
+ tiddler=<>
+ field="text"
+
+/>$button><$reveal
+
+ state=<>
+ type="popup"
+ position="below"
+ animate="yes"
+ tag="span"
+
+><$transclude
+
+ tiddler={{!!dropdown}}
+ mode="block"
+
+/>$reveal>$set>$list>$wikify>$list>
+\end
+
+\define toolbar-button-outer()
+<$set
+
+ name="buttonClasses"
+ value={{!!button-classes}}
+
+><>$set>
+\end
+
+<>
\ No newline at end of file
diff --git a/core/ui/EditTemplate/body.tid b/core/ui/EditTemplate/body.tid
index 24932c16e..f6cb3469e 100644
--- a/core/ui/EditTemplate/body.tid
+++ b/core/ui/EditTemplate/body.tid
@@ -2,6 +2,9 @@ title: $:/core/ui/EditTemplate/body
tags: $:/tags/EditTemplate
\define lingo-base() $:/language/EditTemplate/Body/
+\define config-visibility-title()
+$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
+\end
<$list filter="[is[current]has[_canonical_uri]]">
diff --git a/core/ui/ControlPanel/Parsing.tid b/core/ui/ControlPanel/Parsing.tid
index de027f9c3..4ba0f0ae1 100644
--- a/core/ui/ControlPanel/Parsing.tid
+++ b/core/ui/ControlPanel/Parsing.tid
@@ -4,30 +4,30 @@ caption: {{$:/language/ControlPanel/Parsing/Caption}}
\define lingo-base() $:/language/ControlPanel/Parsing/
-\define parsing-inner(typeCap)
-
+