Merge branch 'client-redesign'

Conflicts:
	yaws.dev.conf
This commit is contained in:
Jonathan Bernard 2011-06-07 08:38:09 -05:00
commit bc364b2ebd
74 changed files with 5126 additions and 2511 deletions

View File

@ -3,22 +3,23 @@ BEAMS = $(MODS:src/%.erl=ebin/%.beam)
TEST_MODS = $(wildcard test/*.erl)
TEST_BEAMS = $(TEST_MODS:test/%.erl=test/%.beam)
TS_ROOT=/usr/lib/yaws/jdb-labs/timestamper
CWD = `pwd`
default: compile
all : compile test
compile : $(BEAMS)
compile : init $(BEAMS)
compile-test : $(TEST_BEAMS)
compile-test : init $(TEST_BEAMS)
test : start-test-server run-test stop-test-server
test-shell : compile compile-test
test-shell : compile compile-test config-yaws-dev
@echo Starting an interactive YAWS shell with test paths loaded.
@yaws -i --pa test --id test_inst
run-test : compile compile-test
run-test : compile compile-test config-yaws-dev
@erl -pa ./ebin -pa ./test -run timestamper_api_tests test -run init stop -noshell
start-test-server :

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
db/test/ts_entry.DCL Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
doc/features.todo.txt Normal file
View File

@ -0,0 +1,4 @@
- Switch to local storage if unable to reach the server, sync when server is
available.
- Provide full-text search on timestamp marks and notes. Use Lucene in a
seperate process? Build our own Erlang indexing code?

4
doc/issues/0000tn4.rst Normal file
View File

@ -0,0 +1,4 @@
Refactor models and views.
==========================
Try to find the behavior that is common to mobile and desktop versions.

View File

@ -0,0 +1,2 @@
Add UI for note taking.
=======================

View File

@ -0,0 +1,9 @@
Add Markdown converter for notes.
=================================
Brief description.
========= ==========
Created: 2011-05-15
Resolved: 2011-05-15
========= ==========

View File

@ -0,0 +1,11 @@
Duration mis-set on new entries.
================================
Fix the duration bug when adding new events. Need to set the nextModel for
the previously 'current' timestamp and set the nextModel of the new timestamp
to 'null'
========= ==========
Created: 2011-05-15
Resolved: 2011-05-15
========= ==========

View File

@ -0,0 +1,10 @@
Generate day seperators.
========================
When generating EventViews in the EventListView, we need to automatically
create and insert day seperators (see prototype).
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,10 @@
Fix UI for tasks with a duration a day or longer.
=================================================
Tasks that are extremely long-running can overflow the space set aside for
the *Duration* column.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,14 @@
Fix user menu UI.
=================
UI for user menu does not work.
Resolution
----------
UI menu changed to be displayed to the right of the username, in the empty black space vailable.
========= ==========
Created: 2011-05-15
Resolved: 2011-06-01
========= ==========

View File

@ -0,0 +1,9 @@
Fix timeline menu UI.
=====================
UI for timeline menu does not work.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,9 @@
Implement timeline selection.
=============================
Allow the user to change timelines.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,15 @@
Create UI for timeline creation.
================================
There should be a way through the interface for a user to create a new
timeline.
Resolution
----------
Abstracted the login dialog CSS to support other dialogs of the same look and feel. Used this to create a dialog for creating new timelines.
========= ==========
Created: 2011-05-15
Resolved: 2011-06-01
========= ==========

View File

@ -0,0 +1,9 @@
Implement a date-picker for start time.
=======================================
Use a custom jQuery UI theme?
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,18 @@
Implement UI for entry re-order when chronological order changes.
=================================================================
The UI should re-order the EntryList display when the chronological of the entries
changes based on user input.
Two ways to do this spring to mind:
1. ``slideUp`` the EntryView at it's original position, find the new position,
move it in the DOM and ``slideDown`` the element into view.
2. Detach the element from the list, position it absolutely, animate it to it's new
absolute position (based on the position of its new neighbors) and re-insert it
into the list.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,10 @@
Create a subtle alternating background for EntryViews
=====================================================
The goal is to visually tie together elements in the same row, and subtley
distinquish each row from its neighbors.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,9 @@
Create tooltip/some help system.
================================
Need some way to make actions discoverable and easy to understand.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,10 @@
Create a real-time tick-tock for the current entry duration.
============================================================
Have some visible indication that the display is being updated as time passes.
Blink the field, or just the units.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,10 @@
Automatic code highlighting.
============================
Look at content in ``<pre>`` and ``<code>`` blocks and see if we can highlight it.
Planning to use Highlight.js to do this.
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,9 @@
Create new timeline button in timeline menu.
============================================
Need a button to trigger the new timeline dialog.
========= ==========
Created: 2011-06-01
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,7 @@
Duration of next previous entries are not updated when a timestamp is updated.
==============================================================================
========= ==========
Created: 2011-06-01
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,7 @@
Implement timeline creation.
============================
========= ==========
Created: 2011-05-15
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1 @@
Prototype out mobile workflow.

34
doc/model.txt Normal file
View File

@ -0,0 +1,34 @@
Data
----
UserModel
TimelineModel
TimelineListModel
EntryListModel
EntryModel
Views
-----
EntryView
NewEntryInput
TimelineListView
TimelineView
UserView
Data Dependencies
-----------------
UserModel: none
TimelineModel: none
TimelineListModel: UserModel
EntryModel: none
EntryListModel: TimelineModel
View Dependencies
-----------------
UserView: UserModel
TimelineView: TimelineModel, UserView
TimelineListView: TimelineListModel, UserView
EntryView: EntryModel, EntryListView
EntryListView: EntryListModel, TimelineView

BIN
doc/model.xcf Normal file

Binary file not shown.

29
doc/todo.rst Normal file
View File

@ -0,0 +1,29 @@
Current
=======
Upcoming
========
- Generate day seperators
- Fix UI for tasks with a duration a day or longer
- Fix hover UI for user menu
- Fix hover UI for timeline menu
- Test (implement?) timeline selection
- Add UI for timeline creation
- Change the UI for editing the start-time. Use a date-picker (custom jQuery
UI theme?)
- Fix UI for timestamp edits which change the order of events in the timeline.
- Create a light, alternating background for entries
- Add hover-enabled icons for editing entries/showing notes
- Create tooltips.
- Create a realtime tick-tock for the duration of the current item.
- Mobile version of the app.
- Refactor code, seperate out reusable bits for mobile version.
- Automatic code-highlighting (Highlight.js)
Done
====
- Add UI for note-taking
- Add Markdown converter for notes.
- Fix the duration bug when adding new events. Need to set the nextModel for
the previously 'current' timestamp and set the nextModel of the new timestamp
to 'null'

View File

@ -3,7 +3,7 @@
start() ->
ok = application:load(mnesia),
ok = application:set_env(mnesia, dir, "/home/jdbernard/projects/timestamper/web-app/db/test"),
ok = application:set_env(mnesia, dir, "/home/jdbernard/projects/jdb-labs/timestamper/web-app/db/test"),
ok = mnesia:start(),
error_logger:info_report("TimeStampter app started."),
ok.

View File

@ -34,19 +34,18 @@ out(YArg) ->
dispatch_request(YArg, _Session, []) -> make_json_404(YArg, [{see_docs, "/ts_api_doc"}]);
dispatch_request(YArg, Session, [H|T]) ->
Param = path_element_to_atom(H),
case {Session, Param} of
{_, login} -> do_login(YArg);
{_, logout} -> do_logout(YArg);
case {Session, H} of
{_, "login"} -> do_login(YArg);
{_, "logout"} -> do_logout(YArg);
{not_logged_in, _} -> make_json_401(YArg);
{session_expired, _} -> make_json_401(YArg, [{status, "session expired"}]);
{"not_logged_in", _} -> make_json_401(YArg);
{"session_expired", _} -> make_json_401(YArg, [{error, "session expired"}]);
{_S, app} -> dispatch_app(YArg, Session, T);
{_S, users} -> dispatch_user(YArg, Session, T);
{_S, timelines} -> dispatch_timeline(YArg, Session, T);
{_S, entries} -> dispatch_entry(YArg, Session, T);
{_S, "app"} -> dispatch_app(YArg, Session, T);
{_S, "users"} -> dispatch_user(YArg, Session, T);
{_S, "timelines"} -> dispatch_timeline(YArg, Session, T);
{_S, "entries"} -> dispatch_entry(YArg, Session, T);
{_S, _Other} -> make_json_404(YArg, [{see_docs, "/ts_api_doc/"}])
end.
@ -59,7 +58,7 @@ dispatch_app(YArg, Session, Params) ->
{'GET', ["user_summary", UsernameStr]} ->
case {Session#ts_api_session.username,
path_element_to_atom(UsernameStr)} of
UsernameStr} of
{Username, Username} -> get_user_summary(YArg, Username);
_ -> make_json_401(YArg)
@ -75,10 +74,9 @@ dispatch_app(YArg, Session, Params) ->
% -------- Dispatch for /user -------- %
dispatch_user(YArg, Session, []) ->
dispatch_user(YArg, Session, [atom_to_list(Session#ts_api_session.username)]);
dispatch_user(YArg, Session, [Session#ts_api_session.username]);
dispatch_user(YArg, Session, [H]) ->
Username = path_element_to_atom(H),
dispatch_user(YArg, Session, [Username]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
% compare to the logged-in user
@ -97,8 +95,7 @@ dispatch_user(YArg, Session, [H]) ->
dispatch_timeline(YArg, _Session, []) ->
make_json_404(YArg, [{see_docs, "/ts_api_doc/timelines.html"}]);
dispatch_timeline(YArg, Session, [UrlUsername|_T] = PathElements) ->
Username = path_element_to_atom(UrlUsername),
dispatch_timeline(YArg, Session, [Username|_T] = PathElements) ->
case Session#ts_api_session.username of
Username -> dispatch_timeline(YArg, PathElements);
@ -106,8 +103,7 @@ dispatch_timeline(YArg, Session, [UrlUsername|_T] = PathElements) ->
end.
% just username, list timelines
dispatch_timeline(YArg, [UrlUsername]) ->
Username = path_element_to_atom(UrlUsername),
dispatch_timeline(YArg, [Username]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
case HTTPMethod of
@ -115,9 +111,7 @@ dispatch_timeline(YArg, [UrlUsername]) ->
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/timelines.html"}])
end;
dispatch_timeline(YArg, [UrlUsername, UrlTimelineId]) ->
Username = path_element_to_atom(UrlUsername),
TimelineId = path_element_to_atom(UrlTimelineId),
dispatch_timeline(YArg, [Username, TimelineId]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
case HTTPMethod of
@ -136,34 +130,29 @@ dispatch_timeline(YArg, _Other) ->
dispatch_entry(YArg, _Session, []) ->
make_json_404(YArg, [{see_docs, "/ts_aip_doc/entries.html"}]);
dispatch_entry(YArg, Session, [UrlUsername|_T] = PathElements) ->
Username = path_element_to_atom(UrlUsername),
dispatch_entry(YArg, Session, [Username|_T] = PathElements) ->
case Session#ts_api_session.username of
Username -> dispatch_entry(YArg, PathElements);
_Other -> make_json_404(YArg, [{see_docs, "/ts_api_doc/entries.html"}])
end.
dispatch_entry(YArg, [UrlUsername, UrlTimelineId]) ->
Username = path_element_to_atom(UrlUsername),
TimelineId = path_element_to_atom(UrlTimelineId),
dispatch_entry(YArg, [Username, TimelineId]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
case HTTPMethod of
'GET' -> list_entries(YArg, Username, TimelineId);
'PUT' -> put_entry(YArg, Username, TimelineId);
'POST' -> post_entry(YArg, Username, TimelineId);
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/entries.html"}])
end;
dispatch_entry(YArg, [UrlUsername, UrlTimelineId, UrlEntryId]) ->
Username = path_element_to_atom(UrlUsername),
TimelineId = path_element_to_atom(UrlTimelineId),
dispatch_entry(YArg, [Username, TimelineId, UrlEntryId]) ->
EntryId = list_to_integer(UrlEntryId), % TODO: catch non-numbers
HTTPMethod = (YArg#arg.req)#http_request.method,
case HTTPMethod of
'GET' -> get_entry(YArg, Username, TimelineId, EntryId);
'POST' -> post_entry(YArg, Username, TimelineId, EntryId);
'PUT' -> put_entry(YArg, Username, TimelineId, EntryId);
'DELETE' -> delete_entry(YArg, Username, TimelineId, EntryId);
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/entries.html"}])
end;
@ -184,8 +173,7 @@ do_login(YArg) ->
lists:keyfind(password, 1, Fields)} of
% username and password found
{{username, UnameField}, {password, Password}} ->
Username = list_to_atom(UnameField),
{{username, Username}, {password, Password}} ->
% check the uname, password
case ts_user:check_credentials(Username, Password) of
@ -200,7 +188,7 @@ do_login(YArg) ->
[CookieVal])}}];
% they are not good
false -> make_json_401(YArg, [{status,
false -> make_json_401(YArg, [{error,
"bad username/password combination"}])
end;
@ -224,8 +212,7 @@ get_user_summary(YArg, Username) ->
lists:map(fun ts_json:record_to_ejson/1, Timelines)},
JSONResp = json:encode({struct,
[{status, "ok"},
{user, EJSONUser},
[{user, EJSONUser},
{timelines, EJSONTimelines}
]}),
@ -262,9 +249,7 @@ list_timelines(YArg, Username) ->
EJSONTimelines = {array, lists:map(fun ts_json:record_to_ejson/1, Timelines)},
% create resposne
JSONResponse = json:encode({struct, [
{status, "ok"},
{timelines, EJSONTimelines}]}),
JSONResponse = json:encode(EJSONTimelines),
% return response
{content, "application/json", JSONResponse}.
@ -273,18 +258,22 @@ get_timeline(YArg, Username, TimelineId) ->
% look for timeline
case ts_timeline:lookup(Username, TimelineId) of
% no such timeline, return 404
no_record -> make_json_404(YArg, [{status, "no such timeline"}]);
no_record -> make_json_404(YArg, [{error, "no such timeline"}]);
% return the timeline data
Timeline -> make_json_200(YArg, Timeline)
end.
put_timeline(YArg, Username, TimelineId) ->
post_timeline(YArg, Username, TimelineId) ->
% parse the request body
EJSON = parse_json_body(YArg),
% parse into a Timeline record
TR = ts_json:ejson_to_record(#ts_timeline{}, EJSON),
TR = try ts_json:ejson_to_record(#ts_timeline{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% set username and timeline id
NewRecord = TR#ts_timeline{ref = {Username, TimelineId}},
@ -297,12 +286,7 @@ put_timeline(YArg, Username, TimelineId) ->
% will not create, record exists
{error, {record_exists, ExistingRecord}} ->
EJSONRec = ts_json:record_to_ejson(ExistingRecord),
JSONResponse = json:encode({struct, [
{status, "ignored"},
{timeline, EJSONRec},
{see_docs, "/ts_api_doc/timelines.html#PUT"}
]}),
JSONResponse = json:encode(ts_json:record_to_ejson(ExistingRecord)),
{content, "application/json", JSONResponse};
@ -311,18 +295,22 @@ put_timeline(YArg, Username, TimelineId) ->
make_json_500(YArg, Error)
end.
post_timeline(YArg, Username, TimelineId) ->
put_timeline(YArg, Username, TimelineId) ->
% parse the POST data
EJSON = parse_json_body(YArg),
%{struct, Fields} = EJSON,
% parse into a timeline record
TR = ts_json:ejson_to_record(#ts_timeline{}, EJSON),
TR = try ts_json:ejson_to_record(#ts_timeline{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% not supported right now, would require changing all the entry keys
% check to see if they are changing the timeline id
%NewTimelineId = case lists:keyfind(1, timeline_id, Fields) of
% {timeline_id, Field} -> list_to_atom(Field);
% {timeline_id, Field} -> Field;
% false -> TimelineId end,
% set username and timeline id
@ -332,8 +320,8 @@ post_timeline(YArg, Username, TimelineId) ->
ok -> make_json_200(YArg, NewRecord);
no_record -> make_json_404(YArg,
[{status, "no such timeline"},
{see_docs, "/ts_api_doc/timelines.html#POST"}]);
[{error, "no such timeline"},
{see_docs, "/ts_api_doc/timelines.html#PUT"}]);
Error ->
error_logger:error_report("Unable update timeline: ~p", [Error]),
@ -351,7 +339,7 @@ list_entries(YArg, Username, TimelineId) ->
lists:keyfind("byDate", 1, QueryData)} of
{no_record, _ByDateField} -> make_json_404(
[{status, "no such timeline"},
[{error, "no such timeline"},
{see_docs, "/ts_api_doc/entries.html#LIST"}]);
% listing by date range
@ -385,9 +373,7 @@ list_entries(YArg, Username, TimelineId) ->
EJSONEntries = {array, lists:map(
fun ts_json:record_to_ejson/1, Entries)},
JSONResponse = json:encode({struct, [
{status, "ok"},
{entries, EJSONEntries}]}),
JSONResponse = json:encode(EJSONEntries),
{content, "application/json", JSONResponse};
@ -419,9 +405,7 @@ list_entries(YArg, Username, TimelineId) ->
EJSONEntries = {array, lists:map(
fun ts_json:record_to_ejson/1, Entries)},
JSONResponse = json:encode({struct, [
{status, "ok"},
{entries, EJSONEntries}]}),
JSONResponse = json:encode(EJSONEntries),
{content, "application/json", JSONResponse}
end.
@ -429,18 +413,22 @@ list_entries(YArg, Username, TimelineId) ->
get_entry(YArg, Username, TimelineId, EntryId) ->
case ts_entry:lookup(Username, TimelineId, EntryId) of
% no such entry
no_record -> make_json_404(YArg, [{status, "no such entry"}]);
no_record -> make_json_404(YArg, [{error, "no such entry"}]);
% return the entry data
Entry -> make_json_200(YArg, Entry)
end.
put_entry(YArg, Username, TimelineId) ->
post_entry(YArg, Username, TimelineId) ->
% parse the request body
EJSON = parse_json_body(YArg),
% parse into ts_entry record
ER = ts_json:ejson_to_record(#ts_entry{}, EJSON),
ER = try ts_json:ejson_to_record(#ts_entry{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% set username and timeline id
NewRecord = ER#ts_entry{ref = {Username, TimelineId, undef}},
@ -452,12 +440,7 @@ put_entry(YArg, Username, TimelineId) ->
% will not create, record exists
{error, {record_exists, ExistingRecord}} ->
EJSONRec = ts_json:record_to_ejson(ExistingRecord),
JSONResponse = json:encode({struct, [
{status, "ignored"},
{entry, EJSONRec},
{see_docs, "/ts_api_doc/entries.html#PUT"}
]}),
JSONResponse = json:encode(ts_json:record_to_ejson(ExistingRecord)),
{content, "application/json", JSONResponse};
@ -466,13 +449,17 @@ put_entry(YArg, Username, TimelineId) ->
make_json_500(YArg, OtherError)
end.
post_entry(YArg, Username, TimelineId, EntryId) ->
put_entry(YArg, Username, TimelineId, EntryId) ->
% parse the POST data
EJSON = parse_json_body(YArg),
% parse into ts_entry record
ER = ts_json:ejson_to_record(#ts_entry{}, EJSON),
ER = try ts_json:ejson_to_record(#ts_entry{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% set uername, timeline id, and entry id
NewRecord = ER#ts_entry{ref = {Username, TimelineId, EntryId}},
@ -481,7 +468,7 @@ post_entry(YArg, Username, TimelineId, EntryId) ->
ok -> make_json_200(YArg, NewRecord);
no_record -> make_json_404(YArg,
[{status, "no such entry"}, {see_docs, "/ts_api_doc/entries.html#POST"}]);
[{error, "no such entry"}, {see_docs, "/ts_api_doc/entries.html#POST"}]);
Error ->
error_logger:error_report("TimeStamper: Unable to update entry: ~p", [Error]),
@ -509,10 +496,6 @@ delete_entry(YArg, Username, TimelineId, EntryId) ->
% ======== UTIL METHODS ======== %
% ============================== %
%% Convert one path element to an atom.
path_element_to_atom(PE) ->
list_to_atom(re:replace(PE, "\\s", "_", [{return, list}])).
parse_json_body(YArg) ->
case catch json:decode([], binary_to_list(YArg#arg.clidata)) of
{done, {ok, EJSON}, _} -> EJSON;
@ -524,17 +507,7 @@ parse_json_body(YArg) ->
%% Create a JSON 200 response.
make_json_200(_YArg, Record) ->
EJSONRecord = ts_json:record_to_ejson(Record),
Tag = case element(1, Record) of
ts_user -> user;
ts_timeline -> timeline;
ts_entry -> entry
end,
JSONResponse = json:encode({struct, [
{status, "ok"},
{Tag, EJSONRecord}
]}),
JSONResponse = json:encode(ts_json:record_to_ejson(Record)),
{content, "application/json", JSONResponse}.
make_json_400(YArg) -> make_json_400(YArg, []).
@ -574,19 +547,19 @@ make_json_405(YArg) -> make_json_405(YArg, []).
make_json_405(YArg, Fields) ->
% add default status if not provided
F1 = case lists:keyfind(status, 1, Fields) of
false -> Fields ++ [{status, method_not_allowed}];
false -> Fields ++ [{status, "method not allowed"}];
_Else -> Fields
end,
% add the path they requested
F2 = F1 ++ [{path, (YArg#arg.req)#http_request.path}],
% F2 = F1 ++ [{path, io_lib:format("~s", [(YArg#arg.req)#http_request.path])}],
[{status, 405}, {content, "application/json", json:encode({struct, F2})}].
[{status, 405}, {content, "application/json", json:encode({struct, F1})}].
make_json_500(_YArg, Error) ->
EJSON = {struct, [
{status, "internal server error"},
{error, io_lib:format("~p", [Error])}]},
{error, io_lib:format("~s", [Error])}]},
[{status, 500}, {content, "application/json", json:encode(EJSON)}].
make_json_500(_YArg) ->

View File

@ -4,8 +4,8 @@
pwd_salt,
name,
email,
join_date%,
% ext_data % other extensible data
join_date,
ext_data = [] % other extensible data
}).
% ts_user.ext_data can be:

View File

@ -7,10 +7,11 @@ encode_record(Record) -> lists:flatten(json:encode(record_to_ejson(Record))).
record_to_ejson(Record=#ts_user{}) ->
{struct, [
{username, atom_to_list(Record#ts_user.username)},
{id, Record#ts_user.username},
{name, Record#ts_user.name},
{email, Record#ts_user.email},
{join_date, encode_datetime(Record#ts_user.join_date)}]};
{join_date, encode_datetime(Record#ts_user.join_date)},
{ext_data, Record#ts_user.ext_data}]};
record_to_ejson(Record=#ts_timeline{}) ->
% pull out the username and timeline id
@ -18,8 +19,8 @@ record_to_ejson(Record=#ts_timeline{}) ->
% create the EJSON struct
{struct, [
{username, atom_to_list(Username)},
{timeline_id, atom_to_list(TimelineId)},
{user_id, Username},
{id, TimelineId},
{created, encode_datetime(Record#ts_timeline.created)},
{description, Record#ts_timeline.desc}]};
@ -32,15 +33,15 @@ record_to_ejson(Record=#ts_entry{}) ->
% create the EJSON struct
{struct, [
{username, atom_to_list(Username)},
{timeline_id, atom_to_list(TimelineId)},
{entry_id, EntryId},
{user_id, Username},
{timeline_id, TimelineId},
{id, EntryId},
{timestamp, encode_datetime(DateTime)},
{mark, Record#ts_entry.mark},
{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.0B~3.10.0BZ",
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) ->

View File

@ -2,261 +2,361 @@
* author: Jonathan Bernard
* TimeStamper main CSS for screen media types
*/
/*
$obg: #D9CEB2;
$bor: #948C75;
$ibg: #D5DED9;
$txt: #7A6A53;
$bbg: #99B2B7;
*/
/*
$obg: #979681;
$obor: #E6DEC7;
$ibg: #657A8B;
$bbor: #B34C2B;
$bbg: #252D42;
$txt: #E6DEC7;
*/
/* _rounded.scss */
html {
background: url("/img/loving_blu.png") repeat; }
* {
color: inherit; }
body {
width: 50%;
background-color: #657a8b;
color: #222222;
width: 75%;
margin: auto;
padding: 1em;
border: solid #b34c2b;
border-top: 0;
-moz-border-radius-bottomleft: 1em;
-webkit-border-bottom-left-radius: 1em;
border-bottom-left-radius: 1em;
-moz-border-radius-bottomright: 1em;
-webkit-border-bottom-right-radius: 1em;
border-bottom-right-radius: 1em; }
padding: 0; }
@media all and (min-device-width: 480) {
body {
width: 50%; } }
input {
border: solid thin #555555;
-webkit-box-shadow: inset 0px 2px 4px #CCC;
box-shadow: inset 0px 2px 4px #CCC;
margin: 0;
margin-bottom: 0.5em;
padding: 0;
font-family: Cantarell; }
@media all and (max-device-width: 480) {
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;
background-color: #e6dec7;
border-color: #979681;
border-style: solid;
border-width: 0.2em;
border-bottom-width: 0;
padding: 0.1em 1em 0.1em 1em;
overflow: hidden; }
.last-bar {
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;
background-color: #e6dec7;
padding: 0.1em 1em 0.1em 1em;
overflow: hidden; }
#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;
padding: 0.5em 0 0.5em 2em;
overflow: hidden; }
.bar form label {
color: #626150;
display: block;
overflow: hidden; }
.bar form label span {
float: left;
width: 6em;
padding-top: 0.1em; }
.bar form input.text-input {
border: 1px solid #979681; }
.bar form .form-col {
overflow: hidden;
float: left;
padding-right: 2em; }
.bar form .form-col input.text-input {
width: 10em; }
.bar form div.form-submit {
float: left;
width: 100%;
overflow: hidden;
padding: 0.5em 2em 0.5em 2em;
position: relative; }
.bar form div.form-submit div {
position: relative;
float: right;
left: -50%; }
.bar form div.form-submit div input {
position: relative;
left: 50%; }
.bar form div.form-submit input, .bar form input.form-submit {
border: 1px solid #979681;
background: #f6f3ea; }
#user {
-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; }
#user .control-links {
padding-top: 0.6em; }
#user #fullname, #user #username {
font-weight: bold;
font-size: x-large;
float: left; }
#user #username {
padding-left: 0.2em;
color: #626150; }
#user #change-pwd {
display: none; }
#timeline #timeline-name, #timeline #timeline-desc {
font-weight: bold; }
#timeline #timeline-desc {
color: #626150; }
#timeline .control-links {
padding-top: 0.2em; }
#user-info, #timeline-info {
display: none;
width: 100%;
float: left; }
#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;
#top {
background: #222222;
color: #eeeeee;
margin: 0;
opacity: 1;
padding: 0.5em 0;
padding: 0.5rem 0;
position: fixed;
top: 0px;
width: 75%;
z-index: 1; }
#top * {
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; }
#top #fade-bar {
background: url("img/fade.png") repeat-x;
height: 32px;
width: 100%; }
.entry-bar {
background-color: #e6dec7;
border-color: #979681;
border-style: solid;
border-width: 0.2em;
border-bottom-width: 0;
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;
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; }
.entry-bar .entry-edit {
#timeline {
border-bottom: thin solid #eeeeee;
font-family: Arvo;
font-size: 1.5em;
padding: 0 2em;
padding: 0 2rem; }
#timeline .timeline-desc {
display: inline-block;
width: 70%; }
#timeline .timeline-desc-input {
width: 70%; }
#timeline .timeline-id {
display: inline-block; }
#timeline .timeline-desc-input, #timeline .timeline-id-input {
font-family: inherit;
font-size: inherit;
color: #222222;
display: none; }
.entry-bar .entry-edit .id {
width: 2em;
padding: 0.2em 0.5em 0.2em 0.5em; }
.entry-bar .entry-edit .entry-notes {
padding: 0; }
#timeline.edit-id .timeline-id-input {
display: inline-block; }
#timeline.edit-id .timeline-id {
display: none; }
#timeline.edit-desc .timeline-desc-input {
display: inline-block; }
#timeline.edit-desc .timeline-desc {
display: none; }
#timeline .drop-menu {
text-align: right;
display: inline-block;
width: 29%; }
#timeline .drop-menu .drop-menu-items {
text-align: right;
right: 0;
width: 172.41%; }
#timeline .drop-menu .drop-menu-items .new-timeline-link, #timeline .drop-menu .drop-menu-items .timeline-link {
padding-left: 0.5em;
font-size: medium; }
#timeline .drop-menu .drop-menu-items .new-timeline-link img, #timeline .drop-menu .drop-menu-items .timeline-link img {
position: relative;
top: 4px;
left: -4px; }
.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; }
.dialog {
background: white;
background: rgba(255, 255, 255, 0.5);
color: #eeeeee;
margin: 0;
padding: 0;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100em;
z-index: 10; }
.dialog * {
margin: 0;
padding: 0; }
.dialog div.container {
background: #222222;
border-radius: 10px;
font-family: Cantarell;
margin-left: auto;
margin-right: auto;
margin-top: 4em;
padding: 1em;
width: 20em; }
.dialog div.container h2 {
border-bottom: thin solid #eeeeee;
font-family: Arvo;
padding-bottom: 0.5em;
margin-bottom: 0.5em; }
.dialog div.container label {
display: inline-block;
width: 6em; }
.dialog div.container input {
color: #222222; }
.dialog div.container .button-panel {
margin-top: 0.5em;
overflow: hidden; }
.dialog div.container .button-panel .validate-tips {
font-size: 1em; }
.dialog div.container .button-panel .dialog-button {
float: right;
padding-left: 1em;
font-family: Arvo;
font-size: 1.2em; }
.dialog div.container .button-panel .dialog-button a {
color: #eeeeee; }
#login-dialog {
font-size: small; }
#login-dialog input.text {
margin-bottom: 1em;
width: 95%;
padding: 0.4em; }
#login-dialog form fieldset {
#login.dialog {
background: white;
opacity: 1; }
/*#login {
background: white;
color: $lightTxt;
margin: 0;
padding: 0;
border: 0;
margin: 0; }
#login-dialog .validate-tips {
opacity: 1;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100em;
z-index: 10;
* {
margin: 0;
padding: 0;
}
div.container {
background: $darkBg;
border-radius: 10px;
font-family: Cantarell;
margin-left: auto;
margin-right: auto;
margin-top: 4em;
padding: 1em;
width: 20em;
h2 {
border-bottom: thin solid $lightBg;
font-family: Arvo;
padding-bottom: 0.5em;
margin-bottom: 0.5em;
}
label {
display: inline-block;
width: 6em;
}
input {
color: $darkTxt;
}
#login-button {
text-align: right;
margin-top: 0.5em;
font-family: Arvo;
font-size: 1.2em;
.validate-tips { font-size: 1em; }
a { color: $lightBg; }
}
}
}*/
#user {
font-family: "Josefin Sans";
margin-top: -0.3em;
padding: 0 2em;
margin-top: -0.3rem;
padding: 0 2rem;
width: 100%; }
#user .fullname, #user .username {
display: inline-block;
font-size: larger; }
#user .fullname-input {
font-family: inherit;
font-size: inherit;
color: #222222;
display: none; }
#user.edit-fullname .fullname-input {
display: inline-block; }
#user.edit-fullname .fullname {
display: none; }
#user .user-menu {
display: inline-block; }
#user .user-menu .user-menu-items {
list-style: none;
display: none; }
#user .user-menu .user-menu-items li {
display: inline-block;
padding-left: 0.5em; }
#user .user-menu .user-menu-items a {
text-decoration: none; }
#user .user-menu .user-menu-items a:hover {
text-decoration: underline; }
#user .user-menu:hover .user-menu-items, #user .user-menu-items:hover {
display: inline-block; }
#ui-dialog-title-login-dialog, .ui-dialog-buttonset {
font-size: medium; }
#entry-list {
margin: 6em 0 0 0;
margin: 6rem 0 0 0;
padding-bottom: 1em 0 0 0;
padding-bottom: 1rem 0 0 0; }
#entry-list .day-seperator {
background: #cccccc;
color: #222222;
font-family: Cantarell;
font-weight: bold;
margin: 1em 0 0 0;
margin: 1rem 0 0 0;
padding: 0 2em;
padding: 0 2rem; }
#entry-list .day-seperator * {
margin: 0;
padding: 0; }
#entry-list .day-seperator h4, #entry-list .day-seperator h5 {
display: inline-block; }
#entry-list .day-seperator h5 {
color: #667; }
#entry-list #new-entry {
margin: 0.5em 0 0 0;
padding: 0 2em;
margin: 0.5rem 0 0 0;
padding: 0 2rem; }
#entry-list #new-entry * {
margin: 0;
padding: 0; }
#entry-list .timestamp, #entry-list .timestamp-input, #entry-list .duration {
text-align: right;
width: 14%; }
#entry-list .mark, #entry-list .mark-input {
width: 70%; }
#entry-list .entry {
font-family: Cantarell;
padding: 0 2em;
padding: 0 2rem; }
#entry-list .entry div {
display: inline-block; }
#entry-list .entry .mark {
margin: 0;
padding: 0;
position: relative; }
#entry-list .entry .mark * {
margin: 0;
padding: 0; }
#entry-list .entry .mark img.expand-entry, #entry-list .entry .mark img.collapse-entry {
display: none;
left: -20px;
position: absolute;
top: 6px; }
#entry-list .entry:hover .mark img.expand-entry, #entry-list .entry.show-notes img.collapse-entry {
display: inline; }
#entry-list .entry .mark-input, #entry-list .entry .timestamp-input, #entry-list .entry.show-notes:hover img.expand-entry {
display: none; }
#entry-list .entry .notes {
display: none;
font-family: Cantarell;
font-size: small;
margin: 0;
padding: 0 0 0 1em; }
#entry-list .entry .notes :first-child {
margin-top: 0; }
#entry-list .entry .notes .notes-input, #entry-list .entry .notes pre, #entry-list .entry .notes code {
font-family: 'Anonymous Pro'; }
#entry-list .entry .notes * {
width: 100%; }
#entry-list .entry.edit-mark .mark-input {
display: inline-block; }
#entry-list .entry.edit-mark .mark {
display: none; }
#entry-list .entry.edit-timestamp .timestamp-input {
display: inline-block; }
#entry-list .entry.edit-timestamp .timestamp {
display: none; }
#entry-list .entry .notes-input {
display: none; }
#entry-list .entry.edit-notes .notes-input {
display: block; }
#entry-list .entry.edit-notes .notes-text {
display: none; }
.signup {
.drop-menu {
margin: 0;
padding: 0;
position: relative; }
.drop-menu * {
margin: 0;
padding: 0; }
.drop-menu .drop-menu-items {
display: none;
list-style: none;
position: absolute; }
.drop-menu .drop-menu-items li {
display: inline-block;
padding-left: 0.5em; }
.drop-menu:hover .drop-menu-items, .drop-menu .drop-menu-items:hover {
display: block; }
.drop-menu a {
display: inline-block;
text-decoration: none; }
.drop-menu a:hover {
text-decoration: underline; }
.footer {
background: #222222;
color: #eeeeee;
font-family: Bentham;
margin: 0;
padding: 1em 0;
padding: 1rem 0;
text-align: center;
width: 100%; }
.footer * {
margin: 0;
padding: 0; }
.footer a {
color: white;
text-decoration: none; }
.footer a:hover {
text-decoration: underline; }
.logo {
font-family: Bentham;
text-decoration: overline underline;
color: inherit; }
.hidden {
display: none; }
#signup-checkbox {
display: inline; }

