diff --git a/.eslintrc.yml b/.eslintrc.yml index 049af59e4..0316b8385 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -231,7 +231,10 @@ rules: prefer-spread: 'off' prefer-template: 'off' quote-props: 'off' - quotes: 'off' + quotes: + - error + - double + - avoidEscape: true radix: 'off' require-atomic-updates: error require-await: error 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/cla-check.yml b/.github/workflows/cla-check.yml new file mode 100644 index 000000000..72bba1c54 --- /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 -o "@$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..6783219d1 --- /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" + 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 0ab5b300f..412759161 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules/ /test-results/ /playwright-report/ /playwright/.cache/ +$__StoryList.tid diff --git a/bin/build-site.sh b/bin/build-site.sh index a2193953d..fbb34cc98 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.3.3 + TW5_BUILD_VERSION=v5.3.6 fi echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]" @@ -393,6 +393,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 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/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 64cedab77..b4bdc00f2 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.

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..707c34110 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:

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:

These a poorly worded PR titles:

PR titles may also include a short prefix to indicate the subsystem to which they apply. For example:

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:

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:

These a poorly worded PR titles:

PR titles may also include a short prefix to indicate the subsystem to which they apply. For example:

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/images/discord.tid b/core/images/discord.tid new file mode 100644 index 000000000..7510babb4 --- /dev/null +++ b/core/images/discord.tid @@ -0,0 +1,5 @@ +title: $:/core/images/discord +tags: $:/tags/Image + +\parameters (size:"22pt") +> height=<> class="tc-image-discord tc-image-button" viewBox="0 -28.5 256 256"> \ 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/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/language/en-GB/Buttons.multids b/core/language/en-GB/Buttons.multids index fa769d117..30f89eec7 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 @@ -79,6 +80,7 @@ NewMarkdown/Caption: new Markdown tiddler NewMarkdown/Hint: Create a new Markdown tiddler NewTiddler/Caption: new tiddler NewTiddler/Hint: Create a new tiddler +OpenControlPanel/Hint: Open control panel OpenWindow/Caption: open in new window OpenWindow/Hint: Open tiddler in new window Palette/Caption: palette @@ -103,6 +105,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..a1b164c5c 100644 --- a/core/language/en-GB/ControlPanel.multids +++ b/core/language/en-GB/ControlPanel.multids @@ -198,6 +198,12 @@ Settings/TitleLinks/Yes/Description: Display tiddler titles as links Settings/MissingLinks/Caption: Wiki Links Settings/MissingLinks/Hint: Choose whether to link to tiddlers that do not exist yet Settings/MissingLinks/Description: Enable links to missing tiddlers +SocialCard/Caption: Social Media Card +SocialCard/Domain/Prompt: Domain name to display for the link (for example, ''tiddlywiki.com'') +SocialCard/Hint: This information is used by social and messaging services to display a preview card for links to this TiddlyWiki when hosted online +SocialCard/PreviewUrl/Prompt: Full URL to preview image for this TiddlyWiki +SocialCard/PreviewUrl/Preview: Preview image: +SocialCard/Url/Prompt: Full URL of this TiddlyWiki StoryTiddler/Caption: Story Tiddler StoryTiddler/Hint: This rule cascade is used to dynamically choose the template for displaying a tiddler in the story river. StoryView/Caption: Story View @@ -206,6 +212,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 @@ -229,3 +241,7 @@ ViewTemplateBody/Caption: View Template Body ViewTemplateBody/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the body of a tiddler. ViewTemplateTitle/Caption: View Template Title ViewTemplateTitle/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the title of a tiddler. +ViewTemplateSubtitle/Caption: View Template Subtitle +ViewTemplateSubtitle/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the subtitle of a tiddler. +ViewTemplateTags/Caption: View Template Tags +ViewTemplateTags/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the tags area of a tiddler. 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/Fields.multids b/core/language/en-GB/Fields.multids index 68804f082..9830e96c1 100644 --- a/core/language/en-GB/Fields.multids +++ b/core/language/en-GB/Fields.multids @@ -30,6 +30,7 @@ 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/Misc.multids b/core/language/en-GB/Misc.multids index b5e6e2374..d8c091375 100644 --- a/core/language/en-GB/Misc.multids +++ b/core/language/en-GB/Misc.multids @@ -70,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/Snippets/FunctionDefinition.tid b/core/language/en-GB/Snippets/FunctionDefinition.tid new file mode 100644 index 000000000..e000e38b1 --- /dev/null +++ b/core/language/en-GB/Snippets/FunctionDefinition.tid @@ -0,0 +1,7 @@ +title: $:/language/Snippets/FunctionDefinition +tags: $:/tags/TextEditor/Snippet +caption: Function definition + +\function f.name(param1,param2:"default value") [!is[blank]else] + +<> diff --git a/core/language/en-GB/Snippets/ProcedureDefinition.tid b/core/language/en-GB/Snippets/ProcedureDefinition.tid new file mode 100644 index 000000000..632abcc01 --- /dev/null +++ b/core/language/en-GB/Snippets/ProcedureDefinition.tid @@ -0,0 +1,7 @@ +title: $:/language/Snippets/ProcedureDefinition +tags: $:/tags/TextEditor/Snippet +caption: Procedure definition + +\procedure procName(param1:"default value",param2) +Your text comes here. +\end diff --git a/core/language/en-GB/Types/image_x-icon.tid b/core/language/en-GB/Types/image_x-icon.tid deleted file mode 100644 index 55420387a..000000000 --- a/core/language/en-GB/Types/image_x-icon.tid +++ /dev/null @@ -1,5 +0,0 @@ -title: $:/language/Docs/Types/image/x-icon -description: ICO icon -name: image/x-icon -group: Image -group-sort: 1 diff --git a/core/language/en-GB/Types/text_vnd.tiddlywiki_multiple.tid b/core/language/en-GB/Types/text_vnd.tiddlywiki_multiple.tid new file mode 100644 index 000000000..af15d7ac3 --- /dev/null +++ b/core/language/en-GB/Types/text_vnd.tiddlywiki_multiple.tid @@ -0,0 +1,5 @@ +title: $:/language/Docs/Types/text/vnd.tiddlywiki-multiple +description: Compound tiddler +name: text/vnd.tiddlywiki-multiple +group: Developer +group-sort: 2 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 0) { + var filepath = path.resolve(self.commander.outputPath,filenameResults[0]); + if(self.commander.verbose) { + console.log("Rendering \"" + title + "\" to \"" + filepath + "\""); + } + var parser = wiki.parseTiddler(template || title), + widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title,storyTiddler: title})}), + container = $tw.fakeDocument.createElement("div"); + widgetNode.render(container,null); + var text = type === "text/html" ? container.innerHTML : container.textContent; + $tw.utils.createFileDirectories(filepath); + fs.writeFileSync(filepath,text,"utf8"); + } else { + console.log("Not rendering \"" + title + "\" because the filename filter returned an empty result"); } - var parser = wiki.parseTiddler(template || title), - widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title,storyTiddler: title})}), - container = $tw.fakeDocument.createElement("div"); - widgetNode.render(container,null); - var text = type === "text/html" ? container.innerHTML : container.textContent; - $tw.utils.createFileDirectories(filepath); - fs.writeFileSync(filepath,text,"utf8"); }); return null; }; diff --git a/core/modules/commands/save.js b/core/modules/commands/save.js index 9769cec69..3cb7ef08c 100644 --- a/core/modules/commands/save.js +++ b/core/modules/commands/save.js @@ -43,7 +43,9 @@ Saves individual tiddlers in their raw text or binary format to the specified fi directory: path.resolve(self.commander.outputPath), pathFilters: [filenameFilter], wiki: wiki, - fileInfo: {} + fileInfo: { + overwrite: true + } }); if(self.commander.verbose) { console.log("Saving \"" + title + "\" to \"" + fileInfo.filepath + "\""); diff --git a/core/modules/commands/savewikifolder.js b/core/modules/commands/savewikifolder.js index c0fccd775..461ff6f04 100644 --- a/core/modules/commands/savewikifolder.js +++ b/core/modules/commands/savewikifolder.js @@ -176,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 399af598b..fdcf94d0f 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, 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.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, nobreak:8288, NoBreak:8288, 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/filters/backtranscludes.js b/core/modules/filters/backtranscludes.js index 7d4215073..253b9dd7b 100644 --- a/core/modules/filters/backtranscludes.js +++ b/core/modules/filters/backtranscludes.js @@ -16,11 +16,11 @@ Filter operator for returning all the backtranscludes from a tiddler Export our filter function */ exports.backtranscludes = function(source,operator,options) { - var results = []; + var results = new $tw.utils.LinkedList(); source(function(tiddler,title) { - $tw.utils.pushTop(results,options.wiki.getTiddlerBacktranscludes(title)); + results.pushTop(options.wiki.getTiddlerBacktranscludes(title)); }); - return results; + return results.makeTiddlerIterator(options.wiki); }; })(); diff --git a/core/modules/filters/crypto.js b/core/modules/filters/crypto.js index 24f1a0df9..cfb524d06 100644 --- a/core/modules/filters/crypto.js +++ b/core/modules/filters/crypto.js @@ -14,12 +14,9 @@ Filter operators for cryptography, using the Stanford JavaScript library exports.sha256 = function(source,operator,options) { var results = [], - length = parseInt(operator.operand,10) || 20, - sha256 = function(text) { - return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(text)).substr(0,length); - }; + length = parseInt(operator.operand,10) || 20; source(function(tiddler,title) { - results.push(sha256(title)); + results.push($tw.utils.sha256(title,{length: length})); }); return results; }; diff --git a/core/modules/filters/strings.js b/core/modules/filters/strings.js index 538dd0597..11f7634b7 100644 --- a/core/modules/filters/strings.js +++ b/core/modules/filters/strings.js @@ -127,7 +127,7 @@ function diffPartsToChars(text1,text2,mode) { if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) { chars += String.fromCharCode(lineHash[line]); } else { - if (lineArrayLength == maxLines) { + if(lineArrayLength == maxLines) { line = text.substring(lineStart); lineEnd = text.length; } @@ -217,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; }; @@ -264,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 diff --git a/core/modules/filters/transcludes.js b/core/modules/filters/transcludes.js index bd618296b..8f42b3bae 100644 --- a/core/modules/filters/transcludes.js +++ b/core/modules/filters/transcludes.js @@ -20,7 +20,7 @@ exports.transcludes = function(source,operator,options) { source(function(tiddler,title) { results.pushTop(options.wiki.getTiddlerTranscludes(title)); }); - return results.toArray(); + return results.makeTiddlerIterator(options.wiki); }; })(); 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 index 609d62bfc..77b51b819 100644 --- a/core/modules/indexers/back-indexer.js +++ b/core/modules/indexers/back-indexer.js @@ -70,9 +70,12 @@ BackSubIndexer.prototype.rebuild = function() { * 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); + return this.wiki[this.extractor](parser.tree, tiddler.fields.title); } return []; } 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') { + 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]; 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 a66df7057..bb1832255 100644 --- a/core/modules/parsers/wikiparser/rules/import.js +++ b/core/modules/parsers/wikiparser/rules/import.js @@ -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/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/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..fbdbb4f9d 100644 --- a/core/modules/parsers/wikiparser/rules/table.js +++ b/core/modules/parsers/wikiparser/rules/table.js @@ -93,11 +93,12 @@ var processRow = function(prevColumns) { } // Check whether this is a heading cell var cell; + var start = this.parser.pos; if(chr === "!") { this.parser.pos++; - cell = {type: "element", tag: "th", children: []}; + cell = {type: "element", tag: "th", start: start, children: []}; } else { - cell = {type: "element", tag: "td", children: []}; + cell = {type: "element", tag: "td", start: start, children: []}; } tree.push(cell); // Record information about this cell @@ -121,6 +122,7 @@ var processRow = function(prevColumns) { } // Move back to the closing `|` this.parser.pos--; + cell.end = this.parser.pos; } col++; cellRegExp.lastIndex = this.parser.pos; @@ -150,7 +152,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; } @@ -169,15 +171,17 @@ exports.parse = function() { rowContainer.children = this.parser.parseInlineRun(rowTermRegExp,{eatTerminator: true}); } else { // Create the row - var theRow = {type: "element", tag: "tr", children: []}; + var theRow = {type: "element", tag: "tr", children: [], start: rowMatch.index}; $tw.utils.addClassToParseTreeNode(theRow,rowCount%2 ? "oddRow" : "evenRow"); rowContainer.children.push(theRow); // Process the row theRow.children = processRow.call(this,prevColumns); this.parser.pos = rowMatch.index + rowMatch[0].length; + theRow.end = this.parser.pos; // Increment the row count rowCount++; } + rowContainer.end = this.parser.pos; } rowMatch = rowRegExp.exec(this.parser.source); } 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/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/wikiparser.js b/core/modules/parsers/wikiparser/wikiparser.js index 527e39eba..854171d19 100644 --- a/core/modules/parsers/wikiparser/wikiparser.js +++ b/core/modules/parsers/wikiparser/wikiparser.js @@ -91,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 }; @@ -209,8 +214,13 @@ WikiParser.prototype.parsePragmas = function() { 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 = []; @@ -235,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; @@ -332,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); } @@ -383,7 +410,15 @@ WikiParser.prototype.parseInlineRunTerminatedExtended = function(terminatorRegEx 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 @@ -409,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}); } }; @@ -462,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/put.js b/core/modules/savers/put.js index de9ba9465..a1ebef4bb 100644 --- a/core/modules/savers/put.js +++ b/core/modules/savers/put.js @@ -48,7 +48,7 @@ var PutSaver = function(wiki) { var self = this; var uri = this.uri(); // Async server probe. Until probe finishes, save will fail fast - // See also https://github.com/Jermolene/TiddlyWiki5/issues/2276 + // See also https://github.com/TiddlyWiki/TiddlyWiki5/issues/2276 $tw.utils.httpRequest({ url: uri, type: "OPTIONS", diff --git a/core/modules/server/authenticators/header.js b/core/modules/server/authenticators/header.js index 9d9990d31..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 = $tw.utils.decodeURIComponentSafe(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 ab74214b9..fc8ba9589 100644 --- a/core/modules/startup/plugins.js +++ b/core/modules/startup/plugins.js @@ -61,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 716275cda..d81e07aee 100644 --- a/core/modules/startup/rootwidget.js +++ b/core/modules/startup/rootwidget.js @@ -39,6 +39,7 @@ exports.startup = function() { method: params.method, body: params.body, binary: params.binary, + useDefaultHeaders: params.useDefaultHeaders, oncompletion: params.oncompletion, onprogress: params.onprogress, bindStatus: params["bind-status"], @@ -47,7 +48,11 @@ exports.startup = function() { headers: getPropertiesWithPrefix(params,"header-"), passwordHeaders: getPropertiesWithPrefix(params,"password-header-"), queryStrings: getPropertiesWithPrefix(params,"query-"), - passwordQueryStrings: getPropertiesWithPrefix(params,"password-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) { @@ -68,7 +73,10 @@ 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) { 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 aa9f982ed..34f45d7a5 100644 --- a/core/modules/startup/windows.js +++ b/core/modules/startup/windows.js @@ -56,7 +56,7 @@ exports.startup = function() { return; } // Initialise the document - srcDocument.write(""); + srcDocument.write(""); srcDocument.close(); srcDocument.title = windowTitle; srcWindow.addEventListener("beforeunload",function(event) { diff --git a/core/modules/storyviews/classic.js b/core/modules/storyviews/classic.js index c2848c435..a85e458c5 100644 --- a/core/modules/storyviews/classic.js +++ b/core/modules/storyviews/classic.js @@ -30,12 +30,8 @@ ClassicStoryView.prototype.navigateTo = function(historyInfo) { if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) { return; } - if(duration) { - // Scroll the node into view - this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement}); - } else { - targetElement.scrollIntoView(); - } + // Scroll the node into view + this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement}); }; ClassicStoryView.prototype.insert = function(widget) { @@ -82,6 +78,10 @@ ClassicStoryView.prototype.remove = function(widget) { removeElement = function() { widget.removeChildDomNodes(); }; + // Blur the focus if it is within the descendents of the node we are removing + if($tw.utils.domContains(targetElement,targetElement.ownerDocument.activeElement)) { + targetElement.ownerDocument.activeElement.blur(); + } // Abandon if the list entry isn't a DOM element (it might be a text node) if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) { removeElement(); diff --git a/core/modules/upgraders/system.js b/core/modules/upgraders/system.js index a93a57712..a653a75b1 100644 --- a/core/modules/upgraders/system.js +++ b/core/modules/upgraders/system.js @@ -12,7 +12,7 @@ Upgrader module that suppresses certain system tiddlers that shouldn't be import /*global $tw: false */ "use strict"; -var DONT_IMPORT_LIST = ["$:/Import"], +var DONT_IMPORT_LIST = ["$:/Import", "$:/build"], UNSELECT_PREFIX_LIST = ["$:/temp/","$:/state/","$:/StoryList","$:/HistoryList"], WARN_IMPORT_PREFIX_LIST = ["$:/core/modules/"]; diff --git a/core/modules/utils/dom/dom.js b/core/modules/utils/dom/dom.js index 338d96280..0b71e128c 100644 --- a/core/modules/utils/dom/dom.js +++ b/core/modules/utils/dom/dom.js @@ -270,6 +270,7 @@ Copy plain text to the clipboard on browsers that support it */ exports.copyToClipboard = function(text,options) { options = options || {}; + text = text || ""; var textArea = document.createElement("textarea"); textArea.style.position = "fixed"; textArea.style.top = 0; @@ -289,10 +290,12 @@ exports.copyToClipboard = function(text,options) { var succeeded = false; try { succeeded = document.execCommand("copy"); - } catch (err) { + } catch(err) { } if(!options.doNotNotify) { - $tw.notifier.display(succeeded ? "$:/language/Notifications/CopiedToClipboard/Succeeded" : "$:/language/Notifications/CopiedToClipboard/Failed"); + var successNotification = options.successNotification || "$:/language/Notifications/CopiedToClipboard/Succeeded", + failureNotification = options.failureNotification || "$:/language/Notifications/CopiedToClipboard/Failed" + $tw.notifier.display(succeeded ? successNotification : failureNotification); } document.body.removeChild(textArea); }; @@ -324,7 +327,7 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) { variables["tv-popup-coords"] = Popup.buildCoordinates(Popup.coordinatePrefix.csOffsetParent,nodeRect); var absRect = $tw.utils.extend({}, nodeRect); - for (var currentNode = selectedNode.offsetParent; currentNode; currentNode = currentNode.offsetParent) { + for(var currentNode = selectedNode.offsetParent; currentNode; currentNode = currentNode.offsetParent) { absRect.left += currentNode.offsetLeft; absRect.top += currentNode.offsetTop; } diff --git a/core/modules/utils/dom/http.js b/core/modules/utils/dom/http.js index 05879e5a9..65bdfd1e5 100644 --- a/core/modules/utils/dom/http.js +++ b/core/modules/utils/dom/http.js @@ -69,7 +69,7 @@ HttpClient.prototype.cancelAllHttpRequests = function() { for(var t=this.requests.length - 1; t--; t>=0) { var requestInfo = this.requests[t]; requestInfo.request.cancel(); - } + } } this.requests = []; this.updateRequestTracker(); @@ -100,6 +100,10 @@ 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; @@ -112,6 +116,7 @@ function HttpClientRequest(options) { 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) { @@ -128,6 +133,11 @@ function HttpClientRequest(options) { $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) { @@ -156,6 +166,7 @@ HttpClientRequest.prototype.send = function(callback) { 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", @@ -231,7 +242,8 @@ Make an HTTP request. Options are: exports.httpRequest = function(options) { var type = options.type || "GET", url = options.url, - headers = options.headers || {accept: "application/json"}, + useDefaultHeaders = options.useDefaultHeaders !== false ? true : false, + headers = options.headers || (useDefaultHeaders ? {accept: "application/json"} : {}), hasHeader = function(targetHeader) { targetHeader = targetHeader.toLowerCase(); var result = false; @@ -257,7 +269,7 @@ exports.httpRequest = function(options) { if(hasHeader("Content-Type") && ["application/x-www-form-urlencoded","multipart/form-data","text/plain"].indexOf(getHeader["Content-Type"]) === -1) { return false; } - return true; + return true; }, returnProp = options.returnProp || "responseText", request = new XMLHttpRequest(), @@ -283,7 +295,7 @@ exports.httpRequest = function(options) { // Set up the state change handler request.onreadystatechange = function() { if(this.readyState === 4) { - if(this.status === 200 || this.status === 201 || this.status === 204) { + if(this.status >= 200 && this.status < 300) { // Success! options.callback(null,this[returnProp],this); return; @@ -307,10 +319,10 @@ exports.httpRequest = function(options) { 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 diff --git a/core/modules/utils/edition-info.js b/core/modules/utils/edition-info.js index f8a5cab06..b9d97f962 100644 --- a/core/modules/utils/edition-info.js +++ b/core/modules/utils/edition-info.js @@ -29,10 +29,14 @@ exports.getEditionInfo = function() { for(var entryIndex=0; entryIndex