diff options
-rw-r--r-- | cmd/ponzu/main.go | 9 | ||||
-rw-r--r-- | content/item.go | 3 | ||||
-rw-r--r-- | management/editor/editor.go | 70 | ||||
-rw-r--r-- | management/editor/elements.go | 25 | ||||
-rw-r--r-- | management/manager/manager.go | 84 | ||||
-rw-r--r-- | system/admin/handlers.go | 15 | ||||
-rw-r--r-- | system/admin/static/common/js/util.js | 47 | ||||
-rw-r--r-- | system/admin/upload.go | 54 | ||||
-rw-r--r-- | system/api/handlers.go | 2 |
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 |