~chrisppy/beagles

effb893aab96ec1934acc2176c1321fb2ee823b2 — Chris Palmer 1 year, 4 months ago 4b756cd
Finish update impl and add mark read
9 files changed, 151 insertions(+), 52 deletions(-)

M Makefile
M README.md
M db.go
M go.mod
M go.sum
M model.go
M queue.go
M rss.go
M ui.go
M Makefile => Makefile +1 -2
@@ 21,8 21,7 @@ beagles: $(GOSRC)
	mkdir -m755 -p $(SHAREDIR)/$(PKGNAME) $(CONFIGDIR)/$(PKGNAME) \
		$(CACHEDIR)/$(PKGNAME)
	$(GO) build $(GOFLAGS) \
		-ldflags "-X main.Prefix=$(PREFIX) \
		-X main.Version=$(VERSION)/$(PKGNAME) \
		-ldflags "-X main.Version=$(VERSION)/$(PKGNAME) \
		-X main.ShareDir=$(SHAREDIR)/$(PKGNAME) \
		-X main.ConfigDir=$(CONFIGDIR)/$(PKGNAME) \
		-X main.CacheDir=$(CACHEDIR)/$(PKGNAME)" \

M README.md => README.md +1 -1
@@ 22,7 22,7 @@ Then compile beagles:

beagles allows the ability to set the configuration directory at compile time:

    $ make PREFIX=/usr CONFIGDIR=$HOME/.config
    $ make PREFIX=/usr CONFIGDIR=$HOME/.config CACHEDIR=$HOME/.cache SHAREDIR=$HOME/.local/share

## Installation


M db.go => db.go +34 -30
@@ 131,7 131,7 @@ func (s *Storage) CreateFeed(url string) error {
		return fmt.Errorf("unable to fetch url: %s", err.Error())
	}

	feed := convertFromFeed(f)
	feed := convertFromFeed(f, url)
	if feed == nil {
		return fmt.Errorf("failed getting rss feed for url: %s", url)
	}


