wikity 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/compile.js CHANGED
@@ -58,15 +58,15 @@ function compile(dir = '.', config = {}) {
58
58
  toc += `${`<ol>`.repeat(lvl - 1)} <li> <a href="#${encodeURI(text.replace(/ /g, '_'))}">${text}</a> </li> ${`</ol>`.repeat(lvl - 1)}`;
59
59
  });
60
60
  const tocElem = (0, dedent_1.default) `
61
- <div id="toc">
62
- <span id="toc-heading">
61
+ <div id="page+toc">
62
+ <span id="page+toc+heading">
63
63
  <strong>Contents</strong>
64
64
  [<a href="javascript:void(0)" onclick="
65
- document.querySelector('#toc ol').setAttribute('style', this.innerText === 'hide' ? 'display: none;' : '');
65
+ document.getElementById('page+toc+contents').setAttribute('style', this.innerText === 'hide' ? 'display: none;' : '');
66
66
  this.innerText = this.innerText === 'hide' ? 'show' : 'hide';
67
67
  ">hide</a>]
68
68
  </span>
69
- <ol>${toc}</ol>
69
+ <ol id="page+toc+contents">${toc}</ol>
70
70
  </div>
71
71
  `;
72
72
  // Set TOC on page
@@ -106,7 +106,7 @@ function compile(dir = '.', config = {}) {
106
106
  if (!fs_1.default.existsSync(path_1.default.dirname(outFilePath))) {
107
107
  fs_1.default.mkdirSync(path_1.default.dirname(outFilePath));
108
108
  }
109
- const renderedHtml = formatter.render(html).replace(/(<\/\w+>)(\S)/g, '$1 $2');
109
+ const renderedHtml = formatter.render(html.replace(/(<\/\w+>) +(\S)/g, '$1&#32;$2'));
110
110
  fs_1.default.writeFileSync(outFilePath, frontMatter + '\n' + renderedHtml, 'utf8');
111
111
  // Move images
112
112
  (0, glob_1.default)(imagesFolder + '/*', {}, (err, files) => {
@@ -131,12 +131,7 @@ function compile(dir = '.', config = {}) {
131
131
  if (config.customStyles) {
132
132
  styles += config.customStyles;
133
133
  }
134
- const cssOutput = config.eleventy ? (0, dedent_1.default) `
135
- ---
136
- permalink: /wiki.css
137
- ---
138
- ${styles}
139
- ` : styles;
134
+ const cssOutput = config.eleventy ? ['---', 'permalink: /wiki.css', '---', styles].join('\n') : styles;
140
135
  const cssOutFilename = config.eleventy ? 'wiki.css.njk' : 'wiki.css';
141
136
  fs_1.default.writeFileSync(path_1.default.join(outputFolder, cssOutFilename), cssOutput);
142
137
  }
package/dist/parse.js CHANGED
@@ -6,22 +6,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.parse = exports.rawParse = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
- const escape_html_1 = __importDefault(require("escape-html"));
10
9
  const dateformat_1 = __importDefault(require("dateformat"));
11
10
  const common_1 = require("./common");
12
11
  const r = String.raw;
13
12
  const MAX_RECURSION = 20;
14
13
  const arg = r `\s*([^|}]+?)\s*`;
15
- function fullyEscape(text) {
16
- return (0, escape_html_1.default)(text)
17
- .replace(/{/g, '&#123;'); // avoid keeping plain {{}} which is parsed as a template call
14
+ function cleanLink(link) {
15
+ return encodeURI(link.replace(/ /g, '_'));
18
16
  }
17
+ function parseDimensions(dimStr) {
18
+ const regex = /(\d*)(?:x(\d*))?px/;
19
+ const match = dimStr.match(regex);
20
+ if (!match)
21
+ return { width: 'auto', height: 'auto' };
22
+ const [, width, height] = match;
23
+ return {
24
+ width: width || 'auto',
25
+ height: height || 'auto',
26
+ };
27
+ }
28
+ const escaper = (text, n = 0) => `%${text}#${n}`;
19
29
  function rawParse(data, config = {}) {
20
30
  return parse(data, config).data;
21
31
  }
22
32
  exports.rawParse = rawParse;
23
33
  function parse(data, config = {}) {
24
34
  var _a, _b, _c;
35
+ const KEY = Math.random().toString().slice(2); // key used to allow certain disallowed HTML elements
25
36
  const templatesFolder = (_a = config.templatesFolder) !== null && _a !== void 0 ? _a : 'templates';
26
37
  const imagesFolder = (_b = config.imagesFolder) !== null && _b !== void 0 ? _b : 'images';
27
38
  const outputFolder = (_c = config.outputFolder) !== null && _c !== void 0 ? _c : 'wikity-out';
@@ -31,7 +42,6 @@ function parse(data, config = {}) {
31
42
  const refs = [];
32
43
  let nowikiCount = 0;
33
44
  let rawExtLinkCount = 0;
34
- let refCount = 0;
35
45
  let outText = data;
36
46
  for (let l = 0, last = ''; l < MAX_RECURSION; l++) {
37
47
  if (last === outText)
@@ -39,24 +49,122 @@ function parse(data, config = {}) {
39
49
  last = outText;
40
50
  outText = outText
41
51
  // Nowiki: <nowiki></nowiki>
42
- .replace((0, common_1.RegExpBuilder)(r `<nowiki> ([^]+?) </nowiki>`), (_, m) => `%NOWIKI#${nowikis.push(m), nowikiCount++}%`)
52
+ .replace((0, common_1.RegExpBuilder)(r `<nowiki> ([^]+?) </nowiki>`), (_, m) => (nowikis.push(m), escaper('NOWIKI', nowikiCount++)))
43
53
  // Sanitise unacceptable HTML
44
- .replace((0, common_1.RegExpBuilder)(r `<(/?) \s* (?= script|link|meta|iframe|frameset|object|embed|applet|form|input|button|textarea )`), '&lt;$1')
54
+ .replace((0, common_1.RegExpBuilder)(r `< \s* (?= (?: script|link|meta|iframe|frameset|object|embed|applet|form|input|button|textarea ) (?! \s* key.{0,10}${KEY}) )`), '&lt;')
45
55
  .replace((0, common_1.RegExpBuilder)(r `(?<= <[^>]+ ) (\bon(\w+))`), 'data-$2')
46
56
  // Comments: <!-- -->
47
57
  .replace(/<!--[^]+?-->/g, '')
48
58
  // Lines: ----
49
59
  .replace(/^-{4,}/gm, '<hr>')
60
+ // Images: [[File:Image.png|options|caption]]
61
+ .replace((0, common_1.RegExpBuilder)(r `\[\[ (?:File|Image): (.*?) (\|.+?)? \]\]`), (_, file, params = '') => {
62
+ if (params.includes('{{'))
63
+ return _;
64
+ if (!file)
65
+ return '';
66
+ const path = path_1.default.join(imagesFolder, file.trim().replace(/ /g, '_'));
67
+ let caption = '';
68
+ let imageData = {};
69
+ let imageArgs = params.split('|').map((arg) => arg.replace(/"/g, '&quot;'));
70
+ for (const param of imageArgs) {
71
+ if (['left', 'right', 'center', 'none'].includes(param)) {
72
+ imageData.float = param;
73
+ }
74
+ if (['baseline', 'sub', 'super', 'top', 'text-bottom', 'middle', 'bottom', 'text-bottom'].includes(param)) {
75
+ imageData.align = param;
76
+ }
77
+ else if (['border', 'frameless', 'frame', 'framed', 'thumb', 'thumbnail'].includes(param)) {
78
+ imageData.type = { framed: 'frame', thumbnail: 'thumb' }[param] || param;
79
+ if (imageData.type === 'thumb') {
80
+ imageData.hasCaption = true;
81
+ }
82
+ }
83
+ else if (param.endsWith('px')) {
84
+ const { width, height } = parseDimensions(param);
85
+ imageData.width = width;
86
+ imageData.height = height;
87
+ }
88
+ else if (param.startsWith('upright=')) {
89
+ imageData.width = +param.replace('upright=', '') * 300;
90
+ }
91
+ else if (param.startsWith('link=')) {
92
+ imageData.link = param.replace('link=', '');
93
+ }
94
+ else if (param.startsWith('alt=')) {
95
+ imageData.alt = param.replace('alt=', '');
96
+ }
97
+ else if (param.startsWith('style=')) {
98
+ imageData.style = param.replace('style=', '');
99
+ }
100
+ else if (param.startsWith('class=')) {
101
+ imageData.class = param.replace('class=', '');
102
+ }
103
+ else {
104
+ caption = param;
105
+ }
106
+ }
107
+ let content = `
108
+ <figure
109
+ class="
110
+ ${imageData.class || ''}
111
+ image-container
112
+ image-${imageData.type || 'default'}
113
+ "
114
+ style="
115
+ display: inline-block;
116
+ float: ${imageData.float || 'none'};
117
+ vertical-align: ${imageData.align || 'unset'};
118
+ ${imageData.style || ''}
119
+ "
120
+ >
121
+ <img
122
+ src="${path_1.default.basename(imagesFolder)}/${path_1.default.relative(imagesFolder, path)}"
123
+ alt="${imageData.alt || file}"
124
+ width="${imageData.width || 300}"
125
+ height="${imageData.height || 300}"
126
+ >
127
+ ${imageData.hasCaption ? `<figcaption>${caption}</figcaption>` : ''}
128
+ </figure>
129
+ `;
130
+ const imageLink = imageData.link;
131
+ if (imageLink) {
132
+ content = `<a href="${cleanLink(imageLink)}" title="${imageLink}">${content}</a>`;
133
+ }
134
+ return content;
135
+ })
136
+ // Internal links: [[Page]] and [[Page|Text]]
137
+ .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \]\]`), (_, link) => {
138
+ if (_.includes('{{'))
139
+ return _;
140
+ const content = `<a class="internal-link" title="${link}" href="./${cleanLink(link)}">${link}</a>`;
141
+ return content;
142
+ })
143
+ .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \| ([^\]]+?) \]\]`), (_, link, text) => {
144
+ if (link.includes('{{'))
145
+ return _;
146
+ const content = `<a class="internal-link" title="${link}" href="./${cleanLink(link)}">${text}</a>`;
147
+ return content;
148
+ })
149
+ .replace((0, common_1.RegExpBuilder)(r `(</a>)([a-z]+)`), '$2$1')
150
+ // External links: [href Page] and just [href]
151
+ .replace((0, common_1.RegExpBuilder)(r `\[ ((?:\w+:)?\/\/ [^\s\]]+) (\s [^\]]+?)? \]`), (_, href, txt) => {
152
+ if (_.includes('{{'))
153
+ return _;
154
+ const content = `<a class="external-link" href="${href}">${txt || '[' + (++rawExtLinkCount) + ']'}</a>`;
155
+ return content;
156
+ })
157
+ // Magic words: {{!}}, {{reflist}}, etc
158
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* ! \s* }}`), escaper('VERT'))
159
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* !! \s* }}`), escaper('VERT').repeat(2))
160
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* = \s* }}`), escaper('EQUALS'))
161
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* [Rr]eflist \s* }}`), '<references/>')
50
162
  // Metadata: displayTitle, __NOTOC__, etc
