@@ -2,6 +2,9 @@ package htmlhouse | |||
import ( | |||
"database/sql" | |||
"fmt" | |||
"html/template" | |||
"io/ioutil" | |||
"net/http" | |||
"github.com/gorilla/mux" | |||
@@ -9,10 +12,11 @@ import ( | |||
) | |||
type app struct { | |||
cfg *config | |||
router *mux.Router | |||
db *sql.DB | |||
session sessionManager | |||
cfg *config | |||
router *mux.Router | |||
db *sql.DB | |||
session sessionManager | |||
templates map[string]*template.Template | |||
} | |||
func newApp() (*app, error) { | |||
@@ -35,6 +39,8 @@ func newApp() (*app, error) { | |||
return app, err | |||
} | |||
app.initTemplates() | |||
app.initRouter() | |||
return app, nil | |||
@@ -49,11 +55,43 @@ func (app *app) initRouter() { | |||
api := app.router.PathPrefix("/⌂/").Subrouter() | |||
api.HandleFunc("/create", app.handler(createHouse)).Methods("POST").Name("create") | |||
api.HandleFunc("/{house:[A-Za-z0-9.-]{8}}", app.handler(renovateHouse)).Methods("POST").Name("update") | |||
app.router.HandleFunc("/", app.handler(getEditor)).Methods("GET").Name("index") | |||
app.router.HandleFunc("/edit/{house:[A-Za-z0-9.-]{8}}.html", app.handler(getEditor)).Methods("GET").Name("edit") | |||
app.router.HandleFunc("/{house:[A-Za-z0-9.-]{8}}.html", app.handler(getHouse)).Methods("GET").Name("get") | |||
app.router.PathPrefix("/").Handler(http.FileServer(http.Dir(app.cfg.StaticDir))) | |||
} | |||
type EditorPage struct { | |||
ID string | |||
Content string | |||
} | |||
func getEditor(app *app, w http.ResponseWriter, r *http.Request) error { | |||
vars := mux.Vars(r) | |||
house := vars["house"] | |||
if house == "" { | |||
defaultPage, err := ioutil.ReadFile(app.cfg.StaticDir + "/default.html") | |||
if err != nil { | |||
fmt.Printf("\n%s\n", err) | |||
defaultPage = []byte("<!DOCTYPE html>\n<html>\n</html>") | |||
} | |||
app.templates["editor"].ExecuteTemplate(w, "editor", &EditorPage{"", string(defaultPage)}) | |||
return nil | |||
} | |||
html, err := getHouseHTML(app, house) | |||
if err != nil { | |||
return err | |||
} | |||
app.templates["editor"].ExecuteTemplate(w, "editor", &EditorPage{house, html}) | |||
return nil | |||
} | |||
type handlerFunc func(app *app, w http.ResponseWriter, r *http.Request) error | |||
func (app *app) handler(h handlerFunc) http.HandlerFunc { | |||
@@ -33,18 +33,58 @@ func createHouse(app *app, w http.ResponseWriter, r *http.Request) error { | |||
return impart.WriteSuccess(w, resUser, http.StatusCreated) | |||
} | |||
func getHouse(app *app, w http.ResponseWriter, r *http.Request) error { | |||
func renovateHouse(app *app, w http.ResponseWriter, r *http.Request) error { | |||
vars := mux.Vars(r) | |||
houseID := vars["house"] | |||
html := r.FormValue("html") | |||
if strings.TrimSpace(html) == "" { | |||
return impart.HTTPError{http.StatusBadRequest, "Supply something to publish."} | |||
} | |||
// Fetch HTML | |||
authHouseID, err := app.session.readToken(r) | |||
if err != nil { | |||
return err | |||
} | |||
if authHouseID != houseID { | |||
return impart.HTTPError{http.StatusUnauthorized, "Bad token for this ⌂ house ⌂."} | |||
} | |||
_, err = app.db.Exec("UPDATE houses SET html = ? WHERE id = ?", html, houseID) | |||
if err != nil { | |||
return err | |||
} | |||
if err = app.session.writeToken(w, houseID); err != nil { | |||
return err | |||
} | |||
resUser := newSessionInfo(houseID) | |||
return impart.WriteSuccess(w, resUser, http.StatusCreated) | |||
} | |||
func getHouseHTML(app *app, houseID string) (string, error) { | |||
var html string | |||
err := app.db.QueryRow("SELECT html FROM houses WHERE id = ?", houseID).Scan(&html) | |||
switch { | |||
case err == sql.ErrNoRows: | |||
return impart.HTTPError{http.StatusNotFound, "Return to sender. Address unknown."} | |||
return "", impart.HTTPError{http.StatusNotFound, "Return to sender. Address unknown."} | |||
case err != nil: | |||
fmt.Printf("Couldn't fetch: %v\n", err) | |||
return "", err | |||
} | |||
return html, nil | |||
} | |||
func getHouse(app *app, w http.ResponseWriter, r *http.Request) error { | |||
vars := mux.Vars(r) | |||
houseID := vars["house"] | |||
// Fetch HTML | |||
html, err := getHouseHTML(app, houseID) | |||
if err != nil { | |||
return err | |||
} | |||
@@ -30,6 +30,12 @@ body { | |||
h1 { | |||
display: inline; | |||
} | |||
a { | |||
margin: 0 0 0 1em; | |||
&:link, &:visited { | |||
color: blue; | |||
} | |||
} | |||
nav { | |||
display: inline; | |||
margin: 0 1em; | |||
@@ -37,7 +43,7 @@ body { | |||
span, a { | |||
margin: 0 0 0 1em; | |||
} | |||
a { | |||
a, a:link, a:visited { | |||
color: #999; | |||
&.current { | |||
color: #666; | |||
@@ -0,0 +1,24 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<title>HTMLhouse</title> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
<style type="text/css"> | |||
body { | |||
font-family: Helvetica, Tahoma, Arial, sans-serif; | |||
font-size: 100%; | |||
margin: 1em; | |||
} | |||
img { | |||
max-width: 100%; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<h1>HTMLhouse</h1> | |||
<p>Welcome!</p> | |||
</body> | |||
</html> |
@@ -0,0 +1,23 @@ | |||
package htmlhouse | |||
import ( | |||
"fmt" | |||
"html/template" | |||
) | |||
const ( | |||
templatesDir = "templates/" | |||
) | |||
func initTemplate(app *app, name string) { | |||
fmt.Printf("Loading %s%s.html\n", templatesDir, name) | |||
app.templates[name] = template.Must(template.New("").ParseFiles(templatesDir + name + ".html")) | |||
} | |||
func (app *app) initTemplates() { | |||
app.templates = map[string]*template.Template{} | |||
// Initialize dynamic pages | |||
initTemplate(app, "editor") | |||
} |
@@ -1,4 +1,4 @@ | |||
<!DOCTYPE HTML> | |||
{{define "editor"}}<!DOCTYPE HTML> | |||
<html> | |||
<head> | |||
@@ -30,32 +30,15 @@ | |||
<header> | |||
<h1>HTMLhouse</h1> | |||
<nav> | |||
{{if .ID}}<a class="home" href="/"><⌂/></a>{{end}} | |||
<a href="/about.html">about</a> | |||
<a href="mailto:hello@html.house">contact</a> | |||
</nav> | |||
<a id="publish" href="#">publish</a> | |||
{{if .ID}}<a href="/{{.ID}}.html">view</a>{{end}} | |||
<a id="publish" href="#">{{if .ID}}update{{else}}publish{{end}}</a> | |||
</header> | |||
<pre id="editor"><!DOCTYPE html> | |||
<html> | |||
<head> | |||
<title>HTMLhouse</title> | |||
<style type="text/css"> | |||
body { | |||
font-family: Helvetica, Tahoma, Arial, sans-serif; | |||
font-size: 100%; | |||
margin: 1em; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<h1>HTMLhouse</h1> | |||
<p>Welcome!</p> | |||
</body> | |||
</html></pre> | |||
<pre id="editor">{{.Content}}</pre> | |||
<div id="preview-wrap"> | |||
<iframe id="preview"></iframe> | |||
@@ -63,8 +46,10 @@ | |||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" type="text/javascript" charset="utf-8"></script> | |||
<script src="/js/ace.js" type="text/javascript" charset="utf-8"></script> | |||
<script src="https://write.as/js/h.js"></script> | |||
<script> | |||
//var $view = window.frames['preview'].document.getElementsByTag("body")[0]; | |||
var typingTimer; | |||
function updatePreview() { | |||
// $view.innerHTML = editor.getSession().getValue(); | |||
$view.contents().find('body').html(editor.getSession().getValue()); | |||
@@ -76,11 +61,40 @@ editor.session.setMode("ace/mode/html"); | |||
editor.getSession().on('change', updatePreview); | |||
updatePreview(); | |||
var publishing = false; | |||
$("#publish").on('click', function(e) { | |||
e.preventDefault(); | |||
$.post("/⌂/create", {html: editor.getSession().getValue()}, function(data, status, xhr) { | |||
if (data.meta.code == 201) { | |||
window.location = '/' + data.data.id + '.html'; | |||
if (publishing) { | |||
return; | |||
} | |||
var token; | |||
var houses = JSON.parse(H.get('neighborhood', '[]')); | |||
for (var i=0; i<houses.length; i++) { | |||
if (houses[i].id == "{{.ID}}") { | |||
token = houses[i].token; | |||
break; | |||
} | |||
} | |||
publishing = true; | |||
$.ajax({ | |||
type: "POST", | |||
url: "/⌂/{{if .ID}}{{.ID}}{{else}}create{{end}}",{{if .ID}} | |||
beforeSend: function (request) { | |||
request.setRequestHeader("Authorization", token); | |||
},{{end}} | |||
data: {html: editor.getSession().getValue()}, | |||
success: function(data, status, xhr) { | |||
publishing = false; | |||
if (data.meta.code == 201) { | |||
var houses = JSON.parse(H.get('neighborhood', '[]')); | |||
houses.push({id: data.data.id, token: xhr.getResponseHeader('Authorization')}); | |||
H.set('neighborhood', JSON.stringify(houses)); | |||
window.location = '/' + data.data.id + '.html'; | |||
} | |||
}, | |||
failure: function(data, status, xhr) { | |||
publishing = false; | |||
} | |||
}); | |||
}); | |||
@@ -95,4 +109,4 @@ $("#publish").on('click', function(e) { | |||
ga('send', 'pageview'); | |||
</script> | |||
</body> | |||
</html> | |||
</html>{{end}} |