diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 1e644e1611..286a842bcb 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -21,7 +21,7 @@ body:
attributes:
label: To Reproduce
description: "Steps to reproduce the behavior:"
- value: |
+ placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -41,7 +41,7 @@ body:
attributes:
label: TiddlyWiki Configuration
description: please complete the following information
- value: |
+ placeholder: |
- Version [e.g. v5.1.24]
- Saving mechanism [e.g. Node.js, TiddlyDesktop, TiddlyHost etc]
- Plugins installed [e.g. Freelinks, TiddlyMap]
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 556b93919d..dca23b783f 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Discuss feature request
- url: https://github.com/Jermolene/TiddlyWiki5/discussions
+ url: https://github.com/TiddlyWiki/TiddlyWiki5/discussions
about: Open new discussion about new feature
- name: Talk.Tiddlywiki Forum
url: https://talk.tiddlywiki.org
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8daf2f468c..737d523ef7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,7 +5,7 @@ on:
- master
- tiddlywiki-com
env:
- NODE_VERSION: "12"
+ NODE_VERSION: "18"
jobs:
test:
runs-on: ubuntu-latest
@@ -14,7 +14,13 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: "${{ env.NODE_VERSION }}"
- - run: "./bin/test.sh"
+ - run: "./bin/ci-test.sh"
+ - uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: playwright-report
+ path: playwright-report/
+ retention-days: 30
build-prerelease:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
@@ -54,6 +60,7 @@ jobs:
TW5_BUILD_TIDDLYWIKI: "./node_modules/tiddlywiki/tiddlywiki.js"
TW5_BUILD_MAIN_EDITION: "./editions/tw5.com"
TW5_BUILD_OUTPUT: "./output"
+ TW5_BUILD_ARCHIVE: "./output"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml
new file mode 100644
index 0000000000..331727b71b
--- /dev/null
+++ b/.github/workflows/cla-check.yml
@@ -0,0 +1,30 @@
+name: Check CLA Signature
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ paths-ignore:
+ - 'licenses/cla-individual.md'
+jobs:
+ check_cla:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ if: ${{ (github.event.pull_request.user.login != github.repository_owner) }}
+ steps:
+ - run: |
+ if ! curl -s https://raw.githubusercontent.com/Jermolene/TiddlyWiki5/tiddlywiki-com/licenses/cla-individual.md | grep -io "@$USER,"; then
+ echo "CLA not signed"
+ gh pr comment "$NUMBER" -b "@$USER It appears that this is your first contribution to the project, welcome.
+
+ With apologies for the bureaucracy, please could you prepare a separate PR to the 'tiddlywiki-com' branch with your signature for the Contributor License Agreement (see [contributing.md](https://github.com/TiddlyWiki/TiddlyWiki5/blob/master/contributing.md))."
+ else
+ echo "CLA already signed"
+ gh pr comment "$NUMBER" -b "Confirmed: **$USER** has already signed the Contributor License Agreement (see [contributing.md](https://github.com/TiddlyWiki/TiddlyWiki5/blob/master/contributing.md))"
+ fi
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ NUMBER: ${{ github.event.pull_request.number }}
+ USER: ${{ github.actor }}
diff --git a/.github/workflows/cla-signed.yml b/.github/workflows/cla-signed.yml
new file mode 100644
index 0000000000..01d57d0147
--- /dev/null
+++ b/.github/workflows/cla-signed.yml
@@ -0,0 +1,70 @@
+name: CLA Signed
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - closed
+ paths:
+ - 'licenses/cla-individual.md'
+
+env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ NUMBER: ${{ github.event.pull_request.number }}
+ AUTHOR: ${{ github.event.pull_request.user.login }}
+
+jobs:
+ # check if PRs updating the CLA are targetting the tiddlywiki-com branch
+ check-signature-branch:
+ if: (github.event.pull_request.merged != true) && (github.event.pull_request.user.login != github.repository_owner)
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - run: |
+ if [[ "$BRANCH" != "tiddlywiki-com" ]]; then
+ echo "This CLA signature targets the wrong branch: $BRANCH"
+ gh pr comment "$NUMBER" -b "@$AUTHOR Signatures to the CLA must target the 'tiddlywiki-com' branch."
+ fi
+ env:
+ BRANCH: ${{ github.event.pull_request.base.ref }}
+
+ # leave a comment on each open PR by a given author when their signature is added to the CLA
+ cla-signed:
+ if: (github.event.pull_request.merged == true) && (github.event.pull_request.user.login != github.repository_owner)
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - name: List open PRs by user
+ id: list-prs
+ uses: actions/github-script@v6
+ with:
+ result-encoding: string
+ script: |
+ const owner = context.repo.owner,
+ repo = context.repo.repo,
+ author = context.payload.pull_request.user.login;
+
+ const { data: pullRequests } = await github.rest.pulls.list({
+ owner: owner,
+ repo: repo,
+ state: 'open',
+ sort: 'created',
+ direction: 'desc',
+ per_page: 100
+ });
+ const userPullRequests = pullRequests.filter(pr => pr.user.login === author),
+ prNumbers = userPullRequests.map(pr => pr.number).join(',');
+ console.log(`Open pull requests by ${author}:${prNumbers}`);
+ return prNumbers;
+
+ - name: Comment open PRs by the same author
+ run: |
+ prs=($(echo ${{ steps.list-prs.outputs.result }} | tr "," "\n"))
+
+ for number in "${prs[@]}"
+ do
+ gh pr comment "$number" -b "**$AUTHOR** has signed the Contributor License Agreement (see [contributing.md](https://github.com/TiddlyWiki/TiddlyWiki5/blob/master/contributing.md))"
+ done
diff --git a/.gitignore b/.gitignore
index ad7e8e07f6..412759161e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,11 @@
.DS_Store
.c9/
+.vs/
.vscode/
tmp/
output/
node_modules/
-
+/test-results/
+/playwright-report/
+/playwright/.cache/
+$__StoryList.tid
diff --git a/bin/build-site.sh b/bin/build-site.sh
index cd52673896..43ba7b9d7b 100755
--- a/bin/build-site.sh
+++ b/bin/build-site.sh
@@ -5,7 +5,7 @@
# Default to the current version number for building the plugin library
if [ -z "$TW5_BUILD_VERSION" ]; then
- TW5_BUILD_VERSION=v5.2.6
+ TW5_BUILD_VERSION=v5.3.5
fi
echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]"
@@ -84,10 +84,27 @@ echo -e -n "title: $:/build\ncommit: $TW5_BUILD_COMMIT\n\n$TW5_BUILD_DETAILS\n"
######################################################
#
-# Core distribution
+# Core distributions
#
######################################################
+# Conditionally build archive if $TW5_BUILD_ARCHIVE variable is set, otherwise do nothing
+#
+# /archive/Empty-TiddlyWiki-.html Empty archived version
+# /archive/TiddlyWiki-.html Full archived version
+
+if [ -n "$TW5_BUILD_ARCHIVE" ]; then
+
+node $TW5_BUILD_TIDDLYWIKI \
+ $TW5_BUILD_MAIN_EDITION \
+ --verbose \
+ --version \
+ --load $TW5_BUILD_OUTPUT/build.tid \
+ --output $TW5_BUILD_ARCHIVE \
+ --build archive \
+ || exit 1
+fi
+
# /index.html Main site
# /favicon.ico Favicon for main site
# /static.html Static rendering of default tiddlers
@@ -95,6 +112,7 @@ echo -e -n "title: $:/build\ncommit: $TW5_BUILD_COMMIT\n\n$TW5_BUILD_DETAILS\n"
# /static/* Static single tiddlers
# /static/static.css Static stylesheet
# /static/favicon.ico Favicon for static pages
+
node $TW5_BUILD_TIDDLYWIKI \
$TW5_BUILD_MAIN_EDITION \
--verbose \
@@ -104,13 +122,15 @@ node $TW5_BUILD_TIDDLYWIKI \
--build favicon static index \
|| exit 1
-# /empty.html Empty
-# /empty.hta For Internet Explorer
+# /empty.html Empty
+# /empty.hta For Internet Explorer
+# /empty-external-core.html External core empty
+# /tiddlywikicore-.js Core plugin javascript
node $TW5_BUILD_TIDDLYWIKI \
- $TW5_BUILD_MAIN_EDITION \
+ ./editions/empty \
--verbose \
--output $TW5_BUILD_OUTPUT \
- --build empty \
+ --build empty emptyexternalcore \
|| exit 1
@@ -136,6 +156,14 @@ node $TW5_BUILD_TIDDLYWIKI \
--build index favicon static \
|| exit 1
+# /tour.html tour edition
+node $TW5_BUILD_TIDDLYWIKI \
+ ./editions/tour \
+ --verbose \
+ --output $TW5_BUILD_OUTPUT \
+ --rendertiddler $:/core/save/all tour.html text/plain \
+ || exit 1
+
# /share.html Custom edition for sharing via the URL
node $TW5_BUILD_TIDDLYWIKI \
./editions/share \
@@ -351,6 +379,17 @@ node $TW5_BUILD_TIDDLYWIKI \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/highlight/empty.html text/plain \
|| exit 1
+# /plugins/tiddlywiki/geospatial/index.html Demo wiki with geospatial plugin
+# /plugins/tiddlywiki/geospatial/empty.html Empty wiki with geospatial plugin
+node $TW5_BUILD_TIDDLYWIKI \
+ ./editions/geospatialdemo \
+ --verbose \
+ --load $TW5_BUILD_OUTPUT/build.tid \
+ --output $TW5_BUILD_OUTPUT \
+ --rendertiddler $:/core/save/all plugins/tiddlywiki/geospatial/index.html text/plain \
+ --rendertiddler $:/core/save/empty plugins/tiddlywiki/geospatial/empty.html text/plain \
+ || exit 1
+
######################################################
#
# Language editions
@@ -359,14 +398,14 @@ node $TW5_BUILD_TIDDLYWIKI \
# Delete any existing static content
-rm $TW5_BUILD_OUTPUT/languages/de-AT/static/*
-rm $TW5_BUILD_OUTPUT/languages/de-DE/static/*
-rm $TW5_BUILD_OUTPUT/languages/es-ES/static/*
-rm $TW5_BUILD_OUTPUT/languages/fr-FR/static/*
-rm $TW5_BUILD_OUTPUT/languages/ja-JP/static/*
-rm $TW5_BUILD_OUTPUT/languages/ko-KR/static/*
-rm $TW5_BUILD_OUTPUT/languages/zh-Hans/static/*
-rm $TW5_BUILD_OUTPUT/languages/zh-Hant/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/de-AT/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/de-DE/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/es-ES/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/fr-FR/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/ja-JP/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/ko-KR/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/zh-Hans/static/*
+rm -rf $TW5_BUILD_OUTPUT/languages/zh-Hant/static/*
# /languages/de-AT/index.html Demo wiki with de-AT language
# /languages/de-AT/empty.html Empty wiki with de-AT language
diff --git a/bin/ci-pre-build.sh b/bin/ci-pre-build.sh
index 6f4b0ca781..a11b8e0c4f 100755
--- a/bin/ci-pre-build.sh
+++ b/bin/ci-pre-build.sh
@@ -7,4 +7,4 @@ npm --force install tiddlywiki || exit 1
# Pull existing GitHub pages content
-git clone --depth=1 --branch=master "https://github.com/Jermolene/jermolene.github.io.git" output
+git clone --depth=1 --branch=master "https://github.com/TiddlyWiki/tiddlywiki.com-gh-pages.git" output
diff --git a/bin/ci-push.sh b/bin/ci-push.sh
index dff297c801..fe8373785e 100755
--- a/bin/ci-push.sh
+++ b/bin/ci-push.sh
@@ -10,6 +10,6 @@ git config --global user.email "actions@github.com"
git config --global user.name "GitHub Actions"
git add -A .
git commit --message "GitHub build: $GITHUB_RUN_NUMBER of $TW5_BUILD_BRANCH ($(date +'%F %T %Z'))"
-git remote add deploy "https://$GH_TOKEN@github.com/Jermolene/jermolene.github.io.git" &>/dev/null
+git remote add deploy "https://$GH_TOKEN@github.com/TiddlyWiki/tiddlywiki.com-gh-pages.git" &>/dev/null
git push deploy master &>/dev/null
cd ..
diff --git a/bin/ci-test.sh b/bin/ci-test.sh
new file mode 100755
index 0000000000..ffcae66b26
--- /dev/null
+++ b/bin/ci-test.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# test TiddlyWiki5 for tiddlywiki.com
+
+node ./tiddlywiki.js \
+ ./editions/test \
+ --verbose \
+ --version \
+ --rendertiddler $:/core/save/all test.html text/plain \
+ --test \
+ || exit 1
+
+npm install playwright @playwright/test
+npx playwright install chromium firefox --with-deps
+
+npx playwright test
diff --git a/bin/clean.sh b/bin/clean.sh
index 522479edb7..5a56e1971a 100755
--- a/bin/clean.sh
+++ b/bin/clean.sh
@@ -2,4 +2,4 @@
# Remove any output files
-find . -regex "^./editions/[a-z0-9\.-]*/output/.*" -delete
+find . -regex "^./editions/.*/output/.*" -delete
diff --git a/bin/readme-bld.sh b/bin/readme-bld.sh
index 198c3abd00..e7c9df5647 100755
--- a/bin/readme-bld.sh
+++ b/bin/readme-bld.sh
@@ -15,3 +15,11 @@ node $TW5_BUILD_TIDDLYWIKI \
--output . \
--build readmes \
|| exit 1
+
+# tw.org readmes
+node $TW5_BUILD_TIDDLYWIKI \
+ editions/tw.org \
+ --verbose \
+ --output . \
+ --build readmes \
+ || exit 1
diff --git a/boot/boot.js b/boot/boot.js
index aeaa919feb..ea20c83fd4 100644
--- a/boot/boot.js
+++ b/boot/boot.js
@@ -142,15 +142,15 @@ $tw.utils.each = function(object,callback) {
var next,f,length;
if(object) {
if(Object.prototype.toString.call(object) == "[object Array]") {
- for (f=0, length=object.length; fThis community exists because TiddlyWiki is more useful when people share and work together.
This community is a beautiful but fragile thing: a collection of diverse people from all over the planet, united in their interest in the project, and their commitment to helping one another achieve and learn more.
We try to make the community as broad and welcoming as possible by remembering some basic principles of culture and behaviour.
These principles guide technical and non-technical decisions, and help contributors and leaders support our project and community.
We are optimistic and hopeful We aim to foster a learning environment that is collaborative and safe for everyone We recognise that the motivation for sharing and helping is usually for appreciation, and not financial gain, and so we take care to acknowledge and thank the people who enrich the community by sharing what they have created While we are united in our interest in TiddlyWiki, we differ in every other conceivable way. We choose to focus on what unites us, and avoid unnecessarily mixing contentious topics like religion and politics We treat each other with respect, and start with the assumption that others are acting in good faith We avoid discriminatory language We try to use our strength as a community to help others We avoid responding when angry or upset because we try to de-escalate conflict We make sure we critique ideas, not people When we disagree with others we do so graciously, and treat others with dignity and respoect We do not tolerate intolerance towards others We seek first to understand others, and then to be understood We have fun Our discussions are in English. It is not the first language of many people in the community, nor do we all share the same cultural background and reference points. So we take care to use language that is clear and unambigous, and avoid cultural references or jokes that will not be widely understood.
It is not acceptable to make jokes or other comments that discriminate by race, gender, sexuality, or other protected characteristic.
As an inclusive community, we are committed to making sure that TiddlyWiki is an accessible tool that understands the needs of people with disabilities.
\ No newline at end of file
diff --git a/contributing.md b/contributing.md
index 9dc10d0da2..549e862a62 100644
--- a/contributing.md
+++ b/contributing.md
@@ -1,3 +1,3 @@
-Here we focus on contributions via GitHub Pull Requests but there are many other ways that anyone can help the TiddlyWiki project, such as reporting bugs or helping to improve our documentation .
Rules for Pull Requests PRs must meet these minimum requirements before they can be considered for merging:
The material in the PR must be free of licensing restrictions. Which means that either:The author must hold the copyright in all of the material themselves The material must be licensed under a license compatible with TiddlyWiki 's BSD license The author must sign the Contributors License Agreement (see below) Each PR should only make a single feature change The title of the PR should be 50 characters or less The title of the PR should be capitalised, and should not end with a period The title of the PR should be written in the imperative mood. See below Adequate explanation in the body of the PR for the motivation and implementation of the change. Focus on the why and what , rather than the how PRs must be self-contained. Although they can link to material elsewhere, everything needed to understand the intention of the PR should be included Any visual changes introduced by the PR should be noted and illustrated with before/after screenshots Documentation as appropriate for end-users or developers Observe the coding style Read the developers documentation Please open a consultation issue prior to investing time in making a large PR Imperative Mood for PR Titles The "imperative mood" means written as if giving a command or instruction. See this post for more details, but the gist is that the title of the PR should make sense when used to complete the sentence "If applied, this commit will...". So for example, these are good PR titles:
If applied, this commit will update the contributing guidelines If applied, this commit will change css-escape-polyfill to a $tw.utils method If applied, this commit will make it easier to subclass the wikitext parser with a custom rule set These a poorly worded PR titles:
If applied, this commit will edit text widgets should use default text for missing fields If applied, this commit will signing the CLA If applied, this commit will don't crash if options.event is missing PR titles may also include a short prefix to indicate the subsystem to which they apply. For example:
Menu plugin: Include menu text in aerial rotator Commenting on Pull Requests One of the principles of open source is that many pairs of eyes on the code can improve quality. So, we welcome comments and critiques of pending PRs. Conventional Comments has some techniques to help make comments as constructive and actionable as possible. Notably, they recommend prefixing a comment with a label to clarify the intention:
praise Praises highlight something positive. Try to leave at least one of these comments per review. Do not leave false praise (which can actually be damaging). Do look for something to sincerely praise nitpick Nitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement suggestion Suggestions are specific requests to improve the subject under review. It is assumed that we all want to do what's best, so these comments are never dismissed as “mere suggestions”, but are taken seriously issue Issues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion question Questions are appropriate if you have a potential concern but are not quite sure if it's relevant or not. Asking the author for clarification or investigation can lead to a quick resolution thought Thoughts represent an idea that popped up from reviewing. These comments are non-blocking by nature, but they are extremely valuable and can lead to more focused initiatives and mentoring opportunities chore Chores are simple tasks that must be done before the subject can be “officially” accepted. Usually, these comments reference some common process. Try to leave a link to the process description so that the reader knows how to resolve the chore
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).
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 Ensure that the "branch" dropdown at the top left is set to tiddlywiki-com 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 bottomeg: 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".
+
Here we focus on contributions via GitHub Pull Requests but there are many other ways that anyone can help the TiddlyWiki project, such as reporting bugs or helping to improve our documentation .
Rules for Pull Requests PRs must meet these minimum requirements before they can be considered for merging:
The material in the PR must be free of licensing restrictions. Which means that either:The author must hold the copyright in all of the material themselves The material must be licensed under a license compatible with TiddlyWiki 's BSD license The author must sign the Contributors License Agreement (see below) Each PR should only make a single feature change The title of the PR should be 50 characters or less The title of the PR should be capitalised, and should not end with a period The title of the PR should be written in the imperative mood. See below Adequate explanation in the body of the PR for the motivation and implementation of the change. Focus on the why and what , rather than the how PRs must be self-contained. Although they can link to material elsewhere, everything needed to understand the intention of the PR should be included Any visual changes introduced by the PR should be noted and illustrated with before/after screenshots Documentation as appropriate for end-users or developers Observe the coding style Read the developers documentation Please open a consultation issue prior to investing time in making a large PR Imperative Mood for PR Titles The "imperative mood" means written as if giving a command or instruction. See this post for more details, but the gist is that the title of the PR should make sense when used to complete the sentence "If applied, this commit will...". So for example, these are good PR titles:
If applied, this commit will update the contributing guidelines If applied, this commit will change css-escape-polyfill to a $tw.utils method If applied, this commit will make it easier to subclass the wikitext parser with a custom rule set These a poorly worded PR titles:
If applied, this commit will edit text widgets should use default text for missing fields If applied, this commit will signing the CLA If applied, this commit will don't crash if options.event is missing PR titles may also include a short prefix to indicate the subsystem to which they apply. For example:
Menu plugin: Include menu text in aerial rotator Commenting on Pull Requests One of the principles of open source is that many pairs of eyes on the code can improve quality. So, we welcome comments and critiques of pending PRs. Conventional Comments has some techniques to help make comments as constructive and actionable as possible. Notably, they recommend prefixing a comment with a label to clarify the intention:
praise Praises highlight something positive. Try to leave at least one of these comments per review. Do not leave false praise (which can actually be damaging). Do look for something to sincerely praise nitpick Nitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement suggestion Suggestions are specific requests to improve the subject under review. It is assumed that we all want to do what's best, so these comments are never dismissed as “mere suggestions”, but are taken seriously issue Issues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion question Questions are appropriate if you have a potential concern but are not quite sure if it's relevant or not. Asking the author for clarification or investigation can lead to a quick resolution thought Thoughts represent an idea that popped up from reviewing. These comments are non-blocking by nature, but they are extremely valuable and can lead to more focused initiatives and mentoring opportunities chore Chores are simple tasks that must be done before the subject can be “officially” accepted. Usually, these comments reference some common process. Try to leave a link to the process description so that the reader knows how to resolve the chore
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).
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 Ensure that the "branch" dropdown at the top left is set to tiddlywiki-com 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 bottomeg: 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".
This file was automatically generated by TiddlyWiki5
\ No newline at end of file
diff --git a/core/acknowledgements.tid b/core/acknowledgements.tid
index e7acee1295..cb54e3d233 100644
--- a/core/acknowledgements.tid
+++ b/core/acknowledgements.tid
@@ -3,7 +3,7 @@ title: $:/Acknowledgements
TiddlyWiki incorporates code from these fine OpenSource projects:
* [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]]
-* [[The Jasmine JavaScript Test Framework|http://pivotal.github.io/jasmine/]]
+* [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]]
* [[Normalize.css by Nicolas Gallagher|http://necolas.github.io/normalize.css/]]
And media from these projects:
diff --git a/core/copyright.tid b/core/copyright.tid
index ce0d6b02fa..3f52380cc2 100644
--- a/core/copyright.tid
+++ b/core/copyright.tid
@@ -4,7 +4,7 @@ type: text/plain
TiddlyWiki created by Jeremy Ruston, (jeremy [at] jermolene [dot] com)
Copyright (c) 2004-2007, Jeremy Ruston
-Copyright (c) 2007-2023, UnaMesa Association
+Copyright (c) 2007-2024, UnaMesa Association
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/core/icon.tid b/core/icon.tid
index b327440dfe..21246d176c 100644
--- a/core/icon.tid
+++ b/core/icon.tid
@@ -1,4 +1,5 @@
title: $:/core/icon
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/add-comment.tid b/core/images/add-comment.tid
index 1782218060..a118506ed1 100644
--- a/core/images/add-comment.tid
+++ b/core/images/add-comment.tid
@@ -1,4 +1,5 @@
title: $:/core/images/add-comment
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-add-comment tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/advanced-search-button.tid b/core/images/advanced-search-button.tid
index 6fda3fe8b1..8e5699c4da 100755
--- a/core/images/advanced-search-button.tid
+++ b/core/images/advanced-search-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/advanced-search-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-advanced-search-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/auto-height.tid b/core/images/auto-height.tid
index 78f95418b6..76deecbad2 100755
--- a/core/images/auto-height.tid
+++ b/core/images/auto-height.tid
@@ -1,4 +1,5 @@
title: $:/core/images/auto-height
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-auto-height tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/blank.tid b/core/images/blank.tid
index 731b55a5a0..565ef6bec5 100755
--- a/core/images/blank.tid
+++ b/core/images/blank.tid
@@ -1,4 +1,5 @@
title: $:/core/images/blank
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-blank tc-image-button" viewBox="0 0 128 128"/>
\ No newline at end of file
diff --git a/core/images/bold.tid b/core/images/bold.tid
index 67a00f8948..d9259e4a19 100755
--- a/core/images/bold.tid
+++ b/core/images/bold.tid
@@ -1,4 +1,5 @@
title: $:/core/images/bold
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-bold tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/cancel-button.tid b/core/images/cancel-button.tid
index c55620b067..3bb982bc1c 100755
--- a/core/images/cancel-button.tid
+++ b/core/images/cancel-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/cancel-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-cancel-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/chevron-down.tid b/core/images/chevron-down.tid
index f1b363dfc3..df28c87a4e 100755
--- a/core/images/chevron-down.tid
+++ b/core/images/chevron-down.tid
@@ -1,4 +1,5 @@
title: $:/core/images/chevron-down
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-chevron-down tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/chevron-left.tid b/core/images/chevron-left.tid
index e4c69d95a9..b7f9ad7ae5 100755
--- a/core/images/chevron-left.tid
+++ b/core/images/chevron-left.tid
@@ -1,4 +1,5 @@
title: $:/core/images/chevron-left
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-chevron-left tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/chevron-right.tid b/core/images/chevron-right.tid
index 6ff5b6c0dc..9ec7c96fe7 100755
--- a/core/images/chevron-right.tid
+++ b/core/images/chevron-right.tid
@@ -1,4 +1,5 @@
title: $:/core/images/chevron-right
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-chevron-right tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/chevron-up.tid b/core/images/chevron-up.tid
index 9acbdec406..45366f2864 100755
--- a/core/images/chevron-up.tid
+++ b/core/images/chevron-up.tid
@@ -1,4 +1,5 @@
title: $:/core/images/chevron-up
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-chevron-up tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/clone-button.tid b/core/images/clone-button.tid
index 9ff4903ad5..cc0ed75951 100755
--- a/core/images/clone-button.tid
+++ b/core/images/clone-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/clone-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-clone-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/close-all-button.tid b/core/images/close-all-button.tid
index 3334c5dbc3..02d25fa6f0 100755
--- a/core/images/close-all-button.tid
+++ b/core/images/close-all-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/close-all-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-close-all-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/close-button.tid b/core/images/close-button.tid
index c462c9bac7..a05669ccde 100755
--- a/core/images/close-button.tid
+++ b/core/images/close-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/close-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-close-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/close-others-button.tid b/core/images/close-others-button.tid
index 1cd54d797b..6eb7790181 100755
--- a/core/images/close-others-button.tid
+++ b/core/images/close-others-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/close-others-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-close-others-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/copy-clipboard.tid b/core/images/copy-clipboard.tid
index e4e55b6e98..d6514ad7c3 100644
--- a/core/images/copy-clipboard.tid
+++ b/core/images/copy-clipboard.tid
@@ -1,4 +1,5 @@
title: $:/core/images/copy-clipboard
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-copy-clipboard tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/delete-button.tid b/core/images/delete-button.tid
index e8c9b61087..99f83b3bad 100755
--- a/core/images/delete-button.tid
+++ b/core/images/delete-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/delete-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-delete-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/done-button.tid b/core/images/done-button.tid
index 265528d068..7672b7d278 100755
--- a/core/images/done-button.tid
+++ b/core/images/done-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/done-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-done-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/down-arrow.tid b/core/images/down-arrow.tid
index 4cac65a004..7f1273b2c5 100755
--- a/core/images/down-arrow.tid
+++ b/core/images/down-arrow.tid
@@ -1,4 +1,5 @@
title: $:/core/images/down-arrow
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-down-arrow tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/download-button.tid b/core/images/download-button.tid
index e3a5496391..8b06356ab5 100755
--- a/core/images/download-button.tid
+++ b/core/images/download-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/download-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-download-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/edit-button.tid b/core/images/edit-button.tid
index 190dffc418..e4644ef7c8 100755
--- a/core/images/edit-button.tid
+++ b/core/images/edit-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/edit-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-edit-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/erase.tid b/core/images/erase.tid
index 22b31e7cc9..6cc2011a94 100755
--- a/core/images/erase.tid
+++ b/core/images/erase.tid
@@ -1,4 +1,5 @@
title: $:/core/images/erase
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-erase tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/excise.tid b/core/images/excise.tid
index e6c2c9404a..fa351baffe 100755
--- a/core/images/excise.tid
+++ b/core/images/excise.tid
@@ -1,4 +1,5 @@
title: $:/core/images/excise
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-excise tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/export-button.tid b/core/images/export-button.tid
index eb3284c805..b9f0dca7fc 100755
--- a/core/images/export-button.tid
+++ b/core/images/export-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/export-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-export-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/file.tid b/core/images/file.tid
index 67ebc22aca..682796fbfc 100755
--- a/core/images/file.tid
+++ b/core/images/file.tid
@@ -1,4 +1,5 @@
title: $:/core/images/file
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-file tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/fixed-height.tid b/core/images/fixed-height.tid
index c151622672..b2b5083b92 100755
--- a/core/images/fixed-height.tid
+++ b/core/images/fixed-height.tid
@@ -1,4 +1,5 @@
title: $:/core/images/fixed-height
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-fixed-height tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/fold-all-button.tid b/core/images/fold-all-button.tid
index edff8b22b5..260efe9b80 100755
--- a/core/images/fold-all-button.tid
+++ b/core/images/fold-all-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/fold-all-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-fold-all tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/fold-button.tid b/core/images/fold-button.tid
index f0b2b474df..cfc2291a5a 100755
--- a/core/images/fold-button.tid
+++ b/core/images/fold-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/fold-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-fold tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/fold-others-button.tid b/core/images/fold-others-button.tid
index 5cb086f85d..a5457c4615 100755
--- a/core/images/fold-others-button.tid
+++ b/core/images/fold-others-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/fold-others-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-fold-others tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/folder.tid b/core/images/folder.tid
index 4b89418ff0..8c5d7efb18 100755
--- a/core/images/folder.tid
+++ b/core/images/folder.tid
@@ -1,4 +1,5 @@
title: $:/core/images/folder
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-folder tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/full-screen-button.tid b/core/images/full-screen-button.tid
index 8572ff6469..7e20183b78 100755
--- a/core/images/full-screen-button.tid
+++ b/core/images/full-screen-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/full-screen-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-full-screen-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/github.tid b/core/images/github.tid
index 6389f5cdce..b82c54e72b 100755
--- a/core/images/github.tid
+++ b/core/images/github.tid
@@ -1,4 +1,5 @@
title: $:/core/images/github
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-github tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/gitter.tid b/core/images/gitter.tid
index 876fc3da1e..4af54ddba6 100644
--- a/core/images/gitter.tid
+++ b/core/images/gitter.tid
@@ -1,4 +1,5 @@
title: $:/core/images/gitter
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-gitter tc-image-button" viewBox="0 0 18 25">
\ No newline at end of file
diff --git a/core/images/globe.tid b/core/images/globe.tid
index 9448ed7a45..0e2c56b40a 100755
--- a/core/images/globe.tid
+++ b/core/images/globe.tid
@@ -1,4 +1,5 @@
title: $:/core/images/globe
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-globe tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/heading-1.tid b/core/images/heading-1.tid
index f8a98123bb..d160284c90 100755
--- a/core/images/heading-1.tid
+++ b/core/images/heading-1.tid
@@ -1,4 +1,5 @@
title: $:/core/images/heading-1
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-heading-1 tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/heading-2.tid b/core/images/heading-2.tid
index ef0022cc65..8daad0f10f 100755
--- a/core/images/heading-2.tid
+++ b/core/images/heading-2.tid
@@ -1,4 +1,5 @@
title: $:/core/images/heading-2
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-heading-2 tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/heading-3.tid b/core/images/heading-3.tid
index d706d067b1..8a489e7998 100755
--- a/core/images/heading-3.tid
+++ b/core/images/heading-3.tid
@@ -1,4 +1,5 @@
title: $:/core/images/heading-3
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-heading-3 tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/heading-4.tid b/core/images/heading-4.tid
index 16d4440dbf..5aee3f3f8a 100755
--- a/core/images/heading-4.tid
+++ b/core/images/heading-4.tid
@@ -1,4 +1,5 @@
title: $:/core/images/heading-4
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-heading-4 tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/heading-5.tid b/core/images/heading-5.tid
index 8f34b70582..c9a54a335e 100755
--- a/core/images/heading-5.tid
+++ b/core/images/heading-5.tid
@@ -1,4 +1,5 @@
title: $:/core/images/heading-5
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-heading-5 tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/heading-6.tid b/core/images/heading-6.tid
index b348c70afe..1034e71164 100755
--- a/core/images/heading-6.tid
+++ b/core/images/heading-6.tid
@@ -1,4 +1,5 @@
title: $:/core/images/heading-6
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-heading-6 tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/help.tid b/core/images/help.tid
index 07167ae936..5c0cf13abd 100755
--- a/core/images/help.tid
+++ b/core/images/help.tid
@@ -1,4 +1,5 @@
title: $:/core/images/help
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-help tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/home-button.tid b/core/images/home-button.tid
index 10c00626a4..952ab9a602 100755
--- a/core/images/home-button.tid
+++ b/core/images/home-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/home-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-home-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/import-button.tid b/core/images/import-button.tid
index 6850513efc..a42c416e58 100755
--- a/core/images/import-button.tid
+++ b/core/images/import-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/import-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-import-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/info-button.tid b/core/images/info-button.tid
index 2679a135a8..e8f8e98c41 100755
--- a/core/images/info-button.tid
+++ b/core/images/info-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/info-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-info-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/input-button.tid b/core/images/input-button.tid
new file mode 100644
index 0000000000..731a1e0cdb
--- /dev/null
+++ b/core/images/input-button.tid
@@ -0,0 +1,5 @@
+title: $:/core/images/input-button
+tags: $:/tags/Image
+
+\parameters (size:"22pt")
+> height=<> class="tc-image-input-button tc-image-button" viewBox="0 0 22 22">
\ No newline at end of file
diff --git a/core/images/italic.tid b/core/images/italic.tid
index 06aada5892..766d3707aa 100755
--- a/core/images/italic.tid
+++ b/core/images/italic.tid
@@ -1,4 +1,5 @@
title: $:/core/images/italic
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-italic tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/layout-button.tid b/core/images/layout-button.tid
index 19371cde18..a859edbc54 100755
--- a/core/images/layout-button.tid
+++ b/core/images/layout-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/layout-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-layout-button tc-image-button" viewBox="0 0 24 24" stroke-width="1" stroke="none">
\ No newline at end of file
diff --git a/core/images/left-arrow.tid b/core/images/left-arrow.tid
index a418581cdf..b64fc2ab6b 100755
--- a/core/images/left-arrow.tid
+++ b/core/images/left-arrow.tid
@@ -3,4 +3,5 @@ modified: 20150315235324760
tags: $:/tags/Image
title: $:/core/images/left-arrow
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-left-arrow tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/line-width.tid b/core/images/line-width.tid
index f77763ce63..9cecc33cb2 100755
--- a/core/images/line-width.tid
+++ b/core/images/line-width.tid
@@ -1,4 +1,5 @@
title: $:/core/images/line-width
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-line-width tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/link.tid b/core/images/link.tid
index 395307c421..c1d134f7e2 100644
--- a/core/images/link.tid
+++ b/core/images/link.tid
@@ -1,4 +1,5 @@
title: $:/core/images/link
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-link tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/linkify.tid b/core/images/linkify.tid
index 40acdc19a6..d616c2ac99 100644
--- a/core/images/linkify.tid
+++ b/core/images/linkify.tid
@@ -1,4 +1,5 @@
title: $:/core/images/linkify
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-linkify-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/list-bullet.tid b/core/images/list-bullet.tid
index 322dd4ae6c..065d96c4f2 100755
--- a/core/images/list-bullet.tid
+++ b/core/images/list-bullet.tid
@@ -1,4 +1,5 @@
title: $:/core/images/list-bullet
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-list-bullet tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/list-number.tid b/core/images/list-number.tid
index 1619178230..b70d4db154 100755
--- a/core/images/list-number.tid
+++ b/core/images/list-number.tid
@@ -1,4 +1,5 @@
title: $:/core/images/list-number
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-list-number tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/list.tid b/core/images/list.tid
index 22b784bfe5..793b479573 100644
--- a/core/images/list.tid
+++ b/core/images/list.tid
@@ -1,4 +1,5 @@
title: $:/core/images/list
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-list tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/locked-padlock.tid b/core/images/locked-padlock.tid
index 14d95560c9..f6722cddd9 100755
--- a/core/images/locked-padlock.tid
+++ b/core/images/locked-padlock.tid
@@ -1,4 +1,5 @@
title: $:/core/images/locked-padlock
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-locked-padlock tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/mail.tid b/core/images/mail.tid
index cf0f4eab7a..9d46d6effa 100755
--- a/core/images/mail.tid
+++ b/core/images/mail.tid
@@ -1,4 +1,5 @@
title: $:/core/images/mail
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-mail tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/mastodon.tid b/core/images/mastodon.tid
index b27c751fde..a6b41d355a 100644
--- a/core/images/mastodon.tid
+++ b/core/images/mastodon.tid
@@ -1,6 +1,7 @@
title: $:/core/images/mastodon
tags: $:/tags/Image
-
+\parameters (size:"22pt")
+> height=<> class="tc-image-mastodon tc-image-button" viewBox="0 0 128 128">
diff --git a/core/images/menu-button.tid b/core/images/menu-button.tid
index 6d1872e4c2..958721d312 100755
--- a/core/images/menu-button.tid
+++ b/core/images/menu-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/menu-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-menu-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/minus-button.tid b/core/images/minus-button.tid
index 7132ed3e95..40ee75a8e9 100644
--- a/core/images/minus-button.tid
+++ b/core/images/minus-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/minus-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-minus-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/mono-block.tid b/core/images/mono-block.tid
index f8695302bd..bf58400aa2 100755
--- a/core/images/mono-block.tid
+++ b/core/images/mono-block.tid
@@ -1,4 +1,5 @@
title: $:/core/images/mono-block
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-mono-block tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/mono-line.tid b/core/images/mono-line.tid
index 09cfe513ee..1ab73b8297 100755
--- a/core/images/mono-line.tid
+++ b/core/images/mono-line.tid
@@ -1,4 +1,5 @@
title: $:/core/images/mono-line
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-mono-line tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/network-activity.tid b/core/images/network-activity.tid
new file mode 100644
index 0000000000..2efdfd4d41
--- /dev/null
+++ b/core/images/network-activity.tid
@@ -0,0 +1,11 @@
+title: $:/core/images/network-activity
+tags: $:/tags/Image
+
+
+<$list filter="[{$:/state/http-requests}match[0]]" variable="ignore">
+
+$list>
+<$list filter="[{$:/state/http-requests}!match[0]]" variable="ignore">
+
+$list>
+
\ No newline at end of file
diff --git a/core/images/new-button.tid b/core/images/new-button.tid
index 6e592ada93..d4cfd34f63 100755
--- a/core/images/new-button.tid
+++ b/core/images/new-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/new-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-new-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/new-here-button.tid b/core/images/new-here-button.tid
index ab0f7a6f3d..8e304f5e70 100755
--- a/core/images/new-here-button.tid
+++ b/core/images/new-here-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/new-here-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-new-here-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/new-image-button.tid b/core/images/new-image-button.tid
index 16b63c3c37..53b10d4812 100755
--- a/core/images/new-image-button.tid
+++ b/core/images/new-image-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/new-image-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-new-image-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/new-journal-button.tid b/core/images/new-journal-button.tid
index fb67c8007d..5b793deb5a 100755
--- a/core/images/new-journal-button.tid
+++ b/core/images/new-journal-button.tid
@@ -1,4 +1,4 @@
title: $:/core/images/new-journal-button
tags: $:/tags/Image
-<>
\ No newline at end of file
+<$parameters size="22pt" day=<>>> height=<> class="tc-image-new-journal-button tc-image-button" viewBox="0 0 128 128"><$text text=<>/> $parameters>
\ No newline at end of file
diff --git a/core/images/opacity.tid b/core/images/opacity.tid
index e9a29aea28..0211644f9d 100755
--- a/core/images/opacity.tid
+++ b/core/images/opacity.tid
@@ -1,4 +1,5 @@
title: $:/core/images/opacity
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-opacity tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/open-window.tid b/core/images/open-window.tid
index 14b556484d..d918b1fc8c 100755
--- a/core/images/open-window.tid
+++ b/core/images/open-window.tid
@@ -1,4 +1,5 @@
title: $:/core/images/open-window
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-open-window tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/options-button.tid b/core/images/options-button.tid
index bd0ffcb1ac..18fbf8b007 100755
--- a/core/images/options-button.tid
+++ b/core/images/options-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/options-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-options-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/paint.tid b/core/images/paint.tid
index bb536b53a3..660fda2bf4 100755
--- a/core/images/paint.tid
+++ b/core/images/paint.tid
@@ -1,4 +1,5 @@
title: $:/core/images/paint
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-paint tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/palette.tid b/core/images/palette.tid
index d605fd853e..1c79035498 100755
--- a/core/images/palette.tid
+++ b/core/images/palette.tid
@@ -1,4 +1,5 @@
title: $:/core/images/palette
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-palette tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/permalink-button.tid b/core/images/permalink-button.tid
index e8fd0aecda..f1cf38b414 100755
--- a/core/images/permalink-button.tid
+++ b/core/images/permalink-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/permalink-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-permalink-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/permaview-button.tid b/core/images/permaview-button.tid
index b2ad9a4089..82e5334757 100755
--- a/core/images/permaview-button.tid
+++ b/core/images/permaview-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/permaview-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-permaview-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/picture.tid b/core/images/picture.tid
index 7d035e2fc8..2af427e2e6 100755
--- a/core/images/picture.tid
+++ b/core/images/picture.tid
@@ -1,4 +1,5 @@
title: $:/core/images/picture
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-picture tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/plugin-generic-language.tid b/core/images/plugin-generic-language.tid
index 5c777d98d2..d663d15636 100755
--- a/core/images/plugin-generic-language.tid
+++ b/core/images/plugin-generic-language.tid
@@ -1,4 +1,5 @@
title: $:/core/images/plugin-generic-language
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> viewBox="0 0 128 128" class="tc-image-plugin-generic-language tc-image-button">
\ No newline at end of file
diff --git a/core/images/plugin-generic-plugin.tid b/core/images/plugin-generic-plugin.tid
index ab2e6670e9..06073dd7cb 100755
--- a/core/images/plugin-generic-plugin.tid
+++ b/core/images/plugin-generic-plugin.tid
@@ -1,4 +1,5 @@
title: $:/core/images/plugin-generic-plugin
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> viewBox="0 0 128 128" class="tc-image-plugin-generic-plugin tc-image-button">
\ No newline at end of file
diff --git a/core/images/plugin-generic-theme.tid b/core/images/plugin-generic-theme.tid
index 9ae3cd779a..ab899b3e49 100755
--- a/core/images/plugin-generic-theme.tid
+++ b/core/images/plugin-generic-theme.tid
@@ -1,4 +1,5 @@
title: $:/core/images/plugin-generic-theme
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> viewBox="0 0 128 128" class="tc-image-plugin-generic-theme tc-image-button">
\ No newline at end of file
diff --git a/core/images/plus-button.tid b/core/images/plus-button.tid
index b001f3e2f0..c9a696d43b 100644
--- a/core/images/plus-button.tid
+++ b/core/images/plus-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/plus-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-plus-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/preview-closed.tid b/core/images/preview-closed.tid
index 5986d8966b..cf17730d5f 100755
--- a/core/images/preview-closed.tid
+++ b/core/images/preview-closed.tid
@@ -1,4 +1,5 @@
title: $:/core/images/preview-closed
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-preview-closed tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/preview-open.tid b/core/images/preview-open.tid
index 4664990b48..cb30bf474e 100755
--- a/core/images/preview-open.tid
+++ b/core/images/preview-open.tid
@@ -1,4 +1,5 @@
title: $:/core/images/preview-open
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-preview-open tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/print-button.tid b/core/images/print-button.tid
index 55b33c8965..12bffd41d2 100644
--- a/core/images/print-button.tid
+++ b/core/images/print-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/print-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-print-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/quote.tid b/core/images/quote.tid
index 7134306a61..0c4fcf25ad 100755
--- a/core/images/quote.tid
+++ b/core/images/quote.tid
@@ -1,4 +1,5 @@
title: $:/core/images/quote
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-quote tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/refresh-button.tid b/core/images/refresh-button.tid
index 2422b0679e..f8e3fc69ef 100755
--- a/core/images/refresh-button.tid
+++ b/core/images/refresh-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/refresh-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-refresh-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/right-arrow.tid b/core/images/right-arrow.tid
index 42e7dea563..64f839b552 100755
--- a/core/images/right-arrow.tid
+++ b/core/images/right-arrow.tid
@@ -1,4 +1,5 @@
title: $:/core/images/right-arrow
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-right-arrow tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/rotate-left.tid b/core/images/rotate-left.tid
index 188d3b45c7..da6034b19b 100644
--- a/core/images/rotate-left.tid
+++ b/core/images/rotate-left.tid
@@ -1,4 +1,5 @@
title: $:/core/images/rotate-left
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-rotate-left tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/save-button-dynamic.tid b/core/images/save-button-dynamic.tid
new file mode 100644
index 0000000000..7a351d6179
--- /dev/null
+++ b/core/images/save-button-dynamic.tid
@@ -0,0 +1,13 @@
+title: $:/core/images/save-button-dynamic
+tags: $:/tags/Image
+
+\parameters (size:"22pt")
+> height=<> class="tc-image-save-button-dynamic tc-image-button" viewBox="0 0 128 128">
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/images/save-button.tid b/core/images/save-button.tid
index a667566161..912ad248cd 100755
--- a/core/images/save-button.tid
+++ b/core/images/save-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/save-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-save-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/size.tid b/core/images/size.tid
index db84ecf9bb..ea9aa10948 100755
--- a/core/images/size.tid
+++ b/core/images/size.tid
@@ -1,4 +1,5 @@
title: $:/core/images/size
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-size tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/spiral.tid b/core/images/spiral.tid
index ca4684cab6..f3a5271acd 100755
--- a/core/images/spiral.tid
+++ b/core/images/spiral.tid
@@ -1,4 +1,5 @@
title: $:/core/images/spiral
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-spiral tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/stamp.tid b/core/images/stamp.tid
index ba385aaae8..8511a457f3 100755
--- a/core/images/stamp.tid
+++ b/core/images/stamp.tid
@@ -1,4 +1,5 @@
title: $:/core/images/stamp
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-stamp tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/standard-layout.tid b/core/images/standard-layout.tid
new file mode 100644
index 0000000000..1b83375c95
--- /dev/null
+++ b/core/images/standard-layout.tid
@@ -0,0 +1,7 @@
+title: $:/core/images/standard-layout
+tags: $:/tags/Image
+
+\parameters (size:"22pt")
+> height=<> class="tc-image-standard-layout tc-image-button" viewBox="0 0 128 128">
+
+
\ No newline at end of file
diff --git a/core/images/star-filled.tid b/core/images/star-filled.tid
index 10b8f1c3de..262448d51e 100755
--- a/core/images/star-filled.tid
+++ b/core/images/star-filled.tid
@@ -1,4 +1,5 @@
title: $:/core/images/star-filled
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-star-filled tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/storyview-classic.tid b/core/images/storyview-classic.tid
index 86872817b0..457434bc9d 100755
--- a/core/images/storyview-classic.tid
+++ b/core/images/storyview-classic.tid
@@ -1,4 +1,5 @@
title: $:/core/images/storyview-classic
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-storyview-classic tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/storyview-pop.tid b/core/images/storyview-pop.tid
index a610c89d5a..a4a9fb9ee3 100755
--- a/core/images/storyview-pop.tid
+++ b/core/images/storyview-pop.tid
@@ -1,4 +1,5 @@
title: $:/core/images/storyview-pop
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-storyview-pop tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/storyview-zoomin.tid b/core/images/storyview-zoomin.tid
index 61b7ff2735..725f36e711 100755
--- a/core/images/storyview-zoomin.tid
+++ b/core/images/storyview-zoomin.tid
@@ -1,4 +1,5 @@
title: $:/core/images/storyview-zoomin
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-storyview-zoomin tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/strikethrough.tid b/core/images/strikethrough.tid
index 1f7a1c202d..de4eefeec9 100755
--- a/core/images/strikethrough.tid
+++ b/core/images/strikethrough.tid
@@ -1,4 +1,5 @@
title: $:/core/images/strikethrough
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-strikethrough tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/subscript.tid b/core/images/subscript.tid
index 96548bdb51..76ec353995 100755
--- a/core/images/subscript.tid
+++ b/core/images/subscript.tid
@@ -1,4 +1,5 @@
title: $:/core/images/subscript
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-subscript tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/superscript.tid b/core/images/superscript.tid
index 149e448936..ab0d0d1b2d 100755
--- a/core/images/superscript.tid
+++ b/core/images/superscript.tid
@@ -1,4 +1,5 @@
title: $:/core/images/superscript
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-superscript tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/tag-button.tid b/core/images/tag-button.tid
index 9f6cad8b46..ab407f7809 100755
--- a/core/images/tag-button.tid
+++ b/core/images/tag-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/tag-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-tag-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/theme-button.tid b/core/images/theme-button.tid
index d80a0e82ae..6b7aab2af5 100755
--- a/core/images/theme-button.tid
+++ b/core/images/theme-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/theme-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-theme-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/timestamp-off.tid b/core/images/timestamp-off.tid
index 0d20f28d6b..33e018f3a8 100644
--- a/core/images/timestamp-off.tid
+++ b/core/images/timestamp-off.tid
@@ -1,4 +1,5 @@
title: $:/core/images/timestamp-off
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-timestamp-off tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/timestamp-on.tid b/core/images/timestamp-on.tid
index 3a16df7eb6..cad04aada2 100644
--- a/core/images/timestamp-on.tid
+++ b/core/images/timestamp-on.tid
@@ -1,4 +1,5 @@
title: $:/core/images/timestamp-on
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-timestamp-on tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/tip.tid b/core/images/tip.tid
index f8109ada42..0259183066 100755
--- a/core/images/tip.tid
+++ b/core/images/tip.tid
@@ -1,4 +1,5 @@
title: $:/core/images/tip
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-tip tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/transcludify.tid b/core/images/transcludify.tid
index 0579feb105..5102d13707 100644
--- a/core/images/transcludify.tid
+++ b/core/images/transcludify.tid
@@ -1,4 +1,5 @@
title: $:/core/images/transcludify
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-transcludify-button tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/twitter.tid b/core/images/twitter.tid
index 28cfccdbd0..e3ae13dc1c 100755
--- a/core/images/twitter.tid
+++ b/core/images/twitter.tid
@@ -1,4 +1,5 @@
title: $:/core/images/twitter
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-twitter tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/underline.tid b/core/images/underline.tid
index 768d2a1998..341b27cfa8 100755
--- a/core/images/underline.tid
+++ b/core/images/underline.tid
@@ -1,4 +1,5 @@
title: $:/core/images/underline
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-underline tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/unfold-all-button.tid b/core/images/unfold-all-button.tid
index e496bdd89a..e44000b188 100755
--- a/core/images/unfold-all-button.tid
+++ b/core/images/unfold-all-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/unfold-all-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-unfold-all tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/unfold-button.tid b/core/images/unfold-button.tid
index cfad70570b..57f53fd6f8 100755
--- a/core/images/unfold-button.tid
+++ b/core/images/unfold-button.tid
@@ -1,4 +1,5 @@
title: $:/core/images/unfold-button
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-unfold tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/unlocked-padlock.tid b/core/images/unlocked-padlock.tid
index c5367c0857..941f7bec57 100755
--- a/core/images/unlocked-padlock.tid
+++ b/core/images/unlocked-padlock.tid
@@ -1,4 +1,5 @@
title: $:/core/images/unlocked-padlock
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-unlocked-padlock tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/up-arrow.tid b/core/images/up-arrow.tid
index a1bd132bf6..4d157352b4 100755
--- a/core/images/up-arrow.tid
+++ b/core/images/up-arrow.tid
@@ -3,4 +3,5 @@ modified: 20150316000831867
tags: $:/tags/Image
title: $:/core/images/up-arrow
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-up-arrow tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/video.tid b/core/images/video.tid
index 3bf0bb2597..b4fef628d8 100755
--- a/core/images/video.tid
+++ b/core/images/video.tid
@@ -1,4 +1,5 @@
title: $:/core/images/video
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-video tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/images/warning.tid b/core/images/warning.tid
index 53bff59ec6..1822da89fe 100755
--- a/core/images/warning.tid
+++ b/core/images/warning.tid
@@ -1,4 +1,5 @@
title: $:/core/images/warning
tags: $:/tags/Image
-
\ No newline at end of file
+\parameters (size:"22pt")
+> height=<> class="tc-image-warning tc-image-button" viewBox="0 0 128 128">
\ No newline at end of file
diff --git a/core/language/en-GB/Buttons.multids b/core/language/en-GB/Buttons.multids
index 85a71ac080..15273d18ea 100644
--- a/core/language/en-GB/Buttons.multids
+++ b/core/language/en-GB/Buttons.multids
@@ -28,6 +28,7 @@ Encryption/ClearPassword/Caption: clear password
Encryption/ClearPassword/Hint: Clear the password and save this wiki without encryption
Encryption/SetPassword/Caption: set password
Encryption/SetPassword/Hint: Set a password for saving this wiki with encryption
+EmergencyDownload/Caption: download tiddlers as json
ExportPage/Caption: export all
ExportPage/Hint: Export all tiddlers
ExportTiddler/Caption: export tiddler
@@ -67,6 +68,8 @@ More/Caption: more
More/Hint: More actions
NewHere/Caption: new here
NewHere/Hint: Create a new tiddler tagged with this one
+NetworkActivity/Caption: network activity
+NetworkActivity/Hint: Cancel all network activity
NewJournal/Caption: new journal
NewJournal/Hint: Create a new journal tiddler
NewJournalHere/Caption: new journal here
@@ -101,6 +104,8 @@ ShowSideBar/Caption: show sidebar
ShowSideBar/Hint: Show sidebar
TagManager/Caption: tag manager
TagManager/Hint: Open tag manager
+TestCaseImport/Caption: import tiddlers
+TestCaseImport/Hint: Import tiddlers
Timestamp/Caption: timestamps
Timestamp/Hint: Choose whether modifications update timestamps
Timestamp/On/Caption: timestamps are on
diff --git a/core/language/en-GB/ControlPanel.multids b/core/language/en-GB/ControlPanel.multids
index d8321edbf3..93cfc3c106 100644
--- a/core/language/en-GB/ControlPanel.multids
+++ b/core/language/en-GB/ControlPanel.multids
@@ -206,6 +206,12 @@ Stylesheets/Caption: Stylesheets
Stylesheets/Expand/Caption: Expand All
Stylesheets/Hint: This is the rendered CSS of the current stylesheet tiddlers tagged with <>
Stylesheets/Restore/Caption: Restore
+TestCases/Caption: Test Cases
+TestCases/Hint: Test cases are self contained examples for testing and learning
+TestCases/All/Caption: All Test Cases
+TestCases/All/Hint: All Test Cases
+TestCases/Failed/Caption: Failed Test Cases
+TestCases/Failed/Hint: Only Failed Test Cases
Theme/Caption: Theme
Theme/Prompt: Current theme:
TiddlerFields/Caption: Tiddler Fields
diff --git a/core/language/en-GB/Docs/ModuleTypes.multids b/core/language/en-GB/Docs/ModuleTypes.multids
index 9a03d8887b..5d5902c76d 100644
--- a/core/language/en-GB/Docs/ModuleTypes.multids
+++ b/core/language/en-GB/Docs/ModuleTypes.multids
@@ -9,7 +9,7 @@ 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.
+isfilteroperator: Parameters for the ''is'' filter operator.
library: Generic module type for general purpose JavaScript modules.
macro: JavaScript macro definitions.
parser: Parsers for different content types.
diff --git a/core/language/en-GB/Docs/PaletteColours.multids b/core/language/en-GB/Docs/PaletteColours.multids
index 98addbf854..bc1b36c3df 100644
--- a/core/language/en-GB/Docs/PaletteColours.multids
+++ b/core/language/en-GB/Docs/PaletteColours.multids
@@ -65,6 +65,13 @@ sidebar-tab-foreground-selected: Sidebar tab foreground for selected tabs
sidebar-tab-foreground: Sidebar tab foreground
sidebar-tiddler-link-foreground-hover: Sidebar tiddler link foreground hover
sidebar-tiddler-link-foreground: Sidebar tiddler link foreground
+stability-stable: Badge for stability level "stable"
+stability-experimental: Badge for stability level "experimental"
+stability-deprecated: Badge for stability level "deprecated"
+stability-legacy: Badge for stability level "legacy"
+testcase-accent-level-1: Test case accent colour with no nesting
+testcase-accent-level-2: Test case accent colour with 2nd level nesting
+testcase-accent-level-3: Test case accent colour with 3rd level nesting or higher
site-title-foreground: Site title foreground
static-alert-foreground: Static alert foreground
tab-background-selected: Tab background for selected tabs
diff --git a/core/language/en-GB/Exporters.multids b/core/language/en-GB/Exporters.multids
index e455b8bf11..6ac52efe70 100644
--- a/core/language/en-GB/Exporters.multids
+++ b/core/language/en-GB/Exporters.multids
@@ -3,4 +3,4 @@ title: $:/language/Exporters/
StaticRiver: Static HTML
JsonFile: JSON file
CsvFile: CSV file
-TidFile: ".tid" file
+TidFile: TID text file
diff --git a/core/language/en-GB/Fields.multids b/core/language/en-GB/Fields.multids
index c094580d5d..9830e96c1b 100644
--- a/core/language/en-GB/Fields.multids
+++ b/core/language/en-GB/Fields.multids
@@ -1,11 +1,14 @@
title: $:/language/Docs/Fields/
_canonical_uri: The full URI of an external image tiddler
+author: Name of the author of a plugin
bag: The name of the bag from which a tiddler came
caption: The text to be displayed on a tab or button
+class: The CSS class applied to a tiddler when rendering it - see [[Custom styles by user-class]]. Also used for [[Modals]]
code-body: The view template will display the tiddler as code if set to ''yes''
color: The CSS color value associated with a tiddler
component: The name of the component responsible for an [[alert tiddler|AlertMechanism]]
+core-version: For a plugin, indicates what version of TiddlyWiki with which it is compatible
current-tiddler: Used to cache the top tiddler in a [[history list|HistoryMechanism]]
created: The date a tiddler was created
creator: The name of the person who created a tiddler
@@ -22,9 +25,12 @@ list-before: If set, the title of a tiddler before which this tiddler should be
list-after: If set, the title of the tiddler after which this tiddler should be added to the ordered list of tiddler titles, or at the end of the list if this field is present but empty
modified: The date and time at which a tiddler was last modified
modifier: The tiddler title associated with the person who last modified a tiddler
+module-type: For javascript tiddlers, specifies what kind of module it is
name: The human readable name associated with a plugin tiddler
+parent-plugin: For a plugin, specifies which plugin of which it is a sub-plugin
plugin-priority: A numerical value indicating the priority of a plugin tiddler
plugin-type: The type of plugin in a plugin tiddler
+stability: The development status of a plugin: deprecated, experimental, stable, or legacy
revision: The revision of the tiddler held at the server
released: Date of a TiddlyWiki release
source: The source URL associated with a tiddler
diff --git a/core/language/en-GB/Help/commands.tid b/core/language/en-GB/Help/commands.tid
index 454159b449..7551885f08 100644
--- a/core/language/en-GB/Help/commands.tid
+++ b/core/language/en-GB/Help/commands.tid
@@ -10,7 +10,7 @@ Sequentially run the command tokens returned from a filter
Examples
```
---commands "[enlist{$:/build-commands-as-text}]"
+--commands "[enlist:raw{$:/build-commands-as-text}]"
```
```
diff --git a/core/language/en-GB/Help/listen.tid b/core/language/en-GB/Help/listen.tid
index 45df723817..d9f6a247f2 100644
--- a/core/language/en-GB/Help/listen.tid
+++ b/core/language/en-GB/Help/listen.tid
@@ -18,7 +18,7 @@ All parameters are optional with safe defaults, and can be specified in any orde
* ''anon-username'' - the username for signing edits for anonymous users
* ''username'' - optional username for basic authentication
* ''password'' - optional password for basic authentication
-* ''authenticated-user-header'' - optional name of header to be used for trusted authentication
+* ''authenticated-user-header'' - optional name of request header to be used for trusted authentication.
* ''readers'' - comma-separated list of principals allowed to read from this wiki
* ''writers'' - comma-separated list of principals allowed to write to this wiki
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
diff --git a/core/language/en-GB/Help/savewikifolder.tid b/core/language/en-GB/Help/savewikifolder.tid
index bda1d19a3c..82565f7bc0 100644
--- a/core/language/en-GB/Help/savewikifolder.tid
+++ b/core/language/en-GB/Help/savewikifolder.tid
@@ -4,7 +4,7 @@ description: Saves a wiki to a new wiki folder
<<.from-version "5.1.20">> Saves the current wiki as a wiki folder, including tiddlers, plugins and configuration:
```
---savewikifolder []
+--savewikifolder [] [ [=] ]*
```
* The target wiki folder must be empty or non-existent
@@ -12,8 +12,23 @@ description: Saves a wiki to a new wiki folder
* Plugins from the official plugin library are replaced with references to those plugins in the `tiddlywiki.info` file
* Custom plugins are unpacked into their own folder
+The following options are supported:
+
+* ''filter'': a filter expression that defines the tiddlers to include in the output.
+* ''explodePlugins'': defaults to "yes"
+** ''yes'' will "explode" plugins into separate tiddler files and save them to the plugin directory within the wiki folder
+** ''no'' will suppress exploding plugins into their constituent tiddler files. It will save the plugin as a single JSON tiddler in the tiddlers folder
+
+Note that both ''explodePlugins'' options will produce wiki folders that build the exact same original wiki. The difference lies in how plugins are represented in the wiki folder.
+
A common usage is to convert a TiddlyWiki HTML file into a wiki folder:
```
tiddlywiki --load ./mywiki.html --savewikifolder ./mywikifolder
```
+
+Save the plugin to the tiddlers directory of the target wiki folder:
+
+```
+tiddlywiki --load ./mywiki.html --savewikifolder ./mywikifolder explodePlugins=no
+```
diff --git a/core/language/en-GB/Help/server.tid b/core/language/en-GB/Help/server.tid
index 78e9c8ab1b..da68650311 100644
--- a/core/language/en-GB/Help/server.tid
+++ b/core/language/en-GB/Help/server.tid
@@ -1,5 +1,5 @@
title: $:/language/Help/server
-description: Provides an HTTP server interface to TiddlyWiki (deprecated in favour of the new listen command)
+description: (deprecated: see 'listen' command) Provides an HTTP server interface to TiddlyWiki
Legacy command to serve a wiki over HTTP.
diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids
index 00cb3c99c5..d8c0913750 100644
--- a/core/language/en-GB/Misc.multids
+++ b/core/language/en-GB/Misc.multids
@@ -25,10 +25,12 @@ Encryption/RepeatPassword: Repeat password
Encryption/PasswordNoMatch: Passwords do not match
Encryption/SetPassword: Set password
Error/Caption: Error
+Error/DeserializeOperator/MissingOperand: Filter Error: Missing operand for 'deserialize' operator
+Error/DeserializeOperator/UnknownDeserializer: Filter Error: Unknown deserializer provided as operand for the 'deserialize' operator
Error/Filter: Filter error
Error/FilterSyntax: Syntax error in filter expression
Error/FilterRunPrefix: Filter Error: Unknown prefix for filter run
-Error/IsFilterOperator: Filter Error: Unknown operand for the 'is' filter operator
+Error/IsFilterOperator: Filter Error: Unknown parameter for the 'is' filter operator
Error/FormatFilterOperator: Filter Error: Unknown suffix for the 'format' filter operator
Error/LoadingPluginLibrary: Error loading plugin library
Error/NetworkErrorAlert: `''Network Error'' It looks like the connection to the server has been lost. This may indicate a problem with your network connection. Please attempt to restore network connectivity before continuing. ''Any unsaved changes will be automatically synchronised when connectivity is restored''.`
@@ -40,6 +42,7 @@ Error/RetrievingSkinny: Error retrieving skinny tiddler list
Error/SavingToTWEdit: Error saving to TWEdit
Error/WhileSaving: Error while saving
Error/XMLHttpRequest: XMLHttpRequest error code
+Error/ZoominTextNode: Story View Error: It appears you tried to interact with a tiddler that displays in a custom container. This is most likely caused by using `$:/tags/StoryTiddlerTemplateFilter` with a template that contains text or whitespace at the start. Please use the pragma `\whitespace trim` and ensure the whole contents of the tiddler is wrapped in a single HTML element. The text that caused this issue:
InternalJavaScriptError/Title: Internal JavaScript Error
InternalJavaScriptError/Hint: Well, this is embarrassing. It is recommended that you restart TiddlyWiki by refreshing your browser
LayoutSwitcher/Description: Open the layout switcher
@@ -67,7 +70,7 @@ 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.
PageTemplate/Description: the default ~TiddlyWiki layout
-PageTemplate/Name: Default ~PageTemplate
+PageTemplate/Name: Standard Layout
PluginReloadWarning: Please save {{$:/core/ui/Buttons/save-wiki}} and reload {{$:/core/ui/Buttons/refresh}} to allow changes to ~JavaScript plugins to take effect
RecentChanges/DateFormat: DDth MMM YYYY
Shortcuts/Input/AdvancedSearch/Hint: Open the ~AdvancedSearch panel from within the sidebar search field
diff --git a/core/language/en-GB/Modals/SaveInstructions.tid b/core/language/en-GB/Modals/SaveInstructions.tid
deleted file mode 100644
index 45028fc45e..0000000000
--- a/core/language/en-GB/Modals/SaveInstructions.tid
+++ /dev/null
@@ -1,21 +0,0 @@
-title: $:/language/Modals/SaveInstructions
-subtitle: Save your work
-footer: <$button message="tm-close-tiddler">Close$button>
-help: https://tiddlywiki.com/static/SavingChanges.html
-
-Your changes to this wiki need to be saved as a ~TiddlyWiki HTML file.
-
-!!! Desktop browsers
-
-# Select ''Save As'' from the ''File'' menu
-# Choose a filename and location
-#* Some browsers also require you to explicitly specify the file saving format as ''Webpage, HTML only'' or similar
-# Close this tab
-
-!!! Smartphone browsers
-
-# Create a bookmark to this page
-#* If you've got iCloud or Google Sync set up then the bookmark will automatically sync to your desktop where you can open it and save it as above
-# Close this tab
-
-//If you open the bookmark again in Mobile Safari you will see this message again. If you want to go ahead and use the file, just click the ''close'' button below//
diff --git a/core/language/en-GB/SiteTitle.tid b/core/language/en-GB/SiteTitle.tid
index 9f522664a9..a32da7dfe6 100644
--- a/core/language/en-GB/SiteTitle.tid
+++ b/core/language/en-GB/SiteTitle.tid
@@ -1,3 +1,3 @@
title: $:/SiteTitle
-My ~TiddlyWiki
\ No newline at end of file
+My TiddlyWiki
\ No newline at end of file
diff --git a/core/language/en-GB/Types/image_svg_xml.tid b/core/language/en-GB/Types/image_svg_xml.tid
index 9f7c23ba32..94c3ea949d 100644
--- a/core/language/en-GB/Types/image_svg_xml.tid
+++ b/core/language/en-GB/Types/image_svg_xml.tid
@@ -1,5 +1,5 @@
title: $:/language/Docs/Types/image/svg+xml
-description: Structured Vector Graphics image
+description: SVG image
name: image/svg+xml
group: Image
group-sort: 1
diff --git a/core/language/en-GB/Types/image_x-icon.tid b/core/language/en-GB/Types/image_x-icon.tid
index 6ae32331c6..55420387a2 100644
--- a/core/language/en-GB/Types/image_x-icon.tid
+++ b/core/language/en-GB/Types/image_x-icon.tid
@@ -1,5 +1,5 @@
title: $:/language/Docs/Types/image/x-icon
-description: ICO format icon file
+description: ICO icon
name: image/x-icon
group: Image
group-sort: 1
diff --git a/core/modules/commands/listen.js b/core/modules/commands/listen.js
index 3c5f6a63a1..ca6e6e0765 100644
--- a/core/modules/commands/listen.js
+++ b/core/modules/commands/listen.js
@@ -18,7 +18,7 @@ exports.info = {
name: "listen",
synchronous: true,
namedParameterMode: true,
- mandatoryParameters: [],
+ mandatoryParameters: []
};
var Command = function(params,commander,callback) {
diff --git a/core/modules/commands/makelibrary.js b/core/modules/commands/makelibrary.js
index 36a1399a2c..3af2e49433 100644
--- a/core/modules/commands/makelibrary.js
+++ b/core/modules/commands/makelibrary.js
@@ -27,33 +27,8 @@ var Command = function(params,commander,callback) {
Command.prototype.execute = function() {
var wiki = this.commander.wiki,
- fs = require("fs"),
- path = require("path"),
upgradeLibraryTitle = this.params[0] || UPGRADE_LIBRARY_TITLE,
- tiddlers = {};
- // Collect up the library plugins
- var collectPlugins = function(folder) {
- var pluginFolders = $tw.utils.getSubdirectories(folder) || [];
- for(var p=0; p []
+--savewikifolder [ [=] ]*
+
+The following options are supported:
+
+* ''filter'': a filter expression defining the tiddlers to be included in the output
+* ''explodePlugins'': set to "no" to suppress exploding plugins into their constituent shadow tiddlers (defaults to "yes")
+
+Supports backward compatibility with --savewikifolder [] [ [=] ]*
\*/
(function(){
@@ -35,14 +42,28 @@ Command.prototype.execute = function() {
if(this.params.length < 1) {
return "Missing wiki folder path";
}
- var wikifoldermaker = new WikiFolderMaker(this.params[0],this.params[1],this.commander);
+ var regFilter = /^[a-zA-Z0-9\.\-_]+=/g, // dynamic parameters
+ namedParames,
+ tiddlerFilter,
+ options = {};
+ if (regFilter.test(this.params[1])) {
+ namedParames = this.commander.extractNamedParameters(this.params.slice(1));
+ tiddlerFilter = namedParames.filter || "[all[tiddlers]]";
+ } else {
+ namedParames = this.commander.extractNamedParameters(this.params.slice(2));
+ tiddlerFilter = this.params[1];
+ }
+ tiddlerFilter = tiddlerFilter || "[all[tiddlers]]";
+ options.explodePlugins = namedParames.explodePlugins || "yes";
+ var wikifoldermaker = new WikiFolderMaker(this.params[0],tiddlerFilter,this.commander,options);
return wikifoldermaker.save();
};
-function WikiFolderMaker(wikiFolderPath,wikiFilter,commander) {
+function WikiFolderMaker(wikiFolderPath,wikiFilter,commander,options) {
this.wikiFolderPath = wikiFolderPath;
- this.wikiFilter = wikiFilter || "[all[tiddlers]]";
+ this.wikiFilter = wikiFilter;
this.commander = commander;
+ this.explodePlugins = options.explodePlugins;
this.wiki = commander.wiki;
this.savedPaths = []; // So that we can detect filename clashes
}
@@ -93,10 +114,13 @@ WikiFolderMaker.prototype.save = function() {
self.log("Adding built-in plugin: " + libraryDetails.name);
newWikiInfo[libraryDetails.type] = newWikiInfo[libraryDetails.type] || [];
$tw.utils.pushTop(newWikiInfo[libraryDetails.type],libraryDetails.name);
- } else {
+ } else if(self.explodePlugins !== "no") {
// A custom plugin
self.log("Processing custom plugin: " + title);
self.saveCustomPlugin(tiddler);
+ } else if(self.explodePlugins === "no") {
+ self.log("Processing custom plugin to tiddlders folder: " + title);
+ self.saveTiddler("tiddlers", tiddler);
}
} else {
// Ordinary tiddler
@@ -152,7 +176,10 @@ WikiFolderMaker.prototype.saveCustomPlugin = function(pluginTiddler) {
this.saveJSONFile(directory + path.sep + "plugin.info",pluginInfo);
self.log("Writing " + directory + path.sep + "plugin.info: " + JSON.stringify(pluginInfo,null,$tw.config.preferences.jsonSpaces));
var pluginTiddlers = $tw.utils.parseJSONSafe(pluginTiddler.fields.text).tiddlers; // A hashmap of tiddlers in the plugin
- $tw.utils.each(pluginTiddlers,function(tiddler) {
+ $tw.utils.each(pluginTiddlers,function(tiddler,title) {
+ if(!tiddler.title) {
+ tiddler.title = title;
+ }
self.saveTiddler(directory,new $tw.Tiddler(tiddler));
});
};
diff --git a/core/modules/config.js b/core/modules/config.js
index 77e74edf8c..399af598b3 100644
--- a/core/modules/config.js
+++ b/core/modules/config.js
@@ -30,7 +30,7 @@ exports.textPrimitives.wikiLink = exports.textPrimitives.upperLetter + "+" +
exports.textPrimitives.upperLetter +
exports.textPrimitives.anyLetter + "*";
-exports.htmlEntities = {quot:34, amp:38, apos:39, lt:60, gt:62, nbsp:160, iexcl:161, cent:162, pound:163, curren:164, yen:165, brvbar:166, sect:167, uml:168, copy:169, ordf:170, laquo:171, not:172, shy:173, reg:174, macr:175, deg:176, plusmn:177, sup2:178, sup3:179, acute:180, micro:181, para:182, middot:183, cedil:184, sup1:185, ordm:186, raquo:187, frac14:188, frac12:189, frac34:190, iquest:191, Agrave:192, Aacute:193, Acirc:194, Atilde:195, Auml:196, Aring:197, AElig:198, Ccedil:199, Egrave:200, Eacute:201, Ecirc:202, Euml:203, Igrave:204, Iacute:205, Icirc:206, Iuml:207, ETH:208, Ntilde:209, Ograve:210, Oacute:211, Ocirc:212, Otilde:213, Ouml:214, times:215, Oslash:216, Ugrave:217, Uacute:218, Ucirc:219, Uuml:220, Yacute:221, THORN:222, szlig:223, agrave:224, aacute:225, acirc:226, atilde:227, auml:228, aring:229, aelig:230, ccedil:231, egrave:232, eacute:233, ecirc:234, euml:235, igrave:236, iacute:237, icirc:238, iuml:239, eth:240, ntilde:241, ograve:242, oacute:243, ocirc:244, otilde:245, ouml:246, divide:247, oslash:248, ugrave:249, uacute:250, ucirc:251, uuml:252, yacute:253, thorn:254, yuml:255, OElig:338, oelig:339, Scaron:352, scaron:353, Yuml:376, fnof:402, circ:710, tilde:732, Alpha:913, Beta:914, Gamma:915, Delta:916, Epsilon:917, Zeta:918, Eta:919, Theta:920, Iota:921, Kappa:922, Lambda:923, Mu:924, Nu:925, Xi:926, Omicron:927, Pi:928, Rho:929, Sigma:931, Tau:932, Upsilon:933, Phi:934, Chi:935, Psi:936, Omega:937, alpha:945, beta:946, gamma:947, delta:948, epsilon:949, zeta:950, eta:951, theta:952, iota:953, kappa:954, lambda:955, mu:956, nu:957, xi:958, omicron:959, pi:960, rho:961, sigmaf:962, sigma:963, tau:964, upsilon:965, phi:966, chi:967, psi:968, omega:969, thetasym:977, upsih:978, piv:982, ensp:8194, emsp:8195, thinsp:8201, zwnj:8204, zwj:8205, lrm:8206, rlm:8207, ndash:8211, mdash:8212, lsquo:8216, rsquo:8217, sbquo:8218, ldquo:8220, rdquo:8221, bdquo:8222, dagger:8224, Dagger:8225, bull:8226, hellip:8230, permil:8240, prime:8242, Prime:8243, lsaquo:8249, rsaquo:8250, oline:8254, frasl:8260, euro:8364, image:8465, weierp:8472, real:8476, trade:8482, alefsym:8501, larr:8592, uarr:8593, rarr:8594, darr:8595, harr:8596, crarr:8629, lArr:8656, uArr:8657, rArr:8658, dArr:8659, hArr:8660, forall:8704, part:8706, exist:8707, empty:8709, nabla:8711, isin:8712, notin:8713, ni:8715, prod:8719, sum:8721, minus:8722, lowast:8727, radic:8730, prop:8733, infin:8734, ang:8736, and:8743, or:8744, cap:8745, cup:8746, int:8747, there4:8756, sim:8764, cong:8773, asymp:8776, ne:8800, equiv:8801, le:8804, ge:8805, sub:8834, sup:8835, nsub:8836, sube:8838, supe:8839, oplus:8853, otimes:8855, perp:8869, sdot:8901, lceil:8968, rceil:8969, lfloor:8970, rfloor:8971, lang:9001, rang:9002, loz:9674, spades:9824, clubs:9827, hearts:9829, diams:9830 };
+exports.htmlEntities = {quot:34, dollar:36, amp:38, apos:39, lt:60, gt:62, nbsp:160, iexcl:161, cent:162, pound:163, curren:164, yen:165, brvbar:166, sect:167, uml:168, copy:169, ordf:170, laquo:171, not:172, shy:173, reg:174, macr:175, deg:176, plusmn:177, sup2:178, sup3:179, acute:180, micro:181, para:182, middot:183, cedil:184, sup1:185, ordm:186, raquo:187, frac14:188, frac12:189, frac34:190, iquest:191, Agrave:192, Aacute:193, Acirc:194, Atilde:195, Auml:196, Aring:197, AElig:198, Ccedil:199, Egrave:200, Eacute:201, Ecirc:202, Euml:203, Igrave:204, Iacute:205, Icirc:206, Iuml:207, ETH:208, Ntilde:209, Ograve:210, Oacute:211, Ocirc:212, Otilde:213, Ouml:214, times:215, Oslash:216, Ugrave:217, Uacute:218, Ucirc:219, Uuml:220, Yacute:221, THORN:222, szlig:223, agrave:224, aacute:225, acirc:226, atilde:227, auml:228, aring:229, aelig:230, ccedil:231, egrave:232, eacute:233, ecirc:234, euml:235, igrave:236, iacute:237, icirc:238, iuml:239, eth:240, ntilde:241, ograve:242, oacute:243, ocirc:244, otilde:245, ouml:246, divide:247, oslash:248, ugrave:249, uacute:250, ucirc:251, uuml:252, yacute:253, thorn:254, yuml:255, OElig:338, oelig:339, Scaron:352, scaron:353, Yuml:376, fnof:402, circ:710, tilde:732, Alpha:913, Beta:914, Gamma:915, Delta:916, Epsilon:917, Zeta:918, Eta:919, Theta:920, Iota:921, Kappa:922, Lambda:923, Mu:924, Nu:925, Xi:926, Omicron:927, Pi:928, Rho:929, Sigma:931, Tau:932, Upsilon:933, Phi:934, Chi:935, Psi:936, Omega:937, alpha:945, beta:946, gamma:947, delta:948, epsilon:949, zeta:950, eta:951, theta:952, iota:953, kappa:954, lambda:955, mu:956, nu:957, xi:958, omicron:959, pi:960, rho:961, sigmaf:962, sigma:963, tau:964, upsilon:965, phi:966, chi:967, psi:968, omega:969, thetasym:977, upsih:978, piv:982, ensp:8194, emsp:8195, thinsp:8201, zwnj:8204, zwj:8205, lrm:8206, rlm:8207, ndash:8211, mdash:8212, lsquo:8216, rsquo:8217, sbquo:8218, ldquo:8220, rdquo:8221, bdquo:8222, dagger:8224, Dagger:8225, bull:8226, hellip:8230, permil:8240, prime:8242, Prime:8243, lsaquo:8249, rsaquo:8250, oline:8254, frasl:8260, euro:8364, image:8465, weierp:8472, real:8476, trade:8482, alefsym:8501, larr:8592, uarr:8593, rarr:8594, darr:8595, harr:8596, crarr:8629, lArr:8656, uArr:8657, rArr:8658, dArr:8659, hArr:8660, forall:8704, part:8706, exist:8707, empty:8709, nabla:8711, isin:8712, notin:8713, ni:8715, prod:8719, sum:8721, minus:8722, lowast:8727, radic:8730, prop:8733, infin:8734, ang:8736, and:8743, or:8744, cap:8745, cup:8746, int:8747, there4:8756, sim:8764, cong:8773, asymp:8776, ne:8800, equiv:8801, le:8804, ge:8805, sub:8834, sup:8835, nsub:8836, sube:8838, supe:8839, oplus:8853, otimes:8855, perp:8869, sdot:8901, lceil:8968, rceil:8969, lfloor:8970, rfloor:8971, lang:9001, rang:9002, loz:9674, spades:9824, clubs:9827, hearts:9829, diams:9830 };
exports.htmlVoidElements = "area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(",");
diff --git a/core/modules/editor/engines/framed.js b/core/modules/editor/engines/framed.js
index 10ab90b39f..34d11dec7c 100644
--- a/core/modules/editor/engines/framed.js
+++ b/core/modules/editor/engines/framed.js
@@ -60,7 +60,7 @@ function FramedEngine(options) {
this.domNode.value = this.value;
}
// Set the attributes
- if(this.widget.editType) {
+ if(this.widget.editType && this.widget.editTag !== "textarea") {
this.domNode.setAttribute("type",this.widget.editType);
}
if(this.widget.editPlaceholder) {
@@ -118,6 +118,8 @@ FramedEngine.prototype.copyStyles = function() {
this.domNode.style.margin = "0";
// In Chrome setting -webkit-text-fill-color overrides the placeholder text colour
this.domNode.style["-webkit-text-fill-color"] = "currentcolor";
+ // Ensure we don't force text direction to LTR
+ this.domNode.style.removeProperty("direction");
};
/*
@@ -162,13 +164,13 @@ FramedEngine.prototype.fixHeight = function() {
if(this.widget.editAutoHeight) {
if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) {
var newHeight = $tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight);
- this.iframeNode.style.height = (newHeight + 14) + "px"; // +14 for the border on the textarea
+ this.iframeNode.style.height = newHeight + "px";
}
} else {
var fixedHeight = parseInt(this.widget.wiki.getTiddlerText(HEIGHT_VALUE_TITLE,"400px"),10);
fixedHeight = Math.max(fixedHeight,20);
this.domNode.style.height = fixedHeight + "px";
- this.iframeNode.style.height = (fixedHeight + 14) + "px";
+ this.iframeNode.style.height = fixedHeight + "px";
}
}
};
@@ -177,9 +179,11 @@ FramedEngine.prototype.fixHeight = function() {
Focus the engine node
*/
FramedEngine.prototype.focus = function() {
- if(this.domNode.focus && this.domNode.select) {
+ if(this.domNode.focus) {
this.domNode.focus();
- this.domNode.select();
+ }
+ if(this.domNode.select) {
+ $tw.utils.setSelectionByPosition(this.domNode,this.widget.editFocusSelectFromStart,this.widget.editFocusSelectFromEnd);
}
};
diff --git a/core/modules/editor/engines/simple.js b/core/modules/editor/engines/simple.js
index e8be19a75e..809dc58ea9 100644
--- a/core/modules/editor/engines/simple.js
+++ b/core/modules/editor/engines/simple.js
@@ -34,7 +34,7 @@ function SimpleEngine(options) {
this.domNode.value = this.value;
}
// Set the attributes
- if(this.widget.editType) {
+ if(this.widget.editType && this.widget.editTag !== "textarea") {
this.domNode.setAttribute("type",this.widget.editType);
}
if(this.widget.editPlaceholder) {
@@ -119,10 +119,12 @@ SimpleEngine.prototype.fixHeight = function() {
/*
Focus the engine node
*/
-SimpleEngine.prototype.focus = function() {
- if(this.domNode.focus && this.domNode.select) {
+SimpleEngine.prototype.focus = function() {
+ if(this.domNode.focus) {
this.domNode.focus();
- this.domNode.select();
+ }
+ if(this.domNode.select) {
+ $tw.utils.setSelectionByPosition(this.domNode,this.widget.editFocusSelectFromStart,this.widget.editFocusSelectFromEnd);
}
};
diff --git a/core/modules/editor/factory.js b/core/modules/editor/factory.js
index 90ab66ae9a..6157ec67fd 100644
--- a/core/modules/editor/factory.js
+++ b/core/modules/editor/factory.js
@@ -180,6 +180,8 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
this.editMinHeight = this.getAttribute("minHeight",DEFAULT_MIN_TEXT_AREA_HEIGHT);
this.editFocusPopup = this.getAttribute("focusPopup");
this.editFocus = this.getAttribute("focus");
+ this.editFocusSelectFromStart = $tw.utils.parseNumber(this.getAttribute("focusSelectFromStart","0"));
+ this.editFocusSelectFromEnd = $tw.utils.parseNumber(this.getAttribute("focusSelectFromEnd","0"));
this.editTabIndex = this.getAttribute("tabindex");
this.editCancelPopups = this.getAttribute("cancelPopups","") === "yes";
this.editInputActions = this.getAttribute("inputActions");
@@ -218,7 +220,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
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 || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || changedTiddlers[HEIGHT_MODE_TITLE] || changedTiddlers[ENABLE_TOOLBAR_TITLE] || changedAttributes.disabled || changedAttributes.fileDrop) {
+ if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.placeholder || changedAttributes.size || changedAttributes.autoHeight || changedAttributes.minHeight || changedAttributes.focusPopup || changedAttributes.rows || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || changedTiddlers[HEIGHT_MODE_TITLE] || changedTiddlers[ENABLE_TOOLBAR_TITLE] || changedTiddlers["$:/palette"] || changedAttributes.disabled || changedAttributes.fileDrop) {
this.refreshSelf();
return true;
} else if (changedTiddlers[this.editRefreshTitle]) {
diff --git a/core/modules/filterrunprefixes/cascade.js b/core/modules/filterrunprefixes/cascade.js
index da6894d21b..486e75f456 100644
--- a/core/modules/filterrunprefixes/cascade.js
+++ b/core/modules/filterrunprefixes/cascade.js
@@ -25,20 +25,10 @@ exports.cascade = function(operationSubFunction,options) {
if(!filterFnList[index]) {
filterFnList[index] = options.wiki.compileFilter(filter);
}
- var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler")
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler","")
+ }));
if(output.length !== 0) {
result = output[0];
return false;
diff --git a/core/modules/filterrunprefixes/filter.js b/core/modules/filterrunprefixes/filter.js
index 783b699c23..4ab0571095 100644
--- a/core/modules/filterrunprefixes/filter.js
+++ b/core/modules/filterrunprefixes/filter.js
@@ -19,23 +19,13 @@ exports.filter = function(operationSubFunction,options) {
var resultsToRemove = [],
index = 0;
results.each(function(title) {
- var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler"),
- "index": "" + index,
- "revIndex": "" + (results.length - 1 - index),
- "length": "" + results.length
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler",""),
+ "index": "" + index,
+ "revIndex": "" + (results.length - 1 - index),
+ "length": "" + results.length
+ }));
if(filtered.length === 0) {
resultsToRemove.push(title);
}
diff --git a/core/modules/filterrunprefixes/map.js b/core/modules/filterrunprefixes/map.js
index efcb5b5344..b756d6699c 100644
--- a/core/modules/filterrunprefixes/map.js
+++ b/core/modules/filterrunprefixes/map.js
@@ -21,23 +21,13 @@ exports.map = function(operationSubFunction,options) {
flatten = (suffixes[0] && suffixes[0][0] === "flat") ? true : false;
results.clear();
$tw.utils.each(inputTitles,function(title) {
- var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler"),
- "index": "" + index,
- "revIndex": "" + (inputTitles.length - 1 - index),
- "length": "" + inputTitles.length
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler",""),
+ "index": "" + index,
+ "revIndex": "" + (inputTitles.length - 1 - index),
+ "length": "" + inputTitles.length
+ }));
if(filtered.length && flatten) {
$tw.utils.each(filtered,function(value) {
results.push(value);
diff --git a/core/modules/filterrunprefixes/reduce.js b/core/modules/filterrunprefixes/reduce.js
index 8fe819e3fc..ee2998837a 100644
--- a/core/modules/filterrunprefixes/reduce.js
+++ b/core/modules/filterrunprefixes/reduce.js
@@ -18,24 +18,14 @@ exports.reduce = function(operationSubFunction,options) {
var accumulator = "",
index = 0;
results.each(function(title) {
- var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler"),
- "index": "" + index,
- "revIndex": "" + (results.length - 1 - index),
- "length": "" + results.length,
- "accumulator": "" + accumulator
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler"),
+ "index": "" + index,
+ "revIndex": "" + (results.length - 1 - index),
+ "length": "" + results.length,
+ "accumulator": "" + accumulator
+ }));
if(list.length > 0) {
accumulator = "" + list[0];
}
diff --git a/core/modules/filterrunprefixes/sort.js b/core/modules/filterrunprefixes/sort.js
index 6865b175c4..d8d3761261 100644
--- a/core/modules/filterrunprefixes/sort.js
+++ b/core/modules/filterrunprefixes/sort.js
@@ -25,20 +25,10 @@ exports.sort = function(operationSubFunction,options) {
indexes = new Array(inputTitles.length),
compareFn;
results.each(function(title) {
- var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler")
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler")
+ }));
sortKeys.push(key[0] || "");
});
results.clear();
diff --git a/core/modules/filterrunprefixes/then.js b/core/modules/filterrunprefixes/then.js
new file mode 100644
index 0000000000..9f6d5c76a6
--- /dev/null
+++ b/core/modules/filterrunprefixes/then.js
@@ -0,0 +1,32 @@
+/*\
+title: $:/core/modules/filterrunprefixes/then.js
+type: application/javascript
+module-type: filterrunprefix
+
+Replace results of previous runs unless empty
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter prefix function
+*/
+exports.then = function(operationSubFunction) {
+ return function(results,source,widget) {
+ if(results.length !== 0) {
+ // Only run if previous run(s) produced results
+ var thisRunResult = operationSubFunction(source,widget);
+ if(thisRunResult.length !== 0) {
+ // Replace results only if this run actually produces a result
+ results.clear();
+ results.pushTop(thisRunResult);
+ }
+ }
+ };
+};
+
+})();
diff --git a/core/modules/filters.js b/core/modules/filters.js
index 1bb5fe9ff3..aa82a352af 100644
--- a/core/modules/filters.js
+++ b/core/modules/filters.js
@@ -12,6 +12,8 @@ Adds tiddler filtering methods to the $tw.Wiki object.
/*global $tw: false */
"use strict";
+var widgetClass = require("$:/core/modules/widgets/widget.js").widget;
+
/* Maximum permitted filter recursion depth */
var MAX_FILTER_DEPTH = 300;
@@ -255,19 +257,21 @@ exports.compileFilter = function(filterString) {
var operands = [],
operatorFunction;
if(!operator.operator) {
+ // Use the "title" operator if no operator is specified
operatorFunction = filterOperators.title;
} else if(!filterOperators[operator.operator]) {
- operatorFunction = filterOperators.field;
+ // Unknown operators treated as "[unknown]" - at run time we can distinguish between a custom operator and falling back to the default "field" operator
+ operatorFunction = filterOperators["[unknown]"];
} else {
+ // Use the operator function
operatorFunction = filterOperators[operator.operator];
}
-
$tw.utils.each(operator.operands,function(operand) {
if(operand.indirect) {
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
} else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
- operand.value = widget.getVariable(varTree.name,{params:varTree.params,defaultValue: ""});
+ operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || "";
} else {
operand.value = operand.text;
}
diff --git a/core/modules/filters/all.js b/core/modules/filters/all.js
index a36749e92a..3554a74b32 100644
--- a/core/modules/filters/all.js
+++ b/core/modules/filters/all.js
@@ -28,12 +28,8 @@ function getAllFilterOperators() {
Export our filter function
*/
exports.all = function(source,operator,options) {
- // Get our suboperators
- var allFilterOperators = getAllFilterOperators();
- // Cycle through the suboperators accumulating their results
- var results = new $tw.utils.LinkedList(),
- subops = operator.operand.split("+");
// Check for common optimisations
+ var subops = operator.operand.split("+");
if(subops.length === 1 && subops[0] === "") {
return source;
} else if(subops.length === 1 && subops[0] === "tiddlers") {
@@ -46,6 +42,10 @@ exports.all = function(source,operator,options) {
return options.wiki.eachShadowPlusTiddlers;
}
// Do it the hard way
+ // Get our suboperators
+ var allFilterOperators = getAllFilterOperators();
+ // Cycle through the suboperators accumulating their results
+ var results = new $tw.utils.LinkedList();
for(var t=0; t 0) === target) {
results.push(title);
}
diff --git a/core/modules/filters/format/timestamp.js b/core/modules/filters/format/timestamp.js
new file mode 100644
index 0000000000..d6f5afe30d
--- /dev/null
+++ b/core/modules/filters/format/timestamp.js
@@ -0,0 +1,25 @@
+/*\
+title: $:/core/modules/filters/format/timestamp.js
+type: application/javascript
+module-type: formatfilteroperator
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter function
+*/
+exports.timestamp = function(source,operand,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ if (title.match(/^-?\d+$/)) {
+ var value = new Date(Number(title));
+ results.push($tw.utils.formatDateString(value,operand || "[UTC]YYYY0MM0DD0hh0mm0ss0XXX"));
+ }
+ });
+ return results;
+};
+})();
\ No newline at end of file
diff --git a/core/modules/filters/function.js b/core/modules/filters/function.js
new file mode 100644
index 0000000000..79210fb780
--- /dev/null
+++ b/core/modules/filters/function.js
@@ -0,0 +1,36 @@
+/*\
+title: $:/core/modules/filters/function.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator returning those input titles that are returned from a function
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter function
+*/
+exports.function = function(source,operator,options) {
+ var functionName = operator.operands[0],
+ params = [];
+ $tw.utils.each(operator.operands.slice(1),function(param) {
+ params.push({value: param});
+ });
+ var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName,{params: params, source: source});
+ if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
+ return variableInfo.resultList ? variableInfo.resultList : [variableInfo.text];
+ }
+ // Return the input list if the function wasn't found
+ var results = [];
+ source(function(tiddler,title) {
+ results.push(title);
+ });
+ return results;
+};
+
+})();
diff --git a/core/modules/filters/json-ops.js b/core/modules/filters/json-ops.js
index 2be9ec7549..0c58964ebc 100644
--- a/core/modules/filters/json-ops.js
+++ b/core/modules/filters/json-ops.js
@@ -68,6 +68,54 @@ exports["jsontype"] = function(source,operator,options) {
return results;
};
+exports["jsonset"] = function(source,operator,options) {
+ var suffixes = operator.suffixes || [],
+ type = suffixes[0] && suffixes[0][0],
+ indexes = operator.operands.slice(0,-1),
+ value = operator.operands[operator.operands.length - 1],
+ results = [];
+ if(operator.operands.length === 1 && operator.operands[0] === "") {
+ value = undefined; // Prevents the value from being assigned
+ }
+ switch(type) {
+ case "string":
+ // Use value unchanged
+ break;
+ case "boolean":
+ value = (value === "true" ? true : (value === "false" ? false : undefined));
+ break;
+ case "number":
+ value = $tw.utils.parseNumber(value);
+ break;
+ case "array":
+ indexes = operator.operands;
+ value = [];
+ break;
+ case "object":
+ indexes = operator.operands;
+ value = {};
+ break;
+ case "null":
+ indexes = operator.operands;
+ value = null;
+ break;
+ case "json":
+ value = $tw.utils.parseJSONSafe(value,function() {return undefined;});
+ break;
+ default:
+ // Use value unchanged
+ break;
+ }
+ source(function(tiddler,title) {
+ var data = $tw.utils.parseJSONSafe(title,title);
+ if(data) {
+ data = setDataItem(data,indexes,value);
+ results.push(JSON.stringify(data));
+ }
+ });
+ return results;
+};
+
/*
Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
@@ -165,6 +213,18 @@ function getDataItemType(data,indexes) {
}
}
+function getItemAtIndex(item,index) {
+ if($tw.utils.hop(item,index)) {
+ return item[index];
+ } else if($tw.utils.isArray(item)) {
+ index = $tw.utils.parseInt(index);
+ if(index < 0) { index = index + item.length };
+ return item[index]; // Will be undefined if index was out-of-bounds
+ } else {
+ return undefined;
+ }
+}
+
/*
Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
@@ -177,7 +237,7 @@ function getDataItem(data,indexes) {
for(var i=0; i 0) {
accumulator = "" + list[0];
}
diff --git a/core/modules/filters/sortsub.js b/core/modules/filters/sortsub.js
index e9f676daa7..d328be09c9 100644
--- a/core/modules/filters/sortsub.js
+++ b/core/modules/filters/sortsub.js
@@ -25,19 +25,10 @@ exports.sortsub = function(source,operator,options) {
inputTitles.push(title);
var r = filterFn.call(options.wiki,function(iterator) {
iterator(options.wiki.getTiddler(title),title);
- },{
- getVariable: function(name,opts) {
- opts = opts || {};
- switch(name) {
- case "currentTiddler":
- return "" + title;
- case "..currentTiddler":
- return options.widget.getVariable("currentTiddler");
- default:
- return options.widget.getVariable(name,opts);
- }
- }
- });
+ },options.widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": options.widget.getVariable("currentTiddler")
+ }));
sortKeys.push(r[0] || "");
});
// Rather than sorting the titles array, we'll sort the indexes so that we can consult both arrays
diff --git a/core/modules/filters/strings.js b/core/modules/filters/strings.js
index 60a36265d9..11f7634b74 100644
--- a/core/modules/filters/strings.js
+++ b/core/modules/filters/strings.js
@@ -74,6 +74,113 @@ exports.join = makeStringReducingOperator(
},null
);
+var dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
+
+exports.levenshtein = makeStringBinaryOperator(
+ function(a,b) {
+ var dmpObject = new dmp.diff_match_patch(),
+ diffs = dmpObject.diff_main(a,b);
+ return [dmpObject.diff_levenshtein(diffs) + ""];
+ }
+);
+
+// these two functions are adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
+function diffLineWordMode(text1,text2,mode) {
+ var dmpObject = new dmp.diff_match_patch();
+ var a = diffPartsToChars(text1,text2,mode);
+ var lineText1 = a.chars1;
+ var lineText2 = a.chars2;
+ var lineArray = a.lineArray;
+ var diffs = dmpObject.diff_main(lineText1,lineText2,false);
+ dmpObject.diff_charsToLines_(diffs,lineArray);
+ return diffs;
+}
+
+function diffPartsToChars(text1,text2,mode) {
+ var lineArray = [];
+ var lineHash = {};
+ lineArray[0] = '';
+
+ function diff_linesToPartsMunge_(text,mode) {
+ var chars = '';
+ var lineStart = 0;
+ var lineEnd = -1;
+ var lineArrayLength = lineArray.length,
+ regexpResult;
+ var searchRegexp = /\W+/g;
+ while(lineEnd < text.length - 1) {
+ if(mode === "words") {
+ regexpResult = searchRegexp.exec(text);
+ lineEnd = searchRegexp.lastIndex;
+ if(regexpResult === null) {
+ lineEnd = text.length;
+ }
+ lineEnd = --lineEnd;
+ } else {
+ lineEnd = text.indexOf('\n', lineStart);
+ if(lineEnd == -1) {
+ lineEnd = text.length - 1;
+ }
+ }
+ var line = text.substring(lineStart, lineEnd + 1);
+
+ if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) {
+ chars += String.fromCharCode(lineHash[line]);
+ } else {
+ if(lineArrayLength == maxLines) {
+ line = text.substring(lineStart);
+ lineEnd = text.length;
+ }
+ chars += String.fromCharCode(lineArrayLength);
+ lineHash[line] = lineArrayLength;
+ lineArray[lineArrayLength++] = line;
+ }
+ lineStart = lineEnd + 1;
+ }
+ return chars;
+ }
+ var maxLines = 40000;
+ var chars1 = diff_linesToPartsMunge_(text1,mode);
+ maxLines = 65535;
+ var chars2 = diff_linesToPartsMunge_(text2,mode);
+ return {chars1: chars1, chars2: chars2, lineArray: lineArray};
+};
+
+exports.makepatches = function(source,operator,options) {
+ var dmpObject = new dmp.diff_match_patch(),
+ suffix = operator.suffix || "",
+ result = [];
+
+ source(function(tiddler,title) {
+ var diffs, patches;
+ if(suffix === "lines" || suffix === "words") {
+ diffs = diffLineWordMode(title,operator.operand,suffix);
+ patches = dmpObject.patch_make(title,diffs);
+ } else {
+ patches = dmpObject.patch_make(title,operator.operand);
+ }
+ Array.prototype.push.apply(result,[dmpObject.patch_toText(patches)]);
+ });
+
+ return result;
+};
+
+exports.applypatches = makeStringBinaryOperator(
+ function(a,b) {
+ var dmpObject = new dmp.diff_match_patch(),
+ patches;
+ try {
+ patches = dmpObject.patch_fromText(b);
+ } catch(e) {
+ }
+ if(patches) {
+ return [dmpObject.patch_apply(patches,a)[0]];
+ } else {
+ return [a];
+ }
+ }
+);
+
function makeStringBinaryOperator(fnCalc) {
return function(source,operator,options) {
var result = [];
@@ -110,7 +217,10 @@ exports.splitregexp = function(source,operator,options) {
return ["RegExp error: " + ex];
}
source(function(tiddler,title) {
- Array.prototype.push.apply(result,title.split(regExp));
+ var parts = title.split(regExp).map(function(part){
+ return part || ""; // make sure it's a string
+ });
+ Array.prototype.push.apply(result,parts);
});
return result;
};
@@ -157,7 +267,7 @@ exports.pad = function(source,operator,options) {
} else {
var padString = "",
padStringLength = targetLength - title.length;
- while (padStringLength > padString.length) {
+ while(padStringLength > padString.length) {
padString += fill;
}
//make sure we do not exceed the specified length
@@ -184,4 +294,4 @@ exports.charcode = function(source,operator,options) {
return [chars.join("")];
};
-})();
+})();
\ No newline at end of file
diff --git a/core/modules/filters/substitute.js b/core/modules/filters/substitute.js
new file mode 100644
index 0000000000..655ef7321b
--- /dev/null
+++ b/core/modules/filters/substitute.js
@@ -0,0 +1,36 @@
+/*\
+title: $:/core/modules/filters/substitute.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator for substituting variables and embedded filter expressions with their corresponding values
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter function
+*/
+exports.substitute = function(source,operator,options) {
+ var results = [],
+ operands = [];
+ $tw.utils.each(operator.operands,function(operand,index){
+ operands.push({
+ name: (index + 1).toString(),
+ value: operand
+ });
+ });
+ source(function(tiddler,title) {
+ if(title) {
+ results.push(options.wiki.getSubstitutedText(title,options.widget,{substitutions:operands}));
+ }
+ });
+ return results;
+};
+
+})();
+
\ No newline at end of file
diff --git a/core/modules/filters/transcludes.js b/core/modules/filters/transcludes.js
new file mode 100644
index 0000000000..8f42b3bae2
--- /dev/null
+++ b/core/modules/filters/transcludes.js
@@ -0,0 +1,26 @@
+/*\
+title: $:/core/modules/filters/transcludes.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator for returning all the transcludes from a tiddler
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter function
+*/
+exports.transcludes = function(source,operator,options) {
+ var results = new $tw.utils.LinkedList();
+ source(function(tiddler,title) {
+ results.pushTop(options.wiki.getTiddlerTranscludes(title));
+ });
+ return results.makeTiddlerIterator(options.wiki);
+};
+
+})();
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
new file mode 100644
index 0000000000..6ae5baaf0a
--- /dev/null
+++ b/core/modules/filters/unknown.js
@@ -0,0 +1,49 @@
+/*\
+title: $:/core/modules/filters/unknown.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator for handling unknown filter operators.
+
+Not intended to be used directly by end users, hence the square brackets around the name.
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var fieldFilterOperatorFn = require("$:/core/modules/filters/field.js").field;
+
+/*
+Export our filter function
+*/
+exports["[unknown]"] = function(source,operator,options) {
+ // Check for a user defined filter operator
+ if(operator.operator.indexOf(".") !== -1) {
+ var params = [];
+ $tw.utils.each(operator.operands,function(param) {
+ params.push({value: param});
+ });
+ var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator,{params: params, source: source});
+ if(variableInfo && variableInfo.srcVariable) {
+ var list = variableInfo.resultList ? variableInfo.resultList : [variableInfo.text];
+ if(operator.prefix === "!") {
+ var results = [];
+ source(function(tiddler,title) {
+ if(list.indexOf(title) === -1) {
+ results.push(title);
+ }
+ });
+ return results;
+ } else {
+ return list;
+ }
+ }
+ }
+ // Otherwise, use the "field" operator
+ return fieldFilterOperatorFn(source,operator,options);
+};
+
+})();
diff --git a/core/modules/filters/x-listops.js b/core/modules/filters/x-listops.js
index 760f581a17..ae17297a59 100644
--- a/core/modules/filters/x-listops.js
+++ b/core/modules/filters/x-listops.js
@@ -202,7 +202,7 @@ Extended filter operators to manipulate the current list.
}
if(resultsIndex !== -1) {
i = i + step;
- nextOperandIndex = (i < opLength ? i : i - opLength);
+ nextOperandIndex = (i < opLength ? i : i % opLength);
if(operands.length > 1) {
results.splice(resultsIndex,1,operands[nextOperandIndex]);
} else {
diff --git a/core/modules/indexers/back-indexer.js b/core/modules/indexers/back-indexer.js
new file mode 100644
index 0000000000..77b51b8194
--- /dev/null
+++ b/core/modules/indexers/back-indexer.js
@@ -0,0 +1,122 @@
+/*\
+title: $:/core/modules/indexers/back-indexer.js
+type: application/javascript
+module-type: indexer
+
+By parsing the tiddler text, indexes the tiddlers' back links, back transclusions, block level back links.
+
+\*/
+function BackIndexer(wiki) {
+ this.wiki = wiki;
+}
+
+BackIndexer.prototype.init = function() {
+ this.subIndexers = {
+ link: new BackSubIndexer(this,"extractLinks"),
+ transclude: new BackSubIndexer(this,"extractTranscludes"),
+ };
+};
+
+BackIndexer.prototype.rebuild = function() {
+ $tw.utils.each(this.subIndexers,function(subIndexer) {
+ subIndexer.rebuild();
+ });
+};
+
+BackIndexer.prototype.update = function(updateDescriptor) {
+ $tw.utils.each(this.subIndexers,function(subIndexer) {
+ subIndexer.update(updateDescriptor);
+ });
+};
+function BackSubIndexer(indexer,extractor) {
+ this.wiki = indexer.wiki;
+ this.indexer = indexer;
+ this.extractor = extractor;
+ /**
+ * {
+ * [target title, e.g. tiddler title being linked to]:
+ * {
+ * [source title, e.g. tiddler title that has link syntax in its text]: true
+ * }
+ * }
+ */
+ this.index = null;
+}
+
+BackSubIndexer.prototype.init = function() {
+ // lazy init until first lookup
+ this.index = null;
+}
+
+BackSubIndexer.prototype._init = function() {
+ this.index = Object.create(null);
+ var self = this;
+ this.wiki.forEachTiddler(function(sourceTitle,tiddler) {
+ var newTargets = self._getTarget(tiddler);
+ $tw.utils.each(newTargets, function(target) {
+ if(!self.index[target]) {
+ self.index[target] = Object.create(null);
+ }
+ self.index[target][sourceTitle] = true;
+ });
+ });
+}
+
+BackSubIndexer.prototype.rebuild = function() {
+ this.index = null;
+}
+
+/*
+* Get things that is being referenced in the text, e.g. tiddler names in the link syntax.
+*/
+BackSubIndexer.prototype._getTarget = function(tiddler) {
+ if(this.wiki.isBinaryTiddler(tiddler.fields.text)) {
+ return [];
+ }
+ var parser = this.wiki.parseText(tiddler.fields.type, tiddler.fields.text, {});
+ if(parser) {
+ return this.wiki[this.extractor](parser.tree, tiddler.fields.title);
+ }
+ return [];
+}
+
+BackSubIndexer.prototype.update = function(updateDescriptor) {
+ // lazy init/update until first lookup
+ if(!this.index) {
+ return;
+ }
+ var newTargets = [],
+ oldTargets = [],
+ self = this;
+ if(updateDescriptor.old.exists) {
+ oldTargets = this._getTarget(updateDescriptor.old.tiddler);
+ }
+ if(updateDescriptor.new.exists) {
+ newTargets = this._getTarget(updateDescriptor.new.tiddler);
+ }
+
+ $tw.utils.each(oldTargets,function(target) {
+ if(self.index[target]) {
+ delete self.index[target][updateDescriptor.old.tiddler.fields.title];
+ }
+ });
+ $tw.utils.each(newTargets,function(target) {
+ if(!self.index[target]) {
+ self.index[target] = Object.create(null);
+ }
+ self.index[target][updateDescriptor.new.tiddler.fields.title] = true;
+ });
+}
+
+BackSubIndexer.prototype.lookup = function(title) {
+ if(!this.index) {
+ this._init();
+ }
+ if(this.index[title]) {
+ return Object.keys(this.index[title]);
+ } else {
+ return [];
+ }
+}
+
+exports.BackIndexer = BackIndexer;
diff --git a/core/modules/indexers/backlinks-index.js b/core/modules/indexers/backlinks-index.js
deleted file mode 100644
index 5902e28291..0000000000
--- a/core/modules/indexers/backlinks-index.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/*\
-title: $:/core/modules/indexers/backlinks-indexer.js
-type: application/javascript
-module-type: indexer
-
-Indexes the tiddlers' backlinks
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global modules: false */
-"use strict";
-
-
-function BacklinksIndexer(wiki) {
- this.wiki = wiki;
-}
-
-BacklinksIndexer.prototype.init = function() {
- this.index = null;
-}
-
-BacklinksIndexer.prototype.rebuild = function() {
- this.index = null;
-}
-
-BacklinksIndexer.prototype._getLinks = function(tiddler) {
- var parser = this.wiki.parseText(tiddler.fields.type, tiddler.fields.text, {});
- if(parser) {
- return this.wiki.extractLinks(parser.tree);
- }
- return [];
-}
-
-BacklinksIndexer.prototype.update = function(updateDescriptor) {
- if(!this.index) {
- return;
- }
- var newLinks = [],
- oldLinks = [],
- self = this;
- if(updateDescriptor.old.exists) {
- oldLinks = this._getLinks(updateDescriptor.old.tiddler);
- }
- if(updateDescriptor.new.exists) {
- newLinks = this._getLinks(updateDescriptor.new.tiddler);
- }
-
- $tw.utils.each(oldLinks,function(link) {
- if(self.index[link]) {
- delete self.index[link][updateDescriptor.old.tiddler.fields.title];
- }
- });
- $tw.utils.each(newLinks,function(link) {
- if(!self.index[link]) {
- self.index[link] = Object.create(null);
- }
- self.index[link][updateDescriptor.new.tiddler.fields.title] = true;
- });
-}
-
-BacklinksIndexer.prototype.lookup = function(title) {
- if(!this.index) {
- this.index = Object.create(null);
- var self = this;
- this.wiki.forEachTiddler(function(title,tiddler) {
- var links = self._getLinks(tiddler);
- $tw.utils.each(links, function(link) {
- if(!self.index[link]) {
- self.index[link] = Object.create(null);
- }
- self.index[link][title] = true;
- });
- });
- }
- if(this.index[title]) {
- return Object.keys(this.index[title]);
- } else {
- return [];
- }
-}
-
-exports.BacklinksIndexer = BacklinksIndexer;
-
-})();
diff --git a/core/modules/indexers/field-indexer.js b/core/modules/indexers/field-indexer.js
index e49583610d..3aefc99ac5 100644
--- a/core/modules/indexers/field-indexer.js
+++ b/core/modules/indexers/field-indexer.js
@@ -32,18 +32,18 @@ FieldIndexer.prototype.setMaxIndexedValueLength = function(length) {
FieldIndexer.prototype.addIndexMethods = function() {
var self = this;
+ // get all tiddlers, including those overwrite shadow tiddlers
this.wiki.each.byField = function(name,value) {
- var titles = self.wiki.allTitles(),
- lookup = self.lookup(name,value);
+ var lookup = self.lookup(name,value);
return lookup && lookup.filter(function(title) {
- return titles.indexOf(title) !== -1;
+ return self.wiki.tiddlerExists(title)
});
};
+ // get shadow tiddlers, including shadow tiddlers that is overwritten
this.wiki.eachShadow.byField = function(name,value) {
- var titles = self.wiki.allShadowTitles(),
- lookup = self.lookup(name,value);
+ var lookup = self.lookup(name,value);
return lookup && lookup.filter(function(title) {
- return titles.indexOf(title) !== -1;
+ return self.wiki.isShadowTiddler(title)
});
};
this.wiki.eachTiddlerPlusShadows.byField = function(name,value) {
diff --git a/core/modules/macros/csvtiddlers.js b/core/modules/macros/csvtiddlers.js
index 7b34ce04d2..a492fd81c9 100644
--- a/core/modules/macros/csvtiddlers.js
+++ b/core/modules/macros/csvtiddlers.js
@@ -35,9 +35,11 @@ exports.run = function(filter,format) {
// Collect all the fields
for(t=0;t"'=]+)/g,
- reUnquotedAttribute = /([^\/\s<>"'=]+)/g,
+ var reAttributeName = /([^\/\s>"'`=]+)/g,
+ reUnquotedAttribute = /([^\/\s<>"'`=]+)/g,
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g,
- reIndirectValue = /\{\{([^\}]+)\}\}/g;
+ reIndirectValue = /\{\{([^\}]+)\}\}/g,
+ reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the attribute name
@@ -302,8 +362,15 @@ exports.parseAttribute = function(source,pos) {
node.type = "macro";
node.value = macroInvocation;
} else {
- node.type = "string";
- node.value = "true";
+ var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
+ if(substitutedValue) {
+ pos = substitutedValue.end;
+ node.type = "substituted";
+ node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
+ } else {
+ node.type = "string";
+ node.value = "true";
+ }
}
}
}
diff --git a/core/modules/parsers/pdfparser.js b/core/modules/parsers/pdfparser.js
index 95d74ef4b3..c7830bf697 100644
--- a/core/modules/parsers/pdfparser.js
+++ b/core/modules/parsers/pdfparser.js
@@ -15,7 +15,7 @@ The PDF parser embeds a PDF viewer
var ImageParser = function(type,text,options) {
var element = {
type: "element",
- tag: "embed",
+ tag: "iframe",
attributes: {}
},
src;
@@ -25,6 +25,8 @@ var ImageParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:application/pdf;base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["application/pdf"] = ImageParser;
diff --git a/core/modules/parsers/textparser.js b/core/modules/parsers/textparser.js
index 4f55f6f0ca..17f9bde106 100644
--- a/core/modules/parsers/textparser.js
+++ b/core/modules/parsers/textparser.js
@@ -14,12 +14,16 @@ The plain text parser processes blocks of source text into a degenerate parse tr
var TextParser = function(type,text,options) {
this.tree = [{
- type: "codeblock",
+ type: "genesis",
attributes: {
- code: {type: "string", value: text},
- language: {type: "string", value: type}
+ $type: {name: "$type", type: "string", value: "$codeblock"},
+ code: {name: "code", type: "string", value: text},
+ language: {name: "language", type: "string", value: type},
+ $remappable: {name: "$remappable", type:"string", value: "no"}
}
}];
+ this.source = text;
+ this.type = type;
};
exports["text/plain"] = TextParser;
@@ -30,4 +34,3 @@ exports["text/css"] = TextParser;
exports["application/x-tiddler-dictionary"] = TextParser;
})();
-
diff --git a/core/modules/parsers/videoparser.js b/core/modules/parsers/videoparser.js
index f1c281c7c8..1c8a38bb27 100644
--- a/core/modules/parsers/videoparser.js
+++ b/core/modules/parsers/videoparser.js
@@ -28,6 +28,8 @@ var VideoParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["video/ogg"] = VideoParser;
diff --git a/core/modules/parsers/wikiparser/rules/codeblock.js b/core/modules/parsers/wikiparser/rules/codeblock.js
index 262038f87c..6c3480566b 100644
--- a/core/modules/parsers/wikiparser/rules/codeblock.js
+++ b/core/modules/parsers/wikiparser/rules/codeblock.js
@@ -29,13 +29,16 @@ exports.init = function(parser) {
exports.parse = function() {
var reEnd = /(\r?\n```$)/mg;
+ var languageStart = this.parser.pos + 3,
+ languageEnd = languageStart + this.match[1].length;
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;
// Look for the end of the block
reEnd.lastIndex = this.parser.pos;
var match = reEnd.exec(this.parser.source),
- text;
+ text,
+ codeStart = this.parser.pos;
// Process the block
if(match) {
text = this.parser.source.substring(this.parser.pos,match.index);
@@ -48,8 +51,8 @@ exports.parse = function() {
return [{
type: "codeblock",
attributes: {
- code: {type: "string", value: text},
- language: {type: "string", value: this.match[1]}
+ code: {type: "string", value: text, start: codeStart, end: this.parser.pos},
+ language: {type: "string", value: this.match[1], start: languageStart, end: languageEnd}
}
}];
};
diff --git a/core/modules/parsers/wikiparser/rules/codeinline.js b/core/modules/parsers/wikiparser/rules/codeinline.js
index ee91498338..048fc051c0 100644
--- a/core/modules/parsers/wikiparser/rules/codeinline.js
+++ b/core/modules/parsers/wikiparser/rules/codeinline.js
@@ -33,7 +33,8 @@ exports.parse = function() {
// Look for the end marker
reEnd.lastIndex = this.parser.pos;
var match = reEnd.exec(this.parser.source),
- text;
+ text,
+ start = this.parser.pos;
// Process the text
if(match) {
text = this.parser.source.substring(this.parser.pos,match.index);
@@ -47,7 +48,9 @@ exports.parse = function() {
tag: "code",
children: [{
type: "text",
- text: text
+ text: text,
+ start: start,
+ end: this.parser.pos
}]
}];
};
diff --git a/core/modules/parsers/wikiparser/rules/conditional.js b/core/modules/parsers/wikiparser/rules/conditional.js
new file mode 100644
index 0000000000..23940fd88a
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/conditional.js
@@ -0,0 +1,120 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/conditional.js
+type: application/javascript
+module-type: wikirule
+
+Conditional shortcut syntax
+
+```
+This is a <% if [{something}] %>Elephant<% elseif [{else}] %>Pelican<% else %>Crocodile<% endif %>
+```
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "conditional";
+exports.types = {inline: true, block: true};
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /\<\%\s*if\s+/mg;
+ this.terminateIfRegExp = /\%\>/mg;
+};
+
+exports.findNextMatch = function(startPos) {
+ // Look for the next <% if shortcut
+ this.matchRegExp.lastIndex = startPos;
+ this.match = this.matchRegExp.exec(this.parser.source);
+ // If not found then return no match
+ if(!this.match) {
+ return undefined;
+ }
+ // Check for the next %>
+ this.terminateIfRegExp.lastIndex = this.match.index;
+ this.terminateIfMatch = this.terminateIfRegExp.exec(this.parser.source);
+ // If not found then return no match
+ if(!this.terminateIfMatch) {
+ return undefined;
+ }
+ // Return the position at which the construction was found
+ return this.match.index;
+};
+
+/*
+Parse the most recent match
+*/
+exports.parse = function() {
+ // Get the filter condition
+ var filterCondition = this.parser.source.substring(this.match.index + this.match[0].length,this.terminateIfMatch.index);
+ // Advance the parser position to past the %>
+ this.parser.pos = this.terminateIfMatch.index + this.terminateIfMatch[0].length;
+ // Parse the if clause
+ return this.parseIfClause(filterCondition);
+};
+
+exports.parseIfClause = function(filterCondition) {
+ // Create the list widget
+ var listWidget = {
+ type: "list",
+ tag: "$list",
+ isBlock: this.is.block,
+ children: [
+ {
+ type: "list-template",
+ tag: "$list-template"
+ },
+ {
+ type: "list-empty",
+ tag: "$list-empty"
+ }
+ ]
+ };
+ $tw.utils.addAttributeToParseTreeNode(listWidget,"filter",filterCondition);
+ $tw.utils.addAttributeToParseTreeNode(listWidget,"variable","condition");
+ $tw.utils.addAttributeToParseTreeNode(listWidget,"limit","1");
+ // Check for an immediately following double linebreak
+ var hasLineBreak = !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
+ // Parse the body looking for else or endif
+ var reEndString = "\\<\\%\\s*(endif)\\s*\\%\\>|\\<\\%\\s*(else)\\s*\\%\\>|\\<\\%\\s*(elseif)\\s+([\\s\\S]+?)\\%\\>",
+ ex;
+ if(hasLineBreak) {
+ ex = this.parser.parseBlocksTerminatedExtended(reEndString);
+ } else {
+ var reEnd = new RegExp(reEndString,"mg");
+ ex = this.parser.parseInlineRunTerminatedExtended(reEnd,{eatTerminator: true});
+ }
+ // Put the body into the list template
+ listWidget.children[0].children = ex.tree;
+ // Check for an else or elseif
+ if(ex.match) {
+ if(ex.match[1] === "endif") {
+ // Nothing to do if we just found an endif
+ } else if(ex.match[2] === "else") {
+ // Check for an immediately following double linebreak
+ hasLineBreak = !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
+ // If we found an else then we need to parse the body looking for the endif
+ var reEndString = "\\<\\%\\s*(endif)\\s*\\%\\>",
+ ex;
+ if(hasLineBreak) {
+ ex = this.parser.parseBlocksTerminatedExtended(reEndString);
+ } else {
+ var reEnd = new RegExp(reEndString,"mg");
+ ex = this.parser.parseInlineRunTerminatedExtended(reEnd,{eatTerminator: true});
+ }
+ // Put the parsed content inside the list empty template
+ listWidget.children[1].children = ex.tree;
+ } else if(ex.match[3] === "elseif") {
+ // Parse the elseif clause by reusing this parser, passing the new filter condition
+ listWidget.children[1].children = this.parseIfClause(ex.match[4]);
+ }
+ }
+ // Return the parse tree node
+ return [listWidget];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/extlink.js b/core/modules/parsers/wikiparser/rules/extlink.js
index e06f88d8d3..5b9f57adf0 100644
--- a/core/modules/parsers/wikiparser/rules/extlink.js
+++ b/core/modules/parsers/wikiparser/rules/extlink.js
@@ -31,6 +31,7 @@ exports.init = function(parser) {
exports.parse = function() {
// Move past the match
+ var start = this.parser.pos;
this.parser.pos = this.matchRegExp.lastIndex;
// Create the link unless it is suppressed
if(this.match[0].substr(0,1) === "~") {
@@ -46,7 +47,7 @@ exports.parse = function() {
rel: {type: "string", value: "noopener noreferrer"}
},
children: [{
- type: "text", text: this.match[0]
+ type: "text", text: this.match[0], start: start, end: this.parser.pos
}]
}];
}
diff --git a/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js b/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js
index 7ab4801bf8..73bdff813d 100644
--- a/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js
+++ b/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js
@@ -31,6 +31,16 @@ exports.init = function(parser) {
exports.parse = function() {
// Move past the match
+ var filterStart = this.parser.pos + 3;
+ var filterEnd = filterStart + this.match[1].length;
+ var toolTipStart = filterEnd + 1;
+ var toolTipEnd = toolTipStart + (this.match[2] ? this.match[2].length : 0);
+ var templateStart = toolTipEnd + 2;
+ var templateEnd = templateStart + (this.match[3] ? this.match[3].length : 0);
+ var styleStart = templateEnd + 2;
+ var styleEnd = styleStart + (this.match[4] ? this.match[4].length : 0);
+ var classesStart = styleEnd + 1;
+ var classesEnd = classesStart + (this.match[5] ? this.match[5].length : 0);
this.parser.pos = this.matchRegExp.lastIndex;
// Get the match details
var filter = this.match[1],
@@ -42,21 +52,21 @@ exports.parse = function() {
var node = {
type: "list",
attributes: {
- filter: {type: "string", value: filter}
+ filter: {type: "string", value: filter, start: filterStart, end: filterEnd},
},
isBlock: true
};
if(tooltip) {
- node.attributes.tooltip = {type: "string", value: tooltip};
+ node.attributes.tooltip = {type: "string", value: tooltip, start: toolTipStart, end: toolTipEnd};
}
if(template) {
- node.attributes.template = {type: "string", value: template};
+ node.attributes.template = {type: "string", value: template, start: templateStart, end: templateEnd};
}
if(style) {
- node.attributes.style = {type: "string", value: style};
+ node.attributes.style = {type: "string", value: style, start: styleStart, end: styleEnd};
}
if(classes) {
- node.attributes.itemClass = {type: "string", value: classes.split(".").join(" ")};
+ node.attributes.itemClass = {type: "string", value: classes.split(".").join(" "), start: classesStart, end: classesEnd};
}
return [node];
};
diff --git a/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js b/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js
index 029fd68027..c0b19a941e 100644
--- a/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js
+++ b/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js
@@ -30,6 +30,16 @@ exports.init = function(parser) {
};
exports.parse = function() {
+ var filterStart = this.parser.pos + 3;
+ var filterEnd = filterStart + this.match[1].length;
+ var toolTipStart = filterEnd + 1;
+ var toolTipEnd = toolTipStart + (this.match[2] ? this.match[2].length : 0);
+ var templateStart = toolTipEnd + 2;
+ var templateEnd = templateStart + (this.match[3] ? this.match[3].length : 0);
+ var styleStart = templateEnd + 2;
+ var styleEnd = styleStart + (this.match[4] ? this.match[4].length : 0);
+ var classesStart = styleEnd + 1;
+ var classesEnd = classesStart + (this.match[5] ? this.match[5].length : 0);
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;
// Get the match details
@@ -42,20 +52,20 @@ exports.parse = function() {
var node = {
type: "list",
attributes: {
- filter: {type: "string", value: filter}
+ filter: {type: "string", value: filter, start: filterStart, end: filterEnd},
}
};
if(tooltip) {
- node.attributes.tooltip = {type: "string", value: tooltip};
+ node.attributes.tooltip = {type: "string", value: tooltip, start: toolTipStart, end: toolTipEnd};
}
if(template) {
- node.attributes.template = {type: "string", value: template};
+ node.attributes.template = {type: "string", value: template, start: templateStart, end: templateEnd};
}
if(style) {
- node.attributes.style = {type: "string", value: style};
+ node.attributes.style = {type: "string", value: style, start: styleStart, end: styleEnd};
}
if(classes) {
- node.attributes.itemClass = {type: "string", value: classes.split(".").join(" ")};
+ node.attributes.itemClass = {type: "string", value: classes.split(".").join(" "), start: classesStart, end: classesEnd};
}
return [node];
};
diff --git a/core/modules/parsers/wikiparser/rules/fnprocdef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js
new file mode 100644
index 0000000000..85bd14d5c6
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -0,0 +1,97 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/fnprocdef.js
+type: application/javascript
+module-type: wikirule
+
+Wiki pragma rule for function, procedure and widget definitions
+
+```
+\function name(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
+
+\procedure name(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
+
+\widget $mywidget(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
+```
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "fnprocdef";
+exports.types = {pragma: true};
+
+/*
+Instantiate parse rule
+*/
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*))?\)(\s*\r?\n)?/mg;
+};
+
+/*
+Parse the most recent match
+*/
+exports.parse = function() {
+ // Move past the macro name and parameters
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Parse the parameters
+ var params = [];
+ if(this.match[3]) {
+ params = $tw.utils.parseParameterDefinition(this.match[4]);
+ }
+ // Is the remainder of the line blank after the parameter close paren?
+ var reEnd;
+ if(this.match[5]) {
+ // If so, it is a multiline definition and the end of the body is marked with \end
+ reEnd = new RegExp("((:?^|\\r?\\n)[^\\S\\n\\r]*\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[2]) + ")?(?:$|\\r?\\n))","mg");
+ } else {
+ // Otherwise, the end of the definition is marked by the end of the line
+ reEnd = /($|\r?\n)/mg;
+ // Move past any whitespace
+ this.parser.pos = $tw.utils.skipWhiteSpace(this.parser.source,this.parser.pos);
+ }
+ // Find the end of the definition
+ reEnd.lastIndex = this.parser.pos;
+ var text,
+ endMatch = reEnd.exec(this.parser.source);
+ if(endMatch) {
+ text = this.parser.source.substring(this.parser.pos,endMatch.index);
+ this.parser.pos = endMatch.index + endMatch[0].length;
+ } else {
+ // We didn't find the end of the definition, so we'll make it blank
+ text = "";
+ }
+ // Save the macro definition
+ var parseTreeNodes = [{
+ type: "set",
+ attributes: {},
+ children: [],
+ params: params
+ }];
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[2]);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);
+ if(this.match[1] === "function") {
+ parseTreeNodes[0].isFunctionDefinition = true;
+ } else if(this.match[1] === "procedure") {
+ parseTreeNodes[0].isProcedureDefinition = true;
+ } else if(this.match[1] === "widget") {
+ parseTreeNodes[0].isWidgetDefinition = true;
+ }
+ if(this.parser.configTrimWhiteSpace) {
+ parseTreeNodes[0].configTrimWhiteSpace = true;
+ }
+ return parseTreeNodes;
+};
+
+})();
+
\ No newline at end of file
diff --git a/core/modules/parsers/wikiparser/rules/hardlinebreaks.js b/core/modules/parsers/wikiparser/rules/hardlinebreaks.js
index c278686b4a..94f517cd47 100644
--- a/core/modules/parsers/wikiparser/rules/hardlinebreaks.js
+++ b/core/modules/parsers/wikiparser/rules/hardlinebreaks.js
@@ -45,10 +45,11 @@ exports.parse = function() {
reEnd.lastIndex = this.parser.pos;
match = reEnd.exec(this.parser.source);
if(match) {
+ var start = this.parser.pos;
this.parser.pos = reEnd.lastIndex;
// Add a line break if the terminator was a line break
if(match[2]) {
- tree.push({type: "element", tag: "br"});
+ tree.push({type: "element", tag: "br", start: start, end: this.parser.pos});
}
}
} while(match && !match[1]);
diff --git a/core/modules/parsers/wikiparser/rules/heading.js b/core/modules/parsers/wikiparser/rules/heading.js
index de4e45c27f..7a0ecb9db9 100644
--- a/core/modules/parsers/wikiparser/rules/heading.js
+++ b/core/modules/parsers/wikiparser/rules/heading.js
@@ -30,15 +30,17 @@ exports.parse = function() {
// Move past the !s
this.parser.pos = this.matchRegExp.lastIndex;
// Parse any classes, whitespace and then the heading itself
+ var classStart = this.parser.pos;
var classes = this.parser.parseClasses();
+ var classEnd = this.parser.pos;
this.parser.skipWhitespace({treatNewlinesAsNonWhitespace: true});
var tree = this.parser.parseInlineRun(/(\r?\n)/mg);
// Return the heading
return [{
type: "element",
- tag: "h" + headingLevel,
+ tag: "h" + headingLevel,
attributes: {
- "class": {type: "string", value: classes.join(" ")}
+ "class": {type: "string", value: classes.join(" "), start: classStart, end: classEnd}
},
children: tree
}];
diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js
index 7fc4bb96e3..61c4ad9e1e 100644
--- a/core/modules/parsers/wikiparser/rules/html.js
+++ b/core/modules/parsers/wikiparser/rules/html.js
@@ -44,6 +44,10 @@ Parse the most recent match
exports.parse = function() {
// Retrieve the most recent match so that recursive calls don't overwrite it
var tag = this.nextTag;
+ if (!tag.isSelfClosing) {
+ tag.openTagStart = tag.start;
+ tag.openTagEnd = tag.end;
+ }
this.nextTag = null;
// Advance the parser position to past the tag
this.parser.pos = tag.end;
@@ -60,6 +64,27 @@ exports.parse = function() {
var reEnd = new RegExp("(" + reEndString + ")","mg");
tag.children = this.parser.parseInlineRun(reEnd,{eatTerminator: true});
}
+ tag.end = this.parser.pos;
+ tag.closeTagEnd = tag.end;
+ if (tag.closeTagEnd === tag.openTagEnd || this.parser.source[tag.closeTagEnd - 1] !== '>') {
+ tag.closeTagStart = tag.end;
+ } else {
+ tag.closeTagStart = tag.closeTagEnd - 2;
+ var closeTagMinPos = tag.children.length > 0 ? tag.children[tag.children.length-1].end : tag.openTagEnd;
+ if (!Number.isSafeInteger(closeTagMinPos)) closeTagMinPos = tag.openTagEnd;
+ while (tag.closeTagStart >= closeTagMinPos) {
+ var char = this.parser.source[tag.closeTagStart];
+ if (char === '>') {
+ tag.closeTagStart = -1;
+ break;
+ }
+ if (char === '<') break;
+ tag.closeTagStart -= 1;
+ }
+ if (tag.closeTagStart < closeTagMinPos) {
+ tag.closeTagStart = tag.end;
+ }
+ }
}
// Return the tag
return [tag];
@@ -78,7 +103,7 @@ exports.parseTag = function(source,pos,options) {
orderedAttributes: []
};
// Define our regexps
- var reTagName = /([a-zA-Z0-9\-\$]+)/g;
+ var reTagName = /([a-zA-Z0-9\-\$\.]+)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a less than sign
@@ -93,9 +118,6 @@ exports.parseTag = function(source,pos,options) {
return null;
}
node.tag = token.match[1];
- if(node.tag.slice(1).indexOf("$") !== -1) {
- return null;
- }
if(node.tag.charAt(0) === "$") {
node.type = node.tag.substr(1);
}
@@ -141,7 +163,7 @@ exports.parseTag = function(source,pos,options) {
exports.findNextTag = function(source,pos,options) {
// A regexp for finding candidate HTML tags
- var reLookahead = /<([a-zA-Z\-\$]+)/g;
+ var reLookahead = /<([a-zA-Z\-\$\.]+)/g;
// Find the next candidate
reLookahead.lastIndex = pos;
var match = reLookahead.exec(source);
diff --git a/core/modules/parsers/wikiparser/rules/image.js b/core/modules/parsers/wikiparser/rules/image.js
index 6b379d9c5d..6f58225e0c 100644
--- a/core/modules/parsers/wikiparser/rules/image.js
+++ b/core/modules/parsers/wikiparser/rules/image.js
@@ -122,9 +122,9 @@ exports.parseImage = function(source,pos) {
}
pos = token.end;
if(token.match[1]) {
- node.attributes.tooltip = {type: "string", value: token.match[1].trim()};
+ node.attributes.tooltip = {type: "string", value: token.match[1].trim(),start: token.start,end:token.start + token.match[1].length - 1};
}
- node.attributes.source = {type: "string", value: (token.match[2] || "").trim()};
+ node.attributes.source = {type: "string", value: (token.match[2] || "").trim(), start: token.start + (token.match[1] ? token.match[1].length : 0), end: token.end - 2};
// Update the end position
node.end = pos;
return node;
diff --git a/core/modules/parsers/wikiparser/rules/import.js b/core/modules/parsers/wikiparser/rules/import.js
index 9556c7af5e..bb1832255d 100644
--- a/core/modules/parsers/wikiparser/rules/import.js
+++ b/core/modules/parsers/wikiparser/rules/import.js
@@ -25,7 +25,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\import[^\S\n]/mg;
+ this.matchRegExp = /\\import[^\S\n]/mg;
};
/*
@@ -38,13 +38,14 @@ exports.parse = function() {
// Parse the filter terminated by a line break
var reMatch = /(.*)(?:$|\r?\n)/mg;
reMatch.lastIndex = this.parser.pos;
+ var filterStart = this.parser.source;
var match = reMatch.exec(this.parser.source);
this.parser.pos = reMatch.lastIndex;
// Parse tree nodes to return
return [{
type: "importvariables",
attributes: {
- filter: {type: "string", value: match[1]}
+ filter: {type: "string", value: match[1], start: filterStart, end: this.parser.pos}
},
children: []
}];
diff --git a/core/modules/parsers/wikiparser/rules/list.js b/core/modules/parsers/wikiparser/rules/list.js
index 17eab6dada..d89c201b9e 100644
--- a/core/modules/parsers/wikiparser/rules/list.js
+++ b/core/modules/parsers/wikiparser/rules/list.js
@@ -74,6 +74,7 @@ exports.parse = function() {
// Match the list marker
var reMatch = /([\*#;:>]+)/mg;
reMatch.lastIndex = this.parser.pos;
+ var start = this.parser.pos;
var match = reMatch.exec(this.parser.source);
if(!match || match.index !== this.parser.pos) {
break;
@@ -94,9 +95,21 @@ exports.parse = function() {
}
// Construct the list element or reuse the previous one at this level
if(listStack.length <= t) {
- var listElement = {type: "element", tag: listInfo.listTag, children: [
- {type: "element", tag: listInfo.itemTag, children: []}
- ]};
+ var listElement = {
+ type: "element",
+ tag: listInfo.listTag,
+ children: [
+ {
+ type: "element",
+ tag: listInfo.itemTag,
+ children: [],
+ start: start,
+ end: this.parser.pos,
+ }
+ ],
+ start: start,
+ end: this.parser.pos,
+ };
// Link this list element into the last child item of the parent list item
if(t) {
var prevListItem = listStack[t-1].children[listStack[t-1].children.length-1];
@@ -105,21 +118,33 @@ exports.parse = function() {
// Save this element in the stack
listStack[t] = listElement;
} else if(t === (match[0].length - 1)) {
- listStack[t].children.push({type: "element", tag: listInfo.itemTag, children: []});
+ listStack[t].children.push({
+ type: "element",
+ tag: listInfo.itemTag,
+ children: [],
+ start: start,
+ end: this.parser.pos,
+ });
}
}
if(listStack.length > match[0].length) {
listStack.splice(match[0].length,listStack.length - match[0].length);
}
// Process the body of the list item into the last list item
+ var classStart = this.parser.pos;
var lastListChildren = listStack[listStack.length-1].children,
lastListItem = lastListChildren[lastListChildren.length-1],
classes = this.parser.parseClasses();
+ var classEnd = this.parser.pos;
this.parser.skipWhitespace({treatNewlinesAsNonWhitespace: true});
var tree = this.parser.parseInlineRun(/(\r?\n)/mg);
lastListItem.children.push.apply(lastListItem.children,tree);
+ lastListItem.end = this.parser.pos;
+ listStack[listStack.length-1].end = this.parser.pos;
if(classes.length > 0) {
$tw.utils.addClassToParseTreeNode(lastListItem,classes.join(" "));
+ lastListItem.attributes.class.start = classStart;
+ lastListItem.attributes.class.end = classEnd;
}
// Consume any whitespace following the list item
this.parser.skipWhitespace();
diff --git a/core/modules/parsers/wikiparser/rules/macrocallblock.js b/core/modules/parsers/wikiparser/rules/macrocallblock.js
index 6f50fdbb0b..a2c10e04ab 100644
--- a/core/modules/parsers/wikiparser/rules/macrocallblock.js
+++ b/core/modules/parsers/wikiparser/rules/macrocallblock.js
@@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) {
var nextStart = startPos;
// Try parsing at all possible macrocall openers until we match
while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) {
- var nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart);
+ var nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart);
if(nextCall) {
var c = this.parser.source.charAt(nextCall.end);
// Ensure EOL after parsed macro
diff --git a/core/modules/parsers/wikiparser/rules/macrocallinline.js b/core/modules/parsers/wikiparser/rules/macrocallinline.js
index 165a70dcec..e9f79f09e6 100644
--- a/core/modules/parsers/wikiparser/rules/macrocallinline.js
+++ b/core/modules/parsers/wikiparser/rules/macrocallinline.js
@@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) {
var nextStart = startPos;
// Try parsing at all possible macrocall openers until we match
while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) {
- this.nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart);
+ this.nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart);
if(this.nextCall) {
return nextStart;
}
diff --git a/core/modules/parsers/wikiparser/rules/macrodef.js b/core/modules/parsers/wikiparser/rules/macrodef.js
index 22a7b5ba75..2001f70d56 100644
--- a/core/modules/parsers/wikiparser/rules/macrodef.js
+++ b/core/modules/parsers/wikiparser/rules/macrodef.js
@@ -27,7 +27,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\define\s+([^(\s]+)\(\s*([^)]*)\)(\s*\r?\n)?/mg;
+ this.matchRegExp = /\\define\s+([^(\s]+)\(\s*([^)]*)\)(\s*\r?\n)?/mg;
};
/*
@@ -54,11 +54,11 @@ exports.parse = function() {
paramMatch = reParam.exec(paramString);
}
}
- // Is this a multiline definition?
+ // Is the remainder of the \define line blank after the parameter close paren?
var reEnd;
if(this.match[3]) {
- // If so, the end of the body is marked with \end
- reEnd = new RegExp("(\\r?\\n\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[1]) + ")?(?:$|\\r?\\n))","mg");
+ // If so, it is a multiline definition and the end of the body is marked with \end
+ reEnd = new RegExp("((?:^|\\r?\\n)[^\\S\\n\\r]*\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[1]) + ")?(?:$|\\r?\\n))","mg");
} else {
// Otherwise, the end of the definition is marked by the end of the line
reEnd = /($|\r?\n)/mg;
@@ -77,16 +77,16 @@ exports.parse = function() {
text = "";
}
// Save the macro definition
- return [{
+ var parseTreeNodes = [{
type: "set",
- attributes: {
- name: {type: "string", value: this.match[1]},
- value: {type: "string", value: text}
- },
+ attributes: {},
children: [],
params: params,
isMacroDefinition: true
}];
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[1]);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);
+ return parseTreeNodes;
};
})();
diff --git a/core/modules/parsers/wikiparser/rules/parameters.js b/core/modules/parsers/wikiparser/rules/parameters.js
new file mode 100644
index 0000000000..60bbd89017
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/parameters.js
@@ -0,0 +1,60 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/parameters.js
+type: application/javascript
+module-type: wikirule
+
+Wiki pragma rule for parameter definitions
+
+```
+\parameters(param:defaultvalue,param2:defaultvalue)
+definition text
+```
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "parameters";
+exports.types = {pragma: true};
+
+/*
+Instantiate parse rule
+*/
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /\\parameters\s*\(([^)]*)\)(\s*\r?\n)?/mg;
+};
+
+/*
+Parse the most recent match
+*/
+exports.parse = function() {
+ // Move past the macro name and parameters
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Parse the parameters
+ var params = $tw.utils.parseParameterDefinition(this.match[1]);
+ var attributes = Object.create(null),
+ orderedAttributes = [];
+ $tw.utils.each(params,function(param) {
+ var name = param.name;
+ // Parameter names starting with dollar must be escaped to double dollars for the parameters widget
+ if(name.charAt(0) === "$") {
+ name = "$" + name;
+ }
+ var attribute = {name: name, type: "string", value: param["default"] || ""};
+ attributes[name] = attribute;
+ orderedAttributes.push(attribute);
+ });
+ // Save the macro definition
+ return [{
+ type: "parameters",
+ attributes: attributes,
+ orderedAttributes: orderedAttributes
+ }];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/parsermode.js b/core/modules/parsers/wikiparser/rules/parsermode.js
index ad4287f996..72a6de067c 100644
--- a/core/modules/parsers/wikiparser/rules/parsermode.js
+++ b/core/modules/parsers/wikiparser/rules/parsermode.js
@@ -26,7 +26,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\parsermode[^\S\n]/mg;
+ this.matchRegExp = /\\parsermode[^\S\n]/mg;
};
/*
diff --git a/core/modules/parsers/wikiparser/rules/prettyextlink.js b/core/modules/parsers/wikiparser/rules/prettyextlink.js
index 4c497c2574..4707fa0d0a 100644
--- a/core/modules/parsers/wikiparser/rules/prettyextlink.js
+++ b/core/modules/parsers/wikiparser/rules/prettyextlink.js
@@ -96,15 +96,20 @@ exports.parseLink = function(source,pos) {
splitPos = null;
}
// Pull out the tooltip and URL
- var tooltip, URL;
+ var tooltip, URL, urlStart;
+ textNode.start = pos;
if(splitPos) {
+ urlStart = splitPos + 1;
URL = source.substring(splitPos + 1,closePos).trim();
textNode.text = source.substring(pos,splitPos).trim();
+ textNode.end = splitPos;
} else {
+ urlStart = pos;
URL = source.substring(pos,closePos).trim();
textNode.text = URL;
+ textNode.end = closePos;
}
- node.attributes.href = {type: "string", value: URL};
+ node.attributes.href = {type: "string", value: URL, start: urlStart, end: closePos};
node.attributes.target = {type: "string", value: "_blank"};
node.attributes.rel = {type: "string", value: "noopener noreferrer"};
// Update the end position
diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js
index 56a2850a39..66c19dc886 100644
--- a/core/modules/parsers/wikiparser/rules/prettylink.js
+++ b/core/modules/parsers/wikiparser/rules/prettylink.js
@@ -29,32 +29,39 @@ exports.init = function(parser) {
exports.parse = function() {
// Move past the match
+ var start = this.parser.pos + 2;
this.parser.pos = this.matchRegExp.lastIndex;
// Process the link
var text = this.match[1],
- link = this.match[2] || text;
+ link = this.match[2] || text,
+ textEndPos = this.parser.source.indexOf("|", start);
+ if (textEndPos < 0 || textEndPos > this.matchRegExp.lastIndex) {
+ textEndPos = this.matchRegExp.lastIndex - 2;
+ }
+ var linkStart = this.match[2] ? (start + this.match[1].length + 1) : start;
+ var linkEnd = linkStart + link.length;
if($tw.utils.isLinkExternal(link)) {
return [{
type: "element",
tag: "a",
attributes: {
- href: {type: "string", value: link},
+ href: {type: "string", value: link, start: linkStart, end: linkEnd},
"class": {type: "string", value: "tc-tiddlylink-external"},
target: {type: "string", value: "_blank"},
rel: {type: "string", value: "noopener noreferrer"}
},
children: [{
- type: "text", text: text
+ type: "text", text: text, start: start, end: textEndPos
}]
}];
} else {
return [{
type: "link",
attributes: {
- to: {type: "string", value: link}
+ to: {type: "string", value: link, start: linkStart, end: linkEnd}
},
children: [{
- type: "text", text: text
+ type: "text", text: text, start: start, end: textEndPos
}]
}];
}
diff --git a/core/modules/parsers/wikiparser/rules/quoteblock.js b/core/modules/parsers/wikiparser/rules/quoteblock.js
index 71b6896800..fdd6c860b7 100644
--- a/core/modules/parsers/wikiparser/rules/quoteblock.js
+++ b/core/modules/parsers/wikiparser/rules/quoteblock.js
@@ -3,30 +3,7 @@ title: $:/core/modules/parsers/wikiparser/rules/quoteblock.js
type: application/javascript
module-type: wikirule
-Wiki text rule for quote blocks. For example:
-
-```
- <<<.optionalClass(es) optional cited from
- a quote
- <<<
-
- <<<.optionalClass(es)
- a quote
- <<< optional cited from
-```
-
-Quotes can be quoted by putting more 0) {
tree.unshift({
type: "element",
tag: "cite",
- children: cite
+ children: cite,
+ start: citeStart,
+ end: citeEnd
});
}
// Parse any optional cite
this.parser.skipWhitespace({treatNewlinesAsNonWhitespace: true});
+ citeStart = this.parser.pos;
cite = this.parser.parseInlineRun(/(\r?\n)/mg);
+ citeEnd = this.parser.pos;
// If we got a cite, push it
if(cite.length > 0) {
tree.push({
type: "element",
tag: "cite",
- children: cite
+ children: cite,
+ start: citeStart,
+ end: citeEnd
});
}
// Return the blockquote element
@@ -81,7 +67,7 @@ exports.parse = function() {
type: "element",
tag: "blockquote",
attributes: {
- class: { type: "string", value: classes.join(" ") },
+ class: { type: "string", value: classes.join(" "), start: classStart, end: classEnd },
},
children: tree
}];
diff --git a/core/modules/parsers/wikiparser/rules/rules.js b/core/modules/parsers/wikiparser/rules/rules.js
index cfc03330b3..1d0f80f29b 100644
--- a/core/modules/parsers/wikiparser/rules/rules.js
+++ b/core/modules/parsers/wikiparser/rules/rules.js
@@ -26,7 +26,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\rules[^\S\n]/mg;
+ this.matchRegExp = /\\rules[^\S\n]/mg;
};
/*
diff --git a/core/modules/parsers/wikiparser/rules/syslink.js b/core/modules/parsers/wikiparser/rules/syslink.js
index 6eb2cdcd44..6bcbee3848 100644
--- a/core/modules/parsers/wikiparser/rules/syslink.js
+++ b/core/modules/parsers/wikiparser/rules/syslink.js
@@ -29,10 +29,11 @@ exports.init = function(parser) {
exports.parse = function() {
var match = this.match[0];
// Move past the match
+ var start = this.parser.pos;
this.parser.pos = this.matchRegExp.lastIndex;
// Create the link unless it is suppressed
if(match.substr(0,1) === "~") {
- return [{type: "text", text: match.substr(1)}];
+ return [{type: "text", text: match.substr(1), start: start+1, end: this.parser.pos}];
} else {
return [{
type: "link",
@@ -41,10 +42,12 @@ exports.parse = function() {
},
children: [{
type: "text",
- text: match
+ text: match,
+ start: start,
+ end: this.parser.pos
}]
}];
}
};
-})();
\ No newline at end of file
+})();
diff --git a/core/modules/parsers/wikiparser/rules/table.js b/core/modules/parsers/wikiparser/rules/table.js
index 61cd719482..59aa81e913 100644
--- a/core/modules/parsers/wikiparser/rules/table.js
+++ b/core/modules/parsers/wikiparser/rules/table.js
@@ -150,7 +150,7 @@ exports.parse = function() {
} else {
// Otherwise, create a new row if this one is of a different type
if(rowType !== currRowType) {
- rowContainer = {type: "element", tag: rowContainerTypes[rowType], children: []};
+ rowContainer = {type: "element", tag: rowContainerTypes[rowType], children: [], start: this.parser.pos, end: this.parser.pos};
table.children.push(rowContainer);
currRowType = rowType;
}
@@ -178,6 +178,7 @@ exports.parse = function() {
// Increment the row count
rowCount++;
}
+ rowContainer.end = this.parser.pos;
}
rowMatch = rowRegExp.exec(this.parser.source);
}
diff --git a/core/modules/parsers/wikiparser/rules/transcludeblock.js b/core/modules/parsers/wikiparser/rules/transcludeblock.js
index 56a4f63b8f..c033c24409 100644
--- a/core/modules/parsers/wikiparser/rules/transcludeblock.js
+++ b/core/modules/parsers/wikiparser/rules/transcludeblock.js
@@ -23,7 +23,7 @@ exports.types = {block: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?\}\}(?:\r?\n|$)/mg;
+ this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg;
};
exports.parse = function() {
@@ -31,13 +31,22 @@ exports.parse = function() {
this.parser.pos = this.matchRegExp.lastIndex;
// Get the match details
var template = $tw.utils.trim(this.match[2]),
- textRef = $tw.utils.trim(this.match[1]);
+ textRef = $tw.utils.trim(this.match[1]),
+ params = this.match[3] ? this.match[3].split("|") : [];
// Prepare the transclude widget
var transcludeNode = {
type: "transclude",
attributes: {},
isBlock: true
};
+ $tw.utils.each(params,function(paramValue,index) {
+ var name = "" + index;
+ transcludeNode.attributes[name] = {
+ name: name,
+ type: "string",
+ value: paramValue
+ }
+ });
// Prepare the tiddler widget
var tr, targetTitle, targetField, targetIndex, tiddlerNode;
if(textRef) {
@@ -48,14 +57,14 @@ exports.parse = function() {
tiddlerNode = {
type: "tiddler",
attributes: {
- tiddler: {type: "string", value: targetTitle}
+ tiddler: {name: "tiddler", type: "string", value: targetTitle}
},
isBlock: true,
children: [transcludeNode]
};
}
if(template) {
- transcludeNode.attributes.tiddler = {type: "string", value: template};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: template};
if(textRef) {
return [tiddlerNode];
} else {
@@ -63,12 +72,12 @@ exports.parse = function() {
}
} else {
if(textRef) {
- transcludeNode.attributes.tiddler = {type: "string", value: targetTitle};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: targetTitle};
if(targetField) {
- transcludeNode.attributes.field = {type: "string", value: targetField};
+ transcludeNode.attributes["$field"] = {name: "$field", type: "string", value: targetField};
}
if(targetIndex) {
- transcludeNode.attributes.index = {type: "string", value: targetIndex};
+ transcludeNode.attributes["$index"] = {name: "$index", type: "string", value: targetIndex};
}
return [tiddlerNode];
} else {
diff --git a/core/modules/parsers/wikiparser/rules/transcludeinline.js b/core/modules/parsers/wikiparser/rules/transcludeinline.js
index dbf39bfb6b..3ce9dc78e6 100644
--- a/core/modules/parsers/wikiparser/rules/transcludeinline.js
+++ b/core/modules/parsers/wikiparser/rules/transcludeinline.js
@@ -23,7 +23,7 @@ exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?\}\}/mg;
+ this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg;
};
exports.parse = function() {
@@ -31,12 +31,21 @@ exports.parse = function() {
this.parser.pos = this.matchRegExp.lastIndex;
// Get the match details
var template = $tw.utils.trim(this.match[2]),
- textRef = $tw.utils.trim(this.match[1]);
+ textRef = $tw.utils.trim(this.match[1]),
+ params = this.match[3] ? this.match[3].split("|") : [];
// Prepare the transclude widget
var transcludeNode = {
type: "transclude",
attributes: {}
};
+ $tw.utils.each(params,function(paramValue,index) {
+ var name = "" + index;
+ transcludeNode.attributes[name] = {
+ name: name,
+ type: "string",
+ value: paramValue
+ }
+ });
// Prepare the tiddler widget
var tr, targetTitle, targetField, targetIndex, tiddlerNode;
if(textRef) {
@@ -47,13 +56,13 @@ exports.parse = function() {
tiddlerNode = {
type: "tiddler",
attributes: {
- tiddler: {type: "string", value: targetTitle}
+ tiddler: {name: "tiddler", type: "string", value: targetTitle}
},
children: [transcludeNode]
};
}
if(template) {
- transcludeNode.attributes.tiddler = {type: "string", value: template};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: template};
if(textRef) {
return [tiddlerNode];
} else {
@@ -61,12 +70,12 @@ exports.parse = function() {
}
} else {
if(textRef) {
- transcludeNode.attributes.tiddler = {type: "string", value: targetTitle};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: targetTitle};
if(targetField) {
- transcludeNode.attributes.field = {type: "string", value: targetField};
+ transcludeNode.attributes["$field"] = {name: "$field", type: "string", value: targetField};
}
if(targetIndex) {
- transcludeNode.attributes.index = {type: "string", value: targetIndex};
+ transcludeNode.attributes["$index"] = {name: "$index", type: "string", value: targetIndex};
}
return [tiddlerNode];
} else {
diff --git a/core/modules/parsers/wikiparser/rules/typedblock.js b/core/modules/parsers/wikiparser/rules/typedblock.js
index 4195e57e52..07c88be15f 100644
--- a/core/modules/parsers/wikiparser/rules/typedblock.js
+++ b/core/modules/parsers/wikiparser/rules/typedblock.js
@@ -46,6 +46,7 @@ exports.parse = function() {
renderType = this.match[2];
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;
+ var start = this.parser.pos;
// Look for the end of the block
reEnd.lastIndex = this.parser.pos;
var match = reEnd.exec(this.parser.source),
@@ -74,7 +75,9 @@ exports.parse = function() {
tag: "pre",
children: [{
type: "text",
- text: text
+ text: text,
+ start: start,
+ end: this.parser.pos
}]
}];
}
diff --git a/core/modules/parsers/wikiparser/rules/whitespace.js b/core/modules/parsers/wikiparser/rules/whitespace.js
index e3b0c8e3f5..5fd9d4b833 100644
--- a/core/modules/parsers/wikiparser/rules/whitespace.js
+++ b/core/modules/parsers/wikiparser/rules/whitespace.js
@@ -26,7 +26,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\whitespace[^\S\n]/mg;
+ this.matchRegExp = /\\whitespace[^\S\n]/mg;
};
/*
diff --git a/core/modules/parsers/wikiparser/rules/wikilink.js b/core/modules/parsers/wikiparser/rules/wikilink.js
index fadc4587ee..6b195f9ff9 100644
--- a/core/modules/parsers/wikiparser/rules/wikilink.js
+++ b/core/modules/parsers/wikiparser/rules/wikilink.js
@@ -36,6 +36,7 @@ exports.parse = function() {
// Get the details of the match
var linkText = this.match[0];
// Move past the macro call
+ var start = this.parser.pos;
this.parser.pos = this.matchRegExp.lastIndex;
// If the link starts with the unwikilink character then just output it as plain text
if(linkText.substr(0,1) === $tw.config.textPrimitives.unWikiLink) {
@@ -57,7 +58,9 @@ exports.parse = function() {
},
children: [{
type: "text",
- text: linkText
+ text: linkText,
+ start: start,
+ end: this.parser.pos
}]
}];
};
diff --git a/core/modules/parsers/wikiparser/rules/wikilinkprefix.js b/core/modules/parsers/wikiparser/rules/wikilinkprefix.js
new file mode 100644
index 0000000000..60cb3d992c
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/wikilinkprefix.js
@@ -0,0 +1,40 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/wikilinkprefix.js
+type: application/javascript
+module-type: wikirule
+
+Wiki text inline rule for suppressed wiki links. For example:
+
+```
+~SuppressedLink
+```
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "wikilinkprefix";
+exports.types = {inline: true};
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = new RegExp($tw.config.textPrimitives.unWikiLink + $tw.config.textPrimitives.wikiLink,"mg");
+};
+
+/*
+Parse the most recent match
+*/
+exports.parse = function() {
+ // Get the details of the match
+ var linkText = this.match[0];
+ // Move past the wikilink
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Return the link without unwikilink character as plain text
+ return [{type: "text", text: linkText.substr(1)}];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/wikiparser.js b/core/modules/parsers/wikiparser/wikiparser.js
index 4c7419030d..854171d197 100644
--- a/core/modules/parsers/wikiparser/wikiparser.js
+++ b/core/modules/parsers/wikiparser/wikiparser.js
@@ -32,6 +32,7 @@ options: see below:
parseAsInline: true to parse text as inline instead of block
wiki: reference to wiki to use
_canonical_uri: optional URI of content if text is missing or empty
+ configTrimWhiteSpace: true to trim whitespace
*/
var WikiParser = function(type,text,options) {
this.wiki = options.wiki;
@@ -46,7 +47,7 @@ var WikiParser = function(type,text,options) {
this.source = text || "";
this.sourceLength = this.source.length;
// Flag for ignoring whitespace
- this.configTrimWhiteSpace = false;
+ this.configTrimWhiteSpace = options.configTrimWhiteSpace !== undefined ? options.configTrimWhiteSpace : false;
// Parser mode
this.parseAsInline = options.parseAsInline;
// Set current parse position
@@ -90,6 +91,11 @@ var WikiParser = function(type,text,options) {
} else {
topBranch.push.apply(topBranch,this.parseBlocks());
}
+ // Build rules' name map
+ this.usingRuleMap = {};
+ $tw.utils.each(this.pragmaRules, function (ruleInfo) { self.usingRuleMap[ruleInfo.rule.name] = Object.getPrototypeOf(ruleInfo.rule); });
+ $tw.utils.each(this.blockRules, function (ruleInfo) { self.usingRuleMap[ruleInfo.rule.name] = Object.getPrototypeOf(ruleInfo.rule); });
+ $tw.utils.each(this.inlineRules, function (ruleInfo) { self.usingRuleMap[ruleInfo.rule.name] = Object.getPrototypeOf(ruleInfo.rule); });
// Return the parse tree
};
@@ -193,6 +199,7 @@ Parse any pragmas at the beginning of a block of parse text
WikiParser.prototype.parsePragmas = function() {
var currentTreeBranch = this.tree;
while(true) {
+ var savedPos = this.pos;
// Skip whitespace
this.skipWhitespace();
// Check for the end of the text
@@ -203,16 +210,24 @@ WikiParser.prototype.parsePragmas = function() {
var nextMatch = this.findNextMatch(this.pragmaRules,this.pos);
// If not, just exit
if(!nextMatch || nextMatch.matchIndex !== this.pos) {
+ this.pos = savedPos;
break;
}
// Process the pragma rule
+ var start = this.pos;
var subTree = nextMatch.rule.parse();
if(subTree.length > 0) {
+ // Set the start and end positions of the pragma rule if
+ if (subTree[0].start === undefined) subTree[0].start = start;
+ if (subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos;
+ $tw.utils.each(subTree, function (node) { node.rule = nextMatch.rule.name; });
// Quick hack; we only cope with a single parse tree node being returned, which is true at the moment
currentTreeBranch.push.apply(currentTreeBranch,subTree);
subTree[0].children = [];
currentTreeBranch = subTree[0].children;
}
+ // Skip whitespace after the pragma
+ this.skipWhitespace();
}
return currentTreeBranch;
};
@@ -222,7 +237,7 @@ Parse a block from the current position
terminatorRegExpString: optional regular expression string that identifies the end of plain paragraphs. Must not include capturing parenthesis
*/
WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
- var terminatorRegExp = terminatorRegExpString ? new RegExp("(" + terminatorRegExpString + "|\\r?\\n\\r?\\n)","mg") : /(\r?\n\r?\n)/mg;
+ var terminatorRegExp = terminatorRegExpString ? new RegExp(terminatorRegExpString + "|\\r?\\n\\r?\\n","mg") : /(\r?\n\r?\n)/mg;
this.skipWhitespace();
if(this.pos >= this.sourceLength) {
return [];
@@ -230,7 +245,15 @@ WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
// Look for a block rule that applies at the current position
var nextMatch = this.findNextMatch(this.blockRules,this.pos);
if(nextMatch && nextMatch.matchIndex === this.pos) {
- return nextMatch.rule.parse();
+ var start = this.pos;
+ var subTree = nextMatch.rule.parse();
+ // Set the start and end positions of the first and last blocks if they're not already set
+ if (subTree.length > 0) {
+ if (subTree[0].start === undefined) subTree[0].start = start;
+ if (subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos;
+ }
+ $tw.utils.each(subTree, function (node) { node.rule = nextMatch.rule.name; });
+ return subTree;
}
// Treat it as a paragraph if we didn't find a block rule
var start = this.pos;
@@ -263,11 +286,21 @@ WikiParser.prototype.parseBlocksUnterminated = function() {
};
/*
-Parse blocks of text until a terminating regexp is encountered
+Parse blocks of text until a terminating regexp is encountered. Wrapper for parseBlocksTerminatedExtended that just returns the parse tree
*/
WikiParser.prototype.parseBlocksTerminated = function(terminatorRegExpString) {
- var terminatorRegExp = new RegExp("(" + terminatorRegExpString + ")","mg"),
- tree = [];
+ var ex = this.parseBlocksTerminatedExtended(terminatorRegExpString);
+ return ex.tree;
+};
+
+/*
+Parse blocks of text until a terminating regexp is encountered
+*/
+WikiParser.prototype.parseBlocksTerminatedExtended = function(terminatorRegExpString) {
+ var terminatorRegExp = new RegExp(terminatorRegExpString,"mg"),
+ result = {
+ tree: []
+ };
// Skip any whitespace
this.skipWhitespace();
// Check if we've got the end marker
@@ -276,7 +309,7 @@ WikiParser.prototype.parseBlocksTerminated = function(terminatorRegExpString) {
// Parse the text into blocks
while(this.pos < this.sourceLength && !(match && match.index === this.pos)) {
var blocks = this.parseBlock(terminatorRegExpString);
- tree.push.apply(tree,blocks);
+ result.tree.push.apply(result.tree,blocks);
// Skip any whitespace
this.skipWhitespace();
// Check if we've got the end marker
@@ -285,8 +318,9 @@ WikiParser.prototype.parseBlocksTerminated = function(terminatorRegExpString) {
}
if(match && match.index === this.pos) {
this.pos = match.index + match[0].length;
+ result.match = match;
}
- return tree;
+ return result;
};
/*
@@ -316,7 +350,16 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
this.pos = nextMatch.matchIndex;
}
// Process the run rule
- tree.push.apply(tree,nextMatch.rule.parse());
+ var start = this.pos;
+ var subTree = nextMatch.rule.parse();
+ // Set the start and end positions of the first and last child if they're not already set
+ if (subTree.length > 0) {
+ // Set the start and end positions of the first and last child if they're not already set
+ if (subTree[0].start === undefined) subTree[0].start = start;
+ if (subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos;
+ }
+ $tw.utils.each(subTree, function (node) { node.rule = nextMatch.rule.name; });
+ tree.push.apply(tree,subTree);
// Look for the next run rule
nextMatch = this.findNextMatch(this.inlineRules,this.pos);
}
@@ -329,6 +372,11 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
};
WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,options) {
+ var ex = this.parseInlineRunTerminatedExtended(terminatorRegExp,options);
+ return ex.tree;
+};
+
+WikiParser.prototype.parseInlineRunTerminatedExtended = function(terminatorRegExp,options) {
options = options || {};
var tree = [];
// Find the next occurrence of the terminator
@@ -348,7 +396,10 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
if(options.eatTerminator) {
this.pos += terminatorMatch[0].length;
}
- return tree;
+ return {
+ match: terminatorMatch,
+ tree: tree
+ };
}
}
// Process any inline rule, along with the text preceding it
@@ -359,7 +410,15 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
this.pos = inlineRuleMatch.matchIndex;
}
// Process the inline rule
- tree.push.apply(tree,inlineRuleMatch.rule.parse());
+ var start = this.pos;
+ var subTree = inlineRuleMatch.rule.parse();
+ // Set the start and end positions of the first and last child if they're not already set
+ if (subTree.length > 0) {
+ if (subTree[0].start === undefined) subTree[0].start = start;
+ if (subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos;
+ }
+ $tw.utils.each(subTree, function (node) { node.rule = inlineRuleMatch.rule.name; });
+ tree.push.apply(tree,subTree);
// Look for the next inline rule
inlineRuleMatch = this.findNextMatch(this.inlineRules,this.pos);
// Look for the next terminator match
@@ -372,7 +431,9 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
this.pushTextWidget(tree,this.source.substr(this.pos),this.pos,this.sourceLength);
}
this.pos = this.sourceLength;
- return tree;
+ return {
+ tree: tree
+ };
};
/*
@@ -383,7 +444,7 @@ WikiParser.prototype.pushTextWidget = function(array,text,start,end) {
text = $tw.utils.trim(text);
}
if(text) {
- array.push({type: "text", text: text, start: start, end: end});
+ array.push({type: "text", text: text, start: start, end: end});
}
};
@@ -436,4 +497,3 @@ WikiParser.prototype.amendRules = function(type,names) {
exports["text/vnd.tiddlywiki"] = WikiParser;
})();
-
diff --git a/core/modules/saver-handler.js b/core/modules/saver-handler.js
index 119c3e67a3..23056bcc2c 100644
--- a/core/modules/saver-handler.js
+++ b/core/modules/saver-handler.js
@@ -95,6 +95,7 @@ function SaverHandler(options) {
if($tw.browser) {
$tw.rootWidget.addEventListener("tm-save-wiki",function(event) {
self.saveWiki({
+ wiki: event.widget.wiki,
template: event.param,
downloadType: "text/plain",
variables: event.paramObject
@@ -102,6 +103,7 @@ function SaverHandler(options) {
});
$tw.rootWidget.addEventListener("tm-download-file",function(event) {
self.saveWiki({
+ wiki: event.widget.wiki,
method: "download",
template: event.param,
downloadType: "text/plain",
@@ -147,20 +149,22 @@ Save the wiki contents. Options are:
method: "save", "autosave" or "download"
template: the tiddler containing the template to save
downloadType: the content type for the saved file
+ wiki: optional wiki, overriding the default wiki specified in the constructor
*/
SaverHandler.prototype.saveWiki = function(options) {
options = options || {};
var self = this,
+ wiki = options.wiki || this.wiki,
method = options.method || "save";
// Ignore autosave if disabled
- if(method === "autosave" && ($tw.config.disableAutoSave || this.wiki.getTiddlerText(this.titleAutoSave,"yes") !== "yes")) {
+ if(method === "autosave" && ($tw.config.disableAutoSave || wiki.getTiddlerText(this.titleAutoSave,"yes") !== "yes")) {
return false;
}
var variables = options.variables || {},
template = (options.template ||
- this.wiki.getTiddlerText("$:/config/SaveWikiButton/Template","$:/core/save/all")).trim(),
+ wiki.getTiddlerText("$:/config/SaveWikiButton/Template","$:/core/save/all")).trim(),
downloadType = options.downloadType || "text/plain",
- text = this.wiki.renderTiddler(downloadType,template,options),
+ text = wiki.renderTiddler(downloadType,template,options),
callback = function(err) {
if(err) {
alert($tw.language.getString("Error/WhileSaving") + ":\n\n" + err);
diff --git a/core/modules/savers/beaker.js b/core/modules/savers/beaker.js
deleted file mode 100644
index dc24ef67f1..0000000000
--- a/core/modules/savers/beaker.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/*\
-title: $:/core/modules/savers/beaker.js
-type: application/javascript
-module-type: saver
-
-Saves files using the Beaker browser's (https://beakerbrowser.com) Dat protocol (https://datproject.org/)
-Compatible with beaker >= V0.7.2
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-/*
-Set up the saver
-*/
-var BeakerSaver = function(wiki) {
- this.wiki = wiki;
-};
-
-BeakerSaver.prototype.save = function(text,method,callback) {
- var dat = new DatArchive("" + window.location),
- pathname = ("" + window.location.pathname).split("#")[0];
- dat.stat(pathname).then(function(value) {
- if(value.isDirectory()) {
- pathname = pathname + "/index.html";
- }
- dat.writeFile(pathname,text,"utf8").then(function(value) {
- callback(null);
- },function(reason) {
- callback("Beaker Saver Write Error: " + reason);
- });
- },function(reason) {
- callback("Beaker Saver Stat Error: " + reason);
- });
- return true;
-};
-
-/*
-Information about this saver
-*/
-BeakerSaver.prototype.info = {
- name: "beaker",
- priority: 3000,
- capabilities: ["save", "autosave"]
-};
-
-/*
-Static method that returns true if this saver is capable of working
-*/
-exports.canSave = function(wiki) {
- return !!window.DatArchive && location.protocol==="dat:";
-};
-
-/*
-Create an instance of this saver
-*/
-exports.create = function(wiki) {
- return new BeakerSaver(wiki);
-};
-
-})();
diff --git a/core/modules/savers/github.js b/core/modules/savers/github.js
index f9b87263d4..c0a34f2d64 100644
--- a/core/modules/savers/github.js
+++ b/core/modules/savers/github.js
@@ -31,7 +31,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
headers = {
"Accept": "application/vnd.github.v3+json",
"Content-Type": "application/json;charset=UTF-8",
- "Authorization": "Basic " + window.btoa(username + ":" + password),
+ "Authorization": "Basic " + $tw.utils.base64Encode(username + ":" + password),
"If-None-Match": ""
};
// Bail if we don't have everything we need
diff --git a/core/modules/savers/hyperdrive.js b/core/modules/savers/hyperdrive.js
deleted file mode 100644
index 2323926728..0000000000
--- a/core/modules/savers/hyperdrive.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/*\
-title: $:/core/modules/savers/hyperdrive.js
-type: application/javascript
-module-type: saver
-
-Saves files using the Hyperdrive Protocol (https://hypercore-protocol.org/#hyperdrive) Beaker browser beta-1.0 and later (https://beakerbrowser.com)
-Compatible with beaker >= V1.0.0
-
-\*/
-(function(){
-
-/*jslint node: true, browser: true */
-/*global $tw: false */
-"use strict";
-
-/*
-Set up the saver
-*/
-var HyperdriveSaver = function(wiki) {
- this.wiki = wiki;
-};
-
-HyperdriveSaver.prototype.save = function(text,method,callback) {
- var dat = beaker.hyperdrive.drive("" + window.location),
- pathname = ("" + window.location.pathname).split("#")[0];
- dat.stat(pathname).then(function(value) {
- if(value.isDirectory()) {
- pathname = pathname + "/index.html";
- }
- dat.writeFile(pathname,text,"utf8").then(function(value) {
- callback(null);
- },function(reason) {
- callback("Hyperdrive Saver Write Error: " + reason);
- });
- },function(reason) {
- callback("Hyperdrive Saver Stat Error: " + reason);
- });
- return true;
-};
-
-/*
-Information about this saver
-*/
-HyperdriveSaver.prototype.info = {
- name: "beaker-1.x",
- priority: 3000,
- capabilities: ["save", "autosave"]
-};
-
-/*
-Static method that returns true if this saver is capable of working
-*/
-exports.canSave = function(wiki) {
- return !!window.beaker && !!beaker.hyperdrive && location.protocol==="hyper:";
-};
-
-/*
-Create an instance of this saver
-*/
-exports.create = function(wiki) {
- return new HyperdriveSaver(wiki);
-};
-
-})();
diff --git a/core/modules/server/authenticators/header.js b/core/modules/server/authenticators/header.js
index 78ae6cb0ad..cc1d6bdaf8 100644
--- a/core/modules/server/authenticators/header.js
+++ b/core/modules/server/authenticators/header.js
@@ -37,7 +37,9 @@ HeaderAuthenticator.prototype.authenticateRequest = function(request,response,st
return false;
} else {
// authenticatedUsername will be undefined for anonymous users
- state.authenticatedUsername = username;
+ if(username) {
+ state.authenticatedUsername = $tw.utils.decodeURIComponentSafe(username);
+ }
return true;
}
};
diff --git a/core/modules/server/server.js b/core/modules/server/server.js
index 258ddfa317..d3c98f8fc2 100644
--- a/core/modules/server/server.js
+++ b/core/modules/server/server.js
@@ -140,6 +140,11 @@ function sendResponse(request,response,statusCode,headers,data,encoding) {
return;
}
}
+ } else {
+ // RFC 7231, 6.1. Overview of Status Codes:
+ // Browser clients may cache 200, 203, 204, 206, 300, 301,
+ // 404, 405, 410, 414, and 501 unless given explicit cache controls
+ headers["Cache-Control"] = headers["Cache-Control"] || "no-store";
}
/*
If the gzip=yes is set, check if the user agent permits compression. If so,
diff --git a/core/modules/startup/plugins.js b/core/modules/startup/plugins.js
index cad61b1045..fc8ba95894 100644
--- a/core/modules/startup/plugins.js
+++ b/core/modules/startup/plugins.js
@@ -15,6 +15,7 @@ Startup logic concerned with managing plugins
// Export name and synchronous status
exports.name = "plugins";
exports.after = ["load-modules"];
+exports.before = ["startup"];
exports.synchronous = true;
var TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE = "$:/status/RequireReloadDueToPluginChange";
@@ -60,7 +61,7 @@ exports.startup = function() {
// Collect the shadow tiddlers of any modified plugins
$tw.utils.each(changes.modifiedPlugins,function(pluginTitle) {
var pluginInfo = $tw.wiki.getPluginInfo(pluginTitle);
- if(pluginInfo) {
+ if(pluginInfo && pluginInfo.tiddlers) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = false;
});
diff --git a/core/modules/startup/render.js b/core/modules/startup/render.js
index e505124639..7206a51d02 100644
--- a/core/modules/startup/render.js
+++ b/core/modules/startup/render.js
@@ -29,7 +29,11 @@ var THROTTLE_REFRESH_TIMEOUT = 400;
exports.startup = function() {
// Set up the title
- $tw.titleWidgetNode = $tw.wiki.makeTranscludeWidget(PAGE_TITLE_TITLE,{document: $tw.fakeDocument, parseAsInline: true});
+ $tw.titleWidgetNode = $tw.wiki.makeTranscludeWidget(PAGE_TITLE_TITLE, {
+ document: $tw.fakeDocument,
+ parseAsInline: true,
+ importPageMacros: true,
+ });
$tw.titleContainer = $tw.fakeDocument.createElement("div");
$tw.titleWidgetNode.render($tw.titleContainer,null);
document.title = $tw.titleContainer.textContent;
@@ -81,6 +85,8 @@ exports.startup = function() {
deferredChanges = Object.create(null);
$tw.hooks.invokeHook("th-page-refreshed");
}
+ var throttledRefresh = $tw.perf.report("throttledRefresh",refresh);
+
// Add the change event handler
$tw.wiki.addEventListener("change",$tw.perf.report("mainRefresh",function(changes) {
// Check if only tiddlers that are throttled have changed
@@ -101,7 +107,7 @@ exports.startup = function() {
if(isNaN(timeout)) {
timeout = THROTTLE_REFRESH_TIMEOUT;
}
- timerId = setTimeout(refresh,timeout);
+ timerId = setTimeout(throttledRefresh,timeout);
$tw.utils.extend(deferredChanges,changes);
} else {
$tw.utils.extend(deferredChanges,changes);
diff --git a/core/modules/startup/rootwidget.js b/core/modules/startup/rootwidget.js
index 41f3fe03f3..d81e07aee2 100644
--- a/core/modules/startup/rootwidget.js
+++ b/core/modules/startup/rootwidget.js
@@ -20,6 +20,44 @@ exports.before = ["story"];
exports.synchronous = true;
exports.startup = function() {
+ // Install the HTTP client event handler
+ $tw.httpClient = new $tw.utils.HttpClient();
+ var getPropertiesWithPrefix = function(properties,prefix) {
+ var result = Object.create(null);
+ $tw.utils.each(properties,function(value,name) {
+ if(name.indexOf(prefix) === 0) {
+ result[name.substring(prefix.length)] = properties[name];
+ }
+ });
+ return result;
+ };
+ $tw.rootWidget.addEventListener("tm-http-request",function(event) {
+ var params = event.paramObject || {};
+ $tw.httpClient.initiateHttpRequest({
+ wiki: event.widget.wiki,
+ url: params.url,
+ method: params.method,
+ body: params.body,
+ binary: params.binary,
+ useDefaultHeaders: params.useDefaultHeaders,
+ oncompletion: params.oncompletion,
+ onprogress: params.onprogress,
+ bindStatus: params["bind-status"],
+ bindProgress: params["bind-progress"],
+ variables: getPropertiesWithPrefix(params,"var-"),
+ headers: getPropertiesWithPrefix(params,"header-"),
+ passwordHeaders: getPropertiesWithPrefix(params,"password-header-"),
+ queryStrings: getPropertiesWithPrefix(params,"query-"),
+ passwordQueryStrings: getPropertiesWithPrefix(params,"password-query-"),
+ basicAuthUsername: params["basic-auth-username"],
+ basicAuthUsernameFromStore: params["basic-auth-username-from-store"],
+ basicAuthPassword: params["basic-auth-password"],
+ basicAuthPasswordFromStore: params["basic-auth-password-from-store"]
+ });
+ });
+ $tw.rootWidget.addEventListener("tm-http-cancel-all-requests",function(event) {
+ $tw.httpClient.cancelAllHttpRequests();
+ });
// Install the modal message mechanism
$tw.modal = new $tw.utils.Modal($tw.wiki);
$tw.rootWidget.addEventListener("tm-modal",function(event) {
@@ -35,18 +73,17 @@ exports.startup = function() {
});
// Install the copy-to-clipboard mechanism
$tw.rootWidget.addEventListener("tm-copy-to-clipboard",function(event) {
- $tw.utils.copyToClipboard(event.param);
+ $tw.utils.copyToClipboard(event.param,{
+ successNotification: event.paramObject && event.paramObject.successNotification,
+ failureNotification: event.paramObject && event.paramObject.failureNotification
+ });
});
// Install the tm-focus-selector message
$tw.rootWidget.addEventListener("tm-focus-selector",function(event) {
var selector = event.param || "",
element,
- doc = event.event && event.event.target ? event.event.target.ownerDocument : document;
- try {
- element = doc.querySelector(selector);
- } catch(e) {
- console.log("Error in selector: ",selector)
- }
+ baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document;
+ element = $tw.utils.querySelectorSafe(selector,baseElement);
if(element && element.focus) {
element.focus(event.paramObject);
}
@@ -87,13 +124,6 @@ exports.startup = function() {
}
});
}
- // If we're being viewed on a data: URI then give instructions for how to save
- if(document.location.protocol === "data:") {
- $tw.rootWidget.dispatchEvent({
- type: "tm-modal",
- param: "$:/language/Modals/SaveInstructions"
- });
- }
};
})();
diff --git a/core/modules/startup/startup.js b/core/modules/startup/startup.js
index e0990228fb..e09f6393fd 100755
--- a/core/modules/startup/startup.js
+++ b/core/modules/startup/startup.js
@@ -27,6 +27,11 @@ exports.startup = function() {
if($tw.browser) {
$tw.browser.isIE = (/msie|trident/i.test(navigator.userAgent));
$tw.browser.isFirefox = !!document.mozFullScreenEnabled;
+ // 2023-07-21 Edge returns UA below. So we use "isChromeLike"
+ //'mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/114.0.0.0 safari/537.36 edg/114.0.1823.82'
+ $tw.browser.isChromeLike = navigator.userAgent.toLowerCase().indexOf("chrome") > -1;
+ $tw.browser.hasTouch = !!window.matchMedia && window.matchMedia("(pointer: coarse)").matches;
+ $tw.browser.isMobileChrome = $tw.browser.isChromeLike && $tw.browser.hasTouch;
}
// Platform detection
$tw.platform = {};
diff --git a/core/modules/startup/story.js b/core/modules/startup/story.js
index 734f6ae767..c58c759c39 100644
--- a/core/modules/startup/story.js
+++ b/core/modules/startup/story.js
@@ -93,7 +93,9 @@ exports.startup = function() {
updateAddressBar: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_UPDATE_ADDRESS_BAR,"yes").trim() === "yes" ? "permalink" : "none",
updateHistory: $tw.wiki.getTiddlerText(CONFIG_UPDATE_HISTORY,"no").trim(),
targetTiddler: event.param || event.tiddlerTitle,
- copyToClipboard: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_COPY_TO_CLIPBOARD,"yes").trim() === "yes" ? "permalink" : "none"
+ copyToClipboard: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_COPY_TO_CLIPBOARD,"yes").trim() === "yes" ? "permalink" : "none",
+ successNotification: event.paramObject && event.paramObject.successNotification,
+ failureNotification: event.paramObject && event.paramObject.failureNotification
});
});
// Listen for the tm-permaview message
@@ -102,7 +104,9 @@ exports.startup = function() {
updateAddressBar: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_UPDATE_ADDRESS_BAR,"yes").trim() === "yes" ? "permaview" : "none",
updateHistory: $tw.wiki.getTiddlerText(CONFIG_UPDATE_HISTORY,"no").trim(),
targetTiddler: event.param || event.tiddlerTitle,
- copyToClipboard: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_COPY_TO_CLIPBOARD,"yes").trim() === "yes" ? "permaview" : "none"
+ copyToClipboard: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_COPY_TO_CLIPBOARD,"yes").trim() === "yes" ? "permaview" : "none",
+ successNotification: event.paramObject && event.paramObject.successNotification,
+ failureNotification: event.paramObject && event.paramObject.failureNotification
});
});
}
@@ -177,6 +181,8 @@ options.updateAddressBar: "permalink", "permaview" or "no" (defaults to "permavi
options.updateHistory: "yes" or "no" (defaults to "no")
options.copyToClipboard: "permalink", "permaview" or "no" (defaults to "no")
options.targetTiddler: optional title of target tiddler for permalink
+options.successNotification: optional title of tiddler to use as the notification in case of success
+options.failureNotification: optional title of tiddler to use as the notification in case of failure
*/
function updateLocationHash(options) {
// Get the story and the history stack
@@ -205,14 +211,18 @@ function updateLocationHash(options) {
break;
}
// Copy URL to the clipboard
+ var url = "";
switch(options.copyToClipboard) {
case "permalink":
- $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler));
+ url = $tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler);
break;
case "permaview":
- $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList)));
+ url = $tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList));
break;
}
+ if(url) {
+ $tw.utils.copyToClipboard(url,{successNotification: options.successNotification, failureNotification: options.failureNotification});
+ }
// Only change the location hash if we must, thus avoiding unnecessary onhashchange events
if($tw.utils.getLocationHash() !== $tw.locationHash) {
if(options.updateHistory === "yes") {
diff --git a/core/modules/startup/windows.js b/core/modules/startup/windows.js
index 384961b7b2..34f45d7a58 100644
--- a/core/modules/startup/windows.js
+++ b/core/modules/startup/windows.js
@@ -40,7 +40,7 @@ exports.startup = function() {
variables = $tw.utils.extend({},paramObject,{currentTiddler: title, "tv-window-id": windowID});
// Open the window
var srcWindow,
- srcDocument;
+ srcDocument;
// In case that popup blockers deny opening a new window
try {
srcWindow = window.open("","external-" + windowID,"scrollbars,width=" + width + ",height=" + height + (top ? ",top=" + top : "" ) + (left ? ",left=" + left : "" )),
@@ -52,10 +52,11 @@ exports.startup = function() {
$tw.windows[windowID] = srcWindow;
// Check for reopening the same window
if(srcWindow.haveInitialisedWindow) {
+ srcWindow.focus();
return;
}
// Initialise the document
- srcDocument.write("");
+ srcDocument.write("