@ktjs/core 0.22.5 → 0.24.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/dist/index.d.ts +227 -298
- package/dist/index.iife.js +295 -145
- package/dist/index.legacy.js +291 -143
- package/dist/index.mjs +290 -145
- package/dist/jsx/index.d.ts +180 -322
- package/dist/jsx/index.mjs +145 -145
- package/dist/jsx/jsx-runtime.d.ts +20 -8
- package/dist/jsx/jsx-runtime.mjs +144 -98
- package/package.json +2 -2
package/dist/jsx/index.mjs
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
1
|
+
// Shared constants
|
|
2
|
+
// Empty for now - can be extended with framework-wide constants
|
|
3
|
+
/**
|
|
4
|
+
* Mark the attribute as SVG to handle special cases during rendering.
|
|
5
|
+
*/
|
|
6
|
+
const SVG_ATTR_FLAG = '__kt_svg__';
|
|
7
|
+
/**
|
|
8
|
+
* Mark the attribute as MathML to handle special cases during rendering.
|
|
9
|
+
*/
|
|
10
|
+
const MATHML_ATTR_FLAG = '__kt_mathml__';
|
|
11
|
+
|
|
1
12
|
// Cached native methods for performance optimization
|
|
2
13
|
const $isArray = Array.isArray;
|
|
3
|
-
const $
|
|
4
|
-
const $
|
|
14
|
+
const $is = Object.is;
|
|
15
|
+
const $random = Math.random;
|
|
16
|
+
const $isThenable = (o) => typeof o?.then === 'function';
|
|
5
17
|
|
|
6
18
|
// DOM manipulation utilities
|
|
7
19
|
// # dom natives
|
|
20
|
+
const $isNode = (x) => x?.nodeType > 0;
|
|
21
|
+
/**
|
|
22
|
+
* Safe replace `oldNode` With `newNode`
|
|
23
|
+
*/
|
|
24
|
+
const $replaceNode = (oldNode, newNode) => {
|
|
25
|
+
if ($isNode(oldNode) && $isNode(newNode)) {
|
|
26
|
+
if (newNode.contains(oldNode)) {
|
|
27
|
+
newNode.remove();
|
|
28
|
+
}
|
|
29
|
+
oldNode.replaceWith(newNode);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
8
32
|
/**
|
|
9
33
|
* & Remove `bind` because it is shockingly slower than wrapper
|
|
10
34
|
* & `window.document` is safe because it is not configurable and its setter is undefined
|
|
@@ -50,129 +74,21 @@ const applyModel = (element, valueRef, propName, eventName) => {
|
|
|
50
74
|
element.addEventListener(eventName, () => (valueRef.value = element[propName]));
|
|
51
75
|
};
|
|
52
76
|
|
|
77
|
+
if (typeof Symbol === 'undefined') {
|
|
78
|
+
window.Symbol = function Symbol(description) {
|
|
79
|
+
return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
53
83
|
// Shared utilities and cached native methods for kt.js framework
|
|
54
|
-
|
|
55
|
-
Object.defineProperty(window, '__ktjs__', { value: '0.22.2' });
|
|
84
|
+
Object.defineProperty(window, '__ktjs__', { value: '0.22.6' });
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* @internal
|
|
64
|
-
*/
|
|
65
|
-
_value;
|
|
66
|
-
/**
|
|
67
|
-
* @internal
|
|
68
|
-
*/
|
|
69
|
-
_onChanges;
|
|
70
|
-
constructor(_value, _onChanges) {
|
|
71
|
-
this._value = _value;
|
|
72
|
-
this._onChanges = _onChanges;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* If new value and old value are both nodes, the old one will be replaced in the DOM
|
|
76
|
-
*/
|
|
77
|
-
get value() {
|
|
78
|
-
return this._value;
|
|
79
|
-
}
|
|
80
|
-
set value(newValue) {
|
|
81
|
-
if (newValue === this._value) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
// replace the old node with the new one in the DOM if both are nodes
|
|
85
|
-
if (this._value instanceof Node && newValue instanceof Node) {
|
|
86
|
-
if (newValue.contains(this._value)) {
|
|
87
|
-
this._value.remove();
|
|
88
|
-
}
|
|
89
|
-
this._value.replaceWith(newValue);
|
|
90
|
-
}
|
|
91
|
-
const oldValue = this._value;
|
|
92
|
-
this._value = newValue;
|
|
93
|
-
for (let i = 0; i < this._onChanges.length; i++) {
|
|
94
|
-
this._onChanges[i](newValue, oldValue);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Register a callback when the value changes
|
|
99
|
-
* @param callback (newValue, oldValue) => xxx
|
|
100
|
-
*/
|
|
101
|
-
addOnChange(callback) {
|
|
102
|
-
if (typeof callback !== 'function') {
|
|
103
|
-
throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
|
|
104
|
-
}
|
|
105
|
-
this._onChanges.push(callback);
|
|
106
|
-
}
|
|
107
|
-
removeOnChange(callback) {
|
|
108
|
-
for (let i = this._onChanges.length - 1; i >= 0; i--) {
|
|
109
|
-
if (this._onChanges[i] === callback) {
|
|
110
|
-
this._onChanges.splice(i, 1);
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const isKTRef = (obj) => obj?.isKT === true;
|
|
118
|
-
/**
|
|
119
|
-
* Reference to the created HTML element.
|
|
120
|
-
* - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
|
|
121
|
-
* - can alse be used to store normal values, but it is not reactive.
|
|
122
|
-
* - if the value is already a `KTRef`, it will be returned **directly**.
|
|
123
|
-
* @param value mostly an HTMLElement
|
|
124
|
-
*/
|
|
125
|
-
function ref(value, onChange) {
|
|
126
|
-
if (isKTRef(value)) {
|
|
127
|
-
if (onChange) {
|
|
128
|
-
value.addOnChange(onChange);
|
|
129
|
-
}
|
|
130
|
-
return value;
|
|
131
|
-
}
|
|
132
|
-
return new KTRef(value, onChange ? [onChange] : []);
|
|
133
|
-
}
|
|
134
|
-
function deref(value) {
|
|
135
|
-
return isKTRef(value) ? value.value : value;
|
|
136
|
-
}
|
|
137
|
-
function kcollect() {
|
|
138
|
-
const newObj = {};
|
|
139
|
-
const entries = $entries(this);
|
|
140
|
-
for (let i = 0; i < entries.length; i++) {
|
|
141
|
-
const key = entries[i][0];
|
|
142
|
-
if (key === 'kcollect') {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
newObj[key] = entries[i][1].value;
|
|
146
|
-
}
|
|
147
|
-
return newObj;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Make all first-level properties of the object a `KTRef`.
|
|
151
|
-
* - `obj.a.b` is not reactive
|
|
152
|
-
*/
|
|
153
|
-
const surfaceRef = (obj) => {
|
|
154
|
-
const entries = $entries(obj);
|
|
155
|
-
const newObj = { kcollect };
|
|
156
|
-
for (let i = 0; i < entries.length; i++) {
|
|
157
|
-
newObj[entries[i][0]] = ref(entries[i][1]);
|
|
158
|
-
}
|
|
159
|
-
return newObj;
|
|
160
|
-
};
|
|
161
|
-
// # asserts
|
|
162
|
-
/**
|
|
163
|
-
* Assert k-model to be a ref object
|
|
164
|
-
*/
|
|
165
|
-
const $modelOrRef = (props, defaultValue) => {
|
|
166
|
-
// & props is an object. Won't use it in any other place
|
|
167
|
-
if ('k-model' in props) {
|
|
168
|
-
const kmodel = props['k-model'];
|
|
169
|
-
if (!kmodel?.isKT) {
|
|
170
|
-
throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
|
|
171
|
-
}
|
|
172
|
-
return kmodel;
|
|
173
|
-
}
|
|
174
|
-
return ref(defaultValue);
|
|
175
|
-
};
|
|
86
|
+
// export const KT_TYPE_REF = 1 as const;
|
|
87
|
+
// export const KT_TYPE_COMPUTED = 2 as const;
|
|
88
|
+
// export type KTReactiveTypeEnum = typeof KT_TYPE_REF | typeof KT_TYPE_COMPUTED;
|
|
89
|
+
const isKT = (obj) => obj?.isKT;
|
|
90
|
+
const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
|
|
91
|
+
const isComputed = (obj) => obj?.ktType === 2 /* KTReactiveType.COMPUTED */;
|
|
176
92
|
|
|
177
93
|
const booleanHandler = (element, key, value) => {
|
|
178
94
|
if (key in element) {
|
|
@@ -232,14 +148,25 @@ function attrIsObject(element, attr) {
|
|
|
232
148
|
}
|
|
233
149
|
}
|
|
234
150
|
}
|
|
151
|
+
if ('k-html' in attr) {
|
|
152
|
+
const html = attr['k-html'];
|
|
153
|
+
if (isKT(html)) {
|
|
154
|
+
element.innerHTML = html.value;
|
|
155
|
+
html.addOnChange((v) => (element.innerHTML = v));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
element.innerHTML = html;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
235
161
|
for (const key in attr) {
|
|
236
|
-
if (key === '
|
|
162
|
+
if (key === 'k-if' ||
|
|
163
|
+
key === 'k-model' ||
|
|
164
|
+
key === 'ref' ||
|
|
165
|
+
key === 'class' ||
|
|
237
166
|
key === 'className' ||
|
|
238
167
|
key === 'style' ||
|
|
239
168
|
key === 'children' ||
|
|
240
|
-
key === 'k-
|
|
241
|
-
key.startsWith('k-model') ||
|
|
242
|
-
key === 'ref') {
|
|
169
|
+
key === 'k-html') {
|
|
243
170
|
continue;
|
|
244
171
|
}
|
|
245
172
|
const o = attr[key];
|
|
@@ -249,6 +176,7 @@ function attrIsObject(element, attr) {
|
|
|
249
176
|
}
|
|
250
177
|
// normal attributes
|
|
251
178
|
else {
|
|
179
|
+
// todo 这里也可以绑定ref的
|
|
252
180
|
(handlers[key] || defaultHandler)(element, key, o);
|
|
253
181
|
}
|
|
254
182
|
}
|
|
@@ -265,13 +193,25 @@ function applyAttr(element, attr) {
|
|
|
265
193
|
}
|
|
266
194
|
}
|
|
267
195
|
|
|
196
|
+
const assureNode = (o) => ($isNode(o) ? o : document.createTextNode(o));
|
|
268
197
|
function apdSingle(element, c) {
|
|
269
198
|
// & JSX should ignore false, undefined, and null
|
|
270
199
|
if (c === false || c === undefined || c === null) {
|
|
271
200
|
return;
|
|
272
201
|
}
|
|
273
|
-
if (
|
|
274
|
-
|
|
202
|
+
if (isKT(c)) {
|
|
203
|
+
let node = assureNode(c.value);
|
|
204
|
+
$append.call(element, node);
|
|
205
|
+
c.addOnChange((newValue, oldValue) => {
|
|
206
|
+
if ($isNode(newValue) && $isNode(oldValue)) {
|
|
207
|
+
// & this case is handled automically in `class KTRef`
|
|
208
|
+
// todo 2 cases might be able to merge into 1
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const oldNode = node;
|
|
212
|
+
node = assureNode(newValue);
|
|
213
|
+
oldNode.replaceWith(node);
|
|
214
|
+
});
|
|
275
215
|
}
|
|
276
216
|
else {
|
|
277
217
|
$append.call(element, c);
|
|
@@ -316,8 +256,74 @@ function applyContent(element, content) {
|
|
|
316
256
|
}
|
|
317
257
|
}
|
|
318
258
|
|
|
259
|
+
class KTRef {
|
|
260
|
+
/**
|
|
261
|
+
* Indicates that this is a KTRef instance
|
|
262
|
+
*/
|
|
263
|
+
isKT = true;
|
|
264
|
+
ktType = 1 /* KTReactiveType.REF */;
|
|
265
|
+
/**
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
_value;
|
|
269
|
+
/**
|
|
270
|
+
* @internal
|
|
271
|
+
*/
|
|
272
|
+
_onChanges;
|
|
273
|
+
constructor(_value, _onChanges) {
|
|
274
|
+
this._value = _value;
|
|
275
|
+
this._onChanges = _onChanges;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* If new value and old value are both nodes, the old one will be replaced in the DOM
|
|
279
|
+
*/
|
|
280
|
+
get value() {
|
|
281
|
+
return this._value;
|
|
282
|
+
}
|
|
283
|
+
set value(newValue) {
|
|
284
|
+
if ($is(newValue, this._value)) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const oldValue = this._value;
|
|
288
|
+
$replaceNode(oldValue, newValue);
|
|
289
|
+
this._value = newValue;
|
|
290
|
+
for (let i = 0; i < this._onChanges.length; i++) {
|
|
291
|
+
this._onChanges[i](newValue, oldValue);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Register a callback when the value changes
|
|
296
|
+
* @param callback (newValue, oldValue) => xxx
|
|
297
|
+
*/
|
|
298
|
+
addOnChange(callback) {
|
|
299
|
+
if (typeof callback !== 'function') {
|
|
300
|
+
throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
|
|
301
|
+
}
|
|
302
|
+
this._onChanges.push(callback);
|
|
303
|
+
}
|
|
304
|
+
removeOnChange(callback) {
|
|
305
|
+
for (let i = this._onChanges.length - 1; i >= 0; i--) {
|
|
306
|
+
if (this._onChanges[i] === callback) {
|
|
307
|
+
this._onChanges.splice(i, 1);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Reference to the created HTML element.
|
|
316
|
+
* - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
|
|
317
|
+
* - can alse be used to store normal values, but it is not reactive.
|
|
318
|
+
* - if the value is already a `KTRef`, it will be returned **directly**.
|
|
319
|
+
* @param value mostly an HTMLElement
|
|
320
|
+
*/
|
|
321
|
+
function ref(value, onChange) {
|
|
322
|
+
return new KTRef(value, []);
|
|
323
|
+
}
|
|
324
|
+
|
|
319
325
|
function applyKModel(element, valueRef) {
|
|
320
|
-
if (!
|
|
326
|
+
if (!isKT(valueRef)) {
|
|
321
327
|
console.warn('[kt.js warn] k-model value must be a KTRef.');
|
|
322
328
|
return;
|
|
323
329
|
}
|
|
@@ -344,9 +350,6 @@ const htmlCreator = (tag) => document.createElement(tag);
|
|
|
344
350
|
const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
|
|
345
351
|
const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
|
|
346
352
|
let creator = htmlCreator;
|
|
347
|
-
// # consts
|
|
348
|
-
const SVG_ATTR_FLAG = '__kt_svg__';
|
|
349
|
-
const MATHML_ATTR_FLAG = '__kt_mathml__';
|
|
350
353
|
/**
|
|
351
354
|
* Create an enhanced HTMLElement.
|
|
352
355
|
* - Only supports HTMLElements, **NOT** SVGElements or other Elements.
|
|
@@ -357,7 +360,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
|
|
|
357
360
|
* ## About
|
|
358
361
|
* @package @ktjs/core
|
|
359
362
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
360
|
-
* @version 0.
|
|
363
|
+
* @version 0.24.0 (Last Update: 2026.02.05 12:08:54.164)
|
|
361
364
|
* @license MIT
|
|
362
365
|
* @link https://github.com/baendlorel/kt.js
|
|
363
366
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
|
@@ -387,13 +390,7 @@ const h = (tag, attr, content) => {
|
|
|
387
390
|
applyAttr(element, attr);
|
|
388
391
|
applyContent(element, content);
|
|
389
392
|
if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
|
|
390
|
-
|
|
391
|
-
if (isKTRef(kmodel)) {
|
|
392
|
-
applyKModel(element, kmodel);
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
throw new Error('[kt.js error] k-model value must be a KTRef.');
|
|
396
|
-
}
|
|
393
|
+
applyKModel(element, attr['k-model']);
|
|
397
394
|
}
|
|
398
395
|
return element;
|
|
399
396
|
};
|
|
@@ -413,20 +410,23 @@ const placeholder = () => document.createComment('k-if');
|
|
|
413
410
|
* @param props properties/attributes
|
|
414
411
|
*/
|
|
415
412
|
function jsx(tag, props) {
|
|
416
|
-
|
|
413
|
+
if (isComputed(props.ref)) {
|
|
414
|
+
throw new Error('[kt.js error] Cannot assign a computed value to an element.');
|
|
415
|
+
}
|
|
416
|
+
const maybeDummyRef = isRef(props.ref) ? props.ref : dummyRef;
|
|
417
417
|
let el;
|
|
418
418
|
if ('k-if' in props) {
|
|
419
419
|
const kif = props['k-if'];
|
|
420
420
|
let condition = kif; // assume boolean by default
|
|
421
421
|
// Handle reactive k-if
|
|
422
|
-
if (
|
|
422
|
+
if (isKT(kif)) {
|
|
423
423
|
kif.addOnChange((newValue, oldValue) => {
|
|
424
424
|
if (newValue === oldValue) {
|
|
425
425
|
return;
|
|
426
426
|
}
|
|
427
427
|
const oldEl = el;
|
|
428
428
|
el = newValue ? create(tag, props) : placeholder();
|
|
429
|
-
oldEl
|
|
429
|
+
$replaceNode(oldEl, el);
|
|
430
430
|
maybeDummyRef.value = el;
|
|
431
431
|
});
|
|
432
432
|
condition = kif.value;
|
|
@@ -507,4 +507,4 @@ function createRedrawable(creator) {
|
|
|
507
507
|
return elRef;
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
export {
|
|
510
|
+
export { Fragment, h as createElement, createRedrawable, jsx, jsxDEV, jsxs };
|
|
@@ -7,13 +7,19 @@ type HTMLTag = keyof HTMLElementTagNameMap;
|
|
|
7
7
|
type SVGTag = keyof SVGElementTagNameMap;
|
|
8
8
|
type MathMLTag = keyof MathMLElementTagNameMap;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
declare const enum KTReactiveType {
|
|
11
|
+
REF = 1,
|
|
12
|
+
COMPUTED = 2
|
|
13
|
+
}
|
|
14
|
+
type ReactiveChangeHandler<T> = (newValue: T, oldValue: T) => void;
|
|
15
|
+
|
|
11
16
|
declare class KTRef<T> {
|
|
12
17
|
/**
|
|
13
18
|
* Indicates that this is a KTRef instance
|
|
14
19
|
*/
|
|
15
|
-
isKT:
|
|
16
|
-
|
|
20
|
+
isKT: true;
|
|
21
|
+
ktType: KTReactiveType;
|
|
22
|
+
constructor(_value: T, _onChanges: Array<ReactiveChangeHandler<T>>);
|
|
17
23
|
/**
|
|
18
24
|
* If new value and old value are both nodes, the old one will be replaced in the DOM
|
|
19
25
|
*/
|
|
@@ -23,8 +29,8 @@ declare class KTRef<T> {
|
|
|
23
29
|
* Register a callback when the value changes
|
|
24
30
|
* @param callback (newValue, oldValue) => xxx
|
|
25
31
|
*/
|
|
26
|
-
addOnChange(callback:
|
|
27
|
-
removeOnChange(callback:
|
|
32
|
+
addOnChange(callback: ReactiveChangeHandler<T>): void;
|
|
33
|
+
removeOnChange(callback: ReactiveChangeHandler<T>): boolean;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
type HTML<T extends (HTMLTag | SVGTag | MathMLTag) & otherstring> = T extends SVGTag
|
|
@@ -60,6 +66,12 @@ interface KTBaseAttribute {
|
|
|
60
66
|
*/
|
|
61
67
|
'k-model'?: KTRef<any>;
|
|
62
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Directly apply html string to `innerHTML`.
|
|
71
|
+
* - Would be reactive if `KTRef` instance is provided
|
|
72
|
+
*/
|
|
73
|
+
'k-html'?: any;
|
|
74
|
+
|
|
63
75
|
// # normal HTML attributes
|
|
64
76
|
id?: string;
|
|
65
77
|
class?: string;
|
|
@@ -113,11 +125,11 @@ interface KTBaseAttribute {
|
|
|
113
125
|
method?: 'POST' | 'GET' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE' | otherstring;
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
type
|
|
128
|
+
type KTPrefixedEventAttribute = {
|
|
117
129
|
[EventName in keyof HTMLElementEventMap as `on:${EventName}`]?: (ev: HTMLElementEventMap[EventName]) => void;
|
|
118
130
|
};
|
|
119
131
|
|
|
120
|
-
type KTAttribute = KTBaseAttribute &
|
|
132
|
+
type KTAttribute = KTBaseAttribute & KTPrefixedEventAttribute;
|
|
121
133
|
|
|
122
134
|
/**
|
|
123
135
|
* Create an enhanced HTMLElement.
|
|
@@ -129,7 +141,7 @@ type KTAttribute = KTBaseAttribute & KTPrefixedEventHandlers;
|
|
|
129
141
|
* ## About
|
|
130
142
|
* @package @ktjs/core
|
|
131
143
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
132
|
-
* @version 0.
|
|
144
|
+
* @version 0.24.0 (Last Update: 2026.02.05 12:08:54.164)
|
|
133
145
|
* @license MIT
|
|
134
146
|
* @link https://github.com/baendlorel/kt.js
|
|
135
147
|
* @link https://baendlorel.github.io/ Welcome to my site!
|