miki-template 2.2.2 → 2.3.0
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/.github/workflows/docs.yml +3 -1
- package/.github/workflows/release.yml +1 -0
- package/benchmarks/ejs-results.json +6 -6
- package/benchmarks/ejs.js +5 -3
- package/benchmarks/handlebars-results.json +6 -6
- package/benchmarks/handlebars.js +5 -8
- package/benchmarks/miki-results.json +6 -6
- package/benchmarks/miki.js +6 -3
- package/benchmarks/pug-results.json +6 -6
- package/benchmarks/pug.js +5 -3
- package/docs/api/async-render.md +88 -3
- package/docs/api/cache.md +90 -3
- package/docs/api/compile.md +131 -3
- package/docs/api/context-processors.md +80 -3
- package/docs/api/filters.md +223 -3
- package/docs/api/finder.md +97 -3
- package/docs/api/helpers.md +56 -3
- package/docs/api/i18n.md +160 -3
- package/docs/api/index.md +82 -28
- package/docs/api/libraries.md +210 -3
- package/docs/api/render-partial.md +84 -3
- package/docs/api/render.md +95 -3
- package/docs/api/security.md +148 -3
- package/docs/api/setup-express.md +78 -2
- package/docs/api/tags.md +138 -4
- package/docs/filter.md +0 -0
- package/docs/guide/advanced-usage.md +403 -6
- package/docs/guide/async-rendering.md +312 -4
- package/docs/guide/context-processors.md +261 -4
- package/docs/guide/custom-filters.md +315 -4
- package/docs/guide/custom-tags.md +275 -4
- package/docs/guide/filters.md +675 -3
- package/docs/guide/getting-started.md +109 -7
- package/docs/guide/installation.md +99 -4
- package/docs/guide/partial-templates.md +371 -4
- package/docs/guide/quick-start.md +228 -6
- package/docs/guide/security.md +348 -3
- package/docs/guide/tags.md +789 -6
- package/docs/guide/template-discovery.md +174 -4
- package/docs/guide/template-inheritance.md +277 -4
- package/docs/index.md +24 -42
- package/docs/integrations/elysia.md +4 -2
- package/docs/integrations/express.md +219 -219
- package/docs/integrations/fastify.md +4 -2
- package/docs/integrations/hono.md +4 -2
- package/docs/integrations/index.md +68 -68
- package/docs/integrations/koa.md +4 -2
- package/docs/integrations/nestjs.md +4 -2
- package/docs/integrations/tsed.md +4 -2
- package/docs/performance.md +45 -8
- package/ex.mjs +1 -1
- package/mkdocs.yml +0 -22
- package/overrides/main.html +1 -1
- package/package.json +1 -1
- package/requirements-docs.txt +2 -1
- package/src/codegen.js +905 -0
- package/src/context.js +42 -30
- package/src/filters.js +16 -0
- package/src/index.js +66 -61
- package/src/tags/control.js +15 -12
- package/src/utils.js +60 -0
- package/tests/filters.test.js +9 -0
- package/.github/workflows/npm-publish-github-packages.yml +0 -36
- package/docs/javascripts/extra.js +0 -174
- package/docs/stylesheets/extra.css +0 -819
- package/overrides/partials/footer.html +0 -9
package/src/context.js
CHANGED
|
@@ -12,6 +12,18 @@ class Context {
|
|
|
12
12
|
this.partialDefs = new Map(); // Store partial definitions
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
get _local() {
|
|
16
|
+
return this.scopes[0] || {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
set _local(value) {
|
|
20
|
+
if (this.scopes.length === 0) {
|
|
21
|
+
this.scopes.unshift(value);
|
|
22
|
+
} else {
|
|
23
|
+
this.scopes[0] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
/**
|
|
16
28
|
* Reset cycle state and blocks for a fresh render.
|
|
17
29
|
* Called at the start of each render pass.
|
|
@@ -49,14 +61,20 @@ class Context {
|
|
|
49
61
|
return '';
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
const
|
|
64
|
+
// Fast path for non-dotted single variable lookups
|
|
65
|
+
const isString = typeof path === 'string';
|
|
66
|
+
const hasDot = isString && path.includes('.');
|
|
67
|
+
|
|
68
|
+
const baseName = hasDot ? path.split('.')[0] : path;
|
|
54
69
|
|
|
55
70
|
let current = undefined;
|
|
56
71
|
let found = false;
|
|
57
72
|
|
|
58
73
|
// Search scopes from top (most local) to bottom (most global)
|
|
59
|
-
|
|
74
|
+
const scopes = this.scopes;
|
|
75
|
+
const len = scopes.length;
|
|
76
|
+
for (let i = 0; i < len; i++) {
|
|
77
|
+
const scope = scopes[i];
|
|
60
78
|
if (scope && typeof scope === 'object' && baseName in scope) {
|
|
61
79
|
current = scope[baseName];
|
|
62
80
|
found = true;
|
|
@@ -65,43 +83,37 @@ class Context {
|
|
|
65
83
|
}
|
|
66
84
|
|
|
67
85
|
if (!found) {
|
|
68
|
-
// Variable truly missing — return undefined so templates can
|
|
69
|
-
// distinguish "missing" from "explicitly null". The variable node
|
|
70
|
-
// and filters handle undefined gracefully.
|
|
71
86
|
return undefined;
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
current = current.call(parent);
|
|
89
|
+
if (hasDot) {
|
|
90
|
+
const parts = path.split('.');
|
|
91
|
+
for (let i = 1; i < parts.length; i++) {
|
|
92
|
+
if (current === undefined || current === null) {
|
|
93
|
+
return current === null ? null : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const parent = current;
|
|
97
|
+
const part = parts[i];
|
|
98
|
+
|
|
99
|
+
if (typeof current === 'object' && part in current) {
|
|
100
|
+
current = current[part];
|
|
101
|
+
} else if (Array.isArray(current) && !isNaN(part)) {
|
|
102
|
+
current = current[parseInt(part, 10)];
|
|
103
|
+
} else {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (typeof current === 'function') {
|
|
108
|
+
current = current.call(parent);
|
|
109
|
+
}
|
|
96
110
|
}
|
|
97
111
|
}
|
|
98
112
|
|
|
99
|
-
// If the final resolved value is a function, call it with no arguments
|
|
100
113
|
if (typeof current === 'function') {
|
|
101
114
|
current = current.call(null);
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
// Preserve null/undefined so filters like default_if_none can detect them.
|
|
105
117
|
return current;
|
|
106
118
|
}
|
|
107
119
|
|
package/src/filters.js
CHANGED
|
@@ -971,5 +971,21 @@ registerFilter('uuid', () => {
|
|
|
971
971
|
});
|
|
972
972
|
});
|
|
973
973
|
|
|
974
|
+
registerFilter('repeat', (val, arg) => {
|
|
975
|
+
const str = String(val === null || val === undefined ? '' : val);
|
|
976
|
+
const count = parseInt(arg, 10);
|
|
977
|
+
if (Number.isNaN(count) || count <= 0) return '';
|
|
978
|
+
return str.repeat(count);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
registerFilter('range', (val, arg) => {
|
|
982
|
+
const end = parseInt(val, 10);
|
|
983
|
+
const start = arg !== undefined && arg !== null ? parseInt(arg, 10) : 0;
|
|
984
|
+
const step = 1;
|
|
985
|
+
const out = [];
|
|
986
|
+
for (let i = start; i < end; i += step) out.push(i);
|
|
987
|
+
return out;
|
|
988
|
+
});
|
|
989
|
+
|
|
974
990
|
module.exports = { registerFilter, getFilter };
|
|
975
991
|
|
package/src/index.js
CHANGED
|
@@ -219,12 +219,15 @@ function readParentSource(parentName, viewsDirs) {
|
|
|
219
219
|
|
|
220
220
|
async function renderASTAsync(nodes, context) {
|
|
221
221
|
context.parentTemplate = null;
|
|
222
|
-
|
|
223
|
-
for (
|
|
224
|
-
const result =
|
|
225
|
-
|
|
222
|
+
let output = '';
|
|
223
|
+
for (let i = 0, len = nodes.length; i < len; i++) {
|
|
224
|
+
const result = nodes[i].render(context);
|
|
225
|
+
if (result instanceof Promise) {
|
|
226
|
+
output += await result;
|
|
227
|
+
} else {
|
|
228
|
+
output += result;
|
|
229
|
+
}
|
|
226
230
|
}
|
|
227
|
-
let output = parts.join('');
|
|
228
231
|
|
|
229
232
|
if (context.parentTemplate) {
|
|
230
233
|
const parentName = context.parentTemplate;
|
|
@@ -279,13 +282,14 @@ async function renderASTAsync(nodes, context) {
|
|
|
279
282
|
*/
|
|
280
283
|
function renderAST(nodes, context) {
|
|
281
284
|
context.parentTemplate = null;
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
let output = '';
|
|
286
|
+
for (let i = 0, len = nodes.length; i < len; i++) {
|
|
287
|
+
const result = nodes[i].render(context);
|
|
284
288
|
if (result instanceof Promise) {
|
|
285
289
|
throw new Error('Async node encountered during sync render. Use asyncRender() instead.');
|
|
286
290
|
}
|
|
287
|
-
|
|
288
|
-
}
|
|
291
|
+
output += result;
|
|
292
|
+
}
|
|
289
293
|
|
|
290
294
|
if (context.parentTemplate) {
|
|
291
295
|
const parentName = context.parentTemplate;
|
|
@@ -354,78 +358,79 @@ function compile(templateStr, options = {}) {
|
|
|
354
358
|
}
|
|
355
359
|
collectPartials(nodes);
|
|
356
360
|
|
|
361
|
+
const { renderBody } = require('./utils');
|
|
362
|
+
const { generateCode, canCodegen } = require('./codegen');
|
|
363
|
+
|
|
364
|
+
const useCodegen = true;
|
|
365
|
+
let compiledRender = null;
|
|
366
|
+
if (useCodegen && canCodegen(nodes)) {
|
|
367
|
+
compiledRender = generateCode(nodes);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function makeContext(contextObj, callOpts) {
|
|
371
|
+
const mergedOpts = callOpts ? { ...opts, ...callOpts } : opts;
|
|
372
|
+
const processedContextObj = applyContextProcessors({ ...contextObj });
|
|
373
|
+
const context = new Context(processedContextObj, mergedOpts);
|
|
374
|
+
context.reset();
|
|
375
|
+
if (parser.blocks) {
|
|
376
|
+
for (const [name, blockList] of Object.entries(parser.blocks)) {
|
|
377
|
+
context.blocks[name] = [...blockList];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for (const [name, partial] of Object.entries(partialDefs)) {
|
|
381
|
+
context.registerPartial(name, partial);
|
|
382
|
+
}
|
|
383
|
+
return context;
|
|
384
|
+
}
|
|
385
|
+
|
|
357
386
|
return {
|
|
387
|
+
_usesCodegen: !!compiledRender,
|
|
358
388
|
render: (contextObj = {}) => {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
context.blocks[name] = [...blockList];
|
|
389
|
+
const context = makeContext(contextObj);
|
|
390
|
+
if (compiledRender) {
|
|
391
|
+
const res = compiledRender(context);
|
|
392
|
+
if (context.parentTemplate) {
|
|
393
|
+
return renderAST(nodes, context);
|
|
365
394
|
}
|
|
366
|
-
|
|
367
|
-
// Register partial definitions from compile-time
|
|
368
|
-
for (const [name, partial] of Object.entries(partialDefs)) {
|
|
369
|
-
context.registerPartial(name, partial);
|
|
395
|
+
return res;
|
|
370
396
|
}
|
|
371
397
|
return renderAST(nodes, context);
|
|
372
398
|
},
|
|
373
399
|
renderWith: (contextObj = {}, callOptions = {}) => {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
for (const [name, blockList] of Object.entries(parser.blocks)) {
|
|
380
|
-
context.blocks[name] = [...blockList];
|
|
400
|
+
const context = makeContext(contextObj, callOptions);
|
|
401
|
+
if (compiledRender) {
|
|
402
|
+
const res = compiledRender(context);
|
|
403
|
+
if (context.parentTemplate) {
|
|
404
|
+
return renderAST(nodes, context);
|
|
381
405
|
}
|
|
382
|
-
|
|
383
|
-
for (const [name, partial] of Object.entries(partialDefs)) {
|
|
384
|
-
context.registerPartial(name, partial);
|
|
406
|
+
return res;
|
|
385
407
|
}
|
|
386
408
|
return renderAST(nodes, context);
|
|
387
409
|
},
|
|
388
410
|
asyncRender: async (contextObj = {}) => {
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
context.blocks[name] = [...blockList];
|
|
411
|
+
const context = makeContext(contextObj);
|
|
412
|
+
if (compiledRender) {
|
|
413
|
+
const res = compiledRender(context);
|
|
414
|
+
if (context.parentTemplate || res instanceof Promise || (typeof res === 'string' && res.includes('[object Promise]'))) {
|
|
415
|
+
return await renderASTAsync(nodes, context);
|
|
395
416
|
}
|
|
396
|
-
|
|
397
|
-
for (const [name, partial] of Object.entries(partialDefs)) {
|
|
398
|
-
context.registerPartial(name, partial);
|
|
417
|
+
return res;
|
|
399
418
|
}
|
|
400
419
|
return await renderASTAsync(nodes, context);
|
|
401
420
|
},
|
|
402
421
|
asyncRenderWith: async (contextObj = {}, callOptions = {}) => {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
for (const [name, blockList] of Object.entries(parser.blocks)) {
|
|
409
|
-
context.blocks[name] = [...blockList];
|
|
422
|
+
const context = makeContext(contextObj, callOptions);
|
|
423
|
+
if (compiledRender) {
|
|
424
|
+
const res = compiledRender(context);
|
|
425
|
+
if (context.parentTemplate || res instanceof Promise || (typeof res === 'string' && res.includes('[object Promise]'))) {
|
|
426
|
+
return await renderASTAsync(nodes, context);
|
|
410
427
|
}
|
|
411
|
-
|
|
412
|
-
for (const [name, partial] of Object.entries(partialDefs)) {
|
|
413
|
-
context.registerPartial(name, partial);
|
|
428
|
+
return res;
|
|
414
429
|
}
|
|
415
430
|
return await renderASTAsync(nodes, context);
|
|
416
431
|
},
|
|
417
432
|
renderBlock: (blockName, contextObj = {}) => {
|
|
418
|
-
const
|
|
419
|
-
const context = new Context(processedContextObj, opts);
|
|
420
|
-
context.blocks = {};
|
|
421
|
-
if (parser.blocks) {
|
|
422
|
-
for (const [name, blockList] of Object.entries(parser.blocks)) {
|
|
423
|
-
context.blocks[name] = [...blockList];
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
for (const [name, partial] of Object.entries(partialDefs)) {
|
|
427
|
-
context.registerPartial(name, partial);
|
|
428
|
-
}
|
|
433
|
+
const context = makeContext(contextObj);
|
|
429
434
|
renderAST(nodes, context);
|
|
430
435
|
const blockStack = context.blocks[blockName];
|
|
431
436
|
if (!blockStack || blockStack.length === 0) {
|
|
@@ -442,7 +447,7 @@ function compile(templateStr, options = {}) {
|
|
|
442
447
|
}
|
|
443
448
|
context.push({ block: { super: superVal } });
|
|
444
449
|
context.blockRenderIndices[blockName] = 0;
|
|
445
|
-
const result = blockStack[0].body
|
|
450
|
+
const result = renderBody(blockStack[0].body, context);
|
|
446
451
|
context.pop();
|
|
447
452
|
context.blockRenderIndices[blockName] = -1;
|
|
448
453
|
return result;
|
|
@@ -457,7 +462,7 @@ function compile(templateStr, options = {}) {
|
|
|
457
462
|
if (!partial) {
|
|
458
463
|
throw new Error(`Partial '${partialName}' not found`);
|
|
459
464
|
}
|
|
460
|
-
return partial.body
|
|
465
|
+
return renderBody(partial.body, context);
|
|
461
466
|
}
|
|
462
467
|
};
|
|
463
468
|
});
|
package/src/tags/control.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Control flow template tags: if, for, with, cycle, comment, with, firstof.
|
|
3
3
|
*/
|
|
4
4
|
const { parseVariableExpression } = require('../parser');
|
|
5
|
+
const { getFilter } = require('../filters');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Helper to parse a string into an array of tokens.
|
|
@@ -206,27 +207,28 @@ class ForNode {
|
|
|
206
207
|
this.filters = filters;
|
|
207
208
|
this.body = body;
|
|
208
209
|
this.emptyBody = emptyBody;
|
|
210
|
+
this._filterFns = filters.map(f => {
|
|
211
|
+
const fn = getFilter(f.name);
|
|
212
|
+
return { fn, arg: f.arg };
|
|
213
|
+
});
|
|
209
214
|
}
|
|
210
215
|
|
|
211
216
|
render(context) {
|
|
212
217
|
let rawItems = context.get(this.iterablePath);
|
|
213
218
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const filterFn = getFilter(filterInfo.name);
|
|
218
|
-
if (!filterFn) {
|
|
219
|
-
throw new Error(`Unknown filter: '${filterInfo.name}'`);
|
|
219
|
+
for (const { fn, arg } of this._filterFns) {
|
|
220
|
+
if (!fn) {
|
|
221
|
+
throw new Error(`Unknown filter: '${arg.name}'`);
|
|
220
222
|
}
|
|
221
223
|
let argVal = undefined;
|
|
222
|
-
if (
|
|
223
|
-
if (
|
|
224
|
-
argVal =
|
|
225
|
-
} else if (
|
|
226
|
-
argVal = context.get(
|
|
224
|
+
if (arg) {
|
|
225
|
+
if (arg.type === 'literal') {
|
|
226
|
+
argVal = arg.value;
|
|
227
|
+
} else if (arg.type === 'variable') {
|
|
228
|
+
argVal = context.get(arg.value);
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
|
-
rawItems =
|
|
231
|
+
rawItems = fn(rawItems, argVal);
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
let items = [];
|
|
@@ -706,6 +708,7 @@ module.exports = {
|
|
|
706
708
|
PartialDefNode,
|
|
707
709
|
PartialNode,
|
|
708
710
|
evaluateCondition,
|
|
711
|
+
tokenizeExpr,
|
|
709
712
|
parsers: {
|
|
710
713
|
if: parseIf,
|
|
711
714
|
for: parseFor,
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fast string concatenation for an array of AST nodes.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the common pattern
|
|
5
|
+
* body.map(n => n.render(context)).join('')
|
|
6
|
+
* with a tight loop that calls .render() and concatenates directly.
|
|
7
|
+
* V8 optimizes `let s = ''; s += x` well (cons strings), whereas
|
|
8
|
+
* `.map().join('')` allocates an intermediate array.
|
|
9
|
+
*/
|
|
10
|
+
function renderBody(body, context) {
|
|
11
|
+
let out = '';
|
|
12
|
+
if (!body) return out;
|
|
13
|
+
for (let i = 0, len = body.length; i < len; i++) {
|
|
14
|
+
const r = body[i].render(context);
|
|
15
|
+
if (r instanceof Promise) {
|
|
16
|
+
out += '';
|
|
17
|
+
} else {
|
|
18
|
+
out += r;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Async version of renderBody — awaits Promises from any node.
|
|
26
|
+
*/
|
|
27
|
+
async function renderBodyAsync(body, context) {
|
|
28
|
+
let out = '';
|
|
29
|
+
if (!body) return out;
|
|
30
|
+
for (let i = 0, len = body.length; i < len; i++) {
|
|
31
|
+
const r = body[i].render(context);
|
|
32
|
+
if (r instanceof Promise) out += await r;
|
|
33
|
+
else out += r;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render body, returning a Promise or string based on what nodes
|
|
40
|
+
* produce. Used by HelperNode and the render* entry points.
|
|
41
|
+
*/
|
|
42
|
+
function renderBodyMaybeAsync(body, context) {
|
|
43
|
+
let out = '';
|
|
44
|
+
let pending = null;
|
|
45
|
+
for (let i = 0, len = body.length; i < len; i++) {
|
|
46
|
+
const r = body[i].render(context);
|
|
47
|
+
if (r instanceof Promise) {
|
|
48
|
+
if (!pending) pending = [];
|
|
49
|
+
pending.push(r.then(v => { out += v; }));
|
|
50
|
+
} else {
|
|
51
|
+
out += r;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (pending) {
|
|
55
|
+
return Promise.all(pending).then(() => out);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { renderBody, renderBodyAsync, renderBodyMaybeAsync };
|
package/tests/filters.test.js
CHANGED
|
@@ -251,4 +251,13 @@ test('Filters - credit_card, ssn, ip_address, uuid', () => {
|
|
|
251
251
|
|
|
252
252
|
const uuid = getFilter('uuid');
|
|
253
253
|
assert.ok(uuid().match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/));
|
|
254
|
+
|
|
255
|
+
test('Filters - repeat and range', () => {
|
|
256
|
+
const repeat = getFilter('repeat');
|
|
257
|
+
assert.strictEqual(repeat('a', 3), 'aaa');
|
|
258
|
+
assert.strictEqual(repeat('', 5), '');
|
|
259
|
+
const range = getFilter('range');
|
|
260
|
+
assert.deepEqual(range('5'), [0,1,2,3,4]);
|
|
261
|
+
assert.deepEqual(range('5', '2'), [2,3,4]);
|
|
262
|
+
});
|
|
254
263
|
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: Node.js Package
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [created]
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
build:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
- uses: actions/setup-node@v4
|
|
16
|
-
with:
|
|
17
|
-
node-version: 20
|
|
18
|
-
- run: npm ci
|
|
19
|
-
- run: npm test
|
|
20
|
-
|
|
21
|
-
publish-gpr:
|
|
22
|
-
needs: build
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
permissions:
|
|
25
|
-
contents: read
|
|
26
|
-
packages: write
|
|
27
|
-
steps:
|
|
28
|
-
- uses: actions/checkout@v4
|
|
29
|
-
- uses: actions/setup-node@v4
|
|
30
|
-
with:
|
|
31
|
-
node-version: 20
|
|
32
|
-
registry-url: https://npm.pkg.github.com/
|
|
33
|
-
- run: npm ci
|
|
34
|
-
- run: npm publish
|
|
35
|
-
env:
|
|
36
|
-
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
document$.subscribe(function () {
|
|
2
|
-
console.log("miki-template docs initialized");
|
|
3
|
-
|
|
4
|
-
const navbar = document.querySelector(".md-header");
|
|
5
|
-
if (navbar) {
|
|
6
|
-
let lastScroll = 0;
|
|
7
|
-
const scrollThreshold = 50;
|
|
8
|
-
|
|
9
|
-
window.addEventListener("scroll", function () {
|
|
10
|
-
const currentScroll = window.pageYOffset;
|
|
11
|
-
|
|
12
|
-
if (currentScroll <= 0) {
|
|
13
|
-
navbar.style.boxShadow = "0 2px 8px var(--md-shadow-color)";
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (currentScroll > lastScroll && currentScroll > scrollThreshold) {
|
|
18
|
-
navbar.style.transform = "translateY(-100%)";
|
|
19
|
-
navbar.style.transition = "transform 0.3s ease";
|
|
20
|
-
} else {
|
|
21
|
-
navbar.style.transform = "translateY(0)";
|
|
22
|
-
navbar.style.transition = "transform 0.3s ease";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
lastScroll = currentScroll;
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
document.querySelectorAll(".md-clipboard").forEach(function (button) {
|
|
30
|
-
button.addEventListener("click", async function () {
|
|
31
|
-
const code = this.closest(".highlight").querySelector("code").innerText;
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
await navigator.clipboard.writeText(code);
|
|
35
|
-
const originalHTML = this.innerHTML;
|
|
36
|
-
this.innerHTML = '<span class="md-icon">✓</span> Copied!';
|
|
37
|
-
this.style.color = "var(--md-accent-fg-color)";
|
|
38
|
-
this.style.opacity = "1";
|
|
39
|
-
|
|
40
|
-
setTimeout(function () {
|
|
41
|
-
button.innerHTML = originalHTML;
|
|
42
|
-
button.style.color = "";
|
|
43
|
-
button.style.opacity = "";
|
|
44
|
-
}, 2000);
|
|
45
|
-
} catch (err) {
|
|
46
|
-
console.error("Failed to copy: ", err);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
document.querySelectorAll(".md-typeset a[href^='#']").forEach(function (anchor) {
|
|
52
|
-
anchor.addEventListener("click", function (e) {
|
|
53
|
-
const targetId = this.getAttribute("href");
|
|
54
|
-
if (targetId === "#") return;
|
|
55
|
-
|
|
56
|
-
const target = document.querySelector(targetId);
|
|
57
|
-
if (target) {
|
|
58
|
-
e.preventDefault();
|
|
59
|
-
const headerOffset = 80;
|
|
60
|
-
const elementPosition = target.getBoundingClientRect().top;
|
|
61
|
-
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
|
|
62
|
-
|
|
63
|
-
window.scrollTo({
|
|
64
|
-
top: offsetPosition,
|
|
65
|
-
behavior: "smooth",
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
history.replaceState(null, null, targetId);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
document.querySelectorAll(".md-typeset .task-list-item input[type='checkbox']").forEach(function (checkbox) {
|
|
74
|
-
checkbox.addEventListener("change", function () {
|
|
75
|
-
const label = this.closest(".task-list-item");
|
|
76
|
-
if (this.checked) {
|
|
77
|
-
label.style.opacity = "0.6";
|
|
78
|
-
label.style.textDecoration = "line-through";
|
|
79
|
-
} else {
|
|
80
|
-
label.style.opacity = "1";
|
|
81
|
-
label.style.textDecoration = "none";
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
document.querySelectorAll(".md-tabs__link").forEach(function (tab) {
|
|
87
|
-
tab.addEventListener("click", function () {
|
|
88
|
-
document.querySelectorAll(".md-tabs__link").forEach(function (t) {
|
|
89
|
-
t.style.transform = "scale(1)";
|
|
90
|
-
});
|
|
91
|
-
this.style.transform = "scale(1.05)";
|
|
92
|
-
setTimeout(function () {
|
|
93
|
-
tab.style.transform = "scale(1)";
|
|
94
|
-
}, 200);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
document.querySelectorAll(".md-typeset .tabbed-set > label").forEach(function (tabLabel) {
|
|
99
|
-
tabLabel.addEventListener("click", function () {
|
|
100
|
-
this.style.transform = "scale(0.98)";
|
|
101
|
-
setTimeout(function () {
|
|
102
|
-
tabLabel.style.transform = "scale(1)";
|
|
103
|
-
}, 100);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const searchInput = document.querySelector(".md-search__input");
|
|
108
|
-
if (searchInput) {
|
|
109
|
-
searchInput.addEventListener("focus", function () {
|
|
110
|
-
this.parentElement.style.boxShadow = "0 0 0 3px var(--md-accent-fg-color), 0 4px 12px var(--md-shadow-color)";
|
|
111
|
-
this.parentElement.style.transition = "box-shadow 0.2s ease";
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
searchInput.addEventListener("blur", function () {
|
|
115
|
-
this.parentElement.style.boxShadow = "";
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
document.querySelectorAll(".md-typeset .admonition").forEach(function (admonition) {
|
|
120
|
-
admonition.addEventListener("mouseenter", function () {
|
|
121
|
-
this.style.transform = "translateY(-2px)";
|
|
122
|
-
this.style.transition = "transform 0.2s ease";
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
admonition.addEventListener("mouseleave", function () {
|
|
126
|
-
this.style.transform = "translateY(0)";
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
document.querySelectorAll(".md-typeset .md-button").forEach(function (button) {
|
|
131
|
-
button.addEventListener("mouseenter", function () {
|
|
132
|
-
this.style.transform = "translateY(-2px)";
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
button.addEventListener("mouseleave", function () {
|
|
136
|
-
this.style.transform = "translateY(0)";
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
document.querySelectorAll(".md-content__inner table:not([class]) tbody tr").forEach(function (row) {
|
|
141
|
-
row.addEventListener("mouseenter", function () {
|
|
142
|
-
this.style.transition = "background-color 0.2s ease";
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const observerOptions = {
|
|
147
|
-
root: null,
|
|
148
|
-
rootMargin: "0px",
|
|
149
|
-
threshold: 0.1,
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const observer = new IntersectionObserver(function (entries) {
|
|
153
|
-
entries.forEach(function (entry) {
|
|
154
|
-
if (entry.isIntersecting) {
|
|
155
|
-
entry.target.style.opacity = "1";
|
|
156
|
-
entry.target.style.transform = "translateY(0)";
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}, observerOptions);
|
|
160
|
-
|
|
161
|
-
document.querySelectorAll(".md-typeset h2, .md-typeset h3, .md-typeset .admonition").forEach(function (el) {
|
|
162
|
-
el.style.opacity = "0";
|
|
163
|
-
el.style.transform = "translateY(10px)";
|
|
164
|
-
el.style.transition = "opacity 0.5s ease, transform 0.5s ease";
|
|
165
|
-
observer.observe(el);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
setTimeout(function () {
|
|
169
|
-
document.querySelectorAll(".md-typeset h2, .md-typeset h3, .md-typeset .admonition").forEach(function (el) {
|
|
170
|
-
el.style.opacity = "1";
|
|
171
|
-
el.style.transform = "translateY(0)";
|
|
172
|
-
});
|
|
173
|
-
}, 100);
|
|
174
|
-
});
|