/* 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 {
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;
+}
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 \
# 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 := \
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 \
(* COCANWIKI - a wiki written in Objective CAML.
* Written by Richard W.M. Jones <rich@merjis.com>.
* 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
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)
(* COCANWIKI - a wiki written in Objective CAML.
* Written by Richard W.M. Jones <rich@merjis.com>.
* 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
| 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
--- /dev/null
+(* COCANWIKI - a wiki written in Objective CAML.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * 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. *)
+ "<p>" ^ url ^ " is not an actual date.</p>"
+ | 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
(* COCANWIKI - a wiki written in Objective CAML.
* Written by Richard W.M. Jones <rich@merjis.com>.
* 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
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';
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;
(* 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.
) 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
(* COCANWIKI - a wiki written in Objective CAML.
* Written by Richard W.M. Jones <rich@merjis.com>.
* 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
open Cgi
open Printf
+open GregorianDate
+
open Cocanwiki
open Cocanwiki_template
open Cocanwiki_server_settings
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);
--- /dev/null
+<table class="cal_month">
+<tr>
+ <th colspan="2" class="cal_month_header">
+ <a href="/::prev_yyyy::/::prev_mm::" class="cal_month_left" title="Previous month"><<</a>
+ ::month_name_html:: <a href="/::yyyy::">::yyyy::</a>
+ <a href="/::next_yyyy::/::next_mm::" class="cal_month_right" title="Next month">>></a>
+ </th>
+</tr>
+<tr>
+ <td></td>
+ <td class="cal_month_events">
+ <ul>
+ ::table(monthly_events)::<li><a href="/::page::">::title_html::</a></li>::end::
+ </ul>
+ </td>
+<tr>
+::table(days)::
+<tr class="cal_month_row::if(is_weekend):: cal_month_weekend::end::">
+ <th> <a href="/::yyyy::/::mm::/::dd::">::dd::</a> </th>
+ <td>
+ <ul>
+ ::table(events)::<li><a href="/::page::">::title_html::</a></li>::end::
+ </ul>
+ </td>
+</tr>
+::end::
+</table>
<head>
<title>::title_html::</title>
::if(is_old_version)::<meta name="robots" content="noindex,nofollow"/>::end::
-<meta name="description" content="::description_html_tag::" />
+::if(has_description)::<meta name="description" content="::description_html_tag::" />::end::
<meta name="author" content="http://www.merjis.com/" />
<link rel="stylesheet" href="::theme_css_html_tag::" type="text/css" title="Standard"/>
::if(has_host_css)::<link rel="stylesheet" href="/_global.css" type="text/css" title="Standard"/>::end::