Commits

Torbjorn Tornkvist  committed 5a3a671

Got list entries and show entry to work again.

  • Participants
  • Parent commits 222c1e5

Comments (0)

Files changed (15)

 SIMPLE_BRIDGE_EBIN=$NITROGEN_TOP_DIR/apps/simple_bridge/ebin
 NPROCREG_EBIN=$NITROGEN_TOP_DIR/apps/nprocreg/ebin
 EOPENID_EBIN=/home/tobbe/git/eopenid/ebin
+REDBUG_EBIN=/home/tobbe/Kreditor/svn/trunk/lib/eper/ebin
 
 

File ebin/redhot2.app

          ,{port, 8283}
 	 ,{log_dir, "/tmp"}
 	 ,{doc_root, "./www"}
-	 ,{acl, ["http://etnt.myopenid.com/"
-                ]}
-         ,{users,[{"http://etnt.myopenid.com/",
-                   [{name, "Torbjorn Tornkvist"}
-                    ,{email, "etnt@redhoterlang.com"}]}
-                 ]}
+	 ,{authors, [{"http://etnt.myopenid.com/" % Claimed OpenID
+                     ,"tobbe"                    % Nickname
+                     ,"tobbe@tornkvist.org"},    % Email,
+                    {"http://mats.cronqvist.myopenid.com/"
+                     ,"masse"                 
+                     ,"masse@cronqvi.st"},
+                    {"http://klacke.myopenid.com/"
+                     ,"klacke"                 
+                     ,"klacke@hyber.org"} 
+                   ]}
+
         ]
   }
 ]}.

File ebin/redhot2.beam

Binary file modified.

File ebin/redhot2_common.beam

Binary file modified.

File src/redhot2.erl

          , twitter_user/0
          , twitter_passwd/0
          , log_dir/0
+         , author2email/1
+         , maybe_nick/1
+         , to_latin1/1
+         , gtostr/1
+         , gtostr/2
          , hostname/0
          , default_port/0
          , i2l/1
 twitter_passwd()    -> get_env(twitter_passwd, []).
 log_dir()           -> get_env(error_logger_mf_file, "/tmp/redhot").
 
-           
+
+author2email(Nick) when is_binary(Nick) ->
+    author2email(b2l(Nick));
+author2email(Nick) when is_list(Nick) ->
+    {value,{_,_,Email}} = lists:keysearch(Nick, 2, authors()),
+    Email.
+
+maybe_nick(Who) ->
+    case lists:keysearch(Who, 1, authors()) of
+        {value,{Who, Nick, _Email}} -> Nick;
+        _                           -> Who
+    end.
+
+
+%% The stupid browser persist to send utf-8 characters...
+to_latin1(Str) when is_list(Str) -> to_latin1(list_to_binary(Str));
+to_latin1(Str) when is_binary(Str) ->
+    case binary_to_list(<< <<C>> || <<C/utf8>> <= Str >>) of
+        []   -> Str;
+        Lstr -> Lstr
+    end.
+          
+
+%%-----------------------------------------------------------------------------
+%% @spec gtostr(Gsecs::greg_secs()) -> string()
+%% @doc Equivalent to <code>gtostr(Gsecs, date_time)</code>.
+%%-----------------------------------------------------------------------------
+gtostr(Secs) when is_float(Secs) -> gtostr(trunc(Secs)); % float !??
+gtostr(Secs) -> gtostr(Secs, date_time).
+
+%%-----------------------------------------------------------------------------
+%% @spec gtostr(Gsecs::greg_secs(), Format::format()) -> string()
+%% @doc Returns standard format string
+%% The returned formats look like this:
+%% <pre>
+%% Returns : date      -> "YYYY-MM-DD"
+%%           xdate     -> "YYYYMMDD"
+%%           time      -> "HH:MM:SS"
+%%           date_time -> "YYYY-MM-DD HH:MM:SS"  (default)
+%%           iso8106   -> "YYYYMMDDTHHMMSS"
+%% </pre>
+%% Note    : the YYYY part can be 1+ chars
+%%-----------------------------------------------------------------------------
+gtostr(undefined, _) -> "-";
+gtostr(Secs, date) ->
+    {{Year, Month, Day}, _} = calendar:gregorian_seconds_to_datetime(Secs),
+    lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w", [Year, Month, Day]));
+gtostr(Secs, xdate) ->
+    {{Year, Month, Day}, _} = calendar:gregorian_seconds_to_datetime(Secs),
+    lists:flatten(io_lib:format("~w~2.2.0w~2.2.0w", [Year, Month, Day]));
+gtostr(Secs, xdatex) ->
+    {{Year, Month, Day}, _} = calendar:gregorian_seconds_to_datetime(Secs),
+    Year2 = Year rem 1000,
+    lists:flatten(io_lib:format("~2.2.0w~2.2.0w~2.2.0w", [Year2, Month, Day]));
+gtostr(Secs, days) ->
+    {{Year, Month, Day}, _} = calendar:gregorian_seconds_to_datetime(Secs),
+    lists:flatten(io_lib:format("~w", [calendar:date_to_gregorian_days(
+					 Year, Month, Day)]));
+gtostr(Secs, time) ->
+    {_, {Hour, Minute, Second}} = calendar:gregorian_seconds_to_datetime(Secs),
+    lists:flatten(io_lib:format("~2.2.0w:~2.2.0w:~2.2.0w",
+				[Hour, Minute, Second]));
+gtostr(Secs, date_time) ->
+    {{Year, Month, Day}, {Hour, Minute, Second}} =
+	calendar:gregorian_seconds_to_datetime(Secs),
+    lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w ~2.2.0w:~2.2.0w:~2.2.0w",
+				[Year, Month, Day, Hour, Minute, Second]));
+gtostr(Secs, iso8601) ->
+    {{Year, Month, Day}, {Hour, Minute, Second}} =
+	calendar:gregorian_seconds_to_datetime(Secs),
+    lists:flatten(io_lib:format("~w~2.2.0w~2.2.0wT~2.2.0w~2.2.0w~2.2.0w",
+				[Year, Month, Day, Hour, Minute, Second])).
 
 default_port() -> 8080.
 

