PageSourceSearch

https://littlebirdelectronics.com.au/assets/controllers/ecommerce/review_moderation_controller-b4d37a29.js

js littlebirdelectronics.com.au collected 2026-09-25 14:11:16 UTC 3,290 bytes, 93 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Makes the moderation panel on a review answer the click rather than the
4// round trip. Publishing a review is a decision a curator has already made by
5// the time they press the button; waiting ~300ms to find out whether the app
6// agreed reads as a dead button, and that is exactly how this one read before
7// (the server's turbo_stream only ever replaced the index's table row, so the
8// show page changed nothing at all).
9//
10// On submit we move the panel to the state the action is about to produce. The
11// server's replace follows and overrules us — and if the request failed, we put
12// the original state back rather than leaving a lie on screen.
13export default class extends Controller {
14  static targets = ["badge", "control"]
15  static values = {
16    status: String,   // pending | published | hidden — the column, as-is
17    archived: Boolean,
18    spam: Boolean
19  }
20
21  connect() {
22    this.committed = this.snapshot()
23  }
24
25  optimistic(event) {
26    switch (event.currentTarget.dataset.moderationNext) {
27      case "published": this.statusValue = "published"; break
28      case "hidden": this.statusValue = "hidden"; break
29      case "archived": this.archivedValue = true; break
30      case "unarchived": this.archivedValue = false; break
31      default: break
32    }
33  }
34
35  // A failed submit leaves the panel claiming something that didn't happen.
36  // The flash says so too, but the buttons are what the curator is looking at.
37  settle(event) {
38    if (event.detail?.success) return
39    this.restore(this.committed)
40  }
41
42  // Every setter above routes through here, so the panel is never redrawn by
43  // hand.
44  statusValueChanged() { this.render() }
45  archivedValueChanged() { this.render() }
46  spamValueChanged() { this.render() }
47
48  render() {
49    if (!this.hasBadgeTarget) return
50
51    const state = this.badgeState()
52    this.badgeTargets.forEach((badge) => {
53      badge.classList.toggle("hidden", badge.dataset.state !== state)
54    })
55    this.controlTargets.forEach((control) => {
56      control.classList.toggle("hidden", !this.visible(control))
57    })
58  }
59
60  // Mirrors Ecommerce::Admin::ReviewsHelper#review_badge_state. Same order,
61  // same answers — the two are checked against each other by the panel's test.
62  badgeState() {
63    if (this.spamValue) return "spam"
64    if (this.archivedValue) return "archived"
65    if (this.published) return "published"
66    if (this.statusValue === "pending") return "pending"
67    return "hidden"
68  }
69
70  // Ecommerce::Review#published? — the status column alone is not enough.
71  get published() {
72    return this.statusValue === "published" && !this.archivedValue && !this.spamValue
73  }
74
75  visible(control) {
76    const rules = control.dataset
77    if ("showUnlessPublished" in rules) return !this.published
78    if ("showUnlessHidden" in rules) return this.statusValue !== "hidden"
79    if ("showWhenArchived" in rules) return this.archivedValue
80    if ("showUnlessArchived" in rules) return !this.archivedValue
81    return true
82  }
83
84  snapshot() {
85    return { status: this.statusValue, archived: this.archivedValue, spam: this.spamValue }
86  }
87
88  restore({ status, archived, spam }) {
89    this.statusValue = status
90    this.archivedValue = archived
91    this.spamValue = spam
92  }
93}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.