1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-26 12:53:42 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Mario Pietsch
70b4557738 [DOCS] Add link to "TiddlyWiki Archive" tiddler to the TiddlyWiki Releases info (#9613) 2026-01-25 16:59:54 +01:00
Mario Pietsch
efe58e41bc [DOCS] Fix some typos in "days Operator (Examples)" (#9479)
* [DOCS] Fix some typos in "days Operator (Examples)"

* Remove created and modified fields from days.tid

Remove created and modified fields from days.tid
2026-01-21 09:09:14 +01:00
Mario Pietsch
79e3d14698 [DOCS] Make TaskManagementExamples more "hackable" (#9482)
* [DOCS] Make TaskManagementExamples more "hackable"

* Update modified date in TaskManagementExample.tid

* Fix modified date in TaskManagementExampleDraggable
2026-01-20 13:23:17 +01:00
superuser-does
763d717a13 Improvements to DateFormat tiddler (#9583) 2026-01-20 13:18:24 +01:00
685 changed files with 7807 additions and 9884 deletions

4
.gitattributes vendored
View File

@@ -1,4 +0,0 @@
boot/** -linguist-generated
**/tiddlywiki.files linguist-language=JSON
**/tiddlywiki.info linguist-language=JSON
**/plugin.info linguist-language=JSON

View File

@@ -1,62 +0,0 @@
---
name: Bug report
about: Create a report to help us improve TiddlyWiki 5
title: "[Report] "
type: report
---
<!-- Remove elements, that you do not need -->
<!-- Add screenshots where needed -->
**Problem Description**
<!-- Describe your problem: A clear and concise description of what your problem is -->
**To Reproduce**
Steps to reproduce the behavior:
1. At https://tiddlywiki.com
2. Click on ...
3. Scroll down to ...
4. See ...
**Expected behavior**
As a user,
<!-- As a developer, -->
I would expect ...
**TiddlyWiki Configuration**
<!-- Please complete the following information -->
- Report created with: [Wiki Information](https://tiddlywiki.com/#%24%3A%2Fcore%2Fui%2FControlPanel%2FWikiInformation)
<!-- Your report comes here -->
<!-- or -->
<!-- Add it manually -->
- Version: <!-- e.g. v5.3.8 -->
- Saving mechanism: <!-- e.g. Node.js, TiddlyDesktop, TiddlyHost etc -->
- Plugins installed: <!-- e.g. Freelinks, TiddlyMap ... other 3rd party plugins -->
**Desktop**
<!-- Please complete the following information -->
- OS: <!-- e.g. iOS -->
- Browser: <!-- e.g. chrome, safari, FireFox -- Version: -->
**Smartphone**
<!-- Please complete the following information -->
- Device: <!-- e.g. iPhone6 -->
- OS: <!-- e.g. iOS8.1 -->
- Browser: <!-- e.g. stock browser, safari, FireFox -- Version: -->
**Additional context**
<!-- Add any other context about the problem here. -->

67
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Bug report
description: Create a report to help us improve TiddlyWiki 5
title: "[BUG] "
body:
- type: textarea
id: Describe
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: Expected
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: false
- type: textarea
id: Reproduce
attributes:
label: To Reproduce
description: "Steps to reproduce the behavior:"
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: false
- type: textarea
id: Screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
placeholder: Drag image here to upload screenshot!
validations:
required: false
- type: textarea
id: Configuration
attributes:
label: TiddlyWiki Configuration
description: please complete the following information
placeholder: |
- Version [e.g. v5.1.24]
- Saving mechanism [e.g. Node.js, TiddlyDesktop, TiddlyHost etc]
- Plugins installed [e.g. Freelinks, TiddlyMap]
### Desktop (please complete the following information):
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
### Smartphone (please complete the following information):
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
validations:
required: true
- type: textarea
id: Context
attributes:
label: Additional context
description: Add any other context about the problem here.

View File

@@ -4,23 +4,17 @@ about: Suggest an idea for TiddlyWiki 5
title: "[IDEA]" title: "[IDEA]"
labels: '' labels: ''
assignees: '' assignees: ''
type: idea
--- ---
**Is your idea related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the problem is. Eg:
As a user, I would like [...]
**Describe the solution you'd like** **Describe the solution you'd like**
A clear and concise description of what you want to happen. A clear and concise description of what you want to happen.
**Describe alternatives you've considered** **Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or features you've considered.
**Additional context** **Additional context**
Add any other context or screenshots about the feature request here. Add any other context or screenshots about the feature request here.

View File

@@ -10,7 +10,7 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: "${{ env.NODE_VERSION }}" node-version: "${{ env.NODE_VERSION }}"
@@ -30,7 +30,7 @@ jobs:
TW5_BUILD_MAIN_EDITION: "./editions/prerelease" TW5_BUILD_MAIN_EDITION: "./editions/prerelease"
TW5_BUILD_OUTPUT: "./output/prerelease" TW5_BUILD_OUTPUT: "./output/prerelease"
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: "${{ env.NODE_VERSION }}" node-version: "${{ env.NODE_VERSION }}"
@@ -62,7 +62,7 @@ jobs:
TW5_BUILD_OUTPUT: "./output" TW5_BUILD_OUTPUT: "./output"
TW5_BUILD_ARCHIVE: "./output" TW5_BUILD_ARCHIVE: "./output"
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: "${{ env.NODE_VERSION }}" node-version: "${{ env.NODE_VERSION }}"

View File

@@ -1,40 +0,0 @@
name: ESLint
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
concurrency:
group: lint-${{ github.event.pull_request.number || github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
# Needed for GitHub Checks API
checks: write
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm install --include=dev
- name: Run ESLint with reviewdog (GitHub Checks)
uses: reviewdog/action-eslint@v1
with:
eslint_flags: '.'
reporter: github-pr-check
fail_level: error
level: error
tool_name: ESLint PR code

View File

@@ -20,7 +20,7 @@ jobs:
steps: steps:
- name: build-size-check - name: build-size-check
id: get_sizes id: get_sizes
uses: TiddlyWiki/cerebrus@v6 uses: TiddlyWiki/cerebrus@v4
with: with:
pr_number: ${{ github.event.pull_request.number }} pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }} repo: ${{ github.repository }}

View File

@@ -25,7 +25,7 @@ jobs:
steps: steps:
- name: Build and check size - name: Build and check size
uses: TiddlyWiki/cerebrus@v6 uses: TiddlyWiki/cerebrus@v4
with: with:
pr_number: ${{ inputs.pr_number }} pr_number: ${{ inputs.pr_number }}
repo: ${{ github.repository }} repo: ${{ github.repository }}

View File

@@ -0,0 +1,18 @@
name: Validate PR Paths
on:
pull_request_target:
types: [opened, reopened, synchronize]
jobs:
validate-pr:
runs-on: ubuntu-latest
steps:
- name: Validate PR
uses: TiddlyWiki/cerebrus@v4
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
base_ref: ${{ github.base_ref }}
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,37 +0,0 @@
name: PR Validation
on:
pull_request_target:
types: [opened, reopened, synchronize]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
validate-pr:
runs-on: ubuntu-latest
steps:
# Step 1: Validate PR paths
- name: Validate PR Paths
uses: TiddlyWiki/cerebrus@v6
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
base_ref: ${{ github.event.pull_request.base.ref }}
github_token: ${{ secrets.GITHUB_TOKEN }}
mode: rules
continue-on-error: true
# Step 2: Validate change notes
- name: Validate Change Notes
uses: TiddlyWiki/cerebrus@v6
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
base_ref: ${{ github.event.pull_request.base.ref }}
github_token: ${{ secrets.GITHUB_TOKEN }}
mode: changenotes
continue-on-error: false

View File

@@ -5,7 +5,7 @@
# Default to the current version number for building the plugin library # Default to the current version number for building the plugin library
if [ -z "$TW5_BUILD_VERSION" ]; then if [ -z "$TW5_BUILD_VERSION" ]; then
TW5_BUILD_VERSION=v5.4.0 TW5_BUILD_VERSION=v5.3.8
fi fi
echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]" echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]"
@@ -73,8 +73,10 @@ rm $TW5_BUILD_OUTPUT/dev/static/*
echo "<a href='./plugins/tiddlywiki/tw2parser/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/tw2parser/index.html</a>" > $TW5_BUILD_OUTPUT/classicparserdemo.html echo "<a href='./plugins/tiddlywiki/tw2parser/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/tw2parser/index.html</a>" > $TW5_BUILD_OUTPUT/classicparserdemo.html
echo "<a href='./plugins/tiddlywiki/codemirror/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/codemirror/index.html</a>" > $TW5_BUILD_OUTPUT/codemirrordemo.html echo "<a href='./plugins/tiddlywiki/codemirror/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/codemirror/index.html</a>" > $TW5_BUILD_OUTPUT/codemirrordemo.html
echo "<a href='./plugins/tiddlywiki/d3/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/d3/index.html</a>" > $TW5_BUILD_OUTPUT/d3demo.html
echo "<a href='./plugins/tiddlywiki/highlight/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/highlight/index.html</a>" > $TW5_BUILD_OUTPUT/highlightdemo.html echo "<a href='./plugins/tiddlywiki/highlight/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/highlight/index.html</a>" > $TW5_BUILD_OUTPUT/highlightdemo.html
echo "<a href='./plugins/tiddlywiki/markdown/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/markdown/index.html</a>" > $TW5_BUILD_OUTPUT/markdowndemo.html echo "<a href='./plugins/tiddlywiki/markdown/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/markdown/index.html</a>" > $TW5_BUILD_OUTPUT/markdowndemo.html
echo "<a href='./plugins/tiddlywiki/tahoelafs/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/tahoelafs/index.html</a>" > $TW5_BUILD_OUTPUT/tahoelafs.html
# Put the build details into a .tid file so that it can be included in each build (deleted at the end of this script) # Put the build details into a .tid file so that it can be included in each build (deleted at the end of this script)
@@ -299,6 +301,26 @@ node $TW5_BUILD_TIDDLYWIKI \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/katex/empty.html text/plain \ --rendertiddler $:/core/save/empty plugins/tiddlywiki/katex/empty.html text/plain \
|| exit 1 || exit 1
# /plugins/tiddlywiki/tahoelafs/index.html Demo wiki with Tahoe-LAFS plugin
# /plugins/tiddlywiki/tahoelafs/empty.html Empty wiki with Tahoe-LAFS plugin
node $TW5_BUILD_TIDDLYWIKI \
./editions/tahoelafs \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT \
--rendertiddler $:/core/save/all plugins/tiddlywiki/tahoelafs/index.html text/plain \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/tahoelafs/empty.html text/plain \
|| exit 1
# /plugins/tiddlywiki/d3/index.html Demo wiki with D3 plugin
# /plugins/tiddlywiki/d3/empty.html Empty wiki with D3 plugin
node $TW5_BUILD_TIDDLYWIKI \
./editions/d3demo \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT \
--rendertiddler $:/core/save/all plugins/tiddlywiki/d3/index.html text/plain \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/d3/empty.html text/plain \
|| exit 1
# /plugins/tiddlywiki/codemirror/index.html Demo wiki with codemirror plugin # /plugins/tiddlywiki/codemirror/index.html Demo wiki with codemirror plugin
# /plugins/tiddlywiki/codemirror/empty.html Empty wiki with codemirror plugin # /plugins/tiddlywiki/codemirror/empty.html Empty wiki with codemirror plugin
node $TW5_BUILD_TIDDLYWIKI \ node $TW5_BUILD_TIDDLYWIKI \

View File

@@ -8,8 +8,6 @@ On the server this file is executed directly to boot TiddlyWiki. In the browser,
\*/ \*/
/* eslint-disable @stylistic/indent */
var _boot = (function($tw) { var _boot = (function($tw) {
/*jslint node: true, browser: true */ /*jslint node: true, browser: true */
@@ -46,8 +44,12 @@ $tw.utils.hop = function(object,property) {
return object ? Object.prototype.hasOwnProperty.call(object,property) : false; return object ? Object.prototype.hasOwnProperty.call(object,property) : false;
}; };
/** @deprecated Use Array.isArray instead */ /*
$tw.utils.isArray = value => Array.isArray(value); Determine if a value is an array
*/
$tw.utils.isArray = function(value) {
return Object.prototype.toString.call(value) == "[object Array]";
};
/* /*
Check if an array is equal by value and by reference. Check if an array is equal by value and by reference.
@@ -126,22 +128,35 @@ $tw.utils.pushTop = function(array,value) {
return array; return array;
}; };
/** @deprecated Use instanceof Date instead */ /*
$tw.utils.isDate = value => value instanceof Date; Determine if a value is a date
*/
$tw.utils.isDate = function(value) {
return Object.prototype.toString.call(value) === "[object Date]";
};
/** @deprecated Use array iterative methods instead */ /*
Iterate through all the own properties of an object or array. Callback is invoked with (element,title,object)
*/
$tw.utils.each = function(object,callback) { $tw.utils.each = function(object,callback) {
var next,f,length;
if(object) { if(object) {
if(Array.isArray(object)) { if(Object.prototype.toString.call(object) == "[object Array]") {
object.every((element,index,array) => { for(f=0, length=object.length; f<length; f++) {
const next = callback(element,index,array); next = callback(object[f],f,object);
return next !== false; if(next === false) {
}); break;
}
}
} else { } else {
Object.entries(object).every(entry => { var keys = Object.keys(object);
const next = callback(entry[1], entry[0], object); for(f=0, length=keys.length; f<length; f++) {
return next !== false; var key = keys[f];
}); next = callback(object[key],key,object);
if(next === false) {
break;
}
}
} }
} }
}; };
@@ -316,13 +331,32 @@ $tw.utils.htmlDecode = function(s) {
return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&"); return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
}; };
/** @deprecated Use window.location.hash instead. */ /*
$tw.utils.getLocationHash = () => window.location.hash; Get the browser location.hash. We don't use location.hash because of the way that Firefox auto-urldecodes it (see http://stackoverflow.com/questions/1703552/encoding-of-window-location-hash)
*/
$tw.utils.getLocationHash = function() {
var href = window.location.href;
var idx = href.indexOf('#');
if(idx === -1) {
return "#";
} else if(href.substr(idx + 1,1) === "#" || href.substr(idx + 1,3) === "%23") {
// Special case: ignore location hash if it itself starts with a #
return "#";
} else {
return href.substring(idx);
}
};
/** @deprecated Pad a string to a given length with "0"s. Length defaults to 2 */ /*
$tw.utils.pad = function(value,length = 2) { Pad a string to a given length with "0"s. Length defaults to 2
const s = value.toString(); */
return s.padStart(length, "0"); $tw.utils.pad = function(value,length) {
length = length || 2;
var s = value.toString();
if(s.length < length) {
s = "000000000000000000000000000".substr(0,length - s.length) + s;
}
return s;
}; };
// Convert a date into UTC YYYYMMDDHHMMSSmmm format // Convert a date into UTC YYYYMMDDHHMMSSmmm format
@@ -596,7 +630,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Compile the code into a function // Compile the code into a function
var fn; var fn;
if($tw.browser) { if($tw.browser) {
fn = window["eval"](code + "\n\n//# sourceURL=" + filename); // eslint-disable-line no-eval -- See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839 fn = window["eval"](code + "\n\n//# sourceURL=" + filename);
} else { } else {
if(sandbox){ if(sandbox){
fn = vm.runInContext(code,sandbox,filename) fn = vm.runInContext(code,sandbox,filename)
@@ -607,7 +641,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Call the function and return the exports // Call the function and return the exports
return fn.apply(null,contextValues); return fn.apply(null,contextValues);
}; };
$tw.utils.sandbox = !$tw.browser ? vm.createContext({}) : undefined; $tw.utils.sandbox = !$tw.browser ? vm.createContext({}) : undefined;
/* /*
Run code in a sandbox with only the specified context variables in scope Run code in a sandbox with only the specified context variables in scope
*/ */
@@ -765,13 +799,12 @@ the password, and to encrypt/decrypt a block of text
$tw.utils.Crypto = function() { $tw.utils.Crypto = function() {
var sjcl = $tw.node ? (global.sjcl || require("./sjcl.js")) : window.sjcl, var sjcl = $tw.node ? (global.sjcl || require("./sjcl.js")) : window.sjcl,
currentPassword = null, currentPassword = null,
callSjcl = function(method,inputText,password,options) { callSjcl = function(method,inputText,password) {
options = options || {};
password = password || currentPassword; password = password || currentPassword;
var outputText; var outputText;
try { try {
if(password) { if(password) {
outputText = sjcl[method](password,inputText,options); outputText = sjcl[method](password,inputText);
} }
} catch(ex) { } catch(ex) {
console.log("Crypto error:" + ex); console.log("Crypto error:" + ex);
@@ -797,8 +830,7 @@ $tw.utils.Crypto = function() {
return !!currentPassword; return !!currentPassword;
} }
this.encrypt = function(text,password) { this.encrypt = function(text,password) {
// set default ks:256 -- see: http://bitwiseshiftleft.github.io/sjcl/doc/convenience.js.html return callSjcl("encrypt",text,password);
return callSjcl("encrypt",text,password,{v:1,iter:10000,ks:256,ts:64,mode:"ccm",adata:"",cipher:"aes"});
}; };
this.decrypt = function(text,password) { this.decrypt = function(text,password) {
return callSjcl("decrypt",text,password); return callSjcl("decrypt",text,password);
@@ -1401,7 +1433,7 @@ $tw.Wiki = function(options) {
checkTiddler = function(tiddler,title) { checkTiddler = function(tiddler,title) {
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType)) { if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType)) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title); var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
if(title === "$:/core" || title === "$:/core-server" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") { if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
pluginTiddlers.push(tiddler); pluginTiddlers.push(tiddler);
registeredTitles.push(tiddler.fields.title); registeredTitles.push(tiddler.fields.title);
@@ -1498,8 +1530,7 @@ Define all modules stored in ordinary tiddlers
*/ */
$tw.Wiki.prototype.defineTiddlerModules = function() { $tw.Wiki.prototype.defineTiddlerModules = function() {
this.each(function(tiddler,title) { this.each(function(tiddler,title) {
// Modules in draft tiddlers are disabled if(tiddler.hasField("module-type")) {
if(tiddler.hasField("module-type") && (!tiddler.hasField("draft.of"))) {
switch(tiddler.fields.type) { switch(tiddler.fields.type) {
case "application/javascript": case "application/javascript":
// We only define modules that haven't already been defined, because in the browser modules in system tiddlers are defined in inline script // We only define modules that haven't already been defined, because in the browser modules in system tiddlers are defined in inline script
@@ -1526,11 +1557,6 @@ $tw.Wiki.prototype.defineShadowModules = function() {
this.eachShadow(function(tiddler,title) { this.eachShadow(function(tiddler,title) {
// Don't define the module if it is overidden by an ordinary tiddler // Don't define the module if it is overidden by an ordinary tiddler
if(!self.tiddlerExists(title) && tiddler.hasField("module-type")) { if(!self.tiddlerExists(title) && tiddler.hasField("module-type")) {
if(tiddler.hasField("draft.of")) {
// Report a fundamental problem
console.warn(`TiddlyWiki: Plugins should not contain tiddlers with a 'draft.of' field: ${tiddler.fields.title}`);
return;
}
// Define the module // Define the module
$tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],tiddler.fields.text); $tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],tiddler.fields.text);
} }
@@ -1879,7 +1905,7 @@ $tw.loadTiddlersFromFile = function(filepath,fields) {
fileSize = fs.statSync(filepath).size, fileSize = fs.statSync(filepath).size,
data; data;
if(fileSize > $tw.config.maxEditFileSize) { if(fileSize > $tw.config.maxEditFileSize) {
data = "File " + filepath + " not loaded because it is too large"; data = "File " + filepath + "not loaded because it is too large";
console.log("Warning: " + data); console.log("Warning: " + data);
ext = ".txt"; ext = ".txt";
} else { } else {
@@ -1950,41 +1976,22 @@ filepath: pathname of the directory containing the specification file
$tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) { $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
var tiddlers = []; var tiddlers = [];
// Read the specification // Read the specification
var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"), function(e) { var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"));
console.log("Warning: tiddlywiki.files in " + filepath + " invalid: " + e.message);
return {};
});
// Helper to process a file // Helper to process a file
var processFile = function(filename,isTiddlerFile,fields,isEditableFile,rootPath) { var processFile = function(filename,isTiddlerFile,fields,isEditableFile,rootPath) {
var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)], var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)],
type = (extInfo || {}).type || fields.type || "text/plain", type = (extInfo || {}).type || fields.type || "text/plain",
typeInfo = $tw.config.contentTypeInfo[type] || {}, typeInfo = $tw.config.contentTypeInfo[type] || {},
pathname = path.resolve(filepath,filename), pathname = path.resolve(filepath,filename),
text = fs.readFileSync(pathname,typeInfo.encoding || "utf8"),
metadata = $tw.loadMetadataForFile(pathname) || {}, metadata = $tw.loadMetadataForFile(pathname) || {},
fileTooLarge = false, fileTiddlers;
text, fileTiddlers;
if("_canonical_uri" in fields) {
text = "";
} else if(fs.statSync(pathname).size > $tw.config.maxEditFileSize) {
var msg = "File " + pathname + " not loaded because it is too large";
console.log("Warning: " + msg);
fileTooLarge = true;
text = isTiddlerFile ? msg : "";
} else {
text = fs.readFileSync(pathname,typeInfo.encoding || "utf8");
}
if(isTiddlerFile) { if(isTiddlerFile) {
fileTiddlers = $tw.wiki.deserializeTiddlers(fileTooLarge ? ".txt" : path.extname(pathname),text,metadata) || []; fileTiddlers = $tw.wiki.deserializeTiddlers(path.extname(pathname),text,metadata) || [];
} else { } else {
fileTiddlers = [$tw.utils.extend({text: text},metadata)]; fileTiddlers = [$tw.utils.extend({text: text},metadata)];
} }
var combinedFields = $tw.utils.extend({},fields,metadata); var combinedFields = $tw.utils.extend({},fields,metadata);
if(fileTooLarge && isTiddlerFile) {
delete combinedFields.type; // type altered
}
$tw.utils.each(fileTiddlers,function(tiddler) { $tw.utils.each(fileTiddlers,function(tiddler) {
$tw.utils.each(combinedFields,function(fieldInfo,name) { $tw.utils.each(combinedFields,function(fieldInfo,name) {
if(typeof fieldInfo === "string" || $tw.utils.isArray(fieldInfo)) { if(typeof fieldInfo === "string" || $tw.utils.isArray(fieldInfo)) {
@@ -2059,7 +2066,6 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
} else if(tidInfo.suffix) { } else if(tidInfo.suffix) {
tidInfo.fields.text = {suffix: tidInfo.suffix}; tidInfo.fields.text = {suffix: tidInfo.suffix};
} }
tidInfo.fields = tidInfo.fields || {};
processFile(tidInfo.file,tidInfo.isTiddlerFile,tidInfo.fields); processFile(tidInfo.file,tidInfo.isTiddlerFile,tidInfo.fields);
}); });
// Process any listed directories // Process any listed directories
@@ -2081,7 +2087,6 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
var thisPath = path.relative(filepath, files[t]), var thisPath = path.relative(filepath, files[t]),
filename = path.basename(thisPath); filename = path.basename(thisPath);
if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) { if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) {
dirSpec.fields = dirSpec.fields || {};
processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile,dirSpec.path); processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile,dirSpec.path);
} }
} }
@@ -2345,7 +2350,6 @@ $tw.loadTiddlersNode = function() {
}); });
// Load the core tiddlers // Load the core tiddlers
$tw.wiki.addTiddler($tw.loadPluginFolder($tw.boot.corePath)); $tw.wiki.addTiddler($tw.loadPluginFolder($tw.boot.corePath));
$tw.wiki.addTiddler($tw.loadPluginFolder($tw.boot.coreServerPath));
// Load any extra plugins // Load any extra plugins
$tw.utils.each($tw.boot.extraPlugins,function(name) { $tw.utils.each($tw.boot.extraPlugins,function(name) {
if(name.charAt(0) === "+") { // Relative path to plugin if(name.charAt(0) === "+") { // Relative path to plugin
@@ -2419,7 +2423,6 @@ $tw.boot.initStartup = function(options) {
// System paths and filenames // System paths and filenames
$tw.boot.bootPath = options.bootPath || path.dirname(module.filename); $tw.boot.bootPath = options.bootPath || path.dirname(module.filename);
$tw.boot.corePath = path.resolve($tw.boot.bootPath,"../core"); $tw.boot.corePath = path.resolve($tw.boot.bootPath,"../core");
$tw.boot.coreServerPath = path.resolve($tw.boot.bootPath,"../core-server");
// If there's no arguments then default to `--help` // If there's no arguments then default to `--help`
if($tw.boot.argv.length === 0) { if($tw.boot.argv.length === 0) {
$tw.boot.argv = ["--help"]; $tw.boot.argv = ["--help"];
@@ -2544,10 +2547,10 @@ $tw.boot.execStartup = function(options){
if($tw.safeMode) { if($tw.safeMode) {
$tw.wiki.processSafeMode(); $tw.wiki.processSafeMode();
} }
// Register typed modules from the tiddlers we've just loaded and any modules within plugins // Register typed modules from the tiddlers we've just loaded
// Tiddlers should appear last so that they may overwrite shadows during module registration
$tw.wiki.defineShadowModules();
$tw.wiki.defineTiddlerModules(); $tw.wiki.defineTiddlerModules();
// And any modules within plugins
$tw.wiki.defineShadowModules();
// Make sure the crypto state tiddler is up to date // Make sure the crypto state tiddler is up to date
if($tw.crypto) { if($tw.crypto) {
$tw.crypto.updateCryptoStateTiddler(); $tw.crypto.updateCryptoStateTiddler();
@@ -2616,13 +2619,11 @@ $tw.boot.executeNextStartupTask = function(callback) {
$tw.boot.log(s.join(" ")); $tw.boot.log(s.join(" "));
// Execute task // Execute task
if(!$tw.utils.hop(task,"synchronous") || task.synchronous) { if(!$tw.utils.hop(task,"synchronous") || task.synchronous) {
const thenable = task.startup(); task.startup();
if(thenable && typeof thenable.then === "function"){ if(task.name) {
thenable.then(asyncTaskCallback); $tw.boot.executedStartupModules[task.name] = true;
return true;
} else {
return asyncTaskCallback();
} }
return $tw.boot.executeNextStartupTask(callback);
} else { } else {
task.startup(asyncTaskCallback); task.startup(asyncTaskCallback);
return true; return true;
@@ -2767,8 +2768,6 @@ return $tw;
}); });
/* eslint-enable @stylistic/indent */
if(typeof(exports) !== "undefined") { if(typeof(exports) !== "undefined") {
exports.TiddlyWiki = _boot; exports.TiddlyWiki = _boot;
} else { } else {

View File

@@ -12,8 +12,6 @@ See Boot.js for further details of the boot process.
\*/ \*/
/* eslint-disable @stylistic/indent */
var _bootprefix = (function($tw) { var _bootprefix = (function($tw) {
"use strict"; "use strict";
@@ -116,8 +114,6 @@ return $tw;
}); });
/* eslint-enable @stylistic/indent */
if(typeof(exports) === "undefined") { if(typeof(exports) === "undefined") {
// Set up $tw global for the browser // Set up $tw global for the browser
window.$tw = _bootprefix(window.$tw); window.$tw = _bootprefix(window.$tw);

View File

@@ -1,11 +0,0 @@
{
"title": "$:/core-server",
"name": "Core Server Components",
"description": "TiddlyWiki5 core server components",
"author": "JeremyRuston",
"core-version": ">=5.0.0",
"platform": "server",
"plugin-priority": "0",
"list": "readme",
"stability": "STABILITY_2_STABLE"
}

View File

@@ -1,7 +0,0 @@
title: $:/core-server/readme
This plugin contains TiddlyWiki's core components that are only needed on the server, comprising:
* Commands
* HTTP server code
* Utility functions for server

View File

@@ -1,73 +0,0 @@
/*\
title: $:/core/modules/server/routes/get-file.js
type: application/javascript
module-type: route
GET /files/:filepath
\*/
"use strict";
exports.methods = ["GET"];
exports.path = /^\/files\/(.+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var path = require("path"),
fs = require("fs"),
suppliedFilename = $tw.utils.decodeURIComponentSafe(state.params[0]),
baseFilename = path.resolve(state.boot.wikiPath,"files"),
filename = path.resolve(baseFilename,suppliedFilename),
extension = path.extname(filename);
// Check that the filename is inside the wiki files folder
if(path.relative(baseFilename,filename).indexOf("..") === 0) {
return state.sendResponse(404,{"Content-Type": "text/plain"},"File '" + suppliedFilename + "' not found");
}
fs.stat(filename, function(err, stats) {
if(err) {
return state.sendResponse(404,{"Content-Type": "text/plain"},"File '" + suppliedFilename + "' not found");
} else {
var type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream"),
responseHeaders = {
"Content-Type": type,
"Accept-Ranges": "bytes"
};
var rangeHeader = request.headers.range,
stream;
if(rangeHeader) {
// Handle range requests
var parts = rangeHeader.replace(/bytes=/, "").split("-"),
start = parseInt(parts[0], 10),
end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1;
// Validate start and end
if(isNaN(start) || isNaN(end) || start < 0 || end < start || end >= stats.size) {
responseHeaders["Content-Range"] = "bytes */" + stats.size;
return response.writeHead(416, responseHeaders).end();
}
var chunksize = (end - start) + 1;
responseHeaders["Content-Range"] = "bytes " + start + "-" + end + "/" + stats.size;
responseHeaders["Content-Length"] = chunksize;
response.writeHead(206, responseHeaders);
stream = fs.createReadStream(filename, {start: start, end: end});
} else {
responseHeaders["Content-Length"] = stats.size;
response.writeHead(200, responseHeaders);
stream = fs.createReadStream(filename);
}
// Common stream error handling
stream.on("error", function(err) {
if(!response.headersSent) {
response.writeHead(500, {"Content-Type": "text/plain"});
response.end("Read error");
} else {
response.destroy();
}
});
stream.pipe(response);
}
});
};

View File

@@ -1,30 +0,0 @@
/*\
title: $:/core-modules/modules/utils/base64.js
type: application/javascript
module-type: utils-node
Base64 UTF-8 utlity functions.
\*/
"use strict";
const{ TextEncoder, TextDecoder } = require("node:util");
exports.btoa = binstr => Buffer.from(binstr, "binary").toString("base64");
exports.atob = b64 => Buffer.from(b64, "base64").toString("binary");
function base64ToBytes(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, m => m.codePointAt(0));
};
function bytesToBase64(bytes) {
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = str => bytesToBase64(new TextEncoder().encode(str));
exports.base64DecodeUtf8 = str => new TextDecoder().decode(base64ToBytes(str));

View File

@@ -1,95 +0,0 @@
/*\
title: $:/core-server/modules/utils/escapecss.js
type: application/javascript
module-type: utils-node
Provides CSS.escape() functionality.
\*/
"use strict";
exports.escapeCSS = (function() {
// see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/* eslint-disable */
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
/* eslint-enable */
})();

View File

@@ -5,4 +5,3 @@ TiddlyWiki incorporates code from these fine OpenSource projects:
* [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]] * [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]]
* [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]] * [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]]
* [[modern-normalize by Sindre Sorhus|https://github.com/sindresorhus/modern-normalize]] * [[modern-normalize by Sindre Sorhus|https://github.com/sindresorhus/modern-normalize]]
* [[diff-match-patch-es by antfu|https://github.com/antfu/diff-match-patch-es]]

View File

@@ -147,7 +147,7 @@ Settings/AutoSave/Disabled/Description: Do not save changes automatically
Settings/AutoSave/Enabled/Description: Save changes automatically Settings/AutoSave/Enabled/Description: Save changes automatically
Settings/AutoSave/Hint: Attempt to automatically save changes during editing when using a supporting saver Settings/AutoSave/Hint: Attempt to automatically save changes during editing when using a supporting saver
Settings/CamelCase/Caption: Camel Case Wiki Links Settings/CamelCase/Caption: Camel Case Wiki Links
Settings/CamelCase/Hint: Requires reload to take effect Settings/CamelCase/Hint: You can globally disable automatic linking of ~CamelCase phrases. Requires reload to take effect
Settings/CamelCase/Description: Enable automatic ~CamelCase linking Settings/CamelCase/Description: Enable automatic ~CamelCase linking
Settings/Caption: Settings Settings/Caption: Settings
Settings/EditorToolbar/Caption: Editor Toolbar Settings/EditorToolbar/Caption: Editor Toolbar

View File

@@ -1,6 +1,5 @@
title: $:/language/ title: $:/language/
Alerts: Alerts
AboveStory/ClassicPlugin/Warning: It looks like you are trying to load a plugin designed for ~TiddlyWiki Classic. Please note that [[these plugins do not work with TiddlyWiki version 5.x.x|https://tiddlywiki.com/#TiddlyWikiClassic]]. ~TiddlyWiki Classic plugins detected: AboveStory/ClassicPlugin/Warning: It looks like you are trying to load a plugin designed for ~TiddlyWiki Classic. Please note that [[these plugins do not work with TiddlyWiki version 5.x.x|https://tiddlywiki.com/#TiddlyWikiClassic]]. ~TiddlyWiki Classic plugins detected:
BinaryWarning/Prompt: This tiddler contains binary data BinaryWarning/Prompt: This tiddler contains binary data
ClassicWarning/Hint: This tiddler is written in TiddlyWiki Classic wiki text format, which is not fully compatible with TiddlyWiki version 5. See https://tiddlywiki.com/static/Upgrading.html for more details. ClassicWarning/Hint: This tiddler is written in TiddlyWiki Classic wiki text format, which is not fully compatible with TiddlyWiki version 5. See https://tiddlywiki.com/static/Upgrading.html for more details.

View File

@@ -99,18 +99,16 @@ Commander.prototype.executeNextCommand = function() {
} }
} }
if(command.info.synchronous) { if(command.info.synchronous) {
// Synchronous command (await thenables) // Synchronous command
c = new command.Command(params,this); c = new command.Command(params,this);
err = c.execute(); err = c.execute();
if(err && typeof err.then === "function") { if(err) {
err.then(e => { e ? this.callback(e) : this.executeNextCommand(); });
} else if(err) {
this.callback(err); this.callback(err);
} else { } else {
this.executeNextCommand(); this.executeNextCommand();
} }
} else { } else {
// Asynchronous command (await thenables) // Asynchronous command
c = new command.Command(params,this,function(err) { c = new command.Command(params,this,function(err) {
if(err) { if(err) {
self.callback(err); self.callback(err);
@@ -119,9 +117,7 @@ Commander.prototype.executeNextCommand = function() {
} }
}); });
err = c.execute(); err = c.execute();
if(err && typeof err.then === "function") { if(err) {
err.then(e => { if(e) this.callback(e); });
} else if(err) {
this.callback(err); this.callback(err);
} }
} }

View File

@@ -76,7 +76,6 @@ WikiFolderMaker.prototype.tiddlersToIgnore = [
"$:/boot/boot.js", "$:/boot/boot.js",
"$:/boot/bootprefix.js", "$:/boot/bootprefix.js",
"$:/core", "$:/core",
"$:/core-server",
"$:/library/sjcl.js", "$:/library/sjcl.js",
"$:/temp/info-plugin" "$:/temp/info-plugin"
]; ];

View File

@@ -34,7 +34,7 @@ function FramedEngine(options) {
var paletteTitle = this.widget.wiki.getTiddlerText("$:/palette"); var paletteTitle = this.widget.wiki.getTiddlerText("$:/palette");
var colorScheme = (this.widget.wiki.getTiddler(paletteTitle) || {fields: {}}).fields["color-scheme"] || "light"; var colorScheme = (this.widget.wiki.getTiddler(paletteTitle) || {fields: {}}).fields["color-scheme"] || "light";
this.iframeDoc.open(); this.iframeDoc.open();
this.iframeDoc.write("<!DOCTYPE html><html><head><meta name='color-scheme' content='" + colorScheme + "'></head><body></body></html>"); this.iframeDoc.write("<meta name='color-scheme' content='" + colorScheme + "'>");
this.iframeDoc.close(); this.iframeDoc.close();
// Style the iframe // Style the iframe
this.iframeNode.className = this.dummyTextArea.className; this.iframeNode.className = this.dummyTextArea.className;
@@ -156,8 +156,8 @@ Fix the height of textarea to fit content
FramedEngine.prototype.fixHeight = function() { FramedEngine.prototype.fixHeight = function() {
// Make sure styles are updated // Make sure styles are updated
this.copyStyles(); this.copyStyles();
// If .editRows is initialised, it takes precedence // Adjust height
if(this.widget.editTag === "textarea" && !this.widget.editRows) { if(this.widget.editTag === "textarea") {
if(this.widget.editAutoHeight) { if(this.widget.editAutoHeight) {
if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) { if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) {
var newHeight = $tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight); var newHeight = $tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight);

View File

@@ -100,8 +100,7 @@ SimpleEngine.prototype.getText = function() {
Fix the height of textarea to fit content Fix the height of textarea to fit content
*/ */
SimpleEngine.prototype.fixHeight = function() { SimpleEngine.prototype.fixHeight = function() {
// If .editRows is initialised, it takes precedence if(this.widget.editTag === "textarea") {
if((this.widget.editTag === "textarea") && !this.widget.editRows) {
if(this.widget.editAutoHeight) { if(this.widget.editAutoHeight) {
if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) { if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) {
$tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight); $tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight);

View File

@@ -68,7 +68,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
// Fix height // Fix height
this.engine.fixHeight(); this.engine.fixHeight();
// Focus if required // Focus if required
if($tw.browser && (this.editFocus === "true" || this.editFocus === "yes") && !$tw.utils.hasClass(this.parentDomNode.ownerDocument.activeElement,"tc-keep-focus")) { if(this.editFocus === "true" || this.editFocus === "yes") {
this.engine.focus(); this.engine.focus();
} }
// Add widget message listeners // Add widget message listeners

View File

@@ -1,41 +0,0 @@
/*\
title: $:/core/modules/filterrunprefixes/let.js
type: application/javascript
module-type: filterrunprefix
Assign a value to a variable
\*/
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter prefix function
*/
exports.let = function(operationSubFunction,options) {
// Return the filter run prefix function
return function(results,source,widget) {
// Save the result list
var resultList = results.toArray();
// Clear the results
results.clear();
// Evaluate the subfunction to get the variable name
var subFunctionResults = operationSubFunction(source,widget);
if(subFunctionResults.length === 0) {
return;
}
var name = subFunctionResults[0];
if(typeof name !== "string" || name.length === 0) {
return;
}
// Assign the result of the subfunction to the variable
var variables = {};
variables[name] = resultList;
// Return the variables
return {
variables: variables
};
};
};

View File

@@ -35,7 +35,7 @@ function parseFilterOperation(operators,filterString,p) {
operator.prefix = filterString.charAt(p++); operator.prefix = filterString.charAt(p++);
} }
// Get the operator name // Get the operator name
nextBracketPos = filterString.substring(p).search(/[\[\{<\/\(]/); nextBracketPos = filterString.substring(p).search(/[\[\{<\/]/);
if(nextBracketPos === -1) { if(nextBracketPos === -1) {
throw "Missing [ in filter expression"; throw "Missing [ in filter expression";
} }
@@ -79,17 +79,13 @@ function parseFilterOperation(operators,filterString,p) {
operand.variable = true; operand.variable = true;
nextBracketPos = filterString.indexOf(">",p); nextBracketPos = filterString.indexOf(">",p);
break; break;
case "(": // Round brackets
operand.multiValuedVariable = true;
nextBracketPos = filterString.indexOf(")",p);
break;
case "/": // regexp brackets case "/": // regexp brackets
var rex = /^((?:[^\\\/]|\\.)*)\/(?:\(([mygi]+)\))?/g, var rex = /^((?:[^\\\/]|\\.)*)\/(?:\(([mygi]+)\))?/g,
rexMatch = rex.exec(filterString.substring(p)); rexMatch = rex.exec(filterString.substring(p));
if(rexMatch) { if(rexMatch) {
operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); operator.regexp = new RegExp(rexMatch[1], rexMatch[2]);
// DEPRECATION WARNING // DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp); console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
nextBracketPos = p + rex.lastIndex - 1; nextBracketPos = p + rex.lastIndex - 1;
} }
else { else {
@@ -116,7 +112,7 @@ function parseFilterOperation(operators,filterString,p) {
// Check for multiple operands // Check for multiple operands
while(filterString.charAt(p) === ",") { while(filterString.charAt(p) === ",") {
p++; p++;
if(/^[\[\{<\/\(]/.test(filterString.substring(p))) { if(/^[\[\{<\/]/.test(filterString.substring(p))) {
nextBracketPos = p; nextBracketPos = p;
p++; p++;
parseOperand(filterString.charAt(nextBracketPos)); parseOperand(filterString.charAt(nextBracketPos));
@@ -145,15 +141,7 @@ exports.parseFilter = function(filterString) {
p = 0, // Current position in the filter string p = 0, // Current position in the filter string
match; match;
var whitespaceRegExp = /(\s+)/mg, var whitespaceRegExp = /(\s+)/mg,
// Groups: operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
// 1 - entire filter run prefix
// 2 - filter run prefix itself
// 3 - filter run prefix suffixes
// 4 - opening square bracket following filter run prefix
// 5 - double quoted string following filter run prefix
// 6 - single quoted string following filter run prefix
// 7 - anything except for whitespace and square brackets
operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
while(p < filterString.length) { while(p < filterString.length) {
// Skip any whitespace // Skip any whitespace
whitespaceRegExp.lastIndex = p; whitespaceRegExp.lastIndex = p;
@@ -164,45 +152,38 @@ exports.parseFilter = function(filterString) {
// Match the start of the operation // Match the start of the operation
if(p < filterString.length) { if(p < filterString.length) {
operandRegExp.lastIndex = p; operandRegExp.lastIndex = p;
match = operandRegExp.exec(filterString);
if(!match || match.index !== p) {
throw $tw.language.getString("Error/FilterSyntax");
}
var operation = { var operation = {
prefix: "", prefix: "",
operators: [] operators: []
}; };
match = operandRegExp.exec(filterString); if(match[1]) {
if(match && match.index === p) { operation.prefix = match[1];
// If there is a filter run prefix p = p + operation.prefix.length;
if(match[1]) { if(match[2]) {
operation.prefix = match[1]; operation.namedPrefix = match[2];
p = p + operation.prefix.length; }
// Name for named prefixes if(match[3]) {
if(match[2]) { operation.suffixes = [];
operation.namedPrefix = match[2]; $tw.utils.each(match[3].split(":"),function(subsuffix) {
} operation.suffixes.push([]);
// Suffixes for filter run prefix $tw.utils.each(subsuffix.split(","),function(entry) {
if(match[3]) { entry = $tw.utils.trim(entry);
operation.suffixes = []; if(entry) {
$tw.utils.each(match[3].split(":"),function(subsuffix) { operation.suffixes[operation.suffixes.length -1].push(entry);
operation.suffixes.push([]); }
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
if(entry) {
operation.suffixes[operation.suffixes.length -1].push(entry);
}
});
}); });
} });
} }
// Opening square bracket
if(match[4]) {
p = parseFilterOperation(operation.operators,filterString,p);
} else {
p = match.index + match[0].length;
}
} else {
// No filter run prefix
p = parseFilterOperation(operation.operators,filterString,p);
} }
// Quoted strings and unquoted title if(match[4]) { // Opening square bracket
p = parseFilterOperation(operation.operators,filterString,p);
} else {
p = match.index + match[0].length;
}
if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title
operation.operators.push( operation.operators.push(
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]} {operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
@@ -232,12 +213,7 @@ exports.getFilterRunPrefixes = function() {
exports.filterTiddlers = function(filterString,widget,source) { exports.filterTiddlers = function(filterString,widget,source) {
var fn = this.compileFilter(filterString); var fn = this.compileFilter(filterString);
try { return fn.call(this,source,widget);
const fnResult = fn.call(this,source,widget);
return fnResult;
} catch(e) {
return [`${$tw.language.getString("Error/Filter")}: ${e}`];
}
}; };
/* /*
@@ -275,8 +251,6 @@ exports.compileFilter = function(filterString) {
results = []; results = [];
$tw.utils.each(operation.operators,function(operator) { $tw.utils.each(operation.operators,function(operator) {
var operands = [], var operands = [],
multiValueOperands = [],
isMultiValueOperand = [],
operatorFunction; operatorFunction;
if(!operator.operator) { if(!operator.operator) {
// Use the "title" operator if no operator is specified // Use the "title" operator if no operator is specified
@@ -292,46 +266,28 @@ exports.compileFilter = function(filterString) {
if(operand.indirect) { if(operand.indirect) {
var currTiddlerTitle = widget && widget.getVariable("currentTiddler"); var currTiddlerTitle = widget && widget.getVariable("currentTiddler");
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle); operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
operand.multiValue = [operand.value];
} else if(operand.variable) { } else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text); var varTree = $tw.utils.parseFilterVariable(operand.text);
operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || ""; operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || "";
operand.multiValue = [operand.value];
} else if(operand.multiValuedVariable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
var resultList = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source});
if((resultList.length > 0 && resultList[0] !== undefined) || resultList.length === 0) {
operand.multiValue = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source}) || [];
operand.value = operand.multiValue[0] || "";
} else {
operand.value = "";
operand.multiValue = [];
}
operand.isMultiValueOperand = true;
} else { } else {
operand.value = operand.text; operand.value = operand.text;
operand.multiValue = [operand.value];
} }
operands.push(operand.value); operands.push(operand.value);
multiValueOperands.push(operand.multiValue);
isMultiValueOperand.push(!!operand.isMultiValueOperand);
}); });
// Invoke the appropriate filteroperator module // Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{ results = operatorFunction(accumulator,{
operator: operator.operator, operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined, operand: operands.length > 0 ? operands[0] : undefined,
operands: operands, operands: operands,
multiValueOperands: multiValueOperands, prefix: operator.prefix,
isMultiValueOperand: isMultiValueOperand, suffix: operator.suffix,
prefix: operator.prefix, suffixes: operator.suffixes,
suffix: operator.suffix, regexp: operator.regexp
suffixes: operator.suffixes, },{
regexp: operator.regexp wiki: self,
},{ widget: widget
wiki: self, });
widget: widget
});
if($tw.utils.isArray(results)) { if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results); accumulator = self.makeTiddlerIterator(results);
} else { } else {
@@ -363,8 +319,6 @@ exports.compileFilter = function(filterString) {
return filterRunPrefixes["and"](operationSubFunction, options); return filterRunPrefixes["and"](operationSubFunction, options);
case "~": // This operation is unioned into the result only if the main result so far is empty case "~": // This operation is unioned into the result only if the main result so far is empty
return filterRunPrefixes["else"](operationSubFunction, options); return filterRunPrefixes["else"](operationSubFunction, options);
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
return filterRunPrefixes["let"](operationSubFunction, options);
default: default:
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) { if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options); return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
@@ -391,13 +345,7 @@ exports.compileFilter = function(filterString) {
self.filterRecursionCount = (self.filterRecursionCount || 0) + 1; self.filterRecursionCount = (self.filterRecursionCount || 0) + 1;
if(self.filterRecursionCount < MAX_FILTER_DEPTH) { if(self.filterRecursionCount < MAX_FILTER_DEPTH) {
$tw.utils.each(operationFunctions,function(operationFunction) { $tw.utils.each(operationFunctions,function(operationFunction) {
var operationResult = operationFunction(results,source,widget); operationFunction(results,source,widget);
if(operationResult) {
if(operationResult.variables) {
// If the filter run prefix has returned variables, create a new fake widget with those variables
widget = widget.makeFakeWidgetWithVariables(operationResult.variables);
}
}
}); });
} else { } else {
results.push("/**-- Excessive filter recursion --**/"); results.push("/**-- Excessive filter recursion --**/");

View File

@@ -16,8 +16,12 @@ exports.json = function(source,operand,options) {
spaces = /^\d+$/.test(operand) ? parseInt(operand,10) : operand; spaces = /^\d+$/.test(operand) ? parseInt(operand,10) : operand;
} }
source(function(tiddler,title) { source(function(tiddler,title) {
var data = $tw.utils.parseJSONSafe(title,function(){return undefined;}); var data = $tw.utils.parseJSONSafe(title);
try {
data = JSON.parse(title);
} catch(e) {
data = undefined;
}
if(data !== undefined) { if(data !== undefined) {
results.push(JSON.stringify(data,null,spaces)); results.push(JSON.stringify(data,null,spaces));
} }

View File

@@ -16,8 +16,8 @@ exports.function = function(source,operator,options) {
var functionName = operator.operands[0], var functionName = operator.operands[0],
params = [], params = [],
results; results;
$tw.utils.each(operator.multiValueOperands.slice(1),function(paramList) { $tw.utils.each(operator.operands.slice(1),function(param) {
params.push({value: paramList[0] || "",multiValue: paramList}); params.push({value: param});
}); });
// console.log(`Calling ${functionName} with params ${JSON.stringify(params)}`); // console.log(`Calling ${functionName} with params ${JSON.stringify(params)}`);
var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName,{params: params, source: source}); var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName,{params: params, source: source});

View File

@@ -113,22 +113,6 @@ exports["jsonset"] = function(source,operator,options) {
return results; return results;
}; };
exports["jsondelete"] = function(source,operator,options) {
var indexes = operator.operands,
results = [];
source(function(tiddler,title) {
var data = $tw.utils.parseJSONSafe(title,title);
// If parsing failed (data equals original title and is a string), return unchanged
if(data === title && typeof data === "string") {
results.push(title);
} else if(data) {
data = deleteDataItem(data,indexes);
results.push(JSON.stringify(data));
}
});
return results;
};
/* /*
Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid
*/ */
@@ -160,7 +144,7 @@ function convertDataItemValueToStrings(item) {
return ["null"] return ["null"]
} else if(typeof item === "object") { } else if(typeof item === "object") {
var results = [],i,t; var results = [],i,t;
if(Array.isArray(item)) { if($tw.utils.isArray(item)) {
// Return all the items in arrays recursively // Return all the items in arrays recursively
for(i=0; i<item.length; i++) { for(i=0; i<item.length; i++) {
t = convertDataItemValueToStrings(item[i]) t = convertDataItemValueToStrings(item[i])
@@ -194,7 +178,7 @@ function convertDataItemKeysToStrings(item) {
return []; return [];
} }
var results = []; var results = [];
if(Array.isArray(item)) { if($tw.utils.isArray(item)) {
for(var i=0; i<item.length; i++) { for(var i=0; i<item.length; i++) {
results.push(i.toString()); results.push(i.toString());
} }
@@ -217,7 +201,7 @@ function getDataItemType(data,indexes) {
return item; return item;
} else if(item === null) { } else if(item === null) {
return "null"; return "null";
} else if(Array.isArray(item)) { } else if($tw.utils.isArray(item)) {
return "array"; return "array";
} else if(typeof item === "object") { } else if(typeof item === "object") {
return "object"; return "object";
@@ -229,7 +213,7 @@ function getDataItemType(data,indexes) {
function getItemAtIndex(item,index) { function getItemAtIndex(item,index) {
if($tw.utils.hop(item,index)) { if($tw.utils.hop(item,index)) {
return item[index]; return item[index];
} else if(Array.isArray(item)) { } else if($tw.utils.isArray(item)) {
index = $tw.utils.parseInt(index); index = $tw.utils.parseInt(index);
if(index < 0) { index = index + item.length }; if(index < 0) { index = index + item.length };
return item[index]; // Will be undefined if index was out-of-bounds return item[index]; // Will be undefined if index was out-of-bounds
@@ -239,16 +223,15 @@ function getItemAtIndex(item,index) {
} }
/* /*
Traverse the index chain and return the item at the specified depth. Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid
Returns the item at the end of the traversal, or undefined if traversal fails.
*/ */
function traverseIndexChain(data,indexes,stopBeforeLast) { function getDataItem(data,indexes) {
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) { if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
return data; return data;
} }
// Get the item
var item = data; var item = data;
var stopIndex = stopBeforeLast ? indexes.length - 1 : indexes.length; for(var i=0; i<indexes.length; i++) {
for(var i = 0; i < stopIndex; i++) {
if(item !== undefined) { if(item !== undefined) {
if(item !== null && ["number","string","boolean"].indexOf(typeof item) === -1) { if(item !== null && ["number","string","boolean"].indexOf(typeof item) === -1) {
item = getItemAtIndex(item,indexes[i]); item = getItemAtIndex(item,indexes[i]);
@@ -260,13 +243,6 @@ function traverseIndexChain(data,indexes,stopBeforeLast) {
return item; return item;
} }
/*
Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
function getDataItem(data,indexes) {
return traverseIndexChain(data,indexes,false);
}
/* /*
Given a JSON data structure, an array of index strings and a value, return the data structure with the value added at the end of the index chain. If any of the index strings are invalid then the JSON data structure is returned unmodified. If the root item is targetted then a different data object will be returned Given a JSON data structure, an array of index strings and a value, return the data structure with the value added at the end of the index chain. If any of the index strings are invalid then the JSON data structure is returned unmodified. If the root item is targetted then a different data object will be returned
*/ */
@@ -279,15 +255,18 @@ function setDataItem(data,indexes,value) {
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) { if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
return value; return value;
} }
// Traverse the JSON data structure using the index chain up to the parent // Traverse the JSON data structure using the index chain
var current = traverseIndexChain(data,indexes,true); var current = data;
if(current === undefined) { for(var i = 0; i < indexes.length - 1; i++) {
// Return the original JSON data structure if any of the index strings are invalid current = getItemAtIndex(current,indexes[i]);
return data; if(current === undefined) {
// Return the original JSON data structure if any of the index strings are invalid
return data;
}
} }
// Add the value to the end of the index chain // Add the value to the end of the index chain
var lastIndex = indexes[indexes.length - 1]; var lastIndex = indexes[indexes.length - 1];
if(Array.isArray(current)) { if($tw.utils.isArray(current)) {
lastIndex = $tw.utils.parseInt(lastIndex); lastIndex = $tw.utils.parseInt(lastIndex);
if(lastIndex < 0) { lastIndex = lastIndex + current.length }; if(lastIndex < 0) { lastIndex = lastIndex + current.length };
} }
@@ -297,32 +276,3 @@ function setDataItem(data,indexes,value) {
} }
return data; return data;
} }
/*
Given a JSON data structure and an array of index strings, return the data structure with the item at the end of the index chain deleted. If any of the index strings are invalid then the JSON data structure is returned unmodified. If the root item is targetted then the JSON data structure is returned unmodified.
*/
function deleteDataItem(data,indexes) {
// Check for the root item - don't delete the root
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
return data;
}
// Traverse the JSON data structure using the index chain up to the parent
var current = traverseIndexChain(data,indexes,true);
if(current === undefined || current === null) {
// Return the original JSON data structure if any of the index strings are invalid
return data;
}
// Delete the item at the end of the index chain
var lastIndex = indexes[indexes.length - 1];
if(Array.isArray(current) && current !== null) {
lastIndex = $tw.utils.parseInt(lastIndex);
if(lastIndex < 0) { lastIndex = lastIndex + current.length };
// Check if index is valid before splicing
if(lastIndex >= 0 && lastIndex < current.length) {
current.splice(lastIndex,1);
}
} else if(typeof current === "object" && current !== null) {
delete current[lastIndex];
}
return data;
}

View File

@@ -217,10 +217,6 @@ function makeNumericReducingOperator(fnCalc,initialValue,fnFinal) {
source(function(tiddler,title) { source(function(tiddler,title) {
result.push($tw.utils.parseNumber(title)); result.push($tw.utils.parseNumber(title));
}); });
// We return an empty array if there are no input titles
if(result.length === 0) {
return [];
}
var value = result.reduce(function(accumulator,currentValue) { var value = result.reduce(function(accumulator,currentValue) {
return fnCalc(accumulator,currentValue); return fnCalc(accumulator,currentValue);
},initialValue); },initialValue);

View File

@@ -14,31 +14,31 @@ Export our filter function
*/ */
exports.sort = function(source,operator,options) { exports.sort = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,false,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,false);
return results; return results;
}; };
exports.nsort = function(source,operator,options) { exports.nsort = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,true,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,true);
return results; return results;
}; };
exports.sortan = function(source, operator, options) { exports.sortan = function(source, operator, options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results, operator.operands[0] || "title", operator.prefix === "!",false,false,true,operator.operands[1]); options.wiki.sortTiddlers(results, operator.operand || "title", operator.prefix === "!",false,false,true);
return results; return results;
}; };
exports.sortcs = function(source,operator,options) { exports.sortcs = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,false,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,false);
return results; return results;
}; };
exports.nsortcs = function(source,operator,options) { exports.nsortcs = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,true,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,true);
return results; return results;
}; };

View File

@@ -37,14 +37,14 @@ exports.trim = function(source,operator,options) {
operand = (operator.operand || ""), operand = (operator.operand || ""),
fnCalc; fnCalc;
if(suffix === "prefix") { if(suffix === "prefix") {
fnCalc = function(a,b) {return [$tw.utils.trimPrefix(a,b)];}; fnCalc = function(a,b) {return [$tw.utils.trimPrefix(a,b)];}
} else if(suffix === "suffix") { } else if(suffix === "suffix") {
fnCalc = function(a,b) {return [$tw.utils.trimSuffix(a,b)];}; fnCalc = function(a,b) {return [$tw.utils.trimSuffix(a,b)];}
} else { } else {
if(operand === "") { if(operand === "") {
fnCalc = function(a) {return [$tw.utils.trim(a)];}; fnCalc = function(a) {return [$tw.utils.trim(a)];}
} else { } else {
fnCalc = function(a,b) {return [$tw.utils.trimSuffix($tw.utils.trimPrefix(a,b),b)];}; fnCalc = function(a,b) {return [$tw.utils.trimSuffix($tw.utils.trimPrefix(a,b),b)];}
} }
} }
source(function(tiddler,title) { source(function(tiddler,title) {
@@ -71,53 +71,107 @@ exports.join = makeStringReducingOperator(
},null },null
); );
const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); var dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
exports.levenshtein = makeStringBinaryOperator( exports.levenshtein = makeStringBinaryOperator(
function(a,b) { function(a,b) {
const diffs = dmp.diffMain(a,b); var dmpObject = new dmp.diff_match_patch(),
return [dmp.diffLevenshtein(diffs).toString()]; diffs = dmpObject.diff_main(a,b);
return [dmpObject.diff_levenshtein(diffs) + ""];
} }
); );
// this function is adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs // these two functions are adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
function diffLineWordMode(text1,text2,mode) { function diffLineWordMode(text1,text2,mode) {
var a = $tw.utils.diffPartsToChars(text1,text2,mode); var dmpObject = new dmp.diff_match_patch();
var a = diffPartsToChars(text1,text2,mode);
var lineText1 = a.chars1; var lineText1 = a.chars1;
var lineText2 = a.chars2; var lineText2 = a.chars2;
var lineArray = a.lineArray; var lineArray = a.lineArray;
var diffs = dmp.diffMain(lineText1,lineText2,false); var diffs = dmpObject.diff_main(lineText1,lineText2,false);
dmp.diffCharsToLines(diffs,lineArray); dmpObject.diff_charsToLines_(diffs,lineArray);
return diffs; return diffs;
} }
function diffPartsToChars(text1,text2,mode) {
var lineArray = [];
var lineHash = {};
lineArray[0] = '';
function diff_linesToPartsMunge_(text,mode) {
var chars = '';
var lineStart = 0;
var lineEnd = -1;
var lineArrayLength = lineArray.length,
regexpResult;
var searchRegexp = /\W+/g;
while(lineEnd < text.length - 1) {
if(mode === "words") {
regexpResult = searchRegexp.exec(text);
lineEnd = searchRegexp.lastIndex;
if(regexpResult === null) {
lineEnd = text.length;
}
lineEnd = --lineEnd;
} else {
lineEnd = text.indexOf('\n', lineStart);
if(lineEnd == -1) {
lineEnd = text.length - 1;
}
}
var line = text.substring(lineStart, lineEnd + 1);
if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) {
chars += String.fromCharCode(lineHash[line]);
} else {
if(lineArrayLength == maxLines) {
line = text.substring(lineStart);
lineEnd = text.length;
}
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
lineStart = lineEnd + 1;
}
return chars;
}
var maxLines = 40000;
var chars1 = diff_linesToPartsMunge_(text1,mode);
maxLines = 65535;
var chars2 = diff_linesToPartsMunge_(text2,mode);
return {chars1: chars1, chars2: chars2, lineArray: lineArray};
};
exports.makepatches = function(source,operator,options) { exports.makepatches = function(source,operator,options) {
var suffix = operator.suffix || "", var dmpObject = new dmp.diff_match_patch(),
suffix = operator.suffix || "",
result = []; result = [];
source(function(tiddler,title) { source(function(tiddler,title) {
let diffs, patches; var diffs, patches;
if(suffix === "lines" || suffix === "words") { if(suffix === "lines" || suffix === "words") {
diffs = diffLineWordMode(title,operator.operand,suffix); diffs = diffLineWordMode(title,operator.operand,suffix);
patches = dmp.patchMake(title,diffs); patches = dmpObject.patch_make(title,diffs);
} else { } else {
patches = dmp.patchMake(title,operator.operand); patches = dmpObject.patch_make(title,operator.operand);
} }
Array.prototype.push.apply(result,[dmp.patchToText(patches)]); Array.prototype.push.apply(result,[dmpObject.patch_toText(patches)]);
}); });
return result; return result;
}; };
exports.applypatches = makeStringBinaryOperator( exports.applypatches = makeStringBinaryOperator(
function(a,b) { function(a,b) {
let patches; var dmpObject = new dmp.diff_match_patch(),
patches;
try { try {
patches = dmp.patchFromText(b); patches = dmpObject.patch_fromText(b);
} catch(e) { } catch(e) {
} }
if(patches) { if(patches) {
return [dmp.patchApply(patches,a)[0]]; return [dmpObject.patch_apply(patches,a)[0]];
} else { } else {
return [a]; return [a];
} }
@@ -225,7 +279,7 @@ exports.pad = function(source,operator,options) {
} }
}); });
return results; return results;
}; }
exports.charcode = function(source,operator,options) { exports.charcode = function(source,operator,options) {
var chars = []; var chars = [];

View File

@@ -16,13 +16,12 @@ exports.title = function(source,operator,options) {
var results = []; var results = [];
if(operator.prefix === "!") { if(operator.prefix === "!") {
source(function(tiddler,title) { source(function(tiddler,title) {
var titleList = operator.multiValueOperands[0] || []; if(tiddler && tiddler.fields.title !== operator.operand) {
if(tiddler && titleList.indexOf(tiddler.fields.title) === -1) {
results.push(title); results.push(title);
} }
}); });
} else { } else {
Array.prototype.push.apply(results,operator.multiValueOperands[0]); results.push(operator.operand);
} }
return results; return results;
}; };

View File

@@ -20,8 +20,8 @@ exports["[unknown]"] = function(source,operator,options) {
// Check for a user defined filter operator // Check for a user defined filter operator
if(operator.operator.indexOf(".") !== -1) { if(operator.operator.indexOf(".") !== -1) {
var params = []; var params = [];
$tw.utils.each(operator.multiValueOperands,function(paramList) { $tw.utils.each(operator.operands,function(param) {
params.push({value: paramList[0] || "",multiValue: paramList}); params.push({value: param});
}); });
var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator,{params: params, source: source}); var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator,{params: params, source: source});
if(variableInfo && variableInfo.srcVariable) { if(variableInfo && variableInfo.srcVariable) {

View File

@@ -1,86 +0,0 @@
/*\
title: $:/core/modules/info/windowdimensions.js
type: application/javascript
module-type: info
\*/
exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
if(!$tw.browser) {
return [];
}
class WindowDimensionsTracker {
constructor(updateCallback) {
this.updateCallback = updateCallback;
this.resizeHandlers = new Map();
this.dimensionsInfo = [
["outer/width", win => win.outerWidth],
["outer/height", win => win.outerHeight],
["inner/width", win => win.innerWidth],
["inner/height", win => win.innerHeight],
["client/width", win => win.document.documentElement.clientWidth],
["client/height", win => win.document.documentElement.clientHeight]
];
}
buildTiddlers(win,windowId) {
const prefix = `$:/info/browser/window/${windowId}/`;
return this.dimensionsInfo.map(([suffix, getter]) => ({
title: prefix + suffix,
text: String(getter(win))
}));
}
clearTiddlers(windowId) {
const prefix = `$:/info/browser/window/${windowId}/`,
deletions = this.dimensionsInfo.map(([suffix]) => prefix + suffix);
this.updateCallback([], deletions);
}
getUpdateHandler(win,windowId) {
let scheduled = false;
return () => {
if(!scheduled) {
scheduled = true;
requestAnimationFrame(() => {
this.updateCallback(this.buildTiddlers(win,windowId), []);
scheduled = false;
});
}
};
}
trackWindow(win,windowId) {
const handler = this.getUpdateHandler(win, windowId);
handler(); // initial update
win.addEventListener("resize",handler,{passive:true});
this.resizeHandlers.set(windowId,{win, handler});
}
untrackWindow(windowId) {
const entry = this.resizeHandlers.get(windowId);
if(entry) {
entry.win.removeEventListener("resize", entry.handler);
this.resizeHandlers.delete(windowId);
}
this.clearTiddlers(windowId);
}
}
const tracker = new WindowDimensionsTracker(updateInfoTiddlersCallback);
// Track main window
tracker.trackWindow(window,"system/main");
// Hook into event bus for user windows
if($tw.eventBus) {
$tw.eventBus.on("window:opened", ({window: win, windowID}) => {
tracker.trackWindow(win, "user/" + windowID);
});
$tw.eventBus.on("window:closed", ({windowID}) => {
tracker.untrackWindow("user/" + windowID);
});
}
return [];
};

View File

@@ -7,34 +7,23 @@ The audio parser parses an audio tiddler into an embeddable HTML element
\*/ \*/
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict"; "use strict";
var AudioParser = function(type,text,options) { var AudioParser = function(type,text,options) {
var element = { var element = {
type: "element", type: "element",
tag: "$audio", // Using $audio to enable widget interception tag: "audio",
attributes: { attributes: {
controls: {type: "string", value: "controls"}, controls: {type: "string", value: "controls"},
style: {type: "string", value: "width: 100%; object-fit: contain"} style: {type: "string", value: "width: 100%; object-fit: contain"}
} }
}; },
src;
// Pass through source information
if(options._canonical_uri) { if(options._canonical_uri) {
element.attributes.src = {type: "string", value: options._canonical_uri}; element.attributes.src = {type: "string", value: options._canonical_uri};
element.attributes.type = {type: "string", value: type};
} else if(text) { } else if(text) {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text}; element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
element.attributes.type = {type: "string", value: type};
} }
// Pass through tiddler title if available
if(options.title) {
element.attributes.tiddler = {type: "string", value: options.title};
}
this.tree = [element]; this.tree = [element];
this.source = text; this.source = text;
this.type = type; this.type = type;
@@ -44,4 +33,3 @@ exports["audio/ogg"] = AudioParser;
exports["audio/mpeg"] = AudioParser; exports["audio/mpeg"] = AudioParser;
exports["audio/mp3"] = AudioParser; exports["audio/mp3"] = AudioParser;
exports["audio/mp4"] = AudioParser; exports["audio/mp4"] = AudioParser;

View File

@@ -11,13 +11,10 @@ The CSV text parser processes CSV files into a table wrapped in a scrollable wid
var CsvParser = function(type,text,options) { var CsvParser = function(type,text,options) {
// Special handler for tab-delimited files // Special handler for tab-delimited files
if( if (type === 'text/tab-delimited-values' && !options.separator) {
!options.separator &&
(type === "text/tab-delimited-values" || type === "text/tab-separated-values")
) {
options.separator = "\t"; options.separator = "\t";
} }
// Table framework // Table framework
this.tree = [{ this.tree = [{
"type": "scrollable", "children": [{ "type": "scrollable", "children": [{
@@ -35,7 +32,7 @@ var CsvParser = function(type,text,options) {
$tw.utils.each(lines, function(columns) { $tw.utils.each(lines, function(columns) {
maxColumns = Math.max(columns.length, maxColumns); maxColumns = Math.max(columns.length, maxColumns);
}); });
for(var line=0; line<lines.length; line++) { for(var line=0; line<lines.length; line++) {
var columns = lines[line]; var columns = lines[line];
var row = { var row = {
@@ -58,4 +55,3 @@ var CsvParser = function(type,text,options) {
exports["text/csv"] = CsvParser; exports["text/csv"] = CsvParser;
exports["text/tab-delimited-values"] = CsvParser; exports["text/tab-delimited-values"] = CsvParser;
exports["text/tab-separated-values"] = CsvParser;

View File

@@ -11,16 +11,17 @@ The image parser parses an image into an embeddable HTML element
var ImageParser = function(type,text,options) { var ImageParser = function(type,text,options) {
var element = { var element = {
type: "image", type: "element",
attributes: {} tag: "img",
}; attributes: {}
};
if(options._canonical_uri) { if(options._canonical_uri) {
element.attributes.source = {type: "string", value: options._canonical_uri}; element.attributes.src = {type: "string", value: options._canonical_uri};
} else if(text) { } else if(text) {
if(type === "image/svg+xml" || type === ".svg") { if(type === "image/svg+xml" || type === ".svg") {
element.attributes.source = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)}; element.attributes.src = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)};
} else { } else {
element.attributes.source = {type: "string", value: "data:" + type + ";base64," + text}; element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
} }
} }
this.tree = [element]; this.tree = [element];

View File

@@ -82,7 +82,6 @@ exports.parseTokenString = function(source,pos,token) {
/* /*
Look for a token matching a regex. Returns null if not found, otherwise returns {type: "regexp", match:, start:, end:,} Look for a token matching a regex. Returns null if not found, otherwise returns {type: "regexp", match:, start:, end:,}
Use the "Y" (sticky) flag to avoid searching the entire rest of the string
*/ */
exports.parseTokenRegExp = function(source,pos,reToken) { exports.parseTokenRegExp = function(source,pos,reToken) {
var node = { var node = {
@@ -173,7 +172,7 @@ exports.parseMacroParameter = function(source,pos) {
start: pos start: pos
}; };
// Define our regexp // Define our regexp
const reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/y; var reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/g;
// Skip whitespace // Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos); pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for the parameter // Look for the parameter
@@ -241,7 +240,7 @@ exports.parseMacroInvocation = function(source,pos) {
params: [] params: []
}; };
// Define our regexps // Define our regexps
const reMacroName = /([^\s>"'=]+)/y; var reMacroName = /([^\s>"'=]+)/g;
// Skip whitespace // Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos); pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double less than sign // Look for a double less than sign
@@ -278,7 +277,7 @@ exports.parseFilterVariable = function(source) {
params: [], params: [],
}, },
pos = 0, pos = 0,
reName = /([^\s"']+)/y; reName = /([^\s"']+)/g;
// If there is no whitespace or it is an empty string then there are no macro parameters // If there is no whitespace or it is an empty string then there are no macro parameters
if(/^\S*$/.test(source)) { if(/^\S*$/.test(source)) {
node.name = source; node.name = source;
@@ -303,11 +302,11 @@ exports.parseAttribute = function(source,pos) {
start: pos start: pos
}; };
// Define our regexps // Define our regexps
const reAttributeName = /([^\/\s>"'`=]+)/y, var reAttributeName = /([^\/\s>"'`=]+)/g,
reUnquotedAttribute = /([^\/\s<>"'`=]+)/y, reUnquotedAttribute = /([^\/\s<>"'`=]+)/g,
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/y, reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g,
reIndirectValue = /\{\{([^\}]+)\}\}/y, reIndirectValue = /\{\{([^\}]+)\}\}/g,
reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/y; reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g;
// Skip whitespace // Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos); pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the attribute name // Get the attribute name

View File

@@ -22,7 +22,7 @@ Note that the syntax for comments is simplified to an opening "<!--" sequence an
"use strict"; "use strict";
exports.name = "commentblock"; exports.name = "commentblock";
exports.types = {block: true, pragma: true}; exports.types = {block:true, pragma:true};
exports.init = function(parser) { exports.init = function(parser) {
this.parser = parser; this.parser = parser;
@@ -43,18 +43,9 @@ exports.findNextMatch = function(startPos) {
return undefined; return undefined;
}; };
exports.parse = function() { exports.parse = function() {
// Move past the match // Move past the match
this.parser.pos = this.endMatchRegExp.lastIndex; this.parser.pos = this.endMatchRegExp.lastIndex;
// Return a node representing the comment that is not rendered // Don't return any elements
var commentStart = this.match.index; return [];
var commentEnd = this.endMatch.index + this.endMatch[0].length;
return [{
type: "void",
children: [],
text: this.parser.source.slice(commentStart, commentEnd),
start: commentStart,
end: commentEnd
}];
}; };

View File

@@ -40,13 +40,6 @@ exports.findNextMatch = function(startPos) {
exports.parse = function() { exports.parse = function() {
// Move past the match // Move past the match
this.parser.pos = this.endMatchRegExp.lastIndex; this.parser.pos = this.endMatchRegExp.lastIndex;
// Return a node representing the inline comment // Don't return any elements
var commentStart = this.match.index; return [];
var commentEnd = this.endMatch.index + this.endMatch[0].length;
return [{
type: "void",
text: this.parser.source.slice(commentStart, commentEnd),
start: commentStart,
end: commentEnd
}];
}; };

View File

@@ -39,7 +39,7 @@ exports.parse = function() {
// Return the classed span // Return the classed span
return [{ return [{
type: "element", type: "element",
tag: "s", tag: "strike",
children: tree children: tree
}]; }];
}; };

View File

@@ -28,11 +28,11 @@ exports.init = function(parser) {
exports.parse = function() { exports.parse = function() {
// Move past the match // Move past the match
var start = this.parser.pos; var start = this.parser.pos;
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;
// Create the link unless it is suppressed // Create the link unless it is suppressed
if(this.match[0].substr(0,1) === "~") { if(this.match[0].substr(0,1) === "~") {
return [{type: "text", text: this.match[0].substr(1), start: start, end: this.parser.pos}]; return [{type: "text", text: this.match[0].substr(1)}];
} else { } else {
return [{ return [{
type: "element", type: "element",

View File

@@ -6,15 +6,15 @@ module-type: wikirule
Wiki pragma rule for function, procedure and widget definitions Wiki pragma rule for function, procedure and widget definitions
``` ```
\function name(param:"defaultvalue", param2:"defaultvalue") \function name(param:defaultvalue,param2:defaultvalue)
definition text definition text
\end \end
\procedure name(param:"defaultvalue", param2:"defaultvalue") \procedure name(param:defaultvalue,param2:defaultvalue)
definition text definition text
\end \end
\widget $mywidget(param:"defaultvalue", param2:"defaultvalue") \widget $mywidget(param:defaultvalue,param2:defaultvalue)
definition text definition text
\end \end
``` ```

View File

@@ -50,8 +50,6 @@ exports.parse = function() {
} }
} }
} while(match && !match[1]); } while(match && !match[1]);
// Mark first and last node, and return the nodes // Return the nodes
if(tree[0]) tree[0].isRuleStart = true;
if(tree[tree.length-1]) tree[tree.length-1].isRuleEnd = true;
return tree; return tree;
}; };

View File

@@ -41,7 +41,7 @@ Parse the most recent match
exports.parse = function() { exports.parse = function() {
// Retrieve the most recent match so that recursive calls don't overwrite it // Retrieve the most recent match so that recursive calls don't overwrite it
var tag = this.nextTag; var tag = this.nextTag;
if(!tag.isSelfClosing) { if (!tag.isSelfClosing) {
tag.openTagStart = tag.start; tag.openTagStart = tag.start;
tag.openTagEnd = tag.end; tag.openTagEnd = tag.end;
} }
@@ -49,7 +49,7 @@ exports.parse = function() {
// Advance the parser position to past the tag // Advance the parser position to past the tag
this.parser.pos = tag.end; this.parser.pos = tag.end;
// Check for an immediately following double linebreak // Check for an immediately following double linebreak
var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y); var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
// Set whether we're in block mode // Set whether we're in block mode
tag.isBlock = this.is.block || hasLineBreak; tag.isBlock = this.is.block || hasLineBreak;
// Parse the body if we need to // Parse the body if we need to
@@ -63,22 +63,22 @@ exports.parse = function() {
} }
tag.end = this.parser.pos; tag.end = this.parser.pos;
tag.closeTagEnd = tag.end; tag.closeTagEnd = tag.end;
if(tag.closeTagEnd === tag.openTagEnd || this.parser.source[tag.closeTagEnd - 1] !== ">") { if (tag.closeTagEnd === tag.openTagEnd || this.parser.source[tag.closeTagEnd - 1] !== '>') {
tag.closeTagStart = tag.end; tag.closeTagStart = tag.end;
} else { } else {
tag.closeTagStart = tag.closeTagEnd - 2; tag.closeTagStart = tag.closeTagEnd - 2;
var closeTagMinPos = tag.children.length > 0 ? tag.children[tag.children.length-1].end : tag.openTagEnd; var closeTagMinPos = tag.children.length > 0 ? tag.children[tag.children.length-1].end : tag.openTagEnd;
if(!Number.isSafeInteger(closeTagMinPos)) closeTagMinPos = tag.openTagEnd; if (!Number.isSafeInteger(closeTagMinPos)) closeTagMinPos = tag.openTagEnd;
while(tag.closeTagStart >= closeTagMinPos) { while (tag.closeTagStart >= closeTagMinPos) {
var char = this.parser.source[tag.closeTagStart]; var char = this.parser.source[tag.closeTagStart];
if(char === ">") { if (char === '>') {
tag.closeTagStart = -1; tag.closeTagStart = -1;
break; break;
} }
if(char === "<") break; if (char === '<') break;
tag.closeTagStart -= 1; tag.closeTagStart -= 1;
} }
if(tag.closeTagStart < closeTagMinPos) { if (tag.closeTagStart < closeTagMinPos) {
tag.closeTagStart = tag.end; tag.closeTagStart = tag.end;
} }
} }
@@ -100,7 +100,7 @@ exports.parseTag = function(source,pos,options) {
orderedAttributes: [] orderedAttributes: []
}; };
// Define our regexps // Define our regexps
const reTagName = /([a-zA-Z0-9\-\$\.]+)/y; var reTagName = /([a-zA-Z0-9\-\$\.]+)/g;
// Skip whitespace // Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos); pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a less than sign // Look for a less than sign
@@ -148,7 +148,7 @@ exports.parseTag = function(source,pos,options) {
pos = token.end; pos = token.end;
// Check for a required line break // Check for a required line break
if(options.requireLineBreak) { if(options.requireLineBreak) {
token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y); token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
if(!token) { if(!token) {
return null; return null;
} }

View File

@@ -113,7 +113,7 @@ exports.parseImage = function(source,pos) {
// Skip whitespace // Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos); pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the source up to the terminating `]]` // Get the source up to the terminating `]]`
token = $tw.utils.parseTokenRegExp(source,pos,/(?:([^|\]]*?)\|)?([^\]]+?)\]\]/y); token = $tw.utils.parseTokenRegExp(source,pos,/(?:([^|\]]*?)\|)?([^\]]+?)\]\]/g);
if(!token) { if(!token) {
return null; return null;
} }

View File

@@ -59,7 +59,6 @@ var listTypes = {
":": {listTag: "dl", itemTag: "dd"}, ":": {listTag: "dl", itemTag: "dd"},
">": {listTag: "blockquote", itemTag: "div"} ">": {listTag: "blockquote", itemTag: "div"}
}; };
exports.listTypes = listTypes;
/* /*
Parse the most recent match Parse the most recent match

View File

@@ -29,7 +29,7 @@ exports.findNextMatch = function(startPos) {
var c = this.parser.source.charAt(nextCall.end); var c = this.parser.source.charAt(nextCall.end);
// Ensure EOL after parsed macro // Ensure EOL after parsed macro
// If we didn't need to support IE, we'd just use /(?:\r?\n|$)/ym // If we didn't need to support IE, we'd just use /(?:\r?\n|$)/ym
if((c === "") || (c === "\n") || ((c === "\r") && this.parser.source.charAt(nextCall.end+1) === "\n")) { if ((c === "") || (c === "\n") || ((c === "\r") && this.parser.source.charAt(nextCall.end+1) === "\n")) {
this.nextCall = nextCall; this.nextCall = nextCall;
return nextStart; return nextStart;
} }

View File

@@ -42,5 +42,3 @@ exports.parse = function() {
this.parser.pos = call.end; this.parser.pos = call.end;
return [call]; return [call];
}; };

View File

@@ -52,10 +52,9 @@ exports.parse = function() {
} }
} }
// Is the remainder of the \define line blank after the parameter close paren? // Is the remainder of the \define line blank after the parameter close paren?
var reEnd,isBlock = true; var reEnd;
if(this.match[3]) { if(this.match[3]) {
// If so, it is a multiline definition and the end of the body is marked with \end // If so, it is a multiline definition and the end of the body is marked with \end
isBlock = false;
reEnd = new RegExp("((?:^|\\r?\\n)[^\\S\\n\\r]*\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[1]) + ")?\\s*?(?:$|\\r?\\n))","mg"); reEnd = new RegExp("((?:^|\\r?\\n)[^\\S\\n\\r]*\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[1]) + ")?\\s*?(?:$|\\r?\\n))","mg");
} else { } else {
// Otherwise, the end of the definition is marked by the end of the line // Otherwise, the end of the definition is marked by the end of the line
@@ -80,8 +79,7 @@ exports.parse = function() {
attributes: {}, attributes: {},
children: [], children: [],
params: params, params: params,
isMacroDefinition: true, isMacroDefinition: true
isBlock: isBlock && !!endMatch
}]; }];
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[1]); $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[1]);
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text); $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);

View File

@@ -31,7 +31,6 @@ Parse the most recent match
*/ */
exports.parse = function() { exports.parse = function() {
// Move past the pragma invocation // Move past the pragma invocation
var start = this.parser.pos;
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;
// Parse whitespace delimited tokens terminated by a line break // Parse whitespace delimited tokens terminated by a line break
var reMatch = /[^\S\n]*(\S+)|(\r?\n)/mg, var reMatch = /[^\S\n]*(\S+)|(\r?\n)/mg,
@@ -59,11 +58,6 @@ exports.parse = function() {
this.parser.parseAsInline = true; this.parser.parseAsInline = true;
} }
} }
return [{ // No parse tree nodes to return
type: "void", return [];
children: [],
parseAsInline: this.parser.parseAsInline,
start: start,
end: this.parser.pos
}];
}; };

View File

@@ -113,5 +113,3 @@ exports.parseLink = function(source,pos) {
node.end = closePos + 2; node.end = closePos + 2;
return node; return node;
}; };

View File

@@ -32,7 +32,7 @@ exports.parse = function() {
var text = this.match[1], var text = this.match[1],
link = this.match[2] || text, link = this.match[2] || text,
textEndPos = this.parser.source.indexOf("|", start); textEndPos = this.parser.source.indexOf("|", start);
if(textEndPos < 0 || textEndPos > this.matchRegExp.lastIndex) { if (textEndPos < 0 || textEndPos > this.matchRegExp.lastIndex) {
textEndPos = this.matchRegExp.lastIndex - 2; textEndPos = this.matchRegExp.lastIndex - 2;
} }
var linkStart = this.match[2] ? (start + this.match[1].length + 1) : start; var linkStart = this.match[2] ? (start + this.match[1].length + 1) : start;

View File

@@ -54,13 +54,6 @@ exports.parse = function() {
if(tokens.length > 0) { if(tokens.length > 0) {
this.parser.amendRules(tokens[0],tokens.slice(1)); this.parser.amendRules(tokens[0],tokens.slice(1));
} }
// No widget to render, return void node. // No parse tree nodes to return
return [{ return [];
type: "void",
attributes: {
action: {type: "string", value: tokens[0]},
rules: {type: "string", value: tokens.slice(1).join(" ")}
},
children: []
}];
}; };

View File

@@ -64,8 +64,5 @@ exports.parse = function() {
$tw.utils.addAttributeToParseTreeNode(tree[t],"style",styles.join("")); $tw.utils.addAttributeToParseTreeNode(tree[t],"style",styles.join(""));
} }
} }
return [{ return tree;
type: "void",
children: tree
}]
}; };

View File

@@ -21,7 +21,7 @@ exports.types = {inline: true};
exports.init = function(parser) { exports.init = function(parser) {
this.parser = parser; this.parser = parser;
// Regexp to match /@@(styles)?\s*(\.class\s+)?/ // Regexp to match
this.matchRegExp = /@@((?:[^\.\r\n\s:]+:[^\r\n;]+;)+)?(\.(?:[^\r\n\s]+)\s+)?/mg; this.matchRegExp = /@@((?:[^\.\r\n\s:]+:[^\r\n;]+;)+)?(\.(?:[^\r\n\s]+)\s+)?/mg;
}; };

View File

@@ -23,27 +23,6 @@ exports.init = function(parser) {
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg; this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg;
}; };
/*
Reject the match if we don't have a template or text reference
*/
exports.findNextMatch = function(startPos) {
this.matchRegExp.lastIndex = startPos;
this.match = this.matchRegExp.exec(this.parser.source);
if(this.match) {
var template = $tw.utils.trim(this.match[2]),
textRef = $tw.utils.trim(this.match[1]);
// Bail if we don't have a template or text reference
if(!template && !textRef) {
return undefined;
} else {
return this.match.index;
}
} else {
return undefined;
}
return this.match ? this.match.index : undefined;
};
exports.parse = function() { exports.parse = function() {
// Move past the match // Move past the match
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;

View File

@@ -23,27 +23,6 @@ exports.init = function(parser) {
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg; this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg;
}; };
/*
Reject the match if we don't have a template or text reference
*/
exports.findNextMatch = function(startPos) {
this.matchRegExp.lastIndex = startPos;
this.match = this.matchRegExp.exec(this.parser.source);
if(this.match) {
var template = $tw.utils.trim(this.match[2]),
textRef = $tw.utils.trim(this.match[1]);
// Bail if we don't have a template or text reference
if(!template && !textRef) {
return undefined;
} else {
return this.match.index;
}
} else {
return undefined;
}
return this.match ? this.match.index : undefined;
};
exports.parse = function() { exports.parse = function() {
// Move past the match // Move past the match
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;

View File

@@ -60,37 +60,22 @@ exports.parse = function() {
var parser = this.parser.wiki.parseText(parseType,text,{defaultType: "text/plain"}); var parser = this.parser.wiki.parseText(parseType,text,{defaultType: "text/plain"});
// If there's no render type, just return the parse tree // If there's no render type, just return the parse tree
if(!renderType) { if(!renderType) {
return [{ return parser.tree;
type: "void",
children: $tw.utils.isArray(parser.tree) ? parser.tree : [parser.tree],
parseType: parseType,
renderType: renderType,
text: text,
start: start,
end: this.parser.pos
}];
} else { } else {
// Otherwise, render to the rendertype and return in a <PRE> tag // Otherwise, render to the rendertype and return in a <PRE> tag
var widgetNode = this.parser.wiki.makeWidget(parser), var widgetNode = this.parser.wiki.makeWidget(parser),
container = $tw.fakeDocument.createElement("div"); container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null); widgetNode.render(container,null);
var renderResult = renderType === "text/html" ? container.innerHTML : container.textContent; text = renderType === "text/html" ? container.innerHTML : container.textContent;
// Use void node to carry important info for typedblock
return [{ return [{
type: "void", type: "element",
tag: "pre",
children: [{ children: [{
type: "element", type: "text",
tag: "pre", text: text,
children: [{ start: start,
type: "text", end: this.parser.pos
text: renderResult, }]
}]
}],
parseType: parseType,
renderType: renderType,
text: text,
start: start,
end: this.parser.pos
}]; }];
} }
}; };

View File

@@ -215,8 +215,8 @@ WikiParser.prototype.parsePragmas = function() {
var subTree = nextMatch.rule.parse(); var subTree = nextMatch.rule.parse();
if(subTree.length > 0) { if(subTree.length > 0) {
// Set the start and end positions of the pragma rule if // Set the start and end positions of the pragma rule if
if(subTree[0].start === undefined) subTree[0].start = start; if (subTree[0].start === undefined) subTree[0].start = start;
if(subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos; 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; }); $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 // Quick hack; we only cope with a single parse tree node being returned, which is true at the moment
currentTreeBranch.push.apply(currentTreeBranch,subTree); currentTreeBranch.push.apply(currentTreeBranch,subTree);
@@ -245,9 +245,9 @@ WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
var start = this.pos; var start = this.pos;
var subTree = nextMatch.rule.parse(); var subTree = nextMatch.rule.parse();
// Set the start and end positions of the first and last blocks if they're not already set // Set the start and end positions of the first and last blocks if they're not already set
if(subTree.length > 0) { if (subTree.length > 0) {
if(subTree[0].start === undefined) subTree[0].start = start; if (subTree[0].start === undefined) subTree[0].start = start;
if(subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos; 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; }); $tw.utils.each(subTree, function (node) { node.rule = nextMatch.rule.name; });
return subTree; return subTree;
@@ -256,7 +256,7 @@ WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
var start = this.pos; var start = this.pos;
var children = this.parseInlineRun(terminatorRegExp); var children = this.parseInlineRun(terminatorRegExp);
var end = this.pos; var end = this.pos;
return [{type: "element", tag: "p", children: children, start: start, end: end, rule: "parseblock" }]; return [{type: "element", tag: "p", children: children, start: start, end: end }];
}; };
/* /*
@@ -350,10 +350,10 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
var start = this.pos; var start = this.pos;
var subTree = nextMatch.rule.parse(); var subTree = nextMatch.rule.parse();
// Set the start and end positions of the first and last child if they're not already set // Set the start and end positions of the first and last child if they're not already set
if(subTree.length > 0) { if (subTree.length > 0) {
// Set the start and end positions of the first and last child if they're not already set // 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[0].start === undefined) subTree[0].start = start;
if(subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos; 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; }); $tw.utils.each(subTree, function (node) { node.rule = nextMatch.rule.name; });
tree.push.apply(tree,subTree); tree.push.apply(tree,subTree);
@@ -410,9 +410,9 @@ WikiParser.prototype.parseInlineRunTerminatedExtended = function(terminatorRegEx
var start = this.pos; var start = this.pos;
var subTree = inlineRuleMatch.rule.parse(); var subTree = inlineRuleMatch.rule.parse();
// Set the start and end positions of the first and last child if they're not already set // Set the start and end positions of the first and last child if they're not already set
if(subTree.length > 0) { if (subTree.length > 0) {
if(subTree[0].start === undefined) subTree[0].start = start; if (subTree[0].start === undefined) subTree[0].start = start;
if(subTree[subTree.length - 1].end === undefined) subTree[subTree.length - 1].end = this.pos; 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; }); $tw.utils.each(subTree, function (node) { node.rule = inlineRuleMatch.rule.name; });
tree.push.apply(tree,subTree); tree.push.apply(tree,subTree);

View File

@@ -35,9 +35,7 @@ DownloadSaver.prototype.save = function(text,method,callback,options) {
} }
// Set up the link // Set up the link
var link = document.createElement("a"); var link = document.createElement("a");
// We prefer Blobs if they're available, unless we're dealing with a tiddler type declaring itself full of base64 encoded content. if(Blob !== undefined) {
// Then we use data urls, because browsers will know to decode the stream and download the actual binary file as intended.
if(Blob !== undefined && !type.includes(";base64")) {
var blob = new Blob([text], {type: type}); var blob = new Blob([text], {type: type});
link.setAttribute("href", URL.createObjectURL(blob)); link.setAttribute("href", URL.createObjectURL(blob));
} else { } else {

View File

@@ -1,66 +0,0 @@
/*\
title: $:/core/modules/savers/postmessage.js
type: application/javascript
module-type: saver
Handles saving changes via window.postMessage() to the window.parent
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Select the appropriate saver module and set it up
*/
var PostMessageSaver = function(wiki) {
this.publisher = new $tw.utils.BrowserMessagingPublisher({type: "SAVE"});
};
PostMessageSaver.prototype.save = function(text,method,callback,options) {
// Fail if the publisher hasn't been fully initialised
if(!this.publisher.canSend()) {
return false;
}
// Send the save request
this.publisher.send({
verb: "SAVE",
body: text
},function(err) {
if(err) {
callback("PostMessageSaver Error: " + err);
} else {
callback(null);
}
});
// Indicate that we handled the save
return true;
};
/*
Information about this saver
*/
PostMessageSaver.prototype.info = {
name: "postmessage",
capabilities: ["save", "autosave"],
priority: 100
};
/*
Static method that returns true if this saver is capable of working
*/
exports.canSave = function(wiki) {
// Provisionally say that we can save
return true;
};
/*
Create an instance of this saver
*/
exports.create = function(wiki) {
return new PostMessageSaver(wiki);
};
})();

View File

@@ -8,14 +8,10 @@ DELETE /recipes/default/tiddlers/:title
\*/ \*/
"use strict"; "use strict";
exports.methods = ["DELETE"]; exports.method = "DELETE";
exports.path = /^\/bags\/default\/tiddlers\/(.+)$/; exports.path = /^\/bags\/default\/tiddlers\/(.+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]); var title = $tw.utils.decodeURIComponentSafe(state.params[0]);
state.wiki.deleteTiddler(title); state.wiki.deleteTiddler(title);

View File

@@ -8,14 +8,10 @@ GET /favicon.ico
\*/ \*/
"use strict"; "use strict";
exports.methods = ["GET"]; exports.method = "GET";
exports.path = /^\/favicon.ico$/; exports.path = /^\/favicon.ico$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var buffer = state.wiki.getTiddlerText("$:/favicon.ico",""); var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
state.sendResponse(200,{"Content-Type": "image/x-icon"},buffer,"base64"); state.sendResponse(200,{"Content-Type": "image/x-icon"},buffer,"base64");

View File

@@ -0,0 +1,42 @@
/*\
title: $:/core/modules/server/routes/get-file.js
type: application/javascript
module-type: route
GET /files/:filepath
\*/
"use strict";
exports.method = "GET";
exports.path = /^\/files\/(.+)$/;
exports.handler = function(request,response,state) {
var path = require("path"),
fs = require("fs"),
util = require("util"),
suppliedFilename = $tw.utils.decodeURIComponentSafe(state.params[0]),
baseFilename = path.resolve(state.boot.wikiPath,"files"),
filename = path.resolve(baseFilename,suppliedFilename),
extension = path.extname(filename);
// Check that the filename is inside the wiki files folder
if(path.relative(baseFilename,filename).indexOf("..") !== 0) {
// Send the file
fs.readFile(filename,function(err,content) {
var status,content,type = "text/plain";
if(err) {
console.log("Error accessing file " + filename + ": " + err.toString());
status = 404;
content = "File '" + suppliedFilename + "' not found";
} else {
status = 200;
content = content;
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
}
state.sendResponse(status,{"Content-Type": type},content);
});
} else {
state.sendResponse(404,{"Content-Type": "text/plain"},"File '" + suppliedFilename + "' not found");
}
};

View File

@@ -8,14 +8,10 @@ GET /
\*/ \*/
"use strict"; "use strict";
exports.methods = ["GET"]; exports.method = "GET";
exports.path = /^\/$/; exports.path = /^\/$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")), var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")),
responseHeaders = { responseHeaders = {

View File

@@ -8,14 +8,10 @@ GET /login-basic -- force a Basic Authentication challenge
\*/ \*/
"use strict"; "use strict";
exports.methods = ["GET"]; exports.method = "GET";
exports.path = /^\/login-basic$/; exports.path = /^\/login-basic$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
if(!state.authenticatedUsername) { if(!state.authenticatedUsername) {
// Challenge if there's no username // Challenge if there's no username

View File

@@ -8,14 +8,10 @@ GET /status
\*/ \*/
"use strict"; "use strict";
exports.methods = ["GET"]; exports.method = "GET";
exports.path = /^\/status$/; exports.path = /^\/status$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var text = JSON.stringify({ var text = JSON.stringify({
username: state.authenticatedUsername || state.server.get("anon-username") || "", username: state.authenticatedUsername || state.server.get("anon-username") || "",

View File

@@ -8,14 +8,10 @@ GET /:title
\*/ \*/
"use strict"; "use strict";
exports.methods = ["GET"]; exports.method = "GET";
exports.path = /^\/([^\/]+)$/; exports.path = /^\/([^\/]+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]), var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
tiddler = state.wiki.getTiddler(title); tiddler = state.wiki.getTiddler(title);

Some files were not shown because too many files have changed in this diff Show More