Initial commit
This commit is contained in:
commit
7cd61129ec
|
@ -0,0 +1,12 @@
|
|||
# Goldberry
|
||||
|
||||
Goldberry is a graphical smallnet client supporting gopher and gemini protocols, as well as local files (coming soon).
|
||||
|
||||
This is an experimental project at the moment, but builds fine and works well. Try it out if you like. Goldberry is meant to be the graphical consort or my other client [bombadillo](https://bombadillo.colorfield.space).
|
||||
|
||||
## Building
|
||||
|
||||
Details coming soon as I work out the best toolchain. The brave can get `wails`, the main gui dependency here, and any of its dependencies for their system and then run `wails build` from the main folder of the repo to generate a build for their architecture.
|
||||
|
||||
Pre-built binaries coming soon (for 64 bit linux at least, more if I can work out cross compilation via docker).
|
||||
|
Binary file not shown.
|
@ -0,0 +1,121 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type History struct {
|
||||
urlList []string
|
||||
ptr int
|
||||
}
|
||||
|
||||
var ClientHistory = History{make([]string, 0, 10), -1}
|
||||
|
||||
func (h *History) Add(u string) {
|
||||
if h.ptr > -1 && len(h.urlList) > 0 {
|
||||
h.urlList = h.urlList[:h.ptr+1]
|
||||
} else {
|
||||
h.urlList = make([]string, 0, 10)
|
||||
}
|
||||
h.urlList = append(h.urlList, u)
|
||||
h.ptr++
|
||||
}
|
||||
|
||||
func (h *History) Back() (string, error) {
|
||||
if h.ptr > 0 {
|
||||
h.ptr--
|
||||
return h.urlList[h.ptr], nil
|
||||
}
|
||||
return "", fmt.Errorf("Unable to go back")
|
||||
}
|
||||
|
||||
func (h *History) Forward() (string, error) {
|
||||
if h.ptr < len(h.urlList) - 1 && len(h.urlList) > 0 {
|
||||
h.ptr++
|
||||
return h.urlList[h.ptr], nil
|
||||
}
|
||||
return "", fmt.Errorf("Unable to go back")
|
||||
}
|
||||
|
||||
|
||||
func MakeImage(data, mime string) string {
|
||||
var out strings.Builder
|
||||
out.WriteString("<img src=\"data:")
|
||||
out.WriteString(mime)
|
||||
out.WriteString(";base64,")
|
||||
out.WriteString(base64.StdEncoding.EncodeToString([]byte(data)))
|
||||
out.WriteString("\" alt=\"\">")
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func RenderSearchForm(queryText string, u *url.URL) (string, error) {
|
||||
out := `
|
||||
<form id="search" action="%s">
|
||||
<label for="QueryResponse">%s</label>
|
||||
<input type="text" name="Query Response" id="QueryResponse" />
|
||||
<input type="submit" name="s" value="Submit" />
|
||||
</form>
|
||||
`
|
||||
action := u.String()
|
||||
if u.Scheme == "gopher" {
|
||||
action = strings.Replace(action, "/7", "/1", 1)
|
||||
}
|
||||
return fmt.Sprintf(out, action, queryText), nil
|
||||
}
|
||||
|
||||
func NavigateTo(addr string) (string, error) {
|
||||
redirectCount = 0
|
||||
if strings.Index(addr, " ") > -1 || strings.Index(addr, "://") < 0 {
|
||||
addr = fmt.Sprintf(settings.SearchURL, url.PathEscape(addr))
|
||||
}
|
||||
|
||||
u, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return ConvertErrorToHTML(97, "Error encoding URL"), nil
|
||||
}
|
||||
|
||||
if u.Scheme == "" {
|
||||
u.Scheme = "gemini"
|
||||
}
|
||||
|
||||
u.Scheme = strings.ToLower(u.Scheme)
|
||||
|
||||
switch u.Scheme {
|
||||
case "gemini":
|
||||
if u.Port() == "" {
|
||||
u.Host = u.Host + ":1965"
|
||||
}
|
||||
return GetGemini(u)
|
||||
case "http", "https":
|
||||
if u.Port() == "" && u.Scheme == "http" {
|
||||
u.Host = u.Host + ":80"
|
||||
} else if u.Port() == "" && u.Scheme == "https" {
|
||||
u.Host = u.Host + ":443"
|
||||
}
|
||||
if !settings.OpenWebLinks {
|
||||
return WebDisabled, nil
|
||||
}
|
||||
err := appCtrl.OpenHTTP(u.String())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to open URL in system web browser")
|
||||
}
|
||||
return "", fmt.Errorf("Opened URL in system web browser")
|
||||
case "gopher":
|
||||
if u.Port() == "" {
|
||||
u.Host = u.Host + ":70"
|
||||
}
|
||||
if !settings.OpenGopherLinks {
|
||||
return GopherDisabled, nil
|
||||
}
|
||||
return RetrieveGopher(u)
|
||||
case "file":
|
||||
// handle things
|
||||
return "Stuff and things", nil
|
||||
case "goldberry":
|
||||
return RouteGoldberry(u)
|
||||
}
|
||||
return WTFError, nil
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/wailsapp/wails"
|
||||
)
|
||||
|
||||
// Counter is what we use for counting
|
||||
type Counter struct {
|
||||
r *wails.Runtime
|
||||
store *wails.Store
|
||||
}
|
||||
|
||||
// WailsInit is called when the component is being initialised
|
||||
func (c *Counter) WailsInit(runtime *wails.Runtime) error {
|
||||
c.r = runtime
|
||||
c.store = runtime.Store.New("Counter", 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RandomValue sets the counter to a random value
|
||||
func (c *Counter) RandomValue() {
|
||||
c.store.Set(rand.Intn(1000))
|
||||
}
|
||||
|
||||
// Increment will increment the counter
|
||||
func (c *Counter) Increment() {
|
||||
|
||||
increment := func(data int) int {
|
||||
return data + 1
|
||||
}
|
||||
|
||||
// Update the store using the increment function
|
||||
c.store.Update(increment)
|
||||
}
|
||||
|
||||
// Decrement will decrement the counter
|
||||
func (c *Counter) Decrement() {
|
||||
|
||||
decrement := func(data int) int {
|
||||
return data - 1
|
||||
}
|
||||
// Update the store using the decrement function
|
||||
c.store.Update(decrement)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
WebDisabled string = `<h1 class="error-header">X1</h1><p>Loading http(s) URLs is disabled currently disabled.</p><p>This can be adjusted in your settings. Which can be found in one of these locations:</p><ul><li><pre>$XDG_CONFIG_HOME/goldberry/goldberry.json</pre></li><li><pre>~/.config/goldberry/goldberry.json</li></ul>`
|
||||
GopherDisabled string = `<h1 class="error-header">X2</h1><p>Loading gopher URLs is disabled currently disabled.</p><p>This can be adjusted in your settings. Which can be found in one of these locations:</p><ul><li><pre>$XDG_CONFIG_HOME/goldberry/goldberry.json</pre></li><li><pre>~/.config/goldberry/goldberry.json</li></ul>`
|
||||
FileDisabled string = `<h1 class="error-header">X3</h1><p>Loading file/local URLs is disabled currently disabled.</p><p>This can be adjusted in your settings. Which can be found in one of these locations:</p><ul><li><pre>$XDG_CONFIG_HOME/goldberry/goldberry.json</pre></li><li><pre>~/.config/goldberry/goldberry.json</li></ul>`
|
||||
TooManyRedirects string = `<h1 class="error-header">X4</h1><p>Too many redirects were encountered.</p>`
|
||||
WTFError string = `<h1 class="error-header">X9</h1><p>General client error. This is a code bug and nothing you did wrong as a user.</p>`
|
||||
)
|
||||
|
||||
// Used for status codes returned from gemini, thus the int
|
||||
func ConvertErrorToHTML(status int, msg string) string {
|
||||
format := `
|
||||
<h1 class="error-header">Error: %d</h1>
|
||||
<p class="">%s</p>
|
||||
`
|
||||
return fmt.Sprintf(format, status, msg)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,250 @@
|
|||
|
||||
html,
|
||||
body {
|
||||
background-color: white;
|
||||
color: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
height: 35px;
|
||||
background-color: rgba(40,40,40);
|
||||
padding: 5px;
|
||||
font-family: monospace;
|
||||
font-size: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header button {
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
height: 100%;
|
||||
width: 35px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
transition: background-color 0.2s;
|
||||
margin: 0 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
header button:disabled {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
header#ui #ui-back::before, header#ui #ui-forward::before {
|
||||
content: '\22B2';
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
font-size: 1rem;
|
||||
color: silver;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header#ui #ui-forward::before {
|
||||
content: '\22B3';
|
||||
}
|
||||
|
||||
header button:hover, header button:focus {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
header form {
|
||||
height: 35px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
width: calc(100% - 115px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
header form input {
|
||||
height: 100%;
|
||||
padding-left: 10px;
|
||||
margin: 0;
|
||||
vertical-align: center;
|
||||
background-color: rgba(50,50,50,1);
|
||||
color: silver;
|
||||
border: 1px solid whitesmoke;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
header input:focus {
|
||||
border-color: goldenrod;
|
||||
}
|
||||
|
||||
|
||||
@keyframes colorPulse {
|
||||
50% {
|
||||
background-color: #BBB161;
|
||||
color: black;
|
||||
}
|
||||
100% {
|
||||
background-color: rgb(50,50,50);
|
||||
color: silver;
|
||||
}
|
||||
}
|
||||
|
||||
header input.loading {
|
||||
animation: colorPulse 2s linear infinite;
|
||||
}
|
||||
|
||||
#ui-spinner {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 30px;
|
||||
margin-left: 2px;
|
||||
border-radius: 5px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
#ui-spinner.loading {
|
||||
animation: colorPulse 1s linear infinite;
|
||||
}
|
||||
|
||||
#main {
|
||||
background-color: black;
|
||||
color: silver;
|
||||
font-family: serif, sans-serif, monospace;
|
||||
font-size: 1em;
|
||||
height: calc(100vh - 45px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#content {
|
||||
max-width: 900px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 0 5rem;
|
||||
}
|
||||
|
||||
#content a, #content a:active {
|
||||
display: inline-block;
|
||||
padding: 2px 0;
|
||||
color: goldenrod;
|
||||
text-decoration: none;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#content a:visited {
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
#content a::before {
|
||||
content: '\279c';
|
||||
margin-right: 0.5em;
|
||||
color: gray;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
transition: margin-right 0.2s;
|
||||
}
|
||||
|
||||
#content a.gopher::before {
|
||||
content: '\2754';
|
||||
}
|
||||
|
||||
#content a.gopher.type-h::before {
|
||||
content: '\1F578';
|
||||
}
|
||||
|
||||
#content a.gopher.type-7::before {
|
||||
content: '\1F50E';
|
||||
}
|
||||
|
||||
#content a.gopher.type-0::before {
|
||||
content: '\1F4DD';
|
||||
}
|
||||
|
||||
#content a.gopher.type-1::before {
|
||||
content: '\1F4C1';
|
||||
}
|
||||
|
||||
#content a.gopher.type-g::before,
|
||||
#content a.gopher.type-I::before {
|
||||
content: '\1F3AB';
|
||||
}
|
||||
|
||||
#content a.gopher.type-s::before {
|
||||
content: '\1F3A7';
|
||||
}
|
||||
|
||||
#content a.gopher.type-9::before {
|
||||
content: '\1F4BE';
|
||||
}
|
||||
|
||||
#content a.gopher.type-W::before {
|
||||
content: '\1F30E';
|
||||
}
|
||||
|
||||
#content a:focus::before, #content a:hover::before {
|
||||
color: limegreen;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#content pre {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#content pre.gopher {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#content h1, #content h2, #content h3 {
|
||||
font-family: serif;
|
||||
}
|
||||
|
||||
#content h1.error-header {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#content p {
|
||||
font-family: sans-serif;
|
||||
color: silver;
|
||||
}
|
||||
|
||||
#content blockquote {
|
||||
border-left: 4px solid cyan;
|
||||
}
|
||||
|
||||
#content ul li {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
#content img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
#ui-url-display {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
color: white;
|
||||
background-color: #2E2E2E;
|
||||
font-size: 0.9em;
|
||||
transition: 0.2s;
|
||||
border-radius: 0 5px 0 0;
|
||||
}
|
||||
|
||||
#ui-url-display.active {
|
||||
opacity: 0.9;
|
||||
padding: 2px 5px;
|
||||
border-top: 1px dotted gray;
|
||||
border-right: 1px dotted gray;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
../acorn/bin/acorn
|
|
@ -0,0 +1 @@
|
|||
../ansi-html/bin/ansi-html
|
|
@ -0,0 +1 @@
|
|||
../atob/bin/atob.js
|
|
@ -0,0 +1 @@
|
|||
../errno/cli.js
|
|
@ -0,0 +1 @@
|
|||
../eslint/bin/eslint.js
|
|
@ -0,0 +1 @@
|
|||
../esprima/bin/esparse.js
|
|
@ -0,0 +1 @@
|
|||
../esprima/bin/esvalidate.js
|
|
@ -0,0 +1 @@
|
|||
../import-local/fixtures/cli.js
|
|
@ -0,0 +1 @@
|
|||
../js-yaml/bin/js-yaml.js
|
|
@ -0,0 +1 @@
|
|||
../jsesc/bin/jsesc
|
|
@ -0,0 +1 @@
|
|||
../json5/lib/cli.js
|
|
@ -0,0 +1 @@
|
|||
../miller-rabin/bin/miller-rabin
|
|
@ -0,0 +1 @@
|
|||
../mime/cli.js
|
|
@ -0,0 +1 @@
|
|||
../mkdirp/bin/cmd.js
|
|
@ -0,0 +1 @@
|
|||
../multicast-dns/cli.js
|
|
@ -0,0 +1 @@
|
|||
../@babel/parser/bin/babel-parser.js
|
|
@ -0,0 +1 @@
|
|||
../rimraf/bin.js
|
|
@ -0,0 +1 @@
|
|||
../semver/bin/semver.js
|
|
@ -0,0 +1 @@
|
|||
../sha.js/bin.js
|
|
@ -0,0 +1 @@
|
|||
../terser/bin/terser
|
|
@ -0,0 +1 @@
|
|||
../uuid/bin/uuid
|
|
@ -0,0 +1 @@
|
|||
../webpack/bin/webpack.js
|
|
@ -0,0 +1 @@
|
|||
../webpack-cli/bin/cli.js
|
|
@ -0,0 +1 @@
|
|||
../webpack-dev-server/bin/webpack-dev-server.js
|
|
@ -0,0 +1 @@
|
|||
../which/bin/which
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue