wikity 1.3.3 → 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
@@ -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,9 +42,7 @@ 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
- const escaper = (text, n) => `%${text}#${n}`;
37
46
  for (let l = 0, last = ''; l < MAX_RECURSION; l++) {
38
47
  if (last === outText)
39
48
  break;
@@ -42,21 +51,113 @@ function parse(data, config = {}) {
42
51
  // Nowiki: <nowiki></nowiki>
43
52
  .replace((0, common_1.RegExpBuilder)(r `<nowiki> ([^]+?) </nowiki>`), (_, m) => (nowikis.push(m), escaper('NOWIKI', nowikiCount++)))
44
53
  // Sanitise unacceptable HTML
45
- .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;')
46
55
  .replace((0, common_1.RegExpBuilder)(r `(?<= <[^>]+ ) (\bon(\w+))`), 'data-$2')
47
56
  // Comments: <!-- -->
48
57
  .replace(/<!--[^]+?-->/g, '')
49
58
  // Lines: ----
50
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
+ })
51
136
  // Internal links: [[Page]] and [[Page|Text]]
52
- .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \]\]`), `<a class{{=}}"internal-link" title{{=}}"$1" href{{=}}"$1">$1</a>`)
53
- .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \| ([^\]]+?) \]\]`), `<a class{{=}}"internal-link" title{{=}}"$1" href{{=}}"/$1">$2</a>`)
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
+ })
54
149
  .replace((0, common_1.RegExpBuilder)(r `(</a>)([a-z]+)`), '$2$1')
55
150
  // External links: [href Page] and just [href]
56
- .replace((0, common_1.RegExpBuilder)(r `\[ ((?:\w+:)?\/\/ [^\s\]]+) (\s [^\]]+?)? \]`), (_, href, txt) => `<a class{{=}}"external-link" href{{=}}"${href}">${txt || '[' + (++rawExtLinkCount) + ']'}</a>`)
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
+ })
57
157
  // Magic words: {{!}}, {{reflist}}, etc
58
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* ! \s* }}`), escaper('VERT', 0))
59
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* = \s* }}`), escaper('EQUALS', 0))
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'))
60
161
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* [Rr]eflist \s* }}`), '<references/>')
61
162
  // Metadata: displayTitle, __NOTOC__, etc
62
163
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* displayTitle: ([^}]+) }}`), (_, title) => (metadata.displayTitle = title, ''))
@@ -78,9 +179,40 @@ function parse(data, config = {}) {
78
179
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? padright: ${arg} \|${arg} \|${arg} }}`), (_, str, n, char) => str.padEnd(+n, char))
79
180
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? replace: ${arg} \|${arg} \|${arg} }}`), (_, str, find, rep) => str.split(find).join(rep))
80
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
+ })
81
213
  // Parser functions: {{#if:}}, {{#switch:}}, etc
