diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1e644e161..286a842bc 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 556b93919..dca23b783 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 8daf2f468..737d523ef 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 000000000..331727b71 --- /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 000000000..01d57d014 --- /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 ad7e8e07f..412759161 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 cd5267389..43ba7b9d7 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 6f4b0ca78..a11b8e0c4 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 dff297c80..fe8373785 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 000000000..ffcae66b2 --- /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 522479edb..5a56e1971 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 198c3abd0..e7c9df564 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 aeaa919fe..ea20c83fd 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 9dc10d0da..549e862a6 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 e7acee129..cb54e3d23 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 ce0d6b02f..3f52380cc 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 b327440df..21246d176 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 178221806..a118506ed 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 6fda3fe8b..8e5699c4d 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 78f95418b..76deecbad 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 731b55a5a..565ef6bec 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 67a00f894..d9259e4a1 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 c55620b06..3bb982bc1 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 f1b363dfc..df28c87a4 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 e4c69d95a..b7f9ad7ae 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 6ff5b6c0d..9ec7c96fe 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 9acbdec40..45366f286 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 9ff4903ad..cc0ed7595 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 3334c5dbc..02d25fa6f 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 c462c9bac..a05669ccd 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 1cd54d797..6eb779018 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 e4e55b6e9..d6514ad7c 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 e8c9b6108..99f83b3ba 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 265528d06..7672b7d27 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 4cac65a00..7f1273b2c 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 e3a549639..8b06356ab 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 190dffc41..e4644ef7c 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 22b31e7cc..6cc2011a9 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 e6c2c9404..fa351baff 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 eb3284c80..b9f0dca7f 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 67ebc22ac..682796fbf 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 c15162267..b2b5083b9 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 edff8b22b..260efe9b8 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 f0b2b474d..cfc2291a5 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 5cb086f85..a5457c461 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 4b89418ff..8c5d7efb1 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 8572ff646..7e20183b7 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 6389f5cdc..b82c54e72 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 876fc3da1..4af54ddba 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 9448ed7a4..0e2c56b40 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 f8a98123b..d160284c9 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 ef0022cc6..8daad0f10 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 d706d067b..8a489e799 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 16d4440db..5aee3f3f8 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 8f34b7058..c9a54a335 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 b348c70af..1034e7116 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 07167ae93..5c0cf13ab 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 10c00626a..952ab9a60 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 6850513ef..a42c416e5 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 2679a135a..e8f8e98c4 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 000000000..731a1e0cd --- /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 06aada589..766d3707a 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 19371cde1..a859edbc5 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 a418581cd..b64fc2ab6 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 f77763ce6..9cecc33cb 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 395307c42..c1d134f7e 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 40acdc19a..d616c2ac9 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 322dd4ae6..065d96c4f 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 161917823..b70d4db15 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 22b784bfe..793b47957 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 14d95560c..f6722cddd 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 cf0f4eab7..9d46d6eff 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 b27c751fd..a6b41d355 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 6d1872e4c..958721d31 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 7132ed3e9..40ee75a8e 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 f8695302b..bf58400aa 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 09cfe513e..1ab73b829 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 000000000..2efdfd4d4 --- /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 6e592ada9..d4cfd34f6 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 ab0f7a6f3..8e304f5e7 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 16b63c3c3..53b10d481 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 fb67c8007..5b793deb5 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 e9a29aea2..0211644f9 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 14b556484..d918b1fc8 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 bd0ffcb1a..18fbf8b00 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 bb536b53a..660fda2bf 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 d605fd853..1c7903549 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 e8fd0aecd..f1cf38b41 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 b2ad9a408..82e533475 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 7d035e2fc..2af427e2e 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 5c777d98d..d663d1563 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 ab2e6670e..06073dd7c 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 9ae3cd779..ab899b3e4 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 b001f3e2f..c9a696d43 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 5986d8966..cf17730d5 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 4664990b4..cb30bf474 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 55b33c896..12bffd41d 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 7134306a6..0c4fcf25a 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 2422b0679..f8e3fc69e 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 42e7dea56..64f839b55 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 188d3b45c..da6034b19 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 000000000..7a351d617 --- /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 a66756616..912ad248c 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 db84ecf9b..ea9aa1094 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 ca4684cab..f3a5271ac 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 ba385aaae..8511a457f 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 000000000..1b83375c9 --- /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 10b8f1c3d..262448d51 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 86872817b..457434bc9 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 a610c89d5..a4a9fb9ee 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 61b7ff273..725f36e71 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 1f7a1c202..de4eefeec 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 96548bdb5..76ec35399 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 149e44893..ab0d0d1b2 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 9f6cad8b4..ab407f780 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 d80a0e82a..6b7aab2af 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 0d20f28d6..33e018f3a 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 3a16df7eb..cad04aada 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 f8109ada4..025918306 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 0579feb10..5102d1370 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 28cfccdbd..e3ae13dc1 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 768d2a199..341b27cfa 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 e496bdd89..e44000b18 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 cfad70570..57f53fd6f 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 c5367c085..941f7bec5 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 a1bd132bf..4d157352b 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 3bf0bb259..b4fef628d 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 53bff59ec..1822da89f 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 85a71ac08..15273d18e 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 d8321edbf..93cfc3c10 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 9a03d8887..5d5902c76 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 98addbf85..bc1b36c3d 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 e455b8bf1..6ac52efe7 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 c094580d5..9830e96c1 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 454159b44..7551885f0 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 45df72381..d9f6a247f 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 bda1d19a3..82565f7bc 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 78e9c8ab1..da6865031 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 00cb3c99c..d8c091375 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 45028fc45..000000000 --- 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 9f522664a..a32da7dfe 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 9f7c23ba3..94c3ea949 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 6ae32331c..55420387a 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 3c5f6a63a..ca6e6e076 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 36a1399a2..3af2e4943 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 77e74edf8..399af598b 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 10ab90b39..34d11dec7 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 e8be19a75..809dc58ea 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 90ab66ae9..6157ec67f 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 da6894d21..486e75f45 100644 --- a/core/modules/filterrunprefixes/cascade.js +++ b/core/modules/filterrunprefixes/cascade.js @@ -25,20 +25,10 @@ exports.cascade = function(operationSubFunction,options) { if(!filterFnList[index]) { filterFnList[index] = options.wiki.compileFilter(filter); } - var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),{ - getVariable: function(name,opts) { - opts = opts || {}; - opts.variables = { - "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler") - }; - if(name in opts.variables) { - return opts.variables[name]; - } else { - return widget.getVariable(name,opts); - } - } - }); + var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ + "currentTiddler": "" + title, + "..currentTiddler": widget.getVariable("currentTiddler","") + })); if(output.length !== 0) { result = output[0]; return false; diff --git a/core/modules/filterrunprefixes/filter.js b/core/modules/filterrunprefixes/filter.js index 783b699c2..4ab057109 100644 --- a/core/modules/filterrunprefixes/filter.js +++ b/core/modules/filterrunprefixes/filter.js @@ -19,23 +19,13 @@ exports.filter = function(operationSubFunction,options) { var resultsToRemove = [], index = 0; results.each(function(title) { - var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{ - getVariable: function(name,opts) { - opts = opts || {}; - opts.variables = { - "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler"), - "index": "" + index, - "revIndex": "" + (results.length - 1 - index), - "length": "" + results.length - }; - if(name in opts.variables) { - return opts.variables[name]; - } else { - return widget.getVariable(name,opts); - } - } - }); + var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ + "currentTiddler": "" + title, + "..currentTiddler": widget.getVariable("currentTiddler",""), + "index": "" + index, + "revIndex": "" + (results.length - 1 - index), + "length": "" + results.length + })); if(filtered.length === 0) { resultsToRemove.push(title); } diff --git a/core/modules/filterrunprefixes/map.js b/core/modules/filterrunprefixes/map.js index efcb5b534..b756d6699 100644 --- a/core/modules/filterrunprefixes/map.js +++ b/core/modules/filterrunprefixes/map.js @@ -21,23 +21,13 @@ exports.map = function(operationSubFunction,options) { flatten = (suffixes[0] && suffixes[0][0] === "flat") ? true : false; results.clear(); $tw.utils.each(inputTitles,function(title) { - var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{ - getVariable: function(name,opts) { - opts = opts || {}; - opts.variables = { - "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler"), - "index": "" + index, - "revIndex": "" + (inputTitles.length - 1 - index), - "length": "" + inputTitles.length - }; - if(name in opts.variables) { - return opts.variables[name]; - } else { - return widget.getVariable(name,opts); - } - } - }); + var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ + "currentTiddler": "" + title, + "..currentTiddler": widget.getVariable("currentTiddler",""), + "index": "" + index, + "revIndex": "" + (inputTitles.length - 1 - index), + "length": "" + inputTitles.length + })); if(filtered.length && flatten) { $tw.utils.each(filtered,function(value) { results.push(value); diff --git a/core/modules/filterrunprefixes/reduce.js b/core/modules/filterrunprefixes/reduce.js index 8fe819e3f..ee2998837 100644 --- a/core/modules/filterrunprefixes/reduce.js +++ b/core/modules/filterrunprefixes/reduce.js @@ -18,24 +18,14 @@ exports.reduce = function(operationSubFunction,options) { var accumulator = "", index = 0; results.each(function(title) { - var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{ - getVariable: function(name,opts) { - opts = opts || {}; - opts.variables = { - "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler"), - "index": "" + index, - "revIndex": "" + (results.length - 1 - index), - "length": "" + results.length, - "accumulator": "" + accumulator - }; - if(name in opts.variables) { - return opts.variables[name]; - } else { - return widget.getVariable(name,opts); - } - } - }); + var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ + "currentTiddler": "" + title, + "..currentTiddler": widget.getVariable("currentTiddler"), + "index": "" + index, + "revIndex": "" + (results.length - 1 - index), + "length": "" + results.length, + "accumulator": "" + accumulator + })); if(list.length > 0) { accumulator = "" + list[0]; } diff --git a/core/modules/filterrunprefixes/sort.js b/core/modules/filterrunprefixes/sort.js index 6865b175c..d8d376126 100644 --- a/core/modules/filterrunprefixes/sort.js +++ b/core/modules/filterrunprefixes/sort.js @@ -25,20 +25,10 @@ exports.sort = function(operationSubFunction,options) { indexes = new Array(inputTitles.length), compareFn; results.each(function(title) { - var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{ - getVariable: function(name,opts) { - opts = opts || {}; - opts.variables = { - "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler") - }; - if(name in opts.variables) { - return opts.variables[name]; - } else { - return widget.getVariable(name,opts); - } - } - }); + var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ + "currentTiddler": "" + title, + "..currentTiddler": widget.getVariable("currentTiddler") + })); sortKeys.push(key[0] || ""); }); results.clear(); diff --git a/core/modules/filterrunprefixes/then.js b/core/modules/filterrunprefixes/then.js new file mode 100644 index 000000000..9f6d5c76a --- /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 1bb5fe9ff..aa82a352a 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 a36749e92..3554a74b3 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 000000000..d6f5afe30 --- /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 000000000..79210fb78 --- /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 2be9ec754..0c58964eb 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 e9f676daa..d328be09c 100644 --- a/core/modules/filters/sortsub.js +++ b/core/modules/filters/sortsub.js @@ -25,19 +25,10 @@ exports.sortsub = function(source,operator,options) { inputTitles.push(title); var r = filterFn.call(options.wiki,function(iterator) { iterator(options.wiki.getTiddler(title),title); - },{ - getVariable: function(name,opts) { - opts = opts || {}; - switch(name) { - case "currentTiddler": - return "" + title; - case "..currentTiddler": - return options.widget.getVariable("currentTiddler"); - default: - return options.widget.getVariable(name,opts); - } - } - }); + },options.widget.makeFakeWidgetWithVariables({ + "currentTiddler": "" + title, + "..currentTiddler": options.widget.getVariable("currentTiddler") + })); sortKeys.push(r[0] || ""); }); // Rather than sorting the titles array, we'll sort the indexes so that we can consult both arrays diff --git a/core/modules/filters/strings.js b/core/modules/filters/strings.js index 60a36265d..11f7634b7 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 000000000..655ef7321 --- /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 000000000..8f42b3bae --- /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 000000000..6ae5baaf0 --- /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 760f581a1..ae17297a5 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 000000000..77b51b819 --- /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 5902e2829..000000000 --- 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 e49583610..3aefc99ac 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 7b34ce04d..a492fd81c 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 95d74ef4b..c7830bf69 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 4f55f6f0c..17f9bde10 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 f1c281c7c..1c8a38bb2 100644 --- a/core/modules/parsers/videoparser.js +++ b/core/modules/parsers/videoparser.js @@ -28,6 +28,8 @@ var VideoParser = function(type,text,options) { element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text}; } this.tree = [element]; + this.source = text; + this.type = type; }; exports["video/ogg"] = VideoParser; diff --git a/core/modules/parsers/wikiparser/rules/codeblock.js b/core/modules/parsers/wikiparser/rules/codeblock.js index 262038f87..6c3480566 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 ee9149833..048fc051c 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 000000000..23940fd88 --- /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 e06f88d8d..5b9f57adf 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 7ab4801bf..73bdff813 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 029fd6802..c0b19a941 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 000000000..85bd14d5c --- /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 c278686b4..94f517cd4 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 de4e45c27..7a0ecb9db 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 7fc4bb96e..61c4ad9e1 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 6b379d9c5..6f58225e0 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 9556c7af5..bb1832255 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 17eab6dad..d89c201b9 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 6f50fdbb0..a2c10e04a 100644 --- a/core/modules/parsers/wikiparser/rules/macrocallblock.js +++ b/core/modules/parsers/wikiparser/rules/macrocallblock.js @@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) { var nextStart = startPos; // Try parsing at all possible macrocall openers until we match while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) { - var nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart); + var nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart); if(nextCall) { var c = this.parser.source.charAt(nextCall.end); // Ensure EOL after parsed macro diff --git a/core/modules/parsers/wikiparser/rules/macrocallinline.js b/core/modules/parsers/wikiparser/rules/macrocallinline.js index 165a70dce..e9f79f09e 100644 --- a/core/modules/parsers/wikiparser/rules/macrocallinline.js +++ b/core/modules/parsers/wikiparser/rules/macrocallinline.js @@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) { var nextStart = startPos; // Try parsing at all possible macrocall openers until we match while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) { - this.nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart); + this.nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart); if(this.nextCall) { return nextStart; } diff --git a/core/modules/parsers/wikiparser/rules/macrodef.js b/core/modules/parsers/wikiparser/rules/macrodef.js index 22a7b5ba7..2001f70d5 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 000000000..60bbd8901 --- /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 ad4287f99..72a6de067 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 4c497c257..4707fa0d0 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 56a2850a3..66c19dc88 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 71b689680..fdd6c860b 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 cfc03330b..1d0f80f29 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 6eb2cdcd4..6bcbee384 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 61cd71948..59aa81e91 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 56a4f63b8..c033c2440 100644 --- a/core/modules/parsers/wikiparser/rules/transcludeblock.js +++ b/core/modules/parsers/wikiparser/rules/transcludeblock.js @@ -23,7 +23,7 @@ exports.types = {block: true}; exports.init = function(parser) { this.parser = parser; // Regexp to match - this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?\}\}(?:\r?\n|$)/mg; + this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg; }; exports.parse = function() { @@ -31,13 +31,22 @@ exports.parse = function() { this.parser.pos = this.matchRegExp.lastIndex; // Get the match details var template = $tw.utils.trim(this.match[2]), - textRef = $tw.utils.trim(this.match[1]); + textRef = $tw.utils.trim(this.match[1]), + params = this.match[3] ? this.match[3].split("|") : []; // Prepare the transclude widget var transcludeNode = { type: "transclude", attributes: {}, isBlock: true }; + $tw.utils.each(params,function(paramValue,index) { + var name = "" + index; + transcludeNode.attributes[name] = { + name: name, + type: "string", + value: paramValue + } + }); // Prepare the tiddler widget var tr, targetTitle, targetField, targetIndex, tiddlerNode; if(textRef) { @@ -48,14 +57,14 @@ exports.parse = function() { tiddlerNode = { type: "tiddler", attributes: { - tiddler: {type: "string", value: targetTitle} + tiddler: {name: "tiddler", type: "string", value: targetTitle} }, isBlock: true, children: [transcludeNode] }; } if(template) { - transcludeNode.attributes.tiddler = {type: "string", value: template}; + transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: template}; if(textRef) { return [tiddlerNode]; } else { @@ -63,12 +72,12 @@ exports.parse = function() { } } else { if(textRef) { - transcludeNode.attributes.tiddler = {type: "string", value: targetTitle}; + transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: targetTitle}; if(targetField) { - transcludeNode.attributes.field = {type: "string", value: targetField}; + transcludeNode.attributes["$field"] = {name: "$field", type: "string", value: targetField}; } if(targetIndex) { - transcludeNode.attributes.index = {type: "string", value: targetIndex}; + transcludeNode.attributes["$index"] = {name: "$index", type: "string", value: targetIndex}; } return [tiddlerNode]; } else { diff --git a/core/modules/parsers/wikiparser/rules/transcludeinline.js b/core/modules/parsers/wikiparser/rules/transcludeinline.js index dbf39bfb6..3ce9dc78e 100644 --- a/core/modules/parsers/wikiparser/rules/transcludeinline.js +++ b/core/modules/parsers/wikiparser/rules/transcludeinline.js @@ -23,7 +23,7 @@ exports.types = {inline: true}; exports.init = function(parser) { this.parser = parser; // Regexp to match - this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?\}\}/mg; + this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg; }; exports.parse = function() { @@ -31,12 +31,21 @@ exports.parse = function() { this.parser.pos = this.matchRegExp.lastIndex; // Get the match details var template = $tw.utils.trim(this.match[2]), - textRef = $tw.utils.trim(this.match[1]); + textRef = $tw.utils.trim(this.match[1]), + params = this.match[3] ? this.match[3].split("|") : []; // Prepare the transclude widget var transcludeNode = { type: "transclude", attributes: {} }; + $tw.utils.each(params,function(paramValue,index) { + var name = "" + index; + transcludeNode.attributes[name] = { + name: name, + type: "string", + value: paramValue + } + }); // Prepare the tiddler widget var tr, targetTitle, targetField, targetIndex, tiddlerNode; if(textRef) { @@ -47,13 +56,13 @@ exports.parse = function() { tiddlerNode = { type: "tiddler", attributes: { - tiddler: {type: "string", value: targetTitle} + tiddler: {name: "tiddler", type: "string", value: targetTitle} }, children: [transcludeNode] }; } if(template) { - transcludeNode.attributes.tiddler = {type: "string", value: template}; + transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: template}; if(textRef) { return [tiddlerNode]; } else { @@ -61,12 +70,12 @@ exports.parse = function() { } } else { if(textRef) { - transcludeNode.attributes.tiddler = {type: "string", value: targetTitle}; + transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: targetTitle}; if(targetField) { - transcludeNode.attributes.field = {type: "string", value: targetField}; + transcludeNode.attributes["$field"] = {name: "$field", type: "string", value: targetField}; } if(targetIndex) { - transcludeNode.attributes.index = {type: "string", value: targetIndex}; + transcludeNode.attributes["$index"] = {name: "$index", type: "string", value: targetIndex}; } return [tiddlerNode]; } else { diff --git a/core/modules/parsers/wikiparser/rules/typedblock.js b/core/modules/parsers/wikiparser/rules/typedblock.js index 4195e57e5..07c88be15 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 e3b0c8e3f..5fd9d4b83 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 fadc4587e..6b195f9ff 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 000000000..60cb3d992 --- /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 4c7419030..854171d19 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 119c3e67a..23056bcc2 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 dc24ef67f..000000000 --- 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 f9b87263d..c0a34f2d6 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 232392672..000000000 --- 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 78ae6cb0a..cc1d6bdaf 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 258ddfa31..d3c98f8fc 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 cad61b104..fc8ba9589 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 e50512463..7206a51d0 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 41f3fe03f..d81e07aee 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 e0990228f..e09f6393f 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 734f6ae76..c58c759c3 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 384961b7b..34f45d7a5 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 b2796e953..d02f705e7 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 c06fcb143..f7627e1ac 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 73be0f0a8..905bb2750 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 000000000..fac4b3fa7 --- /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 d28161ac6..fb28465fe 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 265cbd86f..5319e0481 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 000000000..3aeb4d25a --- /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 54068471e..00f06562d 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 fc8412006..7b1d1e904 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 de3c91fb8..8130825b0 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 a724d8448..aef0fe630 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 68ebe7980..e07513b0a 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 000000000..63f12e1f9 --- /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 104503b25..0dcba1688 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 efa08162c..043b2557f 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 e7bd49b93..eb7758e90 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 000000000..de88c95af --- /dev/null +++ b/core/modules/widgets/fill.js @@ -0,0 +1,30 @@ +/*\ +title: $:/core/modules/widgets/fill.js +type: application/javascript +module-type: widget + +Sub-widget used by the transclude widget for specifying values for slots within transcluded content. It doesn't do anything by itself because the transclude widget only ever deals with the parse tree nodes, and doesn't instantiate the widget itself + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var FillWidget = function(parseTreeNode,options) { + // Initialise + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +FillWidget.prototype = new Widget(); + +exports.fill = FillWidget; + +})(); + \ No newline at end of file diff --git a/core/modules/widgets/genesis.js b/core/modules/widgets/genesis.js index 6d30f3c36..c8403a3d4 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 f14e84c44..52496fd74 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 a73abfdcf..3e1ac3fc6 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 afd3a2f20..2b2886530 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 16356ee01..f02a7cae2 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 78322d0f6..4f4a66b17 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 41344a02e..d4ad41995 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 9de2e5d67..2c4adc355 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 85a3561f2..1457cd11f 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 3f1efbe7a..efdbba83f 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 000000000..fdd2cb963 --- /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 363836227..aa7a32cf1 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 4dd55dc3c..db2699cc4 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 aadc040df..227c455c3 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 cd789423f..2940e3be0 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 cc97067c7..f8e98f390 100755 --- a/core/modules/widgets/setvariable.js +++ b/core/modules/widgets/setvariable.js @@ -48,7 +48,17 @@ SetWidget.prototype.execute = function() { this.setValue = this.getAttribute("value"); this.setEmptyValue = this.getAttribute("emptyValue"); // Set context variable - this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition); + if(this.parseTreeNode.isMacroDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true); + } else if(this.parseTreeNode.isFunctionDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true}); + } else if(this.parseTreeNode.isProcedureDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace}); + } else if(this.parseTreeNode.isWidgetDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace}); + } else { + this.setVariable(this.setName,this.getValue()); + } // Construct the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/widgets/slot.js b/core/modules/widgets/slot.js new file mode 100644 index 000000000..eeaaedefe --- /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 000000000..0fd55531c --- /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 d7862d2eb..35b4941bd 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 6effc8939..070836bff 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 60f55e8bb..eb84fab4a 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 c3f272348..2954454d5 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 3b62eb311..6c3fa6a73 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 b296ba783..e8e280998 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 eea273c30..bacd385b2 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 3cf954bb5..5d8757386 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 d84b4ec83..067f32519 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 da319c2e9..8902acdea 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 4583bc1ce..3b70c9d11 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 0b04549f9..3d42be807 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 1dbd3503b..7b0bb2d78 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 cd15cedc3..1f4908878 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 2dfca5d52..70cb8bbc0 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 000000000..496d6ed04 --- /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 a8170663f..f962569a2 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 6a5c4c1bb..b161584d7 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 1e3c032f3..6402bcee5 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 6b62b4ac9..2e12290a7 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 b298ad49f..a316d1954 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 6f0da4822..0b1c33b59 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 a4b5cd6e9..da4353fba 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 0a4a84295..b23b348f0 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 1a803bd86..6c9fd80a5 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 0d14509e5..aa5175c01 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 5da5fb752..8b6482846 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 a4537305a..a3297ee78 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 84dd0c432..2dc115266 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 c5a460f28..5b82f9b32 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 0690130e4..e6ed18a7a 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 ae15818a0..d67586b8d 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 000000000..27fdff998 --- /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 35bb22855..d7f408bd9 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 dd5580ad5..b2ef2832a 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 ce8612b72..fe096d6d0 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 818d1f292..3a3173875 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 42e4dc3a9..be658a1ff 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 d1b096281..38c3f34fb 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 f4a4b13c2..74004ffa0 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 36377bb85..3feace84b 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 47f277bd4..c3ad60aac 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 acd3421c7..1f4c5fc7b 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 aa142bf62..ad7384568 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 371b6d61b..e539b8f82 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 92d46601e..dc98b1ae6 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 4a7ba5f2e..e0149c9a1 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 4a123ba99..f35f8a1f1 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 af63de1ee..bd118a103 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 5a496d5e1..6c15936e7 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 b3d1d9763..1ea9061ae 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 c1acdc7bd..1620dfe39 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 a25b2a39e..c02d653d7 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 00bdb191e..d76394ee8 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 000000000..401e14113 --- /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 000000000..571fb93c2 --- /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 000000000..4ab2d062d --- /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 f88865997..f8d923da2 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 000000000..bfae2ceb6 --- /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 4d6dec5d6..6ad84a139 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 4e5bf0e33..5c53d8c22 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 a2128efb0..d004032f1 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 3e2f5e3f3..e97cedd78 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 6a767517b..0edc33505 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 8d829b30e..c8d4131fe 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 faa89639f..c1c38b72a 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 c539009ee..3872e7674 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 e2766935b..d2887a180 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 106b28d3c..d5c63eb5f 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 8c85c87a4..4b913a020 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 000000000..2f4dcb4e0 --- /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 0ec32b007..be936b4b8 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 0c5082205..d4b4cbe91 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 000000000..9a395abd6 --- /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 000000000..24234d47a --- /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 01eb5c8d9..eefba8be3 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 000000000..0fb7dcf12 --- /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 cbf6eca2a..91bcfae86 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 a911c7aed..c754aad8a 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 3765a9a88..000000000 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 000000000..75be8e27d 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 2f3e81713..76d0be1a8 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 48074d928..d907bc9e6 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 484917633..2ba23b5fb 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 523878d1e..000000000 --- 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 2f82b46a4..9c01b852a 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]]