51
163
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* displayTitle: ([^}]+) }}`), (_, title) => (metadata.displayTitle = title, ''))
52
164
  .replace((0, common_1.RegExpBuilder)(r `__NOINDEX__`), () => (metadata.noindex = true, ''))
53
165
  .replace((0, common_1.RegExpBuilder)(r `__NOTOC__`), () => (metadata.notoc = true, ''))
54
166
  .replace((0, common_1.RegExpBuilder)(r `__FORCETOC__`), () => (metadata.toc = true, ''))
55
167
  .replace((0, common_1.RegExpBuilder)(r `__TOC__`), () => (metadata.toc = true, `<toc></toc>`))
56
- // Magic words: {{!}}, {{reflist}}, etc
57
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* ! \s* }}`), '&vert;')
58
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* = \s* }}`), '&equals;')
59
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* [Rr]eflist \s* }}`), '<references/>')
60
168
  // String functions: {{lc:}}, {{ucfirst:}}, {{len:}}, etc
61
169
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? urlencode: ${arg} }}`), (_, m) => encodeURI(m))
62
170
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? urldecode: ${arg} }}`), (_, m) => decodeURI(m))
@@ -71,9 +179,40 @@ function parse(data, config = {}) {
71
179
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? padright: ${arg} \|${arg} \|${arg} }}`), (_, str, n, char) => str.padEnd(+n, char))
72
180
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? replace: ${arg} \|${arg} \|${arg} }}`), (_, str, find, rep) => str.split(find).join(rep))
73
181
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? explode: ${arg} \|${arg} \|${arg} }}`), (_, str, delim, pos) => str.split(delim)[+pos])
182
+ // Magic functions: {{#ev:youtube}}, etc
183
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? ev: \s* (\w+) \s* \| \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, platform, args) => {
184
+ var _a;
185
+ // See mediawiki.org/wiki/Extension:EmbedVideo_(fork) for docs
186
+ const params = args.split('|');
187
+ for (let i = 0; i < 10; i++)
188
+ (_a = params[i]) !== null && _a !== void 0 ? _a : (params[i] = ''); // fill up with empty strings
189
+ const [id, dimensions, alignment, description, container, urlargs, autoresize] = params;
190
+ const { width, height } = parseDimensions(dimensions);
191
+ const source = {
192
+ // Add platforms
193
+ 'youtube': `//www.youtube.com/embed/${id}`,
194
+ 'vimeo': `//player.vimeo.com/video/${id}`,
195
+ }[platform];
196
+ if (!source)
197
+ return `<code>Failed to load video ${id} from ${platform}.</code>`;
198
+ return `
199
+ <iframe key="${KEY}"
200
+ src="${source}"
201
+ width="${width}"
202
+ height="${height}"
203
+ frameborder="0"
204
+ allowfullscreen="true"
205
+ loading="lazy"
206
+ title="${description !== null && description !== void 0 ? description : 'Play video'}"
207
+ ${alignment ? `style="float: ${alignment};"` : ''}
208
+ >
209
+ </iframe>
210
+ ${description ? `<figcaption>${description}</figcaption>` : ''}
211
+ `;
212
+ })
74
213
  // Parser functions: {{#if:}}, {{#switch:}}, etc
