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

Contributing to TiddlyWiki5

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:

praisePraises 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
nitpickNitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement
suggestionSuggestions 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
issueIssues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion
questionQuestions 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
thoughtThoughts 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
choreChores 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

  1. Navigate to licenses/CLA-individual or licenses/CLA-entity according to whether you are signing as an individual or representative of an organisation
  2. Ensure that the "branch" dropdown at the top left is set to tiddlywiki-com
  3. Click the "edit" button at the top-right corner (clicking this button will fork the project so you can edit the file)
  4. Add your name at the bottom
    • eg: Jeremy Ruston, @Jermolene, 2011/11/22
  5. Below the edit box for the CLA text you should see a box labelled Propose file change
  6. Enter a brief title to explain the change (eg, "Signing the CLA")
  7. Click the green button labelled Propose file change
  8. 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". +

Contributing to TiddlyWiki5

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:

praisePraises 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
nitpickNitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement
suggestionSuggestions 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
issueIssues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion
questionQuestions 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
thoughtThoughts 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
choreChores 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

  1. Navigate to licenses/CLA-individual or licenses/CLA-entity according to whether you are signing as an individual or representative of an organisation
  2. Ensure that the "branch" dropdown at the top left is set to tiddlywiki-com
  3. Click the "edit" button at the top-right corner (clicking this button will fork the project so you can edit the file)
  4. Add your name at the bottom
    • eg: Jeremy Ruston, @Jermolene, 2011/11/22
  5. Below the edit box for the CLA text you should see a box labelled Propose file change
  6. Enter a brief title to explain the change (eg, "Signing the CLA")
  7. Click the green button labelled Propose file change
  8. 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 filter="[{$:/state/http-requests}!match[0]]" variable="ignore"> + + + \ 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=<>/> \ 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 -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(""); srcDocument.close(); srcDocument.title = windowTitle; srcWindow.addEventListener("beforeunload",function(event) { diff --git a/core/modules/storyviews/zoomin.js b/core/modules/storyviews/zoomin.js index b2796e953f..d02f705e73 100644 --- a/core/modules/storyviews/zoomin.js +++ b/core/modules/storyviews/zoomin.js @@ -17,6 +17,10 @@ var easing = "cubic-bezier(0.645, 0.045, 0.355, 1)"; // From http://easings.net/ var ZoominListView = function(listWidget) { var self = this; this.listWidget = listWidget; + this.textNodeLogger = new $tw.utils.Logger("zoomin story river view", { + enable: true, + colour: 'red' + }); // Get the index of the tiddler that is at the top of the history var history = this.listWidget.wiki.getTiddlerDataCached(this.listWidget.historyTitle,[]), targetTiddler; @@ -48,7 +52,10 @@ ZoominListView.prototype.navigateTo = function(historyInfo) { var listItemWidget = this.listWidget.children[listElementIndex], targetElement = listItemWidget.findFirstDomNode(); // Abandon if the list entry isn't a DOM element (it might be a text node) - if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) { + if(!targetElement) { + return; + } else if (targetElement.nodeType === Node.TEXT_NODE) { + this.logTextNodeRoot(targetElement); return; } // Make the new tiddler be position absolute and visible so that we can measure it @@ -122,7 +129,7 @@ function findTitleDomNode(widget,targetClass) { targetClass = targetClass || "tc-title"; var domNode = widget.findFirstDomNode(); if(domNode && domNode.querySelector) { - return domNode.querySelector("." + targetClass); + return $tw.utils.querySelectorSafe("." + targetClass,domNode); } return null; } @@ -130,7 +137,10 @@ function findTitleDomNode(widget,targetClass) { ZoominListView.prototype.insert = function(widget) { var targetElement = widget.findFirstDomNode(); // Abandon if the list entry isn't a DOM element (it might be a text node) - if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) { + if(!targetElement) { + return; + } else if (targetElement.nodeType === Node.TEXT_NODE) { + this.logTextNodeRoot(targetElement); return; } // Make the newly inserted node position absolute and hidden @@ -173,16 +183,21 @@ ZoominListView.prototype.remove = function(widget) { var toWidgetDomNode = toWidget && toWidget.findFirstDomNode(); // Set up the tiddler we're moving back in if(toWidgetDomNode) { - $tw.utils.addClass(toWidgetDomNode,"tc-storyview-zoomin-tiddler"); - $tw.utils.setStyle(toWidgetDomNode,[ - {display: "block"}, - {transformOrigin: "50% 50%"}, - {transform: "translateX(0px) translateY(0px) scale(10)"}, - {transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", opacity " + duration + "ms " + easing}, - {opacity: "0"}, - {zIndex: "500"} - ]); - this.currentTiddlerDomNode = toWidgetDomNode; + if (toWidgetDomNode.nodeType === Node.TEXT_NODE) { + this.logTextNodeRoot(toWidgetDomNode); + toWidgetDomNode = null; + } else { + $tw.utils.addClass(toWidgetDomNode,"tc-storyview-zoomin-tiddler"); + $tw.utils.setStyle(toWidgetDomNode,[ + {display: "block"}, + {transformOrigin: "50% 50%"}, + {transform: "translateX(0px) translateY(0px) scale(10)"}, + {transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", opacity " + duration + "ms " + easing}, + {opacity: "0"}, + {zIndex: "500"} + ]); + this.currentTiddlerDomNode = toWidgetDomNode; + } } // Animate them both // Force layout @@ -206,6 +221,10 @@ ZoominListView.prototype.remove = function(widget) { return true; // Indicate that we'll delete the DOM node }; +ZoominListView.prototype.logTextNodeRoot = function(node) { + this.textNodeLogger.log($tw.language.getString("Error/ZoominTextNode") + " " + node.textContent); +}; + exports.zoomin = ZoominListView; })(); diff --git a/core/modules/syncer.js b/core/modules/syncer.js index c06fcb1439..f7627e1ac5 100644 --- a/core/modules/syncer.js +++ b/core/modules/syncer.js @@ -24,7 +24,7 @@ Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval"; Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading"; Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done"; Syncer.prototype.titleSyncThrottleInterval = "$:/config/SyncThrottleInterval"; -Syncer.prototype.taskTimerInterval = 1 * 1000; // Interval for sync timer +Syncer.prototype.taskTimerInterval = 0.25 * 1000; // Interval for sync timer Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s... Syncer.prototype.errorRetryInterval = 5 * 1000; // Interval to retry after an error Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s @@ -74,9 +74,11 @@ function Syncer(options) { this.titlesHaveBeenLazyLoaded = {}; // Hashmap of titles of tiddlers that have already been lazily loaded from the server // Timers this.taskTimerId = null; // Timer for task dispatch - this.pollTimerId = null; // Timer for polling server // Number of outstanding requests this.numTasksInProgress = 0; + // True when we want to force an immediate sync from the server + this.forceSyncFromServer = false; + this.timestampLastSyncFromServer = new Date(); // Listen out for changes to tiddlers this.wiki.addEventListener("change",function(changes) { // Filter the changes to just include ones that are being synced @@ -203,33 +205,37 @@ Syncer.prototype.readTiddlerInfo = function() { Checks whether the wiki is dirty (ie the window shouldn't be closed) */ Syncer.prototype.isDirty = function() { - this.logger.log("Checking dirty status"); - // Check tiddlers that are in the store and included in the filter function - var titles = this.getSyncedTiddlers(); - for(var index=0; index tiddlerInfo.changeCount) { + var self = this; + function checkIsDirty() { + // Check tiddlers that are in the store and included in the filter function + var titles = self.getSyncedTiddlers(); + for(var index=0; index tiddlerInfo.changeCount) { + return true; + } + } else { + // If the tiddler isn't known on the server then it needs to be saved to the server return true; } - } else { - // If the tiddler isn't known on the server then it needs to be saved to the server + } + } + // Check tiddlers that are known from the server but not currently in the store + titles = Object.keys(self.tiddlerInfo); + for(index=0; index 0 || updates.deletions.length > 0) { - self.processTaskQueue(); - } - } - }); - } else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) { - this.logger.log("Retrieving skinny tiddler list"); - cancelNextSync(); - this.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) { - triggerNextSync(); - // Check for errors - if(err) { - self.displayError($tw.language.getString("Error/RetrievingSkinny"),err); - return; - } - // Keep track of which tiddlers we already know about have been reported this time - var previousTitles = Object.keys(self.tiddlerInfo); - // Process each incoming tiddler - for(var t=0; t= (this.timestampLastSyncFromServer.valueOf() + this.pollTimerInterval)))) { + return new SyncFromServerTask(this); + } + // Third, we check tiddlers that are known from the server but not currently in the store, and so need deleting on the server titles = Object.keys(this.tiddlerInfo); for(index=0; index 0) { + for(var t=this.requests.length - 1; t--; t>=0) { + var requestInfo = this.requests[t]; + requestInfo.request.cancel(); + } + } + this.requests = []; + this.updateRequestTracker(); +}; + +HttpClient.prototype.cancelHttpRequest = function(targetId) { + var targetIndex = this.getRequestIndex(targetId); + if(targetIndex !== null) { + this.requests[targetIndex].request.cancel(); + this.requests.splice(targetIndex,1); + this.updateRequestTracker(); + } +}; + +/* +Initiate an HTTP request. Options: +wiki: wiki to be used for executing action strings +url: URL for request +method: method eg GET, POST +body: text of request body +binary: set to "yes" to force binary processing of response payload +oncompletion: action string to be invoked on completion +onprogress: action string to be invoked on progress updates +bindStatus: optional title of tiddler to which status ("pending", "complete", "error") should be written +bindProgress: optional title of tiddler to which the progress of the request (0 to 100) should be bound +variables: hashmap of variable name to string value passed to action strings +headers: hashmap of header name to header value to be sent with the request +passwordHeaders: hashmap of header name to password store name to be sent with the request +queryStrings: hashmap of query string parameter name to parameter value to be sent with the request +passwordQueryStrings: hashmap of query string parameter name to password store name to be sent with the request +basicAuthUsername: plain username for basic authentication +basicAuthUsernameFromStore: name of password store entry containing username +basicAuthPassword: plain password for basic authentication +basicAuthPasswordFromStore: name of password store entry containing password +*/ +function HttpClientRequest(options) { + var self = this; + console.log("Initiating an HTTP request",options) + this.wiki = options.wiki; + this.completionActions = options.oncompletion; + this.progressActions = options.onprogress; + this.bindStatus = options["bindStatus"]; + this.bindProgress = options["bindProgress"]; + this.method = options.method || "GET"; + this.body = options.body || ""; + this.binary = options.binary || ""; + this.useDefaultHeaders = options.useDefaultHeaders !== "false" ? true : false, + this.variables = options.variables; + var url = options.url; + $tw.utils.each(options.queryStrings,function(value,name) { + url = $tw.utils.setQueryStringParameter(url,name,value); + }); + $tw.utils.each(options.passwordQueryStrings,function(value,name) { + url = $tw.utils.setQueryStringParameter(url,name,$tw.utils.getPassword(value) || ""); + }); + this.url = url; + this.requestHeaders = {}; + $tw.utils.each(options.headers,function(value,name) { + self.requestHeaders[name] = value; + }); + $tw.utils.each(options.passwordHeaders,function(value,name) { + self.requestHeaders[name] = $tw.utils.getPassword(value) || ""; + }); + this.basicAuthUsername = options.basicAuthUsername || (options.basicAuthUsernameFromStore && $tw.utils.getPassword(options.basicAuthUsernameFromStore)) || ""; + this.basicAuthPassword = options.basicAuthPassword || (options.basicAuthPasswordFromStore && $tw.utils.getPassword(options.basicAuthPasswordFromStore)) || ""; + if(this.basicAuthUsername && this.basicAuthPassword) { + this.requestHeaders.Authorization = "Basic " + $tw.utils.base64Encode(this.basicAuthUsername + ":" + this.basicAuthPassword); + } +} + +HttpClientRequest.prototype.send = function(callback) { + var self = this, + setBinding = function(title,text) { + if(title) { + self.wiki.addTiddler(new $tw.Tiddler({title: title, text: text})); + } + }; + if(this.url) { + setBinding(this.bindStatus,"pending"); + setBinding(this.bindProgress,"0"); + // Set the request tracker tiddler + var requestTrackerTitle = this.wiki.generateNewTitle("$:/temp/HttpRequest"); + this.wiki.addTiddler({ + title: requestTrackerTitle, + tags: "$:/tags/HttpRequest", + text: JSON.stringify({ + url: this.url, + type: this.method, + status: "inprogress", + headers: this.requestHeaders, + data: this.body + }) + }); + this.xhr = $tw.utils.httpRequest({ + url: this.url, + type: this.method, + useDefaultHeaders: this.useDefaultHeaders, + headers: this.requestHeaders, + data: this.body, + returnProp: this.binary === "" ? "responseText" : "response", + responseType: this.binary === "" ? "text" : "arraybuffer", + callback: function(err,data,xhr) { + var hasSucceeded = xhr.status >= 200 && xhr.status < 300, + completionCode = hasSucceeded ? "complete" : "error", + headers = {}; + $tw.utils.each(xhr.getAllResponseHeaders().split("\r\n"),function(line) { + var pos = line.indexOf(":"); + if(pos !== -1) { + headers[line.substr(0,pos)] = line.substr(pos + 1).trim(); + } + }); + setBinding(self.bindStatus,completionCode); + setBinding(self.bindProgress,"100"); + var resultVariables = { + status: xhr.status.toString(), + statusText: xhr.statusText, + error: (err || "").toString(), + data: (data || "").toString(), + headers: JSON.stringify(headers) + }; + /* Convert data from binary to base64 */ + if (xhr.responseType === "arraybuffer") { + var binary = "", + bytes = new Uint8Array(data), + len = bytes.byteLength; + for (var i=0; i= 200 && this.status < 300) { // Success! options.callback(null,this[returnProp],this); return; } // Something went wrong - options.callback($tw.language.getString("Error/XMLHttpRequest") + ": " + this.status,null,this); + options.callback($tw.language.getString("Error/XMLHttpRequest") + ": " + this.status,this[returnProp],this); } }; + // Handle progress + if(options.progress) { + request.onprogress = function(event) { + console.log("Progress event",event) + options.progress(event.lengthComputable,event.loaded,event.total); + }; + } // Make the request request.open(type,url,true); + // Headers if(headers) { $tw.utils.each(headers,function(header,headerTitle,object) { request.setRequestHeader(headerTitle,header); }); } - if(data && !hasHeader("Content-Type")) { + if(data && !hasHeader("Content-Type") && useDefaultHeaders) { request.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"); } - if(!hasHeader("X-Requested-With") && !isSimpleRequest(type,headers)) { + if(!hasHeader("X-Requested-With") && !isSimpleRequest(type,headers) && useDefaultHeaders) { request.setRequestHeader("X-Requested-With","TiddlyWiki"); } + // Send data try { request.send(data); } catch(e) { @@ -104,4 +334,19 @@ exports.httpRequest = function(options) { return request; }; +exports.setQueryStringParameter = function(url,paramName,paramValue) { + var URL = $tw.browser ? window.URL : require("url").URL, + newUrl; + try { + newUrl = new URL(url); + } catch(e) { + } + if(newUrl && paramName) { + newUrl.searchParams.set(paramName,paramValue || ""); + return newUrl.toString(); + } else { + return url; + } +}; + })(); diff --git a/core/modules/utils/dom/scroller.js b/core/modules/utils/dom/scroller.js index 73be0f0a87..905bb27507 100644 --- a/core/modules/utils/dom/scroller.js +++ b/core/modules/utils/dom/scroller.js @@ -127,8 +127,8 @@ PageScroller.prototype.scrollIntoView = function(element,callback,options) { }; PageScroller.prototype.scrollSelectorIntoView = function(baseElement,selector,callback,options) { - baseElement = baseElement || document.body; - var element = baseElement.querySelector(selector); + baseElement = baseElement || document; + var element = $tw.utils.querySelectorSafe(selector,baseElement); if(element) { this.scrollIntoView(element,callback,options); } diff --git a/core/modules/utils/errors.js b/core/modules/utils/errors.js new file mode 100644 index 0000000000..fac4b3fa77 --- /dev/null +++ b/core/modules/utils/errors.js @@ -0,0 +1,23 @@ +/*\ +title: $:/core/modules/utils/errors.js +type: application/javascript +module-type: utils + +Custom errors for TiddlyWiki. + +\*/ +(function(){ + +function TranscludeRecursionError() { + Error.apply(this,arguments); + this.signatures = Object.create(null); +}; + +/* Maximum permitted depth of the widget tree for recursion detection */ +TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH = 1000; + +TranscludeRecursionError.prototype = Object.create(Error); + +exports.TranscludeRecursionError = TranscludeRecursionError; + +})(); diff --git a/core/modules/utils/fakedom.js b/core/modules/utils/fakedom.js index d28161ac6a..fb28465fee 100755 --- a/core/modules/utils/fakedom.js +++ b/core/modules/utils/fakedom.js @@ -42,7 +42,7 @@ var TW_TextNode = function(text) { this.textContent = text + ""; }; -Object.setPrototypeOf(TW_TextNode,TW_Node.prototype); +Object.setPrototypeOf(TW_TextNode.prototype,TW_Node.prototype); Object.defineProperty(TW_TextNode.prototype, "nodeType", { get: function() { @@ -67,7 +67,7 @@ var TW_Element = function(tag,namespace) { this.namespaceURI = namespace || "http://www.w3.org/1999/xhtml"; }; -Object.setPrototypeOf(TW_Element,TW_Node.prototype); +Object.setPrototypeOf(TW_Element.prototype,TW_Node.prototype); Object.defineProperty(TW_Element.prototype, "style", { get: function() { @@ -104,7 +104,11 @@ TW_Element.prototype.setAttribute = function(name,value) { if(this.isRaw) { throw "Cannot setAttribute on a raw TW_Element"; } - this.attributes[name] = value + ""; + if(name === "style") { + this.style = value; + } else { + this.attributes[name] = value + ""; + } }; TW_Element.prototype.setAttributeNS = function(namespace,name,value) { diff --git a/core/modules/utils/filesystem.js b/core/modules/utils/filesystem.js index 265cbd86f4..5319e04814 100644 --- a/core/modules/utils/filesystem.js +++ b/core/modules/utils/filesystem.js @@ -228,7 +228,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) { hasUnsafeFields = hasUnsafeFields || /[\x00-\x1F]/mg.test(value); hasUnsafeFields = hasUnsafeFields || ($tw.utils.trim(value) !== value); } - hasUnsafeFields = hasUnsafeFields || /:/mg.test(fieldName); + hasUnsafeFields = hasUnsafeFields || /:|#/mg.test(fieldName); }); // Check for field values if(hasUnsafeFields) { @@ -238,7 +238,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) { } else { // Save as a .tid or a text/binary file plus a .meta file var tiddlerType = tiddler.fields.type || "text/vnd.tiddlywiki"; - if(tiddlerType === "text/vnd.tiddlywiki") { + if(tiddlerType === "text/vnd.tiddlywiki" || tiddler.hasField("_canonical_uri")) { // Save as a .tid file fileInfo.type = "application/x-tiddler"; fileInfo.hasMetaFile = false; @@ -316,11 +316,13 @@ Options include: pathFilters: optional array of filters to be used to generate the base path wiki: optional wiki for evaluating the pathFilters fileInfo: an existing fileInfo object to check against + fileInfo.overwrite: if true, turns off filename clash numbers (defaults to false) */ exports.generateTiddlerFilepath = function(title,options) { var directory = options.directory || "", extension = options.extension || "", originalpath = (options.fileInfo && options.fileInfo.originalpath) ? options.fileInfo.originalpath : "", + overwrite = options.fileInfo && options.fileInfo.overwrite || false, filepath; // Check if any of the pathFilters applies if(options.pathFilters && options.wiki) { @@ -381,19 +383,20 @@ exports.generateTiddlerFilepath = function(title,options) { filepath += char.charCodeAt(0).toString(); }); } - // Add a uniquifier if the file already exists - var fullPath, oldPath = (options.fileInfo) ? options.fileInfo.filepath : undefined, + // Add a uniquifier if the file already exists (default) + var fullPath = path.resolve(directory, filepath + extension); + if (!overwrite) { + var oldPath = (options.fileInfo) ? options.fileInfo.filepath : undefined, count = 0; - do { - fullPath = path.resolve(directory,filepath + (count ? "_" + count : "") + extension); - if(oldPath && oldPath == fullPath) { - break; - } - count++; - } while(fs.existsSync(fullPath)); + do { + fullPath = path.resolve(directory,filepath + (count ? "_" + count : "") + extension); + if(oldPath && oldPath == fullPath) break; + count++; + } while(fs.existsSync(fullPath)); + } // If the last write failed with an error, or if path does not start with: // the resolved options.directory, the resolved wikiPath directory, the wikiTiddlersPath directory, - // or the 'originalpath' directory, then encodeURIComponent() and resolve to tiddler directory. + // or the 'originalpath' directory, then $tw.utils.encodeURIComponentExtended() and resolve to options.directory. var writePath = $tw.hooks.invokeHook("th-make-tiddler-path",fullPath,fullPath), encode = (options.fileInfo || {writeError: false}).writeError == true; if(!encode) { @@ -403,7 +406,7 @@ exports.generateTiddlerFilepath = function(title,options) { writePath.indexOf(path.resolve($tw.boot.wikiTiddlersPath,originalpath)) == 0 ); } if(encode) { - writePath = path.resolve(directory,encodeURIComponent(fullPath)); + writePath = path.resolve(directory,$tw.utils.encodeURIComponentExtended(fullPath)); } // Return the full path to the file return writePath; diff --git a/core/modules/utils/repository.js b/core/modules/utils/repository.js new file mode 100644 index 0000000000..3aeb4d25a8 --- /dev/null +++ b/core/modules/utils/repository.js @@ -0,0 +1,52 @@ +/*\ +title: $:/core/modules/utils/repository.js +type: application/javascript +module-type: utils + +Utilities for working with the TiddlyWiki repository file structure + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Get an object containing all the plugins as a hashmap by title of the JSON representation of the plugin +Options: + +ignoreEnvironmentVariables: defaults to false +*/ +exports.getAllPlugins = function(options) { + options = options || {}; + var fs = require("fs"), + path = require("path"), + tiddlers = {}; + // Collect up the library plugins + var collectPlugins = function(folder) { + var pluginFolders = $tw.utils.getSubdirectories(folder) || []; + for(var p=0; p{}\[\]`|"\\^]+(?:\/|\b)/i; @@ -809,18 +819,50 @@ exports.hashString = function(str) { },0); }; +/* +Cryptographic hash function as used by sha256 filter operator +options.length .. number of characters returned defaults to 64 +*/ +exports.sha256 = function(str, options) { + options = options || {} + return $tw.sjcl.codec.hex.fromBits($tw.sjcl.hash.sha256.hash(str)).substr(0,options.length || 64); +} + +/* +Base64 utility functions that work in either browser or Node.js +*/ +if(typeof window !== 'undefined') { + exports.btoa = function(binstr) { return window.btoa(binstr); } + exports.atob = function(b64) { return window.atob(b64); } +} else { + exports.btoa = function(binstr) { + return Buffer.from(binstr, 'binary').toString('base64'); + } + exports.atob = function(b64) { + return Buffer.from(b64, 'base64').toString('binary'); + } +} + /* Decode a base64 string */ -exports.base64Decode = function(string64) { - return base64utf8.base64.decode.call(base64utf8,string64); +exports.base64Decode = function(string64,binary,urlsafe) { + var encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64; + if(binary) return exports.atob(encoded) + else return base64utf8.base64.decode.call(base64utf8,encoded); }; /* Encode a string to base64 */ -exports.base64Encode = function(string64) { - return base64utf8.base64.encode.call(base64utf8,string64); +exports.base64Encode = function(string64,binary,urlsafe) { + var encoded; + if(binary) encoded = exports.btoa(string64); + else encoded = base64utf8.base64.encode.call(base64utf8,string64); + if(urlsafe) { + encoded = encoded.replace(/\+/g,'-').replace(/\//g,'_'); + } + return encoded; }; /* @@ -889,7 +931,7 @@ IE does not have sign function */ exports.sign = Math.sign || function(x) { x = +x; // convert to a number - if (x === 0 || isNaN(x)) { + if(x === 0 || isNaN(x)) { return x; } return x > 0 ? 1 : -1; @@ -902,7 +944,7 @@ exports.strEndsWith = function(str,ending,position) { if(str.endsWith) { return str.endsWith(ending,position); } else { - if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > str.length) { + if(typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > str.length) { position = str.length; } position -= ending.length; diff --git a/core/modules/widgets/action-deletefield.js b/core/modules/widgets/action-deletefield.js index 54068471ef..00f06562dd 100644 --- a/core/modules/widgets/action-deletefield.js +++ b/core/modules/widgets/action-deletefield.js @@ -37,6 +37,7 @@ Compute the internal state of the widget DeleteFieldWidget.prototype.execute = function() { this.actionTiddler = this.getAttribute("$tiddler",this.getVariable("currentTiddler")); this.actionField = this.getAttribute("$field",null); + this.actionTimestamp = this.getAttribute("$timestamp","yes") === "yes"; }; /* @@ -69,11 +70,15 @@ DeleteFieldWidget.prototype.invokeAction = function(triggeringWidget,event) { $tw.utils.each(this.attributes,function(attribute,name) { if(name.charAt(0) !== "$" && name !== "title") { removeFields[name] = undefined; - hasChanged = true; + if(name in tiddler.fields) { + hasChanged = true; + } } }); if(hasChanged) { - this.wiki.addTiddler(new $tw.Tiddler(this.wiki.getCreationFields(),tiddler,removeFields,this.wiki.getModificationFields())); + var creationFields = this.actionTimestamp ? this.wiki.getCreationFields() : {}; + var modificationFields = this.actionTimestamp ? this.wiki.getModificationFields() : {}; + this.wiki.addTiddler(new $tw.Tiddler(creationFields,tiddler,removeFields,modificationFields)); } } return true; // Action was invoked diff --git a/core/modules/widgets/action-log.js b/core/modules/widgets/action-log.js index fc84120064..7b1d1e9041 100644 --- a/core/modules/widgets/action-log.js +++ b/core/modules/widgets/action-log.js @@ -66,7 +66,12 @@ LogWidget.prototype.log = function() { }); for(var v in this.variables) { - allVars[v] = this.getVariable(v,{defaultValue:""}); + var variable = this.parentWidget && this.parentWidget.variables[v]; + if(variable && variable.isFunctionDefinition) { + allVars[v] = variable.value; + } else { + allVars[v] = this.getVariable(v,{defaultValue:""}); + } } if(this.filter) { filteredVars = this.wiki.compileFilter(this.filter).call(this.wiki,this.wiki.makeTiddlerIterator(allVars)); diff --git a/core/modules/widgets/browse.js b/core/modules/widgets/browse.js index de3c91fb81..8130825b0f 100644 --- a/core/modules/widgets/browse.js +++ b/core/modules/widgets/browse.js @@ -70,6 +70,11 @@ BrowseWidget.prototype.render = function(parent,nextSibling) { } return false; },false); + // Assign data- attributes + this.assignAttributes(domNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); // Insert element parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); @@ -95,6 +100,11 @@ BrowseWidget.prototype.execute = function() { Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ BrowseWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if($tw.utils.count(changedAttributes) > 0) { + this.refreshSelf(); + return true; + } return false; }; diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index a724d8448b..aef0fe6309 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -59,6 +59,11 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { $tw.utils.pushTop(classes,"tc-popup-handle"); } domNode.className = classes.join(" "); + // Assign data- attributes + this.assignAttributes(domNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); // Assign other attributes if(this.style) { domNode.setAttribute("style",this.style); @@ -250,18 +255,25 @@ ButtonWidget.prototype.updateDomNodeClasses = function() { //Add new classes from updated class attribute. $tw.utils.pushTop(domNodeClasses,newClasses); this.domNode.className = domNodeClasses.join(" "); -} +}; /* Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ ButtonWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.actions || changedAttributes.to || changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.selectedClass || changedAttributes.style || changedAttributes.dragFilter || changedAttributes.dragTiddler || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup]) || (this.popupTitle && changedTiddlers[this.popupTitle]) || changedAttributes.popupAbsCoords || changedAttributes.setTitle || changedAttributes.setField || changedAttributes.setIndex || changedAttributes.popupTitle || changedAttributes.disabled || changedAttributes["default"]) { + if(changedAttributes.tooltip || changedAttributes.actions || changedAttributes.to || changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.selectedClass || changedAttributes.style || changedAttributes.dragFilter || changedAttributes.dragTiddler || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup]) || (this.popupTitle && changedTiddlers[this.popupTitle]) || changedAttributes.popupAbsCoords || changedAttributes.setTitle || changedAttributes.setField || changedAttributes.setIndex || changedAttributes.popupTitle || changedAttributes.disabled || changedAttributes["default"]) { this.refreshSelf(); return true; - } else if(changedAttributes["class"]) { - this.updateDomNodeClasses(); + } else { + if(changedAttributes["class"]) { + this.updateDomNodeClasses(); + } + this.assignAttributes(this.domNodes[0],{ + changedAttributes: changedAttributes, + sourcePrefix: "data-", + destPrefix: "data-" + }); } return this.refreshChildren(changedTiddlers); }; diff --git a/core/modules/widgets/checkbox.js b/core/modules/widgets/checkbox.js index 68ebe79802..e07513b0a0 100644 --- a/core/modules/widgets/checkbox.js +++ b/core/modules/widgets/checkbox.js @@ -53,6 +53,11 @@ CheckboxWidget.prototype.render = function(parent,nextSibling) { this.labelDomNode.appendChild(this.inputDomNode); this.spanDomNode = this.document.createElement("span"); this.labelDomNode.appendChild(this.spanDomNode); + // Assign data- attributes + this.assignAttributes(this.inputDomNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); // Add a click event handler $tw.utils.addEventListeners(this.inputDomNode,[ {name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"} @@ -112,11 +117,11 @@ CheckboxWidget.prototype.getValue = function() { var list; if(this.checkboxListField) { if($tw.utils.hop(tiddler.fields,this.checkboxListField)) { - list = tiddler.getFieldList(this.checkboxListField); + list = tiddler.getFieldList(this.checkboxListField) || []; } else { list = $tw.utils.parseStringArray(this.checkboxDefault || "") || []; } - } else if (this.checkboxListIndex) { + } else if(this.checkboxListIndex) { list = $tw.utils.parseStringArray(this.wiki.extractTiddlerDataItem(tiddler,this.checkboxListIndex,this.checkboxDefault || "")) || []; } else { list = this.wiki.filterTiddlers(this.checkboxFilter,this) || []; @@ -208,16 +213,22 @@ CheckboxWidget.prototype.handleChangeEvent = function(event) { if(this.checkboxListField || this.checkboxListIndex) { var fieldContents, listContents, oldPos, newPos; if(this.checkboxListField) { - fieldContents = tiddler ? tiddler.fields[this.checkboxListField] : undefined; + fieldContents = (tiddler ? tiddler.fields[this.checkboxListField] : undefined) || []; } else { fieldContents = this.wiki.extractTiddlerDataItem(this.checkboxTitle,this.checkboxListIndex); } if($tw.utils.isArray(fieldContents)) { // Make a copy so we can modify it without changing original that's refrenced elsewhere listContents = fieldContents.slice(0); - } else { - listContents = $tw.utils.parseStringArray(fieldContents) || []; + } else if(fieldContents === undefined) { + listContents = []; + } else if(typeof fieldContents === "string") { + listContents = $tw.utils.parseStringArray(fieldContents); // No need to copy since parseStringArray returns a fresh array, not refrenced elsewhere + } else { + // Field was neither an array nor a string; it's probably something that shouldn't become + // an array (such as a date field), so bail out *without* triggering actions + return; } oldPos = notValue ? listContents.indexOf(notValue) : -1; newPos = value ? listContents.indexOf(value) : -1; @@ -319,6 +330,11 @@ CheckboxWidget.prototype.refresh = function(changedTiddlers) { $tw.utils.removeClass(this.labelDomNode,"tc-checkbox-checked"); } } + this.assignAttributes(this.inputDomNode,{ + changedAttributes: changedAttributes, + sourcePrefix: "data-", + destPrefix: "data-" + }); return this.refreshChildren(changedTiddlers) || refreshed; } }; @@ -326,3 +342,4 @@ CheckboxWidget.prototype.refresh = function(changedTiddlers) { exports.checkbox = CheckboxWidget; })(); + \ No newline at end of file diff --git a/core/modules/widgets/data.js b/core/modules/widgets/data.js new file mode 100644 index 0000000000..63f12e1f97 --- /dev/null +++ b/core/modules/widgets/data.js @@ -0,0 +1,184 @@ +/*\ +title: $:/core/modules/widgets/data.js +type: application/javascript +module-type: widget + +Widget to dynamically represent one or more tiddlers + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var DataWidget = function(parseTreeNode,options) { + this.dataWidgetTag = parseTreeNode.type; + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +DataWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +DataWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.dataPayload = this.computeDataTiddlerValues(); // Array of $tw.Tiddler objects + this.domNode = this.document.createTextNode(this.readDataTiddlerValuesAsJson()); + parent.insertBefore(this.domNode,nextSibling); + this.domNodes.push(this.domNode); +}; + +/* +Compute the internal state of the widget +*/ +DataWidget.prototype.execute = function() { + // Nothing to do here +}; + +/* +Read the tiddler value(s) from a data widget as an array of tiddler field objects (not $tw.Tiddler objects) +*/ +DataWidget.prototype.readDataTiddlerValues = function() { + var results = []; + $tw.utils.each(this.dataPayload,function(tiddler,index) { + results.push(tiddler.getFieldStrings()); + }); + return results; +}; + +/* +Read the tiddler value(s) from a data widget as an array of tiddler field objects (not $tw.Tiddler objects) +*/ +DataWidget.prototype.readDataTiddlerValuesAsJson = function() { + return JSON.stringify(this.readDataTiddlerValues(),null,4); +}; + +/* +Compute list of tiddlers from a data widget +*/ +DataWidget.prototype.computeDataTiddlerValues = function() { + var self = this; + // Read any attributes not prefixed with $ + var item = Object.create(null); + $tw.utils.each(this.attributes,function(value,name) { + if(name.charAt(0) !== "$") { + item[name] = value; + } + }); + // Deal with $tiddler, $filter or $compound-tiddler attributes + var tiddlers = [],title; + if(this.hasAttribute("$tiddler")) { + title = this.getAttribute("$tiddler"); + if(title) { + var tiddler = this.wiki.getTiddler(title); + if(tiddler) { + tiddlers.push(tiddler); + } + } + } + if(this.hasAttribute("$filter")) { + var filter = this.getAttribute("$filter"); + if(filter) { + var titles = this.wiki.filterTiddlers(filter); + $tw.utils.each(titles,function(title) { + var tiddler = self.wiki.getTiddler(title); + if(tiddler) { + tiddlers.push(tiddler); + } + }); + } + } + if(this.hasAttribute("$compound-tiddler")) { + title = this.getAttribute("$compound-tiddler"); + if(title) { + tiddlers.push.apply(tiddlers,this.extractCompoundTiddler(title)); + } + } + // Return the literal item if none of the special attributes were used + if(!this.hasAttribute("$tiddler") && !this.hasAttribute("$filter") && !this.hasAttribute("$compound-tiddler")) { + if(Object.keys(item).length > 0 && !!item.title) { + return [new $tw.Tiddler(item)]; + } else { + return []; + } + } else { + // Apply the item fields to each of the tiddlers + delete item.title; // Do not overwrite the title + if(Object.keys(item).length > 0) { + $tw.utils.each(tiddlers,function(tiddler,index) { + tiddlers[index] = new $tw.Tiddler(tiddler,item); + }); + } + return tiddlers; + } +}; + +/* +Helper to extract tiddlers from text/vnd.tiddlywiki-multiple tiddlers +*/ +DataWidget.prototype.extractCompoundTiddler = function(title) { + var tiddler = this.wiki.getTiddler(title); + if(tiddler && tiddler.fields.type === "text/vnd.tiddlywiki-multiple") { + var text = tiddler.fields.text || "", + rawTiddlers = text.split(/\r?\n\+\r?\n/), + tiddlers = []; + $tw.utils.each(rawTiddlers,function(rawTiddler) { + var fields = Object.create(null), + split = rawTiddler.split(/\r?\n\r?\n/mg); + if(split.length >= 1) { + fields = $tw.utils.parseFields(split[0],fields); + } + if(split.length >= 2) { + fields.text = split.slice(1).join("\n\n"); + } + tiddlers.push(new $tw.Tiddler(fields)); + }); + return tiddlers; + } else { + return []; + } +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +DataWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + var newPayload = this.computeDataTiddlerValues(); + if(hasPayloadChanged(this.dataPayload,newPayload)) { + this.dataPayload = newPayload; + this.domNode.textContent = this.readDataTiddlerValuesAsJson(); + return true; + } else { + return false; + } +}; + +/* +Compare two arrays of tiddlers and return true if they are different +*/ +function hasPayloadChanged(a,b) { + if(a.length === b.length) { + for(var t=0; t 0) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.tag || changedAttributes.selector || changedAttributes.dragimagetype || changedAttributes.enable || changedAttributes.startactions || changedAttributes.endactions) { this.refreshSelf(); return true; + } else { + if(changedAttributes["class"]) { + this.updateDomNodeClasses(); + } + this.assignAttributes(this.domNodes[0],{ + changedAttributes: changedAttributes, + sourcePrefix: "data-", + destPrefix: "data-" + }); } return this.refreshChildren(changedTiddlers); }; exports.draggable = DraggableWidget; -})(); \ No newline at end of file +})(); diff --git a/core/modules/widgets/droppable.js b/core/modules/widgets/droppable.js index 104503b25c..0dcba16881 100644 --- a/core/modules/widgets/droppable.js +++ b/core/modules/widgets/droppable.js @@ -42,6 +42,11 @@ DroppableWidget.prototype.render = function(parent,nextSibling) { domNode = this.document.createElement(tag); this.domNode = domNode; this.assignDomNodeClasses(); + // Assign data- attributes and style. attributes + this.assignAttributes(domNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); // Add event handlers if(this.droppableEnable) { $tw.utils.addEventListeners(domNode,[ @@ -166,8 +171,15 @@ DroppableWidget.prototype.refresh = function(changedTiddlers) { if(changedAttributes.tag || changedAttributes.enable || changedAttributes.disabledClass || changedAttributes.actions || changedAttributes.effect) { this.refreshSelf(); return true; - } else if(changedAttributes["class"]) { - this.assignDomNodeClasses(); + } else { + if(changedAttributes["class"]) { + this.assignDomNodeClasses(); + } + this.assignAttributes(this.domNodes[0],{ + changedAttributes: changedAttributes, + sourcePrefix: "data-", + destPrefix: "data-" + }); } return this.refreshChildren(changedTiddlers); }; diff --git a/core/modules/widgets/dropzone.js b/core/modules/widgets/dropzone.js index efa08162cf..043b2557f3 100644 --- a/core/modules/widgets/dropzone.js +++ b/core/modules/widgets/dropzone.js @@ -232,12 +232,34 @@ DropZoneWidget.prototype.handleDropEvent = function(event) { }; DropZoneWidget.prototype.handlePasteEvent = function(event) { - var self = this, - readFileCallback = function(tiddlerFieldsArray) { + var self = this; + var readFileCallback = function(tiddlerFieldsArray) { self.readFileCallback(tiddlerFieldsArray); }; + var getItem = function(type) { + type = type || "text/plain"; + return function(str) { + // Use the deserializer specified if any + if(self.dropzoneDeserializer) { + tiddlerFields = self.wiki.deserializeTiddlers(null,str,{title: self.wiki.generateNewTitle("Untitled " + type)},{deserializer:self.dropzoneDeserializer}); + if(tiddlerFields && tiddlerFields.length) { + readFileCallback(tiddlerFields); + } + } else { + tiddlerFields = { + title: self.wiki.generateNewTitle("Untitled " + type), + text: str, + type: type + }; + if($tw.log.IMPORT) { + console.log("Importing string '" + str + "', type: '" + type + "'"); + } + readFileCallback([tiddlerFields]); + } + } + }; // Let the browser handle it if we're in a textarea or input box - if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1 && !event.target.isContentEditable) { + if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1 && !event.target.isContentEditable && !event.twEditor) { var self = this, items = event.clipboardData.items; // Enumerate the clipboard items @@ -249,29 +271,26 @@ DropZoneWidget.prototype.handlePasteEvent = function(event) { callback: readFileCallback, deserializer: this.dropzoneDeserializer }); - } else if(item.kind === "string") { - // Create tiddlers from string items - var tiddlerFields, - type = item.type; - item.getAsString(function(str) { - // Use the deserializer specified if any - if(self.dropzoneDeserializer) { - tiddlerFields = self.wiki.deserializeTiddlers(null,str,{title: self.wiki.generateNewTitle("Untitled")},{deserializer:self.dropzoneDeserializer}); + } else if(item.kind === "string" && !["text/html", "text/plain", "Text"].includes(item.type) && $tw.utils.itemHasValidDataType(item)) { + // Try to import the various data types we understand + var fallbackTitle = self.wiki.generateNewTitle("Untitled"); + //Use the deserializer specified if any + if(this.dropzoneDeserializer) { + item.getAsString(function(str){ + var tiddlerFields = self.wiki.deserializeTiddlers(null,str,{title: fallbackTitle},{deserializer:self.dropzoneDeserializer}); if(tiddlerFields && tiddlerFields.length) { readFileCallback(tiddlerFields); } - } else { - tiddlerFields = { - title: self.wiki.generateNewTitle("Untitled"), - text: str, - type: type - }; - if($tw.log.IMPORT) { - console.log("Importing string '" + str + "', type: '" + type + "'"); - } - readFileCallback([tiddlerFields]); - } - }); + }); + } else { + $tw.utils.importPaste(item,fallbackTitle,readFileCallback); + } + } else if(item.kind === "string") { + // Create tiddlers from string items + var tiddlerFields; + // It's important to give getAsString a closure with the right type + // So it can be added to the import queue + item.getAsString(getItem(item.type)); } } // Tell the browser that we've handled the paste diff --git a/core/modules/widgets/edit.js b/core/modules/widgets/edit.js index e7bd49b933..eb7758e908 100644 --- a/core/modules/widgets/edit.js +++ b/core/modules/widgets/edit.js @@ -90,7 +90,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of EditWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); // Refresh if an attribute has changed, or the type associated with the target tiddler has changed - if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || (changedTiddlers[this.editTitle] && this.getEditorType() !== this.editorType)) { + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || (this.getEditorType() !== this.editorType)) { this.refreshSelf(); return true; } else { diff --git a/core/modules/widgets/fill.js b/core/modules/widgets/fill.js new file mode 100644 index 0000000000..de88c95af3 --- /dev/null +++ b/core/modules/widgets/fill.js @@ -0,0 +1,30 @@ +/*\ +title: $:/core/modules/widgets/fill.js +type: application/javascript +module-type: widget + +Sub-widget used by the transclude widget for specifying values for slots within transcluded content. It doesn't do anything by itself because the transclude widget only ever deals with the parse tree nodes, and doesn't instantiate the widget itself + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var FillWidget = function(parseTreeNode,options) { + // Initialise + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +FillWidget.prototype = new Widget(); + +exports.fill = FillWidget; + +})(); + \ No newline at end of file diff --git a/core/modules/widgets/genesis.js b/core/modules/widgets/genesis.js index 6d30f3c36c..c8403a3d41 100644 --- a/core/modules/widgets/genesis.js +++ b/core/modules/widgets/genesis.js @@ -46,6 +46,7 @@ GenesisWidget.prototype.execute = function() { this.genesisRemappable = this.getAttribute("$remappable","yes") === "yes"; this.genesisNames = this.getAttribute("$names",""); this.genesisValues = this.getAttribute("$values",""); + this.genesisIsBlock = this.getAttribute("$mode",this.parseTreeNode.isBlock && "block") === "block"; // Do not create a child widget if the $type attribute is missing or blank if(!this.genesisType) { this.makeChildWidgets(this.parseTreeNode.children); @@ -60,6 +61,7 @@ GenesisWidget.prototype.execute = function() { tag: nodeTag, attributes: {}, orderedAttributes: [], + isBlock: this.genesisIsBlock, children: this.parseTreeNode.children || [], isNotRemappable: !this.genesisRemappable }]; diff --git a/core/modules/widgets/image.js b/core/modules/widgets/image.js index f14e84c440..52496fd742 100644 --- a/core/modules/widgets/image.js +++ b/core/modules/widgets/image.js @@ -58,24 +58,25 @@ ImageWidget.prototype.render = function(parent,nextSibling) { if(this.wiki.isImageTiddler(this.imageSource)) { var type = tiddler.fields.type, text = tiddler.fields.text, - _canonical_uri = tiddler.fields._canonical_uri; + _canonical_uri = tiddler.fields._canonical_uri, + typeInfo = $tw.config.contentTypeInfo[type] || {}, + deserializerType = typeInfo.deserializerType || type; // If the tiddler has body text then it doesn't need to be lazily loaded if(text) { - // Render the appropriate element for the image type - switch(type) { - case "application/pdf": + // Render the appropriate element for the image type by looking up the encoding in the content type info + var encoding = typeInfo.encoding || "utf8"; + if (encoding === "base64") { + // .pdf .png .jpg etc. + src = "data:" + deserializerType + ";base64," + text; + if (deserializerType === "application/pdf") { tag = "embed"; - src = "data:application/pdf;base64," + text; - break; - case "image/svg+xml": - src = "data:image/svg+xml," + encodeURIComponent(text); - break; - default: - src = "data:" + type + ";base64," + text; - break; + } + } else { + // .svg .tid .xml etc. + src = "data:" + deserializerType + "," + encodeURIComponent(text); } } else if(_canonical_uri) { - switch(type) { + switch(deserializerType) { case "application/pdf": tag = "embed"; src = _canonical_uri; @@ -99,6 +100,9 @@ ImageWidget.prototype.render = function(parent,nextSibling) { if(this.imageClass) { domNode.setAttribute("class",this.imageClass); } + if(this.imageUsemap) { + domNode.setAttribute("usemap",this.imageUsemap); + } if(this.imageWidth) { domNode.setAttribute("width",this.imageWidth); } @@ -138,6 +142,7 @@ ImageWidget.prototype.execute = function() { this.imageWidth = this.getAttribute("width"); this.imageHeight = this.getAttribute("height"); this.imageClass = this.getAttribute("class"); + this.imageUsemap = this.getAttribute("usemap"); this.imageTooltip = this.getAttribute("tooltip"); this.imageAlt = this.getAttribute("alt"); this.lazyLoading = this.getAttribute("loading"); @@ -148,7 +153,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ ImageWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.tooltip || changedTiddlers[this.imageSource]) { + if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource]) { this.refreshSelf(); return true; } else { diff --git a/core/modules/widgets/importvariables.js b/core/modules/widgets/importvariables.js index a73abfdcfd..3e1ac3fc6f 100644 --- a/core/modules/widgets/importvariables.js +++ b/core/modules/widgets/importvariables.js @@ -49,41 +49,47 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) { this.tiddlerList = tiddlerList || this.wiki.filterTiddlers(this.filter,this); // Accumulate the <$set> widgets from each tiddler $tw.utils.each(this.tiddlerList,function(title) { - var parser = widgetPointer.wiki.parseTiddler(title,{parseAsInline:true}); + var parser = widgetPointer.wiki.parseTiddler(title,{parseAsInline:true, configTrimWhiteSpace:false}); if(parser) { var parseTreeNode = parser.tree[0]; - while(parseTreeNode && parseTreeNode.type === "set") { + while(parseTreeNode && ["setvariable","set","parameters"].indexOf(parseTreeNode.type) !== -1) { var node = { type: "set", attributes: parseTreeNode.attributes, params: parseTreeNode.params, - isMacroDefinition: parseTreeNode.isMacroDefinition + isMacroDefinition: parseTreeNode.isMacroDefinition, + isFunctionDefinition: parseTreeNode.isFunctionDefinition, + isProcedureDefinition: parseTreeNode.isProcedureDefinition, + isWidgetDefinition: parseTreeNode.isWidgetDefinition, + configTrimWhiteSpace: parseTreeNode.configTrimWhiteSpace }; - if (parseTreeNode.isMacroDefinition) { - // Macro definitions can be folded into - // current widget instead of adding - // another link to the chain. - var widget = widgetPointer.makeChildWidget(node); - widget.computeAttributes(); - widget.execute(); - // We SHALLOW copy over all variables - // in widget. We can't use - // $tw.utils.assign, because that copies - // up the prototype chain, which we - // don't want. - $tw.utils.each(Object.keys(widget.variables), function(key) { - widgetPointer.variables[key] = widget.variables[key]; - }); - } else { - widgetPointer.children = [widgetPointer.makeChildWidget(node)]; - // No more regenerating children for - // this widget. If it needs to refresh, - // it'll do so along with the the whole - // importvariable tree. - if (widgetPointer != this) { - widgetPointer.makeChildWidgets = function(){}; + if(parseTreeNode.type === "set" || parseTreeNode.type === "setvariable") { + if(parseTreeNode.isMacroDefinition || parseTreeNode.isProcedureDefinition || parseTreeNode.isWidgetDefinition || parseTreeNode.isFunctionDefinition) { + // Macro definitions can be folded into + // current widget instead of adding + // another link to the chain. + var widget = widgetPointer.makeChildWidget(node); + widget.computeAttributes(); + widget.execute(); + // We SHALLOW copy over all variables + // in widget. We can't use + // $tw.utils.assign, because that copies + // up the prototype chain, which we + // don't want. + $tw.utils.each(Object.keys(widget.variables), function(key) { + widgetPointer.variables[key] = widget.variables[key]; + }); + } else { + widgetPointer.children = [widgetPointer.makeChildWidget(node)]; + // No more regenerating children for + // this widget. If it needs to refresh, + // it'll do so along with the the whole + // importvariable tree. + if (widgetPointer != this) { + widgetPointer.makeChildWidgets = function(){}; + } + widgetPointer = widgetPointer.children[0]; } - widgetPointer = widgetPointer.children[0]; } parseTreeNode = parseTreeNode.children && parseTreeNode.children[0]; } diff --git a/core/modules/widgets/let.js b/core/modules/widgets/let.js index afd3a2f204..2b2886530c 100644 --- a/core/modules/widgets/let.js +++ b/core/modules/widgets/let.js @@ -53,7 +53,9 @@ LetWidget.prototype.computeAttributes = function() { name = attribute.name; // Now that it's prepped, we're allowed to look this variable up // when defining later variables - self.currentValueFor[name] = value; + if(value !== undefined) { + self.currentValueFor[name] = value; + } }); // Run through again, setting variables and looking for differences $tw.utils.each(this.currentValueFor,function(value,name) { diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 16356ee012..f02a7cae21 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -43,6 +43,11 @@ LinkWidget.prototype.render = function(parent,nextSibling) { } else { // Just insert the link text var domNode = this.document.createElement("span"); + // Assign data- attributes + this.assignAttributes(domNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); this.domNodes.push(domNode); @@ -97,8 +102,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { // Expand the tv-wikilink-template variable to construct the href var wikiLinkTemplateMacro = this.getVariable("tv-wikilink-template"), wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$"; - wikiLinkText = $tw.utils.replaceString(wikiLinkTemplate,"$uri_encoded$",encodeURIComponent(this.to)); - wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to))); + wikiLinkText = $tw.utils.replaceString(wikiLinkTemplate,"$uri_encoded$",$tw.utils.encodeURIComponentExtended(this.to)); + wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$uri_doubleencoded$",$tw.utils.encodeURIComponentExtended($tw.utils.encodeURIComponentExtended(this.to))); } // Override with the value of tv-get-export-link if defined wikiLinkText = this.getVariable("tv-get-export-link",{params: [{name: "to",value: this.to}],defaultValue: wikiLinkText}); @@ -138,6 +143,11 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { widget: this }); } + // Assign data- attributes + this.assignAttributes(domNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); // Insert the link into the DOM and render any children parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); @@ -207,8 +217,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ LinkWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip || - changedAttributes["class"] || changedAttributes.tabindex || changedAttributes.draggable || changedAttributes.tag) { + if($tw.utils.count(changedAttributes) > 0 || changedTiddlers[this.to]) { this.refreshSelf(); return true; } @@ -218,3 +227,4 @@ LinkWidget.prototype.refresh = function(changedTiddlers) { exports.link = LinkWidget; })(); + \ No newline at end of file diff --git a/core/modules/widgets/linkcatcher.js b/core/modules/widgets/linkcatcher.js index 78322d0f64..4f4a66b175 100644 --- a/core/modules/widgets/linkcatcher.js +++ b/core/modules/widgets/linkcatcher.js @@ -92,12 +92,9 @@ LinkCatcherWidget.prototype.handleNavigateEvent = function(event) { this.executingActions = false; } } else { - // This is a navigate event generated by the actions of this linkcatcher, so we don't trap it again, but just pass it to the parent - this.parentWidget.dispatchEvent({ - type: "tm-navigate", - param: event.navigateTo, - navigateTo: event.navigateTo - }); + // This is a navigate event generated by the actions of this linkcatcher, + // so we don't trap it again, but just pass it to the parent + this.parentWidget.dispatchEvent(event); } return false; }; diff --git a/core/modules/widgets/list.js b/core/modules/widgets/list.js index 41344a02eb..d4ad419952 100755 --- a/core/modules/widgets/list.js +++ b/core/modules/widgets/list.js @@ -28,6 +28,18 @@ Inherit from the base widget class */ ListWidget.prototype = new Widget(); +ListWidget.prototype.initialise = function(parseTreeNode,options) { + // Bail if parseTreeNode is undefined, meaning that the ListWidget constructor was called without any arguments so that it can be subclassed + if(parseTreeNode === undefined) { + return; + } + // First call parent constructor to set everything else up + Widget.prototype.initialise.call(this,parseTreeNode,options); + // Now look for <$list-template> and <$list-empty> widgets as immediate child widgets + // This is safe to do during initialization because parse trees never change after creation + this.findExplicitTemplates(); +} + /* Render this widget into the DOM */ @@ -38,8 +50,8 @@ ListWidget.prototype.render = function(parent,nextSibling) { $tw.modules.applyMethods("storyview",this.storyViews); } this.parentDomNode = parent; - this.computeAttributes(); - this.execute(); + var changedAttributes = this.computeAttributes(); + this.execute(changedAttributes); this.renderChildren(parent,nextSibling); // Construct the storyview var StoryView = this.storyViews[this.storyViewName]; @@ -59,7 +71,8 @@ ListWidget.prototype.render = function(parent,nextSibling) { /* Compute the internal state of the widget */ -ListWidget.prototype.execute = function() { +ListWidget.prototype.execute = function(changedAttributes) { + var self = this; // Get our attributes this.template = this.getAttribute("template"); this.editTemplate = this.getAttribute("editTemplate"); @@ -67,6 +80,10 @@ ListWidget.prototype.execute = function() { this.counterName = this.getAttribute("counter"); this.storyViewName = this.getAttribute("storyview"); this.historyTitle = this.getAttribute("history"); + // Create join template only if needed + if(this.join === undefined || (changedAttributes && changedAttributes.join)) { + this.join = this.makeJoinTemplate(); + } // Compose the list elements this.list = this.getTiddlerList(); var members = [], @@ -85,18 +102,57 @@ ListWidget.prototype.execute = function() { this.history = []; }; +ListWidget.prototype.findExplicitTemplates = function() { + var self = this; + this.explicitListTemplate = null; + this.explicitEmptyTemplate = null; + this.explicitJoinTemplate = null; + this.hasTemplateInBody = false; + var searchChildren = function(childNodes) { + var foundInlineTemplate = false; + $tw.utils.each(childNodes,function(node) { + if(node.type === "list-template") { + self.explicitListTemplate = node.children; + } else if(node.type === "list-empty") { + self.explicitEmptyTemplate = node.children; + } else if(node.type === "list-join") { + self.explicitJoinTemplate = node.children; + } else if(node.type === "element" && node.tag === "p") { + searchChildren(node.children); + foundInlineTemplate = true; + } else { + foundInlineTemplate = true; + } + }); + return foundInlineTemplate; + }; + this.hasTemplateInBody = searchChildren(this.parseTreeNode.children); +} + ListWidget.prototype.getTiddlerList = function() { + var limit = $tw.utils.getInt(this.getAttribute("limit",""),undefined); var defaultFilter = "[!is[system]sort[title]]"; - return this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this); + var results = this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this); + if(limit !== undefined) { + if(limit >= 0) { + results = results.slice(0,limit); + } else { + results = results.slice(limit); + } + } + return results; }; ListWidget.prototype.getEmptyMessage = function() { var parser, - emptyMessage = this.getAttribute("emptyMessage",""); - // this.wiki.parseText() calls - // new Parser(..), which should only be done, if needed, because it's heavy! - if (emptyMessage === "") { - return []; + emptyMessage = this.getAttribute("emptyMessage"); + // If emptyMessage attribute is not present or empty then look for an explicit empty template + if(!emptyMessage) { + if(this.explicitEmptyTemplate) { + return this.explicitEmptyTemplate; + } else { + return []; + } } parser = this.wiki.parseText("text/vnd.tiddlywiki",emptyMessage,{parseAsInline: true}); if(parser) { @@ -106,6 +162,24 @@ ListWidget.prototype.getEmptyMessage = function() { } }; +/* +Compose the template for a join between list items +*/ +ListWidget.prototype.makeJoinTemplate = function() { + var parser, + join = this.getAttribute("join",""); + if(join) { + parser = this.wiki.parseText("text/vnd.tiddlywiki",join,{parseAsInline:true}) + if(parser) { + return parser.tree; + } else { + return []; + } + } else { + return this.explicitJoinTemplate; // May be null, and that's fine + } +}; + /* Compose the template for a list item */ @@ -114,6 +188,7 @@ ListWidget.prototype.makeItemTemplate = function(title,index) { var tiddler = this.wiki.getTiddler(title), isDraft = tiddler && tiddler.hasField("draft.of"), template = this.template, + join = this.join, templateTree; if(isDraft && this.editTemplate) { template = this.editTemplate; @@ -122,22 +197,29 @@ ListWidget.prototype.makeItemTemplate = function(title,index) { if(template) { templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: template}}}]; } else { + // Check for child nodes of the list widget if(this.parseTreeNode.children && this.parseTreeNode.children.length > 0) { - templateTree = this.parseTreeNode.children; - } else { + // Check for a <$list-item> widget + if(this.explicitListTemplate) { + templateTree = this.explicitListTemplate; + } else if(this.hasTemplateInBody) { + templateTree = this.parseTreeNode.children; + } + } + if(!templateTree || templateTree.length === 0) { // Default template is a link to the title templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ - {type: "text", text: title} + {type: "text", text: title} ]}]}]; } } // Return the list item - var parseTreeNode = {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree}; + var parseTreeNode = {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree, join: join}; + parseTreeNode.isLast = index === this.list.length - 1; if(this.counterName) { parseTreeNode.counter = (index + 1).toString(); parseTreeNode.counterName = this.counterName; parseTreeNode.isFirst = index === 0; - parseTreeNode.isLast = index === this.list.length - 1; } return parseTreeNode; }; @@ -153,7 +235,7 @@ ListWidget.prototype.refresh = function(changedTiddlers) { this.storyview.refreshStart(changedTiddlers,changedAttributes); } // Completely refresh if any of our attributes have changed - if(changedAttributes.filter || changedAttributes.variable || changedAttributes.counter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) { + if(changedAttributes.filter || changedAttributes.variable || changedAttributes.counter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.join || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) { this.refreshSelf(); result = true; } else { @@ -225,6 +307,8 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) { // If we are providing an counter variable then we must refresh the items, otherwise we can rearrange them var hasRefreshed = false,t; if(this.counterName) { + var mustRefreshOldLast = false; + var oldLength = this.children.length; // Cycle through the list and remove and re-insert the first item that has changed, and all the remaining items for(t=0; t 0) { + var oldLastIdx = oldLength-1; + this.removeListItem(oldLastIdx); + this.insertListItem(oldLastIdx,this.list[oldLastIdx]); + } // If there are items to remove and we have not refreshed then recreate the item that will now be at the last position if(!hasRefreshed && this.children.length > this.list.length) { this.removeListItem(this.list.length-1); @@ -246,10 +339,29 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) { } } else { // Cycle through the list, inserting and removing list items as needed + var mustRecreateLastItem = false; + if(this.join && this.join.length) { + if(this.children.length !== this.list.length) { + mustRecreateLastItem = true; + } else if(prevList[prevList.length-1] !== this.list[this.list.length-1]) { + mustRecreateLastItem = true; + } + } + var isLast = false, wasLast = false; for(t=0; t0) { + // First re-create previosly-last item that will no longer be last + this.removeListItem(t-1); + this.insertListItem(t-1,this.list[t-1]); + } this.insertListItem(t,this.list[t]); hasRefreshed = true; } else { @@ -258,9 +370,15 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) { this.removeListItem(n); hasRefreshed = true; } - // Refresh the item we're reusing - var refreshed = this.children[t].refresh(changedTiddlers); - hasRefreshed = hasRefreshed || refreshed; + // Refresh the item we're reusing, or recreate if necessary + if(mustRecreateLastItem && (isLast || wasLast)) { + this.removeListItem(t); + this.insertListItem(t,this.list[t]); + hasRefreshed = true; + } else { + var refreshed = this.children[t].refresh(changedTiddlers); + hasRefreshed = hasRefreshed || refreshed; + } } } } @@ -350,8 +468,17 @@ ListItemWidget.prototype.execute = function() { this.setVariable(this.parseTreeNode.counterName + "-first",this.parseTreeNode.isFirst ? "yes" : "no"); this.setVariable(this.parseTreeNode.counterName + "-last",this.parseTreeNode.isLast ? "yes" : "no"); } + // Add join if needed + var children = this.parseTreeNode.children, + join = this.parseTreeNode.join; + if(join && join.length && !this.parseTreeNode.isLast) { + children = children.slice(0); + $tw.utils.each(join,function(joinNode) { + children.push(joinNode); + }) + } // Construct the child widgets - this.makeChildWidgets(); + this.makeChildWidgets(children); }; /* @@ -363,4 +490,37 @@ ListItemWidget.prototype.refresh = function(changedTiddlers) { exports.listitem = ListItemWidget; +/* +Make <$list-template> and <$list-empty> widgets that do nothing +*/ +var ListTemplateWidget = function(parseTreeNode,options) { + // Main initialisation inherited from widget.js + this.initialise(parseTreeNode,options); +}; +ListTemplateWidget.prototype = new Widget(); +ListTemplateWidget.prototype.render = function() {} +ListTemplateWidget.prototype.refresh = function() { return false; } + +exports["list-template"] = ListTemplateWidget; + +var ListEmptyWidget = function(parseTreeNode,options) { + // Main initialisation inherited from widget.js + this.initialise(parseTreeNode,options); +}; +ListEmptyWidget.prototype = new Widget(); +ListEmptyWidget.prototype.render = function() {} +ListEmptyWidget.prototype.refresh = function() { return false; } + +exports["list-empty"] = ListEmptyWidget; + +var ListJoinWidget = function(parseTreeNode,options) { + // Main initialisation inherited from widget.js + this.initialise(parseTreeNode,options); +}; +ListJoinWidget.prototype = new Widget(); +ListJoinWidget.prototype.render = function() {} +ListJoinWidget.prototype.refresh = function() { return false; } + +exports["list-join"] = ListJoinWidget; + })(); diff --git a/core/modules/widgets/macrocall.js b/core/modules/widgets/macrocall.js index 9de2e5d672..2c4adc3553 100644 --- a/core/modules/widgets/macrocall.js +++ b/core/modules/widgets/macrocall.js @@ -37,7 +37,7 @@ MacroCallWidget.prototype.render = function(parent,nextSibling) { Compute the internal state of the widget */ MacroCallWidget.prototype.execute = function() { - // Get the parse type if specified + this.macroName = this.parseTreeNode.name || this.getAttribute("$name"), this.parseType = this.getAttribute("$type","text/vnd.tiddlywiki"); this.renderOutput = this.getAttribute("$output","text/html"); // Merge together the parameters specified in the parse tree with the specified attributes @@ -47,49 +47,27 @@ MacroCallWidget.prototype.execute = function() { params.push({name: name, value: attribute}); } }); - // Get the macro value - var macroName = this.parseTreeNode.name || this.getAttribute("$name"), - variableInfo = this.getVariableInfo(macroName,{params: params}), - text = variableInfo.text, - parseTreeNodes; - // Are we rendering to HTML? - if(this.renderOutput === "text/html") { - // If so we'll return the parsed macro - // Check if we've already cached parsing this macro - var mode = this.parseTreeNode.isBlock ? "blockParser" : "inlineParser", - parser; - if(variableInfo.srcVariable && variableInfo.srcVariable[mode]) { - parser = variableInfo.srcVariable[mode]; - } else { - parser = this.wiki.parseText(this.parseType,text, - {parseAsInline: !this.parseTreeNode.isBlock}); - if(variableInfo.isCacheable && variableInfo.srcVariable) { - variableInfo.srcVariable[mode] = parser; - } - } - var parseTreeNodes = parser ? parser.tree : []; - // Wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__" - var attributes = {}; - $tw.utils.each(variableInfo.params,function(param) { - var name = "__" + param.name + "__"; - attributes[name] = { - name: name, - type: "string", - value: param.value - }; - }); + // Make a transclude widget + var positionalName = 0, parseTreeNodes = [{ - type: "vars", - attributes: attributes, - children: parseTreeNodes + type: "transclude", + isBlock: this.parseTreeNode.isBlock, + children: this.parseTreeNode.children }]; - } else if(this.renderOutput === "text/raw") { - parseTreeNodes = [{type: "text", text: text}]; - } else { - // Otherwise, we'll render the text - var plainText = this.wiki.renderText("text/plain",this.parseType,text,{parentWidget: this}); - parseTreeNodes = [{type: "text", text: plainText}]; - } + $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$variable",this.macroName); + $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$type",this.parseType); + $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$output",this.renderOutput); + $tw.utils.each(params,function(param) { + var name = param.name; + if(name) { + if(name.charAt(0) === "$") { + name = "$" + name; + } + $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],name,param.value); + } else { + $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],(positionalName++) + "",param.value); + } + }); // Construct the child widgets this.makeChildWidgets(parseTreeNodes); }; diff --git a/core/modules/widgets/messagecatcher.js b/core/modules/widgets/messagecatcher.js index 85a3561f24..1457cd11f4 100644 --- a/core/modules/widgets/messagecatcher.js +++ b/core/modules/widgets/messagecatcher.js @@ -82,7 +82,7 @@ MessageCatcherWidget.prototype.render = function(parent,nextSibling) { } }); // Render children - this.renderChildren(parent,null); + this.renderChildren(parent,nextSibling); }; /* diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 3f1efbe7ad..efdbba83f9 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -227,10 +227,7 @@ NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) { originalTitle = tiddler ? tiddler.fields["draft.of"] : "", originalTiddler = originalTitle ? this.wiki.getTiddler(originalTitle) : undefined, confirmationTitle, - win = event.event && event.event.view ? event.event.view : window; - if(!tiddler) { - return false; - } + win = event.event && event.event.view ? event.event.view : window; // Check if the tiddler we're deleting is in draft mode if(originalTitle) { // If so, we'll prompt for confirmation referencing the original tiddler @@ -240,7 +237,7 @@ NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) { confirmationTitle = title; } // Seek confirmation - if((this.wiki.getTiddler(originalTitle) || (tiddler.fields.text || "") !== "") && !win.confirm($tw.language.getString( + if(((originalTitle && this.wiki.getTiddler(originalTitle)) || (tiddler && ((tiddler.fields.text || "") !== ""))) && !win.confirm($tw.language.getString( "ConfirmDeleteTiddler", {variables: {title: confirmationTitle} @@ -257,8 +254,10 @@ NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) { this.removeTitleFromStory(storyList,originalTitle); } // Invoke the hook function and delete this tiddler - $tw.hooks.invokeHook("th-deleting-tiddler",tiddler); - this.wiki.deleteTiddler(title); + if(tiddler) { + $tw.hooks.invokeHook("th-deleting-tiddler",tiddler); + this.wiki.deleteTiddler(title); + } // Remove the closed tiddler from the story this.removeTitleFromStory(storyList,title); this.saveStoryList(storyList); @@ -500,7 +499,8 @@ NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) { // Get the tiddlers var tiddlers = $tw.utils.parseJSONSafe(event.param,[]); // Get the current $:/Import tiddler - var importTitle = event.importTitle ? event.importTitle : IMPORT_TITLE, + var paramObject = event.paramObject || {}, + importTitle = event.importTitle || paramObject.importTitle || IMPORT_TITLE, importTiddler = this.wiki.getTiddler(importTitle), importData = this.wiki.getTiddlerData(importTitle,{}), newFields = new Object({ @@ -541,7 +541,7 @@ NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) { newFields.text = JSON.stringify(importData,null,$tw.config.preferences.jsonSpaces); this.wiki.addTiddler(new $tw.Tiddler(importTiddler,newFields)); // Update the story and history details - var autoOpenOnImport = event.autoOpenOnImport ? event.autoOpenOnImport : this.getVariable("tv-auto-open-on-import"); + var autoOpenOnImport = event.autoOpenOnImport || paramObject.autoOpenOnImport || this.getVariable("tv-auto-open-on-import"); if(autoOpenOnImport !== "no") { var storyList = this.getStoryList(), history = []; diff --git a/core/modules/widgets/parameters.js b/core/modules/widgets/parameters.js new file mode 100644 index 0000000000..fdd2cb9637 --- /dev/null +++ b/core/modules/widgets/parameters.js @@ -0,0 +1,108 @@ +/*\ +title: $:/core/modules/widgets/parameters.js +type: application/javascript +module-type: widget + +Widget for definition of transclusion parameters + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget, + TranscludeWidget = require("$:/core/modules/widgets/transclude.js").transclude; + +var ParametersWidget = function(parseTreeNode,options) { + // Initialise + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +ParametersWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +ParametersWidget.prototype.render = function(parent,nextSibling) { + // Call the constructor + Widget.call(this); + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +ParametersWidget.prototype.execute = function() { + var self = this; + this.parametersDepth = Math.max(parseInt(this.getAttribute("$depth","1"),10) || 1,1); + // Find the parent transclusions + var pointer = this.parentWidget, + depth = this.parametersDepth; + while(pointer) { + if(pointer instanceof TranscludeWidget) { + depth--; + if(depth <= 0) { + break; + } + } + pointer = pointer.parentWidget; + } + // Process each parameter + if(pointer instanceof TranscludeWidget) { + // Get the value for each defined parameter + $tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(self.parseTreeNode),function(attr,index) { + var name = attr.name; + // If the attribute name starts with $$ then reduce to a single dollar + if(name.substr(0,2) === "$$") { + name = name.substr(1); + } + var value = pointer.getTransclusionParameter(name,index,self.getAttribute(attr.name,"")); + self.setVariable(name,value); + }); + // Assign any metaparameters + $tw.utils.each(pointer.getTransclusionMetaParameters(),function(getValue,name) { + var variableName = self.getAttribute("$" + name); + if(variableName) { + self.setVariable(variableName,getValue(name)); + } + }); + } else { + // There is no parent transclude. i.e. direct rendering. + // We use default values only. + $tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(self.parseTreeNode),function(attr,index) { + var name = attr.name; + // If the attribute name starts with $$ then reduce to a single dollar + if(name.substr(0,2) === "$$") { + name = name.substr(1); + } + var value = self.getAttribute(attr.name,""); + self.setVariable(name,value); + }); + } + // Construct the child widgets + this.makeChildWidgets(); +}; + +/* +Refresh the widget by ensuring our attributes are up to date +*/ +ParametersWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(Object.keys(changedAttributes).length) { + this.refreshSelf(); + return true; + } + return this.refreshChildren(changedTiddlers); +}; + +exports.parameters = ParametersWidget; + +})(); diff --git a/core/modules/widgets/radio.js b/core/modules/widgets/radio.js index 363836227c..aa7a32cf1b 100644 --- a/core/modules/widgets/radio.js +++ b/core/modules/widgets/radio.js @@ -40,6 +40,10 @@ RadioWidget.prototype.render = function(parent,nextSibling) { ); this.inputDomNode = this.document.createElement("input"); this.inputDomNode.setAttribute("type","radio"); + this.assignAttributes(this.inputDomNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); if(isChecked) { this.inputDomNode.checked = true; } diff --git a/core/modules/widgets/range.js b/core/modules/widgets/range.js index 4dd55dc3ce..db2699cc46 100644 --- a/core/modules/widgets/range.js +++ b/core/modules/widgets/range.js @@ -50,6 +50,10 @@ RangeWidget.prototype.render = function(parent,nextSibling) { this.inputDomNode.setAttribute("disabled",true); } this.inputDomNode.value = this.getValue(); + this.assignAttributes(this.inputDomNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); // Add a click event handler $tw.utils.addEventListeners(this.inputDomNode,[ {name:"mousedown", handlerObject:this, handlerMethod:"handleMouseDownEvent"}, diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js index aadc040df1..227c455c3b 100644 --- a/core/modules/widgets/scrollable.js +++ b/core/modules/widgets/scrollable.js @@ -12,6 +12,8 @@ Scrollable widget /*global $tw: false */ "use strict"; +var DEBOUNCE_INTERVAL = 100; // Delay after last scroll event before updating the bound tiddler + var Widget = require("$:/core/modules/widgets/widget.js").widget; var ScrollableWidget = function(parseTreeNode,options) { @@ -119,8 +121,8 @@ ScrollableWidget.prototype.scrollIntoView = function(element,callback,options) { }; ScrollableWidget.prototype.scrollSelectorIntoView = function(baseElement,selector,callback,options) { - baseElement = baseElement || document.body; - var element = baseElement.querySelector(selector); + baseElement = baseElement || document; + var element = $tw.utils.querySelectorSafe(selector,baseElement); if(element) { this.scrollIntoView(element,callback,options); } @@ -171,6 +173,53 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) { parent.insertBefore(this.outerDomNode,nextSibling); this.renderChildren(this.innerDomNode,null); this.domNodes.push(this.outerDomNode); + // If the scroll position is bound to a tiddler + if(this.scrollableBind) { + // After a delay for rendering, scroll to the bound position + this.updateScrollPositionFromBoundTiddler(); + // Set up event listener + this.currentListener = this.listenerFunction.bind(this); + this.outerDomNode.addEventListener("scroll", this.currentListener); + } +}; + +ScrollableWidget.prototype.listenerFunction = function(event) { + self = this; + clearTimeout(this.timeout); + this.timeout = setTimeout(function() { + var existingTiddler = self.wiki.getTiddler(self.scrollableBind), + newTiddlerFields = { + title: self.scrollableBind, + "scroll-left": self.outerDomNode.scrollLeft.toString(), + "scroll-top": self.outerDomNode.scrollTop.toString() + }; + if(!existingTiddler || (existingTiddler.fields["title"] !== newTiddlerFields["title"]) || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) { + self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields)); + } + }, DEBOUNCE_INTERVAL); +} + +ScrollableWidget.prototype.updateScrollPositionFromBoundTiddler = function() { + // Bail if we're running on the fakedom + if(!this.outerDomNode.scrollTo) { + return; + } + var tiddler = this.wiki.getTiddler(this.scrollableBind); + if(tiddler) { + var scrollLeftTo = this.outerDomNode.scrollLeft; + if(parseFloat(tiddler.fields["scroll-left"]).toString() === tiddler.fields["scroll-left"]) { + scrollLeftTo = parseFloat(tiddler.fields["scroll-left"]); + } + var scrollTopTo = this.outerDomNode.scrollTop; + if(parseFloat(tiddler.fields["scroll-top"]).toString() === tiddler.fields["scroll-top"]) { + scrollTopTo = parseFloat(tiddler.fields["scroll-top"]); + } + this.outerDomNode.scrollTo({ + top: scrollTopTo, + left: scrollLeftTo, + behavior: "instant" + }) + } }; /* @@ -178,6 +227,7 @@ Compute the internal state of the widget */ ScrollableWidget.prototype.execute = function() { // Get attributes + this.scrollableBind = this.getAttribute("bind"); this.fallthrough = this.getAttribute("fallthrough","yes"); this["class"] = this.getAttribute("class"); // Make child widgets @@ -193,7 +243,22 @@ ScrollableWidget.prototype.refresh = function(changedTiddlers) { this.refreshSelf(); return true; } - return this.refreshChildren(changedTiddlers); + // If the bound tiddler has changed, update the eventListener and update scroll position + if(changedAttributes["bind"]) { + if(this.currentListener) { + this.outerDomNode.removeEventListener("scroll", this.currentListener, false); + } + this.scrollableBind = this.getAttribute("bind"); + this.currentListener = this.listenerFunction.bind(this); + this.outerDomNode.addEventListener("scroll", this.currentListener); + } + // Refresh children + var result = this.refreshChildren(changedTiddlers); + // If the bound tiddler has changed, update scroll position + if(changedAttributes["bind"] || changedTiddlers[this.getAttribute("bind")]) { + this.updateScrollPositionFromBoundTiddler(); + } + return result; }; exports.scrollable = ScrollableWidget; diff --git a/core/modules/widgets/select.js b/core/modules/widgets/select.js index cd789423f6..2940e3be00 100644 --- a/core/modules/widgets/select.js +++ b/core/modules/widgets/select.js @@ -40,7 +40,31 @@ SelectWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); this.execute(); - this.renderChildren(parent,nextSibling); + //Create element + var domNode = this.document.createElement("select"); + if(this.selectClass) { + domNode.className = this.selectClass; + } + // Assign data- attributes + this.assignAttributes(domNode,{ + sourcePrefix: "data-", + destPrefix: "data-" + }); + if(this.selectMultiple) { + domNode.setAttribute("multiple","multiple"); + } + if(this.selectSize) { + domNode.setAttribute("size",this.selectSize); + } + if(this.selectTabindex) { + domNode.setAttribute("tabindex",this.selectTabindex); + } + if(this.selectTooltip) { + domNode.setAttribute("title",this.selectTooltip); + } + this.parentDomNode.insertBefore(domNode,nextSibling); + this.renderChildren(domNode,null); + this.domNodes.push(domNode); this.setSelectValue(); if(this.selectFocus == "yes") { this.getSelectDomNode().focus(); @@ -113,7 +137,7 @@ SelectWidget.prototype.setSelectValue = function() { Get the DOM node of the select element */ SelectWidget.prototype.getSelectDomNode = function() { - return this.children[0].domNodes[0]; + return this.domNodes[0]; }; // Return an array of the selected opion values @@ -145,27 +169,11 @@ SelectWidget.prototype.execute = function() { this.selectDefault = this.getAttribute("default"); this.selectMultiple = this.getAttribute("multiple", false); this.selectSize = this.getAttribute("size"); + this.selectTabindex = this.getAttribute("tabindex"); this.selectTooltip = this.getAttribute("tooltip"); this.selectFocus = this.getAttribute("focus"); // Make the child widgets - var selectNode = { - type: "element", - tag: "select", - children: this.parseTreeNode.children - }; - if(this.selectClass) { - $tw.utils.addAttributeToParseTreeNode(selectNode,"class",this.selectClass); - } - if(this.selectMultiple) { - $tw.utils.addAttributeToParseTreeNode(selectNode,"multiple","multiple"); - } - if(this.selectSize) { - $tw.utils.addAttributeToParseTreeNode(selectNode,"size",this.selectSize); - } - if(this.selectTooltip) { - $tw.utils.addAttributeToParseTreeNode(selectNode,"title",this.selectTooltip); - } - this.makeChildWidgets([selectNode]); + this.makeChildWidgets(); }; /* @@ -174,17 +182,21 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of SelectWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); // If we're using a different tiddler/field/index then completely refresh ourselves - if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip) { + if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.tabindex) { this.refreshSelf(); return true; - // If the target tiddler value has changed, just update setting and refresh the children } else { if(changedAttributes.class) { this.selectClass = this.getAttribute("class"); this.getSelectDomNode().setAttribute("class",this.selectClass); } - + this.assignAttributes(this.getSelectDomNode(),{ + changedAttributes: changedAttributes, + sourcePrefix: "data-", + destPrefix: "data-" + }); var childrenRefreshed = this.refreshChildren(changedTiddlers); + // If the target tiddler value has changed, just update setting and refresh the children if(changedTiddlers[this.selectTitle] || childrenRefreshed) { this.setSelectValue(); } diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js index cc97067c77..f8e98f3909 100755 --- a/core/modules/widgets/setvariable.js +++ b/core/modules/widgets/setvariable.js @@ -48,7 +48,17 @@ SetWidget.prototype.execute = function() { this.setValue = this.getAttribute("value"); this.setEmptyValue = this.getAttribute("emptyValue"); // Set context variable - this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition); + if(this.parseTreeNode.isMacroDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true); + } else if(this.parseTreeNode.isFunctionDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true}); + } else if(this.parseTreeNode.isProcedureDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace}); + } else if(this.parseTreeNode.isWidgetDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace}); + } else { + this.setVariable(this.setName,this.getValue()); + } // Construct the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/widgets/slot.js b/core/modules/widgets/slot.js new file mode 100644 index 0000000000..eeaaedefe0 --- /dev/null +++ b/core/modules/widgets/slot.js @@ -0,0 +1,82 @@ +/*\ +title: $:/core/modules/widgets/slot.js +type: application/javascript +module-type: widget + +Widget for definition of slots within transcluded content. The values provided by the translusion are passed to the slot. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget, + TranscludeWidget = require("$:/core/modules/widgets/transclude.js").transclude; + +var SlotWidget = function(parseTreeNode,options) { + // Initialise + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +SlotWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +SlotWidget.prototype.render = function(parent,nextSibling) { + // Call the constructor + Widget.call(this); + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +SlotWidget.prototype.execute = function() { + var self = this; + this.slotName = this.getAttribute("$name"); + this.slotDepth = parseInt(this.getAttribute("$depth","1"),10) || 1; + // Find the parent transclusions + var pointer = this.parentWidget, + depth = this.slotDepth; + while(pointer) { + if(pointer instanceof TranscludeWidget && pointer.hasVisibleSlots()) { + depth--; + if(depth <= 0) { + break; + } + } + pointer = pointer.parentWidget; + } + var parseTreeNodes = [{type: "text", attributes: {text: {type: "string", value: "Missing slot reference!"}}}]; + if(pointer instanceof TranscludeWidget) { + // Get the parse tree nodes comprising the slot contents + parseTreeNodes = pointer.getTransclusionSlotFill(this.slotName,this.parseTreeNode.children); + } + // Construct the child widgets + this.makeChildWidgets(parseTreeNodes); +}; + +/* +Refresh the widget by ensuring our attributes are up to date +*/ +SlotWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes["$name"] || changedAttributes["$depth"]) { + this.refreshSelf(); + return true; + } + return this.refreshChildren(changedTiddlers); +}; + +exports.slot = SlotWidget; + +})(); diff --git a/core/modules/widgets/testcase.js b/core/modules/widgets/testcase.js new file mode 100644 index 0000000000..0fd55531c0 --- /dev/null +++ b/core/modules/widgets/testcase.js @@ -0,0 +1,165 @@ +/*\ +title: $:/core/modules/widgets/testcase.js +type: application/javascript +module-type: widget + +Widget to display a test case + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var TestCaseWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +TestCaseWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +TestCaseWidget.prototype.render = function(parent,nextSibling) { + var self = this; + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + // Create container DOM node + var domNode = this.document.createElement("div"); + this.domNodes.push(domNode); + parent.insertBefore(domNode,nextSibling); + // Render the children into a hidden DOM node + var parser = { + tree: [{ + type: "widget", + attributes: {}, + orderedAttributes: [], + children: this.parseTreeNode.children || [] + }] + }; + this.contentRoot = this.wiki.makeWidget(parser,{ + document: $tw.fakeDocument, + parentWidget: this + }); + this.contentContainer = $tw.fakeDocument.createElement("div"); + this.contentRoot.render(this.contentContainer,null); + // Create a wiki + this.testcaseWiki = new $tw.Wiki(); + // Always load the core plugin + var loadTiddler = function(title) { + var tiddler = self.wiki.getTiddler(title); + if(tiddler) { + self.testcaseWiki.addTiddler(tiddler); + } + } + loadTiddler("$:/core"); + loadTiddler("$:/plugins/tiddlywiki/codemirror"); + // Load tiddlers from child data widgets + var tiddlers = []; + this.findChildrenDataWidgets(this.contentRoot.children,"data",function(widget) { + Array.prototype.push.apply(tiddlers,widget.readDataTiddlerValues()); + }); + var jsonPayload = JSON.stringify(tiddlers); + this.testcaseWiki.addTiddlers(tiddlers); + // Unpack plugin tiddlers + this.testcaseWiki.readPluginInfo(); + this.testcaseWiki.registerPluginTiddlers("plugin"); + this.testcaseWiki.unpackPluginTiddlers(); + this.testcaseWiki.addIndexersToWiki(); + // Generate a `transclusion` variable that depends on the values of the payload tiddlers so that the template can easily make unique state tiddlers + this.setVariable("transclusion",$tw.utils.hashString(jsonPayload)); + // Generate a `payloadTiddlers` variable that contains the payload in JSON format + this.setVariable("payloadTiddlers",jsonPayload); + // Only run the tests if the testcase output and expected results were specified, and those tiddlers actually exist in the wiki + var shouldRunTests = false; + if(this.testcaseTestOutput && this.testcaseWiki.tiddlerExists(this.testcaseTestOutput) && this.testcaseTestExpectedResult && this.testcaseWiki.tiddlerExists(this.testcaseTestExpectedResult)) { + shouldRunTests = true; + } + // Render the test rendering if required + if(shouldRunTests) { + var testcaseOutputContainer = $tw.fakeDocument.createElement("div"); + var testcaseOutputWidget = this.testcaseWiki.makeTranscludeWidget(this.testcaseTestOutput,{ + document: $tw.fakeDocument, + parseAsInline: false, + parentWidget: this, + variables: { + currentTiddler: this.testcaseTestOutput + } + }); + testcaseOutputWidget.render(testcaseOutputContainer); + } + // Clear changes queue + this.testcaseWiki.clearTiddlerEventQueue(); + // Run the actions if provided + if(this.testcaseWiki.tiddlerExists(this.testcaseTestActions)) { + testcaseOutputWidget.invokeActionString(this.testcaseWiki.getTiddlerText(this.testcaseTestActions)); + testcaseOutputWidget.refresh(this.testcaseWiki.changedTiddlers,testcaseOutputContainer); + } + // Set up the test result variables + var testResult = "", + outputHTML = "", + expectedHTML = ""; + if(shouldRunTests) { + outputHTML = testcaseOutputContainer.children[0].innerHTML; + expectedHTML = this.testcaseWiki.getTiddlerText(this.testcaseTestExpectedResult); + if(outputHTML === expectedHTML) { + testResult = "pass"; + } else { + testResult = "fail"; + } + this.setVariable("outputHTML",outputHTML); + this.setVariable("expectedHTML",expectedHTML); + this.setVariable("testResult",testResult); + this.setVariable("currentTiddler",this.testcaseTestOutput); + } + // Don't display anything if testHideIfPass is "yes" and the tests have passed + if(this.testcaseHideIfPass === "yes" && testResult !== "fail") { + return; + } + // Render the page root template of the subwiki + var rootWidget = this.testcaseWiki.makeTranscludeWidget(this.testcaseTemplate,{ + document: this.document, + parseAsInline: false, + parentWidget: this + }); + rootWidget.render(domNode); + // Trap changes in the wiki and refresh the rendering + this.testcaseWiki.addEventListener("change",function(changes) { + rootWidget.refresh(changes,domNode); + }); +}; + +/* +Compute the internal state of the widget +*/ +TestCaseWidget.prototype.execute = function() { + this.testcaseTemplate = this.getAttribute("template","$:/core/ui/testcases/DefaultTemplate"); + this.testcaseTestOutput = this.getAttribute("testOutput"); + this.testcaseTestActions = this.getAttribute("testActions"); + this.testcaseTestExpectedResult = this.getAttribute("testExpectedResult"); + this.testcaseHideIfPass = this.getAttribute("testHideIfPass"); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +TestCaseWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if($tw.utils.count(changedAttributes) > 0) { + this.refreshSelf(); + return true; + } else { + return this.contentRoot.refresh(changedTiddlers); + } +}; + +exports["testcase"] = TestCaseWidget; + +})(); diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js index d7862d2eb7..35b4941bd8 100755 --- a/core/modules/widgets/transclude.js +++ b/core/modules/widgets/transclude.js @@ -30,55 +30,410 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); this.execute(); - this.renderChildren(parent,nextSibling); + try { + this.renderChildren(parent,nextSibling); + } catch(error) { + if(error instanceof $tw.utils.TranscludeRecursionError) { + // We were infinite looping. + // We need to try and abort as much of the loop as we can, so we will keep "throwing" upward until we find a transclusion that has a different signature. + // Hopefully that will land us just outside where the loop began. That's where we want to issue an error. + // Rendering widgets beneath this point may result in a freezing browser if they explode exponentially. + var transcludeSignature = this.getVariable("transclusion"); + if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) { + // For the first fifty transcludes we climb up, we simply collect signatures. + // We're assuming that those first 50 will likely include all transcludes involved in the loop. + error.signatures[transcludeSignature] = true; + } else if(!error.signatures[transcludeSignature]) { + // Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering. + this.children = [this.makeChildWidget({type: "error", attributes: { + "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} + }})]; + this.renderChildren(parent,nextSibling); + return; + } + } + throw error; + } }; /* Compute the internal state of the widget */ TranscludeWidget.prototype.execute = function() { - // Get our parameters - this.transcludeTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); - this.transcludeSubTiddler = this.getAttribute("subtiddler"); - this.transcludeField = this.getAttribute("field"); - this.transcludeIndex = this.getAttribute("index"); - this.transcludeMode = this.getAttribute("mode"); - this.recursionMarker = this.getAttribute("recursionMarker","yes"); - // Parse the text reference + // Get our attributes, string parameters, and slot values into properties of the widget object + this.collectAttributes(); + this.collectStringParameters(); + this.collectSlotFillParameters(); + // Determine whether we're being used in inline or block mode var parseAsInline = !this.parseTreeNode.isBlock; if(this.transcludeMode === "inline") { parseAsInline = true; } else if(this.transcludeMode === "block") { parseAsInline = false; } - var parser = this.wiki.parseTextReference( + // Set 'thisTiddler' + this.setVariable("thisTiddler",this.transcludeTitle); + var parseTreeNodes, target; + // Process the transclusion according to the output type + switch(this.transcludeOutput || "text/html") { + case "text/html": + // Return the parse tree nodes of the target + target = this.parseTransclusionTarget(parseAsInline); + this.parseAsInline = target.parseAsInline; + parseTreeNodes = target.parseTreeNodes; + break; + case "text/raw": + // Just return the raw text + target = this.getTransclusionTarget(); + parseTreeNodes = [{type: "text", text: target.text}]; + break; + default: + // "text/plain" is the plain text result of wikifying the text + target = this.parseTransclusionTarget(parseAsInline); + var widgetNode = this.wiki.makeWidget(target.parser,{ + parentWidget: this, + document: $tw.fakeDocument + }); + var container = $tw.fakeDocument.createElement("div"); + widgetNode.render(container,null); + parseTreeNodes = [{type: "text", text: container.textContent}]; + break; + } + this.sourceText = target.text; + this.parserType = target.type; + // Set the legacy transclusion context variables only if we're not transcluding a variable + if(!this.transcludeVariable) { + var recursionMarker = this.makeRecursionMarker(); + this.setVariable("transclusion",recursionMarker); + } + // Construct the child widgets + this.makeChildWidgets(parseTreeNodes); +}; + +/* +Collect the attributes we need, in the process determining whether we're being used in legacy mode +*/ +TranscludeWidget.prototype.collectAttributes = function() { + var self = this; + // Detect legacy mode + this.legacyMode = true; + $tw.utils.each(this.attributes,function(value,name) { + if(name.charAt(0) === "$") { + self.legacyMode = false; + } + }); + // Get the attributes for the appropriate mode + if(this.legacyMode) { + this.transcludeTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); + this.transcludeSubTiddler = this.getAttribute("subtiddler"); + this.transcludeField = this.getAttribute("field"); + this.transcludeIndex = this.getAttribute("index"); + this.transcludeMode = this.getAttribute("mode"); + this.recursionMarker = this.getAttribute("recursionMarker","yes"); + } else { + this.transcludeVariable = this.getAttribute("$variable"); + this.transcludeVariableIsFunction = false; + this.transcludeType = this.getAttribute("$type"); + this.transcludeOutput = this.getAttribute("$output","text/html"); + this.transcludeTitle = this.getAttribute("$tiddler",this.getVariable("currentTiddler")); + this.transcludeSubTiddler = this.getAttribute("$subtiddler"); + this.transcludeField = this.getAttribute("$field"); + this.transcludeIndex = this.getAttribute("$index"); + this.transcludeMode = this.getAttribute("$mode"); + this.recursionMarker = this.getAttribute("$recursionMarker","yes"); + } +}; + +/* +Collect string parameters +*/ +TranscludeWidget.prototype.collectStringParameters = function() { + var self = this; + this.stringParametersByName = Object.create(null); + if(!this.legacyMode) { + $tw.utils.each(this.attributes,function(value,name) { + if(name.charAt(0) === "$") { + if(name.charAt(1) === "$") { + // Attributes starting $$ represent parameters starting with a single $ + name = name.slice(1); + } else { + // Attributes starting with a single $ are reserved for the widget + return; + } + } + self.stringParametersByName[name] = value; + }); + } +}; + +/* +Collect slot value parameters +*/ +TranscludeWidget.prototype.collectSlotFillParameters = function() { + var self = this; + this.slotFillParseTrees = Object.create(null); + if(this.legacyMode) { + this.slotFillParseTrees["ts-missing"] = this.parseTreeNode.children; + } else { + this.slotFillParseTrees["ts-raw"] = this.parseTreeNode.children; + var noFillWidgetsFound = true, + searchParseTreeNodes = function(nodes) { + $tw.utils.each(nodes,function(node) { + if(node.type === "fill") { + if(node.attributes["$name"] && node.attributes["$name"].type === "string") { + var slotValueName = node.attributes["$name"].value; + self.slotFillParseTrees[slotValueName] = node.children || []; + } + noFillWidgetsFound = false; + } else { + searchParseTreeNodes(node.children); + } + }); + }; + searchParseTreeNodes(this.parseTreeNode.children); + if(noFillWidgetsFound) { + this.slotFillParseTrees["ts-missing"] = this.parseTreeNode.children; + } + } +}; + +/* +Get transcluded details as an object {text:,type:} +*/ +TranscludeWidget.prototype.getTransclusionTarget = function() { + var self = this; + var text; + // Return the text and type of the target + if(this.hasAttribute("$variable")) { + if(this.transcludeVariable) { + // Transcluding a variable + var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}); + this.transcludeVariableIsFunction = variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition; + text = variableInfo.text; + this.transcludeFunctionResult = text; + return { + text: variableInfo.text, + type: this.transcludeType + }; + } + } else { + // Transcluding a text reference + var parserInfo = this.wiki.getTextReferenceParserInfo( + this.transcludeTitle, + this.transcludeField, + this.transcludeIndex, + { + subTiddler: this.transcludeSubTiddler, + defaultType: this.transcludeType + }); + return { + text: parserInfo.text, + type: parserInfo.type + }; + } +}; + +/* +Get transcluded parse tree nodes as an object {text:,type:,parseTreeNodes:,parseAsInline:} +*/ +TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) { + var self = this; + var parser; + // Get the parse tree + if(this.hasAttribute("$variable")) { + if(this.transcludeVariable) { + // Transcluding a variable + var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}), + srcVariable = variableInfo && variableInfo.srcVariable; + if(srcVariable && srcVariable.isFunctionDefinition) { + this.transcludeVariableIsFunction = true; + this.transcludeFunctionResult = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || ""; + } + if(variableInfo.text) { + if(srcVariable && srcVariable.isFunctionDefinition) { + parser = { + tree: [{ + type: "text", + text: this.transcludeFunctionResult + }], + source: this.transcludeFunctionResult, + type: "text/vnd.tiddlywiki" + }; + if(parseAsInline) { + parser.tree[0] = { + type: "text", + text: this.transcludeFunctionResult + }; + } else { + parser.tree[0] = { + type: "element", + tag: "p", + children: [{ + type: "text", + text: this.transcludeFunctionResult + }] + } + } + } else { + var cacheKey = (parseAsInline ? "inlineParser" : "blockParser") + (this.transcludeType || ""); + if(variableInfo.isCacheable && srcVariable[cacheKey]) { + parser = srcVariable[cacheKey]; + } else { + parser = this.wiki.parseText(this.transcludeType,variableInfo.text || "",{parseAsInline: parseAsInline, configTrimWhiteSpace: srcVariable && srcVariable.configTrimWhiteSpace}); + if(variableInfo.isCacheable) { + srcVariable[cacheKey] = parser; + } + } + } + if(parser) { + // Add parameters widget for procedures and custom widgets + if(srcVariable && (srcVariable.isProcedureDefinition || srcVariable.isWidgetDefinition)) { + parser = { + tree: [ + { + type: "parameters", + children: parser.tree + } + ], + source: parser.source, + type: parser.type + } + $tw.utils.each(srcVariable.params,function(param) { + var name = param.name; + // Parameter names starting with dollar must be escaped to double dollars + if(name.charAt(0) === "$") { + name = "$" + name; + } + $tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"]) + }); + } else if(srcVariable && !srcVariable.isFunctionDefinition) { + // For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__" + parser = { + tree: [ + { + type: "vars", + children: parser.tree + } + ], + source: parser.source, + type: parser.type + } + $tw.utils.each(variableInfo.params,function(param) { + $tw.utils.addAttributeToParseTreeNode(parser.tree[0],"__" + param.name + "__",param.value) + }); + } + } + } + } + } else { + // Transcluding a text reference + parser = this.wiki.parseTextReference( this.transcludeTitle, this.transcludeField, this.transcludeIndex, { parseAsInline: parseAsInline, - subTiddler: this.transcludeSubTiddler - }), - parseTreeNodes = parser ? parser.tree : this.parseTreeNode.children; - this.sourceText = parser ? parser.source : null; - this.parserType = parser? parser.type : null; - // Set context variables for recursion detection - var recursionMarker = this.makeRecursionMarker(); - if(this.recursionMarker === "yes") { - this.setVariable("transclusion",recursionMarker); + subTiddler: this.transcludeSubTiddler, + defaultType: this.transcludeType + }); } - // Check for recursion - if(parser) { - if(this.parentWidget && this.parentWidget.hasVariable("transclusion",recursionMarker)) { - parseTreeNodes = [{type: "error", attributes: { - "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} - }}]; + // Return the parse tree + return { + parser: parser, + parseTreeNodes: parser ? parser.tree : (this.slotFillParseTrees["ts-missing"] || []), + parseAsInline: parseAsInline, + text: parser && parser.source, + type: parser && parser.type + }; +}; + +/* +Fetch all the string parameters as an ordered array of {name:, value:} where the name is optional +*/ +TranscludeWidget.prototype.getOrderedTransclusionParameters = function() { + var result = []; + // Collect the parameters + for(var name in this.stringParametersByName) { + var value = this.stringParametersByName[name]; + result.push({name: name, value: value}); + } + // Sort numerical parameter names first + result.sort(function(a,b) { + var aIsNumeric = !isNaN(a.name), + bIsNumeric = !isNaN(b.name); + if(aIsNumeric && bIsNumeric) { + return a.name - b.name; + } else if(aIsNumeric) { + return -1; + } else if(bIsNumeric) { + return 1; + } else { + return a.name === b.name ? 0 : (a.name < b.name ? -1 : 1); + } + }); + // Remove names from numerical parameters + $tw.utils.each(result,function(param,index) { + if(!isNaN(param.name)) { + delete param.name; + } + }); + return result; +}; + +/* +Fetch the value of a parameter +*/ +TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaultValue) { + if(name in this.stringParametersByName) { + return this.stringParametersByName[name]; + } else { + var name = "" + index; + if(name in this.stringParametersByName) { + return this.stringParametersByName[name]; } } - // Construct the child widgets - this.makeChildWidgets(parseTreeNodes); + return defaultValue; }; +/* +Get one of the special parameters to be provided by the parameters widget +*/ +TranscludeWidget.prototype.getTransclusionMetaParameters = function() { + var self = this; + return { + "parseMode": function() { + return self.parseAsInline ? "inline" : "block"; + }, + "parseTreeNodes": function() { + return JSON.stringify(self.parseTreeNode.children || []); + }, + "slotFillParseTreeNodes": function() { + return JSON.stringify(self.slotFillParseTrees); + }, + "params": function() { + return JSON.stringify(self.stringParametersByName); + } + }; +}; + +/* +Fetch the value of a slot +*/ +TranscludeWidget.prototype.getTransclusionSlotFill = function(name,defaultParseTreeNodes) { + if(name && this.slotFillParseTrees[name] && this.slotFillParseTrees[name].length > 0) { + return this.slotFillParseTrees[name]; + } else { + return defaultParseTreeNodes || []; + } +}; + +/* +Return whether this transclusion should be visible to the slot widget +*/ +TranscludeWidget.prototype.hasVisibleSlots = function() { + return this.getAttribute("$fillignore","no") === "no"; +} + /* Compose a string comprising the title, field and/or index to identify this transclusion for recursion detection */ @@ -99,16 +454,24 @@ TranscludeWidget.prototype.makeRecursionMarker = function() { }; TranscludeWidget.prototype.parserNeedsRefresh = function() { + // Doesn't need to consider transcluded variables because a parent variable can't change once a widget has been created var parserInfo = this.wiki.getTextReferenceParserInfo(this.transcludeTitle,this.transcludeField,this.transcludeIndex,{subTiddler:this.transcludeSubTiddler}); return (this.sourceText === undefined || parserInfo.sourceText !== this.sourceText || parserInfo.parserType !== this.parserType) }; +TranscludeWidget.prototype.functionNeedsRefresh = function() { + var oldResult = this.transcludeFunctionResult; + var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}); + var newResult = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || ""; + return oldResult !== newResult; +} + /* Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ TranscludeWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(($tw.utils.count(changedAttributes) > 0) || (changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) { + if(($tw.utils.count(changedAttributes) > 0) || (this.transcludeVariableIsFunction && this.functionNeedsRefresh()) || (!this.transcludeVariable && changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) { this.refreshSelf(); return true; } else { diff --git a/core/modules/widgets/view.js b/core/modules/widgets/view.js index 6effc8939a..070836bffe 100755 --- a/core/modules/widgets/view.js +++ b/core/modules/widgets/view.js @@ -168,11 +168,11 @@ ViewWidget.prototype.getValueAsHtmlTextEncoded = function() { }; ViewWidget.prototype.getValueAsUrlEncoded = function() { - return encodeURIComponent(this.getValueAsText()); + return $tw.utils.encodeURIComponentExtended(this.getValueAsText()); }; ViewWidget.prototype.getValueAsDoubleUrlEncoded = function() { - return encodeURIComponent(encodeURIComponent(this.getValueAsText())); + return $tw.utils.encodeURIComponentExtended($tw.utils.encodeURIComponentExtended(this.getValueAsText())); }; ViewWidget.prototype.getValueAsDate = function(format) { diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 60f55e8bbc..eb84fab4a8 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -12,9 +12,6 @@ Widget base class /*global $tw: false */ "use strict"; -/* Maximum permitted depth of the widget tree for recursion detection */ -var MAX_WIDGET_TREE_DEPTH = 1000; - /* Create a widget object for a parse tree node parseTreeNode: reference to the parse tree node to be rendered @@ -41,10 +38,7 @@ Widget.prototype.initialise = function(parseTreeNode,options) { this.parseTreeNode = parseTreeNode; this.wiki = options.wiki; this.parentWidget = options.parentWidget; - this.variables = Object.create(null); - if(this.parentWidget) { - Object.setPrototypeOf(this.variables,this.parentWidget.variables); - } + this.variables = Object.create(this.parentWidget ? this.parentWidget.variables : null); this.document = options.document; this.attributes = {}; this.children = []; @@ -92,9 +86,22 @@ name: name of the variable value: value of the variable params: array of {name:, default:} for each parameter isMacroDefinition: true if the variable is set via a \define macro pragma (and hence should have variable substitution performed) +options includes: + isProcedureDefinition: true if the variable is set via a \procedure pragma (and hence should not have variable substitution performed) + isFunctionDefinition: true if the variable is set via a \function pragma (and hence should not have variable substitution performed) + isWidgetDefinition: true if the variable is set via a \widget pragma (and hence should not have variable substitution performed) */ -Widget.prototype.setVariable = function(name,value,params,isMacroDefinition) { - this.variables[name] = {value: value, params: params, isMacroDefinition: !!isMacroDefinition}; +Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) { + options = options || {}; + this.variables[name] = { + value: value, + params: params, + isMacroDefinition: !!isMacroDefinition, + isFunctionDefinition: !!options.isFunctionDefinition, + isProcedureDefinition: !!options.isProcedureDefinition, + isWidgetDefinition: !!options.isWidgetDefinition, + configTrimWhiteSpace: !!options.configTrimWhiteSpace + }; }; /* @@ -102,42 +109,79 @@ Get the prevailing value of a context variable name: name of variable options: see below Options include + params: array of {name:, value:} for each parameter defaultValue: default value if the variable is not defined +source: optional source iterator for evaluating function invocations +allowSelfAssigned: if true, includes the current widget in the context chain instead of just the parent Returns an object with the following fields: -params: array of {name:,value:} of parameters passed to wikitext variables +params: array of {name:,value:} or {value:} of parameters to be applied text: text of variable, with parameters properly substituted +resultList: result of variable evaluation as an array +srcVariable: reference to the object defining the variable */ Widget.prototype.getVariableInfo = function(name,options) { options = options || {}; - var actualParams = options.params || [], - parentWidget = this.parentWidget; + var self = this, + actualParams = options.params || [], + variable; + if(options.allowSelfAssigned) { + variable = this.variables[name]; + } else { + variable = this.parentWidget && this.parentWidget.variables[name]; + } // Check for the variable defined in the parent widget (or an ancestor in the prototype chain) - if(parentWidget && name in parentWidget.variables) { - var variable = parentWidget.variables[name], - originalValue = variable.value, + if(variable) { + var originalValue = variable.value, value = originalValue, - params = this.resolveVariableParameters(variable.params,actualParams); - // Substitute any parameters specified in the definition - $tw.utils.each(params,function(param) { - value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value); - }); - // Only substitute variable references if this variable was defined with the \define pragma + params = [], + resultList = [value]; + // Only substitute parameter and variable references if this variable was defined with the \define pragma if(variable.isMacroDefinition) { - value = this.substituteVariableReferences(value,options); + params = self.resolveVariableParameters(variable.params,actualParams); + // Substitute any parameters specified in the definition + $tw.utils.each(params,function(param) { + value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value); + }); + value = self.substituteVariableReferences(value,options); + resultList = [value]; + } else if(variable.isFunctionDefinition) { + // Function evaluations + params = self.resolveVariableParameters(variable.params,actualParams); + var variables = options.variables || Object.create(null); + // Apply default parameter values + $tw.utils.each(variable.params,function(param,index) { + if(param["default"]) { + variables[param.name] = param["default"]; + } + }); + // Parameters are an array of {value:} or {name:, value:} pairs + $tw.utils.each(params,function(param) { + variables[param.name] = param.value; + }); + resultList = this.wiki.filterTiddlers(value,this.makeFakeWidgetWithVariables(variables),options.source); + value = resultList[0] || ""; + } else { + params = variable.params; } return { text: value, params: params, + resultList: resultList, srcVariable: variable, isCacheable: originalValue === value }; } // If the variable doesn't exist in the parent widget then look for a macro module + var text = this.evaluateMacroModule(name,actualParams); + if(text === undefined) { + text = options.defaultValue; + } return { - text: this.evaluateMacroModule(name,actualParams,options.defaultValue) + text: text, + resultList: [text] }; }; @@ -148,6 +192,11 @@ Widget.prototype.getVariable = function(name,options) { return this.getVariableInfo(name,options).text; }; +/* +Maps actual parameters onto formal parameters, returning an array of {name:,value:} objects +formalParams - Array of {name:,default:} (default value is optional) +actualParams - Array of string values or {name:,value:} (name is optional) +*/ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams) { formalParams = formalParams || []; actualParams = actualParams || []; @@ -160,7 +209,7 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams) paramInfo = formalParams[p]; paramValue = undefined; for(var m=0; m 6) { + domNode.style[$tw.utils.unHyphenateCss(name.substr(6))] = value; + return; + } + // Check if the sourcePrefix is a match + if(name.substr(0,sourcePrefix.length) === sourcePrefix) { + name = destPrefix + name.substr(sourcePrefix.length); + } else { + value = undefined; + } // Check for excluded attribute names - if(options.excludeEventAttributes && name.substr(0,2) === "on") { + if(options.excludeEventAttributes && name.substr(0,2).toLowerCase() === EVENT_ATTRIBUTE_PREFIX) { value = undefined; } if(value !== undefined) { @@ -337,26 +450,24 @@ Widget.prototype.assignAttributes = function(domNode,options) { namespace = "http://www.w3.org/1999/xlink"; name = name.substr(6); } - // Handle styles - if(name.substr(0,6) === "style." && name.length > 6) { - domNode.style[$tw.utils.unHyphenateCss(name.substr(6))] = value; - } else { - // Setting certain attributes can cause a DOM error (eg xmlns on the svg element) - try { - domNode.setAttributeNS(namespace,name,value); - } catch(e) { - } + // Setting certain attributes can cause a DOM error (eg xmlns on the svg element) + try { + domNode.setAttributeNS(namespace,name,value); + } catch(e) { } } - } - // Not all parse tree nodes have the orderedAttributes property + }; + // If the parse tree node has the orderedAttributes property then use that order if(this.parseTreeNode.orderedAttributes) { $tw.utils.each(this.parseTreeNode.orderedAttributes,function(attribute,index) { - assignAttribute(attribute.name,self.attributes[attribute.name]); - }); + if(attribute.name in changedAttributes) { + assignAttribute(attribute.name,self.getAttribute(attribute.name)); + } + }); + // Otherwise update each changed attribute irrespective of order } else { - $tw.utils.each(Object.keys(self.attributes).sort(),function(name) { - assignAttribute(name,self.attributes[name]); + $tw.utils.each(changedAttributes,function(value,name) { + assignAttribute(name,self.getAttribute(name)); }); } }; @@ -383,10 +494,8 @@ Widget.prototype.makeChildWidgets = function(parseTreeNodes,options) { this.children = []; var self = this; // Check for too much recursion - if(this.getAncestorCount() > MAX_WIDGET_TREE_DEPTH) { - this.children.push(this.makeChildWidget({type: "error", attributes: { - "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} - }})); + if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH) { + throw new $tw.utils.TranscludeRecursionError(); } else { // Create set variable widgets for each variable $tw.utils.each(options.variables,function(value,name) { @@ -413,7 +522,34 @@ options include: variables: optional hashmap of variables to wrap around the widget */ Widget.prototype.makeChildWidget = function(parseTreeNode,options) { + var self = this; options = options || {}; + // Check whether this node type is defined by a custom widget definition + var variableDefinitionName = "$" + parseTreeNode.type; + if(this.variables[variableDefinitionName]) { + var isOverrideable = function() { + // Widget is overrideable if its name contains a period, or if it is an existing JS widget and we're not in safe mode + return parseTreeNode.type.indexOf(".") !== -1 || (!!self.widgetClasses[parseTreeNode.type] && !$tw.safeMode); + }; + if(!parseTreeNode.isNotRemappable && isOverrideable()) { + var variableInfo = this.getVariableInfo(variableDefinitionName,{allowSelfAssigned: true}); + if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.value && variableInfo.srcVariable.isWidgetDefinition) { + var newParseTreeNode = { + type: "transclude", + children: parseTreeNode.children, + isBlock: parseTreeNode.isBlock + }; + $tw.utils.addAttributeToParseTreeNode(newParseTreeNode,"$variable",variableDefinitionName); + $tw.utils.each(parseTreeNode.attributes,function(attr,name) { + // If the attribute starts with a dollar then add an extra dollar so that it doesn't clash with the $xxx attributes of transclude + name = name.charAt(0) === "$" ? "$" + name : name; + $tw.utils.addAttributeToParseTreeNode(newParseTreeNode,$tw.utils.extend({},attr,{name: name})); + }); + parseTreeNode = newParseTreeNode; + } + } + } + // Get the widget class for this node type var WidgetClass = this.widgetClasses[parseTreeNode.type]; if(!WidgetClass) { WidgetClass = this.widgetClasses.text; @@ -675,6 +811,35 @@ Widget.prototype.allowActionPropagation = function() { return true; }; +/* +Find child <$data> widgets recursively. The tag name allows aliased versions of the widget to be found too +*/ +Widget.prototype.findChildrenDataWidgets = function(children,tag,callback) { + var self = this; + $tw.utils.each(children,function(child) { + if(child.dataWidgetTag === tag) { + callback(child); + } + if(child.children) { + self.findChildrenDataWidgets(child.children,tag,callback); + } + }); +}; + +/* +Evaluate a variable with parameters. This is a static convenience method that attempts to evaluate a variable as a function, returning an array of strings +*/ +Widget.evaluateVariable = function(widget,name,options) { + var result; + if(widget.getVariableInfo) { + var variableInfo = widget.getVariableInfo(name,options); + result = variableInfo.resultList || [variableInfo.text]; + } else { + result = [widget.getVariable(name)]; + } + return result; +}; + exports.widget = Widget; })(); diff --git a/core/modules/wiki.js b/core/modules/wiki.js index c3f2723481..2954454d56 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -534,8 +534,8 @@ Return an array of tiddler titles that link to the specified tiddler */ exports.getTiddlerBacklinks = function(targetTitle) { var self = this, - backlinksIndexer = this.getIndexer("BacklinksIndexer"), - backlinks = backlinksIndexer && backlinksIndexer.lookup(targetTitle); + backIndexer = this.getIndexer("BackIndexer"), + backlinks = backIndexer && backIndexer.subIndexers.link.lookup(targetTitle); if(!backlinks) { backlinks = []; @@ -549,6 +549,82 @@ exports.getTiddlerBacklinks = function(targetTitle) { return backlinks; }; + +/* +Return an array of tiddler titles that are directly transcluded within the given parse tree. `title` is the tiddler being parsed, we will ignore its self-referential transclusions, only return + */ +exports.extractTranscludes = function(parseTreeRoot, title) { + // Count up the transcludes + var transcludes = [], + checkParseTree = function(parseTree, parentNode) { + for(var t=0; t`) means self-referential transclusion. + value = title; + } else if(parseTreeNode.attributes.field && parseTreeNode.attributes.field.type === "string") { + // Old usage with Empty value (like `<$transclude field='created'/>`) + value = title; + } + // Deduplicate the result. + if(value && transcludes.indexOf(value) === -1) { + $tw.utils.pushTop(transcludes,value); + } + } + if(parseTreeNode.children) { + checkParseTree(parseTreeNode.children,parseTreeNode); + } + } + }; + checkParseTree(parseTreeRoot); + return transcludes; +}; + + +/* +Return an array of tiddler titles that are transcluded from the specified tiddler +*/ +exports.getTiddlerTranscludes = function(title) { + var self = this; + // We'll cache the transcludes so they only get computed if the tiddler changes + return this.getCacheForTiddler(title,"transcludes",function() { + // Parse the tiddler + var parser = self.parseTiddler(title); + if(parser) { + // this will ignore self-referential transclusions from `title` + return self.extractTranscludes(parser.tree,title); + } + return []; + }); +}; + +/* +Return an array of tiddler titles that transclude to the specified tiddler +*/ +exports.getTiddlerBacktranscludes = function(targetTitle) { + var self = this, + backIndexer = this.getIndexer("BackIndexer"), + backtranscludes = backIndexer && backIndexer.subIndexers.transclude.lookup(targetTitle); + + if(!backtranscludes) { + backtranscludes = []; + } + return backtranscludes; +}; + /* Return a hashmap of tiddler titles that are referenced but not defined. Each value is the number of times the missing tiddler is referenced */ @@ -988,7 +1064,8 @@ exports.parseText = function(type,text,options) { return new Parser(type,text,{ parseAsInline: options.parseAsInline, wiki: this, - _canonical_uri: options._canonical_uri + _canonical_uri: options._canonical_uri, + configTrimWhiteSpace: options.configTrimWhiteSpace }); }; @@ -1028,10 +1105,11 @@ exports.parseTextReference = function(title,field,index,options) { }; exports.getTextReferenceParserInfo = function(title,field,index,options) { - var tiddler, + var defaultType = options.defaultType || "text/vnd.tiddlywiki", + tiddler, parserInfo = { sourceText : null, - parserType : "text/vnd.tiddlywiki" + parserType : defaultType }; if(options.subTiddler) { tiddler = this.getSubTiddler(title,options.subTiddler); @@ -1061,6 +1139,34 @@ exports.getTextReferenceParserInfo = function(title,field,index,options) { return parserInfo; } +/* +Parse a block of text of a specified MIME type + text: text on which to perform substitutions + widget + options: see below +Options include: + substitutions: an optional array of substitutions +*/ +exports.getSubstitutedText = function(text,widget,options) { + options = options || {}; + text = text || ""; + var self = this, + substitutions = options.substitutions || [], + output; + // Evaluate embedded filters and substitute with first result + output = text.replace(/\$\{([\S\s]+?)\}\$/g, function(match,filter) { + return self.filterTiddlers(filter,widget)[0] || ""; + }); + // Process any substitutions provided in options + $tw.utils.each(substitutions,function(substitute) { + output = $tw.utils.replaceString(output,new RegExp("\\$" + $tw.utils.escapeRegExp(substitute.name) + "\\$","mg"),substitute.value); + }); + // Substitute any variable references with their values + return output.replace(/\$\(([^\)\$]+)\)\$/g, function(match,varname) { + return widget.getVariable(varname,{defaultValue: ""}) + }); +}; + /* Make a widget tree for a parse tree parser: parser object @@ -1077,19 +1183,20 @@ exports.makeWidget = function(parser,options) { children: [] }, currWidgetNode = widgetNode; - // Create set variable widgets for each variable - $tw.utils.each(options.variables,function(value,name) { - var setVariableWidget = { - type: "set", + // Create let variable widget for variables + if($tw.utils.count(options.variables) > 0) { + var letVariableWidget = { + type: "let", attributes: { - name: {type: "string", value: name}, - value: {type: "string", value: value} }, children: [] }; - currWidgetNode.children = [setVariableWidget]; - currWidgetNode = setVariableWidget; - }); + $tw.utils.each(options.variables,function(value,name) { + $tw.utils.addAttributeToParseTreeNode(letVariableWidget,name,"" + value); + }); + currWidgetNode.children = [letVariableWidget]; + currWidgetNode = letVariableWidget; + } // Add in the supplied parse tree nodes currWidgetNode.children = parser ? parser.tree : []; // Create the widget @@ -1146,7 +1253,7 @@ exports.makeTranscludeWidget = function(title,options) { if(options.importVariables) { parseTreeImportVariables.attributes.filter.value = options.importVariables; } else if(options.importPageMacros) { - parseTreeImportVariables.attributes.filter.value = "[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]"; + parseTreeImportVariables.attributes.filter.value = this.getTiddlerText("$:/core/config/GlobalImportFilter"); } parseTreeDiv.tree[0].children.push(parseTreeImportVariables); parseTreeImportVariables.children.push(parseTreeTransclude); @@ -1256,7 +1363,7 @@ exports.search = function(text,options) { console.log("Regexp error parsing /(" + text + ")/" + flags + ": ",e); } } else if(options.some) { - terms = text.trim().split(/ +/); + terms = text.trim().split(/[^\S\xA0]+/); if(terms.length === 1 && terms[0] === "") { searchTermsRegExps = null; } else { @@ -1267,7 +1374,7 @@ exports.search = function(text,options) { searchTermsRegExps.push(new RegExp("(" + regExpStr + ")",flags)); } } else { // default: words - terms = text.split(/ +/); + terms = text.split(/[^\S\xA0]+/); if(terms.length === 1 && terms[0] === "") { searchTermsRegExps = null; } else { @@ -1412,6 +1519,14 @@ exports.checkTiddlerText = function(title,targetText,options) { return text === targetText; } +/* +Execute an action string without an associated context widget +*/ +exports.invokeActionString = function(actions,event,variables,options) { + var widget = this.makeWidget(null,{parentWidget: options.parentWidget}); + widget.invokeActionString(actions,null,event,variables); +}; + /* Read an array of browser File objects, invoking callback(tiddlerFieldsArray) once they're all read */ diff --git a/core/palettes/GruvBoxDark.tid b/core/palettes/GruvBoxDark.tid index 3b62eb3112..6c3fa6a73a 100644 --- a/core/palettes/GruvBoxDark.tid +++ b/core/palettes/GruvBoxDark.tid @@ -82,6 +82,10 @@ sidebar-tab-foreground: <> sidebar-tiddler-link-foreground-hover: #458588 sidebar-tiddler-link-foreground: #98971a site-title-foreground: <> +stability-deprecated: #cc241d +stability-experimental: #d79921 +stability-legacy: #458588 +stability-stable: #98971a static-alert-foreground: #B48EAD tab-background-selected: #ebdbb2 tab-background: #665c54 diff --git a/core/palettes/Nord.tid b/core/palettes/Nord.tid index b296ba7834..e8e2809989 100644 --- a/core/palettes/Nord.tid +++ b/core/palettes/Nord.tid @@ -82,6 +82,10 @@ sidebar-tab-foreground: <> sidebar-tiddler-link-foreground-hover: #A3BE8C sidebar-tiddler-link-foreground: #81A1C1 site-title-foreground: <> +stability-deprecated: #bf616a +stability-experimental: #d08770 +stability-legacy: #88c0d0 +stability-stable: #a3be8c static-alert-foreground: #B48EAD tab-background-selected: #ECEFF4 tab-background: #4C566A diff --git a/core/palettes/SolarizedDark.tid b/core/palettes/SolarizedDark.tid index eea273c307..bacd385b2a 100644 --- a/core/palettes/SolarizedDark.tid +++ b/core/palettes/SolarizedDark.tid @@ -18,7 +18,7 @@ button-foreground: #93a1a1 code-background: #073642 code-border: #586e75 code-foreground: #93a1a1 -dirty-indicator: inherit +dirty-indicator: #dc322f download-background: #859900 download-foreground: #073642 dragger-background: #073642 @@ -72,6 +72,10 @@ sidebar-tab-foreground-selected: #93a1a1 sidebar-tiddler-link-foreground: #2aa198 sidebar-tiddler-link-foreground-hover: #eee8d5 site-title-foreground: #d33682 +stability-deprecated: #dc322f +stability-experimental: #b58900 +stability-legacy: #268bd2 +stability-stable: #859900 static-alert-foreground: #93a1a1 tab-background: #073642 tab-background-selected: #002b36 diff --git a/core/palettes/SolarizedLight.tid b/core/palettes/SolarizedLight.tid index 3cf954bb55..5d8757386b 100644 --- a/core/palettes/SolarizedLight.tid +++ b/core/palettes/SolarizedLight.tid @@ -18,7 +18,7 @@ button-foreground: #586e75 code-background: #eee8d5 code-border: #93a1a1 code-foreground: #586e75 -dirty-indicator: inherit +dirty-indicator: #dc322f download-background: #859900 download-foreground: #eee8d5 dragger-background: #eee8d5 @@ -72,6 +72,10 @@ sidebar-tab-foreground-selected: #586e75 sidebar-tiddler-link-foreground: #2aa198 sidebar-tiddler-link-foreground-hover: #002b36 site-title-foreground: #d33682 +stability-deprecated: #dc322f +stability-experimental: #b58900 +stability-legacy: #268bd2 +stability-stable: #859900 static-alert-foreground: #586e75 tab-background: #eee8d5 tab-background-selected: #fdf6e3 diff --git a/core/palettes/Vanilla.tid b/core/palettes/Vanilla.tid index d84b4ec831..067f325197 100644 --- a/core/palettes/Vanilla.tid +++ b/core/palettes/Vanilla.tid @@ -54,6 +54,7 @@ modal-footer-background: #f5f5f5 modal-footer-border: #dddddd modal-header-border: #eeeeee muted-foreground: #bbb +network-activity-foreground: #448844 notification-background: #ffffdd notification-border: #999999 page-background: #f4f4f4 @@ -81,6 +82,10 @@ sidebar-tab-foreground: <> sidebar-tiddler-link-foreground-hover: #444444 sidebar-tiddler-link-foreground: #999999 site-title-foreground: <> +stability-stable: #008000 +stability-experimental: #c07c00 +stability-deprecated: #ff0000 +stability-legacy: #0000ff static-alert-foreground: #aaaaaa tab-background-selected: #ffffff tab-background: #d8d8d8 @@ -94,6 +99,9 @@ table-footer-background: #a8a8a8 table-header-background: #f0f0f0 tag-background: #ec6 tag-foreground: #ffffff +testcase-accent-level-1: #c1eaff +testcase-accent-level-2: #E3B740 +testcase-accent-level-3: #5FD564 tiddler-background: <> tiddler-border: <> tiddler-controls-foreground-hover: #888888 diff --git a/core/plugin.info b/core/plugin.info index da319c2e99..8902acdea9 100644 --- a/core/plugin.info +++ b/core/plugin.info @@ -5,5 +5,6 @@ "author": "JeremyRuston", "core-version": ">=5.0.0", "plugin-priority": "0", - "list": "readme" + "list": "readme", + "stability": "STABILITY_2_STABLE" } diff --git a/core/templates/exporters/StaticRiver.tid b/core/templates/exporters/StaticRiver.tid index 4583bc1cea..3b70c9d11a 100644 --- a/core/templates/exporters/StaticRiver.tid +++ b/core/templates/exporters/StaticRiver.tid @@ -3,6 +3,7 @@ tags: $:/tags/Exporter description: {{$:/language/Exporters/StaticRiver}} extension: .html +\define tv-config-static() yes \define tv-wikilink-template() #$uri_encoded$ \define tv-config-toolbar-icons() no \define tv-config-toolbar-text() no @@ -14,6 +15,7 @@ extension: .html + {{$:/core/wiki/title}} diff --git a/core/templates/exporters/StaticRiverContent.tid b/core/templates/exporters/StaticRiverContent.tid index 0b04549f9d..3d42be8071 100644 --- a/core/templates/exporters/StaticRiverContent.tid +++ b/core/templates/exporters/StaticRiverContent.tid @@ -3,5 +3,5 @@ title: $:/core/templates/exporters/StaticRiver/Content \define renderContent() {{{ $(exportFilter)$ ||$:/core/templates/static-tiddler}}} \end -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\import [subfilter{$:/core/config/GlobalImportFilter}] <> diff --git a/core/templates/exporters/TidFile.tid b/core/templates/exporters/TidFile.tid index 1dbd3503b8..7b0bb2d787 100644 --- a/core/templates/exporters/TidFile.tid +++ b/core/templates/exporters/TidFile.tid @@ -7,5 +7,5 @@ condition: [compare:lte[1]] \define renderContent() {{{ $(exportFilter)$ +[limit[1]] ||$:/core/templates/tid-tiddler}}} \end -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\import [subfilter{$:/core/config/GlobalImportFilter}] <> \ No newline at end of file diff --git a/core/templates/external-js/save-all-external-js.tid b/core/templates/external-js/save-all-external-js.tid index cd15cedc35..1f49088781 100644 --- a/core/templates/external-js/save-all-external-js.tid +++ b/core/templates/external-js/save-all-external-js.tid @@ -1,10 +1,14 @@ title: $:/core/save/all-external-js -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\whitespace trim +\import [subfilter{$:/core/config/GlobalImportFilter}] \define saveTiddlerFilter() -[is[tiddler]] -[prefix[$:/state/popup/]] -[prefix[$:/temp/]] -[prefix[$:/HistoryList]] -[status[pending]plugin-type[import]] -[[$:/core]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] $(publishFilter)$ +[is[tiddler]] -[prefix[$:/state/popup/]] -[prefix[$:/temp/]] -[prefix[$:/HistoryList]] -[status[pending]plugin-type[import]] -[[$:/core]] -[[$:/boot/boot.css]] -[is[system]type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] $(publishFilter)$ \end + + \define defaultCoreURL() %24%3A%2Fcore%2Ftemplates%2Ftiddlywiki5.js + <$let coreURL={{{ [[coreURL]is[variable]thenelse] }}}> {{$:/core/templates/tiddlywiki5-external-js.html}} diff --git a/core/templates/external-js/save-offline-external-js.tid b/core/templates/external-js/save-offline-external-js.tid index 2dfca5d52d..70cb8bbc0d 100644 --- a/core/templates/external-js/save-offline-external-js.tid +++ b/core/templates/external-js/save-offline-external-js.tid @@ -1,8 +1,9 @@ title: $:/core/save/offline-external-js -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\whitespace trim +\import [subfilter{$:/core/config/GlobalImportFilter}] \define saveTiddlerFilter() -[is[tiddler]] -[prefix[$:/state/popup/]] -[prefix[$:/temp/]] -[prefix[$:/HistoryList]] -[status[pending]plugin-type[import]] -[[$:/core]] -[[$:/plugins/tiddlywiki/filesystem]] -[[$:/plugins/tiddlywiki/tiddlyweb]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] $(publishFilter)$ +[is[tiddler]] -[prefix[$:/state/popup/]] -[prefix[$:/temp/]] -[prefix[$:/HistoryList]] -[status[pending]plugin-type[import]] -[[$:/core]] -[[$:/plugins/tiddlywiki/filesystem]] -[[$:/plugins/tiddlywiki/tiddlyweb]] -[[$:/boot/boot.css]] -[is[system]type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] $(publishFilter)$ \end \define defaultCoreURL() tiddlywikicore-$(version)$.js <$let coreURL={{{ [[coreURL]is[variable]thenelse] }}}> diff --git a/core/templates/external-js/tiddlywiki.js.load.tid b/core/templates/external-js/tiddlywiki.js.load.tid new file mode 100644 index 0000000000..496d6ed044 --- /dev/null +++ b/core/templates/external-js/tiddlywiki.js.load.tid @@ -0,0 +1,3 @@ +title: $:/core/templates/tiddlywiki.js/load-tiddler + +_load(window,<$macrocall $name="jsontiddler" $output="text/raw"/>); \ No newline at end of file diff --git a/core/templates/external-js/tiddlywiki.js.tid b/core/templates/external-js/tiddlywiki.js.tid index a8170663f7..f962569a2b 100644 --- a/core/templates/external-js/tiddlywiki.js.tid +++ b/core/templates/external-js/tiddlywiki.js.tid @@ -1,15 +1,48 @@ + title: $:/core/templates/tiddlywiki5.js \rules only filteredtranscludeinline transcludeinline codeinline -/* -{{ $:/core/copyright.txt ||$:/core/templates/plain-text-tiddler}} -`*/ -` -{{{ [is[system]type[application/javascript]library[yes]] ||$:/core/templates/plain-text-tiddler}}} - -{{ $:/boot/bootprefix.js ||$:/core/templates/plain-text-tiddler}} - -{{$:/core/templates/tiddlywiki5.js/tiddlers}} - -{{ $:/boot/boot.js ||$:/core/templates/plain-text-tiddler}} +`/* +`{{ $:/core/copyright.txt ||$:/core/templates/plain-text-tiddler}}` +*/ + +$tw = (typeof $tw === 'undefined') ? Object.create(null) : $tw; + +$tw.preloadTiddlers = $tw.preloadTiddlers || []; + +_load = function(window,tiddler) { + "use strict"; + var f; + $tw.preloadTiddlers.push(tiddler); + if(tiddler.library === "yes") { + var module = { exports:{} }; + var moduleName = function moduleName(path) { + var word = path.split("/").pop(); + word = word.substring(0,word.indexOf(".")) || word; + return word; + } + f = new Function("module",tiddler.text); + f(module); + window[moduleName(tiddler.title)] = module.exports; + } else { + f = new Function("window",tiddler.text); + f(window); + } +} + +/* ~~ Library modules ~~ */ + +`{{{ [is[system]type[application/javascript]library[yes]] ||$:/core/templates/tiddlywiki.js/load-tiddler}}}` + +/* ~~ Boot kernel prologue ~~ */ + +`{{ $:/boot/bootprefix.js ||$:/core/templates/tiddlywiki.js/load-tiddler}}` + +/* ~~ Core tiddlers ~~ */ + +`{{$:/core/templates/tiddlywiki5.js/tiddlers}}` + +/* ~~ Boot kernel ~~ */ + +`{{ $:/boot/boot.js ||$:/core/templates/tiddlywiki.js/load-tiddler}}` diff --git a/core/templates/external-js/tiddlywiki5-external-js.html.tid b/core/templates/external-js/tiddlywiki5-external-js.html.tid index 6a5c4c1bb9..b161584d71 100644 --- a/core/templates/external-js/tiddlywiki5-external-js.html.tid +++ b/core/templates/external-js/tiddlywiki5-external-js.html.tid @@ -1,48 +1,50 @@ title: $:/core/templates/tiddlywiki5-external-js.html -\rules only filteredtranscludeinline transcludeinline - -{{$:/core/templates/MOTW.html}} +<$set name="saveTiddlerAndShadowsFilter" filter="[subfilter] [subfilterplugintiddlers[]]"> +` +`{{$:/core/templates/MOTW.html}}` -{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified/TopHead]] ||$:/core/templates/raw-static-tiddler}}} +`{{{ [enlisttag[$:/tags/RawMarkupWikified/TopHead]] ||$:/core/templates/raw-static-tiddler}}}` - + - + -{{$:/core/wiki/title}} +`{{$:/core/wiki/title}}` -{{{ [all[shadows+tiddlers]tag[$:/core/wiki/rawmarkup]] [all[shadows+tiddlers]tag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}} -{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}} +`{{{ [enlisttag[$:/core/wiki/rawmarkup]] ||$:/core/templates/plain-text-tiddler}}}` +`{{{ [enlisttag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}}` +`{{{ [enlisttag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}` -{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified/TopBody]] ||$:/core/templates/raw-static-tiddler}}} +`{{{ [enlisttag[$:/tags/RawMarkupWikified/TopBody]] ||$:/core/templates/raw-static-tiddler}}}`
-{{$:/boot/boot.css||$:/core/templates/css-tiddler}} +`{{$:/boot/boot.css||$:/core/templates/css-tiddler}}`
-{{$:/core/templates/store.area.template.html}} +`{{$:/core/templates/store.area.template.html}}` -{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified/BottomBody]] ||$:/core/templates/raw-static-tiddler}}} +`{{{ [enlisttag[$:/tags/RawMarkupWikified/BottomBody]] ||$:/core/templates/raw-static-tiddler}}}` + + - - - +` + \ No newline at end of file diff --git a/core/templates/html-json-skinny-tiddler.tid b/core/templates/html-json-skinny-tiddler.tid index 1e3c032f3e..6402bcee52 100644 --- a/core/templates/html-json-skinny-tiddler.tid +++ b/core/templates/html-json-skinny-tiddler.tid @@ -1,4 +1,3 @@ title: $:/core/templates/html-json-skinny-tiddler -<$list filter="[compare:number:gteq[1]] ~[!match[1]]">`,`<$text text=<>/> -<$jsontiddler tiddler=<> exclude="text" escapeUnsafeScriptChars="yes"/> +<$text text=<>/><$jsontiddler tiddler=<> exclude="text" escapeUnsafeScriptChars="yes"/> diff --git a/core/templates/html-json-tiddler.tid b/core/templates/html-json-tiddler.tid index 6b62b4ac99..2e12290a72 100644 --- a/core/templates/html-json-tiddler.tid +++ b/core/templates/html-json-tiddler.tid @@ -1,3 +1,3 @@ title: $:/core/templates/html-json-tiddler -<$list filter="[!match[1]]">`,`<$text text=<>/><$jsontiddler tiddler=<> escapeUnsafeScriptChars="yes"/> \ No newline at end of file +<$jsontiddler tiddler=<> escapeUnsafeScriptChars="yes"/> \ No newline at end of file diff --git a/core/templates/save-all.tid b/core/templates/save-all.tid index b298ad49f7..a316d1954c 100644 --- a/core/templates/save-all.tid +++ b/core/templates/save-all.tid @@ -1,7 +1,7 @@ title: $:/core/save/all -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\import [subfilter{$:/core/config/GlobalImportFilter}] \define saveTiddlerFilter() -[is[tiddler]] -[prefix[$:/state/popup/]] -[prefix[$:/temp/]] -[prefix[$:/HistoryList]] -[status[pending]plugin-type[import]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] $(publishFilter)$ +[is[tiddler]] -[prefix[$:/state/popup/]] -[prefix[$:/temp/]] -[prefix[$:/HistoryList]] -[status[pending]plugin-type[import]] -[[$:/boot/boot.css]] -[is[system]type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] $(publishFilter)$ \end {{$:/core/templates/tiddlywiki5.html}} diff --git a/core/templates/save-empty.tid b/core/templates/save-empty.tid index 6f0da4822c..0b1c33b595 100644 --- a/core/templates/save-empty.tid +++ b/core/templates/save-empty.tid @@ -1,6 +1,6 @@ title: $:/core/save/empty \define saveTiddlerFilter() -[is[system]] -[prefix[$:/state/popup/]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] +[is[system]] -[prefix[$:/state/popup/]] -[[$:/boot/boot.css]] -[is[system]type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] \end {{$:/core/templates/tiddlywiki5.html}} diff --git a/core/templates/save-lazy-all.tid b/core/templates/save-lazy-all.tid index a4b5cd6e97..da4353fba6 100644 --- a/core/templates/save-lazy-all.tid +++ b/core/templates/save-lazy-all.tid @@ -1,7 +1,7 @@ title: $:/core/save/lazy-all \define saveTiddlerFilter() -[is[system]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] [is[tiddler]type[application/javascript]] +[sort[title]] +[is[system]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]] -[[$:/boot/boot.css]] -[is[system]type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] [is[tiddler]type[application/javascript]] +[sort[title]] \end \define skinnySaveTiddlerFilter() [!is[system]] -[type[application/javascript]] diff --git a/core/templates/save-lazy-images.tid b/core/templates/save-lazy-images.tid index 0a4a842959..b23b348f0a 100644 --- a/core/templates/save-lazy-images.tid +++ b/core/templates/save-lazy-images.tid @@ -1,7 +1,7 @@ title: $:/core/save/lazy-images \define saveTiddlerFilter() -[is[tiddler]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] -[!is[system]is[image]] +[sort[title]] +[is[tiddler]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]] -[[$:/boot/boot.css]] -[is[system]type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] -[!is[system]is[image]] +[sort[title]] \end \define skinnySaveTiddlerFilter() [!is[system]is[image]] diff --git a/core/templates/server/static.tiddler.html.tid b/core/templates/server/static.tiddler.html.tid index 1a803bd860..6c9fd80a57 100644 --- a/core/templates/server/static.tiddler.html.tid +++ b/core/templates/server/static.tiddler.html.tid @@ -1,8 +1,9 @@ title: $:/core/templates/server/static.tiddler.html \whitespace trim +\define tv-config-static() yes \define tv-wikilink-template() $uri_encoded$ -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\import [subfilter{$:/core/config/GlobalImportFilter}] diff --git a/core/templates/single.tiddler.window.tid b/core/templates/single.tiddler.window.tid index 0d14509e5a..aa5175c01c 100644 --- a/core/templates/single.tiddler.window.tid +++ b/core/templates/single.tiddler.window.tid @@ -4,7 +4,7 @@ title: $:/core/templates/single.tiddler.window \define containerClasses() tc-page-container tc-page-view-$(storyviewTitle)$ tc-language-$(languageTitle)$ \end -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\import [subfilter{$:/core/config/GlobalImportFilter}] <$vars tv-config-toolbar-icons={{$:/config/Toolbar/Icons}} diff --git a/core/templates/static.template.html.tid b/core/templates/static.template.html.tid index 5da5fb7524..8b6482846a 100644 --- a/core/templates/static.template.html.tid +++ b/core/templates/static.template.html.tid @@ -1,6 +1,7 @@ title: $:/core/templates/static.template.html type: text/vnd.tiddlywiki-html +\define tv-config-static() yes \define tv-wikilink-template() static/$uri_doubleencoded$.html \define tv-config-toolbar-icons() no \define tv-config-toolbar-text() no diff --git a/core/templates/static.tiddler.html.tid b/core/templates/static.tiddler.html.tid index a4537305a6..a3297ee783 100644 --- a/core/templates/static.tiddler.html.tid +++ b/core/templates/static.tiddler.html.tid @@ -1,10 +1,11 @@ title: $:/core/templates/static.tiddler.html \define tv-wikilink-template() $uri_doubleencoded$.html +\define tv-config-static() yes \define tv-config-toolbar-icons() no \define tv-config-toolbar-text() no \define tv-config-toolbar-class() tc-btn-invisible -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\import [subfilter{$:/core/config/GlobalImportFilter}] ` diff --git a/core/templates/store.area.template.html.tid b/core/templates/store.area.template.html.tid index 84dd0c4328..2dc115266a 100644 --- a/core/templates/store.area.template.html.tid +++ b/core/templates/store.area.template.html.tid @@ -6,14 +6,12 @@ title: $:/core/templates/store.area.template.html <$list filter="[[storeAreaFormat]is[variable]getvariable[]else[json]match[json]]"> `` `` @@ -22,8 +20,8 @@ title: $:/core/templates/store.area.template.html <$reveal type="nomatch" state="$:/isEncrypted" text="yes"> `` diff --git a/core/ui/AdvancedSearch/Filter.tid b/core/ui/AdvancedSearch/Filter.tid index c5a460f28f..5b82f9b32c 100644 --- a/core/ui/AdvancedSearch/Filter.tid +++ b/core/ui/AdvancedSearch/Filter.tid @@ -40,10 +40,8 @@ caption: {{$:/language/Search/Filter/Caption}} <$action-sendmessage $message="tm-edit-tiddler" $param={{{ [<__tiddler__>get[text]] }}}/> \end - \whitespace trim <> - - <$reveal state="$:/temp/advancedsearch" type="nomatch" text=""> <$set name="resultCount" value="<$count filter={{$:/temp/advancedsearch}}/>">
-<> +

