summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/ponzu/main.go9
-rw-r--r--content/item.go3
-rw-r--r--management/editor/editor.go70
-rw-r--r--management/editor/elements.go25
-rw-r--r--management/manager/manager.go84
-rw-r--r--system/admin/handlers.go15
-rw-r--r--system/admin/static/common/js/util.js47
-rw-r--r--system/admin/upload.go54
-rw-r--r--system/api/handlers.go2
9 files changed, 246 insertions, 63 deletions
diff --git a/cmd/ponzu/main.go b/cmd/ponzu/main.go
index ee5ae1f..2e32552 100644
--- a/cmd/ponzu/main.go
+++ b/cmd/ponzu/main.go
@@ -36,11 +36,11 @@ new <directory>:
generate, gen, g <type>:
- Generate a content type file with boilerplate code to implement
- the editor.Editable interface. Must be given one (1) parameter of
- the name of the type for the new content.
+ Generate a content type file with boilerplate code to implement
+ the editor.Editable interface. Must be given one (1) parameter of
+ the name of the type for the new content.
- Example:
+ Example:
$ ponzu gen review
@@ -67,6 +67,7 @@ generate, gen, g <type>:
database, since the first process to open it recieves a lock. If you intend
to run the Admin and API on separate processes, you must call them with the
'ponzu' command independently.
+
`
var (
diff --git a/content/item.go b/content/item.go
index e96a34e..7b25b14 100644
--- a/content/item.go
+++ b/content/item.go
@@ -4,5 +4,6 @@ package content
type Item struct {
ID int `json:"id"`
Slug string `json:"slug"`
- Timestamp string `json:"timestamp"`
+ Timestamp int64 `json:"timestamp"`
+ Updated int64 `json:"updated"`
}
diff --git a/management/editor/editor.go b/management/editor/editor.go
index f8b1970..dc6f181 100644
--- a/management/editor/editor.go
+++ b/management/editor/editor.go
@@ -43,6 +43,58 @@ func Form(post Editable, fields ...Field) ([]byte, error) {
// content items with Item embedded have some default fields we need to render
editor.ViewBuf.Write([]byte(`<tr class="col s4 default-fields"><td>`))
+
+ publishTime := `
+<div class="row">
+ <div class="input-field col s6">
+ <label class="active">MM</label>
+ <select class="month __ponzu browser-default">
+ <option value="1">Jan - 01</option>
+ <option value="2">Feb - 02</option>
+ <option value="3">Mar - 03</option>
+ <option value="4">Apr - 04</option>
+ <option value="5">May - 05</option>
+ <option value="6">Jun - 06</option>
+ <option value="7">Jul - 07</option>
+ <option value="8">Aug - 08</option>
+ <option value="9">Sep - 09</option>
+ <option value="10">Oct - 10</option>
+ <option value="11">Nov - 11</option>
+ <option value="12">Dec - 12</option>
+ </select>
+ </div>
+ <div class="input-field col s2">
+ <label class="active">DD</label>
+ <input value="" class="day __ponzu" maxlength="2" type="text" placeholder="DD" />
+ </div>
+ <div class="input-field col s4">
+ <label class="active">YYYY</label>
+ <input value="" class="year __ponzu" maxlength="4" type="text" placeholder="YYYY" />
+ </div>
+</div>
+
+<div class="row">
+ <div class="input-field col s3">
+ <label class="active">HH</label>
+ <input value="" class="hour __ponzu" maxlength="2" type="text" placeholder="HH" />
+ </div>
+ <div class="col s1">:</div>
+ <div class="input-field col s3">
+ <label class="active">MM</label>
+ <input value="" class="minute __ponzu" maxlength="2" type="text" placeholder="MM" />
+ </div>
+ <div class="input-field col s4">
+ <label class="active">Period</label>
+ <select class="period __ponzu browser-default">
+ <option value="AM">AM</option>
+ <option value="PM">PM</option>
+ </select>
+ </div>
+</div>
+ `
+
+ editor.ViewBuf.Write([]byte(publishTime))
+
addPostDefaultFieldsToEditorView(post, editor)
submit := `
@@ -87,12 +139,6 @@ func addFieldToEditorView(e *Editor, f Field) {
func addPostDefaultFieldsToEditorView(p Editable, e *Editor) {
defaults := []Field{
Field{
- View: Input("Timestamp", p, map[string]string{
- "label": "Publish Date",
- "type": "date",
- }),
- },
- Field{
View: Input("Slug", p, map[string]string{
"label": "URL Slug",
"type": "text",
@@ -100,6 +146,18 @@ func addPostDefaultFieldsToEditorView(p Editable, e *Editor) {
"placeholder": "Will be set automatically",
}),
},
+ Field{
+ View: Timestamp("Timestamp", p, map[string]string{
+ "type": "hidden",
+ "class": "timestamp __ponzu",
+ }),
+ },
+ Field{
+ View: Timestamp("Updated", p, map[string]string{
+ "type": "hidden",
+ "class": "updated __ponzu",
+ }),
+ },
}
for _, f := range defaults {
diff --git a/management/editor/elements.go b/management/editor/elements.go
index e8a2fab..390d8df 100644
--- a/management/editor/elements.go
+++ b/management/editor/elements.go
@@ -37,6 +37,31 @@ func Textarea(fieldName string, p interface{}, attrs map[string]string) []byte {
return domElement(e)
}
+// Timestamp returns the []byte of an <input> HTML element with a label.
+// IMPORTANT:
+// The `fieldName` argument will cause a panic if it is not exactly the string
+// form of the struct field that this editor input is representing
+func Timestamp(fieldName string, p interface{}, attrs map[string]string) []byte {
+ var data string
+ val := valueFromStructField(fieldName, p)
+ if val.Int() == 0 {
+ data = ""
+ } else {
+ data = fmt.Sprintf("%d", val.Int())
+ }
+
+ e := &element{
+ TagName: "input",
+ Attrs: attrs,
+ Name: tagNameFromStructField(fieldName, p),
+ label: attrs["label"],
+ data: data,
+ viewBuf: &bytes.Buffer{},
+ }
+
+ return domElementSelfClose(e)
+}
+
// File returns the []byte of a <input type="file"> HTML element with a label.
// IMPORTANT:
// The `fieldName` argument will cause a panic if it is not exactly the string
diff --git a/management/manager/manager.go b/management/manager/manager.go
index c0056ac..a8665ba 100644
--- a/management/manager/manager.go
+++ b/management/manager/manager.go
@@ -16,11 +16,87 @@ const managerHTML = `
{{ .Editor }}
</form>
<script>
- // remove all bad chars from all inputs in the form, except file fields
- $('form input:not([type=file]), form textarea').on('blur', function(e) {
- var val = e.target.value;
- e.target.value = replaceBadChars(val);
+ $(function() {
+ // remove all bad chars from all inputs in the form, except file fields
+ $('form input:not([type=file]), form textarea').on('blur', function(e) {
+ var val = e.target.value;
+ e.target.value = replaceBadChars(val);
+ });
+
+ var updateTimestamp = function(dt, $ts) {
+ var year = dt.year.val(),
+ month = dt.month.val()-1,
+ day = dt.day.val(),
+ hour = dt.hour.val(),
+ minute = dt.minute.val();
+
+ if (dt.period == "PM") {
+ hours = hours + 12;
+ }
+
+ var date = new Date(year, month, day, hour, minute);
+
+ $ts.val(date.getTime());
+ }
+
+ var setDefaultTimeAndDate = function(dt, $ts, $up, unix) {
+ var time = getPartialTime(unix),
+ date = getPartialDate(unix);
+
+ dt.hour.val(time.hh);
+ dt.minute.val(time.mm);
+ dt.period.val(time.pd);
+ dt.year.val(date.yyyy);
+ dt.month.val(date.mm);
+ dt.day.val(date.dd);
+ }
+
+ // set time time and date inputs using the hidden timestamp input.
+ // if it is empty, set it to now and use that value for time and date
+ var publish_time_hh = $('input.__ponzu.hour'),
+ publish_time_mm = $('input.__ponzu.minute'),
+ publish_time_pd = $('select.__ponzu.period'),
+ publish_date_yyyy = $('input.__ponzu.year'),
+ publish_date_mm = $('select.__ponzu.month'),
+ publish_date_dd = $('input.__ponzu.day'),
+ timestamp = $('input.__ponzu.timestamp'),
+ updated = $('input.__ponzu.updated'),
+ getFields = function() {
+ return {
+ hour: publish_time_hh,
+ minute: publish_time_mm,
+ period: publish_time_pd,
+ year: publish_date_yyyy,
+ month: publish_date_mm,
+ day: publish_date_dd
+ }
+ },
+ time;
+
+ if (timestamp.val() !== "") {
+ time = parseInt(timestamp.val());
+ } else {
+ time = (new Date()).getTime();
+ }
+
+ setDefaultTimeAndDate(getFields(), timestamp, updated, time);
+
+ var timeUpdated = false;
+ $('form').on('submit', function(e) {
+ if (timeUpdated === true) {
+ timeUpdated = false;
+ return;
+ }
+
+ e.preventDefault();
+
+ updateTimestamp(getFields(), timestamp);
+
+ timeUpdated = true;
+ $('form').submit();
+ });
});
+
</script>
</div>
`
diff --git a/system/admin/handlers.go b/system/admin/handlers.go
index d492335..edf351d 100644
--- a/system/admin/handlers.go
+++ b/system/admin/handlers.go
@@ -435,21 +435,18 @@ func editHandler(res http.ResponseWriter, req *http.Request) {
cid := req.FormValue("id")
t := req.FormValue("type")
ts := req.FormValue("timestamp")
+ up := req.FormValue("updated")
// create a timestamp if one was not set
- date := make(map[string]int)
if ts == "" {
- now := time.Now()
- date["year"] = now.Year()
- date["month"] = int(now.Month())
- date["day"] = now.Day()
-
- // create timestamp format 'yyyy-mm-dd' and set in PostForm for
- // db insertion
- ts = fmt.Sprintf("%d-%02d-%02d", date["year"], date["month"], date["day"])
+ ts := fmt.Sprintf("%d", time.Now().Unix()*1000)
req.PostForm.Set("timestamp", ts)
}
+ if up == "" {
+ req.PostForm.Set("updated", ts)
+ }
+
urlPaths, err := storeFileUploads(req)
if err != nil {
fmt.Println(err)
diff --git a/system/admin/static/common/js/util.js b/system/admin/static/common/js/util.js
index d45d9e3..7f4c8ab 100644
--- a/system/admin/static/common/js/util.js
+++ b/system/admin/static/common/js/util.js
@@ -20,4 +20,51 @@ function replaceBadChars(text) {
s = s.replace(/[\u02DC\u00A0]/g, " ");
return s;
+}
+
+
+// Returns a local partial time object based on unix timestamp
+function getPartialTime(unix) {
+ var date = new Date(unix);
+ var t = {};
+ var hours = date.getHours();
+ if (hours < 10) {
+ hours = "0" + String(hours);
+ }
+
+ t.hh = hours;
+ if (hours > 12) {
+ t.hh = hours - 12;
+ t.pd = "PM";
+ } else if (hours === 12) {
+ t.pd = "PM";
+ } else if (hours < 12) {
+ t.pd = "AM";
+ }
+
+ var minutes = date.getMinutes();
+ if (minutes < 10) {
+ minutes = "0" + String(minutes);
+ }
+ t.mm = minutes;
+
+ return t;
+}
+
+// Returns a local partial date object based on unix timestamp
+function getPartialDate(unix) {
+ var date = new Date(unix);
+ var d = {};
+
+ d.yyyy = date.getFullYear();
+
+ d.mm = date.getMonth()+1;
+
+ var day = date.getDate();
+ if (day < 10) {
+ day = "0" + String(day);
+ }
+ d.dd = day;
+
+ return d;
} \ No newline at end of file
diff --git a/system/admin/upload.go b/system/admin/upload.go
index a2cde4c..7f2a4fa 100644
--- a/system/admin/upload.go
+++ b/system/admin/upload.go
@@ -7,7 +7,6 @@ import (
"os"
"path/filepath"
"strconv"
- "strings"
"time"
)
@@ -17,45 +16,16 @@ func storeFileUploads(req *http.Request) (map[string]string, error) {
return nil, fmt.Errorf("%s", err)
}
- ts := req.FormValue("timestamp")
+ ts := req.FormValue("timestamp") // timestamp in milliseconds since unix epoch
- // To use for FormValue name:urlPath
- urlPaths := make(map[string]string)
-
- // get ts values individually to use as directory names when storing
- // uploaded images
- date := make(map[string]int)
if ts == "" {
- now := time.Now()
- date["year"] = now.Year()
- date["month"] = int(now.Month())
- date["day"] = now.Day()
-
- // create timestamp format 'yyyy-mm-dd' and set in PostForm for
- // db insertion
- ts = fmt.Sprintf("%d-%02d-%02d", date["year"], date["month"], date["day"])
- req.PostForm.Set("timestamp", ts)
- } else {
- tsParts := strings.Split(ts, "-")
- year, err := strconv.Atoi(tsParts[0])
- if err != nil {
- return nil, fmt.Errorf("%s", err)
- }
+ ts = fmt.Sprintf("%d", time.Now().Unix()*1000) // Unix() returns seconds since unix epoch
+ }
- month, err := strconv.Atoi(tsParts[1])
- if err != nil {
- return nil, fmt.Errorf("%s", err)
- }
+ req.Form.Set("timestamp", ts)
- day, err := strconv.Atoi(tsParts[2])
- if err != nil {
- return nil, fmt.Errorf("%s", err)
- }
-
- date["year"] = year
- date["month"] = month
- date["day"] = day
- }
+ // To use for FormValue name:urlPath
+ urlPaths := make(map[string]string)
// get or create upload directory to save files from request
pwd, err := os.Getwd()
@@ -64,11 +34,17 @@ func storeFileUploads(req *http.Request) (map[string]string, error) {
return nil, err
}
- tsParts := strings.Split(ts, "-")
+ i, err := strconv.ParseInt(ts, 10, 64)
+ if err != nil {
+ return nil, err
+ }
+
+ tm := time.Unix(int64(i/1000), int64(i%1000))
+
urlPathPrefix := "api"
uploadDirName := "uploads"
- uploadDir := filepath.Join(pwd, uploadDirName, tsParts[0], tsParts[1])
+ uploadDir := filepath.Join(pwd, uploadDirName, fmt.Sprintf("%d", tm.Year()), fmt.Sprintf("%d", tm.Month()))
err = os.MkdirAll(uploadDir, os.ModeDir|os.ModePerm)
// loop over all files and save them to disk
@@ -104,7 +80,7 @@ func storeFileUploads(req *http.Request) (map[string]string, error) {
}
// add name:urlPath to req.PostForm to be inserted into db
- urlPath := fmt.Sprintf("/%s/%s/%s/%s/%s", urlPathPrefix, uploadDirName, tsParts[0], tsParts[1], filename)
+ urlPath := fmt.Sprintf("/%s/%s/%d/%d/%s", urlPathPrefix, uploadDirName, tm.Year(), tm.Month(), filename)
urlPaths[name] = urlPath
}
diff --git a/system/api/handlers.go b/system/api/handlers.go
index 5fb79bc..0c9139f 100644
--- a/system/api/handlers.go
+++ b/system/api/handlers.go
@@ -32,6 +32,8 @@ func postsHandler(res http.ResponseWriter, req *http.Request) {
// num := q.Get("num")
// page := q.Get("page")
+ // TODO: inplement time-based ?after=time.Time, ?before=time.Time between=time.Time|time.Time
+
if t == "" {
res.WriteHeader(http.StatusBadRequest)
return