|
@@ -9,6 +9,7 @@ package issues
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"fmt"
|
|
"fmt"
|
|
|
|
|
+ "path/filepath"
|
|
|
"strings"
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
@@ -115,7 +116,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
}
|
|
}
|
|
|
case widget: // widget is initialized from m.load()
|
|
case widget: // widget is initialized from m.load()
|
|
|
switch T := msg.(type) {
|
|
switch T := msg.(type) {
|
|
|
- case createIssue:
|
|
|
|
|
|
|
+ case create:
|
|
|
m.widget = T
|
|
m.widget = T
|
|
|
cmds = append(cmds, T.render, T.init())
|
|
cmds = append(cmds, T.render, T.init())
|
|
|
default:
|
|
default:
|
|
@@ -127,7 +128,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// finally, handle input updates if any
|
|
// finally, handle input updates if any
|
|
|
- if w, ok := m.widget.(createIssue); ok {
|
|
|
|
|
|
|
+ if w, ok := m.widget.(create); ok {
|
|
|
var cmd tea.Cmd
|
|
var cmd tea.Cmd
|
|
|
m.widget, cmd = w.update(msg)
|
|
m.widget, cmd = w.update(msg)
|
|
|
cmds = append(cmds, cmd, w.render)
|
|
cmds = append(cmds, cmd, w.render)
|
|
@@ -151,9 +152,9 @@ func (m Model) View() string {
|
|
|
// WIDGET DEFINITIONS ---------------------------------------------------------
|
|
// WIDGET DEFINITIONS ---------------------------------------------------------
|
|
|
// ----------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-// interface definition for widgets
|
|
|
|
|
-//
|
|
|
|
|
// TODO add keyhelp func to widget interface
|
|
// TODO add keyhelp func to widget interface
|
|
|
|
|
+
|
|
|
|
|
+// interface definition for widgets
|
|
|
type widget interface {
|
|
type widget interface {
|
|
|
render() tea.Msg
|
|
render() tea.Msg
|
|
|
}
|
|
}
|
|
@@ -167,17 +168,19 @@ type inputField struct {
|
|
|
title string
|
|
title string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// widget for creating an issue
|
|
|
|
|
-//
|
|
|
|
|
// TODO invoke editor for descriptions
|
|
// TODO invoke editor for descriptions
|
|
|
-type createIssue struct {
|
|
|
|
|
|
|
+// TODO handle reset on esc
|
|
|
|
|
+
|
|
|
|
|
+// widget for creating an issue
|
|
|
|
|
+type create struct {
|
|
|
inputFields []inputField
|
|
inputFields []inputField
|
|
|
Path string
|
|
Path string
|
|
|
selected int
|
|
selected int
|
|
|
Err error // not implemented
|
|
Err error // not implemented
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func initialCreateIssueModel(path string, placeholder string) createIssue {
|
|
|
|
|
|
|
+// constructor for createIssue widget
|
|
|
|
|
+func initialCreateModel(path string, placeholder string) create {
|
|
|
spawnInput := func(f bool) textinput.Model {
|
|
spawnInput := func(f bool) textinput.Model {
|
|
|
ti := textinput.New()
|
|
ti := textinput.New()
|
|
|
ti.Placeholder = placeholder
|
|
ti.Placeholder = placeholder
|
|
@@ -196,9 +199,22 @@ func initialCreateIssueModel(path string, placeholder string) createIssue {
|
|
|
} else {
|
|
} else {
|
|
|
inputs = append(inputs, inputField{title: t, input: spawnInput(false)})
|
|
inputs = append(inputs, inputField{title: t, input: spawnInput(false)})
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ switch t {
|
|
|
|
|
+ case "title":
|
|
|
|
|
+ parsed := parsePathToHuman(path)
|
|
|
|
|
+
|
|
|
|
|
+ if parsed == "." {
|
|
|
|
|
+ parsed = ""
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ inputs[i].input.SetValue(parsed)
|
|
|
|
|
+ case "status":
|
|
|
|
|
+ inputs[i].input.SetValue("open")
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return createIssue{
|
|
|
|
|
|
|
+ return create{
|
|
|
inputFields: inputs,
|
|
inputFields: inputs,
|
|
|
Path: path,
|
|
Path: path,
|
|
|
selected: 0,
|
|
selected: 0,
|
|
@@ -206,46 +222,46 @@ func initialCreateIssueModel(path string, placeholder string) createIssue {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (ci createIssue) init() tea.Cmd {
|
|
|
|
|
|
|
+func (c create) init() tea.Cmd {
|
|
|
return textinput.Blink
|
|
return textinput.Blink
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (ci createIssue) update(msg tea.Msg) (createIssue, tea.Cmd) {
|
|
|
|
|
|
|
+func (c create) update(msg tea.Msg) (create, tea.Cmd) {
|
|
|
var cmds []tea.Cmd
|
|
var cmds []tea.Cmd
|
|
|
var cmd tea.Cmd
|
|
var cmd tea.Cmd
|
|
|
|
|
|
|
|
// simple anon funcs to increment the selected index
|
|
// simple anon funcs to increment the selected index
|
|
|
incrementSelected := func() {
|
|
incrementSelected := func() {
|
|
|
- if ci.selected < len(ci.inputFields) {
|
|
|
|
|
- ci.selected++
|
|
|
|
|
- for i := 0; i < len(ci.inputFields); i++ {
|
|
|
|
|
- if i == ci.selected {
|
|
|
|
|
- ci.inputFields[i].input.Focus()
|
|
|
|
|
|
|
+ if c.selected < len(c.inputFields) {
|
|
|
|
|
+ c.selected++
|
|
|
|
|
+ for i := 0; i < len(c.inputFields); i++ {
|
|
|
|
|
+ if i == c.selected {
|
|
|
|
|
+ c.inputFields[i].input.Focus()
|
|
|
} else {
|
|
} else {
|
|
|
- ci.inputFields[i].input.Blur()
|
|
|
|
|
|
|
+ c.inputFields[i].input.Blur()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- ci.selected = 0
|
|
|
|
|
- ci.inputFields[ci.selected].input.Focus()
|
|
|
|
|
|
|
+ c.selected = 0
|
|
|
|
|
+ c.inputFields[c.selected].input.Focus()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
decrementSelected := func() {
|
|
decrementSelected := func() {
|
|
|
- if ci.selected != 0 {
|
|
|
|
|
- ci.selected--
|
|
|
|
|
- for i := 0; i < len(ci.inputFields); i++ {
|
|
|
|
|
- if i == ci.selected {
|
|
|
|
|
- ci.inputFields[i].input.Focus()
|
|
|
|
|
|
|
+ if c.selected != 0 {
|
|
|
|
|
+ c.selected--
|
|
|
|
|
+ for i := 0; i < len(c.inputFields); i++ {
|
|
|
|
|
+ if i == c.selected {
|
|
|
|
|
+ c.inputFields[i].input.Focus()
|
|
|
} else {
|
|
} else {
|
|
|
- ci.inputFields[i].input.Blur()
|
|
|
|
|
|
|
+ c.inputFields[i].input.Blur()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- for i := 0; i < len(ci.inputFields); i++ {
|
|
|
|
|
- ci.inputFields[i].input.Blur()
|
|
|
|
|
|
|
+ for i := 0; i < len(c.inputFields); i++ {
|
|
|
|
|
+ c.inputFields[i].input.Blur()
|
|
|
}
|
|
}
|
|
|
- ci.selected = len(ci.inputFields)
|
|
|
|
|
|
|
+ c.selected = len(c.inputFields)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -257,10 +273,10 @@ func (ci createIssue) update(msg tea.Msg) (createIssue, tea.Cmd) {
|
|
|
case "shift+tab":
|
|
case "shift+tab":
|
|
|
decrementSelected()
|
|
decrementSelected()
|
|
|
case "enter":
|
|
case "enter":
|
|
|
- if ci.selected == len(ci.inputFields) { // confirm create
|
|
|
|
|
- ci.selected++
|
|
|
|
|
- } else if ci.selected == len(ci.inputFields)+1 { // confirmed
|
|
|
|
|
- cmds = append(cmds, ci.create)
|
|
|
|
|
|
|
+ if c.selected == len(c.inputFields) { // confirm create
|
|
|
|
|
+ c.selected++
|
|
|
|
|
+ } else if c.selected == len(c.inputFields)+1 { // confirmed
|
|
|
|
|
+ cmds = append(cmds, c.create)
|
|
|
} else {
|
|
} else {
|
|
|
incrementSelected()
|
|
incrementSelected()
|
|
|
}
|
|
}
|
|
@@ -268,7 +284,7 @@ func (ci createIssue) update(msg tea.Msg) (createIssue, tea.Cmd) {
|
|
|
cmds = append(cmds, tea.Quit)
|
|
cmds = append(cmds, tea.Quit)
|
|
|
}
|
|
}
|
|
|
case createResult:
|
|
case createResult:
|
|
|
- cmds = append(cmds, ci.write(Issue(msg)))
|
|
|
|
|
|
|
+ cmds = append(cmds, c.write(Issue(msg)))
|
|
|
case writeResult:
|
|
case writeResult:
|
|
|
switch value := msg.(type) {
|
|
switch value := msg.(type) {
|
|
|
case bool:
|
|
case bool:
|
|
@@ -281,16 +297,16 @@ func (ci createIssue) update(msg tea.Msg) (createIssue, tea.Cmd) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for i, ti := range ci.inputFields {
|
|
|
|
|
- ci.inputFields[i].input, cmd = ti.input.Update(msg)
|
|
|
|
|
|
|
+ for i, ti := range c.inputFields {
|
|
|
|
|
+ c.inputFields[i].input, cmd = ti.input.Update(msg)
|
|
|
cmds = append(cmds, cmd)
|
|
cmds = append(cmds, cmd)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
cmds = append(cmds, cmd)
|
|
cmds = append(cmds, cmd)
|
|
|
- return ci, tea.Batch(cmds...)
|
|
|
|
|
|
|
+ return c, tea.Batch(cmds...)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (ci createIssue) render() tea.Msg {
|
|
|
|
|
|
|
+func (c create) render() tea.Msg {
|
|
|
borderStyle := lipgloss.NewStyle().
|
|
borderStyle := lipgloss.NewStyle().
|
|
|
BorderStyle(lipgloss.NormalBorder()).
|
|
BorderStyle(lipgloss.NormalBorder()).
|
|
|
Margin(1).
|
|
Margin(1).
|
|
@@ -299,7 +315,7 @@ func (ci createIssue) render() tea.Msg {
|
|
|
ulStyle := lipgloss.NewStyle().Underline(true)
|
|
ulStyle := lipgloss.NewStyle().Underline(true)
|
|
|
|
|
|
|
|
var output string
|
|
var output string
|
|
|
- for _, field := range ci.inputFields {
|
|
|
|
|
|
|
+ for _, field := range c.inputFields {
|
|
|
output = output + fmt.Sprintf(
|
|
output = output + fmt.Sprintf(
|
|
|
"\n%s:%s",
|
|
"\n%s:%s",
|
|
|
field.title,
|
|
field.title,
|
|
@@ -309,14 +325,14 @@ func (ci createIssue) render() tea.Msg {
|
|
|
|
|
|
|
|
output = strings.TrimLeft(output, "\n")
|
|
output = strings.TrimLeft(output, "\n")
|
|
|
|
|
|
|
|
- if ci.selected < len(ci.inputFields) {
|
|
|
|
|
|
|
+ if c.selected < len(c.inputFields) {
|
|
|
output = output + borderStyle.Render("press enter to submit...")
|
|
output = output + borderStyle.Render("press enter to submit...")
|
|
|
- } else if ci.selected == len(ci.inputFields) {
|
|
|
|
|
|
|
+ } else if c.selected == len(c.inputFields) {
|
|
|
output = output + borderStyle.Render(ulStyle.Render("press enter to submit..."))
|
|
output = output + borderStyle.Render(ulStyle.Render("press enter to submit..."))
|
|
|
- } else if ci.selected == len(ci.inputFields)+1 {
|
|
|
|
|
|
|
+ } else if c.selected == len(c.inputFields)+1 {
|
|
|
confirmPrompt := fmt.Sprintf(
|
|
confirmPrompt := fmt.Sprintf(
|
|
|
- "creating issue titled \"%s\"...\n\n%s",
|
|
|
|
|
- ulStyle.Render(ci.inputFields[0].input.Value()),
|
|
|
|
|
|
|
+ "create issue titled \"%s\"?\n\n%s",
|
|
|
|
|
+ ulStyle.Render(c.inputFields[0].input.Value()),
|
|
|
ulStyle.Render("press enter to confirm..."),
|
|
ulStyle.Render("press enter to confirm..."),
|
|
|
)
|
|
)
|
|
|
output = output + borderStyle.Render(confirmPrompt)
|
|
output = output + borderStyle.Render(confirmPrompt)
|
|
@@ -417,15 +433,15 @@ func (m Model) load() tea.Msg {
|
|
|
return collection
|
|
return collection
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return initialCreateIssueModel(m.Path, "lorem ipsum")
|
|
|
|
|
|
|
+ return initialCreateModel(m.Path, "lorem ipsum")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
type createResult Issue
|
|
type createResult Issue
|
|
|
|
|
|
|
|
-// A widget for creating issues
|
|
|
|
|
-//
|
|
|
|
|
// TODO implement description field in createIssue.create cmd
|
|
// TODO implement description field in createIssue.create cmd
|
|
|
-func (ci createIssue) create() tea.Msg {
|
|
|
|
|
|
|
+
|
|
|
|
|
+// A widget for creating issues
|
|
|
|
|
+func (c create) create() tea.Msg {
|
|
|
data := make(map[string]string)
|
|
data := make(map[string]string)
|
|
|
commaSplit := func(t string) []string {
|
|
commaSplit := func(t string) []string {
|
|
|
s := strings.Split(t, ",")
|
|
s := strings.Split(t, ",")
|
|
@@ -438,12 +454,12 @@ func (ci createIssue) create() tea.Msg {
|
|
|
return s
|
|
return s
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for _, field := range ci.inputFields {
|
|
|
|
|
|
|
+ for _, field := range c.inputFields {
|
|
|
data[field.title] = field.input.Value()
|
|
data[field.title] = field.input.Value()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var newIssue = Issue{
|
|
var newIssue = Issue{
|
|
|
- Path: ci.Path,
|
|
|
|
|
|
|
+ Path: c.Path,
|
|
|
Tags: VariadicField{Path: "/tags"},
|
|
Tags: VariadicField{Path: "/tags"},
|
|
|
Blockedby: VariadicField{Path: "/blockedby"},
|
|
Blockedby: VariadicField{Path: "/blockedby"},
|
|
|
}
|
|
}
|
|
@@ -452,6 +468,10 @@ func (ci createIssue) create() tea.Msg {
|
|
|
switch key {
|
|
switch key {
|
|
|
case "title":
|
|
case "title":
|
|
|
newIssue.Title = value
|
|
newIssue.Title = value
|
|
|
|
|
+ if parsePathToHuman(newIssue.Path) != value {
|
|
|
|
|
+ dir, _ := filepath.Split(newIssue.Path)
|
|
|
|
|
+ newIssue.Path = filepath.Join(dir, value)
|
|
|
|
|
+ }
|
|
|
case "status":
|
|
case "status":
|
|
|
newIssue.Status = Field{Path: "/status", Data: value}
|
|
newIssue.Status = Field{Path: "/status", Data: value}
|
|
|
case "description":
|
|
case "description":
|
|
@@ -477,7 +497,7 @@ func (ci createIssue) create() tea.Msg {
|
|
|
type writeResult any
|
|
type writeResult any
|
|
|
|
|
|
|
|
// Wraps a cmd func, passes an initialized Issue to WriteIssue()
|
|
// Wraps a cmd func, passes an initialized Issue to WriteIssue()
|
|
|
-func (ci createIssue) write(issue Issue) tea.Cmd {
|
|
|
|
|
|
|
+func (c create) write(issue Issue) tea.Cmd {
|
|
|
return func() tea.Msg {
|
|
return func() tea.Msg {
|
|
|
result, err := WriteIssue(issue, false)
|
|
result, err := WriteIssue(issue, false)
|
|
|
if err != nil {
|
|
if err != nil {
|