From ae40d142ea634f9bd4e64f4f5d333021eefaf05b Mon Sep 17 00:00:00 2001 From: rich Date: Thu, 7 Oct 2004 16:54:24 +0000 Subject: [PATCH] Monthly calendar view. --- html/_css/standard.css | 73 +++++++++++++- scripts/.depend | 4 + scripts/Makefile | 8 +- scripts/cocanwiki.ml | 6 +- scripts/cocanwiki_date.ml | 8 +- scripts/cocanwiki_ext_calendar.ml | 199 ++++++++++++++++++++++++++++++++++++++ scripts/page.ml | 129 ++++++++++++++++-------- scripts/stats.ml | 24 ++--- templates/calendar_day.html | 0 templates/calendar_month.html | 27 ++++++ templates/calendar_year.html | 0 templates/page.html | 2 +- 12 files changed, 419 insertions(+), 61 deletions(-) create mode 100644 scripts/cocanwiki_ext_calendar.ml create mode 100644 templates/calendar_day.html create mode 100644 templates/calendar_month.html create mode 100644 templates/calendar_year.html diff --git a/html/_css/standard.css b/html/_css/standard.css index b2fa24e..70c16d7 100644 --- a/html/_css/standard.css +++ b/html/_css/standard.css @@ -1,5 +1,5 @@ /* Stylesheet for COCANWIKI. - * $Id: standard.css,v 1.7 2004/10/05 14:51:11 rich Exp $ + * $Id: standard.css,v 1.8 2004/10/07 16:54:24 rich Exp $ */ body { @@ -275,3 +275,74 @@ div#search_div { top: 0.95em; text-align: right; } + +/* WikiForms - hide the edit link for the top section. */ +div#form_div p.edit_link { + display: none; +} + +/* Calendar extension. */ +table.cal_month { + border-collapse: collapse; + border: 1px solid #eee; + width: 90%; + margin-left: 5%; + margin-right: 5%; +} + +table.cal_month th.cal_month_header { + background-color: #eef; +} + +table.cal_month th.cal_month_header a.cal_month_left { + margin-right: 2em; +} + +table.cal_month th.cal_month_header a.cal_month_right { + margin-left: 2em; +} + +table.cal_month td.cal_month_events { +} + +table.cal_month td.cal_month_events ul { + list-style: none; + padding: 0px; + margin: 0px; +} + +table.cal_month td.cal_month_events li { + display: inline; + margin-right: 2em; +} + +table.cal_month tr.cal_month_row { + border: 1px solid #eef; + font-size: 0.7em; +} + +table.cal_month tr.cal_month_weekend { + background-color: #fef; +} + +table.cal_month tr.cal_month_row th { + text-align: right; + background-color: #eef; + border: 1px solid #fff; + padding-right: 1em; + width: 4em; +} + +table.cal_month tr.cal_month_row td { +} + +table.cal_month tr.cal_month_row ul { + list-style: none; + padding: 0px; + margin: 0px; +} + +table.cal_month tr.cal_month_row li { + display: inline; + margin-right: 2em; +} diff --git a/scripts/.depend b/scripts/.depend index 87ebd07..dd737e6 100644 --- a/scripts/.depend +++ b/scripts/.depend @@ -10,6 +10,10 @@ cocanwiki_create_host.cmo: cocanwiki_create_host.cmi cocanwiki_create_host.cmx: cocanwiki_create_host.cmi cocanwiki_diff.cmo: cocanwiki_files.cmo cocanwiki_diff.cmx: cocanwiki_files.cmx +cocanwiki_ext_calendar.cmo: cocanwiki.cmo cocanwiki_date.cmo \ + cocanwiki_strings.cmo cocanwiki_template.cmi +cocanwiki_ext_calendar.cmx: cocanwiki.cmx cocanwiki_date.cmx \ + cocanwiki_strings.cmx cocanwiki_template.cmx cocanwiki_images.cmo: cocanwiki_files.cmo cocanwiki_strings.cmo \ cocanwiki_images.cmi cocanwiki_images.cmx: cocanwiki_files.cmx cocanwiki_strings.cmx \ diff --git a/scripts/Makefile b/scripts/Makefile index 8022749..bb56bd0 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -1,10 +1,11 @@ # Makefile for COCANWIKI. -# $Id: Makefile,v 1.32 2004/10/06 10:34:29 rich Exp $ +# $Id: Makefile,v 1.33 2004/10/07 16:54:24 rich Exp $ include ../Makefile.config OCAMLC := ocamlc -OCAMLCFLAGS := -w s -I +apache -I +pcre -I +dbi -I +extlib +# XXX Move GregorianDate out of +merjis. +OCAMLCFLAGS := -w s -I +apache -I +pcre -I +dbi -I +extlib -I +merjis CPP := cpp LIB_OBJS := \ @@ -21,7 +22,8 @@ LIB_OBJS := \ cocanwiki_emailnotify.cmo \ wikilib.cmo \ cocanwiki_links.cmo \ - cocanwiki_create_host.cmo + cocanwiki_create_host.cmo \ + cocanwiki_ext_calendar.cmo OBJS := change_password.cmo \ change_password_form.cmo \ diff --git a/scripts/cocanwiki.ml b/scripts/cocanwiki.ml index 981379f..5664582 100644 --- a/scripts/cocanwiki.ml +++ b/scripts/cocanwiki.ml @@ -1,7 +1,7 @@ (* COCANWIKI - a wiki written in Objective CAML. * Written by Richard W.M. Jones . * Copyright (C) 2004 Merjis Ltd. - * $Id: cocanwiki.ml,v 1.14 2004/10/04 15:19:56 rich Exp $ + * $Id: cocanwiki.ml,v 1.15 2004/10/07 16:54:24 rich Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -218,3 +218,7 @@ let linkname_of_sectionname str = if not (isalnum str.[i]) then str.[i] <- '_' done; str + +(* List of extensions currently registered. *) +type extension_t = Dbi.connection -> int -> string -> string +let extensions = ref ([] : (string * extension_t) list) diff --git a/scripts/cocanwiki_date.ml b/scripts/cocanwiki_date.ml index cd7fe01..42edc44 100644 --- a/scripts/cocanwiki_date.ml +++ b/scripts/cocanwiki_date.ml @@ -1,7 +1,7 @@ (* COCANWIKI - a wiki written in Objective CAML. * Written by Richard W.M. Jones . * Copyright (C) 2004 Merjis Ltd. - * $Id: cocanwiki_date.ml,v 1.4 2004/09/09 12:21:22 rich Exp $ + * $Id: cocanwiki_date.ml,v 1.5 2004/10/07 16:54:24 rich Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +35,12 @@ let short_month = function | 9 -> "Sep" | 10 -> "Oct" | 11 -> "Nov" | 12 -> "Dec" | _ -> invalid_arg "short_month" +let long_month = function + | 1 -> "January" | 2 -> "February" | 3 -> "March" | 4 -> "April" + | 5 -> "May" | 6 -> "June" | 7 -> "July" | 8 -> "August" + | 9 -> "September" | 10 -> "October" | 11 -> "November" | 12 -> "December" + | _ -> invalid_arg "short_month" + (* Generate a printable datestamp for pages. *) let printable_date' date = sprintf "%d %s %04d" date.Dbi.day (short_month date.Dbi.month) date.Dbi.year diff --git a/scripts/cocanwiki_ext_calendar.ml b/scripts/cocanwiki_ext_calendar.ml new file mode 100644 index 0000000..2e35698 --- /dev/null +++ b/scripts/cocanwiki_ext_calendar.ml @@ -0,0 +1,199 @@ +(* COCANWIKI - a wiki written in Objective CAML. + * Written by Richard W.M. Jones . + * Copyright (C) 2004 Merjis Ltd. + * $Id: cocanwiki_ext_calendar.ml,v 1.1 2004/10/07 16:54:24 rich Exp $ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + *) + +open Apache +open Registry +open Cgi +open Printf + +open ExtList + +open GregorianDate + +open Cocanwiki +open Cocanwiki_template +open Cocanwiki_strings +open Cocanwiki_date + +let day_template = _get_template "calendar_day.html" +let month_template = _get_template "calendar_month.html" +let year_template = _get_template "calendar_year.html" + +let rec range a b = + if a <= b then + a :: range (a+1) b + else + [] + +let extension (dbh : Dbi.connection) hostid url = + (* Validate a date in the form "yyyy[/mm[/dd]]". Returns a (yyyy, mm, dd) + * tuple with missing fields set to 0. If the string doesn't parse or the + * date isn't valid, then raises Not_found. + *) + let valid_date str = + if String.length str = 4 && + isdigit str.[0] && isdigit str.[1] && + isdigit str.[2] && isdigit str.[3] then ( + let yyyy = int_of_string (String.sub str 0 4) in + (yyyy, 0, 0) + ) + else if String.length str = 7 && + isdigit str.[0] && isdigit str.[1] && + isdigit str.[2] && isdigit str.[3] && + str.[4] = '/' && + isdigit str.[5] && isdigit str.[6] then ( + let yyyy = int_of_string (String.sub str 0 4) in + let mm = int_of_string (String.sub str 5 2) in + if mm >= 1 && mm <= 12 then (yyyy, mm, 0) else raise Not_found + ) + else if String.length str = 10 && + isdigit str.[0] && isdigit str.[1] && + isdigit str.[2] && isdigit str.[3] && + str.[4] = '/' && + isdigit str.[5] && isdigit str.[6] && + str.[7] = '/' && + isdigit str.[8] && isdigit str.[9] then ( + let yyyy = int_of_string (String.sub str 0 4) in + let mm = int_of_string (String.sub str 5 2) in + let dd = int_of_string (String.sub str 8 2) in + let date = (yyyy, mm, dd) in + if GregorianDate.check_date date then date else raise Not_found + ) + else + raise Not_found + in + + (* From the links table, find all links to this page, or sub-calendar pages. + * This query overselects. We then filter the real pages in OCaml. + *) + let sth = + dbh#prepare_cached + "select li.from_url, p.title, li.to_url + from links li, pages p + where li.hostid = ? and li.to_url like ? + and li.hostid = p.hostid and li.from_url = p.url" in + sth#execute [`Int hostid; `String (url ^ "%")]; + + let pages = + let results = + sth#map (function [`String from_url; `String title; `String to_url] -> + from_url, title, to_url + | _ -> assert false) in + List.filter_map + (fun (from_url, title, to_url) -> + try let date = valid_date to_url in Some (date, title, from_url) + with Not_found -> None) results in + let pages = List.sort pages in + + (* Validate the date in the URL itself. *) + let date = + try Some (valid_date url) + with Not_found -> None in + + match date with + | None -> (* Not a valid date. *) + "

" ^ url ^ " is not an actual date.

" + | Some (yyyy, 0, 0) -> (* Year view. *) + let template = year_template in + failwith "not impl"; + + + + + + + + template#to_string + + | Some (yyyy, mm, 0) -> (* Month view. *) + let template = month_template in + + template#set "month_name" (long_month mm); + template#set "yyyy" (string_of_int yyyy); + template#set "mm" (sprintf "%02d" mm); + + let prev_yyyy, prev_mm = + if mm = 1 then yyyy - 1, 12 + else yyyy, mm - 1 in + let next_yyyy, next_mm = + if mm = 12 then yyyy + 1, 1 + else yyyy, mm + 1 in + template#set "prev_yyyy" (string_of_int prev_yyyy); + template#set "prev_mm" (sprintf "%02d" prev_mm); + template#set "next_yyyy" (string_of_int next_yyyy); + template#set "next_mm" (sprintf "%02d" next_mm); + + (* Get all monthly events and all daily events. *) + let monthly_events, daily_events = + List.partition (function ((_, _, 0), _, _) -> true | _ -> false) + pages in + + (* Table of monthly events. *) + let table = + List.map (fun (_, title, page) -> + [ "title", Template.VarString title; + "page", Template.VarString page ]) monthly_events in + template#table "monthly_events" table; + + (* How many days in this month? *) + let max_dd = GregorianDate.days_in_month yyyy mm in + let days = range 1 max_dd in + + let table = + List.map (fun dd -> + let events = + List.filter (fun ((_, _, d), _, _) -> d = dd) + daily_events in + let table = + List.map (fun (_, title, page) -> + [ "title", Template.VarString title; + "page", Template.VarString page ]) + events in + let is_weekend = + GregorianDate.day_of_week (yyyy, mm, dd) >= 6 in + [ "dd", Template.VarString (sprintf "%02d" dd); + "is_weekend", Template.VarConditional is_weekend; + "events", Template.VarTable table ]) + days in + template#table "days" table; + + template#to_string + + | Some (yyyy, mm, dd) -> (* Single day view. *) + let template = day_template in + failwith "not impl"; + + + + + + + + + + + + + template#to_string + +(* Register the extension. *) +let () = + extensions := ("calendar", extension) :: !extensions diff --git a/scripts/page.ml b/scripts/page.ml index 52debcc..7e2f02d 100644 --- a/scripts/page.ml +++ b/scripts/page.ml @@ -1,7 +1,7 @@ (* COCANWIKI - a wiki written in Objective CAML. * Written by Richard W.M. Jones . * Copyright (C) 2004 Merjis Ltd. - * $Id: page.ml,v 1.25 2004/10/04 15:19:56 rich Exp $ + * $Id: page.ml,v 1.26 2004/10/07 16:54:24 rich Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -156,14 +156,33 @@ let run r (q : cgi) (dbh : Dbi.connection) hostid String.concat "" (List.concat [ head ; body ; tail ]) in + (* Check the templates table for extensions. *) + let get_extension url = + let sth = dbh#prepare_cached "select extension from templates + where ? ~ url_regexp + order by ordering + limit 1" in + sth#execute [`String url]; + + try + let name = sth#fetch1string () in + Some (List.assoc name !extensions) + with + Not_found -> None + in + (* This code generates ordinary pages. *) let make_page title description pageid last_modified_date has_page_css - version page page' = + version page page' extension = let t = template_page in t#set "title" title; - t#set "description" description; - t#set "pageid" (string_of_int pageid); - t#set "last_modified_date" (printable_date last_modified_date); + t#set "last_modified_date" last_modified_date; + + (match description with + None -> t#conditional "has_description" false + | Some description -> + t#conditional "has_description" true; + t#set "description" description); if page <> page' then (* redirection *) ( t#set "page" page'; @@ -190,38 +209,56 @@ let run r (q : cgi) (dbh : Dbi.connection) hostid t#conditional "has_stats" has_stats; (* Pull out the sections in this page. *) - let sth = dbh#prepare_cached - "select ordering, sectionname, content, divname - from contents - where pageid = ? - order by ordering" in - sth#execute [`Int pageid]; - let sections = - sth#map - (function [`Int ordering; - (`Null | `String _) as sectionname; - `String content; - (`Null | `String _) as divname] -> - let divname, has_divname = - match divname with - `Null -> "", false - | `String divname -> divname, true in - let sectionname, has_sectionname = - match sectionname with - `Null -> "", false - | `String sectionname -> sectionname, true in - let linkname = linkname_of_sectionname sectionname in - [ "ordering", Template.VarString (string_of_int ordering); - "has_sectionname", Template.VarConditional has_sectionname; - "sectionname", Template.VarString sectionname; - "linkname", Template.VarString linkname; - "content", - Template.VarString - (Wikilib.xhtml_of_content dbh hostid content); - "has_divname", Template.VarConditional has_divname; - "divname", Template.VarString divname ] - | _ -> assert false) in + match pageid with + None -> [] + | Some pageid -> + let sth = dbh#prepare_cached + "select ordering, sectionname, content, divname + from contents where pageid = ? order by ordering" in + sth#execute [`Int pageid]; + + sth#map + (function [`Int ordering; + (`Null | `String _) as sectionname; + `String content; + (`Null | `String _) as divname] -> + let divname, has_divname = + match divname with + `Null -> "", false + | `String divname -> divname, true in + let sectionname, has_sectionname = + match sectionname with + `Null -> "", false + | `String sectionname -> sectionname, true in + let linkname = linkname_of_sectionname sectionname in + [ "ordering", Template.VarString (string_of_int ordering); + "has_sectionname", + Template.VarConditional has_sectionname; + "sectionname", Template.VarString sectionname; + "linkname", Template.VarString linkname; + "content", + Template.VarString + (Wikilib.xhtml_of_content dbh hostid content); + "has_divname", Template.VarConditional has_divname; + "divname", Template.VarString divname ] + | _ -> assert false) in + + (* Call an extension to generate the first section in this page? *) + let sections = + match extension with + None -> sections + | Some extension -> + let content = extension dbh hostid page' in + let section = [ + "ordering", Template.VarString "0"; + "has_sectionname", Template.VarConditional false; + "linkname", Template.VarString ""; + "content", Template.VarString content; + "has_divname", Template.VarConditional true; + "divname", Template.VarString "form_div"; + ] in + section :: sections in t#table "sections" sections; @@ -292,7 +329,7 @@ let run r (q : cgi) (dbh : Dbi.connection) hostid (* Fetch a page by name. This function can give three answers: * (1) Page fetched OK (fetches some details of the page). * (2) Page is a redirect (fetches the name of the redirect page). - * (3) Page not found in database, ie. 404 error. + * (3) Page not found in database, could be template or 404 error. *) (* XXX Should do a case-insensitive matching of URLs, and if the URL differs * in case only should redirect to the lowercase version. @@ -374,12 +411,24 @@ let run r (q : cgi) (dbh : Dbi.connection) hostid ) else match fetch_page page' version allow_redirect with | FPOK (pageid, title, description, last_modified_date, has_page_css)-> - make_page title description pageid last_modified_date has_page_css - version page page' + (* Check if the page is also a template. *) + let extension = get_extension page' in + make_page title (Some description) (Some pageid) + (printable_date last_modified_date) has_page_css + version page page' extension | FPRedirect page' -> loop page' (i+1) | FPNotFound -> - make_404 () + (* Might be a templated page with no content in it. *) + let extension = get_extension page' in + (match extension with + | (Some _) as extension -> + let title = page' in + make_page title None None + "Now" false None page page' + extension + | None -> + make_404 ()) in loop page 0 diff --git a/scripts/stats.ml b/scripts/stats.ml index e92d182..3d29afb 100644 --- a/scripts/stats.ml +++ b/scripts/stats.ml @@ -1,7 +1,7 @@ (* COCANWIKI - a wiki written in Objective CAML. * Written by Richard W.M. Jones . * Copyright (C) 2004 Merjis Ltd. - * $Id: stats.ml,v 1.1 2004/09/23 15:16:21 rich Exp $ + * $Id: stats.ml,v 1.2 2004/10/07 16:54:24 rich Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ open Registry open Cgi open Printf +open GregorianDate + open Cocanwiki open Cocanwiki_template open Cocanwiki_server_settings @@ -41,20 +43,14 @@ let run r (q : cgi) (dbh : Dbi.connection) hostid { hostname = hostname } _ = template#set "stats_page" stats_page; - (* Get the current business week. Tricky - because GregorianDate isn't - * around to help us, it's best to get the database to help us out. - * - * XXX This fails sometimes near New Year. Better to get GD into extlib - * or this codebase. - *) - let sth = - dbh#prepare_cached "select extract (year from current_date) :: int, - extract (week from current_date) :: int" in - sth#execute []; + (* Get the current business week. *) let year, week = - match sth#fetch1 () with - [ `Int year; `Int week ] -> year, week - | _ -> assert false in + let date = + let tm = Unix.gmtime (Unix.time ()) in + (tm.Unix.tm_year + 1900, tm.Unix.tm_mon + 1, tm.Unix.tm_mday) + in + fst (GregorianDate.business_of_standard date) + in template#set "year" (string_of_int year); template#set "week" (string_of_int week); diff --git a/templates/calendar_day.html b/templates/calendar_day.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/calendar_month.html b/templates/calendar_month.html new file mode 100644 index 0000000..8694800 --- /dev/null +++ b/templates/calendar_month.html @@ -0,0 +1,27 @@ + + + + + + + + +::table(days):: + + + + +::end:: +
+ << + ::month_name_html:: ::yyyy:: + >> +
+ +
::dd:: + +
diff --git a/templates/calendar_year.html b/templates/calendar_year.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/page.html b/templates/page.html index 13f69c4..2755987 100644 --- a/templates/page.html +++ b/templates/page.html @@ -3,7 +3,7 @@ ::title_html:: ::if(is_old_version)::::end:: - +::if(has_description)::::end:: ::if(has_host_css)::::end:: -- 1.8.3.1