wikity 1.3.3 → 1.3.5

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
@@ -8,7 +8,6 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const glob_1 = __importDefault(require("glob"));
10
10
  const dedent_1 = __importDefault(require("dedent"));
11
- const formatter = require('html-formatter'); // does not have type package
12
11
  const parse_1 = require("./parse");
13
12
  const wiki_css_1 = __importDefault(require("./wiki.css"));
14
13
  function eleventyCompile(dir = '.', config = {}) {
@@ -40,7 +39,7 @@ function compile(dir = '.', config = {}) {
40
39
  const outFilename = filename.replace(/ /g, '_').replace('.wiki', '.html');
41
40
  const outFilePath = path_1.default.join(outputFolder, outFilename);
42
41
  const urlPath = outFilename.replace(/(?<=^|\/)\w/g, m => m.toUpperCase()); // capitalise first letters
43
- const displayTitle = metadata.displayTitle || urlPath.replace('.html', '');
42
+ const displayTitle = metadata.displayTitle || urlPath.replace('.html', '').replace(/_/g, ' ');
44
43
  // Eleventy configuration
45
44
  const frontMatter = config.eleventy ? (0, dedent_1.default) `
46
45
  ---
@@ -53,20 +52,22 @@ function compile(dir = '.', config = {}) {
53
52
  let headings = Array.from(parsedContent.match(/<h\d[^>]*>.+?<\/h\d>/gs) || []);
54
53
  headings.forEach(match => {
55
54
  var _a;
56
- const text = match.replace(/\s*<\/?h\d[^>]*>\s*/g, '');
55
+ const headingInner = match.replace(/\s*<\/?h\d[^>]*>\s*/g, '');
56
+ const text = headingInner.replace(/<.+?>/g, ''); // remove tags from inner
57
57
  const lvl = +(((_a = match.match(/\d/g)) === null || _a === void 0 ? void 0 : _a[0]) || -1);
58
58
  toc += `${`<ol>`.repeat(lvl - 1)} <li> <a href="#${encodeURI(text.replace(/ /g, '_'))}">${text}</a> </li> ${`</ol>`.repeat(lvl - 1)}`;
59
59
  });
60
+ toc = toc.replace(/<\/ol>\s*<ol>/g, '');
60
61
  const tocElem = (0, dedent_1.default) `
61
- <div id="toc">
62
- <span id="toc-heading">
62
+ <div id="page+toc">
63
+ <span id="page+toc+heading">
63
64
  <strong>Contents</strong>
64
65
  [<a href="javascript:void(0)" onclick="
65
- document.querySelector('#toc ol').setAttribute('style', this.innerText === 'hide' ? 'display: none;' : '');
66
+ document.getElementById('page+toc+contents').setAttribute('style', this.innerText === 'hide' ? 'display: none;' : '');
66
67
  this.innerText = this.innerText === 'hide' ? 'show' : 'hide';
67
68
  ">hide</a>]
68
69
  </span>
69
- <ol>${toc}</ol>
70
+ <ol id="page+toc+contents">${toc}</ol>
70
71
  </div>
71
72
  `;
72
73
  // Set TOC on page
@@ -106,8 +107,8 @@ function compile(dir = '.', config = {}) {
106
107
  if (!fs_1.default.existsSync(path_1.default.dirname(outFilePath))) {
107
108
  fs_1.default.mkdirSync(path_1.default.dirname(outFilePath));
108
109
  }
109
- const renderedHtml = formatter.render(html.replace(/(<\/\w+>) +(\S)/g, '$1&#32;$2'));
110
- fs_1.default.writeFileSync(outFilePath, frontMatter + '\n' + renderedHtml, 'utf8');
110
+ const formattedHtml = html.replace(/\n[\n\s]+/g, '\n');
111
+ fs_1.default.writeFileSync(outFilePath, frontMatter + '\n' + formattedHtml, 'utf8');
111
112
  // Move images
112
113
  (0, glob_1.default)(imagesFolder + '/*', {}, (err, files) => {
113
114
  if (err) {
@@ -131,12 +132,7 @@ function compile(dir = '.', config = {}) {
131
132
  if (config.customStyles) {
132
133
  styles += config.customStyles;
133
134
  }
134
- const cssOutput = config.eleventy ? (0, dedent_1.default) `
135
- ---
136
- permalink: /wiki.css
137
- ---
138
- ${styles}
139
- ` : styles;
135
+ const cssOutput = config.eleventy ? ['---', 'permalink: /wiki.css', '---', styles].join('\n') : styles;
140
136
  const cssOutFilename = config.eleventy ? 'wiki.css.njk' : 'wiki.css';
141
137
  fs_1.default.writeFileSync(path_1.default.join(outputFolder, cssOutFilename), cssOutput);
142
138
  }
package/dist/parse.js CHANGED
@@ -6,22 +6,38 @@ 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 toLinkText(link) {
15
+ const cleanedLink = encodeURI(link.trim().replace(/ /g, '_'));
16
+ return cleanedLink[0].toUpperCase() + cleanedLink.slice(1);
18
17
  }
18
+ function toFileText(link) {
19
+ const cleanedLink = link.trim().replace(/ /g, '_');
20
+ return cleanedLink[0].toUpperCase() + cleanedLink.slice(1);
21
+ }
22
+ function parseDimensions(dimStr) {
23
+ const regex = /(\d*)(?:x(\d*))?px/;
24
+ const match = dimStr.match(regex);
25
+ if (!match)
26
+ return { width: 'auto', height: 'auto' };
27
+ const [, width, height] = match;
28
+ return {
29
+ width: width || 'auto',
30
+ height: height || 'auto',
31
+ };
32
+ }
33
+ const escaper = (text, n = 0) => `%${text}#${n}`;
19
34
  function rawParse(data, config = {}) {
20
35
  return parse(data, config).data;
21
36
  }
22
37
  exports.rawParse = rawParse;
23
38
  function parse(data, config = {}) {
24
39
  var _a, _b, _c;
40
+ const KEY = Math.random().toString().slice(2); // key used to allow certain disallowed HTML elements
25
41
  const templatesFolder = (_a = config.templatesFolder) !== null && _a !== void 0 ? _a : 'templates';
26
42
  const imagesFolder = (_b = config.imagesFolder) !== null && _b !== void 0 ? _b : 'images';
27
43
  const outputFolder = (_c = config.outputFolder) !== null && _c !== void 0 ? _c : 'wikity-out';
@@ -31,9 +47,7 @@ function parse(data, config = {}) {
31
47
  const refs = [];
32
48
  let nowikiCount = 0;
33
49
  let rawExtLinkCount = 0;
34
- let refCount = 0;
35
50
  let outText = data;
36
- const escaper = (text, n) => `%${text}#${n}`;
37
51
  for (let l = 0, last = ''; l < MAX_RECURSION; l++) {
38
52
  if (last === outText)
39
53
  break;
@@ -42,21 +56,125 @@ function parse(data, config = {}) {
42
56
  // Nowiki: <nowiki></nowiki>
43
57
  .replace((0, common_1.RegExpBuilder)(r `<nowiki> ([^]+?) </nowiki>`), (_, m) => (nowikis.push(m), escaper('NOWIKI', nowikiCount++)))
44
58
  // 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')
59
+ .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
60
  .replace((0, common_1.RegExpBuilder)(r `(?<= <[^>]+ ) (\bon(\w+))`), 'data-$2')
47
61
  // Comments: <!-- -->
48
62
  .replace(/<!--[^]+?-->/g, '')
49
63
  // Lines: ----
50
64
  .replace(/^-{4,}/gm, '<hr>')
51
- // 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>`)
65
+ // Images: [[File:Image.png|options|caption]]
66
+ .replace((0, common_1.RegExpBuilder)(r `\[\[ (?:File|Image): (.*?) (\|.+?)? \]\]`), (_, file, params = '') => {
67
+ var _a;
68
+ if (params.includes('{{'))
69
+ return _;
70
+ if (params.includes('[['))
71
+ return _;
72
+ if (!file)
73
+ return '';
74
+ const path = path_1.default.join(imagesFolder, file.trim().replace(/ /g, '_'));
75
+ let caption = '';
76
+ let imageData = {};
77
+ let imageArgs = params.split('|').map((arg) => arg.replace(/"/g, '&quot;'));
78
+ for (const param of imageArgs) {
79
+ if (['left', 'right', 'center', 'none'].includes(param)) {
80
+ imageData.float = param;
81
+ }
82
+ if (['baseline', 'sub', 'super', 'top', 'text-bottom', 'middle', 'bottom', 'text-bottom'].includes(param)) {
83
+ imageData.align = param;
84
+ }
85
+ else if (['border', 'frameless', 'frame', 'framed', 'thumb', 'thumbnail'].includes(param)) {
86
+ imageData.type = { framed: 'frame', thumbnail: 'thumb' }[param] || param;
87
+ if (imageData.type === 'thumb') {
88
+ imageData.hasCaption = true;
89
+ (_a = imageData.float) !== null && _a !== void 0 ? _a : (imageData.float = 'right');
90
+ }
91
+ }
92
+ else if (param.endsWith('px')) {
93
+ const { width, height } = parseDimensions(param);
94
+ imageData.width = width;
95
+ imageData.height = height;
96
+ }
97
+ else if (param.startsWith('upright=')) {
98
+ imageData.width = +param.replace('upright=', '') * 300;
99
+ }
100
+ else if (param.startsWith('link=')) {
101
+ imageData.link = param.replace('link=', '');
102
+ }
103
+ else if (param.startsWith('alt=')) {
104
+ imageData.alt = param.replace('alt=', '');
105
+ }
106
+ else if (param.startsWith('style=')) {
107
+ imageData.style = param.replace('style=', '');
108
+ }
109
+ else if (param.startsWith('class=')) {
110
+ imageData.class = param.replace('class=', '');
111
+ }
112
+ else {
113
+ caption = param;
114
+ }
115
+ }
116
+ let content = `
117
+ <figure
118
+ class="
119
+ ${imageData.class || ''}
120
+ image-container
121
+ image-${imageData.type || 'default'}
122
+ "
123
+ style="
124
+ display: inline-block;
125
+ float: ${imageData.float || 'none'};
126
+ clear: ${imageData.float || 'none'};
127
+ vertical-align: ${imageData.align || 'unset'};
128
+ background: #fff;
129
+ ${imageData.style || ''}
130
+ "
131
+ >
132
+ <img
133
+ src="${path_1.default.basename(imagesFolder)}/${path_1.default.relative(imagesFolder, path)}"
134
+ alt="${imageData.alt || file}"
135
+ width="${imageData.width || 300}"
136
+ height="${imageData.height || ''}"
137
+ >
138
+ ${imageData.hasCaption ? `<figcaption>${caption}</figcaption>` : ''}
139
+ </figure>
140
+ `;
141
+ const imageLink = imageData.link;
142
+ if (imageLink) {
143
+ content = `<a href="${toLinkText(imageLink)}" title="${imageLink}">${content}</a>`;
144
+ }
145
+ return content;
146
+ })
147
+ // Internal links
148
+ // [[Page]]
149
+ .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \]\]`), (_, link) => {
150
+ if (_.includes('{{'))
151
+ return _;
152
+ if (/(?:File|Image):/.test(link))
153
+ return _;
154
+ const content = `<a class="internal-link" title="${link}" href="./${toLinkText(link)}">${link}</a>`;
155
+ return content;
156
+ })
157
+ // [[Page|Text]]
158
+ .replace((0, common_1.RegExpBuilder)(r `\[\[ ([^\]|]+?) \| ([^\]]+?) \]\]`), (_, link, text) => {
159
+ if (link.includes('{{'))
160
+ return _;
161
+ if (/(?:File|Image):/.test(link))
162
+ return _;
163
+ const content = `<a class="internal-link" title="${link}" href="./${toLinkText(link)}">${text}</a>`;
164
+ return content;
165
+ })
54
166
  .replace((0, common_1.RegExpBuilder)(r `(</a>)([a-z]+)`), '$2$1')
55
167
  // 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>`)
168
+ .replace((0, common_1.RegExpBuilder)(r `\[ ((?:\w+:)?\/\/ [^\s\]]+) (\s [^\]]+?)? \]`), (_, href, txt) => {
169
+ if (_.includes('{{'))
170
+ return _;
171
+ const content = `<a class="external-link" href="${href}">${txt || '[' + (++rawExtLinkCount) + ']'}</a>`;
172
+ return content;
173
+ })
57
174
  // 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))
175
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* ! \s* }}`), escaper('VERT'))
176
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* !! \s* }}`), escaper('VERT').repeat(2))
177
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* = \s* }}`), escaper('EQUALS'))
60
178
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* [Rr]eflist \s* }}`), '<references/>')
61
179
  // Metadata: displayTitle, __NOTOC__, etc