View File

@ -3,328 +3,474 @@
* TimeStamper main CSS for screen media types
*/
/*
$obg: #D9CEB2;
$bor: #948C75;
$ibg: #D5DED9;
$txt: #7A6A53;
$bbg: #99B2B7;
*/
/*
$obg: #979681;
$obor: #E6DEC7;
$ibg: #657A8B;
$bbor: #B34C2B;
$bbg: #252D42;
$txt: #E6DEC7;
*/
$obg: #252D42;
$obor: #B34C2B;
$ibg: #657A8B;
$bbor: #979681;
$bbg: #E6DEC7;
$txt: #252D42;
$greyTxt: darken($bbor, 20%);
$iBorWidth: 0.2em;
@import "rounded";
html {
//background-color: $obg;
background: url('/img/loving_blu.png') repeat;
$darkTxt: #222;
$lightTxt: #eee;
$darkBg: #222;
$lightBg: #eee;
$medBg: #CCC;
* {
color: inherit;
}
body {
width: 50%;
background-color: $ibg;
color: $darkTxt;
width: 75%;
margin: auto;
padding: 1em;
border: solid $obor;
border-top: 0;
@include rounded2(bottom, left, 1em);
@include rounded2(bottom, right, 1em);
padding: 0;
}
@media all and (min-device-width: 480) { body { width: 50%; }}
@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;
}
}
input {
border: solid thin lighten($darkTxt, 20%);
-webkit-box-shadow: inset 0px 2px 4px #CCC;
box-shadow: inset 0px 2px 4px #CCC;
margin: 0;
margin-bottom: 0.5em; // IE fix
padding: 0;
font-family: Cantarell;
}
.bar {
#top {
background: $darkBg;
color: $lightTxt;
margin: 0;
opacity: 1;
padding: 0.5em 0; // IE Fix
padding: 0.5rem 0;
position: fixed;
top: 0px;
width: 75%;
z-index: 1;
font-family: Helvetica, sans-serif;
color: $txt;
background-color: $bbg;
border-color: $bbor;
border-style: solid;
border-width: $iBorWidth;
border-bottom-width: 0;
padding: 0.1em 1em 0.1em 1em;
overflow: hidden;
}
.last-bar {
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 {
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; }
}
}
}
.bar form {
border-top: solid 1px $bbor;
padding: 0.5em 0 0.5em 2em;
overflow: hidden;
label {
span {
float: left;
width: 6em;
padding-top: 0.1em;
}
color: $greyTxt;
display: block;
overflow: hidden;
* {
margin: 0;
padding: 0;
}
input.text-input { border: 1px solid $bbor; }
.form-col {
overflow: hidden;
float: left;
padding-right: 2em;
input.text-input { width: 10em; }
}
div.form-submit {
float: left;
#fade-bar {
background: url('img/fade.png') repeat-x;
height: 32px;
width: 100%;
overflow: hidden;
padding: 0.5em 2em 0.5em 2em;
position: relative;
div {
position: relative;
float: right;
left: -50%;
input {
position: relative;
left: 50%;
}
}
}
div.form-submit input, input.form-submit {
border: 1px solid $bbor;
background: lighten($bbg, 10%);
}
}
#user {
@include rounded2(top, left, 0.5em);
@include rounded2(top, right, 0.5em);
.control-links { padding-top: 0.6em; }
#fullname, #username {
font-weight: bold;
font-size: x-large;
float: left;
}
#username {
padding-left: 0.2em;
color: $greyTxt;
}
#change-pwd { display: none; }
#user-info .form-submit {
}
}
#timeline {
#timeline-name, #timeline-desc { font-weight: bold; }
border-bottom: thin solid $lightBg;
font-family: Arvo;
font-size: 1.5em;
padding: 0 2em;
padding: 0 2rem;
#timeline-desc { color: $greyTxt; }
.timeline-desc {
display: inline-block;
width: 70%;
}
.control-links { padding-top: 0.2em; }
.timeline-desc-input { width: 70% }
.timeline-id { display: inline-block; }
.timeline-desc-input, .timeline-id-input {
font-family: inherit;
font-size: inherit;
color: $darkBg;
display: none;
}
&.edit-id {
.timeline-id-input { display: inline-block; }
.timeline-id { display: none; }
}
&.edit-desc {
.timeline-desc-input { display: inline-block; }
.timeline-desc { display: none; }
}
.drop-menu {
text-align: right;
display: inline-block;
width: 29%;
.drop-menu-items {
text-align: right;
right: 0;
width: 172.41%;
.new-timeline-link, .timeline-link {
padding-left: 0.5em;
font-size: medium;
img {
position: relative;
top: 4px;
left: -4px;
}
}
}
}
}
#user-info, #timeline-info {
display: none;
.dialog {
background: white;
background: rgba(255, 255, 255, 0.5);
color: $lightTxt;
margin: 0;
padding: 0;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
float: left;
height: 100em;
z-index: 10;
* {
margin: 0;
padding: 0;
}
div.container {
background: $darkBg;
border-radius: 10px;
font-family: Cantarell;
margin-left: auto;
margin-right: auto;
margin-top: 4em;
padding: 1em;
width: 20em;
h2 {
border-bottom: thin solid $lightBg;
font-family: Arvo;
padding-bottom: 0.5em;
margin-bottom: 0.5em;
}
label {
display: inline-block;
width: 6em;
}
input {
color: $darkTxt;
}
.button-panel {
margin-top: 0.5em;
overflow: hidden;
.validate-tips { font-size: 1em; }
.dialog-button {
float: right;
padding-left: 1em;
font-family: Arvo;
font-size: 1.2em;
a { color: $lightBg; }
}
}
}
}
#new-entry {
#login.dialog {
background: white;
opacity: 1;
}
@include rounded2(bottom, right, 0.5em);
@include rounded2(bottom, left, 0.5em);
border-bottom: solid $bbor $iBorWidth;
/*#login {
margin-bottom: 1em;
background: white;
color: $lightTxt;
margin: 0;
padding: 0;
opacity: 1;
form {
border: 0;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100em;
z-index: 10;
* {
margin: 0;
padding: 0;
}
div.container {
background: $darkBg;
border-radius: 10px;
font-family: Cantarell;
margin-left: auto;
margin-right: auto;
margin-top: 4em;
padding: 1em;
width: 20em;
h2 {
border-bottom: thin solid $lightBg;
font-family: Arvo;
padding-bottom: 0.5em;
margin-bottom: 0.5em;
}
label {
display: inline-block;
width: 6em;
}
input {
color: $darkTxt;
}
#login-button {
text-align: right;
margin-top: 0.5em;
font-family: Arvo;
font-size: 1.2em;
.validate-tips { font-size: 1em; }
a { color: $lightBg; }
}
}
}*/
#user {
font-family: "Josefin Sans";
margin-top: -0.3em; // IE fix
padding: 0 2em;
margin-top: -0.3rem;
padding: 0 2rem;
width: 100%;
.fullname, .username {
display: inline-block;
font-size: larger;
}
.fullname-input {
font-family: inherit;
font-size: inherit;
color: $darkBg;
display: none;
}
&.edit-fullname{
.fullname-input { display: inline-block; }
.fullname { display: none; }
}
.user-menu { display: inline-block; }
.user-menu .user-menu-items {
list-style: none;
display: none;
li {
display: inline-block;
padding-left: 0.5em;
}
a { text-decoration: none; }
a:hover { text-decoration: underline; }
}
.user-menu:hover .user-menu-items, .user-menu-items:hover { display: inline-block; }
}
#entry-list {
margin: 6em 0 0 0;
margin: 6rem 0 0 0;
padding-bottom: 1em 0 0 0;
padding-bottom: 1rem 0 0 0;
.day-seperator {
background: $medBg;
color: $darkBg;
font-family: Cantarell;
font-weight: bold;
margin: 1em 0 0 0;
margin: 1rem 0 0 0;
padding: 0 2em;
padding: 0 2rem;
* {
margin: 0;
padding: 0;
}
h4, h5 { display: inline-block; }
h5 { color: #667; }
}
#new-entry {
margin: 0.5em 0 0 0;
padding: 0 2em;
margin: 0.5rem 0 0 0;
padding: 0 2rem;
* {
margin: 0;
padding: 0;
}
}
.timestamp, .timestamp-input, .duration {
text-align: right;
width: 14%;
}
.mark, .mark-input { width: 70%; }
.entry {
font-family: Cantarell;
padding: 0 2em;
padding: 0 2rem;
div { display: inline-block; }
.mark {
margin: 0;
padding: 0;
position: relative;
* {
margin: 0;
padding: 0;
}
img.expand-entry, img.collapse-entry {
display: none;
left: -20px;
position: absolute;
top: 6px;
}
}
&:hover .mark img.expand-entry, &.show-notes img.collapse-entry { display: inline; }
.mark-input, .timestamp-input,
&.show-notes:hover img.expand-entry { display: none; }
.notes {
display: none;
font-family: Cantarell;
font-size: small;
margin: 0;
padding: 0 0 0 1em;
:first-child { margin-top: 0; }
.notes-input, pre, code { font-family: 'Anonymous Pro'; }
}
.notes * { width: 100%; }
&.edit-mark {
.mark-input { display: inline-block; }
.mark { display: none; }
}
&.edit-timestamp {
.timestamp-input { display: inline-block; }
.timestamp { display: none; }
}
.notes-input { display: none; }
&.edit-notes .notes-input { display: block; }
&.edit-notes .notes-text { display: none; }
}
}
.drop-menu {
margin: 0;
padding: 0;
position: relative;
* {
margin: 0;
padding: 0;
}
input { color: $greyTxt; }
#add-notes {
.drop-menu-items {
display: none;
padding: 0.5em 0 0.5em 2em;
}
list-style: none;
position: absolute;
#new-entry-input {
margin-right: 1em;
width: 15em;
}
}
.entry-bar {
background-color: $bbg;
border-color: $bbor;
border-style: solid;
border-width: $iBorWidth;
border-bottom-width: 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;
width: 2em;
text-align: right;
}
.details {
float: left;
.entry-mark {
li {
display: inline-block;
padding-left: 0.5em;
font-size: medium;
font-weight: bold;
font-family: Helvetica, sans-serif;
}
.entry-notes {
display: none;
padding-left: 1.5em;
}
}
.entry-edit {
display: none;
&:hover .drop-menu-items, .drop-menu-items:hover { display: block; }
.id {
width: 2em;
padding: 0.2em 0.5em 0.2em 0.5em;
}
a {
display: inline-block;
text-decoration: none;
.entry-notes {
padding: 0;
&:hover {
text-decoration: underline;
}
}
}
.top-entry {
.footer {
@include rounded2(top, left, 0.5em);
@include rounded2(top, right, 0.5em);
}
background: $darkBg;
color: $lightTxt;
font-family: Bentham;
margin: 0;
padding: 1em 0;
padding: 1rem 0;
text-align: center;
width: 100%;
#login-dialog {
font-size: small;
//label, input { display: block }
input.text {
margin-bottom: 1em;
width: 95%;
padding: 0.4em;
* {
margin: 0;
padding: 0;
}
form fieldset {
padding: 0; border: 0; margin: 0;
a {
color: lighten($lightTxt, 20%);
text-decoration: none;
&:hover { text-decoration: underline; }
}
.validate-tips { display: none; }
}
#ui-dialog-title-login-dialog, .ui-dialog-buttonset {
font-size: medium;
.logo {
font-family: Bentham;
text-decoration: overline underline;
color: inherit;
}
.signup { display: none; }
#signup-checkbox { display: inline; }
.hidden { display: none; }

BIN
www/img/br_down_icon_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
www/img/br_down_icon_24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
www/img/br_down_icon_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
www/img/br_down_icon_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
www/img/br_up_icon_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
www/img/br_up_icon_24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
www/img/br_up_icon_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
www/img/br_up_icon_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
www/img/pencil_icon&48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
www/img/pencil_icon_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
www/img/pencil_icon_24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
www/img/pencil_icon_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,11 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<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 href='http://fonts.googleapis.com/css?family=Anonymous+Pro|Arvo|Bentham|Cantarell|Josefin+Sans' rel='stylesheet' 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. -->
<!-- Needed for IE, but I'm not sure if I'm going to support IE with this tool. -->
<!--<script type="text/javascript" src="/js/json2.js"></script>-->
<!-- PROD -->
<!--
@ -13,195 +12,156 @@
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/jquery-ui.min.js"></script>
-->
<!-- DEV -->
<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/jquery-1.5.js"></script>
<script type="text/javascript" src="/js/underscore-min.js"></script>
<script type="text/javascript" src="/js/showdown.js"></script>
<script type="text/javascript" src="/js/underscore.js"></script>
<script type="text/javascript" src="/js/ICanHaz.js"></script>
<script type="text/javascript" src="/js/backbone.js"></script>
<script type="text/javascript" src="/js/ts.js"></script>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script type="text/javascript">
<erl>
out(YArg) ->
Session = ts_api_session:get_session(YArg),
case Session of not_logged_in -> {html, "//not logged in"}; session_expired -> {html, "//session expired"};
_S ->
Username = element(2, Session),
<script id="entry" type="text/html">
<div class="entry-bar" id="entry-{{entry_id}}">
<div class="entry-display">
<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="$('#entry-{{entry_id}} .entry-display .entry-notes').slideToggle('slow');"
href="#">show notes</a>
<a onclick="toggleEditEntry(event, {{entry_id}})"
href="#">edit</a>
<a onclick="deleteEntry(event, {{entry_id}})"
href="#">del</a>
</div>
</div>
<div class="entry-edit">
<form action="/ts/update-entry.yaws"
onsubmit="updateEntry(event, {{entry_id}})">
<input type="text" id="entry-{{entry_id}}-id-input"
class="id" value="{{entry_id}}"/>
<div class="details">
<input type="text" id="entry-{{entry_id}}-mark-input"
class="entry-mark" value="{{mark}}"/></br>
<textarea id="entry-{{entry_id}}-notes-input"
class="entry-notes" rows="8" cols="40" >{{notes}}</textarea>
</div>
</form>
<div class="control-links">
<a onclick="$('#entry-{{entry_id}} .entry-edit .entry-notes').slideToggle('slow');"
href="#">show notes</a>
<a onclick="updateEntry(event, {{entry_id}})"
href="#">save changes</a>
</div>
</div>
</div>
% get the user
{content, _, UserJSON} = ts_api:get_user(YArg, Username),
UserRecord = ts_user:lookup(Username),
% get the timelines
{content, _, TimelineListJSON} = ts_api:list_timelines(YArg, Username),
% get the selected timeline
SelectedTimeline = case lists:keyfind(
selected_timeline, 1, element(8, UserRecord)) of
false -> ts_timeline:list(Username, 0, 1);
T -> T
end,
% get entries for this timeline
{content, _, EntryListJSON} =
ts_api:list_entries(YArg, Username, SelectedTimeline),
{html, f(
"function bootstrap() {~n"
" var data = {};~n"
" data.user = ~p;~n"
" data.timelines = ~p;~n"
" data.initialTimelineId = ~p;~n"
" data.entries = ~p;~n"
" return data;~n"
"};",
[UserJSON, TimelineListJSON, SelectedTimeline, EntryListJSON])}
end.
</erl>
</script>
<script type="text/html" id="userTemplate">
<div class="fullname">{{name}}</div>
<input class="fullname-input" type="text" value="{{name}}"/></div>
<div class="user-menu">
<div class="username"> - {{id}}</div>
<ul class="user-menu-items">
<li><a href="#">logout</a></li>
<li><a href="#">user info</a></li>
</ul>
</div>
</script>
<script type="text/html" id="timelineTemplate">
<span class="timeline-desc">{{description}}</span>
<input class="timeline-desc-input" type="text" value='{{description}}'/>
<div class="drop-menu">
<div class="timeline-id">(&nbsp;{{id}}&nbsp;)</div>
<input class="timeline-id-input" type="text" value='{{id}}'/>
<ul class="drop-menu-items">
<li class="new-timeline-link"><a href="#">
<img src="/img/round_plus_icon_16.png"/>new</a></li>
</ul>
</div>
</script>
<script type="text/html" id="timelineLinkTemplate">
<li class="timeline-link"><a href="#">{{id}}</a></li>
</script>
<script type="text/html" id="entryTemplate">
<div class="mark">
<img class="expand-entry" src="/img/br_down_icon_16.png"/>
<img class="collapse-entry" src="/img/br_up_icon_16.png"/>
<span>{{mark}}</span>
</div>
<input class="mark-input" type="text" value="{{mark}}"/>
<div class="timestamp">{{start}}</div>
<input class="timestamp-input" type="text" value="{{timestamp}}"/>
<div class="duration">{{duration}}</div>
<div class="notes">
<div class="notes-text">{{notes}}</div>
<textarea class="notes-input" rows="10">{{notes}}</textarea>
</div>
</script>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
</head>
<body>
<div id="user" class="bar">
<span id="fullname">Not Logged In</span>
<span id="username">- no_user</span>
<div class="control-links">
<a href="/ts/edit-user.yaws"
onclick="$('#user-info').slideToggle('slow'); return false;">
user info</a>
<a href="/ts/logout.yaws" onclick="logout(event)">logout</a>
</div>
<div id="user-info">
<form action="/ts/update-user.yaws" onsubmit="updateUser(event)">
<div class="form-col">
<label for="fullname-input"><span>name:</span>
<input id="fullname-input" name="fullname"
class="text-input" type="text"/>
</label>
<label for="email-input"><span>email:</span>
<input id="email-input" name="email"
class="text-input" type="text"/>
</label>
</div>
<div class="form-col">
<div id="change-pwd">
<label for="old-pwd-input"><span>password:</span>
<input id="old-pwd-input" name="old-pwd"
class="text-input" type="password"/>
</label>
<label for="new-pwd-input"><span>new pwd:</span>
<input id="new-pwd-input" name="new-pwd"
class="text-input" type="password"/>
</label>
<label for="new-pwd-conf-input"><span>confirm:</span>
<input id="new-pwd-conf-input" name="new-pwd-conf"
class="text-input" type="password"/>
</label>
</div>
<label for="enable-pwd-change-input">
<input name="enable-pwd-change" type="checkbox"
id="enable-pwd-change-input"
onclick="$('#change-pwd').slideToggle('slow');"/>
change password
</label>
</div>
<div class="form-submit">
<div>
<input name="submit-user" type="submit"
value="save changes"/>
</div>
</div>
</form>
</div>
</div>
<div id="timeline" class="bar">
<span id="timeline-name">timeline |</span>
<span id="timeline-desc">timeline description</span>
<div class="control-links">
<a href="/ts/edit-timeline.yaws"
onclick="$('#timeline-info').slideToggle('slow'); return false;">
timeline info</a>
<a href="/ts/select-timeline.yaws"
onclick="showTimelineMenu(event)">change timelines</a>
</div>
<div id="timeline-info">
<form action="/ts/update-timeline.yaws"
onsubmit="updateTimeline(event); false">
<label for="timeline-desc-input"><span>description:</span>
<input id="timeline-desc-input" class="text-input"
name="timeline-desc" type="text"/>
</label>
<div class="form-submit">
<div><input name="submit-timeline" type="submit"
value="save changes"/></div>
</div>
</form>
</div>
</div>
<div id="new-entry" class="bar">
<form action="/ts/new-entry.yaws" onsubmit="newEntry(event)">
begin a new activity:
<input name="new-entry" id="new-entry-input"
class="text-input" type="text"/>
<input name="submit-entry" id="submit-entry"
class="form-submit" type="submit" value="create entry"/>
<div class="control-links">
<a id="show-notes" href="#"
onclick="$('#add-notes').slideToggle('slow');">
add notes</a>
<!-- == LOGIN FORM == -->
<div id="login" class="hidden dialog">
<div class="container">
<h2>Login</h2>
<div><label>Username: </label><input type="text" id="username-input"></input></div>
<div><label>Password: </label><input type="password" id="password-input"></input></div>
<div class="button-panel">
<span class='validate-tips'></span>
<div id="login-button" class="dialog-button"><a href="#">login</a></div>
</div>
<div id="add-notes" class="form-col">
<label for="new-notes-input">notes:</label>
<textarea name="new-notes" id="new-notes-input"
class="text-input" rows="8" cols="40" ></textarea>
</div>
</form>
</div>
<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">
<form>
<fieldset>
<label for="login-name">Username:</label>
<input type="text" name="login-name" id="login-name"
class="text ui-widget-content ui-corner-all"></input>
<div class="signup">
<label for="signup-fullname">Full name:</label>
<input type="text" name="signup-fullname" id="signup-fullname"
class="text ui-widget-content ui-corner-all"></input>
<label for="signup-email">eMail address:</label>
<input type="text" name="signup-email" id="signup-email"
class="text ui-widget-content ui-corner-all"></input>
<!-- == NEW TIMELINE FORM == -->
<div id="new-timeline" class="hidden dialog">
<div class="container">
<h2>Create a new timeline:</h2>
<div><label>Timeline ID: </label><input type="text" id="new-timeline-id"></input></div>
<div><label>Description: </label><input type="text" id="new-timeline-desc"></input></div>
<div class="button-panel">
<span class='validate-tips'></span>
<div id="new-timeline-create" class="dialog-button"><a href="#">create</a></div>
<div id="new-timeline-cancel" class="dialog-button"><a href="#">cancel</a></div>
</div>
<label for="login-password">Password:</label>
<input type="password" name="login-password" id="login-password"
class="text ui-widget-content ui-corner-all"></input>
<div class="signup">
<label for="confirm-password">Confirm password:</label>
<input type="password" name="confirm-password" id="confirm-password"
class="text ui-widget-content ui-corner-all"></input>
</div>
<label for="signup-checkbox">
<input type="checkbox" id="signup-checkbox" name="signup-checkbox"
onclick="toggleSignUp(event)"/>
I'm a new user!
</label>
</fieldset>
</form>
<p class="validate-tips"></p>
</div>
</div>
<div id="top">
<!-- == TIMELINE == -->
<div id="timeline"><!-- replaced on login by app -->
<div class="timeline-desc">Login</div>
</div>
<!-- == USER == -->
<div id="user"><!-- replaced on login by app -->
</div>
</div>
<div id="entry-list">
<div class="day-seperator">
<h4 class="mark">Today</h4>
<h5 class="timestamp">start</h5>
<h5 class="duration">duration</h5>
</div>
<div id="new-entry">
<input id="new-entry-input" class="mark-input"
placeholder="Start a new task..." type="text" />
</div>
<div id="entries"></div>
</div>
<div class="footer">
Copyright 2011 <a href="http://www.jdb-labs.com"><span class="logo">JDB Labs</span> LLC.</a>
</div>
</body>
</html>

27
www/js/backbone-min.js vendored Normal file
View File

@ -0,0 +1,27 @@
// Backbone.js 0.3.3
// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://documentcloud.github.com/backbone
(function(){var e;e=typeof exports!=="undefined"?exports:this.Backbone={};e.VERSION="0.3.3";var f=this._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var h=this.jQuery||this.Zepto;e.emulateHTTP=false;e.emulateJSON=false;e.Events={bind:function(a,b){this._callbacks||(this._callbacks={});(this._callbacks[a]||(this._callbacks[a]=[])).push(b);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=0,g=c.length;d<g;d++)if(b===c[d]){c.splice(d,
1);break}}else c[a]=[]}else this._callbacks={};return this},trigger:function(a){var b,c,d,g;if(!(c=this._callbacks))return this;if(b=c[a]){d=0;for(g=b.length;d<g;d++)b[d].apply(this,Array.prototype.slice.call(arguments,1))}if(b=c.all){d=0;for(g=b.length;d<g;d++)b[d].apply(this,arguments)}return this}};e.Model=function(a,b){a||(a={});if(this.defaults)a=f.extend({},this.defaults,a);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.set(a,{silent:true});this._previousAttributes=
f.clone(this.attributes);if(b&&b.collection)this.collection=b.collection;this.initialize(a,b)};f.extend(e.Model.prototype,e.Events,{_previousAttributes:null,_changed:false,initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.attributes[a];return this._escapedAttributes[a]=(b==null?"":b).replace(/&(?!\w+;)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,
"&quot;")},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return false;if("id"in a)this.id=a.id;for(var g in a){var i=a[g];if(!f.isEqual(c[g],i)){c[g]=i;delete d[g];if(!b.silent){this._changed=true;this.trigger("change:"+g,this,i,b)}}}!b.silent&&this._changed&&this.change(b);return this},unset:function(a,b){b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&
!this._performValidation(c,b))return false;delete this.attributes[a];delete this._escapedAttributes[a];if(!b.silent){this._changed=true;this.trigger("change:"+a,this,void 0,b);this.change(b)}return this},clear:function(a){a||(a={});var b=this.attributes,c={};for(attr in b)c[attr]=void 0;if(!a.silent&&this.validate&&!this._performValidation(c,a))return false;this.attributes={};this._escapedAttributes={};if(!a.silent){this._changed=true;for(attr in b)this.trigger("change:"+attr,this,void 0,a);this.change(a)}return this},
fetch:function(a){a||(a={});var b=this,c=j(a.error,b,a);(this.sync||e.sync)("read",this,function(d){if(!b.set(b.parse(d),a))return false;a.success&&a.success(b,d)},c);return this},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return false;var c=this,d=j(b.error,c,b),g=this.isNew()?"create":"update";(this.sync||e.sync)(g,this,function(i){if(!c.set(c.parse(i),b))return false;b.success&&b.success(c,i)},d);return this},destroy:function(a){a||(a={});var b=this,c=j(a.error,b,a);(this.sync||e.sync)("delete",
this,function(d){b.collection&&b.collection.remove(b);a.success&&a.success(b,d)},c);return this},url:function(){var a=k(this.collection);if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+this.id},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return!this.id},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=false},hasChanged:function(a){if(a)return this._previousAttributes[a]!=
this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=false,d;for(d in a)if(!f.isEqual(b[d],a[d])){c=c||{};c[d]=a[d]}return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);if(c){b.error?b.error(this,c):this.trigger("error",this,c,b);return false}return true}});
e.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;delete b.comparator}this._boundOnModelEvent=f.bind(this._onModelEvent,this);this._reset();a&&this.refresh(a,{silent:true});this.initialize(a,b)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c<d;c++)this._add(a[c],b);else this._add(a,b);return this},remove:function(a,
b){if(f.isArray(a))for(var c=0,d=a.length;c<d;c++)this._remove(a[c],b);else this._remove(a,b);return this},get:function(a){if(a==null)return null;return this._byId[a.id!=null?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");this.models=this.sortBy(this.comparator);a.silent||this.trigger("refresh",this,a);return this},pluck:function(a){return f.map(this.models,
function(b){return b.get(a)})},refresh:function(a,b){a||(a=[]);b||(b={});this._reset();this.add(a,{silent:true});b.silent||this.trigger("refresh",this,b);return this},fetch:function(a){a||(a={});var b=this,c=j(a.error,b,a);(this.sync||e.sync)("read",this,function(d){b.refresh(b.parse(d));a.success&&a.success(b,d)},c);return this},create:function(a,b){var c=this;b||(b={});if(a instanceof e.Model)a.collection=c;else a=new this.model(a,{collection:c});return a.save(null,{success:function(d,g){c.add(d);
b.success&&b.success(d,g)},error:b.error})},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_add:function(a,b){b||(b={});a instanceof e.Model||(a=new this.model(a,{collection:this}));var c=this.getByCid(a);if(c)throw Error(["Can't add the same model to a set twice",c.id]);this._byId[a.id]=a;this._byCid[a.cid]=a;a.collection=this;this.models.splice(this.comparator?this.sortedIndex(a,this.comparator):
this.length,0,a);a.bind("all",this._boundOnModelEvent);this.length++;b.silent||a.trigger("add",a,this,b);return a},_remove:function(a,b){b||(b={});a=this.getByCid(a)||this.get(a);if(!a)return null;delete this._byId[a.id];delete this._byCid[a.cid];delete a.collection;this.models.splice(this.indexOf(a),1);this.length--;b.silent||a.trigger("remove",a,this,b);a.unbind("all",this._boundOnModelEvent);return a},_onModelEvent:function(a,b){if(a==="change:id"){delete this._byId[b.previous("id")];this._byId[b.id]=
b}this.trigger.apply(this,arguments)}});f.each(["forEach","each","map","reduce","reduceRight","find","detect","filter","select","reject","every","all","some","any","include","invoke","max","min","sortBy","sortedIndex","toArray","size","first","rest","last","without","indexOf","lastIndexOf","isEmpty"],function(a){e.Collection.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});e.Controller=function(a){a||(a={});if(a.routes)this.routes=a.routes;this._bindRoutes();
this.initialize(a)};var o=/:([\w\d]+)/g,p=/\*([\w\d]+)/g;f.extend(e.Controller.prototype,e.Events,{initialize:function(){},route:function(a,b,c){e.history||(e.history=new e.History);f.isRegExp(a)||(a=this._routeToRegExp(a));e.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d))},this))},saveLocation:function(a){e.history.saveLocation(a)},_bindRoutes:function(){if(this.routes)for(var a in this.routes){var b=this.routes[a];
this.route(a,b,this[b])}},_routeToRegExp:function(a){a=a.replace(o,"([^/]*)").replace(p,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});e.History=function(){this.handlers=[];this.fragment=this.getFragment();f.bindAll(this,"checkUrl")};var l=/^#*/;f.extend(e.History.prototype,{interval:50,getFragment:function(a){return(a||window.location).hash.replace(l,"")},start:function(){var a=document.documentMode;if(a=h.browser.msie&&(!a||a<=7))this.iframe=h('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow;
"onhashchange"in window&&!a?h(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);return this.loadUrl()},route:function(a,b){this.handlers.push({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();if(a==this.fragment&&this.iframe)a=this.getFragment(this.iframe.location);if(a==this.fragment||a==decodeURIComponent(this.fragment))return false;if(this.iframe)window.location.hash=this.iframe.location.hash=a;this.loadUrl()},loadUrl:function(){var a=this.fragment=
this.getFragment();return f.any(this.handlers,function(b){if(b.route.test(a)){b.callback(a);return true}})},saveLocation:function(a){a=(a||"").replace(l,"");if(this.fragment!=a){window.location.hash=this.fragment=a;if(this.iframe&&a!=this.getFragment(this.iframe.location)){this.iframe.document.open().close();this.iframe.location.hash=a}}}});e.View=function(a){this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize(a)};var q=/^(\w+)\s*(.*)$/;f.extend(e.View.prototype,e.Events,
{tagName:"div",$:function(a){return h(a,this.el)},initialize:function(){},render:function(){return this},remove:function(){h(this.el).remove();return this},make:function(a,b,c){a=document.createElement(a);b&&h(a).attr(b);c&&h(a).html(c);return a},delegateEvents:function(a){if(a||(a=this.events)){h(this.el).unbind();for(var b in a){var c=a[b],d=b.match(q),g=d[1];d=d[2];c=f.bind(this[c],this);d===""?h(this.el).bind(g,c):h(this.el).delegate(d,g,c)}}},_configure:function(a){if(this.options)a=f.extend({},
this.options,a);if(a.model)this.model=a.model;if(a.collection)this.collection=a.collection;if(a.el)this.el=a.el;if(a.id)this.id=a.id;if(a.className)this.className=a.className;if(a.tagName)this.tagName=a.tagName;this.options=a},_ensureElement:function(){if(!this.el){var a={};if(this.id)a.id=this.id;if(this.className)a["class"]=this.className;this.el=this.make(this.tagName,a)}}});var m=function(a,b){var c=r(this,a,b);c.extend=m;return c};e.Model.extend=e.Collection.extend=e.Controller.extend=e.View.extend=
m;var s={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};e.sync=function(a,b,c,d){var g=s[a];a=a==="create"||a==="update"?JSON.stringify(b.toJSON()):null;b={url:k(b),type:g,contentType:"application/json",data:a,dataType:"json",processData:false,success:c,error:d};if(e.emulateJSON){b.contentType="application/x-www-form-urlencoded";b.processData=true;b.data=a?{model:a}:{}}if(e.emulateHTTP)if(g==="PUT"||g==="DELETE"){if(e.emulateJSON)b.data._method=g;b.type="POST";b.beforeSend=function(i){i.setRequestHeader("X-HTTP-Method-Override",
g)}}h.ajax(b)};var n=function(){},r=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};n.prototype=a.prototype;d.prototype=new n;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},k=function(a){if(!(a&&a.url))throw Error("A 'url' property or function must be specified");return f.isFunction(a.url)?a.url():a.url},j=function(a,b,c){return function(d){a?a(b,d):b.trigger("error",b,d,c)}}})();