<>

<$list filter={{$:/temp/advancedsearch}}> addsuffix[-primaryList]] -[[$:/temp/advancedsearch/selected-item]get[text]] +[then[]else[tc-list-item-selected]] }}}> <$transclude tiddler="$:/core/ui/ListItemTemplate"/> diff --git a/core/ui/AdvancedSearch/Standard.tid b/core/ui/AdvancedSearch/Standard.tid index 0690130e4b..e6ed18a7ab 100644 --- a/core/ui/AdvancedSearch/Standard.tid +++ b/core/ui/AdvancedSearch/Standard.tid @@ -54,17 +54,18 @@ caption: {{$:/language/Search/Standard/Caption}} variable="listItem"> <$vars userInput={{{ [[$:/temp/advancedsearch]get[text]] }}} - configTiddler={{{ [[$:/state/search/currentTab]!is[missing]get[text]] ~[{$:/config/SearchResults/Default}] }}} + configTiddler={{{ [[$:/state/advancedsearch/standard/currentTab]!is[missing]get[text]] ~[{$:/config/SearchResults/Default}] }}} searchListState="$:/temp/advancedsearch/selected-item"> -<$list - filter="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]butfirst[]limit[1]]" - emptyMessage="<$list filter='[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]]'><$transclude/>"> +<$list filter="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]butfirst[]limit[1]]"> <$macrocall $name="tabs" tabsList="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]]" default={{$:/config/SearchResults/Default}} actions="<$action-setfield $tiddler='$:/state/advancedsearch/standard/currentTab' text=<>/>" explicitState="$:/state/tab/search-results/advancedsearch" /> +<$list filter="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]butfirst[]limit[1]] :else[[]]"> +<$list filter="[all[shadows+tiddlers]tag[$:/tags/SearchResults]!has[draft.of]]"><$transclude mode="block"/> + diff --git a/core/ui/AlertTemplate.tid b/core/ui/AlertTemplate.tid index ae15818a0e..d67586b8d3 100644 --- a/core/ui/AlertTemplate.tid +++ b/core/ui/AlertTemplate.tid @@ -1,3 +1,4 @@ +code-body: yes title: $:/core/ui/AlertTemplate \whitespace trim diff --git a/core/ui/Components/VisibleTransclude.tid b/core/ui/Components/VisibleTransclude.tid new file mode 100644 index 0000000000..27fdff998a --- /dev/null +++ b/core/ui/Components/VisibleTransclude.tid @@ -0,0 +1,48 @@ +title: $:/core/ui/VisibleTransclude + + +\widget $transclude() + +<$parameters tiddler="" $$tiddler="" mode="" $$mode="" $parseMode="@parseMode" $params="@params"> + + <$let + mode={{{ [[$mode]is[variable]then<$mode>!is[blank]] :else[[mode]is[variable]then!is[blank]] :else[<@parseMode>] }}} + outputTag={{{ [match[inline]then[span]else[div]] }}} + outputColour={{{ [match[inline]then[green]else[red]] }}} + > + + <$genesis $type=<> style="color:white;padding:4px;" style.background=<>> + <$genesis $type=<> style="display: inline-block;"> +
+ + <$list filter="[<@params>jsonindexes[]]" emptyMessage="(none)"> +
+ <$text text=<>/><$text text=": "/><$text text={{{ [<@params>jsonget] }}}/> +
+ +
+ + <$genesis $type=<> style="background:white;color:black;padding:4px;"> + + <$list filter="[<@params>jsonindexes[]] :filter[prefix[$]] +[limit[1]]" variable="ignore" emptyMessage=""" + + <$genesis $type="$transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" recursionMarker="no" mode=<> $$fillignore="yes"> + + <$slot $name="ts-raw"/> + + """> + + <$genesis $type="$transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" $$recursionMarker="no" $$mode=<> $$fillignore="yes"> + + <$slot $name="ts-raw"/> + + + + + + +\end diff --git a/core/ui/Components/plugin-info.tid b/core/ui/Components/plugin-info.tid index 35bb228555..d7f408bd91 100644 --- a/core/ui/Components/plugin-info.tid +++ b/core/ui/Components/plugin-info.tid @@ -45,7 +45,17 @@ $:/config/Plugins/Disabled/$(currentTiddler)$ <$view field="title"/>