62
180
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* displayTitle: ([^}]+) }}`), (_, title) => (metadata.displayTitle = title, ''))
@@ -78,9 +196,40 @@ function parse(data, config = {}) {
78
196
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? padright: ${arg} \|${arg} \|${arg} }}`), (_, str, n, char) => str.padEnd(+n, char))
79
197
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? replace: ${arg} \|${arg} \|${arg} }}`), (_, str, find, rep) => str.split(find).join(rep))
80
198
  .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? explode: ${arg} \|${arg} \|${arg} }}`), (_, str, delim, pos) => str.split(delim)[+pos])
199
+ // Magic functions: {{#ev:youtube}}, etc
200
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* #? ev: \s* (\w+) \s* \| \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, platform, args) => {
201
+ var _a;
202
+ // See mediawiki.org/wiki/Extension:EmbedVideo_(fork) for docs
203
+ const params = args.split('|');
204
+ for (let i = 0; i < 10; i++)
205
+ (_a = params[i]) !== null && _a !== void 0 ? _a : (params[i] = ''); // fill up with empty strings
206
+ const [id, dimensions, alignment, description, container, urlargs, autoresize] = params;
207
+ const { width, height } = parseDimensions(dimensions);
208
+ const source = {
209
+ // Add platforms
210
+ 'youtube': `//www.youtube.com/embed/${id}`,
211
+ 'vimeo': `//player.vimeo.com/video/${id}`,
212
+ }[platform];
213
+ if (!source)
214
+ return `<code>Failed to load video ${id} from ${platform}.</code>`;
215
+ return `
216
+ <iframe key="${KEY}"
217
+ src="${source}"
218
+ width="${width}"
219
+ height="${height}"
220
+ frameborder="0"
221
+ allowfullscreen="true"
222
+ loading="lazy"
223
+ title="${description !== null && description !== void 0 ? description : 'Play video'}"
224
+ ${alignment ? `style="float: ${alignment};"` : ''}
225
+ >
226
+ </iframe>
227
+ ${description ? `<figcaption>${description}</figcaption>` : ''}
228
+ `;
229
+ })
81
230
  // Parser functions: {{#if:}}, {{#switch:}}, etc
