├── .gitignore
├── LICENSE
├── README.md
├── app.go
├── go.mod
├── go.sum
├── main.go
└── models
├── idea.go
└── submit.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 nore-dev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
What CLI
5 |
6 | **[What to code](https://what-to-code.com) CLI client**
7 |
8 |
9 | 
10 | ## About The Project
11 |
12 |
13 |
14 |
15 | This project allows you to view ideas from [**what-to-code.com**](https://what-to-code.com) from your terminal. You can also share your own ideas and like the ideas you like.
16 |
17 | 
18 |
19 | ## Built With
20 | * [Golang](https://go.dev/)
21 | * [Bubble Tea](https://github.com/charmbracelet/bubbletea)
22 | ## Contributing
23 |
24 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
25 |
26 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
27 | Don't forget to give the project a star! Thanks again!
28 |
29 | 1. Fork the Project
30 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
31 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
32 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
33 | 5. Open a Pull Request
34 |
35 |
36 | ## LICENSE
37 |
38 | Distributed under the **MIT** License. See `LICENSE.txt` for more information.
39 |
40 | (back to top)
41 |
--------------------------------------------------------------------------------
/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 |
7 | "github.com/charmbracelet/bubbles/list"
8 | tea "github.com/charmbracelet/bubbletea"
9 | "github.com/charmbracelet/lipgloss"
10 | "github.com/nore-dev/what-cli/models"
11 | )
12 |
13 | type App struct {
14 | list list.Model
15 | ideaModel models.IdeaModel
16 | submitModel models.SubmitModel
17 | selected bool
18 | isIdeaPage bool
19 | }
20 |
21 | var (
22 | itemStyle = lipgloss.NewStyle().PaddingLeft(4)
23 | selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
24 | paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
25 | helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
26 | )
27 |
28 | type Page string
29 |
30 | func (p Page) FilterValue() string { return "" }
31 |
32 | type itemDelegate struct{}
33 |
34 | func (d itemDelegate) Height() int { return 1 }
35 | func (d itemDelegate) Spacing() int { return 0 }
36 | func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
37 | return nil
38 | }
39 |
40 | func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
41 | i, ok := listItem.(Page)
42 | if !ok {
43 | return
44 | }
45 |
46 | fn := itemStyle.Render
47 | if index == m.Index() {
48 | fn = func(s string) string {
49 | return selectedItemStyle.Render("> " + s)
50 | }
51 | }
52 |
53 | fmt.Fprintf(w, fn(string(i)))
54 | }
55 |
56 | func NewApp() App {
57 | items := []list.Item{
58 | Page("Popular"),
59 | Page("Recent"),
60 | Page("Rising"),
61 | Page("Oldest"),
62 | Page("Random"),
63 | Page("Submit"),
64 | }
65 |
66 | app := App{
67 | list: list.New(items, itemDelegate{}, 0, 0),
68 | selected: false,
69 | isIdeaPage: false,
70 | ideaModel: models.NewIdeaModel(),
71 | submitModel: models.NewSubmitModel(),
72 | }
73 |
74 | app.list.Title = "What CLI"
75 |
76 | app.list.Styles.PaginationStyle = paginationStyle
77 | app.list.Styles.HelpStyle = helpStyle
78 |
79 | app.list.SetHeight(14)
80 | app.list.SetShowStatusBar(false)
81 | app.list.SetFilteringEnabled(false)
82 | app.list.SetShowFilter(false)
83 |
84 | return app
85 | }
86 |
87 | func (a App) Init() tea.Cmd {
88 | return nil
89 | }
90 |
91 | func (a App) getOrder() string {
92 | switch a.list.SelectedItem() {
93 | case Page("Popular"):
94 | return "POPULAR"
95 | case Page("Oldest"):
96 | return "OLDEST"
97 | case Page("Rising"):
98 | return "RISING"
99 | case Page("Recent"):
100 | return "RECENT"
101 | case Page("Random"):
102 | return "RANDOM"
103 | }
104 |
105 | return ""
106 | }
107 |
108 | func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
109 |
110 | switch msg := msg.(type) {
111 | case tea.KeyMsg:
112 | switch msg.String() {
113 | case "ctrl+c", "q", "esc":
114 | if a.selected {
115 | a.selected = false
116 | } else {
117 | return a, tea.Quit
118 | }
119 | case "enter", " ":
120 | switch a.list.SelectedItem() {
121 | case Page("Submit"):
122 | a.isIdeaPage = false
123 | a.selected = true
124 | default:
125 | if !a.selected {
126 | a.ideaModel.Clear()
127 | a.ideaModel.Order = a.getOrder()
128 | }
129 |
130 | a.selected = true
131 | a.isIdeaPage = true
132 | }
133 | }
134 | }
135 |
136 | var cmd tea.Cmd
137 | if a.selected {
138 | if a.isIdeaPage {
139 | a.ideaModel, _ = a.ideaModel.Update(msg)
140 | } else {
141 | a.submitModel, cmd = a.submitModel.Update(msg)
142 | }
143 | } else {
144 | a.list, _ = a.list.Update(msg)
145 | }
146 |
147 | return a, cmd
148 | }
149 |
150 | func (a App) View() string {
151 | v := a.list.View()
152 |
153 | if a.selected {
154 | if a.isIdeaPage {
155 | v = a.ideaModel.View()
156 | } else {
157 | v = a.submitModel.View()
158 | }
159 | }
160 |
161 | return v
162 | }
163 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/nore-dev/what-cli
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/charmbracelet/bubbles v0.10.2
7 | github.com/charmbracelet/bubbletea v0.19.3
8 | github.com/charmbracelet/lipgloss v0.4.0
9 | )
10 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
3 | github.com/charmbracelet/bubbles v0.10.2 h1:VK1Q7nnBMDFTlrMmvBgE9nidtU5udsIcZvFXvjE2Cfk=
4 | github.com/charmbracelet/bubbles v0.10.2/go.mod h1:jOA+DUF1rjZm7gZHcNyIVW+YrBPALKfpGVdJu8UiJsA=
5 | github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw=
6 | github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
7 | github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
8 | github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
9 | github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
10 | github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
11 | github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
12 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
13 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
14 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
15 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
16 | github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
17 | github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
18 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
19 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
20 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
21 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
22 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
23 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
24 | github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
25 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
26 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
27 | github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
28 | github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
29 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
30 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
31 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
32 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
33 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
34 | github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
35 | github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
36 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
37 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
38 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
39 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
40 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
41 | golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0=
42 | golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
43 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | tea "github.com/charmbracelet/bubbletea"
7 | )
8 |
9 | func main() {
10 | p := tea.NewProgram(NewApp(), tea.WithAltScreen())
11 |
12 | if err := p.Start(); err != nil {
13 | os.Exit(1)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/models/idea.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 |
9 | tea "github.com/charmbracelet/bubbletea"
10 | "github.com/charmbracelet/lipgloss"
11 | )
12 |
13 | var (
14 | ideaStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1).Width(40).Align(lipgloss.Center)
15 | titleStyle = lipgloss.NewStyle().Bold(true)
16 | descriptionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#aaa")).Margin(1).Italic(true)
17 | likeStyle = lipgloss.NewStyle().UnsetAlign().Align(lipgloss.Left).Background(lipgloss.Color("#CD5C5C")).
18 | Foreground(lipgloss.Color("#fff")).PaddingLeft(1).PaddingRight(1)
19 | tagStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0f0"))
20 | orderStyle = lipgloss.NewStyle().
21 | Foreground(lipgloss.Color("#FFFDF5")).
22 | Background(lipgloss.Color("#25A065")).
23 | Padding(0, 1)
24 | helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#626262")).MarginTop(2)
25 | modelStyle = lipgloss.NewStyle().MarginLeft(2)
26 | )
27 |
28 | const API_URL = "https://what-to-code.com/api"
29 |
30 | type Tag struct {
31 | Value string `json:"value,omitempty"`
32 | }
33 |
34 | type Idea struct {
35 | Description string `json:"description,omitempty"`
36 | Id int `json:"id,omitempty"`
37 | Likes int `json:"likes,omitempty"`
38 | Title string `json:"title,omitempty"`
39 | Tags []Tag `json:"tags,omitempty"`
40 | }
41 |
42 | type IdeaModel struct {
43 | list []Idea
44 | index int
45 | page int
46 | Order string
47 | hasMore bool
48 | }
49 |
50 | func NewIdeaModel() IdeaModel {
51 | return IdeaModel{
52 | index: 0,
53 | page: 0,
54 | list: []Idea{},
55 | Order: "POPULAR",
56 | hasMore: true,
57 | }
58 | }
59 |
60 | func (i IdeaModel) Init() tea.Cmd {
61 | return nil
62 | }
63 |
64 | func (i IdeaModel) Update(msg tea.Msg) (IdeaModel, tea.Cmd) {
65 |
66 | // First Time
67 | if len(i.list) == 0 {
68 | i.getMoreIdeas()
69 | }
70 |
71 | switch msg := msg.(type) {
72 | case tea.KeyMsg:
73 | switch msg.String() {
74 | case "w", "up", "a", "k":
75 | if i.index > 0 {
76 | i.index -= 1
77 | }
78 | case "s", "down", "d", "j":
79 | if i.index < len(i.list) {
80 | i.index += 1
81 | }
82 |
83 | if i.index == len(i.list) {
84 | if !i.hasMore {
85 | i.index -= 1
86 | break
87 | }
88 | i.getMoreIdeas()
89 | }
90 |
91 | case " ", "enter":
92 | i.Like()
93 |
94 | }
95 | }
96 |
97 | return i, nil
98 | }
99 |
100 | func (i IdeaModel) renderTags() string {
101 | s := ""
102 | for _, tag := range i.currentIdea().Tags {
103 | s += "#" + tag.Value
104 | }
105 |
106 | return s
107 | }
108 |
109 | func (i *IdeaModel) getMoreIdeas() {
110 |
111 | if i.Order == "RANDOM" {
112 | i.getRandomIdea()
113 | return
114 | }
115 |
116 | var newIdeas []Idea
117 |
118 | res, _ := http.Get(i.getListUrl())
119 |
120 | body, _ := ioutil.ReadAll(res.Body)
121 | err := json.Unmarshal(body, &newIdeas)
122 |
123 | if err != nil {
124 | fmt.Print(err)
125 | }
126 |
127 | defer res.Body.Close()
128 |
129 | if len(newIdeas) == 0 {
130 | i.index -= 1
131 | i.hasMore = false
132 | return
133 | }
134 |
135 | i.list = append(i.list, newIdeas...)
136 | i.page += 1
137 | }
138 |
139 | func (i *IdeaModel) getRandomIdea() {
140 | if len(i.list) == 0 {
141 | i.list = append(i.list, i.getIdea("random"))
142 | } else {
143 | *i.currentIdea() = i.getIdea("random")
144 | }
145 | }
146 |
147 | func (i IdeaModel) getListUrl() string {
148 | return fmt.Sprintf("%s/ideas?order=%s&page=%d", API_URL, i.Order, i.page)
149 | }
150 |
151 | func text_default(text string, def string) string {
152 | if len(text) == 0 {
153 | return "There is no " + def
154 | }
155 | return text
156 | }
157 |
158 | func (i *IdeaModel) Clear() {
159 | i.hasMore = true
160 | i.index = 0
161 | i.list = nil
162 | }
163 |
164 | func (i IdeaModel) Like() {
165 | http.Post(fmt.Sprintf("%s/ideas/%d/like", API_URL, i.currentIdea().Id), "", nil)
166 |
167 | *i.currentIdea() = i.getIdea(fmt.Sprintf("%d", i.currentIdea().Id))
168 | }
169 |
170 | func (i IdeaModel) currentIdea() *Idea {
171 | if i.Order == "RANDOM" {
172 | return &i.list[0]
173 | }
174 |
175 | return &i.list[i.index]
176 | }
177 |
178 | func (i IdeaModel) getIdea(id string) Idea {
179 | var idea Idea
180 |
181 | res, _ := http.Get(fmt.Sprintf("%s/ideas/%s", API_URL, id))
182 |
183 | body, _ := ioutil.ReadAll(res.Body)
184 | err := json.Unmarshal(body, &idea)
185 |
186 | if err != nil {
187 | fmt.Print(err)
188 | }
189 |
190 | defer res.Body.Close()
191 |
192 | return idea
193 | }
194 |
195 | func (i IdeaModel) View() string {
196 | s := titleStyle.Render(i.currentIdea().Title) + "\n"
197 |
198 | s += descriptionStyle.Render(text_default(i.currentIdea().Description, "description")) + "\n"
199 | s += likeStyle.Render(fmt.Sprintf("♥ %d", i.currentIdea().Likes)) + "\n\n"
200 | s += tagStyle.Render(text_default(i.renderTags(), "tag"))
201 |
202 | return modelStyle.Render(orderStyle.Render(i.Order) + "\n" + ideaStyle.Render(s) +
203 | helpStyle.Render("↑/k/w previous idea • ↓/j/s next idea • space like/dislike • q/esc quit"))
204 | }
205 |
--------------------------------------------------------------------------------
/models/submit.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/charmbracelet/bubbles/textinput"
5 | tea "github.com/charmbracelet/bubbletea"
6 | "github.com/charmbracelet/lipgloss"
7 | )
8 |
9 | var (
10 | submitStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Width(35).Height(10).Padding(0, 1, 1)
11 | backgroundStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFDF5")).
12 | Background(lipgloss.Color("#25A065")).Padding(0, 1)
13 | )
14 |
15 | type SubmitModel struct {
16 | fields []textinput.Model
17 | index int
18 | }
19 |
20 | func NewSubmitModel() SubmitModel {
21 | title := textinput.New()
22 | title.Placeholder = "Title"
23 | title.CharLimit = 100
24 | title.Focus()
25 |
26 | description := textinput.New()
27 | description.Placeholder = "Description"
28 | description.CharLimit = 1000
29 |
30 | tags := textinput.New()
31 | tags.Placeholder = "Tags"
32 |
33 | return SubmitModel{
34 | fields: []textinput.Model{
35 | title,
36 | description,
37 | tags,
38 | },
39 | index: 0,
40 | }
41 | }
42 |
43 | func (s SubmitModel) Init() tea.Cmd {
44 | return textinput.Blink
45 | }
46 |
47 | func (s *SubmitModel) Clear() {
48 | for i, _ := range s.fields {
49 | s.fields[i].SetValue("")
50 | }
51 |
52 | s.index = 0
53 | }
54 |
55 | func (s SubmitModel) Update(msg tea.Msg) (SubmitModel, tea.Cmd) {
56 |
57 | switch msg := msg.(type) {
58 | case tea.KeyMsg:
59 | switch msg.String() {
60 | case "up":
61 | if s.index > 0 {
62 | s.index -= 1
63 | s.fields[s.index].Focus()
64 | }
65 | case "down":
66 | if s.index < len(s.fields)-1 {
67 | s.index += 1
68 | s.fields[s.index].Focus()
69 | }
70 | }
71 | }
72 |
73 | s.fields[s.index], _ = s.fields[s.index].Update(msg)
74 |
75 | return s, nil
76 | }
77 |
78 | func (s SubmitModel) View() string {
79 | return backgroundStyle.Render("SUBMIT") + "\n" +
80 | submitStyle.Render(s.fields[s.index].View()) +
81 | helpStyle.Render("↑ previous field • ↓/j/s next field • enter submit • q/esc go back")
82 | }
83 |
--------------------------------------------------------------------------------