82
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => {
83
- if (/{{\s*#/.test(content))
214
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => {
215
+ if (content.includes('{{'))
84
216
  return _;
85
217
  const args = content.trim().split(/\s*\|\s*/);
86
218
  switch (name) {
@@ -112,8 +244,11 @@ function parse(data, config = {}) {
112
244
  }
113
245
  })
114
246
  // Templates: {{template}}
115
- .replace((0, common_1.RegExpBuilder)(r `(?<!{) {{ \s* ([^#{}|]+?) (\|[^{}]+)? }} (?!})`), (_, title, params = '') => {
116
- 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);
117
252
  let content = '';
118
253
  // Try retrieve template content
119
254
  try {
@@ -125,106 +260,26 @@ function parse(data, config = {}) {
125
260
  return `<a class="internal-link redlink" title="${title}" href="${relPage}">${title}</a>`;
126
261
  }
127
262
  // Remove non-template sections
128
- content = content
263
+ content = content.trim()
129
264
  .replace(/<noinclude>.*?<\/noinclude>/gs, '')
130
265
  .replace(/.*<(includeonly|onlyinclude)>|<\/(includeonly|onlyinclude)>.*/gs, '');
131
266
  // Substitute arguments
132
267
  const argMatch = (arg) => (0, common_1.RegExpBuilder)(r `{{{ \s* ${arg} (?:\|([^}]*))? \s* }}}`);
133
268
  const args = params.split('|');
134
- // provided key=value template arguments
269
+ // parse provided key=value template arguments
135
270
  for (let i = 1; i < args.length; i++) {
136
- const parts = args[i].split('=');
137
- const [arg, val] = parts[1]
138
- ? [parts[0], parts.slice(1).join('=')]
139
- : [i.toString(), parts[0]];
140
- 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());
141
277
  }
142
278
  return content;
143
279
  })
144
280
  // Unparsed arguments
145
- .replace((0, common_1.RegExpBuilder)(r `{{{ \s* [^{}|]+? (?:\|([^}]*))? \s* }}}`), (_, _name, defaultVal) => {
281
+ .replace((0, common_1.RegExpBuilder)(r `{{{ \s* [^{}|]+? (?:\|([^}]*))? \s* }}}`), (_, defaultVal) => {
146
282
  return defaultVal !== null && defaultVal !== void 0 ? defaultVal : '';
147
- })
148
- // Images: [[File:Image.png|options|caption]]
149
- .replace((0, common_1.RegExpBuilder)(r `\[\[ (?:File|Image): (.+?) (\|.+?)? \]\]`), (_, file, params = '') => {
150
- if (/{{/.test(params))
151
- return _;
152
- const path = path_1.default.join(imagesFolder, file.trim().replace(/ /g, '_'));
153
- let caption = '';
154
- let imageData = {};
155
- let imageArgs = params.split('|').map((arg) => arg.replace(/"/g, '&quot;'));
156
- for (const param of imageArgs) {
157
- if (['left', 'right', 'center', 'none'].includes(param)) {
158
- imageData.float = param;
159
- }
160
- if (['baseline', 'sub', 'super', 'top', 'text-bottom', 'middle', 'bottom', 'text-bottom'].includes(param)) {
161
- imageData.align = param;
162
- }
163
- else if (['border', 'frameless', 'frame', 'framed', 'thumb', 'thumbnail'].includes(param)) {
164
- imageData.type = { framed: 'frame', thumbnail: 'thumb' }[param] || param;
165
- if (imageData.type === 'thumb') {
166
- imageData.hasCaption = true;
167
- }
168
- }
169
- else if (param.endsWith('px')) {
170
- param.replace(/(?:(\w+)?(x))?(\w+)px/, (_, size1, auto, size2) => {
171
- if (size1) {
172
- Object.assign(imageData, { width: size1, height: size2 });
173
- }
174
- else if (auto) {
175
- Object.assign(imageData, { width: 'auto', height: size2 });
176
- }
177
- else {
178
- Object.assign(imageData, { width: size2, height: 'auto' });
179
- }
180
- return '';
181
- });
182
- }
183
- else if (param.startsWith('upright=')) {
184
- imageData.width = +param.replace('upright=', '') * 300;
185
- }
186
- else if (param.startsWith('link=')) {
187
- imageData.link = param.replace('link=', '');
188
- }
189
- else if (param.startsWith('alt=')) {
190
- imageData.alt = param.replace('alt=', '');
191
- }
192
- else if (param.startsWith('style=')) {
193
- imageData.style = param.replace('style=', '');
194
- }
195
- else if (param.startsWith('class=')) {
196
- imageData.class = param.replace('class=', '');
197
- }
198
- else {
199
- caption = param;
200
- }
201
- }
202
- let content = `
203
- <figure
204
- class="
205
- ${imageData.class || ''}
206
- image-container
207
- image-${imageData.type || 'default'}
208
- "
209
- style="
210
- float: ${imageData.float || 'none'};
211
- vertical-align: ${imageData.align || 'unset'};
212
- ${imageData.style || ''}
213
- "
214
- >
215
- <img
216
- src="${path_1.default.basename(imagesFolder)}/${path_1.default.relative(imagesFolder, path)}"
217
- alt="${imageData.alt || file}"
218
- width="${imageData.width || 300}"
219
- height="${imageData.height || 300}"
220
- >
221
- ${imageData.hasCaption ? `<figcaption>${caption}</figcaption>` : ''}
222
- </figure>
223
- `;
224
- if (imageData.link) {
225
- content = `<a href="/${imageData.link}" title="${imageData.link}">${content}</a>`;
226
- }
227
- return content;
228
283
  })
229
284
  // Markup: '''bold''' and '''italic'''
230
285
  .replace((0, common_1.RegExpBuilder)(r `''' ([^']+?) '''`), '<b>$1</b>')
@@ -232,46 +287,89 @@ function parse(data, config = {}) {
232
287
  // Headings: ==heading==
233
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}>`)
234
289
  // Bulleted list: *item
235
- .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
+ })
236
296
  .replace((0, common_1.RegExpBuilder)(r `</ul> (\s*?) <ul>`), '$1')
237
297
  // Numbered list: #item
238
- .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
+ })
239
304
  .replace((0, common_1.RegExpBuilder)(r `</ol> (\s*?) <ol>`), '$1')
240
305
  // Definition list: ;head, :item
241
306
  .replace((0, common_1.RegExpBuilder)(r `^ ; (.+?) : (.+?) $`), `<dl><dt>$1</td><dd>$2</dd></dl>`)
242
- .replace((0, common_1.RegExpBuilder)(r `^ (:+) (.+?) $`), (_, lvl, txt) => `${'<dl>'.repeat(lvl.length)}<dd>${txt}</dd>${'</dl>'.repeat(lvl.length)}`)
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
+ })
243
313
  .replace((0, common_1.RegExpBuilder)(r `^ ; (.+) $`), '<dl><dt>$1</dt></dl>')
244
314
  .replace((0, common_1.RegExpBuilder)(r `</dl> (\s*?) <dl>`), '$1')
245
315
  // Tables: {|, |+, !, |-, |, |}
246
- .replace((0, common_1.RegExpBuilder)(r `^ \{\| (.*?) $`), (_, attrs) => `<table ${attrs}><tr>`)
247
- .replace((0, common_1.RegExpBuilder)(r `^ ! ([^]+?) (?= \n^[!|] )`), (_, content) => `<th>${content}</th>`)
248
- .replace((0, common_1.RegExpBuilder)(r `^ \|\+ (.*?) $`), (_, content) => `<caption>${content}</caption>`)
249
- .replace((0, common_1.RegExpBuilder)(r `^ \|[^-+}] ([^]*?) (?= \n | \|\| )`), (_, content) => `<td>${content}</td>`)
250
- .replace((0, common_1.RegExpBuilder)(r `\|\|[^-+}] ([^]*?) (?= \n | \|\| )`), (_, content) => `<td>${content}</td>`)
251
- .replace((0, common_1.RegExpBuilder)(r `^ \|- (.*?) $`), (_, attrs) => `</tr><tr ${attrs}>`)
252
- .replace((0, common_1.RegExpBuilder)(r `^ \|\}`), `</tr></table>`)
253
- // References: <ref></ref>, <references/>
254
- .replace((0, common_1.RegExpBuilder)(r `<ref> (.+?) </ref>`), (_, text) => {
255
- refs.push(text);
256
- refCount++;
257
- 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>`;
258
345
  })
259
- .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>')
260
346
  // Nonstandard: ``code`` and ```code blocks```
261
347
  .replace((0, common_1.RegExpBuilder)(r ` \`\`\` ([^\`]+?) \`\`\` `), '<pre>$1</pre>')
262
348
  .replace((0, common_1.RegExpBuilder)(r ` \`\` ([^\`]+?) \`\` `), '<code>$1</code>')
263
349
  // Spacing
264
350
  .replace(/(\r?\n){2}/g, '\n</p><p>\n');
265
351
  }
266
- // Restore nowiki contents
352
+ // Final (one-time) substitutions
267
353
  for (let i = 0; i < nowikis.length; i++) {
268
354
  outText = outText
269
- .replace(escaper('NOWIKI', i), (_, n) => fullyEscape(nowikis[n]));
355
+ // Restore nowiki contents
356
+ .replace(escaper('NOWIKI', i), nowikis[i]);
270
357
  }
271
- // Substitute magic word functions
272
358
  outText = outText
273
- .replaceAll(escaper('VERT', 0), '|')
274
- .replaceAll(escaper('EQUALS', 0), '=');
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
371
+ outText = outText
372
+ .replaceAll('{{', '&#123;&#123;');
275
373
  const result = { data: outText, metadata: metadata };
276
374
  return result;
277
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.3",
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)* |