75
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => {
76
- if (/{{\s*#/.test(content))
214
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => {
215
+ if (content.includes('{{'))
77
216
  return _;
78
217
  const args = content.trim().split(/\s*\|\s*/);
79
218
  switch (name) {
@@ -105,8 +244,11 @@ function parse(data, config = {}) {
105
244
  }
106
245
  })
107
246
  // Templates: {{template}}
108
- .replace((0, common_1.RegExpBuilder)(r `(?<!{) {{ \s* ([^#{}|]+?) (\|[^{}]+)? }} (?!})`), (_, title, params = '') => {
109
- const page = path_1.default.join(templatesFolder, title.trim().replace(/ /g, '_'));
247
+ .replace((0, common_1.RegExpBuilder)(r `(?<!{) {{ \s* ([^#{}|]+?) \s* (\|[^{}]+)? }} (?!})`), (_, title, params = '') => {
248
+ if (params.includes('{{'))
249
+ return _;
250
+ const templateFile = title.trim().replace(/ /g, '_');
251
+ const page = path_1.default.join(templatesFolder, templateFile);
110
252
  let content = '';
111
253
  // Try retrieve template content
112
254
  try {
@@ -118,153 +260,116 @@ function parse(data, config = {}) {
118
260
  return `<a class="internal-link redlink" title="${title}" href="${relPage}">${title}</a>`;
119
261
  }
120
262
  // Remove non-template sections
121
- content = content
263
+ content = content.trim()
122
264
  .replace(/<noinclude>.*?<\/noinclude>/gs, '')
123
265
  .replace(/.*<(includeonly|onlyinclude)>|<\/(includeonly|onlyinclude)>.*/gs, '');
124
266
  // Substitute arguments
125
267
  const argMatch = (arg) => (0, common_1.RegExpBuilder)(r `{{{ \s* ${arg} (?:\|([^}]*))? \s* }}}`);
126
268
  const args = params.split('|');
127
- // provided key=value template arguments
269
+ // parse provided key=value template arguments
128
270
  for (let i = 1; i < args.length; i++) {
129
- const parts = args[i].split('=');
130
- const [arg, val] = parts[1]
131
- ? [parts[0], ...parts.slice(1)]
132
- : [i.toString(), parts[0]];
133
- content = content.replace(argMatch(arg), (_, defaultVal) => val || defaultVal || '');
271
+ const data = args[i];
272
+ const parts = data.split('=');
273
+ const isNamed = parts.length > 1;
274
+ const arg = isNamed ? parts[0] : i.toString();
275
+ const val = isNamed ? parts.slice(1).join('=') : data;
276
+ content = content.replace(argMatch(arg), (_, defaultVal) => (val || defaultVal || '').trim());
134
277
  }
135
278
  return content;
136
279
  })
137
280
  // Unparsed arguments
138
- .replace((0, common_1.RegExpBuilder)(r `{{{ \s* [^{}|]+? (?:\|([^}]*))? \s* }}}`), (_, _name, defaultVal) => {
281
+ .replace((0, common_1.RegExpBuilder)(r `{{{ \s* [^{}|]+? (?:\|([^}]*))? \s* }}}`), (_, defaultVal) => {
139
282
  return defaultVal !== null && defaultVal !== void 0 ? defaultVal : '';
140
- })
141
- // Images: [[File:Image.png|options|caption]]
142
- .replace((0, common_1.RegExpBuilder)(r `\[\[ (?:File|Image): (.+?) (\|.+?)? \]\]`), (_, file, params = '') => {
143
- if (/{{/.test(params))
144
- return _;
145
- const path = path_1.default.join(imagesFolder, file.trim().replace(/ /g, '_'));
146
- let caption = '';
147
- let imageData = {};
148
- let imageArgs = params.split('|').map((arg) => arg.replace(/"/g, '&quot;'));
149
- for (const param of imageArgs) {
150
- if (['left', 'right', 'center', 'none'].includes(param)) {
151
- imageData.float = param;
152
- }
153
- if (['baseline', 'sub', 'super', 'top', 'text-bottom', 'middle', 'bottom', 'text-bottom'].includes(param)) {
154
- imageData.align = param;
155
- }
156
- else if (['border', 'frameless', 'frame', 'framed', 'thumb', 'thumbnail'].includes(param)) {
157
- imageData.type = { framed: 'frame', thumbnail: 'thumb' }[param] || param;
158
- if (imageData.type === 'thumb') {
159
- imageData.hasCaption = true;
160
- }
161
- }
162
- else if (param.endsWith('px')) {
163
- param.replace(/(?:(\w+)?(x))?(\w+)px/, (_, size1, auto, size2) => {
164
- if (size1) {
165
- Object.assign(imageData, { width: size1, height: size2 });
166
- }
167
- else if (auto) {
168
- Object.assign(imageData, { width: 'auto', height: size2 });
169
- }
170
- else {
171
- Object.assign(imageData, { width: size2, height: 'auto' });
172
- }
173
- return '';
174
- });
175
- }
176
- else if (param.startsWith('upright=')) {
177
- imageData.width = +param.replace('upright=', '') * 300;
178
- }
179
- else if (param.startsWith('link=')) {
180
- imageData.link = param.replace('link=', '');
181
- }
182
- else if (param.startsWith('alt=')) {
183
- imageData.alt = param.replace('alt=', '');
184
- }
185
- else if (param.startsWith('style=')) {
186
- imageData.style = param.replace('style=', '');
187
- }
188
- else if (param.startsWith('class=')) {
189
- imageData.class = param.replace('class=', '');
190
- }
191
- else {
192
- caption = param;
193
- }
194
- }
195
- let content = `
196
- <figure
197
- class="
198
- ${imageData.class || ''}
199
- image-container
200
- image-${imageData.type || 'default'}
201
- "
202
- style="
203
- float: ${imageData.float || 'none'};
204
- vertical-align: ${imageData.align || 'unset'};
205
- ${imageData.style || ''}
206
- "
207
- >
208
- <img
209
- src="${path_1.default.basename(imagesFolder)}/${path_1.default.relative(imagesFolder, path)}"
210
- alt="${imageData.alt || file}"
211
- width="${imageData.width || 300}"
212
- height="${imageData.height || 300}"
213
- >
214
- ${imageData.hasCaption ? `<figcaption>${caption}</figcaption>` : ''}
215
- </figure>
216
- `;
217
- if (imageData.link) {
218
- content = `<a href="/${imageData.link}" title="${imageData.link}">${content}</a>`;
219
- }
220
- return content;
221
283
  })
222
284
  // Markup: '''bold''' and '''italic'''
223
285
  .replace((0, common_1.RegExpBuilder)(r `''' ([^']+?) '''`), '<b>$1</b>')
224
286
  .replace((0, common_1.RegExpBuilder)(r `'' ([^']+?) ''`), '<i>$1</i>')
225
287
  // Headings: ==heading==
226
288
  .replace((0, common_1.RegExpBuilder)(r `^ (=+) \s* (.+?) \s* \1 \s* $`), (_, lvl, txt) => `<h${lvl.length} id="${encodeURI(txt.replace(/ /g, '_'))}">${txt}</h${lvl.length}>`)
227
- // Internal links: [[Page]] and [[Page|Text]]
228
- .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \]\]`), `<a class="internal-link" title="$1" href="$1">$1</a>`)
229
- .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \| ([^\]]+?) \]\]`), `<a class="internal-link" title="$1" href="/$1">$2</a>`)
230
- .replace((0, common_1.RegExpBuilder)(r `(</a>)([a-z]+)`), '$2$1')
231
- // External links: [href Page] and just [href]
232
- .replace((0, common_1.RegExpBuilder)(r `\[ ((?:\w+:)?\/\/ [^\s\]]+) (\s [^\]]+?)? \]`), (_, href, txt) => `<a class="external-link" href="${href}">${txt || '[' + (++rawExtLinkCount) + ']'}</a>`)
233
289
  // Bulleted list: *item
234
- .replace((0, common_1.RegExpBuilder)(r `^ (\*+) (.+?) $`), (_, lvl, txt) => `${'<ul>'.repeat(lvl.length)}<li>${txt}</li>${'</ul>'.repeat(lvl.length)}`)
290
+ .replace((0, common_1.RegExpBuilder)(r `^ (\*+) (.+?) $`), (_, lvl, content) => {
291
+ if (content.includes('{{'))
292
+ return _;
293
+ const depth = lvl.length;
294
+ return `${'<ul>'.repeat(depth)}<li>${content}</li>${'</ul>'.repeat(depth)}`;
295
+ })
235
296
  .replace((0, common_1.RegExpBuilder)(r `</ul> (\s*?) <ul>`), '$1')
236
297
  // Numbered list: #item
237
- .replace((0, common_1.RegExpBuilder)(r `^ (#+) (.+?) $`), (_, lvl, txt) => `${'<ol>'.repeat(lvl.length)}<li>${txt}</li>${'</ol>'.repeat(lvl.length)}`)
298
+ .replace((0, common_1.RegExpBuilder)(r `^ (#+) (.+?) $`), (_, lvl, content) => {
299
+ if (content.includes('{{'))
300
+ return _;
301
+ const depth = lvl.length;
302
+ return `${'<ol>'.repeat(depth)}<li>${content}</li>${'</ol>'.repeat(depth)}`;
303
+ })
238
304
  .replace((0, common_1.RegExpBuilder)(r `</ol> (\s*?) <ol>`), '$1')
239
305
  // Definition list: ;head, :item
306
+ .replace((0, common_1.RegExpBuilder)(r `^ ; (.+?) : (.+?) $`), `<dl><dt>$1</td><dd>$2</dd></dl>`)
307
+ .replace((0, common_1.RegExpBuilder)(r `^ (:+) (.+?) $`), (_, lvl, content) => {
308
+ if (content.includes('{{'))
309
+ return _;
310
+ const depth = lvl.length;
311
+ return `${'<dl>'.repeat(depth)}<dd>${content}</dd>${'</dl>'.repeat(depth)}`;
312
+ })
240
313
  .replace((0, common_1.RegExpBuilder)(r `^ ; (.+) $`), '<dl><dt>$1</dt></dl>')
241
- .replace((0, common_1.RegExpBuilder)(r `^ (:+) (.+?) $`), (_, lvl, txt) => `${'<dl>'.repeat(lvl.length)}<dd>${txt}</dd>${'</dl>'.repeat(lvl.length)}`)
242
314
  .replace((0, common_1.RegExpBuilder)(r `</dl> (\s*?) <dl>`), '$1')
243
315
  // Tables: {|, |+, !, |-, |, |}
244
- .replace((0, common_1.RegExpBuilder)(r `^ \{\| (.*?) $`), (_, attrs) => `<table ${attrs}><tr>`)
245
- .replace((0, common_1.RegExpBuilder)(r `^ ! ([^]+?) (?= \n^[!|] )`), (_, content) => `<th>${content}</th>`)
246
- .replace((0, common_1.RegExpBuilder)(r `^ \|\+ (.*?) $`), (_, content) => `<caption>${content}</caption>`)
247
- .replace((0, common_1.RegExpBuilder)(r `^ \|[^-+}] ([^]*?) (?= \n | \|\| )`), (_, content) => `<td>${content}</td>`)
248
- .replace((0, common_1.RegExpBuilder)(r `\|\|[^-+}] ([^]*?) (?= \n | \|\| )`), (_, content) => `<td>${content}</td>`)
249
- .replace((0, common_1.RegExpBuilder)(r `^ \|- (.*?) $`), (_, attrs) => `</tr><tr ${attrs}>`)
250
- .replace((0, common_1.RegExpBuilder)(r `^ \|\}`), `</tr></table>`)
251
- // References: <ref></ref>, <references/>
252
- .replace((0, common_1.RegExpBuilder)(r `<ref> (.+?) </ref>`), (_, text) => {
253
- refs.push(text);
254
- refCount++;
255
- return `<sup><a id="cite-${refCount}" class="ref" href="#ref-${refCount}">[${refCount}]</a></sup>`;
316
+ .replace(/\{\|.+\|\}/gs, (tableInner) => {
317
+ return tableInner
318
+ // {| data (open table)
319
+ .replace((0, common_1.RegExpBuilder)(r `^ \{\| (.*?) $`), (_, attrs) => `<table ${attrs}><tr>`)
320
+ // |+ data
321
+ .replace((0, common_1.RegExpBuilder)(r `^ \|\+ (.*?) $`), (_, content) => `<caption>${content}</caption>`)
322
+ // |- (new row)
323
+ .replace((0, common_1.RegExpBuilder)(r `^ \|- (.*?) $`), (_, attrs) => `</tr><tr ${attrs}>`)
324
+ // |} (close)
325
+ .replace((0, common_1.RegExpBuilder)(r `^ \|\}`), `</tr></table>`)
326
+ // content: !head, !data|head, |text, |data|text, !!head, ||data
327
+ .replace((0, common_1.RegExpBuilder)(r `( ^! | ^\| | !! | \|\| ) (?: ( [^|\n]+? ) \|)? ( [^|\n]*? ) (?= $ | !! | \|\| )`), (_, type, data, content) => {
328
+ const elem = /!/.test(type) ? 'th' : 'td';
329
+ return `<${elem} ${data !== null && data !== void 0 ? data : ''}>${content}</${elem}>`;
330
+ });
331
+ })
332
+ // References: <ref></ref>
333
+ .replace((0, common_1.RegExpBuilder)(r `< ref \s* (?: name \s* = \s* ["']? ([^>'"]+) ["']? [^>]* )?> (.+?) </ ref >`), (_, refname, text) => {
334
+ if (_.includes('{{'))
335
+ return _;
336
+ const refData = { ref: text, n: refs.length + 1, name: refname };
337
+ refs.push(refData);
338
+ return `<sup class="refnote"><a id="cite-${refData.n}" class="ref" href="#ref-${refData.n}">[${refData.n}]</a></sup>`;
339
+ })
340
+ .replace((0, common_1.RegExpBuilder)(r `< ref \s* name \s* = \s* ["']? ( [^>"']+ ) ["']? \s* (?: /> | > .* </ref> )`), (_, refname) => {
341
+ const ref = refs.find(ref => ref.name === refname);
342
+ if (!ref)
343
+ return '';
344
+ return `<sup class="refnote"><a id="cite-${ref.n}" class="ref" href="#ref-${ref.n}">[${ref.n}]</a></sup>`;
256
345
  })
257
- .replace((0, common_1.RegExpBuilder)(r `<references \s* /?>`), '<ol>' + refs.map((ref, i) => `<li id="ref-${+i + 1}"> <a href="#cite-${+i + 1}">↑</a> ${ref} </li>`).join('\n') + '</ol>')
258
346
  // Nonstandard: ``code`` and ```code blocks```
259
347
  .replace((0, common_1.RegExpBuilder)(r ` \`\`\` ([^\`]+?) \`\`\` `), '<pre>$1</pre>')
260
348
  .replace((0, common_1.RegExpBuilder)(r ` \`\` ([^\`]+?) \`\` `), '<code>$1</code>')
261
349
  // Spacing
262
350
  .replace(/(\r?\n){2}/g, '\n</p><p>\n');
263
351
  }
264
- // Final changes (run only once)
352
+ // Final (one-time) substitutions
353
+ for (let i = 0; i < nowikis.length; i++) {
354
+ outText = outText
355
+ // Restore nowiki contents
356
+ .replace(escaper('NOWIKI', i), nowikis[i]);
357
+ }
358
+ outText = outText
359
+ // References: <references />
360
+ .replace((0, common_1.RegExpBuilder)(r `<references \s* /?>`), () => {
361
+ const references = refs.map(({ ref, n }) => {
362
+ const refline = `<li id="ref-${n}"> <a href="#cite-${n}">↑</a> ${ref} </li>`;
363
+ return refline;
364
+ }).join('\n');
365
+ return `<ol>${references}</ol>`;
366
+ })
367
+ // Magic word functions
368
+ .replaceAll(escaper('VERT'), '|')
369
+ .replaceAll(escaper('EQUALS'), '=');
370
+ // Escape all {{ to avoid crashes
265
371
  outText = outText
266
- // Restore nowiki contents
267
- .replace(/%NOWIKI#(\d+)%/g, (_, n) => fullyEscape(nowikis[n]));
372
+ .replaceAll('{{', '&#123;&#123;');
268
373
  const result = { data: outText, metadata: metadata };
269
374
  return result;
270
375
  }
package/dist/wiki.css.js CHANGED
@@ -10,6 +10,7 @@ dd, dl dl {margin-block: 0; margin-inline-start: 30px;}
10
10
 
11
11
  figure {margin: 1em;}
12
12
  .image-thumb, .image-frame {padding: 6px; border: 1px solid gray;}
13
+ .image-default {margin: 0;}
13
14
  figcaption {padding-top: 6px;}
14
15
 
15
16
  table.wikitable {border-collapse: collapse;}
@@ -25,7 +26,13 @@ a.external-link::after {content: '\1f855';}
25
26
  a.redlink {color: #d33;}
26
27
  a.redlink:visited {color: #b44;}
27
28
 
28
- #toc {display: inline-block; border: 1px solid #aab; padding: 8px; background-color: #f8f8f8; font-size: 95%;}
29
- #toc-heading {display: block; text-align: center;}
30
- #toc ol {margin: 0 0 0 1.3em;}
29
+ sup.refnote {margin-left: -5px;}
30
+
31
+ #page+toc {border: 1px solid #aab; padding: 8px; width: fit-content; background-color: #f8f8f8; font-size: 95%;}
32
+ #page+toc-heading {display: block; text-align: center;}
33
+ #page+toc ol {margin: 0 0 0 1.3em;}
34
+
35
+ #infobox {float: right; clear: right; margin: 0 0 1em 1em; width: 300px; padding: 2px; border: 1px solid #CCC; overflow: auto; font-size: 90%;}
36
+ #infobox tr:first-child :first-child {padding: 10px 10px 0; text-align: center; font-weight: bold; font-size: 120%;}
37
+ #infobox th {padding-left: 10px; text-align: left;}
31
38
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikity",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Compile wikitext to HTML: wikitext as a templating language.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -162,6 +162,7 @@ Your wikitext (`*.wiki`) files go in the root directory by default.
162
162
  | `{{#explode:A-B-C-D\|-\|2}}` | C |
163
163
  | `{{#urlencode:t e x t}}` | t%20e%20x%20t |
164
164
  | `{{#urldecode:a%20b%27c}}` | a b'c |
165
+ | `{{#ev:youtube\|dQw4w9WgXcQ}}` | *(YouTube embed)* |
165
166
  | `<noinclude>No</noinclude>` | *(blank outside a template)* |
166
167
  | `<onlyinclude>Yes</onlyinclude>` | Yes |
167
168
  | `<includeonly>Yes</includeonly>` | Yes *(blank inside a template)* |