@@ 225,7 225,7 @@ func (s *Storage) DeleteFeed(url string) error {

	db, err := openDB(s.Path)
	if err != nil {
		return err
		return fmt.Errorf("unable to open db: %s", err.Error())
	}
	defer db.Close()



@@ 234,9 234,12 @@ func (s *Storage) DeleteFeed(url string) error {
		// Delete from item database
		if err := db.Update(func(tx *bolt.Tx) error {
			if err := tx.Bucket([]byte(queueTbl)).Delete([]byte(key)); err != nil {
				return err
				return fmt.Errorf("error deleting from queue bucket: %s", err.Error())
			}
			return tx.Bucket([]byte(itemTbl)).Delete([]byte(key))
			if err := tx.Bucket([]byte(itemTbl)).Delete([]byte(key)); err != nil {
				return fmt.Errorf("error deleting from item bucket: %s", err.Error())
			}
			return nil

		}); err != nil {
			return fmt.Errorf("unable to delete queue and item: %s", err.Error())


@@ 274,7 277,7 @@ func (s *Storage) DeleteFeed(url string) error {
func (s *Storage) Update() error {
	db, err := openDB(s.Path)
	if err != nil {
		return err
		return fmt.Errorf("unable to open db: %s", err.Error())
	}
	defer db.Close()



@@ 283,7 286,7 @@ func (s *Storage) Update() error {

		items, err := findNewItems(f)
		if err != nil {
			return err
			return fmt.Errorf("error finding new items for `%s`: %s", updateURL, err.Error())
		}

		for _, item := range items {


@@ 294,28 297,28 @@ func (s *Storage) Update() error {
			}
			itemJSON, err := json.Marshal(item)
			if err != nil {
				return err
				return fmt.Errorf("unable to marshal item `%s` for `%s`: %s", location, updateURL, err.Error())
			}
			queueJSON, err := json.Marshal(q)
			if err != nil {
				return err
				return fmt.Errorf("unable to marshal queue item `%s` for `%s`: %s", location, updateURL, err.Error())
			}

			if err := db.Update(func(tx *bolt.Tx) error {
				// Add to Queue Database
				if err := tx.Bucket([]byte(queueTbl)).Put([]byte(location), queueJSON); err != nil {
					return err
					return fmt.Errorf("error putting `%s` into queue bucket for `%s`: %s", location, updateURL, err.Error())
				}

				// Add to Item Database
				if err := tx.Bucket([]byte(itemTbl)).Put([]byte(location), itemJSON); err != nil {
					return err
					return fmt.Errorf("error putting `%s` into item bucket for `%s`: %s", location, updateURL, err.Error())
				}

				return nil

			}); err != nil {
				return err
				return fmt.Errorf("unable to update queue and item `%s` for `%s`: %s", location, updateURL, err.Error())
			}

			s.Items[location] = item


@@ 327,19 330,19 @@ func (s *Storage) Update() error {
		if len(items) != 0 {
			feedJSON, err := json.Marshal(s.Feeds[updateURL])
			if err != nil {
				return err
				return fmt.Errorf("unable to marshal feed for `%s`: %s", updateURL, err.Error())
			}

			if err := db.Update(func(tx *bolt.Tx) error {
				// Update Feed Database
				if err := tx.Bucket([]byte(itemTbl)).Put([]byte(updateURL), feedJSON); err != nil {
					return err
				if err := tx.Bucket([]byte(feedTbl)).Put([]byte(updateURL), feedJSON); err != nil {
					return fmt.Errorf("error putting into feed bucket for `%s`: %s", updateURL, err.Error())
				}

				return nil

			}); err != nil {
				return err
				return fmt.Errorf("unable to update feed for `%s`: %s", updateURL, err.Error())
			}
		}
	}


@@ 349,27 352,28 @@ func (s *Storage) Update() error {
	return nil
}

// MarkPlayed with update the Storage to note the an item has been read,
// will update the Feed unread count and remove the item from the queue.
func (s *Storage) MarkPlayed(location string) error {
	if location == "" {
// MarkRead wil update the Storage to note the an item has been read,
// remove the item from the list.
func (s *Storage) MarkRead(qitem *QueueItem) error {
	if qitem == nil || qitem.Item.Link == "" {
		return fmt.Errorf("location was empty, unable to mark played")
	}

	db, err := openDB(s.Path)
	if err != nil {
		return err
		return fmt.Errorf("unable to open db: %s", err.Error())
	}
	defer db.Close()

	item := s.Items[location]
	link := qitem.Item.Link
	item := s.Items[link]
	item.Read = true

	feed := s.Feeds[item.FeedURL]

	index := -1
	for i, e := range s.Queue {
		if e.Item.Location == location {
		if e.Item.Link == link {
			index = i
			break
		}


@@ 381,34 385,34 @@ func (s *Storage) MarkPlayed(location string) error {

	itemJSON, err := json.Marshal(item)
	if err != nil {
		return err
		return fmt.Errorf("unable to marshal item: %s", err.Error())
	}

	feedJSON, err := json.Marshal(feed)
	if err != nil {
		return err
		return fmt.Errorf("unable to marshal feed: %s", err.Error())
	}

	if err := db.Update(func(tx *bolt.Tx) error {
		// Remove from Queue Database
		if err := tx.Bucket([]byte(queueTbl)).Delete([]byte(location)); err != nil {
			return err
		if err := tx.Bucket([]byte(queueTbl)).Delete([]byte(link)); err != nil {
			return fmt.Errorf("error deleting from queue bucket: %s", err.Error())
		}

		// Mark read in Item Database
		if err := tx.Bucket([]byte(itemTbl)).Put([]byte(location), itemJSON); err != nil {
			return err
		if err := tx.Bucket([]byte(itemTbl)).Put([]byte(link), itemJSON); err != nil {
			return fmt.Errorf("error putting into item bucket: %s", err.Error())
		}

		// Update Unread Count is Feed Database
		if err := tx.Bucket([]byte(feedTbl)).Put([]byte(feed.UpdateURL), feedJSON); err != nil {
			return err
			return fmt.Errorf("error putting into feed bucket: %s", err.Error())
		}

		return nil

	}); err != nil {
		return err
		return fmt.Errorf("unable to update feed, item, and queue: %s", err.Error())
	}

	return nil

M go.mod => go.mod +5 -1
@@ 7,10 7,14 @@ require (
	github.com/DataDrake/waterlog v1.0.5
	github.com/gdamore/tcell v1.3.0
	github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
	github.com/kr/pretty v0.1.0 // indirect
	github.com/mmcdole/gofeed v1.0.0
	github.com/olekukonko/tablewriter v0.0.4 // indirect
	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
	github.com/stretchr/testify v1.4.0 // indirect
	gitlab.com/tslocum/cview v1.4.4
	go.etcd.io/bbolt v1.3.4
	golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
	golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
	gopkg.in/yaml.v2 v2.3.0 // indirect
)

M go.sum => go.sum +22 -2
@@ 8,6 8,7 @@ github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=


@@ 17,6 18,11 @@ github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebK
github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=


@@ 36,30 42,44 @@ github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gitlab.com/tslocum/cview v1.4.4 h1:sh1MUSN5zFd7vK+lHEq1jAxRD82TJb6uFW+EnECsEyc=
gitlab.com/tslocum/cview v1.4.4/go.mod h1:+bEf1cg6IoWvL16YHJAKwGGpQf5s/nxXAA7YJr+WOHE=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 h1:rJm0LuqUjoDhSk2zO9ISMSToQxGz7Os2jRiOL8AWu4c=
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372 h1:zWPUEY/PjVHT+zO3L8OfkjrtIjf55joTxn/RQP/AjOI=
golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

M model.go => model.go +2 -0
@@ 30,6 30,7 @@ type QueueItem struct {
	PlaybackPOS int  `json:"playbackposition"`
}

// Compare will determine equality of dates
func (q *QueueItem) Compare(i *QueueItem) bool {
	return q.Item.Date.Sub(i.Item.Date) < 0
}


@@ 43,6 44,7 @@ type Feed struct {
	Title       string   `json:"title"`
	Description string   `json:"description"`
	UpdateURL   string   `json:"updateurl"`
	Link        string   `json:"link"`
	Items       []string `json:"items"`
}


M queue.go => queue.go +23 -4
@@ 56,14 56,14 @@ func (w *queue) increment() int {
		return -1
	}

	currItemIndex := w.Widget.GetCurrentItem()
	currItemIndex := w.index()

	newIndex := (currItemIndex + 1)
	if newIndex >= size {
		newIndex = 0
	}

	w.Widget.SetCurrentItem(newIndex)
	w.setIndex(newIndex)

	return newIndex
}


@@ 74,18 74,37 @@ func (w *queue) decrement() int {
		return -1
	}

	currItemIndex := w.Widget.GetCurrentItem()
	currItemIndex := w.index()

	newIndex := (currItemIndex - 1)
	if newIndex < 0 {
		newIndex = size - 1
	}

	w.Widget.SetCurrentItem(newIndex)
	w.setIndex(newIndex)

	return newIndex
}

func (w *queue) setIndex(newIndex int) {
	w.Widget.SetCurrentItem(newIndex)
}

func (w *queue) index() int {
	return w.Widget.GetCurrentItem()
}

func (w *queue) size() int {
	return w.Widget.GetItemCount()
}

func (w *queue) remove() int {
	i := w.index()
	w.Widget.RemoveItem(i)

	return i
}

func (w *queue) add(item Item) {
	name := item.Title
	if strings.Contains(item.Type, "audio") {

M rss.go => rss.go +6 -5
@@ 34,7 34,7 @@ func findNewItems(f *Feed) ([]*Item, error) {
it:
	for _, item := range feed.Items {
		for _, location := range f.Items {
			if item.Enclosures[0].URL == location {
			if item.Link == location {
				continue it
			}
		}


@@ 76,7 76,7 @@ func convertFromItem(item *gofeed.Item) *Item {
	return i
}

func convertFromFeed(feed *gofeed.Feed) *Feed {
func convertFromFeed(feed *gofeed.Feed, url string) *Feed {
	if feed == nil {
		return nil
	}


@@ 84,12 84,13 @@ func convertFromFeed(feed *gofeed.Feed) *Feed {
	f := &Feed{
		Title:       feed.Title,
		Description: feed.Description,
		UpdateURL:   feed.FeedLink,
		UpdateURL:   url,
		Link:        feed.FeedLink,
		Items:       make([]string, len(feed.Items)),
	}

	if f.UpdateURL == "" {
		f.UpdateURL = feed.Link
	if f.Link == "" {
		f.Link = feed.Link
	}

	return f

M ui.go => ui.go +57 -7
@@ 123,6 123,8 @@ func initUI(db *Storage, theme *themeType) *ui {
				return i.moveUp(event)
			case 'l':
				return i.moveRight(event)
			case 'm':
				return i.markRead(event)
			}
		}



@@ 164,7 166,19 @@ func initUI(db *Storage, theme *themeType) *ui {
}

func (i *ui) updateFeed(args []string) {
	// TODO: Update feeds
	if err := i.DB.Update(); err != nil {
		panic(err.Error())
	}
	i.App.draw(func() {
		i.ShowNotes.clear()
		i.Queue.clear()
		for x, item := range i.DB.Queue {
			if x == 0 {
				i.ShowNotes.setText(item.Item.Content)
			}
			i.Queue.add(item.Item)
		}
	})
}

func (i *ui) addFeed(args []string) {


@@ 181,7 195,6 @@ func (i *ui) addFeed(args []string) {
			i.Queue.add(item.Item)
		}
	})

}
func (i *ui) deleteFeed(args []string) {
	if err := i.DB.DeleteFeed(args[1]); err != nil {


@@ 199,6 212,40 @@ func (i *ui) deleteFeed(args []string) {
	})
}

func (i *ui) markRead(event *tcell.EventKey) *tcell.EventKey {
	switch i.Status {
	case queueStatus, showNotesStatus:
		if i.InCmdMode {
			return event
		}

		index := i.Queue.remove()
		if err := i.DB.MarkRead(i.DB.Queue[index]); err != nil {
			panic(err.Error())
		}

		if i.Queue.size() > 0 && index != -1 {
			switch index {
			case i.Queue.size():
				index--
				i.Queue.setIndex(index)
			case 0:
				i.Queue.setIndex(0)
			default:
				i.Queue.setIndex(index)
			}
			i.ShowNotes.setText(i.DB.Queue[index].Item.Content)
		}

		if i.Queue.size() == 0 {
			i.ShowNotes.setText("")
		}
		return nil
	}

	return event
}

func (i *ui) moveLeft(event *tcell.EventKey) *tcell.EventKey {
	switch i.Status {
	case showNotesStatus:


@@ 287,14 334,17 @@ KEYS:
	h,🠔
		move to the left section (if applicable)
	
	l,🠖
		move the the right section (if applicable)
	j,🠗
		navigate down in the current section (if applicable)
	
	k,🠕
		navigate up in the current section (if applicable)

	j,🠗
		navigate down in the current section (if applicable)
	
	l,🠖
		move the the right section (if applicable)
	
	m
		mark current post as read

	:
		enter command mode