summaryrefslogtreecommitdiff
path: root/main.ur
blob: 4110c96eab9d5e07798076a7e73d932898c74cef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
(* Copyright 2015 the Massachusetts Institute of Technology

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License.  You may obtain a copy of the
License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.  See the License for the
specific language governing permissions and limitations under the License. *)

structure Configuration = struct
  val main_page_name = "Main Page"
  val wiki_title = "UrWiki demo"
end

type id = int

table commit : { Id : id,
                 Created : time,
                 Content : string }
  PRIMARY KEY Id
sequence commit_id_next

table article : { Title : string,
                  Head : id }
  PRIMARY KEY Title,
  CONSTRAINT Head
    FOREIGN KEY Head REFERENCES commit(Id)

datatype mode = View | Edit
val eq_mode =
  mkEq (fn x y =>
          case (x, y) of
              (View, View) => True
            | (Edit, Edit) => True
            | _ => False)

fun only_in mode_source target_mode =
  current_mode <- signal mode_source;
  return (if current_mode = target_mode
          then null
          else Style.invisible)

fun commit_article title text : transaction unit =
  id <- nextval commit_id_next;
  creation_time <- now;
  dml (INSERT INTO commit (Id, Created, Content)
       VALUES ({[id]}, {[creation_time]}, {[text]}));
  (* TODO(bbaren): This is ugly.  Use CAS instead? *)
  sql_error <- tryDml (INSERT INTO article (Title, Head)
                       VALUES ({[title]}, {[id]}));
  case sql_error of
      None =>
        (* We created a new article. *)
        return ()
    | Some _ =>
        (* The article already exists. *)
        dml (UPDATE article
             SET Head = {[id]}
             WHERE Title = {[title]})

fun wiki requested_article_title =
  (* Look up the article. *)
  extant_articles <-
    queryL (SELECT article.Title, commit.Content
             FROM article LEFT JOIN commit ON article.Head = commit.Id
             WHERE article.Title = {[requested_article_title]});
  let
    val article =
      case extant_articles of
          Nil => {Title = requested_article_title, Body = "Not found."}
        | art :: Nil => {Title = art.Article.Title,
                         Body = show art.Commit.Content}
        | _ :: _ :: _ => error
                           <xml>
                             Multiple articles with title
                             ‘{[requested_article_title]}’
                           </xml>
  in
    (* Stuff the article text in a source so we can live-update it as the user
    edits. *)
    article_body_source <- source article.Body;
    (* Initially, we're in View mode, and we can switch to Edit mode on user
    request. *)
    page_mode <- source View;
    return
      <xml>
        <head>
          <title>
            {[if article.Title = Configuration.main_page_name
              then Configuration.wiki_title
              else article.Title ^ " – " ^ Configuration.wiki_title]}
          </title>
          <link rel="stylesheet" href="/urwiki.css" />
        </head>
        <body>
          (* Page headings *)
          <div>
            <h1>{[Configuration.wiki_title]}</h1>
            <ul>
              <li>
                <a link={wiki Configuration.main_page_name}>
                  {[Configuration.main_page_name]}
                </a>
              </li>
            </ul>
          </div>
          (* Article *)
          <dyn signal={text <- signal article_body_source;
                       return <xml>{[text]}</xml>} /><br />
          (* Editing panel *)
          <div>
            (* Controls for View mode *)
            <div dynClass={only_in page_mode View}>
              <button value="Edit" onclick={fn _ => set page_mode Edit} />
            </div>
            (* Controls for Edit mode *)
            <div dynClass={only_in page_mode Edit}>
              <ctextarea source={article_body_source} /><br />
              <button
                 value="Commit"
                 onclick={fn _ =>
                            text <- get article_body_source;
                            rpc (commit_article article.Title text);
                            set page_mode View} />
            </div>
          </div>
        </body>
      </xml>
  end