├── .gitattributes ├── .gitignore ├── bin └── index.js ├── changelog.md ├── example ├── .eleventy.js ├── .gitignore ├── package.json └── src │ ├── Index.wiki │ ├── images │ └── diamond.png │ └── templates │ └── Header.wiki ├── license.md ├── package-lock.json ├── package.json ├── readme.md ├── src ├── cli.ts ├── common.ts ├── compile.ts ├── index.ts ├── parse.ts └── wiki.css.ts ├── test ├── .eleventy.js └── src │ ├── example.wiki │ ├── images │ └── Example.png │ └── templates │ ├── plain.wiki │ └── subst.wiki └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # General 2 | * text=auto 3 | 4 | # Module CSS 5 | *.css.* linguist-language=CSS 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Site output 5 | _site/ 6 | wikity-out/ 7 | 8 | # Compiled source 9 | dist/ 10 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../dist/cli.js'); 3 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.6 4 | *2024-11-14* 5 | - Changed TOC generation to only include wikitext headings. 6 | - Fixed TOC being misplaced when non-wikitext headings are on the page. 7 | - Fixed TOC styling. 8 | - Fixed spacing issues in output such as in metadata and cite notes. 9 | - Fixed layout issues with non-`thumb` images. 10 | 11 | ## 1.3.5 12 | *2024-05-12* 13 | - Changed output to no longer be prettified. 14 | - Fixed spacing issues resulting from output prettification. 15 | - Fixed a crash occurring when invalid dates are given to `#time`. 16 | - Fixed links in image captions breaking the output. 17 | - Fixed underscores appearing in the default page title when present in the URL. 18 | - Fixed links not being automatically capitalised. 19 | - Fixed named references not working. 20 | - Fixed template calls inside table cells breaking the table. 21 | - Fixed headings not working when containing formatting. 22 | - Fixed all index numbers in the TOC being '1'. 23 | - Fixed images being deformed to 300x300px by default. 24 | 25 | ## 1.3.4 26 | *2024-05-18* 27 | - Added support for video embeds: currently only supports `{{#ev:youtube}}` and `{{#ev:vimeo}}`. 28 | - Added support for named references. 29 | - Added styling for infoboxes (`#infobox`). 30 | - Changed images to be displayed inline by default when no float is given. 31 | - Fixed a crash occurring when attempting to apply custom styles. 32 | - Fixed a crash occurring when a template goes unparsed. 33 | - Fixed unused parameters being substituted with random numbers instead of their default values. 34 | - Fixed templates not being trimmed before being substituted. 35 | - Fixed lists not outputting properly when a parser function is used on the line. 36 | - Fixed tables not allowing attributes syntax. 37 | - Fixed files and links becoming broken when parameters are inside them. 38 | - Fixed images being erroneously parsed as regular links. 39 | - Fixed internal links sometimes having wrong targets. 40 | - Fixed the TOC being inline in the default styles. 41 | - Fixed CSS selector for TOC heading not being specific enough. 42 | 43 | ## 1.3.3 44 | *2024-05-08* 45 | - Fixed characters around elements being erroneously padded with spaces. 46 | - Fixed inline definition lists not being parsed. 47 | - Fixed equal signs in named template arguments creating broken output. 48 | - Fixed piped links in template arguments creating broken output. 49 | 50 | ## 1.3.2 51 | *2024-05-08* 52 | - Fixed `nowiki` content not being fully unparsed. 53 | - Fixed output files not having the correct path to the default stylesheet. 54 | - Fixed URLs in output files being absolute. 55 | - Fixed various issues with incorrect outputted file names. 56 | - Fixed `||` not being treated as valid table cell syntax. 57 | - Fixed front matter being generated even when config option `eleventy` was set to `false`. 58 | - Fixed defaults not being used when `compile` called from CLI. 59 | - Unmarked Eleventy as a peer dependency. 60 | 61 | ## 1.3.1 62 | *2024-05-03* 63 | - Allowed support for Eleventy >1.0.0. 64 | - Fixed crash occurring when compiling images with no parameters. 65 | - Fixed default template arguments not being parsed. 66 | - Fixed output description metadata property being malformed. 67 | 68 | ## 1.3.0 69 | *2021-05-16* 70 | - Added configuration option `imagesFolder` to configure the folder that images are placed in. 71 | - Added support for images using the `[[File:Image.png|options|caption]]` syntax. 72 | - Added `class="wikitable"` table styling. 73 | - Fixed a crash occurring when the templates folder did not exist when compiling. 74 | 75 | ## 1.2.1 76 | *2021-04-02* 77 | - Added previously-unimplemented `config` option to `parse()` to configure the `templatesFolder`. 78 | - Changed table of contents to not require default styling to collapse. 79 | - Fixed internal links being incorrectly root-relative. 80 | - Fixed templates output folder not being created on initialisation. 81 | - Fixed unset arguments not being removed from the output. 82 | 83 | ## 1.2.0 84 | *2021-04-01* 85 | - Added function `eleventyPlugin()` for use with Eleventy's `addPlugin` method. 86 | - Added configuration option `outputFolder` to configure the folder the compiled HTML files are placed in. 87 | - Added configuration option `templatesFolder` to configure the folder templates are placed in. 88 | - Added CLI options `--outputFolder`, `--templatesFolder`, `--eleventy`, and `--defaultStyles` to change configuration options. 89 | - Added support for tables. 90 | - Added a warning when the parser detects non-repetition-based `#time` function syntax is being used. 91 | 92 | ## 1.1.0 93 | *2021-03-28* 94 | - Added `parse` CLI command to implement `parse()`. 95 | - Added a generated table of contents if there are over 4 headings. 96 | - Added support for `nowiki` tag. 97 | - Added support for `onlyinclude`, `includeonly`, and `noinclude` tags in templates. 98 | - Added support for magic words `__TOC__`, `__FORCETOC__`, `__NOTOC__`, and `__NOINDEX__`. 99 | - Added support for control function `{{displaytitle:}}` to control the page's displayed title. 100 | - Added support for string functions `lc:`, `uc:`, `lcfirst:`, `ucfirst:`, `replace:`, `explode:`, `sub:`, `len:`, `pos:`, `padleft:`, `padright:`, `urlencode:`, and `urldecode:`. 101 | - Added support for horizontal rules using `----`. 102 | - Changed time codes in `#datetime`/`#date`/`#time` function to be based on reduplication instead of unique characters with escaping based on quoting instead of prefixing with a backslash (i.e., `{{#time: j F Y (\n\o\w)}}` → `{{#time: dd mmm yyyy "(now)"}}`). 103 | - Fixed inline tags removing whitespace from either end. 104 | - Fixed single-line-only syntax not being parsed correctly. 105 | 106 | ## 1.0.1 107 | *2021-03-28* 108 | - Changed HTML output to be prettified. 109 | - Fixed arguments not being substituted properly. 110 | - Fixed nested templates and parser functions not being substituted properly. 111 | - Fixed templates and parser functions spread out across multiple lines not being parsed. 112 | 113 | ## 1.0.0 114 | *2021-03-27* 115 | - Added `compile()` function and CLI command to compile wikitext into HTML. 116 | - Added `parse()` function to parse raw wikitext input. 117 | -------------------------------------------------------------------------------- /example/.eleventy.js: -------------------------------------------------------------------------------- 1 | const wikity = require('wikity'); 2 | 3 | module.exports = function (eleventyConfig) { 4 | const wikityOptions = { 5 | eleventy: true, 6 | templatesFolder: 'templates', 7 | imagesFolder: 'images', 8 | outputFolder: 'wikity-out/', 9 | }; 10 | const wikityPlugin = () => wikity.compile('src', wikityOptions); 11 | eleventyConfig.addPlugin(wikityPlugin); 12 | eleventyConfig.addPassthroughCopy({ 'src/images': 'wiki/images' }); 13 | } 14 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | _site/ 3 | wikity-out/ 4 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my_wiki", 3 | "scripts": { 4 | "build": "eleventy", 5 | "dev": "eleventy --watch --serve" 6 | }, 7 | "dependencies": { 8 | "@11ty/eleventy": "^3.0.0", 9 | "wikity": "^1.3.5" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/src/Index.wiki: -------------------------------------------------------------------------------- 1 | {{Header}} 2 | 3 | = Index Page = 4 | 5 | Welcome to the [[File:diamond.png|10px]] wiki. 6 | -------------------------------------------------------------------------------- /example/src/images/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nixinova/Wikity/96b079b5701a58031a81d30b2552929f910fe0be/example/src/images/diamond.png -------------------------------------------------------------------------------- /example/src/templates/Header.wiki: -------------------------------------------------------------------------------- 1 |
2 | '''Header Content''' 3 |
4 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # ISC License 2 | 3 | Copyright © 2021–2024 Nixinova 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | The software is provided "as is" and the author disclaims all warranties 10 | with regard to this software including all implied warranties of 11 | merchantability and fitness. In no event shall the author be liable for 12 | any special, direct, indirect, or consequential damages or any damages 13 | whatsoever resulting from loss of use, data or profits, whether in an 14 | action of contract, negligence or other tortious action, arising out of 15 | or in connection with the use or performance of this software. 16 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wikity", 3 | "version": "1.3.6", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "wikity", 9 | "version": "1.3.6", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dateformat": "^4.6.3", 13 | "dedent": "^1.5.3", 14 | "escape-html": "^1.0.3", 15 | "glob": "^8.1.0" 16 | }, 17 | "bin": { 18 | "wikity": "bin/index.js" 19 | }, 20 | "devDependencies": { 21 | "@types/dateformat": "^3.0.1", 22 | "@types/dedent": "^0.7.2", 23 | "@types/escape-html": "^1.0.4", 24 | "@types/glob": "^8.1.0", 25 | "@types/node": "ts5.0", 26 | "typescript": "~5.0.4 <5.1" 27 | }, 28 | "engines": { 29 | "node": ">=12" 30 | } 31 | }, 32 | "node_modules/@types/dateformat": { 33 | "version": "3.0.1", 34 | "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz", 35 | "integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==", 36 | "dev": true 37 | }, 38 | "node_modules/@types/dedent": { 39 | "version": "0.7.2", 40 | "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.2.tgz", 41 | "integrity": "sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==", 42 | "dev": true 43 | }, 44 | "node_modules/@types/escape-html": { 45 | "version": "1.0.4", 46 | "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", 47 | "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", 48 | "dev": true 49 | }, 50 | "node_modules/@types/glob": { 51 | "version": "8.1.0", 52 | "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", 53 | "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", 54 | "dev": true, 55 | "dependencies": { 56 | "@types/minimatch": "^5.1.2", 57 | "@types/node": "*" 58 | } 59 | }, 60 | "node_modules/@types/minimatch": { 61 | "version": "5.1.2", 62 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", 63 | "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", 64 | "dev": true 65 | }, 66 | "node_modules/@types/node": { 67 | "version": "20.12.8", 68 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz", 69 | "integrity": "sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==", 70 | "dev": true, 71 | "dependencies": { 72 | "undici-types": "~5.26.4" 73 | } 74 | }, 75 | "node_modules/balanced-match": { 76 | "version": "1.0.2", 77 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 78 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 79 | }, 80 | "node_modules/brace-expansion": { 81 | "version": "2.0.1", 82 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 83 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 84 | "dependencies": { 85 | "balanced-match": "^1.0.0" 86 | } 87 | }, 88 | "node_modules/dateformat": { 89 | "version": "4.6.3", 90 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", 91 | "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", 92 | "engines": { 93 | "node": "*" 94 | } 95 | }, 96 | "node_modules/dedent": { 97 | "version": "1.5.3", 98 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", 99 | "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", 100 | "peerDependencies": { 101 | "babel-plugin-macros": "^3.1.0" 102 | }, 103 | "peerDependenciesMeta": { 104 | "babel-plugin-macros": { 105 | "optional": true 106 | } 107 | } 108 | }, 109 | "node_modules/escape-html": { 110 | "version": "1.0.3", 111 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 112 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 113 | }, 114 | "node_modules/fs.realpath": { 115 | "version": "1.0.0", 116 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 117 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 118 | }, 119 | "node_modules/glob": { 120 | "version": "8.1.0", 121 | "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", 122 | "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", 123 | "dependencies": { 124 | "fs.realpath": "^1.0.0", 125 | "inflight": "^1.0.4", 126 | "inherits": "2", 127 | "minimatch": "^5.0.1", 128 | "once": "^1.3.0" 129 | }, 130 | "engines": { 131 | "node": ">=12" 132 | }, 133 | "funding": { 134 | "url": "https://github.com/sponsors/isaacs" 135 | } 136 | }, 137 | "node_modules/inflight": { 138 | "version": "1.0.6", 139 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 140 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 141 | "dependencies": { 142 | "once": "^1.3.0", 143 | "wrappy": "1" 144 | } 145 | }, 146 | "node_modules/inherits": { 147 | "version": "2.0.4", 148 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 149 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 150 | }, 151 | "node_modules/minimatch": { 152 | "version": "5.1.6", 153 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 154 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 155 | "dependencies": { 156 | "brace-expansion": "^2.0.1" 157 | }, 158 | "engines": { 159 | "node": ">=10" 160 | } 161 | }, 162 | "node_modules/once": { 163 | "version": "1.4.0", 164 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 165 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 166 | "dependencies": { 167 | "wrappy": "1" 168 | } 169 | }, 170 | "node_modules/typescript": { 171 | "version": "5.0.4", 172 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", 173 | "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", 174 | "dev": true, 175 | "bin": { 176 | "tsc": "bin/tsc", 177 | "tsserver": "bin/tsserver" 178 | }, 179 | "engines": { 180 | "node": ">=12.20" 181 | } 182 | }, 183 | "node_modules/undici-types": { 184 | "version": "5.26.5", 185 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 186 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 187 | "dev": true 188 | }, 189 | "node_modules/wrappy": { 190 | "version": "1.0.2", 191 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 192 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 193 | } 194 | }, 195 | "dependencies": { 196 | "@types/dateformat": { 197 | "version": "3.0.1", 198 | "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz", 199 | "integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==", 200 | "dev": true 201 | }, 202 | "@types/dedent": { 203 | "version": "0.7.2", 204 | "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.2.tgz", 205 | "integrity": "sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==", 206 | "dev": true 207 | }, 208 | "@types/escape-html": { 209 | "version": "1.0.4", 210 | "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", 211 | "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", 212 | "dev": true 213 | }, 214 | "@types/glob": { 215 | "version": "8.1.0", 216 | "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", 217 | "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", 218 | "dev": true, 219 | "requires": { 220 | "@types/minimatch": "^5.1.2", 221 | "@types/node": "*" 222 | } 223 | }, 224 | "@types/minimatch": { 225 | "version": "5.1.2", 226 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", 227 | "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", 228 | "dev": true 229 | }, 230 | "@types/node": { 231 | "version": "20.12.8", 232 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz", 233 | "integrity": "sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==", 234 | "dev": true, 235 | "requires": { 236 | "undici-types": "~5.26.4" 237 | } 238 | }, 239 | "balanced-match": { 240 | "version": "1.0.2", 241 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 242 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 243 | }, 244 | "brace-expansion": { 245 | "version": "2.0.1", 246 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 247 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 248 | "requires": { 249 | "balanced-match": "^1.0.0" 250 | } 251 | }, 252 | "dateformat": { 253 | "version": "4.6.3", 254 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", 255 | "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" 256 | }, 257 | "dedent": { 258 | "version": "1.5.3", 259 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", 260 | "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", 261 | "requires": {} 262 | }, 263 | "escape-html": { 264 | "version": "1.0.3", 265 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 266 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 267 | }, 268 | "fs.realpath": { 269 | "version": "1.0.0", 270 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 271 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 272 | }, 273 | "glob": { 274 | "version": "8.1.0", 275 | "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", 276 | "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", 277 | "requires": { 278 | "fs.realpath": "^1.0.0", 279 | "inflight": "^1.0.4", 280 | "inherits": "2", 281 | "minimatch": "^5.0.1", 282 | "once": "^1.3.0" 283 | } 284 | }, 285 | "inflight": { 286 | "version": "1.0.6", 287 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 288 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 289 | "requires": { 290 | "once": "^1.3.0", 291 | "wrappy": "1" 292 | } 293 | }, 294 | "inherits": { 295 | "version": "2.0.4", 296 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 297 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 298 | }, 299 | "minimatch": { 300 | "version": "5.1.6", 301 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 302 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 303 | "requires": { 304 | "brace-expansion": "^2.0.1" 305 | } 306 | }, 307 | "once": { 308 | "version": "1.4.0", 309 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 310 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 311 | "requires": { 312 | "wrappy": "1" 313 | } 314 | }, 315 | "typescript": { 316 | "version": "5.0.4", 317 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", 318 | "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", 319 | "dev": true 320 | }, 321 | "undici-types": { 322 | "version": "5.26.5", 323 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 324 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 325 | "dev": true 326 | }, 327 | "wrappy": { 328 | "version": "1.0.2", 329 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 330 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wikity", 3 | "version": "1.3.6", 4 | "description": "Compile wikitext to HTML! Supporst use of wikitext as a templating language.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "tsc && cd test && eleventy" 8 | }, 9 | "bin": { 10 | "wikity": "bin/index.js" 11 | }, 12 | "engines": { 13 | "node": ">=12" 14 | }, 15 | "files": [ 16 | "bin/", 17 | "dist/" 18 | ], 19 | "keywords": [ 20 | "compiler", 21 | "compilation", 22 | "wikitext", 23 | "mediawiki", 24 | "html", 25 | "template-language", 26 | "templating-language", 27 | "eleventy", 28 | "eleventy-plugin" 29 | ], 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/Nixinova/Wikity.git" 33 | }, 34 | "author": "Nixinova (https://nixinova.com)", 35 | "bugs": { 36 | "url": "https://github.com/Nixinova/Wikity/issues" 37 | }, 38 | "homepage": "https://github.com/Nixinova/Wikity#readme", 39 | "license": "ISC", 40 | "dependencies": { 41 | "dateformat": "^4.6.3", 42 | "dedent": "^1.5.3", 43 | "escape-html": "^1.0.3", 44 | "glob": "^8.1.0" 45 | }, 46 | "devDependencies": { 47 | "@types/dateformat": "^3.0.1", 48 | "@types/dedent": "^0.7.2", 49 | "@types/escape-html": "^1.0.4", 50 | "@types/glob": "^8.1.0", 51 | "@types/node": "ts5.0", 52 | "typescript": "~5.0.4 <5.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Latest version](https://img.shields.io/github/v/release/Nixinova/Wikity?label=latest%20version&style=flat-square)](https://github.com/Nixinova/Wikity/releases) 2 | [![Last updated](https://img.shields.io/github/release-date/Nixinova/Wikity?label=updated&style=flat-square)](https://github.com/Nixinova/Wikity/releases) 3 | [![npm downloads](https://img.shields.io/npm/dt/wikity?logo=npm)](https://www.npmjs.com/package/wikity) 4 | 5 | # Wikity 6 | 7 | **Wikity** is a tool that allows you to use Wikitext (used by Wikipedia, Fandom, etc) as a templating language to create HTML pages. 8 | 9 | This package comes with built-in support for compilation using [Eleventy](https://11ty.dev). 10 | 11 | ## Exemplar 12 | 13 | See the `example` folder for a template on making your own wiki site! 14 | 15 | ## Install 16 | 17 | Wikity is available [on npm](https://www.npmjs.com/package/wikity). 18 | Install locally to use in a Node package or install globally for use from the command-line. 19 | 20 | | Local install | Global install | 21 | | -------------------- | ----------------------- | 22 | | `npm install wikity` | `npm install -g wikity` | 23 | 24 | ## API 25 | 26 | ### Node 27 | 28 | - `wikity.compile(folder?: string, options?: object): void` 29 | - Compile all Wikitext (`.wiki`) files from an input folder (defaults to the current directory, `.`) into HTML. 30 | - `wikity.parse(input: string, options?: object): string` 31 | - Parse raw wikitext input into HTML. 32 | 33 | - **Options**: 34 | - `eleventy: boolean = false` 35 | - Whether [front matter](https://www.11ty.dev/docs/data-frontmatter/) will be added to the outputted HTML for Eleventy to read (default: `false`). 36 | (**This parameter *must* be set to `true` if you want to use this with Eleventy.**) 37 | - `outputFolder: string` 38 | - *Used only with `compile()`.* 39 | - Where outputted HTML files shall be placed. 40 | - Default: `'wikity-out'`. 41 | - `templatesFolder: string` 42 | - What folder to place templates in. 43 | - Default: `'templates'`. 44 | - `imagesFolder: string` 45 | - What folder to place images in. 46 | - Default: `'images'`. 47 | - `defaultStyles: boolean` 48 | - *Used only with `compile()`.* 49 | - Whether to use default wiki styling. 50 | - Default: to `true` when called from `compile()` and `false` when called from `parse()`. 51 | - `customStyles: string` 52 | - *Used only with `compile()`.* 53 | - Custom CSS styles to add to the wiki pages. 54 | - Default: empty (`''`). 55 | 56 | #### Example 57 | 58 | ```js 59 | const wikity = require('wikity'); 60 | 61 | // compile all .wiki files inside this directory 62 | wikity.compile(); 63 | 64 | // parse wikitext from an input string 65 | let html = wikity.parse(`'''bold''' [[link|text]]`); // bold text 66 | ``` 67 | 68 | #### As an Eleventy plugin 69 | 70 | Use Wikity along with Eleventy to have all your wiki files compiled during the build process: 71 | 72 | ```js 73 | // .eleventy.js (eleventy's configuration file) 74 | const wikity = require('wikity'); 75 | module.exports = function (eleventyConfig) { 76 | const rootFolder = 'src'; 77 | const templatesFolder = 'templates', imagesFolder = 'images'; // defaults; relative to root folder 78 | const outputFolder = 'wikity-out'; // default 79 | const wikityOptions = { eleventy: true, templatesFolder, imagesFolder, outputFolder }; 80 | const wikityPlugin = () => wikity.compile(rootFolder, wikityOptions); 81 | eleventyConfig.addPlugin(wikityPlugin); 82 | eleventyConfig.addPassthroughCopy({ 'src/images': 'wiki/' + imagesFolder }); // Eleventy does not pass through images by default 83 | } 84 | ``` 85 | 86 | The above will use the following file structure (with some example wiki files given): 87 | 88 | - `src/` 89 | - `templates/`: Directory for wiki templates (called like `{{this}}`) 90 | - `images/`: Directory to place images (called like `[[File:this]]`) 91 | - `Index.wiki`: Example file 92 | - `Other_Page.wiki`: Example other file 93 | - `wikity-out/`: File templates compiled from the `.wiki` files (add this to `.gitignore`) 94 | - `wiki/`: Output HTML files compiled from `wikity-out/` (add this to `.gitignore`) 95 | 96 | (View the above starting at the URL path `/wiki/` when ran in an HTTP server.) 97 | 98 | ### Command-line 99 | ```cmd 100 | $ wikity help 101 | Display a help message 102 | $ wikity compile [] [-o ] [-t ] [-e] [-d] 103 | Compile Wikity with various options 104 | $ wikity parse 105 | Parse raw input into HTML 106 | $ wikity version 107 | Display the latest version of Wikity 108 | ``` 109 | 110 | ## Usage 111 | 112 | Use [Wikitext](https://en.wikipedia.org/wiki/Help:Wikitext) (file extension `.wiki`) to create your pages. 113 | 114 | Any wiki templates (called using `{{template name}}`) must be inside the `templates/` folder by default. 115 | Any files must be inside the `images/` folder by default. 116 | Your wikitext (`*.wiki`) files go in the root directory by default. 117 | 118 | ### Wiki markup 119 | 120 | | Markup | Preview | 121 | | -------------------------------- | ----------------------------------------- | 122 | | `'''bold'''` | **bold** | 123 | | `''italic''` | *italic* | 124 | | `'''''bold italic'''''` | ***bold italic*** | 125 | | ` ``code`` ` | `code` | 126 | | ` ```code block``` ` |
code block
| 127 | | `=heading=` | heading | 128 | | `==subheading==` | subheading | 129 | | `*bulleted` |
  • bulleted
