Files
fizzy/app/javascript/controllers/terminal_controller.js
T
Jorge Manrubia 59303a4628 Merge branch 'main' into fizzy-do-3
* main: (29 commits)
  Ensure that a "reconsidered" card gets an updated timestamp
  Try a fresh activity design
  Log the authenticated user
  Remove leading space when avatar is hidden on pinned cards
  Display card number in notifications and events
  Ensure that cards with an empty title aren't mysterious in notifs
  bin/dev ensures puma runs solid queue
  Adjust selection color to feel more terminal-y
  Eliminate double-tab behavior
  Clear error state when you start editing
  Color terminal text on error
  Don't indicate spelling errors
  The command is lowercase
  Remind 7 days before, not 30
  Make the icon square
  Subtler bubble styles
  Variable bubble shapes
  Bubble CSS
  Rename 'badge' to 'bubble'
  Address PR feedback
  ...

# Conflicts:
#	app/javascript/controllers/terminal_controller.js
#	app/views/commands/_form.html.erb
2025-05-08 10:29:54 +02:00

122 lines
2.9 KiB
JavaScript

import { Controller } from "@hotwired/stimulus"
import { HttpStatus } from "helpers/http_helpers"
export default class extends Controller {
static targets = [ "input", "form", "confirmation" ]
static classes = [ "error", "confirmation", "help" ]
// Actions
focus() {
this.inputTarget.focus()
}
executeCommand(event) {
if (this.#hasHelpMenuCommand) {
this.#showHelpMenu()
event.preventDefault()
event.stopPropagation()
} else {
this.hideHelpMenu()
}
}
hideHelpMenu() {
if (this.#isHelpMenuOpened) {
if (this.#hasHelpMenuCommand) { this.#reset() }
this.element.classList.remove(this.helpClass)
}
}
handleCommandResponse(event) {
if (event.detail.success) {
this.#reset()
} else {
const response = event.detail.fetchResponse.response
this.#handleErrorResponse(response)
}
}
restoreCommand(event) {
const target = event.target.querySelector("[data-line]") || event.target
if (target.dataset.line) {
this.#reset(target.dataset.line)
this.focus()
}
}
hideError() {
this.element.classList.remove(this.errorClass)
}
get #hasHelpMenuCommand() {
return this.inputTarget.value == "/help" || this.inputTarget.value == "/?"
}
#showHelpMenu() {
this.element.classList.add(this.helpClass)
}
get #isHelpMenuOpened() {
return this.element.classList.contains(this.helpClass)
}
async #handleErrorResponse(response) {
const status = response.status
const message = await response.text()
if (status === HttpStatus.UNPROCESSABLE) {
this.#showError()
} else if (status === HttpStatus.CONFLICT) {
this.#requestConfirmation(message)
}
}
#reset(inputValue = "") {
this.formTarget.reset()
this.inputTarget.value = inputValue
this.confirmationTarget.value = ""
this.element.classList.remove(this.errorClass)
this.element.classList.remove(this.confirmationClass)
}
#showError() {
this.element.classList.add(this.errorClass)
}
async #requestConfirmation(message) {
const originalInputValue = this.inputTarget.value
this.element.classList.add(this.confirmationClass)
this.inputTarget.value = `${message}? [Y/n] `
try {
await this.#waitForConfirmation()
this.#submitWithConfirmation(originalInputValue)
} catch {
this.#reset(originalInputValue)
}
}
#waitForConfirmation() {
return new Promise((resolve, reject) => {
this.inputTarget.addEventListener("keydown", (event) => {
event.preventDefault()
const key = event.key.toLowerCase()
if (key === "enter" || key === "y") {
resolve()
} else {
reject()
}
}, { once: true })
})
}
#submitWithConfirmation(inputValue) {
this.inputTarget.value = inputValue
this.confirmationTarget.value = "confirmed"
this.formTarget.requestSubmit()
}
}