Remove Fizzy Do command logic
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
class CommandsController < ApplicationController
|
||||
def create
|
||||
command = parse_command(params[:command])
|
||||
result = command.execute
|
||||
respond_with_execution_result(result)
|
||||
end
|
||||
|
||||
private
|
||||
def parse_command(string)
|
||||
command_parser.parse(string)
|
||||
end
|
||||
|
||||
def command_parser
|
||||
@command_parser ||= Command::Parser.new(user: Current.user, script_name: request.script_name)
|
||||
end
|
||||
|
||||
def respond_with_execution_result(result)
|
||||
# This saves unnecessary back and forth between server and client (e.g: to redirect)-
|
||||
result = result.is_a?(Array) && result.one? ? result.first : result
|
||||
|
||||
case result
|
||||
when Command::Result::Redirection
|
||||
redirect_to result.url
|
||||
when Command::Result::ShowModal
|
||||
respond_with_turbo_frame_modal(result.turbo_frame, result.url)
|
||||
else
|
||||
redirect_back_or_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
def respond_with_turbo_frame_modal(turbo_frame, url)
|
||||
render json: { turbo_frame: turbo_frame, url: url }, status: :accepted
|
||||
end
|
||||
end
|
||||
@@ -1,91 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { HttpStatus } from "helpers/http_helpers"
|
||||
import { isMultiLineString } from "helpers/text_helpers";
|
||||
import { marked } from "marked"
|
||||
import { nextFrame } from "helpers/timing_helpers";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "input", "form", "modalTurboFrame" ]
|
||||
static classes = [ "error", "busy" ]
|
||||
static outlets = [ "dialog" ]
|
||||
|
||||
// Actions
|
||||
|
||||
async focus() {
|
||||
await nextFrame()
|
||||
|
||||
this.inputTarget.focus()
|
||||
this.inputTarget.selection.placeCursorAtTheEnd()
|
||||
}
|
||||
|
||||
executeCommand(event) {
|
||||
if (!this.inputTarget.value.trim()) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
submitCommand({ target }) {
|
||||
this.#submitCommand()
|
||||
}
|
||||
|
||||
handleCommandResponse(event) {
|
||||
const response = event.detail.fetchResponse?.response
|
||||
|
||||
if (event.detail.success) {
|
||||
this.#handleSuccessResponse(response)
|
||||
} else if (response) {
|
||||
this.#handleErrorResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
hideError() {
|
||||
this.element.classList.remove(this.errorClass)
|
||||
}
|
||||
|
||||
commandSubmitted() {
|
||||
this.element.classList.add(this.busyClass)
|
||||
}
|
||||
|
||||
#reset(inputValue = "") {
|
||||
this.inputTarget.value = inputValue
|
||||
|
||||
this.element.classList.remove(this.errorClass)
|
||||
this.element.classList.remove(this.busyClass)
|
||||
}
|
||||
|
||||
#handleSuccessResponse(response) {
|
||||
if (response.headers.get("Content-Type")?.includes("application/json")) {
|
||||
response.json().then((responseJson) => {
|
||||
this.#handleJsonResponse(responseJson)
|
||||
})
|
||||
}
|
||||
this.#reset()
|
||||
}
|
||||
|
||||
async #handleErrorResponse(response) {
|
||||
this.element.classList.add(this.errorClass)
|
||||
}
|
||||
|
||||
#handleJsonResponse(responseJson) {
|
||||
const { redirect_to, turbo_frame, url } = responseJson
|
||||
|
||||
if (redirect_to) {
|
||||
Turbo.visit(redirect_to)
|
||||
}
|
||||
|
||||
if (turbo_frame && url) {
|
||||
this.#showTurboFrameModal(turbo_frame, url)
|
||||
}
|
||||
}
|
||||
|
||||
#submitCommand() {
|
||||
this.formTarget.requestSubmit()
|
||||
}
|
||||
|
||||
#showTurboFrameModal(name, url) {
|
||||
this.inputTarget.blur()
|
||||
this.modalTurboFrameTarget.id = name
|
||||
this.modalTurboFrameTarget.src = url
|
||||
this.dialogOutlet.open()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
class Command
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def execute
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
private
|
||||
def redirect_to(...)
|
||||
Command::Result::Redirection.new(...)
|
||||
end
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
class Command::Ask < Command
|
||||
attr_reader :question
|
||||
|
||||
def initialize(question)
|
||||
@question = question
|
||||
end
|
||||
|
||||
def execute
|
||||
conversation = Conversation.create_or_find_by(user: Current.user)
|
||||
conversation.ask(question) if question.present?
|
||||
|
||||
Command::Result::ShowModal.new(turbo_frame: "conversation", url: conversation_path)
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
class Command::GoToCard < Command
|
||||
attr_reader :card
|
||||
|
||||
def initialize(card)
|
||||
@card = card
|
||||
end
|
||||
|
||||
def execute
|
||||
redirect_to card
|
||||
end
|
||||
end
|
||||
@@ -1,51 +0,0 @@
|
||||
class Command::Parser
|
||||
attr_reader :user, :script_name
|
||||
|
||||
def initialize(user: user, script_name:)
|
||||
@user = user
|
||||
@script_name = script_name
|
||||
end
|
||||
|
||||
def parse(string)
|
||||
parse_command(string).tap do |command|
|
||||
command.default_url_options[:script_name] = script_name
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def parse_command(string)
|
||||
parse_rich_text_command as_plain_text_with_attachable_references(string)
|
||||
end
|
||||
|
||||
def as_plain_text_with_attachable_references(string)
|
||||
ActionText::Content.new(string).render_attachments(&:to_gid).fragment.to_plain_text
|
||||
end
|
||||
|
||||
def parse_rich_text_command(string)
|
||||
command_name, *command_arguments = string.strip.split(" ")
|
||||
combined_arguments = command_arguments.join(" ")
|
||||
|
||||
case command_name
|
||||
when "/ask"
|
||||
Command::Ask.new(combined_arguments)
|
||||
else
|
||||
parse_free_string(string)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_free_string(string)
|
||||
if card = single_card_from(string)
|
||||
Command::GoToCard.new(card)
|
||||
else
|
||||
parse_with_fallback(string)
|
||||
end
|
||||
end
|
||||
|
||||
def single_card_from(string)
|
||||
user.accessible_cards.find_by_id(string)
|
||||
end
|
||||
|
||||
def parse_with_fallback(string)
|
||||
Command::Search.new(string)
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
class Command::Result::Redirection
|
||||
attr_reader :url
|
||||
|
||||
def initialize(url)
|
||||
@url = url
|
||||
end
|
||||
|
||||
def as_json
|
||||
{ redirect_to: url }
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
class Command::Result::ShowModal
|
||||
attr_reader :url, :turbo_frame
|
||||
|
||||
def initialize(url:, turbo_frame:)
|
||||
@url = url
|
||||
@turbo_frame = turbo_frame
|
||||
end
|
||||
|
||||
def as_json
|
||||
{ turbo_frame: turbo_frame, url: url }
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
class Command::Search < Command
|
||||
attr_reader :terms
|
||||
|
||||
def initialize(terms)
|
||||
@terms = terms
|
||||
end
|
||||
|
||||
|
||||
def execute
|
||||
redirect_to search_path(q: terms)
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
<%= form_tag commands_path,
|
||||
id: "commands_form",
|
||||
class: [ "flex align-center gap-half" ],
|
||||
data: {
|
||||
controller: "form",
|
||||
terminal_target: "form",
|
||||
action: "
|
||||
keydown.enter->terminal#submitCommand
|
||||
turbo:submit-start->terminal#commandSubmitted
|
||||
turbo:submit-end->terminal#focus
|
||||
keydown.meta+k@document->terminal#focus:prevent
|
||||
keydown.ctrl+k@document->terminal#focus:prevent
|
||||
turbo:submit-end->terminal#handleCommandResponse
|
||||
"
|
||||
} do %>
|
||||
<%= rich_textarea_tag "command", nil,
|
||||
toolbar: false,
|
||||
class: "terminal__input input hide-focus-ring fill-transparent unpad",
|
||||
"single-line": true,
|
||||
id: "command",
|
||||
attachments: false,
|
||||
data: {
|
||||
terminal_target: "input"
|
||||
},
|
||||
placeholder: platform.desktop? ? "Press #{ hotkey_label(["ctrl", "K"]) } to search or type /commands…" : "Search or type /commands…",
|
||||
spellcheck: "false" do %>
|
||||
<%= global_mentions_prompt %>
|
||||
<%= tags_prompt %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1,37 +0,0 @@
|
||||
<section class="terminal__help shadow" aria-labelledby="help-menu-title">
|
||||
<h2 id="help-menu-title"><strong>Fizzy do > Help</strong></h2>
|
||||
<dl>
|
||||
<h3>Search</h3>
|
||||
<dt>Type keywords to search cards and comments</dt>
|
||||
<dd>"CSS bug"</dd>
|
||||
|
||||
<h3>Navigate</h3>
|
||||
<dt>Type a card ID to go to it</dt>
|
||||
<dd>"137"</dd>
|
||||
<dt>Type a name to see someone’s profile</dt>
|
||||
<dd>"@jason"</dd>
|
||||
|
||||
<h3>Filter</h3>
|
||||
<dt>Type card IDs to isolate them</dt>
|
||||
<dd>"131, 137, 39"</dd>
|
||||
<dt>Type a tag to see just tagged cards</dt>
|
||||
<dd>"#design"</dd>
|
||||
|
||||
<h3>Commands</h3>
|
||||
<dt>Type / to issue commands</dt>
|
||||
<dd>"/move to Doing"</dd>
|
||||
|
||||
<dt>Type @ to match people</dt>
|
||||
<dd>"/assign to @jason"</dd>
|
||||
|
||||
<dt>Type # to match tags</dt>
|
||||
<dd>"/tag this #design"</dd>
|
||||
|
||||
<h3>Tips</h3>
|
||||
<dt>Combine for powerful actions</dt>
|
||||
<dd>"/move to Doing and /assign to @jason"</dd>
|
||||
|
||||
<dt>Or use natural language</dt>
|
||||
<dd>"Show cards added by Jason this month"</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -1,27 +0,0 @@
|
||||
<%= tag.div \
|
||||
id: "command-terminal",
|
||||
class: "terminal full-width flex flex-column justify-end",
|
||||
data: {
|
||||
controller: "terminal",
|
||||
terminal_dialog_outlet: "#terminal-modal",
|
||||
terminal_error_class: "terminal--error",
|
||||
terminal_confirmation_class: "terminal--confirmation",
|
||||
terminal_help_class: "terminal--showing-help",
|
||||
terminal_output_class: "terminal--showing-output",
|
||||
terminal_busy_class: "terminal--busy",
|
||||
toggle_class_toggle_class: "terminal--open"
|
||||
} do %>
|
||||
<%= tag.dialog \
|
||||
id: "terminal-modal",
|
||||
class: "terminal__modal panel shadow",
|
||||
data: {
|
||||
controller: "dialog",
|
||||
dialog_target: "dialog",
|
||||
dialog_modal_value: "true",
|
||||
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside" } do %>
|
||||
<%= turbo_frame_tag "terminal-turbo-frame", refresh: :morph, data: { terminal_target: "modalTurboFrame" }, target: "_top" %>
|
||||
<% end %>
|
||||
|
||||
<%= render "commands/form" %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class CommandsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "command that results in a redirect" do
|
||||
post commands_path, params: { command: "#{cards(:logo).id}" }
|
||||
|
||||
assert_redirected_to cards(:logo)
|
||||
end
|
||||
|
||||
test "command that triggers a redirect back" do
|
||||
post commands_path, params: { command: "design" }
|
||||
|
||||
assert_redirected_to search_path(q: "design")
|
||||
end
|
||||
end
|
||||
@@ -1,22 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class Command::GoToCardTest < ActionDispatch::IntegrationTest
|
||||
include CommandTestHelper
|
||||
|
||||
setup do
|
||||
@card = cards(:logo)
|
||||
end
|
||||
|
||||
test "redirect to the card perma" do
|
||||
result = execute_command "#{@card.id}"
|
||||
|
||||
assert_equal @card, result.url
|
||||
end
|
||||
|
||||
test "result in a regular search if the card does not exist" do
|
||||
command = parse_command "123"
|
||||
|
||||
result = command.execute
|
||||
assert_equal search_path(q: "123"), result.url
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user