This commit is contained in:
Philippe Torrel
2026-11-03 14:52:15 +01:00
parent ed2c0b9906
commit f475f40350
104 changed files with 12459 additions and 4509 deletions
File diff suppressed because it is too large Load Diff
@@ -4,255 +4,209 @@
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
(function($) {
var $window = $(window),
$body = $('body'),
$wrapper = $('#wrapper'),
$header = $('#header'),
$nav = $('#nav'),
$main = $('#main'),
$navPanelToggle, $navPanel, $navPanelInner;
// Breakpoints.
breakpoints({
default: ['1681px', null ],
xlarge: ['1281px', '1680px' ],
large: ['981px', '1280px' ],
medium: ['737px', '980px' ],
small: ['481px', '736px' ],
xsmall: ['361px', '480px' ],
xxsmall: [null, '360px' ]
});
/**
* Applies parallax scrolling to an element's background image.
* @return {jQuery} jQuery object.
*/
$.fn._parallax = function(intensity) {
var $window = $(window),
$this = $(this);
if (this.length == 0 || intensity === 0)
return $this;
if (this.length > 1) {
for (var i=0; i < this.length; i++)
$(this[i])._parallax(intensity);
return $this;
}
if (!intensity)
intensity = 0.25;
$this.each(function() {
var $t = $(this),
$bg = $('<div class="bg"></div>').appendTo($t),
on, off;
on = function() {
$bg
.removeClass('fixed')
.css('transform', 'matrix(1,0,0,1,0,0)');
$window
.on('scroll._parallax', function() {
var pos = parseInt($window.scrollTop()) - parseInt($t.position().top);
$bg.css('transform', 'matrix(1,0,0,1,0,' + (pos * intensity) + ')');
});
};
off = function() {
$bg
.addClass('fixed')
.css('transform', 'none');
$window
.off('scroll._parallax');
};
// Disable parallax on ..
if (browser.name == 'ie' // IE
|| browser.name == 'edge' // Edge
|| window.devicePixelRatio > 1 // Retina/HiDPI (= poor performance)
|| browser.mobile) // Mobile devices
off();
// Enable everywhere else.
else {
breakpoints.on('>large', on);
breakpoints.on('<=large', off);
}
});
$window
.off('load._parallax resize._parallax')
.on('load._parallax resize._parallax', function() {
$window.trigger('scroll');
});
return $(this);
};
// Play initial animations on page load.
$window.on('load', function() {
window.setTimeout(function() {
$body.removeClass('is-preload');
}, 100);
});
// Scrolly.
$('.scrolly').scrolly();
// Background.
$wrapper._parallax(0.925);
// Nav Panel.
// Toggle.
$navPanelToggle = $(
'<a href="#navPanel" id="navPanelToggle">Menu</a>'
)
.appendTo($wrapper);
// Change toggle styling once we've scrolled past the header.
$header.scrollex({
bottom: '5vh',
enter: function() {
$navPanelToggle.removeClass('alt');
},
leave: function() {
$navPanelToggle.addClass('alt');
}
});
// Panel.
$navPanel = $(
'<div id="navPanel">' +
'<nav>' +
'</nav>' +
'<a href="#navPanel" class="close"></a>' +
'</div>'
)
.appendTo($body)
.panel({
delay: 500,
hideOnClick: true,
hideOnSwipe: true,
resetScroll: true,
resetForms: true,
side: 'right',
target: $body,
visibleClass: 'is-navPanel-visible'
});
// Get inner.
$navPanelInner = $navPanel.children('nav');
// Move nav content on breakpoint change.
var $navContent = $nav.children();
breakpoints.on('>medium', function() {
// NavPanel -> Nav.
$navContent.appendTo($nav);
// Flip icon classes.
$nav.find('.icons, .icon')
.removeClass('alt');
});
breakpoints.on('<=medium', function() {
// Nav -> NavPanel.
$navContent.appendTo($navPanelInner);
// Flip icon classes.
$navPanelInner.find('.icons, .icon')
.addClass('alt');
});
// Hack: Disable transitions on WP.
if (browser.os == 'wp'
&& browser.osVersion < 10)
$navPanel
.css('transition', 'none');
// Intro.
var $intro = $('#intro');
if ($intro.length > 0) {
// Hack: Fix flex min-height on IE.
if (browser.name == 'ie') {
$window.on('resize.ie-intro-fix', function() {
var h = $intro.height();
if (h > $window.height())
$intro.css('height', 'auto');
else
$intro.css('height', h);
}).trigger('resize.ie-intro-fix');
}
// Hide intro on scroll (> small).
breakpoints.on('>small', function() {
$main.unscrollex();
$main.scrollex({
mode: 'bottom',
top: '25vh',
bottom: '-50vh',
enter: function() {
$intro.addClass('hidden');
},
leave: function() {
$intro.removeClass('hidden');
}
});
});
// Hide intro on scroll (<= small).
breakpoints.on('<=small', function() {
$main.unscrollex();
$main.scrollex({
mode: 'middle',
top: '15vh',
bottom: '-15vh',
enter: function() {
$intro.addClass('hidden');
},
leave: function() {
$intro.removeClass('hidden');
}
});
});
}
})(jQuery);
(function (jq) {
var $window = $(window),
$body = $('body'),
$wrapper = $('#wrapper'),
$header = $('#header'),
$nav = $('#nav'),
$main = $('#main'),
$navPanelToggle,
$navPanel,
$navPanelInner;
// Breakpoints.
breakpoints({
default: ['1681px', null],
xlarge: ['1281px', '1680px'],
large: ['981px', '1280px'],
medium: ['737px', '980px'],
small: ['481px', '736px'],
xsmall: ['361px', '480px'],
xxsmall: [null, '360px'],
});
/**
* Applies parallax scrolling to an element's background image.
* @return {jQuery} jQuery object.
*/
$.fn._parallax = function (intensity) {
var $window = $(window),
$this = $(this);
if (this.length == 0 || intensity === 0) return $this;
if (this.length > 1) {
for (var i = 0; i < this.length; i++) $(this[i])._parallax(intensity);
return $this;
}
if (!intensity) {
intensity = 0.25;
}
$this.each(function () {
var $t = $(this),
$bg = $('<div class="bg"></div>').appendTo($t),
on,
off;
on = function () {
$bg.removeClass('fixed').css('transform', 'matrix(1,0,0,1,0,0)');
$window.on('scroll._parallax', function () {
var pos = parseInt($window.scrollTop()) - parseInt($t.position().top);
$bg.css('transform', 'matrix(1,0,0,1,0,' + pos * intensity + ')');
});
};
off = function () {
$bg.addClass('fixed').css('transform', 'none');
$window.off('scroll._parallax');
};
// Disable parallax on ..
if (
browser.name == 'ie' || // IE
browser.name == 'edge' || // Edge
window.devicePixelRatio > 1 || // Retina/HiDPI (= poor performance)
browser.mobile
)
// Mobile devices
off();
// Enable everywhere else.
else {
breakpoints.on('>large', on);
breakpoints.on('<=large', off);
}
});
$window.off('load._parallax resize._parallax').on('load._parallax resize._parallax', function () {
$window.trigger('scroll');
});
return $(this);
};
// Play initial animations on page load.
$window.on('load', function () {
window.setTimeout(function () {
$body.removeClass('is-preload');
}, 100);
});
// Scrolly.
$('.scrolly').scrolly();
// Background.
$wrapper._parallax(0.925);
// Nav Panel.
// Toggle.
$navPanelToggle = $('<a href="#navPanel" id="navPanelToggle">Menu</a>').appendTo($wrapper);
// Change toggle styling once we've scrolled past the header.
$header.scrollex({
bottom: '5vh',
enter: function () {
$navPanelToggle.removeClass('alt');
},
leave: function () {
$navPanelToggle.addClass('alt');
},
});
// Panel.
$navPanel = $('<div id="navPanel">' + '<nav>' + '</nav>' + '<a href="#navPanel" class="close"></a>' + '</div>')
.appendTo($body)
.panel({
delay: 500,
hideOnClick: true,
hideOnSwipe: true,
resetScroll: true,
resetForms: true,
side: 'right',
target: $body,
visibleClass: 'is-navPanel-visible',
});
// Get inner.
$navPanelInner = $navPanel.children('nav');
// Move nav content on breakpoint change.
var $navContent = $nav.children();
breakpoints.on('>medium', function () {
// NavPanel -> Nav.
$navContent.appendTo($nav);
// Flip icon classes.
$nav.find('.icons, .icon').removeClass('alt');
});
breakpoints.on('<=medium', function () {
// Nav -> NavPanel.
$navContent.appendTo($navPanelInner);
// Flip icon classes.
$navPanelInner.find('.icons, .icon').addClass('alt');
});
// Hack: Disable transitions on WP.
if (browser.os == 'wp' && browser.osVersion < 10) $navPanel.css('transition', 'none');
// Intro.
var $intro = $('#intro');
if ($intro.length > 0) {
// Hack: Fix flex min-height on IE.
if (browser.name == 'ie') {
$window
.on('resize.ie-intro-fix', function () {
var h = $intro.height();
if (h > $window.height()) $intro.css('height', 'auto');
else $intro.css('height', h);
})
.trigger('resize.ie-intro-fix');
}
// Hide intro on scroll (> small).
breakpoints.on('>small', function () {
$main.unscrollex();
$main.scrollex({
mode: 'bottom',
top: '25vh',
bottom: '-50vh',
enter: function () {
$intro.addClass('hidden');
},
leave: function () {
$intro.removeClass('hidden');
},
});
});
// Hide intro on scroll (<= small).
breakpoints.on('<=small', function () {
$main.unscrollex();
$main.scrollex({
mode: 'middle',
top: '15vh',
bottom: '-15vh',
enter: function () {
$intro.addClass('hidden');
},
leave: function () {
$intro.removeClass('hidden');
},
});
});
}
})(jQuery);
@@ -0,0 +1,2 @@
# eslint-config-airbnb-base@15 deklariert nur ESLint 7/8, das Setup läuft auf ESLint 9.
legacy-peer-deps=true
@@ -0,0 +1,5 @@
node_modules
assets
dev/js/libs
package-lock.json
dev/js/modules/ternaryLintExample.js
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "always",
"printWidth": 100
}
@@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}
@@ -0,0 +1,6 @@
{
"eslint.enable": true,
"eslint.useFlatConfig": true,
"eslint.validate": ["javascript"],
"eslint.workingDirectories": [{ "mode": "auto" }]
}
@@ -0,0 +1,47 @@
# ESLint und Prettier
ESLint prüft den JavaScript-Code. Die Konfiguration steht in `eslint.config.mjs`. Basis ist **Airbnb Base**. Darüber liegen zwei Regeln für Klammern bei ternären Operatoren.
## Befehle
```bash
npm run lint
npm run lint:fix
npm run format
npm run dev
```
`lint` meldet Verstöße. `lint:fix` korrigiert, was automatisch behebbar ist. `format` lässt zuerst Prettier laufen und setzt danach die Ternary-Klammern mit ESLint wieder. `dev` startet die Seite und schreibt die aktuellen Lint-Fehler in die geladene Seite.
Im Editor zeigt die Erweiterung **ESLint** (`dbaeumer.vscode-eslint`) dieselben Fehler als Wellenlinie und im Panel „Probleme“, sobald die Datei offen ist. Die Erweiterung ist für dieses Projekt empfohlen. Wenn nach der Installation noch nichts markiert ist, das Fenster neu laden.
Geprüft werden die eigenen Dateien. Ignoriert werden `node_modules`, das Bundle unter `assets/` und die mitgelieferte UUID-Bibliothek in `dev/js/libs`.
## Klammern bei ternären Operatoren
Die Bedingung eines ternären Operators soll in Klammern stehen:
```javascript
const status = (isActive) ? 'online' : 'offline';
```
- `local/ternary-condition-parens` verlangt die Klammern und setzt sie beim Fix.
- `@stylistic/no-extra-parens` verbietet überflüssige Klammern an anderen Stellen und lässt die Klammern um eine Ternary-Bedingung stehen.
Verschachtelte Ternaries sind erlaubt (`no-nested-ternary` ist aus).
## Prettier
Die Prettier-Konfiguration liegt in `.prettierrc.json` (einfache Anführungszeichen, Zeilenbreite 100, Klammern um Arrow-Parameter).
Prettier hat keine Einstellung, die `(isActive)` behält. Ein einzelnes `prettier --write` entfernt diese Klammern. `npm run format` führt deshalb danach `eslint --fix` aus. Der zweite Schritt setzt die Klammern zurück. Die Beispieldatei ist von beiden Schritten ausgenommen, damit die absichtlichen Fehler dort stehen bleiben.
## Meldungen in der laufenden Seite
ESLint liest die Dateien, bevor sie ausgeführt werden. `(isActive) ? 'online' : 'offline'` und `isActive ? 'online' : 'offline'` sind beide gültiges JavaScript. Der Browser wirft dafür keine Exception.
`npm run dev` prüft die Dateien bei jedem Build erneut. Die Fehler erscheinen oben auf der Seite und als `console.error` in der Browser-Konsole. Dieselben Texte liefert `npm run lint` im Terminal.
## Beispiel
`dev/js/modules/ternaryLintExample.js` enthält gültige und ungültige Zeilen. Die Datei hängt nicht am Programmcode, ihre Lint-Fehler stehen aber mit auf der Seite. `npm run lint:fix` schreibt die Beispielzeilen um.
@@ -0,0 +1,78 @@
(() => {
// node_modules/uuid/dist/stringify.js
var byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
// node_modules/uuid/dist/rng.js
var rnds8 = new Uint8Array(16);
function rng() {
return crypto.getRandomValues(rnds8);
}
// node_modules/uuid/dist/v4.js
function v4(options, buf, offset) {
if (!buf && !options && crypto.randomUUID) {
return crypto.randomUUID();
}
return _v4(options, buf, offset);
}
function _v4(options, buf, offset) {
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error("Random bytes length must be >= 16");
}
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
if (buf) {
offset = offset || 0;
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
var v4_default = v4;
// dev/js/modules/Counter.js
var Counter = (elOrSelector = null) => {
const module = elOrSelector || document.querySelector(elOrSelector);
const countEl = module.querySelector('input[id^="count"]');
const btnEl = module.querySelector('button[id^="incrementButton"]');
const id = v4_default();
countEl.id = `count-${id}`;
btnEl.id = `incrementButton-${id}`;
const counter = { count: 0 };
const init = () => {
btnEl.addEventListener("click", onClickIncrement);
};
const onClickIncrement = () => {
const count = counter.count + 1;
countEl.value = count;
};
init();
};
var Counter_default = Counter;
// dev/js/main.js
(() => {
const DOM = {
counters: Array.from(document.querySelectorAll(".counter"))
};
const init = () => {
DOM.counters.forEach((el) => {
Counter_default(el);
});
};
init();
})();
})();
@@ -0,0 +1,28 @@
(() => {
const findings = [{"file":"dev/js/modules/Counter.js","line":22,"column":37,"rule":"no-use-before-define","message":"'onClickIncrement' was used before it was defined."},{"file":"dev/js/modules/ternaryLintExample.js","line":39,"column":12,"rule":"@stylistic/no-extra-parens","message":"Unnecessary parentheses around expression."}];
if (findings.length === 0) {
return;
}
const box = document.createElement('div');
box.className = 'alert alert-danger m-3';
box.setAttribute('role', 'alert');
const title = document.createElement('strong');
title.textContent = 'ESLint: ' + findings.length + ' Fehler';
box.append(title);
const list = document.createElement('ul');
list.className = 'mb-0 mt-2';
for (const finding of findings) {
const text = finding.file + ':' + finding.line + ':' + finding.column + ' ' + finding.message + ' (' + finding.rule + ')';
console.error('[ESLint] ' + text);
const item = document.createElement('li');
item.textContent = text;
list.append(item);
}
box.append(list);
document.body.prepend(box);
})();
@@ -0,0 +1,15 @@
export { default as MAX } from './max.js';
export { default as NIL } from './nil.js';
export { default as parse } from './parse.js';
export { default as stringify } from './stringify.js';
export type * from './types.js';
export { default as v1 } from './v1.js';
export { default as v1ToV6 } from './v1ToV6.js';
export { default as v3 } from './v3.js';
export { default as v4 } from './v4.js';
export { default as v5 } from './v5.js';
export { default as v6 } from './v6.js';
export { default as v6ToV1 } from './v6ToV1.js';
export { default as v7 } from './v7.js';
export { default as validate } from './validate.js';
export { default as version } from './version.js';
@@ -0,0 +1,14 @@
export { default as MAX } from './max.js';
export { default as NIL } from './nil.js';
export { default as parse } from './parse.js';
export { default as stringify } from './stringify.js';
export { default as v1 } from './v1.js';
export { default as v1ToV6 } from './v1ToV6.js';
export { default as v3 } from './v3.js';
export { default as v4 } from './v4.js';
export { default as v5 } from './v5.js';
export { default as v6 } from './v6.js';
export { default as v6ToV1 } from './v6ToV1.js';
export { default as v7 } from './v7.js';
export { default as validate } from './validate.js';
export { default as version } from './version.js';
@@ -0,0 +1,2 @@
declare const _default: "ffffffff-ffff-ffff-ffff-ffffffffffff";
export default _default;
@@ -0,0 +1 @@
export default 'ffffffff-ffff-ffff-ffff-ffffffffffff';
@@ -0,0 +1,2 @@
declare function md5(bytes: Uint8Array): Uint8Array;
export default md5;
@@ -0,0 +1,135 @@
function md5(bytes) {
const words = uint8ToUint32(bytes);
const md5Bytes = wordsToMd5(words, bytes.length * 8);
return uint32ToUint8(md5Bytes);
}
function uint32ToUint8(input) {
const bytes = new Uint8Array(input.length * 4);
for (let i = 0; i < input.length * 4; i++) {
bytes[i] = (input[i >> 2] >>> ((i % 4) * 8)) & 0xff;
}
return bytes;
}
function getOutputLength(inputLength8) {
return (((inputLength8 + 64) >>> 9) << 4) + 14 + 1;
}
function wordsToMd5(x, len) {
const xpad = new Uint32Array(getOutputLength(len)).fill(0);
xpad.set(x);
xpad[len >> 5] |= 0x80 << (len % 32);
xpad[xpad.length - 1] = len;
x = xpad;
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
for (let i = 0; i < x.length; i += 16) {
const olda = a;
const oldb = b;
const oldc = c;
const oldd = d;
a = md5ff(a, b, c, d, x[i], 7, -680876936);
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5gg(b, c, d, a, x[i], 20, -373897302);
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5hh(d, a, b, c, x[i], 11, -358537222);
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5ii(a, b, c, d, x[i], 6, -198630844);
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safeAdd(a, olda);
b = safeAdd(b, oldb);
c = safeAdd(c, oldc);
d = safeAdd(d, oldd);
}
return Uint32Array.of(a, b, c, d);
}
function uint8ToUint32(input) {
if (input.length === 0) {
return new Uint32Array();
}
const output = new Uint32Array(getOutputLength(input.length * 8)).fill(0);
for (let i = 0; i < input.length; i++) {
output[i >> 2] |= (input[i] & 0xff) << ((i % 4) * 8);
}
return output;
}
function safeAdd(x, y) {
const lsw = (x & 0xffff) + (y & 0xffff);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
function bitRotateLeft(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
function md5cmn(q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
}
function md5ff(a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t);
}
function md5gg(a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t);
}
function md5hh(a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5ii(a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
}
export default md5;
@@ -0,0 +1,2 @@
declare const _default: "00000000-0000-0000-0000-000000000000";
export default _default;
@@ -0,0 +1 @@
export default '00000000-0000-0000-0000-000000000000';
@@ -0,0 +1,3 @@
import type { NonSharedArrayBuffer } from './types.js';
declare function parse(uuid: string): NonSharedArrayBuffer;
export default parse;
@@ -0,0 +1,9 @@
import validate from './validate.js';
function parse(uuid) {
if (!validate(uuid)) {
throw TypeError('Invalid UUID');
}
let v;
return Uint8Array.of((v = parseInt(uuid.slice(0, 8), 16)) >>> 24, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff, (v = parseInt(uuid.slice(9, 13), 16)) >>> 8, v & 0xff, (v = parseInt(uuid.slice(14, 18), 16)) >>> 8, v & 0xff, (v = parseInt(uuid.slice(19, 23), 16)) >>> 8, v & 0xff, ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff, (v / 0x100000000) & 0xff, (v >>> 24) & 0xff, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff);
}
export default parse;
@@ -0,0 +1,2 @@
declare const _default: RegExp;
export default _default;
@@ -0,0 +1 @@
export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
@@ -0,0 +1 @@
export default function rng(): Uint8Array;
@@ -0,0 +1,4 @@
const rnds8 = new Uint8Array(16);
export default function rng() {
return crypto.getRandomValues(rnds8);
}
@@ -0,0 +1,2 @@
declare function sha1(bytes: Uint8Array): Uint8Array;
export default sha1;
@@ -0,0 +1,70 @@
function f(s, x, y, z) {
switch (s) {
case 0:
return (x & y) ^ (~x & z);
case 1:
return x ^ y ^ z;
case 2:
return (x & y) ^ (x & z) ^ (y & z);
case 3:
return x ^ y ^ z;
}
}
function ROTL(x, n) {
return (x << n) | (x >>> (32 - n));
}
function sha1(bytes) {
const K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
const H = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];
const newBytes = new Uint8Array(bytes.length + 1);
newBytes.set(bytes);
newBytes[bytes.length] = 0x80;
bytes = newBytes;
const l = bytes.length / 4 + 2;
const N = Math.ceil(l / 16);
const M = new Array(N);
for (let i = 0; i < N; ++i) {
const arr = new Uint32Array(16);
for (let j = 0; j < 16; ++j) {
arr[j] =
(bytes[i * 64 + j * 4] << 24) |
(bytes[i * 64 + j * 4 + 1] << 16) |
(bytes[i * 64 + j * 4 + 2] << 8) |
bytes[i * 64 + j * 4 + 3];
}
M[i] = arr;
}
M[N - 1][14] = ((bytes.length - 1) * 8) / 2 ** 32;
M[N - 1][14] = Math.floor(M[N - 1][14]);
M[N - 1][15] = ((bytes.length - 1) * 8) & 0xffffffff;
for (let i = 0; i < N; ++i) {
const W = new Uint32Array(80);
for (let t = 0; t < 16; ++t) {
W[t] = M[i][t];
}
for (let t = 16; t < 80; ++t) {
W[t] = ROTL(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1);
}
let a = H[0];
let b = H[1];
let c = H[2];
let d = H[3];
let e = H[4];
for (let t = 0; t < 80; ++t) {
const s = Math.floor(t / 20);
const T = (ROTL(a, 5) + f(s, b, c, d) + e + K[s] + W[t]) >>> 0;
e = d;
d = c;
c = ROTL(b, 30) >>> 0;
b = a;
a = T;
}
H[0] = (H[0] + a) >>> 0;
H[1] = (H[1] + b) >>> 0;
H[2] = (H[2] + c) >>> 0;
H[3] = (H[3] + d) >>> 0;
H[4] = (H[4] + e) >>> 0;
}
return Uint8Array.of(H[0] >> 24, H[0] >> 16, H[0] >> 8, H[0], H[1] >> 24, H[1] >> 16, H[1] >> 8, H[1], H[2] >> 24, H[2] >> 16, H[2] >> 8, H[2], H[3] >> 24, H[3] >> 16, H[3] >> 8, H[3], H[4] >> 24, H[4] >> 16, H[4] >> 8, H[4]);
}
export default sha1;
@@ -0,0 +1,3 @@
export declare function unsafeStringify(arr: Uint8Array, offset?: number): string;
declare function stringify(arr: Uint8Array, offset?: number): string;
export default stringify;
@@ -0,0 +1,35 @@
import validate from './validate.js';
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1));
}
export function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] +
byteToHex[arr[offset + 1]] +
byteToHex[arr[offset + 2]] +
byteToHex[arr[offset + 3]] +
'-' +
byteToHex[arr[offset + 4]] +
byteToHex[arr[offset + 5]] +
'-' +
byteToHex[arr[offset + 6]] +
byteToHex[arr[offset + 7]] +
'-' +
byteToHex[arr[offset + 8]] +
byteToHex[arr[offset + 9]] +
'-' +
byteToHex[arr[offset + 10]] +
byteToHex[arr[offset + 11]] +
byteToHex[arr[offset + 12]] +
byteToHex[arr[offset + 13]] +
byteToHex[arr[offset + 14]] +
byteToHex[arr[offset + 15]]).toLowerCase();
}
function stringify(arr, offset = 0) {
const uuid = unsafeStringify(arr, offset);
if (!validate(uuid)) {
throw TypeError('Stringified UUID is invalid');
}
return uuid;
}
export default stringify;
@@ -0,0 +1,22 @@
export type UUIDTypes<TBuf extends Uint8Array = Uint8Array> = string | TBuf;
export type Version1Options = {
node?: Uint8Array;
clockseq?: number;
random?: Uint8Array;
rng?: () => Uint8Array;
msecs?: number;
nsecs?: number;
_v6?: boolean;
};
export type Version4Options = {
random?: Uint8Array;
rng?: () => Uint8Array;
};
export type Version6Options = Version1Options;
export type Version7Options = {
random?: Uint8Array;
msecs?: number;
seq?: number;
rng?: () => Uint8Array;
};
export type NonSharedArrayBuffer = ReturnType<typeof Uint8Array.of>;
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,70 @@
import * as assert from 'node:assert/strict';
import v1 from './v1.js';
import v3 from './v3.js';
import v4 from './v4.js';
import v5 from './v5.js';
import v6 from './v6.js';
import v7 from './v7.js';
function usage() {
console.log('Usage:');
console.log(' uuid');
console.log(' uuid v1');
console.log(' uuid v3 <name> <namespace uuid>');
console.log(' uuid v4');
console.log(' uuid v5 <name> <namespace uuid>');
console.log(' uuid v6');
console.log(' uuid v7');
console.log(' uuid --help');
console.log('\nNote: <namespace uuid> may be "URL" or "DNS" to use the corresponding UUIDs defined by RFC9562');
}
const args = process.argv.slice(2);
if (args.indexOf('--help') >= 0) {
usage();
process.exit(0);
}
const version = args.shift() || 'v4';
switch (version) {
case 'v1':
console.log(v1());
break;
case 'v3': {
const name = args.shift();
let namespace = args.shift();
assert.ok(name != null, 'v3 name not specified');
assert.ok(namespace != null, 'v3 namespace not specified');
if (namespace === 'URL') {
namespace = v3.URL;
}
if (namespace === 'DNS') {
namespace = v3.DNS;
}
console.log(v3(name, namespace));
break;
}
case 'v4':
console.log(v4());
break;
case 'v5': {
const name = args.shift();
let namespace = args.shift();
assert.ok(name != null, 'v5 name not specified');
assert.ok(namespace != null, 'v5 namespace not specified');
if (namespace === 'URL') {
namespace = v5.URL;
}
if (namespace === 'DNS') {
namespace = v5.DNS;
}
console.log(v5(name, namespace));
break;
}
case 'v6':
console.log(v6());
break;
case 'v7':
console.log(v7());
break;
default:
usage();
process.exit(1);
}
@@ -0,0 +1,11 @@
import type { Version1Options } from './types.js';
type V1State = {
node?: Uint8Array;
clockseq?: number;
msecs?: number;
nsecs?: number;
};
declare function v1(options?: Version1Options, buf?: undefined, offset?: number): string;
declare function v1<Buf extends Uint8Array = Uint8Array>(options: Version1Options | undefined, buf: Buf, offset?: number): Buf;
export declare function updateV1State(state: V1State, now: number, rnds: Uint8Array): V1State;
export default v1;
@@ -0,0 +1,87 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
const _state = {};
function v1(options, buf, offset) {
let bytes;
const isV6 = options?._v6 ?? false;
if (options) {
const optionsKeys = Object.keys(options);
if (optionsKeys.length === 1 && optionsKeys[0] === '_v6') {
options = undefined;
}
}
if (options) {
bytes = v1Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.nsecs, options.clockseq, options.node, buf, offset);
}
else {
const now = Date.now();
const rnds = rng();
updateV1State(_state, now, rnds);
bytes = v1Bytes(rnds, _state.msecs, _state.nsecs, isV6 ? undefined : _state.clockseq, isV6 ? undefined : _state.node, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
export function updateV1State(state, now, rnds) {
state.msecs ??= -Infinity;
state.nsecs ??= 0;
if (now === state.msecs) {
state.nsecs++;
if (state.nsecs >= 10000) {
state.node = undefined;
state.nsecs = 0;
}
}
else if (now > state.msecs) {
state.nsecs = 0;
}
else if (now < state.msecs) {
state.node = undefined;
}
if (!state.node) {
state.node = rnds.slice(10, 16);
state.node[0] |= 0x01;
state.clockseq = ((rnds[8] << 8) | rnds[9]) & 0x3fff;
}
state.msecs = now;
return state;
}
function v1Bytes(rnds, msecs, nsecs, clockseq, node, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
nsecs ??= 0;
clockseq ??= ((rnds[8] << 8) | rnds[9]) & 0x3fff;
if (node == null) {
node = rnds.slice(10, 16);
node[0] |= 0x01;
}
msecs += 12219292800000;
const t = (msecs & 0xfffffff) * 10000 + nsecs;
const tl = t >>> 0;
buf[offset++] = (tl >>> 24) & 0xff;
buf[offset++] = (tl >>> 16) & 0xff;
buf[offset++] = (tl >>> 8) & 0xff;
buf[offset++] = tl & 0xff;
const tmh = (((msecs / 0x10000000) | 0) * 625 + ((t / 0x100000000) | 0)) & 0xfffffff;
buf[offset++] = (tmh >>> 8) & 0xff;
buf[offset++] = tmh & 0xff;
buf[offset++] = ((tmh >>> 24) & 0xf) | 0x10;
buf[offset++] = (tmh >>> 16) & 0xff;
buf[offset++] = (clockseq >>> 8) | 0x80;
buf[offset++] = clockseq & 0xff;
for (let n = 0; n < 6; ++n) {
buf[offset++] = node[n];
}
return buf;
}
export default v1;
@@ -0,0 +1,3 @@
import type { NonSharedArrayBuffer } from './types.js';
export default function v1ToV6(uuid: string): string;
export default function v1ToV6(uuid: Uint8Array): NonSharedArrayBuffer;
@@ -0,0 +1,10 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export default function v1ToV6(uuid) {
const v1Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;
const v6Bytes = _v1ToV6(v1Bytes);
return typeof uuid === 'string' ? unsafeStringify(v6Bytes) : v6Bytes;
}
function _v1ToV6(v1Bytes) {
return Uint8Array.of(((v1Bytes[6] & 0x0f) << 4) | ((v1Bytes[7] >> 4) & 0x0f), ((v1Bytes[7] & 0x0f) << 4) | ((v1Bytes[4] & 0xf0) >> 4), ((v1Bytes[4] & 0x0f) << 4) | ((v1Bytes[5] & 0xf0) >> 4), ((v1Bytes[5] & 0x0f) << 4) | ((v1Bytes[0] & 0xf0) >> 4), ((v1Bytes[0] & 0x0f) << 4) | ((v1Bytes[1] & 0xf0) >> 4), ((v1Bytes[1] & 0x0f) << 4) | ((v1Bytes[2] & 0xf0) >> 4), 0x60 | (v1Bytes[2] & 0x0f), v1Bytes[3], v1Bytes[8], v1Bytes[9], v1Bytes[10], v1Bytes[11], v1Bytes[12], v1Bytes[13], v1Bytes[14], v1Bytes[15]);
}
@@ -0,0 +1,9 @@
import type { UUIDTypes } from './types.js';
export { DNS, URL } from './v35.js';
declare function v3(value: string | Uint8Array, namespace: UUIDTypes, buf?: undefined, offset?: number): string;
declare function v3<TBuf extends Uint8Array = Uint8Array>(value: string | Uint8Array, namespace: UUIDTypes, buf: TBuf, offset?: number): TBuf;
declare namespace v3 {
var DNS: string;
var URL: string;
}
export default v3;
@@ -0,0 +1,9 @@
import md5 from './md5.js';
import v35, { DNS, URL } from './v35.js';
export { DNS, URL } from './v35.js';
function v3(value, namespace, buf, offset) {
return v35(0x30, md5, value, namespace, buf, offset);
}
v3.DNS = DNS;
v3.URL = URL;
export default v3;
@@ -0,0 +1,7 @@
import type { UUIDTypes } from './types.js';
export declare function stringToBytes(str: string): Uint8Array;
export declare const DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
export declare const URL = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
type HashFunction = (bytes: Uint8Array) => Uint8Array;
export default function v35<TBuf extends Uint8Array = Uint8Array>(version: 0x30 | 0x50, hash: HashFunction, value: string | Uint8Array, namespace: UUIDTypes, buf?: TBuf, offset?: number): UUIDTypes<TBuf>;
export {};
@@ -0,0 +1,39 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export function stringToBytes(str) {
str = unescape(encodeURIComponent(str));
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; ++i) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
export const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
export const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
export default function v35(version, hash, value, namespace, buf, offset) {
const valueBytes = typeof value === 'string' ? stringToBytes(value) : value;
const namespaceBytes = typeof namespace === 'string' ? parse(namespace) : namespace;
if (typeof namespace === 'string') {
namespace = parse(namespace);
}
if (namespace?.length !== 16) {
throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)');
}
let bytes = new Uint8Array(16 + valueBytes.length);
bytes.set(namespaceBytes);
bytes.set(valueBytes, namespaceBytes.length);
bytes = hash(bytes);
bytes[6] = (bytes[6] & 0x0f) | version;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
if (buf) {
offset ??= 0;
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; ++i) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
@@ -0,0 +1,4 @@
import type { Version4Options } from './types.js';
declare function v4(options?: Version4Options, buf?: undefined, offset?: number): string;
declare function v4<TBuf extends Uint8Array = Uint8Array>(options: Version4Options | undefined, buf: TBuf, offset?: number): TBuf;
export default v4;
@@ -0,0 +1,29 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
function v4(options, buf, offset) {
if (!buf && !options && crypto.randomUUID) {
return crypto.randomUUID();
}
return _v4(options, buf, offset);
}
function _v4(options, buf, offset) {
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
if (buf) {
offset = offset || 0;
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
export default v4;
@@ -0,0 +1,9 @@
import type { UUIDTypes } from './types.js';
export { DNS, URL } from './v35.js';
declare function v5(value: string | Uint8Array, namespace: UUIDTypes, buf?: undefined, offset?: number): string;
declare function v5<TBuf extends Uint8Array = Uint8Array>(value: string | Uint8Array, namespace: UUIDTypes, buf: TBuf, offset?: number): TBuf;
declare namespace v5 {
var DNS: string;
var URL: string;
}
export default v5;
@@ -0,0 +1,9 @@
import sha1 from './sha1.js';
import v35, { DNS, URL } from './v35.js';
export { DNS, URL } from './v35.js';
function v5(value, namespace, buf, offset) {
return v35(0x50, sha1, value, namespace, buf, offset);
}
v5.DNS = DNS;
v5.URL = URL;
export default v5;
@@ -0,0 +1,4 @@
import type { Version6Options } from './types.js';
declare function v6(options?: Version6Options, buf?: undefined, offset?: number): string;
declare function v6<TBuf extends Uint8Array = Uint8Array>(options: Version6Options | undefined, buf: TBuf, offset?: number): TBuf;
export default v6;
@@ -0,0 +1,20 @@
import { unsafeStringify } from './stringify.js';
import v1 from './v1.js';
import v1ToV6 from './v1ToV6.js';
function v6(options, buf, offset) {
options ??= {};
offset ??= 0;
let bytes = v1({ ...options, _v6: true }, new Uint8Array(16));
bytes = v1ToV6(bytes);
if (buf) {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; i++) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
export default v6;
@@ -0,0 +1,2 @@
export default function v6ToV1(uuid: string): string;
export default function v6ToV1(uuid: Uint8Array): Uint8Array;
@@ -0,0 +1,10 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export default function v6ToV1(uuid) {
const v6Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;
const v1Bytes = _v6ToV1(v6Bytes);
return typeof uuid === 'string' ? unsafeStringify(v1Bytes) : v1Bytes;
}
function _v6ToV1(v6Bytes) {
return Uint8Array.of(((v6Bytes[3] & 0x0f) << 4) | ((v6Bytes[4] >> 4) & 0x0f), ((v6Bytes[4] & 0x0f) << 4) | ((v6Bytes[5] & 0xf0) >> 4), ((v6Bytes[5] & 0x0f) << 4) | (v6Bytes[6] & 0x0f), v6Bytes[7], ((v6Bytes[1] & 0x0f) << 4) | ((v6Bytes[2] & 0xf0) >> 4), ((v6Bytes[2] & 0x0f) << 4) | ((v6Bytes[3] & 0xf0) >> 4), 0x10 | ((v6Bytes[0] & 0xf0) >> 4), ((v6Bytes[0] & 0x0f) << 4) | ((v6Bytes[1] & 0xf0) >> 4), v6Bytes[8], v6Bytes[9], v6Bytes[10], v6Bytes[11], v6Bytes[12], v6Bytes[13], v6Bytes[14], v6Bytes[15]);
}
@@ -0,0 +1,9 @@
import type { Version7Options } from './types.js';
type V7State = {
msecs?: number;
seq?: number;
};
declare function v7(options?: Version7Options, buf?: undefined, offset?: number): string;
declare function v7<TBuf extends Uint8Array = Uint8Array>(options: Version7Options | undefined, buf: TBuf, offset?: number): TBuf;
export declare function updateV7State(state: V7State, now: number, rnds: Uint8Array): V7State;
export default v7;
@@ -0,0 +1,68 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
const _state = {};
function v7(options, buf, offset) {
let bytes;
if (options) {
bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
}
else {
const now = Date.now();
const rnds = rng();
updateV7State(_state, now, rnds);
bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
export function updateV7State(state, now, rnds) {
state.msecs ??= -Infinity;
state.seq ??= 0;
if (now > state.msecs) {
state.seq = v7Sequence(rnds);
state.msecs = now;
}
else {
state.seq = (state.seq + 1) | 0;
if (state.seq === 0) {
state.msecs++;
}
}
return state;
}
function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
seq ??= v7Sequence(rnds);
buf[offset++] = (msecs / 0x10000000000) & 0xff;
buf[offset++] = (msecs / 0x100000000) & 0xff;
buf[offset++] = (msecs / 0x1000000) & 0xff;
buf[offset++] = (msecs / 0x10000) & 0xff;
buf[offset++] = (msecs / 0x100) & 0xff;
buf[offset++] = msecs & 0xff;
buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f);
buf[offset++] = (seq >>> 20) & 0xff;
buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f);
buf[offset++] = (seq >>> 6) & 0xff;
buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03);
buf[offset++] = rnds[11];
buf[offset++] = rnds[12];
buf[offset++] = rnds[13];
buf[offset++] = rnds[14];
buf[offset++] = rnds[15];
return buf;
}
function v7Sequence(rnds) {
return ((rnds[6] & 0x7f) << 24) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
}
export default v7;
@@ -0,0 +1,2 @@
declare function validate(uuid: unknown): boolean;
export default validate;
@@ -0,0 +1,5 @@
import REGEX from './regex.js';
function validate(uuid) {
return typeof uuid === 'string' && REGEX.test(uuid);
}
export default validate;
@@ -0,0 +1,2 @@
declare function version(uuid: string): number;
export default version;
@@ -0,0 +1,8 @@
import validate from './validate.js';
function version(uuid) {
if (!validate(uuid)) {
throw TypeError('Invalid UUID');
}
return parseInt(uuid.slice(14, 15), 16);
}
export default version;
@@ -0,0 +1,23 @@
import Counter from './modules/Counter.js'; // wichtig mit Dateiendung, da kein Bundler
(() => {
// === DOM & VARS =======
const DOM = {
counters: Array.from(document.querySelectorAll('.counter')),
};
// === INIT =============
const init = () => {
DOM.counters.forEach((el) => {
Counter(el);
});
};
// === EVENTHANDLER =====
// === XHR/FETCH ========
// === FUNCTIONS ========
init();
})();
@@ -0,0 +1,36 @@
import { v4 } from 'uuid';
const Counter = (elOrSelector = null) => {
// ===== DOM & VARS =====
// TODO: forEach for string selector
const module = elOrSelector || document.querySelector(elOrSelector);
const countEl = module.querySelector('input[id^="count"]');
const btnEl = module.querySelector('button[id^="incrementButton"]');
const id = v4();
countEl.id = `count-${id}`;
btnEl.id = `incrementButton-${id}`;
// console.log('id:', id);
const counter = { count: 0 };
// ===== INIT =====
const init = () => {
btnEl.addEventListener('click', onClickIncrement);
};
// ===== EVENT HANDLER =====
const onClickIncrement = () => {
const count = counter.count + 1;
// count++;
countEl.value = count;
};
// ===== CALL INIT =====
init();
};
export default Counter;
@@ -0,0 +1,42 @@
// Demo für die Ternary-Klammerregeln. Diese Datei wird nicht gebündelt.
// Die ESLint-Erweiterung markiert die fehlerhaften Zeilen direkt im Editor.
// npm run lint zeigt dieselben Meldungen im Terminal.
// npm run dev zeigt sie in der Seite und in der Browser-Konsole.
// npm run lint:fix ergänzt fehlende Klammern und entfernt überflüssige.
const ternaryLintExample = {
// gültig
status(isActive) {
// prettier-ignore
return (isActive) ? 'online' : 'offline';
},
// Fehler: local/ternary-condition-parens
statusOhneKlammern(isActive) {
return (isActive) ? 'online' : 'offline';
},
// gültig: zusammengesetzte Bedingung bleibt in Klammern
label(count, hasError) {
// prettier-ignore
return (count > 0 && !hasError) ? 'Valid' : 'Invalid';
},
// Fehler: local/ternary-condition-parens
labelOhneKlammern(count, hasError) {
return (count > 0 && !hasError) ? 'Valid' : 'Invalid';
},
// gültig: verschachtelte Ternaries sind erlaubt, jede Bedingung in Klammern
nested(isAdmin, isActive) {
// prettier-ignore
return (isAdmin) ? ((isActive) ? 'Admin online' : 'Admin offline') : 'Gast';
},
// Fehler: @stylistic/no-extra-parens
summeMitExtraKlammern(a, b) {
return a + b;
},
};
export default ternaryLintExample;
@@ -0,0 +1,110 @@
import * as esbuild from 'esbuild';
import { ESLint } from 'eslint';
import fs from 'node:fs';
import path from 'node:path';
const outfile = path.resolve('assets/js/bundle.js');
const reportFile = path.resolve('assets/js/lint-report.js');
const watch = process.argv.includes('--watch');
function runtimeReport(results) {
const findings = [];
results.forEach((result) => {
const file = path.relative(process.cwd(), result.filePath);
result.messages.forEach((message) => {
if (message.severity !== 2) {
return;
}
findings.push({
file,
line: message.line ?? 0,
column: message.column ?? 0,
rule: message.ruleId ?? '',
message: message.message,
});
});
});
const script = `(() => {
const findings = ${JSON.stringify(findings)};
if (findings.length === 0) {
return;
}
const box = document.createElement('div');
box.className = 'alert alert-danger m-3';
box.setAttribute('role', 'alert');
const title = document.createElement('strong');
title.textContent = 'ESLint: ' + findings.length + ' Fehler';
box.append(title);
const list = document.createElement('ul');
list.className = 'mb-0 mt-2';
for (const finding of findings) {
const text = finding.file + ':' + finding.line + ':' + finding.column + ' ' + finding.message + ' (' + finding.rule + ')';
console.error('[ESLint] ' + text);
const item = document.createElement('li');
item.textContent = text;
list.append(item);
}
box.append(list);
document.body.prepend(box);
})();
`;
return { findings, script };
}
function printFindings(findings) {
if (findings.length === 0) {
console.log('ESLint: keine Fehler');
return;
}
console.error(`ESLint: ${findings.length} Fehler (auch in der geladenen Seite)`);
findings.forEach((finding) => {
console.error(`${finding.file}:${finding.line}:${finding.column} ${finding.message} (${finding.rule})`);
});
}
const eslintRuntime = {
name: 'eslint-runtime',
setup(build) {
build.onEnd(async (result) => {
if (result.errors.length > 0) {
return;
}
try {
const eslint = new ESLint({ cwd: process.cwd() });
const results = await eslint.lintFiles(['dev/js/**/*.js']);
const report = runtimeReport(results);
fs.writeFileSync(reportFile, report.script);
printFindings(report.findings);
} catch (error) {
console.error('ESLint-Laufzeitbericht fehlgeschlagen:', error);
}
});
},
};
const context = await esbuild.context({
entryPoints: ['dev/js/main.js'],
bundle: true,
outfile,
plugins: [eslintRuntime],
});
if (watch) {
await context.watch();
} else {
await context.rebuild();
await context.dispose();
}
@@ -0,0 +1,95 @@
import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import globals from 'globals';
import ternaryConditionParens from './eslint/ternary-condition-parens.mjs';
const compat = new FlatCompat({
baseDirectory: import.meta.dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default [
{
ignores: ['node_modules/**', 'assets/**', 'dev/js/libs/**'],
},
...compat.extends('airbnb-base'),
{
files: ['**/*.{js,mjs}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser,
},
},
plugins: {
'@stylistic': stylistic,
local: {
rules: {
'ternary-condition-parens': ternaryConditionParens,
},
},
},
settings: {
'import/resolver': {
node: {
extensions: ['.mjs', '.js', '.json'],
// uuid@14 hat nur ein exports-Feld. Der Resolver folgt exports erst mit Kategorie.
exportsCategory: 'pattern-trailers-no-dir-slash+json-imports',
moduleSystem: 'import',
},
},
},
rules: {
// Airbnb schaltet die Core-Regel aus. Stylistic übernimmt sie,
// damit Prettier diese Stellen nicht mehr formatieren muss.
'no-extra-parens': 'off',
'no-nested-ternary': 'off',
'no-use-before-define': 'off',
// Erzwingt Klammern um die Ternary-Bedingung. Stylistic kann das nur erlauben.
'local/ternary-condition-parens': 'error',
'@stylistic/no-extra-parens': [
'error',
'all',
{
conditionalAssign: false,
nestedBinaryExpressions: false,
ternaryOperandBinaryExpressions: false,
ignoreJSX: 'all',
nestedConditionalExpressions: false,
// Überspringt die Ternary-Prüfung, sonst entfernt --fix (isActive).
// Arrow-Body ersetzt die veraltete Option enforceForArrowConditionals.
ignoredNodes: [
'ConditionalExpression',
'ArrowFunctionExpression[body.type=ConditionalExpression]',
],
},
],
// Dieses Projekt importiert mit Dateiendung, Airbnb verlangt sonst „never“.
'import/extensions': [
'error',
'ignorePackages',
{
js: 'always',
mjs: 'always',
},
],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['eslint.config.mjs', 'esbuild.config.mjs', 'eslint/**'],
},
],
},
},
{
files: ['esbuild.config.mjs'],
rules: {
'no-console': 'off',
},
},
];
@@ -0,0 +1,43 @@
/** @type {import('eslint').Rule.RuleModule} */
export default {
meta: {
type: 'layout',
docs: {
description: 'Klammern um die Bedingung eines ternären Operators verlangen.',
},
fixable: 'code',
schema: [],
messages: {
required: 'Bedingung des ternären Operators in Klammern setzen: ({{test}}) ? … : …',
},
},
create(context) {
const { sourceCode } = context;
return {
ConditionalExpression(node) {
const before = sourceCode.getTokenBefore(node.test);
const after = sourceCode.getTokenAfter(node.test);
const isWrapped = before?.value === '(' && after?.value === ')';
if (isWrapped) {
return;
}
context.report({
node: node.test,
messageId: 'required',
data: {
test: sourceCode.getText(node.test),
},
fix(fixer) {
return [
fixer.insertTextBefore(node.test, '('),
fixer.insertTextAfter(node.test, ')'),
];
},
});
},
};
},
};
@@ -0,0 +1,89 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Counter Example</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="assets/js/bundle.js" defer></script>
<!-- <script src="assets/js/lint-report.js" defer></script> -->
</head>
<body>
<main id="main">
<section class="section-hero">
<div class="container py-3">
<div class="col-12">
<div class="mb-3">
<h2>HERO</h2>
</div>
</div>
</div>
</section>
<section class="section-content">
<div class="container">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum animi eaque doloremque
error voluptate alias dolorem esse qui voluptatum eius placeat unde consequuntur sequi
dolor, repudiandae nobis. Excepturi, dolores tenetur.
</p>
</div>
<div class="counter">
<div class="container py-5">
<h3>Counter</h3>
<div class="row mt-4">
<div class="col col-12 col-sm-10 col-lg-6">
<div class="input-group mb-3">
<span class="input-group-text">Count:</span>
<input
type="text"
id="count"
class="form-control input-count"
value="0"
readonly
aria-label="Counter"
/>
</div>
<button id="incrementButton" class="btn btn-primary w-100">Increment</button>
</div>
</div>
</div>
</div>
<!-- ▲ /counter ▲ -->
</section>
</main>
<footer>
<div class="container py-3 align-items-center">
<div class="col">
<p>&copy; copyright 2026 Donkey-Coders</p>
</div>
<div class="col">
<div class="counter">
<div class="container py-5">
<h3>Counter</h3>
<div class="row mt-4">
<div class="col col-12 col-sm-10 col-lg-6">
<div class="input-group mb-3">
<span class="input-group-text">Count:</span>
<input
type="text"
id="count-2"
class="form-control input-count"
value="0"
readonly
aria-label="Counter"
/>
</div>
<button id="incrementButton-2" class="btn btn-primary w-100">Increment</button>
</div>
</div>
</div>
</div>
<!-- ▲ /counter ▲ -->
</div>
</div>
</footer>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
{
"name": "webseite-html-js-bundled",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"bundle": "node esbuild.config.mjs --watch",
"dev": "run-p bundle server",
"server": "npx http-server -c-1 -p 3000",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write . && eslint . --fix --ignore-pattern dev/js/modules/ternaryLintExample.js"
},
"keywords": [],
"type": "module",
"devDependencies": {
"@eslint/eslintrc": "^3.3.7",
"@eslint/js": "^10.0.1",
"@stylistic/eslint-plugin": "^5.10.0",
"esbuild": "^0.28.2",
"eslint": "^10.14.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.32.0",
"globals": "^17.14.0",
"http-server": "^14.1.1",
"npm-run-all": "^4.1.5",
"prettier": "^3.9.10"
},
"dependencies": {
"uuid": "^14.0.3"
}
}