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..707c34110 100644
--- a/contributing.md
+++ b/contributing.md
@@ -1,3 +1,3 @@
-
PRs must meet these minimum requirements before they can be considered for merging:
The material in the PR must be free of licensing restrictions. Which means that either:
The author must hold the copyright in all of the material themselves
The material must be licensed under a license compatible with TiddlyWiki's BSD license
The author must sign the Contributors License Agreement (see below)
Each PR should only make a single feature change
The title of the PR should be 50 characters or less
The title of the PR should be capitalised, and should not end with a period
The title of the PR should be written in the imperative mood. See below
Adequate explanation in the body of the PR for the motivation and implementation of the change. Focus on the why and what, rather than the how
PRs must be self-contained. Although they can link to material elsewhere, everything needed to understand the intention of the PR should be included
Any visual changes introduced by the PR should be noted and illustrated with before/after screenshots
Documentation as appropriate for end-users or developers
Observe the coding style
Read the developers documentation
Please open a consultation issue prior to investing time in making a large PR
Imperative Mood for PR Titles
The "imperative mood" means written as if giving a command or instruction. See this post for more details, but the gist is that the title of the PR should make sense when used to complete the sentence "If applied, this commit will...". So for example, these are good PR titles:
If applied, this commit will update the contributing guidelines
If applied, this commit will change css-escape-polyfill to a $tw.utils method
If applied, this commit will make it easier to subclass the wikitext parser with a custom rule set
These a poorly worded PR titles:
If applied, this commit will edit text widgets should use default text for missing fields
If applied, this commit will signing the CLA
If applied, this commit will don't crash if options.event is missing
PR titles may also include a short prefix to indicate the subsystem to which they apply. For example:
Menu plugin: Include menu text in aerial rotator
Commenting on Pull Requests
One of the principles of open source is that many pairs of eyes on the code can improve quality. So, we welcome comments and critiques of pending PRs. Conventional Comments has some techniques to help make comments as constructive and actionable as possible. Notably, they recommend prefixing a comment with a label to clarify the intention:
praise
Praises highlight something positive. Try to leave at least one of these comments per review. Do not leave false praise (which can actually be damaging). Do look for something to sincerely praise
nitpick
Nitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement
suggestion
Suggestions are specific requests to improve the subject under review. It is assumed that we all want to do what's best, so these comments are never dismissed as “mere suggestions”, but are taken seriously
issue
Issues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion
question
Questions are appropriate if you have a potential concern but are not quite sure if it's relevant or not. Asking the author for clarification or investigation can lead to a quick resolution
thought
Thoughts represent an idea that popped up from reviewing. These comments are non-blocking by nature, but they are extremely valuable and can lead to more focused initiatives and mentoring opportunities
chore
Chores are simple tasks that must be done before the subject can be “officially” accepted. Usually, these comments reference some common process. Try to leave a link to the process description so that the reader knows how to resolve the chore
Contributor License Agreement
Like other OpenSource projects, TiddlyWiki5 needs a signed contributor license agreement from individual contributors. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the UnaMesa Association (the legal entity that owns TiddlyWiki on behalf of the community).
Ensure that the "branch" dropdown at the top left is set to tiddlywiki-com
Click the "edit" button at the top-right corner (clicking this button will fork the project so you can edit the file)
Add your name at the bottom
eg: Jeremy Ruston, @Jermolene, 2011/11/22
Below the edit box for the CLA text you should see a box labelled Propose file change
Enter a brief title to explain the change (eg, "Signing the CLA")
Click the green button labelled Propose file change
On the following screen, click the green button labelled Create pull request
The CLA documents used for this project were created using Harmony Project Templates. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity".
+
PRs must meet these minimum requirements before they can be considered for merging:
The material in the PR must be free of licensing restrictions. Which means that either:
The author must hold the copyright in all of the material themselves
The material must be licensed under a license compatible with TiddlyWiki's BSD license
The author must sign the Contributors License Agreement (see below)
Each PR should only make a single feature change
The title of the PR should be 50 characters or less
The title of the PR should be capitalised, and should not end with a period
The title of the PR should be written in the imperative mood. See below
Adequate explanation in the body of the PR for the motivation and implementation of the change. Focus on the why and what, rather than the how
PRs must be self-contained. Although they can link to material elsewhere, everything needed to understand the intention of the PR should be included
Any visual changes introduced by the PR should be noted and illustrated with before/after screenshots
Documentation as appropriate for end-users or developers
Observe the coding style
Read the developers documentation
Please open a consultation issue prior to investing time in making a large PR
Imperative Mood for PR Titles
The "imperative mood" means written as if giving a command or instruction. See this post for more details, but the gist is that the title of the PR should make sense when used to complete the sentence "If applied, this commit will...". So for example, these are good PR titles:
If applied, this commit will update the contributing guidelines
If applied, this commit will change css-escape-polyfill to a $tw.utils method
If applied, this commit will make it easier to subclass the wikitext parser with a custom rule set
These a poorly worded PR titles:
If applied, this commit will edit text widgets should use default text for missing fields
If applied, this commit will signing the CLA
If applied, this commit will don't crash if options.event is missing
PR titles may also include a short prefix to indicate the subsystem to which they apply. For example:
Menu plugin: Include menu text in aerial rotator
Commenting on Pull Requests
One of the principles of open source is that many pairs of eyes on the code can improve quality. So, we welcome comments and critiques of pending PRs. Conventional Comments has some techniques to help make comments as constructive and actionable as possible. Notably, they recommend prefixing a comment with a label to clarify the intention:
praise
Praises highlight something positive. Try to leave at least one of these comments per review. Do not leave false praise (which can actually be damaging). Do look for something to sincerely praise
nitpick
Nitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement
suggestion
Suggestions are specific requests to improve the subject under review. It is assumed that we all want to do what's best, so these comments are never dismissed as “mere suggestions”, but are taken seriously
issue
Issues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion
question
Questions are appropriate if you have a potential concern but are not quite sure if it's relevant or not. Asking the author for clarification or investigation can lead to a quick resolution
thought
Thoughts represent an idea that popped up from reviewing. These comments are non-blocking by nature, but they are extremely valuable and can lead to more focused initiatives and mentoring opportunities
chore
Chores are simple tasks that must be done before the subject can be “officially” accepted. Usually, these comments reference some common process. Try to leave a link to the process description so that the reader knows how to resolve the chore
Contributor License Agreement
Like other OpenSource projects, TiddlyWiki5 needs a signed contributor license agreement from individual contributors. This is a legal agreement that allows contributors to assert that they own the copyright of their contribution, and that they agree to license it to the UnaMesa Association (the legal entity that owns TiddlyWiki on behalf of the community).
Ensure that the "branch" dropdown at the top left is set to tiddlywiki-com
Click the "edit" button at the top-right corner (clicking this button will fork the project so you can edit the file)
Add your name at the bottom
eg: Jeremy Ruston, @Jermolene, 2011/11/22
Below the edit box for the CLA text you should see a box labelled Propose file change
Enter a brief title to explain the change (eg, "Signing the CLA")
Click the green button labelled Propose file change
On the following screen, click the green button labelled Create pull request
The CLA documents used for this project were created using Harmony Project Templates. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity".
This file was automatically generated by TiddlyWiki5
\ No newline at end of file
diff --git a/core/copyright.tid b/core/copyright.tid
index ce0d6b02f..233295ce2 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-2025, UnaMesa Association
All rights reserved.
Redistribution and use in source and binary forms, with or without
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")
+
\ 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")
+
\ No newline at end of file
diff --git a/core/images/new-journal-button.tid b/core/images/new-journal-button.tid
index 3b04d5786..5b793deb5 100755
--- a/core/images/new-journal-button.tid
+++ b/core/images/new-journal-button.tid
@@ -1,6 +1,4 @@
title: $:/core/images/new-journal-button
tags: $:/tags/Image
-<$parameters size="22pt" day=<>>
-
-$parameters>
\ No newline at end of file
+<$parameters size="22pt" day=<>>$parameters>
\ 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")
+
\ No newline at end of file
diff --git a/core/language/en-GB/Buttons.multids b/core/language/en-GB/Buttons.multids
index fa769d117..2fa732fd9 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
@@ -129,6 +133,7 @@ Excise/Caption/Replace/Link: link
Excise/Caption/Replace/Transclusion: transclusion
Excise/Caption/Tag: Tag new tiddler with the title of this tiddler
Excise/Caption/TiddlerExists: Warning: tiddler already exists
+Excise/DefaultTitle: New Excision
Excise/Hint: Excise the selected text into a new tiddler
Heading1/Caption: heading 1
Heading1/Hint: Apply heading level 1 formatting to lines containing selection
diff --git a/core/language/en-GB/ControlPanel.multids b/core/language/en-GB/ControlPanel.multids
index d8321edbf..129dab0b4 100644
--- a/core/language/en-GB/ControlPanel.multids
+++ b/core/language/en-GB/ControlPanel.multids
@@ -96,6 +96,10 @@ Plugins/PluginWillRequireReload: (requires reload)
Plugins/Plugins/Caption: Plugins
Plugins/Plugins/Hint: Plugins
Plugins/Reinstall/Caption: reinstall
+Plugins/Stability/Deprecated: DEPRECATED
+Plugins/Stability/Experimental: EXPERIMENTAL
+Plugins/Stability/Legacy: LEGACY
+Plugins/Stability/Stable: STABLE
Plugins/Themes/Caption: Themes
Plugins/Themes/Hint: Theme plugins
Plugins/Update/Caption: update
@@ -171,6 +175,8 @@ Settings/NavigationPermalinkviewMode/UpdateAddressBar/Description: Update addres
Settings/PerformanceInstrumentation/Caption: Performance Instrumentation
Settings/PerformanceInstrumentation/Hint: Displays performance statistics in the browser developer console. Requires reload to take effect
Settings/PerformanceInstrumentation/Description: Enable performance instrumentation
+Settings/RecentLimit/Caption: Recent Tab Limit
+Settings/RecentLimit/Hint: Maximum number of tiddlers to be displayed under the sidebar "Recent" tab
Settings/ToolbarButtonStyle/Caption: Toolbar Button Style
Settings/ToolbarButtonStyle/Hint: Choose the style for toolbar buttons:
Settings/ToolbarButtonStyle/Styles/Borderless: Borderless
@@ -198,6 +204,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 +218,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 +247,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/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/EditTemplate.multids b/core/language/en-GB/EditTemplate.multids
index c4bfa5e56..8b2e6e17a 100644
--- a/core/language/en-GB/EditTemplate.multids
+++ b/core/language/en-GB/EditTemplate.multids
@@ -26,6 +26,8 @@ Tags/ClearInput/Caption: clear input
Tags/ClearInput/Hint: Clear tag input
Tags/Dropdown/Caption: tag list
Tags/Dropdown/Hint: Show tag list
+Tags/EmptyMessage: No tags found
+Tags/EmptyMessage/System: No system tags found
Title/BadCharacterWarning: Warning: avoid using any of the characters <> in tiddler titles
Title/Exists/Prompt: Target tiddler already exists
Title/Relink/Prompt: Update ''<$text text=<>/>'' to ''<$text text=<>/>'' in the //tags// and //list// fields of other tiddlers
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 1330e60a0..9830e96c1 100644
--- a/core/language/en-GB/Fields.multids
+++ b/core/language/en-GB/Fields.multids
@@ -4,6 +4,7 @@ _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]]
@@ -29,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/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/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 2c10d1acb..d8c091375 100644
--- a/core/language/en-GB/Misc.multids
+++ b/core/language/en-GB/Misc.multids
@@ -30,7 +30,7 @@ Error/DeserializeOperator/UnknownDeserializer: Filter Error: Unknown deserialize
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''.`
@@ -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/Search.multids b/core/language/en-GB/Search.multids
index 2a57a6416..f5aa478bf 100644
--- a/core/language/en-GB/Search.multids
+++ b/core/language/en-GB/Search.multids
@@ -6,6 +6,8 @@ Filter/Hint: Search via a [[filter expression|https://tiddlywiki.com/static/Filt
Filter/Matches: //<> matches//
Matches: //<> matches//
Matches/All: All matches:
+Matches/NoMatch: //No match//
+Matches/NoResult: //No search result//
Matches/Title: Title matches:
Search: Search
Search/TooShort: Search text too short
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_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
deleted file mode 100644
index 6ae32331c..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 format icon file
-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/build.js b/core/modules/commands/build.js
index 8471119d7..60456372d 100644
--- a/core/modules/commands/build.js
+++ b/core/modules/commands/build.js
@@ -24,7 +24,7 @@ var Command = function(params,commander) {
Command.prototype.execute = function() {
// Get the build targets defined in the wiki
- var buildTargets = $tw.boot.wikiInfo.build;
+ var buildTargets = $tw.boot.wikiInfo && $tw.boot.wikiInfo.build;
if(!buildTargets) {
return "No build targets defined";
}
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/savetiddlers.js b/core/modules/commands/savetiddlers.js
index d3b82d726..9c750e204 100644
--- a/core/modules/commands/savetiddlers.js
+++ b/core/modules/commands/savetiddlers.js
@@ -46,7 +46,7 @@ Command.prototype.execute = function() {
type = tiddler.fields.type || "text/vnd.tiddlywiki",
contentTypeInfo = $tw.config.contentTypeInfo[type] || {encoding: "utf8"},
filename = path.resolve(pathname,$tw.utils.encodeURIComponentExtended(title));
- fs.writeFileSync(filename,tiddler.fields.text,contentTypeInfo.encoding);
+ fs.writeFileSync(filename,tiddler.fields.text || "",contentTypeInfo.encoding);
});
return null;
};
diff --git a/core/modules/commands/savewikifolder.js b/core/modules/commands/savewikifolder.js
index 48e9be56a..461ff6f04 100644
--- a/core/modules/commands/savewikifolder.js
+++ b/core/modules/commands/savewikifolder.js
@@ -5,7 +5,14 @@ module-type: command
Command to save the current wiki as a wiki folder
---savewikifolder []
+--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 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/editor/engines/framed.js b/core/modules/editor/engines/framed.js
index 01b9974c2..2a3472513 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) {
diff --git a/core/modules/editor/engines/simple.js b/core/modules/editor/engines/simple.js
index 64c087133..9f60656bf 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) {
diff --git a/core/modules/editor/operations/text/excise.js b/core/modules/editor/operations/text/excise.js
index ced771719..0753705c5 100644
--- a/core/modules/editor/operations/text/excise.js
+++ b/core/modules/editor/operations/text/excise.js
@@ -12,20 +12,27 @@ Text editor operation to excise the selection to a new tiddler
/*global $tw: false */
"use strict";
+function isMarkdown(mediaType) {
+ return mediaType === 'text/markdown' || mediaType === 'text/x-markdown';
+}
+
exports["excise"] = function(event,operation) {
var editTiddler = this.wiki.getTiddler(this.editTitle),
- editTiddlerTitle = this.editTitle;
+ editTiddlerTitle = this.editTitle,
+ wikiLinks = !isMarkdown(editTiddler.fields.type),
+ excisionBaseTitle = $tw.language.getString("Buttons/Excise/DefaultTitle");
if(editTiddler && editTiddler.fields["draft.of"]) {
editTiddlerTitle = editTiddler.fields["draft.of"];
}
- var excisionTitle = event.paramObject.title || this.wiki.generateNewTitle("New Excision");
+ var excisionTitle = event.paramObject.title || this.wiki.generateNewTitle(excisionBaseTitle);
this.wiki.addTiddler(new $tw.Tiddler(
this.wiki.getCreationFields(),
this.wiki.getModificationFields(),
{
title: excisionTitle,
text: operation.selection,
- tags: event.paramObject.tagnew === "yes" ? [editTiddlerTitle] : []
+ tags: event.paramObject.tagnew === "yes" ? [editTiddlerTitle] : [],
+ type: editTiddler.fields.type
}
));
operation.replacement = excisionTitle;
@@ -34,7 +41,8 @@ exports["excise"] = function(event,operation) {
operation.replacement = "{{" + operation.replacement+ "}}";
break;
case "link":
- operation.replacement = "[[" + operation.replacement+ "]]";
+ operation.replacement = wikiLinks ? "[[" + operation.replacement+ "]]"
+ : ("[" + operation.replacement + "](<#" + operation.replacement + ">)");
break;
case "macro":
operation.replacement = "<<" + (event.paramObject.macro || "translink") + " \"\"\"" + operation.replacement + "\"\"\">>";
diff --git a/core/modules/editor/operations/text/wrap-selection.js b/core/modules/editor/operations/text/wrap-selection.js
index 6800cbe5b..665d72eb4 100644
--- a/core/modules/editor/operations/text/wrap-selection.js
+++ b/core/modules/editor/operations/text/wrap-selection.js
@@ -13,37 +13,125 @@ Text editor operation to wrap the selection with the specified prefix and suffix
"use strict";
exports["wrap-selection"] = function(event,operation) {
- if(operation.selStart === operation.selEnd) {
- // No selection; check if we're within the prefix/suffix
- if(operation.text.substring(operation.selStart - event.paramObject.prefix.length,operation.selStart + event.paramObject.suffix.length) === event.paramObject.prefix + event.paramObject.suffix) {
+ var o = operation,
+ prefix = event.paramObject.prefix,
+ suffix = event.paramObject.suffix,
+ trimSelection = event.paramObject.trimSelection || "no",
+ selLength = o.selEnd - o.selStart;
+
+ // This function detects, if trailing spaces are part of the selection __and__ if the user wants to handle them
+ // Returns "yes", "start", "end", "no" (default)
+ // yes .. there are trailing spaces at both ends
+ // start .. there are trailing spaces at the start
+ // end .. there are trailing spaces at the end
+ // no .. no trailing spaces are taken into account
+ var trailingSpaceAt = function(sel) {
+ var _start,
+ _end,
+ result;
+ // trimSelection is a user parameter, which this evaluations takes into account
+ switch(trimSelection) {
+ case "end":
+ result = (sel.trimEnd().length !== selLength) ? "end" : "no";
+ break;
+ case "yes":
+ _start = sel.trimStart().length !== selLength;
+ _end = sel.trimEnd().length !== selLength;
+ result = (_start && _end) ? "yes" : (_start) ? "start" : (_end) ? "end" : "no";
+ break;
+ case "start":
+ result = (sel.trimStart().length !== selLength) ? "start" : "no";
+ break;
+ default:
+ result = "no";
+ break;
+ }
+ return result;
+ }
+
+ function togglePrefixSuffix() {
+ if(o.text.substring(o.selStart - prefix.length, o.selStart + suffix.length) === prefix + suffix) {
// Remove the prefix and suffix
- operation.cutStart = operation.selStart - event.paramObject.prefix.length;
- operation.cutEnd = operation.selEnd + event.paramObject.suffix.length;
- operation.replacement = "";
- operation.newSelStart = operation.cutStart;
- operation.newSelEnd = operation.newSelStart;
+ o.cutStart = o.selStart - prefix.length;
+ o.cutEnd = o.selEnd + suffix.length;
+ o.replacement = "";
+ o.newSelStart = o.cutStart;
+ o.newSelEnd = o.newSelStart;
} else {
// Wrap the cursor instead
- operation.cutStart = operation.selStart;
- operation.cutEnd = operation.selEnd;
- operation.replacement = event.paramObject.prefix + event.paramObject.suffix;
- operation.newSelStart = operation.selStart + event.paramObject.prefix.length;
- operation.newSelEnd = operation.newSelStart;
+ o.cutStart = o.selStart;
+ o.cutEnd = o.selEnd;
+ o.replacement = prefix + suffix;
+ o.newSelStart = o.selStart + prefix.length;
+ o.newSelEnd = o.newSelStart;
}
- } else if(operation.text.substring(operation.selStart,operation.selStart + event.paramObject.prefix.length) === event.paramObject.prefix && operation.text.substring(operation.selEnd - event.paramObject.suffix.length,operation.selEnd) === event.paramObject.suffix) {
+ }
+
+ // options: lenPrefix, lenSuffix
+ function removePrefixSuffix(options) {
+ options = options || {};
+ var _lenPrefix = options.lenPrefix || 0;
+ var _lenSuffix = options.lenSuffix || 0;
+
+ o.cutStart = o.selStart - _lenPrefix;
+ o.cutEnd = o.selEnd + _lenSuffix;
+ o.replacement = (_lenPrefix || _lenSuffix) ? o.selection : o.selection.substring(prefix.length, o.selection.length - suffix.length);
+ o.newSelStart = o.cutStart;
+ o.newSelEnd = o.cutStart + o.replacement.length;
+ }
+
+ function addPrefixSuffix() {
+ // remove trailing space if requested
+ switch(trailingSpaceAt(o.selection)) {
+ case "no":
+ // has no trailing spaces
+ o.cutStart = o.selStart;
+ o.cutEnd = o.selEnd;
+ o.replacement = prefix + o.selection + suffix;
+ o.newSelStart = o.selStart;
+ o.newSelEnd = o.selStart + o.replacement.length;
+ break;
+ case "yes":
+ // handle both ends
+ o.cutStart = o.selEnd - (o.selection.trimStart().length);
+ o.cutEnd = o.selection.trimEnd().length + o.selStart;
+ o.replacement = prefix + o.selection.trim() + suffix;
+ o.newSelStart = o.cutStart;
+ o.newSelEnd = o.cutStart + o.replacement.length;
+ break;
+ case "start":
+ // handle leading
+ o.cutStart = o.selEnd - (o.selection.trimStart().length);
+ o.cutEnd = o.selEnd;
+ o.replacement = prefix + o.selection.trimStart() + suffix;
+ o.newSelStart = o.cutStart;
+ o.newSelEnd = o.cutStart + o.replacement.length;
+ break;
+ case "end":
+ // handle trailing
+ o.cutStart = o.selStart;
+ o.cutEnd = o.selection.trimEnd().length + o.selStart;
+ o.replacement = prefix + o.selection.trimEnd() + suffix;
+ o.newSelStart = o.selStart;
+ o.newSelEnd = o.selStart + o.replacement.length;
+ break;
+ }
+ }
+
+ if(o.selStart === o.selEnd) {
+ // No selection; Create prefix and suffix. Set cursor in between them: ""|""
+ togglePrefixSuffix();
+ } else if(o.text.substring(o.selStart, o.selStart + prefix.length) === prefix &&
+ o.text.substring(o.selEnd - suffix.length,o.selEnd) === suffix) {
// Prefix and suffix are already present, so remove them
- operation.cutStart = operation.selStart;
- operation.cutEnd = operation.selEnd;
- operation.replacement = operation.selection.substring(event.paramObject.prefix.length,operation.selection.length - event.paramObject.suffix.length);
- operation.newSelStart = operation.selStart;
- operation.newSelEnd = operation.selStart + operation.replacement.length;
+ removePrefixSuffix();
+ } else if(o.text.substring(o.selStart - prefix.length, o.selStart) === prefix &&
+ o.text.substring(o.selEnd, o.selEnd + suffix.length) === suffix) {
+ // Prefix and suffix are present BUT not selected -> remove them
+ removePrefixSuffix({"lenPrefix": prefix.length, "lenSuffix": suffix.length});
} else {
// Add the prefix and suffix
- operation.cutStart = operation.selStart;
- operation.cutEnd = operation.selEnd;
- operation.replacement = event.paramObject.prefix + operation.selection + event.paramObject.suffix;
- operation.newSelStart = operation.selStart;
- operation.newSelEnd = operation.selStart + operation.replacement.length;
+ addPrefixSuffix();
}
};
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/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 padString.length) {
+ while(padStringLength > padString.length) {
padString += fill;
}
//make sure we do not exceed the specified length
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/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/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
@@ -361,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/textparser.js b/core/modules/parsers/textparser.js
index 06b08f30f..17f9bde10 100644
--- a/core/modules/parsers/textparser.js
+++ b/core/modules/parsers/textparser.js
@@ -14,10 +14,12 @@ 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;
@@ -32,4 +34,3 @@ exports["text/css"] = TextParser;
exports["application/x-tiddler-dictionary"] = TextParser;
})();
-
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..c2d6a43b8
--- /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
index 5d0a8878b..85bd14d5c 100644
--- a/core/modules/parsers/wikiparser/rules/fnprocdef.js
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -35,7 +35,7 @@ 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;
+ this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*))?\)(\s*\r?\n)?/mg;
};
/*
@@ -49,11 +49,11 @@ exports.parse = function() {
if(this.match[3]) {
params = $tw.utils.parseParameterDefinition(this.match[4]);
}
- // Is this a multiline definition?
+ // Is the remainder of the line blank after the parameter close paren?
var reEnd;
if(this.match[5]) {
- // 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[2]) + ")?(?:$|\\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[2]) + ")?(?:$|\\r?\\n))","mg");
} else {
// Otherwise, the end of the definition is marked by the end of the line
reEnd = /($|\r?\n)/mg;
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 4dbd6a07c..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];
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/macrodef.js b/core/modules/parsers/wikiparser/rules/macrodef.js
index 74a94a385..2001f70d5 100644
--- a/core/modules/parsers/wikiparser/rules/macrodef.js
+++ b/core/modules/parsers/wikiparser/rules/macrodef.js
@@ -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[^\\S\\n\\r]*\\\\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;
diff --git a/core/modules/parsers/wikiparser/rules/parameters.js b/core/modules/parsers/wikiparser/rules/parameters.js
index f288740aa..60bbd8901 100644
--- a/core/modules/parsers/wikiparser/rules/parameters.js
+++ b/core/modules/parsers/wikiparser/rules/parameters.js
@@ -26,7 +26,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\parameters\s*\(([^)]*)\)(\s*\r?\n)?/mg;
+ this.matchRegExp = /\\parameters\s*\(([^)]*)\)(\s*\r?\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/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 9cdb91913..e1793fb89 100644
--- a/core/modules/parsers/wikiparser/wikiparser.js
+++ b/core/modules/parsers/wikiparser/wikiparser.js
@@ -92,6 +92,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
};
@@ -195,6 +200,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
@@ -205,16 +211,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;
};
@@ -224,7 +238,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 [];
@@ -232,7 +246,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;
@@ -265,11 +287,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
@@ -278,7 +310,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
@@ -287,8 +319,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;
};
/*
@@ -318,7 +351,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);
}
@@ -331,6 +373,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
@@ -350,7 +397,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
@@ -361,7 +411,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
@@ -374,7 +432,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
+ };
};
/*
@@ -385,7 +445,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});
}
};
@@ -438,4 +498,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/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/put.js b/core/modules/savers/put.js
index de9ba9465..69689e6db 100644
--- a/core/modules/savers/put.js
+++ b/core/modules/savers/put.js
@@ -20,7 +20,7 @@ Retrieve ETag if available
*/
var retrieveETag = function(self) {
var headers = {
- Accept: "*/*;charset=UTF-8"
+ Accept: "*/*"
};
$tw.utils.httpRequest({
url: self.uri(),
@@ -48,14 +48,14 @@ 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",
callback: function(err,data,xhr) {
// Check DAV header http://www.webdav.org/specs/rfc2518.html#rfc.section.9.1
if(!err) {
- self.serverAcceptsPuts = xhr.status === 200 && !!xhr.getResponseHeader("dav");
+ self.serverAcceptsPuts = xhr.status >= 200 && xhr.status < 300 && !!xhr.getResponseHeader("dav");
}
}
});
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/routes/get-login-basic.js b/core/modules/server/routes/get-login-basic.js
index d573a0b5d..69d3bf908 100644
--- a/core/modules/server/routes/get-login-basic.js
+++ b/core/modules/server/routes/get-login-basic.js
@@ -25,7 +25,7 @@ exports.handler = function(request,response,state) {
response.end();
} else {
// Redirect to the root wiki if login worked
- var location = ($tw.syncadaptor && $tw.syncadaptor.host)? $tw.syncadaptor.host: "/";
+ var location = ($tw.syncadaptor && $tw.syncadaptor.host)? $tw.syncadaptor.host: `${state.pathPrefix}/`;
response.writeHead(302,{
Location: location
});
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 f5d90afb5..d96d569c3 100644
--- a/core/modules/startup/rootwidget.js
+++ b/core/modules/startup/rootwidget.js
@@ -38,6 +38,8 @@ exports.startup = function() {
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"],
@@ -46,7 +48,13 @@ 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"],
+ bearerAuthToken: params["bearer-auth-token"],
+ bearerAuthTokenFromStore: params["bearer-auth-token-from-store"]
});
});
$tw.rootWidget.addEventListener("tm-http-cancel-all-requests",function(event) {
@@ -67,7 +75,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/startup.js b/core/modules/startup/startup.js
index e0990228f..b971e473b 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 = {};
@@ -83,8 +88,10 @@ exports.startup = function() {
if($tw.browser) {
var pluginTiddler = $tw.wiki.getTiddler(plugins[0]);
if(pluginTiddler) {
+ document.documentElement.setAttribute("lang",pluginTiddler.getFieldString("name"));
document.documentElement.setAttribute("dir",pluginTiddler.getFieldString("text-direction") || "auto");
} else {
+ document.documentElement.setAttribute("lang","en-GB");
document.documentElement.removeAttribute("dir");
}
}
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("