Add Normalize.css

This commit is contained in:
Trevor Slocum 2020-11-21 20:44:22 -08:00
parent 260018cc9d
commit 320c52d436
5 changed files with 473 additions and 19 deletions

0
go.sum Normal file
View file

View file

@ -58,9 +58,6 @@ func main() {
data = gmitohtml.Convert(data, "") data = gmitohtml.Convert(data, "")
data = append([]byte("<!DOCTYPE html>\n<html>\n<body>\n"), data...)
data = append(data, []byte("\n</body>\n</html>")...)
if view { if view {
openBrowser(string(append([]byte("data:text/html,"), []byte(url.PathEscape(string(data)))...))) openBrowser(string(append([]byte("data:text/html,"), []byte(url.PathEscape(string(data)))...)))
return return

361
pkg/gmitohtml/assets.go Normal file
View file

@ -0,0 +1,361 @@
package gmitohtml
var fs = make(inMemoryFS)
func loadAssets() {
fs["/assets/style.css"] = loadFile("style.css", `
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0.67em;
}
/**
* Render the main element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on h1 elements within section and
* article contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
}
h1, h2, h3, h4, h5, h6, h7 {
margin: 0.25em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd em font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
margin: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd em font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent sub and sup elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from fieldset elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* fieldset elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to inherit in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}
`, fs)
}

View file

@ -12,6 +12,7 @@ import (
"net/url" "net/url"
"path" "path"
"strings" "strings"
"sync"
) )
// ErrInvalidURL is the error returned when the URL is invalid. // ErrInvalidURL is the error returned when the URL is invalid.
@ -19,6 +20,8 @@ var ErrInvalidURL = errors.New("invalid URL")
var daemonAddress string var daemonAddress string
var assetLock sync.Mutex
func rewriteURL(u string, loc *url.URL) string { func rewriteURL(u string, loc *url.URL) string {
if daemonAddress != "" { if daemonAddress != "" {
if strings.HasPrefix(u, "gemini://") { if strings.HasPrefix(u, "gemini://") {
@ -41,7 +44,6 @@ func rewriteURL(u string, loc *url.URL) string {
func Convert(page []byte, u string) []byte { func Convert(page []byte, u string) []byte {
var result []byte var result []byte
var lastPreformatted bool
var preformatted bool var preformatted bool
parsedURL, err := url.Parse(u) parsedURL, err := url.Parse(u)
@ -56,16 +58,12 @@ func Convert(page []byte, u string) []byte {
l := len(line) l := len(line)
if l >= 3 && string(line[0:3]) == "```" { if l >= 3 && string(line[0:3]) == "```" {
preformatted = !preformatted preformatted = !preformatted
continue
}
if preformatted != lastPreformatted {
if preformatted { if preformatted {
result = append(result, []byte("<pre>\n")...) result = append(result, []byte("<pre>\n")...)
} else { } else {
result = append(result, []byte("</pre>\n")...) result = append(result, []byte("</pre>\n")...)
} }
lastPreformatted = preformatted continue
} }
if preformatted { if preformatted {
@ -96,7 +94,7 @@ func Convert(page []byte, u string) []byte {
} }
} }
if heading > 0 { if heading > 0 {
result = append(result, []byte(fmt.Sprintf("<h%d>%s</h%d>", heading, line, heading))...) result = append(result, []byte(fmt.Sprintf("<h%d>%s</h%d>", heading, line[heading:], heading))...)
continue continue
} }
@ -104,11 +102,17 @@ func Convert(page []byte, u string) []byte {
result = append(result, []byte("<br>")...) result = append(result, []byte("<br>")...)
} }
if preformatted {
result = append(result, []byte("</pre>\n")...)
}
result = append([]byte("<!DOCTYPE html>\n<html>\n<head>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<link rel=\"stylesheet\" href=\"/assets/style.css\"></link>\n</head>\n<body>\n"), result...)
result = append(result, []byte("\n</body>\n</html>")...)
return result return result
} }
// Fetch downloads and converts a Gemini page. // Fetch downloads and converts a Gemini page.
func Fetch(u string, clientCertFile string, clientCertKey string) ([]byte, []byte, error) { func fetch(u string, clientCertFile string, clientCertKey string) ([]byte, []byte, error) {
if u == "" { if u == "" {
return nil, nil, ErrInvalidURL return nil, nil, ErrInvalidURL
} }
@ -179,7 +183,7 @@ func handleRequest(writer http.ResponseWriter, request *http.Request) {
return return
} }
header, data, err := Fetch(u.String(), "", "") header, data, err := fetch(u.String(), "", "")
if err != nil { if err != nil {
fmt.Fprintf(writer, "Error: failed to fetch %s: %s", u, err) fmt.Fprintf(writer, "Error: failed to fetch %s: %s", u, err)
return return
@ -194,24 +198,35 @@ func handleRequest(writer http.ResponseWriter, request *http.Request) {
} }
if len(header) > 3 && !bytes.HasPrefix(header[3:], []byte("text/gemini")) { if len(header) > 3 && !bytes.HasPrefix(header[3:], []byte("text/gemini")) {
writer.Header().Set("Content-type", string(header[3:])) writer.Header().Set("Content-Type", string(header[3:]))
} else { } else {
writer.Header().Set("Content-type", "text/html; charset=utf-8") writer.Header().Set("Content-Type", "text/html; charset=utf-8")
} }
writer.Write([]byte("<!DOCTYPE html>\n<html>\n<body>\n"))
writer.Write(data) writer.Write(data)
writer.Write([]byte("\n</body>\n</html>")) }
func handleAssets(writer http.ResponseWriter, request *http.Request) {
assetLock.Lock()
defer assetLock.Unlock()
writer.Header().Set("Cache-Control", "max-age=86400")
http.FileServer(fs).ServeHTTP(writer, request)
} }
// StartDaemon starts the page conversion daemon. // StartDaemon starts the page conversion daemon.
func StartDaemon(address string) error { func StartDaemon(address string) error {
loadAssets()
daemonAddress = address daemonAddress = address
http.HandleFunc("/", handleRequest) handler := http.NewServeMux()
handler.HandleFunc("/assets/style.css", handleAssets)
handler.HandleFunc("/", handleRequest)
go func() { go func() {
log.Fatal(http.ListenAndServe(address, nil)) log.Fatal(http.ListenAndServe(address, handler))
}() }()
return nil return nil
} }

81
pkg/gmitohtml/fs.go Normal file
View file

@ -0,0 +1,81 @@
package gmitohtml
import (
"io"
"net/http"
"os"
"time"
)
var modTime = time.Now()
type inMemoryFS map[string]http.File
func (fs inMemoryFS) Open(name string) (http.File, error) {
if f, ok := fs[name]; ok {
f.Seek(0, io.SeekStart)
return f, nil
}
panic("No file")
}
type inMemoryFile struct {
at int64
Name string
data []byte
fs inMemoryFS
}
func loadFile(name string, val string, fs inMemoryFS) *inMemoryFile {
return &inMemoryFile{at: 0,
Name: name,
data: []byte(val),
fs: fs}
}
func (f *inMemoryFile) Close() error {
return nil
}
func (f *inMemoryFile) Stat() (os.FileInfo, error) {
return &inMemoryFileInfo{f}, nil
}
func (f *inMemoryFile) Readdir(count int) ([]os.FileInfo, error) {
res := make([]os.FileInfo, len(f.fs))
i := 0
for _, file := range f.fs {
res[i], _ = file.Stat()
i++
}
return res, nil
}
func (f *inMemoryFile) Read(b []byte) (int, error) {
i := 0
for f.at < int64(len(f.data)) && i < len(b) {
b[i] = f.data[f.at]
i++
f.at++
}
return i, nil
}
func (f *inMemoryFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
f.at = offset
case 1:
f.at += offset
case 2:
f.at = int64(len(f.data)) + offset
}
return f.at, nil
}
type inMemoryFileInfo struct {
file *inMemoryFile
}
func (s *inMemoryFileInfo) Name() string { return s.file.Name }
func (s *inMemoryFileInfo) Size() int64 { return int64(len(s.file.data)) }
func (s *inMemoryFileInfo) Mode() os.FileMode { return os.ModeTemporary }
func (s *inMemoryFileInfo) ModTime() time.Time { return modTime }
func (s *inMemoryFileInfo) IsDir() bool { return false }
func (s *inMemoryFileInfo) Sys() interface{} { return nil }