82
- .replace((0, common_1.RegExpBuilder)(r `{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => {
83
- if (/{{\s*#/.test(content))
231
+ .replace((0, common_1.RegExpBuilder)(r `{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => {
232
+ if (content.includes('{{'))
84
233
  return _;
85
234
  const args = content.trim().split(/\s*\|\s*/);
86
235
  switch (name) {
@@ -108,12 +257,20 @@ function parse(data, config = {}) {
108
257
  const errMsg = `Wikity does not use Wikipedia's #time function syntax. Use repetition-based formatting (e.g. yyyy-mm-dd) instead.`;
109
258
  console.warn(`<Wikity> [WARN] ${errMsg}`);
110
259
  }
111
- return (0, dateformat_1.default)(args[1] ? new Date(args[1]) : new Date(), args[0]);
260
+ try {
261
+ return (0, dateformat_1.default)(args[1] ? new Date(args[1]) : new Date(), args[0]);
262
+ }
263
+ catch {
264
+ return args[1] || args[0];
265
+ }
112
266
  }
113
267
  })
114
268
  // Templates: {{template}}
115
- .replace((0, common_1.RegExpBuilder)(r `(?<!{) {{ \s* ([^#{}|]+?) (\|[^{}]+)? }} (?!})`), (_, title, params = '') => {
116
- const page = path_1.default.join(templatesFolder, title.trim().replace(/ /g, '_'));
269
+ .replace((0, common_1.RegExpBuilder)(r `(?<!{) {{ \s* ([^#{}|]+?) \s* (\|[^{}]+)? }} (?!})`), (_, title, params = '') => {
270
+ if (params.includes('{{'))
271
+ return _;
272
+ const templateFile = toFileText(title);
273
+ const page = path_1.default.join(templatesFolder, templateFile);
117
274
  let content = '';
118
275
  // Try retrieve template content
119
276
  try {
@@ -125,153 +282,141 @@ function parse(data, config = {}) {
125
282
  return `<a class="internal-link redlink" title="${title}" href="${relPage}">${title}</a>`;
126
283
  }
127
284
  // Remove non-template sections
128
- content = content
285
+ content = content.trim()
129
286
  .replace(/<noinclude>.*?<\/noinclude>/gs, '')
130
287
  .replace(/.*<(includeonly|onlyinclude)>|<\/(includeonly|onlyinclude)>.*/gs, '');
131
288
  // Substitute arguments
132
289
  const argMatch = (arg) => (0, common_1.RegExpBuilder)(r `{{{ \s* ${arg} (?:\|([^}]*))? \s* }}}`);
133
290
  const args = params.split('|');
134
- // provided key=value template arguments
291
+ // parse provided key=value template arguments
135
292
  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 || '');
293
+ const data = args[i];
294
+ const parts = data.split('=');
295
+ const isNamed = parts.length > 1;
296
+ const arg = isNamed ? parts[0] : i.toString();
297
+ const val = isNamed ? parts.slice(1).join('=') : data;
298
+ content = content.replace(argMatch(arg), (_, defaultVal) => (val || defaultVal || '').trim());
141
299
  }
142
300
  return content;
143
301
  })
144
302
  // Unparsed arguments
145
- .replace((0, common_1.RegExpBuilder)(r `{{{ \s* [^{}|]+? (?:\|([^}]*))? \s* }}}`), (_, _name, defaultVal) => {
303
+ .replace((0, common_1.RegExpBuilder)(r `{{{ \s* [^{}|]+? (?:\|([^}]*))? \s* }}}`), (_, defaultVal) => {
146
304
  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
305
  })
229
306
  // Markup: '''bold''' and '''italic'''
230
307
  .replace((0, common_1.RegExpBuilder)(r `''' ([^']+?) '''`), '<b>$1</b>')
231
308
  .replace((0, common_1.RegExpBuilder)(r `'' ([^']+?) ''`), '<i>$1</i>')
232
309
  // Headings: ==heading==
233
- .replace((0, common_1.RegExpBuilder)(r `^ (=+) \s* (.+?) \s* \1 \s* $`), (_, lvl, txt) => `<h${lvl.length} id="${encodeURI(txt.replace(/ /g, '_'))}">${txt}</h${lvl.length}>`)
310
+ .replace((0, common_1.RegExpBuilder)(r `^ (=+) \s* (.+?) \s* \1 \s* $`), (_, lvl, txt) => {
311
+ const linkForm = encodeURI(txt.replace(/ /g, '_').replace(/<.+?>/g, ''));
312
+ return `<h${lvl.length} id="${linkForm}">${txt}</h${lvl.length}>`;
313
+ })
234
314
  // Bulleted list: *item
235
- .replace((0, common_1.RegExpBuilder)(r `^ (\*+) (.+?) $`), (_, lvl, txt) => `${'<ul>'.repeat(lvl.length)}<li>${txt}</li>${'</ul>'.repeat(lvl.length)}`)
315
+ .replace((0, common_1.RegExpBuilder)(r `^ (\*+) (.+?) $`), (_, lvl, content) => {
316
+ if (content.includes('{{'))
317
+ return _;
318
+ const depth = lvl.length;
319
+ return `${'<ul>'.repeat(depth)}<li>${content}</li>${'</ul>'.repeat(depth)}`;
320
+ })
236
321
  .replace((0, common_1.RegExpBuilder)(r `</ul> (\s*?) <ul>`), '$1')
237
322
  // Numbered list: #item
238
- .replace((0, common_1.RegExpBuilder)(r `^ (#+) (.+?) $`), (_, lvl, txt) => `${'<ol>'.repeat(lvl.length)}<li>${txt}</li>${'</ol>'.repeat(lvl.length)}`)
323
+ .replace((0, common_1.RegExpBuilder)(r `^ (#+) (.+?) $`), (_, lvl, content) => {
324
+ if (content.includes('{{'))
325
+ return _;
326
+ const depth = lvl.length;
327
+ return `${'<ol>'.repeat(depth)}<li>${content}</li>${'</ol>'.repeat(depth)}`;
328
+ })
239
329
  .replace((0, common_1.RegExpBuilder)(r `</ol> (\s*?) <ol>`), '$1')
240
330
  // Definition list: ;head, :item
241
331
  .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)}`)
332
+ .replace((0, common_1.RegExpBuilder)(r `^ (:+) (.+?) $`), (_, lvl, content) => {
333
+ if (content.includes('{{'))
334
+ return _;
335
+ const depth = lvl.length;
336
+ return `${'<dl>'.repeat(depth)}<dd>${content}</dd>${'</dl>'.repeat(depth)}`;
337
+ })
243
338
  .replace((0, common_1.RegExpBuilder)(r `^ ; (.+) $`), '<dl><dt>$1</dt></dl>')
244
339
  .replace((0, common_1.RegExpBuilder)(r `</dl> (\s*?) <dl>`), '$1')
245
340
  // 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>`;
341
+ .replace(/\{\|.+\|\}/gs, (tableInner) => {
342
+ if (tableInner.includes('{{'))
343
+ return tableInner;
344
+ return tableInner
345
+ // {| data (open table)
346
+ .replace((0, common_1.RegExpBuilder)(r `^ \{\| (.*?) $`), (_, attrs) => `<table ${attrs}><tr>`)
347
+ // |+ data
348
+ .replace((0, common_1.RegExpBuilder)(r `^ \|\+ (.*?) $`), (_, content) => `<caption>${content}</caption>`)
349
+ // |- (new row)
350
+ .replace((0, common_1.RegExpBuilder)(r `^ \|- (.*?) $`), (_, attrs) => `</tr><tr ${attrs}>`)
351
+ // |} (close)
352
+ .replace((0, common_1.RegExpBuilder)(r `^ \|\}`), `</tr></table>`)
353
+ // content: !head, !data|head, |text, |data|text, !!head, ||data
354
+ .replace((0, common_1.RegExpBuilder)(r `( ^! | ^\| | !! | \|\| ) (?: ( [^|\n]+? ) \|)? ( [^|\n]*? ) (?= $ | !! | \|\| )`), (_, type, data, content) => {
355
+ const elem = /!/.test(type) ? 'th' : 'td';
356
+ return `<${elem} ${data !== null && data !== void 0 ? data : ''}>${content}</${elem}>`;
357
+ });
358
+ })
359
+ // References: <ref></ref>
360
+ .replace((0, common_1.RegExpBuilder)(r `< ref \s* (?: name \s* = \s* ["']? ([^>'"]+) ["']? [^>]* )?> (.+?) </ ref >`), (_, refname, text) => {
361
+ if (_.includes('{{'))
362
+ return _;
363
+ const refData = { ref: text, id: refs.length + 1, name: refname, i: 0 };
364
+ refs.push(refData);
365
+ return `<sup class="refnote"><a id="cite-${refData.id}" class="ref" href="#ref-${refData.id}">[${refData.id}]</a></sup>`;
366
+ })
367
+ .replace((0, common_1.RegExpBuilder)(r `< ref \s* name \s* = \s* ["']? ( [^>"']+ ) ["']? \s* (?: /> | > .*? </ref> )`), (_, refname) => {
368
+ const ref = refs.find(ref => ref.name === refname);
369
+ if (!ref)
370
+ return _;
371
+ ref.i++;
372
+ const content = `
373
+ <sup class="refnote">
374
+ <a id="cite-${ref.id}${ref.i > 0 ? `_${ref.i}` : ''}" class="ref" href="#ref-${ref.id}">
375
+ [${ref.id}]
376
+ </a>
377
+ </sup>
378
+ `;
379
+ return content;
258
380
  })
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
381
  // Nonstandard: ``code`` and ```code blocks```
261
382
  .replace((0, common_1.RegExpBuilder)(r ` \`\`\` ([^\`]+?) \`\`\` `), '<pre>$1</pre>')
262
383
  .replace((0, common_1.RegExpBuilder)(r ` \`\` ([^\`]+?) \`\` `), '<code>$1</code>')
263
384
  // Spacing
264
385
  .replace(/(\r?\n){2}/g, '\n</p><p>\n');
265
386
  }
266
- // Restore nowiki contents
387
+ // Final (one-time) substitutions
267
388
  for (let i = 0; i < nowikis.length; i++) {
268
389
  outText = outText
269
- .replace(escaper('NOWIKI', i), (_, n) => fullyEscape(nowikis[n]));
390
+ // Restore nowiki contents
391
+ .replace(escaper('NOWIKI', i), nowikis[i]);
270
392
  }
271
- // Substitute magic word functions
272
393
  outText = outText
273
- .replaceAll(escaper('VERT', 0), '|')
274
- .replaceAll(escaper('EQUALS', 0), '=');
394
+ // References: <references />
395
+ .replace((0, common_1.RegExpBuilder)(r `<references \s* /?>`), () => {
396
+ const references = refs.map((refdata) => {
397
+ let multiRefContent = '';
398
+ if (refdata.i > 0) {
399
+ for (let i = 0; i <= refdata.i; i++) {
400
+ multiRefContent += `<sup><a href="#cite-${refdata.id}${i > 0 ? `_${i}` : ''}">${i + 1}</a></sup>`;
401
+ }
402
+ }
403
+ const refline = `
404
+ <li id="ref-${refdata.id}">
405
+ ${refdata.i > 0 ? '&uarr;' : `<a href="#cite-${refdata.id}">&uarr;</a>`}
406
+ ${multiRefContent}
407
+ ${refdata.ref}
408
+ </li>
409
+ `;
410
+ return refline;
411
+ }).join('\n');
412
+ return `<ol>${references}</ol>`;
413
+ })
414
+ // Magic word functions
415
+ .replaceAll(escaper('VERT'), '|')
416
+ .replaceAll(escaper('EQUALS'), '=');
417
+ // Escape all {{ to avoid crashes
418
+ outText = outText
419
+ .replaceAll('{{', '&#123;&#123;');
275
420
  const result = { data: outText, metadata: metadata };
276
421
  return result;
277
422
  }
package/dist/wiki.css.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = String.raw `
4
- body {font-family: sans-serif; margin: 4em; max-width: 1000px; background: #eee;}
4
+ body {font-family: sans-serif; margin: auto; max-width: 1000px; background: #eee;}
5
5
  main {margin: 3em -1em; background: #fff; padding: 1em;}
6
6
  h1, h2 {margin-bottom: 0.6em; font-weight: normal; border-bottom: 1px solid #a2a9b1;}
7
7
  ul, ol {margin: 0.3em 0 0 1.6em; padding: 0;}
@@ -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,11 @@ 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
+ #page+toc {border: 1px solid #aab; padding: 8px; width: fit-content; background-color: #f8f8f8; font-size: 95%;}
30
+ #page+toc-heading {display: block; text-align: center;}
31
+ #page+toc ol {margin: 0 0 0 1.3em;}
32
+
33
+ #infobox {float: right; clear: right; margin: 0 0 1em 1em; width: 300px; padding: 2px; border: 1px solid #CCC; overflow: auto; font-size: 90%;}
34
+ #infobox tr:first-child :first-child {padding: 10px; text-align: center; font-weight: bold; font-size: 120%;}
35
+ #infobox th {padding-left: 10px; text-align: left;}
31
36
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikity",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "Compile wikitext to HTML: wikitext as a templating language.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -41,8 +41,7 @@
41
41
  "dateformat": "^4.6.3",
42
42
  "dedent": "^1.5.3",
43
43
  "escape-html": "^1.0.3",
44
- "glob": "^8.1.0",
45
- "html-formatter": "^0.1.9"
44
+ "glob": "^8.1.0"
46
45
  },
47
46
  "devDependencies": {
48
47
  "@types/dateformat": "^3.0.1",
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)* |