1098
www/js/backbone.js Normal file

File diff suppressed because it is too large Load Diff

419
www/js/showdown-min.js vendored Normal file
View File

@ -0,0 +1,419 @@
/*
A A L Source code at:
T C A <http://www.attacklab.net/>
T K B
*/
var Showdown={};
Showdown.converter=function(){
var _1;
var _2;
var _3;
var _4=0;
this.makeHtml=function(_5){
_1=new Array();
_2=new Array();
_3=new Array();
_5=_5.replace(/~/g,"~T");
_5=_5.replace(/\$/g,"~D");
_5=_5.replace(/\r\n/g,"\n");
_5=_5.replace(/\r/g,"\n");
_5="\n\n"+_5+"\n\n";
_5=_6(_5);
_5=_5.replace(/^[ \t]+$/mg,"");
_5=_7(_5);
_5=_8(_5);
_5=_9(_5);
_5=_a(_5);
_5=_5.replace(/~D/g,"$$");
_5=_5.replace(/~T/g,"~");
return _5;
};
var _8=function(_b){
var _b=_b.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(_c,m1,m2,m3,m4){
m1=m1.toLowerCase();
_1[m1]=_11(m2);
if(m3){
return m3+m4;
}else{
if(m4){
_2[m1]=m4.replace(/"/g,"&quot;");
}
}
return "";
});
return _b;
};
var _7=function(_12){
_12=_12.replace(/\n/g,"\n\n");
var _13="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";
var _14="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";
_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,_15);
_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,_15);
_12=_12.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,_15);
_12=_12.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,_15);
_12=_12.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,_15);
_12=_12.replace(/\n\n/g,"\n");
return _12;
};
var _15=function(_16,m1){
var _18=m1;
_18=_18.replace(/\n\n/g,"\n");
_18=_18.replace(/^\n/,"");
_18=_18.replace(/\n+$/g,"");
_18="\n\n~K"+(_3.push(_18)-1)+"K\n\n";
return _18;
};
var _9=function(_19){
_19=_1a(_19);
var key=_1c("<hr />");
_19=_19.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
_19=_19.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
_19=_19.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
_19=_1d(_19);
_19=_1e(_19);
_19=_1f(_19);
_19=_7(_19);
_19=_20(_19);
return _19;
};
var _21=function(_22){
_22=_23(_22);
_22=_24(_22);
_22=_25(_22);
_22=_26(_22);
_22=_27(_22);
_22=_28(_22);
_22=_11(_22);
_22=_29(_22);
_22=_22.replace(/ +\n/g," <br />\n");
return _22;
};
var _24=function(_2a){
var _2b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
_2a=_2a.replace(_2b,function(_2c){
var tag=_2c.replace(/(.)<\/?code>(?=.)/g,"$1`");
tag=_2e(tag,"\\`*_");
return tag;
});
return _2a;
};
var _27=function(_2f){
_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_30);
_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_30);
_2f=_2f.replace(/(\[([^\[\]]+)\])()()()()()/g,_30);
return _2f;
};
var _30=function(_31,m1,m2,m3,m4,m5,m6,m7){
if(m7==undefined){
m7="";
}
var _39=m1;
var _3a=m2;
var _3b=m3.toLowerCase();
var url=m4;
var _3d=m7;
if(url==""){
if(_3b==""){
_3b=_3a.toLowerCase().replace(/ ?\n/g," ");
}
url="#"+_3b;
if(_1[_3b]!=undefined){
url=_1[_3b];
if(_2[_3b]!=undefined){
_3d=_2[_3b];
}
}else{
if(_39.search(/\(\s*\)$/m)>-1){
url="";
}else{
return _39;
}
}
}
url=_2e(url,"*_");
var _3e="<a href=\""+url+"\"";
if(_3d!=""){
_3d=_3d.replace(/"/g,"&quot;");
_3d=_2e(_3d,"*_");
_3e+=" title=\""+_3d+"\"";
}
_3e+=">"+_3a+"</a>";
return _3e;
};
var _26=function(_3f){
_3f=_3f.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_40);
_3f=_3f.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_40);
return _3f;
};
var _40=function(_41,m1,m2,m3,m4,m5,m6,m7){
var _49=m1;
var _4a=m2;
var _4b=m3.toLowerCase();
var url=m4;
var _4d=m7;
if(!_4d){
_4d="";
}
if(url==""){
if(_4b==""){
_4b=_4a.toLowerCase().replace(/ ?\n/g," ");
}
url="#"+_4b;
if(_1[_4b]!=undefined){
url=_1[_4b];
if(_2[_4b]!=undefined){
_4d=_2[_4b];
}
}else{
return _49;
}
}
_4a=_4a.replace(/"/g,"&quot;");
url=_2e(url,"*_");
var _4e="<img src=\""+url+"\" alt=\""+_4a+"\"";
_4d=_4d.replace(/"/g,"&quot;");
_4d=_2e(_4d,"*_");
_4e+=" title=\""+_4d+"\"";
_4e+=" />";
return _4e;
};
var _1a=function(_4f){
_4f=_4f.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,function(_50,m1){
return _1c("<h1>"+_21(m1)+"</h1>");
});
_4f=_4f.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(_52,m1){
return _1c("<h2>"+_21(m1)+"</h2>");
});
_4f=_4f.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(_54,m1,m2){
var _57=m1.length;
return _1c("<h"+_57+">"+_21(m2)+"</h"+_57+">");
});
return _4f;
};
var _58;
var _1d=function(_59){
_59+="~0";
var _5a=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
if(_4){
_59=_59.replace(_5a,function(_5b,m1,m2){
var _5e=m1;
var _5f=(m2.search(/[*+-]/g)>-1)?"ul":"ol";
_5e=_5e.replace(/\n{2,}/g,"\n\n\n");
var _60=_58(_5e);
_60=_60.replace(/\s+$/,"");
_60="<"+_5f+">"+_60+"</"+_5f+">\n";
return _60;
});
}else{
_5a=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
_59=_59.replace(_5a,function(_61,m1,m2,m3){
var _65=m1;
var _66=m2;
var _67=(m3.search(/[*+-]/g)>-1)?"ul":"ol";
var _66=_66.replace(/\n{2,}/g,"\n\n\n");
var _68=_58(_66);
_68=_65+"<"+_67+">\n"+_68+"</"+_67+">\n";
return _68;
});
}
_59=_59.replace(/~0/,"");
return _59;
};
_58=function(_69){
_4++;
_69=_69.replace(/\n{2,}$/,"\n");
_69+="~0";
_69=_69.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(_6a,m1,m2,m3,m4){
var _6f=m4;
var _70=m1;
var _71=m2;
if(_70||(_6f.search(/\n{2,}/)>-1)){
_6f=_9(_72(_6f));
}else{
_6f=_1d(_72(_6f));
_6f=_6f.replace(/\n$/,"");
_6f=_21(_6f);
}
return "<li>"+_6f+"</li>\n";
});
_69=_69.replace(/~0/g,"");
_4--;
return _69;
};
var _1e=function(_73){
_73+="~0";
_73=_73.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(_74,m1,m2){
var _77=m1;
var _78=m2;
_77=_79(_72(_77));
_77=_6(_77);
_77=_77.replace(/^\n+/g,"");
_77=_77.replace(/\n+$/g,"");
_77="<pre><code>"+_77+"\n</code></pre>";
return _1c(_77)+_78;
});
_73=_73.replace(/~0/,"");
return _73;
};
var _1c=function(_7a){
_7a=_7a.replace(/(^\n+|\n+$)/g,"");
return "\n\n~K"+(_3.push(_7a)-1)+"K\n\n";
};
var _23=function(_7b){
_7b=_7b.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(_7c,m1,m2,m3,m4){
var c=m3;
c=c.replace(/^([ \t]*)/g,"");
c=c.replace(/[ \t]*$/g,"");
c=_79(c);
return m1+"<code>"+c+"</code>";
});
return _7b;
};
var _79=function(_82){
_82=_82.replace(/&/g,"&amp;");
_82=_82.replace(/</g,"&lt;");
_82=_82.replace(/>/g,"&gt;");
_82=_2e(_82,"*_{}[]\\",false);
return _82;
};
var _29=function(_83){
_83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"<strong>$2</strong>");
_83=_83.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"<em>$2</em>");
return _83;
};
var _1f=function(_84){
_84=_84.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(_85,m1){
var bq=m1;
bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0");
bq=bq.replace(/~0/g,"");
bq=bq.replace(/^[ \t]+$/gm,"");
bq=_9(bq);
bq=bq.replace(/(^|\n)/g,"$1 ");
bq=bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(_88,m1){
var pre=m1;
pre=pre.replace(/^ /mg,"~0");
pre=pre.replace(/~0/g,"");
return pre;
});
return _1c("<blockquote>\n"+bq+"\n</blockquote>");
});
return _84;
};
var _20=function(_8b){
_8b=_8b.replace(/^\n+/g,"");
_8b=_8b.replace(/\n+$/g,"");
var _8c=_8b.split(/\n{2,}/g);
var _8d=new Array();
var end=_8c.length;
for(var i=0;i<end;i++){
var str=_8c[i];
if(str.search(/~K(\d+)K/g)>=0){
_8d.push(str);
}else{
if(str.search(/\S/)>=0){
str=_21(str);
str=str.replace(/^([ \t]*)/g,"<p>");
str+="</p>";
_8d.push(str);
}
}
}
end=_8d.length;
for(var i=0;i<end;i++){
while(_8d[i].search(/~K(\d+)K/)>=0){
var _91=_3[RegExp.$1];
_91=_91.replace(/\$/g,"$$$$");
_8d[i]=_8d[i].replace(/~K\d+K/,_91);
}
}
return _8d.join("\n\n");
};
var _11=function(_92){
_92=_92.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
_92=_92.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
return _92;
};
var _25=function(_93){
_93=_93.replace(/\\(\\)/g,_94);
_93=_93.replace(/\\([`*_{}\[\]()>#+-.!])/g,_94);
return _93;
};
var _28=function(_95){
_95=_95.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
_95=_95.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(_96,m1){
return _98(_a(m1));
});
return _95;
};
var _98=function(_99){
function char2hex(ch){
var _9b="0123456789ABCDEF";
var dec=ch.charCodeAt(0);
return (_9b.charAt(dec>>4)+_9b.charAt(dec&15));
}
var _9d=[function(ch){
return "&#"+ch.charCodeAt(0)+";";
},function(ch){
return "&#x"+char2hex(ch)+";";
},function(ch){
return ch;
}];
_99="mailto:"+_99;
_99=_99.replace(/./g,function(ch){
if(ch=="@"){
ch=_9d[Math.floor(Math.random()*2)](ch);
}else{
if(ch!=":"){
var r=Math.random();
ch=(r>0.9?_9d[2](ch):r>0.45?_9d[1](ch):_9d[0](ch));
}
}
return ch;
});
_99="<a href=\""+_99+"\">"+_99+"</a>";
_99=_99.replace(/">.+:/g,"\">");
return _99;
};
var _a=function(_a3){
_a3=_a3.replace(/~E(\d+)E/g,function(_a4,m1){
var _a6=parseInt(m1);
return String.fromCharCode(_a6);
});
return _a3;
};
var _72=function(_a7){
_a7=_a7.replace(/^(\t|[ ]{1,4})/gm,"~0");
_a7=_a7.replace(/~0/g,"");
return _a7;
};
var _6=function(_a8){
_a8=_a8.replace(/\t(?=\t)/g," ");
_a8=_a8.replace(/\t/g,"~A~B");
_a8=_a8.replace(/~B(.+?)~A/g,function(_a9,m1,m2){
var _ac=m1;
var _ad=4-_ac.length%4;
for(var i=0;i<_ad;i++){
_ac+=" ";
}
return _ac;
});
_a8=_a8.replace(/~A/g," ");
_a8=_a8.replace(/~B/g,"");
return _a8;
};
var _2e=function(_af,_b0,_b1){
var _b2="(["+_b0.replace(/([\[\]\\])/g,"\\$1")+"])";
if(_b1){
_b2="\\\\"+_b2;
}
var _b3=new RegExp(_b2,"g");
_af=_af.replace(_b3,_94);
return _af;
};
var _94=function(_b4,m1){
var _b6=m1.charCodeAt(0);
return "~E"+_b6+"E";
};
};
if(typeof exports!='undefined')exports.Showdown=Showdown;

File diff suppressed because it is too large Load Diff

131
www/js/test.js Normal file
View File

@ -0,0 +1,131 @@
var Test = {};
$(document).ready(function() {
Test.U = Backbone.Model.extend({
url: '/ts_api/users',
initialize: function() {
this.timelines = {};
}
});
Test.E = Backbone.Model.extend({
url: '/ts_api/entries/jdbernard/work'
});
Test.UView = Backbone.View.extend({
el: $("#user"),
model: Test.U,
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.model.view = this;
},
render: function() {
this.$('.fullname').text(this.model.get('name'));
this.$('.username').text(this.model.get('id'));
}
});
Test.EList = Backbone.Collection.extend({
model: Test.E,
url: '/ts_api/entries/jdbernard/work',
initalize: function(models, options) {
this.user = options.user;
},
comparator: function(entry) {
return entry.get('timestamp');
}
});
Test.EView = Backbone.View.extend({
model: Test.E,
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.model.view = this;
},
render: function() {
$(this.el).html("<span class='entry-id'>" + this.model.get('id') + "<span class='mark'>" + this.model.get('mark') + "</span>");
return this;
}
});
Test.EListView = Backbone.View.extend({
el: $("#entry-list"),
initialize: function() {
_.bindAll(this, 'render', 'refresh', 'addOne');
this.collection.bind('add', this.addOne);
this.collection.bind('refresh', this.refresh);
},
addOne: function(entry) {
var view = new Test.EView({model: entry});
$(this.el).append(view.render().el);
},
refresh: function() {
$(this.el).empty();
var thisRef = this;
this.collection.each(function(entry) {thisRef.addOne(entry)});
}
});
Test.currentUser = new Test.U;
Test.userView = new Test.UView({model: Test.currentUser});
// wire the login dialog using jQuery UI
$("#login-dialog").dialog({
autoOpen: false,
height: 400,
width: 400,
modal: true,
buttons: { Login: function(){login()} }
});
$('#login-dialog').dialog('open');
});
function login() {
// lookup the login dialog elements
var name = $("#login-name");
var pwd = $("#login-password");
// call the API via AJAX
$.ajax({
url: "/ts_api/login",
processData: false,
data: JSON.stringify({username: name.val(), password: pwd.val()}),
type: "POST",
error: function(jqXHR, textStatus, error) {
// assuming bad credentials (possible server error or bad request,
// we should check that, FIXME
var tips = $(".validate-tips");
tips.text("Incorrect username/password combination.");
tips.addClass("ui-state-error");
tips.slideDown();
},
success: function(data, textStatus, jqXHR) {
$("#login-dialog").dialog('close');
Test.currentUser.set({id: name.val()}, {silent: true});
Test.currentUser.fetch();
Test.currentEntryList = Test.currentUser.timelines['work'] = new Test.EList([], {user: Test.currentUser});
new Test.EListView({collection: Test.currentEntryList});
Test.currentEntryList.fetch();
}});
}

File diff suppressed because it is too large Load Diff

247
www/prototype.html Normal file
View File

@ -0,0 +1,247 @@
<!DOCTYPE html>
<html>
<head>
<title>TimeStamper - Simple Time Tracking</title>
<link href='http://fonts.googleapis.com/css?family=Arvo|Bentham|Cuprum|Cantarell|Geo|Josefin+Sans' rel='stylesheet' type='text/css'>
<link rel="stylesheet" media="screen" href="css/ts-screen.css" type="text/css"/>
<script type="text/javascript" src="js/jquery-1.5.js"></script>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
</head>
<body>
<div id="top">
<div id="timeline">
<span class="timeline-desc">Work-related activities.</span>
<input class="timeline-desc-input" type="text"/>
<div class="drop-menu">
<div class="timeline-id">(&nbsp;work&nbsp;)</div>
<input class="timeline-id-input" type="text"/>
<ul class="drop-menu-items">
<li class="timeline-link"><a href="#">jdb-labs</a></li>
<li class="timeline-link"><a href="#">personal</a></li>
<li class="timeline-link"><a href="#">vbs-suite</a></li>
</ul>
</div>
</div>
<div id="user">
<div class="fullname">Jonathan Bernard</div>
<input class='fullname-input' type='text'/>
<div class='drop-menu'>
<div class="username"> - jdbernard</div>
<ul class="drop-menu-items">
<li><a href="#">logout</a></li>
<li><a href="#">user info</a></li>
</ul>
</div>
</div>
</div>
<div id="entry-list">
<div class="day-seperator">
<h4 class='mark'>Today</h4>
<h5 class='timestamp'>start</h5>
<h5 class='duration'>duration</h5>
</div>
<div id="new-entry">
<input id="new-entry-input" class="mark-input"
placeholder="Start a new task..." type="text" />
</div>
<div id="entries">
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Entering tickets.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">12:32</div>
<input class="timestamp-input" type="text"/>
<div class="duration">4<span class="tick-tock">hr </span>3<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Helping Steve wth WR Updates.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">9:56</div>
<input class="timestamp-input" type="text"/>
<div class="duration">1<span class="tick-tock">hr </span>15<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Email</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">9:10</div>
<input class="timestamp-input" type="text"/>
<div class="duration">47<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="day-seperator">
<h4 class='mark'>Yesterday</h4>
<h5 class='timestamp'>start</h5>
<h5 class='duration'>duration</h5>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Working #7801.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">3:12 pm</div>
<input class="timestamp-input" type="text"/>
<div class="duration">12<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Lunch.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">11:47 am</div>
<input class="timestamp-input" type="text"/>
<div class="duration">3<span class="tick-tock">hr </span>25<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Entering tickets.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">9:20 am</div>
<input class="timestamp-input" type="text"/>
<div class="duration">2<span class="tick-tock">hr </span>27<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Reproducing #7796.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">9:11 am</div>
<input class="timestamp-input" type="text"/>
<div class="duration">9<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="day-seperator">
<h4 class='mark'>Monday</h4>
<h5 class='timestamp'>start</h5>
<h5 class='duration'>duration</h5>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Working #7733.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">16:41</div>
<input class="timestamp-input" type="text"/>
<div class="duration">1<span class="tick-tock">hr </span>8<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Zend Training: Building Security Into Your PHP Applications</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">10:30</div>
<input class="timestamp-input" type="text"/>
<div class="duration">4<span class="tick-tock">hr </span>11<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">8:40</div>
<input class="timestamp-input" type="text"/>
<div class="duration">1<span class="tick-tock">hr </span>50<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="day-seperator">
<h4 class='mark'>Friday, April 29th</h4>
<h5 class='timestamp'>start</h5>
<h5 class='duration'>duration</h5>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Training Steve: Databases and SQL</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">16:09</div>
<input class="timestamp-input" type="text"/>
<div class="duration">55<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Preparing Instructional Material: Database Basics</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">15:12</div>
<input class="timestamp-input" type="text"/>
<div class="duration">57<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Working #7729.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">15:09</div>
<input class="timestamp-input" type="text"/>
<div class="duration">3<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">14:44</div>
<input class="timestamp-input" type="text"/>
<div class="duration">25<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Working #7728.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">14:41</div>
<input class="timestamp-input" type="text"/>
<div class="duration">3<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">14:00</div>
<input class="timestamp-input" type="text"/>
<div class="duration">41<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Lunch.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">13:05</div>
<input class="timestamp-input" type="text"/>
<div class="duration">55<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Working #7725.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">12:40</div>
<input class="timestamp-input" type="text"/>
<div class="duration">20<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Zend Training: Building Security Into You PHP Applications.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">10:30</div>
<input class="timestamp-input" type="text"/>
<div class="duration">2<span class="tick-tock">hr </span>10<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>Zend Training: Preparing for security training.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">09:25</div>
<input class="timestamp-input" type="text"/>
<div class="duration">1<span class="tick-tock">hr </span>5<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
<div class="entry">
<div class="mark"><img src="img/round_delete_icon&16.png" class="delete-icon"/><span>ITHelp: Working #7700.</span><img src="img/notepad_2_icon&16.png" class="notes-icon"/></div>
<input class="mark-input" type="text"/>
<div class="timestamp">09:17</div>
<input class="timestamp-input" type="text"/>
<div class="duration">8<span class="tick-tock">m </span></div>
<div class="notes">Some notes should go here, but they should be hidden by default</div>
</div>
</div>
</div>
<div class="footer">
Copyright 2011 <a href="http://www.jdb-labs.com"><span class="logo">JDB Labs</span> LLC.</a>
</div>
</body>
</html>

38
www/test.html Normal file
View File

@ -0,0 +1,38 @@
<html>
<head>
<title>Testing Backbone.js</title>
<link rel="stylesheet" media="screen" href="/css/dot-luv/jquery-ui-1.8.10.custom.css" type="text/css"/>
<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.js"></script>
<script type="text/javascript" src="/js/backbone-min.js"></script>
<script type="text/javascript" src="/js/test.js"></script>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
</head>
<body>
<div id="user">
<div class="username">username</div>
<div class="fullname">fullname</div>
</div>
<div id="entry-list"></div>
<div id="login-dialog" title="Login">
<form>
<fieldset>
<label for="login-name">Username:</label>
<input type="text" name="login-name" id="login-name"
class="text ui-widget-content ui-corner-all"></input>
<label for="login-password">Password:</label>
<input type="password" name="login-password" id="login-password"
class="text ui-widget-content ui-corner-all"></input>
</fieldset>
</form>
<p class="validate-tips"></p>
</div>
</body>
</html>

View File

@ -3,7 +3,7 @@ ebin_dir = /home/jdbernard/projects/jdb-labs/timestamper/web-app/ebin
runmod = timestamper_dev
<server timestamper.jdb-labs-local>
<server timestamper-local>
port = 8000
listen = 127.0.0.1
docroot = /home/jdbernard/projects/jdb-labs/timestamper/web-app/www