Implemented entry creation, pagination. Some restyling and bugfixes.
- Bug fix in ts_api:list_entries/3. Case statement matching on atoms but input is a list (string). - Bug fix in ts_api:put_entry/3. Was expecting the wrong result from ts_entry:new/1. - Bug fix in ts_entry:list/4. Code crashed when the starting offset was greater than the total number of elements. Now returns []. - Fixed ts_json:encode_datetime/1 and ts_json:decode_datetime/1 to handle millisecond values in the datetime string (per ISO standard). - Broke out ``control-links`` style to a top-level class. - Added showdown.js, a JS Markdown processor. Not hooked up to anything yet but intend to display entry notes with Markdown. - Added code for entry pagination. Loads the most recent 20 entries and loads more upon demand in batches of 20. - Fixed bug in login routine that kept the user edit fields from being pre-populated. - Rewrote the loadEntries function to double for new entries and loading more existing entries. - Commented displayEntries. Also refactored into displayNewerEntries, which pushed new entries on to the top of the stack, and displayOlderEntries, which tags them onto the bottom. - Implemented hidden notes field for new entry input. - Implemented new entry creation. - Created a helper function to ISO format a Date object. - Expanded entry template to show control links (edit, show notes, del). - Activated the 'load more entries' button.
This commit is contained in:
parent
c185c8cd81
commit
1b1e31059b
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
db/test/id_counter.DCL
Normal file
BIN
db/test/id_counter.DCL
Normal file
Binary file not shown.
Binary file not shown.
BIN
db/test/ts_entry.DCL
Normal file
BIN
db/test/ts_entry.DCL
Normal file
Binary file not shown.
@ -392,13 +392,13 @@ list_entries(YArg, Username, TimelineId) ->
|
||||
|
||||
% read or default the Start
|
||||
Start = case lists:keyfind("start", 1, QueryData) of
|
||||
{start, StartVal} -> list_to_integer(StartVal);
|
||||
{"start", StartVal} -> list_to_integer(StartVal);
|
||||
false -> 0
|
||||
end,
|
||||
|
||||
% read or default the Length
|
||||
Length = case lists:keyfind("length", 1, QueryData) of
|
||||
{length, LengthVal} ->
|
||||
{"length", LengthVal} ->
|
||||
erlang:min(list_to_integer(LengthVal), 500);
|
||||
false -> 50
|
||||
end,
|
||||
@ -443,7 +443,8 @@ put_entry(YArg, Username, TimelineId) ->
|
||||
|
||||
case ts_entry:new(NewRecord) of
|
||||
% record created
|
||||
ok -> [{status, 201}, make_json_200(YArg, NewRecord)];
|
||||
{ok, CreatedRecord} ->
|
||||
[{status, 201}, make_json_200(YArg, CreatedRecord)];
|
||||
|
||||
% will not create, record exists
|
||||
{error, {record_exists, ExistingRecord}} ->
|
||||
@ -456,7 +457,9 @@ put_entry(YArg, Username, TimelineId) ->
|
||||
|
||||
{content, "application/json", JSONResponse};
|
||||
|
||||
_Error -> make_json_500(YArg)
|
||||
OtherError ->
|
||||
io:format("Could not create entry: ~p", [OtherError]),
|
||||
make_json_500(YArg)
|
||||
end.
|
||||
|
||||
post_entry(YArg, Username, TimelineId, EntryId) ->
|
||||
|
@ -51,7 +51,10 @@ when is_integer(Start) and is_integer(Length) ->
|
||||
|
||||
% return only the range selected.
|
||||
% TODO: can we do this without selecting all entries?
|
||||
lists:sublist(SortedEntries, Start + 1, Length);
|
||||
case length(SortedEntries) > Start of
|
||||
true -> lists:sublist(SortedEntries, Start + 1, Length);
|
||||
false -> []
|
||||
end;
|
||||
|
||||
list({Username, Timeline}, StartDateTime, EndDateTime, OrderFun) ->
|
||||
|
||||
|
@ -40,8 +40,8 @@ record_to_ejson(Record=#ts_entry{}) ->
|
||||
{notes, Record#ts_entry.notes}]}.
|
||||
|
||||
encode_datetime({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||
lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ",
|
||||
[Year, Month, Day, Hour, Minute, Second])).
|
||||
lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B~3.10.0BZ",
|
||||
[Year, Month, Day, Hour, Minute, Second, 000])).
|
||||
|
||||
ejson_to_record(_Empty=#ts_timeline{}, EJSON) ->
|
||||
{struct, Fields} = EJSON,
|
||||
@ -70,8 +70,11 @@ decode_datetime(DateTimeString) ->
|
||||
[YearString, MonthString, DayString] =
|
||||
re:split(DateString, "-", [{return, list}]),
|
||||
|
||||
[HourString, MinuteString, SecondString] =
|
||||
re:split(TimeString, ":", [{return, list}]),
|
||||
[HourString, MinuteString, SecondString] =
|
||||
case re:split(TimeString, "[:\\.]", [{return, list}]) of
|
||||
[HS, MS, SS, _MSS] -> [HS, MS, SS];
|
||||
[HS, MS, SS] -> [HS, MS, SS]
|
||||
end,
|
||||
|
||||
Date = {list_to_integer(YearString), list_to_integer(MonthString),
|
||||
list_to_integer(DayString)},
|
||||
|
@ -43,6 +43,23 @@ body {
|
||||
body {
|
||||
width: 80%; } }
|
||||
|
||||
.control-links {
|
||||
color: #c5c5b9;
|
||||
float: right;
|
||||
display: block;
|
||||
height: 100%;
|
||||
font-weight: bold;
|
||||
font-size: smaller; }
|
||||
.control-links:hover {
|
||||
color: #252d42; }
|
||||
.control-links a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
margin-right: 0.5em; }
|
||||
.control-links a:hover {
|
||||
color: #b34c2b;
|
||||
text-decoration: underline; }
|
||||
|
||||
.bar {
|
||||
font-family: Helvetica, sans-serif;
|
||||
color: #252d42;
|
||||
@ -53,42 +70,39 @@ body {
|
||||
border-bottom-width: 0;
|
||||
padding: 0.1em 1em 0.1em 1em;
|
||||
overflow: hidden; }
|
||||
.bar div.control-links {
|
||||
color: #979681;
|
||||
float: right;
|
||||
display: block;
|
||||
height: 100%;
|
||||
font-weight: bold;
|
||||
font-size: smaller; }
|
||||
.bar div.control-links:hover {
|
||||
color: #252d42; }
|
||||
.bar div.control-links a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
margin-right: 0.5em; }
|
||||
.bar div.control-links a:hover {
|
||||
color: #b34c2b;
|
||||
text-decoration: underline; }
|
||||
|
||||
.last-bar {
|
||||
border-bottom-width: 0.2em;
|
||||
border-color: #979681;
|
||||
border-style: solid;
|
||||
border-width: 0.2em;
|
||||
-moz-border-radius-bottomright: 0.5em;
|
||||
-webkit-border-bottom-right-radius: 0.5em;
|
||||
border-bottom-right-radius: 0.5em;
|
||||
-moz-border-radius-bottomleft: 0.5em;
|
||||
-webkit-border-bottom-left-radius: 0.5em;
|
||||
border-bottom-left-radius: 0.5em; }
|
||||
border-bottom-left-radius: 0.5em;
|
||||
background-color: #e6dec7;
|
||||
padding: 0.1em 1em 0.1em 1em;
|
||||
overflow: hidden; }
|
||||
|
||||
#more-entries a {
|
||||
border: 1px solid #979681;
|
||||
background: #f6f3ea;
|
||||
display: block;
|
||||
color: #626150;
|
||||
text-decoration: none;
|
||||
font-size: smaller;
|
||||
float: left; }
|
||||
#more-entries a:hover {
|
||||
color: #b34c2b; }
|
||||
#more-entries {
|
||||
overflow: hidden; }
|
||||
#more-entries div {
|
||||
float: right;
|
||||
left: -50%;
|
||||
position: relative; }
|
||||
#more-entries div a {
|
||||
position: relative;
|
||||
float: right;
|
||||
left: 50%;
|
||||
border: 1px solid #979681;
|
||||
padding: 0.1em;
|
||||
background: #f6f3ea;
|
||||
color: #626150;
|
||||
text-decoration: none;
|
||||
font-size: smaller; }
|
||||
#more-entries div a:hover {
|
||||
color: #b34c2b; }
|
||||
|
||||
.bar form {
|
||||
border-top: solid 1px #979681;
|
||||
@ -158,17 +172,27 @@ body {
|
||||
width: 100%;
|
||||
float: left; }
|
||||
|
||||
#new-entry form {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
#new-entry input {
|
||||
color: #626150; }
|
||||
#new-entry #add-notes {
|
||||
display: none;
|
||||
padding: 0.5em 0 0.5em 2em; }
|
||||
#new-entry #new-entry-input {
|
||||
margin-right: 1em; }
|
||||
#new-entry {
|
||||
-moz-border-radius-bottomright: 0.5em;
|
||||
-webkit-border-bottom-right-radius: 0.5em;
|
||||
border-bottom-right-radius: 0.5em;
|
||||
-moz-border-radius-bottomleft: 0.5em;
|
||||
-webkit-border-bottom-left-radius: 0.5em;
|
||||
border-bottom-left-radius: 0.5em;
|
||||
border-bottom: solid #979681 0.2em;
|
||||
margin-bottom: 1em; }
|
||||
#new-entry form {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
#new-entry input {
|
||||
color: #626150; }
|
||||
#new-entry #add-notes {
|
||||
display: none;
|
||||
padding: 0.5em 0 0.5em 2em; }
|
||||
#new-entry #new-entry-input {
|
||||
margin-right: 1em;
|
||||
width: 15em; }
|
||||
|
||||
.entry-bar {
|
||||
background-color: #e6dec7;
|
||||
@ -176,9 +200,37 @@ body {
|
||||
border-style: solid;
|
||||
border-width: 0.2em;
|
||||
border-bottom-width: 0;
|
||||
padding: 0; }
|
||||
.entry-bar .details .entry-notes {
|
||||
display: none; }
|
||||
padding: 0.1em 1em 0.1em 1em;
|
||||
overflow: hidden; }
|
||||
.entry-bar .id {
|
||||
float: left;
|
||||
-moz-border-radius: 0.5em;
|
||||
-webkit-border-radius: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
padding: 0 0.3em 0 0.3em;
|
||||
background: #b34c2b;
|
||||
color: #c5c5b9;
|
||||
font-weight: bold;
|
||||
min-width: 2em;
|
||||
text-align: right; }
|
||||
.entry-bar .details {
|
||||
float: left; }
|
||||
.entry-bar .details .entry-mark {
|
||||
padding-left: 0.5em;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
font-family: Helvetica, sans-serif; }
|
||||
.entry-bar .details .entry-notes {
|
||||
display: none;
|
||||
padding-left: 1.5em; }
|
||||
|
||||
.top-entry {
|
||||
-moz-border-radius-topleft: 0.5em;
|
||||
-webkit-border-top-left-radius: 0.5em;
|
||||
border-top-left-radius: 0.5em;
|
||||
-moz-border-radius-topright: 0.5em;
|
||||
-webkit-border-top-right-radius: 0.5em;
|
||||
border-top-right-radius: 0.5em; }
|
||||
|
||||
#login-dialog {
|
||||
font-size: small; }
|
||||
|
@ -52,6 +52,28 @@ body {
|
||||
|
||||
@media all and (max-device-width: 480) { body { width: 80%; }}
|
||||
|
||||
.control-links {
|
||||
|
||||
color: lighten($greyTxt, 40%);
|
||||
float: right;
|
||||
display: block;
|
||||
height: 100%;
|
||||
font-weight: bold;
|
||||
font-size: smaller;
|
||||
|
||||
&:hover { color: $txt; }
|
||||
|
||||
a {
|
||||
color: inherit; //lighten($greyTxt, 20%);
|
||||
text-decoration: none;
|
||||
margin-right: 0.5em;
|
||||
&:hover {
|
||||
color: $obor;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
|
||||
font-family: Helvetica, sans-serif;
|
||||
@ -64,47 +86,44 @@ body {
|
||||
padding: 0.1em 1em 0.1em 1em;
|
||||
overflow: hidden;
|
||||
|
||||
div.control-links {
|
||||
|
||||
color: lighten($greyTxt, 20%);
|
||||
float: right;
|
||||
display: block;
|
||||
height: 100%;
|
||||
font-weight: bold;
|
||||
font-size: smaller;
|
||||
|
||||
&:hover { color: $txt; }
|
||||
|
||||
a {
|
||||
color: inherit; //lighten($greyTxt, 20%);
|
||||
text-decoration: none;
|
||||
margin-right: 0.5em;
|
||||
&:hover {
|
||||
color: $obor;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.last-bar {
|
||||
border-bottom-width: $iBorWidth;
|
||||
|
||||
border-color: $bbor;
|
||||
border-style: solid;
|
||||
border-width: $iBorWidth;
|
||||
@include rounded2(bottom, right, 0.5em);
|
||||
@include rounded2(bottom, left, 0.5em);
|
||||
|
||||
background-color: $bbg;
|
||||
padding: 0.1em 1em 0.1em 1em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#more-entries a {
|
||||
border: 1px solid $bbor;
|
||||
background: lighten($bbg, 10%);
|
||||
display: block;
|
||||
color: $greyTxt;
|
||||
text-decoration: none;
|
||||
font-size: smaller;
|
||||
float: left;
|
||||
#more-entries {
|
||||
|
||||
&:hover {
|
||||
color: $obor;
|
||||
overflow: hidden;
|
||||
|
||||
div {
|
||||
float: right;
|
||||
left: -50%;
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
float: right;
|
||||
left: 50%;
|
||||
|
||||
border: 1px solid $bbor;
|
||||
padding: 0.1em;
|
||||
background: lighten($bbg, 10%);
|
||||
color: $greyTxt;
|
||||
text-decoration: none;
|
||||
font-size: smaller;
|
||||
|
||||
&:hover { color: $obor; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,9 +144,7 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input.text-input {
|
||||
border: 1px solid $bbor;
|
||||
}
|
||||
input.text-input { border: 1px solid $bbor; }
|
||||
|
||||
.form-col {
|
||||
overflow: hidden;
|
||||
@ -205,6 +222,12 @@ body {
|
||||
|
||||
#new-entry {
|
||||
|
||||
@include rounded2(bottom, right, 0.5em);
|
||||
@include rounded2(bottom, left, 0.5em);
|
||||
border-bottom: solid $bbor $iBorWidth;
|
||||
|
||||
margin-bottom: 1em;
|
||||
|
||||
form {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
@ -218,7 +241,10 @@ body {
|
||||
padding: 0.5em 0 0.5em 2em;
|
||||
}
|
||||
|
||||
#new-entry-input { margin-right: 1em; }
|
||||
#new-entry-input {
|
||||
margin-right: 1em;
|
||||
width: 15em;
|
||||
}
|
||||
}
|
||||
|
||||
.entry-bar {
|
||||
@ -228,20 +254,43 @@ body {
|
||||
border-style: solid;
|
||||
border-width: $iBorWidth;
|
||||
border-bottom-width: 0;
|
||||
padding: 0;
|
||||
padding: 0.1em 1em 0.1em 1em;
|
||||
overflow: hidden;
|
||||
|
||||
.id {
|
||||
float: left;
|
||||
@include rounded(0.5em);
|
||||
padding: 0 0.3em 0 0.3em;
|
||||
background: $obor;
|
||||
color: lighten($greyTxt, 40%);
|
||||
font-weight: bold;
|
||||
min-width: 2em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.details {
|
||||
|
||||
.entry-mark { }
|
||||
float: left;
|
||||
|
||||
.entry-mark {
|
||||
padding-left: 0.5em;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
font-family: Helvetica, sans-serif;
|
||||
}
|
||||
.entry-notes {
|
||||
display: none;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-entry {
|
||||
|
||||
@include rounded2(top, left, 0.5em);
|
||||
@include rounded2(top, right, 0.5em);
|
||||
}
|
||||
|
||||
#login-dialog {
|
||||
font-size: small;
|
||||
|
||||
|
1361
www/js/showdown.js
Normal file
1361
www/js/showdown.js
Normal file
File diff suppressed because it is too large
Load Diff
155
www/js/ts.js
155
www/js/ts.js
@ -4,11 +4,17 @@ var activeTimeline = [];
|
||||
var entries = [];
|
||||
|
||||
var newEntryBar;
|
||||
var moreEntriesbar;
|
||||
|
||||
var currentEntryOffset = 0;
|
||||
var loadLength = 20;
|
||||
|
||||
/* Setup after the document is ready for manipulation. */
|
||||
$(document).ready(function(){
|
||||
|
||||
// looked up once and remembered
|
||||
newEntryBar = $("#new-entry");
|
||||
moreEntriesBar = $("#more-entries");
|
||||
|
||||
// wire the login dialog using jQuery UI
|
||||
$("#login-dialog").dialog({
|
||||
@ -92,9 +98,8 @@ function loadUser(username) {
|
||||
$("#username").text("- " + user.username);
|
||||
|
||||
// pre-populate the editable user-info fields
|
||||
// TODO: not working
|
||||
$("#fullname-input").text(user.name);
|
||||
$("#email-input").text(user.email);
|
||||
$("#fullname-input").val(user.name);
|
||||
$("#email-input").val(user.email);
|
||||
|
||||
// set the active timeline to the first in the list
|
||||
// TODO: implement a mechanism to remember the last used timeline
|
||||
@ -109,7 +114,7 @@ function loadUser(username) {
|
||||
// choices
|
||||
|
||||
// load the entries for this timeline
|
||||
loadEntries(user, activeTimeline)
|
||||
loadEntries(user, activeTimeline, "new")
|
||||
},
|
||||
|
||||
error: function(jqXHR, textStatus, error) {
|
||||
@ -120,19 +125,31 @@ function loadUser(username) {
|
||||
});
|
||||
}
|
||||
|
||||
/* Read the first 50 entries for a timeline. */
|
||||
function loadEntries(user, timeline) {
|
||||
/* Read entries for a timeline. */
|
||||
function loadEntries(user, timeline, order) {
|
||||
|
||||
// call the API list_entries function via AJAX
|
||||
$.ajax({
|
||||
url: "/ts_api/entries/" + user.username + "/" + timeline.timeline_id + "?order=desc",
|
||||
url: "/ts_api/entries/" + user.username + "/"
|
||||
+ timeline.timeline_id + "?order=asc&start="
|
||||
+ currentEntryOffset + "&length=" + loadLength,
|
||||
type: "GET",
|
||||
|
||||
success: function(data, textStatus, jqXHR) {
|
||||
entries = data.entries;
|
||||
|
||||
// push the entries onto the page
|
||||
displayEntries(entries)
|
||||
|
||||
if (order == "new") {
|
||||
// reverse the list so that the oldest are first
|
||||
entries.reverse()
|
||||
|
||||
// push the entries onto the page
|
||||
displayNewerEntries(entries)
|
||||
} else {
|
||||
displayOlderEntries(entries)
|
||||
}
|
||||
|
||||
// update our current offset
|
||||
currentEntryOffset += loadLength;
|
||||
},
|
||||
|
||||
error: function(jqXHR, textStatus, error) {
|
||||
@ -141,11 +158,50 @@ function loadEntries(user, timeline) {
|
||||
});
|
||||
}
|
||||
|
||||
/* Push the entries onto the top of the entry display. */
|
||||
function displayEntries(entries) {
|
||||
/* Push the entries onto the top of the entry display. This function assumes
|
||||
* that the entries are sorted by id, ascending (0, 1, 2). Each new entry goes
|
||||
* on top, pushing down all existing entries. So the first entry in the
|
||||
* argument list ends up at the bottom of the new entries. */
|
||||
function displayNewerEntries(entries) {
|
||||
|
||||
// for each entry
|
||||
_.each(entries, function(entry) {
|
||||
newEntryBar.after(ich.entry(entry));
|
||||
|
||||
// remove the existing top-entry designation
|
||||
$(".top-entry").removeClass("top-entry");
|
||||
|
||||
// create the new entry from the entry template (see ICanHas.js)
|
||||
var entryElem = ich.entry(entry);
|
||||
|
||||
// mark the new entry as the current top-entry
|
||||
entryElem.addClass("top-entry");
|
||||
|
||||
// push the entry on after the new-entry bar (on top of existing
|
||||
// entries).
|
||||
newEntryBar.after(entryElem);
|
||||
|
||||
// TODO: animate? If so, may need to pause after each to allow
|
||||
// some animation to take place. Going for a continuous push-down
|
||||
// stack effect.
|
||||
});
|
||||
}
|
||||
|
||||
/* Push the entries onto the bottom of the entry display. The function assumes
|
||||
* that the entries are sorted by id, descending (2, 1, 0). Each new entry goes
|
||||
* at the bottom of the stack of entries, extending the stack downwards. So the
|
||||
* last entry in the list is at the very bottom of the stack. */
|
||||
function displayOlderEntries(entries) {
|
||||
|
||||
// for each entry
|
||||
_.each(entries, function(entry) {
|
||||
|
||||
// create the new entry from the template (ICanHas.js)
|
||||
var entryElem = ich.entry(entry);
|
||||
|
||||
// place the entry on top of the bottom of the page
|
||||
moreEntriesBar.before(entryElem);
|
||||
|
||||
// perhaps animate, see comments at the end of displayNewerEntries()
|
||||
});
|
||||
}
|
||||
|
||||
@ -185,8 +241,75 @@ function updateTimeline(event) {
|
||||
/* Show/hide the add notes panel. */
|
||||
function showNewNotes(event) { $("#add-notes").slideToggle("slow"); }
|
||||
|
||||
/* Create a new entry based on the user's input in the new-entry panel. */
|
||||
function newEntry(event) {
|
||||
alert("TODO: create entry vi AJAX");
|
||||
function toggleEntryNotes(event, entryId) {
|
||||
var selector = "#" + entryId + " .entry-notes";
|
||||
$(selector).slideToggle("slow");
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function editEntry(event, entryId) {
|
||||
var selector = "#" + entryId;
|
||||
alert("TODO: implement edit entry. Called for '" + selector + "'");
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/* Create a new entry based on the user's input in the new-entry panel. */
|
||||
function newEntry(event) {
|
||||
|
||||
var mark = $("#new-entry-input").val();
|
||||
var notes = $("#new-notes-input").val();
|
||||
var timestamp = getUTCTimestamp(); // timestamp is handled on client.
|
||||
// This is important. It means that the timestamps are completely in the
|
||||
// hands of the user. Timestamps from this service can not be considered
|
||||
// secure. This service is not a punch-card, but a time-tracker, voluntary
|
||||
// and editable by the user.
|
||||
|
||||
var payload = JSON.stringify(
|
||||
{ mark: mark, notes: notes, timestamp: timestamp });
|
||||
|
||||
$.ajax({url: "/ts_api/entries/" + user.username
|
||||
+ "/" + activeTimeline.timeline_id,
|
||||
type: "PUT",
|
||||
data: payload,
|
||||
|
||||
error: function(jqXHR, textStatus, error) {
|
||||
// TODO: better error handling.
|
||||
alert("Error creating entry: \n" + jqXHR.responseText); },
|
||||
|
||||
success: function(data, textStatus, jqXHR) {
|
||||
// TODO: check that data.status == "ok"
|
||||
|
||||
// grab the entry data
|
||||
var newEntry = data.entry;
|
||||
|
||||
// push the entry on top of the stack
|
||||
displayNewerEntries([newEntry]);
|
||||
|
||||
// clear the input
|
||||
$("#new-entry-input").val("");
|
||||
$("#new-notes-input").val("");
|
||||
}});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/* Delete an entry. */
|
||||
function deleteEntry(event, entryId) {
|
||||
|
||||
}
|
||||
|
||||
/* Generate a UTC timestamp string in ISO format.
|
||||
* Javascript 1.8.5 has Date.toJSON(), which is equivalent to this function,
|
||||
* but is only supported in Firefox 4. */
|
||||
function getUTCTimestamp() {
|
||||
var d = new Date();
|
||||
|
||||
function pad(n){return n<10 ? '0'+n : n}
|
||||
|
||||
return d.getUTCFullYear()+'-'
|
||||
+ pad(d.getUTCMonth()+1)+'-'
|
||||
+ pad(d.getUTCDate())+'T'
|
||||
+ pad(d.getUTCHours())+':'
|
||||
+ pad(d.getUTCMinutes())+':'
|
||||
+ pad(d.getUTCSeconds())+'Z';
|
||||
}
|
||||
|
@ -5,20 +5,30 @@
|
||||
<title>TimeStamper - Simple Time Tracking</title>
|
||||
<link rel="stylesheet" media="screen" href="/css/dot-luv/jquery-ui-1.8.10.custom.css" type="text/css"/>
|
||||
<link rel="stylesheet" media="screen" href="/css/ts-screen.css" type="text/css"/>
|
||||
<!-- Needed for IE, but I'm not going to support IE with this tool. -->
|
||||
<!--<script type="text/javascript" src="/js/json2.js"></script>-->
|
||||
<script type="text/javascript" src="/js/jquery-1.5.min.js"></script>
|
||||
<script type="text/javascript" src="/js/jquery-ui-1.8.10.custom.min.js"></script>
|
||||
<script type="text/javascript" src="/js/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="/js/ICanHaz.min.js"></script>
|
||||
<script type="text/javascript" src="/js/ICanHaz.js"></script>
|
||||
<script type="text/javascript" src="/js/ts.js"></script>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
|
||||
<script id="entry" type="text/html">
|
||||
<div class="entry-bar" id="entry-{{entry_id}}">
|
||||
<span class="id">{{entry_id}}.</span>
|
||||
<span class="details">
|
||||
<span class="entry-mark">{{mark}}</span>
|
||||
<span class="entry-notes">{{notes}}</span>
|
||||
</span>
|
||||
<span class="id">{{entry_id}}</span>
|
||||
<div class="details">
|
||||
<div class="entry-mark">{{mark}}</div>
|
||||
<div class="entry-notes">{{notes}}</div>
|
||||
</div>
|
||||
<div class="control-links">
|
||||
<a onclick="toggleEntryNotes(event, 'entry-{{entry_id}}')"
|
||||
href="#">show notes</a>
|
||||
<a onclick="editEntry(event, 'entry-{{entry_id}}')"
|
||||
href="#">edit</a>
|
||||
<a onclick="deleteEntry(event, 'entry-{{entry_id}}')"
|
||||
href="#">del</a>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
</head>
|
||||
@ -122,8 +132,10 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="more-entries" class="last-bar">
|
||||
<a href="#" onclick="loadMoreEntries();">load more entries</a>
|
||||
<div id="more-entries" class="last-bar top-entry">
|
||||
<div>
|
||||
<a href="#" onclick="loadEntries(user, activeTimeline, 'old');event.preventDefault()">load more entries</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="login-dialog" title="Login">
|
||||
|
Loading…
x
Reference in New Issue
Block a user