|
|
@@ -80,7 +80,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
}
|
|
|
case widget: // widget is initialized from m.load()
|
|
|
switch T := msg.(type) {
|
|
|
- case create:
|
|
|
+ case edit:
|
|
|
m.widget = T
|
|
|
cmds = append(cmds, T.render, T.init())
|
|
|
default:
|
|
|
@@ -106,7 +106,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
// finally, pass msg to widget
|
|
|
var cmd tea.Cmd
|
|
|
switch w := m.widget.(type) {
|
|
|
- case create:
|
|
|
+ case edit:
|
|
|
m.widget, cmd = w.update(msg)
|
|
|
cmds = append(cmds, cmd, w.render)
|
|
|
case Issue:
|
|
|
@@ -144,7 +144,7 @@ type widget interface {
|
|
|
keyhelp() string // renders key usage
|
|
|
}
|
|
|
|
|
|
-// -------- create widget definitions -----------------------------------------
|
|
|
+// -------- edit widget definitions -----------------------------------------
|
|
|
// ----------------------------------------------------------------------------
|
|
|
// TODO(create widget) implement description field in create.create
|
|
|
|
|
|
@@ -163,8 +163,8 @@ type inputField struct {
|
|
|
title string
|
|
|
}
|
|
|
|
|
|
-// struct definition for create widget
|
|
|
-type create struct {
|
|
|
+// struct definition for edit widget
|
|
|
+type edit struct {
|
|
|
inputFields []inputField
|
|
|
Path string
|
|
|
selected int
|
|
|
@@ -172,7 +172,7 @@ type create struct {
|
|
|
}
|
|
|
|
|
|
// constructor for create widget
|
|
|
-func initialCreateWidget(path string, placeholder string) create {
|
|
|
+func newEditBlank(path string, placeholder string) widget { // TODO DEPRECATE
|
|
|
spawnInput := func(f bool) textinput.Model {
|
|
|
ti := textinput.New()
|
|
|
ti.Placeholder = placeholder
|
|
|
@@ -206,7 +206,7 @@ func initialCreateWidget(path string, placeholder string) create {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return create{
|
|
|
+ return edit{
|
|
|
inputFields: inputs,
|
|
|
Path: path,
|
|
|
selected: 0,
|
|
|
@@ -214,46 +214,108 @@ func initialCreateWidget(path string, placeholder string) create {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func newEditWidget(path string) widget {
|
|
|
+ // data prep
|
|
|
+ var e edit
|
|
|
+ var issue Issue
|
|
|
+ var err error
|
|
|
+
|
|
|
+ if IsIssue(path) { // if path is existing issue, load Issue from path
|
|
|
+ issue, err = Issue{}.NewFromPath(path)
|
|
|
+ if err != nil {
|
|
|
+ return e
|
|
|
+ }
|
|
|
+ } else { // if path is not existing issue, create new Issue with sensible defaults
|
|
|
+ issue = Issue{
|
|
|
+ Path: path, Title: parsePathToHuman(path),
|
|
|
+ Status: Field{Path: "/status", Data: "open"},
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // anon function for spawning instantiated textinput.Model's
|
|
|
+ spawnInput := func(f bool) textinput.Model {
|
|
|
+ ti := textinput.New()
|
|
|
+ if f {
|
|
|
+ ti.Focus()
|
|
|
+ }
|
|
|
+ ti.CharLimit = 80
|
|
|
+ ti.Width = 30
|
|
|
+ return ti
|
|
|
+ }
|
|
|
+
|
|
|
+ // inputFields data prep
|
|
|
+ var fields []inputField
|
|
|
+ var input textinput.Model
|
|
|
+
|
|
|
+ // title
|
|
|
+ input = spawnInput(true)
|
|
|
+ input.SetValue(issue.Title)
|
|
|
+ input.Placeholder = "title"
|
|
|
+ fields = append(fields, inputField{input: input, title: "title"})
|
|
|
+
|
|
|
+ // status
|
|
|
+ input = spawnInput(false)
|
|
|
+ input.SetValue(issue.Status.Data)
|
|
|
+ input.Placeholder = "status"
|
|
|
+ fields = append(fields, inputField{input: input, title: "status"})
|
|
|
+
|
|
|
+ // tags
|
|
|
+ input = spawnInput(false)
|
|
|
+ input.SetValue(issue.Tags.AsString())
|
|
|
+ input.Placeholder = "tags, separated by comma"
|
|
|
+ fields = append(fields, inputField{input: input, title: "tags"})
|
|
|
+
|
|
|
+ // blockedby
|
|
|
+ input = spawnInput(false)
|
|
|
+ input.SetValue(issue.Blockedby.AsString())
|
|
|
+ input.Placeholder = "blockers, separated by comma"
|
|
|
+ fields = append(fields, inputField{input: input, title: "blockedby"})
|
|
|
+
|
|
|
+ e.inputFields = fields
|
|
|
+
|
|
|
+ return e
|
|
|
+}
|
|
|
+
|
|
|
// init cmd for create widget
|
|
|
-func (c create) init() tea.Cmd { return textinput.Blink }
|
|
|
+func (e edit) init() tea.Cmd { return textinput.Blink }
|
|
|
|
|
|
// update cmd for create widget
|
|
|
-func (c create) update(msg tea.Msg) (widget, tea.Cmd) {
|
|
|
+func (e edit) update(msg tea.Msg) (widget, tea.Cmd) {
|
|
|
var cmds []tea.Cmd
|
|
|
var cmd tea.Cmd
|
|
|
|
|
|
// simple anon functions to increment the selected index
|
|
|
incrementSelected := func() {
|
|
|
- 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()
|
|
|
+ if e.selected < len(e.inputFields) {
|
|
|
+ e.selected++
|
|
|
+ for i := 0; i < len(e.inputFields); i++ {
|
|
|
+ if i == e.selected {
|
|
|
+ e.inputFields[i].input.Focus()
|
|
|
} else {
|
|
|
- c.inputFields[i].input.Blur()
|
|
|
+ e.inputFields[i].input.Blur()
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
- c.selected = 0
|
|
|
- c.inputFields[c.selected].input.Focus()
|
|
|
+ e.selected = 0
|
|
|
+ e.inputFields[e.selected].input.Focus()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
decrementSelected := func() {
|
|
|
- if c.selected != 0 {
|
|
|
- c.selected--
|
|
|
- for i := 0; i < len(c.inputFields); i++ {
|
|
|
- if i == c.selected {
|
|
|
- c.inputFields[i].input.Focus()
|
|
|
+ if e.selected != 0 {
|
|
|
+ e.selected--
|
|
|
+ for i := 0; i < len(e.inputFields); i++ {
|
|
|
+ if i == e.selected {
|
|
|
+ e.inputFields[i].input.Focus()
|
|
|
} else {
|
|
|
- c.inputFields[i].input.Blur()
|
|
|
+ e.inputFields[i].input.Blur()
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
- for i := 0; i < len(c.inputFields); i++ {
|
|
|
- c.inputFields[i].input.Blur()
|
|
|
+ for i := 0; i < len(e.inputFields); i++ {
|
|
|
+ e.inputFields[i].input.Blur()
|
|
|
}
|
|
|
- c.selected = len(c.inputFields)
|
|
|
+ e.selected = len(e.inputFields)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -265,19 +327,19 @@ func (c create) update(msg tea.Msg) (widget, tea.Cmd) {
|
|
|
case "shift+tab":
|
|
|
decrementSelected()
|
|
|
case "enter":
|
|
|
- if c.selected == len(c.inputFields) { // confirm create
|
|
|
- c.selected++
|
|
|
- } else if c.selected == len(c.inputFields)+1 { // confirmed
|
|
|
- cmds = append(cmds, c.create)
|
|
|
+ if e.selected == len(e.inputFields) { // confirm create
|
|
|
+ e.selected++
|
|
|
+ } else if e.selected == len(e.inputFields)+1 { // confirmed
|
|
|
+ cmds = append(cmds, e.createIssueObject)
|
|
|
} else {
|
|
|
incrementSelected()
|
|
|
}
|
|
|
case "esc": // reset
|
|
|
- for i, field := range c.inputFields {
|
|
|
+ for i, field := range e.inputFields {
|
|
|
field.input.Reset()
|
|
|
switch field.title {
|
|
|
case "title":
|
|
|
- parsed := parsePathToHuman(c.Path)
|
|
|
+ parsed := parsePathToHuman(e.Path)
|
|
|
|
|
|
if parsed == "." {
|
|
|
parsed = ""
|
|
|
@@ -287,40 +349,41 @@ func (c create) update(msg tea.Msg) (widget, tea.Cmd) {
|
|
|
case "status":
|
|
|
field.input.SetValue("open")
|
|
|
}
|
|
|
- c.inputFields[i] = field
|
|
|
+ e.inputFields[i] = field
|
|
|
}
|
|
|
}
|
|
|
case createResult:
|
|
|
- cmds = append(cmds, c.editBlankDescription(Issue(msg)))
|
|
|
+ cmds = append(cmds, e.editBlankDescription(Issue(msg)))
|
|
|
case editorResult:
|
|
|
if msg.err != nil {
|
|
|
- c.err = msg.err
|
|
|
+ e.err = msg.err
|
|
|
} else {
|
|
|
- cmds = append(cmds, c.write(msg.issue))
|
|
|
+ cmds = append(cmds, e.write(msg.issue))
|
|
|
}
|
|
|
case writeResult:
|
|
|
switch value := msg.(type) {
|
|
|
case bool:
|
|
|
if !value {
|
|
|
} else {
|
|
|
- cmds = append(cmds, func() tea.Msg { return loadPath(c.Path) })
|
|
|
+ cmds = append(cmds, func() tea.Msg { return loadPath(e.Path) })
|
|
|
}
|
|
|
case error:
|
|
|
- c.err = value
|
|
|
+ e.selected = -100
|
|
|
+ e.err = value
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- for i, ti := range c.inputFields {
|
|
|
- c.inputFields[i].input, cmd = ti.input.Update(msg)
|
|
|
+ for i, ti := range e.inputFields {
|
|
|
+ e.inputFields[i].input, cmd = ti.input.Update(msg)
|
|
|
cmds = append(cmds, cmd)
|
|
|
}
|
|
|
|
|
|
cmds = append(cmds, cmd)
|
|
|
- return c, tea.Batch(cmds...)
|
|
|
+ return e, tea.Batch(cmds...)
|
|
|
}
|
|
|
|
|
|
-// A tea.Cmd to translate create.inputs to a new Issue object
|
|
|
-func (c create) create() tea.Msg {
|
|
|
+// A tea.Cmd to translate createIssueObject.inputs to a new Issue object
|
|
|
+func (e edit) createIssueObject() tea.Msg {
|
|
|
data := make(map[string]string)
|
|
|
commaSplit := func(t string) []string {
|
|
|
s := strings.Split(t, ",")
|
|
|
@@ -333,12 +396,12 @@ func (c create) create() tea.Msg {
|
|
|
return s
|
|
|
}
|
|
|
|
|
|
- for _, field := range c.inputFields {
|
|
|
+ for _, field := range e.inputFields {
|
|
|
data[field.title] = field.input.Value()
|
|
|
}
|
|
|
|
|
|
var newIssue = Issue{
|
|
|
- Path: c.Path,
|
|
|
+ Path: e.Path,
|
|
|
Tags: VariadicField{Path: "/tags"},
|
|
|
Blockedby: VariadicField{Path: "/blockedby"},
|
|
|
}
|
|
|
@@ -374,7 +437,7 @@ func (c create) create() tea.Msg {
|
|
|
}
|
|
|
|
|
|
// Wraps a tea.Cmd function, passes an initialized Issue to WriteIssue()
|
|
|
-func (c create) write(issue Issue) tea.Cmd {
|
|
|
+func (e edit) write(issue Issue) tea.Cmd {
|
|
|
return func() tea.Msg {
|
|
|
result, err := WriteIssue(issue, false)
|
|
|
if err != nil {
|
|
|
@@ -388,7 +451,7 @@ func (c create) write(issue Issue) tea.Cmd {
|
|
|
// and errors
|
|
|
//
|
|
|
// WARNING! THIS METHOD HANGS UNTIL THE USER KILLS THE EDITOR!
|
|
|
-func (c create) editBlankDescription(issue Issue) tea.Cmd {
|
|
|
+func (e edit) editBlankDescription(issue Issue) tea.Cmd {
|
|
|
data, err := EditTemplate(DescriptionTemplate, InvokeEditor)
|
|
|
var output string
|
|
|
for _, line := range data {
|
|
|
@@ -402,12 +465,13 @@ func (c create) editBlankDescription(issue Issue) tea.Cmd {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (c create) editExistingDescription(issue Issue) tea.Cmd { return func() tea.Msg { return "" } }
|
|
|
+// does this just call InvokeEditor?
|
|
|
+func (e edit) editExistingDescription(issue Issue) tea.Cmd { return func() tea.Msg { return "" } }
|
|
|
|
|
|
// render cmd for create widget
|
|
|
-func (c create) render() tea.Msg {
|
|
|
- if c.err != nil {
|
|
|
- return fmt.Sprintf("failed to create issue... %s", c.err.Error())
|
|
|
+func (e edit) render() tea.Msg {
|
|
|
+ if e.err != nil {
|
|
|
+ return fmt.Sprintf("failed to create issue... %s", e.err.Error())
|
|
|
}
|
|
|
|
|
|
borderStyle := lipgloss.NewStyle().
|
|
|
@@ -418,7 +482,7 @@ func (c create) render() tea.Msg {
|
|
|
ulStyle := lipgloss.NewStyle().Underline(true)
|
|
|
|
|
|
var output string
|
|
|
- for _, field := range c.inputFields {
|
|
|
+ for _, field := range e.inputFields {
|
|
|
output = output + fmt.Sprintf(
|
|
|
"\n%s:%s",
|
|
|
field.title,
|
|
|
@@ -428,14 +492,14 @@ func (c create) render() tea.Msg {
|
|
|
|
|
|
output = strings.TrimLeft(output, "\n")
|
|
|
|
|
|
- if c.selected < len(c.inputFields) {
|
|
|
+ if e.selected < len(e.inputFields) {
|
|
|
output = output + borderStyle.Render("press enter to submit...")
|
|
|
- } else if c.selected == len(c.inputFields) {
|
|
|
+ } else if e.selected == len(e.inputFields) {
|
|
|
output = output + borderStyle.Render(ulStyle.Render("press enter to submit..."))
|
|
|
- } else if c.selected == len(c.inputFields)+1 {
|
|
|
+ } else if e.selected == len(e.inputFields)+1 {
|
|
|
confirmPrompt := fmt.Sprintf(
|
|
|
"create issue titled \"%s\"?\n\n%s",
|
|
|
- ulStyle.Render(c.inputFields[0].input.Value()),
|
|
|
+ ulStyle.Render(e.inputFields[0].input.Value()),
|
|
|
ulStyle.Render("press enter to write description..."),
|
|
|
)
|
|
|
output = output + borderStyle.Render(confirmPrompt)
|
|
|
@@ -445,9 +509,9 @@ func (c create) render() tea.Msg {
|
|
|
}
|
|
|
|
|
|
// keyhelp cmd for create widget
|
|
|
-func (c create) keyhelp() string {
|
|
|
+func (e edit) keyhelp() string {
|
|
|
var output string
|
|
|
- output = output + "\ntab/shift+tab: down/up\t\tenter: input value\t\tesc: reset\t\tctrl+c: quit"
|
|
|
+ output = output + "\ntab/shift+tab: down/up\t\tenter: input value\t\tesc: reset\t\tctrl+e: quit"
|
|
|
return output
|
|
|
}
|
|
|
|
|
|
@@ -503,7 +567,8 @@ func (i Issue) keyhelp() string {
|
|
|
return output
|
|
|
}
|
|
|
|
|
|
-// -------- IssueCollection widget definitions -------------------------------- // ----------------------------------------------------------------------------
|
|
|
+// -------- IssueCollection widget definitions --------------------------------
|
|
|
+// ----------------------------------------------------------------------------
|
|
|
|
|
|
type ( // Type definitions for use in tea.Msg life cycle for IssueCollection widget.
|
|
|
loadPath string // thrown when user selects a path to load.
|
|
|
@@ -609,7 +674,7 @@ func (w createInCollection) update(msg tea.Msg) (widget, tea.Cmd) {
|
|
|
|
|
|
func (w createInCollection) create() tea.Msg {
|
|
|
w.Path = filepath.Join(w.Path, w.name)
|
|
|
- return initialCreateWidget(w.Path, "lorem ipsum")
|
|
|
+ return newEditBlank(w.Path, "lorem ipsum")
|
|
|
}
|
|
|
|
|
|
func (w createInCollection) render() tea.Msg {
|
|
|
@@ -644,5 +709,5 @@ func (m Model) load() tea.Msg {
|
|
|
return collection
|
|
|
}
|
|
|
|
|
|
- return initialCreateWidget(m.Path, "lorem ipsum")
|
|
|
+ return newEditWidget(m.Path)
|
|
|
}
|