When exiting pager or filter, stay in news if we were on news prior

This commit is contained in:
Christian Rocha 2020-11-28 18:30:51 -05:00
parent 4c63c62bfa
commit cffa611dfb
4 changed files with 82 additions and 59 deletions

View file

@ -58,10 +58,11 @@ func (d DocTypeSet) Difference(t ...DocType) DocTypeSet {
// Equals returns whether or not the two sets are equal.
func (d DocTypeSet) Equals(other DocTypeSet) bool {
return d.Contains(other.asSlice()...) && len(d) == len(other)
return d.Contains(other.AsSlice()...) && len(d) == len(other)
}
func (d DocTypeSet) asSlice() (agg []DocType) {
// AsSlice returns the set as a slice of document types.
func (d DocTypeSet) AsSlice() (agg []DocType) {
for k := range d {
agg = append(agg, k)
}

View file

@ -29,6 +29,7 @@ type markdown struct {
charm.Markdown
}
// Generate the value we're doing to filter against.
func (m *markdown) buildFilterValue() {
note, err := normalize(m.Note)
if err != nil {
@ -109,6 +110,7 @@ func wrapMarkdowns(t DocType, md []*charm.Markdown) (m []*markdown) {
return m
}
// Return the time in a human-readable format relative to the current time.
func relativeTime(then time.Time) string {
now := time.Now()
ago := now.Sub(then)
@ -120,6 +122,7 @@ func relativeTime(then time.Time) string {
return then.Format("02 Jan 2006 15:04 MST")
}
// Magnitudes for relative time.
var magnitudes = []humanize.RelTimeMagnitude{
{D: time.Second, Format: "now", DivBy: time.Second},
{D: 2 * time.Second, Format: "1 second %s", DivBy: 1},

View file

@ -43,23 +43,37 @@ type filteredMarkdownMsg []*markdown
// MODEL
// High-level state of the application.
type stashViewState int
const (
stashStateReady stashViewState = iota
stashStateLoadingDocument
stashStateShowingError
stashStateShowNews
)
// Which types of documents we are showing. We use an int as the underlying
// type both for easy equality testing, and because the default state can have
// different types of docs depending on the user's preferences. For example,
// if the local-only flag is passed, the default state contains only documents
// of type local. Otherwise, it could contain stash, news, and converted types.
type stashDocState int
const (
stashShowDefaultDocs stashDocState = iota
stashShowNewsDocs
)
// The current filtering state.
type filterState int
const (
unfiltered filterState = iota
unfiltered filterState = iota // no filter set
filtering // user is actively setting a filter
filterApplied // a filter is applied and user is not editing filter
)
// The state of the currently selected document.
type selectionState int
const (
@ -70,15 +84,22 @@ const (
type stashModel struct {
general *general
state stashViewState
filterState filterState
selectionState selectionState
err error
spinner spinner.Model
noteInput textinput.Model
filterInput textinput.Model
stashFullyLoaded bool // have we loaded all available stashed documents from the server?
loadingFromNetwork bool // are we currently loading something from the network?
viewState stashViewState
filterState filterState
selectionState selectionState
// The types of documents we are showing
docState stashDocState
// Maps document states to document types, i.e. the news state contains a
// set of type "news".
docStateMap map[stashDocState]DocTypeSet
// Tracks what exactly is loaded between the stash, news and local files
loaded DocTypeSet
@ -124,7 +145,7 @@ func (m stashModel) stashedOnly() bool {
}
func (m stashModel) loadingDone() bool {
return m.loaded.Equals(m.general.cfg.DocumentTypes)
return m.loaded.Equals(m.general.cfg.DocumentTypes.Difference(ConvertedDoc))
}
// Returns whether or not we're online. That is, when "local-only" mode is
@ -291,27 +312,19 @@ func (m stashModel) getVisibleMarkdowns() []*markdown {
return m.filteredMarkdowns
}
if m.state == stashStateShowNews {
return m.getMarkdownByType(NewsDoc)
}
return m.getMarkdownByType(LocalDoc, StashedDoc, ConvertedDoc)
return m.getMarkdownByType(m.docStateMap[m.docState].AsSlice()...)
}
// Return the markdowns eligible to be filtered.
func (m stashModel) getFilterableMarkdowns() []*markdown {
if m.state == stashStateShowNews {
return m.getMarkdownByType(NewsDoc)
}
return m.getMarkdownByType(LocalDoc, StashedDoc, ConvertedDoc)
return m.getMarkdownByType(m.docStateMap[m.docState].AsSlice()...)
}
// Command for opening a markdown document in the pager. Note that this also
// alters the model.
func (m *stashModel) openMarkdown(md *markdown) tea.Cmd {
var cmd tea.Cmd
m.state = stashStateLoadingDocument
m.viewState = stashStateLoadingDocument
if md.markdownType == LocalDoc {
cmd = loadLocalMarkdown(md)
@ -406,6 +419,11 @@ func newStashModel(general *general) stashModel {
loaded: NewDocTypeSet(),
loadingFromNetwork: true,
filesStashing: make(map[string]struct{}),
docState: stashShowDefaultDocs,
docStateMap: map[stashDocState]DocTypeSet{
stashShowDefaultDocs: general.cfg.DocumentTypes.Difference(NewsDoc),
stashShowNewsDocs: NewDocTypeSet(NewsDoc),
},
}
return m
@ -482,7 +500,7 @@ func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) {
case spinner.TickMsg:
condition := !m.loadingDone() ||
m.loadingFromNetwork ||
m.state == stashStateLoadingDocument ||
m.viewState == stashStateLoadingDocument ||
len(m.filesStashing) > 0 ||
m.spinner.Visible()
@ -537,13 +555,13 @@ func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) {
}
// Updates per the current state
switch m.state {
case stashStateReady, stashStateShowNews:
switch m.viewState {
case stashStateReady:
cmds = append(cmds, m.handleDocumentBrowsing(msg))
case stashStateShowingError:
// Any key exists the error view
if _, ok := msg.(tea.KeyMsg); ok {
m.state = stashStateReady
m.viewState = stashStateReady
}
}
@ -577,8 +595,11 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
m.index = m.paginator.ItemsOnPage(pages) - 1
case "esc":
if m.isFiltering() {
m.resetFiltering()
m.state = stashStateReady
break
}
m.docState = stashShowDefaultDocs
// Open document
case "enter":
@ -637,16 +658,16 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
// If we're offline disable the news section
return nil
}
if m.state == stashStateShowNews {
if m.docState == stashShowNewsDocs {
// Exit news
m.state = stashStateReady
m.docState = stashShowDefaultDocs
m.resetFiltering()
} else {
// Show news
m.hideStatusMessage()
m.paginator.Page = 0
m.index = 0
m.state = stashStateShowNews
m.docState = stashShowNewsDocs
m.setTotalPages()
}
@ -686,7 +707,7 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
case "x":
m.hideStatusMessage()
validState := m.state == stashStateReady &&
validState := m.viewState == stashStateReady &&
m.selectionState == selectionIdle
if pages == 0 && !validState {
@ -700,8 +721,8 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
// Show errors
case "!":
if m.err != nil && m.state == stashStateReady {
m.state = stashStateShowingError
if m.err != nil && m.viewState == stashStateReady {
m.viewState = stashStateShowingError
return nil
}
}
@ -812,7 +833,7 @@ func (m *stashModel) handleFiltering(msg tea.Msg) tea.Cmd {
// If we've filtered down to nothing, clear the filter
if len(h) == 0 {
m.state = stashStateReady
m.viewState = stashStateReady
m.resetFiltering()
break
}
@ -820,7 +841,7 @@ func (m *stashModel) handleFiltering(msg tea.Msg) tea.Cmd {
// When there's only one filtered markdown left we can just
// "open" it directly
if len(h) == 1 {
m.state = stashStateReady
m.viewState = stashStateReady
m.resetFiltering()
cmds = append(cmds, m.openMarkdown(h[0]))
break
@ -890,12 +911,12 @@ func (m *stashModel) handleNoteInput(msg tea.Msg) tea.Cmd {
func (m stashModel) view() string {
var s string
switch m.state {
switch m.viewState {
case stashStateShowingError:
return errorView(m.err, false)
case stashStateLoadingDocument:
s += " " + m.spinner.View() + " Loading document..."
case stashStateReady, stashStateShowNews:
case stashStateReady:
loadingIndicator := " "
if !m.localOnly() && (!m.loadingDone() || m.loadingFromNetwork || m.spinner.Visible()) {
@ -925,7 +946,7 @@ func (m stashModel) view() string {
// Only draw the normal header if we're not using the header area for
// something else (like a prompt or status message)
if header == "" {
header = stashHeaderView(m)
header = m.headerView()
}
logoOrFilter := glowLogoView(" Glow ")
@ -933,7 +954,7 @@ func (m stashModel) view() string {
// If we're filtering we replace the logo with the filter field
if m.isFiltering() {
logoOrFilter = m.filterInput.View()
} else if m.state == stashStateShowNews {
} else if m.docState == stashShowNewsDocs {
logoOrFilter += newsTitleStyle(" News ")
}
@ -958,10 +979,10 @@ func (m stashModel) view() string {
loadingIndicator,
logoOrFilter,
header,
stashPopulatedView(m),
m.populatedView(),
blankLines,
pagination,
stashHelpView(m),
m.miniHelpView(),
)
}
return "\n" + indent(s, stashIndent)
@ -975,7 +996,7 @@ func glowLogoView(text string) string {
String()
}
func stashHeaderView(m stashModel) string {
func (m stashModel) headerView() string {
loading := !m.loadingDone()
noMarkdowns := len(m.markdowns) == 0
@ -1035,7 +1056,7 @@ func stashHeaderView(m stashModel) string {
return common.Subtle(s) + maybeOffline
}
func stashPopulatedView(m stashModel) string {
func (m stashModel) populatedView() string {
var b strings.Builder
mds := m.getVisibleMarkdowns()
@ -1068,7 +1089,7 @@ func stashPopulatedView(m stashModel) string {
return b.String()
}
func stashHelpView(m stashModel) string {
func (m stashModel) miniHelpView() string {
var (
h []string
isStashed bool
@ -1092,7 +1113,7 @@ func stashHelpView(m stashModel) string {
h = append(h, "enter/esc: cancel")
} else if m.filterState == filtering {
h = append(h, "enter: confirm", "esc: cancel", "ctrl+j/ctrl+k, ↑/↓: choose")
} else if m.state == stashStateShowNews {
} else if m.docState == stashShowNewsDocs {
h = append(h, "enter: open", "esc: return", "j/k, ↑/↓: choose", "q: quit")
} else {
if len(m.markdowns) > 0 {
@ -1118,12 +1139,12 @@ func stashHelpView(m stashModel) string {
h = append(h, "/: filter")
h = append(h, "q: quit")
}
return stashHelpViewBuilder(m.general.width, h...)
return stashMiniHelpViewBuilder(m.general.width, h...)
}
// builds the help view from various sections pieces, truncating it if the view
// would otherwise wrap to two lines.
func stashHelpViewBuilder(windowWidth int, sections ...string) string {
func stashMiniHelpViewBuilder(windowWidth int, sections ...string) string {
if len(sections) == 0 {
return ""
}

View file

@ -164,7 +164,7 @@ type model struct {
// method alters the model we also need to send along any commands returned.
func (m *model) unloadDocument() []tea.Cmd {
m.state = stateShowStash
m.stash.state = stashStateReady
m.stash.viewState = stashStateReady
m.pager.unload()
m.pager.showHelp = false
@ -181,8 +181,7 @@ func (m *model) unloadDocument() []tea.Cmd {
func newModel(cfg Config) tea.Model {
if cfg.GlamourStyle == "auto" {
dbg := te.HasDarkBackground()
if dbg {
if te.HasDarkBackground() {
cfg.GlamourStyle = "dark"
} else {
cfg.GlamourStyle = "light"
@ -190,7 +189,7 @@ func newModel(cfg Config) tea.Model {
}
if len(cfg.DocumentTypes) == 0 {
cfg.DocumentTypes.Add(LocalDoc, StashedDoc, NewsDoc)
cfg.DocumentTypes.Add(LocalDoc, StashedDoc, ConvertedDoc, NewsDoc)
}
general := general{
@ -255,17 +254,21 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
// Send q/esc through in these cases
switch m.stash.state {
case stashStateShowingError, stashStateShowNews:
switch m.stash.viewState {
case stashStateReady:
// Q also quits glow when displaying only newsitems. Esc
// still passes through.
if m.stash.state == stashStateShowNews && msg.String() == "q" {
if msg.String() == "q" {
return m, tea.Quit
}
m.stash, cmd = m.stash.update(msg)
return m, cmd
case stashStateShowingError:
m.stash, cmd = m.stash.update(msg)
return m, cmd
}
// Special cases for the pager
@ -304,11 +307,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Ctrl+C always quits no matter where in the application you are.
case "ctrl+c":
return m, tea.Quit
// Repaint
case "ctrl+l":
// TODO
return m, nil
}
// Window size is received when starting up and on every resize