mirror of
https://github.com/miguelmota/cointop
synced 2024-11-05 00:00:14 +00:00
add support for custom shortcuts
Former-commit-id: 0cbbdec70aeb238f3d2335402125e85ad86a749d [formerly 0cbbdec70aeb238f3d2335402125e85ad86a749d [formerly 8684c983cc8fe39ae992e98c8b1468863edc5522 [formerly 9acdc2f1a2412e48fd78c77e8c33f37a8e65f11a]]] Former-commit-id: da315cd287d39ed5929367e5ba291a1557256722 Former-commit-id: e832893298a69578434b8361effaf09401fc2bcb [formerly b3764ccad924fa795368fbca84852b19f018d873] Former-commit-id: a4e140a3b193a387e61ea03267475b451260a2b0
This commit is contained in:
parent
01c81f3871
commit
555e1b8742
124
README.md
124
README.md
@ -8,6 +8,19 @@
|
||||
|
||||
[`cointop`](https://github.com/miguelmota/cointop) is a fast and lightweight interactive terminal based UI application for tracking and monitoring cryptocurrency coin stats in real-time. The interface is inspired by [`htop`](https://en.wikipedia.org/wiki/Htop).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Install](#install)
|
||||
- [Updating](#updating)
|
||||
- [Usage](#usage)
|
||||
- [Shortcuts](#shortcuts)
|
||||
- [Config](#config)
|
||||
- [Actions](#actions)
|
||||
- [FAQ](#faq)
|
||||
- [Authors](#authors)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
|
||||
- Quick sort shortcuts
|
||||
@ -15,6 +28,7 @@
|
||||
- Pagination
|
||||
- 7 day charts
|
||||
- Color coded
|
||||
- Custom key bindings
|
||||
|
||||
#### Future releases
|
||||
|
||||
@ -23,7 +37,6 @@
|
||||
- Currency conversion (i.e. Euro, Yen)
|
||||
- Markets/Exchanges
|
||||
- CryptoCompare API
|
||||
- Custom shortcuts
|
||||
|
||||
## Install
|
||||
|
||||
@ -51,11 +64,13 @@ sudo curl -s "https://raw.githubusercontent.com/miguelmota/cointop/master/instal
|
||||
|
||||
## Usage
|
||||
|
||||
Just run the `cointop` command to get started:
|
||||
|
||||
```bash
|
||||
$ cointop
|
||||
```
|
||||
|
||||
### Shortcuts
|
||||
## Shortcuts
|
||||
|
||||
List of default shortcut keys:
|
||||
|
||||
@ -100,6 +115,7 @@ Key|Action
|
||||
<kbd>m</kbd>|Sort table by *[m]arket cap*
|
||||
<kbd>M</kbd>|Go to middle of visible table window (vim style)
|
||||
<kbd>n</kbd>|Sort table by *[n]ame*
|
||||
<kbd>o</kbd>|[o]pen row link
|
||||
<kbd>p</kbd>|Sort table by *[p]rice*
|
||||
<kbd>r</kbd>|Sort table by *[r]ank*
|
||||
<kbd>s</kbd>|Sort table by *[s]ymbol*
|
||||
@ -110,6 +126,110 @@ Key|Action
|
||||
<kbd>$</kbd>|Go to last page (vim style)
|
||||
<kbd>?</kbd>|Show help|
|
||||
|
||||
## Config
|
||||
|
||||
The first time you run cointop, it'll create a config file in:
|
||||
|
||||
```
|
||||
~/.cointop/config
|
||||
```
|
||||
|
||||
You can then configure the actions you want for each key:
|
||||
|
||||
(default `~/.cointop/config`)
|
||||
|
||||
```toml
|
||||
[shortcuts]
|
||||
"$" = "last_page"
|
||||
0 = "first_page"
|
||||
1 = "sort_column_1h_change"
|
||||
2 = "sort_column_24h_change"
|
||||
7 = "sort_column_7d_change"
|
||||
"?" = "help"
|
||||
G = "move_to_page_last_row"
|
||||
H = "move_to_page_visible_first_row"
|
||||
L = "move_to_page_visible_last_row"
|
||||
M = "move_to_page_visible_middle_row"
|
||||
a = "sort_column_available_supply"
|
||||
"alt+arrowdown" = "sort_column_desc"
|
||||
"alt+arrowleft" = "sort_left_column"
|
||||
"alt+arrowright" = "sort_right_column"
|
||||
"alt+arrowup" = "sort_column_asc"
|
||||
arrowdown = "move_down"
|
||||
arrowleft = "previous_page"
|
||||
arrowright = "next_page"
|
||||
arrowup = "move_up"
|
||||
c = "toggle_row_chart"
|
||||
"ctrl+c" = "quit"
|
||||
"ctrl+d" = "page_down"
|
||||
"ctrl+n" = "next_page"
|
||||
"ctrl+p" = "previous_page"
|
||||
"ctrl+r" = "refresh"
|
||||
"ctrl+u" = "page_up"
|
||||
end = "move_to_page_last_row"
|
||||
enter = "open_link"
|
||||
esc = "quit"
|
||||
f1 = "help"
|
||||
g = "move_to_page_first_row"
|
||||
h = "previous_page"
|
||||
home = "move_to_page_first_row"
|
||||
j = "move_down"
|
||||
k = "move_up"
|
||||
l = "next_page"
|
||||
m = "sort_column_market_cap"
|
||||
n = "sort_column_name"
|
||||
o = "open_link"
|
||||
p = "sort_column_price"
|
||||
pagedown = "page_down"
|
||||
pageup = "page_up"
|
||||
q = "quit"
|
||||
r = "sort_column_rank"
|
||||
s = "sort_column_symbol"
|
||||
space = "open_link"
|
||||
t = "sort_column_total_supply"
|
||||
u = "sort_column_last_updated"
|
||||
v = "sort_column_24h_volume"
|
||||
```
|
||||
|
||||
## List of actions
|
||||
|
||||
Action|Description
|
||||
----|------|
|
||||
`first_page`|Go to first page
|
||||
`help`|Show help
|
||||
`last_page`|Go to last page
|
||||
`move_to_page_first_row`|Move to first row on page
|
||||
`move_to_page_last_row`|Move to last row on page
|
||||
`move_to_page_visible_first_row`|Move to first visible row on page
|
||||
`move_to_page_visible_last_row`|Move to last visible row on page
|
||||
`move_to_page_visible_middle_row`|Move to middle visible row on page
|
||||
`move_up`|Move one row up
|
||||
`move_down`|Move one row down
|
||||
`next_page`|Go to next page
|
||||
`open_link`|Open row link
|
||||
`page_down`|Move one row down
|
||||
`page_up`|Scroll one page up
|
||||
`previous_page`|Go to previous page
|
||||
`quit`|Quit application
|
||||
`refresh`|Do a manual refresh on the data
|
||||
`sort_column_1h_change`|Sort table by column *1 hour change*
|
||||
`sort_column_24h_change`|Sort table by column *24 hour change*
|
||||
`sort_column_24h_volume`|Sort table by column *24 hour volume*
|
||||
`sort_column_7d_change`|Sort table by column *7 day change*
|
||||
`sort_column_asc`|Sort highlighted column by ascending order
|
||||
`sort_column_available_supply`|Sort table by column *available supply*
|
||||
`sort_column_desc`|Sort highlighted column by descending order
|
||||
`sort_column_last_updated`|Sort table by column *last updated*
|
||||
`sort_column_market_cap`|Sort table by column *market cap*
|
||||
`sort_column_name`|Sort table by column *name*
|
||||
`sort_column_price`|Sort table by column *price*
|
||||
`sort_column_rank`|Sort table by column *rank*
|
||||
`sort_column_symbol`|Sort table by column *symbol*
|
||||
`sort_column_total_supply`|Sort table by column *total supply*
|
||||
`sort_left_column`|Sort the column to the left of the highlighted column
|
||||
`sort_right_column`|Sort the column to the right of the highlighted column
|
||||
`toggle_row_chart`|Toggle the chart for the highlighted row
|
||||
|
||||
## FAQ
|
||||
|
||||
- Q: Where is the data from?
|
||||
|
@ -35,20 +35,13 @@ type Cointop struct {
|
||||
forcerefresh chan bool
|
||||
selectedcoin *apt.Coin
|
||||
maxtablewidth int
|
||||
shortcutkeys map[string]string
|
||||
config config // toml config
|
||||
}
|
||||
|
||||
// Run runs cointop
|
||||
func Run() {
|
||||
g, err := gocui.NewGui(gocui.Output256)
|
||||
if err != nil {
|
||||
log.Fatalf("new gocui: %v", err)
|
||||
}
|
||||
defer g.Close()
|
||||
g.Cursor = true
|
||||
g.Mouse = true
|
||||
g.Highlight = true
|
||||
ct := Cointop{
|
||||
g: g,
|
||||
api: api.NewCMC(),
|
||||
refreshticker: time.NewTicker(1 * time.Minute),
|
||||
sortby: "rank",
|
||||
@ -57,7 +50,18 @@ func Run() {
|
||||
perpage: 100,
|
||||
forcerefresh: make(chan bool),
|
||||
maxtablewidth: 175,
|
||||
shortcutkeys: defaultShortcuts(),
|
||||
}
|
||||
_ = ct.setupConfig()
|
||||
g, err := gocui.NewGui(gocui.Output256)
|
||||
if err != nil {
|
||||
log.Fatalf("new gocui: %v", err)
|
||||
}
|
||||
ct.g = g
|
||||
defer g.Close()
|
||||
g.Cursor = true
|
||||
g.Mouse = true
|
||||
g.Highlight = true
|
||||
g.SetManagerFunc(ct.layout)
|
||||
if err := ct.keybindings(g); err != nil {
|
||||
log.Fatalf("keybindings: %v", err)
|
||||
|
125
cointop/config.go
Normal file
125
cointop/config.go
Normal file
@ -0,0 +1,125 @@
|
||||
package cointop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Shortcuts map[string]interface{} `toml:"shortcuts"`
|
||||
}
|
||||
|
||||
func (ct *Cointop) setupConfig() error {
|
||||
err := ct.makeConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ct.makeConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ct.parseConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ct.loadShortcutsFromConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *Cointop) configDirPath() string {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return ".cointop"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s", usr.HomeDir, "/.cointop")
|
||||
}
|
||||
|
||||
func (ct *Cointop) configPath() string {
|
||||
return fmt.Sprintf("%v%v", ct.configDirPath(), "/config")
|
||||
}
|
||||
|
||||
func (ct *Cointop) makeConfigDir() error {
|
||||
path := ct.configDirPath()
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
_ = os.Mkdir(path, os.ModePerm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *Cointop) makeConfigFile() error {
|
||||
path := ct.configPath()
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
fo, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fo.Close()
|
||||
b, err := ct.configToToml()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fo.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *Cointop) parseConfig() error {
|
||||
var conf config
|
||||
path := ct.configPath()
|
||||
if _, err := toml.DecodeFile(path, &conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ct.config = conf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *Cointop) configToToml() ([]byte, error) {
|
||||
s := defaultShortcuts()
|
||||
ifcs := map[string]interface{}{}
|
||||
for k, v := range s {
|
||||
var i interface{} = v
|
||||
ifcs[k] = i
|
||||
}
|
||||
var inputs = &config{
|
||||
Shortcuts: ifcs,
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
encoder := toml.NewEncoder(&b)
|
||||
err := encoder.Encode(inputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (ct *Cointop) loadShortcutsFromConfig() error {
|
||||
actionsmap := actionsMap()
|
||||
for k, ifc := range ct.config.Shortcuts {
|
||||
k = strings.ToLower(k)
|
||||
v, ok := ifc.(string)
|
||||
if ok {
|
||||
if !actionsmap[k] {
|
||||
continue
|
||||
}
|
||||
if ct.shortcutkeys[k] == "" {
|
||||
continue
|
||||
}
|
||||
ct.shortcutkeys[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -6,58 +6,6 @@ import (
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
// defaults
|
||||
var shortcutkeys = map[string]string{
|
||||
"arrowup": "moveup",
|
||||
"arrowdown": "movedown",
|
||||
"arrowleft": "prevpage",
|
||||
"arrowright": "nextpage",
|
||||
"pagedown": "pagedown",
|
||||
"pageup": "pageup",
|
||||
"home": "movepagefirstrow",
|
||||
"end": "movepagelastrow",
|
||||
"enter": "openlink",
|
||||
"esc": "quit",
|
||||
"space": "openlink",
|
||||
"ctrl+c": "quit",
|
||||
"ctrl+d": "pagedown",
|
||||
"ctrl+n": "nextpage",
|
||||
"ctrl+p": "prevpage",
|
||||
"ctrl+r": "refresh",
|
||||
"ctrl+u": "pageup",
|
||||
"alt+arrowup": "sortcolasc",
|
||||
"alt+arrowdown": "sortcoldesc",
|
||||
"alt+arrowleft": "sortleftcol",
|
||||
"alt+arrowright": "sortrightcol",
|
||||
"f1": "help",
|
||||
"0": "movefirstpage",
|
||||
"1": "sortcol1hchange",
|
||||
"2": "sortcol24hchange",
|
||||
"7": "sortcol7dchange",
|
||||
"a": "sortcolavailablesupply",
|
||||
"c": "togglerowchart",
|
||||
"g": "movepagefirstrow",
|
||||
"G": "movepagelastrow",
|
||||
"h": "prevpage",
|
||||
"H": "movepagevisiblefirstrow",
|
||||
"j": "movedown",
|
||||
"k": "moveup",
|
||||
"l": "nextpage",
|
||||
"L": "movepagevisiblelastrow",
|
||||
"m": "sortcolmarketcap",
|
||||
"M": "movepagevisiblemiddlerow",
|
||||
"n": "sortcolname",
|
||||
"p": "sortcolprice",
|
||||
"r": "sortcolrank",
|
||||
"s": "sortcolsymbol",
|
||||
"t": "sortcoltotalsupply",
|
||||
"u": "sortcollastupdated",
|
||||
"v": "sortcol24hvolume",
|
||||
"q": "quit",
|
||||
"$": "movelastpage",
|
||||
"?": "help",
|
||||
}
|
||||
|
||||
func (ct *Cointop) parseKeys(s string) (interface{}, gocui.Modifier) {
|
||||
var key interface{}
|
||||
mod := gocui.ModNone
|
||||
@ -238,75 +186,76 @@ func (ct *Cointop) parseKeys(s string) (interface{}, gocui.Modifier) {
|
||||
}
|
||||
|
||||
func (ct *Cointop) keybindings(g *gocui.Gui) error {
|
||||
for k, v := range shortcutkeys {
|
||||
for k, v := range ct.shortcutkeys {
|
||||
v = strings.TrimSpace(strings.ToLower(v))
|
||||
var fn func(g *gocui.Gui, v *gocui.View) error
|
||||
key, mod := ct.parseKeys(k)
|
||||
switch v {
|
||||
case "moveup":
|
||||
case "move_up":
|
||||
fn = ct.cursorUp
|
||||
case "movedown":
|
||||
case "move_down":
|
||||
fn = ct.cursorDown
|
||||
case "prevpage":
|
||||
case "previous_page":
|
||||
fn = ct.prevPage
|
||||
case "nextpage":
|
||||
case "next_page":
|
||||
fn = ct.nextPage
|
||||
case "pagedown":
|
||||
case "page_down":
|
||||
fn = ct.pageDown
|
||||
case "pageup":
|
||||
case "page_up":
|
||||
fn = ct.pageUp
|
||||
case "sortcolsymbol":
|
||||
case "sort_column_symbol":
|
||||
fn = ct.sortfn("symbol", false)
|
||||
case "movepagefirstrow":
|
||||
case "move_to_page_first_row":
|
||||
fn = ct.navigateFirstLine
|
||||
case "movepagelastrow":
|
||||
case "move_to_page_last_row":
|
||||
fn = ct.navigateLastLine
|
||||
case "openlink":
|
||||
case "open_link":
|
||||
fn = ct.openLink
|
||||
case "refresh":
|
||||
fn = ct.refresh
|
||||
case "sortcolasc":
|
||||
case "sort_column_asc":
|
||||
fn = ct.sortAsc
|
||||
case "sortcoldesc":
|
||||
case "sort_column_desc":
|
||||
fn = ct.sortDesc
|
||||
case "sortleftcol":
|
||||
case "sort_left_column":
|
||||
fn = ct.sortPrevCol
|
||||
case "sortrightcol":
|
||||
case "sort_right_column":
|
||||
fn = ct.sortNextCol
|
||||
case "help":
|
||||
fn = ct.openHelp
|
||||
case "movefirstpage":
|
||||
case "first_page":
|
||||
fn = ct.firstPage
|
||||
case "sortcol1hchange":
|
||||
case "sort_column_1h_change":
|
||||
fn = ct.sortfn("1hchange", true)
|
||||
case "sortcol24hchange":
|
||||
case "sort_column_24h_change":
|
||||
fn = ct.sortfn("24hchange", true)
|
||||
case "sortcol7dchange":
|
||||
case "sort_column_7d_change":
|
||||
fn = ct.sortfn("7dchange", true)
|
||||
case "sortcolavailablesupply":
|
||||
case "sort_column_available_supply":
|
||||
fn = ct.sortfn("availablesupply", true)
|
||||
case "togglerowchart":
|
||||
case "toggle_row_chart":
|
||||
fn = ct.toggleCoinChart
|
||||
case "movepagevisiblefirstrow":
|
||||
case "move_to_page_visible_first_row":
|
||||
fn = ct.navigatePageFirstLine
|
||||
case "movepagevisiblelastrow":
|
||||
case "move_to_page_visible_last_row":
|
||||
fn = ct.navigatePageLastLine
|
||||
case "sortcolmarketcap":
|
||||
case "sort_column_market_cap":
|
||||
fn = ct.sortfn("marketcap", true)
|
||||
case "movepagevisiblemiddlerow":
|
||||
case "move_to_page_visible_middle_row":
|
||||
fn = ct.navigatePageMiddleLine
|
||||
case "sortcolname":
|
||||
case "sort_column_name":
|
||||
fn = ct.sortfn("name", true)
|
||||
case "sortcolprice":
|
||||
case "sort_column_price":
|
||||
fn = ct.sortfn("price", true)
|
||||
case "sortcolrank":
|
||||
case "sort_column_rank":
|
||||
fn = ct.sortfn("rank", false)
|
||||
case "sortcoltotalsupply":
|
||||
case "sort_column_total_supply":
|
||||
fn = ct.sortfn("totalsupply", true)
|
||||
case "sortcollastupdated":
|
||||
case "sort_column_last_updated":
|
||||
fn = ct.sortfn("lastupdated", true)
|
||||
case "sortcol24hvolume":
|
||||
case "sort_column_24h_volume":
|
||||
fn = ct.sortfn("24hvolume", true)
|
||||
case "movelastpage":
|
||||
case "last_page":
|
||||
fn = ct.lastPage
|
||||
case "quit":
|
||||
fn = ct.quit
|
||||
|
94
cointop/shortcuts.go
Normal file
94
cointop/shortcuts.go
Normal file
@ -0,0 +1,94 @@
|
||||
package cointop
|
||||
|
||||
func actionsMap() map[string]bool {
|
||||
return map[string]bool{
|
||||
"first_page": true,
|
||||
"help": true,
|
||||
"last_page": true,
|
||||
"move_to_page_first_row": true,
|
||||
"move_to_page_last_row": true,
|
||||
"move_to_page_visible_first_row": true,
|
||||
"move_to_page_visible_last_row": true,
|
||||
"move_to_page_visible_middle_row": true,
|
||||
"move_up": true,
|
||||
"move_down": true,
|
||||
"next_page": true,
|
||||
"open_link": true,
|
||||
"page_down": true,
|
||||
"page_up": true,
|
||||
"previous_page": true,
|
||||
"quit": true,
|
||||
"refresh": true,
|
||||
"sort_column_1h_change": true,
|
||||
"sort_column_24h_change": true,
|
||||
"sort_column_24h_volume": true,
|
||||
"sort_column_7d_change": true,
|
||||
"sort_column_asc": true,
|
||||
"sort_column_available_supply": true,
|
||||
"sort_column_desc": true,
|
||||
"sort_column_last_updated": true,
|
||||
"sort_column_market_cap": true,
|
||||
"sort_column_name": true,
|
||||
"sort_column_price": true,
|
||||
"sort_column_rank": true,
|
||||
"sort_column_symbol": true,
|
||||
"sort_column_total_supply": true,
|
||||
"sort_left_column": true,
|
||||
"sort_right_column": true,
|
||||
"toggle_row_chart": true,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultShortcuts() map[string]string {
|
||||
return map[string]string{
|
||||
"arrowup": "move_up",
|
||||
"arrowdown": "move_down",
|
||||
"arrowleft": "previous_page",
|
||||
"arrowright": "next_page",
|
||||
"pagedown": "page_down",
|
||||
"pageup": "page_up",
|
||||
"home": "move_to_page_first_row",
|
||||
"end": "move_to_page_last_row",
|
||||
"enter": "open_link",
|
||||
"esc": "quit",
|
||||
"space": "open_link",
|
||||
"ctrl+c": "quit",
|
||||
"ctrl+d": "page_down",
|
||||
"ctrl+n": "next_page",
|
||||
"ctrl+p": "previous_page",
|
||||
"ctrl+r": "refresh",
|
||||
"ctrl+u": "page_up",
|
||||
"alt+arrowup": "sort_column_asc",
|
||||
"alt+arrowdown": "sort_column_desc",
|
||||
"alt+arrowleft": "sort_left_column",
|
||||
"alt+arrowright": "sort_right_column",
|
||||
"f1": "help",
|
||||
"0": "first_page",
|
||||
"1": "sort_column_1h_change",
|
||||
"2": "sort_column_24h_change",
|
||||
"7": "sort_column_7d_change",
|
||||
"a": "sort_column_available_supply",
|
||||
"c": "toggle_row_chart",
|
||||
"g": "move_to_page_first_row",
|
||||
"G": "move_to_page_last_row",
|
||||
"h": "previous_page",
|
||||
"H": "move_to_page_visible_first_row",
|
||||
"j": "move_down",
|
||||
"k": "move_up",
|
||||
"l": "next_page",
|
||||
"L": "move_to_page_visible_last_row",
|
||||
"m": "sort_column_market_cap",
|
||||
"M": "move_to_page_visible_middle_row",
|
||||
"n": "sort_column_name",
|
||||
"o": "open_link",
|
||||
"p": "sort_column_price",
|
||||
"r": "sort_column_rank",
|
||||
"s": "sort_column_symbol",
|
||||
"t": "sort_column_total_supply",
|
||||
"u": "sort_column_last_updated",
|
||||
"v": "sort_column_24h_volume",
|
||||
"q": "quit",
|
||||
"$": "last_page",
|
||||
"?": "help",
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user