File src/redhot2_common.erl

          , footer/0
          , right/0
          , left/0
+         , gravatar/1
+         , raw_path/0
         ]).
 
 title() ->
 
 
 right() ->
-    #panel { class=right, body=["Bla bla bla..."] }.
+    #panel { class=right, body=[] }.
 
 
 left() ->
-    #panel { class=left, body=["Bla bla bla this and that....."] }.
+    #panel { class=left, body=[redhot2_nav:list_entries()] }.
+
+raw_path() ->
+    RequestBridge = wf_context:request_bridge(),
+    RequestBridge:uri().
+
+
+
+gravatar(Email) ->
+    #gravatar { email=Email,
+		size="30", 
+		rating="g", 
+		default="identicon" }.
 
 
 header(Selected) ->

File src/redhot2_couchdb.erl

          ,store_doc/1
          ,store_doc/2
 	 ,update_user/1
+         ,entry/1
         ]).
 
 -export([http_get_req/1
          ,http_put_req/2
          ,http_put_req/3
          ,find/2
+         ,find/3
         ]).
 
 -import(redhot2, [l2b/1,b2l/1]).
                      "startkey=[\""++b2l(Id)++"\",{}]&"
                      "endkey=[\""++b2l(Id)++"\",0]").
 
+entry(Id) ->
+    get_req(?HOST ++ "/" ++ ?DB_NAME ++ "/" ++b2l(Id)).
+
 %% @doc Create the redhot2 database if it doesn't exist. 
 %%      Also create the views if they don't exist
 init() ->
     
 
 get_from_couchdb(Url) ->
-    R = http_get_req(Url),
+    R = get_req(Url),
     %% Just preserve the Json
     F = fun(X) -> {true,X} end,
     find(F, ["rows","value"], R).
 
+get_req(Url) ->
+    http_get_req(Url).
 
 %%%
 %%% HTTP access

