mirror of
https://github.com/jimeh/ozu.io.git
synced 2026-02-19 08:06:39 +00:00
Make things pretty
This commit is contained in:
@@ -25,7 +25,7 @@ func (h *APIHandler) Shorten(c *routing.Context) error {
|
||||
return h.respondWithError(c, err)
|
||||
}
|
||||
|
||||
r := makeURLResponse(c, uid, url)
|
||||
r := makeResponse(c, uid, url)
|
||||
return h.respond(c, &r)
|
||||
}
|
||||
|
||||
@@ -37,11 +37,11 @@ func (h *APIHandler) Lookup(c *routing.Context) error {
|
||||
return h.respondWithError(c, err)
|
||||
}
|
||||
|
||||
r := makeURLResponse(c, uid, url)
|
||||
r := makeResponse(c, uid, url)
|
||||
return h.respond(c, &r)
|
||||
}
|
||||
|
||||
func (h *APIHandler) respond(c *routing.Context, r *URLResponse) error {
|
||||
func (h *APIHandler) respond(c *routing.Context, r *Response) error {
|
||||
resp, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -53,10 +53,7 @@ func (h *APIHandler) respond(c *routing.Context, r *URLResponse) error {
|
||||
}
|
||||
|
||||
func (h *APIHandler) respondWithError(c *routing.Context, err error) error {
|
||||
r := ErrorResponse{
|
||||
Error: err.Error(),
|
||||
}
|
||||
|
||||
r := Response{Error: err.Error()}
|
||||
resp, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -22,6 +22,20 @@ func NewHandler(s shortener.Shortener) *Handler {
|
||||
func newHandlerTemplate() *template.Template {
|
||||
t := template.New("base")
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"truncate": func(s string) string {
|
||||
var numRunes = 0
|
||||
for index := range s {
|
||||
numRunes++
|
||||
if numRunes > 50 {
|
||||
return s[:index] + "..."
|
||||
}
|
||||
}
|
||||
return s
|
||||
},
|
||||
}
|
||||
t.Funcs(funcMap)
|
||||
|
||||
files, err := AssetDir("templates")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -45,26 +59,37 @@ type Handler struct {
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
// Index handles requests for root.
|
||||
func (h *Handler) Index(c *routing.Context) error {
|
||||
h.respond(c, "index.html", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotFound returns a 404 error page.
|
||||
func (h *Handler) NotFound(c *routing.Context) error {
|
||||
c.NotFound()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Index handles requests for root.
|
||||
func (h *Handler) Index(c *routing.Context) error {
|
||||
template := "index.html"
|
||||
rawURL := c.FormValue("url")
|
||||
|
||||
if len(rawURL) > 0 {
|
||||
uid, url, err := h.shortener.Shorten(rawURL)
|
||||
if err != nil {
|
||||
return h.respond(c, template, makeErrResponse(err))
|
||||
}
|
||||
|
||||
r := makeResponse(c, uid, url)
|
||||
return h.respond(c, template, r)
|
||||
}
|
||||
|
||||
return h.respond(c, template, nil)
|
||||
}
|
||||
|
||||
// Static returns assets serialized via go-bindata
|
||||
func (h *Handler) Static(c *routing.Context) error {
|
||||
p := string(c.Path())[1:]
|
||||
|
||||
data, err := Asset(p)
|
||||
if err != nil {
|
||||
h.NotFound(c)
|
||||
return nil
|
||||
return h.NotFound(c)
|
||||
}
|
||||
|
||||
info, _ := AssetInfo(p)
|
||||
@@ -83,11 +108,10 @@ func (h *Handler) LookupAndRedirect(c *routing.Context) error {
|
||||
|
||||
url, err := h.shortener.Lookup(uid)
|
||||
if err != nil {
|
||||
h.NotFound(c)
|
||||
return nil
|
||||
return h.NotFound(c)
|
||||
}
|
||||
|
||||
r := makeURLResponse(c, uid, url)
|
||||
r := makeResponse(c, uid, url)
|
||||
|
||||
c.Response.Header.Set("Pragma", "no-cache")
|
||||
c.Response.Header.Set("Expires", "Mon, 01 Jan 1990 00:00:00 GMT")
|
||||
@@ -101,11 +125,11 @@ func (h *Handler) LookupAndRedirect(c *routing.Context) error {
|
||||
c.Response.Header.Set("X-Frame-Options", "SAMEORIGIN")
|
||||
c.Response.Header.Set("Vary", "Accept-Encoding")
|
||||
|
||||
h.respond(c, "redirect.html", r)
|
||||
return nil
|
||||
return h.respond(c, "redirect.html", r)
|
||||
}
|
||||
|
||||
func (h *Handler) respond(c *routing.Context, name string, data interface{}) {
|
||||
func (h *Handler) respond(c *routing.Context, name string, data interface{}) error {
|
||||
c.SetContentType("text/html; charset=UTF-8")
|
||||
h.template.ExecuteTemplate(c, name, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,14 +6,18 @@ import (
|
||||
"github.com/qiangxue/fasthttp-routing"
|
||||
)
|
||||
|
||||
func makeURLResponse(c *routing.Context, uid []byte, url []byte) URLResponse {
|
||||
return URLResponse{
|
||||
func makeResponse(c *routing.Context, uid []byte, url []byte) Response {
|
||||
return Response{
|
||||
UID: string(uid),
|
||||
URL: makeShortURL(c, uid),
|
||||
Target: string(url),
|
||||
}
|
||||
}
|
||||
|
||||
func makeErrResponse(err error) Response {
|
||||
return Response{Error: err.Error()}
|
||||
}
|
||||
|
||||
func makeShortURL(c *routing.Context, uid []byte) string {
|
||||
shortURL := &url.URL{
|
||||
Scheme: "http",
|
||||
|
||||
11
web/response.go
Normal file
11
web/response.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package web
|
||||
|
||||
//go:generate easyjson -all response.go
|
||||
|
||||
// Response contains shortened URL info.
|
||||
type Response struct {
|
||||
UID string `json:"uid"`
|
||||
URL string `json:"url"`
|
||||
Target string `json:"target"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
88
web/response_easyjson.go
Normal file
88
web/response_easyjson.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// AUTOGENERATED FILE: easyjson marshaller/unmarshallers.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
)
|
||||
|
||||
var _ = json.RawMessage{} // suppress unused package warning
|
||||
|
||||
func easyjson_6ff3ac1d_decode_github_com_jimeh_ozu_io_web_Response(in *jlexer.Lexer, out *Response) {
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "uid":
|
||||
out.UID = string(in.String())
|
||||
case "url":
|
||||
out.URL = string(in.String())
|
||||
case "target":
|
||||
out.Target = string(in.String())
|
||||
case "error":
|
||||
out.Error = string(in.String())
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
}
|
||||
func easyjson_6ff3ac1d_encode_github_com_jimeh_ozu_io_web_Response(out *jwriter.Writer, in Response) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"uid\":")
|
||||
out.String(string(in.UID))
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"url\":")
|
||||
out.String(string(in.URL))
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"target\":")
|
||||
out.String(string(in.Target))
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"error\":")
|
||||
out.String(string(in.Error))
|
||||
out.RawByte('}')
|
||||
}
|
||||
func (v Response) MarshalJSON() ([]byte, error) {
|
||||
w := jwriter.Writer{}
|
||||
easyjson_6ff3ac1d_encode_github_com_jimeh_ozu_io_web_Response(&w, v)
|
||||
return w.Buffer.BuildBytes(), w.Error
|
||||
}
|
||||
func (v Response) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson_6ff3ac1d_encode_github_com_jimeh_ozu_io_web_Response(w, v)
|
||||
}
|
||||
func (v *Response) UnmarshalJSON(data []byte) error {
|
||||
r := jlexer.Lexer{Data: data}
|
||||
easyjson_6ff3ac1d_decode_github_com_jimeh_ozu_io_web_Response(&r, v)
|
||||
return r.Error()
|
||||
}
|
||||
func (v *Response) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson_6ff3ac1d_decode_github_com_jimeh_ozu_io_web_Response(l, v)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package web
|
||||
|
||||
//go:generate easyjson -all responses.go
|
||||
|
||||
// URLResponse contains shortened URL info.
|
||||
type URLResponse struct {
|
||||
UID string `json:"uid"`
|
||||
URL string `json:"url"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
// ErrorResponse contains error info.
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// AUTOGENERATED FILE: easyjson marshaller/unmarshallers.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
)
|
||||
|
||||
var _ = json.RawMessage{} // suppress unused package warning
|
||||
|
||||
func easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ErrorResponse(in *jlexer.Lexer, out *ErrorResponse) {
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "error":
|
||||
out.Error = string(in.String())
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
}
|
||||
func easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ErrorResponse(out *jwriter.Writer, in ErrorResponse) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"error\":")
|
||||
out.String(string(in.Error))
|
||||
out.RawByte('}')
|
||||
}
|
||||
func (v ErrorResponse) MarshalJSON() ([]byte, error) {
|
||||
w := jwriter.Writer{}
|
||||
easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ErrorResponse(&w, v)
|
||||
return w.Buffer.BuildBytes(), w.Error
|
||||
}
|
||||
func (v ErrorResponse) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ErrorResponse(w, v)
|
||||
}
|
||||
func (v *ErrorResponse) UnmarshalJSON(data []byte) error {
|
||||
r := jlexer.Lexer{Data: data}
|
||||
easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ErrorResponse(&r, v)
|
||||
return r.Error()
|
||||
}
|
||||
func (v *ErrorResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ErrorResponse(l, v)
|
||||
}
|
||||
func easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_URLResponse(in *jlexer.Lexer, out *URLResponse) {
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "uid":
|
||||
out.UID = string(in.String())
|
||||
case "url":
|
||||
out.URL = string(in.String())
|
||||
case "target":
|
||||
out.Target = string(in.String())
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
}
|
||||
func easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_URLResponse(out *jwriter.Writer, in URLResponse) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"uid\":")
|
||||
out.String(string(in.UID))
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"url\":")
|
||||
out.String(string(in.URL))
|
||||
if !first {
|
||||
out.RawByte(',')
|
||||
}
|
||||
first = false
|
||||
out.RawString("\"target\":")
|
||||
out.String(string(in.Target))
|
||||
out.RawByte('}')
|
||||
}
|
||||
func (v URLResponse) MarshalJSON() ([]byte, error) {
|
||||
w := jwriter.Writer{}
|
||||
easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_URLResponse(&w, v)
|
||||
return w.Buffer.BuildBytes(), w.Error
|
||||
}
|
||||
func (v URLResponse) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_URLResponse(w, v)
|
||||
}
|
||||
func (v *URLResponse) UnmarshalJSON(data []byte) error {
|
||||
r := jlexer.Lexer{Data: data}
|
||||
easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_URLResponse(&r, v)
|
||||
return r.Error()
|
||||
}
|
||||
func (v *URLResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_URLResponse(l, v)
|
||||
}
|
||||
1
web/static/furtive.min.css
vendored
Normal file
1
web/static/furtive.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,3 +1,100 @@
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-family: "Open Sans", Helvetica, Arial, sans-serif;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.world {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
text-align: center;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.world .content {
|
||||
width: 584px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-moz-transform: translate(-50%, -50%);
|
||||
-o-transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
font-family: "Open Sans Condensed", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 64px;
|
||||
height: 70px;
|
||||
margin-top: -25px;
|
||||
}
|
||||
|
||||
.slogan {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.message input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #333;
|
||||
font-family: "Open Sans", Helvetica, Arial, sans-serif;
|
||||
font-size: 21px;
|
||||
font-weight: normal;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message .info {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.form input {
|
||||
font-family: "Open Sans", Helvetica, Arial, sans-serif;
|
||||
font-size: 21px;
|
||||
font-weight: normal;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form input:focus { outline: none; }
|
||||
|
||||
.form button {
|
||||
color: #fff;
|
||||
font-family: "Open Sans", Helvetica, Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder { /* WebKit, Blink, Edge */
|
||||
color: #ccc;
|
||||
}
|
||||
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
|
||||
color: #ccc;
|
||||
opacity: 1;
|
||||
}
|
||||
::-moz-placeholder { /* Mozilla Firefox 19+ */
|
||||
color: #ccc;
|
||||
opacity: 1;
|
||||
}
|
||||
:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ozu.io</title>
|
||||
<meta name="description" content="ozu.io - A Drunken URL Shortener">
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans+Condensed:700|Open+Sans:400italic,400,700" type="text/css">
|
||||
<link rel="stylesheet" href="/static/furtive.min.css">
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
{{template "_head.html"}}
|
||||
Welcome to ozu.io, a drunken URL shortener.
|
||||
<div class="world">
|
||||
<div class="content">
|
||||
<div class="logo-wrapper">
|
||||
<div class="logo">ozu.io</div>
|
||||
<div class="slogan">a drunken URL shortener</div>
|
||||
</div>
|
||||
{{if .URL}}
|
||||
<script type="text/javascript">
|
||||
window.onload = function(){
|
||||
document.getElementById('shortURL').focus();
|
||||
}
|
||||
</script>
|
||||
<div class="message">
|
||||
<input id="shortURL" onfocus="this.select()" readonly="readonly" value="{{.URL}}">
|
||||
<div class="info">
|
||||
is short for <a href="{{.Target | html}}">{{.Target | truncate | html}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Error}}
|
||||
<div class="message error">
|
||||
{{.Error | html}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form">
|
||||
<form>
|
||||
<input type="text" name="url" placeholder="http://">
|
||||
<button type="submit" class="btn--blue">
|
||||
Shorten <i class="fa fa-arrow-right" aria-hidden="true"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "_foot.html"}}
|
||||
|
||||
Reference in New Issue
Block a user