mirror of
https://github.com/butterski/obsidian-rpg-dice-roller.git
synced 2026-07-22 06:57:00 +00:00
Enhance Dice Roller Plugin: Update version, add inline formula detection, improve UI interactions, and refine error handling
This commit is contained in:
parent
5fbf2304e3
commit
a77fafdfba
9 changed files with 116 additions and 83 deletions
16
main.ts
16
main.ts
|
|
@ -18,7 +18,7 @@ export default class DiceRollerPlugin extends Plugin {
|
|||
|
||||
// Add ribbon icon to open dice roller
|
||||
this.addRibbonIcon('dice', 'Open Dice Roller', () => {
|
||||
this.activateDiceView();
|
||||
void this.activateDiceView();
|
||||
});
|
||||
|
||||
// Add command to open dice roller
|
||||
|
|
@ -26,7 +26,7 @@ export default class DiceRollerPlugin extends Plugin {
|
|||
id: 'open-dice-roller',
|
||||
name: 'Open Dice Roller',
|
||||
callback: () => {
|
||||
this.activateDiceView();
|
||||
void this.activateDiceView();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -54,10 +54,10 @@ export default class DiceRollerPlugin extends Plugin {
|
|||
}
|
||||
|
||||
onunload() {
|
||||
this.app.workspace.detachLeavesOfType(DICE_BUILDER_VIEW_TYPE);
|
||||
|
||||
}
|
||||
|
||||
async activateDiceView() {
|
||||
async activateDiceView(): Promise<DiceBuilderView | null> {
|
||||
const { workspace } = this.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
|
|
@ -81,8 +81,12 @@ export default class DiceRollerPlugin extends Plugin {
|
|||
|
||||
// Reveal the leaf in case it is in a collapsed sidebar
|
||||
if (leaf) {
|
||||
workspace.revealLeaf(leaf);
|
||||
await workspace.revealLeaf(leaf);
|
||||
if (leaf.view instanceof DiceBuilderView) {
|
||||
return leaf.view;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
refreshDiceView() {
|
||||
|
|
@ -96,7 +100,7 @@ export default class DiceRollerPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, ((await this.loadData()) ?? {}) as Partial<DiceRollerSettings>);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"id": "rpg-dice-roller",
|
||||
"name": "RPG Dice Roller",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"version": "1.1.0",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Build RPG dice roll commands with d20 syntax for Discord and Roll20. Create formulas visually with advantage/disadvantage support.",
|
||||
"author": "Miłosz Kucharski",
|
||||
"authorUrl": "https://mzkuch.pl",
|
||||
"fundingUrl": "https://github.com/sponsors/Butterski",
|
||||
"fundingUrl": "https://buymeacoffee.com/butterski",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export class DiceParser {
|
|||
advantage,
|
||||
disadvantage
|
||||
};
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,13 +135,21 @@ export class DiceParser {
|
|||
* Example: "Roll 2d6 + 4 for damage" or "Attack: 1d20 + 5"
|
||||
*/
|
||||
static detectInlineFormulas(text: string): Array<{formula: string, start: number, end: number}> {
|
||||
// Match common dice patterns: XdY, XdY+Z, XdY-Z, etc.
|
||||
const regex = /(\d*d\d+(?:[a-z]+[<>]?\d+)?(?:\s*[+-]\s*(?:\d*d\d+(?:[a-z]+[<>]?\d+)?|\d+))*(?:\s+(?:adv|dis|advantage|disadvantage))?)/gi;
|
||||
// Match common dice patterns: XdY, XdY+Z, XdY-Z, Z+XdY, etc.
|
||||
// We match a sequence of terms (dice or numbers) separated by + or -
|
||||
// Then we filter to ensure at least one dice term is present
|
||||
const regex = /((?:\d*d\d+(?:[a-z]+[<>]?\d+)?|\d+)(?:\s*[+-]\s*(?:\d*d\d+(?:[a-z]+[<>]?\d+)?|\d+))*(?:\s+(?:adv|dis|advantage|disadvantage))?)/gi;
|
||||
const results = [];
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const formula = match[1].trim();
|
||||
|
||||
// Must contain a 'd' to be a dice formula (avoids matching just "5 + 5")
|
||||
if (!formula.toLowerCase().includes('d')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate it's a real dice formula
|
||||
if (this.parse(formula)) {
|
||||
results.push({
|
||||
|
|
|
|||
|
|
@ -25,54 +25,43 @@ export function registerRollSyntaxProcessor(plugin: DiceRollerPlugin) {
|
|||
codeEl.setAttribute('data-formula', formula);
|
||||
|
||||
// Make it clickable
|
||||
codeEl.style.cursor = 'pointer';
|
||||
codeEl.addClass('dice-roll-clickable');
|
||||
|
||||
// Add click handler
|
||||
codeEl.addEventListener('click', async (e) => {
|
||||
codeEl.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const parsed = DiceParser.parse(formula);
|
||||
|
||||
if (parsed) {
|
||||
// Find the dice view and load the formula
|
||||
const leaves = plugin.app.workspace.getLeavesOfType('dice-builder-view');
|
||||
|
||||
if (leaves.length > 0) {
|
||||
const view = leaves[0].view;
|
||||
if (view instanceof DiceBuilderView) {
|
||||
view.loadFormula(parsed);
|
||||
plugin.app.workspace.revealLeaf(leaves[0]);
|
||||
new Notice(`Loaded dice formula: ${formula}`);
|
||||
}
|
||||
} else {
|
||||
// Open the dice view first
|
||||
await plugin.activateDiceView();
|
||||
// Wait a bit for the view to open
|
||||
setTimeout(() => {
|
||||
const newLeaves = plugin.app.workspace.getLeavesOfType('dice-builder-view');
|
||||
if (newLeaves.length > 0) {
|
||||
const view = newLeaves[0].view;
|
||||
if (view instanceof DiceBuilderView) {
|
||||
view.loadFormula(parsed);
|
||||
new Notice(`Loaded dice formula: ${formula}`);
|
||||
}
|
||||
void (async () => {
|
||||
// Find the dice view and load the formula
|
||||
const leaves = plugin.app.workspace.getLeavesOfType('dice-builder-view');
|
||||
|
||||
if (leaves.length > 0) {
|
||||
const view = leaves[0].view;
|
||||
if (view instanceof DiceBuilderView) {
|
||||
view.loadFormula(parsed);
|
||||
|
||||
// Use window.setTimeout per popout window guidelines
|
||||
window.setTimeout(() => {
|
||||
void plugin.app.workspace.revealLeaf(leaves[0]);
|
||||
}, 10);
|
||||
|
||||
new Notice(`Loaded dice formula: ${formula}`);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
// Open the dice view first
|
||||
const view = await plugin.activateDiceView();
|
||||
if (view) {
|
||||
view.loadFormula(parsed);
|
||||
new Notice(`Loaded dice formula: ${formula}`);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
codeEl.addEventListener('mouseenter', () => {
|
||||
codeEl.style.transform = 'translateY(-1px)';
|
||||
codeEl.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.15)';
|
||||
});
|
||||
|
||||
codeEl.addEventListener('mouseleave', () => {
|
||||
codeEl.style.transform = '';
|
||||
codeEl.style.boxShadow = '';
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ export class DiceRollerSettingTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'RPG Dice Roller Settings' });
|
||||
new Setting(containerEl)
|
||||
.setName('RPG Dice Roller Settings')
|
||||
.setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default platform')
|
||||
|
|
@ -52,6 +54,17 @@ export class DiceRollerSettingTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Enable inline formula detection')
|
||||
.setDesc('Automatically detect dice formulas like "1d20 + 5" in your notes. If disabled, only ROLL[...] syntax will be detected.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.enableInlineFormulas)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enableInlineFormulas = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.refreshDiceView();
|
||||
}));
|
||||
|
||||
containerEl.createEl('h3', { text: 'About' });
|
||||
|
||||
const aboutDiv = containerEl.createDiv({ cls: 'dice-roller-about' });
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ export interface DiceRollerSettings {
|
|||
discordPrefix: string;
|
||||
roll20Prefix: string;
|
||||
defaultPlatform: 'discord' | 'roll20';
|
||||
enableInlineFormulas: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: DiceRollerSettings = {
|
||||
discordPrefix: '.r',
|
||||
roll20Prefix: '/roll',
|
||||
defaultPlatform: 'discord'
|
||||
defaultPlatform: 'discord',
|
||||
enableInlineFormulas: true
|
||||
}
|
||||
|
||||
export interface DiceFormula {
|
||||
|
|
|
|||
69
src/view.ts
69
src/view.ts
|
|
@ -10,6 +10,8 @@ export const DICE_BUILDER_VIEW_TYPE = 'dice-builder-view';
|
|||
export class DiceBuilderView extends ItemView {
|
||||
plugin: DiceRollerPlugin;
|
||||
private currentFormula: DiceFormula;
|
||||
private builderContainer: HTMLElement;
|
||||
private suggestionsContainer: HTMLElement;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: DiceRollerPlugin) {
|
||||
super(leaf);
|
||||
|
|
@ -30,21 +32,27 @@ export class DiceBuilderView extends ItemView {
|
|||
}
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
container.addClass('dice-builder-container');
|
||||
this.contentEl.empty();
|
||||
this.contentEl.addClass('dice-builder-container');
|
||||
|
||||
this.render(container);
|
||||
this.builderContainer = this.contentEl.createDiv({ cls: 'dice-builder-section' });
|
||||
this.suggestionsContainer = this.contentEl.createDiv({ cls: 'dice-suggestions-section' });
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
async onClose(): Promise<void> {
|
||||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
private render(containerEl?: Element): void {
|
||||
const container = containerEl || this.containerEl.children[1];
|
||||
container.empty();
|
||||
private render(): void {
|
||||
this.renderBuilder();
|
||||
this.renderSuggestions();
|
||||
}
|
||||
|
||||
private renderBuilder(): void {
|
||||
this.builderContainer.empty();
|
||||
const container = this.builderContainer;
|
||||
|
||||
// Title
|
||||
const titleEl = container.createEl('h4', { text: 'Dice Formula Builder' });
|
||||
|
|
@ -63,7 +71,7 @@ export class DiceBuilderView extends ItemView {
|
|||
clearBtn.addClass('dice-clear-button');
|
||||
clearBtn.onclick = () => {
|
||||
this.currentFormula = { parts: [], advantage: false, disadvantage: false };
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
};
|
||||
|
||||
// Dice buttons section
|
||||
|
|
@ -167,7 +175,7 @@ export class DiceBuilderView extends ItemView {
|
|||
if (this.currentFormula.advantage) {
|
||||
this.currentFormula.disadvantage = false;
|
||||
}
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
};
|
||||
|
||||
const disBtn = advDisRow.createEl('button', {
|
||||
|
|
@ -180,7 +188,7 @@ export class DiceBuilderView extends ItemView {
|
|||
if (this.currentFormula.disadvantage) {
|
||||
this.currentFormula.advantage = false;
|
||||
}
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
};
|
||||
|
||||
// Action buttons
|
||||
|
|
@ -197,9 +205,6 @@ export class DiceBuilderView extends ItemView {
|
|||
const rollSyntaxBtn = actionSection.createEl('button', { text: 'Create ROLL[...] Syntax' });
|
||||
rollSyntaxBtn.addClass('dice-action-button');
|
||||
rollSyntaxBtn.onclick = () => this.createRollSyntax();
|
||||
|
||||
// Suggestions section
|
||||
this.renderSuggestions();
|
||||
}
|
||||
|
||||
private addDice(quantity: number, sides: number, operator: '+' | '-'): void {
|
||||
|
|
@ -210,7 +215,7 @@ export class DiceBuilderView extends ItemView {
|
|||
operator
|
||||
};
|
||||
this.currentFormula.parts.push(part);
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
}
|
||||
|
||||
private addModifier(value: number, operator: '+' | '-'): void {
|
||||
|
|
@ -220,7 +225,7 @@ export class DiceBuilderView extends ItemView {
|
|||
operator
|
||||
};
|
||||
this.currentFormula.parts.push(part);
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
}
|
||||
|
||||
private getCommandPrefix(): string {
|
||||
|
|
@ -274,15 +279,16 @@ export class DiceBuilderView extends ItemView {
|
|||
}
|
||||
|
||||
private renderSuggestions(): void {
|
||||
const container = this.containerEl.children[1];
|
||||
const suggestionsSection = container.createDiv({ cls: 'dice-suggestions-section' });
|
||||
suggestionsSection.createEl('h5', { text: 'Suggestions from Open Notes' });
|
||||
const container = this.suggestionsContainer;
|
||||
container.empty();
|
||||
|
||||
container.createEl('h5', { text: 'Suggestions from Open Notes' });
|
||||
|
||||
// Get all open markdown views
|
||||
const markdownLeaves = this.app.workspace.getLeavesOfType('markdown');
|
||||
|
||||
if (markdownLeaves.length === 0) {
|
||||
suggestionsSection.createEl('p', {
|
||||
container.createEl('p', {
|
||||
text: 'No open notes',
|
||||
cls: 'dice-no-suggestions'
|
||||
});
|
||||
|
|
@ -308,8 +314,11 @@ export class DiceBuilderView extends ItemView {
|
|||
// Detect ROLL[...] syntax
|
||||
const rollSyntaxMatches = DiceParser.detectRollSyntax(content);
|
||||
|
||||
// Detect inline formulas
|
||||
const inlineMatches = DiceParser.detectInlineFormulas(content);
|
||||
// Detect inline formulas (if enabled)
|
||||
let inlineMatches: Array<{formula: string, start: number, end: number}> = [];
|
||||
if (this.plugin.settings.enableInlineFormulas) {
|
||||
inlineMatches = DiceParser.detectInlineFormulas(content);
|
||||
}
|
||||
|
||||
const allMatches = [
|
||||
...rollSyntaxMatches.map(m => ({ formula: m.formula, source: '📌 ROLL' })),
|
||||
|
|
@ -326,26 +335,26 @@ export class DiceBuilderView extends ItemView {
|
|||
});
|
||||
|
||||
if (uniqueFormulas.size === 0) {
|
||||
suggestionsSection.createEl('p', {
|
||||
container.createEl('p', {
|
||||
text: 'No dice formulas found in current note',
|
||||
cls: 'dice-no-suggestions'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const suggestionsList = suggestionsSection.createDiv({ cls: 'dice-suggestions-list' });
|
||||
const suggestionsList = container.createDiv({ cls: 'dice-suggestions-list' });
|
||||
|
||||
uniqueFormulas.forEach((match) => {
|
||||
const suggestionItem = suggestionsList.createDiv({ cls: 'dice-suggestion-item' });
|
||||
|
||||
const leftSection = suggestionItem.createDiv({ cls: 'dice-suggestion-left' });
|
||||
|
||||
const formulaSpan = leftSection.createEl('span', {
|
||||
leftSection.createEl('span', {
|
||||
text: match.formula,
|
||||
cls: 'dice-suggestion-formula'
|
||||
});
|
||||
|
||||
const sourceSpan = leftSection.createEl('span', {
|
||||
leftSection.createEl('span', {
|
||||
text: match.source,
|
||||
cls: 'dice-suggestion-source'
|
||||
});
|
||||
|
|
@ -356,7 +365,7 @@ export class DiceBuilderView extends ItemView {
|
|||
const parsed = DiceParser.parse(match.formula);
|
||||
if (parsed) {
|
||||
this.currentFormula = parsed;
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
new Notice(`Loaded: ${match.formula}`);
|
||||
}
|
||||
};
|
||||
|
|
@ -386,7 +395,7 @@ export class DiceBuilderView extends ItemView {
|
|||
this.currentFormula.advantage = false;
|
||||
}
|
||||
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
new Notice(`Added: ${match.formula}`);
|
||||
}
|
||||
};
|
||||
|
|
@ -399,6 +408,6 @@ export class DiceBuilderView extends ItemView {
|
|||
|
||||
public loadFormula(formula: DiceFormula): void {
|
||||
this.currentFormula = formula;
|
||||
this.render();
|
||||
this.renderBuilder();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
styles.css
10
styles.css
|
|
@ -393,3 +393,13 @@ code[class*="ROLL"],
|
|||
font-size: 0.95rem !important;
|
||||
font-family: var(--font-monospace);
|
||||
}
|
||||
|
||||
.dice-roll-clickable {
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s ease, box-shadow 0.1s ease;
|
||||
}
|
||||
|
||||
.dice-roll-clickable:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue