Skip to content
Snippets Groups Projects
Unverified Commit 41663fb9 authored by Kevin Lyda's avatar Kevin Lyda
Browse files

Initial pass at ADD

Also using an editor based on tview.  Works for now; might be fine.
parent 340583b8
Branches
Tags
No related merge requests found
......@@ -33,6 +33,9 @@ sqlite trigger tracing: `.trace stdout --row --profile --stmt --expanded --plain
* An EDT inspired [editor](https://sourceforge.net/projects/edt-text-editor/)
* [gkilo](https://github.com/vcnovaes/gkilo)
* This [kilo editor](https://viewsourcecode.org/snaptoken/kilo/) tutorial
* Using giu, a [text-editor](https://serge-hulne.medium.com/coding-a-simple-text-editor-in-go-using-giu-quick-and-dirty-b9b97ab41e4a) (needs cgo, no)
* [bubbletea](https://github.com/charmbracelet/bubbletea) seems to be the tui that's winning
* Another option is tview - [simpler](https://github.com/rivo/tview).
* Cleanup help output.
* Remove the node/cluster/newsgroup/mailing-list related flags.
* Database
......
// Package editor provides a simple editor.
package editor
import (
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
// Editor is the editor for text files.
func Editor(placeholder, title string) (string, error) {
app := tview.NewApplication()
textArea := tview.NewTextArea().
SetPlaceholder(placeholder)
textArea.SetTitle(title)
helpInfo := tview.NewTextView().
SetText(" Press F1 for help, press Ctrl-C to exit")
position := tview.NewTextView().
SetTextAlign(tview.AlignRight)
pages := tview.NewPages()
pages.SetBackgroundColor(tcell.ColorDefault)
updateInfos := func() {
fromRow, fromColumn, toRow, toColumn := textArea.GetCursor()
if fromRow == toRow && fromColumn == toColumn {
// position.SetText(fmt.Sprintf("Row: [yellow]%d[white], Column: [yellow]%d ", fromRow, fromColumn))
position.SetText(fmt.Sprintf("Row: %d, Column: %d ", fromRow, fromColumn))
} else {
// position.SetText(fmt.Sprintf("[red]From[white] Row: [yellow]%d[white], Column: [yellow]%d[white] - [red]To[white] Row: [yellow]%d[white], To Column: [yellow]%d ", fromRow, fromColumn, toRow, toColumn))
position.SetText(fmt.Sprintf("From Row: %d, Column: %d - To Row: %d, To Column: %d ", fromRow, fromColumn, toRow, toColumn))
}
}
textArea.SetMovedFunc(updateInfos)
updateInfos()
mainView := tview.NewGrid().
SetRows(0, 1).
AddItem(textArea, 0, 0, 1, 2, 0, 0, true).
AddItem(helpInfo, 1, 0, 1, 1, 0, 0, false).
AddItem(position, 1, 1, 1, 1, 0, 0, false)
help1 := tview.NewTextView().
SetText(`Navigation
Left arrow: Move left.
Right arrow: Move right.
Down arrow: Move down.
Up arrow: Move up.
Ctrl-A, Home: Move to the beginning of the current line.
Ctrl-E, End: Move to the end of the current line.
Ctrl-F, page down: Move down by one page.
Ctrl-B, page up: Move up by one page.
Alt-Up arrow: Scroll the page up.
Alt-Down arrow: Scroll the page down.
Alt-Left arrow: Scroll the page to the left.
Alt-Right arrow: Scroll the page to the right.
Alt-B, Ctrl-Left arrow: Move back by one word.
Alt-F, Ctrl-Right arrow: Move forward by one word.
Press Enter for more help, press Escape to return.`)
help2 := tview.NewTextView().
SetText(`Editing
Type to enter text.
Ctrl-H, Backspace: Delete the left character.
Ctrl-D, Delete: Delete the right character.
Ctrl-K: Delete until the end of the line.
Ctrl-W: Delete the rest of the word.
Ctrl-U: Delete the current line.
Press Enter for more help, press Escape to return.`)
help3 := tview.NewTextView().
SetText(`Selecting Text
Move while holding Shift.
Double-click to select a word.
Clipboard
Ctrl-Q: Copy.
Ctrl-X: Cut.
Ctrl-V: Paste.
Undo
Ctrl-Z: Undo.
Ctrl-Y: Redo.
Press Enter for more help, press Escape to return.`)
help := tview.NewFrame(help1).
SetBorders(1, 1, 0, 0, 2, 2)
help.SetBorder(true).
SetTitle("Help").
SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyEscape {
pages.SwitchToPage("main")
return nil
} else if event.Key() == tcell.KeyEnter {
switch {
case help.GetPrimitive() == help1:
help.SetPrimitive(help2)
case help.GetPrimitive() == help2:
help.SetPrimitive(help3)
case help.GetPrimitive() == help3:
help.SetPrimitive(help1)
}
return nil
}
return event
})
pages.AddAndSwitchToPage("main", mainView, true).
AddPage("help", tview.NewGrid().
SetColumns(0, 64, 0).
SetRows(0, 22, 0).
AddItem(help, 1, 1, 1, 1, 0, 0, true), true, false)
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyF1 {
pages.ShowPage("help") //TODO: Check when clicking outside help window with the mouse. Then clicking help again.
return nil
}
return event
})
err := app.SetRoot(pages, true).Run()
if err != nil {
return "", err
}
return textArea.GetText(), nil
}
......@@ -14,17 +14,25 @@ require (
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.7.1 // indirect
github.com/golang-migrate/migrate v3.5.4+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/tview v0.0.0-20250501113434-0c592cd31026 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
modernc.org/libc v1.65.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.10.0 // indirect
......
......@@ -9,6 +9,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc=
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
......@@ -24,8 +28,12 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
......@@ -34,17 +42,60 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/tview v0.0.0-20250501113434-0c592cd31026 h1:ij8h8B3psk3LdMlqkfPTKIzeGzTaZLOiyplILMlxPAM=
github.com/rivo/tview v0.0.0-20250501113434-0c592cd31026/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/urfave/cli/v3 v3.3.2 h1:BYFVnhhZ8RqT38DxEYVFPPmGFTEf7tJwySTXsVRrS/o=
github.com/urfave/cli/v3 v3.3.2/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y=
modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs=
modernc.org/libc v1.65.1 h1:EwykJ3C7c5pCiZTU3dLkgRm3VdFGNFc8UXXzhhEZvbQ=
......
......@@ -13,6 +13,7 @@ topic of the message.
Format:
ADD [file-name]`,
MaxArgs: 1,
Action: ActionAdd,
Flags: dclish.Flags{
"/ALL": {
Description: `This option is restricted to privileged users. It is used in conjunction
......
......@@ -7,6 +7,7 @@ import (
"git.lyda.ie/kevin/bulletin/accounts"
"git.lyda.ie/kevin/bulletin/dclish"
"git.lyda.ie/kevin/bulletin/editor"
)
// ActionDirectory handles the `DIRECTORY` command. This lists all the
......@@ -21,3 +22,75 @@ func ActionDirectory(cmd *dclish.Command) error {
fmt.Println("TODO: List messages in folder")
return nil
}
// ActionAdd handles the `ADD` command. This adds a message to a folder.
func ActionAdd(cmd *dclish.Command) error {
optAll := 0
optBell := 0
optBroadcast := 0
optEdit := 0
optExpiration := 0
optExtract := 0
optFolder := []string{}
optIndent := 0
optPermanent := 0
optShutdown := 0
optSignature := 0
optSubject := ""
optSystem := 0
if cmd.Flags["/ALL"].Value == "true" {
optAll = 1
}
if cmd.Flags["/BELL"].Value == "true" {
optBell = 1
}
if cmd.Flags["/BROADCAST"].Value == "true" {
optBroadcast = 1
}
if cmd.Flags["/EDIT"].Value == "true" {
optEdit = 1
}
if cmd.Flags["/EXPIRATION"].Value == "true" {
optExpiration = 1
}
if cmd.Flags["/EXTRACT"].Value == "true" {
optExtract = 1
}
if cmd.Flags["/FOLDER"].Value != "" {
fmt.Printf("/FOLDER = %s\n", cmd.Flags["/FOLDER"].Value)
optFolder = strings.Split(cmd.Flags["/FOLDER"].Value, ",")
}
if cmd.Flags["/INDENT"].Value == "true" {
optIndent = 1
}
if cmd.Flags["/PERMANENT"].Value == "true" {
optPermanent = 1
}
if cmd.Flags["/SHUTDOWN"].Value == "true" {
optShutdown = 1
}
if cmd.Flags["/SIGNATURE"].Value == "true" {
optSignature = 1
}
if cmd.Flags["/SUBJECT"].Value != "" {
optSubject = cmd.Flags["/SUBJECT"].Value
}
if cmd.Flags["/SYSTEM"].Value == "true" {
optSystem = 1
}
todoRemove := optAll + optBell + optBroadcast + optEdit + optExpiration + optExtract + optIndent + optPermanent + optShutdown + optSignature + optSystem
if len(optFolder) == 0 {
optFolder = []string{accounts.User.CurrentFolder}
}
// TODO: check if folders exist.
// TODO: prompt for subject if optSubject == ""
message, err := editor.Editor(fmt.Sprintf("Enter message for '%s'...", optSubject), "Edit message")
if err != nil {
return err
}
fmt.Printf("TODO: The message you entered %d...\n%s\n...will be posted to %s.\n", todoRemove, message, strings.Join(optFolder, ", "))
return nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment