├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── design ├── process.epgz └── process.png └── scripts ├── add-plugin.js └── safe-rename.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.json 3 | *.html 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 David Alfonso 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Redirect to avoid "Command not found" error due to make optimizations. 3 | NODEJS := $(shell command -v node 2> /dev/null) 4 | NPM := $(shell command -v npm 2> /dev/null) 5 | PANDOC := $(shell command -v pandoc 2> /dev/null) 6 | 7 | # A temporary wiki needs to be create in order to import tiddlers and 8 | # export them to html (as if they were to be rendered on the browser). 9 | WIKI_NAME := tmp_wiki 10 | TIDDLYWIKI_INFO := $(WIKI_NAME)/tiddlywiki.info 11 | 12 | # Intermediate directory where html and metadata files are generated 13 | # using TiddlyWiki. 14 | TW_OUTPUT_DIR := $(WIKI_NAME)/output 15 | 16 | # Output directory where final markdown files will be stored. 17 | MARKDOWN_DIR := markdown_tiddlers 18 | 19 | TIDDLYWIKI_JS := node_modules/tiddlywiki/tiddlywiki.js 20 | ADD_PLUGIN_JS := scripts/add-plugin.js 21 | SAFE_RENAME_JS := scripts/safe-rename.js 22 | ORIGINAL_TIDDLYWIKI := wiki.html 23 | 24 | # This will only return something after make export-html has been run. 25 | HTML_TIDDLERS := $(wildcard $(TW_OUTPUT_DIR)/*.html) 26 | MARKDOWN_TIDDLERS := $(patsubst $(TW_OUTPUT_DIR)/%.html, \ 27 | $(MARKDOWN_DIR)/%.md, \ 28 | $(HTML_TIDDLERS)) 29 | 30 | .PHONY: export-html 31 | export-html : deps pre 32 | @echo "Exporting all tiddlers from $(ORIGINAL_TIDDLYWIKI) to html" 33 | $(NODEJS) $(TIDDLYWIKI_JS) $(WIKI_NAME) --load $(ORIGINAL_TIDDLYWIKI) \ 34 | --render [!is[system]] [encodeuricomponent[]addsuffix[.html]] \ 35 | --render [!is[system]] [encodeuricomponent[]addsuffix[.meta]] \ 36 | text/plain $$:/core/templates/tiddler-metadata 37 | @echo "Renaming all .html and .meta files to safe characters..." 38 | $(NODEJS) $(SAFE_RENAME_JS) $(TW_OUTPUT_DIR) 39 | 40 | .PHONY: deps 41 | deps : 42 | ifndef NODEJS 43 | @echo "Node is not available. Please install nodejs." 44 | exit 1 45 | endif 46 | ifndef NPM 47 | @echo "Npm is not available. Please install npm." 48 | exit 1 49 | endif 50 | ifndef PANDOC 51 | @echo "Pandoc is not available. Please install pandoc." 52 | exit 1 53 | endif 54 | @echo "Dependencies OK" 55 | 56 | .PHONY: pre 57 | pre : $(TIDDLYWIKI_JS) $(TIDDLYWIKI_INFO) $(ORIGINAL_TIDDLYWIKI) 58 | @echo "Prerequisites OK" 59 | 60 | $(ORIGINAL_TIDDLYWIKI) : 61 | @echo "You must put a copy of your tiddlywiki in ./$(ORIGINAL_TIDDLYWIKI)" 62 | @echo "Aborting initialization" 63 | @exit 1 64 | 65 | $(TIDDLYWIKI_JS) : 66 | @echo "Installing TiddlyWiki..." 67 | $(NPM) install tiddlywiki 68 | 69 | $(TIDDLYWIKI_INFO) : $(TIDDLYWIKI_JS) 70 | @echo "Setting up temporary wiki..." 71 | $(NODEJS) $(TIDDLYWIKI_JS) $(WIKI_NAME) --init empty 72 | $(NODEJS) $(ADD_PLUGIN_JS) $(TIDDLYWIKI_INFO) tiddlywiki/tw2parser 73 | $(NODEJS) $(ADD_PLUGIN_JS) $(TIDDLYWIKI_INFO) tiddlywiki/markdown 74 | 75 | .PHONY: convert 76 | convert : $(MARKDOWN_DIR) $(MARKDOWN_TIDDLERS) 77 | 78 | $(MARKDOWN_DIR) : 79 | @echo "Creating folder '$(MARKDOWN_DIR)'..." 80 | mkdir $(MARKDOWN_DIR) 81 | 82 | $(MARKDOWN_DIR)/%.md : $(TW_OUTPUT_DIR)/%.html 83 | @echo "Generating markdown file '$(@F)'..." 84 | @echo "---" > "$@" 85 | @cat "$(^:html=meta)" >> "$@" 86 | @echo "---" >> "$@" 87 | @$(PANDOC) -f html-native_divs-native_spans -t commonmark \ 88 | --wrap=preserve -o - "$^" >> "$@" 89 | 90 | .PHONY: clean 91 | clean : 92 | rm -r $(WIKI_NAME) $(MARKDOWN_DIR) 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TiddlyWiki Migrator 2 | 3 | TiddlyWiki Migrator is a set of scripts put together to automate the migration from a TiddlyWiki (TW). It allows to export all tiddlers in a single-file HTML TiddlyWiki to multiple markdown files (one per tiddler), excluding system tiddlers. 4 | 5 | ![Process design](design/process.png) 6 | 7 | ## Dependencies 8 | 9 | * Node.js (tested with v10.4.0) 10 | * Npm (tested with 6.1.0) 11 | * Pandoc (tested with 2.2.1) 12 | 13 | ## Usage 14 | 15 | 1. Clone this repository. 16 | 2. Copy your single-file html TiddlyWiki (2 or 5) in your cloned repository. 17 | 3. Rename your TW html to `wiki.html` 18 | 4. Export your tiddlers: 19 | ``` 20 | $ make 21 | ``` 22 | 5. Convert all your tiddlers to Markdown: 23 | ``` 24 | $ make convert 25 | ``` 26 | 27 | Your tiddlers will be in the `markdown_tiddlers` directory. 28 | 29 | ## Use case 30 | 31 | A TW user has decided to abandon the use of TiddlyWiki. For that, she wants to save all tiddlers as markdown files, one for each tiddler from her TiddlyWiki. 32 | 33 | ## Features 34 | 35 | * Supports classic (version 2) and modern (version 5) TiddlyWikis. 36 | * Supported tiddler formats: 37 | * `text/vnd.tiddlywiki` (modern TW format) 38 | * `text/x-tiddlywiki` (classic TW format) 39 | * `text/x-markdown` 40 | * Exports to Markdown using Pandoc using options to conveniently simplify the resulting text. 41 | * Exported Markdown files follow a safe-name policy, while keeping the name as similar to the original as possible (this includes translating special vowels to the corresponding simple ones). 42 | * Metadata from tiddlers is exported as YFML at the beginning of each Markdown file. Metadata includes: creation date, last modification date, tags, etc. 43 | 44 | ## Motivation 45 | 46 | ### What is TiddlyWiki? 47 | TiddlyWiki "classic" (v2.x) is an offline, self-contained html wiki, where you can create, modify and delete tiddlers. 48 | 49 | TiddlyWiki "modern" (v5.x) is a brand new TW which can run just like v2.x or using node.js as a backend service to store/retrieve tiddlers. 50 | 51 | No doubt, TiddlyWiki is a useful and interesting piece of software. 52 | 53 | You can find more information on the [TiddlyWiki web](https://tiddlywiki.com). 54 | 55 | ### What is a tiddler? 56 | A tiddler is composed by: 57 | 58 | * a title, 59 | * a creation date, 60 | * a last modification date, 61 | * a creator and modifier person name, 62 | * zero or more tags, 63 | * a text and 64 | * a format (e.g. "text/x-tiddlywiki") 65 | 66 | ### Why markdown? 67 | Markdown is a possible tiddler's type (as of new versions of TiddlyWiki). 68 | 69 | Markdown format is commonplace on the Internet: in forums, blogs, source code repo platforms, etc. 70 | 71 | Markdown files usually end with the .md extension and follow the Markdown text format. 72 | 73 | ### Where can I read more about the process of building this script? 74 | 75 | I wrote [a post](https://davidalfonso.es/posts/tiddlywiki-to-markdown) with some more detail. 76 | -------------------------------------------------------------------------------- /design/process.epgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidag/tiddlywiki-migrator/dd869e885d0f5b1b8ab45a96b6a7f39ad87a4671/design/process.epgz -------------------------------------------------------------------------------- /design/process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidag/tiddlywiki-migrator/dd869e885d0f5b1b8ab45a96b6a7f39ad87a4671/design/process.png -------------------------------------------------------------------------------- /scripts/add-plugin.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | if (process.argv.length != 4) { 4 | console.log("Wrong parameters"); 5 | process.exit(1); 6 | } 7 | 8 | const TIDDLYWIKI_INFO_FILE = process.argv[2]; 9 | const PLUGIN_NAME = process.argv[3]; 10 | 11 | let data = fs.readFileSync(TIDDLYWIKI_INFO_FILE); 12 | 13 | let info = JSON.parse(data); 14 | info.plugins.push(PLUGIN_NAME); 15 | 16 | fs.writeFileSync(TIDDLYWIKI_INFO_FILE, JSON.stringify(info, null, 4)); 17 | -------------------------------------------------------------------------------- /scripts/safe-rename.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | if (process.argv.length != 3) { 4 | console.log("Wrong parameters. Directory name expected."); 5 | process.exit(1); 6 | } 7 | 8 | const PATHNAME = process.argv[2].endsWith('/') ? 9 | process.argv[2] : process.argv[2] + '/'; 10 | 11 | // List all filenames in dir received as argument 12 | fs.readdir(PATHNAME, (err, files) => { 13 | if (err) throw err; 14 | files.forEach((filename) => { 15 | let newFilename = decodeURIComponent(filename); 16 | 17 | // Clean filename (CUSTOMIZE THIS) 18 | newFilename = newFilename 19 | // Remove accents/diacritics 20 | .normalize('NFD').replace(/[\u0300-\u036f]/g, "") 21 | // Make lowercase 22 | .toLowerCase() 23 | // Convert separators to low line 24 | .replace(/\s+/g, '_') 25 | // Remove any non-safe character 26 | .replace(/[^0-9a-zA-Z-._]/g, ""); 27 | 28 | fs.rename(PATHNAME + filename, PATHNAME + newFilename, (err) => { 29 | if (err) throw err; 30 | }); 31 | }); 32 | }); 33 | 34 | --------------------------------------------------------------------------------