This commit is contained in:
@@ -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 };
|
||||
130
frontend/app/services/widgets/DiceParser.js
Normal file
130
frontend/app/services/widgets/DiceParser.js
Normal 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user