-
<$view field="version"/>
+
+ <%if [get[stability]match[STABILITY_0_DEPRECATED]] %> + DEPRECATED + <%elseif [get[stability]match[STABILITY_1_EXPERIMENTAL]] %> + EXPERIMENTAL + <%elseif [get[stability]match[STABILITY_2_STABLE]] %> + STABLE + <%elseif [get[stability]match[STABILITY_3_LEGACY]] %> + LEGACY + <%endif%> + <$view field="version"/>

\end diff --git a/core/ui/ControlPanel/Basics.tid b/core/ui/ControlPanel/Basics.tid index dd5580ad56..b2ef2832a4 100644 --- a/core/ui/ControlPanel/Basics.tid +++ b/core/ui/ControlPanel/Basics.tid @@ -26,10 +26,10 @@ caption: {{$:/language/ControlPanel/Basics/Caption}} |<$link to="$:/SiteSubtitle"><> |<$edit-text tiddler="$:/SiteSubtitle" default="" tag="input"/> | |<$link to="$:/status/UserName"><> |<$edit-text tiddler="$:/status/UserName" default="" tag="input"/> | |<$link to="$:/config/AnimationDuration"><> |<$edit-text tiddler="$:/config/AnimationDuration" default="" tag="input"/> | -|<$link to="$:/DefaultTiddlers"><> |<>
<$edit class="tc-edit-texteditor" tiddler="$:/DefaultTiddlers"/>
//<>// | +|<$link to="$:/DefaultTiddlers"><> |<>
<$edit class="tc-edit-texteditor" tiddler="$:/DefaultTiddlers" autoHeight="yes"/>
//<>// | |<$link to="$:/language/DefaultNewTiddlerTitle"><> |<$edit-text tiddler="$:/language/DefaultNewTiddlerTitle" default="" tag="input"/> | |<$link to="$:/config/NewJournal/Title"><> |<$edit-text tiddler="$:/config/NewJournal/Title" default="" tag="input"/> | -|<$link to="$:/config/NewJournal/Text"><> |<$edit tiddler="$:/config/NewJournal/Text" class="tc-edit-texteditor" default=""/> | +|<$link to="$:/config/NewJournal/Text"><> |<$edit tiddler="$:/config/NewJournal/Text" class="tc-edit-texteditor" default="" autoHeight="yes"/> | |<$link to="$:/config/NewTiddler/Tags"><> |<$vars currentTiddler="$:/config/NewTiddler/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><><$action-listops $tiddler=<> $field="text" $subfilter={{{ [get[tags]] }}}/><$action-setfield $tiddler=<> tags=""/> | |<$link to="$:/config/NewJournal/Tags"><> |<$vars currentTiddler="$:/config/NewJournal/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><><$action-listops $tiddler=<> $field="text" $subfilter={{{ [get[tags]] }}}/><$action-setfield $tiddler=<> tags=""/> | |<$link to="$:/config/AutoFocus"><> |{{$:/snippets/minifocusswitcher}} | diff --git a/core/ui/ControlPanel/Modals/AddPlugins.tid b/core/ui/ControlPanel/Modals/AddPlugins.tid index ce8612b724..fe096d6d0b 100644 --- a/core/ui/ControlPanel/Modals/AddPlugins.tid +++ b/core/ui/ControlPanel/Modals/AddPlugins.tid @@ -70,9 +70,20 @@ $:/state/add-plugin-info/$(connectionTiddler)$/$(assetInfo)$

