1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-24 03:44:41 +00:00

Compare commits

..

90 Commits

Author SHA1 Message Date
Jeremy Ruston
7e76db15aa Merge branch 'master' into browser-messaging-saver 2025-10-29 14:50:42 +00:00
Jeremy Ruston
5848d66e96 Merge branch 'tiddlywiki-com' 2025-10-29 14:48:14 +00:00
well-noted
485051951e Allow intercept of audioparser by widget and pass attributes (#9024)
* Initial Commit

* Removes Function wrapper

* Remove Function wrapper from parser

* Convert spaces to tabs in audio.js widget

* Fix indentation

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2025-10-29 12:19:09 +00:00
XLBilly
135e685811 Improve alert accessibility (#9248)
* Improve alert accessibility

* Remove procedure
2025-10-29 12:08:01 +00:00
Simon Huber
99682c5731 [Bugfix] - keep focus on Element with tc-keep-focus class (#9233)
* Update factory.js

* add tc-keep-focus class to type input field

* tryna fix with energy
2025-10-29 12:00:40 +00:00
yaisog
d58eec47c0 Modify output of some math operators for empty inputs (#9337)
* Initial commit

* Adapt test to new behavior
2025-10-29 11:57:39 +00:00
Jeremy Ruston
059978ec63 Fix crash with empty transclusions (#8145)
* Initial commit

* Remove testing code
2025-10-29 11:33:17 +00:00
Mario Pietsch
b5e20a58a6 Muted palette, minor adjustments (#9015)
* Muted palette, minor adjustments

* revert download-background

* Change sidebar tiddler link foreground color

Change sidebar tiddler link foreground colour to make the search dropdown text readable. Using: #aaaaaa
2025-10-29 11:18:31 +00:00
Mario Pietsch
e8fe6b98bc Change camel-case hint, since it is disabled by default now. (#9211)
* Change camel-case hint, since it is disabled by default now.

* change camel-case hint text for other languages, where it is obvious
2025-10-29 11:18:00 +00:00
Mario Pietsch
317104774c list-links-draggable defaults to currentTiddler if tiddler parameter is missing (#9254)
* list-links-draggable if tiddler parameter undefined default to currentTiddler

* update list-links-draggable docs with the new behaviour
2025-10-29 11:17:02 +00:00
Simon Huber
deed8631d8 exports.search: don't break the loop if encoding is base64, just continue (#9247)
This makes it possible to find tiddlers with base64 encoding using `search:*:words[searchterm]` for example

fixes #9246
2025-10-29 11:15:54 +00:00
XLBilly
d733b77e2f Replace CSS property macros in Snow White theme (#9243) 2025-10-29 11:07:37 +00:00
Simon Huber
46c26a64b9 Update framed.js to avoid quirks mode (#9227) 2025-10-29 11:05:59 +00:00
XLBilly
ec81d6663b [5.4.0] Update highlightjs plugin (#9118)
* Bump highlightjs version to 11.11.1

* highlightblock.js support es6
2025-10-29 11:04:53 +00:00
bepuzzled
23f0a9bf79 Improve handling of $link widget's draggable="no" (#9262)
* fix to to improve browser's compliance with user's intent when the widget's draggable attribute is set to no

* Update link.js with comments from the PR review

indentation changed from spaces to tabs
modified else statement into a else if statement for additional specificity

---------

Co-authored-by: Frédéric Demers <fastfreddy@fdemers.ca>
2025-10-29 11:03:19 +00:00
yogoshell
bad87c405e docs: Correcting typo in description field (#9087) 2025-10-28 22:16:32 +01:00
yogoshell
6f23a078b7 Signing CLA (#9086)
Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2025-10-28 22:15:04 +01:00
Mario Pietsch
c3706b8a79 [Docs] Convert Markdown links into TW-Links. Remove redundant TiddlyWiki links. Add links to current docs. (#9209) 2025-10-28 22:12:05 +01:00
Mohammad Rahmani
7ca8fb29af Docs: Fix Issue in Triggering $:/AdvancedSearch (#9244) 2025-10-28 22:11:20 +01:00
Markus Sauermann
d39bb5274e Fix typo in Filter Syntax (#9111) 2025-10-28 22:07:54 +01:00
lin onetwo
81b69783c4 docs: How to add new cascade and use it (#8472)
* docs: How to add new cascade and use it

* Update Cascade Mechanism.tid

* docs: update

* Update How to Create a Custom Cascade Entry.tid

* Refine with deepseek
2025-10-27 15:59:14 +00:00
Rishu Kumar
40cc62f727 fix: renaming of incoming tiddlers being imported (#9368)
* fix: UX on pasting image

* fix: Rename on pasting image from clipboard
2025-10-27 16:13:42 +01:00
Rishu Kumar
b349adde16 Fix Renaming on pasting image from clipboard (#9369)
We can do Renaming on pasting image from clipboard
2025-10-27 16:11:32 +01:00
Saq Imtiaz
d63a1896b3 docs: update more links for PR maker (#9362) 2025-10-23 17:48:57 +02:00
Saq Imtiaz
b65fa11643 Update ContributionBanner to reflect new URI for pr maker 2025-10-23 16:56:21 +02:00
Saq Imtiaz
4043499633 Update link in make-pr-maker-link procedure
Update URI for PR maker
2025-10-23 16:53:30 +02:00
Saq Imtiaz
6a39a4e13b Docs: add link to Contributing guidelines in developers tiddler (#9361) 2025-10-23 12:07:37 +01:00
Cameron Fischer
b5153c0066 :reverse was ignored when prefix sorting by version (#9280) 2025-10-23 12:03:35 +01:00
lin onetwo
b061f90f87 Get-file web server route now support streaming (#9078)
* Update get-file.js

* Update WebServer API_ Get File.tid

* Update get-file.js

* Update get-file.js

* Update get-file.js

* Update get-file.js

* Update core-server/server/routes/get-file.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update core-server/server/routes/get-file.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update get-file.js

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 11:55:30 +01:00
Jeremy Ruston
8be83cf01b Merge branch 'tiddlywiki-com' 2025-10-21 15:45:21 +01:00
Mario Pietsch
5b5147dade Make ReleaseTemplate use code-body (#9340) 2025-10-21 13:00:06 +02:00
Scott Sauyet
ad6ac480f9 Remove css props rather than setting transition to none (#9284)
* Use style property deletion rather than setting to none

* Remove cecily and slide, fix quotation marks

* Fix returning typo

* Add helper functions, use some timeouts to order events
2025-10-21 11:57:10 +01:00
Cameron Fischer
8168512e95 Support for downloading base64 files (#9297)
* Support for downloading base64 files

* Added comment explaining why we'd use data urls over blobs

* Using more modern string##includes method
2025-10-21 11:53:32 +01:00
Leilei332
276fdc8634 Purge deprecated plugins and editions (#9350) 2025-10-21 11:51:12 +01:00
yaisog
0b38ced43a Preserve the module insertion order in forEachModuleOfType (#9305)
* Initial commit

* Update testcase

* Revert "Update testcase"

This reverts commit 0d24c4233f.

* Revert initial commit and swap definition order
2025-10-21 11:37:55 +01:00
cdruan
d7e48207b9 Fix crash when processing large files from tiddlywiki.files (#9341)
* skip reading file content when _canonical_uri is present

* skip loading file when file size is too large
2025-10-21 11:30:39 +01:00
Mohammad Rahmani
2c8fafee48 docs: Fix Slider Plugin Url (#9354) 2025-10-21 12:30:37 +02:00
欧阳
a6383aaaea feat: specify the space applied between CJK and non-CJK characters (#9349) 2025-10-21 11:26:32 +01:00
Joseph Yi
61619c07c8 chore: bump checkout action v4 to v5 (#9342) 2025-10-15 21:27:23 +01:00
Joseph Yi
09a42a54c0 Chore: Signing CLA (#9344) 2025-10-15 21:25:01 +01:00
Mario Pietsch
f4f31c37fc Remove tweb.at from community resources (#9330) 2025-10-12 10:55:39 +01:00
Jeremy Ruston
61e638c972 Merge branch 'tiddlywiki-com' 2025-10-11 15:53:49 +01:00
lin onetwo
6bdd51d72a Fix (eslint): removes unneeded dependencies (#9332)
* Clean up eslint.config.mjs by removing unused import and comments

Removed commented-out import statements for clarity.

* Remove @eslint/compat from devDependencies

Removed deprecated @eslint/compat dependency.

* Update eslint.config.mjs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-11 14:11:21 +01:00
Mario Pietsch
ed3405672a Rename Intertwingled innovations.webp to Innovations.webp (#9331) 2025-10-11 11:19:55 +02:00
Mario Pietsch
e3af967cbb [DOCS] Convert PNG images into WEBP to reduce wiki size by 3MB (#9162)
* convert PNG images into WEBP to reduce wiki size by 3MB

* convert Community Survey 2025.png to webp
2025-10-10 15:02:30 +01:00
Mario Pietsch
6b0d3fab5d [Docs] Fix typo in DefaultTiddlers (#9202) 2025-10-10 15:01:00 +01:00
Mario Pietsch
13f1689e7e [DOC] ContentType Info and Links - Add Compount Tiddler Type (#9249) 2025-10-10 15:00:43 +01:00
Mario Pietsch
4c09a88272 [DOCS] Improve "range Operator" documentation. Make placeholder "terms" consistent with filter strings (#9276)
* [DOCS] Improve "range Operator" documentation. Make placeholder "terms" consistent with filter strings

* update range operator table formatting a bit
2025-10-10 14:59:27 +01:00
Jeremy Ruston
fbf110e209 Merge branch 'tiddlywiki-com' 2025-10-10 14:58:29 +01:00
Mario Pietsch
3c1d658fad Adjust Missing Links for Community Resources (#9329)
* Adjust Missing Links for Community Resources

* Update _Lucky Sushi_ online shop by sini-Kit.tid
2025-10-10 14:44:57 +01:00
Cameron Fischer
5d738673ac Added "Hello There" Thumbnail for TW5-Graph (#9322)
* Added "Hello There" Thumbnail for TW5-Graph

* Preventing TiddlyWiki link in TW5-Graph description

* Added modified date for TW5-Graph post
2025-10-10 14:30:54 +01:00
cdruan
91871d2ced Markdown: Fix missing inline support and macrocall args parsing error (#9295)
* Rewrote tw_macrocallinline() parser so macrocall args containing ">"
will be parsed properly.

* Use $tw.log.MARKDOWN flag to print debug messages.

* Fix markdown parser to respect parseAsInline option. (#8917)
2025-10-10 13:31:11 +01:00
Leilei332
5143da9cce Fix CTRL-alt-right shortcut not working (#9325) 2025-10-10 13:30:22 +01:00
Leilei332
186d1b014a Migrate most deprecated eslint rules (#9328)
* Migrate to eslint stylistic

* Remove & replace some deprecated rules
2025-10-10 13:29:35 +01:00
Saq Imtiaz
58e41ee0ad Feature: extend server routes to support multiple methods (#9207) 2025-10-10 12:37:09 +01:00
Saq Imtiaz
97824cc3a3 Feature: extend server route exports to allow ordering (#9206) 2025-10-10 12:36:36 +01:00
Arlen Beiler
6e493755be Add support for commands and startups which return promises (#9103) 2025-10-09 16:06:03 +01:00
Jeremy Ruston
64fce62075 [v5.4.0] Update configuration defaults (#9107)
* Change default sidebar layout to fluid-fixed

* Default to not wrapping code blocks

* codewrapping should be "pre" not "no"

Co-authored-by: Leilei332 <LeiYiXia29@outlook.com>

* Info panel should stay open by default

---------

Co-authored-by: Leilei332 <LeiYiXia29@outlook.com>
2025-10-08 12:32:34 +01:00
Leilei332
5a4ff56477 Fix markup not included in external core edition (#9218) 2025-10-08 10:41:08 +01:00
Leilei332
3889a2a0d0 Fix nested span.tc-keyboard element in core (#9235)
* Fix nested `span.keyboard` element in core

* Fix switching search results not working
2025-10-08 10:28:59 +01:00
Leilei332
34737f4e28 Improve ARIA support for link widget (#9167) 2025-10-07 15:46:43 +01:00
Leilei332
94673a1028 Allow button widget to use all ARIA attibutes (#9154)
* Allow button widget to use all ARIA attibutes

* Add link in docs

* Update docs
2025-10-07 13:05:09 +01:00
Mario Pietsch
b462aaa9a3 Modules in draft tidders should not be executed (#9293)
* modules in draft tiddlers should not be defined

* improve warning message and use console.warn
2025-10-07 12:03:11 +01:00
Mario Pietsch
4552c117fc Add th-debug-element hook and data-debug-element minimal useful info (#9281)
* add th-debug-element hook and date-debug-element minimal useful info

* invoke th-dom-rendering-element hook

* improve comment
2025-10-07 12:02:08 +01:00
Leilei332
dc7f2a57bb Use currentColor to style svg (#9316)
* Replace fill: with currentColor

* Remove fill from wikitext

* Replace fill property with color property for svg selectors

* Further remove fill property

* Replace more fill properties

* Replace fill in seamless theme

* Replace fill in plugins
2025-10-07 11:55:26 +01:00
Jeremy Ruston
7e91bac6b8 Merge branch 'tiddlywiki-com' 2025-10-07 10:43:50 +01:00
Leilei332
15ba415a45 Use s instead of strike (#9131)
* Use s instead of strike

* Update docs
2025-10-06 17:28:29 +01:00
Leilei332
2405e65308 Remove support for IE in range widget (#9275) 2025-10-06 17:26:34 +01:00
Saq Imtiaz
865f36a6f5 Removes deprecated attributes from $eventcatcher (#9259)
* fix: removed deprecated attributes

* docs: update for eventcatcher
2025-10-06 17:12:08 +01:00
Saq Imtiaz
a36356a07d Adds info tiddlers for viewport dimensions (#9260)
* feat: added info tiddlers for viewport dimensions

* feat: multi window support for dimensions info tiddlers

* refactor: introduce standalone eventbus and refactor for ES2017

* docs: extended docs for InfoMechanism to cover new additions
2025-10-06 17:00:13 +01:00
Leilei332
ee23097816 Set modal's mask-closable attribute to yes by default (#9313)
* Set modal's mask-closable attribute to yes by default

* Make backdrop closable only when field value is yes or empty
2025-10-06 16:40:20 +01:00
Mario Pietsch
e753851d49 Add output directory to eslint ignore configuration (#9317) 2025-10-06 16:27:11 +01:00
Leilei332
cae3d0fa2c Switch to native support for base64 utf8 (#9253)
* Switch to native support for base64 utf8

* Fix error on nodejs
2025-10-06 16:25:45 +01:00
Mario Pietsch
7c020d0eb6 Add a CSV Table as Example to Typed Blocks in Wikitext (#9318) 2025-10-06 16:22:31 +01:00
Saq Imtiaz
afe2ac45d9 Fix: Update eslint.yml to not run on push 2025-10-02 22:38:59 +02:00
Saq Imtiaz
6791f0f130 Create eslint.yml (#9315)
Adds a workflow to lint PRs, and reports issues found in lines modified in the PR.
2025-10-02 22:33:09 +02:00
Mario Pietsch
a2e5c2cca2 Add documentation for Encrypted Import Problems (#9100) 2025-10-02 11:49:59 +01:00
Mario Pietsch
87ba87bdd2 Set AES strength to 256 bit (#8249)
* Set AES strength to 256 bit

* Update Encryption tiddler to AES 256
2025-10-02 11:49:45 +01:00
Leilei332
619bdfcab5 Reimplement regexp sticky flag (#9119) 2025-10-02 11:42:30 +01:00
Scott Sauyet
5ff4e02a61 Update TableOfContents.tid (#9312)
Move Welcome back to the top of the TOC.

Third attempt.  Someday I'll get this right.
2025-10-02 11:22:37 +01:00
Arlen Beiler
f8170cd50a [5.4.0] Update eslint target to 2017 and do initial fixes (#9135)
* eslint manual fixes
- update eslint to 2017
- add self to forbidden globals
- fix a few unfixable bugs caught by eslint
- convert newer features in twitter-archivist

* add eslint plugin to forbid features

* import changes from @saqimtiaz

* add package.json changes
2025-10-01 15:08:00 +01:00
Jeremy Ruston
5389dc0fa7 Merge branch 'tiddlywiki-com' 2025-09-25 16:48:39 +01:00
Jeremy Ruston
2cc7c96eec Remove inadvertently committed files 2025-09-25 16:48:22 +01:00
Jeremy Ruston
8cd3d4e22c HireJeremy: Tweaks 2025-09-20 11:52:39 +01:00
Jeremy Ruston
37e09d1c25 Add Bluesky link for Jermolene 2025-09-17 14:02:13 +01:00
Jeremy Ruston
7a080092d0 Allow community cards to be edited in the browser under Node.js 2025-09-16 11:05:04 +01:00
jeremy@jermolene.com
33d4e8ea26 Don't use a variable before it's defined... 2021-02-24 15:27:58 +00:00
jeremy@jermolene.com
f980d8a7b2 Merge branch 'master' into browser-messaging-saver 2021-02-22 23:10:20 +00:00
jeremy@jermolene.com
e31a201269 Subscriber: Make onmessage be async 2021-02-22 22:39:37 +00:00
jeremy@jermolene.com
58d291c116 First commit 2021-02-22 19:20:58 +00:00
301 changed files with 2894 additions and 30495 deletions

View File

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

40
.github/workflows/eslint.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
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

@@ -73,10 +73,8 @@ 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/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/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)
@@ -301,26 +299,6 @@ node $TW5_BUILD_TIDDLYWIKI \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/katex/empty.html text/plain \
|| 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/empty.html Empty wiki with codemirror plugin
node $TW5_BUILD_TIDDLYWIKI \

View File

@@ -641,7 +641,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Call the function and return the exports
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
*/
@@ -799,12 +799,13 @@ the password, and to encrypt/decrypt a block of text
$tw.utils.Crypto = function() {
var sjcl = $tw.node ? (global.sjcl || require("./sjcl.js")) : window.sjcl,
currentPassword = null,
callSjcl = function(method,inputText,password) {
callSjcl = function(method,inputText,password,options) {
options = options || {};
password = password || currentPassword;
var outputText;
try {
if(password) {
outputText = sjcl[method](password,inputText);
outputText = sjcl[method](password,inputText,options);
}
} catch(ex) {
console.log("Crypto error:" + ex);
@@ -830,7 +831,8 @@ $tw.utils.Crypto = function() {
return !!currentPassword;
}
this.encrypt = function(text,password) {
return callSjcl("encrypt",text,password);
// set default ks:256 -- see: http://bitwiseshiftleft.github.io/sjcl/doc/convenience.js.html
return callSjcl("encrypt",text,password,{v:1,iter:10000,ks:256,ts:64,mode:"ccm",adata:"",cipher:"aes"});
};
this.decrypt = function(text,password) {
return callSjcl("decrypt",text,password);
@@ -1530,7 +1532,8 @@ Define all modules stored in ordinary tiddlers
*/
$tw.Wiki.prototype.defineTiddlerModules = function() {
this.each(function(tiddler,title) {
if(tiddler.hasField("module-type")) {
// Modules in draft tiddlers are disabled
if(tiddler.hasField("module-type") && (!tiddler.hasField("draft.of"))) {
switch(tiddler.fields.type) {
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
@@ -1557,6 +1560,11 @@ $tw.Wiki.prototype.defineShadowModules = function() {
this.eachShadow(function(tiddler,title) {
// Don't define the module if it is overidden by an ordinary tiddler
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
$tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],tiddler.fields.text);
}
@@ -1905,7 +1913,7 @@ $tw.loadTiddlersFromFile = function(filepath,fields) {
fileSize = fs.statSync(filepath).size,
data;
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);
ext = ".txt";
} else {
@@ -1976,22 +1984,41 @@ filepath: pathname of the directory containing the specification file
$tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
var tiddlers = [];
// Read the specification
var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"));
var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"), function(e) {
console.log("Warning: tiddlywiki.files in " + filepath + " invalid: " + e.message);
return {};
});
// Helper to process a file
var processFile = function(filename,isTiddlerFile,fields,isEditableFile,rootPath) {
var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)],
type = (extInfo || {}).type || fields.type || "text/plain",
typeInfo = $tw.config.contentTypeInfo[type] || {},
pathname = path.resolve(filepath,filename),
text = fs.readFileSync(pathname,typeInfo.encoding || "utf8"),
metadata = $tw.loadMetadataForFile(pathname) || {},
fileTiddlers;
fileTooLarge = false,
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) {
fileTiddlers = $tw.wiki.deserializeTiddlers(path.extname(pathname),text,metadata) || [];
fileTiddlers = $tw.wiki.deserializeTiddlers(fileTooLarge ? ".txt" : path.extname(pathname),text,metadata) || [];
} else {
fileTiddlers = [$tw.utils.extend({text: text},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(combinedFields,function(fieldInfo,name) {
if(typeof fieldInfo === "string" || $tw.utils.isArray(fieldInfo)) {
@@ -2066,6 +2093,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
} else if(tidInfo.suffix) {
tidInfo.fields.text = {suffix: tidInfo.suffix};
}
tidInfo.fields = tidInfo.fields || {};
processFile(tidInfo.file,tidInfo.isTiddlerFile,tidInfo.fields);
});
// Process any listed directories
@@ -2087,6 +2115,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
var thisPath = path.relative(filepath, files[t]),
filename = path.basename(thisPath);
if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) {
dirSpec.fields = dirSpec.fields || {};
processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile,dirSpec.path);
}
}
@@ -2549,10 +2578,10 @@ $tw.boot.execStartup = function(options){
if($tw.safeMode) {
$tw.wiki.processSafeMode();
}
// Register typed modules from the tiddlers we've just loaded
$tw.wiki.defineTiddlerModules();
// And any modules within plugins
// Register typed modules from the tiddlers we've just loaded and any modules within plugins
// Tiddlers should appear last so that they may overwrite shadows during module registration
$tw.wiki.defineShadowModules();
$tw.wiki.defineTiddlerModules();
// Make sure the crypto state tiddler is up to date
if($tw.crypto) {
$tw.crypto.updateCryptoStateTiddler();
@@ -2621,11 +2650,13 @@ $tw.boot.executeNextStartupTask = function(callback) {
$tw.boot.log(s.join(" "));
// Execute task
if(!$tw.utils.hop(task,"synchronous") || task.synchronous) {
task.startup();
if(task.name) {
$tw.boot.executedStartupModules[task.name] = true;
const thenable = task.startup();
if(thenable && typeof thenable.then === "function"){
thenable.then(asyncTaskCallback);
return true;
} else {
return asyncTaskCallback();
}
return $tw.boot.executeNextStartupTask(callback);
} else {
task.startup(asyncTaskCallback);
return true;

View File

@@ -6,6 +6,7 @@ talk.tiddlywiki.org: jeremyruston
github: Jermolene
linkedin: www.linkedin.com/in/jermy
flickr: www.flickr.com/photos/jermy/
bluesky: https://bsky.app/profile/jermolene.bsky.social
homepage: jermolene.com
email: jeremy@jermolene.com
avatar: /9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgICAgJCAkKCgkNDgwODRMREBARExwUFhQWFBwrGx8bGx8bKyYuJSMlLiZENS8vNUROQj5CTl9VVV93cXecnNEBCAgICAkICQoKCQ0ODA4NExEQEBETHBQWFBYUHCsbHxsbHxsrJi4lIyUuJkQ1Ly81RE5CPkJOX1VVX3dxd5yc0f/CABEIACAAIAMBIgACEQEDEQH/xAAtAAEBAAMAAAAAAAAAAAAAAAAHBgIEBQEBAQEBAAAAAAAAAAAAAAAAAgQBBf/aAAwDAQACEAMQAAAANF4uTuPRhD2nBLnUiJvKM0DtMKy//8QAKxAAAgIBAwMDAQkAAAAAAAAAAQIDBBEABRITITEiMkFxFEJRUmFicoGR/9oACAEBAAE/AInTA6gUGP4ZOQbW1bPsmyUq1q+gmvFPUzZPDkPamtwqU75ks04JakroVcg5RwRjg66NUx25KbzqJYyMngfqSuq0M3NZYIebJIvZozIvI/iNPcp/aalSdJXsS4VcKeIzlvU3jVTcYLNiaGISrjkhWQYDfQ63pYAzCDBsOiu7Dsx4EHH6r2w2ttimjd2IsNErhhJHKI04/uzqxuCxpBYVVWKSHqwMyMSQ33SB7dUJFmlkMYRgnqZgCMf7rf8AeEt3A9YOhjXAb2k8u7dtT1RZeOtXmYxiOPj4ZWY/lb51skqUNnNW/wBNzC7IpB6gQeeB/jq/fqGOaLbowuYn5MAQOw8LjW5Vmeo0qIsqYLLKjHIZmwv9fB1//8QAHxEAAQMEAwEAAAAAAAAAAAAAEQABAgMSIWExMkFR/9oACAECAQE/AD9iTy2lJmHUB8BVKM4SNSOj46a29saX/8QAHREAAgICAwEAAAAAAAAAAAAAAQIAAwQRITGBkf/aAAgBAwEBPwDHpFpJZtamVSiBWT2Yt7hmCDsb+TKtsKqpGg3M/9k=

View File

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

View File

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

View File

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

View File

@@ -8,35 +8,66 @@ GET /files/:filepath
\*/
"use strict";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/files\/(.+)$/;
exports.info = {
priority: 100
};
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");
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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,10 +10,14 @@ GET /recipes/default/tiddlers.json?filter=<filter>
var DEFAULT_FILTER = "[all[tiddlers]!is[system]sort[title]]";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/recipes\/default\/tiddlers.json$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var filter = state.queryParameters.filter || DEFAULT_FILTER;
if(state.wiki.getTiddlerText("$:/config/Server/AllowAllExternalFilters") !== "yes") {

View File

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

View File

@@ -74,6 +74,11 @@ function Server(options) {
// console.log("Loading server route " + title);
self.addRoute(routeDefinition);
});
this.routes.sort((a, b) => {
const priorityA = a.info?.priority ?? 100,
priorityB = b.info?.priority ?? 100;
return priorityB - priorityA;
});
// Initialise the http vs https
this.listenOptions = null;
this.protocol = "http";
@@ -217,7 +222,7 @@ Server.prototype.findMatchingRoute = function(request,state) {
} else {
match = potentialRoute.path.exec(pathname);
}
if(match && request.method === potentialRoute.method) {
if(match && (potentialRoute.methods?.includes(request.method) || potentialRoute.method === request.method)) {
state.params = [];
for(var p=1; p<match.length; p++) {
state.params.push(match[p]);

View File

@@ -147,7 +147,7 @@ Settings/AutoSave/Disabled/Description: Do not 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/CamelCase/Caption: Camel Case Wiki Links
Settings/CamelCase/Hint: You can globally disable automatic linking of ~CamelCase phrases. Requires reload to take effect
Settings/CamelCase/Hint: Requires reload to take effect
Settings/CamelCase/Description: Enable automatic ~CamelCase linking
Settings/Caption: Settings
Settings/EditorToolbar/Caption: Editor Toolbar

View File

@@ -34,7 +34,7 @@ function FramedEngine(options) {
var paletteTitle = this.widget.wiki.getTiddlerText("$:/palette");
var colorScheme = (this.widget.wiki.getTiddler(paletteTitle) || {fields: {}}).fields["color-scheme"] || "light";
this.iframeDoc.open();
this.iframeDoc.write("<meta name='color-scheme' content='" + colorScheme + "'>");
this.iframeDoc.write("<!DOCTYPE html><html><head><meta name='color-scheme' content='" + colorScheme + "'></head><body></body></html>");
this.iframeDoc.close();
// Style the iframe
this.iframeNode.className = this.dummyTextArea.className;

View File

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

View File

@@ -217,6 +217,10 @@ function makeNumericReducingOperator(fnCalc,initialValue,fnFinal) {
source(function(tiddler,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) {
return fnCalc(accumulator,currentValue);
},initialValue);

View File

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

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ exports.parse = function() {
// Advance the parser position to past the tag
this.parser.pos = tag.end;
// 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|$))/g);
var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y);
// Set whether we're in block mode
tag.isBlock = this.is.block || hasLineBreak;
// Parse the body if we need to
@@ -100,7 +100,7 @@ exports.parseTag = function(source,pos,options) {
orderedAttributes: []
};
// Define our regexps
var reTagName = /([a-zA-Z0-9\-\$\.]+)/g;
const reTagName = /([a-zA-Z0-9\-\$\.]+)/y;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a less than sign
@@ -148,7 +148,7 @@ exports.parseTag = function(source,pos,options) {
pos = token.end;
// Check for a required line break
if(options.requireLineBreak) {
token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y);
if(!token) {
return null;
}

View File

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

View File

@@ -23,6 +23,27 @@ exports.init = function(parser) {
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() {
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;

View File

@@ -23,6 +23,27 @@ exports.init = function(parser) {
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() {
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;

View File

@@ -35,7 +35,9 @@ DownloadSaver.prototype.save = function(text,method,callback,options) {
}
// Set up the link
var link = document.createElement("a");
if(Blob !== undefined) {
// We prefer Blobs if they're available, unless we're dealing with a tiddler type declaring itself full of base64 encoded content.
// 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});
link.setAttribute("href", URL.createObjectURL(blob));
} else {

View File

@@ -0,0 +1,66 @@
/*\
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

@@ -0,0 +1,46 @@
/*\
title: $:/core/modules/startup/eventbus.js
type: application/javascript
module-type: startup
Event bus for cross module communication
\*/
exports.name = "eventbus";
exports.platforms = ["browser"];
exports.before = ["windows"];
exports.synchronous = true;
$tw.eventBus = {
listenersMap: new Map(),
on(event,handler) {
if(!this.listenersMap.has(event)) {
this.listenersMap.set(event,new Set());
}
const listeners = this.listenersMap.get(event);
listeners.add(handler);
},
off(event,handler) {
const listeners = this.listenersMap.get(event);
if(listeners) {
listeners.delete(handler);
}
},
once(event,handler) {
const wrapper = (...args) => {
handler(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
},
emit(event,data) {
const listeners = this.listenersMap.get(event);
if(listeners) {
listeners.forEach(fn => fn(data));
}
}
};

View File

@@ -19,6 +19,16 @@ exports.synchronous = true;
var FAVICON_TITLE = "$:/favicon.ico";
exports.startup = function() {
var setFavicon = function() {
var tiddler = $tw.wiki.getTiddler(FAVICON_TITLE);
if(tiddler) {
var faviconLink = document.getElementById("faviconLink"),
dataURI = $tw.utils.makeDataUri(tiddler.fields.text,tiddler.fields.type,tiddler.fields._canonical_uri);
faviconLink.setAttribute("href",dataURI);
$tw.faviconPublisher.send({verb: "FAVICON",body: dataURI});
}
}
$tw.faviconPublisher = new $tw.utils.BrowserMessagingPublisher({type: "FAVICON", onsubscribe: setFavicon});
// Set up the favicon
setFavicon();
// Reset the favicon when the tiddler changes
@@ -28,11 +38,3 @@ exports.startup = function() {
}
});
};
function setFavicon() {
var tiddler = $tw.wiki.getTiddler(FAVICON_TITLE);
if(tiddler) {
var faviconLink = document.getElementById("faviconLink");
faviconLink.setAttribute("href",$tw.utils.makeDataUri(tiddler.fields.text,tiddler.fields.type,tiddler.fields._canonical_uri));
}
}

View File

@@ -19,11 +19,17 @@ var TITLE_INFO_PLUGIN = "$:/temp/info-plugin";
exports.startup = function() {
// Function to bake the info plugin with new tiddlers
var updateInfoPlugin = function(tiddlerFieldsArray) {
// additions: array of tiddler field objects
// removals: array of titles to remove
var updateInfoPlugin = function(additions = [], removals = []) {
// Get the existing tiddlers
var json = $tw.wiki.getTiddlerData(TITLE_INFO_PLUGIN,{tiddlers: {}});
// Add the new ones
$tw.utils.each(tiddlerFieldsArray,function(fields) {
$tw.utils.each(removals,function(title) {
if(json.tiddlers[title]) {
delete json.tiddlers[title];
}
});
$tw.utils.each(additions,function(fields) {
if(fields && fields.title) {
json.tiddlers[fields.title] = fields;
}
@@ -47,7 +53,7 @@ exports.startup = function() {
}
});
updateInfoPlugin(tiddlerFieldsArray);
var changes = $tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]);
$tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]);
$tw.wiki.registerPluginTiddlers("info",[TITLE_INFO_PLUGIN]);
$tw.wiki.unpackPluginTiddlers();
};

View File

@@ -33,10 +33,15 @@ exports.startup = function() {
});
$tw.titleContainer = $tw.fakeDocument.createElement("div");
$tw.titleWidgetNode.render($tw.titleContainer,null);
document.title = $tw.titleContainer.textContent;
var publishTitle = function() {
$tw.titlePublisher.send({verb: "PAGETITLE",body: document.title});
document.title = $tw.titleContainer.textContent;
};
$tw.titlePublisher = new $tw.utils.BrowserMessagingPublisher({type: "PAGETITLE", onsubscribe: publishTitle});
publishTitle();
$tw.wiki.addEventListener("change",function(changes) {
if($tw.titleWidgetNode.refresh(changes,$tw.titleContainer,null)) {
document.title = $tw.titleContainer.textContent;
publishTitle();
}
});
// Set up the styles

View File

@@ -56,9 +56,11 @@ exports.startup = function() {
srcDocument.write("<!DOCTYPE html><head></head><body class='tc-body tc-single-tiddler-window'></body></html>");
srcDocument.close();
srcDocument.title = windowTitle;
$tw.eventBus.emit("window:opened",{windowID, window: srcWindow});
srcWindow.addEventListener("beforeunload",function(event) {
delete $tw.windows[windowID];
$tw.wiki.removeEventListener("change",refreshHandler);
$tw.eventBus.emit("window:closed",{windowID});
},false);
// Set up the styles
var styleWidgetNode = $tw.wiki.makeTranscludeWidget("$:/core/ui/PageStylesheet",{

View File

@@ -47,16 +47,16 @@ ClassicStoryView.prototype.insert = function(widget) {
// Reset the margin once the transition is over
setTimeout(function() {
$tw.utils.setStyle(targetElement,[
{transition: "none"},
{marginBottom: ""}
]);
$tw.utils.removeStyle(targetElement, "transition");
},duration);
// Set up the initial position of the element
$tw.utils.setStyle(targetElement,[
{transition: "none"},
{marginBottom: (-currHeight) + "px"},
{opacity: "0.0"}
]);
$tw.utils.removeStyle(targetElement, "transition");
$tw.utils.forceLayout(targetElement);
// Transition to the final position
$tw.utils.setStyle(targetElement,[
@@ -64,7 +64,7 @@ ClassicStoryView.prototype.insert = function(widget) {
"margin-bottom " + duration + "ms " + easing},
{marginBottom: currMarginBottom + "px"},
{opacity: "1.0"}
]);
]);
}
};
@@ -94,11 +94,9 @@ ClassicStoryView.prototype.remove = function(widget) {
setTimeout(removeElement,duration);
// Animate the closure
$tw.utils.setStyle(targetElement,[
{transition: "none"},
{transform: "translateX(0px)"},
{marginBottom: currMarginBottom + "px"},
{opacity: "1.0"}
]);
$tw.utils.removeStyles(targetElement, ["transition", "transform", "opacity"]);
$tw.utils.forceLayout(targetElement);
$tw.utils.setStyle(targetElement,[
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", " +
@@ -113,4 +111,4 @@ ClassicStoryView.prototype.remove = function(widget) {
}
};
exports.classic = ClassicStoryView;
exports.classic = ClassicStoryView;

View File

@@ -37,10 +37,7 @@ PopStoryView.prototype.insert = function(widget) {
}
// Reset once the transition is over
setTimeout(function() {
$tw.utils.setStyle(targetElement,[
{transition: "none"},
{transform: "none"}
]);
$tw.utils.removeStyles(targetElement, ["transition", "transform"]);
$tw.utils.setStyle(widget.document.body,[
{"overflow-x": ""}
]);
@@ -51,10 +48,10 @@ PopStoryView.prototype.insert = function(widget) {
]);
// Set up the initial position of the element
$tw.utils.setStyle(targetElement,[
{transition: "none"},
{transform: "scale(2)"},
{opacity: "0.0"}
]);
$tw.utils.removeStyle(targetElement, "transition");
$tw.utils.forceLayout(targetElement);
// Transition to the final position
$tw.utils.setStyle(targetElement,[
@@ -63,6 +60,9 @@ PopStoryView.prototype.insert = function(widget) {
{transform: "scale(1)"},
{opacity: "1.0"}
]);
setTimeout(function() {
$tw.utils.removeStyles(targetElement, ["transition", "transform", "opactity"]);
}, duration)
};
PopStoryView.prototype.remove = function(widget) {
@@ -81,11 +81,7 @@ PopStoryView.prototype.remove = function(widget) {
// Remove the element at the end of the transition
setTimeout(removeElement,duration);
// Animate the closure
$tw.utils.setStyle(targetElement,[
{transition: "none"},
{transform: "scale(1)"},
{opacity: "1.0"}
]);
$tw.utils.removeStyles(targetElement, ["transition", "transform", "opacity"]);
$tw.utils.forceLayout(targetElement);
$tw.utils.setStyle(targetElement,[
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in-out, " +
@@ -95,4 +91,4 @@ PopStoryView.prototype.remove = function(widget) {
]);
};
exports.pop = PopStoryView;
exports.pop = PopStoryView;

View File

@@ -96,6 +96,9 @@ ZoominListView.prototype.navigateTo = function(historyInfo) {
{transform: "translateX(0px) translateY(0px) scale(1)"},
{zIndex: "500"},
]);
setTimeout(function() {
$tw.utils.removeStyles(targetElement, ["transition", "opacity", "transform", "zIndex"]);
}, duration);
// Transform the previous tiddler out of the way and then hide it
if(prevCurrentTiddler && prevCurrentTiddler !== targetElement) {
scale = zoomBounds.width / sourceBounds.width;
@@ -207,6 +210,9 @@ ZoominListView.prototype.remove = function(widget) {
{opacity: "0"},
{zIndex: "0"}
]);
setTimeout(function() {
$tw.utils.removeStyles(toWidgetDomNode, ["transformOrigin", "transform", "transition", "opacity", "zIndex"]);
}, duration);
setTimeout(removeElement,duration);
// Now the tiddler we're going back to
if(toWidgetDomNode) {
@@ -222,4 +228,4 @@ ZoominListView.prototype.logTextNodeRoot = function(node) {
this.textNodeLogger.log($tw.language.getString("Error/ZoominTextNode") + " " + node.textContent);
};
exports.zoomin = ZoominListView;
exports.zoomin = ZoominListView;

View File

@@ -1,142 +0,0 @@
// From https://gist.github.com/Nijikokun/5192472
//
// UTF8 Module
//
// Cleaner and modularized utf-8 encoding and decoding library for javascript.
//
// copyright: MIT
// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com
(function (name, definition, context, dependencies) {
if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); }
else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); }
else { context[name] = definition.apply(context); }
})('utf8', function () {
return {
encode: function (string) {
if (typeof string !== 'string') return string;
else string = string.replace(/\r\n/g, "\n");
var output = "", i = 0, charCode;
for (i; i < string.length; i++) {
charCode = string.charCodeAt(i);
if (charCode < 128) {
output += String.fromCharCode(charCode);
} else if ((charCode > 127) && (charCode < 2048)) {
output += String.fromCharCode((charCode >> 6) | 192);
output += String.fromCharCode((charCode & 63) | 128);
} else if ((charCode > 55295) && (charCode < 57344) && string.length > i+1) {
// Surrogate pair
var hiSurrogate = charCode;
var loSurrogate = string.charCodeAt(i+1);
i++; // Skip the low surrogate on the next loop pass
var codePoint = (((hiSurrogate - 55296) << 10) | (loSurrogate - 56320)) + 65536;
output += String.fromCharCode((codePoint >> 18) | 240);
output += String.fromCharCode(((codePoint >> 12) & 63) | 128);
output += String.fromCharCode(((codePoint >> 6) & 63) | 128);
output += String.fromCharCode((codePoint & 63) | 128);
} else {
// Not a surrogate pair, or a dangling surrogate without its partner that we'll just encode as-is
output += String.fromCharCode((charCode >> 12) | 224);
output += String.fromCharCode(((charCode >> 6) & 63) | 128);
output += String.fromCharCode((charCode & 63) | 128);
}
}
return output;
},
decode: function (string) {
if (typeof string !== 'string') return string;
var output = "", i = 0, charCode = 0;
while (i < string.length) {
charCode = string.charCodeAt(i);
if (charCode < 128) {
output += String.fromCharCode(charCode),
i++;
} else if ((charCode > 191) && (charCode < 224)) {
output += String.fromCharCode(((charCode & 31) << 6) | (string.charCodeAt(i + 1) & 63));
i += 2;
} else if ((charCode > 223) && (charCode < 240)) {
output += String.fromCharCode(((charCode & 15) << 12) | ((string.charCodeAt(i + 1) & 63) << 6) | (string.charCodeAt(i + 2) & 63));
i += 3;
} else {
var codePoint = ((charCode & 7) << 18) | ((string.charCodeAt(i + 1) & 63) << 12) | ((string.charCodeAt(i + 2) & 63) << 6) | (string.charCodeAt(i + 3) & 63);
// output += String.fromCodePoint(codePoint); // Can't do this because Internet Explorer doesn't have String.fromCodePoint
output += String.fromCharCode(((codePoint - 65536) >> 10) + 55296) + String.fromCharCode(((codePoint - 65536) & 1023) + 56320); // So we do this instead
i += 4;
}
}
return output;
}
};
}, this);
// Base64 Module
//
// Cleaner, modularized and properly scoped base64 encoding and decoding module for strings.
//
// copyright: MIT
// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com
(function (name, definition, context, dependencies) {
if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); }
else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); }
else { context[name] = definition.apply(context); }
})('base64', function (utf8) {
var $this = this;
var $utf8 = utf8 || this.utf8;
var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
return {
encode: function (input) {
if (typeof $utf8 === 'undefined') throw { error: "MissingMethod", message: "UTF8 Module is missing." };
if (typeof input !== 'string') return input;
else input = $utf8.encode(input);
var output = "", a, b, c, d, e, f, g, i = 0;
while (i < input.length) {
a = input.charCodeAt(i++);
b = input.charCodeAt(i++);
c = input.charCodeAt(i++);
d = a >> 2;
e = ((a & 3) << 4) | (b >> 4);
f = ((b & 15) << 2) | (c >> 6);
g = c & 63;
if (isNaN(b)) f = g = 64;
else if (isNaN(c)) g = 64;
output += map.charAt(d) + map.charAt(e) + map.charAt(f) + map.charAt(g);
}
return output;
},
decode: function (input) {
if (typeof $utf8 === 'undefined') throw { error: "MissingMethod", message: "UTF8 Module is missing." };
if (typeof input !== 'string') return input;
else input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
var output = "", a, b, c, d, e, f, g, i = 0;
while (i < input.length) {
d = map.indexOf(input.charAt(i++));
e = map.indexOf(input.charAt(i++));
f = map.indexOf(input.charAt(i++));
g = map.indexOf(input.charAt(i++));
a = (d << 2) | (e >> 4);
b = ((e & 15) << 4) | (f >> 2);
c = ((f & 3) << 6) | g;
output += String.fromCharCode(a);
if (f != 64) output += String.fromCharCode(b);
if (g != 64) output += String.fromCharCode(c);
}
return $utf8.decode(output);
}
}
}, this, [ "utf8" ]);

View File

@@ -1,9 +0,0 @@
// From https://gist.github.com/Nijikokun/5192472
//
// UTF8 Module
//
// Cleaner and modularized utf-8 encoding and decoding library for javascript.
//
// copyright: MIT
// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com
!function(r,e,o,t){void 0!==o.module&&o.module.exports?o.module.exports=e.apply(o):void 0!==o.define&&"function"===o.define&&o.define.amd?define("utf8",[],e):o.utf8=e.apply(o)}(0,function(){return{encode:function(r){if("string"!=typeof r)return r;r=r.replace(/\r\n/g,"\n");for(var e,o="",t=0;t<r.length;t++)if((e=r.charCodeAt(t))<128)o+=String.fromCharCode(e);else if(e>127&&e<2048)o+=String.fromCharCode(e>>6|192),o+=String.fromCharCode(63&e|128);else if(e>55295&&e<57344&&r.length>t+1){var i=e,n=r.charCodeAt(t+1);t++;var d=65536+(i-55296<<10|n-56320);o+=String.fromCharCode(d>>18|240),o+=String.fromCharCode(d>>12&63|128),o+=String.fromCharCode(d>>6&63|128),o+=String.fromCharCode(63&d|128)}else o+=String.fromCharCode(e>>12|224),o+=String.fromCharCode(e>>6&63|128),o+=String.fromCharCode(63&e|128);return o},decode:function(r){if("string"!=typeof r)return r;for(var e="",o=0,t=0;o<r.length;)if((t=r.charCodeAt(o))<128)e+=String.fromCharCode(t),o++;else if(t>191&&t<224)e+=String.fromCharCode((31&t)<<6|63&r.charCodeAt(o+1)),o+=2;else if(t>223&&t<240)e+=String.fromCharCode((15&t)<<12|(63&r.charCodeAt(o+1))<<6|63&r.charCodeAt(o+2)),o+=3;else{var i=(7&t)<<18|(63&r.charCodeAt(o+1))<<12|(63&r.charCodeAt(o+2))<<6|63&r.charCodeAt(o+3);e+=String.fromCharCode(55296+(i-65536>>10))+String.fromCharCode(56320+(i-65536&1023)),o+=4}return e}}},this),function(r,e,o,t){if(void 0!==o.module&&o.module.exports){if(t&&o.require)for(var i=0;i<t.length;i++)o[t[i]]=o.require(t[i]);o.module.exports=e.apply(o)}else void 0!==o.define&&"function"===o.define&&o.define.amd?define("base64",t||[],e):o.base64=e.apply(o)}(0,function(r){var e=r||this.utf8,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return{encode:function(r){if(void 0===e)throw{error:"MissingMethod",message:"UTF8 Module is missing."};if("string"!=typeof r)return r;r=e.encode(r);for(var t,i,n,d,f,a,h,C="",c=0;c<r.length;)d=(t=r.charCodeAt(c++))>>2,f=(3&t)<<4|(i=r.charCodeAt(c++))>>4,a=(15&i)<<2|(n=r.charCodeAt(c++))>>6,h=63&n,isNaN(i)?a=h=64:isNaN(n)&&(h=64),C+=o.charAt(d)+o.charAt(f)+o.charAt(a)+o.charAt(h);return C},decode:function(r){if(void 0===e)throw{error:"MissingMethod",message:"UTF8 Module is missing."};if("string"!=typeof r)return r;r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");for(var t,i,n,d,f,a,h="",C=0;C<r.length;)t=o.indexOf(r.charAt(C++))<<2|(d=o.indexOf(r.charAt(C++)))>>4,i=(15&d)<<4|(f=o.indexOf(r.charAt(C++)))>>2,n=(3&f)<<6|(a=o.indexOf(r.charAt(C++))),h+=String.fromCharCode(t),64!=f&&(h+=String.fromCharCode(i)),64!=a&&(h+=String.fromCharCode(n));return e.decode(h)}}},this,["utf8"]);

View File

@@ -1,14 +0,0 @@
{
"tiddlers": [
{
"file": "base64-utf8.module.min.js",
"fields": {
"type": "application/javascript",
"title": "$:/core/modules/utils/base64-utf8/base64-utf8.module.js",
"module-type": "library"
},
"prefix": "(function(){",
"suffix": "}).call(exports);"
}
]
}

View File

@@ -24,6 +24,26 @@ exports.setStyle = function(element,styles) {
}
};
/*
Remove style properties of an element
element: dom node
styleProperties: ordered array of string property names
*/
exports.removeStyles = function(element, styleProperties) {
for (var i=0; i<styleProperties.length; i++) {
element.style.removeProperty($tw.utils.convertStyleNameToPropertyName(styleProperties[i]));
}
}
/*
Remove single style property of an element
element: dom node
styleProperty: string property name
*/
exports.removeStyle = function(element, styleProperty) {
$tw.utils.removeStyles(element, [styleProperty])
}
/*
Converts a standard CSS property name into the local browser-specific equivalent. For example:
"background-color" --> "backgroundColor"

View File

@@ -210,7 +210,7 @@ Modal.prototype.display = function(title,options) {
bodyWidgetNode.addEventListener("tm-close-tiddler",closeHandler,false);
footerWidgetNode.addEventListener("tm-close-tiddler",closeHandler,false);
// Whether to close the modal dialog when the mask (area outside the modal) is clicked
if(tiddler.fields && (tiddler.fields["mask-closable"] === "yes" || tiddler.fields["mask-closable"] === "true")) {
if(tiddler.fields && (tiddler.fields["mask-closable"] === "yes" || tiddler.fields["mask-closable"] === "true" || tiddler.fields["mask-closable"] === "" || "mask-closable" in tiddler.fields === false)) {
modalBackdrop.addEventListener("click",closeHandler,false);
}
// Set the initial styles for the message

View File

@@ -0,0 +1,126 @@
/*\
title: $:/core/modules/utils/messaging.js
type: application/javascript
module-type: utils-browser
Messaging utilities for use with window.postMessage() etc.
This module intentionally has no dependencies so that it can be included in non-TiddlyWiki projects
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var RESPONSE_TIMEOUT = 2 * 1000;
/*
Class to handle subscribing to publishers
target: Target window (eg iframe.contentWindow)
type: String indicating type of item for which subscriptions are being provided (eg "SAVING")
onsubscribe: Function to be invoked with err parameter when the subscription is established, or there is a timeout
onmessage: Function to be invoked when a new message arrives, invoked with (data,callback). The callback is invoked with the argument (response)
*/
function BrowserMessagingSubscriber(options) {
var self = this;
this.target = options.target;
this.type = options.type;
this.onsubscribe = options.onsubscribe || function() {};
this.onmessage = options.onmessage;
this.hasConfirmed = false;
this.channel = new MessageChannel();
this.channel.port1.addEventListener("message",function(event) {
if(this.timerID) {
clearTimeout(this.timerID);
this.timerID = null;
}
if(event.data) {
if(event.data.verb === "SUBSCRIBED") {
self.hasConfirmed = true;
self.onsubscribe(null);
} else if(event.data.verb === self.type) {
self.onmessage(event.data,function(response) {
// Send the response back on the supplied port, and then close it
event.ports[0].postMessage(response);
event.ports[0].close();
});
}
}
});
// Set a timer so that if we don't hear from the iframe before a timeout we alert the user
this.timerID = setTimeout(function() {
if(!self.hasConfirmed) {
self.onsubscribe("NO_RESPONSE");
}
},RESPONSE_TIMEOUT);
this.channel.port1.start();
this.target.postMessage({verb: "SUBSCRIBE",to: self.type},"*",[this.channel.port2]);
}
exports.BrowserMessagingSubscriber = BrowserMessagingSubscriber;
/*
Class to handle publishing subscriptions
type: String indicating type of item for which subscriptions are being provided (eg "SAVING")
onsubscribe: Function to be invoked when a subscription occurs
*/
function BrowserMessagingPublisher(options) {
var self = this;
this.type = options.type;
this.hostIsListening = false;
this.port = null;
// Listen to connection requests from the host
window.addEventListener("message",function(event) {
if(event.data && event.data.verb === "SUBSCRIBE" && event.data.to === self.type) {
self.hostIsListening = true;
// Acknowledge
self.port = event.ports[0];
self.port.postMessage({verb: "SUBSCRIBED", to: self.type});
if(options.onsubscribe) {
options.onsubscribe(event.data);
}
}
});
}
BrowserMessagingPublisher.prototype.canSend = function() {
return !!this.hostIsListening && !!this.port;
};
BrowserMessagingPublisher.prototype.send = function(data,callback) {
var self = this;
callback = callback || function() {};
// Check that we've been initialised by the host
if(!this.hostIsListening || !this.port) {
return false;
}
// Create a channel for the confirmation
var channel = new MessageChannel();
channel.port1.addEventListener("message",function(event) {
if(event.data && event.data.verb === "OK") {
callback(null);
} else {
callback("BrowserMessagingPublisher for " + self.type + " error: " + (event.data || {}).verb);
}
channel.port1.close();
});
channel.port1.start();
// Send the save request with the port for the response
this.port.postMessage(data,[channel.port2]);
};
BrowserMessagingPublisher.prototype.close = function() {
if(this.port) {
this.port.close();
this.hostIsListening = false;
this.port = null;
}
};
exports.BrowserMessagingPublisher = BrowserMessagingPublisher;
})();

View File

@@ -9,8 +9,6 @@ Various static utility functions.
"use strict";
var base64utf8 = require("$:/core/modules/utils/base64-utf8/base64-utf8.module.js");
/*
Display a message, in colour if we're on a terminal
*/
@@ -842,22 +840,50 @@ if(typeof window !== 'undefined') {
}
}
exports.base64ToBytes = function(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0));
};
exports.bytesToBase64 = function(bytes) {
const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = function(str) {
if ($tw.browser) {
return exports.bytesToBase64(new TextEncoder().encode(str));
} else {
const buff = Buffer.from(str, "utf-8");
return buff.toString("base64");
}
};
exports.base64DecodeUtf8 = function(str) {
if ($tw.browser) {
return new TextDecoder().decode(exports.base64ToBytes(str));
} else {
const buff = Buffer.from(str, "base64");
return buff.toString("utf-8");
}
};
/*
Decode a base64 string
*/
exports.base64Decode = function(string64,binary,urlsafe) {
var encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64;
const encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64;
if(binary) return exports.atob(encoded)
else return base64utf8.base64.decode.call(base64utf8,encoded);
else return exports.base64DecodeUtf8(encoded);
};
/*
Encode a string to base64
*/
exports.base64Encode = function(string64,binary,urlsafe) {
var encoded;
let encoded;
if(binary) encoded = exports.btoa(string64);
else encoded = base64utf8.base64.encode.call(base64utf8,string64);
else encoded = exports.base64EncodeUtf8(string64);
if(urlsafe) {
encoded = encoded.replace(/\+/g,'-').replace(/\//g,'_');
}
@@ -1023,7 +1049,7 @@ exports.makeCompareFunction = function(type,options) {
return compare(dateA,dateB);
},
"version": function(a,b) {
return $tw.utils.compareVersions(a,b);
return compare($tw.utils.compareVersions(a,b),0);
},
"alphanumeric": function(a,b) {
if(!isCaseSensitive) {

View File

@@ -0,0 +1,103 @@
/*\
title: $:/core/modules/widgets/audio.js
type: application/javascript
module-type: widget
Basic Audio widget for displaying audio files.
This is a simple implementation that can be overridden by plugins
for more advanced functionality.
\*/
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var AudioWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
AudioWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
AudioWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
// Create audio element
var audioElement = this.document.createElement("audio");
audioElement.setAttribute("controls", this.getAttribute("controls", "controls"));
audioElement.setAttribute("style", this.getAttribute("style", "width: 100%; object-fit: contain"));
audioElement.className = "tw-audio-element";
// Set source
if(this.audioSource) {
if (this.audioSource.indexOf("data:") === 0) {
audioElement.setAttribute("src", this.audioSource);
} else {
var sourceElement = this.document.createElement("source");
sourceElement.setAttribute("src", this.audioSource);
if(this.audioType) {
sourceElement.setAttribute("type", this.audioType);
}
audioElement.appendChild(sourceElement);
}
}
// Insert the audio into the DOM
parent.insertBefore(audioElement, nextSibling);
this.domNodes.push(audioElement);
};
/*
Compute the internal state of the widget
*/
AudioWidget.prototype.execute = function() {
// Get the audio source and type
this.audioSource = this.getAttribute("src");
this.audioType = this.getAttribute("type");
this.audioControls = this.getAttribute("controls", "controls");
// Try to get from tiddler attribute
if(!this.audioSource && this.getAttribute("tiddler")) {
var tiddlerTitle = this.getAttribute("tiddler");
var tiddler = this.wiki.getTiddler(tiddlerTitle);
if(tiddler) {
if(tiddler.fields._canonical_uri) {
this.audioSource = tiddler.fields._canonical_uri;
this.audioType = tiddler.fields.type;
} else if(tiddler.fields.text) {
this.audioSource = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
this.audioType = tiddler.fields.type;
}
}
}
// Make sure we have a tiddler for saving timestamps
this.tiddlerTitle = this.getAttribute("tiddler");
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
AudioWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.src || changedAttributes.type || changedAttributes.controls || changedAttributes.tiddler) {
this.refreshSelf();
return true;
} else {
return false;
}
};
exports.audio = AudioWidget;

View File

@@ -61,6 +61,10 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
sourcePrefix: "data-",
destPrefix: "data-"
});
this.assignAttributes(domNode,{
sourcePrefix: "aria-",
destPrefix: "aria-"
});
// Assign other attributes
if(this.style) {
domNode.setAttribute("style",this.style);
@@ -68,9 +72,6 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
if(this.tooltip) {
domNode.setAttribute("title",this.tooltip);
}
if(this["aria-label"]) {
domNode.setAttribute("aria-label",this["aria-label"]);
}
if (this.role) {
domNode.setAttribute("role", this.role);
}
@@ -215,7 +216,6 @@ ButtonWidget.prototype.execute = function() {
this.setTo = this.getAttribute("setTo");
this.popup = this.getAttribute("popup");
this.hover = this.getAttribute("hover");
this["aria-label"] = this.getAttribute("aria-label");
this.role = this.getAttribute("role");
this.tooltip = this.getAttribute("tooltip");
this.style = this.getAttribute("style");
@@ -271,6 +271,10 @@ ButtonWidget.prototype.refresh = function(changedTiddlers) {
sourcePrefix: "data-",
destPrefix: "data-"
});
this.assignAttributes(this.domNodes[0],{
sourcePrefix: "aria-",
destPrefix: "aria-"
});
}
return this.refreshChildren(changedTiddlers);
};

View File

@@ -74,6 +74,8 @@ ElementWidget.prototype.render = function(parent,nextSibling) {
// Create the DOM node and render children
var domNode = this.document.createElementNS(this.namespace,this.tag);
this.assignAttributes(domNode,{excludeEventAttributes: true});
// Allow hooks to manipulate the DOM node. Eg: Add debug info
$tw.hooks.invokeHook("th-dom-rendering-element", domNode, this);
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);

View File

@@ -44,7 +44,7 @@ EventWidget.prototype.render = function(parent,nextSibling) {
domNode.addEventListener(type,function(event) {
var selector = self.getAttribute("selector"),
matchSelector = self.getAttribute("matchSelector"),
actions = self.getAttribute("$"+type) || self.getAttribute("actions-"+type),
actions = self.getAttribute("$"+type),
stopPropagation = self.getAttribute("stopPropagation","onaction"),
selectedNode = event.target,
selectedNodeRect,
@@ -122,9 +122,6 @@ EventWidget.prototype.execute = function() {
self.types.push(key.slice(1));
}
});
if(!this.types.length) {
this.types = this.getAttribute("events","").split(" ");
}
this.elementTag = this.getAttribute("tag");
// Make child widgets
this.makeChildWidgets();

View File

@@ -45,6 +45,10 @@ LinkWidget.prototype.render = function(parent,nextSibling) {
sourcePrefix: "data-",
destPrefix: "data-"
});
this.assignAttributes(domNode,{
sourcePrefix: "aria-",
destPrefix: "aria-"
});
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
@@ -125,9 +129,13 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
});
domNode.setAttribute("title",tooltipText);
}
if(this["aria-label"]) {
domNode.setAttribute("aria-label",this["aria-label"]);
if(this.role) {
domNode.setAttribute("role",this.role);
}
this.assignAttributes(domNode,{
sourcePrefix: "aria-",
destPrefix: "aria-"
})
// Add a click event handler
$tw.utils.addEventListeners(domNode,[
{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"},
@@ -139,6 +147,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
dragTiddlerFn: function() {return self.to;},
widget: this
});
} else if(this.draggable === "no") {
domNode.setAttribute("draggable","false");
}
// Assign data- attributes
this.assignAttributes(domNode,{
@@ -188,7 +198,7 @@ LinkWidget.prototype.execute = function() {
// Pick up our attributes
this.to = this.getAttribute("to",this.getVariable("currentTiddler"));
this.tooltip = this.getAttribute("tooltip");
this["aria-label"] = this.getAttribute("aria-label");
this.role = this.getAttribute("role");
this.linkClasses = this.getAttribute("class");
this.overrideClasses = this.getAttribute("overrideClass");
this.tabIndex = this.getAttribute("tabindex");

View File

@@ -94,8 +94,6 @@ RangeWidget.prototype.getActionVariables = function(options) {
// actionsStart
RangeWidget.prototype.handleMouseDownEvent = function(event) {
this.mouseDown = true; // TODO remove once IE is gone.
this.startValue = this.inputDomNode.value; // TODO remove this line once IE is gone!
this.handleEvent(event);
// Trigger actions
if(this.actionsMouseDown) {
@@ -106,26 +104,16 @@ RangeWidget.prototype.handleMouseDownEvent = function(event) {
// actionsStop
RangeWidget.prototype.handleMouseUpEvent = function(event) {
this.mouseDown = false; // TODO remove once IE is gone.
this.handleEvent(event);
// Trigger actions
if(this.actionsMouseUp) {
var variables = this.getActionVariables()
this.invokeActionString(this.actionsMouseUp,this,event,variables);
}
// TODO remove the following if() once IE is gone!
if ($tw.browser.isIE) {
if (this.startValue !== this.inputDomNode.value) {
this.handleChangeEvent(event);
this.startValue = this.inputDomNode.value;
}
}
}
RangeWidget.prototype.handleChangeEvent = function(event) {
if (this.mouseDown) { // TODO refactor this function once IE is gone.
this.handleInputEvent(event);
}
this.handleInputEvent(event);
};
RangeWidget.prototype.handleInputEvent = function(event) {
@@ -152,8 +140,6 @@ RangeWidget.prototype.handleEvent = function(event) {
Compute the internal state of the widget
*/
RangeWidget.prototype.execute = function() {
// TODO remove the next 1 lines once IE is gone!
this.mouseUp = true; // Needed for IE10
// Get the parameters from the attributes
this.tiddlerTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.tiddlerField = this.getAttribute("field","text");

View File

@@ -181,7 +181,7 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
};
ScrollableWidget.prototype.listenerFunction = function(event) {
self = this;
var self = this;
clearTimeout(this.timeout);
this.timeout = setTimeout(function() {
var existingTiddler = self.wiki.getTiddler(self.scrollableBind),

View File

@@ -1443,7 +1443,7 @@ exports.search = function(text,options) {
// Don't search the text field if the content type is binary
var fieldName = searchFields[fieldIndex];
if(fieldName === "text" && contentTypeInfo.encoding !== "utf8") {
break;
continue;
}
var str = tiddler.fields[fieldName],
t;

View File

@@ -47,6 +47,7 @@ modal-footer-background: #f5f5f5
modal-footer-border: #dddddd
modal-header-border: #eeeeee
muted-foreground: #bbb
network-activity-foreground: <<colour primary>>
notification-background: #ffffdd
notification-border: #999999
page-background: #6f6f70
@@ -56,22 +57,26 @@ primary: #29a6ee
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: #000000
sidebar-controls-foreground-hover: #222222
sidebar-controls-foreground: #c2c1c2
sidebar-foreground-shadow: rgba(255,255,255,0)
sidebar-foreground-shadow: transparent
sidebar-foreground: #d3d2d4
sidebar-muted-foreground-hover: #444444
sidebar-muted-foreground-hover: #333333
sidebar-muted-foreground: #c0c0c0
sidebar-tab-background-selected: #6f6f70
sidebar-tab-background: #666667
sidebar-tab-border-selected: #999
sidebar-tab-border: #515151
sidebar-tab-divider: #999
sidebar-tab-foreground-selected:
sidebar-tab-foreground: #999
sidebar-tab-foreground-selected: #bfbfbf
sidebar-tab-foreground: #b0b0b0
sidebar-tiddler-link-foreground-hover: #444444
sidebar-tiddler-link-foreground: #d1d0d2
sidebar-tiddler-link-foreground: #aaaaaa
site-title-foreground: <<colour tiddler-title-foreground>>
stability-deprecated: #bf616a
stability-experimental: #d08770
stability-legacy: #88c0d0
stability-stable: #a3be8c
static-alert-foreground: #aaaaaa
tab-background-selected: #ffffff
tab-background: #d8d8d8

View File

@@ -1,12 +1,13 @@
title: $:/core/templates/tiddlywiki5-external-js.html
<$set name="saveTiddlerAndShadowsFilter" filter="[subfilter<saveTiddlerFilter>] [subfilter<saveTiddlerFilter>plugintiddlers[]]">
<$set name="rawMarkupFilter" filter="[enlist<saveTiddlerAndShadowsFilter>] +[[$:/core]plugintiddlers[]]">
`<!doctype html>
`{{$:/core/templates/MOTW.html}}`<html lang="`<$text text={{{ [{$:/language}get[name]] }}}/>`">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<!--~~ Raw markup for the top of the head section ~~-->
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkupWikified/TopHead]] ||$:/core/templates/raw-static-tiddler}}}`
`{{{ [enlist<rawMarkupFilter>tag[$:/tags/RawMarkupWikified/TopHead]] ||$:/core/templates/raw-static-tiddler}}}`
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
<meta name="application-name" content="TiddlyWiki" />
<meta name="generator" content="TiddlyWiki" />
@@ -22,13 +23,13 @@ title: $:/core/templates/tiddlywiki5-external-js.html
<!--~~ This is a Tiddlywiki file. The points of interest in the file are marked with this pattern ~~-->
<!--~~ Raw markup ~~-->
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/core/wiki/rawmarkup]] ||$:/core/templates/plain-text-tiddler}}}`
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}}`
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}`
`{{{ [enlist<rawMarkupFilter>tag[$:/core/wiki/rawmarkup]] ||$:/core/templates/plain-text-tiddler}}}`
`{{{ [enlist<rawMarkupFilter>tag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}}`
`{{{ [enlist<rawMarkupFilter>tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}`
</head>
<body class="tc-body">
<!--~~ Raw markup for the top of the body section ~~-->
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkupWikified/TopBody]] ||$:/core/templates/raw-static-tiddler}}}`
`{{{ [enlist<rawMarkupFilter>tag[$:/tags/RawMarkupWikified/TopBody]] ||$:/core/templates/raw-static-tiddler}}}`
<!--~~ Static styles ~~-->
<div id="styleArea">
`{{$:/boot/boot.css||$:/core/templates/css-tiddler}}`
@@ -42,9 +43,10 @@ title: $:/core/templates/tiddlywiki5-external-js.html
<!--~~ Ordinary tiddlers ~~-->
`{{$:/core/templates/store.area.template.html}}`
<!--~~ Raw markup for the bottom of the body section ~~-->
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkupWikified/BottomBody]] ||$:/core/templates/raw-static-tiddler}}}`
`{{{ [enlist<rawMarkupFilter>tag[$:/tags/RawMarkupWikified/BottomBody]] ||$:/core/templates/raw-static-tiddler}}}`
<!--~~ Load external JavaScripts ~~-->
<script src="`{{{ [<coreURL>] }}}`" onerror="alert('Error: Cannot load `{{{ [<coreURL>] }}}`');"></script>
</body>
</html>`
</$set>
</$set>

View File

@@ -62,28 +62,34 @@ caption: {{$:/language/Search/Filter/Caption}}
</$list>
\end
\procedure input-actions()
<%if [<event-key-descriptor>match[((input-tab-right))]] %>
<<set-next-input-tab>>
<%elseif [<event-key-descriptor>match[((input-tab-left))]] %>
<<set-previous-input-tab>>
<%endif%>
\end
\whitespace trim
<<lingo Filter/Hint>>
<div class="tc-search tc-advanced-search">
<$keyboard key="((input-tab-right))" actions=<<set-next-input-tab>> class="tc-small-gap-right">
<$keyboard key="((input-tab-left))" actions=<<set-previous-input-tab>>>
<$transclude $variable="keyboard-driven-input"
tiddler="$:/temp/advancedsearch/input"
storeTitle="$:/temp/advancedsearch"
refreshTitle="$:/temp/advancedsearch/refresh"
selectionStateTitle="$:/temp/advancedsearch/selected-item"
type="search"
tag="input"
focus={{$:/config/Search/AutoFocus}}
configTiddlerFilter="[[$:/temp/advancedsearch]]"
firstSearchFilterField="text"
inputAcceptActions=<<input-accept-actions>>
inputAcceptVariantActions=<<input-accept-variant-actions>>
inputCancelActions=<<cancel-search-actions>>
/>
</$keyboard>
<$keyboard key="((input-tab-right)) ((input-tab-left))" actions=<<input-actions>> class="tc-small-gap-right">
<$transclude $variable="keyboard-driven-input"
tiddler="$:/temp/advancedsearch/input"
storeTitle="$:/temp/advancedsearch"
refreshTitle="$:/temp/advancedsearch/refresh"
selectionStateTitle="$:/temp/advancedsearch/selected-item"
type="search"
tag="input"
focus={{$:/config/Search/AutoFocus}}
configTiddlerFilter="[[$:/temp/advancedsearch]]"
firstSearchFilterField="text"
inputAcceptActions=<<input-accept-actions>>
inputAcceptVariantActions=<<input-accept-variant-actions>>
inputCancelActions=<<cancel-search-actions>>
/>
</$keyboard>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/AdvancedSearch/FilterButton]!has[draft.of]]">
<$transclude/>

View File

@@ -53,13 +53,20 @@ first-search-filter: [all[shadows]search<userInput>sort[title]limit[250]] -[[$:/
</$list></$list>
\end
\procedure input-actions()
<%if [<event-key-descriptor>match[((input-tab-right))]] %>
<<set-next-input-tab>>
<%elseif [<event-key-descriptor>match[((input-tab-left))]] %>
<<set-previous-input-tab>>
<%endif%>
\end
\whitespace trim
<<lingo Shadows/Hint>>
<div class="tc-search">
<$keyboard key="((input-tab-right))" actions=<<set-next-input-tab>>>
<$keyboard key="((input-tab-left))" actions=<<set-previous-input-tab>>>
<$keyboard key="((input-tab-right)) ((input-tab-left))" actions=<<input-actions>>>
<$transclude $variable="keyboard-driven-input"
tiddler="$:/temp/advancedsearch/input"
storeTitle="$:/temp/advancedsearch"
@@ -74,7 +81,6 @@ first-search-filter: [all[shadows]search<userInput>sort[title]limit[250]] -[[$:/
inputAcceptVariantActions=<<input-accept-variant-actions>>
filterMinLength={{$:/config/Search/MinLength}}/>
</$keyboard>
</$keyboard>
&#32;
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
<$button class="tc-btn-invisible">

View File

@@ -17,15 +17,24 @@ caption: {{$:/language/Search/Standard/Caption}}
\procedure input-accept-variant-actions() <$list filter="[{$:/config/Search/NavigateOnEnter/enable}match[yes]]" emptyMessage="<$list filter='[<tiddler>get[text]!is[missing]] :else[<tiddler>get[text]is[shadow]]'><$list filter='[<tiddler>get[text]minlength[1]]'><$action-sendmessage $message='tm-edit-tiddler' $param={{{ [<tiddler>get[text]] }}}/></$list></$list>"><$list filter="[<tiddler>get[text]minlength[1]]"><$action-sendmessage $message="tm-edit-tiddler" $param={{{ [<tiddler>get[text]] }}}/></$list></$list>
\procedure input-actions()
<%if [<event-code>match[ArrowRight]] :and[<modifier>match[alt-shift]] %>
<<next-search-tab>>
<%elseif [<event-code>match[ArrowLeft]] :and[<modifier>match[alt-shift]] %>
<<previous-search-tab>>
<%elseif [<event-key-descriptor>match[((input-tab-right))]] %>
<<set-next-input-tab>>
<%elseif [<event-key-descriptor>match[((input-tab-left))]] %>
<<set-previous-input-tab>>
<%endif%>
\end
\whitespace trim
<<lingo Standard/Hint>>
<div class="tc-search">
<$keyboard key="((input-tab-right))" actions=<<set-next-input-tab>>>
<$keyboard key="((input-tab-left))" actions=<<set-previous-input-tab>>>
<$keyboard key="shift-alt-Right" actions=<<next-search-tab>>>
<$keyboard key="shift-alt-Left" actions=<<previous-search-tab>>>
<$keyboard key="((input-tab-right)) ((input-tab-left)) shift-alt-Right shift-alt-Left" actions=<<input-actions>>>
<$transclude $variable="keyboard-driven-input"
tiddler="$:/temp/advancedsearch/input"
storeTitle="$:/temp/advancedsearch"
@@ -40,9 +49,6 @@ caption: {{$:/language/Search/Standard/Caption}}
configTiddlerFilter="[[$:/state/advancedsearch/standard/currentTab]!is[missing]get[text]] :else[{$:/config/SearchResults/Default}]"
filterMinLength={{$:/config/Search/MinLength}}/>
</$keyboard>
</$keyboard>
</$keyboard>
</$keyboard>
&#32;
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
<$button class="tc-btn-invisible">

View File

@@ -52,13 +52,20 @@ first-search-filter: [is[system]search<userInput>sort[title]limit[250]] :except[
</$list></$list>
\end
\procedure input-actions()
<%if [<event-key-descriptor>match[((input-tab-right))]] %>
<<set-next-input-tab>>
<%elseif [<event-key-descriptor>match[((input-tab-left))]] %>
<<set-previous-input-tab>>
<%endif%>
\end
\whitespace trim
<<lingo System/Hint>>
<div class="tc-search">
<$keyboard key="((input-tab-right))" actions=<<set-next-input-tab>>>
<$keyboard key="((input-tab-left))" actions=<<set-previous-input-tab>>>
<$keyboard key="((input-tab-right)) ((input-tab-left))" actions=<<input-actions>>>
<$transclude $variable="keyboard-driven-input"
tiddler="$:/temp/advancedsearch/input"
storeTitle="$:/temp/advancedsearch"
@@ -73,7 +80,6 @@ first-search-filter: [is[system]search<userInput>sort[title]limit[250]] :except[
inputAcceptVariantActions=<<input-accept-variant-actions>>
filterMinLength={{$:/config/Search/MinLength}}/>
</$keyboard>
</$keyboard>
&#32;
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="">
<$button class="tc-btn-invisible">

View File

@@ -2,9 +2,9 @@ code-body: yes
title: $:/core/ui/AlertTemplate
\whitespace trim
<div class="tc-alert">
<div class="tc-alert" role="alertdialog">
<div class="tc-alert-toolbar">
<$button class="tc-btn-invisible"><$action-deletetiddler $tiddler=<<currentTiddler>>/>{{$:/core/images/cancel-button}}</$button>
<$button class="tc-btn-invisible" aria-label={{$:/language/Buttons/Close/Caption}}><$action-deletetiddler $tiddler=<<currentTiddler>>/>{{$:/core/images/cancel-button}}</$button>
</div>
<div class="tc-alert-subtitle">
<$wikify name="format" text=<<lingo Tiddler/DateFormat>>>
@@ -19,7 +19,7 @@ title: $:/core/ui/AlertTemplate
</$reveal>
</$wikify>
</div>
<div class="tc-alert-body">
<div class="tc-alert-body" role="alert" aria-atomic="true">
<$transclude/>

View File

@@ -17,13 +17,11 @@ tags: $:/tags/EditTemplate
<$let backgroundColor=<<colour>> >
<span class="tc-tag-label tc-tag-list-item tc-small-gap-right"
data-tag-title=<<currentTiddler>>
style=`color:$(foregroundColor)$; fill:$(foregroundColor)$; background-color:$(backgroundColor)$;`
style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$;`
>
<$transclude tiddler=<<icon>>/>
<$view field="title" format="text"/>
<$button class="tc-btn-invisible tc-remove-tag-button"
style.fill=<<foregroundColor>>
>
<$button class="tc-btn-invisible tc-remove-tag-button">
<$action-listops $tiddler=<<saveTiddler>> $field=<<tagField>> $subfilter="-[{!!title}]"/>
{{$:/core/images/close-button}}
</$button>

View File

@@ -10,7 +10,7 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
<em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
<div class="tc-type-selector-dropdown-wrapper">
<div class="tc-type-selector"><$fieldmangler>
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>> selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>> selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle tc-keep-focus" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>
</$fieldmangler></div>
<div class="tc-block-dropdown-wrapper">

View File

@@ -37,8 +37,6 @@ title: $:/core/ui/ImportListing
\define suppressedField() suppressed-$(payloadTiddler)$
\define newImportTitleTiddler() $:/temp/NewImportTitle-$(payloadTiddler)$
\define previewPopupState() $(currentTiddler)$!!popup-$(payloadTiddler)$
\define renameFieldState() $(currentTiddler)$!!state-rename-$(payloadTiddler)$
@@ -102,19 +100,7 @@ title: $:/core/ui/ImportListing
<$reveal type="match" state=<<renameFieldState>> text="yes" tag="tr">
<td colspan="3">
<div class="tc-flex">
<$edit-text tiddler=<<newImportTitleTiddler>> default={{{[subfilter<payloadTitleFilter>]}}} tag="input" class="tc-import-rename tc-flex-grow-1"/>
<span class="tc-small-gap-left">
<$button class="tc-btn-invisible" set=<<renameFieldState>> setTo="no" tooltip={{{[<lingo-base>addsuffix[Listing/Rename/CancelRename]get[text]]}}}>
{{$:/core/images/close-button}}
<$action-deletetiddler $tiddler=<<newImportTitleTiddler>>/>
</$button>
<span class="tc-small-gap-right"/>
</span>
<$button class="tc-btn-invisible" set=<<renameFieldState>> setTo="no" tooltip={{{[<lingo-base>addsuffix[Listing/Rename/ConfirmRename]get[text]]}}}>
{{$:/core/images/done-button}}
<$action-setfield $field=<<renameField>> $value={{{[<newImportTitleTiddler>get[text]minlength[1]else<payloadTiddler>]}}} />
<$action-deletetiddler $tiddler=<<newImportTitleTiddler>>/>
</$button>
<$edit-text field=<<renameField>> default={{{[<payloadTiddler>]}}} tag="input" class="tc-import-rename tc-flex-grow-1"/>
</div>
</td>
</$reveal>

View File

@@ -1,8 +1,8 @@
title: $:/core/ui/PageTemplate/alerts
tags: $:/tags/PageTemplate
<div class="tc-alerts">
<div class="tc-alerts" role="region" aria-label="Alerts">
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Alert]!has[draft.of]]" template="$:/core/ui/AlertTemplate" storyview="pop"/>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Alert]!is[draft]]" template="$:/core/ui/AlertTemplate" storyview="pop"/>
</div>

View File

@@ -46,13 +46,21 @@ tags: $:/tags/SideBarSegment
\procedure advanced-search-actions() <$action-setfield $tiddler="$:/temp/advancedsearch" text={{$:/temp/search/input}}/><$action-setfield $tiddler="$:/temp/advancedsearch/input" text={{$:/temp/search/input}}/><<delete-state-tiddlers>><$action-navigate $to="$:/AdvancedSearch"/><$action-setfield $tiddler="$:/temp/advancedsearch/refresh" text="yes"/><$action-sendmessage $message="tm-focus-selector" $param="""[data-tiddler-title="$:/AdvancedSearch"] .tc-search input""" preventScroll="true"/><$action-deletetiddler $filter="$:/temp/search $:/temp/search/input $:/temp/search/refresh [<searchListState>]"/>
\procedure input-actions()
<%if [<event-key-descriptor>match[((input-tab-right))]] %>
<<set-next-input-tab>>
<%elseif [<event-key-descriptor>match[((input-tab-left))]] %>
<<set-previous-input-tab>>
<%elseif [<event-key-descriptor>match[((advanced-search-sidebar))]] %>
<<advanced-search-actions>>
<%endif%>
\end
<div class="tc-sidebar-lists tc-sidebar-search">
<$vars editTiddler="$:/temp/search" searchTiddler="$:/temp/search/input" searchListState=<<qualify "$:/state/search-list/selected-item">>>
<div class="tc-search">
<$keyboard key="((input-tab-right))" actions=<<set-next-input-tab>>>
<$keyboard key="((input-tab-left))" actions=<<set-previous-input-tab>>>
<$keyboard key="((advanced-search-sidebar))" actions=<<advanced-search-actions>>>
<$keyboard key="((input-tab-right)) ((input-tab-left)) ((advanced-search-sidebar))" actions=<<input-actions>>>
<form class="tc-form-inline">
<$transclude $variable="keyboard-driven-input" tiddler=<<editTiddler>> storeTitle=<<searchTiddler>>
selectionStateTitle=<<searchListState>> refreshTitle="$:/temp/search/refresh" type="search"
@@ -62,8 +70,6 @@ tags: $:/tags/SideBarSegment
configTiddlerFilter="[[$:/state/search/currentTab]!is[missing]get[text]] :else[{$:/config/SearchResults/Default}]"/>
</form>
</$keyboard>
</$keyboard>
</$keyboard>
<$reveal state=<<searchTiddler>> type="nomatch" text="">
<$button tooltip={{$:/language/Buttons/AdvancedSearch/Hint}} aria-label={{$:/language/Buttons/AdvancedSearch/Caption}} class="tc-btn-invisible">
<<advanced-search-actions>>

View File

@@ -2,7 +2,7 @@ title: $:/core/ui/ViewTemplate/title
tags: $:/tags/ViewTemplate
\whitespace trim
\define title-styles() fill:$(foregroundColor)$;
\define title-styles() color:$(foregroundColor)$;
<div class="tc-tiddler-title tc-clearfix">
<div class="tc-titlebar">

View File

@@ -120,11 +120,21 @@ tags: $:/tags/Macro
\procedure keyboard-driven-input(tiddler,storeTitle,field:"text",index:"",tag:"input",type,focus:"",inputAcceptActions,inputAcceptVariantActions,inputCancelActions,placeholder:"",default:"",class,focusPopup,rows,minHeight,tabindex,size,autoHeight,filterMinLength:"0",refreshTitle,selectionStateTitle,cancelPopups:"",configTiddlerFilter,firstSearchFilterField:"first-search-filter",secondSearchFilterField:"second-search-filter")
\whitespace trim
<$keyboard key="((input-accept))" actions=<<inputAcceptActions>>>
<$keyboard key="((input-accept-variant))" actions=<<inputAcceptVariantActions>>>
<$keyboard key="((input-up))" actions=<<input-next-actions-before>>>
<$keyboard key="((input-down))" actions=<<input-next-actions-after>>>
<$keyboard key="((input-cancel))" actions=<<inputCancelActions>>>
\procedure keyboard-driven-input-actions()
<%if [<event-key-descriptor>match[((input-accept))]] %>
<<inputAcceptActions>>
<%elseif [<event-key-descriptor>match[((input-accept-variant))]] %>
<<inputAcceptVariantActions>>
<%elseif [<event-key-descriptor>match[((input-up))]] %>
<<input-next-actions-before>>
<%elseif [<event-key-descriptor>match[((input-down))]] %>
<<input-next-actions-after>>
<%elseif [<event-key-descriptor>match[((input-cancel))]] %>
<<inputCancelActions>>
<%endif%>
\end keyboard-driven-input-actions
<$keyboard key="((input-accept)) ((input-accept-variant)) ((input-up)) ((input-down)) ((input-cancel))" actions=<<keyboard-driven-input-actions>>>
<$edit-text
tiddler=<<tiddler>> field=<<field>> index=<<index>>
inputActions=<<keyboard-input-actions>> tag=<<tag>> class=<<class>>
@@ -134,8 +144,4 @@ tags: $:/tags/Macro
refreshTitle=<<refreshTitle>> cancelPopups=<<cancelPopups>>
/>
</$keyboard>
</$keyboard>
</$keyboard>
</$keyboard>
</$keyboard>
\end
\end keyboard-driven-input

View File

@@ -24,44 +24,49 @@ tags: $:/tags/Macro
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate)
\whitespace trim
<span class="tc-links-draggable-list">
<$vars targetTiddler="""$tiddler$""" targetField="""$field$""">
<$genesis $type=<<__type__>> class="$class$">
<$list filter="[list[$tiddler$!!$field$]]" emptyMessage=<<__emptyMessage__>>>
<$droppable
actions=<<list-links-draggable-drop-actions>>
tag="""$subtype$"""
enable=<<tv-enable-drag-and-drop>>
>
<div class="tc-droppable-placeholder"/>
<div>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$let tv-wikilinks="no">
<$transclude field="caption">
<$view field="title"/>
</$transclude>
</$let>
</$link>
</$transclude>
</div>
</$droppable>
</$list>
<$tiddler tiddler="">
<$droppable
actions=<<list-links-draggable-drop-actions>>
tag="div"
enable=<<tv-enable-drag-and-drop>>
>
<div class="tc-droppable-placeholder">
{{$:/core/images/blank}}
</div>
<div style="height:0.5em;"/>
</$droppable>
</$tiddler>
</$genesis>
</$vars>
</span>
<$set name="_tiddler" value="""$tiddler$""" emptyValue=<<currentTiddler>> >
<$let field-reference={{{ [<_tiddler>] "!!" [[$field$]] +[join[]] }}}
targetTiddler=<<_tiddler>>
targetField="""$field$"""
>
<span class="tc-links-draggable-list">
<$genesis $type=<<__type__>> class="$class$">
<$list filter="[list<field-reference>]" emptyMessage=<<__emptyMessage__>>>
<$droppable
actions=<<list-links-draggable-drop-actions>>
tag="""$subtype$"""
enable=<<tv-enable-drag-and-drop>>
>
<div class="tc-droppable-placeholder"/>
<div>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$let tv-wikilinks="no">
<$transclude field="caption">
<$view field="title"/>
</$transclude>
</$let>
</$link>
</$transclude>
</div>
</$droppable>
</$list>
<$tiddler tiddler="">
<$droppable
actions=<<list-links-draggable-drop-actions>>
tag="div"
enable=<<tv-enable-drag-and-drop>>
>
<div class="tc-droppable-placeholder">
{{$:/core/images/blank}}
</div>
<div style="height:0.5em;"/>
</$droppable>
</$tiddler>
</$genesis>
</span>
</$let>
</$set>
\end
\define list-tagged-draggable-drop-actions(tag)

View File

@@ -3,7 +3,6 @@ tags: $:/tags/Macro
\define tag-pill-styles()
background-color:$(backgroundColor)$;
fill:$(foregroundColor)$;
color:$(foregroundColor)$;
\end

View File

@@ -21,7 +21,7 @@ tags: $:/tags/Macro
style="width:$width$px;height:$height$px;background-color:$background-color$;"
></$reveal></div><div
class="tc-thumbnail-icon"
style="fill:$color$;color:$color$;"
style="color:$color$;"
>$icon$</div><div class="tc-thumbnail-caption">$caption$</div></div></$link>
\end

View File

@@ -1,44 +0,0 @@
title: CloudData
type: application/json
[
{"text": "Tokyo/Yokohama", "size": 33.200},
{"text": "New York Metro", "size": 17.800},
{"text": "Sao Paulo", "size": 17.700},
{"text": "Seoul/Incheon", "size": 17.500},
{"text": "Mexico City", "size": 17.400},
{"text": "Osaka/Kobe/Kyoto", "size": 16.425},
{"text": "Manila", "size": 14.750},
{"text": "Mumbai", "size": 14.350},
{"text": "Delhi", "size": 14.300},
{"text": "Jakarta", "size": 14.250},
{"text": "Lagos", "size": 13.400},
{"text": "Kolkata", "size": 12.700},
{"text": "Cairo", "size": 12.200},
{"text": "Los Angeles", "size": 11.789},
{"text": "Buenos Aires", "size": 11.200},
{"text": "Rio de Janeiro", "size": 10.800},
{"text": "Moscow", "size": 10.500},
{"text": "Shanghai", "size": 10.000},
{"text": "Karachi", "size": 9.800},
{"text": "Paris", "size": 9.645},
{"text": "Istanbul", "size": 9.000},
{"text": "Nagoya", "size": 9.000},
{"text": "Beijing", "size": 8.614},
{"text": "Chicago", "size": 8.308},
{"text": "London", "size": 8.278},
{"text": "Shenzhen", "size": 8.000},
{"text": "Essen/Dusseldorf", "size": 7.350},
{"text": "Tehran", "size": 7.250},
{"text": "Bogota", "size": 7.000},
{"text": "Lima", "size": 7.000},
{"text": "Bangkok", "size": 6.500},
{"text": "Johannesburg/East Rand", "size": 6.000},
{"text": "Chennai", "size": 5.950},
{"text": "Taipei", "size": 5.700},
{"text": "Baghdad", "size": 5.500},
{"text": "Santiago", "size": 5.425},
{"text": "Bangalore", "size": 5.400},
{"text": "Hyderabad", "size": 5.300},
{"text": "St Petersburg", "size": 5.300}
]

View File

@@ -1,3 +0,0 @@
title: $:/DefaultTiddlers
[[HelloThere]]

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +0,0 @@
title: HelloThere
This is a demo of TiddlyWiki5 incorporating a plugin for the [[D3.js]] visualisation library.
! Word Cloud
<$d3cloud data="CloudData" spiral={{$:/spiral}}/>
//[[Raw data|CloudData]]//
! Bar Chart
<$d3bar grouped={{$:/grouped}} data="GraphData"/>
<$button set="$:/grouped" setTo="yes">grouped</$button> <$button set="$:/grouped" setTo="no">stacked</$button>
//[[Raw data|GraphData]]//

View File

@@ -1,3 +0,0 @@
title: $:/SiteSubtitle
a demo of the D3.js plugin for TiddlyWiki5

View File

@@ -1,3 +0,0 @@
title: $:/SiteTitle
d3demo

View File

@@ -1,3 +0,0 @@
title: $:/grouped
yes

View File

@@ -1,3 +0,0 @@
title: $:/spiral
archimedean

View File

@@ -1,16 +0,0 @@
{
"description": "Demo of the D3 plugin",
"plugins": [
"tiddlywiki/d3"
],
"themes": [
"tiddlywiki/vanilla",
"tiddlywiki/snowwhite"
],
"includeWikis": [
],
"build": {
"index": [
"--rendertiddler","$:/core/save/all","d3demo.html","text/plain"]
}
}

View File

@@ -0,0 +1,127 @@
created: 20240802065815656
modified: 20240802065836064
title: How to Create a Custom Cascade Entry
type: text/vnd.tiddlywiki
This guide explains how to add a new [[cascade|https://tiddlywiki.com/#Cascades]] to the ~TiddlyWiki core or your own plugins. This allows third-party plugins to extend the functionality of the core or your plugin.
!! How Cascade Works in the Core
This section explains how the existing WikiText in the core interacts with the new WikiText youll add, only for learning purpose. You dont need to modify the core WikiText when adding a new cascade.
!!! The Default Template as a Fallback
The default behavior in ~TiddlyWiki is defined by [[$:/core/ui/ViewTemplate/tags/default]].
<pre>
<$view tiddler="$:/core" subtiddler="$:/core/ui/ViewTemplate/tags/default" mode=block format=text/>
</pre>
!!! Transclusion of the Active Template
[[$:/core/ui/ViewTemplate/tags]] uses a filter expression to find the cascade filter and the view template youll add.
<pre>
<$view tiddler="$:/core" subtiddler="$:/core/ui/ViewTemplate/tags" mode=block format=text/>
</pre>
The `:cascade` clause collects all tiddlers it finds and uses their filter text sequentially. Most filters wont return any text and will be skipped. The first filter that returns a tiddler title becomes the result of the `:cascade` clause. If no filters return a result, the fallback default filter will be used.
The `:and[!is[blank]else` clause provides additional fallback protection, though its often redundant because a fallback is typically tagged with `$:/tags/ViewTemplateTagsFilter`. However, including fallbacks is a good practice for defensive programming.
!! Adding a New Cascade Entry
This section contains the WikiText youll need to add to the core. Modify it to suit your needs instead of copying it directly.
!!! Creating a Control Panel Tab
To create a new tab under [[ControlPanel|$:/ControlPanel]] → Advanced → [[Cascade|$:/core/ui/ControlPanel/Cascades]], use the following code:
[[$:/core/ui/ControlPanel/ViewTemplateTags]] uses a filter expression to find the cascade filter and the view template youll add.
<pre>
<$view tiddler="$:/core" subtiddler="$:/core/ui/ControlPanel/ViewTemplateTags" mode=block format=text/>
</pre>
Add the following metadata:
```tid
tags: $:/tags/ControlPanel/Cascades
caption: {{$:/language/ControlPanel/ViewTemplateTags/Caption}}
```
!!! Adding a New Language Entry
Its important to add related language files. Create a file starting with `title: $:/language/ControlPanel/`:
```multid
title: $:/language/ControlPanel/
ViewTemplateTags/Caption: View Template Tags
ViewTemplateTags/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the tags area of a tiddler.
```
!!! Adding Default Configuration
Similar to the language file, add a config file starting with `title: $:/config/ViewTemplateTagsFilters/`. For example:
```tid
title: $:/config/ViewTemplateTagsFilters/
tags: $:/tags/ViewTemplateTagsFilter
default: [[$:/core/ui/ViewTemplate/tags/default]]
```
Different templates may have their own config files. Ensure youre adding to the correct file or creating a new one if it doesnt exist.
!! Using the New Cascade
This section provides a simplified example based on a real-world use case. It demonstrates how to override the default template with a custom template.
!!! Your Template
Add the content you want to display conditionally. Update `publisher/plugin-name` to your plugins name.
```tid
code-body: yes
title: $:/plugins/publisher/plugin-name/EditMode
\whitespace trim
<$reveal type="nomatch" stateTitle=<<folded-state>> text="hide" tag="div" retain="yes" animate="yes">
<div class="tc-tags-wrapper" style="display:flex">
<$transclude tiddler="$:/core/ui/EditTemplate/tags"/>
<$button class="tc-btn-invisible" style="margin-left:1em;">
{{$:/core/images/done-button}}
<$action-deletetiddler $tiddler={{{ [[$:/state/edit-view-mode-tags/]addsuffix<storyTiddler>] }}}/>
</$button>
</div>
</$reveal>
```
!!! The Condition
Write a filter that ends with `then[$:/plugins/publisher/plugin-name/EditMode]`.
```tid
code-body: yes
tags: $:/tags/ViewTemplateTagsFilter
title: $:/plugins/publisher/plugin-name/CascadeEditMode
list-before: $:/config/ViewTemplateTagsFilters/default
[[$:/state/edit-view-mode-tags/]addsuffix<currentTiddler>get[text]compare:string:eq[yes]then[$:/plugins/publisher/plugin-name/EditMode]]
```
!!! A Button to Trigger the Condition
```tid
code-body: yes
tags: $:/tags/ViewTemplate/Tags
title: $:/plugins/publisher/plugin-name/TriggerEdit
\whitespace trim
<%if [<storyTiddler>get[tags]!is[blank]] %>
<$button class="tc-btn-invisible" set={{{ [[$:/state/edit-view-mode-tags/]addsuffix<storyTiddler>] }}} setTo="yes" tooltip="add tags">
{{$:/core/images/new-here-button}}
</$button>
<%endif%>
```

View File

@@ -1,9 +1,10 @@
chapter.of: UI and Rendering Pipeline
created: 20140717175203036
modified: 20140717182314488
modified: 20240802065804331
sub.num: 5
tags: doc
title: RootWidget and Rendering Startup
type: text/vnd.tiddlywiki
The previous parts of this chapter showed how WikiText is transformed to DOM nodes which dynamically react to tiddler changes and a way to compose tiddlers from other tiddlers.
This last part describes how the TiddlyWiki core plug-in starts up a UI build from tiddlers and WikiText.
@@ -29,6 +30,9 @@ and a listener is registered at the store which executes the refresh function of
[[Techniques for including other tiddlers and Templates|Transclusion and TextReference]] are finally used in [[$:/core/ui/PageTemplate]] to build the TiddlyWiki UI only from tiddlers written in WikiText (with widgets implemented in javascript):
For example to implement the list of open wiki pages the [[$:/core/ui/PageTemplate]] contains a [[navigator widget|$:/core/modules/widgets/navigator.js]] which maintains a list of open tiddlers in a field of [[$:/StoryList]] and handles events like ``tm-navigate`` by adding a tiddler specified as parameter to the top of the list in [[$:/StoryList]].
The [[story tiddler|$:/core/ui/PageTemplate/story]] transcluded in [[$:/core/ui/PageTemplate]] then uses a ~ListWidget to transclude all tiddlers in [[$:/StoryList]] through a special template [[$:/core/ui/ViewTemplate]].
The ViewTemplate here is a combination of different fragments, like title fragment and body fragment, each fragment can be override individually using [[Cascade Mechanism|How to Create a Custom Cascade Entry]].
A event of the type ``tm-close-tiddler`` would remove a specified tiddler from [[$:/StoryList]].
The [[Event Mechanism]] would trigger a changed event which triggers a call of the ~ListWidget's refresh function which would remove the tiddler from the list, closing the tiddler.

View File

@@ -1,5 +1,6 @@
created: 20141115211411211
title: ReleaseTemplate
code-body: yes
type: text/vnd.tiddlywiki
<h2><$link to=<<currentTab>>><$view tiddler=<<currentTab>> field="title"/></$link></h2>

View File

@@ -19,4 +19,5 @@ The pre-release is also available as an [[empty wiki|https://tiddlywiki.com/prer
</div>
<div class="tc-subtitle">Updated: <$view field="modified" format="date" template={{$:/language/Tiddler/DateFormat}}/></div>
<$transclude mode="block"/>
</$list>

View File

@@ -1,3 +0,0 @@
title: $:/DefaultTiddlers
HelloThere

View File

@@ -1,6 +0,0 @@
title: HelloThere
This is an experimental edition of TiddlyWiki5 for use with [[Tahoe-LAFS|https://tahoe-lafs.org/]]. At this point it is largely for experimentation by @zooko. Click the ''save changes'' button to PUT the updated TiddlyWiki HTML file back to the server.
<$button message="tm-new-tiddler">New Tiddler</$button>
<$button message="tm-save-wiki">Save Changes</$button>

View File

@@ -1,3 +0,0 @@
title: $:/SiteSubtitle
Tahoe-LAFS edition

View File

@@ -1,3 +0,0 @@
title: $:/SiteTitle
TiddlyWiki5

View File

@@ -1,14 +0,0 @@
{
"description": "Demo of TahoeLAFS plugin",
"plugins": [
"tiddlywiki/tahoelafs"
],
"themes": [
"tiddlywiki/vanilla",
"tiddlywiki/snowwhite"
],
"build": {
"index": [
"--rendertiddler","$:/core/save/all","tahoelafs.html","text/plain"]
}
}

View File

@@ -103,7 +103,7 @@ Tests the checkbox widget thoroughly.
];
var indexModeTests = fieldModeTests.map(data => {
var newData = {...data};
var newData = Object.assign({}, data);
var newName = data.testName.replace('field mode', 'index mode');
var tiddlerOneAlreadyExists = false;
var newTiddlers = data.tiddlers.map(tiddler => {
@@ -274,14 +274,13 @@ Tests the checkbox widget thoroughly.
listModeTests
.filter(data => data.widgetText.includes("listField='colors'"))
.map(data => {
var newData = {
...data,
tiddlers: data.tiddlers.map(tiddler => ({...tiddler, list: tiddler.colors, colors: undefined})),
var newData = Object.assign({}, data, {
tiddlers: data.tiddlers.map(tiddler => Object.assign({}, tiddler, {list: tiddler.colors, colors: undefined})),
widgetText: data.widgetText.replace("listField='colors'", "listField='list'"),
expectedChange: {
"Colors": { list: data.expectedChange.Colors.colors.split(' ') }
},
}
})
return newData;
})
);
@@ -289,20 +288,19 @@ Tests the checkbox widget thoroughly.
listModeTests
.filter(data => data.widgetText.includes("listField='colors'"))
.map(data => {
var newData = {
...data,
tiddlers: data.tiddlers.map(tiddler => ({...tiddler, tags: tiddler.colors, colors: undefined})),
var newData = Object.assign({}, data, {
tiddlers: data.tiddlers.map(tiddler => Object.assign({}, tiddler, {tags: tiddler.colors, colors: undefined})),
widgetText: data.widgetText.replace("listField='colors'", "listField='tags'"),
expectedChange: {
"Colors": { tags: data.expectedChange.Colors.colors.split(' ') }
},
}
})
return newData;
})
);
var indexListModeTests = listModeTests.map(data => {
var newData = {...data};
var newData = Object.assign({}, data);
var newName = data.testName.replace('list mode', 'index list mode');
var newTiddlers = data.tiddlers.map(tiddler => {
if (tiddler.hasOwnProperty('colors')) {

View File

@@ -372,13 +372,13 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
it("should handle the variance operator", function() {
expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]variance[]]").join(","))).toBeCloseTo(2.92);
expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]variance[]]").join(","))).toBeCloseTo(3.367);
expect(wiki.filterTiddlers(" +[variance[]]").toString()).toBe("NaN");
expect(wiki.filterTiddlers(" +[variance[]]").toString()).toBe("");
});
it("should handle the standard-deviation operator", function() {
expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]standard-deviation[]]").join(","))).toBeCloseTo(1.71);
expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]standard-deviation[]]").join(","))).toBeCloseTo(1.835);
expect(wiki.filterTiddlers(" +[standard-deviation[]]").toString()).toBe("NaN");
expect(wiki.filterTiddlers(" +[standard-deviation[]]").toString()).toBe("");
});
it("should handle the :intersection prefix", function() {
@@ -420,6 +420,8 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
expect(wiki.filterTiddlers("[tag[cakes]] :sort:string[{!!title}]").join(",")).toBe("cheesecake,Cheesecake,chocolate cake,Chocolate Cake,Persian love cake,Pound cake");
expect(wiki.filterTiddlers("[tag[cakes]] :sort:string:casesensitive[{!!title}]").join(",")).toBe("Cheesecake,Chocolate Cake,Persian love cake,Pound cake,cheesecake,chocolate cake");
expect(wiki.filterTiddlers("[tag[cakes]] :sort:string:casesensitive,reverse[{!!title}]").join(",")).toBe("chocolate cake,cheesecake,Pound cake,Persian love cake,Chocolate Cake,Cheesecake");
expect(wiki.filterTiddlers("1.2.0 1.0.0 1.0.5 :sort:version[{!!title}]").join(",")).toBe("1.0.0,1.0.5,1.2.0");
expect(wiki.filterTiddlers("1.2.0 1.0.0 1.0.5 :sort:version:reverse[{!!title}]").join(",")).toBe("1.2.0,1.0.5,1.0.0");
});
it("should handle the :map prefix", function() {

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