FEATURE: adds a policy builder

FEATURE: adds a policy builder

The builder is accessible from the popup menu in the composer (gear icon) and when editing a policy (gear icon upper right of a policy component).

diff --git a/assets/javascripts/discourse/components/policy-form-field.js.es6 b/assets/javascripts/discourse/components/policy-form-field.js.es6
new file mode 100644
index 0000000..4449440
--- /dev/null
+++ b/assets/javascripts/discourse/components/policy-form-field.js.es6
@@ -0,0 +1,5 @@
+import Component from "@ember/component";
+
+export default Component.extend({
+  tagName: "",
+});
diff --git a/assets/javascripts/discourse/components/policy-group-input.js.es6 b/assets/javascripts/discourse/components/policy-group-input.js.es6
new file mode 100644
index 0000000..c4c2779
--- /dev/null
+++ b/assets/javascripts/discourse/components/policy-group-input.js.es6
@@ -0,0 +1,33 @@
+import Group from "discourse/models/group";
+import Component from "@ember/component";
+import { action, computed } from "@ember/object";
+
+export default Component.extend({
+  allGroups: null,
+  group: null,
+  onChangeGroup: null,
+
+  init() {
+    this._super(...arguments);
+
+    this.set(
+      "allGroups",
+      Group.findAll().then((groups) => groups.filterBy("automatic", false))
+    );
+  },
+
+  siteGroups: computed("site.groups", function () {
+    return (this.site.groups || [])
+      .map((g) => {
+        if (g.id === 0) return; // prevents group "everyone" to be listed
+        if (g.automatic) return;
+        return g.name;
+      })
+      .filter(Boolean);
+  }),
+
+  @action
+  onChange(values) {
+    this.onChangeGroup && this.onChangeGroup(values);
+  },
+});
diff --git a/assets/javascripts/discourse/components/policy-reminder-input.js.es6 b/assets/javascripts/discourse/components/policy-reminder-input.js.es6
new file mode 100644
index 0000000..47b35af
--- /dev/null
+++ b/assets/javascripts/discourse/components/policy-reminder-input.js.es6
@@ -0,0 +1,19 @@
+import Component from "@ember/component";
+import I18n from "I18n";
+
+const VALID_REMINDERS = [
+  {
+    id: "daily",
+    name: I18n.t("daily"),
+  },
+  {
+    id: "weekly",
+    name: I18n.t("weekly"),
+  },
+];
+
+export default Component.extend({
+  reminder: null,
+  onChangeReminder: null,
+  validReminders: VALID_REMINDERS,
+});
diff --git a/assets/javascripts/discourse/controllers/policy-builder.js.es6 b/assets/javascripts/discourse/controllers/policy-builder.js.es6
new file mode 100644
index 0000000..7fcd957
--- /dev/null
+++ b/assets/javascripts/discourse/controllers/policy-builder.js.es6
@@ -0,0 +1,114 @@
+import I18n from "I18n";
+import TextLib from "discourse/lib/text";
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+import Controller from "@ember/controller";
+import EmberObject, { action } from "@ember/object";
+import getURL from "discourse-common/lib/get-url";
+import { ajax } from "discourse/lib/ajax";
+
+export default Controller.extend(ModalFunctionality, {
+  toolbarEvent: null,
+  form: null,
+  post: null,
+  isSaving: false,
+
+  onShow() {
+    if (!this.form) {
+      this.set("form", EmberObject.create({ reminder: "daily", version: 1 }));
+    }
+  },
+
+  onClose() {
+    this.set("form", null);
+  },
+
+  @action
+  insertPolicy() {
+    if (!this._validateForm(this.form)) {
+      return;
+    }
+
+    const markdownParams = this._buildParams(this.form);
+    this.toolbarEvent.addText(
+      `[policy ${markdownParams.join(" ")}]\n[/policy]`
+    );
+    this.send("closeModal");
+  },
+
+  @action
+  updatePolicy() {
+    if (!this._validateForm(this.form)) {
+      return;
+    }
+
+    this.set("isSaving", true);
+
+    const endpoint = getURL(`/posts/${this.post.id}`);
+    const options = { type: "GET", cache: false };
+
+    return ajax(endpoint, options)
+      .then((result) => {
+        const raw = result.raw;
+        const newRaw = this._replaceRaw(this.form, raw);
+
+        if (newRaw) {
+          const props = {
+            raw: newRaw,
+            edit_reason: I18n.t("discourse_policy.edit_reason"),
+          };
+
+          return TextLib.cookAsync(raw).then((cooked) => {
+            props.cooked = cooked.string;
+            this.post.save(props);
+          });
+        }
+      })
+      .finally(() => {
+        this.set("isSaving", false);
+        this.send("closeModal");
+      });
+  },
+
+  @action
+  onChangeRenewStartChange(value) {
+    this.form.set("renew-start", value);
+  },
+
+  _buildParams(form) {
+    const markdownParams = [];
+    Object.keys(form).forEach((key) => {
+      const value = form[key];
+
+      if (value && value.length) {
+        markdownParams.push(`${key}="${value}"`);
+      }
+    });
+    return markdownParams;
+  },
+
+  _replaceRaw(form, raw) {
+    const eventRegex = new RegExp(`\\[policy\\s(.*?)\\]`, "m");
+    const policyMatches = raw.match(eventRegex);
+
+    if (policyMatches && policyMatches[1]) {
+      const markdownParams = this._buildParams(form);
+      return raw.replace(eventRegex, `[policy ${markdownParams.join(" ")}]`);
+    }
+
+    return false;
+  },
+
+  _validateForm(form) {
+    if (!form.group || !form.group.length) {
+      this.flash(I18n.t("discourse_policy.builder.errors.group"), "error");
+      return false;
+    }
+
+    if (!form.version || !form.version.length) {
+      this.flash(I18n.t("discourse_policy.builder.errors.version"), "error");
+      return false;
+    }
+
+    return true;
+  },
+});
diff --git a/assets/javascripts/discourse/templates/components/policy-form-field.hbs b/assets/javascripts/discourse/templates/components/policy-form-field.hbs
new file mode 100644
index 0000000..4f72e66
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/policy-form-field.hbs
@@ -0,0 +1,16 @@
+<div class="policy-field {{name}}">
+  <div class="policy-field-label">
+    <span class="label">
+      {{i18n (concat "discourse_policy.builder." name ".label")}}
+      {{#if mandatory}}
+        <span class="mandatory-field">*</span>
+      {{/if}}
+    </span>
+  </div>
+  <div class="policy-field-control">
+    {{yield}}
+  </div>
+  <span class="policy-field-description">
+    <span class="description">{{i18n (concat "discourse_policy.builder." name ".description")}}</span>
+  </span>
+</div>
diff --git a/assets/javascripts/discourse/templates/components/policy-group-input.hbs b/assets/javascripts/discourse/templates/components/policy-group-input.hbs
new file mode 100644
index 0000000..260e8df
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/policy-group-input.hbs
@@ -0,0 +1,8 @@
+{{group-chooser
+  content=siteGroups
+  valueProperty=null
+  nameProperty=null
+  value=group
+  onChange=(action "onChange")
+  options=(hash maximum=1)
+}}
diff --git a/assets/javascripts/discourse/templates/components/policy-reminder-input.hbs b/assets/javascripts/discourse/templates/components/policy-reminder-input.hbs
new file mode 100644
index 0000000..759c4fa
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/policy-reminder-input.hbs
@@ -0,0 +1,5 @@
+{{combo-box
+  value=reminder
+  content=validReminders
+  onChange=onChangeReminder
+}}
diff --git a/assets/javascripts/discourse/templates/modal/policy-builder.hbs b/assets/javascripts/discourse/templates/modal/policy-builder.hbs
new file mode 100644
index 0000000..b61d768
--- /dev/null
+++ b/assets/javascripts/discourse/templates/modal/policy-builder.hbs
@@ -0,0 +1,79 @@
+{{#d-modal-body
+  title=(concat "discourse_policy.builder.title")
+  class="policy-builder"
+}}
+  {{#policy-form-field name="group" mandatory=true}}
+    {{policy-group-input group=form.group onChangeGroup=(action (mut form.group))}}
+  {{/policy-form-field}}
+
+  {{#policy-form-field name="version" mandatory=true}}
+    {{input
+      name="version"
+      type="number"
+      value=form.version
+      input=(action (mut form.version)
+      value="target.value")
+    }}
+  {{/policy-form-field}}
+
+  {{#policy-form-field name="renew"}}
+    {{input
+      name="renew"
+      type="number"
+      value=form.renew
+      input=(action (mut form.renew) value="target.value")
+    }}

[... diff too long, it was truncated ...]

GitHub sha: e5f8d4b6

1 Like

Guessing this should not be referencing post_event?

2 Likes