File src/redhot2_inets.erl

 %%% @doc This is the routing table.
 routes() ->
     [{"/",            redhot2_web_index}
+     , {"/entry",     redhot2_web_entry}
+     , {"/about",     redhot2_web_about}
      , {"/login",     redhot2_web_login}
      , {"/logout",    redhot2_web_logout}
      , {"/auth",      redhot2_web_auth}

File src/redhot2_nav.erl

+%% @author Torbjorn Tornkvist <etnt@redhoterlang.com>
+%% @copyright 2010 Torbjorn Tornkvist
+
+-module(redhot2_nav).
+
+-include_lib ("nitrogen/include/wf.hrl").
+
+-export([event/1
+         , list_entries/0
+        ]).
+
+-import(redhot2, [gtostr/2
+                  , to_latin1/1
+                  , b2l/1
+                 ]).
+
+
+event({entry,Id}) ->
+    wf:redirect("/entry/"++b2l(Id)).
+
+
+list_entries() ->
+    Auth = wf:session(authenticated),
+    F = fun({obj,L}, Acc) ->
+                case {Auth, proplists:get_value("published",L)} of
+                    {Auth, PubP} when PubP == true orelse (Auth == true andalso (not PubP)) ->
+                        [list_entry(Auth, L, PubP) | Acc];
+                    _ ->
+                        Acc
+                end
+        end,
+    lists:foldr(F, [], redhot2_couchdb:entries()).
+
+list_entry(Auth, L, PubP) ->
+    C = trunc(proplists:get_value("created",L)), % hm...a float here strange.. 
+    A = proplists:get_value("author",L),
+    T = proplists:get_value("title",L),
+    R = proplists:get_value("id",L),
+    Tid = wf:temp_id(),
+    Xid = wf:temp_id(),
+    M = #panel { body =
+                 [#panel { body =
+                           [if (Auth andalso (not PubP)) ->
+                                    #panel{class="l_date_unpub",
+                                           body=[#link {id=Xid,
+                                                        text=gtostr(C, date)}]};
+                               true ->
+                                    #panel {class="l_date" , body=gtostr(C, date)}
+                            end,
+                            #panel {class="l_author", body=to_latin1(A)},
+                            #link {class="l_title", id=Tid, text=to_latin1(T)}
+                            ]}]},
+    wf:wire(Tid, #event {type=click, postback={entry,R}, delegate=?MODULE}),
+    if (Auth andalso (not PubP)) ->
+            wf:wire(Xid, #event {type=click, postback={edit,R}, delegate=?MODULE});
+       true ->
+            false
+    end,
+    M.
+

File src/redhot2_web_about.erl

+%% @author Torbjorn Tornkvist etnt@redhoterlang.com
+%% @copyright YYYY Torbjorn Tornkvist.
+
+-module(redhot2_web_about).
+
+-include_lib("nitrogen/include/wf.hrl").
+
+-export([main/0
+         , title/0
+         , layout/0
+	 , event/1
+	]).
+
+
+main() ->
+    #template { file="./templates/grid.html" }.
+
+title() ->
+    redhot2_common:title().
+
+layout() ->
+    #container_12 {
+        body=[#grid_12 { class=header, body=redhot2_common:header(home) },
+              #grid_clear {},
+
+              #grid_10 { alpha=true, body=about() },
+              #grid_2 { omega=true, body=redhot2_common:right() },
+              #grid_clear {},
+              
+              #grid_12 { body=redhot2_common:footer() }
+             ]}.
+
+about() ->
+        "<p>This is a blog application written in Erlang.</p>
+
+<p>It has been functioning as a lab bench for experimenting with
+different tools and techniques over the years. The current version
+is version 4.
+<p>
+Now it is making use of <a href='http://nitrogenproject.com/'>Nitrogen 2.0</a>
+and <a href='http://couchdb.apache.org/'>CouchDB</a>. Version 3 implemented a
+desktop like interface, with no visible pages but the top one. I have now
+returned to a more REST like structure.
+</p>
+
+
+<p>If you would like to try it out or just look at the code, 
+you will find the git repository 
+<a href='http://dev.tornkvist.org/'>here</a>.
+To checkout the code with git, run:</p>
+<pre>
+      git clone git://dev.tornkvist.org/redhot
+</pre>    
+
+<p>Note that this is still work in progress...</p>". % " emacs mode bug ?
+
+
+
+event(Event) ->
+    io:format("Event=~p~n",[Event]),
+    ok.

File src/redhot2_web_entry.erl

+%% @author Torbjorn Tornkvist etnt@redhoterlang.com
+%% @copyright YYYY Torbjorn Tornkvist.
+
+-module(redhot2_web_entry).
+
+-include_lib("nitrogen/include/wf.hrl").
+
+-export([main/0
+         , title/0
+         , layout/0
+	 , event/1
+	]).
+
+-import(redhot2,
+        [author2email/1
+         , maybe_nick/1
+         , gtostr/1
+         , to_latin1/1
+         , b2l/1
+        ]).
+
+-import(redhot2_common,
+        [gravatar/1
+        ]).
+
+
+main() ->
+    #template { file="./templates/grid.html" }.
+
+title() ->
+    redhot2_common:title().
+
+layout() ->
+    #container_12 {
+        body=[#grid_12 { class=header, body=redhot2_common:header(home) },
+              #grid_clear {},
+
+              #grid_10 { alpha=true, body=article() },
+              #grid_2 { omega=true, body=redhot2_common:right() },
+              #grid_clear {},
+              
+              #grid_12 { body=redhot2_common:footer() }
+             ]}.
+
+event(Event) ->
+    io:format("Event=~p~n",[Event]),
+    ok.
+
+
+article() ->
+    case string:tokens(redhot2_common:raw_path(), "/") of
+        [_,Id] -> article(Id);
+        _      -> wf:redirect("/fixme_error_msg_here")
+    end.
+
+article(Id) ->
+    {obj,L} = redhot2_couchdb:entry(Id),
+    C = proplists:get_value("created",L),
+    T = proplists:get_value("title",L),
+    A = proplists:get_value("author",L),
+    H = proplists:get_value("html",L),
+    Comform = "e_comform",
+    #panel{body=[#span{class="e_date" , text=gtostr(C)},
+                 #span{class="e_title", text=to_latin1(T)},
+                 %#panel{class="e_author", body=["by: ",#span{class="blue",text=A}]},
+                 #panel{class="e_author", body=[#panel{body=[gravatar(author2email(A))]},
+						#panel{body=["by: ",#span{class="blue",text=A}]}]},
+                 #panel{class="l_body" , body=to_latin1(H)}
+%                 #panel{class="e_comhdr", body=comhdr(Comform,Id)},
+%                 #panel{class=Comform, body=comform(Comform,Id)},
+%                 #panel{class="l_comments" , 
+%                        body=format_comments(Id)}
+                ]}.
+
+comhdr(Comform, Id) ->
+    Tid = wf:temp_id(),
+    B=[#link{class="comhdr", text="permalink", url="/web/plink?id="++b2l(Id)},
+       #link{id = Tid, class="comhdr", text="add comment"}],
+    wf:wire(Tid, #event {type=click, actions=#script { script="$('."++Comform++"').toggle('slow');" }}),
+    B.
+
+%%%
+%%% Add comment form
+%%%
+comform(_Comform, Id) ->
+    case wf:session(authenticated) of
+        true ->
+            B=[#panel{body=["Type in your comment! HTML is allowed "
+                            "(enclose code blocks with &lt;pre&gt;&lt;code&gt;)."]},
+               #panel{body=[#textarea { id="com_text",  class="comment", text=""}]},
+               #panel{body=[#button   { id="com_submit",text="Submit"}]}],
+            wf:wire("com_submit", #event {type=click, postback={comment,Id}, delegate=?MODULE}),
+            B;
+        _ ->
+            Op = fun() -> nav:mk_article(Id) end,
+% FIXME            push(Op),
+            mk_openid_form(openid_comment_form_text())
+    end.
+
+
+%%%
+%%% Format the comments
+%%%
+format_comments(Id) ->
+    F = fun({obj,L}) ->
+                C = proplists:get_value("created", L),
+                T = proplists:get_value("text", L),
+                W = proplists:get_value("who",L),
+                A = proplists:get_value("author",L),
+                {true, {W,C,T,A}}
+        end,
+    Cs = redhot_couchdb:find(F, ["rows","value"], redhot_couchdb:comments(Id)),
+    G = fun({Who,Created,Text,Author}) ->
+                #panel{class = "c_body",
+                       body  = [#panel{class=c_is_author(Author),
+                                       body=[#span{class="c_date", 
+                                                   text=gtostr(Created)},
+                                             #span{class="c_who", 
+                                                   text=Who}]},
+                                #panel{class=c_txt(Author),
+                                       body=Text}]}
+        end,
+    [G(X) || X <- Cs].
+
+
+mk_openid_form(Text) ->
+    B=#panel{class="openid",
+             body = [#panel{body=[Text]},
+                     #panel{class="openid_box",
+                            body=[#panel{body=[#textbox{ class="openid_login", 
+                                                         id="claimed_id", 
+                                                         next="auth" }]},
+                                  #panel{body=[#button{ id="auth", 
+                                                        text="Authenticate"}]}]}
+                    ]},
+    wf:wire("auth", #event {type=click, 
+                            postback=claimed_id, 
+                            delegate=?MODULE}),
+    B.
+
+openid_comment_form_text() ->
+    "To avoid spammers you are required to authenticate yourself "
+        "with OpenId. A bit of a hassle perhaps "
+        "but the upside is that you'll be exercising the "
+        "<a href='http://github.com/etnt/eopenid'>eopenid</a> code.".
+
+
+c_is_author(true) -> "c_is_author";
+c_is_author(_)    -> "".
+
+c_txt(true) -> "c_txt c_is_author_txt";
+c_txt(_)    -> "c_txt".