<$text text={{{ [get[name]] ~[get[original-title]split[/]last[1]] }}}/>: -<$view tiddler=<> field="description"/>

+<$view tiddler=<> field="description"/> +

<$view tiddler=<> field="original-title"/>

-
<$view tiddler=<> field="version"/>
+
+<%if [get[stability]match[STABILITY_0_DEPRECATED]] %> + DEPRECATED +<%elseif [get[stability]match[STABILITY_1_EXPERIMENTAL]] %> + EXPERIMENTAL +<%elseif [get[stability]match[STABILITY_2_STABLE]] %> + STABLE +<%elseif [get[stability]match[STABILITY_3_LEGACY]] %> + LEGACY +<%endif%> +<$view tiddler=<> field="version"/>
<$list filter="[get[original-title]get[version]]" variable="installedVersion">
{{$:/language/ControlPanel/Plugins/AlreadyInstalled/Hint}}
@@ -211,7 +222,7 @@ $:/state/add-plugin-info/$(connectionTiddler)$/$(assetInfo)$
\end -\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] +\import [subfilter{$:/core/config/GlobalImportFilter}] \whitespace trim
diff --git a/core/ui/ControlPanel/Parsing.tid b/core/ui/ControlPanel/Parsing.tid index 818d1f292c..3a31738750 100644 --- a/core/ui/ControlPanel/Parsing.tid +++ b/core/ui/ControlPanel/Parsing.tid @@ -12,7 +12,7 @@ field="text" checked="enable" unchecked="disable" default="enable"> -<> +<> \end diff --git a/core/ui/ControlPanel/Saving/DownloadSaver.tid b/core/ui/ControlPanel/Saving/DownloadSaver.tid index 42e4dc3a9a..be658a1ff3 100644 --- a/core/ui/ControlPanel/Saving/DownloadSaver.tid +++ b/core/ui/ControlPanel/Saving/DownloadSaver.tid @@ -2,10 +2,19 @@ title: $:/core/ui/ControlPanel/Saving/DownloadSaver tags: $:/tags/ControlPanel/Saving caption: {{$:/language/ControlPanel/Saving/DownloadSaver/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Saving/DownloadSaver/ +
>> + <> -!! <$link to="$:/config/DownloadSaver/AutoSave"><> +!!.tc-control-panel-accent <$link to="$:/config/DownloadSaver/AutoSave"><> -<$checkbox tiddler="$:/config/DownloadSaver/AutoSave" field="text" checked="yes" unchecked="no" default="no"> <> +<$checkbox tiddler="$:/config/DownloadSaver/AutoSave" + field="text" checked="yes" unchecked="no" default="no" + class="tc-control-panel-item" +> + <> + +
\ No newline at end of file diff --git a/core/ui/ControlPanel/Saving/General.tid b/core/ui/ControlPanel/Saving/General.tid index d1b0962813..38c3f34fbc 100644 --- a/core/ui/ControlPanel/Saving/General.tid +++ b/core/ui/ControlPanel/Saving/General.tid @@ -3,14 +3,22 @@ tags: $:/tags/ControlPanel/Saving caption: {{$:/language/ControlPanel/Saving/General/Caption}} list-before: +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/ +
>> + {{$:/language/ControlPanel/Saving/General/Hint}} -!! <$link to="$:/config/AutoSave"><> +!!.tc-control-panel-accent <$link to="$:/config/AutoSave"><> <> -<$radio tiddler="$:/config/AutoSave" value="yes"> <> +<$radio tiddler="$:/config/AutoSave" value="yes"> + <> + -<$radio tiddler="$:/config/AutoSave" value="no"> <> +<$radio tiddler="$:/config/AutoSave" value="no"> + <> + +
\ No newline at end of file diff --git a/core/ui/ControlPanel/Settings.tid b/core/ui/ControlPanel/Settings.tid index f4a4b13c2b..74004ffa0e 100644 --- a/core/ui/ControlPanel/Settings.tid +++ b/core/ui/ControlPanel/Settings.tid @@ -2,18 +2,6 @@ title: $:/core/ui/ControlPanel/Settings tags: $:/tags/ControlPanel caption: {{$:/language/ControlPanel/Settings/Caption}} -\define lingo-base() $:/language/ControlPanel/Settings/ - -<> - -<$list filter="[all[shadows+tiddlers]tag[$:/tags/ControlPanel/Settings]]"> - -
- -!! <$link><$transclude field="caption"/> - -<$transclude/> - -
- - +
+<$macrocall $name="tabs" tabsList="[all[shadows+tiddlers]tag[$:/tags/ControlPanel/SettingsTab]!has[draft.of]]" default="$:/core/ui/ControlPanel/Settings/TiddlyWiki" explicitState="$:/state/tab--697582678"/> +
\ No newline at end of file diff --git a/core/ui/ControlPanel/Settings/CamelCase.tid b/core/ui/ControlPanel/Settings/CamelCase.tid index 36377bb859..3feace84b2 100644 --- a/core/ui/ControlPanel/Settings/CamelCase.tid +++ b/core/ui/ControlPanel/Settings/CamelCase.tid @@ -2,7 +2,16 @@ title: $:/core/ui/ControlPanel/Settings/CamelCase tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/CamelCase/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/CamelCase/ + <> -<$checkbox tiddler="$:/config/WikiParserRules/Inline/wikilink" field="text" checked="enable" unchecked="disable" default="enable"> <$link to="$:/config/WikiParserRules/Inline/wikilink"><> +<$checkbox tiddler="$:/config/WikiParserRules/Inline/wikilink" + field="text" checked="enable" unchecked="disable" default="enable" + class="tc-control-panel-item" +> + <$link to="$:/config/WikiParserRules/Inline/wikilink" class="tc-tiny-gap-left"> + <> + + diff --git a/core/ui/ControlPanel/Settings/DefaultMoreSidebarTab.tid b/core/ui/ControlPanel/Settings/DefaultMoreSidebarTab.tid index 47f277bd45..c3ad60aac0 100644 --- a/core/ui/ControlPanel/Settings/DefaultMoreSidebarTab.tid +++ b/core/ui/ControlPanel/Settings/DefaultMoreSidebarTab.tid @@ -2,13 +2,18 @@ caption: {{$:/language/ControlPanel/Settings/DefaultMoreSidebarTab/Caption}} tags: $:/tags/ControlPanel/Settings title: $:/core/ui/ControlPanel/Settings/DefaultMoreSidebarTab -\define lingo-base() $:/language/ControlPanel/Settings/DefaultMoreSidebarTab/ \whitespace trim +\define lingo-base() $:/language/ControlPanel/Settings/DefaultMoreSidebarTab/ -<$link to="$:/config/DefaultMoreSidebarTab"><> +<$link to="$:/config/DefaultMoreSidebarTab" class="tc-control-panel-item"> + <> + -<$select tiddler="$:/config/DefaultMoreSidebarTab"> -<$list filter="[all[shadows+tiddlers]tag[$:/tags/MoreSideBar]!has[draft.of]]"> - - +<$select tiddler="$:/config/DefaultMoreSidebarTab" class="tc-select"> + <$list filter="[all[shadows+tiddlers]tag[$:/tags/MoreSideBar]!has[draft.of]]"> + + diff --git a/core/ui/ControlPanel/Settings/DefaultSidebarTab.tid b/core/ui/ControlPanel/Settings/DefaultSidebarTab.tid index acd3421c70..1f4c5fc7bf 100644 --- a/core/ui/ControlPanel/Settings/DefaultSidebarTab.tid +++ b/core/ui/ControlPanel/Settings/DefaultSidebarTab.tid @@ -5,10 +5,16 @@ title: $:/core/ui/ControlPanel/Settings/DefaultSidebarTab \define lingo-base() $:/language/ControlPanel/Settings/DefaultSidebarTab/ \whitespace trim -<$link to="$:/config/DefaultSidebarTab"><> +<$link to="$:/config/DefaultSidebarTab" class="tc-control-panel-item"> + <> + -<$select tiddler="$:/config/DefaultSidebarTab"> -<$list filter="[all[shadows+tiddlers]tag[$:/tags/SideBar]!has[draft.of]]"> - - +<$select tiddler="$:/config/DefaultSidebarTab" class="tc-select"> + <$list filter="[all[shadows+tiddlers]tag[$:/tags/SideBar]!has[draft.of]]"> + + diff --git a/core/ui/ControlPanel/Settings/EditorToolbar.tid b/core/ui/ControlPanel/Settings/EditorToolbar.tid index aa142bf621..ad73845680 100644 --- a/core/ui/ControlPanel/Settings/EditorToolbar.tid +++ b/core/ui/ControlPanel/Settings/EditorToolbar.tid @@ -2,8 +2,15 @@ title: $:/core/ui/ControlPanel/Settings/EditorToolbar tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/EditorToolbar/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/EditorToolbar/ <> -<$checkbox tiddler="$:/config/TextEditor/EnableToolbar" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/TextEditor/EnableToolbar"><> - +<$checkbox tiddler="$:/config/TextEditor/EnableToolbar" + field="text" checked="yes" unchecked="no" default="yes" + class="tc-control-panel-item" +> + <$link to="$:/config/TextEditor/EnableToolbar" class="tc-tiny-gap-left"> + <> + + diff --git a/core/ui/ControlPanel/Settings/InfoPanelMode.tid b/core/ui/ControlPanel/Settings/InfoPanelMode.tid index 371b6d61b2..e539b8f829 100644 --- a/core/ui/ControlPanel/Settings/InfoPanelMode.tid +++ b/core/ui/ControlPanel/Settings/InfoPanelMode.tid @@ -2,9 +2,17 @@ title: $:/core/ui/ControlPanel/Settings/InfoPanelMode tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/InfoPanelMode/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/InfoPanelMode/ -<$link to="$:/config/TiddlerInfo/Mode"><> -<$radio tiddler="$:/config/TiddlerInfo/Mode" value="popup"> <> +<$link to="$:/config/TiddlerInfo/Mode" class="tc-control-panel-item"> + <> + -<$radio tiddler="$:/config/TiddlerInfo/Mode" value="sticky"> <> +<$radio tiddler="$:/config/TiddlerInfo/Mode" value="popup"> + <> + + +<$radio tiddler="$:/config/TiddlerInfo/Mode" value="sticky"> + <> + diff --git a/core/ui/ControlPanel/Settings/LinkToBehaviour.tid b/core/ui/ControlPanel/Settings/LinkToBehaviour.tid index 92d46601ef..dc98b1ae6f 100644 --- a/core/ui/ControlPanel/Settings/LinkToBehaviour.tid +++ b/core/ui/ControlPanel/Settings/LinkToBehaviour.tid @@ -2,21 +2,25 @@ title: $:/core/ui/ControlPanel/Settings/LinkToBehaviour tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/LinkToBehaviour/Caption}} -\define lingo-base() $:/language/ControlPanel/Settings/LinkToBehaviour/ \whitespace trim +\define lingo-base() $:/language/ControlPanel/Settings/LinkToBehaviour/ -<$link to="$:/config/Navigation/openLinkFromInsideRiver"><> +<$link to="$:/config/Navigation/openLinkFromInsideRiver" class="tc-control-panel-item"> + <> + -<$select tiddler="$:/config/Navigation/openLinkFromInsideRiver"> - - - - +<$select tiddler="$:/config/Navigation/openLinkFromInsideRiver" class="tc-select"> + + + + -<$link to="$:/config/Navigation/openLinkFromOutsideRiver"><> +<$link to="$:/config/Navigation/openLinkFromOutsideRiver" class="tc-control-panel-item"> + <> + -<$select tiddler="$:/config/Navigation/openLinkFromOutsideRiver"> - - +<$select tiddler="$:/config/Navigation/openLinkFromOutsideRiver" class="tc-select"> + + diff --git a/core/ui/ControlPanel/Settings/MissingLinks.tid b/core/ui/ControlPanel/Settings/MissingLinks.tid index 4a7ba5f2e1..e0149c9a17 100644 --- a/core/ui/ControlPanel/Settings/MissingLinks.tid +++ b/core/ui/ControlPanel/Settings/MissingLinks.tid @@ -2,8 +2,12 @@ title: $:/core/ui/ControlPanel/Settings/MissingLinks tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/MissingLinks/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/MissingLinks/ <> -<$checkbox tiddler="$:/config/MissingLinks" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/MissingLinks"><> - +<$checkbox tiddler="$:/config/MissingLinks" field="text" checked="yes" unchecked="no" default="yes"> + <$link to="$:/config/MissingLinks" class="tc-control-panel-item"> + <> + + diff --git a/core/ui/ControlPanel/Settings/NavigationAddressBar.tid b/core/ui/ControlPanel/Settings/NavigationAddressBar.tid index 4a123ba99e..f35f8a1f12 100644 --- a/core/ui/ControlPanel/Settings/NavigationAddressBar.tid +++ b/core/ui/ControlPanel/Settings/NavigationAddressBar.tid @@ -2,12 +2,21 @@ title: $:/core/ui/ControlPanel/Settings/NavigationAddressBar tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/NavigationAddressBar/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/NavigationAddressBar/ -<$link to="$:/config/Navigation/UpdateAddressBar"><> +<$link to="$:/config/Navigation/UpdateAddressBar" class="tc-control-panel-item"> + <> + -<$radio tiddler="$:/config/Navigation/UpdateAddressBar" value="permaview"> <> +<$radio tiddler="$:/config/Navigation/UpdateAddressBar" value="permaview"> + <> + -<$radio tiddler="$:/config/Navigation/UpdateAddressBar" value="permalink"> <> +<$radio tiddler="$:/config/Navigation/UpdateAddressBar" value="permalink"> + <> + -<$radio tiddler="$:/config/Navigation/UpdateAddressBar" value="no"> <> +<$radio tiddler="$:/config/Navigation/UpdateAddressBar" value="no"> + <> + diff --git a/core/ui/ControlPanel/Settings/NavigationHistory.tid b/core/ui/ControlPanel/Settings/NavigationHistory.tid index af63de1eee..bd118a103d 100644 --- a/core/ui/ControlPanel/Settings/NavigationHistory.tid +++ b/core/ui/ControlPanel/Settings/NavigationHistory.tid @@ -2,9 +2,17 @@ title: $:/core/ui/ControlPanel/Settings/NavigationHistory tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/NavigationHistory/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/NavigationHistory/ -<$link to="$:/config/Navigation/UpdateHistory"><> -<$radio tiddler="$:/config/Navigation/UpdateHistory" value="yes"> <> +<$link to="$:/config/Navigation/UpdateHistory" class="tc-control-panel-item"> + <> + -<$radio tiddler="$:/config/Navigation/UpdateHistory" value="no"> <> +<$radio tiddler="$:/config/Navigation/UpdateHistory" value="yes"> + <> + + +<$radio tiddler="$:/config/Navigation/UpdateHistory" value="no"> + <> + diff --git a/core/ui/ControlPanel/Settings/NavigationPermalinkviewMode.tid b/core/ui/ControlPanel/Settings/NavigationPermalinkviewMode.tid index 5a496d5e1a..6c15936e7d 100644 --- a/core/ui/ControlPanel/Settings/NavigationPermalinkviewMode.tid +++ b/core/ui/ControlPanel/Settings/NavigationPermalinkviewMode.tid @@ -2,9 +2,24 @@ title: $:/core/ui/ControlPanel/Settings/NavigationPermalinkviewMode tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/NavigationPermalinkviewMode/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/NavigationPermalinkviewMode/ <> -<$checkbox tiddler="$:/config/Navigation/Permalinkview/CopyToClipboard" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/Navigation/Permalinkview/CopyToClipboard"><> +<$checkbox tiddler="$:/config/Navigation/Permalinkview/CopyToClipboard" + field="text" checked="yes" unchecked="no" default="yes" + class="tc-control-panel-item" +> + <$link to="$:/config/Navigation/Permalinkview/CopyToClipboard" class="tc-tiny-gap-left"> + <> + + -<$checkbox tiddler="$:/config/Navigation/Permalinkview/UpdateAddressBar" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/Navigation/Permalinkview/UpdateAddressBar"><> +<$checkbox tiddler="$:/config/Navigation/Permalinkview/UpdateAddressBar" + field="text" checked="yes" unchecked="no" default="yes" + class="tc-control-panel-item" +> + <$link to="$:/config/Navigation/Permalinkview/UpdateAddressBar" class="tc-tiny-gap-left"> + <> + + diff --git a/core/ui/ControlPanel/Settings/PerformanceInstrumentation.tid b/core/ui/ControlPanel/Settings/PerformanceInstrumentation.tid index b3d1d97635..1ea9061ae7 100644 --- a/core/ui/ControlPanel/Settings/PerformanceInstrumentation.tid +++ b/core/ui/ControlPanel/Settings/PerformanceInstrumentation.tid @@ -2,7 +2,15 @@ title: $:/core/ui/ControlPanel/Settings/PerformanceInstrumentation tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/PerformanceInstrumentation/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/PerformanceInstrumentation/ <> -<$checkbox tiddler="$:/config/Performance/Instrumentation" field="text" checked="yes" unchecked="no" default="no"> <$link to="$:/config/Performance/Instrumentation"><> +<$checkbox tiddler="$:/config/Performance/Instrumentation" + field="text" checked="yes" unchecked="no" default="no" + class="tc-control-panel-item" +> + <$link to="$:/config/Performance/Instrumentation" class="tc-tiny-gap-left"> + <> + + diff --git a/core/ui/ControlPanel/Settings/TitleLinks.tid b/core/ui/ControlPanel/Settings/TitleLinks.tid index c1acdc7bd0..1620dfe393 100644 --- a/core/ui/ControlPanel/Settings/TitleLinks.tid +++ b/core/ui/ControlPanel/Settings/TitleLinks.tid @@ -2,9 +2,17 @@ title: $:/core/ui/ControlPanel/Settings/TitleLinks tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/TitleLinks/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/TitleLinks/ -<$link to="$:/config/Tiddlers/TitleLinks"><> -<$radio tiddler="$:/config/Tiddlers/TitleLinks" value="yes"> <> +<$link to="$:/config/Tiddlers/TitleLinks" class="tc-control-panel-item"> + <> + -<$radio tiddler="$:/config/Tiddlers/TitleLinks" value="no"> <> +<$radio tiddler="$:/config/Tiddlers/TitleLinks" value="yes"> + <> + + +<$radio tiddler="$:/config/Tiddlers/TitleLinks" value="no"> + <> + diff --git a/core/ui/ControlPanel/Settings/ToolbarButtonStyle.tid b/core/ui/ControlPanel/Settings/ToolbarButtonStyle.tid index a25b2a39e6..c02d653d75 100644 --- a/core/ui/ControlPanel/Settings/ToolbarButtonStyle.tid +++ b/core/ui/ControlPanel/Settings/ToolbarButtonStyle.tid @@ -2,12 +2,15 @@ title: $:/core/ui/ControlPanel/Settings/ToolbarButtonStyle tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/ToolbarButtonStyle/Caption}} -\define lingo-base() $:/language/ControlPanel/Settings/ToolbarButtonStyle/ \whitespace trim -<$link to="$:/config/Toolbar/ButtonClass"><> +\define lingo-base() $:/language/ControlPanel/Settings/ToolbarButtonStyle/ -<$select tiddler="$:/config/Toolbar/ButtonClass"> -<$list filter="[all[shadows+tiddlers]tag[$:/tags/ToolbarButtonStyle]]"> - - +<$link to="$:/config/Toolbar/ButtonClass" class="tc-control-panel-item"> + <> + + +<$select tiddler="$:/config/Toolbar/ButtonClass" class="tc-select"> + <$list filter="[all[shadows+tiddlers]tag[$:/tags/ToolbarButtonStyle]]"> + + diff --git a/core/ui/ControlPanel/Settings/ToolbarButtons.tid b/core/ui/ControlPanel/Settings/ToolbarButtons.tid index 00bdb191e3..d76394ee8b 100644 --- a/core/ui/ControlPanel/Settings/ToolbarButtons.tid +++ b/core/ui/ControlPanel/Settings/ToolbarButtons.tid @@ -2,9 +2,24 @@ title: $:/core/ui/ControlPanel/Settings/ToolbarButtons tags: $:/tags/ControlPanel/Settings caption: {{$:/language/ControlPanel/Settings/ToolbarButtons/Caption}} +\whitespace trim \define lingo-base() $:/language/ControlPanel/Settings/ToolbarButtons/ <> -<$checkbox tiddler="$:/config/Toolbar/Icons" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/Toolbar/Icons"><> +<$checkbox tiddler="$:/config/Toolbar/Icons" + field="text" checked="yes" unchecked="no" default="yes" + class="tc-control-panel-item" +> + <$link to="$:/config/Toolbar/Icons" class="tc-tiny-gap-left"> + <> + + -<$checkbox tiddler="$:/config/Toolbar/Text" field="text" checked="yes" unchecked="no" default="no"> <$link to="$:/config/Toolbar/Text"><> +<$checkbox tiddler="$:/config/Toolbar/Text" + field="text" checked="yes" unchecked="no" default="no" + class="tc-control-panel-item" +> + <$link to="$:/config/Toolbar/Text" class="tc-tiny-gap-left"> + <> + + diff --git a/core/ui/ControlPanel/TestCases.tid b/core/ui/ControlPanel/TestCases.tid new file mode 100644 index 0000000000..401e141130 --- /dev/null +++ b/core/ui/ControlPanel/TestCases.tid @@ -0,0 +1,10 @@ +title: $:/core/ui/ControlPanel/TestCases +tags: $:/tags/ControlPanel/Advanced +caption: {{$:/language/ControlPanel/TestCases/Caption}} + +\whitespace trim +{{$:/language/ControlPanel/TestCases/Hint}} + +
+<$macrocall $name="tabs" tabsList="[all[shadows+tiddlers]tag[$:/tags/ControlPanel/TestCases]!has[draft.of]]" default="$:/core/ui/ControlPanel/TestCases/All"/> +
diff --git a/core/ui/ControlPanel/TestCasesAll.tid b/core/ui/ControlPanel/TestCasesAll.tid new file mode 100644 index 0000000000..571fb93c23 --- /dev/null +++ b/core/ui/ControlPanel/TestCasesAll.tid @@ -0,0 +1,24 @@ +title: $:/core/ui/ControlPanel/TestCases/All +tags: $:/tags/ControlPanel/TestCases +caption: {{$:/language/ControlPanel/TestCases/All/Caption}} + +\define lingo-base() $:/language/ControlPanel/ +<> + +<$list filter="[all[tiddlers+shadows]tag[$:/tags/wiki-test-spec]type[text/vnd.tiddlywiki-multiple]] [all[tiddlers+shadows]tag[$:/tags/wiki-test-spec-failing]type[text/vnd.tiddlywiki-multiple]]"> + +

