1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-23 03:14:40 +00:00

Compare commits

..

270 Commits

Author SHA1 Message Date
Jermolene
c4b39af052 Version number update for 5.1.18 2018-12-06 09:04:51 +00:00
Jermolene
1c73783ea4 Readme update for v5.1.18 2018-12-06 09:03:22 +00:00
Jermolene
c065dfa4bb Preparing for v5.1.18 release
Phew!
2018-12-06 09:02:05 +00:00
BurningTreeC
a36d8471a8 TOC macro - macrocall wasn't closed (#3622) 2018-12-05 14:30:24 +00:00
BurningTreeC
e3c0616326 Missing stateTitle attribute in unfold viewtemplate (#3617) 2018-12-04 09:13:49 +00:00
BurningTreeC
d00d46a772 Missing stateTitle attribute in subtitle viewtemplate (#3618) 2018-12-04 09:13:27 +00:00
BurningTreeC
40eeba20ef Missing stateTitle attribute in tags viewtemplate (#3619) 2018-12-04 09:13:02 +00:00
BurningTreeC
f163a1f12c Missing stateTitle attr in fold button (#3620) 2018-12-04 09:12:32 +00:00
BurningTreeC
018f7628f5 Remove the last of the new editor styles (reverting) (#3616) 2018-12-03 09:08:11 +00:00
BurningTreeC
bc124c0645 Remove the text-editor box-shadow (reverting) (#3615) 2018-12-03 09:05:52 +00:00
Jermolene
d4716a6f2b Tweak new release banner 2018-12-03 09:03:32 +00:00
BurningTreeC
ad799dbb61 Revert setting styles in framed.js (#3614)
this sets the framed-editor styles to 5.1.17 state
2018-12-03 08:55:16 +00:00
BurningTreeC
31e88dd2c6 Revert "remove unnecessary border-radius already set on the iframe" (#3613)
* Revert "v5.1.18 banner artwork"

This reverts commit 70500140b9.

* Revert "Revert #3607 and #3608"

This reverts commit 87b3e470c2.

* Revert "Fix default global keyboard shortcuts for Mac"

This reverts commit e466f62e7e.

* Revert "Comment plugin: Improve styles"

This reverts commit e17456e3bc.

* Revert "Style tweaks for framed editor + preview (#3608)"

This reverts commit c058378da0.

* Revert "Change to natural counting in range[N] operator (#3609)"

This reverts commit b9df224f99.

* Revert "Update release note contributors list"

This reverts commit 0f3912ba95.

* Revert "Make editor-preview not hide text-editor shadow (#3607)"

This reverts commit 11f02dc362.

* Revert "Editor needs to stand out a bit more (#3606)"

This reverts commit d711ef25ed.

* Revert "Tweak for keyboard-shortcuts how-to (#3605)"

This reverts commit 419ea9a243.

* Revert "Remove unnecessary border-radius already set on the iframe (#3604)"

This reverts commit 288d25e733.
2018-12-03 08:51:50 +00:00
BurningTreeC
e509291b18 Revert "editor needs to stand out a bit more" (#3612)
* Revert "v5.1.18 banner artwork"

This reverts commit 70500140b9.

* Revert "Revert #3607 and #3608"

This reverts commit 87b3e470c2.

* Revert "Fix default global keyboard shortcuts for Mac"

This reverts commit e466f62e7e.

* Revert "Comment plugin: Improve styles"

This reverts commit e17456e3bc.

* Revert "Style tweaks for framed editor + preview (#3608)"

This reverts commit c058378da0.

* Revert "Change to natural counting in range[N] operator (#3609)"

This reverts commit b9df224f99.

* Revert "Update release note contributors list"

This reverts commit 0f3912ba95.

* Revert "Make editor-preview not hide text-editor shadow (#3607)"

This reverts commit 11f02dc362.

* Revert "Editor needs to stand out a bit more (#3606)"

This reverts commit d711ef25ed.
2018-12-03 08:51:17 +00:00
Jeremy Ruston
70500140b9 v5.1.18 banner artwork 2018-12-02 21:13:50 +00:00
Jermolene
87b3e470c2 Revert #3607 and #3608
Hi @BurningTreeC apologies I merged these without properly looking at them, but I think we should move them to 5.1.19 because:

* On Chrome, the new version hides the resize handle on textarea, which can be quite useful
* It's visually quite different, and I think might benefit from more discussion
* It's inconsistent with the CodeMirror editor

Would you mind perhaps starting another PR for edit template enhancements?
2018-12-02 20:57:13 +00:00
Jermolene
e466f62e7e Fix default global keyboard shortcuts for Mac
Fixes #3610
2018-12-02 15:47:32 +00:00
Jermolene
e17456e3bc Comment plugin: Improve styles 2018-12-02 15:15:48 +00:00
BurningTreeC
c058378da0 Style tweaks for framed editor + preview (#3608)
* remove border: none for iframe from framed.js

... inherits the iframe border 1px solid editor-border in vanilla base

* last style tweaks for editor-preview

same border-radius as framed editor

same additional space at the right as at the left

* Update base.tid

* Update base.tid

* looks actually better with this border
2018-12-02 08:40:11 +00:00
Evan Balster
b9df224f99 Change to natural counting in range[N] operator (#3609)
* Add range operator and documentation

* Use 1-based counting in range[N], update docs
2018-12-02 08:39:28 +00:00
Jermolene
0f3912ba95 Update release note contributors list 2018-12-01 18:05:06 +00:00
BurningTreeC
11f02dc362 Make editor-preview not hide text-editor shadow (#3607) 2018-12-01 17:58:06 +00:00
BurningTreeC
d711ef25ed Editor needs to stand out a bit more (#3606) 2018-12-01 17:28:01 +00:00
BurningTreeC
419ea9a243 Tweak for keyboard-shortcuts how-to (#3605) 2018-12-01 17:27:21 +00:00
BurningTreeC
288d25e733 Remove unnecessary border-radius already set on the iframe (#3604) 2018-12-01 15:31:34 +00:00
Jermolene
0ecc7c6071 Update plugin library to 5.1.18 2018-12-01 15:21:31 +00:00
Jermolene
80f44e880c Release note update 2018-12-01 15:21:16 +00:00
Luca Dorigo
4a9e2696d6 Promote Gitter Chat in the Readme (#3322)
* Promote Gitter Chat in the Readme

I'm not sure if it's ok to use inline styles for a small addition like this... also, feel free to change the colors :-)

* Added link to Gitter in 'Forums' tiddler and transcluded it in the readme as per @Jermolene comments

* Fixed transclusion and built readme

* Modified Readme so the 'forums' section is transclued with lower heading levels
2018-12-01 14:25:33 +00:00
BurningTreeC
6e59d2597a Style the framed editor (#3596)
* style the framed editor

this makes the framed editor look a bit less ugly 😎

* Update framed.js

* Update base.tid

* Update base.tid
2018-12-01 14:21:43 +00:00
BurningTreeC
7502ef875e Remove bad outline style from remove-tag-button (#3602) 2018-12-01 13:59:39 +00:00
Jermolene
8c367cdb21 Fix typo in #3601 2018-12-01 13:59:08 +00:00
Jermolene
63031bb3fc Release note update 2018-12-01 13:48:38 +00:00
Andreas Hahn
2bf6203cf5 Improvements to the static single tiddler view as well as documentation. (#3386)
* Improvements to the static single tiddler view as well as documentation.

* Fixed tabs

* Fixed tabs

* Revert static view path

* Documentation updates
2018-12-01 13:30:00 +00:00
BurningTreeC
6e674fe9db KaTeX plugin: add mhchem extension for chemical syntax (#3601)
* add chemParse (mchem extension)

* add mchem.min.js

* Update tiddlywiki.files

* add chemical reference link

* Rename mchem.min.js to mhchem.min.js

* Update tiddlywiki.files

* renaming mchem to mhchem

* Update readme.tid

* add chemical examples

* Update usage.tid
2018-12-01 13:28:51 +00:00
BurningTreeC
a82800050d Update katex to 0.10.0 (#3600)
* update to 0.10.0

* update to 0.10.0

* get current fonts from katex repo

* Update README.md

* update to 0.10.0
2018-12-01 12:37:44 +00:00
Bram Chen
9cd58caafc Update chinese translations (#3594)
* Improve help texts for listen and server commands
2018-11-29 18:02:36 +00:00
Ingo Blechschmidt
ea91ab1632 Twitter follow button docs: suggest https instead of http (#3458) 2018-11-29 11:10:30 +00:00
BurningTreeC
17232cfe91 Fix missing space between edittemplate tags (#3582) (#3585)
* add tc-tag-list-item to edittemplate  tag

... and remove a space that doesn't do anything here

* we need an `&nbsp` here
2018-11-29 09:51:58 +00:00
Arlen22
603c564872 Add the "tiddlywiki" argument to the server start hook (#3592)
* Update Hook__th-server-command-post-start.tid

* Update server.js

* Update listen.js
2018-11-29 09:51:11 +00:00
BurningTreeC
b18c85b85c Add How-To documentation for keyboard shortcuts (#3588)
* add link to "How to"  keyboard shortcuts + styles

* add "How to create keyboard shortcuts"

* Update How to create keyboard shortcuts.tid

* Update How to create keyboard shortcuts.tid

* Update How to create keyboard shortcuts.tid
2018-11-29 09:49:48 +00:00
BurningTreeC
62cbbf1db4 fix textnodes between tags in viewtemplate (#3587)
this removes additional text-nodes in the dom after each `tc-tag-list-item` caused by the last empty line
we could also just remove that line but I don't know if that's a permanent solution or if some mechanism will re-add that line at some point, so I go with the `whitespace trim` pragma
2018-11-29 09:49:29 +00:00
BurningTreeC
0396af849a Undo #3490 - error in popup-cancelling logic (#3586) 2018-11-29 09:49:11 +00:00
BurningTreeC
907d498baf Use stateTitle in dynaview viewtemplate (#3584) 2018-11-29 09:48:35 +00:00
Xavier Cazin
ea76a868bf Update to fr-FR language translations (#3583)
* Update to fr-FR language translations

* Add missing space

* Fix a line feed mistake
2018-11-29 09:48:11 +00:00
BurningTreeC
addc7c0176 Add tv-show-missing-links doc (#3580) 2018-11-29 09:47:55 +00:00
Marxsal
9268a8c3ca Info about using 0.0.0.0, plus warning about using it. (#3519)
* Info about using 0.0.0.0, plus warning about using it.

* Moved information to WebServer. Incls listen.

* More warnings about n.n.n.n
2018-11-29 09:47:37 +00:00
Marxsal
fe52d5462f Document tweaks to TOC for newcomers (#3518) 2018-11-29 09:46:41 +00:00
HC
5b14a97e0f Documentation of Using Excise text (#2533)
* Documentation of Using Excise text 

Documentation on the Excise text function. 
I propose that the  Excise text drop down ( the tiddler: $:/language/Buttons/Excise/Caption/Tag) links to this doc. I don't have permission to (or don't know how to) edit that tiddler in github. 

I propose the following text for the drop-down tiddler:

Tag new tiddler with the title of this tiddler (If the title is changed you have to [[save and re-edit|Using Excise]] to use the new title as tag).


see the issue:
https://github.com/Jermolene/TiddlyWiki5/issues/2531

* Update Using Excise.tid

changed headings in response to discussion

* Doc for  editor toolbar buttons (wikitext)

Documentation for buttons special to the editor toolbar (wikitext)

* Update Using Excise.tid

placed it under the new [[Editor toolbar]]

* Update Using Stamp.tid

placed it under the new [[Editor toolbar]] tiddler

* Update WikiText.tid

added reference to [[Editor toolbar]]

* Update Formatting text in TiddlyWiki.tid

added reference to the [[Editor toolbar]]

* Update WikiText.tid

* Update Editor toolbar.tid

* Update Editor toolbar.tid

* Update Editor toolbar.tid

* Update Editor toolbar.tid

typo
2018-11-27 21:12:44 +00:00
BurningTreeC
545c508138 fix tags-dropdown link (missing-tiddlers ) (#3589) 2018-11-27 20:51:54 +00:00
Mario Pietsch
42f7c03824 tm-open-external-window plus documentation. (#2721)
* first try - tm-open-external-window plus documentation.

* render examples and make help link a global variable
2018-11-27 18:43:25 +00:00
twMat
9232279a79 Update EditBitmapWidget doc to reflect update in code (#2725) 2018-11-27 18:11:21 +00:00
Tobias Beer
ee8821a5b4 Improvde days Operator docs for parameter (#3041)
in response to @rubaboo's [comment](https://github.com/Jermolene/TiddlyWiki5/pull/2972#issuecomment-331284559)
2018-11-27 17:57:13 +00:00
Tobias Beer
e0126b2f77 Tweak layout of HelloThere calls to action (#3046)
should only be at most 4 images per row, centered

the visuals of seeing 6 in the first row and 2 in the second is rather displeasing
2018-11-27 17:56:19 +00:00
Jermolene
19c49ae18a Fix animations for tags in edittemplate
Fixes #3577
2018-11-25 11:13:42 +00:00
BurningTreeC
ad4107a94b Fix animations in "Open" Sidebar-Tab (#3578) 2018-11-25 11:12:06 +00:00
BurningTreeC
8542ebaecb Fix $tw.platform.isLinux on latest firefox (#3495) 2018-11-25 10:51:42 +00:00
BurningTreeC
cd0ce0cde5 Show tag-icons in tags edittemplate (#3447) 2018-11-25 10:43:13 +00:00
BurningTreeC
01407fa8f9 Make tag-icons use the tag-pill-styles (#3448)
this line inherits the `fill: $(foregroundColor)$` from `<<tag-pill-styles>>` in `$:/core/macros/tags`
deleting the line makes the tag-icons fill with the computed color
2018-11-25 10:42:47 +00:00
Rob Hoelz
09ea59240a Cancel non-ancestor popups when showing a new popup (#3490)
Addresses GH #3484

As far as I can tell, the popup level checks in this module are
meant to handle nested popups.  It seems to me that the goal is
for at most a single hierarchy of popups to exist at any given time
- bearing that in mind, this change checks any popups currently tracked
by the module, canceling any that don't share an element hierarchy with
the new popup.
2018-11-25 10:42:21 +00:00
Jermolene
2e7faf3439 Updated Dutch translation
Thanks @gernert
2018-11-25 10:41:41 +00:00
Jermolene
1700eb4ba7 Docs update 2018-11-25 10:37:33 +00:00
twMat
536ab10790 Update docs for $:/tags/SideBarSegment (#3579)
The possibility to rearrange stuff "inside" tag pills is not obvious so makes sense to mention.
2018-11-25 10:36:15 +00:00
Jermolene
f6334723f6 Release note update 2018-11-24 16:12:12 +00:00
Jermolene
a83cd3f984 Change logic of tv-hide-missing-links to tv-show-missing-links
See https://github.com/Jermolene/TiddlyWiki5/pull/3530#issuecomment-441368922
2018-11-24 15:53:39 +00:00
Jermolene
078df9c157 Remove "TiddlyFox Apocalypse" from HelloThere thumbnails 2018-11-24 15:52:49 +00:00
Mario Pietsch
dc972237a2 Prepare for 5.1.18 (#3576) 2018-11-24 14:32:05 +00:00
Jermolene
32c8ef1d62 Docs typo
Thanks @pmario
2018-11-24 14:18:59 +00:00
BurningTreeC
3f91d5b3a1 Add tv-override-missing-links to fix links to missing tiddlers (#3530)
* add tv-override-missing-links variable

this lets us set `tv-override-missing-links` true so that we can fix edge cases like the `Filter` dropdown in the `Advanced Search` when `enable missing links` is unchecked in the `Settings` tab of the Control Panel

* add tv-override-missing-links to filter dropdown

* add tv-override-missing-links to type dropdown

* add tv-override-missing-links to fieldname dropd

* add tv-override-missing-links to TagManager(icons)

* undo tv-override-missing-links TagManager

not needed here

* Update link.js

* Update dropdown.tid

* Update fields.tid

* Update type.tid

* Update dropdown.tid

* Update link.js

* simplify all together

* add tv-hide-missing-links to pagetemplate

* do we need to refresh here...

... if the variable gets set on top of the pagetemplate?
2018-11-24 13:36:48 +00:00
G0erman
f32cb52ba6 Squashed commit of the following: (#3571)
uthor: G0erman <goerman.rafi@gmail.com
Date:   Fri Nov 23 17:21:43 2018 -0500

    Add a line in the end

commit 3cdd78116965f38147f55897adfcef493befe5d0
Author: G0erman <goerman.rafi@gmail.com>
Date:   Fri Nov 23 17:15:47 2018 -0500

    Recover file accidentally deleted

commit c4536e9182cd90a5d4e6e694ac6fc930ea75ddb5
Author: G0erman <goerman.rafi@gmail.com>
Date:   Fri Nov 23 17:12:25 2018 -0500

    Translate instructions about install plugin in node server.

commit 0622d03beb
Author: Jeremy Ruston <jeremy@jermolene.com>
Date:   Wed Nov 21 12:10:35 2018 +0000

    Update release note contributors list

commit 9d0083d8dc
Merge: 8134f8de2 2541b9b09
Author: Jeremy Ruston <jeremy@jermolene.com>
Date:   Wed Nov 21 11:53:43 2018 +0000

    Merge branch 'master' of https://Jermolene@github.com/Jermolene/TiddlyWiki5.git

commit 8134f8de28
Author: Jeremy Ruston <jeremy@jermolene.com>
Date:   Wed Nov 21 11:53:26 2018 +0000

    Update release note

commit 2541b9b090
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Wed Nov 21 12:32:22 2018 +0100

    Pagescroller - get scroll pos in src window (#3561)

commit d55a498fca
Author: Bimba Laszlo <bimbalaszlo@gmail.com>
Date:   Wed Nov 21 12:26:00 2018 +0100

    Docs: Clarify TextWidget with example (#3564)

commit ce7becd64f
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Wed Nov 21 12:24:50 2018 +0100

    KeyboardShortcut docs (#3496)

    * add SystemTag $:/tags/KeyboardShortcut

    * add Keyboard Shortcut Descriptor

    * Update KeyboardShortcuts.tid

    * Update KeyboardShortcutDescriptor.tid

    * Update and rename SystemTag_$__tags_KeyboardShortcut.tid to SystemTag_ $__tags_KeyboardShortcut.tid

    * Create KeyboardShortcutTiddler.tid

    * Update KeyboardShortcutTiddler.tid

    * Update KeyboardShortcutTiddler.tid

    * Update KeyboardShortcuts.tid

    * Update KeyboardShortcuts.tid

commit 09330968cc
Author: Jeremy Ruston <jeremy@jermolene.com>
Date:   Tue Nov 20 13:29:44 2018 +0000

    New "else" prefix for filters (#3558)

    * Experimental "else" prefix for filters

    See #3557

    * Docs updates for "else" filter runs

commit 02f26d94a1
Author: Jermolene <jeremy@jermolene.com>
Date:   Tue Nov 20 13:29:01 2018 +0000

    Releaase note update

commit 7729649f0e
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Tue Nov 20 12:50:12 2018 +0100

    Fix bad function declaration in wiki.js (#3559)

    fix for #3555 - inner function declaration should be on top of its "host" function

    error originally appeared on firefox v38.0.5

commit 430be4ec30
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Tue Nov 20 12:43:15 2018 +0100

    Fix list-tagged-draggable for quotes !! and ## (#3442)

    * fix list-tagged-draggable for quotes !! and ##

    * Update list.tid

    * Update list.tid

    * Update list.tid

commit b009a60b69
Author: Jermolene <jeremy@jermolene.com>
Date:   Tue Nov 20 11:30:06 2018 +0000

    Update release note

    Not quite up to date, got as far as 31 Oct.

commit 8832409666
Author: Jermolene <jeremy@jermolene.com>
Date:   Tue Nov 20 11:29:36 2018 +0000

    Docs typo

commit 6d67dc8eb8
Author: Bimba Laszlo <bimbalaszlo@gmail.com>
Date:   Tue Nov 20 11:06:12 2018 +0100

    Clarify drag-n-drop behaviour again (#3434)

    * Clarify drag-n-drop behaviour again

    Just found the real reason of drag-n-drop issue mentioned in
    b89e8d1635.

    * Trying to formulate correctly

commit f4ca295086
Author: Bimba Laszlo <bimbalaszlo@gmail.com>
Date:   Mon Nov 19 22:01:56 2018 +0100

    Docs: Remove broken `is` operator example (#3556)

    In 737e9ae4cb the multiple suboperator
    functionality was banned, but an example is remained in the docs.

commit b629b1412d
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Mon Nov 19 21:56:59 2018 +0100

    Fix CodeMirror on fake dom (#3547)

    * fix for #3547

    see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

    because of "strict mode" - the `parentNode` property may not be writable and causes an error in `fakedom.js` (appendChild)

    to reproduce: install CodeMirror, install "Tools for exploring internals of TW", open a heavy Tiddler like the Control-Panel in edit-mode, toggle preview on, switch to "parse tree" or "widget tree"

    * check for tw fakedom

commit eea034c32d
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Mon Nov 19 18:59:04 2018 +0100

    Docs: add state* documentation to button and reveal widgets (#3553)

    * docs: add state* attributes to reveal widget

    * docs: add state* attributes to button widget

commit f373d8f2bf
Author: Adrian Morosanu <morosanuae@yahoo.com>
Date:   Mon Nov 19 12:50:09 2018 +0200

    Tree macro - fix for double quotes (and other special characters) (#3552)

    * Added fixes for special characters (e.g. double quotes) (including @BurningTreeC and @Jermolene suggestions)
    * Removed a lot of redundant code
    * Set a default value for "tree" macro prefix parameter ($:/)

commit 4f78d3d81b
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Mon Nov 19 10:35:06 2018 +0100

    Fix untagged-pill popup position (#3550)

    this corrects the popup-position of the untagged-pill in the tag-manager, showing on top of the Tiddler, not below the pill

commit 72679d2041
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Sun Nov 18 21:58:03 2018 +0100

    Fix bad characters in the TagManager (#3549)

    now the tag manager needs some fixes for breaking titles, too

    - the `<<__variable__>>` syntax needs to be used
    - the `tag-pills` don't use the tag-pill macro, but `{{||$:/core/ui/TagTemplate}}` directly
    - some already sufficiently qualified states in the `iconEditor` macro are reduced to not use the `qualify` macro

commit 33ba69e852
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Sun Nov 18 21:57:04 2018 +0100

    Modals: Display in source-window (#3539)

    * Make modals display in source Window

    this makes modals display within the window where they got opened, with the parameter `rootwindow` that, if `yes` or `true`, shows the modal always in the root TW window (`<$action-sendmessage $message="tm-modal" $param="mymodal" rootwindow="yes|true"/>`)

    * pass the full event to $tw.modal.display

    we need the event there to find `srcDocument` and `srcWindow`

    * pass event in options object

    * update modal.js to use options.event

    * add docs for rootwindow tm-modal attribute

commit 3aae643e14
Author: Jermolene <jeremy@jermolene.com>
Date:   Sun Nov 18 19:31:53 2018 +0000

    Reveal widget with lazily loaded state tiddlers

    Fixes #3476

commit e983936c30
Author: Lioric <7177570+Lioric@users.noreply.github.com>
Date:   Sun Nov 18 14:19:10 2018 -0500

    Signing the CLA (#3477)

commit 849844be12
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Sun Nov 18 20:16:46 2018 +0100

    Add stateTitle stateField stateIndex attributes (#3529)

    * make triggerPopup optionally set state directly

    * update button widget for new state attributes

    * update reveal widget for new state attributes

    * fix errors in button widget

commit 2b6514ddc2
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Sun Nov 18 11:54:12 2018 +0100

    Use new state* attributes for  reveal and button widgets (#3531)

    * toc macros: use stateTitle and setTitle

    * tree macros: use stateTitle and setTitle

    * TagManager: use popupTitle, stateTitle setTitle

    * body viewtemplate: use stateTitle for folded-state

    * import-listings: use stateTitle and setTitle

commit ddc76622f2
Author: BurningTreeC <hypnotizedkangaroo@gmail.com>
Date:   Sun Nov 18 11:44:19 2018 +0100

    Remove sourceMappingUrl from hammer.js (#3535)

commit 882cad1a0d
Author: Bram Chen <bram.chen@gmail.com>
Date:   Sat Nov 17 21:48:31 2018 +0800

    Add chinese translations for Title/References (#3545)
2018-11-24 09:50:24 +00:00
Jeremy Ruston
0622d03beb Update release note contributors list 2018-11-21 12:10:35 +00:00
Jeremy Ruston
9d0083d8dc Merge branch 'master' of https://Jermolene@github.com/Jermolene/TiddlyWiki5.git 2018-11-21 11:53:43 +00:00
Jeremy Ruston
8134f8de28 Update release note 2018-11-21 11:53:26 +00:00
BurningTreeC
2541b9b090 Pagescroller - get scroll pos in src window (#3561) 2018-11-21 11:32:22 +00:00
Bimba Laszlo
d55a498fca Docs: Clarify TextWidget with example (#3564) 2018-11-21 11:26:00 +00:00
BurningTreeC
ce7becd64f KeyboardShortcut docs (#3496)
* add SystemTag $:/tags/KeyboardShortcut

* add Keyboard Shortcut Descriptor

* Update KeyboardShortcuts.tid

* Update KeyboardShortcutDescriptor.tid

* Update and rename SystemTag_$__tags_KeyboardShortcut.tid to SystemTag_ $__tags_KeyboardShortcut.tid

* Create KeyboardShortcutTiddler.tid

* Update KeyboardShortcutTiddler.tid

* Update KeyboardShortcutTiddler.tid

* Update KeyboardShortcuts.tid

* Update KeyboardShortcuts.tid
2018-11-21 11:24:50 +00:00
Jeremy Ruston
09330968cc New "else" prefix for filters (#3558)
* Experimental "else" prefix for filters

See #3557

* Docs updates for "else" filter runs
2018-11-20 13:29:44 +00:00
Jermolene
02f26d94a1 Releaase note update 2018-11-20 13:29:01 +00:00
BurningTreeC
7729649f0e Fix bad function declaration in wiki.js (#3559)
fix for #3555 - inner function declaration should be on top of its "host" function

error originally appeared on firefox v38.0.5
2018-11-20 11:50:12 +00:00
BurningTreeC
430be4ec30 Fix list-tagged-draggable for quotes !! and ## (#3442)
* fix list-tagged-draggable for quotes !! and ##

* Update list.tid

* Update list.tid

* Update list.tid
2018-11-20 11:43:15 +00:00
Jermolene
b009a60b69 Update release note
Not quite up to date, got as far as 31 Oct.
2018-11-20 11:30:06 +00:00
Jermolene
8832409666 Docs typo 2018-11-20 11:29:36 +00:00
Bimba Laszlo
6d67dc8eb8 Clarify drag-n-drop behaviour again (#3434)
* Clarify drag-n-drop behaviour again

Just found the real reason of drag-n-drop issue mentioned in
b89e8d1635.

* Trying to formulate correctly
2018-11-20 10:06:12 +00:00
Bimba Laszlo
f4ca295086 Docs: Remove broken is operator example (#3556)
In 737e9ae4cb the multiple suboperator
functionality was banned, but an example is remained in the docs.
2018-11-19 21:01:56 +00:00
BurningTreeC
b629b1412d Fix CodeMirror on fake dom (#3547)
* fix for #3547

see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

because of "strict mode" - the `parentNode` property may not be writable and causes an error in `fakedom.js` (appendChild)

to reproduce: install CodeMirror, install "Tools for exploring internals of TW", open a heavy Tiddler like the Control-Panel in edit-mode, toggle preview on, switch to "parse tree" or "widget tree"

* check for tw fakedom
2018-11-19 20:56:59 +00:00
BurningTreeC
eea034c32d Docs: add state* documentation to button and reveal widgets (#3553)
* docs: add state* attributes to reveal widget

* docs: add state* attributes to button widget
2018-11-19 17:59:04 +00:00
Adrian Morosanu
f373d8f2bf Tree macro - fix for double quotes (and other special characters) (#3552)
* Added fixes for special characters (e.g. double quotes) (including @BurningTreeC and @Jermolene suggestions)
* Removed a lot of redundant code
* Set a default value for "tree" macro prefix parameter ($:/)
2018-11-19 10:50:09 +00:00
BurningTreeC
4f78d3d81b Fix untagged-pill popup position (#3550)
this corrects the popup-position of the untagged-pill in the tag-manager, showing on top of the Tiddler, not below the pill
2018-11-19 09:35:06 +00:00
BurningTreeC
72679d2041 Fix bad characters in the TagManager (#3549)
now the tag manager needs some fixes for breaking titles, too

- the `<<__variable__>>` syntax needs to be used
- the `tag-pills` don't use the tag-pill macro, but `{{||$:/core/ui/TagTemplate}}` directly
- some already sufficiently qualified states in the `iconEditor` macro are reduced to not use the `qualify` macro
2018-11-18 20:58:03 +00:00
BurningTreeC
33ba69e852 Modals: Display in source-window (#3539)
* Make modals display in source Window

this makes modals display within the window where they got opened, with the parameter `rootwindow` that, if `yes` or `true`, shows the modal always in the root TW window (`<$action-sendmessage $message="tm-modal" $param="mymodal" rootwindow="yes|true"/>`)

* pass the full event to $tw.modal.display

we need the event there to find `srcDocument` and `srcWindow`

* pass event in options object

* update modal.js to use options.event

* add docs for rootwindow tm-modal attribute
2018-11-18 20:57:04 +00:00
Jermolene
3aae643e14 Reveal widget with lazily loaded state tiddlers
Fixes #3476
2018-11-18 19:31:53 +00:00
Lioric
e983936c30 Signing the CLA (#3477) 2018-11-18 19:19:10 +00:00
BurningTreeC
849844be12 Add stateTitle stateField stateIndex attributes (#3529)
* make triggerPopup optionally set state directly

* update button widget for new state attributes

* update reveal widget for new state attributes

* fix errors in button widget
2018-11-18 19:16:46 +00:00
BurningTreeC
2b6514ddc2 Use new state* attributes for reveal and button widgets (#3531)
* toc macros: use stateTitle and setTitle

* tree macros: use stateTitle and setTitle

* TagManager: use popupTitle, stateTitle setTitle

* body viewtemplate: use stateTitle for folded-state

* import-listings: use stateTitle and setTitle
2018-11-18 10:54:12 +00:00
BurningTreeC
ddc76622f2 Remove sourceMappingUrl from hammer.js (#3535) 2018-11-18 10:44:19 +00:00
Bram Chen
882cad1a0d Add chinese translations for Title/References (#3545) 2018-11-17 13:48:31 +00:00
Jeremy Ruston
33dd367a65 Introduce new subfilter operator (#3508) 2018-11-16 11:27:19 +00:00
Jermolene
db3f41db29 Docs typo fix for #3540 2018-11-15 21:44:05 +00:00
G0erman
d317bdf3da Documentation language (#3540)
* Improve documentation install language on node.js

* Singing the CLA
2018-11-15 21:43:01 +00:00
BurningTreeC
9752531b61 FIX for keyboard widget keyInfoArray (#3544)
apologies
2018-11-15 21:40:27 +00:00
BurningTreeC
43c8a0f485 Keyboard Widget: refresh keyInfoArray (#3541)
this makes a keyboard widget update its keyInfoArray if its key has the `((descriptor))` format and one of the platform-specific configuration tiddlers with that descriptor-suffix changes
2018-11-15 16:50:23 +00:00
BurningTreeC
4f7297645d Fix action-navigate widget for undefined event (#3542)
we need to prevent the event from being undefined which happens when using `action-navigate` within a global keyboard shortcut
2018-11-15 16:49:41 +00:00
Xavier Codinas
c5d4990cc5 Fix some catalan translations (#3464) 2018-11-15 15:51:34 +00:00
Jermolene
1f296951d4 Minor tweaks for #3534
* Minor rewording of prompt text
* Moved the colon into the translation string, reasoning that it may need to be changed for languages that use punctuation differently
* Corrected expand/collapse arrows
* Moved prompt into expand/collapse button, making it easier to hit use
2018-11-15 15:43:16 +00:00
Bimba Laszlo
88664f0286 Show backlinks on rename (#3534)
* List backlinks when renaming tiddler

* List references inplace

* TiddlerInfo/References accepting specific tiddler

If `operandTitle` is set, it will show its references instead of current
tiddler's.

It's purpose is to prevent code duplication (the references are listed
when renaming tiddler).

* Don't use variable for References template, use <$tiddler>

Set the `current` to desired title with `<$tiddler>` widget.

* List references in a separate block

* Rename state tiddler

* Use qualified state
2018-11-15 15:18:38 +00:00
BurningTreeC
0ff96f9caf [pagescroller] add callback function option (#3473)
this allows using the pagescroller for scrolling elements into view where the rect gets calculated somewhere else

Example: CodeMirror has the `cm.cursorCoords()` function that returns the rect of the textselection (or cursor coordinates)

this scrolls the cursor or text selection into view using tiddlywikis pagescroller:

`$tw.pageScroller.scrollIntoView(undefined, function() { return self.cm.cursorCoords(true,"window"); });`
2018-11-15 14:58:32 +00:00
BurningTreeC
c82edbe6bc New window: add missing click listener for popups (#3538)
this adds a click listener to new windows which enables us to cancel popups by clicking
2018-11-13 18:08:54 +00:00
BurningTreeC
55b5b6dd56 PageScroller: scroll in new windows (#3537)
* make pageScroller work in new windows

* update getScrollPosition to work for new windows

* Update dom.js
2018-11-13 18:07:55 +00:00
Jermolene
8ae62c90df Improve links to demo editions
Now make them relative to the location of index.html
2018-11-13 08:55:13 +00:00
Jermolene
277bc92f92 Add a dynaview demo edition 2018-11-13 08:54:50 +00:00
Bimba Laszlo
196992167f Add link to "code style and auto format settings" repo (#3527)
https://github.com/Jermolene/TiddlyWiki5/pull/3511#issuecomment-436405293
2018-11-08 11:29:31 +00:00
BurningTreeC
cb74536b3c Update CodeMirror config tiddlers (#3524)
* use "text" config scheme

* use "text" config scheme

* use "text" config scheme

* use "text" config scheme

* use "text" config scheme

* use "text" config scheme

* use "text" config scheme

* use "text" config scheme

* use "text" config scheme + set type integer

* use "text" config scheme

* set type integer

* set type integer
2018-11-07 17:58:38 +00:00
BurningTreeC
22caef9e34 Fix NewImageType config (#3523)
* fix new-line in NewImageType config

sorry for this, too

* fix NewImageType config tiddler

* use "text" field for config
2018-11-07 16:51:55 +00:00
BurningTreeC
d64fc5d8cd Fix new-image button (#3521)z
sorry for the mistake
2018-11-07 16:05:56 +00:00
BurningTreeC
9f8a74c6d9 add new-tiddler new-journal new-image keyboard shortcuts (#3512)
* add new-tiddler action tiddler

* use new-tiddler action-tiddler

* use new-tiddler action-tiddler correctly

* use new-journal action-tiddler

* create new-journal action-tiddler

* use new-image action-tiddler

* add new-image action-tiddler

* create new-image type config

users may prefer png over jpeg for example

* add new-tiddler keyboard shortcut

* add keyboard shortcut configs

* add keyboard shortcuts ShortcutInfo

* add new-journal Keyboard shortcut

* add new-image keyboard shortcut

* Create Hidden Setting NewImageType.tid
2018-11-07 12:59:21 +00:00
BurningTreeC
6c9dfd7f62 Fix tag macros problem with double quotes (#3437)
* [Further TOC fixes] - FollowUp: fix tag macros

this fixes the tag macros when titles are used that contain quotes, especially triple quotes and more

* Update tag.tid
2018-11-06 21:35:36 +00:00
Jermolene
4f8e32a647 Change qualify widget parameter name to match qualify macro 2018-11-06 16:41:18 +00:00
BurningTreeC
4d1127ed5b Fix tc-adjust-top-of-scroll (#3475)
* correct scrolling with tc-adjust-top-of-scroll

not multiplying `offset` with t causes jumps at the first animation steps, where the offset value is bigger than `endY - scrollPosition.y`

* correct scroll offset
2018-11-06 16:23:46 +00:00
Marxsal
16eb5e1e32 Doc: Updated info re free WebDAV hosting (#3017) 2018-11-06 15:35:58 +00:00
Marxsal
abda6dd078 Docs: How to customize TiddlyDesktop (#3018)
Adapted from Jeremy's post https://groups.google.com/d/msg/tiddlywiki/KXdrZyr9MZ4/pFPcZtu5DAAJ
2018-11-06 15:35:31 +00:00
Marxsal
729c1e1030 Add lt, gt, lteq, and gteq to text attribute description. (#3470) 2018-11-06 15:34:21 +00:00
Marxsal
88c47d9df4 Documentation resource - Dave Gifford's Toolmap (#3478)
* Documentation resource - Dave Gifford's Toolmap

* Updated description line
2018-11-06 15:33:54 +00:00
Marxsal
899fe7608b Doc - url fix for Timimi documentation (#3479) 2018-11-06 15:33:28 +00:00
Marxsal
7cd1c48643 Doc - additional notes re list widget behavior (#3480) 2018-11-06 15:33:00 +00:00
Jermolene
d7914e3f3e Merge branch 'master' of https://github.com/Jermolene/TiddlyWiki5 2018-11-06 15:29:16 +00:00
Jermolene
698224556e Avoid wikify widget in TOC macro
Fixing the most egregious problem from #3517

@pmario can you kindly retest?
2018-11-06 15:29:13 +00:00
BurningTreeC
aeaf5ee5b6 FIX "Observe openLinkFromOutsideRiver ..." (#3516)
I assumed the attribute to be available but it's not
2018-11-06 14:33:41 +00:00
BurningTreeC
97b098b059 Observe openLinkFromOutsideRiver when creating new tiddlers (#3514) 2018-11-06 13:55:18 +00:00
BurningTreeC
457f03798c Fix error opening new windows with popup-blocker (#3515)
this prevents errors when a popup blocker blocks opening a new window from within a running TW
2018-11-06 13:54:00 +00:00
BurningTreeC
3592333cb8 Add support for global keyboard shortcuts (#3493)
* changes for global keyboardshortcuts

* add keyboard.js startup module

* remove not existing "th-opening-window" hook

* correct title

* use utils.addEventListeners

* define platform lookup-names on startup

* use the startup-lookup-names array

* use the platform-specific lookupNames only

* Update keyboard.js

* move initializations to the constructor

* move initializations to the constructor

* rename hasAnyTiddlerChanged

* don't explicitely create new RegExp

* use $tw.utils.hopArray

* match strings, no regex

* remove hopArray, move to boot.js

* add $tw.utils.hopArray to boot.js

* style update

* style updates

* move more to keyboardManager module

this could probably be moved to rootwidget.js

* move more to keyboardManager module

* add event listener for shortcuts in new windows

* prevent error when opening window is blocked

* add keydown listener on document in startup.js

* delete startup/keyboard.js

* add missing this.shortcutTiddlers

* Update keyboard.js

* Update boot.js

* add exports.hopArray to utils.js

* minor codingstyle tweak

* change how lookupnames get pushed to array

* Update windows.js

* re-add shortcuts-listener for new windows

I removed this before which I think was because I misunderstood what exactly should go to a separate PR
2018-11-06 13:34:51 +00:00
Jermolene
b584295831 Fix crash with search operator with blank field list 2018-11-01 12:56:56 +00:00
A.B. Samma
dff5315afe Signing the CLA as Abraham Samma (#3506) 2018-10-31 11:49:40 +00:00
Jeremy Ruston
6dcdc2049a Enhance search operator (#3502)
* Enhance search operator

* Add support for searching all fields

and also searching all fields except nominated fields.

* Docs tweaks

Thanks @pmario

* Error message improvements

* Improve error message formatting
2018-10-30 17:39:18 +00:00
Jermolene
d6a0b06f02 AWS plugin: Add support for a compressed payload
AWS imposes a limit of 16MB in my testing for the payload of a lambda. Compressing it enables us to pass x2-3 more data, thanks to the inefficiencies of JSON
2018-10-30 09:29:12 +00:00
Jermolene
a75434a347 Docs: fix date format example 2018-10-29 09:18:55 +00:00
Jermolene
78f5465a47 Fix hover colour for textual editor toolbar buttons 2018-10-24 10:33:16 +01:00
jed
baddd89abb Added flag to $tw.utils.parseStringArray to allow non-unique entries (#2027)
* Added flag to $tw.utils.parseStringArray to allow non-unique entries

With this change if you use $tw.utils.parseStringArray(list) you get identical behavior to before and enforces uniqueness in lists, but if you use $tw.utils.parseStringArray(list,true) it allows duplicate values in the list.

Because of how JavaScript handles overloaded functions this shouldn't have any affect on existing code that just passes one argument to the function.

* Update to hopefully remove merge conflicts
2018-10-21 16:58:41 +01:00
Yurii Rashkovskii
c0c1b557eb Problem: revealed dropdown menu on mobiles (#3491)
On mobile phones, tiddler's dropdown menu stays partially off-screen.

Solution: ensure that the revealed coordinates are never negative

Closes #3486
2018-10-21 16:53:45 +01:00
Yurii Rashkovskii
b95f6b523b Signing CLA for Yurii Rashkovskii (#3492) 2018-10-20 12:47:43 +01:00
Jermolene
6b03ba9876 Syncer: support configurable polling interval 2018-10-19 16:32:23 +01:00
Jermolene
d50e2df57b Syncer: fix problem with incoming tiddlers
The bug here is that incoming tiddlers (ie tiddlers that were updated on the server and synced back to the browser) will retain any fields that are currently present but deleted in the incoming tiddler
2018-10-14 15:35:26 +01:00
Jermolene
9c849eb10a WebServer - change /login-basic route to /login/basic
To avoid clashing with tiddlers called "login-basic" :)

We also need to revise the rout for /status for the same reason, but there are backward compatibility issues there
2018-10-13 17:22:21 +01:00
Cameron Fischer
d8007386cf Fix issues with ordering of tagged items (#3301)
* Added better handling for sortByList manual placements

If manual placement specifications show up in an inconvenient order,
sortByList, will go to the trouble of processing them in that order.

* Added tests to confirm solution to (#3296)

...That custom tag ordering will not choke when tiddlers get sorted after their dependencies have been placed around them

* Corrected list-after bug when referencing external titles

* Using more error-proof $tw.utils.hop in sortByList

* minor indentation correction in test-tags.js
2018-10-07 12:15:33 +01:00
BurningTreeC
5dcdff4b37 Fix scrolling with tc-adjust-top-of-scroll (#3467)
not multiplying `offset` with t causes jumps at the first animation steps, where the offset value is bigger than `endY - scrollPosition.y`
2018-10-07 10:23:41 +01:00
Jermolene
240bd7bec4 Docs typo 2018-10-03 21:42:34 +01:00
Jermolene
eeb453d471 Docs update 2018-10-02 14:54:46 +01:00
Jermolene
f0c6a09ea7 Docs update 2018-10-02 14:19:06 +01:00
Jermolene
c208c55a22 Finish web server API docs 2018-10-02 14:16:58 +01:00
Jermolene
83a245ed21 Refactor utility for converting tiddlers to JSON 2018-10-01 11:27:45 +01:00
Jermolene
107b0c17c0 Docs update 2018-09-28 18:30:31 +01:00
Jermolene
f162f4bc7b Typos missed from a9dd8c2 2018-09-28 18:18:07 +01:00
Jermolene
a9dd8c2f52 Use the new import pragma
Good for the core to show best practice
2018-09-28 16:25:54 +01:00
Jermolene
d5da7fd57c Docs updates 2018-09-28 16:02:04 +01:00
Jermolene
7022a98d5a Update splash screen example so that the macros work 2018-09-28 16:01:50 +01:00
Jermolene
fe85845c3c Add new "\import" pragma 2018-09-28 16:01:32 +01:00
Jermolene
f61a61c060 Improvements to splash screen documentation 2018-09-27 11:51:44 +01:00
Jermolene
86f2de0dda Fix typo in example splash screen
Missed off cfb2d7c
2018-09-27 10:11:54 +01:00
Jermolene
cfb2d7c9c8 Add support for splash screens during loading
Fixes #3417

Addresses some of the requirements discussed in #2254
2018-09-27 09:47:55 +01:00
Jermolene
806df86434 Move the system tag documentation into individual tiddlers 2018-09-26 18:24:08 +01:00
Jermolene
7282bf4721 Fix search result count
It's been broken since 097c87f
2018-09-26 16:20:13 +01:00
Jermolene
287a83c1cc Dynaview: Fix parameter name typo 2018-09-26 15:23:49 +01:00
Jermolene
b776f9fe90 Remove version history of TiddlyDesktop
It's hard to keep the listing up to date (see https://github.com/Jermolene/TiddlyDesktop/issues/161), and so it's better to just point users at GitHub
2018-09-24 09:26:40 +01:00
Jermolene
7368cc74e1 Remove inadvertant usage of Object.assign
In order to retain compatibiltiy with IE11

See this discussion: https://groups.google.com/d/topic/tiddlywiki/RjA7_mee5oc/discussion
2018-09-21 10:56:01 +01:00
Jermolene
f98b4d5956 Add more logging for drop operations 2018-09-13 20:42:35 +01:00
Jermolene
e4e7a0912d Revert "Fix tag macro problems with bad characters in title/tags (#3435)"
This reverts commit b76c5011cf.

Thanks @pmario @BurningTreeC, let's redo this.
2018-09-12 08:42:56 +01:00
Jermolene
d650784dd6 Fix typo in keyboard shortcuts UI 2018-09-11 16:06:29 +01:00
BurningTreeC
b76c5011cf Fix tag macro problems with bad characters in title/tags (#3435)
this fixes the tag macros when titles are used that contain quotes, especially triple quotes and more
2018-09-11 16:05:39 +01:00
Jermolene
e5550b91e6 Further TOC fixes
Continuing the work of 587fe9d10e

Fixes the test case in @BurningTreeC's comment: 587fe9d10e (commitcomment-30450611)
2018-09-11 13:02:28 +01:00
Jermolene
587fe9d10e Fix TOC macro with titles ending with double quotes
By almost entirely eliminating text subsitution, we can avoid the situations where special characters in tags or titles gets the macro confused.

These are quite intricate changes, and so I'd appreciate any help reviewing and testing, many thanks.

Fixes #3427
2018-09-10 12:56:38 +01:00
Jermolene
874318091e Fix crash with illegal arguments to decodeuri(component)
Fixes #3428
2018-09-09 20:48:53 +01:00
Jermolene
f9eed0dc87 Docs about using the web server with external JS 2018-09-05 09:10:42 +01:00
Jermolene
f2a38960fc Fix external JS template to work with the TW5 webserver
It's a bit gross that we have to change the filename used to reference the JS file. This is to make it work with the webserver.

At the moment, the webserver exposes system tiddlers as plain text renderings, and ordinary tiddlers as full HTML renderings through a view template. So we have to use a system tiddler title for the JS file.

The workaround I'm thinking of is to remove the blanket exposure of system tiddlers, and instead have a list of system tiddlers that are specifically exposed through a namespace like `127.0.0.1:8080\lib\tiddlywiki.js`. That can't clash with a tiddler title because tiddler titles are URI encoded and so can't contain slashes.
2018-09-05 08:57:29 +01:00
Jeremy Ruston
3d10a35fb7 Add support for externalising TW's JavaScript (#3423)
* Explore externalising TiddlyWiki's JS core into a separate file

* Fix missing newline after copyright notice

* Add an error alert if tiddlywiki.js can't be loaded
2018-09-05 08:44:27 +01:00
Jermolene
8321d2e6fc Docs: webserver and read-only mode 2018-09-05 08:38:47 +01:00
Jermolene
4f39e69e9d Add support for resize tracking 2018-09-01 13:19:28 +01:00
Marxsal
d7b8c1c298 Community doc for Timimi extension (#3415) 2018-08-31 09:59:11 +01:00
Jermolene
19f7287a53 Add $:/tags/TiddlerInfoSegment for adding segments to the tiddler info panel 2018-08-30 13:42:51 +01:00
Jermolene
65af4e7748 DynaView: Fix stylesheet title 2018-08-29 19:02:52 +01:00
Jermolene
25727df649 DynaView: Remove optisizer functionality
It was slow and clunky, and turned out to be easier to do in CSS.
2018-08-29 14:47:57 +01:00
BurningTreeC
4ec8881c2b CodeMirror: revert preventing dragging within textarea (#3414)
this reverts #3070 which prevents dragging text from one codemirror instance to another

there are other ways to solve the issue I tried to solve with this, so we should revert this to the standard behavior
2018-08-28 21:02:32 +01:00
Jermolene
4b630de4bd Release note update 2018-08-26 16:49:02 +01:00
Jermolene
e237d8fa97 Transliterate servername to safe ASCII
Fixes #3410
2018-08-24 14:33:31 +01:00
Jermolene
0f7ce7b67f Refactor navigator widget to use story utility to manipulate the storylist as well as the historylist 2018-08-23 18:31:48 +01:00
Jermolene
3bfa9c6f10 Experimental "persistent draft indicators"
Fixes #3409
2018-08-23 18:02:39 +01:00
Jermolene
63ad284784 Fix variable name in PageTemplate 2018-08-23 17:43:34 +01:00
Jermolene
7dbe117bc5 Add username (if set) to the title of draft tiddlers
Makes things a bit easier to follow when working in multiuser environments.

@inmysocks, @danielo515, @arlen22, @pmario, @drakor does this make sense for your use cases?
2018-08-23 17:43:06 +01:00
Jermolene
72e2238dc9 Release note updates 2018-08-23 13:14:11 +01:00
Jermolene
7a6213dcbf Update code comment for $tw.utils.http 2018-08-23 13:13:49 +01:00
Jermolene
03602215c4 Comment plugin: Autofocus new comment edit box
Fixes #3408
2018-08-22 09:57:31 +01:00
Jermolene
0151d8e564 Docs typo 2018-08-21 18:21:35 +01:00
Jermolene
b7558f98f4 Release note update 2018-08-21 14:01:24 +01:00
Jermolene
09112ed455 Add support for webp, heic, and heif image formats
https://en.wikipedia.org/wiki/WebP
https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format
2018-08-21 11:46:46 +01:00
TechLifeWeb
5756f30edd Docs: Update TW5 Tribal Knowledge.tid (#3327)
This link has been long dead because DropBox changed their policy on public folders. I've moved the file and started making updates to it again.
2018-08-20 18:11:46 +01:00
Jermolene
59b6cc134f Don't include qrcode and nodewebkitsaver plugins in prerelease 2018-08-20 17:56:17 +01:00
Jermolene
ee28f66b0a Docs: tweaks to 'Macro Definitions in WikiText' 2018-08-20 17:55:32 +01:00
twMat
9a2e2cd385 Docs: Update Macro Definitions in WikiText.tid (#3381)
* Update Macro Definitions in WikiText.tid

*The table is intended as an overview.
*The "Parameters as Variables" example is more clean cut than previously.

Please note that the text lacks an example of  direct `<<variable>>` invocation (because I'm not even sure I categorize or name this properly). Also my changes may contain factual errors or faulty lingo - please check!

* Update Macro Definitions in WikiText.tid
2018-08-20 17:50:23 +01:00
Jermolene
d96c844264 Use the sitetitle as the default filename for saving 2018-08-20 15:38:57 +01:00
Jermolene
11529ab399 Docs: clarify docs for "each" filter operator 2018-08-20 15:31:57 +01:00
Jermolene
fbaceaa8bd Don't steal "paste" events from contenteditable elements
Fixes pasting with a WYSIWYG editor
2018-08-20 15:26:41 +01:00
Jens
0c965175aa Docs: Add description of suffix: value (#3393) 2018-08-19 18:37:37 +01:00
Jens
e92eb6a945 Docs: fix regexp default field (#3394) 2018-08-19 18:37:14 +01:00
twMat
dce564c238 Update doc on HTML Block vs Inline mode (#3395) 2018-08-19 18:24:23 +01:00
Arlen22
68b063ab24 Rename TiddlyWiki in the Sky to TiddlyWiki Cloud (#3398) 2018-08-19 18:23:27 +01:00
Andreas Hahn
fe527b7eaf Fixed type mismatch (#3403) 2018-08-19 18:22:52 +01:00
Bram Chen
e72c72f04c Improve chinese translations for CopiedToClipboard/Succeeded (#3404) 2018-08-19 11:00:20 +01:00
Jermolene
f218c946f1 Fix usages of is[current] that can be changed to all[current]
Fixes #3402
2018-08-18 14:47:35 +01:00
Bram Chen
b1f9ff3f6c Add chinese translations for settings of Permalink/permaview Mode (#3401) 2018-08-17 17:21:34 +01:00
Jermolene
7d0b255a2a Improve "copy to clipboard" notification text 2018-08-17 09:29:23 +01:00
Jermolene
5a2e87eb09 Extend permalink/permaview to optionally copy URL to the clipboard
Fixes #3255
2018-08-16 19:39:18 +01:00
Jermolene
b55a3102be Fix typo from 8a38685 2018-08-16 16:08:33 +01:00
Jermolene
8a38685de9 Optionally adjust scroll targets to allow for a top menu bar
Fixes #3396
2018-08-16 12:07:50 +01:00
Jermolene
f210b75a30 Comments plugin: Switch to using the list field to attach comments
https://github.com/Jermolene/TiddlyWiki5/issues/3378#issuecomment-413137626
2018-08-15 13:50:29 +01:00
Jermolene
763f8afaf2 Add "contains" filter operator for searching list fields 2018-08-15 13:50:07 +01:00
Jermolene
3140ff9e49 Extent tm-full-screen message to support forcing the status
Now one can force "enter" or "exit", instead of just toggling the current status
2018-08-14 22:53:53 +01:00
Bimba Laszlo
b89e8d1635 Clarify the drag-n-drop behaviour (#3387)
* Clarify the drag-n-drop behaviour

In my case I misunderstood it: I thought that I need to select the text and
drag the selection, because drag-n-drop did not work by default.

* Fix grammar
2018-08-10 21:29:13 +01:00
Bimba Laszlo
c48aff2c87 Add Bimba Laszlo (@bimlas) to individual CLA (#3391) 2018-08-10 21:28:56 +01:00
Jermolene
7b9dc7557c Popup manager enhancements 2018-08-09 19:39:58 +01:00
Jermolene
c87c18be96 Fix tm-new-tiddler overwriting creation fields of existing tiddlers
Fixes #3371
2018-08-08 15:08:34 +01:00
Jermolene
bb9e2de861 Docs improvements 2018-08-08 10:47:10 +01:00
Jermolene
0a5633dd4a Missed off fcc5a6e
Related to #3378
2018-08-07 21:47:22 +01:00
Jermolene
fcc5a6e796 Missed off ec18a55
Related to #3378
2018-08-07 21:45:48 +01:00
Jermolene
ec18a55033 Comments plugin: several enhancements
As proposed in #3378

* Experimental support for comments on the entire wiki (enable in the plugin config panel). Implemented as comments on $:/SiteTitle, with the comments appearing at the top of the story river
* Refactor the "add comment" button actions so that they can be reused
* Refactor the comment toolbar to be extensible via a system tag
* Add an $:/AdvancedSearch canned filter for accessing all comments
2018-08-07 21:37:07 +01:00
Jermolene
097c87fa7b Modularize the sidebar
Fixes #2758
2018-08-02 13:22:21 +01:00
Jermolene
2d231a2e07 Fix typo in reveal widget
Introduced in a3a4c28143
2018-07-31 13:30:00 +01:00
Jermolene
0285eb600e Clarify date format docs
Fixes #3369
2018-07-30 21:19:59 +01:00
Jermolene
4b9bc1b766 Fix crash with malformed hexadecimal HTML entities
Fixes #3373
2018-07-28 16:22:38 +01:00
Jermolene
a3a4c28143 Fix bug with reveal widget not refreshing when state tiddler changes 2018-07-20 17:07:48 +01:00
Jermolene
6cfd973fbd Readme credits 2018-07-20 14:19:11 +01:00
Bram Chen
794be7ffd7 Update Chinese translations (#3366)
* Add chinese help texts for the new listen command

* Add chinese texts for color setting of `<select>` element
2018-07-20 14:10:30 +01:00
Jermolene
49e9789d9c Readme updates 2018-07-20 13:34:00 +01:00
Jermolene
8520c9d8fd Coding style tweaks for #3346 2018-07-19 21:48:40 +01:00
Evan Balster
be58de8409 Add range operator and documentation (#3346) 2018-07-19 21:42:09 +01:00
Jermolene
34e04b7ca6 Minor tweaks for #3362 2018-07-19 21:28:31 +01:00
Arlen22
aa8b2e11bb Allow all methods and add bodyFormat property to route definition (#3362)
* Allow all methods and add bodyFormat property to route definition

* Set string as the default bodyFormat

* Only set encoding on string routes
2018-07-19 21:24:57 +01:00
Jermolene
8b787cd806 Add new palette colours from #3360 to other core palettes 2018-07-19 21:19:21 +01:00
Jermolene
1317e13974 Fix issue with #3360
See https://github.com/Jermolene/TiddlyWiki5/pull/3360#issuecomment-406400700
2018-07-19 21:18:54 +01:00
twMat
e554561f95 [doc] Update CountWidget.tid (#3348)
* [doc] Update CountWidget.tid

* Update CountWidget.tid
2018-07-19 17:59:24 +01:00
twMat
57d6215fda [doc update] Custom_styles_by_data-tags.tid (#3355)
A few minor improvements that I propose are merged directly.

But, @pmario , we also need a clarifying example showing how to target a tiddler using *multiple* tags. So, under the examples there ought to be something like this:... BUT I fail to get this to work (syntax?):

```
[data-tags*="[[How to apply custom styles]] example-test"] {
  outline: 2px solid green;
}
```

Further, the text states that

`Important: Tiddler tags are not sorted, so the order in the rendered output may be different!`

...but I'm not sure what this means. Does it mean:

```
Important: A tiddlers tags are displayed alphabetically but rendered styles may come out in a different order. As per standard CSS behaviour, styles rendered later override styles rendered earlier.
```
2018-07-19 17:58:58 +01:00
twMat
fcccc85994 [doc] minor tweak to TranscludeWidget.tid (#3356) 2018-07-19 17:58:04 +01:00
Talha Mansoor
e0be9a3d09 Add <select> color setting to the palette (#3360)
* Add <select> color setting to the palette

* Signing the CLA
2018-07-19 17:25:35 +01:00
Bram Chen
d7001c6f6b Update chinese translations for built-in HTTP server (#3364)
* Add chinese help texts for the new listen command
* Add chinese descriptions of "authenticator" and "route" in Docs/ModuleTypes
* Revised chinese help texts for the server command
2018-07-19 17:24:16 +01:00
snlhnk
c05c0d3df6 Module-ize server routes, add static file support and other enhancements(#2679)
* Module-ize server routes and add static file support (#2510)

* Refactor server routes to modules

New module type: serverroute

Caveats: Loading order is not deterministic but this would only matter
if two route modules attempted to use the same path regexp (that would
be silly).

* Add static assets plugin

This plugin allows the node server to fetch static assets in the /assets
directory. I felt that this was a feature that goes above the core
functionality. That is why I added it as a plugin. with the modular
route extensions this was a breeze.

* Add serverroute description to ModuleTypes

* Coding standards tweaks

* Fix filename typo

* Move support for attachments from a plugin into the core

* Missing "else"

* Refactor server handling

* Introduce a new named parameter scheme for commands
* Move the SimpleServer class into it's own module
* Deprecate the --server command because of the unwieldy syntax
* Add a new --listen command using the new syntax

For example:

tiddlywiki mywiki --listen host:0.0.0.0 port:8090

* Add check for unknown parameters

* Add support for multiple basic authentication credentials in a CSV file

Beware: Passwords are stored in plain text. If that's a problem, use an authenticating proxy and the trusted header authentication approach.

* Refactor module locations

* Rename "serverroute" module type to "route"

* Remove support for verifying optional named command parameters

The idea was to be able to flag unknown parameter names, but requiring a command to pre-specify all the parameter names makes it harder for (say) the listen command to be extensible so that plugins can add new optional parameters that they handle. (This is particularly in the context of work in progress to encapsulate authenticators into their own modules).

* Refactor the two authenticators into separate modules and add support for authorization

* Correct mistaken path.join vs. path.resolve

See https://stackoverflow.com/a/39836259

* Docs for the named command parameters

I'd be grateful if anyone with sufficient Windows experience could confirm that the note about double quotes in "NamedCommandParameters" is correct.

* Be consistent about lower case parameter names

* Do the right thing when we have a username but no password

With a username parameter but no password parameter we'll attribute edits to that username, but not require authentication.

* Remove obsolete code

* Add support for requiring authentication without restricting the username

* Refactor authorization checks

* Return read_only status in /status response

* Fix two code typos

* Add basic support for detecting readonly status and avoiding write errors

We now have syncadaptors returning  readonly status and avoid attempting to write to the server if it's going to fail

* Add readonly-styles

We hide editing-related buttons in read only mode

I've made this part of the tiddlyweb plugin but I think a case could be made for putting it into the core.

* Add custom request header as CSRF mitigation

By default we require the header X-Requested-With to be set to TiddlyWiki. Can be overriden by setting csrfdisable to "yes"

See https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers

* Add support for HTTPS

* First pass at a route for serving rendered tiddlers

cc @Drakor

* Tweaks to the single tiddler static view

Adding a simple sidebar

* Switch to "dash" separated parameter names

* Typo

* Docs: Update ServerCommand and ListenCommand

* First pass at docs for the new web server stuff

Writing the docs is turning out to be quite an undertaking, much harder than writing the code!

* Get rid of extraneous paragraphs in static renderings

* Rejig anonymous user handling

Now we can support wikis that are read-only for anonymous access, but allow a user to login for read/write access.

* More docs

Slowly getting there...

* Static tiddler rendering: Fix HTML content in page title

* Docs updates

* Fix server command parameter names

Missed off 30ce7ea

* Docs: Missing quotes

* Avoid inadvertent dependency on Node.js > v9.6.0

The listenOptions parameter of the plain HTTP version of CreateServer was only introduced in v9.6.0

cc @Drakor @pmario

* Typo
2018-07-18 16:54:43 +01:00
Jermolene
9735e13dea Fix broken test 2018-07-03 12:09:59 +01:00
Jermolene
c72e4f01f1 Fix typo in excise button
It meant that the excise button didn't appear on tiddlers of type text/vnd.tiddlywiki
2018-07-03 11:12:12 +01:00
Jermolene
19b54fe905 Docs for the new tm-edit-text-operation save-selection action 2018-06-30 08:35:10 +01:00
Jermolene
2f425c303f Add docs macros to dev edition
Fixes #3345
2018-06-29 16:40:45 +01:00
Jermolene
bef3242075 New text editor operation: save selection to a tiddler 2018-06-29 15:09:51 +01:00
Jermolene
f54a0a11bc Add support for editor toolbar buttons to have action widgets 2018-06-29 15:09:34 +01:00
Jermolene
929b0c9833 Fix broken QR code wifi example 2018-06-23 10:18:39 +01:00
Jermolene
acaa07a964 First stab at a threaded commenting plugin 2018-06-21 08:36:15 +01:00
Bram Chen
23797b05a1 Add chinese help text of debuglevel for the server command (#3337) 2018-06-20 17:16:30 +01:00
Jermolene
1ce9973bed Add optional debug logging to the server command 2018-06-20 12:43:41 +01:00
Jermolene
c29f5a1b61 Server command: map missing path to "/"
Otherwise, the user gets a 404 if we use a path prefix and the user omits the trailing slash
2018-06-20 12:18:35 +01:00
BurningTreeC
2a3f1b4403 Make tm-full-screen work in all windows (#3334)
this makes tm-full-screen work in new windows, too
2018-06-16 10:03:38 +01:00
Jermolene
7557b8b5b7 Remove extraneous debugging code from 35cbb127a3 2018-06-15 11:34:06 +01:00
Jermolene
35cbb127a3 Restrict variable substitutions to macros defined with the define pragma
Fixes #3333
2018-06-15 08:31:02 +01:00
Jermolene
aba9c94f5a Fix coding standards for put.js
A few minor things, but the accidental use of "const" is serious: TW5 is intended to run on ES5.
2018-06-13 15:50:47 +01:00
Jermolene
bacf500d50 Avoid deprecated new Buffer() usage
See https://alexatnet.com/node-js-10-important-changes/#buffer-1

> Uses of new Buffer() and Buffer() outside of the node_modules directory will now emit a runtime deprecation warning.

More details: https://nodejs.org/api/buffer.html#buffer_buffer_from_buffer_alloc_and_buffer_allocunsafe
2018-06-13 11:22:17 +01:00
Jermolene
2e51f08bef Add support for rebasing headings at render time
Fixes #3330
2018-06-10 15:54:23 +01:00
Jermolene
29606c6d24 Refactor new journal actions
Using contained actions, as we were, meant that the actions (and in this case the wikify widget) were being rendered at render time, but might have been out of date by the time the actions were triggered. Using the action attribute ensures the actions are rendered when they are executed.

Fixes #3326
2018-06-06 19:41:02 +01:00
Jermolene
17e73befde Docs: Update IIS setup instructions
Reference to the wiki folder was incorrect
2018-06-01 10:30:46 +01:00
Jermolene
89f99151b3 Correction to the IIS installation instructions 2018-05-30 18:25:30 +01:00
Jermolene
d2682b71ff DynaView: Only apply minimum height to tiddlers that are not yet visible 2018-05-25 11:08:27 +01:00
Jermolene
121e868ca4 Merge branch 'master' of https://github.com/Jermolene/TiddlyWiki5 2018-05-23 14:18:28 +01:00
Jermolene
4b42173962 DynaView: refactor checkVisibility for performance and readability 2018-05-23 14:17:49 +01:00
Jermolene
01bdaff005 Separate the two reveal-on-scroll examples
Making them easier to understand and copy
2018-05-23 14:17:23 +01:00
AnthonyMuscio
edcf1b1d41 Update cla-individual.md (#3305) 2018-05-21 10:40:16 +01:00
BurningTreeC
05af050cbf CodeMirror plugin: tweak for font-size (#3294) 2018-05-19 08:50:18 +01:00
Jermolene
6b14969cf6 Display a warning for binary tiddlers in view mode
The base64 data is currently parsed as wikitext, which is slow and unhelpful

We already display the same warning for binary tiddlers in edit mode.
2018-05-18 17:53:07 +01:00
Jermolene
f0b7c9a3d5 TextSlicer: Fix external links 2018-05-17 18:30:21 +01:00
Jermolene
33453039fc Fix broken import previews
All three were inadvertently showing the existing tiddler (if any), not the imported tiddler.
2018-05-16 17:40:15 +01:00
538 changed files with 19080 additions and 1928 deletions

View File

@@ -268,7 +268,7 @@ $tw.utils.stringifyList = function(value) {
};
// Parse a string array from a bracketted list. For example "OneTiddler [[Another Tiddler]] LastOne"
$tw.utils.parseStringArray = function(value) {
$tw.utils.parseStringArray = function(value, allowDuplicate) {
if(typeof value === "string") {
var memberRegExp = /(?:^|[^\S\xA0])(?:\[\[(.*?)\]\])(?=[^\S\xA0]|$)|([\S\xA0]+)/mg,
results = [], names = {},
@@ -277,7 +277,7 @@ $tw.utils.parseStringArray = function(value) {
match = memberRegExp.exec(value);
if(match) {
var item = match[1] || match[2];
if(item !== undefined && !$tw.utils.hop(names,item)) {
if(item !== undefined && (!$tw.utils.hop(names,item) || allowDuplicate)) {
results.push(item);
names[item] = true;
}
@@ -1985,6 +1985,9 @@ $tw.boot.startup = function(options) {
$tw.utils.registerFileType("image/jpeg","base64",[".jpg",".jpeg"],{flags:["image"]});
$tw.utils.registerFileType("image/png","base64",".png",{flags:["image"]});
$tw.utils.registerFileType("image/gif","base64",".gif",{flags:["image"]});
$tw.utils.registerFileType("image/webp","base64",".webp",{flags:["image"]});
$tw.utils.registerFileType("image/heic","base64",".heic",{flags:["image"]});
$tw.utils.registerFileType("image/heif","base64",".heif",{flags:["image"]});
$tw.utils.registerFileType("image/svg+xml","utf8",".svg",{flags:["image"]});
$tw.utils.registerFileType("image/x-icon","base64",".ico",{flags:["image"]});
$tw.utils.registerFileType("application/font-woff","base64",".woff");
@@ -2002,6 +2005,7 @@ $tw.boot.startup = function(options) {
$tw.utils.registerFileType("text/x-bibtex","utf8",".bib",{deserializerType:"application/x-bibtex"});
$tw.utils.registerFileType("application/x-bibtex","utf8",".bib");
$tw.utils.registerFileType("application/epub+zip","base64",".epub");
$tw.utils.registerFileType("application/octet-stream","base64",".octet-stream");
// Create the wiki store for the app
$tw.wiki = new $tw.Wiki();
// Install built in tiddler fields modules

View File

@@ -0,0 +1,4 @@
title: $:/core/images/add-comment
tags: $:/tags/Image
<svg class="tc-image-add-comment tc-image-button" width="22pt" height="22pt" viewBox="0 0 128 128"><path d="M56 56H36a8 8 0 1 0 0 16h20v20a8 8 0 1 0 16 0V72h20a8 8 0 1 0 0-16H72V36a8 8 0 1 0-16 0v20zm-12.595 58.362c-6.683 7.659-20.297 12.903-36.006 12.903-2.196 0-4.35-.102-6.451-.3 9.652-3.836 17.356-12.24 21.01-22.874C8.516 94.28 0 79.734 0 63.5 0 33.953 28.206 10 63 10s63 23.953 63 53.5S97.794 117 63 117c-6.841 0-13.428-.926-19.595-2.638z" fill-rule="evenodd"/></svg>

View File

@@ -82,8 +82,6 @@ Permaview/Caption: permaview
Permaview/Hint: Set browser address bar to a direct link to all the tiddlers in this story
Print/Caption: print page
Print/Hint: Print the current page
PrintWindow/Caption: print in new window
PrintWindow/Hint: Print tiddler in new window
Refresh/Caption: refresh
Refresh/Hint: Perform a full refresh of the wiki
Save/Caption: ok

View File

@@ -126,6 +126,10 @@ Settings/NavigationHistory/Caption: Navigation History
Settings/NavigationHistory/Hint: Update browser history when navigating to a tiddler:
Settings/NavigationHistory/No/Description: Do not update history
Settings/NavigationHistory/Yes/Description: Update history
Settings/NavigationPermalinkviewMode/Caption: Permalink/permaview Mode
Settings/NavigationPermalinkviewMode/Hint: Choose how permalink/permaview is handled:
Settings/NavigationPermalinkviewMode/CopyToClipboard/Description: Copy permalink/permaview URL to clipboard
Settings/NavigationPermalinkviewMode/UpdateAddressBar/Description: Update address bar with permalink/permaview URL
Settings/PerformanceInstrumentation/Caption: Performance Instrumentation
Settings/PerformanceInstrumentation/Hint: Displays performance statistics in the browser developer console. Requires reload to take effect
Settings/PerformanceInstrumentation/Description: Enable performance instrumentation

View File

@@ -2,6 +2,7 @@ title: $:/language/Docs/ModuleTypes/
allfilteroperator: A sub-operator for the ''all'' filter operator.
animation: Animations that may be used with the RevealWidget.
authenticator: Defines how requests are authenticated by the built-in HTTP server.
bitmapeditoroperation: A bitmap editor toolbar operation.
command: Commands that can be executed under Node.js.
config: Data to be inserted into `$tw.config`.
@@ -12,6 +13,7 @@ isfilteroperator: Operands for the ''is'' filter operator.
library: Generic module type for general purpose JavaScript modules.
macro: JavaScript macro definitions.
parser: Parsers for different content types.
route: Defines how individual URL patterns are handled by the built-in HTTP server.
saver: Savers handle different methods for saving files from the browser.
startup: Startup functions.
storyview: Story views customise the animation and behaviour of list widgets.

View File

@@ -45,6 +45,8 @@ page-background: Page background
pre-background: Preformatted code background
pre-border: Preformatted code border
primary: General primary
select-tag-background: `<select>` element background
select-tag-foreground: `<select>` element text
sidebar-button-foreground: Sidebar button foreground
sidebar-controls-foreground-hover: Sidebar controls foreground hover
sidebar-controls-foreground: Sidebar controls foreground

View File

@@ -22,6 +22,7 @@ Tags/Dropdown/Hint: Show tag list
Title/BadCharacterWarning: Warning: avoid using any of the characters <<bad-chars>> in tiddler titles
Title/Exists/Prompt: Target tiddler already exists
Title/Relink/Prompt: Update ''<$text text=<<fromTitle>>/>'' to ''<$text text=<<toTitle>>/>'' in the //tags// and //list// fields of other tiddlers
Title/References/Prompt: The following references to this tiddler will not be automatically updated:
Type/Dropdown/Caption: content type list
Type/Dropdown/Hint: Show content type list
Type/Delete/Caption: delete content type

View File

@@ -0,0 +1,33 @@
title: $:/language/Help/listen
description: Provides an HTTP server interface to TiddlyWiki
Serves a wiki over HTTP.
The listen command uses NamedCommandParameters:
```
--listen [<name>=<value>]...
```
All parameters are optional with safe defaults, and can be specified in any order. The recognised parameters are:
* ''host'' - optional hostname to serve from (defaults to "127.0.0.1" aka "localhost")
* ''path-prefix'' - optional prefix for paths
* ''port'' - port number on which to listen; non-numeric values are interpreted as a system environment variable from which the port number is extracted (defaults to "8080")
* ''credentials'' - pathname of credentials CSV file (relative to wiki folder)
* ''anon-username'' - the username for signing edits for anonymous users
* ''username'' - optional username for basic authentication
* ''password'' - optional password for basic authentication
* ''authenticated-user-header'' - optional name of header to be used for trusted authentication
* ''readers'' - comma separated list of principals allowed to read from this wiki
* ''writers'' - comma separated list of principals allowed to write to this wiki
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")
* ''tls-cert'' - pathname of TLS certificate file (relative to wiki folder)
* ''tls-key'' - pathname of TLS key file (relative to wiki folder)
* ''debug-level'' - optional debug level; set to "debug" to view request details (defaults to "none")
For information on opening up your instance to the entire local network, and possible security concerns, see the WebServer tiddler at TiddlyWiki.com.

View File

@@ -1,26 +1,25 @@
title: $:/language/Help/server
description: Provides an HTTP server interface to TiddlyWiki
description: Provides an HTTP server interface to TiddlyWiki (deprecated in favour of the new listen command)
The server built in to TiddlyWiki5 is very simple. Although compatible with TiddlyWeb it doesn't support many of the features needed for robust Internet-facing usage.
At the root, it serves a rendering of a specified tiddler. Away from the root, it serves individual tiddlers encoded in JSON, and supports the basic HTTP operations for `GET`, `PUT` and `DELETE`.
Legacy command to serve a wiki over HTTP.
```
--server <port> <roottiddler> <rendertype> <servetype> <username> <password> <host> <pathprefix>
--server <port> <root-tiddler> <root-render-type> <root-serve-type> <username> <password> <host> <path-prefix> <debug-level>
```
The parameters are:
* ''port'' - port number on which to listen; non-numeric values are interpreted as a system environment variable from which the port number is extracted (defaults to "8080")
* ''roottiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
* ''rendertype'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
* ''servetype'' - the content type with which the root tiddler should be served (defaults to "text/html")
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")
* ''username'' - the default username for signing edits
* ''password'' - optional password for basic authentication
* ''host'' - optional hostname to serve from (defaults to "127.0.0.1" aka "localhost")
* ''pathprefix'' - optional prefix for paths
* ''path-prefix'' - optional prefix for paths
* ''debug-level'' - optional debug level; set to "debug" to view request details (defaults to "none")
If the password parameter is specified then the browser will prompt the user for the username and password. Note that the password is transmitted in plain text so this implementation isn't suitable for general use.
If the password parameter is specified then the browser will prompt the user for the username and password. Note that the password is transmitted in plain text so this implementation should only be used on a trusted network or over HTTPS.
For example:
@@ -28,14 +27,16 @@ For example:
--server 8080 $:/core/save/all text/plain text/html MyUserName passw0rd
```
The username and password can be specified as empty strings if you need to set the hostname or pathprefix and don't want to require a password:
The username and password can be specified as empty strings if you need to set the hostname or pathprefix and don't want to require a password.
```
--server 8080 $:/core/save/all text/plain text/html "" "" 192.168.0.245
```
To run multiple TiddlyWiki servers at the same time you'll need to put each one on a different port. It can be useful to use an environment variable to pass the port number to the Node.js process. This example references an environment variable called "MY_PORT_NUMBER":
Using an address like this exposes your system to the local network. For information on opening up your instance to the entire local network, and possible security concerns, see the WebServer tiddler at TiddlyWiki.com.
To run multiple TiddlyWiki servers at the same time you'll need to put each one on a different port. It can be useful to use an environment variable to pass the port number to the Node.js process. This example references an environment variable called "MY_PORT_NUMBER":
```
--server MY_PORT_NUMBER $:/core/save/all text/plain text/html MyUserName passw0rd

View File

@@ -2,5 +2,5 @@ title: $:/language/Notifications/
Save/Done: Saved wiki
Save/Starting: Starting to save wiki
CopiedToClipboard/Succeeded: Copied!
CopiedToClipboard/Succeeded: Copied to clipboard!
CopiedToClipboard/Failed: Failed to copy to clipboard!

View File

@@ -94,6 +94,13 @@ Commander.prototype.executeNextCommand = function() {
if(this.verbose) {
this.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n");
}
// Parse named parameters if required
if(command.info.namedParameterMode) {
params = this.extractNamedParameters(params,command.info.mandatoryParameters);
if(typeof params === "string") {
return this.callback(params);
}
}
if(command.info.synchronous) {
// Synchronous command
c = new command.Command(params,this);
@@ -122,6 +129,35 @@ Commander.prototype.executeNextCommand = function() {
}
};
/*
Given an array of parameter strings `params` in name:value format, and an array of mandatory parameter names in `mandatoryParameters`, returns a hashmap of values or a string if error
*/
Commander.prototype.extractNamedParameters = function(params,mandatoryParameters) {
mandatoryParameters = mandatoryParameters || [];
var errors = [],
paramsByName = Object.create(null);
// Extract the parameters
$tw.utils.each(params,function(param) {
var index = param.indexOf("=");
if(index < 1) {
errors.push("malformed named parameter: '" + param + "'");
}
paramsByName[param.slice(0,index)] = $tw.utils.trim(param.slice(index+1));
});
// Check the mandatory parameters are present
$tw.utils.each(mandatoryParameters,function(mandatoryParameter) {
if(!$tw.utils.hop(paramsByName,mandatoryParameter)) {
errors.push("missing mandatory parameter: '" + mandatoryParameter + "'");
}
});
// Return any errors
if(errors.length > 0) {
return errors.join(" and\n");
} else {
return paramsByName;
}
};
Commander.initCommands = function(moduleType) {
moduleType = moduleType || "command";
$tw.commands = {};

View File

@@ -0,0 +1,48 @@
/*\
title: $:/core/modules/commands/listen.js
type: application/javascript
module-type: command
Listen for HTTP requests and serve tiddlers
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Server = require("$:/core/modules/server/server.js").Server;
exports.info = {
name: "listen",
synchronous: true,
namedParameterMode: true,
mandatoryParameters: [],
};
var Command = function(params,commander,callback) {
var self = this;
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(!$tw.boot.wikiTiddlersPath) {
$tw.utils.warning("Warning: Wiki folder '" + $tw.boot.wikiPath + "' does not exist or is missing a tiddlywiki.info file");
}
// Set up server
this.server = new Server({
wiki: this.commander.wiki,
variables: self.params
});
var nodeServer = this.server.listen();
$tw.hooks.invokeHook("th-server-command-post-start",this.server,nodeServer,"tiddlywiki");
return null;
};
exports.Command = Command;
})();

View File

@@ -3,7 +3,7 @@ title: $:/core/modules/commands/server.js
type: application/javascript
module-type: command
Serve tiddlers over http
Deprecated legacy command for serving tiddlers
\*/
(function(){
@@ -12,304 +12,41 @@ Serve tiddlers over http
/*global $tw: false */
"use strict";
if($tw.node) {
var util = require("util"),
fs = require("fs"),
url = require("url"),
path = require("path"),
http = require("http");
}
var Server = require("$:/core/modules/server/server.js").Server;
exports.info = {
name: "server",
synchronous: true
};
/*
A simple HTTP server with regexp-based routes
*/
function SimpleServer(options) {
this.routes = options.routes || [];
this.wiki = options.wiki;
this.variables = options.variables || {};
}
SimpleServer.prototype.set = function(obj) {
var self = this;
$tw.utils.each(obj,function(value,name) {
self.variables[name] = value;
});
};
SimpleServer.prototype.get = function(name) {
return this.variables[name];
};
SimpleServer.prototype.addRoute = function(route) {
this.routes.push(route);
};
SimpleServer.prototype.findMatchingRoute = function(request,state) {
var pathprefix = this.get("pathprefix") || "";
for(var t=0; t<this.routes.length; t++) {
var potentialRoute = this.routes[t],
pathRegExp = potentialRoute.path,
pathname = state.urlInfo.pathname,
match;
if(pathprefix) {
if(pathname.substr(0,pathprefix.length) === pathprefix) {
pathname = pathname.substr(pathprefix.length);
match = potentialRoute.path.exec(pathname);
} else {
match = false;
}
} else {
match = potentialRoute.path.exec(pathname);
}
if(match && request.method === potentialRoute.method) {
state.params = [];
for(var p=1; p<match.length; p++) {
state.params.push(match[p]);
}
return potentialRoute;
}
}
return null;
};
SimpleServer.prototype.checkCredentials = function(request,incomingUsername,incomingPassword) {
var header = request.headers.authorization || "",
token = header.split(/\s+/).pop() || "",
auth = $tw.utils.base64Decode(token),
parts = auth.split(/:/),
username = parts[0],
password = parts[1];
if(incomingUsername === username && incomingPassword === password) {
return "ALLOWED";
} else {
return "DENIED";
}
};
SimpleServer.prototype.requestHandler = function(request,response) {
// Compose the state object
var self = this;
var state = {};
state.wiki = self.wiki;
state.server = self;
state.urlInfo = url.parse(request.url);
// Find the route that matches this path
var route = self.findMatchingRoute(request,state);
// Check for the username and password if we've got one
var username = self.get("username"),
password = self.get("password");
if(username && password) {
// Check they match
if(self.checkCredentials(request,username,password) !== "ALLOWED") {
var servername = state.wiki.getTiddlerText("$:/SiteTitle") || "TiddlyWiki5";
response.writeHead(401,"Authentication required",{
"WWW-Authenticate": 'Basic realm="Please provide your username and password to login to ' + servername + '"'
});
response.end();
return;
}
}
// Return a 404 if we didn't find a route
if(!route) {
response.writeHead(404);
response.end();
return;
}
// Set the encoding for the incoming request
// TODO: Presumably this would need tweaking if we supported PUTting binary tiddlers
request.setEncoding("utf8");
// Dispatch the appropriate method
switch(request.method) {
case "GET": // Intentional fall-through
case "DELETE":
route.handler(request,response,state);
break;
case "PUT":
var data = "";
request.on("data",function(chunk) {
data += chunk.toString();
});
request.on("end",function() {
state.data = data;
route.handler(request,response,state);
});
break;
}
};
SimpleServer.prototype.listen = function(port,host) {
return http.createServer(this.requestHandler.bind(this)).listen(port,host);
};
var Command = function(params,commander,callback) {
var self = this;
this.params = params;
this.commander = commander;
this.callback = callback;
// Set up server
this.server = new SimpleServer({
wiki: this.commander.wiki
});
// Add route handlers
this.server.addRoute({
method: "PUT",
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
fields = JSON.parse(state.data);
// Pull up any subfields in the `fields` object
if(fields.fields) {
$tw.utils.each(fields.fields,function(field,name) {
fields[name] = field;
});
delete fields.fields;
}
// Remove any revision field
if(fields.revision) {
delete fields.revision;
}
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
var changeCount = state.wiki.getChangeCount(title).toString();
response.writeHead(204, "OK",{
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
"Content-Type": "text/plain"
});
response.end();
}
});
this.server.addRoute({
method: "DELETE",
path: /^\/bags\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]);
state.wiki.deleteTiddler(title);
response.writeHead(204, "OK", {
"Content-Type": "text/plain"
});
response.end();
}
});
this.server.addRoute({
method: "GET",
path: /^\/$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": state.server.get("serveType")});
var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
response.end(text,"utf8");
}
});
this.server.addRoute({
method: "GET",
path: /^\/status$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var text = JSON.stringify({
username: state.server.get("username"),
space: {
recipe: "default"
},
tiddlywiki_version: $tw.version
});
response.end(text,"utf8");
}
});
this.server.addRoute({
method: "GET",
path: /^\/favicon.ico$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "image/x-icon"});
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
response.end(buffer,"base64");
}
});
this.server.addRoute({
method: "GET",
path: /^\/recipes\/default\/tiddlers.json$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var tiddlers = [];
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
var tiddlerFields = {};
$tw.utils.each(tiddler.fields,function(field,name) {
if(name !== "text") {
tiddlerFields[name] = tiddler.getFieldString(name);
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
tiddlers.push(tiddlerFields);
});
var text = JSON.stringify(tiddlers);
response.end(text,"utf8");
}
});
this.server.addRoute({
method: "GET",
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
tiddler = state.wiki.getTiddler(title),
tiddlerFields = {},
knownFields = [
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
];
if(tiddler) {
$tw.utils.each(tiddler.fields,function(field,name) {
var value = tiddler.getFieldString(name);
if(knownFields.indexOf(name) !== -1) {
tiddlerFields[name] = value;
} else {
tiddlerFields.fields = tiddlerFields.fields || {};
tiddlerFields.fields[name] = value;
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
response.writeHead(200, {"Content-Type": "application/json"});
response.end(JSON.stringify(tiddlerFields),"utf8");
} else {
response.writeHead(404);
response.end();
}
}
});
};
Command.prototype.execute = function() {
if(!$tw.boot.wikiTiddlersPath) {
$tw.utils.warning("Warning: Wiki folder '" + $tw.boot.wikiPath + "' does not exist or is missing a tiddlywiki.info file");
}
var port = this.params[0] || "8080",
rootTiddler = this.params[1] || "$:/core/save/all",
renderType = this.params[2] || "text/plain",
serveType = this.params[3] || "text/html",
username = this.params[4],
password = this.params[5],
host = this.params[6] || "127.0.0.1",
pathprefix = this.params[7];
if(parseInt(port,10).toString() !== port) {
port = process.env[port] || 8080;
}
this.server.set({
rootTiddler: rootTiddler,
renderType: renderType,
serveType: serveType,
username: username,
password: password,
pathprefix: pathprefix
// Set up server
this.server = new Server({
wiki: this.commander.wiki,
variables: {
port: this.params[0],
host: this.params[6],
"root-tiddler": this.params[1],
"root-render-type": this.params[2],
"root-serve-type": this.params[3],
username: this.params[4],
password: this.params[5],
"path-prefix": this.params[7],
"debug-level": this.params[8]
}
});
var nodeServer = this.server.listen(port,host);
$tw.utils.log("Serving on " + host + ":" + port,"brown/orange");
$tw.utils.log("(press ctrl-C to exit)","red");
// Warn if required plugins are missing
if(!$tw.wiki.getTiddler("$:/plugins/tiddlywiki/tiddlyweb") || !$tw.wiki.getTiddler("$:/plugins/tiddlywiki/filesystem")) {
$tw.utils.warning("Warning: Plugins required for client-server operation (\"tiddlywiki/filesystem\" and \"tiddlywiki/tiddlyweb\") are missing from tiddlywiki.info file");
}
$tw.hooks.invokeHook('th-server-command-post-start', this.server, nodeServer);
var nodeServer = this.server.listen();
$tw.hooks.invokeHook("th-server-command-post-start",this.server,nodeServer,"tiddlywiki");
return null;
};

View File

@@ -0,0 +1,23 @@
/*\
title: $:/core/modules/editor/operations/text/save-selection.js
type: application/javascript
module-type: texteditoroperation
Text editor operation to save the current selection in a specified tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["save-selection"] = function(event,operation) {
var tiddler = event.paramObject.tiddler,
field = event.paramObject.field || "text";
if(tiddler && field) {
this.wiki.setText(tiddler,field,null,operation.text.substring(operation.selStart,operation.selEnd));
}
};
})();

View File

@@ -40,12 +40,23 @@ function parseFilterOperation(operators,filterString,p) {
nextBracketPos += p;
var bracket = filterString.charAt(nextBracketPos);
operator.operator = filterString.substring(p,nextBracketPos);
// Any suffix?
var colon = operator.operator.indexOf(':');
if(colon > -1) {
// The raw suffix for older filters
operator.suffix = operator.operator.substring(colon + 1);
operator.operator = operator.operator.substring(0,colon) || "field";
// The processed suffix for newer filters
operator.suffixes = [];
$tw.utils.each(operator.suffix.split(":"),function(subsuffix) {
operator.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
if(entry) {
operator.suffixes[operator.suffixes.length - 1].push(entry);
}
});
});
}
// Empty operator means: title
else if(operator.operator === "") {
@@ -108,7 +119,7 @@ exports.parseFilter = function(filterString) {
p = 0, // Current position in the filter string
match;
var whitespaceRegExp = /(\s+)/mg,
operandRegExp = /((?:\+|\-)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
operandRegExp = /((?:\+|\-|~)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
while(p < filterString.length) {
// Skip any whitespace
whitespaceRegExp.lastIndex = p;
@@ -208,6 +219,7 @@ exports.compileFilter = function(filterString) {
operand: operand,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
@@ -247,6 +259,13 @@ exports.compileFilter = function(filterString) {
results.splice(0,results.length);
$tw.utils.pushTop(results,operationSubFunction(source,widget));
};
case "~": // This operation is unioned into the result only if the main result so far is empty
return function(results,source,widget) {
if(results.length === 0) {
// Main result so far is empty
$tw.utils.pushTop(results,operationSubFunction(source,widget));
}
};
}
})());
});

View File

@@ -0,0 +1,45 @@
/*\
title: $:/core/modules/filters/contains.js
type: application/javascript
module-type: filteroperator
Filter operator for finding values in array fields
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.contains = function(source,operator,options) {
var results = [],
fieldname = (operator.suffix || "list").toLowerCase();
if(operator.prefix === "!") {
source(function(tiddler,title) {
if(tiddler) {
var list = tiddler.getFieldList(fieldname);
if(list.indexOf(operator.operand) === -1) {
results.push(title);
}
} else {
results.push(title);
}
});
} else {
source(function(tiddler,title) {
if(tiddler) {
var list = tiddler.getFieldList(fieldname);
if(list.indexOf(operator.operand) !== -1) {
results.push(title);
}
}
});
}
return results;
};
})();

View File

@@ -19,7 +19,12 @@ Export our filter functions
exports.decodeuricomponent = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
results.push(decodeURIComponent(title));
var value = title;
try {
value = decodeURIComponent(title);
} catch(e) {
}
results.push(value);
});
return results;
};
@@ -35,7 +40,12 @@ exports.encodeuricomponent = function(source,operator,options) {
exports.decodeuri = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
results.push(decodeURI(title));
var value = title;
try {
value = decodeURI(title);
} catch(e) {
}
results.push(value);
});
return results;
};

View File

@@ -0,0 +1,99 @@
/*\
title: $:/core/modules/filters/range.js
type: application/javascript
module-type: filteroperator
Filter operator for generating a numeric range.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.range = function(source,operator,options) {
var results = [];
// Split the operand into numbers delimited by these symbols
var parts = operator.operand.split(/[,:;]/g),
beg, end, inc, i, fixed = 0;
for (i=0; i<parts.length; i++) {
// Validate real number
if(!/^\s*[+-]?((\d+(\.\d*)?)|(\.\d+))\s*$/.test(parts[i])) {
return ["range: bad number \"" + parts[i] + "\""];
}
// Count digits; the most precise number determines decimal places in output.
var frac = /\.\d+/.exec(parts[i]);
if(frac) {
fixed = Math.max(fixed,frac[0].length-1);
}
parts[i] = parseFloat(parts[i]);
}
switch(parts.length) {
case 1:
end = parts[0];
if (end >= 1) {
beg = 1;
}
else if (end <= -1) {
beg = -1;
}
else {
return [];
}
inc = 1;
break;
case 2:
beg = parts[0];
end = parts[1];
inc = 1;
break;
case 3:
beg = parts[0];
end = parts[1];
inc = Math.abs(parts[2]);
break;
}
if(inc === 0) {
return ["range: increment 0 causes infinite loop"];
}
// May need to count backwards
var direction = ((end < beg) ? -1 : 1);
inc *= direction;
// Estimate number of resulting elements
if((end - beg) / inc > 10000) {
return ["range: too many steps (over 10K)"];
}
// Avoid rounding error on last step
end += direction * 0.5 * Math.pow(0.1,fixed);
var safety = 10010;
// Enumerate the range
if (end<beg) {
for(i=beg; i>end; i+=inc) {
results.push(i.toFixed(fixed));
if(--safety<0) {
break;
}
}
} else {
for(i=beg; i<end; i+=inc) {
results.push(i.toFixed(fixed));
if(--safety<0) {
break;
}
}
}
if(safety<0) {
return ["range: unexpectedly large output"];
}
// Reverse?
if(operator.prefix === "!") {
results.reverse();
}
return results;
};
})();

View File

@@ -17,11 +17,34 @@ Export our filter function
*/
exports.search = function(source,operator,options) {
var invert = operator.prefix === "!";
if(operator.suffix) {
if(operator.suffixes) {
var hasFlag = function(flag) {
return (operator.suffixes[1] || []).indexOf(flag) !== -1;
},
excludeFields = false,
fieldList = operator.suffixes[0] || [],
firstField = fieldList[0] || "",
firstChar = firstField.charAt(0),
fields;
if(firstChar === "-") {
fields = [firstField.slice(1)].concat(fieldList.slice(1));
excludeFields = true;
} else if(fieldList[0] === "*"){
fields = [];
excludeFields = true;
} else {
fields = fieldList.slice(0);
}
return options.wiki.search(operator.operand,{
source: source,
invert: invert,
field: operator.suffix
field: fields,
excludeField: excludeFields,
caseSensitive: hasFlag("casesensitive"),
literal: hasFlag("literal"),
whitespace: hasFlag("whitespace"),
regexp: hasFlag("regexp"),
words: hasFlag("words")
});
} else {
return options.wiki.search(operator.operand,{

View File

@@ -0,0 +1,33 @@
/*\
title: $:/core/modules/filters/subfilter.js
type: application/javascript
module-type: filteroperator
Filter operator returning its operand evaluated as a filter
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.subfilter = function(source,operator,options) {
var list = options.wiki.filterTiddlers(operator.operand,options.widget,source);
if(operator.prefix === "!") {
var results = [];
source(function(tiddler,title) {
if(list.indexOf(title) === -1) {
results.push(title);
}
});
return results;
} else {
return list;
}
};
})();

View File

@@ -138,6 +138,17 @@ function KeyboardManager(options) {
});
// Save the platform-specific name of the "meta" key
this.metaKeyName = $tw.platform.isMac ? "cmd-" : "win-";
this.shortcutKeysList = [], // Stores the shortcut-key descriptors
this.shortcutActionList = [], // Stores the corresponding action strings
this.shortcutParsedList = []; // Stores the parsed key descriptors
this.lookupNames = ["shortcuts"];
this.lookupNames.push($tw.platform.isMac ? "shortcuts-mac" : "shortcuts-not-mac")
this.lookupNames.push($tw.platform.isWindows ? "shortcuts-windows" : "shortcuts-not-windows");
this.lookupNames.push($tw.platform.isLinux ? "shortcuts-linux" : "shortcuts-not-linux");
this.updateShortcutLists(this.getShortcutTiddlerList());
$tw.wiki.addEventListener("change",function(changes) {
self.handleShortcutChanges(changes);
});
}
/*
@@ -229,10 +240,9 @@ KeyboardManager.prototype.parseKeyDescriptors = function(keyDescriptors,options)
result.push.apply(result,self.parseKeyDescriptors(keyDescriptors,options));
}
};
lookupName("shortcuts");
lookupName($tw.platform.isMac ? "shortcuts-mac" : "shortcuts-not-mac");
lookupName($tw.platform.isWindows ? "shortcuts-windows" : "shortcuts-not-windows");
lookupName($tw.platform.isLinux ? "shortcuts-linux" : "shortcuts-not-linux");
$tw.utils.each(self.lookupNames,function(platformDescriptor) {
lookupName(platformDescriptor);
});
}
} else {
result.push(self.parseKeyDescriptor(keyDescriptor));
@@ -274,6 +284,70 @@ KeyboardManager.prototype.checkKeyDescriptors = function(event,keyInfoArray) {
return false;
};
KeyboardManager.prototype.getShortcutTiddlerList = function() {
return $tw.wiki.getTiddlersWithTag("$:/tags/KeyboardShortcut");
};
KeyboardManager.prototype.updateShortcutLists = function(tiddlerList) {
this.shortcutTiddlers = tiddlerList;
for(var i=0; i<tiddlerList.length; i++) {
var title = tiddlerList[i],
tiddlerFields = $tw.wiki.getTiddler(title).fields;
this.shortcutKeysList[i] = tiddlerFields.key !== undefined ? tiddlerFields.key : undefined;
this.shortcutActionList[i] = tiddlerFields.text;
this.shortcutParsedList[i] = this.shortcutKeysList[i] !== undefined ? this.parseKeyDescriptors(this.shortcutKeysList[i]) : undefined;
}
};
KeyboardManager.prototype.handleKeydownEvent = function(event) {
var key, action;
for(var i=0; i<this.shortcutTiddlers.length; i++) {
if(this.shortcutParsedList[i] !== undefined && this.checkKeyDescriptors(event,this.shortcutParsedList[i])) {
key = this.shortcutParsedList[i];
action = this.shortcutActionList[i];
}
}
if(key !== undefined) {
event.preventDefault();
event.stopPropagation();
$tw.rootWidget.invokeActionString(action,$tw.rootWidget);
return true;
}
return false;
};
KeyboardManager.prototype.detectNewShortcuts = function(changedTiddlers) {
var shortcutConfigTiddlers = [],
handled = false;
$tw.utils.each(this.lookupNames,function(platformDescriptor) {
var descriptorString = "$:/config/" + platformDescriptor + "/";
Object.keys(changedTiddlers).forEach(function(configTiddler) {
var configString = configTiddler.substr(0, configTiddler.lastIndexOf("/") + 1);
if(configString === descriptorString) {
shortcutConfigTiddlers.push(configTiddler);
handled = true;
}
});
});
if(handled) {
return $tw.utils.hopArray(changedTiddlers,shortcutConfigTiddlers);
} else {
return false;
}
};
KeyboardManager.prototype.handleShortcutChanges = function(changedTiddlers) {
var newList = this.getShortcutTiddlerList();
var hasChanged = $tw.utils.hopArray(changedTiddlers,this.shortcutTiddlers) ? true :
($tw.utils.hopArray(changedTiddlers,newList) ? true :
(this.detectNewShortcuts(changedTiddlers))
);
// Re-cache shortcuts if something changed
if(hasChanged) {
this.updateShortcutLists(newList);
}
};
exports.KeyboardManager = KeyboardManager;
})();

View File

@@ -26,19 +26,7 @@ exports.params = [
Run the macro
*/
exports.run = function(filter) {
var tiddlers = this.wiki.filterTiddlers(filter),
data = [];
for(var t=0;t<tiddlers.length; t++) {
var tiddler = this.wiki.getTiddler(tiddlers[t]);
if(tiddler) {
var fields = new Object();
for(var field in tiddler.fields) {
fields[field] = tiddler.getFieldString(field);
}
data.push(fields);
}
}
return JSON.stringify(data,null,$tw.config.preferences.jsonSpaces);
return this.wiki.getTiddlersAsJson(filter);
};
})();

View File

@@ -0,0 +1,29 @@
/*\
title: $:/core/modules/parsers/binaryparser.js
type: application/javascript
module-type: parser
The video parser parses a video tiddler into an embeddable HTML element
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var BINARY_WARNING_MESSAGE = "$:/core/ui/BinaryWarning";
var BinaryParser = function(type,text,options) {
this.tree = [{
type: "transclude",
attributes: {
tiddler: {type: "string", value: BINARY_WARNING_MESSAGE}
}
}];
};
exports["application/octet-stream"] = BinaryParser;
})();

View File

@@ -35,6 +35,9 @@ exports["image/jpg"] = ImageParser;
exports["image/jpeg"] = ImageParser;
exports["image/png"] = ImageParser;
exports["image/gif"] = ImageParser;
exports["image/webp"] = ImageParser;
exports["image/heic"] = ImageParser;
exports["image/heif"] = ImageParser;
exports["image/x-icon"] = ImageParser;
})();

View File

@@ -0,0 +1,53 @@
/*\
title: $:/core/modules/parsers/wikiparser/rules/import.js
type: application/javascript
module-type: wikirule
Wiki pragma rule for importing variable definitions
```
\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
```
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.name = "import";
exports.types = {pragma: true};
/*
Instantiate parse rule
*/
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
this.matchRegExp = /^\\import[^\S\n]/mg;
};
/*
Parse the most recent match
*/
exports.parse = function() {
var self = this;
// Move past the pragma invocation
this.parser.pos = this.matchRegExp.lastIndex;
// Parse the filter terminated by a line break
var reMatch = /(.*)(\r?\n)|$/mg;
reMatch.lastIndex = this.parser.pos;
var match = reMatch.exec(this.parser.source);
this.parser.pos = reMatch.lastIndex;
// Parse tree nodes to return
return [{
type: "importvariables",
attributes: {
filter: {type: "string", value: match[1]}
},
children: []
}];
};
})();

View File

@@ -84,7 +84,8 @@ exports.parse = function() {
value: {type: "string", value: text}
},
children: [],
params: params
params: params,
isMacroDefinition: true
}];
};

View File

@@ -18,16 +18,22 @@ to the current URL, such as a WebDAV server.
/*
Retrieve ETag if available
*/
var RetrieveETag = function(self) {
var headers = { "Accept": "*/*;charset=UTF-8" };
var retrieveETag = function(self) {
var headers = {
Accept: "*/*;charset=UTF-8"
};
$tw.utils.httpRequest({
url: self.uri(),
type: "HEAD",
headers: headers,
callback: function(err, data, xhr) {
if(err) return;
callback: function(err,data,xhr) {
if(err) {
return;
}
var etag = xhr.getResponseHeader("ETag");
if(!etag) return;
if(!etag) {
return;
}
self.etag = etag.replace(/^W\//,"");
}
});
@@ -46,14 +52,14 @@ var PutSaver = function(wiki) {
$tw.utils.httpRequest({
url: uri,
type: "OPTIONS",
callback: function(err, data, xhr) {
callback: function(err,data,xhr) {
// Check DAV header http://www.webdav.org/specs/rfc2518.html#rfc.section.9.1
if(!err) {
self.serverAcceptsPuts = xhr.status === 200 && !!xhr.getResponseHeader("dav");
}
}
});
RetrieveETag(this);
retrieveETag(this);
};
PutSaver.prototype.uri = function() {
@@ -63,12 +69,14 @@ PutSaver.prototype.uri = function() {
// TODO: in case of edit conflict
// Prompt: Do you want to save over this? Y/N
// Merging would be ideal, and may be possible using future generic merge flow
PutSaver.prototype.save = function(text, method, callback) {
PutSaver.prototype.save = function(text,method,callback) {
if(!this.serverAcceptsPuts) {
return false;
}
var self = this;
var headers = { "Content-Type": "text/html;charset=UTF-8" };
var headers = {
"Content-Type": "text/html;charset=UTF-8"
};
if(this.etag) {
headers["If-Match"] = this.etag;
}
@@ -77,10 +85,10 @@ PutSaver.prototype.save = function(text, method, callback) {
type: "PUT",
headers: headers,
data: text,
callback: function(err, data, xhr) {
callback: function(err,data,xhr) {
if(err) {
// response is textual: "XMLHttpRequest error code: 412"
const status = Number(err.substring(err.indexOf(':') + 2, err.length))
var status = Number(err.substring(err.indexOf(':') + 2, err.length))
if(status === 412) { // edit conflict
var message = $tw.language.getString("Error/EditConflict");
callback(message);
@@ -89,8 +97,8 @@ PutSaver.prototype.save = function(text, method, callback) {
}
} else {
self.etag = xhr.getResponseHeader("ETag");
if (self.etag == null) {
RetrieveETag(self);
if(self.etag == null) {
retrieveETag(self);
}
callback(null); // success
}
@@ -105,7 +113,7 @@ Information about this saver
PutSaver.prototype.info = {
name: "put",
priority: 2000,
capabilities: ["save", "autosave"]
capabilities: ["save","autosave"]
};
/*

View File

@@ -0,0 +1,94 @@
/*\
title: $:/core/modules/server/authenticators/basic.js
type: application/javascript
module-type: authenticator
Authenticator for WWW basic authentication
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
if($tw.node) {
var util = require("util"),
fs = require("fs"),
url = require("url"),
path = require("path");
}
function BasicAuthenticator(server) {
this.server = server;
this.credentialsData = [];
}
/*
Returns true if the authenticator is active, false if it is inactive, or a string if there is an error
*/
BasicAuthenticator.prototype.init = function() {
// Read the credentials data
this.credentialsFilepath = this.server.get("credentials");
if(this.credentialsFilepath) {
var resolveCredentialsFilepath = path.resolve($tw.boot.wikiPath,this.credentialsFilepath);
if(fs.existsSync(resolveCredentialsFilepath) && !fs.statSync(resolveCredentialsFilepath).isDirectory()) {
var credentialsText = fs.readFileSync(resolveCredentialsFilepath,"utf8"),
credentialsData = $tw.utils.parseCsvStringWithHeader(credentialsText);
if(typeof credentialsData === "string") {
return "Error: " + credentialsData + " reading credentials from '" + resolveCredentialsFilepath + "'";
} else {
this.credentialsData = credentialsData;
}
} else {
return "Error: Unable to load user credentials from '" + credentialsFilepath + "'";
}
}
// Add the hardcoded username and password if specified
if(this.server.get("username") && this.server.get("password")) {
this.credentialsData = this.credentialsData || [];
this.credentialsData.push({
username: this.server.get("username"),
password: this.server.get("password")
});
}
return this.credentialsData.length > 0;
};
/*
Returns true if the request is authenticated and assigns the "authenticatedUsername" state variable.
Returns false if the request couldn't be authenticated having sent an appropriate response to the browser
*/
BasicAuthenticator.prototype.authenticateRequest = function(request,response,state) {
// Extract the incoming username and password from the request
var header = request.headers.authorization || "";
if(!header && state.allowAnon) {
// If there's no header and anonymous access is allowed then we don't set authenticatedUsername
return true;
}
var token = header.split(/\s+/).pop() || "",
auth = $tw.utils.base64Decode(token),
parts = auth.split(/:/),
incomingUsername = parts[0],
incomingPassword = parts[1];
// Check that at least one of the credentials matches
var matchingCredentials = this.credentialsData.find(function(credential) {
return credential.username === incomingUsername && credential.password === incomingPassword;
});
if(matchingCredentials) {
// If so, add the authenticated username to the request state
state.authenticatedUsername = incomingUsername;
return true;
} else {
// If not, return an authentication challenge
response.writeHead(401,"Authentication required",{
"WWW-Authenticate": 'Basic realm="Please provide your username and password to login to ' + state.server.servername + '"'
});
response.end();
return false;
}
};
exports.AuthenticatorClass = BasicAuthenticator;
})();

View File

@@ -0,0 +1,47 @@
/*\
title: $:/core/modules/server/authenticators/header.js
type: application/javascript
module-type: authenticator
Authenticator for trusted header authentication
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function HeaderAuthenticator(server) {
this.server = server;
this.header = server.get("authenticated-user-header");
}
/*
Returns true if the authenticator is active, false if it is inactive, or a string if there is an error
*/
HeaderAuthenticator.prototype.init = function() {
return !!this.header;
};
/*
Returns true if the request is authenticated and assigns the "authenticatedUsername" state variable.
Returns false if the request couldn't be authenticated having sent an appropriate response to the browser
*/
HeaderAuthenticator.prototype.authenticateRequest = function(request,response,state) {
// Otherwise, authenticate as the username in the specified header
var username = request.headers[this.header];
if(!username && !state.allowAnon) {
response.writeHead(401,"Authorization header required to login to '" + state.server.servername + "'");
response.end();
return false;
} else {
// authenticatedUsername will be undefined for anonymous users
state.authenticatedUsername = username;
return true;
}
};
exports.AuthenticatorClass = HeaderAuthenticator;
})();

View File

@@ -0,0 +1,28 @@
/*\
title: $:/core/modules/server/routes/delete-tiddler.js
type: application/javascript
module-type: route
DELETE /recipes/default/tiddlers/:title
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "DELETE";
exports.path = /^\/bags\/default\/tiddlers\/(.+)$/;
exports.handler = function(request,response,state) {
var title = decodeURIComponent(state.params[0]);
state.wiki.deleteTiddler(title);
response.writeHead(204, "OK", {
"Content-Type": "text/plain"
});
response.end();
};
}());

View File

@@ -0,0 +1,25 @@
/*\
title: $:/core/modules/server/routes/get-favicon.js
type: application/javascript
module-type: route
GET /favicon.ico
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/favicon.ico$/;
exports.handler = function(request,response,state) {
response.writeHead(200, {"Content-Type": "image/x-icon"});
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
response.end(buffer,"base64");
};
}());

View File

@@ -0,0 +1,50 @@
/*\
title: $:/core/modules/server/routes/get-file.js
type: application/javascript
module-type: route
GET /files/:filepath
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/files\/(.+)$/;
exports.handler = function(request,response,state) {
var path = require("path"),
fs = require("fs"),
util = require("util");
var filename = path.resolve($tw.boot.wikiPath,"files",decodeURIComponent(state.params[0])),
extension = path.extname(filename);
fs.readFile(filename,function(err,content) {
var status,content,type = "text/plain";
if(err) {
if(err.code === "ENOENT") {
status = 404;
content = "File '" + filename + "' not found";
} else if(err.code === "EACCES") {
status = 403;
content = "You do not have permission to access the file '" + filename + "'";
} else {
status = 500;
content = err.toString();
}
} else {
status = 200;
content = content;
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
}
response.writeHead(status,{
"Content-Type": type
});
response.end(content);
});
};
}());

View File

@@ -0,0 +1,25 @@
/*\
title: $:/core/modules/server/routes/get-index.js
type: application/javascript
module-type: route
GET /
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/$/;
exports.handler = function(request,response,state) {
response.writeHead(200, {"Content-Type": state.server.get("root-serve-type")});
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler"));
response.end(text,"utf8");
};
}());

View File

@@ -0,0 +1,35 @@
/*\
title: $:/core/modules/server/routes/get-login/basic.js
type: application/javascript
module-type: route
GET /login/basic -- force a Basic Authentication challenge
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/login\/basic$/;
exports.handler = function(request,response,state) {
if(!state.authenticatedUsername) {
// Challenge if there's no username
response.writeHead(401,{
"WWW-Authenticate": 'Basic realm="Please provide your username and password to login to ' + state.server.servername + '"'
});
response.end();
} else {
// Redirect to the root wiki if login worked
response.writeHead(302,{
Location: "/"
});
response.end();
}
};
}());

View File

@@ -0,0 +1,33 @@
/*\
title: $:/core/modules/server/routes/get-status.js
type: application/javascript
module-type: route
GET /status
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/status$/;
exports.handler = function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var text = JSON.stringify({
username: state.authenticatedUsername || state.server.get("anon-username") || "",
anonymous: !state.authenticatedUsername,
read_only: !state.server.isAuthorized("writers",state.authenticatedUsername),
space: {
recipe: "default"
},
tiddlywiki_version: $tw.version
});
response.end(text,"utf8");
};
}());

View File

@@ -0,0 +1,44 @@
/*\
title: $:/core/modules/server/routes/get-tiddler-html.js
type: application/javascript
module-type: route
GET /:title
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/([^\/]+)$/;
exports.handler = function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
tiddler = state.wiki.getTiddler(title);
if(tiddler) {
var renderType = tiddler.getFieldString("_render_type"),
renderTemplate = tiddler.getFieldString("_render_template");
// Tiddler fields '_render_type' and '_render_template' overwrite
// system wide settings for render type and template
if(state.wiki.isSystemTiddler(title)) {
renderType = renderType || state.server.get("system-tiddler-render-type");
renderTemplate = renderTemplate || state.server.get("system-tiddler-render-template");
} else {
renderType = renderType || state.server.get("tiddler-render-type");
renderTemplate = renderTemplate || state.server.get("tiddler-render-template");
}
var text = state.wiki.renderTiddler(renderType,renderTemplate,{parseAsInline: true, variables: {currentTiddler: title}});
// Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS
response.writeHead(200);
response.end(text,"utf8");
} else {
response.writeHead(404);
response.end();
}
};
}());

View File

@@ -0,0 +1,46 @@
/*\
title: $:/core/modules/server/routes/get-tiddler.js
type: application/javascript
module-type: route
GET /recipes/default/tiddlers/:title
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
exports.handler = function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
tiddler = state.wiki.getTiddler(title),
tiddlerFields = {},
knownFields = [
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
];
if(tiddler) {
$tw.utils.each(tiddler.fields,function(field,name) {
var value = tiddler.getFieldString(name);
if(knownFields.indexOf(name) !== -1) {
tiddlerFields[name] = value;
} else {
tiddlerFields.fields = tiddlerFields.fields || {};
tiddlerFields.fields[name] = value;
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
response.writeHead(200, {"Content-Type": "application/json"});
response.end(JSON.stringify(tiddlerFields),"utf8");
} else {
response.writeHead(404);
response.end();
}
};
}());

View File

@@ -0,0 +1,37 @@
/*\
title: $:/core/modules/server/routes/get-tiddlers-json.js
type: application/javascript
module-type: route
GET /recipes/default/tiddlers/tiddlers.json
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/recipes\/default\/tiddlers.json$/;
exports.handler = function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var tiddlers = [];
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
var tiddlerFields = {};
$tw.utils.each(tiddler.fields,function(field,name) {
if(name !== "text") {
tiddlerFields[name] = tiddler.getFieldString(name);
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
tiddlers.push(tiddlerFields);
});
var text = JSON.stringify(tiddlers);
response.end(text,"utf8");
};
}());

View File

@@ -0,0 +1,42 @@
/*\
title: $:/core/modules/server/routes/put-tiddler.js
type: application/javascript
module-type: route
PUT /recipes/default/tiddlers/:title
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "PUT";
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
exports.handler = function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
fields = JSON.parse(state.data);
// Pull up any subfields in the `fields` object
if(fields.fields) {
$tw.utils.each(fields.fields,function(field,name) {
fields[name] = field;
});
delete fields.fields;
}
// Remove any revision field
if(fields.revision) {
delete fields.revision;
}
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
var changeCount = state.wiki.getChangeCount(title).toString();
response.writeHead(204, "OK",{
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
"Content-Type": "text/plain"
});
response.end();
};
}());

View File

@@ -0,0 +1,260 @@
/*\
title: $:/core/modules/server/server.js
type: application/javascript
module-type: library
Serve tiddlers over http
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
if($tw.node) {
var util = require("util"),
fs = require("fs"),
url = require("url"),
path = require("path");
}
/*
A simple HTTP server with regexp-based routes
options: variables - optional hashmap of variables to set (a misnomer - they are really constant parameters)
routes - optional array of routes to use
wiki - reference to wiki object
*/
function Server(options) {
var self = this;
this.routes = options.routes || [];
this.authenticators = options.authenticators || [];
this.wiki = options.wiki;
this.servername = $tw.utils.transliterateToSafeASCII(this.wiki.getTiddlerText("$:/SiteTitle") || "TiddlyWiki5");
// Initialise the variables
this.variables = $tw.utils.extend({},this.defaultVariables);
if(options.variables) {
for(var variable in options.variables) {
if(options.variables[variable]) {
this.variables[variable] = options.variables[variable];
}
}
}
$tw.utils.extend({},this.defaultVariables,options.variables);
// Initialise CSRF
this.csrfDisable = this.get("csrf-disable") === "yes";
// Initialise authorization
var authorizedUserName = (this.get("username") && this.get("password")) ? this.get("username") : "(anon)";
this.authorizationPrincipals = {
readers: (this.get("readers") || authorizedUserName).split(",").map($tw.utils.trim),
writers: (this.get("writers") || authorizedUserName).split(",").map($tw.utils.trim)
}
// Load and initialise authenticators
$tw.modules.forEachModuleOfType("authenticator", function(title,authenticatorDefinition) {
// console.log("Loading server route " + title);
self.addAuthenticator(authenticatorDefinition.AuthenticatorClass);
});
// Load route handlers
$tw.modules.forEachModuleOfType("route", function(title,routeDefinition) {
// console.log("Loading server route " + title);
self.addRoute(routeDefinition);
});
// Initialise the http vs https
this.listenOptions = null;
this.protocol = "http";
var tlsKeyFilepath = this.get("tls-key"),
tlsCertFilepath = this.get("tls-cert");
if(tlsCertFilepath && tlsKeyFilepath) {
this.listenOptions = {
key: fs.readFileSync(path.resolve($tw.boot.wikiPath,tlsKeyFilepath),"utf8"),
cert: fs.readFileSync(path.resolve($tw.boot.wikiPath,tlsCertFilepath),"utf8")
};
this.protocol = "https";
}
this.transport = require(this.protocol);
}
Server.prototype.defaultVariables = {
port: "8080",
host: "127.0.0.1",
"root-tiddler": "$:/core/save/all",
"root-render-type": "text/plain",
"root-serve-type": "text/html",
"tiddler-render-type": "text/html",
"tiddler-render-template": "$:/core/templates/server/static.tiddler.html",
"system-tiddler-render-type": "text/plain",
"system-tiddler-render-template": "$:/core/templates/wikified-tiddler",
"debug-level": "none"
};
Server.prototype.get = function(name) {
return this.variables[name];
};
Server.prototype.addRoute = function(route) {
this.routes.push(route);
};
Server.prototype.addAuthenticator = function(AuthenticatorClass) {
// Instantiate and initialise the authenticator
var authenticator = new AuthenticatorClass(this),
result = authenticator.init();
if(typeof result === "string") {
$tw.utils.error("Error: " + result);
} else if(result) {
// Only use the authenticator if it initialised successfully
this.authenticators.push(authenticator);
}
};
Server.prototype.findMatchingRoute = function(request,state) {
var pathprefix = this.get("path-prefix") || "";
for(var t=0; t<this.routes.length; t++) {
var potentialRoute = this.routes[t],
pathRegExp = potentialRoute.path,
pathname = state.urlInfo.pathname,
match;
if(pathprefix) {
if(pathname.substr(0,pathprefix.length) === pathprefix) {
pathname = pathname.substr(pathprefix.length) || "/";
match = potentialRoute.path.exec(pathname);
} else {
match = false;
}
} else {
match = potentialRoute.path.exec(pathname);
}
if(match && request.method === potentialRoute.method) {
state.params = [];
for(var p=1; p<match.length; p++) {
state.params.push(match[p]);
}
return potentialRoute;
}
}
return null;
};
Server.prototype.methodMappings = {
"GET": "readers",
"OPTIONS": "readers",
"HEAD": "readers",
"PUT": "writers",
"POST": "writers",
"DELETE": "writers"
};
/*
Check whether a given user is authorized for the specified authorizationType ("readers" or "writers"). Pass null or undefined as the username to check for anonymous access
*/
Server.prototype.isAuthorized = function(authorizationType,username) {
var principals = this.authorizationPrincipals[authorizationType] || [];
return principals.indexOf("(anon)") !== -1 || (username && (principals.indexOf("(authenticated)") !== -1 || principals.indexOf(username) !== -1));
}
Server.prototype.requestHandler = function(request,response) {
// Compose the state object
var self = this;
var state = {};
state.wiki = self.wiki;
state.server = self;
state.urlInfo = url.parse(request.url);
// Get the principals authorized to access this resource
var authorizationType = this.methodMappings[request.method] || "readers";
// Check for the CSRF header if this is a write
if(!this.csrfDisable && authorizationType === "writers" && request.headers["x-requested-with"] !== "TiddlyWiki") {
response.writeHead(403,"'X-Requested-With' header required to login to '" + this.servername + "'");
response.end();
return;
}
// Check whether anonymous access is granted
state.allowAnon = this.isAuthorized(authorizationType,null);
// Authenticate with the first active authenticator
if(this.authenticators.length > 0) {
if(!this.authenticators[0].authenticateRequest(request,response,state)) {
// Bail if we failed (the authenticator will have sent the response)
return;
}
}
// Authorize with the authenticated username
if(!this.isAuthorized(authorizationType,state.authenticatedUsername)) {
response.writeHead(401,"'" + state.authenticatedUsername + "' is not authorized to access '" + this.servername + "'");
response.end();
return;
}
// Find the route that matches this path
var route = self.findMatchingRoute(request,state);
// Optionally output debug info
if(self.get("debug-level") !== "none") {
console.log("Request path:",JSON.stringify(state.urlInfo));
console.log("Request headers:",JSON.stringify(request.headers));
console.log("authenticatedUsername:",state.authenticatedUsername);
}
// Return a 404 if we didn't find a route
if(!route) {
response.writeHead(404);
response.end();
return;
}
// Receive the request body if necessary and hand off to the route handler
if(route.bodyFormat === "stream" || request.method === "GET" || request.method === "HEAD") {
// Let the route handle the request stream itself
route.handler(request,response,state);
} else if(route.bodyFormat === "string" || !route.bodyFormat) {
// Set the encoding for the incoming request
request.setEncoding("utf8");
var data = "";
request.on("data",function(chunk) {
data += chunk.toString();
});
request.on("end",function() {
state.data = data;
route.handler(request,response,state);
});
} else if(route.bodyFormat === "buffer") {
var data = [];
request.on("data",function(chunk) {
data.push(chunk);
});
request.on("end",function() {
state.data = Buffer.concat(data);
route.handler(request,response,state);
})
} else {
response.writeHead(400,"Invalid bodyFormat " + route.bodyFormat + " in route " + route.method + " " + route.path.source);
response.end();
}
};
/*
Listen for requests
port: optional port number (falls back to value of "port" variable)
host: optional host address (falls back to value of "hist" variable)
*/
Server.prototype.listen = function(port,host) {
// Handle defaults for port and host
port = port || this.get("port");
host = host || this.get("host");
// Check for the port being a string and look it up as an environment variable
if(parseInt(port,10).toString() !== port) {
port = process.env[port] || 8080;
}
$tw.utils.log("Serving on " + this.protocol + "://" + host + ":" + port,"brown/orange");
$tw.utils.log("(press ctrl-C to exit)","red");
// Warn if required plugins are missing
if(!$tw.wiki.getTiddler("$:/plugins/tiddlywiki/tiddlyweb") || !$tw.wiki.getTiddler("$:/plugins/tiddlywiki/filesystem")) {
$tw.utils.warning("Warning: Plugins required for client-server operation (\"tiddlywiki/filesystem\" and \"tiddlywiki/tiddlyweb\") are missing from tiddlywiki.info file");
}
// Listen
var server;
if(this.listenOptions) {
server = this.transport.createServer(this.listenOptions,this.requestHandler.bind(this));
} else {
server = this.transport.createServer(this.requestHandler.bind(this));
}
return server.listen(port,host);
};
exports.Server = Server;
})();

View File

@@ -59,6 +59,13 @@ exports.startup = function() {
$tw.pageWidgetNode.render($tw.pageContainer,null);
$tw.hooks.invokeHook("th-page-refreshed");
})();
// Remove any splash screen elements
var removeList = document.querySelectorAll(".tc-remove-when-wiki-loaded");
$tw.utils.each(removeList,function(removeItem) {
if(removeItem.parentNode) {
removeItem.parentNode.removeChild(removeItem);
}
});
// Prepare refresh mechanism
var deferredChanges = Object.create(null),
timerId;

View File

@@ -23,7 +23,7 @@ exports.startup = function() {
// Install the modal message mechanism
$tw.modal = new $tw.utils.Modal($tw.wiki);
$tw.rootWidget.addEventListener("tm-modal",function(event) {
$tw.modal.display(event.param,{variables: event.paramObject});
$tw.modal.display(event.param,{variables: event.paramObject, event: event});
});
// Install the notification mechanism
$tw.notifier = new $tw.utils.Notifier($tw.wiki);
@@ -42,10 +42,16 @@ exports.startup = function() {
var fullscreen = $tw.utils.getFullScreenApis();
if(fullscreen) {
$tw.rootWidget.addEventListener("tm-full-screen",function(event) {
if(document[fullscreen._fullscreenElement]) {
document[fullscreen._exitFullscreen]();
if(event.param === "enter") {
event.event.target.ownerDocument.documentElement[fullscreen._requestFullscreen](Element.ALLOW_KEYBOARD_INPUT);
} else if(event.param === "exit") {
event.event.target.ownerDocument[fullscreen._exitFullscreen]();
} else {
document.documentElement[fullscreen._requestFullscreen](Element.ALLOW_KEYBOARD_INPUT);
if(event.event.target.ownerDocument[fullscreen._fullscreenElement]) {
event.event.target.ownerDocument[fullscreen._exitFullscreen]();
} else {
event.event.target.ownerDocument.documentElement[fullscreen._requestFullscreen](Element.ALLOW_KEYBOARD_INPUT);
}
}
});
}

View File

@@ -34,7 +34,7 @@ exports.startup = function() {
if($tw.browser) {
$tw.platform.isMac = /Mac/.test(navigator.platform);
$tw.platform.isWindows = /win/i.test(navigator.platform);
$tw.platform.isLinux = /Linux/i.test(navigator.appVersion);
$tw.platform.isLinux = /Linux/i.test(navigator.platform);
} else {
switch(require("os").platform()) {
case "darwin":
@@ -87,6 +87,14 @@ exports.startup = function() {
});
// Kick off the keyboard manager
$tw.keyboardManager = new $tw.KeyboardManager();
// Listen for shortcuts
if($tw.browser) {
$tw.utils.addEventListeners(document,[{
name: "keydown",
handlerObject: $tw.keyboardManager,
handlerMethod: "handleKeydownEvent"
}]);
}
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
$tw.rootWidget = new widget.widget({
type: "widget",

View File

@@ -27,6 +27,12 @@ var DEFAULT_TIDDLERS_TITLE = "$:/DefaultTiddlers";
// Config
var CONFIG_UPDATE_ADDRESS_BAR = "$:/config/Navigation/UpdateAddressBar"; // Can be "no", "permalink", "permaview"
var CONFIG_UPDATE_HISTORY = "$:/config/Navigation/UpdateHistory"; // Can be "yes" or "no"
var CONFIG_PERMALINKVIEW_COPY_TO_CLIPBOARD = "$:/config/Navigation/Permalinkview/CopyToClipboard"; // Can be "yes" (default) or "no"
var CONFIG_PERMALINKVIEW_UPDATE_ADDRESS_BAR = "$:/config/Navigation/Permalinkview/UpdateAddressBar"; // Can be "yes" (default) or "no"
// Links to help, if there is no param
var HELP_OPEN_EXTERNAL_WINDOW = "http://tiddlywiki.com/#WidgetMessage%3A%20tm-open-external-window";
exports.startup = function() {
// Open startup tiddlers
@@ -53,6 +59,14 @@ exports.startup = function() {
$tw.rootWidget.addEventListener("tm-browser-refresh",function(event) {
window.location.reload(true);
});
// Listen for tm-open-external-window message
$tw.rootWidget.addEventListener("tm-open-external-window",function(event) {
var paramObject = event.paramObject || {},
strUrl = event.param || HELP_OPEN_EXTERNAL_WINDOW,
strWindowName = paramObject.windowName,
strWindowFeatures = paramObject.windowFeatures;
window.open(strUrl, strWindowName, strWindowFeatures);
});
// Listen for the tm-print message
$tw.rootWidget.addEventListener("tm-print",function(event) {
(event.event.view || window).print();
@@ -66,24 +80,26 @@ exports.startup = function() {
storyList = $tw.hooks.invokeHook("th-opening-default-tiddlers-list",storyList);
$tw.wiki.addTiddler({title: DEFAULT_STORY_TITLE, text: "", list: storyList},$tw.wiki.getModificationFields());
if(storyList[0]) {
$tw.wiki.addToHistory(storyList[0]);
$tw.wiki.addToHistory(storyList[0]);
}
});
// Listen for the tm-permalink message
$tw.rootWidget.addEventListener("tm-permalink",function(event) {
updateLocationHash({
updateAddressBar: "permalink",
updateAddressBar: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_UPDATE_ADDRESS_BAR,"yes").trim() === "yes" ? "permalink" : "none",
updateHistory: $tw.wiki.getTiddlerText(CONFIG_UPDATE_HISTORY,"no").trim(),
targetTiddler: event.param || event.tiddlerTitle
targetTiddler: event.param || event.tiddlerTitle,
copyToClipboard: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_COPY_TO_CLIPBOARD,"yes").trim() === "yes" ? "permalink" : "none"
});
});
// Listen for the tm-permaview message
$tw.rootWidget.addEventListener("tm-permaview",function(event) {
updateLocationHash({
updateAddressBar: "permaview",
updateAddressBar: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_UPDATE_ADDRESS_BAR,"yes").trim() === "yes" ? "permaview" : "none",
updateHistory: $tw.wiki.getTiddlerText(CONFIG_UPDATE_HISTORY,"no").trim(),
targetTiddler: event.param || event.tiddlerTitle
});
targetTiddler: event.param || event.tiddlerTitle,
copyToClipboard: $tw.wiki.getTiddlerText(CONFIG_PERMALINKVIEW_COPY_TO_CLIPBOARD,"yes").trim() === "yes" ? "permaview" : "none"
});
});
}
};
@@ -146,41 +162,52 @@ function openStartupTiddlers(options) {
options: See below
options.updateAddressBar: "permalink", "permaview" or "no" (defaults to "permaview")
options.updateHistory: "yes" or "no" (defaults to "no")
options.copyToClipboard: "permalink", "permaview" or "no" (defaults to "no")
options.targetTiddler: optional title of target tiddler for permalink
*/
function updateLocationHash(options) {
if(options.updateAddressBar !== "no") {
// Get the story and the history stack
var storyList = $tw.wiki.getTiddlerList(DEFAULT_STORY_TITLE),
historyList = $tw.wiki.getTiddlerData(DEFAULT_HISTORY_TITLE,[]),
// Get the story and the history stack
var storyList = $tw.wiki.getTiddlerList(DEFAULT_STORY_TITLE),
historyList = $tw.wiki.getTiddlerData(DEFAULT_HISTORY_TITLE,[]),
targetTiddler = "";
if(options.targetTiddler) {
targetTiddler = options.targetTiddler;
} else {
// The target tiddler is the one at the top of the stack
if(historyList.length > 0) {
targetTiddler = historyList[historyList.length-1].title;
}
// Blank the target tiddler if it isn't present in the story
if(storyList.indexOf(targetTiddler) === -1) {
targetTiddler = "";
if(options.targetTiddler) {
targetTiddler = options.targetTiddler;
} else {
// The target tiddler is the one at the top of the stack
if(historyList.length > 0) {
targetTiddler = historyList[historyList.length-1].title;
}
// Blank the target tiddler if it isn't present in the story
if(storyList.indexOf(targetTiddler) === -1) {
targetTiddler = "";
}
}
// Assemble the location hash
if(options.updateAddressBar === "permalink") {
}
// Assemble the location hash
switch(options.updateAddressBar) {
case "permalink":
$tw.locationHash = "#" + encodeURIComponent(targetTiddler);
} else {
break;
case "permaview":
$tw.locationHash = "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList));
}
// Only change the location hash if we must, thus avoiding unnecessary onhashchange events
if($tw.utils.getLocationHash() !== $tw.locationHash) {
if(options.updateHistory === "yes") {
// Assign the location hash so that history is updated
window.location.hash = $tw.locationHash;
} else {
// We use replace so that browser history isn't affected
window.location.replace(window.location.toString().split("#")[0] + $tw.locationHash);
}
break;
}
// Copy URL to the clipboard
switch(options.copyToClipboard) {
case "permalink":
$tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler));
break;
case "permaview":
$tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList)));
break;
}
// Only change the location hash if we must, thus avoiding unnecessary onhashchange events
if($tw.utils.getLocationHash() !== $tw.locationHash) {
if(options.updateHistory === "yes") {
// Assign the location hash so that history is updated
window.location.hash = $tw.locationHash;
} else {
// We use replace so that browser history isn't affected
window.location.replace(window.location.toString().split("#")[0] + $tw.locationHash);
}
}
}

View File

@@ -29,13 +29,20 @@ exports.startup = function() {
title = event.param || event.tiddlerTitle,
paramObject = event.paramObject || {},
template = paramObject.template || "$:/core/templates/single.tiddler.window",
print = paramObject.print === "yes",
width = paramObject.width || "700",
height = paramObject.height || "600",
variables = $tw.utils.extend({},paramObject,{currentTiddler: title});
// Open the window
var srcWindow = window.open("","external-" + title,"scrollbars,width=" + width + ",height=" + height),
var srcWindow,
srcDocument;
// In case that popup blockers deny opening a new window
try {
srcWindow = window.open("","external-" + title,"scrollbars,width=" + width + ",height=" + height),
srcDocument = srcWindow.document;
}
catch(e) {
return;
}
windows[title] = srcWindow;
// Check for reopening the same window
if(srcWindow.haveInitialisedWindow) {
@@ -63,10 +70,6 @@ exports.startup = function() {
var parser = $tw.wiki.parseTiddler(template),
widgetNode = $tw.wiki.makeWidget(parser,{document: srcDocument, parentWidget: $tw.rootWidget, variables: variables});
widgetNode.render(srcDocument.body,srcDocument.body.firstChild);
// Print the window if required
if(print) {
srcWindow.print();
}
// Function to handle refreshes
refreshHandler = function(changes) {
if(styleWidgetNode.refresh(changes,styleContainer,null)) {
@@ -75,6 +78,16 @@ exports.startup = function() {
widgetNode.refresh(changes);
};
$tw.wiki.addEventListener("change",refreshHandler);
// Listen for keyboard shortcuts
$tw.utils.addEventListeners(srcDocument,[{
name: "keydown",
handlerObject: $tw.keyboardManager,
handlerMethod: "handleKeydownEvent"
},{
name: "click",
handlerObject: $tw.popup,
handlerMethod: "handleEvent"
}]);
srcWindow.haveInitialisedWindow = true;
});
// Close open windows when unloading main window

View File

@@ -16,8 +16,11 @@ The syncer tracks changes to the store. If a syncadaptor is used then individual
Defaults
*/
Syncer.prototype.titleIsLoggedIn = "$:/status/IsLoggedIn";
Syncer.prototype.titleIsAnonymous = "$:/status/IsAnonymous";
Syncer.prototype.titleIsReadOnly = "$:/status/IsReadOnly";
Syncer.prototype.titleUserName = "$:/status/UserName";
Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
Syncer.prototype.taskTimerInterval = 1 * 1000; // Interval for sync timer
Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s...
@@ -41,7 +44,7 @@ function Syncer(options) {
this.taskTimerInterval = options.taskTimerInterval || this.taskTimerInterval;
this.throttleInterval = options.throttleInterval || this.throttleInterval;
this.fallbackInterval = options.fallbackInterval || this.fallbackInterval;
this.pollTimerInterval = options.pollTimerInterval || this.pollTimerInterval;
this.pollTimerInterval = options.pollTimerInterval || parseInt(this.wiki.getTiddlerText(this.titleSyncPollingInterval,""),10) || this.pollTimerInterval;
this.logging = "logging" in options ? options.logging : true;
// Make a logger
this.logger = new $tw.utils.Logger("syncer" + ($tw.browser ? "-browser" : "") + ($tw.node ? "-server" : "") + (this.syncadaptor.name ? ("-" + this.syncadaptor.name) : ""),{
@@ -151,7 +154,7 @@ Save an incoming tiddler in the store, and updates the associated tiddlerInfo
*/
Syncer.prototype.storeTiddler = function(tiddlerFields,hasBeenLazyLoaded) {
// Save the tiddler
var tiddler = new $tw.Tiddler(this.wiki.getTiddler(tiddlerFields.title),tiddlerFields);
var tiddler = new $tw.Tiddler(tiddlerFields);
this.wiki.addTiddler(tiddler);
// Save the tiddler revision and changeCount details
this.tiddlerInfo[tiddlerFields.title] = {
@@ -169,12 +172,14 @@ Syncer.prototype.getStatus = function(callback) {
// Mark us as not logged in
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
// Get login status
this.syncadaptor.getStatus(function(err,isLoggedIn,username) {
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous) {
if(err) {
self.logger.alert(err);
return;
}
// Set the various status tiddlers
self.wiki.addTiddler({title: self.titleIsReadOnly,text: isReadOnly ? "yes" : "no"});
self.wiki.addTiddler({title: self.titleIsAnonymous,text: isAnonymous ? "yes" : "no"});
self.wiki.addTiddler({title: self.titleIsLoggedIn,text: isLoggedIn ? "yes" : "no"});
if(isLoggedIn) {
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});

View File

@@ -39,6 +39,18 @@ exports.getFieldString = function(field) {
}
};
/*
Get the value of a field as a list
*/
exports.getFieldList = function(field) {
var value = this.fields[field];
// Check for a missing field
if(value === undefined || value === null) {
return [];
}
return $tw.utils.parseStringArray(value);
};
/*
Get all the fields as a hashmap of strings. Options:
exclude: an array of field names to exclude

46
core/modules/utils/csv.js Normal file
View File

@@ -0,0 +1,46 @@
/*\
title: $:/core/modules/utils/csv.js
type: application/javascript
module-type: utils
A barebones CSV parser
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Parse a CSV string with a header row and return an array of hashmaps.
*/
exports.parseCsvStringWithHeader = function(text,options) {
options = options || {};
var separator = options.separator || ",",
rows = text.split(/\r?\n/mg).map(function(row) {
return $tw.utils.trim(row);
}).filter(function(row) {
return row !== "";
});
if(rows.length < 1) {
return "Missing header row";
}
var headings = rows[0].split(separator),
results = [];
for(var row=1; row<rows.length; row++) {
var columns = rows[row].split(separator),
columnResult = Object.create(null);
if(columns.length !== headings.length) {
return "Malformed CSV row '" + rows[row] + "'";
}
for(var column=0; column<columns.length; column++) {
var columnName = headings[column];
columnResult[columnName] = $tw.utils.trim(columns[column] || "");
}
results.push(columnResult);
}
return results;
}
})();

View File

@@ -82,11 +82,12 @@ Returns:
y: vertical scroll position in pixels
}
*/
exports.getScrollPosition = function() {
if("scrollX" in window) {
return {x: window.scrollX, y: window.scrollY};
exports.getScrollPosition = function(srcWindow) {
var scrollWindow = srcWindow || window;
if("scrollX" in scrollWindow) {
return {x: scrollWindow.scrollX, y: scrollWindow.scrollY};
} else {
return {x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop};
return {x: scrollWindow.document.documentElement.scrollLeft, y: scrollWindow.document.documentElement.scrollTop};
}
};
@@ -119,7 +120,7 @@ exports.resizeTextAreaToFit = function(domNode,minHeight) {
Gets the bounding rectangle of an element in absolute page coordinates
*/
exports.getBoundingPageRect = function(element) {
var scrollPos = $tw.utils.getScrollPosition(),
var scrollPos = $tw.utils.getScrollPosition(element.ownerDocument.defaultView),
clientRect = element.getBoundingClientRect();
return {
left: clientRect.left + scrollPos.x,
@@ -263,4 +264,9 @@ exports.copyToClipboard = function(text,options) {
document.body.removeChild(textArea);
};
exports.getLocationPath = function() {
return window.location.toString().split("#")[0];
};
})();

View File

@@ -134,6 +134,12 @@ exports.makeDraggable = function(options) {
exports.importDataTransfer = function(dataTransfer,fallbackTitle,callback) {
// Try each provided data type in turn
if($tw.log.IMPORT) {
console.log("Available data types:");
for(var type=0; type<dataTransfer.types.length; type++) {
console.log("type",dataTransfer.types[type],dataTransfer.getData(dataTransfer.types[type]))
}
}
for(var t=0; t<importDataTypes.length; t++) {
if(!$tw.browser.isIE || importDataTypes[t].IECompatible) {
// Get the data

View File

@@ -15,6 +15,7 @@ Browser HTTP support
/*
A quick and dirty HTTP function; to be refactored later. Options are:
url: URL to retrieve
headers: hashmap of headers to send
type: GET, PUT, POST etc
callback: function invoked with (err,data)
returnProp: string name of the property to return as first argument of callback
@@ -60,6 +61,9 @@ exports.httpRequest = function(options) {
if(data && !$tw.utils.hop(headers,"Content-type")) {
request.setRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8");
}
if(!$tw.utils.hop(headers,"X-Requested-With")) {
request.setRequestHeader("X-Requested-With","TiddlyWiki");
}
try {
request.send(data);
} catch(e) {

View File

@@ -28,6 +28,10 @@ Options include:
*/
Modal.prototype.display = function(title,options) {
options = options || {};
this.srcDocument = options.variables && (options.variables.rootwindow === "true" ||
options.variables.rootwindow === "yes") ? document :
(options.event.event ? options.event.event.target.ownerDocument : document);
this.srcWindow = this.srcDocument.defaultView;
var self = this,
refreshHandler,
duration = $tw.utils.getAnimationDuration(),
@@ -39,16 +43,16 @@ Modal.prototype.display = function(title,options) {
// Create the variables
var variables = $tw.utils.extend({currentTiddler: title},options.variables);
// Create the wrapper divs
var wrapper = document.createElement("div"),
modalBackdrop = document.createElement("div"),
modalWrapper = document.createElement("div"),
modalHeader = document.createElement("div"),
headerTitle = document.createElement("h3"),
modalBody = document.createElement("div"),
modalLink = document.createElement("a"),
modalFooter = document.createElement("div"),
modalFooterHelp = document.createElement("span"),
modalFooterButtons = document.createElement("span");
var wrapper = this.srcDocument.createElement("div"),
modalBackdrop = this.srcDocument.createElement("div"),
modalWrapper = this.srcDocument.createElement("div"),
modalHeader = this.srcDocument.createElement("div"),
headerTitle = this.srcDocument.createElement("h3"),
modalBody = this.srcDocument.createElement("div"),
modalLink = this.srcDocument.createElement("a"),
modalFooter = this.srcDocument.createElement("div"),
modalFooterHelp = this.srcDocument.createElement("span"),
modalFooterButtons = this.srcDocument.createElement("span");
// Up the modal count and adjust the body class
this.modalCount++;
this.adjustPageClass();
@@ -80,7 +84,7 @@ Modal.prototype.display = function(title,options) {
value: title
}}}],
parentWidget: $tw.rootWidget,
document: document,
document: this.srcDocument,
variables: variables,
importPageMacros: true
});
@@ -88,7 +92,7 @@ Modal.prototype.display = function(title,options) {
// Render the body of the message
var bodyWidgetNode = this.wiki.makeTranscludeWidget(title,{
parentWidget: $tw.rootWidget,
document: document,
document: this.srcDocument,
variables: variables,
importPageMacros: true
});
@@ -96,16 +100,16 @@ Modal.prototype.display = function(title,options) {
// Setup the link if present
if(options.downloadLink) {
modalLink.href = options.downloadLink;
modalLink.appendChild(document.createTextNode("Right-click to save changes"));
modalLink.appendChild(this.srcDocument.createTextNode("Right-click to save changes"));
modalBody.appendChild(modalLink);
}
// Render the footer of the message
if(tiddler && tiddler.fields && tiddler.fields.help) {
var link = document.createElement("a");
var link = this.srcDocument.createElement("a");
link.setAttribute("href",tiddler.fields.help);
link.setAttribute("target","_blank");
link.setAttribute("rel","noopener noreferrer");
link.appendChild(document.createTextNode("Help"));
link.appendChild(this.srcDocument.createTextNode("Help"));
modalFooterHelp.appendChild(link);
modalFooterHelp.style.float = "left";
}
@@ -129,7 +133,7 @@ Modal.prototype.display = function(title,options) {
}}}
]}],
parentWidget: $tw.rootWidget,
document: document,
document: this.srcDocument,
variables: variables,
importPageMacros: true
});
@@ -155,13 +159,13 @@ Modal.prototype.display = function(title,options) {
{opacity: "0"}
]);
$tw.utils.setStyle(modalWrapper,[
{transform: "translateY(" + window.innerHeight + "px)"}
{transform: "translateY(" + self.srcWindow.innerHeight + "px)"}
]);
// Set up an event for the transition end
window.setTimeout(function() {
self.srcWindow.setTimeout(function() {
if(wrapper.parentNode) {
// Remove the modal message from the DOM
document.body.removeChild(wrapper);
self.srcDocument.body.removeChild(wrapper);
}
},duration);
// Don't let anyone else handle the tm-close-tiddler message
@@ -176,10 +180,10 @@ Modal.prototype.display = function(title,options) {
]);
$tw.utils.setStyle(modalWrapper,[
{transformOrigin: "0% 0%"},
{transform: "translateY(" + (-window.innerHeight) + "px)"}
{transform: "translateY(" + (-this.srcWindow.innerHeight) + "px)"}
]);
// Put the message into the document
document.body.appendChild(wrapper);
this.srcDocument.body.appendChild(wrapper);
// Set up animation for the styles
$tw.utils.setStyle(modalBackdrop,[
{transition: "opacity " + duration + "ms ease-out"}
@@ -200,8 +204,9 @@ Modal.prototype.display = function(title,options) {
};
Modal.prototype.adjustPageClass = function() {
if($tw.pageContainer) {
$tw.utils.toggleClass($tw.pageContainer,"tc-modal-displayed",this.modalCount > 0);
var windowContainer = $tw.pageContainer ? ($tw.pageContainer === this.srcDocument.body.firstChild ? $tw.pageContainer : this.srcDocument.body.firstChild) : null;
if(windowContainer) {
$tw.utils.toggleClass(windowContainer,"tc-modal-displayed",this.modalCount > 0);
}
};

View File

@@ -25,9 +25,11 @@ var Popup = function(options) {
/*
Trigger a popup open or closed. Parameters are in a hashmap:
title: title of the tiddler where the popup details are stored
domNode: dom node to which the popup will be positioned
domNode: dom node to which the popup will be positioned (one of domNode or domNodeRect is required)
domNodeRect: rectangle to which the popup will be positioned
wiki: wiki
force: if specified, forces the popup state to true or false (instead of toggling it)
floating: if true, skips registering the popup, meaning that it will need manually clearing
*/
Popup.prototype.triggerPopup = function(options) {
// Check if this popup is already active
@@ -112,8 +114,9 @@ Popup.prototype.show = function(options) {
var info = this.popupInfo(options.domNode);
// Cancel any higher level popups
this.cancel(info.popupLevel);
// Store the popup details if not already there
if(this.findPopup(options.title) === -1) {
if(!options.floating && this.findPopup(options.title) === -1) {
this.popups.push({
title: options.title,
wiki: options.wiki,
@@ -121,9 +124,24 @@ Popup.prototype.show = function(options) {
});
}
// Set the state tiddler
options.wiki.setTextReference(options.title,
"(" + options.domNode.offsetLeft + "," + options.domNode.offsetTop + "," +
options.domNode.offsetWidth + "," + options.domNode.offsetHeight + ")");
var rect;
if(options.domNodeRect) {
rect = options.domNodeRect;
} else {
rect = {
left: options.domNode.offsetLeft,
top: options.domNode.offsetTop,
width: options.domNode.offsetWidth,
height: options.domNode.offsetHeight
};
}
var popupRect = "(" + rect.left + "," + rect.top + "," +
rect.width + "," + rect.height + ")";
if(options.noStateReference) {
options.wiki.setText(options.title,"text",undefined,popupRect);
} else {
options.wiki.setTextReference(options.title,popupRect);
}
// Add the click handler if we have any popups
if(this.popups.length > 0) {
this.rootElement.addEventListener("click",this,true);

View File

@@ -33,9 +33,9 @@ var PageScroller = function() {
};
};
PageScroller.prototype.cancelScroll = function() {
PageScroller.prototype.cancelScroll = function(srcWindow) {
if(this.idRequestFrame) {
this.cancelAnimationFrame.call(window,this.idRequestFrame);
this.cancelAnimationFrame.call(srcWindow,this.idRequestFrame);
this.idRequestFrame = null;
}
};
@@ -53,19 +53,26 @@ PageScroller.prototype.handleEvent = function(event) {
/*
Handle a scroll event hitting the page document
*/
PageScroller.prototype.scrollIntoView = function(element) {
PageScroller.prototype.scrollIntoView = function(element,callback) {
var self = this,
duration = $tw.utils.getAnimationDuration();
duration = $tw.utils.getAnimationDuration(),
srcWindow = element ? element.ownerDocument.defaultView : window;
// Now get ready to scroll the body
this.cancelScroll();
this.cancelScroll(srcWindow);
this.startTime = Date.now();
// Get the height of any position:fixed toolbars
var toolbar = srcWindow.document.querySelector(".tc-adjust-top-of-scroll"),
offset = 0;
if(toolbar) {
offset = toolbar.offsetHeight;
}
// Get the client bounds of the element and adjust by the scroll position
var getBounds = function() {
var clientBounds = element.getBoundingClientRect(),
scrollPosition = $tw.utils.getScrollPosition();
var clientBounds = typeof callback === 'function' ? callback() : element.getBoundingClientRect(),
scrollPosition = $tw.utils.getScrollPosition(srcWindow);
return {
left: clientBounds.left + scrollPosition.x,
top: clientBounds.top + scrollPosition.y,
top: clientBounds.top + scrollPosition.y - offset,
width: clientBounds.width,
height: clientBounds.height
};
@@ -90,17 +97,17 @@ PageScroller.prototype.scrollIntoView = function(element) {
t = ((Date.now()) - self.startTime) / duration;
}
if(t >= 1) {
self.cancelScroll();
self.cancelScroll(srcWindow);
t = 1;
}
t = $tw.utils.slowInSlowOut(t);
var scrollPosition = $tw.utils.getScrollPosition(),
var scrollPosition = $tw.utils.getScrollPosition(srcWindow),
bounds = getBounds(),
endX = getEndPos(bounds.left,bounds.width,scrollPosition.x,window.innerWidth),
endY = getEndPos(bounds.top,bounds.height,scrollPosition.y,window.innerHeight);
window.scrollTo(scrollPosition.x + (endX - scrollPosition.x) * t,scrollPosition.y + (endY - scrollPosition.y) * t);
endX = getEndPos(bounds.left,bounds.width,scrollPosition.x,srcWindow.innerWidth),
endY = getEndPos(bounds.top,bounds.height,scrollPosition.y,srcWindow.innerHeight);
srcWindow.scrollTo(scrollPosition.x + (endX - scrollPosition.x) * t,scrollPosition.y + (endY - scrollPosition.y) * t);
if(t < 1) {
self.idRequestFrame = self.requestAnimationFrame.call(window,drawFrame);
self.idRequestFrame = self.requestAnimationFrame.call(srcWindow,drawFrame);
}
};
drawFrame();

View File

@@ -57,7 +57,7 @@ var FILE_BUFFER_LENGTH = 64 * 1024,
exports.copyFile = function(srcPath,dstPath) {
// Create buffer if required
if(!fileBuffer) {
fileBuffer = new Buffer(FILE_BUFFER_LENGTH);
fileBuffer = Buffer.alloc(FILE_BUFFER_LENGTH);
}
// Create any directories in the destination
$tw.utils.createDirectory(path.dirname(dstPath));

View File

@@ -916,4 +916,10 @@ exports.transliterate = function(str) {
});
};
exports.transliterateToSafeASCII = function(str) {
return str.replace(/[^\x00-\x7F]/g,function(ch) {
return exports.transliterationPairs[ch] || ""
});
};
})();

View File

@@ -149,6 +149,18 @@ exports.isArrayEqual = function(array1,array2) {
});
};
/*
Determine whether an array-item is an object-property
*/
exports.hopArray = function(object,array) {
for(var i=0; i<array.length; i++) {
if($tw.utils.hop(object,array[i])) {
return true;
}
}
return false;
};
/*
Push entries onto an array, removing them first if they already exist in the array
array: array to modify (assumed to be free of duplicates)
@@ -497,15 +509,21 @@ exports.htmlEncode = function(s) {
// Converts all HTML entities to their character equivalents
exports.entityDecode = function(s) {
var converter = String.fromCodePoint || String.fromCharCode,
e = s.substr(1,s.length-2); // Strip the & and the ;
e = s.substr(1,s.length-2), // Strip the & and the ;
c;
if(e.charAt(0) === "#") {
if(e.charAt(1) === "x" || e.charAt(1) === "X") {
return converter(parseInt(e.substr(2),16));
c = parseInt(e.substr(2),16);
} else {
return converter(parseInt(e.substr(1),10));
c = parseInt(e.substr(1),10);
}
if(isNaN(c)) {
return s;
} else {
return converter(c);
}
} else {
var c = $tw.config.htmlEntities[e];
c = $tw.config.htmlEntities[e];
if(c) {
return converter(c);
} else {
@@ -712,7 +730,7 @@ exports.base64Decode = function(string64) {
// TODO
throw "$tw.utils.base64Decode() doesn't work in the browser";
} else {
return (new Buffer(string64,"base64")).toString();
return Buffer.from(string64,"base64").toString();
}
};

View File

@@ -55,6 +55,7 @@ NavigateWidget.prototype.refresh = function(changedTiddlers) {
Invoke the action associated with this widget
*/
NavigateWidget.prototype.invokeAction = function(triggeringWidget,event) {
event = event || {};
var bounds = triggeringWidget && triggeringWidget.getBoundingClientRect && triggeringWidget.getBoundingClientRect(),
suppressNavigation = event.metaKey || event.ctrlKey || (event.button === 1);
if(this.actionScroll === "yes") {

View File

@@ -41,9 +41,9 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
var domNode = this.document.createElement(tag);
// Assign classes
var classes = this["class"].split(" ") || [],
isPoppedUp = this.popup && this.isPoppedUp();
isPoppedUp = (this.popup || this.popupTitle) && this.isPoppedUp();
if(this.selectedClass) {
if(this.set && this.setTo && this.isSelected()) {
if((this.set || this.setTitle) && this.setTo && this.isSelected()) {
$tw.utils.pushTop(classes,this.selectedClass.split(" "));
}
if(isPoppedUp) {
@@ -78,11 +78,11 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
self.dispatchMessage(event);
handled = true;
}
if(self.popup) {
if(self.popup || self.popupTitle) {
self.triggerPopup(event);
handled = true;
}
if(self.set) {
if(self.set || self.setTitle) {
self.setTiddler();
handled = true;
}
@@ -122,11 +122,14 @@ ButtonWidget.prototype.getBoundingClientRect = function() {
};
ButtonWidget.prototype.isSelected = function() {
return this.wiki.getTextReference(this.set,this.defaultSetValue,this.getVariable("currentTiddler")) === this.setTo;
return this.setTitle ? (this.setField ? this.wiki.getTiddler(this.setTitle).getFieldString(this.setField) === this.setTo :
(this.setIndex ? this.wiki.extractTiddlerDataItem(this.setTitle,this.setIndex) === this.setTo :
this.wiki.getTiddlerText(this.setTitle))) || this.defaultSetValue || this.getVariable("currentTiddler") :
this.wiki.getTextReference(this.set,this.defaultSetValue,this.getVariable("currentTiddler")) === this.setTo;
};
ButtonWidget.prototype.isPoppedUp = function() {
var tiddler = this.wiki.getTiddler(this.popup);
var tiddler = this.popupTitle ? this.wiki.getTiddler(this.popupTitle) : this.wiki.getTiddler(this.popup);
var result = tiddler && tiddler.fields.text ? $tw.popup.readPopupState(tiddler.fields.text) : false;
return result;
};
@@ -150,15 +153,30 @@ ButtonWidget.prototype.dispatchMessage = function(event) {
};
ButtonWidget.prototype.triggerPopup = function(event) {
$tw.popup.triggerPopup({
domNode: this.domNodes[0],
title: this.popup,
wiki: this.wiki
});
if(this.popupTitle) {
$tw.popup.triggerPopup({
domNode: this.domNodes[0],
title: this.popupTitle,
wiki: this.wiki,
noStateReference: true
});
} else {
$tw.popup.triggerPopup({
domNode: this.domNodes[0],
title: this.popup,
wiki: this.wiki
});
}
};
ButtonWidget.prototype.setTiddler = function() {
this.wiki.setTextReference(this.set,this.setTo,this.getVariable("currentTiddler"));
if(this.setTitle) {
this.setField ? this.wiki.setText(this.setTitle,this.setField,undefined,this.setTo) :
(this.setIndex ? this.wiki.setText(this.setTitle,undefined,this.setIndex,this.setTo) :
this.wiki.setText(this.setTitle,"text",undefined,this.setTo));
} else {
this.wiki.setTextReference(this.set,this.setTo,this.getVariable("currentTiddler"));
}
};
/*
@@ -183,6 +201,10 @@ ButtonWidget.prototype.execute = function() {
this.buttonTag = this.getAttribute("tag");
this.dragTiddler = this.getAttribute("dragTiddler");
this.dragFilter = this.getAttribute("dragFilter");
this.setTitle = this.getAttribute("setTitle");
this.setField = this.getAttribute("setField");
this.setIndex = this.getAttribute("setIndex");
this.popupTitle = this.getAttribute("popupTitle");
// Make child widgets
this.makeChildWidgets();
};
@@ -192,7 +214,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
ButtonWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.to || changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes["class"] || changedAttributes.selectedClass || changedAttributes.style || changedAttributes.dragFilter || changedAttributes.dragTiddler || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) {
if(changedAttributes.to || changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes["class"] || changedAttributes.selectedClass || changedAttributes.style || changedAttributes.dragFilter || changedAttributes.dragTiddler || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup]) || (this.popupTitle && changedAttributes[this.popupTitle]) || changedAttributes.setTitle || changedAttributes.setField || changedAttributes.setIndex || changedAttributes.popupTitle) {
this.refreshSelf();
return true;
}

View File

@@ -145,7 +145,7 @@ DropZoneWidget.prototype.handlePasteEvent = function(event) {
self.dispatchEvent({type: "tm-import-tiddlers", param: JSON.stringify(tiddlerFieldsArray)});
};
// Let the browser handle it if we're in a textarea or input box
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1) {
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1 && !event.target.isContentEditable) {
var self = this,
items = event.clipboardData.items;
// Enumerate the clipboard items

View File

@@ -35,6 +35,14 @@ ElementWidget.prototype.render = function(parent,nextSibling) {
if($tw.config.htmlUnsafeElements.indexOf(tag) !== -1) {
tag = "safe-" + tag;
}
// Adjust headings by the current base level
var headingLevel = ["h1","h2","h3","h4","h5","h6"].indexOf(tag);
if(headingLevel !== -1) {
var baseLevel = parseInt(this.getVariable("tv-adjust-heading-level","0"),10) || 0;
headingLevel = Math.min(Math.max(headingLevel + 1 + baseLevel,1),6);
tag = "h" + headingLevel;
}
// Create the DOM node
var domNode = this.document.createElementNS(this.namespace,tag);
this.assignAttributes(domNode,{excludeEventAttributes: true});
parent.insertBefore(domNode,nextSibling);

View File

@@ -63,7 +63,8 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
addWidgetNode({
type: "set",
attributes: parseTreeNode.attributes,
params: parseTreeNode.params
params: parseTreeNode.params,
isMacroDefinition: parseTreeNode.isMacroDefinition
});
parseTreeNode = parseTreeNode.children[0];
}

View File

@@ -71,6 +71,7 @@ KeyboardWidget.prototype.dispatchMessage = function(event) {
Compute the internal state of the widget
*/
KeyboardWidget.prototype.execute = function() {
var self = this;
// Get attributes
this.actions = this.getAttribute("actions");
this.message = this.getAttribute("message");
@@ -79,6 +80,13 @@ KeyboardWidget.prototype.execute = function() {
this.tag = this.getAttribute("tag");
this.keyInfoArray = $tw.keyboardManager.parseKeyDescriptors(this.key);
this["class"] = this.getAttribute("class");
if(this.key.substr(0,2) === "((" && this.key.substr(-2,2) === "))") {
this.shortcutTiddlers = [];
var name = this.key.substring(2,this.key.length -2);
$tw.utils.each($tw.keyboardManager.lookupNames,function(platformDescriptor) {
self.shortcutTiddlers.push("$:/config/" + platformDescriptor + "/" + name);
});
}
// Make child widgets
this.makeChildWidgets();
};
@@ -92,6 +100,10 @@ KeyboardWidget.prototype.refresh = function(changedTiddlers) {
this.refreshSelf();
return true;
}
// Update the keyInfoArray if one of its shortcut-config-tiddlers has changed
if(this.shortcutTiddlers && $tw.utils.hopArray(changedTiddlers,this.shortcutTiddlers)) {
this.keyInfoArray = $tw.keyboardManager.parseKeyDescriptors(this.key);
}
return this.refreshChildren(changedTiddlers);
};

View File

@@ -13,7 +13,6 @@ Link widget
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var MISSING_LINK_CONFIG_TITLE = "$:/config/MissingLinks";
var LinkWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
@@ -182,7 +181,7 @@ LinkWidget.prototype.execute = function() {
// Determine the link characteristics
this.isMissing = !this.wiki.tiddlerExists(this.to);
this.isShadow = this.wiki.isShadowTiddler(this.to);
this.hideMissingLinks = ($tw.wiki.getTiddlerText(MISSING_LINK_CONFIG_TITLE,"yes") === "no");
this.hideMissingLinks = (this.getVariable("tv-show-missing-links") || "yes") === "no";
// Make the child widgets
this.makeChildWidgets();
};
@@ -192,7 +191,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
LinkWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip || changedTiddlers[MISSING_LINK_CONFIG_TITLE]) {
if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip) {
this.refreshSelf();
return true;
}

View File

@@ -116,51 +116,7 @@ NavigatorWidget.prototype.replaceFirstTitleInStory = function(storyList,oldTitle
};
NavigatorWidget.prototype.addToStory = function(title,fromTitle) {
var storyList = this.getStoryList();
// Quit if we cannot get hold of the story list
if(!storyList) {
return;
}
// See if the tiddler is already there
var slot = storyList.indexOf(title);
// Quit if it already exists in the story river
if(slot >= 0) {
return;
}
// First we try to find the position of the story element we navigated from
var fromIndex = storyList.indexOf(fromTitle);
if(fromIndex >= 0) {
// The tiddler is added from inside the river
// Determine where to insert the tiddler; Fallback is "below"
switch(this.getAttribute("openLinkFromInsideRiver","below")) {
case "top":
slot = 0;
break;
case "bottom":
slot = storyList.length;
break;
case "above":
slot = fromIndex;
break;
case "below": // Intentional fall-through
default:
slot = fromIndex + 1;
break;
}
} else {
// The tiddler is opened from outside the river. Determine where to insert the tiddler; default is "top"
if(this.getAttribute("openLinkFromOutsideRiver","top") === "bottom") {
// Insert at bottom
slot = storyList.length;
} else {
// Insert at top
slot = 0;
}
}
// Add the tiddler
storyList.splice(slot,0,title);
// Save the story
this.saveStoryList(storyList);
this.wiki.addToStory(title,fromTitle,this.storyTitle,{openLinkFromInsideRiver: this.getAttribute("openLinkFromInsideRiver","top"),openLinkFromOutsideRiver: this.getAttribute("openLinkFromOutsideRiver","top")});
};
/*
@@ -324,9 +280,11 @@ Generate a title for the draft of a given tiddler
*/
NavigatorWidget.prototype.generateDraftTitle = function(title) {
var c = 0,
draftTitle;
draftTitle,
username = this.wiki.getTiddlerText("$:/status/UserName"),
attribution = username ? " by " + username : "";
do {
draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'";
draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'" + attribution;
c++;
} while(this.wiki.tiddlerExists(draftTitle));
return draftTitle;
@@ -498,9 +456,9 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
},
templateTiddler,
additionalFields,
this.wiki.getCreationFields(),
existingTiddler,
filteredAdditionalFields,
this.wiki.getCreationFields(),
{
title: draftTitle,
"draft.of": title,
@@ -510,6 +468,9 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
// Update the story to insert the new draft at the top and remove any existing tiddler
if(storyList.indexOf(draftTitle) === -1) {
var slot = storyList.indexOf(event.navigateFromTitle);
if(slot === -1) {
slot = this.getAttribute("openLinkFromOutsideRiver","top") === "bottom" ? storyList.length - 1 : slot;
}
storyList.splice(slot + 1,0,draftTitle);
}
if(storyList.indexOf(title) !== -1) {

View File

@@ -0,0 +1,66 @@
/*\
title: $:/core/modules/widgets/qualify.js
type: application/javascript
module-type: widget
Qualify text to a variable
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var QualifyWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
QualifyWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
QualifyWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
QualifyWidget.prototype.execute = function() {
// Get our parameters
this.qualifyName = this.getAttribute("name");
this.qualifyTitle = this.getAttribute("title");
// Set context variable
if(this.qualifyName) {
this.setVariable(this.qualifyName,this.qualifyTitle + "-" + this.getStateQualifier());
}
// Construct the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
QualifyWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.name || changedAttributes.title) {
this.refreshSelf();
return true;
} else {
return this.refreshChildren(changedTiddlers);
}
};
exports.qualify = QualifyWidget;
})();

View File

@@ -58,23 +58,23 @@ RevealWidget.prototype.positionPopup = function(domNode) {
domNode.style.zIndex = "1000";
switch(this.position) {
case "left":
domNode.style.left = (this.popup.left - domNode.offsetWidth) + "px";
domNode.style.left = Math.max(0, this.popup.left - domNode.offsetWidth) + "px";
domNode.style.top = this.popup.top + "px";
break;
case "above":
domNode.style.left = this.popup.left + "px";
domNode.style.top = (this.popup.top - domNode.offsetHeight) + "px";
domNode.style.top = Math.max(0, this.popup.top - domNode.offsetHeight) + "px";
break;
case "aboveright":
domNode.style.left = (this.popup.left + this.popup.width) + "px";
domNode.style.top = (this.popup.top + this.popup.height - domNode.offsetHeight) + "px";
domNode.style.top = Math.max(0, this.popup.top + this.popup.height - domNode.offsetHeight) + "px";
break;
case "right":
domNode.style.left = (this.popup.left + this.popup.width) + "px";
domNode.style.top = this.popup.top + "px";
break;
case "belowleft":
domNode.style.left = (this.popup.left + this.popup.width - domNode.offsetWidth) + "px";
domNode.style.left = Math.max(0, this.popup.left + this.popup.width - domNode.offsetWidth) + "px";
domNode.style.top = (this.popup.top + this.popup.height) + "px";
break;
default: // Below
@@ -102,7 +102,10 @@ RevealWidget.prototype.execute = function() {
this.openAnimation = this.animate === "no" ? undefined : "open";
this.closeAnimation = this.animate === "no" ? undefined : "close";
// Compute the title of the state tiddler and read it
this.stateTitle = this.state;
this.stateTiddlerTitle = this.state;
this.stateTitle = this.getAttribute("stateTitle");
this.stateField = this.getAttribute("stateField");
this.stateIndex = this.getAttribute("stateIndex");
this.readState();
// Construct the child widgets
var childNodes = this.isOpen ? this.parseTreeNode.children : [];
@@ -115,7 +118,13 @@ Read the state tiddler
*/
RevealWidget.prototype.readState = function() {
// Read the information from the state tiddler
var state = this.stateTitle ? this.wiki.getTextReference(this.stateTitle,this["default"],this.getVariable("currentTiddler")) : this["default"];
var state = this.stateTitle ? (this.stateField ? this.wiki.getTiddler(this.stateTitle).getFieldString(this.stateField) :
(this.stateIndex ? this.wiki.extractTiddlerDataItem(this.stateTitle,this.stateIndex) :
this.wiki.getTiddlerText(this.stateTitle))) || this["default"] || this.getVariable("currentTiddler") :
(this.stateTiddlerTitle ? this.wiki.getTextReference(this.state,this["default"],this.getVariable("currentTiddler")) : this["default"]);
if(state === null) {
state = this["default"];
}
switch(this.type) {
case "popup":
this.readPopupState(state);
@@ -170,22 +179,21 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
RevealWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes["default"] || changedAttributes.animate) {
if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes["default"] || changedAttributes.animate || changedAttributes.stateTitle || changedAttributes.stateField || changedAttributes.stateIndex) {
this.refreshSelf();
return true;
} else {
var refreshed = false,
currentlyOpen = this.isOpen;
var currentlyOpen = this.isOpen;
this.readState();
if(this.isOpen !== currentlyOpen) {
if(this.isOpen !== currentlyOpen || (this.stateTiddlerTitle && changedTiddlers[this.stateTiddlerTitle])) {
if(this.retain === "yes") {
this.updateState();
} else {
this.refreshSelf();
refreshed = true;
return true;
}
}
return this.refreshChildren(changedTiddlers) || refreshed;
return this.refreshChildren(changedTiddlers);
}
};
@@ -219,7 +227,7 @@ RevealWidget.prototype.updateState = function() {
if(!self.isOpen) {
domNode.setAttribute("hidden","true");
}
}});
}});
}
};

View File

@@ -48,7 +48,7 @@ SetWidget.prototype.execute = function() {
this.setValue = this.getAttribute("value");
this.setEmptyValue = this.getAttribute("emptyValue");
// Set context variable
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params);
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition);
// Construct the child widgets
this.makeChildWidgets();
};

View File

@@ -71,9 +71,10 @@ Set the value of a context variable
name: name of the variable
value: value of the variable
params: array of {name:, default:} for each parameter
isMacroDefinition: true if the variable is set via a \define macro pragma (and hence should have variable substitution performed)
*/
Widget.prototype.setVariable = function(name,value,params) {
this.variables[name] = {value: value, params: params};
Widget.prototype.setVariable = function(name,value,params,isMacroDefinition) {
this.variables[name] = {value: value, params: params, isMacroDefinition: !!isMacroDefinition};
};
/*
@@ -102,7 +103,10 @@ Widget.prototype.getVariableInfo = function(name,options) {
$tw.utils.each(params,function(param) {
value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value);
});
value = this.substituteVariableReferences(value);
// Only substitute variable references if this variable was defined with the \define pragma
if(variable.isMacroDefinition) {
value = this.substituteVariableReferences(value);
}
return {
text: value,
params: params

View File

@@ -538,6 +538,45 @@ exports.findListingsOfTiddler = function(targetTitle,fieldName) {
Sorts an array of tiddler titles according to an ordered list
*/
exports.sortByList = function(array,listTitle) {
var self = this,
replacedTitles = Object.create(null);
function replaceItem(title) {
if(!$tw.utils.hop(replacedTitles, title)) {
replacedTitles[title] = true;
var newPos = -1,
tiddler = self.getTiddler(title);
if(tiddler) {
var beforeTitle = tiddler.fields["list-before"],
afterTitle = tiddler.fields["list-after"];
if(beforeTitle === "") {
newPos = 0;
} else if(afterTitle === "") {
newPos = titles.length;
} else if(beforeTitle) {
replaceItem(beforeTitle);
newPos = titles.indexOf(beforeTitle);
} else if(afterTitle) {
replaceItem(afterTitle);
newPos = titles.indexOf(afterTitle);
if(newPos >= 0) {
++newPos;
}
}
// We get the currPos //after// figuring out the newPos, because recursive replaceItem calls might alter title's currPos
var currPos = titles.indexOf(title);
if(newPos === -1) {
newPos = currPos;
}
if(currPos >= 0 && newPos !== currPos) {
titles.splice(currPos,1);
if(newPos >= currPos) {
newPos--;
}
titles.splice(newPos,0,title);
}
}
}
}
var list = this.getTiddlerList(listTitle);
if(!array || array.length === 0) {
return [];
@@ -561,36 +600,7 @@ exports.sortByList = function(array,listTitle) {
var sortedTitles = titles.slice(0);
for(t=0; t<sortedTitles.length; t++) {
title = sortedTitles[t];
var currPos = titles.indexOf(title),
newPos = -1,
tiddler = this.getTiddler(title);
if(tiddler) {
var beforeTitle = tiddler.fields["list-before"],
afterTitle = tiddler.fields["list-after"];
if(beforeTitle === "") {
newPos = 0;
} else if(afterTitle === "") {
newPos = titles.length;
} else if(beforeTitle) {
newPos = titles.indexOf(beforeTitle);
} else if(afterTitle) {
newPos = titles.indexOf(afterTitle);
if(newPos >= 0) {
++newPos;
}
}
if(newPos === -1) {
newPos = currPos;
}
if(newPos !== currPos) {
titles.splice(currPos,1);
if(newPos >= currPos) {
newPos--;
}
titles.splice(newPos,0,title);
}
}
replaceItem(title);
}
return titles;
}
@@ -623,6 +633,22 @@ exports.getTiddlerAsJson = function(title) {
}
};
exports.getTiddlersAsJson = function(filter) {
var tiddlers = this.filterTiddlers(filter),
data = [];
for(var t=0;t<tiddlers.length; t++) {
var tiddler = this.getTiddler(tiddlers[t]);
if(tiddler) {
var fields = new Object();
for(var field in tiddler.fields) {
fields[field] = tiddler.getFieldString(field);
}
data.push(fields);
}
}
return JSON.stringify(data,null,$tw.config.preferences.jsonSpaces);
};
/*
Get the content of a tiddler as a JavaScript object. How this is done depends on the type of the tiddler:
@@ -785,6 +811,14 @@ exports.initParsers = function(moduleType) {
}
}
});
// Use the generic binary parser for any binary types not registered so far
if($tw.Wiki.parsers["application/octet-stream"]) {
Object.keys($tw.config.contentTypeInfo).forEach(function(type) {
if(!$tw.utils.hop($tw.Wiki.parsers,type) && $tw.config.contentTypeInfo[type].encoding === "base64") {
$tw.Wiki.parsers[type] = $tw.Wiki.parsers["application/octet-stream"];
}
});
}
};
/*
@@ -847,7 +881,7 @@ exports.parseTextReference = function(title,field,index,options) {
}
if(field === "text" || (!field && !index)) {
if(tiddler && tiddler.fields) {
return this.parseText(tiddler.fields.type || "text/vnd.tiddlywiki",tiddler.fields.text,options);
return this.parseText(tiddler.fields.type,tiddler.fields.text,options);
} else {
return null;
}
@@ -1013,8 +1047,13 @@ Options available:
exclude: An array of tiddler titles to exclude from the search
invert: If true returns tiddlers that do not contain the specified string
caseSensitive: If true forces a case sensitive search
literal: If true, searches for literal string, rather than separate search terms
field: If specified, restricts the search to the specified field
field: If specified, restricts the search to the specified field, or an array of field names
excludeField: If true, the field options are inverted to specify the fields that are not to be searched
The search mode is determined by the first of these boolean flags to be true
literal: searches for literal string
whitespace: same as literal except runs of whitespace are treated as a single space
regexp: treats the search term as a regular expression
words: (default) treats search string as a list of tokens, and matches if all tokens are found, regardless of adjacency or ordering
*/
exports.search = function(text,options) {
options = options || {};
@@ -1030,6 +1069,21 @@ exports.search = function(text,options) {
} else {
searchTermsRegExps = [new RegExp("(" + $tw.utils.escapeRegExp(text) + ")",flags)];
}
} else if(options.whitespace) {
terms = [];
$tw.utils.each(text.split(/\s+/g),function(term) {
if(term) {
terms.push($tw.utils.escapeRegExp(term));
}
});
searchTermsRegExps = [new RegExp("(" + terms.join("\\s+") + ")",flags)];
} else if(options.regexp) {
try {
searchTermsRegExps = [new RegExp("(" + text + ")",flags)];
} catch(e) {
searchTermsRegExps = null;
console.log("Regexp error parsing /(" + text + ")/" + flags + ": ",e);
}
} else {
terms = text.split(/ +/);
if(terms.length === 1 && terms[0] === "") {
@@ -1041,6 +1095,25 @@ exports.search = function(text,options) {
}
}
}
// Accumulate the array of fields to be searched or excluded from the search
var fields = [];
if(options.field) {
if($tw.utils.isArray(options.field)) {
$tw.utils.each(options.field,function(fieldName) {
if(fieldName) {
fields.push(fieldName);
}
});
} else {
fields.push(options.field);
}
}
// Use default fields if none specified and we're not excluding fields (excluding fields with an empty field array is the same as searching all fields)
if(fields.length === 0 && !options.excludeField) {
fields.push("title");
fields.push("tags");
fields.push("text");
}
// Function to check a given tiddler for the search term
var searchTiddler = function(title) {
if(!searchTermsRegExps) {
@@ -1051,24 +1124,63 @@ exports.search = function(text,options) {
tiddler = new $tw.Tiddler({title: title, text: "", type: "text/vnd.tiddlywiki"});
}
var contentTypeInfo = $tw.config.contentTypeInfo[tiddler.fields.type] || $tw.config.contentTypeInfo["text/vnd.tiddlywiki"],
match;
for(var t=0; t<searchTermsRegExps.length; t++) {
match = false;
if(options.field) {
match = searchTermsRegExps[t].test(tiddler.getFieldString(options.field));
} else {
// Search title, tags and body
if(contentTypeInfo.encoding === "utf8") {
match = match || searchTermsRegExps[t].test(tiddler.fields.text);
searchFields;
// Get the list of fields we're searching
if(options.excludeField) {
searchFields = Object.keys(tiddler.fields);
$tw.utils.each(fields,function(fieldName) {
var p = searchFields.indexOf(fieldName);
if(p !== -1) {
searchFields.splice(p,1);
}
var tags = tiddler.fields.tags ? tiddler.fields.tags.join("\0") : "";
match = match || searchTermsRegExps[t].test(tags) || searchTermsRegExps[t].test(tiddler.fields.title);
}
if(!match) {
return false;
}
});
} else {
searchFields = fields;
}
return true;
for(var fieldIndex=0; fieldIndex<searchFields.length; fieldIndex++) {
// Don't search the text field if the content type is binary
var fieldName = searchFields[fieldIndex];
if(fieldName === "text" && contentTypeInfo.encoding !== "utf8") {
break;
}
var matches = true,
str = tiddler.fields[fieldName],
t;
if(str) {
if($tw.utils.isArray(str)) {
// If the field value is an array, test each regexp against each field array entry and fail if each regexp doesn't match at least one field array entry
for(t=0; t<searchTermsRegExps.length; t++) {
var thisRegExpMatches = false
for(var s=0; s<str.length; s++) {
if(searchTermsRegExps[t].test(str[s])) {
thisRegExpMatches = true;
break;
}
}
// Bail if the current search expression doesn't match any entry in the current field array
if(!thisRegExpMatches) {
matches = false;
break;
}
}
} else {
// If the field isn't an array, force it to a string and test each regexp against it and fail if any do not match
str = tiddler.getFieldString(fieldName);
for(t=0; t<searchTermsRegExps.length; t++) {
if(!searchTermsRegExps[t].test(str)) {
matches = false;
break;
}
}
}
} else {
matches = false;
}
if(matches) {
return true;
}
};
return false;
};
// Loop through all the tiddlers doing the search
var results = [],
@@ -1146,7 +1258,7 @@ exports.readFiles = function(files,options) {
}
};
for(var f=0; f<files.length; f++) {
this.readFile(files[f],Object.assign({},options,{callback: readFileCallback}));
this.readFile(files[f],$tw.utils.extend({},options,{callback: readFileCallback}));
}
return files.length;
};
@@ -1269,6 +1381,18 @@ exports.addToHistory = function(title,fromPageRect,historyTitle) {
story.addToHistory(title,fromPageRect);
};
/*
Add a new tiddler to the story river
title: a title string or an array of title strings
fromTitle: the title of the tiddler from which the navigation originated
storyTitle: title of story tiddler (defaults to $:/StoryList)
options: see story.js
*/
exports.addToStory = function(title,fromTitle,storyTitle,options) {
var story = new $tw.Story({wiki: this, storyTitle: storyTitle});
story.addToStory(title,fromTitle,options);
};
/*
Invoke the available upgrader modules
titles: array of tiddler titles to be processed

View File

@@ -49,6 +49,8 @@ page-background: #ffffff
pre-background: #f5f5f5
pre-border: #cccccc
primary: #7897f3
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: #000000
sidebar-controls-foreground: #ccc

View File

@@ -49,6 +49,8 @@ page-background: #ddddff
pre-background: #f5f5f5
pre-border: #cccccc
primary: #5778d8
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: #000000
sidebar-controls-foreground: #ffffff

View File

@@ -49,6 +49,8 @@ page-background: #6f6f70
pre-background: #f5f5f5
pre-border: #cccccc
primary: #29a6ee
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: #000000
sidebar-controls-foreground: #c2c1c2

View File

@@ -49,6 +49,8 @@ page-background: <<colour background>>
pre-background: <<colour background>>
pre-border: <<colour foreground>>
primary: #00f
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: <<colour background>>
sidebar-controls-foreground: <<colour foreground>>

View File

@@ -49,6 +49,8 @@ page-background: <<colour background>>
pre-background: <<colour background>>
pre-border: <<colour foreground>>
primary: #00f
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: <<colour background>>
sidebar-controls-foreground: <<colour foreground>>

View File

@@ -51,6 +51,8 @@ page-background: #336438
pre-background: #f5f5f5
pre-border: #cccccc
primary: #5778d8
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: #ccf
sidebar-controls-foreground: #fff

View File

@@ -49,6 +49,8 @@ page-background: #000
pre-background: #f5f5f5
pre-border: #cccccc
primary: #cc0000
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: #000000
sidebar-controls-foreground: #ffffff

View File

@@ -132,6 +132,8 @@ external-link-background: inherit
external-link-foreground-hover: inherit
message-border: #cfd6e6
modal-border: #999999
select-tag-background:
select-tag-foreground:
sidebar-controls-foreground-hover:
sidebar-muted-foreground-hover:
sidebar-tab-background: #ded8c5

View File

@@ -57,6 +57,8 @@ page-background: #f4f4f4
pre-background: #f5f5f5
pre-border: #cccccc
primary: #5778d8
select-tag-background:
select-tag-foreground:
sidebar-button-foreground: <<colour foreground>>
sidebar-controls-foreground-hover: #000000
sidebar-controls-foreground: #aaaaaa

View File

@@ -3,6 +3,5 @@ title: $:/core/templates/exporters/StaticRiver/Content
\define renderContent()
{{{ $(exportFilter)$ ||$:/core/templates/static-tiddler}}}
\end
<$importvariables filter="[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]">
\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
<<renderContent>>
</$importvariables>

View File

@@ -6,4 +6,5 @@ extension: .tid
\define renderContent()
{{{ $(exportFilter)$ +[limit[1]] ||$:/core/templates/tid-tiddler}}}
\end
<$importvariables filter="[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]"><<renderContent>></$importvariables>
\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
<<renderContent>>

View File

@@ -0,0 +1,6 @@
title: $:/core/save/all-external-js
\define saveTiddlerFilter()
[is[tiddler]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]] -[[$:/core]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]] $(publishFilter)$
\end
{{$:/core/templates/tiddlywiki5-external-js.html}}

View File

@@ -0,0 +1,15 @@
title: $:/core/templates/tiddlywiki5.js
\rules only filteredtranscludeinline transcludeinline codeinline
/*
{{ $:/core/copyright.txt ||$:/core/templates/plain-text-tiddler}}
`*/
`<!--~~ Library modules ~~-->
{{{ [is[system]type[application/javascript]library[yes]] ||$:/core/templates/plain-text-tiddler}}}
<!--~~ Boot prefix ~~-->
{{ $:/boot/bootprefix.js ||$:/core/templates/plain-text-tiddler}}
<!--~~ Core plugin ~~-->
{{$:/core/templates/tiddlywiki5.js/tiddlers}}
<!--~~ Boot kernel ~~-->
{{ $:/boot/boot.js ||$:/core/templates/plain-text-tiddler}}

View File

@@ -0,0 +1,9 @@
title: $:/core/templates/tiddlywiki5.js/tiddlers
`
$tw.preloadTiddlerArray(`<$text text=<<jsontiddlers "[[$:/core]]">>/>`);
$tw.preloadTiddlerArray([{
title: "$:/config/SaveWikiButton/Template",
text: "$:/core/save/all-external-js"
}]);
`

View File

@@ -0,0 +1,41 @@
title: $:/core/templates/tiddlywiki5-external-js.html
\rules only filteredtranscludeinline transcludeinline
<!doctype html>
{{$:/core/templates/MOTW.html}}<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="application-name" content="TiddlyWiki" />
<meta name="generator" content="TiddlyWiki" />
<meta name="tiddlywiki-version" content="{{$:/core/templates/version}}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="mobile-web-app-capable" content="yes"/>
<meta name="format-detection" content="telephone=no" />
<meta name="copyright" content="{{$:/core/copyright.txt}}" />
<link id="faviconLink" rel="shortcut icon" href="favicon.ico">
<title>{{$:/core/wiki/title}}</title>
<!--~~ This is a Tiddlywiki file. The points of interest in the file are marked with this pattern ~~-->
<!--~~ Raw markup ~~-->
{{{ [all[shadows+tiddlers]tag[$:/core/wiki/rawmarkup]] [all[shadows+tiddlers]tag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}}
{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}
</head>
<body class="tc-body">
<!--~~ Static styles ~~-->
<div id="styleArea">
{{$:/boot/boot.css||$:/core/templates/css-tiddler}}
</div>
<!--~~ Static content for Google and browsers without JavaScript ~~-->
<noscript>
<div id="splashArea">
{{$:/core/templates/static.area}}
</div>
</noscript>
<!--~~ Ordinary tiddlers ~~-->
{{$:/core/templates/store.area.template.html}}
</body>
<script src="%24%3A%2Fcore%2Ftemplates%2Ftiddlywiki5.js" onerror="alert('Error: Cannot load tiddlywiki.js');"></script>
</html>

View File

@@ -0,0 +1,30 @@
title: $:/core/templates/server/static.sidebar.wikitext
\whitespace trim
<div class="tc-sidebar-scrollable" style="overflow: auto;">
<div class="tc-sidebar-header">
<h1 class="tc-site-title">
<$transclude tiddler="$:/SiteTitle"/>
</h1>
<div class="tc-site-subtitle">
<$transclude tiddler="$:/SiteSubtitle"/>
</div>
<h2>
</h2>
<div class="tc-sidebar-lists">
<$list filter={{$:/DefaultTiddlers}}>
<div class="tc-menu-list-subitem">
<$link><$text text=<<currentTiddler>>/></$link>
</div>
</$list>
</div>
<!-- Currently disabled the recent list as it is unweildy when the responsive narrow view kicks in
<h2>
{{$:/language/SideBar/Recent/Caption}}
</h2>
<div class="tc-sidebar-lists">
<$macrocall $name="timeline" format={{$:/language/RecentChanges/DateFormat}}/>
</div>
</div>
</div>
-->

View File

@@ -0,0 +1,28 @@
title: $:/core/templates/server/static.tiddler.html
\whitespace trim
\define tv-wikilink-template() $uri_encoded$
\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="generator" content="TiddlyWiki" />
<meta name="tiddlywiki-version" content={{$:/core/templates/version}} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="mobile-web-app-capable" content="yes"/>
<meta name="format-detection" content="telephone=no">
<link id="faviconLink" rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="%24%3A%2Fcore%2Ftemplates%2Fstatic.template.css">
<title><$view field="caption" format="plainwikified"><$view field="title"/></$view>: <$view tiddler="$:/core/wiki/title" format="plainwikified"/></title>
</head>
<body class="tc-body">
<$transclude tiddler="$:/core/templates/server/static.sidebar.wikitext" mode="inline"/>
<section class="tc-story-river">
<div class="tc-tiddler-frame">
<$transclude tiddler="$:/core/templates/server/static.tiddler.wikitext" mode="inline"/>
</div>
</section>
</body>
</html>

View File

@@ -0,0 +1,23 @@
title: $:/core/templates/server/static.tiddler.wikitext
\whitespace trim
<div class="tc-tiddler-title">
<div class="tc-titlebar">
<h2><$text text=<<currentTiddler>>/></h2>
</div>
</div>
<div class="tc-subtitle">
<$link to={{!!modifier}}>
<$view field="modifier"/>
</$link> <$view field="modified" format="date" template={{$:/language/Tiddler/DateFormat}}/>
</div>
<div class="tc-tags-wrapper">
<$list filter="[all[current]tags[]sort[title]]">
<a href={{{ [<currentTiddler>encodeuricomponent[]] }}}>
<$macrocall $name="tag-pill" tag=<<currentTiddler>>/>
</a>
</$list>
</div>
<div class="tc-tiddler-body">
<$transclude mode="block"/>
</div>

View File

@@ -4,6 +4,7 @@ title: $:/core/templates/static.tiddler.html
\define tv-config-toolbar-icons() no
\define tv-config-toolbar-text() no
\define tv-config-toolbar-class() tc-btn-invisible
\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
`<!doctype html>
<html>
<head>
@@ -22,9 +23,7 @@ title: $:/core/templates/static.tiddler.html
<body class="tc-body">
`{{$:/StaticBanner||$:/core/templates/html-tiddler}}`
<section class="tc-story-river">
`<$importvariables filter="[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]">
<$view tiddler="$:/core/ui/ViewTemplate" format="htmlwikified"/>
</$importvariables>`
`<$view tiddler="$:/core/ui/ViewTemplate" format="htmlwikified"/>`
</section>
</body>
</html>

View File

@@ -4,8 +4,10 @@ title: $:/core/templates/tiddlywiki5.html
<!doctype html>
{{$:/core/templates/MOTW.html}}<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<!--~~ Raw markup for the top of the head section ~~-->
{{{ [all[shadows+tiddlers]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" />
<meta name="tiddlywiki-version" content="{{$:/core/templates/version}}" />
@@ -24,6 +26,8 @@ title: $:/core/templates/tiddlywiki5.html
{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}
</head>
<body class="tc-body">
<!--~~ Raw markup for the top of the body section ~~-->
{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified/TopBody]] ||$:/core/templates/raw-static-tiddler}}}
<!--~~ Static styles ~~-->
<div id="styleArea">
{{$:/boot/boot.css||$:/core/templates/css-tiddler}}
@@ -48,5 +52,7 @@ title: $:/core/templates/tiddlywiki5.html
<div id="bootKernel" style="display:none;">
{{ $:/boot/boot.js ||$:/core/templates/javascript-tiddler}}
</div>
<!--~~ Raw markup for the bottom of the body section ~~-->
{{{ [all[shadows+tiddlers]tag[$:/tags/RawMarkupWikified/BottomBody]] ||$:/core/templates/raw-static-tiddler}}}
</body>
</html>

View File

@@ -0,0 +1,10 @@
title: $:/core/ui/Actions/new-image
tags: $:/tags/Actions
description: create a new image tiddler
\define get-type()
image/$(imageType)$
\end
<$vars imageType={{$:/config/NewImageType}}>
<$action-sendmessage $message="tm-new-tiddler" type=<<get-type>>/>
</$vars>

View File

@@ -0,0 +1,14 @@
title: $:/core/ui/Actions/new-journal
tags: $:/tags/Actions
description: create a new journal tiddler
<$vars journalTitleTemplate={{$:/config/NewJournal/Title}} journalTags={{$:/config/NewJournal/Tags}} journalText={{$:/config/NewJournal/Text}}>
<$wikify name="journalTitle" text="""<$macrocall $name="now" format=<<journalTitleTemplate>>/>""">
<$reveal type="nomatch" state=<<journalTitle>> text="">
<$action-sendmessage $message="tm-new-tiddler" title=<<journalTitle>> tags=<<journalTags>> text={{{ [<journalTitle>get[]] }}}/>
</$reveal>
<$reveal type="match" state=<<journalTitle>> text="">
<$action-sendmessage $message="tm-new-tiddler" title=<<journalTitle>> tags=<<journalTags>> text=<<journalText>>/>
</$reveal>
</$wikify>
</$vars>

View File

@@ -0,0 +1,5 @@
title: $:/core/ui/Actions/new-tiddler
tags: $:/tags/Actions
description: create a new empty tiddler
<$action-sendmessage $message="tm-new-tiddler"/>

View File

@@ -8,6 +8,7 @@ tags: $:/tags/AdvancedSearch/FilterButton
</span>
<$reveal state=<<qualify "$:/state/filterDropdown">> type="popup" position="belowleft" animate="yes">
<$set name="tv-show-missing-links" value="yes">
<$linkcatcher to="$:/temp/advancedsearch">
<div class="tc-block-dropdown-wrapper">
<div class="tc-block-dropdown tc-edit-type-dropdown">
@@ -16,4 +17,5 @@ tags: $:/tags/AdvancedSearch/FilterButton
</div>
</div>
</$linkcatcher>
</$set>
</$reveal>

View File

@@ -66,7 +66,7 @@ caption: {{$:/language/ControlPanel/KeyboardShortcuts/Caption}}
\define shortcut-list(caption,prefix)
<tr>
<$list filter="[all[tiddlers+shadows][$prefix$$(shortcutName)$]]" variable="shortcutTitle">
<$list filter="[[$prefix$$(shortcutName)$]]" variable="shortcutTitle">
<<shortcut-list-item "$caption$">>
</$list>
</tr>

View File

@@ -119,10 +119,8 @@ $:/state/add-plugin-info/$(connectionTiddler)$/$(assetInfo)$
</$list>
\end
<$importvariables filter="[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]">
\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
<div>
<<plugin-library-listing>>
</div>
</$importvariables>

View File

@@ -0,0 +1,10 @@
title: $:/core/ui/ControlPanel/Settings/NavigationPermalinkviewMode
tags: $:/tags/ControlPanel/Settings
caption: {{$:/language/ControlPanel/Settings/NavigationPermalinkviewMode/Caption}}
\define lingo-base() $:/language/ControlPanel/Settings/NavigationPermalinkviewMode/
<<lingo Hint>>
<$checkbox tiddler="$:/config/Navigation/Permalinkview/CopyToClipboard" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/Navigation/Permalinkview/CopyToClipboard"><<lingo CopyToClipboard/Description>></$link> </$checkbox>
<$checkbox tiddler="$:/config/Navigation/Permalinkview/UpdateAddressBar" field="text" checked="yes" unchecked="no" default="yes"> <$link to="$:/config/Navigation/Permalinkview/UpdateAddressBar"><<lingo UpdateAddressBar/Description>></$link> </$checkbox>

View File

@@ -39,6 +39,7 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
class="tc-btn-invisible $(buttonClasses)$"
tooltip=<<tooltip-text>>
actions={{!!actions}}
><span
@@ -65,6 +66,7 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
class="tc-popup-keep tc-btn-invisible $(buttonClasses)$"
selectedClass="tc-selected"
tooltip=<<tooltip-text>>
actions={{!!actions}}
><span

View File

@@ -5,7 +5,7 @@ tags: $:/tags/EditTemplate
\define config-visibility-title()
$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
\end
<$list filter="[is[current]has[_canonical_uri]]">
<$list filter="[all[current]has[_canonical_uri]]">
<div class="tc-message-box">
@@ -19,7 +19,7 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
</$list>
<$list filter="[is[current]!has[_canonical_uri]]">
<$list filter="[all[current]!has[_canonical_uri]]">
<$reveal state="$:/state/showeditpreview" type="match" text="yes">

View File

@@ -65,6 +65,7 @@ $value={{$:/temp/newfieldvalue}}/>
<$button popup=<<qualify "$:/state/popup/field-dropdown">> class="tc-btn-invisible tc-btn-dropdown" tooltip={{$:/language/EditTemplate/Field/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Field/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button>
<$reveal state=<<qualify "$:/state/popup/field-dropdown">> type="nomatch" text="" default="">
<div class="tc-block-dropdown tc-edit-type-dropdown">
<$set name="tv-show-missing-links" value="yes">
<$linkcatcher to="$:/temp/newfieldname">
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/User>>
@@ -83,6 +84,7 @@ $value={{$:/temp/newfieldvalue}}/>
</$link>
</$list>
</$linkcatcher>
</$set>
</div>
</$reveal>
<span class="tc-edit-field-add-value">

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