File src/redhot2_web_index.erl

         body=[#grid_12 { class=header, body=redhot2_common:header(home) },
               #grid_clear {},
 
-              #grid_6 { alpha=true, body=redhot2_common:left() },
-              #grid_6 { omega=true, body=redhot2_common:right() },
+              #grid_12 { alpha=true, body=redhot2_common:left() },
+%              #grid_8 { alpha=true, body=redhot2_common:left() },
+%              #grid_4 { omega=true, body=redhot2_common:right() },
               #grid_clear {},
               
               #grid_12 { body=redhot2_common:footer() }
 erl \
     -sname ${NAME} \
     -pa ./ebin ${NITROGEN_EBIN} ${SIMPLE_BRIDGE_EBIN} ${NPROCREG_EBIN} \
-        ${EOPENID_EBIN} \
+        ${EOPENID_EBIN} ${REDBUG_EBIN} \
     -pa ./ebin ${NITROGEN_EBIN} ${SIMPLE_BRIDGE_EBIN} ${NPROCREG_EBIN} \
     -eval "application:start(nprocreg)" \
     -eval "application:start(redhot2)"

File templates/grid.html

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
+    <meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1" />
     <title>[[[page:title()]]]</title>
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.0/jquery-ui.min.js"></script>

File www/css/digitalchili.css

 html {
-	font-size:62.5%;
+	font-size:75%;
 }
 
 body {
 	background-color:#eff0e7;
 	margin:0;
 	padding:0;
-	font-family:Georgia, "Times New Roman", Times, serif;
+//	font-family:"Times New Roman", Times, serif;
+	font-family: Verdana,Arial,Helvetica,sans-serif;
 	font-size:1em;
 	line-height:1.6em;
 	color:#595959;
 	color:#d13400;
 }
 
+/* 
+ * List entries
+ */
+.l_date {display:inline-block; color:#d13400; width: 7em; font-weight:normal;}
+.l_date_unpub {display:inline-block; color:blue; width: 7em; font-weight:normal;}
+a:hover.l_date_unpub{text-decoration:underline;}
+.l_author {display:inline-block;margin-left:1em; width: 4em; font-weight:normal;}
+.l_title {margin-left:2em; font-weight:bold; letter-spacing:1px;}
+a.l_title {text-decoration:none;}
+a:hover.l_title {text-decoration:underline;}
+.l_body {margin-left:3em; margin-top:1em;}
+
+/*
+ * Article entry
+ */
+.e_date {font-size:1em; color:#d13400; font-weight:bold;}
+.e_title {font-size:1em; margin-left:2em; font-weight:bold; letter-spacing:1px; color:black;}
+.e_author {float:right; font-size:0.9em; margin:-3em 3em 3em -3em; font-weight:normal;}
+
+code {font-size: 110%;line-height:90%;}
+
+
 
 #header h1 {
 	margin:70px 0 0 0;