+ +<$link> + +<$text text=<>/> + + + +

+ +<$transclude + $tiddler="$:/core/ui/TestCaseTemplate" +/> + + diff --git a/core/ui/ControlPanel/TestCasesFailed.tid b/core/ui/ControlPanel/TestCasesFailed.tid new file mode 100644 index 0000000000..4ab2d062da --- /dev/null +++ b/core/ui/ControlPanel/TestCasesFailed.tid @@ -0,0 +1,15 @@ +title: $:/core/ui/ControlPanel/TestCases/Failed +tags: $:/tags/ControlPanel/TestCases +caption: {{$:/language/ControlPanel/TestCases/Failed/Caption}} + +\define lingo-base() $:/language/ControlPanel/ +<> + +<$list filter="[all[tiddlers+shadows]tag[$:/tags/wiki-test-spec]type[text/vnd.tiddlywiki-multiple]] [all[tiddlers+shadows]tag[$:/tags/wiki-test-spec-failing]type[text/vnd.tiddlywiki-multiple]]"> + +<$transclude + $tiddler="$:/core/ui/TestCaseTemplate" + hideIfPass="yes" +/> + + diff --git a/plugins/tiddlywiki/codemirror/ui/controlpanel/tiddlywiki.tid b/core/ui/ControlPanel/TiddlyWiki.tid similarity index 64% rename from plugins/tiddlywiki/codemirror/ui/controlpanel/tiddlywiki.tid rename to core/ui/ControlPanel/TiddlyWiki.tid index f88865997a..f8d923da2c 100644 --- a/plugins/tiddlywiki/codemirror/ui/controlpanel/tiddlywiki.tid +++ b/core/ui/ControlPanel/TiddlyWiki.tid @@ -1,6 +1,7 @@ title: $:/core/ui/ControlPanel/Settings/TiddlyWiki tags: $:/tags/ControlPanel/SettingsTab caption: TiddlyWiki +list-before: \define lingo-base() $:/language/ControlPanel/Settings/ @@ -8,9 +9,9 @@ caption: TiddlyWiki <$list filter="[all[shadows+tiddlers]tag[$:/tags/ControlPanel/Settings]]"> -
+
> > -!! <$link><$transclude field="caption"/> +!!.tc-control-panel-accent <$link><$transclude field="caption"/> <$transclude/> diff --git a/core/ui/DownloadFullWiki.tid b/core/ui/DownloadFullWiki.tid new file mode 100644 index 0000000000..bfae2ceb6b --- /dev/null +++ b/core/ui/DownloadFullWiki.tid @@ -0,0 +1,18 @@ +title: $:/core/ui/DownloadFullWiki + +\whitespace trim +\rules except wikilink + +To download the standard single-file version of your wiki: + +<$wikify name="site-title" text={{$:/config/SaveWikiButton/Filename}}> +<$let publishFilter="""-[[$:/config/SaveWikiButton/Template]] -[[$:/plugins/tiddlywiki/filesystem]] -[[$:/plugins/tiddlywiki/tiddlyweb]]"""> +<$button tooltip="Download fully standalone wiki" aria-label="download full wiki" class="tc-btn-big-green"> +<$action-sendmessage $message="tm-download-file" $param="$:/core/save/all" publishFilter=<> filename=<>/> +{{$:/core/images/download-button}} + +Download full wiki + + + + \ No newline at end of file diff --git a/core/ui/EditTemplate.tid b/core/ui/EditTemplate.tid index 4d6dec5d68..6ad84a1398 100644 --- a/core/ui/EditTemplate.tid +++ b/core/ui/EditTemplate.tid @@ -1,3 +1,4 @@ +code-body: yes title: $:/core/ui/EditTemplate \define delete-edittemplate-state-tiddlers() @@ -46,9 +47,7 @@ title: $:/core/ui/EditTemplate <$keyboard key="((cancel-edit-tiddler))" actions=<> tag="div"> <$keyboard key="((save-tiddler))" actions=<> tag="div"> <$list filter="[all[shadows+tiddlers]tag[$:/tags/EditTemplate]!has[draft.of]]" variable="listItem"> -<$set name="tv-config-toolbar-class" filter="[] [encodeuricomponent[]addprefix[tc-btn-]]"> <$transclude tiddler=<>/> - diff --git a/core/ui/EditTemplate/Preview/output.tid b/core/ui/EditTemplate/Preview/output.tid index 4e5bf0e331..5c53d8c22c 100644 --- a/core/ui/EditTemplate/Preview/output.tid +++ b/core/ui/EditTemplate/Preview/output.tid @@ -2,7 +2,7 @@ title: $:/core/ui/EditTemplate/body/preview/output tags: $:/tags/EditPreview caption: {{$:/language/EditTemplate/Body/Preview/Type/Output}} -\import [all[shadows+tiddlers]tag[$:/tags/Macro/View]!has[draft.of]] [all[shadows+tiddlers]tag[$:/tags/Macro/View/Body]!has[draft.of]] +\import [all[shadows+tiddlers]tag[$:/tags/Macro/View]!is[draft]] [all[shadows+tiddlers]tag[$:/tags/Macro/View/Body]!is[draft]] [all[shadows+tiddlers]tag[$:/tags/Global/View]!is[draft]] [all[shadows+tiddlers]tag[$:/tags/Global/View/Body]!is[draft]] <$set name="tv-tiddler-preview" value="yes"> <$transclude tiddler={{{ [] :cascade[all[shadows+tiddlers]tag[$:/tags/ViewTemplateBodyFilter]!is[draft]get[text]] :and[!is[blank]else[$:/core/ui/ViewTemplate/body/default]] }}} /> diff --git a/core/ui/EditTemplate/body/default.tid b/core/ui/EditTemplate/body/default.tid index a2128efb08..d004032f14 100644 --- a/core/ui/EditTemplate/body/default.tid +++ b/core/ui/EditTemplate/body/default.tid @@ -1,5 +1,9 @@ title: $:/core/ui/EditTemplate/body/default +\function edit-preview-state() +[{$:/config/ShowEditPreview/PerTiddler}!match[yes]then[$:/state/showeditpreview]] :else[] +[get[text]] :else[[no]] +\end + \define config-visibility-title() $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$ \end @@ -10,15 +14,18 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$ \whitespace trim <$let - edit-preview-state={{{ [{$:/config/ShowEditPreview/PerTiddler}!match[yes]then[$:/state/showeditpreview]] :else[] }}} + qualified-preview-state=<> + editPreviewStateTiddler={{{ [{$:/config/ShowEditPreview/PerTiddler}!match[yes]then[$:/state/showeditpreview]] :else[] }}} importTitle=<> importState=<> > <$dropzone importTitle=<> autoOpenOnImport="no" contentTypesFilter={{$:/config/Editor/ImportContentTypesFilter}} class="tc-dropzone-editor" enable={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} filesOnly="yes" actions=<> > -<$reveal stateTitle=<> type="match" text="yes" tag="div"> -
+
+
match[yes]then[tc-tiddler-preview]else[tc-tiddler-preview-hidden]] [[tc-tiddler-editor]] +[join[ ]] }}}> <$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/> +<$list filter="[get[text]match[yes]]" variable="ignore"> +
<$transclude tiddler={{$:/state/editpreviewtype}} mode="inline"> @@ -29,13 +36,12 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
+ +
- -<$reveal stateTitle=<> type="nomatch" text="yes" tag="div"> +
-<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/> - - + diff --git a/core/ui/EditTemplate/controls.tid b/core/ui/EditTemplate/controls.tid index 3e2f5e3f3d..e97cedd781 100644 --- a/core/ui/EditTemplate/controls.tid +++ b/core/ui/EditTemplate/controls.tid @@ -1,12 +1,18 @@ title: $:/core/ui/EditTemplate/controls tags: $:/tags/EditTemplate -\define config-title() -$:/config/EditToolbarButtons/Visibility/$(listItem)$ -\end +\define config-title() $:/config/EditToolbarButtons/Visibility/$(listItem)$ \whitespace trim
-<$view field="title"/> -<$list filter="[all[shadows+tiddlers]tag[$:/tags/EditToolbar]!has[draft.of]]" variable="listItem"><$reveal type="nomatch" state=<> text="hide"><$transclude tiddler=<>/> -
+ <$view field="title"/> + + <$list filter="[all[shadows+tiddlers]tag[$:/tags/EditToolbar]!has[draft.of]]" variable="listItem"> + <$let tv-config-toolbar-class={{{ [enlist] [encodeuricomponent[]addprefix[tc-btn-]] +[join[ ]] }}}> + <$reveal type="nomatch" state=<> text="hide"> + <$transclude $tiddler=<>/> + + + + +
diff --git a/core/ui/EditTemplate/fields.tid b/core/ui/EditTemplate/fields.tid index 6a767517b4..0edc335054 100644 --- a/core/ui/EditTemplate/fields.tid +++ b/core/ui/EditTemplate/fields.tid @@ -54,7 +54,7 @@ $:/config/EditTemplateFields/Visibility/$(currentField)$ \whitespace trim <$vars name={{{ [get[text]] }}}> <$reveal type="nomatch" text="" default=<>> -<$button tooltip=<>> +<$button tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}> <$action-sendmessage $message="tm-add-field" $name=<> $value={{{ [subfilterget[text]] }}}/> @@ -89,7 +89,7 @@ $value={{{ [subfilterget[text]] }}}/> <$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}> -<$action-deletefield $field=<>/><$set name="currentTiddlerCSSescaped" value={{{ [escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<>/> +<$action-deletefield $field=<>/> {{$:/core/images/delete-button}} diff --git a/core/ui/EditTemplate/tags.tid b/core/ui/EditTemplate/tags.tid index 8d829b30e8..c8d4131feb 100644 --- a/core/ui/EditTemplate/tags.tid +++ b/core/ui/EditTemplate/tags.tid @@ -3,39 +3,63 @@ tags: $:/tags/EditTemplate \whitespace trim -\define lingo-base() $:/language/EditTemplate/ +\procedure lingo-base() $:/language/EditTemplate/ -\define tag-styles() -background-color:$(backgroundColor)$; -fill:$(foregroundColor)$; -color:$(foregroundColor)$; +\procedure tag-body-inner(colour,fallbackTarget,colourA,colourB,icon,tagField:"tags") +<$wikify name="foregroundColor" + text="""<$macrocall $name="contrastcolour" + target=<> + fallbackTarget=<> + colourA=<> + colourB=<>/> + """ +> + <$let backgroundColor=<> > + > + style.color=<> + style.fill=<> + style.background-color=<> + > + <$transclude tiddler=<>/> + <$view field="title" format="text"/> + <$button class="tc-btn-invisible tc-remove-tag-button" + style.fill=<> + > + <$action-listops $tiddler=<> $field=<> $subfilter="-[{!!title}]"/> + {{$:/core/images/close-button}} + + + + \end -\define tag-body-inner(colour,fallbackTarget,colourA,colourB,icon,tagField:"tags") -\whitespace trim -<$vars foregroundColor=<> backgroundColor="""$colour$"""> -> class="tc-tag-label tc-tag-list-item tc-small-gap-right"> -<$transclude tiddler="""$icon$"""/><$view field="title" format="text" /> -<$button class="tc-btn-invisible tc-remove-tag-button" style=<>><$action-listops $tiddler=<> $field=<<__tagField__>> $subfilter="-[{!!title}]"/>{{$:/core/images/close-button}} - - +\procedure tag-body(colour,palette,icon,tagField:"tags") +<$macrocall $name="tag-body-inner" + colour=`$(colour)$` + colourA={{{ [getindex[foreground]] }}} + colourB={{{ [getindex[background]] }}} + fallbackTarget={{{ [getindex[tag-background]] }}} + icon=<> + tagField=<> +/> \end -\define tag-body(colour,palette,icon,tagField:"tags") -<$macrocall $name="tag-body-inner" colour="""$colour$""" fallbackTarget={{$palette$##tag-background}} colourA={{$palette$##foreground}} colourB={{$palette$##background}} icon="""$icon$""" tagField=<<__tagField__>>/> -\end - -\define edit-tags-template(tagField:"tags") -\whitespace trim +\procedure edit-tags-template(tagField:"tags")
-<$list filter="[list[!!$tagField$]sort[title]]" storyview="pop"> -<$macrocall $name="tag-body" colour={{{ [] :cascade[all[shadows+tiddlers]tag[$:/tags/TiddlerColourFilter]!is[draft]get[text]] }}} palette={{$:/palette}} icon={{{ [] :cascade[all[shadows+tiddlers]tag[$:/tags/TiddlerIconFilter]!is[draft]get[text]] }}} tagField=<<__tagField__>>/> - -<$vars tabIndex={{$:/config/EditTabIndex}} cancelPopups="yes"> -<$macrocall $name="tag-picker" tagField=<<__tagField__>>/> - + <$list filter="[getenlist-input[]sort[title]]" storyview="pop"> + <$macrocall $name="tag-body" + colour={{{ [] :cascade[all[shadows+tiddlers]tag[$:/tags/TiddlerColourFilter]!is[draft]get[text]] }}} + palette={{$:/palette}} + icon={{{ [] :cascade[all[shadows+tiddlers]tag[$:/tags/TiddlerIconFilter]!is[draft]get[text]] }}} + tagField=<> + /> + + <$let tabIndex={{$:/config/EditTabIndex}} cancelPopups="yes"> + <$macrocall $name="tag-picker" tagField=<>/> +
\end -<$set name="saveTiddler" value=<>> -<$macrocall $name="edit-tags-template" tagField=<>/> - +<$let saveTiddler=<>> + <$macrocall $name="edit-tags-template" tagField=<>/> + diff --git a/core/ui/EditTemplate/type.tid b/core/ui/EditTemplate/type.tid index faa89639f3..c1c38b72ae 100644 --- a/core/ui/EditTemplate/type.tid +++ b/core/ui/EditTemplate/type.tid @@ -10,7 +10,7 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d <>
<$fieldmangler> -<$macrocall $name="keyboard-driven-input" tiddler=<> storeTitle=<> refreshTitle=<> selectionStateTitle=<> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<>/><$button popup=<> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}<$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[] [] []"/> +<$macrocall $name="keyboard-driven-input" tiddler=<> storeTitle=<> refreshTitle=<> selectionStateTitle=<> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<>/><$button popup=<> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}<$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[] [] [] []"/>
diff --git a/core/ui/EditToolbar/save.tid b/core/ui/EditToolbar/save.tid index c539009ee7..3872e76748 100644 --- a/core/ui/EditToolbar/save.tid +++ b/core/ui/EditToolbar/save.tid @@ -3,16 +3,23 @@ tags: $:/tags/EditToolbar caption: {{$:/core/images/done-button}} {{$:/language/Buttons/Save/Caption}} description: {{$:/language/Buttons/Save/Hint}} +\whitespace trim \define save-tiddler-button() \whitespace trim -<$fieldmangler><$button tooltip={{$:/language/Buttons/Save/Hint}} aria-label={{$:/language/Buttons/Save/Caption}} class=<>> -<> -<$list filter="[match[yes]]"> -{{$:/core/images/done-button}} - -<$list filter="[match[yes]]"> -<$text text={{$:/language/Buttons/Save/Caption}}/> - - +<$fieldmangler> + <$button + tooltip={{$:/language/Buttons/Save/Hint}} + aria-label={{$:/language/Buttons/Save/Caption}} + class=<> + > + <> + <$list filter="[match[yes]]"> + {{$:/core/images/done-button}} + + <$list filter="[match[yes]]"> + <$text text={{$:/language/Buttons/Save/Caption}}/> + + + \end <> diff --git a/core/ui/EditorToolbar/link-dropdown.tid b/core/ui/EditorToolbar/link-dropdown.tid index e2766935bd..d2887a180e 100644 --- a/core/ui/EditorToolbar/link-dropdown.tid +++ b/core/ui/EditorToolbar/link-dropdown.tid @@ -18,7 +18,7 @@ title: $:/core/ui/EditorToolbar/link-dropdown \define external-link() \whitespace trim -<$button class="tc-btn-invisible" style="width: auto; display: inline-block; background-colour: inherit;" actions=<>> +<$button class="tc-btn-invisible tc-btn-mini" style="width: auto; display: inline-block; background-colour: inherit;" actions=<>> {{$:/core/images/chevron-right}} \end @@ -45,7 +45,7 @@ title: $:/core/ui/EditorToolbar/link-dropdown <$reveal tag="span" state=<> type="nomatch" text=""> <> -<$button class="tc-btn-invisible" style="width: auto; display: inline-block; background-colour: inherit;"> +<$button class="tc-btn-invisible tc-btn-mini" style="width: auto; display: inline-block; background-colour: inherit;"> <><$set name="cssEscapedTitle" value={{{ [escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<>/> {{$:/core/images/close-button}} diff --git a/core/ui/EditorToolbar/preview.tid b/core/ui/EditorToolbar/preview.tid index 106b28d3c5..d5c63eb5fa 100644 --- a/core/ui/EditorToolbar/preview.tid +++ b/core/ui/EditorToolbar/preview.tid @@ -9,11 +9,8 @@ button-classes: tc-text-editor-toolbar-item-start-group shortcuts: ((preview)) \whitespace trim -<$reveal state=<> type="match" text="yes" tag="span"> -{{$:/core/images/preview-open}} -<$action-setfield $tiddler=<> $value="no"/> - -<$reveal state=<> type="nomatch" text="yes" tag="span"> -{{$:/core/images/preview-closed}} -<$action-setfield $tiddler=<> $value="yes"/> - + + <$transclude $tiddler={{{ [match[yes]then[$:/core/images/preview-open]else[$:/core/images/preview-closed]] }}} /> + +<$action-setfield $tiddler=<> $value={{{ [get[text]toggle[yes],[no]] }}} /> +<$action-sendmessage $message="tm-edit-text-operation" $param="focus-editor"/> diff --git a/core/ui/ExportTiddlyWikiCore.tid b/core/ui/ExportTiddlyWikiCore.tid index 8c85c87a4f..4b913a0209 100644 --- a/core/ui/ExportTiddlyWikiCore.tid +++ b/core/ui/ExportTiddlyWikiCore.tid @@ -1,11 +1,6 @@ title: $:/core/ui/ExportTiddlyWikiCore \define jsFileName() tiddlywikicore-$(version)$.js -\define noExportMsg() -It appears that you have a wiki with an external ~TiddlyWiki core. The export action cannot be performed. -

You will need to view the page source in your browser. Then go to the very bottom the the source, find the last ` \ No newline at end of file diff --git a/editions/dev/tiddlers/system/$__themes_tiddlywiki_vanilla_options_sidebarlayout.tid b/editions/dev/tiddlers/system/$__themes_tiddlywiki_vanilla_options_sidebarlayout.tid new file mode 100644 index 0000000000..2f4dcb4e09 --- /dev/null +++ b/editions/dev/tiddlers/system/$__themes_tiddlywiki_vanilla_options_sidebarlayout.tid @@ -0,0 +1,6 @@ +created: 20240311150859344 +modified: 20240311150859344 +title: $:/themes/tiddlywiki/vanilla/options/sidebarlayout +type: text/vnd.tiddlywiki + +fluid-fixed \ No newline at end of file diff --git a/editions/dev/tiddlers/system/ContributionBanner.tid b/editions/dev/tiddlers/system/ContributionBanner.tid index 0ec32b0071..be936b4b8c 100644 --- a/editions/dev/tiddlers/system/ContributionBanner.tid +++ b/editions/dev/tiddlers/system/ContributionBanner.tid @@ -3,7 +3,7 @@ tags: $:/tags/EditTemplate list-after: $:/core/ui/EditTemplate/title \define base-github() -https://github.com/Jermolene/TiddlyWiki5/edit/master/editions/dev/tiddlers/ +https://github.com/TiddlyWiki/TiddlyWiki5/edit/master/editions/dev/tiddlers/ \end <$set name="draft-of" value={{{ [get[draft.of]] }}}> diff --git a/editions/dev/tiddlers/system/Sources.tid b/editions/dev/tiddlers/system/Sources.tid index 0c5082205e..d4b4cbe914 100644 --- a/editions/dev/tiddlers/system/Sources.tid +++ b/editions/dev/tiddlers/system/Sources.tid @@ -3,7 +3,7 @@ tags: $:/tags/TiddlerInfo caption: Sources \define github-link-base() -https://github.com/Jermolene/TiddlyWiki5/blob/master/editions/dev/tiddlers/$(title)$ +https://github.com/TiddlyWiki/TiddlyWiki5/blob/master/editions/dev/tiddlers/$(title)$ \end \define make-github-link() diff --git a/editions/dev/tiddlers/system/configWikiParserRulesInlineWikilink.tid b/editions/dev/tiddlers/system/configWikiParserRulesInlineWikilink.tid new file mode 100644 index 0000000000..9a395abd60 --- /dev/null +++ b/editions/dev/tiddlers/system/configWikiParserRulesInlineWikilink.tid @@ -0,0 +1,3 @@ +title: $:/config/WikiParserRules/Inline/wikilink + +enable \ No newline at end of file diff --git a/editions/dev/tiddlers/system/doc-styles.tid b/editions/dev/tiddlers/system/doc-styles.tid new file mode 100644 index 0000000000..24234d47ab --- /dev/null +++ b/editions/dev/tiddlers/system/doc-styles.tid @@ -0,0 +1,40 @@ +created: 20150117152612000 +modified: 20230325101137075 +tags: $:/tags/Stylesheet +title: $:/editions/tw5.com/doc-styles +type: text/vnd.tiddlywiki + +a.doc-from-version.tc-tiddlylink { + display: inline-block; + border-radius: 1em; + background: <>; + color: <>; + fill: <>; + padding: 0 0.4em; + font-size: 0.7em; + text-transform: uppercase; + font-weight: bold; + line-height: 1.5; + vertical-align: text-bottom; +} + +a.doc-deprecated-version.tc-tiddlylink { + display: inline-block; + border-radius: 1em; + background: red; + color: <>; + fill: <>; + padding: 0 0.4em; + font-size: 0.7em; + text-transform: uppercase; + font-weight: bold; + line-height: 1.5; + vertical-align: text-bottom; +} + +.doc-deprecated-version svg, +.doc-from-version svg { + width: 1em; + height: 1em; + vertical-align: text-bottom; +} diff --git a/editions/dev/tiddlers/system/github-fork-ribbon.tid b/editions/dev/tiddlers/system/github-fork-ribbon.tid index 01eb5c8d9e..eefba8be30 100644 --- a/editions/dev/tiddlers/system/github-fork-ribbon.tid +++ b/editions/dev/tiddlers/system/github-fork-ribbon.tid @@ -3,4 +3,4 @@ tags: $:/tags/PageTemplate caption: ~GitHub ribbon description: ~GitHub ribbon for tw5.com/dev -

\ No newline at end of file + \ No newline at end of file diff --git a/editions/dev/tiddlers/system/version-macros.tid b/editions/dev/tiddlers/system/version-macros.tid new file mode 100644 index 0000000000..0fb7dcf125 --- /dev/null +++ b/editions/dev/tiddlers/system/version-macros.tid @@ -0,0 +1,14 @@ +code-body: yes +created: 20161008085627406 +modified: 20221007122259593 +tags: $:/tags/Macro +title: $:/editions/tw5.com/version-macros +type: text/vnd.tiddlywiki + +\define .from-version(version) +<$link to={{{ [<__version__>addprefix[Release ]] }}} class="doc-from-version">{{$:/core/images/warning}} New in: <$text text=<<__version__>>/> +\end + +\define .deprecated-since(version, superseded:"TODO-Link") +<$link to="Deprecated - What does it mean" class="doc-deprecated-version tc-btn-invisible">{{$:/core/images/warning}} Deprecated since: <$text text=<<__version__>>/> (see <$link to=<<__superseded__>>><$text text=<<__superseded__>>/>) +\end diff --git a/editions/dynaviewdemo/tiddlers/ViewTemplate.tid b/editions/dynaviewdemo/tiddlers/ViewTemplate.tid index cbf6eca2aa..91bcfae86a 100644 --- a/editions/dynaviewdemo/tiddlers/ViewTemplate.tid +++ b/editions/dynaviewdemo/tiddlers/ViewTemplate.tid @@ -5,7 +5,7 @@ title: $:/core/ui/ViewTemplate $:/state/folded/$(currentTiddler)$ \end \define cancel-delete-tiddler-actions(message) <$action-sendmessage $message="tm-$message$-tiddler"/> -\import [all[shadows+tiddlers]tag[$:/tags/Macro/View]!has[draft.of]] +\import [all[shadows+tiddlers]tag[$:/tags/Macro/View]!is[draft]] [all[shadows+tiddlers]tag[$:/tags/Global/View]!is[draft]] <$vars storyTiddler=<> tiddlerInfoState=<>>
> data-tags={{!!tags}} class={{{ tc-tiddler-frame tc-tiddler-view-frame [is[tiddler]then[tc-tiddler-exists]] [is[missing]!is[shadow]then[tc-tiddler-missing]] [is[shadow]then[tc-tiddler-exists tc-tiddler-shadow]] [is[shadow]is[tiddler]then[tc-tiddler-overridden-shadow]] [is[system]then[tc-tiddler-system]] [{!!class}] [tags[]encodeuricomponent[]addprefix[tc-tagged-]] +[join[ ]] }}}> <$set name="state" value={{{ [[$:/state/viewtemplate/visibility/]addsuffix] }}}> diff --git a/editions/empty/tiddlywiki.info b/editions/empty/tiddlywiki.info index a911c7aed1..c754aad8ae 100644 --- a/editions/empty/tiddlywiki.info +++ b/editions/empty/tiddlywiki.info @@ -12,6 +12,9 @@ "empty": [ "--render","$:/core/save/all","empty.html","text/plain", "--render","$:/core/save/all","empty.hta","text/plain"], + "emptyexternalcore": [ + "--render","$:/core/save/offline-external-js","empty-external-core.html","text/plain", + "--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffixaddsuffix[.js]]","text/plain"], "externalimages": [ "--savetiddlers","[is[image]]","images", "--setfield","[is[image]]","_canonical_uri","$:/core/templates/canonical-uri-external-image","text/plain", @@ -20,7 +23,7 @@ "static": [ "--render","$:/core/templates/static.template.html","static.html","text/plain", "--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain", - "--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain", + "--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain","$:/core/templates/static.tiddler.html", "--render","$:/core/templates/static.template.css","static/static.css","text/plain"] } -} \ No newline at end of file +} diff --git a/editions/es-ES-server/tiddlers/system/favicon.ico b/editions/es-ES-server/tiddlers/system/favicon.ico deleted file mode 100644 index 3765a9a888..0000000000 Binary files a/editions/es-ES-server/tiddlers/system/favicon.ico and /dev/null differ diff --git a/editions/es-ES-server/tiddlers/system/favicon.png b/editions/es-ES-server/tiddlers/system/favicon.png new file mode 100644 index 0000000000..75be8e27d1 Binary files /dev/null and b/editions/es-ES-server/tiddlers/system/favicon.png differ diff --git a/editions/es-ES-server/tiddlers/system/favicon.ico.meta b/editions/es-ES-server/tiddlers/system/favicon.png.meta similarity index 53% rename from editions/es-ES-server/tiddlers/system/favicon.ico.meta rename to editions/es-ES-server/tiddlers/system/favicon.png.meta index 2f3e81713a..76d0be1a8c 100644 --- a/editions/es-ES-server/tiddlers/system/favicon.ico.meta +++ b/editions/es-ES-server/tiddlers/system/favicon.png.meta @@ -1,2 +1,2 @@ title: $:/favicon.ico -type: image/x-icon +type: image/png diff --git a/editions/es-ES/tiddlers/$__Acknowledgements.tid b/editions/es-ES/tiddlers/$__Acknowledgements.tid index 48074d9281..d907bc9e6a 100644 --- a/editions/es-ES/tiddlers/$__Acknowledgements.tid +++ b/editions/es-ES/tiddlers/$__Acknowledgements.tid @@ -6,7 +6,7 @@ type: text/vnd.tiddlywiki TiddlyWiki incorpora código de los siguientes proyectos OpenSource: * [[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/]] ...y materiales de estos otros proyectos: diff --git a/editions/es-ES/tiddlers/$__ContributionBanner.tid b/editions/es-ES/tiddlers/$__ContributionBanner.tid index 4849176336..2ba23b5fb5 100644 --- a/editions/es-ES/tiddlers/$__ContributionBanner.tid +++ b/editions/es-ES/tiddlers/$__ContributionBanner.tid @@ -6,7 +6,7 @@ title: $:/ContributionBanner type: text/vnd.tiddlywiki \define base-github() -https://github.com/Jermolene/TiddlyWiki5/edit/master/editions/es-ES/tiddlers/ +https://github.com/TiddlyWiki/TiddlyWiki5/edit/master/editions/es-ES/tiddlers/ \end <$set name="draft-of" value={{{ [get[draft.of]] }}}> diff --git a/editions/es-ES/tiddlers/$__StoryList.tid b/editions/es-ES/tiddlers/$__StoryList.tid deleted file mode 100644 index 523878d1e4..0000000000 --- a/editions/es-ES/tiddlers/$__StoryList.tid +++ /dev/null @@ -1,6 +0,0 @@ -created: 20160511060801385 -list: Reference JSONTiddlers -modified: 20160511060801385 -title: $:/StoryList -type: text/vnd.tiddlywiki - diff --git a/editions/es-ES/tiddlers/Articles.tid b/editions/es-ES/tiddlers/Articles.tid index 2f82b46a48..9c01b852a4 100644 --- a/editions/es-ES/tiddlers/Articles.tid +++ b/editions/es-ES/tiddlers/Articles.tid @@ -8,7 +8,7 @@ type: text/vnd.tiddlywiki Estos son algunos artículos recientes publicados sobre ~TiddlyWiki. -Envía nuevos artículos que encuentres via [[GitHub|https://github.com/Jermolene/TiddlyWiki5]] o [[Twitter|https://twitter.com/tiddlywiki]], o publícalas en el [[grupo|https://groups.google.com/forum/?hl=es#!forum/tiddlywiki]] +Envía nuevos artículos que encuentres via [[GitHub|https://github.com/TiddlyWiki/TiddlyWiki5]] o [[Twitter|https://twitter.com/tiddlywiki]], o publícalas en el [[grupo|https://groups.google.com/forum/?hl=es#!forum/tiddlywiki]]