├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── demo ├── tiddlers │ ├── $__DefaultTiddlers.tid │ ├── $__SiteSubtitle.tid │ ├── $__SiteTitle.tid │ ├── $__config_PageControlButtons_Visibility_$__core_ui_Buttons_layout.tid │ ├── $__plugins_linonetwo_itonnote.json │ ├── $__plugins_linonetwo_opened-tiddlers-bar.json │ ├── $__plugins_linonetwo_prevent-edit.json │ ├── $__plugins_linonetwo_prevent-edit.json.meta │ ├── $__plugins_linonetwo_slate-write.json │ ├── $__plugins_linonetwo_tw-calendar.json │ ├── $__plugins_linonetwo_tw-whiteboard.json │ ├── $__plugins_telmiger_EditorCounter.json │ ├── $__plugins_telmiger_EditorCounter.json.meta │ ├── $__plugins_telmiger_HarveyBalls.json │ ├── $__plugins_telmiger_HarveyBalls.json.meta │ ├── $__plugins_telmiger_PluginSize.json │ ├── $__plugins_telmiger_PluginSize.json.meta │ ├── $__plugins_telmiger_rpn.json │ ├── $__plugins_telmiger_rpn.json.meta │ ├── $__theme.tid │ ├── $__themes_linonetwo_itonnote.json │ ├── $__themes_tiddlywiki_vanilla.json │ ├── $__themes_tiddlywiki_vanilla.json.meta │ ├── Index.tid │ ├── docs │ │ ├── useFilter.tid │ │ └── useRenderTiddler.tid │ ├── favicon.ico │ └── favicon.ico.meta └── tiddlywiki.info ├── dprint.json ├── esbuild.config.mjs ├── package.json ├── patches └── @wessberg__connection-observer@1.0.5.patch ├── pnpm-lock.yaml ├── scripts ├── after-build-type.mjs ├── after-build.mjs ├── build-demo-html.mjs ├── download-react.mjs ├── mv-dev.mjs └── run-action.mjs ├── src ├── docs │ ├── FAQ.tid │ ├── example.tid │ ├── install.tid │ └── reactAPIs.tid ├── example.js.meta ├── example.tsx ├── exampleFunction.tsx ├── hooks │ ├── context.ts │ ├── index.ts │ ├── useFilter.ts │ ├── useRenderTiddler.ts │ └── useWidget.ts ├── index.js.meta ├── index.ts ├── plugin.info ├── react-dom-client.js.meta ├── react-dom.js.meta ├── react-jsx-runtime.js.meta ├── react-scheduler.js.meta ├── react.js.meta ├── readme.tid ├── tree.tid ├── type.d.ts ├── widget-type.ts ├── widget.js.meta └── widget.ts ├── tsconfig.eslint.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.scss] 11 | indent_size = 2 12 | 13 | [*.js] 14 | indent_size = 2 15 | 16 | [*.jsx] 17 | indent_size = 2 18 | 19 | [*.ts] 20 | indent_size = 2 21 | 22 | [*.tsx] 23 | indent_size = 2 24 | 25 | [*.vue] 26 | indent_size = 2 27 | 28 | [Makefile] 29 | indent_style = tab 30 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | resources/ 2 | settings-dev/ 3 | out/ 4 | logs/ 5 | template/ 6 | .webpack/ 7 | node_modules/ 8 | localization/ 9 | build-resources/ 10 | .vscode/ 11 | .github/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const tsEslintConfig = require('./tsconfig.eslint.json'); 2 | 3 | module.exports = { 4 | root: true, 5 | ignorePatterns: tsEslintConfig.exclude, 6 | parserOptions: { 7 | project: './tsconfig.eslint.json', 8 | tsconfigRootDir: __dirname, 9 | }, 10 | extends: ['eslint-config-tidgi'], 11 | }; 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: linonetwo # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['dun.mianbaoduo.com/@linonetwo'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Plugins 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | paths-ignore: 8 | - 'README.md' 9 | - 'docs/**' 10 | - '.vscode' 11 | pull_request: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - 'docs/**' 16 | - 'README.md' 17 | - '.vscode' 18 | 19 | concurrency: 20 | group: release-ci-group 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | Plugins: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v3 30 | 31 | - id: setPluginInfoJSON 32 | run: | 33 | content=`cat ./src/plugin.info` 34 | # the following lines are only required for multi line json 35 | content="${content//'%'/'%25'}" 36 | content="${content//$'\n'/'%0A'}" 37 | content="${content//$'\r'/'%0D'}" 38 | # here we replace "title": "$:/plugins/linonetwo/tw-example-plugin", to "title": "linonetwo/tw-example-plugin", 39 | # so it can be used in tiddly-gittly/tw5-plugin-packer to find output path 40 | content="${content//$\:\/plugins\//}" 41 | # end of optional handling for multi line json 42 | echo "::set-output name=pluginInfoJSON::$content" 43 | 44 | - name: Cache dependencies 45 | uses: actions/cache@v3 46 | with: 47 | path: | 48 | **/node_modules 49 | ~/.pnpm-store 50 | ~/.npm 51 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 52 | restore-keys: | 53 | ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 54 | ${{ runner.os }}-node- 55 | 56 | - name: Install Dependencies 57 | run: npm install -g pnpm && pnpm install 58 | 59 | - name: Make Plugins 60 | run: pnpm run make && pnpm run download-react 61 | env: 62 | CI: true 63 | CI_PULL_REQUEST: ${{ github.event_name == 'pull_request' }} 64 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | # this should be the same as `scripts/run-action.mjs` 67 | - uses: tiddly-gittly/tw5-plugin-packer@v0.0.10 68 | with: 69 | minify: 'true' 70 | # here we read plugin author and name from pluginInfoJSON, and pluginInfoJSON is read in the step above with id `setPluginInfoJSON` 71 | source: 'dist/plugins/${{fromJson(steps.setPluginInfoJSON.outputs.pluginInfoJSON).title}}' 72 | output: 'dist/out' 73 | uglifyjs-options: '{ "warnings": false, "ie8": false, "safari10": false }' 74 | cleancss-options: '{ "compatibility": "*", "level": 2 }' 75 | # build dev channel 76 | - uses: tiddly-gittly/tw5-plugin-packer@v0.0.10 77 | with: 78 | minify: 'true' 79 | # here we read plugin author and name from pluginInfoJSON, and pluginInfoJSON is read in the step above with id `setPluginInfoJSON` 80 | source: 'dist/plugins/${{fromJson(steps.setPluginInfoJSON.outputs.pluginInfoJSON).title}}-dev' 81 | output: 'dist/out-dev' 82 | uglifyjs-options: '{ "warnings": false, "ie8": false, "safari10": false }' 83 | cleancss-options: '{ "compatibility": "*", "level": 2 }' 84 | - name: mv dev to output 85 | run: npx zx ./scripts/mv-dev.mjs 86 | 87 | - name: Make Demo Site 88 | run: npx zx ./scripts/build-demo-html.mjs 89 | 90 | - name: Create Release 91 | uses: softprops/action-gh-release@v1 92 | if: startsWith(github.ref, 'refs/tags/') 93 | with: 94 | files: | 95 | dist/out/*.json 96 | dist/out/*.zip 97 | env: 98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | 100 | - name: Deploy 101 | uses: peaceiris/actions-gh-pages@v3 102 | with: 103 | github_token: ${{ secrets.GITHUB_TOKEN }} 104 | # location is described in ./scripts/build-demo-html.mjs 105 | publish_dir: ./dist/output 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | public-dist 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | .DS_Store 106 | tiddlers/$__StoryList.tid 107 | tiddlers/$__language.tid 108 | tiddlers/$__Import.tid 109 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "esbuild" 4 | ] 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 lin onetwo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TiddlyWiki React 2 | 3 | [What’s your opinion about using ReactJS in the plugin?](https://talk.tiddlywiki.org/t/whats-your-opinion-about-using-reactjs-in-the-plugin/2191) Sometimes it is inevitable to use ReactJS in the widget, for example, JSONSchemaForm. 4 | 5 | See [Website for documentation](https://tiddly-gittly.github.io/tw-react/#%24%3A%2Fplugins%2Flinonetwo%2Ftw-react%2Freadme:%24%3A%2Fplugins%2Flinonetwo%2Ftw-react%2Freadme%20Index). 6 | 7 | ## Usage 8 | 9 | Add this to a `type.d.ts` file to your `src/` folder: 10 | 11 | ```ts 12 | declare module '$:/plugins/linonetwo/tw-react/widget.js' { 13 | import { IReactWidgetConstructor } from 'tw-react'; 14 | const widget: IReactWidgetConstructor; 15 | } 16 | ``` 17 | 18 | or in `tsconfig.json` add: 19 | 20 | ```json 21 | "typeRoots" : ["node_modules/@types", "node_modules/tw5-typed", "node_modules/tw-react/dist/lib"], 22 | ``` 23 | 24 | ## Products 25 | 26 | 1. [WhiteBoard plugin](https://talk.tiddlywiki.org/t/whiteboard-plugin-v0-2-3/5302) 27 | 2. [Demo of a new WYSIWYG editor: slate-write (unstable alpha stage)](https://talk.tiddlywiki.org/t/demo-of-a-new-wysiwyg-editor-slate-write-unstable-alpha-stage/2788) 28 | 3. [FlowTiwi - a Multi-column draggable resizable sidebar (beta release)](https://talk.tiddlywiki.org/t/flowtiwi-a-multi-column-draggable-resizable-sidebar-beta-release/3128) 29 | 4. [Smart Form plugin - beta release - Display different form based on the tag you add](https://talk.tiddlywiki.org/t/smart-form-plugin-beta-release-display-different-form-based-on-the-tag-you-add/2417) 30 | 31 | When installing these plugins from [TiddlyWiki-CPL: TiddlyWiki world of Google App Store! ](https://talk.tiddlywiki.org/t/tiddlywiki-cpl-tiddlywiki-world-of-google-app-store/2140) , this React plugin will be automatically installed as a dependency. So you probably don't need to install this manyally. 32 | 33 | (You can also drag `__plugins_linonetwo_tw-react.json` from [Release · tiddly-gittly/tw-react · GitHub](https://github.com/tiddly-gittly/tw-react/releases/latest). But Don't drag them from above demo sites. Because the ReactJS plugin from those demosite is of dev mode, so size is much larger than the one in the CPL and Github release!) 34 | 35 | ## Trouble shotting 36 | 37 | ### React is undefined 38 | 39 | You may need `import React from 'react';` on the top of jsx file, even you are in react 17+ and not using this variable... 40 | 41 | ### My css is not loading 42 | 43 | See things in your `dist/plugins/xxx/yyy`, is there a `zzz.css` file? 44 | 45 | Then you will need to create a `zzz.css.meta` file in your src folder: 46 | 47 | ```tid 48 | title: $:/plugins/linonetwo/flowtiwi-sidebar/flowtiwi-sidebar.css 49 | tags: $:/tags/Stylesheet 50 | type: text/css 51 | ``` 52 | 53 | ### ReferenceError: window is not defined 54 | 55 | You may have this error when booting nodejs wiki: 56 | 57 | ```js 58 | Error executing boot module $:/plugins/linonetwo/tw-whiteboard/widget.js: {} 59 | 60 | $:/plugins/linonetwo/tw-whiteboard/widget.js:29527 61 | var AY = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)").matches : false; 62 | ^ 63 | 64 | ReferenceError: window is not defined 65 | at $:/plugins/linonetwo/tw-whiteboard/widget.js:29527:10 66 | at Script.runInContext (node:vm:139:12) 67 | at Script.runInNewContext (node:vm:144:17) 68 | ``` 69 | 70 | This is because in nodejs context, there are no `window` object. 71 | 72 | You need to only execute your script in browser, like the example in [slate-write](https://github.com/tiddly-gittly/slate-write/blob/a5201885dc839d9b4ce1c25de55b80fb61f08e7b/src/widget.js): 73 | 74 | ```js 75 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 76 | (function slateWriteWidgetIIFE() { 77 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 78 | if (!$tw.browser) { 79 | return; 80 | } 81 | // separate the widget from the exports here, so we can skip the require of react code if `!$tw.browser`. Those ts code will error if loaded in the nodejs side. 82 | const components = require('$:/plugins/linonetwo/slate-write/components/index.js'); 83 | const { widget } = components; 84 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */ 85 | exports.slateWrite = widget; 86 | // fix `Undefined widget 'edit-slateWrite'` 87 | exports['edit-slateWrite'] = widget; 88 | })(); 89 | ``` 90 | 91 | While export the widget from [another file](https://github.com/tiddly-gittly/slate-write/blob/a5201885dc839d9b4ce1c25de55b80fb61f08e7b/src/editor/index.ts#L174): 92 | 93 | ```js 94 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 95 | exports.widget = SlateWriteWidget; 96 | ``` 97 | -------------------------------------------------------------------------------- /demo/tiddlers/$__DefaultTiddlers.tid: -------------------------------------------------------------------------------- 1 | created: 20220312163059497 2 | creator: 林一二 3 | modified: 20220312163109352 4 | modifier: 林一二 5 | title: $:/DefaultTiddlers 6 | type: text/vnd.tiddlywiki 7 | 8 | Index $:/plugins/linonetwo/tw-react/readme -------------------------------------------------------------------------------- /demo/tiddlers/$__SiteSubtitle.tid: -------------------------------------------------------------------------------- 1 | title: $:/SiteSubtitle 2 | type: text/vnd.tiddlywiki 3 | 4 | A ReactJS Plugin for TiddlyWiki -------------------------------------------------------------------------------- /demo/tiddlers/$__SiteTitle.tid: -------------------------------------------------------------------------------- 1 | title: $:/SiteTitle 2 | type: text/vnd.tiddlywiki 3 | 4 | Tw React Demo -------------------------------------------------------------------------------- /demo/tiddlers/$__config_PageControlButtons_Visibility_$__core_ui_Buttons_layout.tid: -------------------------------------------------------------------------------- 1 | title: $:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/layout 2 | type: text/vnd.tiddlywiki 3 | 4 | show -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_linonetwo_itonnote.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "author": "LinOnetwo", 4 | "core-version": ">=5.1.22", 5 | "dependents": "$:/plugins/tiddlywiki/browser-sniff", 6 | "description": "Heavy lifting for new users to set up a powerful and opinionated knowledge management system.", 7 | "list": "readme description ControlPanel", 8 | "name": "ItonNote", 9 | "plugin-type": "plugin", 10 | "text": "{\"tiddlers\":{\"$:/config/DefaultSidebarTab\":{\"title\":\"$:/config/DefaultSidebarTab\",\"creator\":\"LinOnetwo\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"$:/plugins/linonetwo/itonnote/Sidebar/FolderMenu\"},\"$:/config/DownloadSaver/AutoSave\":{\"title\":\"$:/config/DownloadSaver/AutoSave\",\"created\":\"20190601103555586\",\"creator\":\"Lin Onetwo\",\"modified\":\"20200410072837906\",\"modifier\":\"Lin Onetwo\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"no\"},\"$:/config/MissingLinks\":{\"title\":\"$:/config/MissingLinks\",\"created\":\"20190419034301891\",\"modified\":\"20200409033736457\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"yes\"},\"$:/config/Navigation/UpdateAddressBar\":{\"title\":\"$:/config/Navigation/UpdateAddressBar\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"permaview\"},\"$:/config/Navigation/UpdateHistory\":{\"title\":\"$:/config/Navigation/UpdateHistory\",\"created\":\"20190419034422400\",\"modified\":\"20200409033736411\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"yes\"},\"$:/config/Navigation/openLinkFromInsideRiver\":{\"title\":\"$:/config/Navigation/openLinkFromInsideRiver\",\"created\":\"20200409033736445\",\"modified\":\"20200409033736445\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"above\"},\"$:/config/Navigation/openLinkFromOutsideRiver\":{\"title\":\"$:/config/Navigation/openLinkFromOutsideRiver\",\"created\":\"20200409033736433\",\"modified\":\"20200409033736433\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"top\"},\"$:/config/Plugins/Disabled/$:/plugins/sycom/g-analytics\":{\"title\":\"$:/config/Plugins/Disabled/$:/plugins/sycom/g-analytics\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"no\"},\"$:/config/Plugins/Disabled/$:/plugins/tiddlywiki/codemirror-mode-x-tiddlywiki\":{\"title\":\"$:/config/Plugins/Disabled/$:/plugins/tiddlywiki/codemirror-mode-x-tiddlywiki\",\"created\":\"20200411033813183\",\"modified\":\"20200411033814242\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"yes\"},\"$:/config/Plugins/Disabled/$:/plugins/tiddlywiki/codemirror\":{\"title\":\"$:/config/Plugins/Disabled/$:/plugins/tiddlywiki/codemirror\",\"created\":\"20200530042942722\",\"modified\":\"20200530043337009\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"no\"},\"$:/config/Plugins/Disabled/$:/plugins/tiddlywiki/highlight\":{\"title\":\"$:/config/Plugins/Disabled/$:/plugins/tiddlywiki/highlight\",\"created\":\"20190419154112345\",\"modified\":\"20200409033736342\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"no\"},\"$:/config/RelinkOnRename\":{\"title\":\"$:/config/RelinkOnRename\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"yes\"},\"$:/config/Search/MinLength\":{\"title\":\"$:/config/Search/MinLength\",\"created\":\"20190419153747812\",\"modified\":\"20200409033736319\",\"tags\":\"\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"1\"},\"$:/config/Toolbar/ButtonClass\":{\"title\":\"$:/config/Toolbar/ButtonClass\",\"created\":\"20190419034516378\",\"modified\":\"20200409033736308\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"tc-btn-invisible\"},\"$:/config/WikiParserRules/Inline/wikilink\":{\"title\":\"$:/config/WikiParserRules/Inline/wikilink\",\"created\":\"20190419034308697\",\"modified\":\"20200409033736296\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"disable\"},\"$:/config/codemirror/autoCloseTags\":{\"title\":\"$:/config/codemirror/autoCloseTags\",\"text\":\"true\",\"type\":\"bool\",\"created\":\"20211017131109932\",\"creator\":\"林一二\",\"modified\":\"20211017131109937\",\"modifier\":\"林一二\"},\"$:/config/codemirror/indentWithTabs\":{\"title\":\"$:/config/codemirror/indentWithTabs\",\"text\":\"false\",\"type\":\"bool\",\"created\":\"20210622180509486\",\"creator\":\"TiddlyGit User\",\"modified\":\"20210622180509499\",\"modifier\":\"TiddlyGit User\"},\"$:/config/codemirror/keyMap\":{\"title\":\"$:/config/codemirror/keyMap\",\"text\":\"sublime\\n\",\"type\":\"string\",\"created\":\"20211017131058335\",\"creator\":\"林一二\",\"modified\":\"20211017131058343\",\"modifier\":\"林一二\"},\"$:/config/codemirror/styleActiveLine\":{\"title\":\"$:/config/codemirror/styleActiveLine\",\"text\":\"true\",\"type\":\"bool\",\"created\":\"20230523081008543\",\"creator\":\"WhiteFall\",\"modified\":\"20230523081008543\",\"modifier\":\"WhiteFall\"},\"$:/config/markdown/renderWikiTextPragma\":{\"title\":\"$:/config/markdown/renderWikiTextPragma\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\rules only html image macrocallinline syslink transcludeinline wikilink prettylink filteredtranscludeblock macrocallblock transcludeblock \"},\"$:/config/section-editor/config-editor-type\":{\"title\":\"$:/config/section-editor/config-editor-type\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"main-editor\"},\"$:/config/section-editor/config-visibility-toolbar\":{\"title\":\"$:/config/section-editor/config-visibility-toolbar\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"yes\"},\"$:/config/section-editor/hlevel\":{\"title\":\"$:/config/section-editor/hlevel\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"5\"},\"$:/config/section-editor/reader-mode\":{\"title\":\"$:/config/section-editor/reader-mode\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts/cancel-edit-tiddler\":{\"title\":\"$:/config/shortcuts/cancel-edit-tiddler\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"shift-Escape\"},\"$:/config/themes/itonnote/sidebar-search\":{\"title\":\"$:/config/themes/itonnote/sidebar-search\",\"created\":\"20231010115608460\",\"creator\":\"马不前\",\"modified\":\"20231010115608460\",\"modifier\":\"马不前\",\"text\":\"show\"},\"$:/language\":{\"title\":\"$:/language\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"$:/languages/zh-Hans\"},\"$:/themes/tiddlywiki/vanilla/options/sidebarlayout\":{\"title\":\"$:/themes/tiddlywiki/vanilla/options/sidebarlayout\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"fluid-fixed\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/editor-height\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/editor-height\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/heading-4\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/heading-4\",\"created\":\"20231010115437154\",\"modified\":\"20231010115437154\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/linkify\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/linkify\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/mono-block\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/mono-block\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/mono-line\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/mono-line\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/preview-type\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/preview-type\",\"created\":\"20231010115427906\",\"modified\":\"20231010115427906\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/preview\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/preview\",\"created\":\"20231010115425849\",\"modified\":\"20231010115429153\",\"text\":\"show\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/rotate-left\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/rotate-left\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/size\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/size\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/subscript\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/subscript\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/transcludify\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/core/ui/EditorToolbar/transcludify\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/plugins/stobot/sticky/EditorToolbarButton\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/plugins/stobot/sticky/EditorToolbarButton\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/EditorToolbarButtons/Visibility/$:/plugins/tiddlywiki/markdown/EditorToolbar/mono-line\":{\"title\":\"$:/config/EditorToolbarButtons/Visibility/$:/plugins/tiddlywiki/markdown/EditorToolbar/mono-line\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/TextEditor/EditorHeight/Mode\":{\"title\":\"$:/config/TextEditor/EditorHeight/Mode\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"auto\"},\"$:/core/ui/EditorToolbar/linkify\":{\"title\":\"$:/core/ui/EditorToolbar/linkify\",\"caption\":\"{{$:/language/Buttons/Linkify/Caption}}\",\"condition\":\"[!has[type]] [type[text/vnd.tiddlywiki]]\",\"description\":\"{{$:/language/Buttons/Linkify/Hint}}\",\"icon\":\"$:/core/images/linkify\",\"shortcuts\":\"((linkify))\",\"tags\":\"$:/tags/EditorToolbar\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"<$action-sendmessage\\n\\t$message=\\\"tm-edit-text-operation\\\"\\n\\t$param=\\\"wrap-selection\\\"\\n\\tprefix=\\\"[[\\\"\\n\\tsuffix=\\\"]]\\\"\\n/>\\n\"},\"$:/core/ui/EditorToolbar/transcludify\":{\"title\":\"$:/core/ui/EditorToolbar/transcludify\",\"caption\":\"{{$:/language/Buttons/Transcludify/Caption}}\",\"condition\":\"[!has[type]] [type[text/vnd.tiddlywiki]]\",\"description\":\"{{$:/language/Buttons/Transcludify/Hint}}\",\"icon\":\"$:/core/images/transcludify\",\"shortcuts\":\"((transcludify))\",\"tags\":\"$:/tags/EditorToolbar\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"<$action-sendmessage\\n\\t$message=\\\"tm-edit-text-operation\\\"\\n\\t$param=\\\"wrap-selection\\\"\\n\\tprefix=\\\"{{\\\"\\n\\tsuffix=\\\"}}\\\"\\n/>\\n\"},\"$:/plugins/linonetwo/itonnote/Configs/SideBarFolderMenuBaseTitle\":{\"title\":\"$:/plugins/linonetwo/itonnote/Configs/SideBarFolderMenuBaseTitle\",\"creator\":\"LinOnetwo\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"Index\"},\"$:/config/DefaultMoreSidebarTab\":{\"title\":\"$:/config/DefaultMoreSidebarTab\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"$:/core/ui/MoreSideBar/Orphans\"},\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/CommandPalette\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/CommandPalette\",\"created\":\"20231010115031451\",\"modified\":\"20231010115031451\",\"text\":\"hide\"},\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/advanced-search\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/advanced-search\",\"created\":\"20200602124339340\",\"modified\":\"20200602124339360\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/control-panel\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/control-panel\",\"created\":\"20200410174523174\",\"modified\":\"20200410175230294\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/encryption\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/encryption\",\"created\":\"20200410174620924\",\"modified\":\"20200410174809069\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/home\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/home\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/layout\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/layout\",\"created\":\"20221224133314101\",\"creator\":\"林一二\",\"modified\":\"20231010114949947\",\"modifier\":\"林一二\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/more-page-actions\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/core/ui/Buttons/more-page-actions\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/PageControlButtons/Visibility/$:/plugins/kookma/commander/buttons/pagecontrol\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/plugins/kookma/commander/buttons/pagecontrol\",\"created\":\"20200410174517268\",\"modified\":\"20200410174518337\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/PageControlButtons/Visibility/$:/plugins/kookma/solution/buttons/pagecontrol\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/plugins/kookma/solution/buttons/pagecontrol\",\"created\":\"20231010115501994\",\"modified\":\"20231010115506130\",\"text\":\"show\"},\"$:/config/PageControlButtons/Visibility/$:/plugins/kookma/utility/ui/Buttons/ViewFields\":{\"title\":\"$:/config/PageControlButtons/Visibility/$:/plugins/kookma/utility/ui/Buttons/ViewFields\",\"created\":\"20231010114951098\",\"modified\":\"20231010114951098\",\"text\":\"hide\"},\"$:/config/ViewToolbarButtons/Visibility/$:/core/ui/Buttons/export-tiddler\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/core/ui/Buttons/export-tiddler\",\"created\":\"20200410064657446\",\"modified\":\"20200410064708140\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/ViewToolbarButtons/Visibility/$:/core/ui/Buttons/new-here\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/core/ui/Buttons/new-here\",\"created\":\"20200409065701335\",\"modified\":\"20200409065702475\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/ViewToolbarButtons/Visibility/$:/core/ui/Buttons/new-journal-here\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/core/ui/Buttons/new-journal-here\",\"created\":\"20200410064650269\",\"modified\":\"20231010115006123\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/bimlas/locator/viewtemplate/open-in-sidebar\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/bimlas/locator/viewtemplate/open-in-sidebar\",\"created\":\"20231010115004755\",\"modified\":\"20231010115004755\",\"text\":\"hide\"},\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/danielo/encryptTiddler/crypt-button\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/danielo/encryptTiddler/crypt-button\",\"created\":\"20200410064748749\",\"modified\":\"20200410175238416\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/linonetwo/open-in-external-app/ViewToolbarButton\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/linonetwo/open-in-external-app/ViewToolbarButton\",\"created\":\"20231010115002411\",\"modified\":\"20231010115002411\",\"text\":\"hide\"},\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/linonetwo/zx-script/ViewToolbarButton\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/linonetwo/zx-script/ViewToolbarButton\",\"created\":\"20231010115001562\",\"modified\":\"20231010115001562\",\"text\":\"hide\"},\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/tiddlywiki/qrcode/ViewToolbarButton\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/tiddlywiki/qrcode/ViewToolbarButton\",\"created\":\"20231010114959435\",\"modified\":\"20231010114959435\",\"text\":\"hide\"},\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/tiddlywiki/text-slicer/ui/slice-toolbar-button\":{\"title\":\"$:/config/ViewToolbarButtons/Visibility/$:/plugins/tiddlywiki/text-slicer/ui/slice-toolbar-button\",\"created\":\"20200411035036487\",\"modified\":\"20200411035037540\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/palette\":{\"title\":\"$:/palette\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"$:/palettes/Notion\"},\"$:/tags/PageControls\":{\"title\":\"$:/tags/PageControls\",\"list\":\"$:/plugins/linonetwo/omni-search-bar/ui/Buttons/search $:/core/ui/Buttons/home $:/core/ui/Buttons/close-all $:/core/ui/Buttons/fold-all $:/core/ui/Buttons/unfold-all $:/core/ui/Buttons/permaview $:/core/ui/Buttons/more-page-actions $:/core/ui/Buttons/new-tiddler $:/plugins/tiddlywiki/markdown/new-markdown-button $:/plugins/kookma/solution/buttons/pagecontrol $:/core/ui/Buttons/new-journal $:/core/ui/Buttons/new-image $:/core/ui/Buttons/import $:/core/ui/Buttons/export-page $:/core/ui/Buttons/control-panel $:/core/ui/Buttons/advanced-search $:/plugins/kookma/commander/buttons/pagecontrol $:/core/ui/Buttons/manager $:/core/ui/Buttons/tag-manager $:/core/ui/Buttons/language $:/core/ui/Buttons/palette $:/core/ui/Buttons/theme $:/core/ui/Buttons/storyview $:/core/ui/Buttons/encryption $:/core/ui/Buttons/timestamp $:/core/ui/Buttons/full-screen $:/core/ui/Buttons/print $:/core/ui/Buttons/refresh $:/plugins/kookma/utility/pagecontrol/view-fields-button $:/core/ui/Buttons/save-wiki $:/plugins/linonetwo/source-control-management/PageControlButton\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/theme\":{\"title\":\"$:/theme\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"$:/themes/linonetwo/itonnote\"},\"$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint\":{\"title\":\"$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"960px\"},\"$:/themes/tiddlywiki/vanilla/metrics/sidebarwidth\":{\"title\":\"$:/themes/tiddlywiki/vanilla/metrics/sidebarwidth\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"380px\"},\"$:/themes/tiddlywiki/vanilla/metrics/storywidth\":{\"title\":\"$:/themes/tiddlywiki/vanilla/metrics/storywidth\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"770px\"},\"$:/themes/tiddlywiki/vanilla/options/stickytitles\":{\"title\":\"$:/themes/tiddlywiki/vanilla/options/stickytitles\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"yes\"},\"$:/themes/tiddlywiki/vanilla/settings/codefontfamily\":{\"title\":\"$:/themes/tiddlywiki/vanilla/settings/codefontfamily\",\"created\":\"20190420032819437\",\"modified\":\"20200409033737050\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"'Fira Code',\\\"SFMono-Regular\\\",Consolas,\\\"Liberation Mono\\\",Menlo,Courier,monospace\"},\"$:/themes/tiddlywiki/vanilla/settings/editorfontfamily\":{\"title\":\"$:/themes/tiddlywiki/vanilla/settings/editorfontfamily\",\"created\":\"20190421072924643\",\"modified\":\"20200409033737038\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"'Fira Code',\\\"SFMono-Regular\\\",Consolas,\\\"Liberation Mono\\\",Menlo,Courier,monospace\"},\"$:/themes/tiddlywiki/vanilla/settings/fontfamily\":{\"title\":\"$:/themes/tiddlywiki/vanilla/settings/fontfamily\",\"created\":\"20190420034215366\",\"modified\":\"20200409033737026\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"'Fira Code',-apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", Helvetica, Arial, sans-serif, \\\"Apple Color Emoji\\\", \\\"Segoe UI Emoji\\\", \\\"Segoe UI Symbol\\\"\"},\"$:/plugins/linonetwo/itonnote/settings/PageControlBelowPage\":{\"title\":\"$:/plugins/linonetwo/itonnote/settings/PageControlBelowPage\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"yes\"},\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/contents\":{\"title\":\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/contents\",\"created\":\"20200415162108079\",\"modified\":\"20200602041547212\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/hamburger\":{\"title\":\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/hamburger\",\"created\":\"20200415162126215\",\"modified\":\"20200415162128295\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/pagecontrols\":{\"title\":\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/pagecontrols\",\"created\":\"20200415162131716\",\"modified\":\"20200415162330718\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/sidebar\":{\"title\":\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/sidebar\",\"created\":\"20200415162109418\",\"modified\":\"20200415162109442\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/topleftbar\":{\"title\":\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/topleftbar\",\"created\":\"20200415162101755\",\"modified\":\"20200602041539750\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"hide\"},\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/toprightbar\":{\"title\":\"$:/config/plugins/menubar/MenuItems/Visibility/$:/plugins/tiddlywiki/menubar/items/toprightbar\",\"created\":\"20200415162118824\",\"modified\":\"20200415163710486\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"show\"},\"$:/config/shortcuts-mac/bold\":{\"title\":\"$:/config/shortcuts-mac/bold\",\"created\":\"20200602011151844\",\"modified\":\"20200602011151860\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts-mac/italic\":{\"title\":\"$:/config/shortcuts-mac/italic\",\"created\":\"20200602011428084\",\"modified\":\"20200602011428114\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts-mac/new-image\":{\"title\":\"$:/config/shortcuts-mac/new-image\",\"created\":\"20200602011526855\",\"modified\":\"20200602011526866\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts-mac/new-journal\":{\"title\":\"$:/config/shortcuts-mac/new-journal\",\"created\":\"20200602011519033\",\"modified\":\"20200602011519055\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts-mac/toggle-sidebar\":{\"title\":\"$:/config/shortcuts-mac/toggle-sidebar\",\"created\":\"20200602011322158\",\"modified\":\"20200602011322171\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"cmd-B\"},\"$:/config/shortcuts-not-mac/bold\":{\"title\":\"$:/config/shortcuts-not-mac/bold\",\"created\":\"20200602011156768\",\"modified\":\"20200602011156779\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts-not-mac/new-image\":{\"title\":\"$:/config/shortcuts-not-mac/new-image\",\"created\":\"20200602011529909\",\"modified\":\"20200602011529924\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts-not-mac/new-journal\":{\"title\":\"$:/config/shortcuts-not-mac/new-journal\",\"created\":\"20200602011521325\",\"modified\":\"20200602011521342\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/config/shortcuts/bold\":{\"title\":\"$:/config/shortcuts/bold\",\"created\":\"20200602011200184\",\"modified\":\"20200602011200195\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"ctrl-B\"},\"$:/config/shortcuts/toggle-sidebar\":{\"title\":\"$:/config/shortcuts/toggle-sidebar\",\"created\":\"20200602011309990\",\"modified\":\"20200602011310003\",\"type\":\"text/vnd.tiddlywiki\"},\"$:/plugins/linonetwo/itonnote/ControlPanel/Settings\":{\"title\":\"$:/plugins/linonetwo/itonnote/ControlPanel/Settings\",\"caption\":\"ItonNote\",\"tags\":\"$:/tags/ControlPanel/SettingsTab\",\"text\":\"These settings let you customise the behaviour of ItonNote plugin.\\n\\n---\\n\\n!! Appearance\\n\\n;Show page control buttons below page on mobile:\\n:<$checkbox tiddler=\\\"$:/plugins/linonetwo/itonnote/settings/PageControlBelowPage\\\" field=\\\"text\\\" checked=\\\"yes\\\" unchecked=\\\"no\\\"> On mobile sidebar is folded, so adding page control to bottom will be convenient shortcut.\"},\"$:/plugins/linonetwo/itonnote/ControlPanel\":{\"title\":\"$:/plugins/linonetwo/itonnote/ControlPanel\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"!! 设置 Settings\\n\\n!!! 作为文件目录中根文件夹的笔记的标题 \\nTitle of the notes as the root folder in the file tree\\n\\n以这个标题作为标签的其它笔记相当于放入了根文件夹中:\\n\\nOther notes with this title as a tag are equivalent to being placed in the root folder:\\n\\n虚拟根文件夹:\\n<$edit-text\\n\\ttiddler=\\\"$:/plugins/linonetwo/itonnote/Configs/SideBarFolderMenuBaseTitle\\\"\\n\\ttag=\\\"input\\\"\\n\\tdefault=\\\"Index\\\"\\n\\tplaceholder=\\\"\\\" />\\n\"},\"导出文件 Export File\":{\"title\":\"导出文件 Export File\",\"description\":\"导出文件 Export File\",\"extension\":\"\",\"tags\":\"$:/tags/Exporter\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\define renderContent()\\n{{{ $(exportFilter)$ ||$:/core/templates/plain-text-tiddler}}}\\n\\\\end\\n<>\"},\"$:/plugins/linonetwo/itonnote/Help/FolderMenu-zh-CN\":{\"title\":\"$:/plugins/linonetwo/itonnote/Help/FolderMenu-zh-CN\",\"tags\":\"$:/plugins/linonetwo/itonnote/Help/Index\",\"text\":\"侧边栏的「目录结构」标签页里展示了[[通过标签系统自动生成|$:/plugins/linonetwo/itonnote/Help/VirtualFolder-zh_CN]]的文件夹。\\n\\n第一个目录树根据`当前所在的条目`展示,是动态的;第二个目录树从`Index(根文件夹)`条目出发,是静态的。因Echart动态显示上下文的图没有这种树的形式好看且干扰注意力。所以暂用目录树的形式显示上下文。\\n\\n若想修改根文件夹,请打开[[设置|$:/plugins/linonetwo/itonnote/ControlPanel]]。\\n\\n* 打了 A 标签,即相当于将文件存储在文件夹 A 中,并以 A 的内容作为文件夹的 Readme\\n* 在任意条目中使用「创建一个标签为此条目名称的新条目」按钮,可以创建以当前条目为文件夹的文件\\n* 点击 {{$:/core/images/folder}} 按钮(使它变成 {{$:/core/images/fold-button}})可以展开文件夹\\n* 直接点击文件夹的名字可以查看这个文件夹的 Readme\\n\"},\"$:/plugins/linonetwo/itonnote/Help/Index\":{\"title\":\"$:/plugins/linonetwo/itonnote/Help/Index\",\"text\":\"<$list filter=\\\"[[$:/language]get[text]removeprefix[$:/languages/]else[en-GB]]\\\" variable=\\\"lang\\\">\\n\\n\\t<$list filter=\\\"[search[zh]]\\\">\\n\\t\\t<$list filter=\\\"[tagsuffix[zh-CN]]\\\">\\n\\t\\t\\t
\\n\\n\\t\\t\\t\\t!! <>\\n\\t\\t\\t\\t{{!!text}}\\n\\n\\t\\t\\t
\\n\\t\\t\\n\\t\\n\\n\\t<$list filter=\\\"[!search[zh]]\\\">\\n\\t\\t<$list filter=\\\"[tagsuffix[en-GB]]\\\">\\n\\t\\t\\t
\\n\\n\\t\\t\\t\\t!! <>\\n\\t\\t\\t\\t{{!!text}}\\n\\n\\t\\t\\t
\\n\\t\\t\\n\\t\\n\\n\"},\"$:/plugins/linonetwo/itonnote/Help/VirtualFolder-en-GB\":{\"title\":\"$:/plugins/linonetwo/itonnote/Help/VirtualFolder-en-GB\",\"tags\":\"$:/plugins/linonetwo/itonnote/Help/Index\",\"text\":\"!!! Tag\\n\\nThe Tag structure can be thought of as a folder directory structure with soft links to form a graphical structure, since Tag relationships are inherently free, and two notes can be tagged to each other and parented to each other in the folder structure.\\n\\nUsing toc macro, you can create a \\\"file directory\\\" tab in the sidebar, which shows the folder structure generated by the tag. The details are written in [[Official Table-of-Contents Macros doc|https://tiddlywiki.com/#Table-of-Contents%20Macros:%5B%5BTable-of-Contents%20Macros%5D%5D%20%5B%5BTable-of-Contents%20Macros%20(Examples)%5D%5D]], and the plugin should have it pre-populated in [[$:/plugins/linonetwo/itonnote/Sidebar/FolderMenu]], which can be used immediately or modified to override it.\\n\\nThen you can set the \\\"File Directory\\\" tab to be displayed by default in `$:/ControlPanel` -> \\\"Settings\\\" -> \\\"Default Sidebar Tab\\\", so that you can use TiddlyWiki as a folder system. And the plugin should already be pre-configured for this.\\n\\n!!! Slash\\n\\nTiddlyWiki comes with a way to create folders by using slashes in the header.\\n\\nThe various folders that come with the system can be seen via the sidebar under \\\"More\\\" -> \\\"Explore\\\".\\n\\nIf you use the NodeJS version of TiddlyWiki, these tiddlers will also be placed in the corresponding folders on the real file system.\\n\"},\"$:/plugins/linonetwo/itonnote/Help/VirtualFolder-zh_CN\":{\"title\":\"$:/plugins/linonetwo/itonnote/Help/VirtualFolder-zh_CN\",\"tags\":\"$:/plugins/linonetwo/itonnote/Help/Index\",\"text\":\"!!! 标签\\n\\n标签结构可以看作是一种文件夹目录结构,通过软链接形成图论结构,因为标签关系本身是自由的,两个笔记可以相互加对方为标签,并在文件夹结构中互为父级,形成任意自由的结构。\\n\\n使用官方的 toc 宏,你可以在侧边栏创建一个「文件目录」标签,显示标签生成的文件夹结构。详细内容写在 [[官方文档的 Table-of-Contents 一文里|https://tw-cn-doc.cpolar.top/#Table-of-Contents%20Macros:%5B%5BTable-of-Contents%20Macros%5D%5D%20%5B%5BTable-of-Contents%20Macros%20(Examples)%5D%5D]] 中,插件应在 [[$:/plugins/linonetwo/itonnote/Sidebar/FolderMenu]] 中预置了,可以立即使用,你也可以修改覆盖。\\n\\n然后,您可以在 `$:/ControlPanel` ->「Settings」 ->「Default Sidebar Tab」中设置默认显示「文件目录」选项卡,这样您就可以将 TiddlyWiki 用作文件夹系统。插件应该已经为此进行了预配置。\\n\\n!!! 斜杠\\n\\nTiddlyWiki 提供了一种在标题里使用斜杠创建文件夹的方法。\\n\\n你可以通过侧边栏的「更多」->「探索」查看系统自带的各种文件夹。\\n\\n如果你使用的是 NodeJS 版本的 TiddlyWiki,这些 tiddlers 也会被放置在真实文件系统的相应文件夹中。\\n\\n!!! 使用哪种?\\n\\n这是个见仁见智的问题,请[[参考中文教程学习|https://tw-cn.netlify.app/#%E4%BD%BF%E7%94%A8%E6%A0%87%E7%AD%BE%E5%BD%93%E6%96%87%E4%BB%B6%E5%A4%B9:%E4%BD%BF%E7%94%A8%E6%A0%87%E7%AD%BE%E5%BD%93%E6%96%87%E4%BB%B6%E5%A4%B9%20%E6%AC%A2%E8%BF%8E%E6%9D%A5%E5%88%B0%E5%A4%AA%E5%BE%AE%EF%BC%81]],也可以加群讨论!\\n\"},\"$:/plugins/linonetwo/itonnote/Macros/OpenImageInGithub\":{\"title\":\"$:/plugins/linonetwo/itonnote/Macros/OpenImageInGithub\",\"created\":\"20200412034056887\",\"tags\":\"$:/tags/Macro\",\"caption\":\"点击在新标签页打开Github大图\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\define view-big-image(source)\\n\\n \\n\\n\\\\end\"},\"$:/plugins/linonetwo/itonnote/Macros/TransclusionWithEditMe\":{\"title\":\"$:/plugins/linonetwo/itonnote/Macros/TransclusionWithEditMe\",\"tags\":\"$:/tags/Macro 自改TW\",\"caption\":\"带有「编辑此块」的引用transclusion宏\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\define reuse-pane(content)\\n\\n
\\n $content$\\n
\\n\\\\end\\n\\n\\\\define reuse-tiddler(title)\\n<$macrocall $name=\\\"reuse-pane\\\" content=\\\"\\\"\\\"\\n查看引文:[[$title$]]\\n\\\"\\\"\\\" />\\n\\n{{$title$}}\\n\\n\\\\end\"},\"$:/config/ChinesePluginLibrary/GitHub\":{\"title\":\"$:/config/ChinesePluginLibrary/GitHub\",\"caption\":\"<$list filter=\\\"[[$:/language]get[text]removeprefix[$:/languages/]else[en-GB]]\\\" variable=\\\"lang\\\"><$list filter=\\\"[search:title[zh]]\\\">太微中文社区插件源(~GitHub版)<$list filter=\\\"[!search:title[zh]]\\\">TiddlyWiki Chinese CPL(~GitHub Host)\",\"created\":\"20211210064945704\",\"creator\":\"Sttot\",\"modified\":\"20211210070811047\",\"modifier\":\"Sttot\",\"tags\":\"$:/tags/PluginLibrary\",\"type\":\"text/vnd.tiddlywiki\",\"url\":\"https://tiddly-gittly.github.io/TiddlyWiki-CPL/library/index.html\",\"text\":\"\\n<$list filter=\\\"[[$:/language]get[text]removeprefix[$:/languages/]else[en-GB]]\\\" variable=\\\"lang\\\">\\n<$list filter=\\\"[search:title[zh]]\\\" variable=\\\"lang\\\">\\n\\n欢迎使用''【太微中文社区插件源】''!\\n\\n本插件源是由[[太微(TiddlyWiki)中文社区|https://github.com/tiddly-gittly]]维护的、致力于搜集网络上所有与 ~TiddlyWiki5 有关插件的、希望为中国以及全世界的太微用户提供一键安装、更新插件体验的公开插件源。\\n\\n如果还不了解该如何使用太微和本插件源,欢迎阅读[[中文社区共建的太微(TiddlyWiki)教程|https://tw-cn.netlify.app]]里插件相关的部分。如上提到的插件源和教程皆为开源项目,你可以在 [[GitHub|https://github.com/tiddly-gittly]] 中找到并参与贡献!如果乐意,可以通过QQ群等方式加入我们,详情请见如上提到的中文教程。\\n\\n要添加这个插件库到你的 Wiki 中,只需鼠标拖动这个链接到你的 Wiki 里即可:<$link to=<>>{{!!caption}}\\n\\n注意:本插件源版本为 ~GitHub Page 的版本,更新更快,但是可能需要科学上网手段。如果你在国内,而且不清楚什么是“科学上网”,请选用另一个经过 netlify.app 加速的[[版本|$:/config/ChinesePluginLibrary/Netlify]],虽然更新有一定的延迟,但对国内用户更加友好。\\n\\n\\n\\n<$list filter=\\\"[!search:title[zh]]\\\" variable=\\\"lang\\\">\\n\\nWelcome to the ''[TiddlyWiki Chinese Community Plugin Source]''!\\n\\nThis plugin source is maintained by the [[TiddlyWiki Chinese Community]] and is dedicated to collecting all TiddlyWiki5 related plugins on the web, hoping to provide a one-click installation and update plugin experience for TiddlyWiki users in China and around the world.\\n\\nIf you don't know how to use TiddlyWiki and this source, you are welcome to read the plugins related section in the [[TiddlyWiki Tutorials for Chinese Communities|https://tw-cn.netlify.app]]. As mentioned above, both the plugin source and the tutorial are open source projects, you can find them in [[GitHub|https://github.com/tiddly-gittly]] and participate in contributing! If you like, you can join us through QQ groups and other means, see the Chinese tutorials mentioned above for details.\\n\\nTo add this plugin library to your Wiki, just drag this link with your mouse into your Wiki: <$link to=<>{{!!caption}}\\n\\nNote: The source version of this plugin is the ~GitHub Page version, which is faster to update, but may require scientific Internet access. If you are in China and are not sure what GFW is, please use another [[version|$:/config/ChinesePluginLibrary/Netlify]] that is accelerated by netlify.app, although there is a certain delay in updating, but it is more friendly to domestic users more friendly.\\n\\n\\n\"},\"$:/config/ChinesePluginLibrary/Netlify\":{\"title\":\"$:/config/ChinesePluginLibrary/Netlify\",\"caption\":\"<$list filter=\\\"[[$:/language]get[text]removeprefix[$:/languages/]else[en-GB]]\\\" variable=\\\"lang\\\"><$list filter=\\\"[search:title[zh]]\\\">太微中文社区插件源(大陆加速版)<$list filter=\\\"[!search:title[zh]]\\\">TiddlyWiki Chinese CPL(Netlify Host)\",\"created\":\"20211118102827947\",\"creator\":\"Sttot\",\"modified\":\"20211210070641055\",\"modifier\":\"Sttot\",\"tags\":\"$:/tags/PluginLibrary\",\"type\":\"text/vnd.tiddlywiki\",\"url\":\"https://tw-cpl.netlify.app/library/index.html\",\"text\":\"\\n<$list filter=\\\"[[$:/language]get[text]removeprefix[$:/languages/]else[en-GB]]\\\" variable=\\\"lang\\\">\\n<$list filter=\\\"[search:title[zh]]\\\" variable=\\\"lang\\\">\\n\\n欢迎使用''【太微中文社区插件源】''!\\n\\n本插件源是由[[太微(TiddlyWiki)中文社区|https://github.com/tiddly-gittly]]维护的、致力于搜集网络上所有与 ~TiddlyWiki5 有关插件的、希望为中国以及全世界的太微用户提供一键安装、更新插件体验的公开插件源。\\n\\n如果还不了解该如何使用太微和本插件源,欢迎阅读[[中文社区共建的太微(TiddlyWiki)教程|https://tw-cn.netlify.app]]里插件相关的部分。如上提到的插件源和教程皆为开源项目,你可以在 [[GitHub|https://github.com/tiddly-gittly]] 中找到并参与贡献!如果乐意,可以通过QQ群等方式加入我们,详情请见如上提到的中文教程。\\n\\n要添加这个插件库到你的 Wiki 中,只需鼠标拖动这个链接到你的 Wiki 里即可:<$link to=<>>{{!!caption}}\\n\\n注意:本插件源版本为经过 netlify.app 加速的版本,对国内用户更加友好,但是更新有一定的延迟。还提供另一版本,是直接使用 ~GitHub Page 服务器的版本,更新更快,但是可能需要科学上网手段。\\n\\n\\n\\n<$list filter=\\\"[!search:title[zh]]\\\" variable=\\\"lang\\\">\\n\\nWelcome to the ''[TiddlyWiki Chinese Community Plugin Source]''!\\n\\nThis plugin source is maintained by the [[TiddlyWiki Chinese Community]] and is dedicated to collecting all TiddlyWiki5 related plugins on the web, hoping to provide a one-click installation and update plugin experience for TiddlyWiki users in China and around the world.\\n\\nIf you don't know how to use TiddlyWiki and this source, you are welcome to read the plugins related section in the [[TiddlyWiki Tutorials for Chinese Communities|https://tw-cn.netlify.app]]. As mentioned above, both the plugin source and the tutorial are open source projects, you can find them in [[GitHub|https://github.com/tiddly-gittly]] and participate in contributing! If you like, you can join us through QQ groups and other means, see the Chinese tutorials mentioned above for details.\\n\\nTo add this plugin library to your Wiki, just drag this link with your mouse into your Wiki: <$link to=<>{{!!caption}}\\n\\nNote: The source version of this plugin is a version accelerated by netlify.app, which is more friendly to China mainland users, but there is a delay in updating. There is also another version that uses the GitHub Page server directly, which is faster to update, but may require technology to overturn the GFW.\\n\\n\\n\"},\"$:/config/wikilabs/PluginLibraryWL/latest\":{\"title\":\"$:/config/wikilabs/PluginLibraryWL/latest\",\"caption\":\"Wikilabs Library\",\"tags\":\"$:/tags/PluginLibrary\",\"type\":\"text/vnd.tiddlywiki\",\"url\":\"https://wikilabs.github.io/editions/pluginlibraryWL/library/latest/index.html\",\"text\":\"~WikiLabs plugin library latest version!\\n\"},\"$:/config/KookmaPluginLibrary\":{\"title\":\"$:/config/KookmaPluginLibrary\",\"caption\":\"Kookma Plugin Library\",\"created\":\"20200306121057751\",\"modified\":\"20200410154132754\",\"tags\":\"$:/tags/PluginLibrary\",\"type\":\"text/vnd.tiddlywiki\",\"url\":\"https://kookma.github.io/TW-PluginLibrary/library/index.html\",\"text\":\"Kookma plugin library is a set of plugins, themes, and scripts, to extend functionality and add new features to Tiddlywiki. For detail information visit the library at [[GitHub|https://github.com/kookma]]. It is recommended to backup your data before installing any plugin, theme, or script. \\n\\nTo use in other wikis, drag and drop this link to those wikis: [[Kookma Plugin Library|$:/config/KookmaPluginLibrary]]\"},\"$:/config/OfficialPluginLibrary\":{\"title\":\"$:/config/OfficialPluginLibrary\",\"tags\":\"$:/tags/PluginLibrary\",\"url\":\"https://tiddlywiki.com/library/v5.3.0/index.html\",\"caption\":\"{{$:/language/OfficialPluginLibrary}}\",\"text\":\"{{$:/language/OfficialPluginLibrary/Hint}}\"},\"$:/core/ui/ControlPanel/Settings\":{\"title\":\"$:/core/ui/ControlPanel/Settings\",\"tags\":\"$:/tags/ControlPanel\",\"caption\":\"{{$:/language/ControlPanel/Settings/Caption}}\",\"text\":\"
\\n<$macrocall $name=\\\"tabs\\\" tabsList=\\\"[all[shadows+tiddlers]tag[$:/tags/ControlPanel/SettingsTab]!has[draft.of]]\\\" default=\\\"$:/core/ui/ControlPanel/Settings/TiddlyWiki\\\" explicitState=\\\"$:/state/tab--697582678\\\"/>\\n
\\n\"},\"$:/core/ui/ControlPanel/Settings/TiddlyWiki\":{\"title\":\"$:/core/ui/ControlPanel/Settings/TiddlyWiki\",\"tags\":\"$:/tags/ControlPanel/SettingsTab\",\"caption\":\"TiddlyWiki\",\"text\":\"\\\\define lingo-base() $:/language/ControlPanel/Settings/\\n\\n<>\\n\\n<$list filter=\\\"[all[shadows+tiddlers]tag[$:/tags/ControlPanel/Settings]]\\\">\\n\\n
\\n\\n!! <$link><$transclude field=\\\"caption\\\"/>\\n\\n<$transclude/>\\n\\n
\\n\\n\\n\"},\"$:/plugins/linonetwo/itonnote/Sidebar/FolderMenu\":{\"title\":\"$:/plugins/linonetwo/itonnote/Sidebar/FolderMenu\",\"caption\":\"文件目录\",\"creator\":\"LinOnetwo\",\"description\":\"文件夹系统目录结构\",\"is-dropdown\":\"yes\",\"tags\":\"$:/tags/SideBar $:/tags/MenuBar\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"<$scrollable fallthrough=\\\"none\\\" class=\\\"tc-popup-keep tc-menubar-dropdown-sidebar\\\">\\n\\n\\t<$let toc-open-icon=\\\"$:/core/images/fold-button\\\" toc-closed-icon=\\\"$:/core/images/folder\\\">\\n\\t\\t
\\n\\t\\t\\t<$transclude $variable=\\\"toc-selective-expandable\\\" tag={{$:/plugins/linonetwo/itonnote/Configs/SideBarFolderMenuBaseTitle}} />\\n\\t\\t
\\n\\t\\n\\n <$list filter=\\\"[[$:/language]get[text]removeprefix[$:/languages/]else[en-GB]]\\\" variable=\\\"lang\\\">\\n\\n <$list filter=\\\"[search[zh]]\\\">\\n [[使用帮助|$:/plugins/linonetwo/itonnote/Help/Index]]\\n \\n\\n <$list filter=\\\"[!search[zh]]\\\">\\n [[Help|$:/plugins/linonetwo/itonnote/Help/Index]]\\n \\n\\n \\n\\n\\n\"},\"$:/plugins/linonetwo/itonnote/Snippets/OpenImageInGithub\":{\"title\":\"$:/plugins/linonetwo/itonnote/Snippets/OpenImageInGithub\",\"caption\":\"图片:点击在新标签页打开大图\",\"tags\":\"$:/plugins/linonetwo/itonnote/Macros/OpenImageInGithub $:/tags/TextEditor/Snippet\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"<>\"},\"$:/plugins/linonetwo/itonnote/Snippets/TOCAboutCurrentTiddler\":{\"title\":\"$:/plugins/linonetwo/itonnote/Snippets/TOCAboutCurrentTiddler\",\"caption\":\"添加一个使用当前标题的 toc\",\"tags\":\"[[$:/plugins/linonetwo/itonnote/Help/Index]] $:/tags/TextEditor/Snippet\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\n<$let toc-open-icon=\\\"$:/core/images/fold-button\\\" toc-closed-icon=\\\"$:/core/images/folder\\\">\\n
\\n <$transclude $variable=\\\"toc-selective-expandable\\\" tag=<> />\\n
\\n\"},\"$:/plugins/linonetwo/itonnote/Snippets/TransclusionWithEditMe\":{\"title\":\"$:/plugins/linonetwo/itonnote/Snippets/TransclusionWithEditMe\",\"caption\":\"带「编辑此块」的引用Transclusion\",\"tags\":\"$:/plugins/linonetwo/itonnote/Macros/TransclusionWithEditMe $:/tags/TextEditor/Snippet\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"<>\"},\"$:/plugins/linonetwo/itonnote/UI/PageControlBelowPage\":{\"title\":\"$:/plugins/linonetwo/itonnote/UI/PageControlBelowPage\",\"tags\":\"$:/tags/AboveStory\",\"text\":\"\\n<$list filter=\\\"[[$:/plugins/linonetwo/itonnote/settings/PageControlBelowPage]get[text]else[no]match[yes]]\\\">\\n \\n\"},\"$:/plugins/linonetwo/itonnote/UI/style.css\":{\"title\":\"$:/plugins/linonetwo/itonnote/UI/style.css\",\"tags\":\"$:/tags/Stylesheet\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"<$importvariables filter=\\\"[[$:/themes/tiddlywiki/vanilla/base]]\\\">\\n\\n.page-control-below-page {\\n display: none;\\n\\n padding: 10px;\\n background-color: white;\\n -webkit-backdrop-filter: blur(10px);\\n\\n position: fixed;\\n bottom: 0;\\n left: 0;\\n z-index: 200;\\n width: 100%;\\n}\\n.page-control-below-page .tc-page-controls {\\n margin: 0;\\n\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-evenly;\\n}\\n\\n@media (max-width: <>) {\\n .page-control-below-page {\\n display: block;\\n }\\n}\\n\"},\"$:/plugins/linonetwo/itonnote/description\":{\"title\":\"$:/plugins/linonetwo/itonnote/description\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"!!! macros\\n\\n!!!! TransclusionWithEditMe\\n\\n[[$:/plugins/linonetwo/itonnote/Macros/TransclusionWithEditMe]] Usage:\\n\\n使用普通的 [[Transclusion|https://tiddlywiki.com/#Transclusion]] 时,你没法得知源文件在哪里,如果想要修改内容,还得打开编辑模式、复制被引用的 Tiddler 的标题,然后搜索打开编辑,比较麻烦。\\n\\n使用此宏进行引用就很方便了:\\n\\n```tid\\n<>\\n```\\n\\n会直接在引用的区块旁边显示一个「查看引文」的小浮窗,带有指向源文件的链接,直接点开编辑即可。\\n\\n!!!! OpenImageInGithub\\n\\n[[$:/plugins/linonetwo/itonnote/Macros/OpenImageInGithub]] Usage:\\n\\nIf you have `webcatalog-tiddlywiki-menu-app.jpg` in your Wiki, you normally can just `{{webcatalog-tiddlywiki-menu-app.jpg}}` to place it in your tiddler, but you can use this macro to make it clickable, and open large image in the new browser tab:\\n\\n```tid\\n<>\\n```\\n\\n!!! snippets(文本片段)\\n\\n在编辑模式下,有一个图章按钮,点击后会列出一系列文本片段,可以一键添加预制内容,因而无需用脑记住这些复杂的文本片段了。\\n\\n本插件预置了一些文本片段,详见相应的 Macros 的介绍,或相应的插件的介绍:\\n\\n* [[$:/plugins/linonetwo/itonnote/Snippets/TOCAboutCurrentTiddler]]\\n* [[$:/plugins/linonetwo/itonnote/Snippets/OpenImageInGithub]]\\n* [[$:/plugins/linonetwo/itonnote/Snippets/TransclusionWithEditMe]]\\n\"},\"$:/plugins/linonetwo/itonnote/readme\":{\"title\":\"$:/plugins/linonetwo/itonnote/readme\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"!! 功能\\n\\n预配置了一系列琐碎的内容,一般来自各插件的Readme和论坛讨论,但大多数人懒得看Readme,故在此直接帮忙配置好了。\\n\\n具体预置内容介绍可见[[Description|$:/plugins/linonetwo/itonnote/description]]。\\n\\n{{$:/plugins/linonetwo/itonnote/ControlPanel}}\\n\"},\"$:/plugins/linonetwo/itonnote/Startup/closeSidebarOnMobile.js\":{\"module-type\":\"startup\",\"title\":\"$:/plugins/linonetwo/itonnote/Startup/closeSidebarOnMobile.js\",\"type\":\"application/javascript\",\"creator\":\"NicolasPetton\",\"Modern.TiddlyDev#Origin\":\"Startup/closeSidebarOnMobile.ts\",\"text\":\"\\\"use strict\\\";exports.name=\\\"close-sidebar-on-mobile\\\",exports.platforms=[\\\"browser\\\"],exports.after=[\\\"rootwidget\\\"];var isOnMobile=()=>\\\"yes\\\"===$tw.wiki.getTiddlerText(\\\"$:/info/browser/is/mobile\\\")||\\\"yes\\\"===$tw.wiki.getTiddlerText(\\\"$:/info/tidgi-mobile\\\"),closeSidebar=()=>{$tw.wiki.addTiddler({title:\\\"$:/state/sidebar\\\",text:\\\"no\\\"}),$tw.wiki.addTiddler({title:\\\"$:/state/notebook-sidebar\\\",text:\\\"no\\\"})},closeSidebarOnMobile=e=>(isOnMobile()&&closeSidebar(),e),setup=()=>{$tw.hooks.addHook(\\\"th-opening-default-tiddlers-list\\\",closeSidebarOnMobile),$tw.hooks.addHook(\\\"th-importing-file\\\",closeSidebarOnMobile),$tw.hooks.addHook(\\\"th-navigating\\\",closeSidebarOnMobile),$tw.hooks.addHook(\\\"th-new-tiddler\\\",closeSidebarOnMobile),$tw.hooks.addHook(\\\"th-open-command-palette\\\",closeSidebarOnMobile),closeSidebarOnMobile()};exports.startup=setup;\"}}}", 11 | "title": "$:/plugins/linonetwo/itonnote", 12 | "type": "application/json", 13 | "version": "2.0.1", 14 | "Modern.TiddlyDev#SHA256-Hashed": "1ea4c5fec3baffd4a1043203a17d21fd6f9f312c4419f7f8bf47f8194b86ad96" 15 | } 16 | ] -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_linonetwo_opened-tiddlers-bar.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "author": "LinOnetwo", 4 | "core-version": ">=5.1.22", 5 | "dependents": "", 6 | "description": "VSCode style opened files top bar", 7 | "list": "Readme LICENSE", 8 | "name": "Opened Tiddlers Bar", 9 | "plugin-type": "plugin", 10 | "text": "{\"tiddlers\":{\"$:/plugins/linonetwo/opened-tiddlers-bar/AboveStory\":{\"title\":\"$:/plugins/linonetwo/opened-tiddlers-bar/AboveStory\",\"creator\":\"[[Jeffrey Wikinson]]\",\"modifier\":\"LinOnetwo\",\"tags\":\"$:/tags/AboveStory\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"
\\n <>\\n
\"},\"$:/plugins/linonetwo/opened-tiddlers-bar/Readme\":{\"title\":\"$:/plugins/linonetwo/opened-tiddlers-bar/Readme\",\"creator\":\"LinOnetwo\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"!! Features\\n\\nThis plugin provides VSCode style \\\"Opened Tiddlers\\\" top bar\\n\\nUse the middle mouse button or the X button to close the tab.\\n\\nUse Shift + mouse wheel to swipe sideways to view.\\n\\n!! Credits\\n\\nThis plugin is based on [[$:/plugins/bj/storytabs|http://bjtools.tiddlyspot.com/]] , which is in MIT license, thank you, Buggyj!\\n\"},\"$:/plugins/linonetwo/opened-tiddlers-bar/style.css\":{\"title\":\"$:/plugins/linonetwo/opened-tiddlers-bar/style.css\",\"creator\":\"LinOnetwo\",\"tags\":\"$:/tags/Stylesheet\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"/** make things in $:/tags/AboveStory position fixed, so it will always on the top */\\nsection.story-backdrop .opened-tiddlers-bar-container {\\n position: fixed;\\n top: 0;\\n z-index: 1000;\\n width: 100vw;\\n left: 0;\\n\\n overflow-x: auto;\\n white-space: nowrap;\\n overflow: -moz-scrollbars-none;\\n scrollbar-width: none;\\n}\\nsection.story-backdrop .opened-tiddlers-bar-container::-webkit-scrollbar {\\n display: none;\\n}\\n\\nsection.story-backdrop div.opened-tiddlers-bar div.opened-tiddlers-bar-tabs {\\n display: flex;\\n flex-direction: row;\\n justify-content: flex-start;\\n}\\n/** make it possible to drag the app using this bar in TidGi, when titleBar is closed */\\n<$reveal type=\\\"match\\\" state=\\\"$:/state/titleBarOpened\\\" text=\\\"no\\\">\\ndiv.opened-tiddlers-bar-tabs {\\n width: 90vw;\\n overflow-x: auto;\\n white-space: nowrap;\\n overflow: -moz-scrollbars-none;\\n scrollbar-width: none;\\n}\\ndiv.opened-tiddlers-bar-tabs::-webkit-scrollbar {\\n display: none;\\n}\\ndiv.opened-tiddlers-bar-drag-area {\\n width: 5vw;\\n -webkit-app-region: drag;\\n}\\n\\n/** when hide titlebar and hide sidebar (and on macOS, windows not tested), move bar right to avoid collision with close button on the left top of app */\\n/** move right a bit to avoid collision with close button on macOS when title bar hide */\\n<$reveal type=\\\"match\\\" state=\\\"$:/state/titleBarOpened\\\" text=\\\"no\\\">\\n<$reveal type=\\\"match\\\" state=\\\"$:/state/sideBarOpened\\\" text=\\\"no\\\">\\nsection.story-backdrop {\\n width: 100vw;\\n padding-left: 68px;\\n}\\n\\n\\n\\n/** tiddlers bar */\\nsection.story-backdrop > p > .opened-tiddlers-bar-container, section.story-backdrop > p > .opened-tiddlers-bar-container > div.tc-tab-buttons {\\n width: 100%;\\n margin: 0;\\n display: flex;\\n background: transparent;\\n}\\nsection.story-backdrop > p > .opened-tiddlers-bar-container {\\n background-color: white;\\n backdrop-filter: blur(3px);\\n background: <>;\\n}\\n\\n\\ndiv.opened-tiddlers-bar-tabs button {\\n border: none;\\n border-radius: 0;\\n margin: 0;\\n padding: 5px 10px;\\n padding-right: 2px;\\n}\\n/** x button */\\ndiv.opened-tiddlers-bar-tabs button.opened-tiddlers-bar-close-button {\\n border: none;\\n border-radius: 0;\\n margin: 0;\\n padding: 0 5px;\\n opacity: 0;\\n background-color: transparent;\\n}\\n/** show x when tab is hovered */\\ndiv.opened-tiddlers-bar-tabs:hover button.opened-tiddlers-bar-close-button {\\n opacity: 1;\\n}\\nsection.story-backdrop div.opened-tiddlers-bar-tabs button.tc-tab:not(.tc-tab-selected):hover {\\n border-bottom: 2px solid gray;\\n}\\nsection.story-backdrop div.opened-tiddlers-bar-tabs button.tc-tab:nth-child(2) {\\n margin-left: -1em;\\n}\\ndiv.opened-tiddlers-bar.tc-tab-buttons {\\n padding: 0;\\n margin: 0;\\n}\\n\\n/** 收起侧边栏的按钮 */\\n.tc-topbar-right {\\n top: 18px;\\n}\\n\"},\"$:/plugins/linonetwo/opened-tiddlers-bar/tree\":{\"title\":\"$:/plugins/linonetwo/opened-tiddlers-bar/tree\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"<>\"},\"$:/plugins/linonetwo/opened-tiddlers-bar/ui\":{\"title\":\"$:/plugins/linonetwo/opened-tiddlers-bar/ui\",\"creator\":\"[[Jeffrey Wikinson]]\",\"modifier\":\"LinOnetwo\",\"tags\":\"$:/tags/Macro\",\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\define onMiddleClick()\\n<$list filter=\\\"[match[middle]]\\\">\\n<$action-sendmessage $message=\\\"tm-close-tiddler\\\" $param=<>/>\\n\\n\\\\end\\n\\\\define opened-tiddlers-bar()\\n\\\\whitespace trim\\n
\\n
\\n
\\n <$list filter=\\\"[list[$:/StoryList]]\\\" history=\\\"$:/HistoryList\\\" variable=\\\"currentTab\\\">\\n <$reveal type=\\\"match\\\" state=\\\"$:/HistoryList!!current-tiddler\\\" text=<>>\\n <$eventcatcher $auxclick=<>>\\n
\\n <$button to=<> class=\\\"tc-tab-selected tc-tab\\\">\\n <$view tiddler=<> field=\\\"name\\\">\\n <$view tiddler=<> field=\\\"title\\\" />\\n <$button message=\\\"tm-close-tiddler\\\" param=<> class=\\\"tc-image-button opened-tiddlers-bar-close-button\\\">\\n ×\\n \\n \\n
\\n \\n \\n <$reveal type=\\\"nomatch\\\" state=\\\"$:/HistoryList!!current-tiddler\\\" text=<>>\\n <$eventcatcher $auxclick=<>>\\n
\\n <$button to=<> class=\\\"tc-tab\\\" >\\n <$view tiddler=<> field=\\\"name\\\">\\n <$view tiddler=<> field=\\\"title\\\" />\\n \\n <$button message=\\\"tm-close-tiddler\\\" param=<> class=\\\"tc-image-button opened-tiddlers-bar-close-button\\\">\\n ×\\n \\n \\n
\\n \\n \\n \\n
\\n
\\n
\\n\\\\end\\n\"}}}", 11 | "title": "$:/plugins/linonetwo/opened-tiddlers-bar", 12 | "type": "application/json", 13 | "version": "0.3.0", 14 | "Modern.TiddlyDev#SHA256-Hashed": "65cbd562412624e198abd3e89cd34815844332e4fa6c9286c80ae794b4fdf28f" 15 | } 16 | ] -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_linonetwo_prevent-edit.json: -------------------------------------------------------------------------------- 1 | {"tiddlers":{"$:/plugins/linonetwo/prevent-edit/preventPopUp.js":{"title":"$:/plugins/linonetwo/prevent-edit/preventPopUp.js","created":"20200414150455988","creator":"LinOnetwo","module-type":"startup","type":"application/javascript","text":"exports.startup = function() {\n $tw.unloadTasks = $tw.unloadTasks.filter(task => !task.toString().includes('confirmationMessage'));\n}\n"},"$:/plugins/linonetwo/prevent-edit/readme":{"title":"$:/plugins/linonetwo/prevent-edit/readme","created":"20200414135748497","modified":"20200602062349232","type":"text/vnd.tiddlywiki","text":"!! 功能\n\n这个插件主要是让在线部署后的 Wiki 不会弹出「有变动关闭网页前请保存」这样的对话框。\n\n!! 动机\n\n来自 TiddlyMap 的 [[$:/plugins/felixhayashi/tiddlymap/misc/defaultViewHolder]] 和来自系统的 [[$:/StoryList]] 老是自动变:\n\n```diff\ncreated: 20200409022623558\ncreator: Lin Onetwo - 林一二\n-modified: 20200414104033109\n+modified: 20200414135126182\nmodifier: Lin Onetwo - 林一二\ntitle: $:/plugins/felixhayashi/tiddlymap/misc/defaultViewHolder\ntype: text/vnd.tiddlywiki\n```\n\n没有实质性的变化,而且又经常发生在 wiki 部署后,这样在线版 wiki 就会弹出「This page is asking you to confirm that you want to leave - data you have entered may not be saved」。\n\n但我只是想做浏览不想做编辑啊,这让我觉得很烦。\n\n后来发现原来是 onbeforeunload 导致的,[[我去仓库里搜到了|https://github.com/Jermolene/TiddlyWiki5/search?q=beforeunload&unscoped_q=beforeunload]]是 `$tw.utils.each($tw.unloadTasks` 在用它,所以我就过滤了一下 `$tw.unloadTasks`。而且还不能立即做这个过滤,因为 `$:/tags/RawMarkup` 是在沙盒里执行的,不能修改 `$tw` 。\n\n所以最终我跟着 [[Adding Babel Polyfill to TiddlyWiki|https://tiddlywiki.com/dev/#Adding%20Babel%20Polyfill%20to%20TiddlyWiki]] 这篇教程,加了一个 startup script,在启动时、上述代码之后,就清理掉那个导致弹窗的 unloadTask,世界终于清静了。\n\n最终,我把它包装成了这个 [[prevent-edit|$:/plugins/linonetwo/prevent-edit]] 插件。\n"}}} -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_linonetwo_prevent-edit.json.meta: -------------------------------------------------------------------------------- 1 | author: LinOnetwo 2 | core-version: >=5.1.22 3 | created: 20211017092918218 4 | dependents: 5 | description: Prevent "This page is asking you to confirm that you want to leave" in a read-only site 6 | list: readme 7 | modified: 20211017092918218 8 | plugin-type: plugin 9 | title: $:/plugins/linonetwo/prevent-edit 10 | type: application/json 11 | version: 0.0.1 -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_EditorCounter.json: -------------------------------------------------------------------------------- 1 | {"tiddlers":{"$:/plugins/telmiger/EditorCounter/EditTemplate":{"title":"$:/plugins/telmiger/EditorCounter/EditTemplate","created":"20170126185919666","creator":"Thomas Elmiger","list-before":"$:/core/ui/EditTemplate/type","modified":"20200605055914338","modifier":"Thomas Elmiger","tags":"$:/tags/EditTemplate EditorCounter","type":"text/vnd.tiddlywiki","text":"
\n<$reveal type=\"match\" text=\"yes\" state=\"$:/plugins/telmiger/EditorCounter/settings/characters\">\n<$editor-counter tiddler=<> colors='black:0,gray:140,green:300,indianred:800,red:1600'/> characters \n<$reveal type=\"match\" text=\"yes\" state=\"$:/plugins/telmiger/EditorCounter/settings/autosave\">\n <$editor-counter mode=autosave savelimit={{$:/plugins/telmiger/EditorCounter/settings/autosave!!limit}}/> saved\n\n<$reveal type=\"match\" text=\"yes\" state=\"$:/plugins/telmiger/EditorCounter/settings/characters\">    \n\n<$reveal type=\"match\" text=\"yes\" state=\"$:/plugins/telmiger/EditorCounter/settings/words\">\n<$editor-counter mode=word colors='black:0,gray:200,green:300,indianred:1000,red:2000'/> words    \n\n<$reveal type=\"match\" text=\"yes\" state=\"$:/plugins/telmiger/EditorCounter/settings/autosave-pause\">\n<$checkbox tiddler=\"$:/plugins/telmiger/EditorCounter/settings/autosave\" field=\"text\" checked=\"yes\" unchecked=\"no\" default=\"no\"> Autosave\n<$reveal type=\"match\" text=\"yes\" state=\"$:/plugins/telmiger/EditorCounter/settings/autosave\">\n (uncheck to pause)\n\n\n
\n\n"},"$:/plugins/telmiger/EditorCounter/TitleTemplate":{"title":"$:/plugins/telmiger/EditorCounter/TitleTemplate","created":"20170213190419968","creator":"Thomas Elmiger","list-before":"$:/core/ui/EditTemplate/tags","modified":"20200605055914365","modifier":"Thomas Elmiger","tags":"$:/tags/EditTemplate EditorCounter","type":"text/vnd.tiddlywiki","text":"<$reveal type=\"match\" text=\"yes\" state=\"$:/plugins/telmiger/EditorCounter/settings/title\">\n
\n<$editor-counter tiddler=<> field=\"draft.title\" colors='green:20,indianred:30,red:40'/> chars\n
\n\n\n\n"},"$:/plugins/telmiger/EditorCounter/about":{"title":"$:/plugins/telmiger/EditorCounter/about","created":"20200524222834178","modified":"20200605055914378","tags":"","type":"text/vnd.tiddlywiki","text":"The [[EditorCounter plugin|https://tid.li/tw5/plugins.html#EditorCounter]] can display counters for words and characters in the edit view. In single page wikis it can save texts automagically in the background – check the [[plugin settings|$:/plugins/telmiger/EditorCounter/settings]].\n"},"$:/plugins/telmiger/EditorCounter/counter.js":{"title":"$:/plugins/telmiger/EditorCounter/counter.js","text":"/*\\\ntitle: $:/plugins/telmiger/EditorCounter/counter.js\ntype: application/javascript\nmodule-type: widget\n\nversion: 0.6.3\n\nCount the number of words or characters in a tiddler/field/input string – Autosave while editing\n\nUsage: see the plugin’s readme.\n\n\\*/\n(function(){\n\n/*jslint node: true, browser: true */\n/*global $tw: false */\n\"use strict\";\n\nvar Widget = require(\"$:/core/modules/widgets/widget.js\").widget;\n\nvar CounterWidget = function(parseTreeNode,options) {\n\tthis.initialise(parseTreeNode,options);\n};\n\n/*\nInherit from the base widget class\n*/\nCounterWidget.prototype = new Widget();\n\n/*\nRender this widget into the DOM – reset autosave attributes\n*/\nCounterWidget.prototype.render = function(parent,nextSibling) {\n\tthis.parentDomNode = parent;\n\tthis.computeAttributes();\n\t// autosave attributes\n\tthis.diffTotal = 0;\n\tthis.saveNow = false;\n\t// calculate state \n\tthis.execute();\n\tif(this.mode == \"autosave\") {\n\t\t// store the number of last saved characters\n\t\tthis.lastSavedCount = this.currentCount;\n\t\tthis.diffLastCount = this.currentCount;\n\t} \n\tvar textNode = this.document.createTextNode(this.currentCount);\n\tvar domNode = this.document.createElement(\"span\");\n\tparent.insertBefore(domNode,nextSibling);\n\tthis.renderChildren(domNode,null);\n\tthis.domNodes.push(domNode);\n};\n\n/*\nCalculate the numbers\n*/\nCounterWidget.prototype.getLength = function(text) {\n\tvar result = 0;\n\tswitch(this.mode) {\n\t\tcase \"character\":\n\t\t\tresult = text.length.toString();\n\t\t\tbreak;\n\t\tcase \"autosave\":\n\t\t\tresult = text.length.toString();\n\t\t\t// sum up difference since last count\n\t\t\tthis.diffTotal += this.checkAutosave(result);\n\t\t\t// check saving limit\n\t\t\tthis.saveNow = (this.diffTotal >= this.saveLimit) ? true : false;\n\t\t\tbreak;\n\t\tcase \"word\":\n\t\t\tif(text.match(/\\w+/g)) {\n\t\t\t\tresult = text.match(/\\w+/g).length.toString();\n\t\t\t} else {\n\t\t\t\tresult = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault: // \"?!\"\n\t\t\tresult = \"mode undefined\";\n\t}\n\treturn result;\n}\n\n/*\nSet autosave \n*/\nCounterWidget.prototype.checkAutosave = function(textlength) {\n\t// calculate difference since last count\n\tthis.diffChars = Math.abs(textlength - this.diffLastCount);\n\t// store text length\n\tthis.diffLastCount = textlength;\n\treturn this.diffChars;\n}\n\n/*\nGet the colors as an array\n*/\nCounterWidget.prototype.getColors = function() {\n\tvar color_array1 = this.colors.split(',');\n\tvar i;\n\tcolor_array1.sort(function sortfunction(a, b){\n\t\treturn a.split(':')[1] - b.split(':')[1];\n\t});\n\tfor(i = 0; i < color_array1.length; i++) {\n\t\tthis.color_array[i] = color_array1[i].split(':')[0];\n\t\tthis.count_array[i] = color_array1[i].split(':')[1];\n\t}\n}\n\n/*\nGet the numbers\n*/\nCounterWidget.prototype.getNumber = function() {\n\t// Count letters or words as appropriate.\n\tvar result = 0;\n\tif(this.countText) {\n\t\t// text supplied as parameter \n\t\tresult = this.getLength(this.countText);\n\t} else {\n\t\tvar tiddler = this.wiki.getTiddler(this.tiddler);\n\t\tvar fieldContent = tiddler.getFieldString(this.field);\n\t\tif(fieldContent) {\n\t\t\tresult = this.getLength(fieldContent);\n\t\t} else {\n\t\t\tresult = 0;\n\t\t}\n\t}\n\treturn result;\n}\n\n/*\nCompute the internal state of the widget\n*/\nCounterWidget.prototype.execute = function() {\n\t// Get parameters from our attributes\n\tthis.mode = this.getAttribute(\"mode\",\"character\");\n\tthis.saveLimit = this.getAttribute(\"savelimit\",200);\n\tthis.tiddler = this.getAttribute(\"tiddler\",this.getVariable(\"currentTiddler\"));\n\tthis.field = this.getAttribute(\"field\",\"text\");\n\tthis.countText = this.getAttribute(\"text\");\n\tthis.colors = this.getAttribute(\"colors\");\n\tthis.stateTiddler = this.getAttribute(\"colorState\");\n\t// Count letters or words as appropriate.\n\tthis.currentCount = this.getNumber();\n\tthis.diffChars = 0;\n\t//Find the color cut-offs, if any.\n\tif(this.colors) {\n\t\tthis.color_array = [];\n\t\tthis.count_array = [];\n\t\tvar i;\n\t\tthis.getColors();\n\n\t\t// set the color if the counter is high enough. The color with the largest value that is less than this.currentCount wins.\n\t\tfor(i = 0; i < this.color_array.length; i++) {\n\t\t\tif(Number(this.currentCount) >= Number(this.count_array[this.color_array.length - 1 - i])) {\n\t\t\t\tif(this.stateTiddler) {\n\t\t\t\t\tthis.wiki.setText(this.stateTiddler,\"text\",undefined,this.color_array[this.color_array.length -1 - i]);\n\t\t\t\t}\n\t\t\t\tthis.currentCount = '@@color:' + this.color_array[this.color_array.length -1 - i] + ';' \n\t\t\t\t\t+ this.currentCount + '@@';\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif(this.stateTiddler && i === this.color_array.length-1) {\n\t\t\t\tthis.wiki.setText(this.stateTiddler,\"text\",undefined,'');\n\t\t\t}\n\t\t}\n\t}\n\tvar parser = this.wiki.parseText(\"text/vnd.tiddlywiki\",this.currentCount,{parseAsInline: true});\n\tvar parseTreeNodes = parser ? parser.tree : [];\n\tthis.makeChildWidgets(parseTreeNodes);\n};\n\n/*\nSelectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering\n*/\nCounterWidget.prototype.refresh = function(changedTiddlers) {\n\tvar refreshed = false;\n\t// Re-execute the filter to get the count\n\tthis.computeAttributes();\n\tvar oldCount = this.currentCount;\n\tthis.execute();\n\tif(this.currentCount !== oldCount && this.mode !== \"autosave\") {\n\t\t// Regenerate and rerender the widget and replace the existing DOM node\n\t\tthis.refreshSelf();\n\t\trefreshed = true;\n\t}\n\tif(this.saveNow) {\n\t\t// Trigger an autosave and refresh\n\t\t$tw.rootWidget.dispatchEvent({type: \"tm-auto-save-wiki\"});\n\t\tthis.refreshSelf();\n\t\trefreshed = true;\n\t} \n\treturn refreshed;\n};\n\nexports[\"editor-counter\"] = CounterWidget;\n\n})();\n","type":"application/javascript","module-type":"widget","created":"20170207230719552","creator":"Thomas Elmiger","modified":"20200605055914454","modifier":"Thomas Elmiger","tags":"EditorCounter"},"$:/plugins/telmiger/EditorCounter/credits":{"title":"$:/plugins/telmiger/EditorCounter/credits","created":"20170304102433909","creator":"Thomas Elmiger","modified":"20200605055914466","modifier":"Thomas Elmiger","tags":"EditorCounter","type":"text/vnd.tiddlywiki","text":"!! Credits\n\nMany thanks to Jed for his blueprint widget and demo site, originally on http://ooktech.com/jed/ExampleWikis/WordCount/ as well as to Skeeve who obviousliy contributed the original macro version Jed based his solution on.\n\n* http://ooktech.com/TiddlyWiki/\n* http://ooktech.com/TiddlyWiki/WordCount/\n\n//Powered by the fantastic TiddlyWiki community.//\n"},"$:/plugins/telmiger/EditorCounter/icon":{"title":"$:/plugins/telmiger/EditorCounter/icon","created":"20200524221551931","modified":"20200605055914501","tags":"","type":"text/vnd.tiddlywiki","text":"\n \n \n"},"$:/plugins/telmiger/EditorCounter/readme":{"title":"$:/plugins/telmiger/EditorCounter/readme","created":"20170209064758307","creator":"Thomas Elmiger","modified":"20200605055914514","modifier":"Thomas Elmiger","tags":"EditorCounter","type":"text/vnd.tiddlywiki","text":"!! Count Your Texts & Autosave\n\n{{$:/plugins/telmiger/EditorCounter/about}}\n\n!!! The counters\n* In edit mode users can see stats concerning the tiddler’s text and title fields. The numbers are updated whenever they pause typing.\n* Counters for //words// and/or //characters// can be activated in the plugin settings.\n* The colours of the counters change at some predefined numbers.\n** Settings for colors and color limits are not available in the plugin configuration.
Workaround: edit [[the template|$:/plugins/telmiger/EditorCounter/EditTemplate]].\n\n!!! Autosave\n* When activated in the [[plugin settings|$:/plugins/telmiger/EditorCounter/settings]], //autosave// will try to save the wiki in the background during editing: whenever a predefined number of characters has been added or removed, a save is triggered.\n** As an option you can show a checkbox in the editor to pause/activate automatic saving.\n\n!!! Parameters\n\n|!Parameter |!Description |\n|colors |Optionally, define an array of color and number pairs to set minimal color limits. |\n|colorState |Optional state tiddler to save the color value. |\n|field |Calculate for the text in this field. |\n|mode |Entity to calculate: //word// or //character// or //autosave//. Defaults to \"character\". |\n|savelimit |A number of character changes after which //autosave// is performed. Default value: 200 |\n|text |Calculate this text. |\n|tiddler |Optional title of a tiddler to take the field from. Defaults to current tiddler. |\n\n!!! Widget usage\nThis plugin is based on a similar [[solution by Jed Carty|$:/plugins/telmiger/EditorCounter/credits]] – many thanks! \n\nThe widget can be used in other contexts than the plugin. The usage would be very similar to [[Jed’s examples on ooktech.com|http://ooktech.com/jed/ExampleWikis/WordCount/#%24%3A%2Fplugins%2Finmysocks%2FWordCount%2FWord%20Count%20Widget]] \n\nKnown differences: instead of `<$word-count` start with `<$editor-counter`. Default mode is //character//.\n\n!!!! Widget examples\n\n```\n<$editor-counter tiddler=SomeTiddler field=some_field mode=word/>\n<$editor-counter tiddler=SomeTiddler mode=character colors=\"blue:10,green:50,red:100\"/>\n<$editor-counter text=\"some text string\" mode=character colorState=\"$:/state/someTextColor\" colors=\"blue:10,green:50\"/>\n<$editor-counter mode=autosave savelimit=300/>\n```\n"},"$:/plugins/telmiger/EditorCounter/settings/autosave":{"title":"$:/plugins/telmiger/EditorCounter/settings/autosave","created":"20170303132537685","creator":"Thomas Elmiger","limit":"200","modified":"20200605055914542","modifier":"Thomas Elmiger","tags":"EditorCounter","type":"text/vnd.tiddlywiki","text":"no"},"$:/plugins/telmiger/EditorCounter/settings/characters":{"title":"$:/plugins/telmiger/EditorCounter/settings/characters","created":"20170208063042363","creator":"Thomas Elmiger","modified":"20200605055914567","modifier":"Thomas Elmiger","tags":"EditorCounter","type":"text/vnd.tiddlywiki","text":"yes"},"$:/plugins/telmiger/EditorCounter/settings/title":{"title":"$:/plugins/telmiger/EditorCounter/settings/title","created":"20170208211524752","creator":"Thomas Elmiger","modified":"20200605055914580","modifier":"Thomas Elmiger","tags":"EditorCounter","type":"text/vnd.tiddlywiki","text":"yes"},"$:/plugins/telmiger/EditorCounter/settings/words":{"title":"$:/plugins/telmiger/EditorCounter/settings/words","created":"20170208063040756","creator":"Thomas Elmiger","modified":"20200605055914626","modifier":"Thomas Elmiger","tags":"EditorCounter","type":"text/vnd.tiddlywiki","text":"no"},"$:/plugins/telmiger/EditorCounter/settings":{"title":"$:/plugins/telmiger/EditorCounter/settings","created":"20170208062643245","creator":"Thomas Elmiger","modified":"20200605055914527","modifier":"Thomas Elmiger","tags":"EditorCounter","type":"text/vnd.tiddlywiki","text":"!! Counter Settings\nActivate counters here. Your choice will be visible below the text field in edit mode.\n\n<$checkbox tiddler=\"$:/plugins/telmiger/EditorCounter/settings/characters\" field=\"text\" checked=\"yes\" unchecked=\"no\"> Character counter\n\n<$checkbox tiddler=\"$:/plugins/telmiger/EditorCounter/settings/words\" field=\"text\" checked=\"yes\" unchecked=\"no\" default=\"no\"> Word counter\n\n<$checkbox tiddler=\"$:/plugins/telmiger/EditorCounter/settings/title\" field=\"text\" checked=\"yes\" unchecked=\"no\"> Title characters counter\n\n!! Autosave Settings\nAutosave will try to save your wiki in the background after adding or removing 200 characters. You can change this value below.\n\n<$checkbox tiddler=\"$:/plugins/telmiger/EditorCounter/settings/autosave\" field=\"text\" checked=\"yes\" unchecked=\"no\" default=\"no\"> Autosave\n every <$edit-text tiddler=\"$:/plugins/telmiger/EditorCounter/settings/autosave\" field=\"limit\" size=\"4\"/> characters.\n\n<$checkbox tiddler=\"$:/plugins/telmiger/EditorCounter/settings/autosave-pause\" field=\"text\" checked=\"yes\" unchecked=\"no\" default=\"no\"> Show checkbox to start/pause autosave in editor\n\n
''\n\n<$button message=\"tm-save-wiki\" param={{$:/config/SaveWikiButton/Template}} tooltip={{$:/language/Buttons/SaveWiki/Hint}} aria-label={{$:/language/Buttons/SaveWiki/Caption}} class=<>>\n\n<$list filter=\"[prefix[yes]]\">\n{{$:/core/images/save-button}} {{$:/language/Buttons/SaveWiki/Hint}}\n\n<$list filter=\"[prefix[yes]]\">\n<$text text={{$:/language/Buttons/SaveWiki/Caption}}/>\n\n\n\n\n''
\n\nNote: ''All'' characters are counted as entered in the editor, regardless if they are invisible in the result or if they produce more text based on data or transclusion."},"$:/plugins/telmiger/EditorCounter/support":{"title":"$:/plugins/telmiger/EditorCounter/support","created":"20200524222008409","modified":"20200605055914634","tags":"","type":"text/vnd.tiddlywiki","text":"{{$:/plugins/telmiger/support}}"},"$:/plugins/telmiger/support":{"title":"$:/plugins/telmiger/support","created":"20181103150753927","creator":"Thomas Elmiger","modified":"20200605055914649","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"!! Support the Author\n\n''Hi!'' I’m Thomas, the author of [[tid.li/tw5/plugins.html|https://tid.li/tw5/plugins.html]]. Feedback is always welcome, as well as funding for maintenance, support and new projects :)\n\n---\n\n!!! One Time Support\n\nIf using my plugins just makes you happy, consider a one time payment via ~PayPal to reward the effort:\n\nhttps://www.paypal.me/telmiger\n\n---\n\n!!! Permanent Support\n\nIf my tools make you more productive or save you time in your job or your everyday life, you can support me as a Patron: \n\nhttps://www.patreon.com/telmiger\n\n---\n\n!!! Thank You\n\nSubstantial parts of my availabe time go to the deveopment of useful plugins for [[TiddlyWiki|https://tiddlywiki.com/]]. – Many others do the same and I would like to thank them all, especially [[Jeremy Ruston|https://tiddlywiki.com/#JeremyRuston]] and all the active members of the community!\n\n//Hereby I promise to share future revenues (if any) with other developers who’s works I use or who inspired me.//\n\nIf you like my work, I would be very happy to hear from you.\n\n''Thank you very much for your support!''
\n//Thomas//\n\nhttps://thomas-elmiger.ch"}}} -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_EditorCounter.json.meta: -------------------------------------------------------------------------------- 1 | author: Thomas & Jed 2 | core-version: >=5.1.13 3 | created: 20211028154139157 4 | dependents: 5 | description: EditorCounter & Autosaver 6 | list: readme settings credits support 7 | modified: 20211028154139157 8 | name: EditorCounter 9 | plugin-type: plugin 10 | source: https://tid.li/tw5/plugins.html 11 | title: $:/plugins/telmiger/EditorCounter 12 | type: application/json 13 | version: 0.6.6 -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_HarveyBalls.json: -------------------------------------------------------------------------------- 1 | {"tiddlers":{"$:/language/HarveyBalls/chaotic/desc":{"title":"$:/language/HarveyBalls/chaotic/desc","created":"20171004221642521","creator":"Thomas Elmiger","modified":"20190426122338635","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle filled with colour"},"$:/language/HarveyBalls/chaotic/title":{"title":"$:/language/HarveyBalls/chaotic/title","created":"20171004221542587","creator":"Thomas Elmiger","modified":"20190426122338622","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"chaotic"},"$:/language/HarveyBalls/complex/desc":{"title":"$:/language/HarveyBalls/complex/desc","created":"20171005182623257","creator":"Thomas Elmiger","modified":"20190426122338805","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle filled with colour exempt upper right quadrant"},"$:/language/HarveyBalls/complex/title":{"title":"$:/language/HarveyBalls/complex/title","created":"20171005182745229","creator":"Thomas Elmiger","modified":"20190426122338802","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"complex"},"$:/language/HarveyBalls/complicated/desc":{"title":"$:/language/HarveyBalls/complicated/desc","created":"20171005182907849","creator":"Thomas Elmiger","modified":"20190426122338798","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle with left half filled"},"$:/language/HarveyBalls/complicated/title":{"title":"$:/language/HarveyBalls/complicated/title","created":"20171005182951452","creator":"Thomas Elmiger","modified":"20190426122338793","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"complicated"},"$:/language/HarveyBalls/empty/desc":{"title":"$:/language/HarveyBalls/empty/desc","created":"20171001210121157","creator":"Thomas Elmiger","modified":"20190426122338790","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle (not filled)"},"$:/language/HarveyBalls/empty/title":{"title":"$:/language/HarveyBalls/empty/title","created":"20171001210047885","creator":"Thomas Elmiger","modified":"20190426122338786","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"empty"},"$:/language/HarveyBalls/full/desc":{"title":"$:/language/HarveyBalls/full/desc","created":"20171001201844602","creator":"Thomas Elmiger","modified":"20190426122338782","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle filled with colour"},"$:/language/HarveyBalls/full/title":{"title":"$:/language/HarveyBalls/full/title","created":"20171001204350059","creator":"Thomas Elmiger","modified":"20190426122338778","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"full"},"$:/language/HarveyBalls/half/desc":{"title":"$:/language/HarveyBalls/half/desc","created":"20171001211939192","creator":"Thomas Elmiger","modified":"20190426122338775","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle with right half filled"},"$:/language/HarveyBalls/half/title":{"title":"$:/language/HarveyBalls/half/title","created":"20171001213417585","creator":"Thomas Elmiger","modified":"20190426122338771","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"half"},"$:/language/HarveyBalls/obvious/desc":{"title":"$:/language/HarveyBalls/obvious/desc","created":"20171005183829060","creator":"Thomas Elmiger","modified":"20190426122338767","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle with upper left quadrant filled"},"$:/language/HarveyBalls/obvious/title":{"title":"$:/language/HarveyBalls/obvious/title","created":"20171005183754671","creator":"Thomas Elmiger","modified":"20190426122338763","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"obvious"},"$:/language/HarveyBalls/quarter/desc":{"title":"$:/language/HarveyBalls/quarter/desc","created":"20171001211324859","creator":"Thomas Elmiger","modified":"20190426122338752","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle with upper right quadrant filled"},"$:/language/HarveyBalls/quarter/title":{"title":"$:/language/HarveyBalls/quarter/title","created":"20171001211836428","creator":"Thomas Elmiger","modified":"20190426122338747","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"quarter"},"$:/language/HarveyBalls/quarter-empty/desc":{"title":"$:/language/HarveyBalls/quarter-empty/desc","created":"20171001212131114","creator":"Thomas Elmiger","modified":"20190426122338759","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle filled with colour exempt upper left quadrant"},"$:/language/HarveyBalls/quarter-empty/title":{"title":"$:/language/HarveyBalls/quarter-empty/title","created":"20171001212013679","creator":"Thomas Elmiger","modified":"20190426122338755","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"quarter empty"},"$:/language/HarveyBalls/simple/desc":{"title":"$:/language/HarveyBalls/simple/desc","created":"20171004222018780","creator":"Thomas Elmiger","modified":"20190426122338743","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"circle (not filled)"},"$:/language/HarveyBalls/simple/title":{"title":"$:/language/HarveyBalls/simple/title","created":"20171004221927784","creator":"Thomas Elmiger","modified":"20190426122338739","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"simple"},"$:/plugins/telmiger/HarveyBalls/alternatives":{"title":"$:/plugins/telmiger/HarveyBalls/alternatives","created":"20171005185418327","creator":"Thomas Elmiger","modified":"20190426122338735","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"!! Alternatives\n\n!!! SVG from Wikipedia\nOn https://en.wikipedia.org/wiki/Harvey_Balls you will find alternative graphics in one single SVG file. There are neither tiltes nor descriptions in this image.\n\n!!! Fonts\nSome fonts like Arial Unicode MS contain symbols similar to Harvey balls:\n\n@@font-size:2em; \n○●◯◔◑◕◐◒◓◖◗◝◞◟◜◝◡◉◌◍◎☹☺☻\n@@\n\nThese are found starting around U+25CB (white circle). I don’t know if or how a screenreader would announce them. Other dedicated fonts with Harvey balls are available, I found some using the numbers from 0 to 9 and I guess a screenreader would announce them as such. Would numbers from 0 to 9 convey enough meaning to your users? Then you should use numbers instead of Harvey balls if you ask me.\n\n!! Accessibility\n\nIn general font solutions as well as monolithic SVG graphics lack the ability to control or adapt the meaning of the symbols/characters.\n\nThere is no hint popping up from a font if a user hovers over the icon with the pointer of the mouse. \n\nWith my SVG solution, the title is shown as a hint and ~VoiceOver on the Mac announces both title and desc attributes.\n"},"$:/plugins/telmiger/HarveyBalls/chaotic/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/chaotic/icon.svg","created":"20171004221325017","creator":"Thomas Elmiger","modified":"20190426122338730","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/chaotic/title}}{{$:/language/HarveyBalls/chaotic/desc}}"},"$:/plugins/telmiger/HarveyBalls/complex/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/complex/icon.svg","created":"20171005184808610","creator":"Thomas Elmiger","modified":"20190426122338726","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/complex/title}}{{$:/language/HarveyBalls/complex/desc}}"},"$:/plugins/telmiger/HarveyBalls/complicated/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/complicated/icon.svg","created":"20171005184612869","creator":"Thomas Elmiger","modified":"20190426122338722","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/complicated/title}}{{$:/language/HarveyBalls/complicated/desc}}"},"$:/plugins/telmiger/HarveyBalls/credits":{"title":"$:/plugins/telmiger/HarveyBalls/credits","created":"20171005185503725","creator":"Thomas Elmiger","modified":"20190426122338718","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"!!! Credits\nInspired by ~RunningUtes in a [[Google Group discussion|https://groups.google.com/d/msg/tiddlywiki/utsLmgHZAhM/bxKkdwT2CAAJ]] and these great articles:\n\n* https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4\n* https://developer.paciellogroup.com/blog/2013/12/using-aria-enhance-svg-accessibility/"},"$:/plugins/telmiger/HarveyBalls/empty/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/empty/icon.svg","created":"20171001102215819","creator":"Thomas Elmiger","modified":"20190426122338710","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/empty/title}}{{$:/language/HarveyBalls/empty/desc}}"},"$:/plugins/telmiger/HarveyBalls/full/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/full/icon.svg","created":"20171001110625517","creator":"Thomas Elmiger","modified":"20190426122338704","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/full/title}}{{$:/language/HarveyBalls/full/desc}}"},"$:/plugins/telmiger/HarveyBalls/half/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/half/icon.svg","created":"20171001113351829","creator":"Thomas Elmiger","modified":"20190426122338700","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/half/title}}{{$:/language/HarveyBalls/half/desc}}"},"$:/plugins/telmiger/HarveyBalls/icons.css":{"title":"$:/plugins/telmiger/HarveyBalls/icons.css","created":"20171001103035127","creator":"Thomas Elmiger","modified":"20190426122338695","modifier":"Thomas Elmiger","tags":"$:/tags/Stylesheet","type":"text/vnd.tiddlywiki","text":"/* Size and position \n– inspired by https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4\n*/\n\nsvg.em-icon {\n width: 1em;\n height: 1em;\n margin-bottom: -0.125em;\n}\n\n/* Colours */\n\nsvg.em-icon.ball-empty {\n fill: rgba(255,0,0,0.95);\n}\nsvg.em-icon.ball-full {\n fill: green;\n}\nsvg.em-icon.ball-quarter {\n fill: orange;\n}\nsvg.em-icon.ball-half {\n fill: #000;\n}\nsvg.em-icon.ball-quarter-empty {\n fill: blue;\n}"},"$:/plugins/telmiger/HarveyBalls/macros":{"title":"$:/plugins/telmiger/HarveyBalls/macros","created":"20171005202753767","creator":"Thomas Elmiger","modified":"20190426122338691","modifier":"Thomas Elmiger","tags":"$:/tags/Macro","type":"text/vnd.tiddlywiki","text":"\\define HarveyBall(variant:\"empty\")\n<$vars ball=\"\"\"$:/plugins/telmiger/HarveyBalls/$variant$/icon.svg\"\"\"><$transclude tiddler=<>/>\n\\end\n\n\\define HB0() <>\n\\define HB1() <>\n\\define HB2() <>\n\\define HB3() <>\n\\define HB4() <>\n\n\\define HB5() <>\n\\define HB8() <>\n\\define HB13() <>\n\\define HB21() <>\n\\define HB34() <>\n\n\\define HB(nr:\"0\")\n<$vars macro=\"\"\"HB$nr$\"\"\"><$macrocall $name=<>/>\n\\end\n\n<>\n"},"$:/plugins/telmiger/HarveyBalls/obvious/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/obvious/icon.svg","created":"20171005184306362","creator":"Thomas Elmiger","modified":"20190426122338686","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/obvious/title}}{{$:/language/HarveyBalls/obvious/desc}}"},"$:/plugins/telmiger/HarveyBalls/quarter/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/quarter/icon.svg","created":"20171001112413825","creator":"Thomas Elmiger","modified":"20190426122338670","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/quarter/title}}{{$:/language/HarveyBalls/quarter/desc}}\n"},"$:/plugins/telmiger/HarveyBalls/quarter-empty/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/quarter-empty/icon.svg","created":"20171001114641516","creator":"Thomas Elmiger","modified":"20190426122338679","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/quarter-empty/title}}{{$:/language/HarveyBalls/quarter-empty/desc}}"},"$:/plugins/telmiger/HarveyBalls/readme":{"title":"$:/plugins/telmiger/HarveyBalls/readme","created":"20171001103302730","creator":"Thomas Elmiger","modified":"20190426122338661","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"\\define tr(variant,nr:\"\")\n<$vars tit=\"$:/language/HarveyBalls/$variant$/title\" desc=\"$:/language/HarveyBalls/$variant$/desc\" nr=\"\"\"$nr$\"\"\">\n{{$:/plugins/telmiger/HarveyBalls/$variant$/icon.svg}} <><$link to=<>><$transclude tiddler=<>/> <$link to=<>><$transclude tiddler=<>/>\n\n\\end\n\n!! Harvey Balls\n\nHarvey Balls are round ideograms used for visual communication of qualitative information. They are commonly used in comparison tables to indicate the degree to which a particular item meets a particular criterion.\n\n!!! Macros for display\nThe same as `{{$:/plugins/telmiger/HarveyBalls/half/icon.svg}}` – the Harvey ball <> – would be displayed for all three of the following macros:\n\n`<>`\n`<>`\n`<>`\n\nTitles or numbers of the following tables can be used.\n\n!!! Available ideograms\n\n//1) more colour is better//\n\n\n\n<>\n<>\n<>\n<>\n<>\n
icon # title desc
\n\n//2) less colour is better//\n\n\n\n<>\n<>\n<>\n<>\n<>\n
icon # title desc
\n\n!!! Adapt or translate for better accessibility\nTo convey another meaning with your Harvey Balls, it is possible to adapt or translate titles and descriptions. So it is possible to use them e.g. for a performance ratings or a score like:\n\n* poor\n* fair\n* good\n* very good\n* excellent\n\nJust click the text in the table above and adapt the content to your needs. The warning is just a reminder that you are overwriting the standard text. You can always delete your own version to return to the original.\n\n!!! Control dimensions via font size\n\nInside surrounding text, the Harvey balls adapt themselves in size. Examples:\n\n! Title H1 {{$:/plugins/telmiger/HarveyBalls/empty/icon.svg}}\n\n!! Title H2 {{$:/plugins/telmiger/HarveyBalls/full/icon.svg}} more text\n\n!!! Title H3 {{$:/plugins/telmiger/HarveyBalls/quarter/icon.svg}} more text\n\n!!!! Title H4 {{$:/plugins/telmiger/HarveyBalls/half/icon.svg}} more text\n\nNormal text paragraph {{$:/plugins/telmiger/HarveyBalls/quarter-empty/icon.svg}} more text or apply [[styling via Wikitext|http://tiddlywiki.com/#Styles%20and%20Classes%20in%20WikiText]]: \n\n@@font-size:2em; \nYou will need some text {{$:/plugins/telmiger/HarveyBalls/full/icon.svg}} to make this big.\n@@\n\n!!! Adjust colours via CSS\nThe styling of the balls can be changed \n\n# by adapting the style sheet $:/plugins/telmiger/HarveyBalls/icons.css\n# by adapting surrounding text color in case of the //less colour is better// version (it uses fill=\"currentColor\")\n\n\n"},"$:/plugins/telmiger/HarveyBalls/simple/icon.svg":{"title":"$:/plugins/telmiger/HarveyBalls/simple/icon.svg","created":"20171004221820081","creator":"Thomas Elmiger","modified":"20190426122338648","modifier":"Thomas Elmiger","tags":"$:/tags/Image","type":"text/vnd.tiddlywiki","text":"{{$:/language/HarveyBalls/simple/title}}{{$:/language/HarveyBalls/simple/desc}}"}}} -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_HarveyBalls.json.meta: -------------------------------------------------------------------------------- 1 | author: Thomas Elmiger 2 | core-version: >=5.1.14 3 | created: 20211028154138728 4 | dependents: 5 | description: HarveyBalls 6 | list: readme alternatives credits 7 | modified: 20211028154138728 8 | name: HarveyBalls 9 | plugin-type: plugin 10 | source: https://tid.li/tw5/plugins.html 11 | title: $:/plugins/telmiger/HarveyBalls 12 | type: application/json 13 | version: 0.0.6 -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_PluginSize.json: -------------------------------------------------------------------------------- 1 | {"tiddlers":{"$:/plugins/telmiger/PluginSize/ViewTemplate":{"title":"$:/plugins/telmiger/PluginSize/ViewTemplate","created":"20190122065955189","creator":"Thomas Elmiger","list-after":"$:/core/ui/ViewTemplate/plugin","modified":"20200605060258345","modifier":"Thomas Elmiger","tags":"$:/tags/ViewTemplate","type":"text/vnd.tiddlywiki","text":"<$list filter=\"[all[current]has[plugin-type]] -[all[current]field:plugin-type[import]]\">\n{{||$:/plugins/telmiger/PluginSize/template}}\n"},"$:/plugins/telmiger/PluginSize/about":{"title":"$:/plugins/telmiger/PluginSize/about","created":"20200524224655528","modified":"20200605060258364","tags":"","type":"text/vnd.tiddlywiki","text":"This plugin calculates the size of all installed plugins, including themes and languages. It adds a tab //Plugin Info// to the [[$:/ControlPanel]] – the one with the {{$:/core/images/info-button}} icon."},"$:/plugins/telmiger/PluginSize/icon":{"title":"$:/plugins/telmiger/PluginSize/icon","created":"20190127213417005","creator":"Thomas Elmiger","modified":"20200605060258378","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"<>"},"$:/plugins/telmiger/PluginSize/plugin-info":{"title":"$:/plugins/telmiger/PluginSize/plugin-info","caption":"{{$:/core/images/info-button}} Plugin Info","created":"20190308213013859","creator":"Thomas Elmiger","list-after":"$:/core/ui/ControlPanel/Plugins","modified":"20200605060258392","modifier":"Thomas Elmiger","tags":"$:/tags/ControlPanel","type":"text/vnd.tiddlywiki","text":"\\define lingo-base() $:/language/ControlPanel/Plugins/\n\n\\define plugin-table(type)\n<$set name=\"plugin-type\" value=\"\"\"$type$\"\"\">\n<$set name=\"qualified-state\" value=<>>\n<$list filter=\"[!has[draft.of]plugin-type[$type$]sort[description]]\" emptyMessage=<> >\n
\n{{||$:/core/ui/Components/plugin-info}}\n
\n{{||$:/plugins/telmiger/PluginSize/template}}\n\n\n\n\\end\n\n<>\n\n<>\n"},"$:/plugins/telmiger/PluginSize/readme":{"title":"$:/plugins/telmiger/PluginSize/readme","created":"20190127210600496","creator":"Thomas Elmiger","modified":"20200605060258408","modifier":"Thomas Elmiger","tags":"","type":"text/vnd.tiddlywiki","text":"!! Plugin Size\n\n{{$:/plugins/telmiger/PluginSize/about}}\n\n!!! Categories (character count)\n\n* <> more than 500 k\n* <> up to 500 k \n* <> up to 100 k\n* <> up to 50 k\n* <> up to 25 k\n\n!!! Dependencies\n\nThis is done using the following other plugins: \n\n* [[Editor Counter|https://tid.li/tw5/plugins.html#%24%3A%2Fplugins%2Ftelmiger%2FEditorCounter]] to count the characters in the text field of plugin tiddlers\n* [[RPN|https://tid.li/tw5/plugins.html#%24%3A%2Fplugins%2Ftelmiger%2Frpn]] for number formatting\n* [[Harvey Balls|https://tid.li/tw5/plugins.html#%24%3A%2Fplugins%2Ftelmiger%2FHarveyBalls]] for size indication icons\n\n!!! Information\n\nThe size of installed plugins can be found on \n\n* a tab //{{$:/core/images/info-button}} Plugin Info// in the $:/ControlPanel \n* on $:/plugins/telmiger/PluginSize/plugin-info\n\n!!! Copyright \n\n© 2019-2020, Thomas Elmiger"},"$:/plugins/telmiger/PluginSize/styles.css":{"title":"$:/plugins/telmiger/PluginSize/styles.css","created":"20190122231405826","creator":"Thomas Elmiger","modified":"20200605060258415","modifier":"Thomas Elmiger","tags":"$:/tags/Stylesheet","type":"text/vnd.tiddlywiki","text":".tiddler-size-indicator {\n width: 100%;\n background-color: rgba(125,125,125,0.3);\n padding: 0.25rem;\n margin-bottom: 1rem;\n}\n\n.te-info-panel .tc-plugin-info {\n margin-bottom: 0;\n}\n\n.te-info-panel .tc-plugin-info-dropdown {\n margin-top: -1px;\n}"},"$:/plugins/telmiger/PluginSize/support":{"title":"$:/plugins/telmiger/PluginSize/support","created":"20200524225402217","modified":"20200605060258442","tags":"","type":"text/vnd.tiddlywiki","text":"{{$:/plugins/telmiger/support}}"},"$:/plugins/telmiger/PluginSize/template":{"title":"$:/plugins/telmiger/PluginSize/template","created":"20190122222437252","creator":"Thomas Elmiger","list-after":"$:/core/ui/ViewTemplate/plugin","modified":"20200605060258457","modifier":"Thomas Elmiger","type":"text/vnd.tiddlywiki","text":"\\define harveyBall(size)\n<$reveal type=\"lt\" text=\"500001\" default=$size$>\n<$reveal type=\"gt\" text=\"100000\" default=$size$>\n<>\n\n<$reveal type=\"lt\" text=\"100001\" default=$size$>\n<$reveal type=\"gt\" text=\"50000\" default=$size$>\n<>\n\n<$reveal type=\"lt\" text=\"50001\" default=$size$>\n<$reveal type=\"gt\" text=\"25000\" default=$size$>\n<>\n\n<$reveal type=\"lt\" text=\"25001\" default=$size$>\n<>\n\n\n\n\n<$reveal type=\"gt\" text=\"500000\" default=$size$>\n<>\n\n\\end\n
\n<$wikify name=\"size\" text=\"\"\"<$editor-counter tiddler=<> field=\"text\" mode=\"character\"/>\"\"\">\n<$macrocall $name=\"harveyBall\" size=<>/> – plugin size: <$macrocall $name=\"rpn\" a=<> b=\"1000\" operation=\"/\" decimals=\"1\" locale=\"en-GB\"/> k characters (<>)\n\n
"}}} -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_PluginSize.json.meta: -------------------------------------------------------------------------------- 1 | author: Thomas Elmiger 2 | core-version: >=5.1.17 3 | created: 20211028154139262 4 | dependents: $:/plugins/telmiger/EditorCounter $:/plugins/telmiger/HarveyBalls $:/plugins/telmiger/rpn 5 | description: Plugin Size – calculate the extra weight 6 | list: readme 7 | modified: 20211028154139262 8 | name: PluginSize 9 | plugin-type: plugin 10 | source: https://tid.li/tw5/plugins.html 11 | title: $:/plugins/telmiger/PluginSize 12 | type: application/json 13 | version: 0.1.1 -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_rpn.json: -------------------------------------------------------------------------------- 1 | { 2 | "tiddlers": { 3 | "$:/plugins/telmiger/rpn/about": { 4 | "title": "$:/plugins/telmiger/rpn/about", 5 | "created": "20200524154716663", 6 | "modified": "20200604183147819", 7 | "tags": "", 8 | "type": "text/vnd.tiddlywiki", 9 | "text": "\n//rpn// is a lightweight Javascript macro for basic math operations. It is inspired by formerly popular calculators using Reverse Polish Notation (see [[Wikipedia|https://en.wikipedia.org/wiki/Reverse_Polish_notation]] for more info). Usage example:\n\n`<>` output: <>" 10 | }, 11 | "$:/plugins/telmiger/rpn/examples": { 12 | "title": "$:/plugins/telmiger/rpn/examples", 13 | "created": "20170715121619734", 14 | "creator": "Thomas Elmiger", 15 | "modified": "20200604183147986", 16 | "modifier": "Thomas Elmiger", 17 | "tags": "[[ToDo Addons]]", 18 | "type": "text/vnd.tiddlywiki", 19 | "text": "!! Examples\n\n`<>` results in <>\n\n`<>` \n\n<<<\n<>\n<<<\n\n\n`<>` creates a random number in the range from a to b:\n\n<<<\nYour lucky number is <>.\n<<<\n\nCalculate the area of a circle with radius 2:\n\n```\n<$macrocall $name=\"rpn\" a=<> b=pi operation=\"*\" decimals=\"4\"/>\n```\n<<<\n<$macrocall $name=\"rpn\" a=<> b=pi operation=\"*\" decimals=\"4\"/>\n<<<\n\nA 50% discount price, rounded downwards with Swiss formatting applied: \n\n```\n<$macrocall $name=\"rpn\" a=<> b=100 operation=\"/\" decimals=\"2\" precision=\"p\" locale=\"de-CH\"/>\n```\n<<<\n<$macrocall $name=\"rpn\" a=<> b=100 operation=\"/\" decimals=\"2\" precision=\"p\" locale=\"de-CH\"/>\n<<<\n\nFor more examples see https://tid.li/tw5/hacks.html#rpnTest." 20 | }, 21 | "$:/plugins/telmiger/rpn/fields": { 22 | "title": "$:/plugins/telmiger/rpn/fields", 23 | "created": "20181111064908459", 24 | "creator": "Thomas Elmiger", 25 | "modified": "20200604183147998", 26 | "modifier": "Thomas Elmiger", 27 | "tags": "", 28 | "type": "text/vnd.tiddlywiki", 29 | "text": "!! Calculate with Field Values and RPN\n\nThe //rpnFields// macro takes the value of a defined field from filtered tiddlers, applies the same operation to all and saves the result in a given tiddler. It produces a button to start the calculation and calculates using [[RPN|$:/plugins/telmiger/rpn]].\n\n\n!!! Attributes\n\n|!Attribute |!Description |!Default |\n|filter |a filter to select tiddlers – the macro makes sure to use only the ones containinig the specified //field// |all tiddlers except system tiddlers |\n|startvalue |a value to start with, e.g. the result of a preceding calculation |\"0\" |\n|field |the name of the field to take the (numeric) value from |\"text\" |\n|operation |operation to apply to all values |\"+\" |\n|saveTo |the tiddler to store the result in |$:/temp/telmiger/rpn/result |\n|saveField |the field to store the result in |\"text\" |\n|label |the label for the button |\"Calculate\" |\n\n!!! Usage\n\n```\n<>\n\n<>\n\n<>\n```\n\n{{rpnDemo}}\n" 30 | }, 31 | "$:/plugins/telmiger/rpn/icon": { 32 | "title": "$:/plugins/telmiger/rpn/icon", 33 | "created": "20170715123511072", 34 | "modified": "20200604183148010", 35 | "tags": "", 36 | "type": "text/vnd.tiddlywiki", 37 | "text": "\n \n \n" 38 | }, 39 | "$:/plugins/telmiger/rpn/readme": { 40 | "title": "$:/plugins/telmiger/rpn/readme", 41 | "created": "20170714205116352", 42 | "creator": "Thomas Elmiger", 43 | "modified": "20200604183148017", 44 | "modifier": "Thomas Elmiger", 45 | "tags": "[[ToDo Addons]]", 46 | "type": "text/vnd.tiddlywiki", 47 | "text": "!! Simple calculations\n\n{{$:/plugins/telmiger/rpn/about}}\n\n* the macro takes two operands (//a// and //b//) as input – this may be macros or transclusions\n** operands are sent through a simplified [[wikify|http://tiddlywiki.com/#WikifyWidget]] function before calculation starts\n** use //pi// to get π (Math.PI)\n* standard operations: +, -, /, * \n** advanced operations: modulus (%) and rounding (optional: Math.floor or Math.ceil)\n* output formatting\n** limit digits after comma (//decimals//), force //precision// display with //precision:\"p\"// (.toPrecision)\n** //locale// formatting (.toLocaleString)\n* string concatenation (&)\n** the resulting string is sent through the internal [[wikify|http://tiddlywiki.com/#WikifyWidget]] function \n* random numbers in a range from a to b with //precision:\"r\"// (including both a and b)\n\n!!! New: Wikitext Macros\n* calculate using filters and tiddler fields, see //fields// tab (since version 0.7.1)\n\n!!! Installation\nThe plugin available on https://tid.li/tw5/plugins.html includes the documentation.
\nIf you want the functionality only, you can drag this tiddler to your wiki: $:/plugins/telmiger/rpn/rpn.js – then save and reload. \n\n!!! Alternatives\n* [[TW mathematic filter operators (see tiddlywiki.com)|https://tiddlywiki.com/#Mathematics%20Operators]]\n* https://evanbalster.com/tiddlywiki/formulas.html – process numbers and other data just like popular spreadsheet software, with the additional flexibility of functional programming for those who need it\n* http://mathcell.tiddlyspot.com/ – tries to replace spreadsheet applications and uses some identical operators as rpn\n* http://tobibeer.github.io/tb5/#calc – does calculations based on tiddler fields\n* eval – calc combined with ''math.js'' – a heavy math library\n** http://mklauber.github.io/tiddly-mathjs/ \n** http://tobibeer.github.io/tw5-plugins/#math.js" 48 | }, 49 | "$:/plugins/telmiger/rpn/rpn.js": { 50 | "title": "$:/plugins/telmiger/rpn/rpn.js", 51 | "text": "/*\\\ntitle: $:/plugins/telmiger/lib/rpn.js\ntype: application/javascript\nmodule-type: macro\n\nMacro to execute simple calculations in reverse Polish notation\nDocumentation see https://tid.li/tw5/plugins.html\nVersion 0.6.2\n\\*/\n(function(){\n\n/*jslint node: true, browser: true */\n/*global $tw: false */\n\"use strict\";\n\n/*\nInformation about this macro\n*/\n\nexports.name = \"rpn\";\n\nexports.params = [\n\t{name: \"a\"},\n\t{name: \"b\"},\n {name: \"operation\", default: \"+\"},\n {name: \"decimals\", default: \"\"},\n {name: \"precision\", default: \"\"},\n {name: \"locale\", default: \"false\"},\n {name: \"sd\", default: \".\"},\n {name: \"st\", default: \"\"}\n];\n\n/*\nWikify\n*/\nfunction wikifyText(t) {\n\tvar Parser = $tw.wiki.parseText(\"text/vnd.tiddlywiki\",t,{\n\t\t\tparseAsInline: true\n\t\t});\n\tvar WidgetNode = $tw.wiki.makeWidget(Parser,{\n\t\t\tdocument: $tw.fakeDocument\n\t\t});\n\tvar Container = $tw.fakeDocument.createElement(\"div\");\n\tWidgetNode.render(Container,null);\n\treturn Container.textContent;\n}\n\n/*\nFormat decimals and (TODO!) thousands\n*/\nfunction formatNumber(value,sd,st) {\n\tlet string = value.toString();\n\t// string = string; // TODO separator for thousands\n\treturn string.replace(\".\",sd); // separator for decimals\n}\n\n/*\nFormatting with locale\n*/\nfunction formatLocale(value,decimals,precision,locale) { \n\tlet result = \"NaN\";\n\tif(decimals == \"\" || decimals >= 0) {\n\t\tlet minFracDig = (precision == \"p\" ? decimals : \"\");\n\t\tresult = value.toLocaleString(locale, { minimumFractionDigits: minFracDig });\n\t}\n\telse {\n\t\tresult = \"invalid decimals\";\n\t}\n\treturn result;\n}\n\n/*\nPrecision for decimals\n*/\nfunction addPrecisionDecimals(result,decimals) { \n let di = parseInt(decimals);\n let rInteger = result.toString().split('.')[0].length;\n let p = rInteger + di;\n if(p > 0 && p < 101) {\n\tresult = result.toPrecision(p);\n }\n else {\n result = \"invalid decimals\";\n }\n return result;\n}\n\n/*\nRandom numbers including a and b\n*/\nfunction randomInteger(a,b) {\n\tlet min = Math.ceil(a);\n\tlet max = Math.floor(b + 1);\n\treturn Math.floor(Math.random() * (max - min)) + min; \n}\n\n/*\nMath\n*/\nfunction calculate(a,b,operation) {\n let result = \"NaN\";\n switch(operation) {\n case \"-\":\n result = a - b;\n break;\n case \"*\":\n result = a * b;\n break;\n case \"/\":\n result = a / b;\n break;\n case \"%\":\n result = a % b;\n break;\n default:\n result = a + b;\n }\n return result;\n}\n\n/*\nRun the macro\n*/\nexports.run = function(a,b,operation,decimals,precision,locale,sd,st) {\n\tlet aw = wikifyText(a);\n\tlet bw = wikifyText(b);\n\tlet result = \"\";\n\tif(operation == \"&\") {\n\t\tresult = wikifyText(a + b);\n\t}\n\telse {\n\t\t// prepare values\n\t\tvar af = aw.replace(\",\",\".\");\n\t\tvar bf = bw.replace(\",\",\".\");\n\t\taf = (af == \"pi\" ? Math.PI : parseFloat(af));\n\t\tbf = (bf == \"pi\" ? Math.PI : parseFloat(bf));\n\t\t// do the math\n\t\tresult = calculate(af,bf,operation);\n\t\t// precision modes\n\t\tif(precision == \"r\") {\n\t\t\tresult = randomInteger(af,bf); \n\t\t}\n\t\telse if(precision == \"p\" && locale == \"false\") {\n\t\t\tresult = addPrecisionDecimals(result,decimals);\n\t\t}\n\t\telse if(precision == \"c\") {\n\t\t\tresult = Math.ceil(result);\n\t\t}\n\t\telse if(precision == \"f\") {\n\t\t\tresult = Math.floor(result);\n\t\t}\n\t\telse if(precision != \"\" && locale == \"false\") {\n\t\t// formatLocale ignores precision values other than p\n\t\t\tresult = \"invalid precision\";\n\t\t}\n\t\t// if precision is not defined, round based on (max.) decimals\n\t\telse if(decimals != \"\") {\n\t\t\tvar di = parseInt(decimals);\n\t\t\tresult = Math.round(result * Math.pow(10, di)) / Math.pow(10, di); \n\t\t}\n\t\t// format output\n\t\tif(locale != \"false\") {\n\t\t\tresult = formatLocale(result,decimals,precision,locale);\n\t\t}\n\t\telse {\n\t\t\tresult = formatNumber(result,sd,st);\n\t\t}\n\t}\n\treturn result;\n};\n\n})();\n", 52 | "type": "application/javascript", 53 | "module-type": "macro", 54 | "created": "20170626211546891", 55 | "creator": "Thomas Elmiger", 56 | "modified": "20200604183148030", 57 | "modifier": "Thomas Elmiger", 58 | "tags": "[[ToDo Addons]]" 59 | }, 60 | "$:/plugins/telmiger/rpn/rpnFields": { 61 | "title": "$:/plugins/telmiger/rpn/rpnFields", 62 | "created": "20181111075024851", 63 | "creator": "Thomas Elmiger", 64 | "modified": "20200604183148043", 65 | "modifier": "Thomas Elmiger", 66 | "tags": "$:/tags/Macro", 67 | "type": "text/vnd.tiddlywiki", 68 | "text": "\\define rpnFields(filter:\"[tags[]!is[system]]\",startvalue:\"0\",field:\"text\",operation:\"+\",\n saveTo:\"$:/temp/telmiger/rpn/result\",saveField:\"text\",label:\"Calculate\")\n<$button>\n<$vars filter=\"\"\"$filter$\"\"\" startvalue=\"\"\"$startvalue$\"\"\" field=\"\"\"$field$\"\"\" operation=\"\"\"$operation$\"\"\" saveTo=\"\"\"$saveTo$\"\"\" saveField=\"\"\"$saveField$\"\"\">\n<$wikify name=\"countfilter\" text=\"\"\"$filter$ +[has[<>]] +[count[]]\"\"\">\n<$set name=\"items\" filter=<> emptyValue=\"0\">\n<$macrocall $name=\"calcAll\" startvalue=<> counter=<>/>\n\"\"\"$label$\"\"\"\n\n\n\n\n\\end\n\n\\define saveResult(a:\"0\")\n<$action-setfield $tiddler=\"$(saveTo)$\" $field=\"$(saveField)$\" $value=\"\"\"$a$\"\"\"/>\n\\end\n\n\\define calculate(a:\"0\",b:\"0\",counter)\n<$wikify name=\"result\" text='<$macrocall $name=\"rpn\" a=$a$ b=$b$ operation=$(operation)$/>'>\n<$macrocall $name=\"calcAll\" startvalue=<> counter=$counter$/>\n\n\\end\n\n\\define calcAll(startvalue,counter)\n<$wikify name=\"next\" text='<$macrocall $name=\"rpn\" a=$counter$ b=1 operation=\"-\"/>'>\n<$wikify name=\"restfilter\" text=\"\"\"$(filter)$ +[has[$(field)$]] +[rest[<>]limit[1]]\"\"\">\n<$set name=\"proceed\" filter=\"[prefix[-]]\" value=\"saveResult\" emptyValue=\"calculate\">\n<$list filter=<> variable=\"tid\">\n<$vars a=$startvalue$>\n<$wikify name=\"b\" text='<$transclude tiddler=<> field=$(field)$/>'>\n<$macrocall $name=<> a=<> b=<> counter=<>/>\n\n\n\n\n\n\n\\end\n" 69 | }, 70 | "$:/plugins/telmiger/rpn/support": { 71 | "title": "$:/plugins/telmiger/rpn/support", 72 | "created": "20181111202222852", 73 | "modified": "20200604183148049", 74 | "tags": "", 75 | "type": "text/vnd.tiddlywiki", 76 | "text": "{{$:/plugins/telmiger/support}}" 77 | }, 78 | "$:/plugins/telmiger/rpn/usage": { 79 | "title": "$:/plugins/telmiger/rpn/usage", 80 | "created": "20170715121502832", 81 | "creator": "Thomas Elmiger", 82 | "modified": "20200604183148059", 83 | "modifier": "Thomas Elmiger", 84 | "tags": "[[ToDo Addons]]", 85 | "type": "text/vnd.tiddlywiki", 86 | "text": "!! Usage\n\n`<>` output: <>\n\nThe following operations can be used with two operands:\n\n|Operation |Description |h\n|+ |Addition |\n|- |Subtraction |\n|* |Multiplication |\n|/ |Division |\n|% |Modulo (remains after division) |\n|& |Concatenation of two strings^^1^^ |\n| |[none] when generating random numbers, any value would be ignored |\n\n1 – The list of operators found at Stephen Kimmel’s Mathcell project (see readme section) inspired me to implement this easter egg – it has nothing to do with math.\n\n|Parameter |Description |h\n|a |first operand |\n|b |second operand |\n|operation |see above |\n|decimals |digits after comma (number) |\n|precision |defaults to \"\"
\"p\" to show all digits after comma (precision)
\"f\" to round downwards to the next integer (Math.floor)
\"c\" to round upwards to the next integer (Math.ceil)
\"r\" to get a random integer in the range from a to b, a and b included (Math.random) |\n|locale |Formatting according to language or country specific standards, e.g. \"en\", \"de-DE\" or \"de-CH\" – [[toLocaleString|https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Browser_compatibility]] is used, including minimumFractionDigits option if decimals and precision \"p\" are provided. |\n|sd |separator for decimals if //locale// is not used, defaults to \".\" |\n|st |TODO separator for thousands if //locale// is not used, defaults to \"\" |\n\n!!! Known limitations\n* //st// is not implemented yet" 87 | }, 88 | "$:/plugins/telmiger/rpn": { 89 | "text": "{\n \"tiddlers\": {\n \"$:/plugins/telmiger/rpn/examples\": {\n \"created\": \"20170715121619734\",\n \"creator\": \"Thomas Elmiger\",\n \"text\": \"!! Examples\\n\\n`<>` results in <>\\n\\n`<>` \\n\\n<<<\\n<>\\n<<<\\n\\n\\n`<>` creates a random number in the range from a to b:\\n\\n<<<\\nYour lucky number is <>.\\n<<<\\n\\nCalculate the area of a circle with radius 2:\\n\\n```\\n<$macrocall $name=\\\"rpn\\\" a=<> b=pi operation=\\\"*\\\" decimals=\\\"4\\\"/>\\n```\\n<<<\\n<$macrocall $name=\\\"rpn\\\" a=<> b=pi operation=\\\"*\\\" decimals=\\\"4\\\"/>\\n<<<\\n\\nA 50% discount price, rounded downwards with Swiss formatting applied: \\n\\n```\\n<$macrocall $name=\\\"rpn\\\" a=<> b=100 operation=\\\"/\\\" decimals=\\\"2\\\" precision=\\\"p\\\" locale=\\\"de-CH\\\"/>\\n```\\n<<<\\n<$macrocall $name=\\\"rpn\\\" a=<> b=100 operation=\\\"/\\\" decimals=\\\"2\\\" precision=\\\"p\\\" locale=\\\"de-CH\\\"/>\\n<<<\\n\\nFor more examples see https://tid.li/tw5/hacks.html#rpnTest.\",\n \"title\": \"$:/plugins/telmiger/rpn/examples\",\n \"tags\": \"[[ToDo Addons]]\",\n \"modifier\": \"Thomas Elmiger\",\n \"modified\": \"20170814063737796\"\n },\n \"$:/plugins/telmiger/rpn/icon\": {\n \"created\": \"20170715123511072\",\n \"text\": \"\\n \\n \\n\",\n \"title\": \"$:/plugins/telmiger/rpn/icon\",\n \"tags\": \"\",\n \"modified\": \"20200524185736871\"\n },\n \"$:/plugins/telmiger/rpn/readme\": {\n \"created\": \"20170714205116352\",\n \"creator\": \"Thomas Elmiger\",\n \"text\": \"!! Simple calculations\\n\\n{{$:/plugins/telmiger/rpn/about}}\\n\\n* the macro takes two operands (//a// and //b//) as input – this may be macros or transclusions\\n** operands are sent through a simplified [[wikify|http://tiddlywiki.com/#WikifyWidget]] function before calculation starts\\n** use //pi// to get π (Math.PI)\\n* standard operations: +, -, /, * \\n** advanced operations: modulus (%) and rounding (optional: Math.floor or Math.ceil)\\n* output formatting\\n** limit digits after comma (//decimals//), force //precision// display with //precision:\\\"p\\\"// (.toPrecision)\\n** //locale// formatting (.toLocaleString)\\n* string concatenation (&)\\n** the resulting string is sent through the internal [[wikify|http://tiddlywiki.com/#WikifyWidget]] function \\n* random numbers in a range from a to b with //precision:\\\"r\\\"// (including both a and b)\\n\\n!!! New: Wikitext Macros\\n* calculate using filters and tiddler fields, see //fields// tab (since version 0.7.1)\\n\\n!!! Installation\\nThe plugin available on https://tid.li/tw5/plugins.html includes the documentation.
\\nIf you want the functionality only, you can drag this tiddler to your wiki: $:/plugins/telmiger/rpn/rpn.js – then save and reload. \\n\\n!!! Alternatives\\n* [[TW mathematic filter operators (see tiddlywiki.com)|https://tiddlywiki.com/#Mathematics%20Operators]]\\n* https://evanbalster.com/tiddlywiki/formulas.html – process numbers and other data just like popular spreadsheet software, with the additional flexibility of functional programming for those who need it\\n* http://mathcell.tiddlyspot.com/ – tries to replace spreadsheet applications and uses some identical operators as rpn\\n* http://tobibeer.github.io/tb5/#calc – does calculations based on tiddler fields\\n* eval – calc combined with ''math.js'' – a heavy math library\\n** http://mklauber.github.io/tiddly-mathjs/ \\n** http://tobibeer.github.io/tw5-plugins/#math.js\",\n \"title\": \"$:/plugins/telmiger/rpn/readme\",\n \"tags\": \"[[ToDo Addons]]\",\n \"modifier\": \"Thomas Elmiger\",\n \"modified\": \"20200524154810042\"\n },\n \"$:/plugins/telmiger/rpn/rpn.js\": {\n \"text\": \"/*\\\\\\ntitle: $:/plugins/telmiger/lib/rpn.js\\ntype: application/javascript\\nmodule-type: macro\\n\\nMacro to execute simple calculations in reverse Polish notation\\nDocumentation see https://tid.li/tw5/plugins.html\\nVersion 0.6.2\\n\\\\*/\\n(function(){\\n\\n/*jslint node: true, browser: true */\\n/*global $tw: false */\\n\\\"use strict\\\";\\n\\n/*\\nInformation about this macro\\n*/\\n\\nexports.name = \\\"rpn\\\";\\n\\nexports.params = [\\n\\t{name: \\\"a\\\"},\\n\\t{name: \\\"b\\\"},\\n {name: \\\"operation\\\", default: \\\"+\\\"},\\n {name: \\\"decimals\\\", default: \\\"\\\"},\\n {name: \\\"precision\\\", default: \\\"\\\"},\\n {name: \\\"locale\\\", default: \\\"false\\\"},\\n {name: \\\"sd\\\", default: \\\".\\\"},\\n {name: \\\"st\\\", default: \\\"\\\"}\\n];\\n\\n/*\\nWikify\\n*/\\nfunction wikifyText(t) {\\n\\tvar Parser = $tw.wiki.parseText(\\\"text/vnd.tiddlywiki\\\",t,{\\n\\t\\t\\tparseAsInline: true\\n\\t\\t});\\n\\tvar WidgetNode = $tw.wiki.makeWidget(Parser,{\\n\\t\\t\\tdocument: $tw.fakeDocument\\n\\t\\t});\\n\\tvar Container = $tw.fakeDocument.createElement(\\\"div\\\");\\n\\tWidgetNode.render(Container,null);\\n\\treturn Container.textContent;\\n}\\n\\n/*\\nFormat decimals and (TODO!) thousands\\n*/\\nfunction formatNumber(value,sd,st) {\\n\\tlet string = value.toString();\\n\\t// string = string; // TODO separator for thousands\\n\\treturn string.replace(\\\".\\\",sd); // separator for decimals\\n}\\n\\n/*\\nFormatting with locale\\n*/\\nfunction formatLocale(value,decimals,precision,locale) { \\n\\tlet result = \\\"NaN\\\";\\n\\tif(decimals == \\\"\\\" || decimals >= 0) {\\n\\t\\tlet minFracDig = (precision == \\\"p\\\" ? decimals : \\\"\\\");\\n\\t\\tresult = value.toLocaleString(locale, { minimumFractionDigits: minFracDig });\\n\\t}\\n\\telse {\\n\\t\\tresult = \\\"invalid decimals\\\";\\n\\t}\\n\\treturn result;\\n}\\n\\n/*\\nPrecision for decimals\\n*/\\nfunction addPrecisionDecimals(result,decimals) { \\n let di = parseInt(decimals);\\n let rInteger = result.toString().split('.')[0].length;\\n let p = rInteger + di;\\n if(p > 0 && p < 101) {\\n\\tresult = result.toPrecision(p);\\n }\\n else {\\n result = \\\"invalid decimals\\\";\\n }\\n return result;\\n}\\n\\n/*\\nRandom numbers including a and b\\n*/\\nfunction randomInteger(a,b) {\\n\\tlet min = Math.ceil(a);\\n\\tlet max = Math.floor(b + 1);\\n\\treturn Math.floor(Math.random() * (max - min)) + min; \\n}\\n\\n/*\\nMath\\n*/\\nfunction calculate(a,b,operation) {\\n let result = \\\"NaN\\\";\\n switch(operation) {\\n case \\\"-\\\":\\n result = a - b;\\n break;\\n case \\\"*\\\":\\n result = a * b;\\n break;\\n case \\\"/\\\":\\n result = a / b;\\n break;\\n case \\\"%\\\":\\n result = a % b;\\n break;\\n default:\\n result = a + b;\\n }\\n return result;\\n}\\n\\n/*\\nRun the macro\\n*/\\nexports.run = function(a,b,operation,decimals,precision,locale,sd,st) {\\n\\tlet aw = wikifyText(a);\\n\\tlet bw = wikifyText(b);\\n\\tlet result = \\\"\\\";\\n\\tif(operation == \\\"&\\\") {\\n\\t\\tresult = wikifyText(a + b);\\n\\t}\\n\\telse {\\n\\t\\t// prepare values\\n\\t\\tvar af = aw.replace(\\\",\\\",\\\".\\\");\\n\\t\\tvar bf = bw.replace(\\\",\\\",\\\".\\\");\\n\\t\\taf = (af == \\\"pi\\\" ? Math.PI : parseFloat(af));\\n\\t\\tbf = (bf == \\\"pi\\\" ? Math.PI : parseFloat(bf));\\n\\t\\t// do the math\\n\\t\\tresult = calculate(af,bf,operation);\\n\\t\\t// precision modes\\n\\t\\tif(precision == \\\"r\\\") {\\n\\t\\t\\tresult = randomInteger(af,bf); \\n\\t\\t}\\n\\t\\telse if(precision == \\\"p\\\" && locale == \\\"false\\\") {\\n\\t\\t\\tresult = addPrecisionDecimals(result,decimals);\\n\\t\\t}\\n\\t\\telse if(precision == \\\"c\\\") {\\n\\t\\t\\tresult = Math.ceil(result);\\n\\t\\t}\\n\\t\\telse if(precision == \\\"f\\\") {\\n\\t\\t\\tresult = Math.floor(result);\\n\\t\\t}\\n\\t\\telse if(precision != \\\"\\\" && locale == \\\"false\\\") {\\n\\t\\t// formatLocale ignores precision values other than p\\n\\t\\t\\tresult = \\\"invalid precision\\\";\\n\\t\\t}\\n\\t\\t// if precision is not defined, round based on (max.) decimals\\n\\t\\telse if(decimals != \\\"\\\") {\\n\\t\\t\\tvar di = parseInt(decimals);\\n\\t\\t\\tresult = Math.round(result * Math.pow(10, di)) / Math.pow(10, di); \\n\\t\\t}\\n\\t\\t// format output\\n\\t\\tif(locale != \\\"false\\\") {\\n\\t\\t\\tresult = formatLocale(result,decimals,precision,locale);\\n\\t\\t}\\n\\t\\telse {\\n\\t\\t\\tresult = formatNumber(result,sd,st);\\n\\t\\t}\\n\\t}\\n\\treturn result;\\n};\\n\\n})();\\n\",\n \"type\": \"application/javascript\",\n \"title\": \"$:/plugins/telmiger/rpn/rpn.js\",\n \"tags\": \"[[ToDo Addons]]\",\n \"module-type\": \"macro\",\n \"modifier\": \"Thomas Elmiger\",\n \"modified\": \"20170814062342206\",\n \"creator\": \"Thomas Elmiger\",\n \"created\": \"20170626211546891\"\n },\n \"$:/plugins/telmiger/rpn/usage\": {\n \"created\": \"20170715121502832\",\n \"text\": \"!! Usage\\n\\n`<>` output: <>\\n\\nThe following operations can be used with two operands:\\n\\n|Operation |Description |h\\n|+ |Addition |\\n|- |Subtraction |\\n|* |Multiplication |\\n|/ |Division |\\n|% |Modulo (remains after division) |\\n|& |Concatenation of two strings^^1^^ |\\n| |[none] when generating random numbers, any value would be ignored |\\n\\n1 – The list of operators found at Stephen Kimmel’s Mathcell project (see readme section) inspired me to implement this easter egg – it has nothing to do with math.\\n\\n|Parameter |Description |h\\n|a |first operand |\\n|b |second operand |\\n|operation |see above |\\n|decimals |digits after comma (number) |\\n|precision |defaults to \\\"\\\"
\\\"p\\\" to show all digits after comma (precision)
\\\"f\\\" to round downwards to the next integer (Math.floor)
\\\"c\\\" to round upwards to the next integer (Math.ceil)
\\\"r\\\" to get a random integer in the range from a to b, a and b included (Math.random) |\\n|locale |Formatting according to language or country specific standards, e.g. \\\"en\\\", \\\"de-DE\\\" or \\\"de-CH\\\" – [[toLocaleString|https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Browser_compatibility]] is used, including minimumFractionDigits option if decimals and precision \\\"p\\\" are provided. |\\n|sd |separator for decimals if //locale// is not used, defaults to \\\".\\\" |\\n|st |TODO separator for thousands if //locale// is not used, defaults to \\\"\\\" |\\n\\n!!! Known limitations\\n* //st// is not implemented yet\",\n \"title\": \"$:/plugins/telmiger/rpn/usage\",\n \"tags\": \"[[ToDo Addons]]\",\n \"modifier\": \"Thomas Elmiger\",\n \"modified\": \"20200524195900125\",\n \"creator\": \"Thomas Elmiger\"\n },\n \"$:/plugins/telmiger/rpn/fields\": {\n \"created\": \"20181111064908459\",\n \"creator\": \"Thomas Elmiger\",\n \"text\": \"!! Calculate with Field Values and RPN\\n\\nThe //rpnFields// macro takes the value of a defined field from filtered tiddlers, applies the same operation to all and saves the result in a given tiddler. It produces a button to start the calculation and calculates using [[RPN|$:/plugins/telmiger/rpn]].\\n\\n\\n!!! Attributes\\n\\n|!Attribute |!Description |!Default |\\n|filter |a filter to select tiddlers – the macro makes sure to use only the ones containinig the specified //field// |all tiddlers except system tiddlers |\\n|startvalue |a value to start with, e.g. the result of a preceding calculation |\\\"0\\\" |\\n|field |the name of the field to take the (numeric) value from |\\\"text\\\" |\\n|operation |operation to apply to all values |\\\"+\\\" |\\n|saveTo |the tiddler to store the result in |$:/temp/telmiger/rpn/result |\\n|saveField |the field to store the result in |\\\"text\\\" |\\n|label |the label for the button |\\\"Calculate\\\" |\\n\\n!!! Usage\\n\\n```\\n<>\\n\\n<>\\n\\n<>\\n```\\n\\n{{rpnDemo}}\\n\",\n \"title\": \"$:/plugins/telmiger/rpn/fields\",\n \"tags\": \"\",\n \"modifier\": \"Thomas Elmiger\",\n \"modified\": \"20181111194355026\"\n },\n \"$:/plugins/telmiger/rpn/rpnFields\": {\n \"created\": \"20181111075024851\",\n \"creator\": \"Thomas Elmiger\",\n \"text\": \"\\\\define rpnFields(filter:\\\"[tags[]!is[system]]\\\",startvalue:\\\"0\\\",field:\\\"text\\\",operation:\\\"+\\\",\\n saveTo:\\\"$:/temp/telmiger/rpn/result\\\",saveField:\\\"text\\\",label:\\\"Calculate\\\")\\n<$button>\\n<$vars filter=\\\"\\\"\\\"$filter$\\\"\\\"\\\" startvalue=\\\"\\\"\\\"$startvalue$\\\"\\\"\\\" field=\\\"\\\"\\\"$field$\\\"\\\"\\\" operation=\\\"\\\"\\\"$operation$\\\"\\\"\\\" saveTo=\\\"\\\"\\\"$saveTo$\\\"\\\"\\\" saveField=\\\"\\\"\\\"$saveField$\\\"\\\"\\\">\\n<$wikify name=\\\"countfilter\\\" text=\\\"\\\"\\\"$filter$ +[has[<>]] +[count[]]\\\"\\\"\\\">\\n<$set name=\\\"items\\\" filter=<> emptyValue=\\\"0\\\">\\n<$macrocall $name=\\\"calcAll\\\" startvalue=<> counter=<>/>\\n\\\"\\\"\\\"$label$\\\"\\\"\\\"\\n\\n\\n\\n\\n\\\\end\\n\\n\\\\define saveResult(a:\\\"0\\\")\\n<$action-setfield $tiddler=\\\"$(saveTo)$\\\" $field=\\\"$(saveField)$\\\" $value=\\\"\\\"\\\"$a$\\\"\\\"\\\"/>\\n\\\\end\\n\\n\\\\define calculate(a:\\\"0\\\",b:\\\"0\\\",counter)\\n<$wikify name=\\\"result\\\" text='<$macrocall $name=\\\"rpn\\\" a=$a$ b=$b$ operation=$(operation)$/>'>\\n<$macrocall $name=\\\"calcAll\\\" startvalue=<> counter=$counter$/>\\n\\n\\\\end\\n\\n\\\\define calcAll(startvalue,counter)\\n<$wikify name=\\\"next\\\" text='<$macrocall $name=\\\"rpn\\\" a=$counter$ b=1 operation=\\\"-\\\"/>'>\\n<$wikify name=\\\"restfilter\\\" text=\\\"\\\"\\\"$(filter)$ +[has[$(field)$]] +[rest[<>]limit[1]]\\\"\\\"\\\">\\n<$set name=\\\"proceed\\\" filter=\\\"[prefix[-]]\\\" value=\\\"saveResult\\\" emptyValue=\\\"calculate\\\">\\n<$list filter=<> variable=\\\"tid\\\">\\n<$vars a=$startvalue$>\\n<$wikify name=\\\"b\\\" text='<$transclude tiddler=<> field=$(field)$/>'>\\n<$macrocall $name=<> a=<
> b=<> counter=<>/>\\n\\n\\n\\n\\n\\n\\n\\\\end\\n\",\n \"title\": \"$:/plugins/telmiger/rpn/rpnFields\",\n \"tags\": \"$:/tags/Macro\",\n \"modifier\": \"Thomas Elmiger\",\n \"modified\": \"20181111192120506\"\n },\n \"$:/plugins/telmiger/support\": {\n \"created\": \"20200524081316557\",\n \"text\": \"!! Support the Author\\n\\n''Hi!'' I’m Thomas, the author of [[tid.li/tw5/plugins.html|https://tid.li/tw5/plugins.html]]. Feedback is always welcome, as well as funding for maintenance, support and new projects :)\\n\\n---\\n\\n!!! One Time Support\\n\\nIf using my plugins just makes you happy, consider a one time payment via ~PayPal to reward the effort:\\n\\nhttps://www.paypal.me/telmiger\\n\\n---\\n\\n!!! Permanent Support\\n\\nIf my tools make you more productive or save you time in your job or your everyday life, you can support me as a Patron: \\n\\nhttps://www.patreon.com/telmiger\\n\\n---\\n\\n!!! Thank You\\n\\nSubstantial parts of my availabe time go to the deveopment of useful plugins for [[TiddlyWiki|https://tiddlywiki.com/]]. – Many others do the same and I would like to thank them all, especially [[Jeremy Ruston|https://tiddlywiki.com/#JeremyRuston]] and all the active members of the community!\\n\\n//Hereby I promise to share future revenues (if any) with other developers who’s works I use or who inspired me.//\\n\\nIf you like my work, I would be very happy to hear from you.\\n\\n''Thank you very much for your support!''
\\n//Thomas//\\n\\nhttps://thomas-elmiger.ch\",\n \"title\": \"$:/plugins/telmiger/support\",\n \"tags\": \"\",\n \"modifier\": \"Thomas Elmiger\",\n \"modified\": \"20200524081326998\",\n \"creator\": \"Thomas Elmiger\"\n },\n \"$:/plugins/telmiger/rpn/support\": {\n \"created\": \"20181111202222852\",\n \"text\": \"{{$:/plugins/telmiger/support}}\",\n \"title\": \"$:/plugins/telmiger/rpn/support\",\n \"tags\": \"\",\n \"modified\": \"20181111202349869\"\n },\n \"$:/plugins/telmiger/rpn/about\": {\n \"created\": \"20200524154716663\",\n \"modified\": \"20200524200003681\",\n \"title\": \"$:/plugins/telmiger/rpn/about\",\n \"text\": \"\\n//rpn// is a lightweight Javascript macro for basic math operations. It is inspired by formerly popular calculators using Reverse Polish Notation (see [[Wikipedia|https://en.wikipedia.org/wiki/Reverse_Polish_notation]] for more info). Usage example:\\n\\n`<>` output: <>\",\n \"tags\": \"\"\n }\n }\n}", 90 | "type": "application/json", 91 | "author": "Thomas Elmiger", 92 | "core-version": ">=5.1.14", 93 | "created": "20170715124812734", 94 | "dependents": "", 95 | "description": "rpn – Basic Math in Reverse Polish Notation", 96 | "list": "readme usage examples fields support", 97 | "modified": "20200604183203914", 98 | "name": "rpn", 99 | "plugin-type": "plugin", 100 | "source": "https://tid.li/tw5/plugins.html", 101 | "title": "$:/plugins/telmiger/rpn", 102 | "version": "0.7.3" 103 | }, 104 | "$:/plugins/telmiger/support": { 105 | "title": "$:/plugins/telmiger/support", 106 | "created": "20181103150753927", 107 | "creator": "Thomas Elmiger", 108 | "modified": "20200604183148066", 109 | "modifier": "Thomas Elmiger", 110 | "tags": "", 111 | "type": "text/vnd.tiddlywiki", 112 | "text": "!! Support the Author\n\n''Hi!'' I’m Thomas, the author of [[tid.li/tw5/plugins.html|https://tid.li/tw5/plugins.html]]. Feedback is always welcome, as well as funding for maintenance, support and new projects :)\n\n---\n\n!!! One Time Support\n\nIf using my plugins just makes you happy, consider a one time payment via ~PayPal to reward the effort:\n\nhttps://www.paypal.me/telmiger\n\n---\n\n!!! Permanent Support\n\nIf my tools make you more productive or save you time in your job or your everyday life, you can support me as a Patron: \n\nhttps://www.patreon.com/telmiger\n\n---\n\n!!! Thank You\n\nSubstantial parts of my availabe time go to the deveopment of useful plugins for [[TiddlyWiki|https://tiddlywiki.com/]]. – Many others do the same and I would like to thank them all, especially [[Jeremy Ruston|https://tiddlywiki.com/#JeremyRuston]] and all the active members of the community!\n\n//Hereby I promise to share future revenues (if any) with other developers who’s works I use or who inspired me.//\n\nIf you like my work, I would be very happy to hear from you.\n\n''Thank you very much for your support!''
\n//Thomas//\n\nhttps://thomas-elmiger.ch" 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /demo/tiddlers/$__plugins_telmiger_rpn.json.meta: -------------------------------------------------------------------------------- 1 | author: Thomas Elmiger 2 | core-version: >=5.1.14 3 | dependents: 4 | description: rpn – Basic Math in Reverse Polish Notation 5 | list: readme usage examples fields support 6 | name: rpn 7 | plugin-type: plugin 8 | source: https://tid.li/tw5/plugins.html 9 | title: $:/plugins/telmiger/rpn 10 | type: application/json 11 | version: 0.7.3 -------------------------------------------------------------------------------- /demo/tiddlers/$__theme.tid: -------------------------------------------------------------------------------- 1 | created: 20211017053653603 2 | creator: 林一二 3 | modified: 20211017053935937 4 | modifier: 林一二 5 | title: $:/theme 6 | type: text/vnd.tiddlywiki 7 | 8 | $:/themes/linonetwo/itonnote -------------------------------------------------------------------------------- /demo/tiddlers/$__themes_tiddlywiki_vanilla.json.meta: -------------------------------------------------------------------------------- 1 | author: JeremyRuston 2 | core-version: >=5.0.0 3 | dependents: 4 | description: Basic theme 5 | name: Vanilla 6 | plugin-priority: 0 7 | plugin-type: theme 8 | title: $:/themes/tiddlywiki/vanilla 9 | type: application/json 10 | version: 5.3.0 -------------------------------------------------------------------------------- /demo/tiddlers/Index.tid: -------------------------------------------------------------------------------- 1 | title: Index 2 | type: text/vnd.tiddlywiki 3 | 4 | {{$:/snippets/minilanguageswitcher}} 5 | 6 | {{$:/plugins/linonetwo/tw-react/docs/example}} 7 | -------------------------------------------------------------------------------- /demo/tiddlers/docs/useFilter.tid: -------------------------------------------------------------------------------- 1 | title: useFilter 2 | type: text/vnd.tiddlywiki 3 | 4 | !! Usage 5 | 6 | ``` 7 | const resultTitles: string[] = useFilter('[tag[AAA]]'); 8 | 9 | // or with dependencies 10 | 11 | const resultTitles: string[] = useFilter('[tag[AAA]]', []); 12 | ``` 13 | -------------------------------------------------------------------------------- /demo/tiddlers/docs/useRenderTiddler.tid: -------------------------------------------------------------------------------- 1 | title: useRenderTiddler 2 | type: text/vnd.tiddlywiki 3 | 4 | !! Usage example 5 | 6 | In your widget, pass your widget to the react props: 7 | 8 | ```ts 9 | getProps = () => { 10 | ... 11 | 12 | return { parentWidget: this }; 13 | }; 14 | ``` 15 | 16 | And pass your widget to the children using context: 17 | 18 | ```tsx 19 | import { IDefaultWidgetProps, ParentWidgetContext } from '$:/plugins/linonetwo/tw-react/index.js'; 20 | 21 | ... 22 | 23 | return ( 24 | 25 | 26 | 27 | ); 28 | ``` 29 | 30 | Finally, in a child component, you can access the parent widget using the context, and put rendered dom node to react side using react ref: 31 | 32 | ```tsx 33 | import React, { createRef } from 'react'; 34 | import { useRenderTiddler } from '$:/plugins/linonetwo/tw-react/index.js'; 35 | 36 | export function SideBarContent(props: { title: string }): JSX.Element { 37 | const tabContentRef = createRef(); 38 | useRenderTiddler(props.title, tabContentRef); 39 | return
; 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /demo/tiddlers/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiddly-gittly/tw-react/61a9b2e41290736df33fec2340ff85104bfb526a/demo/tiddlers/favicon.ico -------------------------------------------------------------------------------- /demo/tiddlers/favicon.ico.meta: -------------------------------------------------------------------------------- 1 | created: 20200605110941797 2 | modified: 20200605110941797 3 | title: favicon.ico 4 | tmap.id: 57740f72-6155-4ce7-a7c1-280fec16ee55 5 | type: image/x-icon -------------------------------------------------------------------------------- /demo/tiddlywiki.info: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Basic client-server edition", 3 | "plugins": [], 4 | "themes": [ 5 | "tiddlywiki/vanilla" 6 | ], 7 | "languages": [ 8 | "zh-Hans" 9 | ], 10 | "build": { 11 | "index": [ 12 | "--rendertiddler", 13 | "$:/plugins/tiddlywiki/tiddlyweb/save/offline", 14 | "index.html", 15 | "text/plain" 16 | ], 17 | "externalimages": [ 18 | "--setfield", 19 | "[is[binary]] [type[application/msword]]", 20 | "_canonical_uri", 21 | "$:/core/templates/canonical-uri-external-image", 22 | "text/plain", 23 | "--setfield", 24 | "[is[binary]] [type[application/msword]]", 25 | "text", 26 | "", 27 | "text/plain", 28 | "--rendertiddler", 29 | "$:/core/save/all-external-js", 30 | "index.html", 31 | "text/plain" 32 | ], 33 | "externaljs": [ 34 | "--rendertiddler", 35 | "$:/core/templates/tiddlywiki5.js", 36 | "tiddlywiki5.js", 37 | "text/plain" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "lineWidth": 180, 3 | "typescript": { 4 | "quoteProps": "asNeeded", 5 | "quoteStyle": "preferSingle", 6 | "binaryExpression.operatorPosition": "sameLine" 7 | }, 8 | "json": {}, 9 | "markdown": {}, 10 | "includes": [ 11 | "**/*.{ts,tsx,js,jsx,cjs,mjs,json,md}", 12 | "./*.json", 13 | "./*.js", 14 | "packages/*/.*.js" 15 | ], 16 | "excludes": ["**/node_modules", "**/*-lock.json"], 17 | "plugins": [ 18 | "https://plugins.dprint.dev/typescript-0.84.4.wasm", 19 | "https://plugins.dprint.dev/json-0.17.2.wasm", 20 | "https://plugins.dprint.dev/markdown-0.15.2.wasm" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-argument */ 2 | /* eslint-disable @typescript-eslint/restrict-template-expressions */ 3 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */ 4 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 5 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 6 | const esbuild = require('esbuild'); 7 | const { readJsonSync } = require('fs-extra'); 8 | const browserslist = require('browserslist'); 9 | const { esbuildPluginBrowserslist, resolveToEsbuildTarget } = require('esbuild-plugin-browserslist'); 10 | const fs = require('fs-extra'); 11 | 12 | const pluginInfo = readJsonSync('src/plugin.info'); 13 | const [_, __, author, name] = pluginInfo.title.split('/'); 14 | const pluginTitle = `${author}/${name}`; 15 | const packageJSON = readJsonSync('package.json'); 16 | 17 | const distDir = path.join(__dirname, 'dist'); 18 | const devPluginPath = path.join(distDir, 'plugins', author, `${name}-dev`); 19 | const pluginPath = path.join(distDir, 'plugins', author, name); 20 | await Promise.all([fs.mkdirp(devPluginPath), fs.mkdirp(pluginPath)]); 21 | 22 | const result = await esbuild.build({ 23 | write: false, 24 | entryPoints: [...packageJSON.tsFiles.map((tsFileName) => `./src/${tsFileName}.ts`), ...packageJSON.tsxFiles.map((tsFileName) => `./src/${tsFileName}.tsx`)], 25 | bundle: true, 26 | // let tiddly-gittly/tw5-plugin-packer minify it, and let our fix of `module exports` works 27 | minify: false, 28 | outdir: `./dist/plugins/${author}/${name}`, 29 | sourcemap: process.env.CI ? false : 'inline', 30 | format: 'cjs', 31 | platform: 'browser', 32 | external: ['$:/*', 'react', 'react-dom', 'react-dom/client'], 33 | plugins: [ 34 | esbuildPluginBrowserslist(browserslist('defaults and supports es6-module'), { 35 | printUnknownTargets: false, 36 | }), 37 | ], 38 | }); 39 | 40 | for (let out of result.outputFiles) { 41 | let content = new TextDecoder().decode(out.contents); 42 | if (out.path.endsWith('plugins/linonetwo/tw-react/index.js')) { 43 | // skip 44 | } else { 45 | // fix esbuild `module.exports = ` causing library not recognizable 46 | content = content.replace('module.exports = ', ''); 47 | } 48 | await fs.writeFile(out.path, content, 'utf8'); 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "npm scripts to build plugins", 3 | "tsFiles": [ 4 | "widget", 5 | "index" 6 | ], 7 | "tsxFiles": [ 8 | "example" 9 | ], 10 | "license": "MIT", 11 | "name": "tw-react", 12 | "version": "0.6.4", 13 | "url": "https://github.com/tiddly-gittly/tw-react", 14 | "author": "Lin Onetwo", 15 | "types": "dist/lib/index.d.ts", 16 | "files": [ 17 | "dist/output/*.json", 18 | "dist/plugins/linonetwo/tw-react/**/*.js", 19 | "dist/lib/**/*.d.ts" 20 | ], 21 | "main": "dist/plugins/linonetwo/tw-react/index.js", 22 | "scripts": { 23 | "make": "pnpm run clean && pnpm run build", 24 | "dev-demo": "pnpm run dev && zx ./scripts/build-demo-html.mjs && cd ./dist/output && serve .", 25 | "dev": "pnpm run make && pnpm run download-react && pnpm run run-action && zx ./scripts/mv-dev.mjs", 26 | "clean": "rimraf ./dist", 27 | "build": "zx esbuild.config.mjs && zx scripts/after-build.mjs", 28 | "build:type": "tsc --emitDeclarationOnly --declaration && zx scripts/after-build-type.mjs", 29 | "run-action": "zx scripts/run-action.mjs", 30 | "prepublishOnly": "pnpm run make && pnpm run build:type", 31 | "download-react": "zx scripts/download-react.mjs", 32 | "installType": "typesync" 33 | }, 34 | "devDependencies": { 35 | "@modern-js/tsconfig": "^2.45.0", 36 | "@types/archiver": "6.0.2", 37 | "@types/eslint": "8.56.2", 38 | "@types/fs-extra": "11.0.4", 39 | "@types/node": "^20.11.0", 40 | "@types/react": "^19.0.1", 41 | "@types/react-dom": "^19.0.2", 42 | "archiver": "6.0.1", 43 | "browserslist": "4.22.2", 44 | "dprint": "^0.45.0", 45 | "esbuild": "0.19.11", 46 | "esbuild-plugin-browserslist": "0.10.0", 47 | "eslint-config-tidgi": "^1.1.5", 48 | "fs-extra": "11.2.0", 49 | "github-action-ts-run-api": "3.0.4", 50 | "react": "^19.0.0", 51 | "rimraf": "^5.0.5", 52 | "serve": "14.2.1", 53 | "tiddlywiki": "5.3.6", 54 | "tslib": "2.8.1", 55 | "tw5-plugin-packer": "0.0.10", 56 | "tw5-typed": "0.5.14", 57 | "typescript": "5.7.2", 58 | "typesync": "0.12.1", 59 | "zx": "7.2.3" 60 | }, 61 | "peerDependencies": { 62 | "react": "^19.0.0", 63 | "react-dom": "^19.0.0" 64 | }, 65 | "dependencies": { 66 | "@wessberg/connection-observer": "^1.0.5" 67 | }, 68 | "pnpm": { 69 | "patchedDependencies": { 70 | "@wessberg/connection-observer@1.0.5": "patches/@wessberg__connection-observer@1.0.5.patch" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /patches/@wessberg__connection-observer@1.0.5.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CHANGELOG.md b/CHANGELOG.md 2 | deleted file mode 100644 3 | index 8caf391e7549ee0e3b34c975ed1f1696c00daf68..0000000000000000000000000000000000000000 4 | diff --git a/dist/index.js b/dist/index.js 5 | index d0b60e342f33ff6fc42536e7b29b6d634e5f2894..dfa9948eb5a67950204cd00e002f1f276669e174 100644 6 | --- a/dist/index.js 7 | +++ b/dist/index.js 8 | @@ -1,4 +1,4 @@ 9 | -const ORIGINAL_ATTACH_SHADOW = Element.prototype.attachShadow; 10 | +const ORIGINAL_ATTACH_SHADOW = typeof Element !== 'undefined' ? Element.prototype.attachShadow : undefined; 11 | 12 | /** 13 | * Returns true if the environment is relying on the ShadyDOM polyfill. 14 | @@ -18,7 +18,7 @@ function supportsShadowRoots() { 15 | */ 16 | function patchElementPrototypeAttachShadow(callback) { 17 | // If Shadow DOM is not available, or if it is based on the ShadyDOM polyfill, there's nothing (or no need) to patch 18 | - if (ORIGINAL_ATTACH_SHADOW == null || isShady()) 19 | + if (ORIGINAL_ATTACH_SHADOW == null || isShady() || typeof Element === 'undefined') 20 | return; 21 | Element.prototype.attachShadow = function (shadowRootInitDict) { 22 | const shadowRoot = ORIGINAL_ATTACH_SHADOW.call(this, shadowRootInitDict); 23 | @@ -329,7 +329,7 @@ const observeRoot = (() => { 24 | })(); 25 | 26 | // Creates a pausable queue and pass document.documentElement as the initial queue payload 27 | -const rootObserverQueue = createPausableQueue(observeRoot, document.documentElement); 28 | +const rootObserverQueue = createPausableQueue(observeRoot, typeof document !== 'undefined' ? document.documentElement : undefined); 29 | 30 | /** 31 | * An Observer that tracks the DOM-insertion state of observed nodes across Shadow boundaries. 32 | @@ -347,6 +347,9 @@ class ConnectionObserver { 33 | else if (typeof callback !== "function") { 34 | throw new TypeError(`Failed to construct '${ConnectionObserver.name}': The callback provided as parameter 1 is not a function.`); 35 | } 36 | + if (typeof document === 'undefined') { 37 | + return; 38 | + } 39 | // Add this ConnectionObserver to the Set of ConnectionObservers 40 | initializeConnectionObserver(this, callback); 41 | } 42 | -------------------------------------------------------------------------------- /scripts/after-build-type.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/prevent-abbreviations */ 2 | /* eslint-disable @typescript-eslint/no-unsafe-argument */ 3 | /* eslint-disable @typescript-eslint/restrict-template-expressions */ 4 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */ 5 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 6 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 7 | import { fs, path } from 'zx'; 8 | 9 | const repoDir = path.join(__dirname, '..'); 10 | const distDir = path.join(__dirname, '..', 'dist'); 11 | const distLibDir = path.join(distDir, 'lib'); 12 | 13 | const pluginCopyOptions = { 14 | filter: (source, destination) => { 15 | // allow folder (otherwise it will stop at first step) and .d.ts files 16 | if (path.extname(source) === '' || source.endsWith('.d.ts')) { 17 | // Return true to copy the item 18 | return true; 19 | } 20 | }, 21 | }; 22 | await fs.copy(path.join(repoDir, 'src'), distLibDir, pluginCopyOptions); 23 | 24 | // manually add this, otherwise it will be changed by tsc to `/// ` which is totally wrong. 25 | const distributionIndexFilePath = path.join(distLibDir, './index.d.ts'); 26 | const stringToPrepend = '/// '; 27 | const data = await fs.readFile(distributionIndexFilePath, 'utf8'); 28 | const newData = `${stringToPrepend}\n${data}`; 29 | await fs.writeFile(distributionIndexFilePath, newData); 30 | -------------------------------------------------------------------------------- /scripts/after-build.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-argument */ 2 | /* eslint-disable @typescript-eslint/restrict-template-expressions */ 3 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */ 4 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 5 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 6 | import archiver from 'archiver'; 7 | import { fs } from 'zx'; 8 | 9 | const pluginInfo = fs.readJsonSync('src/plugin.info'); 10 | const [_, __, author, name] = pluginInfo.title.split('/'); 11 | const pluginTitle = `${author}/${name}`; 12 | 13 | const repoDir = path.join(__dirname, '..'); 14 | const distDir = path.join(__dirname, '..', 'dist'); 15 | const nodejsPluginOutDir = path.join(distDir, 'plugins', author, name); 16 | const devNodejsPluginOutDir = path.join(distDir, 'plugins', author, `${name}-dev`); 17 | 18 | // cross platform cp -r ${repoDir}/src/ ${nodejsPluginOutDir}/ 19 | const pluginCopyOptions = { 20 | filter: (src, dest) => { 21 | if (!(src.endsWith('.ts') || src.endsWith('.tsx'))) { 22 | // Return true to copy the item 23 | return true; 24 | } 25 | }, 26 | }; 27 | await fs.copy(path.join(repoDir, 'src'), nodejsPluginOutDir, pluginCopyOptions); 28 | // copy to dev channel 29 | await fs.copy(nodejsPluginOutDir, devNodejsPluginOutDir, pluginCopyOptions); 30 | 31 | // zip folder for nodejs wiki 32 | /** 33 | * @param {String} source 34 | * @param {String} out 35 | * @returns {Promise} 36 | */ 37 | function zipDirectory(source, out) { 38 | const archive = archiver('zip', { zlib: { level: 9 } }); 39 | const stream = fs.createWriteStream(out); 40 | 41 | return new Promise((resolve, reject) => { 42 | archive 43 | .directory(source, false) 44 | .on('error', (err) => reject(err)) 45 | .pipe(stream); 46 | 47 | stream.on('close', () => resolve()); 48 | archive.finalize(); 49 | }); 50 | } 51 | 52 | if (process.env.CI) { 53 | const outPath = path.join(__dirname, '..', 'plugins.zip'); 54 | await zipDirectory(path.join(__dirname, '..', 'dist'), outPath); 55 | await fs.move(outPath, path.join(distDir, 'out', 'plugins.zip')); 56 | } 57 | -------------------------------------------------------------------------------- /scripts/build-demo-html.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/prevent-abbreviations */ 2 | /* eslint-disable @typescript-eslint/no-unsafe-argument */ 3 | /* eslint-disable @typescript-eslint/restrict-template-expressions */ 4 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */ 5 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 6 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 7 | import { cd } from 'zx'; 8 | 9 | if (process.platform === 'win32') { 10 | $.prefix = ''; 11 | $.shell = 'pwsh.exe'; 12 | } 13 | /** 14 | * Put plugin into demo folder 15 | */ 16 | const pluginInfo = fs.readJsonSync('src/plugin.info'); 17 | const [_, __, author, name] = pluginInfo.title.split('/'); 18 | const pluginTitle = `${author}/${name}`; 19 | 20 | const repoDirectory = path.resolve(__dirname, '..'); 21 | const distDirectory = path.resolve(repoDirectory, 'dist'); 22 | const jsonPluginFileName = `$__plugins_${pluginTitle.replace('/', '_')}-dev.json`; 23 | // copy demo 24 | await fs.copy(path.join(repoDirectory, 'demo'), distDirectory); 25 | // copy json plugin 26 | await fs.copy(path.join(distDirectory, 'out', jsonPluginFileName), path.join(distDirectory, 'tiddlers', jsonPluginFileName)); 27 | 28 | /** 29 | * Make demo html file 30 | */ 31 | const htmlPath = `${distDirectory}/output/index.html`; 32 | 33 | /** 34 | * Same as `cross-env TIDDLYWIKI_PLUGIN_PATH='node_modules/tiddlywiki/plugins/published' TIDDLYWIKI_THEME_PATH='${wikiFolderName}/themes'` 35 | * 36 | * But we don't need this, because we put the JSON plugin into the dist folder, it will be loaded automatically 37 | */ 38 | // process.env.TIDDLYWIKI_PLUGIN_PATH = `${distDir}/plugins`; 39 | 40 | cd(distDirectory); 41 | await $`tiddlywiki . --build externalimages`; 42 | await $`tiddlywiki . --build externaljs`; 43 | // build dll.js and config tw to load it 44 | // original filename contains invalid char, will cause static server unable to load it 45 | const htmlContent = fs.readFileSync(htmlPath, 'utf8'); 46 | const htmlContentWithCorrectJsPath = htmlContent.replaceAll('%24%3A%2Fcore%2Ftemplates%2Ftiddlywiki5.js', 'tiddlywiki5.js'); 47 | await fs.writeFile(htmlPath, htmlContentWithCorrectJsPath); 48 | -------------------------------------------------------------------------------- /scripts/download-react.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | 4 | const pluginInfo = JSON.parse(await fs.readFile('src/plugin.info')); 5 | const [_, __, author, name] = pluginInfo.title.split('/'); 6 | const pluginTitle = `${author}/${name}`; 7 | 8 | // fix ReferenceError: process is not defined 9 | const fixProcessUndefined = (code) => code.replace('process.env.NODE_ENV', '"development"'); 10 | // prevent collision of name, use a different name. See `src/react-scheduler.js.meta`. 11 | const fixSchedulerNameCollision = (code) => code.replace('require("scheduler")', 'require("react-scheduler.js")'); 12 | 13 | const version = '19'; 14 | const reactPath = `node_modules/react/cjs`; 15 | const reactDomPath = `node_modules/react-dom/cjs`; 16 | const schedulerPath = `node_modules/.pnpm/scheduler@0.25.0/node_modules/scheduler/cjs`; 17 | 18 | const [ 19 | reactDevelopment, 20 | react, 21 | reactDomDevelopment, 22 | reactDom, 23 | reactDomClientDevelopment, 24 | reactClientDom, 25 | reactJsxRuntimeDevelopment, 26 | reactJsxRuntime, 27 | schedulerDevelopment, 28 | scheduler, 29 | ] = await Promise.all([ 30 | fs.readFile(path.join(reactPath, `react.development.js`), 'utf8').then(fixProcessUndefined), 31 | fs.readFile(path.join(reactPath, `react.production.js`), 'utf8'), 32 | fs.readFile(path.join(reactDomPath, `react-dom.development.js`), 'utf8').then(fixProcessUndefined), 33 | fs.readFile(path.join(reactDomPath, `react-dom.production.js`), 'utf8'), 34 | fs.readFile(path.join(reactDomPath, `react-dom-client.development.js`), 'utf8').then(fixProcessUndefined).then(fixSchedulerNameCollision), 35 | fs.readFile(path.join(reactDomPath, `react-dom-client.production.js`), 'utf8').then(fixSchedulerNameCollision), 36 | fs.readFile(path.join(reactPath, `react-jsx-runtime.development.js`), 'utf8').then(fixProcessUndefined), 37 | fs.readFile(path.join(reactPath, `react-jsx-runtime.production.js`), 'utf8'), 38 | fs.readFile(path.join(schedulerPath, `scheduler.development.js`), 'utf8').then(fixProcessUndefined), 39 | fs.readFile(path.join(schedulerPath, `scheduler.production.js`), 'utf8'), 40 | ]); 41 | 42 | const distDir = path.join(__dirname, '..', 'dist'); 43 | const developmentPluginPath = path.join(distDir, 'plugins', author, `${name}-dev`); 44 | const pluginPath = path.join(distDir, 'plugins', author, name); 45 | await Promise.all([ 46 | fs.writeFile(path.join(developmentPluginPath, 'react.js'), reactDevelopment), 47 | fs.writeFile(path.join(pluginPath, 'react.js'), react), 48 | fs.writeFile(path.join(developmentPluginPath, 'react-dom.js'), reactDomDevelopment), 49 | fs.writeFile(path.join(pluginPath, 'react-dom.js'), reactDom), 50 | fs.writeFile(path.join(developmentPluginPath, 'react-dom-client.js'), reactDomClientDevelopment), 51 | fs.writeFile(path.join(pluginPath, 'react-dom-client.js'), reactClientDom), 52 | fs.writeFile(path.join(developmentPluginPath, 'react-jsx-runtime.js'), reactJsxRuntimeDevelopment), 53 | fs.writeFile(path.join(pluginPath, 'react-jsx-runtime.js'), reactJsxRuntime), 54 | fs.writeFile(path.join(developmentPluginPath, 'react-scheduler.js'), schedulerDevelopment), 55 | fs.writeFile(path.join(pluginPath, 'react-scheduler.js'), scheduler), 56 | ]); 57 | -------------------------------------------------------------------------------- /scripts/mv-dev.mjs: -------------------------------------------------------------------------------- 1 | const pluginInfo = fs.readJsonSync('src/plugin.info'); 2 | const [_, __, author, name] = pluginInfo.title.split('/'); 3 | const pluginTitle = `${author}_${name}`; 4 | 5 | const distDir = path.join(__dirname, '..', 'dist'); 6 | await fs.rename(path.join(distDir, 'out-dev', `$__plugins_${pluginTitle}.json`), path.join(distDir, 'out', `$__plugins_${pluginTitle}-dev.json`)); 7 | await fs.remove(path.join(distDir, 'out-dev')); 8 | -------------------------------------------------------------------------------- /scripts/run-action.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-argument */ 2 | /* eslint-disable @typescript-eslint/restrict-template-expressions */ 3 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */ 4 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 5 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 6 | /** 7 | * Run github action that packs JSON plugin 8 | */ 9 | import { RunOptions, RunTarget } from 'github-action-ts-run-api'; 10 | 11 | const pluginInfo = fs.readJsonSync('src/plugin.info'); 12 | const [_, __, author, name] = pluginInfo.title.split('/'); 13 | const pluginTitle = `${author}/${name}`; 14 | 15 | // RunTarget.preJsScript() and RunTarget.postJsScript() are also available 16 | const target = RunTarget.mainJs(path.resolve('node_modules/tw5-plugin-packer/action.yml')); 17 | const options = RunOptions.create() 18 | .setInputs({ 19 | minify: process.env.CI === undefined ? false : true, 20 | source: [`dist/plugins/${pluginTitle}`], 21 | output: 'dist/out', 22 | 'uglifyjs-options': '{ "warnings": false, "ie8": false, "safari10": false }', 23 | 'cleancss-options': '{ "compatibility": "*", "level": 2 }', 24 | }) 25 | // Internally, runner will fake a json file to be picked by @actions/github 26 | .setGithubContext({ payload: { push: { tags: ['v0.1.1'] } } }) 27 | // By default, RUNNER_TEMP is faked for a run and then deleted. Keep it 28 | .setFakeFsOptions({ rmFakedTempDirAfterRun: false }); 29 | 30 | await target.run(options); 31 | await target.run(options.setInputs({ source: [`dist/plugins/${pluginTitle}-dev`], output: 'dist/out-dev' })); 32 | -------------------------------------------------------------------------------- /src/docs/FAQ.tid: -------------------------------------------------------------------------------- 1 | title: $:/plugins/linonetwo/tw-react/docs/FAQ 2 | creator: LinOnetwo 3 | 4 | !! FAQ 5 | 6 | !!! _jsxRuntime 7 | 8 | `slate-write/node_modules/react-dnd/dist/cjs/core/DndProvider.js` has `var _jsxRuntime = require("react/jsx-runtime");` 9 | 10 | But `slate-write/node_modules/react-dnd/dist/esm/core/DndProvider.mjs` has `import { jsx as _jsx } from "react/jsx-runtime.js";` 11 | 12 | Note the difference between `react/jsx-runtime` and `react/jsx-runtime.js`! 13 | 14 | Currently tw-react ship with `react/jsx-runtime.js`, so if your plugin uses cjs version of react-dnd, you will have error: 15 | 16 | ``` 17 | Error executing boot module $:/plugins/linonetwo/slate-write/components/index.js: "Cannot find module named 'react/jsx-runtime' required by module '$:/plugins/linonetwo/slate-write/components/index.js', resolved to react/jsx-runtime" 18 | 19 | undefined 20 | ``` 21 | 22 | The solution is to ask me provide a version of tw-react that has `react/jsx-runtime`, or you can use esm version of `react-dnd`. 23 | -------------------------------------------------------------------------------- /src/docs/example.tid: -------------------------------------------------------------------------------- 1 | title: $:/plugins/linonetwo/tw-react/docs/example 2 | creator: LinOnetwo 3 | type: text/vnd.tiddlywiki 4 | 5 | !! Example 6 | 7 | ```tid 8 | <$likeButtonExampleWidget stateTiddler="$:/state/tw-react/readme/like-button" /> 9 | ``` 10 | 11 | Button1: 12 | 13 | <$likeButtonExampleWidget stateTiddler="$:/state/tw-react/readme/like-button" /> 14 | 15 | Button2: 16 | 17 | <$likeButtonExampleWidget stateTiddler="$:/state/tw-react/readme/like-button" /> 18 | 19 | State: 20 | 21 | {{$:/state/tw-react/readme/like-button}} 22 | 23 | It keeps its state via a state tiddler, and title of state tiddler is pass-in using `getProps = () => ({ stateTiddler: this.getAttribute('stateTiddler') });`, so `stateTiddler` attribute is passed from widget parameter to the React props. 24 | 25 | Two buttons have shared state [[$:/state/tw-react/readme/like-button]], but not updated when state changed (although you can implement this in the `refresh` method by yourself in your own component), so when you click one, and close this tiddler then reopen it, you will see two buttons have same ''clicked'' state. 26 | 27 | `likeButtonExampleWidget` is a class component, it calls a functional component `ExampleFunction` that will use react hook to update self +1 every second. 28 | 29 | See [[example.ts|https://github.com/tiddly-gittly/tw-react/blob/master/src/example.tsx]] and [[exampleFunction.tsx|https://github.com/tiddly-gittly/tw-react/blob/master/src/exampleFunction.tsx]] for example. 30 | -------------------------------------------------------------------------------- /src/docs/install.tid: -------------------------------------------------------------------------------- 1 | title: $:/plugins/linonetwo/tw-react/docs/install 2 | creator: LinOnetwo 3 | 4 | !! Install 5 | 6 | As a user, just install from [[CPL|https://tw-cpl.netlify.app/#linonetwo%2Ftw-react:Index%20linonetwo%2Ftw-react]]. 7 | 8 | As a developer, you can continue reading. 9 | 10 | !!! Require and externalize 'react', 'react-dom' 11 | 12 | Please make sure to externalize them, for example in the [[RollUp|https://rollupjs.org/]]: 13 | 14 | ```json 15 | { 16 | external: ['react', 'react-dom'] 17 | } 18 | ``` 19 | 20 | Otherwise you will get [[You might have more than one copy of React in the same app|https://reactjs.org/docs/error-decoder.html/?invariant=321]] error. 21 | 22 | And use the subclass of Widget provided by this plugin: 23 | 24 | ``` 25 | const Widget = require('$:/plugins/linonetwo/tw-react/widget.js').widget 26 | ``` 27 | 28 | !!! Using Typescript 29 | 30 | # use [[TW5-Typed|https://github.com/tiddly-gittly/TW5-Typed]] for basic tw types, see its readme for install instruction 31 | # install `tw-react` package via `npm i tw-react`, and import the type by `import type { ReactWidget } from 'tw-react';` 32 | 33 | Use the type: 34 | 35 | ```js 36 | import { useRenderTiddler } from '$:/plugins/linonetwo/tw-react/index.js'; 37 | import type { ReactWidget } from 'tw-react'; 38 | 39 | const Widget = require('$:/plugins/linonetwo/tw-react/widget.js').widget as typeof ReactWidget; 40 | 41 | class YourWidget extends Widget { 42 | reactComponent = SomeReactComponent; 43 | getProps = () => { 44 | return { 45 | stateTiddler: this.getAttribute('stateTiddler'), 46 | // ... other props for your react component 47 | }; 48 | }; 49 | } 50 | ``` 51 | 52 | !!! Props 53 | 54 | Anything returned from `getProps` method will pass to react component, passing `stateTiddler` is a good idea, but is not mandatory. 55 | 56 | For example, this is what I returned in my `linonetwo/smart-form` plugin: 57 | 58 | ```js 59 | getProps = () => { 60 | const currentTiddler = this.getAttribute('tiddler', this.getVariable('currentTiddler')); 61 | // with lots of filter running and data transforms... 62 | return { 63 | schema, 64 | formData, 65 | children: null, 66 | onChange, 67 | }; 68 | }; 69 | ``` 70 | 71 | !!! React Hooks 72 | 73 | We provided some hooks for reactive data management with tw data source. You can import them from the `$:/plugins/linonetwo/tw-react/index.js` (Instead of from `tw-react` package, because they already included in the plugin). 74 | 75 | ```ts 76 | import { useFilter } from '$:/plugins/linonetwo/tw-react/index.js' 77 | ``` 78 | 79 | You can't import them from plugin's file, because they are optional, so only included in the npm package, not in the plugin json bundle. 80 | 81 | !!!! useFilter 82 | 83 | Get list of titles from a filter. 84 | 85 | ```ts 86 | const titles = useFilter('[all[]tag[Index]]'); 87 | ``` 88 | 89 | The second parameter is an optional dependencies list, you can use it to trigger re-calculate. Otherwise it only rerun filter when filter text changes. 90 | 91 | ```ts 92 | const [toggle, setToggle] = useState(false); 93 | const titles = useFilter('[all[]tag[Index]]', [toggle]); 94 | 95 | ... 96 | 97 | 98 | ``` 99 | -------------------------------------------------------------------------------- /src/docs/reactAPIs.tid: -------------------------------------------------------------------------------- 1 | title: $:/plugins/linonetwo/tw-react/docs/reactAPIs 2 | creator: LinOnetwo 3 | 4 | !! React APIs 5 | 6 | TBD, read the source now. -------------------------------------------------------------------------------- /src/example.js.meta: -------------------------------------------------------------------------------- 1 | creator: LinOnetwo 2 | title: $:/plugins/linonetwo/tw-react/example.js 3 | type: application/javascript 4 | module-type: widget 5 | -------------------------------------------------------------------------------- /src/example.tsx: -------------------------------------------------------------------------------- 1 | import './widget-type'; 2 | 3 | /** 4 | * // use this commonjs syntax if you are NOT using https://github.com/tiddly-gittly/Modern.TiddlyDev 5 | * const Widget = (require('$:/plugins/linonetwo/tw-react/widget.js') as { widget: IReactWidgetConstructor }).widget; 6 | */ 7 | import { widget as Widget } from '$:/plugins/linonetwo/tw-react/widget.js'; 8 | import type * as ReactType from 'react'; 9 | import type * as ReactDomType from 'react-dom'; 10 | import type * as ReactDomClientType from 'react-dom/client'; 11 | import { ExampleFunction } from './exampleFunction'; 12 | 13 | // you should set these to external in your build tool like `external: ['$:/*', 'react', 'react-dom'],` 14 | const ReactDom = require('react-dom') as typeof ReactDomType & typeof ReactDomClientType; 15 | const React = require('react') as typeof ReactType; 16 | 17 | interface IProps { 18 | /** 19 | * Tiddler to contain the serialized JSON component state 20 | */ 21 | stateTiddler?: string; 22 | } 23 | interface IState { 24 | liked: boolean; 25 | } 26 | class LikeButton extends React.Component { 27 | constructor(props: IProps) { 28 | super(props); 29 | const defaultState: IState = { liked: false }; 30 | // deserialize state from tiddlywiki 31 | try { 32 | this.state = JSON.parse($tw.wiki.getTiddlerText(this.props.stateTiddler ?? '', '{}')) as IState ?? defaultState; 33 | } catch { 34 | this.state = defaultState; 35 | } 36 | } 37 | 38 | setState(nextState: IState) { 39 | super.setState(nextState); 40 | // serialize state to tiddlywiki 41 | if (this.props.stateTiddler === undefined) return; 42 | $tw.wiki.setText(this.props.stateTiddler, 'text', undefined, JSON.stringify(nextState)); 43 | } 44 | 45 | render() { 46 | if (this.state.liked) { 47 | return 'You liked this.'; 48 | } 49 | 50 | return ( 51 | 58 | ); 59 | } 60 | } 61 | 62 | class LikeButtonWidget extends Widget { 63 | reactComponent = LikeButton; 64 | getProps = () => ({ stateTiddler: this.getAttribute('stateTiddler') }); 65 | } 66 | declare let exports: { 67 | likeButtonExampleWidget: typeof Widget; 68 | }; 69 | exports.likeButtonExampleWidget = LikeButtonWidget; 70 | -------------------------------------------------------------------------------- /src/exampleFunction.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function ExampleFunction() { 4 | const [counter, counterSetter] = useState(0); 5 | useEffect(() => { 6 | // not using counter variable to prevent hook reinvoke every time counter changed 7 | let localCounter = 0; 8 | const handle = setInterval(() => { 9 | counterSetter(++localCounter); 10 | }, 1000); 11 | return () => { 12 | clearInterval(handle); 13 | }; 14 | }, []); 15 | return {counter}; 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { Widget } from 'tiddlywiki'; 3 | 4 | /** 5 | * A widget context for rendering child widgets 6 | * 7 | * > it will read the current context value from the closest matching Provider above it in the tree 8 | * > https://reactjs.org/docs/context.html#reactcreatecontext 9 | * so even multiple context is created in different react widget, the value may not collide, I think... 10 | */ 11 | export const ParentWidgetContext = createContext(undefined); 12 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context'; 2 | export * from './useFilter'; 3 | export * from './useRenderTiddler'; 4 | export * from './useWidget'; 5 | -------------------------------------------------------------------------------- /src/hooks/useFilter.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo, useState } from 'react'; 2 | import { Widget } from 'tiddlywiki'; 3 | 4 | /** Filter result is basically a list of title. You can get tiddler detail using tw API like `$tw.wiki.getTiddler` */ 5 | export function useFilter(twFilter: string, widget: Widget = $tw.rootWidget, dependencies: unknown[] = []): string[] { 6 | const [filterResult, setFilterResult] = useState([]); 7 | const compiledFilter = useMemo(() => $tw.wiki.compileFilter(twFilter), [twFilter]); 8 | useEffect(() => { 9 | setFilterResult(compiledFilter(undefined, widget)); 10 | }, [compiledFilter, widget, ...dependencies]); 11 | return filterResult; 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/useRenderTiddler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/no-null */ 2 | import { RefObject, useContext, useEffect } from 'react'; 3 | import { ParentWidgetContext } from './context'; 4 | 5 | interface IOptions { 6 | /** skip rendering for this time useEffect executes */ 7 | skip?: boolean; 8 | } 9 | export function useRenderTiddler(tiddlerTitle: string, containerReference: RefObject, options?: IOptions) { 10 | const parentWidget = useContext(ParentWidgetContext); 11 | useEffect(() => { 12 | const domNode = containerReference.current; 13 | if (domNode === null) { 14 | return; 15 | } 16 | if (parentWidget === undefined) { 17 | throw new Error( 18 | 'Your plugin have a bug: `parentWidget` is undefined, you should use ``, see tw-react for document.', 19 | ); 20 | } 21 | if (options?.skip === true) { 22 | return; 23 | } 24 | const id = String(Math.random()); 25 | const transcludeWidgetNode = $tw.wiki.makeTranscludeWidget(tiddlerTitle, { 26 | document, 27 | parentWidget, 28 | recursionMarker: 'yes', 29 | mode: 'block', 30 | importPageMacros: true, 31 | variables: { 'use-widget-id': id }, 32 | }); 33 | const tiddlerContainer = document.createElement('div'); 34 | domNode.append(tiddlerContainer); 35 | transcludeWidgetNode.render(tiddlerContainer, null); 36 | parentWidget.children.push(transcludeWidgetNode); 37 | return () => { 38 | parentWidget.children = parentWidget.children.filter((child) => child.getVariable('use-widget-id') !== id); 39 | if (domNode === null) { 40 | return; 41 | } 42 | domNode.textContent = ''; 43 | }; 44 | }, [tiddlerTitle, containerReference, parentWidget, options?.skip]); 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/useWidget.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable security-node/detect-insecure-randomness */ 2 | import { RefObject, useContext, useEffect } from 'react'; 3 | import { IParseTreeNode } from 'tiddlywiki'; 4 | import { ParentWidgetContext } from './context'; 5 | 6 | interface IOptions { 7 | /** skip rendering for this time useEffect executes */ 8 | skip?: boolean; 9 | } 10 | export function useWidget(parseTreeNode: IParseTreeNode, containerReference: RefObject, options?: IOptions) { 11 | const parentWidget = useContext(ParentWidgetContext); 12 | useEffect(() => { 13 | const domNode = containerReference.current; 14 | if (domNode === null) { 15 | return; 16 | } 17 | if (parentWidget === undefined) { 18 | throw new Error( 19 | 'Your plugin have a bug: `parentWidget` is undefined, you should use ``, see tw-react for document.', 20 | ); 21 | } 22 | if (options?.skip === true) { 23 | return; 24 | } 25 | const id = String(Math.random()); 26 | // FIXME: this is wrong, see zx-script's latest version 27 | const newWidgetNode = parentWidget.makeChildWidget(parseTreeNode, { variables: { 'use-widget-id': id } }); 28 | 29 | // eslint-disable-next-line unicorn/no-null 30 | newWidgetNode.render(domNode, null); 31 | parentWidget.children.push(newWidgetNode); 32 | return () => { 33 | parentWidget.children = parentWidget.children.filter((child) => child.getVariable('use-widget-id') !== id); 34 | if (domNode === null) { 35 | return; 36 | } 37 | domNode.textContent = ''; 38 | }; 39 | }, [parseTreeNode, containerReference, parentWidget, options?.skip]); 40 | } 41 | -------------------------------------------------------------------------------- /src/index.js.meta: -------------------------------------------------------------------------------- 1 | creator: LinOnetwo 2 | title: $:/plugins/linonetwo/tw-react/index.js 3 | type: application/javascript 4 | module-type: library 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 2 | /// 3 | /** 4 | * This file exports things that can be imported from $:/plugins/linonetwo/tw-react/index.js 5 | */ 6 | import { ParentWidgetContext, useFilter, useRenderTiddler, useWidget } from './hooks'; 7 | 8 | declare let exports: { 9 | ParentWidgetContext: typeof ParentWidgetContext; 10 | useFilter: typeof useFilter; 11 | useRenderTiddler: typeof useRenderTiddler; 12 | useWidget: typeof useWidget; 13 | }; 14 | exports.ParentWidgetContext = ParentWidgetContext; 15 | exports.useFilter = useFilter; 16 | exports.useRenderTiddler = useRenderTiddler; 17 | exports.useWidget = useWidget; 18 | -------------------------------------------------------------------------------- /src/plugin.info: -------------------------------------------------------------------------------- 1 | { 2 | "title": "$:/plugins/linonetwo/tw-react", 3 | "description": "Allow using ReactJS Components in TW widget.", 4 | "author": "LinOnetwo", 5 | "core-version": ">=5.3.0", 6 | "plugin-type": "plugin", 7 | "stability": "STABILITY_2_STABLE", 8 | "version": "1.0.0", 9 | "list": "readme tree" 10 | } -------------------------------------------------------------------------------- /src/react-dom-client.js.meta: -------------------------------------------------------------------------------- 1 | title: react-dom/client 2 | type: application/javascript 3 | module-type: library 4 | -------------------------------------------------------------------------------- /src/react-dom.js.meta: -------------------------------------------------------------------------------- 1 | title: react-dom 2 | type: application/javascript 3 | module-type: library 4 | -------------------------------------------------------------------------------- /src/react-jsx-runtime.js.meta: -------------------------------------------------------------------------------- 1 | title: react/jsx-runtime.js 2 | type: application/javascript 3 | module-type: library 4 | -------------------------------------------------------------------------------- /src/react-scheduler.js.meta: -------------------------------------------------------------------------------- 1 | title: react-scheduler.js 2 | type: application/javascript 3 | module-type: library 4 | -------------------------------------------------------------------------------- /src/react.js.meta: -------------------------------------------------------------------------------- 1 | title: react 2 | type: application/javascript 3 | module-type: library 4 | -------------------------------------------------------------------------------- /src/readme.tid: -------------------------------------------------------------------------------- 1 | title: $:/plugins/linonetwo/tw-react/readme 2 | creator: LinOnetwo 3 | type: text/vnd.tiddlywiki 4 | 5 | !! What is this 6 | 7 | This is a dependency of slate-write WYSIWYG editor and flowtiwi-sidebar and many other dynamic widgets. This plugin enable powerful data operation and UI operation of those plugins, thus become a prerequisite of those plugins. 8 | 9 | !! Example 10 | 11 | {{$:/plugins/linonetwo/tw-react/docs/example}} 12 | 13 | !! Usage 14 | 15 | <> 16 | -------------------------------------------------------------------------------- /src/tree.tid: -------------------------------------------------------------------------------- 1 | title: $:/plugins/linonetwo/tw-react/tree 2 | type: text/vnd.tiddlywiki 3 | 4 | <> -------------------------------------------------------------------------------- /src/type.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 2 | /// 3 | /* eslint-disable @typescript-eslint/no-explicit-any */ 4 | declare module '$:/plugins/linonetwo/tw-react/widget.js' { 5 | import type { IReactWidget, ITWReactProps, ITWReactPropsDefault } from 'tw-react/widget-type'; 6 | import type { Widget } from 'tiddlywiki'; 7 | import type * as ReactType from 'react'; 8 | // import type * as ReactDomType from 'react-dom'; 9 | import type * as ReactDomClientType from 'react-dom/client'; 10 | 11 | export abstract class widget< 12 | IProps extends ITWReactProps = ITWReactPropsDefault, 13 | > extends Widget implements IReactWidget { 14 | root?: ReturnType | undefined; 15 | containerElement?: HTMLDivElement | undefined; 16 | destroy(): void; 17 | getProps?: () => IProps; 18 | /** 19 | * User of tw-react need to assign his react component to this property. 20 | */ 21 | reactComponent: 22 | | ReactType.ClassType, ReactType.ClassicComponentClass> 23 | | ReactType.FunctionComponent 24 | | ReactType.ComponentClass 25 | | null; 26 | } 27 | } 28 | 29 | declare module '$:/plugins/linonetwo/tw-react/index.js' { 30 | export * from 'tw-react/dist/lib/hooks'; 31 | export * from 'tw-react/dist/lib/widget-type'; 32 | } 33 | -------------------------------------------------------------------------------- /src/widget-type.ts: -------------------------------------------------------------------------------- 1 | import type { widget as Widget } from '$:/core/modules/widgets/widget.js'; 2 | import type * as ReactType from 'react'; 3 | import type * as ReactDomType from 'react-dom'; 4 | import type * as ReactDomClientType from 'react-dom/client'; 5 | export type IReactDomType = typeof ReactDomType & typeof ReactDomClientType; 6 | 7 | export interface IReactWidget< 8 | IProps extends ITWReactProps = ITWReactPropsDefault, 9 | > extends Widget { 10 | containerElement: HTMLDivElement | undefined; 11 | destroy(): void; 12 | 13 | getProps: () => IProps; 14 | 15 | /** 16 | * User of tw-react need to assign his react component to this property. 17 | */ 18 | reactComponent: 19 | | ReactType.ClassType, ReactType.ClassicComponentClass> 20 | | ReactType.FunctionComponent 21 | | ReactType.ComponentClass 22 | | null; 23 | root: ReturnType | undefined; 24 | } 25 | export interface IReactWidgetConstructor extends IReactWidget { 26 | new(parseTreeNode: any, options: any): IReactWidget; 27 | } 28 | 29 | export interface IDefaultWidgetProps { 30 | parentWidget?: Widget; 31 | } 32 | export type ITWReactUserProps = Record; 33 | export type ITWReactProps = ITWReactUserProps & IDefaultWidgetProps; 34 | export type ITWReactPropsDefault = IDefaultWidgetProps & Record & any; 35 | -------------------------------------------------------------------------------- /src/widget.js.meta: -------------------------------------------------------------------------------- 1 | creator: LinOnetwo 2 | title: $:/plugins/linonetwo/tw-react/widget.js 3 | type: application/javascript 4 | module-type: library 5 | -------------------------------------------------------------------------------- /src/widget.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/no-null */ 2 | import { ConnectionObserver } from '@wessberg/connection-observer'; 3 | import type * as ReactType from 'react'; 4 | import type * as ReactDomType from 'react-dom'; 5 | import type * as ReactDomClientType from 'react-dom/client'; 6 | import type { IChangedTiddlers, IParseTreeNode, IWidgetInitialiseOptions } from 'tiddlywiki'; 7 | import type { IReactWidget, ITWReactProps, ITWReactPropsDefault } from './widget-type'; 8 | 9 | import { widget as Widget } from '$:/core/modules/widgets/widget.js'; 10 | const ReactDom = require('react-dom') as typeof ReactDomType & typeof ReactDomClientType; 11 | const ReactDomClient = require('react-dom/client') as typeof ReactDomType & typeof ReactDomClientType; 12 | const React = require('react') as typeof ReactType; 13 | if (typeof window !== 'undefined') { 14 | window.React = React; 15 | } else if (typeof global !== 'undefined') { 16 | global.React = React; 17 | } 18 | 19 | class ReactWidgetImpl< 20 | IProps extends ITWReactProps = ITWReactPropsDefault, 21 | > extends Widget implements IReactWidget { 22 | root: ReturnType | undefined; 23 | containerElement: HTMLDivElement | undefined; 24 | connectionObserver: ConnectionObserver | undefined; 25 | 26 | constructor(parseTreeNode: IParseTreeNode, options?: IWidgetInitialiseOptions | undefined) { 27 | super(parseTreeNode, options); 28 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 29 | if (!$tw.browser) { 30 | return; 31 | } 32 | this.connectionObserver = new ConnectionObserver(entries => { 33 | for (const { connected } of entries) { 34 | if (!connected) { 35 | this.destroy(); 36 | this.connectionObserver?.disconnect?.(); 37 | } 38 | } 39 | }); 40 | } 41 | 42 | refresh(changedTiddlers: IChangedTiddlers) { 43 | // we don't need to refresh mount point of react-dom 44 | // but you can override this method to do some custom refresh logic 45 | return false; 46 | } 47 | 48 | /** 49 | * User of tw-react need to assign his react component to this property. 50 | */ 51 | reactComponent: 52 | | ReactType.ClassType, ReactType.ClassicComponentClass> 53 | | ReactType.FunctionComponent 54 | | ReactType.ComponentClass 55 | | null = null; 56 | 57 | getProps: () => IProps = () => ({ parentWidget: this } as unknown as IProps); 58 | 59 | render(parent: Element, nextSibling: Element | null) { 60 | this.parentDomNode = parent; 61 | this.computeAttributes(); 62 | this.execute(); 63 | if (this.reactComponent === undefined || this.reactComponent === null) { 64 | return; 65 | } 66 | const currentProps = this.getProps() ?? {}; 67 | if (currentProps.parentWidget === undefined || currentProps.parentWidget === null) { 68 | currentProps.parentWidget = this; 69 | } 70 | /** don't need this because we handle the initial render of child widget in the `useWidget` hook */ 71 | // this.renderChildren(parent,nextSibling); 72 | 73 | if (this.root === undefined || this.containerElement === undefined) { 74 | this.containerElement = document.createElement('div'); 75 | this.root = ReactDomClient.createRoot(this.containerElement); 76 | let domToObserve = this.containerElement; 77 | // sometimes parent node isTiddlyWikiFakeDom, we can't use it. 78 | if (this.parentDomNode instanceof Node) { 79 | domToObserve = this.parentDomNode as HTMLDivElement; 80 | } 81 | this.connectionObserver?.observe?.(domToObserve); 82 | } 83 | this.domNodes.push(this.containerElement); 84 | try { 85 | parent.insertBefore(this.containerElement, nextSibling); 86 | } catch (error) { 87 | console.warn(`Error while inserting dom node in react widget, this might be cause by use transclude widget for the wikitext contains widget.`, error); 88 | } 89 | const reactElement = React.createElement(this.reactComponent, currentProps); 90 | this.root.render(reactElement); 91 | } 92 | 93 | refreshSelf() { 94 | if (this.reactComponent === undefined || this.reactComponent === null) { 95 | return; 96 | } 97 | if (this.root === undefined && this.parentDomNode !== undefined) { 98 | const nextSibling = this.findNextSiblingDomNode(); 99 | this.render(this.parentDomNode, nextSibling); 100 | return; 101 | } 102 | this.computeAttributes(); 103 | this.execute(); 104 | const currentProps = this.getProps() ?? {}; 105 | if (currentProps.parentWidget === undefined || currentProps.parentWidget === null) { 106 | currentProps.parentWidget = this; 107 | } 108 | const reactElement = React.createElement(this.reactComponent, currentProps); 109 | this.root?.render?.(reactElement); 110 | } 111 | 112 | destroy() { 113 | this.root?.unmount?.(); 114 | } 115 | } 116 | 117 | declare let exports: { 118 | widget: typeof Widget; 119 | }; 120 | exports.widget = ReactWidgetImpl; 121 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": [ 7 | "src/**/*.ts", 8 | "src/**/*.js", 9 | "src/**/*.tsx", 10 | "src/**/*.jsx", 11 | "public/**/*.ts", 12 | "public/**/*.js", 13 | "features/**/*.ts", 14 | "scripts/**/*.ts", 15 | "scripts/**/*.mjs", 16 | "src/**/*.d.ts", 17 | "public/**/*.d.ts", 18 | "./*.json", 19 | "./*.js", 20 | "./*.*.js" 21 | ], 22 | "exclude": ["wiki"] 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@modern-js/tsconfig/base.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "baseUrl": "./", 6 | "outDir": "dist/lib", 7 | "typeRoots" : ["node_modules/@types", "node_modules/tw5-typed"], 8 | "jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 9 | "lib": ["DOM", "ESNext"] 10 | }, 11 | "include": ["src"], 12 | "exclude": ["./**/*.js"] 13 | } 14 | --------------------------------------------------------------------------------