Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.cjs

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
/gems
/auto_generated
.eslintcache
1 change: 0 additions & 1 deletion .template-lintrc.cjs

This file was deleted.

23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# **Plugin Name** Plugin
# Rollmaster

**Plugin Summary**
A Discourse dice rolling plugin, with support for in-post roll history. Powered by the [RPG Dice Roller](https://dice-roller.github.io/documentation/) library.

For more information, please see: **url to meta topic**

---

This is a dice rolling plugin, using the amazing [RPG Dice Roller](https://dice-roller.github.io/documentation/) library. Users use the `[roll]` BBCode tag to roll in post.

```bbcode
[roll]4d20[/roll]

[roll="description of the roll"]4d10r<=3[/roll]
```

Notation of the dice rolls can be found at [RPG Dice Roller: Notation](https://dice-roller.github.io/documentation/guide/notation/), which supports modifiers (rerolls, fudge dice, fate dice, criticals, etc.), math (max/min, sorting, etc.), group rolls, and more.

All rolls are saved and stored in the post history, and can be pulled up for each post, regardless of what the final cooked HTML displays. All rolls are reused in the same post, allowing users to freely edit the contents of their post (or fix their roll notation), while minimizing abuse. For example, suppose a user already rolled `1d20 = 14` using `[roll]1d20[/roll]` in their post. If the post is editted, and the `[roll]1d20[/roll]` remains the same, the plugin will reuse the previous `1d20 = 14` result. If the notation changes, a new roll is made. Changing the description of the roll won't create a reroll either.

If multiple rolls are made in the same post, using the same notation, the plugin will create new rolls for each instance. Upon editing the post, the plugin will match and reuse the rolls in the order they appear in the post.

All rolls made for a post are saved, and displayed in both the post history, and in a separate "Rolls" button in the post actions. This way, if a post has been modified, any user can see the history for any sign of abuse. Restoring an existing roll is as easy as reusing the notation.

12 changes: 12 additions & 0 deletions app/controllers/rollmaster/roll_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,17 @@ def roll
raise Discourse::InvalidParameters, e.message
end
end

# GET /rollmaster/rolls/:post_id
# @param [Integer] post_id the ID of the post to get rolls for
# @return [Array<Hash>] rolls array of rolls associated with the post
# @example URL /rollmaster/rolls/123
def rolls
post_id = params[:post_id]
raise Discourse::InvalidParameters, "post_id is required" if post_id.blank?

rolls = ::Rollmaster::Roll.where(post_id: post_id)
render_serialized(rolls, Rollmaster::RollSerializer, root: "rolls")
end
end
end
1 change: 1 addition & 0 deletions app/model/rollmaster/roll.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Roll < ActiveRecord::Base
# raw :string
# notation :string
# result :string
# desc :string
# created_at :datetime not null
# updated_at :datetime not null
#
Expand Down
7 changes: 7 additions & 0 deletions app/serializers/rollmaster/roll_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module ::Rollmaster
class RollSerializer < ApplicationSerializer
attributes :id, :post_id, :raw, :notation, :result, :desc, :created_at, :updated_at
end
end
138 changes: 0 additions & 138 deletions assets/javascripts/discourse/components/composer-valid-roll.gjs

This file was deleted.

90 changes: 90 additions & 0 deletions assets/javascripts/discourse/components/roll-history-entry.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { cancel } from "@ember/runloop";
import discourseLater from "discourse/lib/later";
import { clipboardCopy } from "discourse/lib/utilities";
import DButton from "discourse/ui-kit/d-button";
import dAgeWithTooltip from "discourse/ui-kit/helpers/d-age-with-tooltip";
import dConcatClass from "discourse/ui-kit/helpers/d-concat-class";
import dIcon from "discourse/ui-kit/helpers/d-icon";
import { i18n } from "discourse-i18n";

export default class RollHistoryEntry extends Component {
@tracked copied = false;
#copyTimer = null;

willDestroy() {
super.willDestroy(...arguments);
cancel(this.#copyTimer);
}

buildBBCode() {
const { desc, raw } = this.args.roll;
return desc ? `[roll="${desc}"]${raw}[/roll]` : `[roll]${raw}[/roll]`;
}

@action
async copyBBCode() {
await clipboardCopy(this.buildBBCode());
cancel(this.#copyTimer);
this.copied = true;
this.#copyTimer = discourseLater(() => (this.copied = false), 2000);
}

<template>
<blockquote
class={{dConcatClass
"bb-rollmaster-result"
"rollmaster-roll-history__roll"
(unless @roll.isCurrent "--historical")
}}
data-test-roll-history-row
data-test-roll-history-status={{if
@roll.isCurrent
"current"
"historical"
}}
...attributes
>
<p class="bb-rollmaster-title">
<span class="bb-rollmaster-description">
{{dIcon "rollmaster-dices"}}
{{if @roll.desc @roll.desc (i18n "rollmaster.bbcode.default")}}:
</span>
<span class="bb-rollmaster-notation">{{@roll.notation}}</span>
<span class="rollmaster-roll-history__title-meta">
{{#if @roll.isCurrent}}
<span class="sr-only">{{i18n "rollmaster.post.current"}}</span>
{{else}}
<span
class="rollmaster-roll-history__historical-indicator"
aria-label={{i18n "rollmaster.post.historical_tooltip"}}
title={{i18n "rollmaster.post.historical_tooltip"}}
>
{{dIcon "far-eye-slash"}}
<span class="sr-only">{{i18n "rollmaster.post.historical"}}</span>
</span>
{{/if}}
<span class="rollmaster-roll-history__timestamp">
{{dAgeWithTooltip @roll.created_at}}
</span>
</span>
</p>
<div class="rollmaster-roll-history__result-row">
<p class="bb-rollmaster-results">{{@roll.result}}</p>
<DButton
class="btn-flat btn-small"
@action={{this.copyBBCode}}
@icon={{if this.copied "check" "copy"}}
@title={{if
this.copied
"rollmaster.post.copied"
"rollmaster.post.copy"
}}
data-test-copy-bbcode
/>
</div>
</blockquote>
</template>
}
70 changes: 70 additions & 0 deletions assets/javascripts/discourse/components/rolls-post-menu-button.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { fn } from "@ember/helper";
import { action } from "@ember/object";
import DButton from "discourse/ui-kit/d-button";
import DModal from "discourse/ui-kit/d-modal";
import { i18n } from "discourse-i18n";
import RollHistoryEntry from "./roll-history-entry";

export default class RollsPostMenuButton extends Component {
static hidden() {
return false;
}

@tracked modalIsOpen = false;

get currentRollIds() {
return new Set(
(this.args.post.current_roll_ids || []).map((rollId) => Number(rollId))
);
}

get rolls() {
return [...(this.args.post.rolls || [])]
.sort(
(left, right) => new Date(left.created_at) - new Date(right.created_at)
)
.map((roll) => ({
...roll,
isCurrent: this.currentRollIds.has(Number(roll.id)),
}));
}

@action
showRolls() {
this.modalIsOpen = true;
}

<template>
<DButton
class="post-action-menu__view-rolls"
...attributes
@action={{this.showRolls}}
@icon="rollmaster-dices"
@title="rollmaster.post.title"
/>

{{#if this.modalIsOpen}}
<DModal
class="rollmaster-roll-history-modal"
data-test-roll-history-modal
@title={{i18n "rollmaster.post.title"}}
@closeModal={{fn (mut this.modalIsOpen) false}}
>
<div class="rollmaster-roll-history" data-test-roll-history>
{{#each this.rolls as |roll|}}
<RollHistoryEntry @roll={{roll}} />
{{else}}
<p
class="rollmaster-roll-history__empty"
data-test-roll-history-empty
>
{{i18n "rollmaster.post.empty"}}
</p>
{{/each}}
</div>
</DModal>
{{/if}}
</template>
}
Loading