CI/CD test
Some checks failed
Build and Deploy Nuxt / build (push) Has been cancelled

This commit is contained in:
2026-05-02 16:15:36 +02:00
parent 139e7d0ef5
commit 3fdced84bf
7 changed files with 315 additions and 31 deletions

View File

@@ -1,15 +1,25 @@
import { Marked } from "marked";
const marker = new Marked();
// optional: use a shared marked instance inside renderer
const renderMarkdown = (text) => marker.parse(text);
const WidgetMap = {
const widget_map = {
test: () => import("~/components/viewer/widgets/TestWidget.vue"),
table: () => import("~/components/viewer/widgets/TableWidget.vue"),
roll: () => import("~/components/viewer/widgets/RollWidget.vue"),
};
const componentCache = {}
const GetWidget = (type) => {
if (!componentCache[type]) {
componentCache[type] = defineAsyncComponent(
widget_map[type]
)
}
return componentCache[type]
}
const marker = new Marked();
const extension = {
name: "something",
level: "block",
@@ -33,12 +43,37 @@ const extension = {
},
};
const inlineExtension = {
name: "something_inline",
level: "inline",
start(src) {
return src.indexOf("@");
},
tokenizer(src) {
const rule = /^@(\w+)\s*\[([^\]]*)\]/;
const match = rule.exec(src);
if (!match) return;
return {
type: "something_inline",
raw: match[0],
name: match[1],
text: match[2],
};
},
renderer(token) {
return `<span class="vue-component" data-component="${token.name}" data-content="${token.text}"></span>`;
},
};
marker.use({
extensions: [extension],
extensions: [extension, inlineExtension],
});
function ParseMarkdown(source) {
return marker.parse(source || "");
}
export { ParseMarkdown, WidgetMap };
export { ParseMarkdown, GetWidget };

View File

@@ -0,0 +1,130 @@
// dice-parser.js
function roll(sides) {
return Math.floor(Math.random() * sides) + 1;
}
function tokenize(expr) {
const re = /(\d*d\d+(?:adv|dis|kh\d+|kl\d+)?|\d+|[+\-*\/()])/gi;
const tokens = [];
let m;
while ((m = re.exec(expr)) !== null) tokens.push(m[0].toLowerCase());
return tokens;
}
function parseDiceToken(tok) {
const m = tok.match(/^(\d*)d(\d+)(adv|dis|kh(\d+)|kl(\d+))?$/i);
if (!m) return null;
const count = parseInt(m[1] || '1');
const sides = parseInt(m[2]);
const mod = (m[3] || '').toLowerCase();
if (count < 1 || count > 1000 || sides < 2 || sides > 10000)
throw new Error(`Invalid dice: ${tok}`);
return { count, sides, mod };
}
export function parse(expr) {
const tokens = tokenize(expr);
if (!tokens.length) throw new Error('Empty expression');
let pos = 0;
const rolls = [];
function peek() { return tokens[pos]; }
function consume() { return tokens[pos++]; }
function parseExpr() { return parseAddSub(); }
function parseAddSub() {
let left = parseMulDiv();
while (peek() === '+' || peek() === '-') {
const op = consume();
const right = parseMulDiv();
left = {
value: op === '+' ? left.value + right.value : left.value - right.value,
rolls: [...left.rolls, ...right.rolls],
};
}
return left;
}
function parseMulDiv() {
let left = parseUnary();
while (peek() === '*' || peek() === '/') {
const op = consume();
const right = parseUnary();
if (op === '/' && right.value === 0) throw new Error('Division by zero');
left = {
value: op === '*' ? left.value * right.value : Math.floor(left.value / right.value),
rolls: [...left.rolls, ...right.rolls],
};
}
return left;
}
function parseUnary() {
if (peek() === '-') {
consume();
const r = parsePrimary();
return { value: -r.value, rolls: r.rolls };
}
return parsePrimary();
}
function parsePrimary() {
const tok = peek();
if (!tok) throw new Error('Unexpected end of expression');
if (tok === '(') {
consume();
const inner = parseExpr();
if (peek() !== ')') throw new Error('Missing closing )');
consume();
return inner;
}
const diceInfo = parseDiceToken(tok);
if (diceInfo) {
consume();
return rollDice(diceInfo);
}
if (/^\d+$/.test(tok)) {
consume();
return { value: parseInt(tok), rolls: [] };
}
throw new Error(`Unexpected token: ${tok}`);
}
function rollDice({ count, sides, mod }) {
let rawRolls, kept;
if (mod === 'adv') {
rawRolls = [roll(sides), roll(sides)];
kept = [Math.max(...rawRolls)];
} else if (mod === 'dis') {
rawRolls = [roll(sides), roll(sides)];
kept = [Math.min(...rawRolls)];
} else if (mod.startsWith('kh')) {
const k = parseInt(mod.slice(2));
rawRolls = Array.from({ length: count }, () => roll(sides));
kept = [...rawRolls].sort((a, b) => b - a).slice(0, k);
} else if (mod.startsWith('kl')) {
const k = parseInt(mod.slice(2));
rawRolls = Array.from({ length: count }, () => roll(sides));
kept = [...rawRolls].sort((a, b) => a - b).slice(0, k);
} else {
rawRolls = Array.from({ length: count }, () => roll(sides));
kept = rawRolls.slice();
}
const entry = { sides, rawRolls, kept, mod, value: kept.reduce((a, b) => a + b, 0) };
rolls.push(entry);
return { value: entry.value, rolls: [entry] };
}
const result = parseExpr();
if (pos < tokens.length) throw new Error(`Unexpected token: ${tokens[pos]}`);
return { total: result.value, rolls: result.rolls };
}