| 130 | | `**sub-bulleted` |
    • sub-bulleted
| 131 | | `#numbered` |
  1. numbered
| 132 | | `##sub-numbered` |
    1. sub-numbered
| 133 | | `;term` |
**term**
| 134 | | `:definition` |
 definition
| 135 | | `Text` | [[1]](#ref-1) | 136 | | `` | 1. [↑](#cite-1) Text | 137 | | `[[internal link]]` | [internal link](#internal_link) | 138 | | `[[link\|display text]]` | [display text](#link) | 139 | | `[external-link]` | [[1]](#external-link) | 140 | | `[external-link display text]` | [display text](#external-link) | 141 | | `[[File:Example.png\|Caption.]]` | ![Caption](Example.png) | 142 | | `{{tp name}}` | *(contents of `templates/tp_name.wiki`)* | 143 | | `{{tp name\|arg=val}}` | *(ditto but `{{{arg}}}` is set to 'val')* | 144 | | `{{{arg}}}` | *(value given by template)* | 145 | | `{{{arg\|default val}}}` | *(ditto but 'default val' if unset)* | 146 | | `{\| style="margin:1em"` | *table opening* | 147 | | `! Cell heading` | **Cell heading** | 148 | | `\|- class="new-row"` | *new table row* | 149 | | `\| Cell content` | Cell content | 150 | | `\|}` | *table closing* | 151 | | `{{#if:non-empty-string\|text}}` | text | 152 | | `{{#ifeq:1\|2\|true\|false}}` | false | 153 | | `{{#vardefine:varname\|text}}` | *(saved to memory)* | 154 | | `{{#var:varname}}` | text *(from memory)* | 155 | | `{{#var:varname\|default val}}` | *(ditto but 'default val' if unset)* | 156 | | `{{#switch:a\|a=1\|b=2\|c=3}}` | 1 | 157 | | `{{#time:dd/mm/yy\|2021-03-28}}` | 28/03/21 | 158 | | `{{#lc:TEXT}}` | text | 159 | | `{{#ucfirst:text}}` | Text | 160 | | `{{#len:12345}}` | 5 | 161 | | `{{#sub:string\|2\|4}}` | ring | 162 | | `{{#pos:text\|x}}` | 2 | 163 | | `{{#padleft:text\|5\|_}}` | _text | 164 | | `{{#padright:msg\|5\|_}}` | msg__ | 165 | | `{{#replace:Message\|e\|3}}` | M3ssag3 | 166 | | `{{#explode:A-B-C-D\|-\|2}}` | C | 167 | | `{{#urlencode:t e x t}}` | t%20e%20x%20t | 168 | | `{{#urldecode:a%20b%27c}}` | a b'c | 169 | | `{{#ev:youtube\|dQw4w9WgXcQ}}` | *(YouTube embed)* | 170 | | `No` | *(blank outside a template)* | 171 | | `Yes` | Yes | 172 | | `Yes` | Yes *(blank inside a template)* | 173 | | `[[no link]]` | [[no link]] | 174 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | const VERSION = require('../package.json').version; 2 | 3 | import wikity from './index'; 4 | 5 | // Helper functions 6 | const indent = (n: number): string => ' '.repeat(n * 4); 7 | const usage = (command: string, ...desc: string[]): void => { 8 | console.log('\n' + indent(2) + command); 9 | desc.forEach((msg: string) => console.log(indent(2.5) + msg)); 10 | } 11 | const arg = (n: number): string => process.argv[n + 1] || ''; 12 | const args = process.argv.slice(1); 13 | 14 | // Run CLI 15 | if (!arg(1)) { 16 | // No arguments 17 | 18 | console.log('Type `wikity help` for a list of commands.'); 19 | } 20 | else if (arg(1).includes('h')) { 21 | // Show help message 22 | 23 | console.log(`\n${indent(1)}Wikity CLI commands:`); 24 | usage(`wikity (help|-h)`, 25 | `Display this help message.`, 26 | ); 27 | usage(`wikity compile [] [-o ] [-t ] -i ] [-e] [-d]`, 28 | `Compile wikitext files from a given folder.`, 29 | ` []\n${indent(3.5)}Input folder ('.' (current folder) if unset).`, 30 | ` (-o|--outputFolder) \n${indent(3.5)}Folder that compiled HTML files are placed in ('wikity-out' if unset).`, 31 | ` (-t|--templatesFolder) \n${indent(3.5)}Where to place wiki templates ('templates' if unset).`, 32 | ` (-i|--imagesFolder) \n${indent(3.5)}Where to place wiki images ('images' if unset).`, 33 | ` (-e|--eleventy)\n${indent(3.5)}Compiles files with Eleventy front matter (false if unset).`, 34 | ` (-d|--defaultStyles)\n${indent(3.5)}Add default wiki styling to all pages (true if unset).`, 35 | ) 36 | usage(`wikity (parse|-p) ""`, 37 | `Parse raw wikitext from the command line.`, 38 | ) 39 | usage(`wikity (version|-v)`, 40 | `Display the current version of Wikity.`, 41 | ); 42 | } 43 | else if (arg(1).includes('c')) { 44 | // Run compilation 45 | 46 | const configArgs: string[] = args.slice(2); 47 | const argsList: string = configArgs.join(' '); 48 | 49 | // retrieve item from arguments list 50 | const getArgContent = (arg: RegExp) => arg.test(argsList) && configArgs.filter((_, i) => arg.test(configArgs[i - 1])).join(' ') || undefined; 51 | 52 | // Fetch user-supplied arguments 53 | const folder = arg(2) || '.'; 54 | const outputFolder = getArgContent(/^-+o/); 55 | const templatesFolder = getArgContent(/^-+t/); 56 | const imagesFolder = getArgContent(/^-+i/); 57 | const eleventy = /^-+e/.test(argsList); 58 | const defaultStyles = /^-+d/.test(argsList); 59 | 60 | wikity.compile(folder, { outputFolder, templatesFolder, imagesFolder, eleventy, defaultStyles }); 61 | } 62 | else if (arg(1).includes('p')) { 63 | // Run parsing 64 | 65 | // second argument is inputted text 66 | const input = arg(2); 67 | 68 | const output = wikity.parse(input); 69 | console.log(output); 70 | } 71 | else if (arg(1).includes('v')) { 72 | // Show version 73 | 74 | console.log('The current version of Wikity is ' + VERSION); 75 | } 76 | else { 77 | // Unknown command 78 | 79 | console.log('Unknown command; type `wikity help` for help'); 80 | } 81 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | export type Metadata = Record; 2 | 3 | export interface Config { 4 | /** The folder that Wikity's compiled HTML files are outputted to (defaults to 'wikity-out') */ 5 | outputFolder?: string, 6 | /** The folder that wiki templates are to be stored in relative to the root folder */ 7 | templatesFolder?: string, 8 | /** The folder that images are to be stored in relative to the root folder */ 9 | imagesFolder?: string, 10 | /** Whether to be set up for Eleventy integration (defaults to false) */ 11 | eleventy?: boolean, 12 | /** Whether to use default wiki styling (defaults to true) */ 13 | defaultStyles?: boolean, 14 | /** Custom CSS styles to add to the wiki pages */ 15 | customStyles?: string, 16 | } 17 | 18 | export interface Result { 19 | data: string; 20 | metadata: Metadata; 21 | } 22 | 23 | export function RegExpBuilder(regex: string, flag: string = 'mgi') { 24 | return RegExp(regex.replace(/ /g, '').replace(/\|\|.+?\|\|/g, ''), flag); 25 | } 26 | -------------------------------------------------------------------------------- /src/compile.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import paths from 'path'; 3 | import glob from 'glob'; 4 | import dedent from 'dedent'; 5 | 6 | import { parse } from './parse'; 7 | import { Config, Result } from './common'; 8 | import defaultStyles from './wiki.css'; 9 | 10 | export function eleventyCompile(dir: string = '.', config: Config = {}): void { 11 | compile(dir, { eleventy: true, ...config }); 12 | } 13 | 14 | export function compile(dir: string = '.', config: Config = {}): void { 15 | // set defaults 16 | config.templatesFolder ??= 'templates'; 17 | config.imagesFolder ??= 'images'; 18 | config.outputFolder ??= 'wikity-out'; 19 | 20 | // directory variables (absolute paths) 21 | const baseDir = paths.resolve(dir); 22 | const templatesFolder = paths.join(baseDir, config.templatesFolder); 23 | const imagesFolder = paths.join(baseDir, config.imagesFolder); 24 | const outputFolder = paths.join(baseDir, config.outputFolder); 25 | const outputImagesFolder = paths.join(baseDir, config.outputFolder, config.imagesFolder); 26 | const newConfig = { ...config, templatesFolder, imagesFolder, outputFolder }; 27 | 28 | let stylesCreated = false; 29 | // Write wikitext files 30 | const files = glob.sync(dir + "/**/*.wiki", {}); 31 | files.forEach((file: string) => { 32 | const fileData: string = fs.readFileSync(file, { encoding: 'utf8' }); 33 | const { data: parsedContent, metadata }: Result = parse(fileData, newConfig); 34 | 35 | let outText: string = parsedContent; 36 | 37 | const filename = file.replace(dir, '').replace(/^[\/\\]/, ''); 38 | const outFilename: string = filename.replace(/ /g, '_').replace('.wiki', '.html'); 39 | const outFilePath = paths.join(outputFolder, outFilename); 40 | const urlPath: string = outFilename.replace(/(?<=^|\/)\w/g, m => m.toUpperCase()); // capitalise first letters 41 | 42 | const displayTitle: string = metadata.displayTitle || urlPath.replace('.html', '').replace(/_/g, ' '); 43 | 44 | // Eleventy configuration 45 | const frontMatter = config.eleventy ? dedent` 46 | --- 47 | permalink: /wiki/${urlPath} 48 | --- 49 | ` : ''; 50 | 51 | // Create TOC 52 | if (!metadata.notoc && (metadata.toc || (outText.match(/]*>/g)?.length || 0) > 3)) { 53 | let toc = ''; 54 | let headings = Array.from(parsedContent.match(/]*>.+?<\/h\d>/gs) || []); 55 | headings.forEach(match => { 56 | const headingInner: string = match.replace(/\s*<\/?h\d[^>]*>\s*/g, ''); 57 | const text = headingInner.replace(/<.+?>/g, ''); // remove tags from inner 58 | const lvl: number = +(match.match(/\d/g)?.[0] || -1); 59 | toc += `${`
    `.repeat(lvl - 1)}
  1. ${text}
  2. ${`
`.repeat(lvl - 1)}`; 60 | }); 61 | toc = toc.replace(/<\/ol>\s*
    /g, ''); 62 | const tocElem = dedent` 63 |
    64 | 65 | Contents 66 | [hide] 70 | 71 |
      ${toc}
    72 |
    73 | `; 74 | // Set TOC on page 75 | if (outText.includes('')) { 76 | // put TOC where explicitly declared 77 | outText = outText.replace('', tocElem); 78 | } 79 | else { 80 | // put TOC above first auto heading 81 | outText = outText.replace(//gs, ''); 87 | 88 | // Create HTML 89 | const folderUpCount = file.split(/[\/\\]/).length - dir.split(/[\/\\]/).length; // number of folders to go up by to get to root 90 | const html = dedent` 91 | 92 | 93 | 94 | 95 | 96 | ${displayTitle} 97 | 98 | 99 | 100 |
    101 |

    ${displayTitle}

    102 |
    103 |
    104 |

    \n${outText} 105 |

    106 |
    107 |
    108 |

    Created using Wikity

    109 |
    110 | 111 | 112 | `; 113 | 114 | // Write to file 115 | if (!fs.existsSync(paths.dirname(outFilePath))) { 116 | fs.mkdirSync(paths.dirname(outFilePath)); 117 | } 118 | const formattedHtml = html.replace(/\n[\n\s]+/g, '\n'); 119 | fs.writeFileSync(outFilePath, frontMatter + '\n' + formattedHtml, 'utf8'); 120 | 121 | // Move images 122 | glob(imagesFolder + '/*', {}, (err, files) => { 123 | if (err) { 124 | console.warn(err); 125 | } 126 | if (!fs.existsSync(outputImagesFolder)) { 127 | fs.mkdirSync(outputImagesFolder); 128 | } 129 | for (const file of files) { 130 | fs.copyFileSync(file, paths.join(outputImagesFolder, paths.basename(file))) 131 | }; 132 | }); 133 | 134 | // Create site styles 135 | if (!stylesCreated) { 136 | stylesCreated = true; 137 | let styles: string = ''; 138 | if (config.defaultStyles !== false) { 139 | styles += defaultStyles; 140 | } 141 | if (config.customStyles) { 142 | styles += config.customStyles; 143 | } 144 | const cssOutput = config.eleventy ? ['---', 'permalink: /wiki.css', '---', styles].join('\n') : styles; 145 | const cssOutFilename = config.eleventy ? 'wiki.css.njk' : 'wiki.css'; 146 | fs.writeFileSync(paths.join(outputFolder, cssOutFilename), cssOutput); 147 | } 148 | 149 | }); 150 | } 151 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { compile, eleventyCompile } from './compile'; 2 | import { rawParse } from './parse'; 3 | 4 | export = { 5 | /** Parse wikitext from a string */ 6 | parse: rawParse, 7 | /** Compile a folder of wikitext files */ 8 | compile: compile, 9 | /** Compile a folder of wikitext files for output in Eleventy @deprecated */ 10 | eleventyPlugin: eleventyCompile, 11 | }; 12 | -------------------------------------------------------------------------------- /src/parse.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import paths from 'path'; 3 | import htmlEscape from 'escape-html'; 4 | import dateFormat from 'dateformat'; 5 | 6 | import { Config, Result, Metadata, RegExpBuilder as re } from './common'; 7 | 8 | const r = String.raw; 9 | const MAX_RECURSION: number = 20; 10 | const arg: string = r`\s*([^|}]+?)\s*`; 11 | 12 | function toLinkText(link: string) { 13 | const cleanedLink = encodeURI(link.trim().replace(/ /g, '_')); 14 | return cleanedLink[0].toUpperCase() + cleanedLink.slice(1); 15 | } 16 | 17 | function toFileText(link: string) { 18 | const cleanedLink = link.trim().replace(/ /g, '_'); 19 | return cleanedLink[0].toUpperCase() + cleanedLink.slice(1); 20 | } 21 | 22 | function parseDimensions(dimStr: string) { 23 | const regex = /(\d*)(?:x(\d*))?px/; 24 | const match = dimStr.match(regex); 25 | if (!match) return { width: 'auto', height: 'auto' }; 26 | const [, width, height] = match; 27 | return { 28 | width: width || 'auto', 29 | height: height || 'auto', 30 | }; 31 | } 32 | 33 | const escaper = (text: string, n: number = 0) => `%${text}#${n}`; 34 | 35 | export function rawParse(data: string, config: Config = {}): string { 36 | return parse(data, config).data; 37 | } 38 | 39 | export function parse(data: string, config: Config = {}): Result { 40 | const KEY = Math.random().toString().slice(2); // key used to allow certain disallowed HTML elements 41 | 42 | const templatesFolder = config.templatesFolder ?? 'templates'; 43 | const imagesFolder = config.imagesFolder ?? 'images'; 44 | const outputFolder = config.outputFolder ?? 'wikity-out'; 45 | 46 | const vars: Metadata = {}; 47 | const metadata: Metadata = {}; 48 | const nowikis: string[] = []; 49 | const refs: Array<{ ref: string, id: number, name: string, i: number }> = []; 50 | let nowikiCount: number = 0; 51 | let rawExtLinkCount: number = 0; 52 | 53 | let outText: string = data; 54 | 55 | for (let l = 0, last = ''; l < MAX_RECURSION; l++) { 56 | if (last === outText) break; 57 | last = outText; 58 | 59 | outText = outText 60 | 61 | // Nowiki: 62 | .replace(re(r` ([^]+?) `), (_, m) => (nowikis.push(m), escaper('NOWIKI', nowikiCount++))) 63 | 64 | // Sanitise unacceptable HTML 65 | .replace(re(r`< \s* (?= (?: script|link|meta|iframe|frameset|object|embed|applet|form|input|button|textarea ) (?! \s* key.{0,10}${KEY}) )`), '<') 66 | .replace(re(r`(?<= <[^>]+ ) (\bon(\w+))`), 'data-$2') 67 | 68 | // Comments: 69 | .replace(//g, '') 70 | 71 | // Lines: ---- 72 | .replace(/^-{4,}/gm, '
    ') 73 | 74 | // Images: [[File:Image.png|options|caption]] 75 | .replace(re(r`\[\[ (?:File|Image): (.*?) (\|.+?)? \]\]`), (_, file, params = '') => { 76 | if (params.includes('{{')) return _; 77 | if (params.includes('[[')) return _; 78 | if (!file) return ''; 79 | 80 | const path: string = paths.join(imagesFolder, file.trim().replace(/ /g, '_')); 81 | let caption: string = ''; 82 | let imageData: { [key: string]: any } = {}; 83 | let imageArgs: string[] = params.split('|').map((arg: string) => arg.replace(/"/g, '"')); 84 | for (const param of imageArgs) { 85 | if (['left', 'right', 'center', 'none'].includes(param)) { 86 | imageData.float = param; 87 | } 88 | if (['baseline', 'sub', 'super', 'top', 'text-bottom', 'middle', 'bottom', 'text-bottom'].includes(param)) { 89 | imageData.align = param; 90 | } 91 | else if (['border', 'frameless', 'frame', 'framed', 'thumb', 'thumbnail'].includes(param)) { 92 | imageData.type = { framed: 'frame', thumbnail: 'thumb' }[param] || param; 93 | if (imageData.type === 'thumb') { 94 | imageData.hasCaption = true; 95 | imageData.float ??= 'right'; 96 | } 97 | } 98 | else if (param.endsWith('px')) { 99 | const { width, height } = parseDimensions(param); 100 | imageData.width = width; 101 | imageData.height = height; 102 | } 103 | else if (param.startsWith('upright=')) { 104 | imageData.width = +param.replace('upright=', '') * 300; 105 | } 106 | else if (param.startsWith('link=')) { 107 | imageData.link = param.replace('link=', ''); 108 | } 109 | else if (param.startsWith('alt=')) { 110 | imageData.alt = param.replace('alt=', ''); 111 | } 112 | else if (param.startsWith('style=')) { 113 | imageData.style = param.replace('style=', ''); 114 | } 115 | else if (param.startsWith('class=')) { 116 | imageData.class = param.replace('class=', ''); 117 | } 118 | else { 119 | caption = param; 120 | } 121 | } 122 | let content = ` 123 |
    138 | ${imageData.alt || file} 144 | ${imageData.hasCaption ? `
    ${caption}
    ` : ''} 145 |
    146 | `; 147 | const imageLink = imageData.link; 148 | if (imageLink) { 149 | content = `${content}`; 150 | } 151 | return content; 152 | }) 153 | 154 | // Internal links 155 | // [[Page]] 156 | .replace(re(r`\[\[ ([^\]|]+?) \]\]`), (_, link) => { 157 | if (_.includes('{{')) return _; 158 | if (/(?:File|Image):/.test(link)) return _; 159 | const content = `${link}`; 160 | return content; 161 | }) 162 | // [[Page|Text]] 163 | .replace(re(r`\[\[ ([^\]|]+?) \| ([^\]]+?) \]\]`), (_, link, text) => { 164 | if (link.includes('{{')) return _; 165 | if (/(?:File|Image):/.test(link)) return _; 166 | const content = `${text}`; 167 | return content; 168 | }) 169 | .replace(re(r`()([a-z]+)`), '$2$1') 170 | 171 | // External links: [href Page] and just [href] 172 | .replace(re(r`\[ ((?:\w+:)?\/\/ [^\s\]]+) (\s [^\]]+?)? \]`), (_, href, txt) => { 173 | if (_.includes('{{')) return _; 174 | const content = `${txt || '[' + (++rawExtLinkCount) + ']'}` 175 | return content; 176 | }) 177 | 178 | // Magic words: {{!}}, {{reflist}}, etc 179 | .replace(re(r`{{ \s* ! \s* }}`), escaper('VERT')) 180 | .replace(re(r`{{ \s* !! \s* }}`), escaper('VERT').repeat(2)) 181 | .replace(re(r`{{ \s* = \s* }}`), escaper('EQUALS')) 182 | .replace(re(r`{{ \s* [Rr]eflist \s* }}`), '') 183 | 184 | // Metadata: displayTitle, __NOTOC__, etc 185 | .replace(re(r`{{ \s* displayTitle: ([^}]+) }}`), (_, title) => (metadata.displayTitle = title, '')) 186 | .replace(re(r`__NOINDEX__`), () => (metadata.noindex = true, '')) 187 | .replace(re(r`__NOTOC__`), () => (metadata.notoc = true, '')) 188 | .replace(re(r`__FORCETOC__`), () => (metadata.toc = true, '')) 189 | .replace(re(r`__TOC__`), () => (metadata.toc = true, ``)) 190 | 191 | // String functions: {{lc:}}, {{ucfirst:}}, {{len:}}, etc 192 | .replace(re(r`{{ \s* #? urlencode: ${arg} }}`), (_, m) => encodeURI(m)) 193 | .replace(re(r`{{ \s* #? urldecode: ${arg} }}`), (_, m) => decodeURI(m)) 194 | .replace(re(r`{{ \s* #? lc: ${arg} }}`), (_, m) => m.toLowerCase()) 195 | .replace(re(r`{{ \s* #? uc: ${arg} }}`), (_, m) => m.toUpperCase()) 196 | .replace(re(r`{{ \s* #? lcfirst: ${arg} }}`), (_, m) => m[0].toLowerCase() + m.substr(1)) 197 | .replace(re(r`{{ \s* #? ucfirst: ${arg} }}`), (_, m) => m[0].toUpperCase() + m.substr(1)) 198 | .replace(re(r`{{ \s* #? len: ${arg} }}`), (_, m) => m.length) 199 | .replace(re(r`{{ \s* #? pos: ${arg} \|${arg} (?: \s*\|${arg} )? }}`), (_, find, str, n = 0) => find.substr(n).indexOf(str)) 200 | .replace(re(r`{{ \s* #? sub: ${arg} \|${arg} (?:\|${arg})? }}`), (_, str, from, len) => str.substr(+from - 1, +len)) 201 | .replace(re(r`{{ \s* #? padleft: ${arg} \|${arg} \|${arg} }}`), (_, str, n, char) => str.padStart(+n, char)) 202 | .replace(re(r`{{ \s* #? padright: ${arg} \|${arg} \|${arg} }}`), (_, str, n, char) => str.padEnd(+n, char)) 203 | .replace(re(r`{{ \s* #? replace: ${arg} \|${arg} \|${arg} }}`), (_, str, find, rep) => str.split(find).join(rep)) 204 | .replace(re(r`{{ \s* #? explode: ${arg} \|${arg} \|${arg} }}`), (_, str, delim, pos) => str.split(delim)[+pos]) 205 | 206 | // Magic functions: {{#ev:youtube}}, etc 207 | .replace(re(r`{{ \s* #? ev: \s* (\w+) \s* \| \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, platform: string, args: string) => { 208 | // See mediawiki.org/wiki/Extension:EmbedVideo_(fork) for docs 209 | const params = args.split('|'); 210 | for (let i = 0; i < 10; i++) params[i] ??= ''; // fill up with empty strings 211 | 212 | const [id, dimensions, alignment, description, container, urlargs, autoresize] = params; 213 | const { width, height } = parseDimensions(dimensions); 214 | const source = { 215 | // Add platforms 216 | 'youtube': `//www.youtube.com/embed/${id}`, 217 | 'vimeo': `//player.vimeo.com/video/${id}`, 218 | }[platform]; 219 | if (!source) return `Failed to load video ${id} from ${platform}.`; 220 | return ` 221 | 232 | ${description ? `
    ${description}
    ` : ''} 233 | `; 234 | }) 235 | 236 | // Parser functions: {{#if:}}, {{#switch:}}, etc 237 | .replace(re(r`{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => { 238 | if (content.includes('{{')) return _; 239 | 240 | const args: string[] = content.trim().split(/\s*\|\s*/); 241 | switch (name) { 242 | case '#if': 243 | return (args[0] ? args[1] : args[2]) || ''; 244 | case '#ifeq': 245 | return (args[0] === args[1] ? args[2] : args[3]) || ''; 246 | case '#vardefine': 247 | vars[args[0]] = args[1] || ''; 248 | return ''; 249 | case '#var': 250 | if (re(r`{{ \s* #vardefine \s* : \s* ${args[0]}`).test(outText)) return _; // wait until var is set 251 | return vars[args[0]] || args[1] || ''; 252 | case '#switch': 253 | return args.slice(1) 254 | .map(arg => arg.split(/\s*=\s*/)) 255 | .filter(duo => args[0] === duo[0].replace('#default', args[0]))[0][1]; 256 | case '#time': 257 | case '#date': 258 | case '#datetime': 259 | // make sure the characters are not inside a string 260 | let parsedMatch = args[0].replace(/".+?"/g, '').replace(/'.+?'/g, ''); 261 | if (/[abcefgijkqruvx]/i.test(parsedMatch)) { 262 | const errMsg = `Wikity does not use Wikipedia's #time function syntax. Use repetition-based formatting (e.g. yyyy-mm-dd) instead.`; 263 | console.warn(` [WARN] ${errMsg}`); 264 | } 265 | try { 266 | return dateFormat(args[1] ? new Date(args[1]) : new Date(), args[0]); 267 | } 268 | catch { 269 | return args[1] || args[0]; 270 | } 271 | } 272 | }) 273 | 274 | // Templates: {{template}} 275 | .replace(re(r`(? { 276 | if (params.includes('{{')) return _; 277 | 278 | const templateFile = toFileText(title); 279 | const page: string = paths.join(templatesFolder, templateFile); 280 | let content = ''; 281 | // Try retrieve template content 282 | try { 283 | content = fs.readFileSync(page + '.wiki', { encoding: 'utf8' }) 284 | } 285 | catch { 286 | // Return redlink if template doesn't exist 287 | const relPage = paths.basename(templatesFolder) + '/' + paths.relative(templatesFolder, page); 288 | return `${title}`; 289 | } 290 | 291 | // Remove non-template sections 292 | content = content.trim() 293 | .replace(/.*?<\/noinclude>/gs, '') 294 | .replace(/.*<(includeonly|onlyinclude)>|<\/(includeonly|onlyinclude)>.*/gs, '') 295 | 296 | // Substitute arguments 297 | const argMatch = (arg: string): RegExp => re(r`{{{ \s* ${arg} (?:\|([^}]*))? \s* }}}`); 298 | const args: string[] = params.split('|'); 299 | // parse provided key=value template arguments 300 | for (let i = 1; i < args.length; i++) { 301 | const data = args[i]; 302 | const parts = data.split('='); 303 | const isNamed = parts.length > 1; 304 | const arg = isNamed ? parts[0] : i.toString(); 305 | const val = isNamed ? parts.slice(1).join('=') : data; 306 | content = content.replace(argMatch(arg), (_, defaultVal) => (val || defaultVal || '').trim()); 307 | } 308 | 309 | return content; 310 | }) 311 | 312 | // Unparsed arguments 313 | .replace(re(r`{{{ \s* [^{}|]+? (?:\|([^}]*))? \s* }}}`), (_, defaultVal) => { 314 | return defaultVal ?? ''; 315 | }) 316 | 317 | // Markup: '''bold''' and '''italic''' 318 | .replace(re(r`''' ([^']+?) '''`), '$1') 319 | .replace(re(r`'' ([^']+?) ''`), '$1') 320 | 321 | // Headings: ==heading== 322 | .replace(re(r`^ (=+) \s* (.+?) \s* \1 \s* $`), (_, lvl, txt) => { 323 | const linkForm = encodeURI(txt.replace(/ /g, '_').replace(/<.+?>/g, '')); 324 | return `${txt}`; 325 | }) 326 | 327 | // Bulleted list: *item 328 | .replace(re(r`^ (\*+) (.+?) $`), (_, lvl, content) => { 329 | if (content.includes('{{')) return _; 330 | const depth = lvl.length; 331 | return `${'
      '.repeat(depth)}
    • ${content}
    • ${'
    '.repeat(depth)}`; 332 | }) 333 | .replace(re(r` (\s*?)
      `), '$1') 334 | 335 | // Numbered list: #item 336 | .replace(re(r`^ (#+) (.+?) $`), (_, lvl, content) => { 337 | if (content.includes('{{')) return _; 338 | const depth = lvl.length; 339 | return `${'
        '.repeat(depth)}
      1. ${content}
      2. ${'
      '.repeat(depth)}`; 340 | }) 341 | .replace(re(r`
(\s*?)
    `), '$1') 342 | 343 | // Definition list: ;head, :item 344 | .replace(re(r`^ ; (.+?) : (.+?) $`), `
    $1
    $2
    `) 345 | .replace(re(r`^ (:+) (.+?) $`), (_, lvl, content) => { 346 | if (content.includes('{{')) return _; 347 | const depth = lvl.length; 348 | return `${'
    '.repeat(depth)}
    ${content}
    ${'
    '.repeat(depth)}`; 349 | }) 350 | .replace(re(r`^ ; (.+) $`), '
    $1
    ') 351 | .replace(re(r` (\s*?)
    `), '$1') 352 | 353 | // Tables: {|, |+, !, |-, |, |} 354 | .replace(/\{\|.+\|\}/gs, (tableInner) => { 355 | if (tableInner.includes('{{')) return tableInner; 356 | return tableInner 357 | // {| data (open table) 358 | .replace(re(r`^ \{\| (.*?) $`), (_, attrs) => ``) 359 | // |+ data 360 | .replace(re(r`^ \|\+ (.*?) $`), (_, content) => ``) 361 | // |- (new row) 362 | .replace(re(r`^ \|- (.*?) $`), (_, attrs) => ``) 363 | // |} (close) 364 | .replace(re(r`^ \|\}`), `
    ${content}
    `) 365 | // content: !head, !data|head, |text, |data|text, !!head, ||data 366 | .replace(re(r`( ^! | ^\| | !! | \|\| ) (?: ( [^|\n]+? ) \|)? ( [^|\n]*? ) (?= $ | !! | \|\| )`), (_, type, data, content) => { 367 | const elem = /!/.test(type) ? 'th' : 'td'; 368 | return `<${elem} ${data ?? ''}>${content}`; 369 | }) 370 | }) 371 | 372 | // References: 373 | .replace(re(r`< ref \s* (?: name \s* = \s* ["']? ([^>'"]+) ["']? [^>]* )?> (.+?) `), (_, refname, text) => { 374 | if (_.includes('{{')) return _; 375 | 376 | const refData = { ref: text, id: refs.length + 1, name: refname, i: 0 }; 377 | refs.push(refData); 378 | return `[${refData.id}]`; 379 | }) 380 | .replace(re(r`< ref \s* name \s* = \s* ["']? ( [^>"']+ ) ["']? \s* (?: /> | > .*? )`), (_, refname) => { 381 | const ref = refs.find(ref => ref.name === refname); 382 | if (!ref) return _; 383 | ref.i++; 384 | const citeId = `cite-${ref.id}${ref.i > 0 ? `_${ref.i}` : ''}` 385 | return `[${ref.id}]`; 386 | }) 387 | 388 | // Nonstandard: ``code`` and ```code blocks``` 389 | .replace(re(r` \`\`\` ([^\`]+?) \`\`\` `), '
    $1
    ') 390 | .replace(re(r` \`\` ([^\`]+?) \`\` `), '$1') 391 | 392 | // Spacing 393 | .replace(/(\r?\n){2}/g, '\n

    \n') 394 | 395 | } 396 | 397 | // Final (one-time) substitutions 398 | 399 | for (let i = 0; i < nowikis.length; i++) { 400 | outText = outText 401 | // Restore nowiki contents 402 | .replace(escaper('NOWIKI', i), nowikis[i]) 403 | } 404 | outText = outText 405 | // References: 406 | .replace(re(r``), () => { 407 | const references = refs.map((refdata) => { 408 | let multiRefContent = ''; 409 | if (refdata.i > 0) { 410 | for (let i = 0; i <= refdata.i; i++) { 411 | multiRefContent += `${i + 1} `; 412 | } 413 | } 414 | const refline = ` 415 |

  1. 416 | ${refdata.i > 0 ? '↑' : ``} 417 | ${multiRefContent} 418 | ${refdata.ref} 419 |
  2. 420 | `; 421 | return refline; 422 | }).join('\n'); 423 | return `
      ${references}
    `; 424 | }) 425 | // Magic word functions 426 | .replaceAll(escaper('VERT'), '|') 427 | .replaceAll(escaper('EQUALS'), '=') 428 | 429 | // Escape all {{ to avoid crashes 430 | outText = outText 431 | .replaceAll('{{', '{{') 432 | 433 | const result: Result = { data: outText, metadata: metadata }; 434 | return result; 435 | } 436 | -------------------------------------------------------------------------------- /src/wiki.css.ts: -------------------------------------------------------------------------------- 1 | export default String.raw` 2 | body {font-family: sans-serif; margin: auto; max-width: 1000px; background: #eee;} 3 | main {margin: 3em -1em; background: #fff; padding: 1em;} 4 | h1, h2 {margin-bottom: 0.6em; font-weight: normal; border-bottom: 1px solid #a2a9b1;} 5 | ul, ol {margin: 0.3em 0 0 1.6em; padding: 0;} 6 | dt {font-weight: bold;} 7 | dd, dl dl {margin-block: 0; margin-inline-start: 30px;} 8 | 9 | figure {margin: 1em; width: 300px;} 10 | .image-thumb, .image-frame {padding: 6px; border: 1px solid gray;} 11 | .image-default {margin: 0;} 12 | figcaption {padding-top: 6px;} 13 | 14 | table.wikitable {border-collapse: collapse;} 15 | table.wikitable, table.wikitable th, table.wikitable td {border: 1px solid gray; padding: 6px;} 16 | table.wikitable th {background-color: #eaecf0; text-align: center;} 17 | 18 | a:not(:hover) {text-decoration: none;} 19 | a.internal-link {color: #04a;} 20 | a.internal-link:visited {color: #26d;} 21 | a.external-link {color: #36b;} 22 | a.external-link:visited {color: #58d;} 23 | a.external-link::after {content: '\1f855';} 24 | a.redlink {color: #d33;} 25 | a.redlink:visited {color: #b44;} 26 | 27 | #page_toc {border: 1px solid #aab; padding: 8px; width: fit-content; background-color: #f8f8f8; font-size: 95%;} 28 | #page_toc_heading {display: block; text-align: center;} 29 | #page_toc ol {margin: 0 0 0 1.3em;} 30 | 31 | #infobox {float: right; clear: right; margin: 0 0 1em 1em; width: 300px; padding: 2px; border: 1px solid #CCC; overflow: auto; font-size: 90%;} 32 | #infobox tr:first-child :first-child {padding: 10px; text-align: center; font-weight: bold; font-size: 120%;} 33 | #infobox th {padding-left: 10px; text-align: left;} 34 | ` 35 | -------------------------------------------------------------------------------- /test/.eleventy.js: -------------------------------------------------------------------------------- 1 | const wikity = require('../dist/index.js') 2 | 3 | module.exports = function (eleventyConfig) { 4 | const rootFolder = 'src'; 5 | const options = { eleventy: true }; 6 | eleventyConfig.addPlugin(() => wikity.compile(rootFolder, options)); 7 | eleventyConfig.addPassthroughCopy({ [`${rootFolder}/images/`]: 'wiki/images' }); 8 | } 9 | -------------------------------------------------------------------------------- /test/src/example.wiki: -------------------------------------------------------------------------------- 1 | {{displaytitle: Example Page}} 2 |

    Example

    3 | '''Wikity''' is a ''very'' useful build tool! 4 | 5 |
    6 | = Headings = 7 | = Root = 8 | == Heading == 9 | === Subheading === 10 | === A [[Link Heading]] === 11 | == Broken heading === 12 | ====== Small heading ====== 13 | == ''fancy'' '''heading'''! == 14 |
    15 | 16 |
    17 | = TOC = 18 | __TOC__ 19 | 20 |
    21 | = Formatting = 22 | ''Italic'' 23 | '''Bold''' 24 | '''''Bold Italic''''' 25 |
    26 | 27 |
    28 | = List = 29 | * Bulleted list 30 | * Bulleted list item 2 31 | ** Sub-item 32 | *** Indented 33 | * Root item 34 | # Numbered list 35 | # Numbered list item 2 36 | ## Sub-item 37 | ### Indented 38 | # Root item 39 | ;Pseudoheader 40 | : Root indent 41 | :: Subindent 42 | ::: Indented 43 |
    44 | 45 |
    46 | = HorizontalRule = 47 | ----- 48 |
    49 | 50 |
    51 | = InternalLink = 52 | [[Link]] 53 | [[Link|Text]] 54 |
    55 | 56 |
    57 | = FileEmbed = 58 | [[File:Example.png]] 59 | [[File:Example.png|48px]] 60 | [[File:Example.png|48x50px]] 61 | [[File:Example.png|x10px]] 62 | [[File:Example.png|alt=Example]] 63 | [[File:Example.png|200px|thumb|right|alt=Alt text|Example text]] 64 | [[File:Example.png|100px|link=]] 65 |
    66 | 67 |
    68 | = ExternalLink = 69 | [https://github.com] 70 | [https://google.com External link] 71 | [not a link] 72 |
    73 | 74 |
    75 | = TemplateCall = 76 | * {{subst}} = '''Substituted text: Null!'''; Unset: (empty); Named: (empty) 77 | * {{subst|Hello!}} = '''Substituted text: Hello!!'''; Unset: (empty); Named: (empty) 78 | * {{subst|1=Hello!}} = '''Substituted text: Hello!!'''; Unset: (empty); Named: (empty) 79 | * {{subst|1= {{plain|text}} }} = '''Substituted text: plain=text!!'''; Unset: (empty); Named: (empty) 80 | * {{subst|named=Named}} = '''Substituted text: Null!'''; Unset: (empty); Named: Named 81 | * {{subst|named= 82 | This [[object]] and this ''[[Item|item]]'' are not coloured! 83 | }} = '''Substituted text: Null!'''; Unset: (empty); Named: This [[object]] and this ''[[Item|item]]'' are not coloured! 84 | {{nonexistent template}} 85 |
    86 | 87 |
    88 | = References = 89 | 90 | Text. 91 | Simple reference 92 | Named reference 93 | 94 | 95 | ; References 96 | {{reflist}} 97 | 98 |
    99 | 100 |
    101 | = HTML = 102 | 103 |
    104 | Code 105 |
    106 | 107 |
    108 | = CodeBock = 109 | ``code`` 110 | ``` 111 | code block 112 | ``` 113 |
    114 |
    115 | 116 |
    117 | = Table = 118 | {| class="wikitable" 119 | |+ Caption 120 | |- 121 | ! Head1 122 | ! Head2 !! Head3 123 | ! color=red | Head4 124 | |- class="row" 125 | | Cell1 126 | | Cell2 || Cell3 127 | | Cell4 128 | |- 129 | | Inline1 || Inline2 130 | ! End1 || End2 131 | |} 132 |
    133 | 134 |
    135 | = Nowiki = 136 | 137 | Non-parsed content & tags. 138 | {{#vardefine: varname | var text }} 139 |
    140 |
    141 |
    142 | 143 |
    144 | = TextFunctions = 145 | Each side of the = must match. 146 | * {{#lc:TEXT}} = text 147 | * {{#ucfirst:text}} = Text 148 | * {{#len:12345}} = 5 149 | * {{#sub:string|2|4}} = ring 150 | * {{#pos:text|x}} = 2 151 | * {{#padleft:text|5|_}} = _text 152 | * {{#padright:msg|5|_}} = msg__ 153 | * {{#replace:Message|e|3}} = M3ssag3 154 | * {{#explode:A-B-C-D|-|2}} = C 155 | * {{#urlencode:t e x t}} = t%20e%20x%20t 156 | * {{#urldecode:a%20b%27c}} = a b'c 157 |
    158 | 159 |
    160 | = MagicFunctions = 161 | {{#unknown:foo}} 162 | {{#ev:youtube|dQw4w9WgXcQ}} 163 | {{#ev:youtube|dQw4w9WgXcQ|600px|left|Important video.}} 164 |
    165 |
    166 | 167 |
    168 | = ParserFunctions = 169 | Each side of the must match. 170 | * {{#if: true | true text | false text }} → true text 171 | * {{#if: {{{variable|}}} | true text | false text }} → false text 172 | * {{#if: {{{image|}}} 173 | | Image: [[File:{{{image|}}}|48px|link={{{link|}}}]] 174 | | None 175 | }} = None 176 | * {{#if: {{{image|Example.png}}} 177 | | Image: [[File:{{{image|Example.png}}}|48px|link={{{link|}}}]] 178 | | None 179 | }} = [Example.png] 180 | * {{ #vardefine: test | value }} → (empty: variable defined) 181 | * [[{{ #var:test }}]] → [[value]] 182 | * "{{#var: unset | this {{=}} unset }}" → "this = unset" 183 | * {{#switch: value 184 | | nope = none 185 | | val = val! 186 | | #default = one 187 | }} → one 188 | * {{#date: yyyy-mm-dd (ddd) 'm' | 27 Mar 21}} → [today's date] m 189 | * {{#vardefine: nested | {{#var: null | nil}} {{#vardefine: x | 1}} {{=}}.}} → (empty: variable defined) 190 | * {{#var: nested}} → nil =. 191 |
    192 | 193 | {{Footer}} 194 | -------------------------------------------------------------------------------- /test/src/images/Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nixinova/Wikity/96b079b5701a58031a81d30b2552929f910fe0be/test/src/images/Example.png -------------------------------------------------------------------------------- /test/src/templates/plain.wiki: -------------------------------------------------------------------------------- 1 | plain={{{1}}} 2 | -------------------------------------------------------------------------------- /test/src/templates/subst.wiki: -------------------------------------------------------------------------------- 1 | { '''Substituted text: {{{1|Null}}}!'''; Unset: {{{__unset__}}}; Named: {{{named}}} } 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "dist/", 4 | "build/" 5 | ], 6 | 7 | "compilerOptions": { 8 | /* More info: https://aka.ms/tsconfig.json */ 9 | /* Examples: https://github.com/tsconfig/bases */ 10 | 11 | /* Basic Options */ 12 | "target": "es2019", // Node 12 13 | "module": "commonjs", 14 | "lib": [ 15 | "es2021" 16 | ], 17 | //"allowJs": true, 18 | //"checkJs": true, 19 | //"jsx": "preserve", 20 | "declaration": true, 21 | //"declarationMap": true, 22 | //"sourceMap": true, 23 | //"outFile": "./", 24 | "outDir": "./dist/", 25 | //"rootDir": "./", 26 | //"composite": true, 27 | //"removeComments": true, 28 | //"noEmit": true, 29 | //"importHelpers": true, 30 | //"downlevelIteration": true, 31 | //"isolatedModules": true, 32 | 33 | /* Type-Checking */ 34 | "strict": true, 35 | "noImplicitAny": true, 36 | "strictNullChecks": true, 37 | "strictFunctionTypes": true, 38 | //"strictBindCallApply": true, 39 | "strictPropertyInitialization": true, 40 | //"noImplicitThis": true, 41 | "useUnknownInCatchVariables": true, 42 | "alwaysStrict": true, 43 | //"noUnusedLocals": true, 44 | "noUnusedParameters": true, 45 | "noImplicitReturns": true, 46 | //"noFallthroughCasesInSwitch": true, 47 | //"noUncheckedIndexedAccess": true, 48 | //"noPropertyAccessFromIndexSignature": true, 49 | 50 | /* Module Resolution Options */ 51 | "moduleResolution": "node", 52 | "resolveJsonModule": true, 53 | //"baseUrl": "./", 54 | //"paths": {}, 55 | //"rootDirs": [], 56 | //"typeRoots": [], 57 | //"types": [], 58 | //"allowSyntheticDefaultImports": true, 59 | "esModuleInterop": true, 60 | //"preserveSymlinks": true, 61 | //"allowUmdGlobalAccess": true, 62 | 63 | /* Advanced Options */ 64 | "skipLibCheck": true, 65 | //"forceConsistentCasingInFileNames": true 66 | } 67 | } 68 | --------------------------------------------------------------------------------