Journal

Contents

1. Introduction

A Journal pattern for QM.  Based on Blog.r by Carl Sassenrath.  Requires the User Management pattern.

2. Model

2.1. models/journal.r

REBOL [
Title: "Blog Entries Database"
Author: "Christopher Ross-Gill"
Type: 'roughcut
]
 
locate: func [id][pad id 4]
 
record: make record [
on-create: does [
set 'date now
set 'title ""
set 'comments 0
set 'tags []
]
 
title: link: when?: next?: previous?: none
 
on-load: does [
title: get 'title
link: pad id 4
when?: get 'date
next?: if 1 < length? owner [pad id + 1 4]
previous?: if id > 1 [pad id - 1 4]
]
 
insert-post: injects [
title: string! length is between [1 52] else "No Title"
date: date! else "Journal entries require a date"
text: string! length is less-than 150'000
]
 
insert-input: injects [title: opt string! date: opt string! text: opt string!]
 
on-save: has [text][
if text: get 'text [
unset 'text
write path/entry.txt text
]
]
 
get-text: does [
either new? [""][
any [read path/entry.txt ""]
]
]
 
comment-count: comments?: has [out /short][
pluralize either short ["Cmt"]["Comment"] any [get 'comments 0]
]
 
age?: does [
how-far? any [get 'date now/date]
]
 
rank: 0
 
finds?: func [query /local content] [
; Show the results of a blog search, listed by search-hit rank:
unless string? query [return none]
 
rank: 0
query: parse query none
 
foreach term query [
if find/any title term [rank: rank + 4]
content: get-text
while [content: find/any/tail content term][rank: rank + 1]
]
 
rank > 0
]
]
 
how-far?: func [
"Convert date/time to a friendly format."
[catch] date [date!]
/local diff
][
diff: now/date - date/date
return case [
diff < 2 ["Today"]
diff < 3 ["Yesterday"]
diff < 7 ["This week"]
diff < 14 ["This fortnight"]
date/month = now/month ["This month"]
date/month = (now/month - 1) ["Last month"]
date/month = (now/month + 11) ["Last month"]
date/year = now/year ["This year"]
date/year = (now/year - 1) ["Last year"]
date ["A while back"]
]
]

2.2. models/discussions.r

REBOL [
title: "Discussions Model"
type: 'roughcut
]
 
record: make record [
on-create: does [
set 'count 0
set 'initiated now
set 'status true
]
 
on-save: does [
set 'last-comment now
]
 
cache: none
 
comments: does [
cache: map any [load root/comments.r []] :construct
]
 
update: does [
unless cache [exit]
save root/comments.r map/only cache :third
]
 
add: func [user content][
add-comment self user content
]
 
hide: func [cmt][
hide-comment self cmt
]
]
 
comment!: context [
title: body: author: date: weight: status: editorial: none
]
 
commit: func [discussion block [block!] /local status][
with discussion [
loop 30 [
if status: not exists? root/lock.r [break]
wait 0.005
]
 
either status [
touch root/lock.r
with discussion block
delete root/lock.r
][
delete root/lock.r
make error! "Database Locked"
]
]
]
 
add-comment: func [discussion user content][
unless discussion/get 'status [return none]
 
if content: import content [
title: string! length is between 1x32
body: string! length is less-than 8'000
][
content: make comment! [
title: content/title
body: content/body
author: user/id
date: now
status: true
editorial: user/has-role? 'editor
]
 
commit discussion [
append comments content
update
set 'count 1 + get 'count
store
cache
]
]
]
 
hide-comment: func [discussion cmt [integer!]][
unless all [cmt > 0 cmt <= discussion/get 'count][
return none
]
 
commit discussion [
cmt: pick comments cmt
cmt/status: false
update
cache
]
]

3. Controller

3.1. controllers/journal.r

REBOL [
Title: "QM Journal"
Type: 'controller
Default: "main"
Template: %templates/qm.rsp
]
 
event "prepare" does [
id: edit: none date: now
format: func [text][
rejoin ["<p>" replace/all copy sanitize text newline <br /> "</p>"]
]
]
 
protect "new" "create" "edit" "store" (not user/has-role? 'editor) [
redirect-to %/user/sign-in
]
 
action "main" does [
title: header/title
; require %markup/textile.r format: :textilize
reverse entries: copy skip tail journal -2
date: now + 1
]
 
action "index" [page: opt integer!] does [
entries: paginate journal page
title: reform ["Page" entries/current "of" entries/last]
]
 
action "show" [id: integer!] does [
entry: select journal id
; require %markup/textile.r format: :textilize
 
if assert-all [
entry [render/status "Not Found" 404]
][
title: entry/title
]
]
 
action "new" does [
entry: select journal 'new [
title: "New Journal Entry"
]
]
 
action "create" does [
entry: select journal 'new
details: get-param/body 'entry
 
either entry/insert-post details [
entry/set 'id 1 + length? journal
entry/set 'author user/id
entry/store
redirect-to journal/show/(entry/id)
][
entry/insert-input details
render %new.rsp
]
]
 
action "edit" [id: integer!] does [
either entry: select journal id [
title: entry/title
][
title: "Not Found"
render/status "Not Found" 404
]
]
 
action "store" [id: integer!] does [
entry: select journal id
details: get-param/body 'entry
 
if assert-all [
entry [
title: "Not Found"
render/status "Not Found" 404
]
entry/insert-post details [
title: entry/title
entry/insert-input details
render %edit.rsp
]
][
entry/set 'editor user/id
entry/store
redirect-to journal/show/(entry/id)
]
]
 
action "search" does [
title: "Search Results"
entries: select journal [finds? get-param 'q]
sort/compare entries func [a b][a/rank > b/rank]
]

4. Views

4.1. views/journal/main.rsp

<% if user/has-role? 'editor [ %><ul>
<li><a href="/journal/new">Create</a></li>
</ul>
<% ] %><div class="hfeed"><%
 
foreach entry entries [
if date <> date: entry/when?/date [
 
%>
<h2><%= form-date date "%A, %B %e%i, %Y" %></h2><%
 
]
 
%>
<div class="hentry">
<h3 class="entry-title"><%! a journal/show/(entry/link) %><%= sanitize entry/title %></a></h3>
<div class="entry-content">
<%= format entry/get-text %>
</div>
</div><%
 
]
 
%>
</div>

4.2. views/journal/index.rsp

<%! form get %/journal/search %><fieldset>
<legend>Search</legend>
<input type="text" size="25" name="q"></p>
</fieldset></form>
 
<p><a href="/journal">Main page</a>
|| <a href="/journal/index">Index</a><% if entries/previous [ %>
|| <%! a journal/index/(entries/previous) %>Previous Page</a><% ] if entries/next [ %>
|| <%! a journal/index/(entries/next) %>Next Page</a><% ] %></p>
 
<table class="index"><% foreach entry entries/records [ %><tr>
<td><%= form-date/gmt entry/get 'date "%e-%b-%Y" %></td>
<td><%! a journal/show/(entry/link) %><%= entry/title %></a></td>
<td>[<%= entry/link %>]</td>
<td><%= entry/comment-count/short %></td>
</tr><% ] %></table>

4.3. views/journal/show.rsp

<div class="hfeed">
<div class="hentry" id="post-<%= entry/link %>">
 
<h3>Article #<%= entry/link %>&#8212;<%= form-date entry/when? "%A, %B %e%i, %Y" %></h3>
 
<div class="entry-content">
<%= format entry/get-text %>
</div>
 
<p><a href="/journal">Main page</a>
|| <a href="/journal/index">Index</a><% if user/has-role? 'editor [ %>
|| <%! a journal/edit/(entry/link) %>Edit</a><% ] if entry/previous? [ %>
|| <%! a journal/show/(entry/previous?) %>Previous</a><% ] if entry/next? [ %>
|| <%! a journal/show/(entry/next?) %>Next</a><% ] %>
|| <%! a journal/comments/(entry/link) %><%= entry/comments? %></a></p>
 
</div>
</div>

4.4. views/journal/edit.rsp

<h2>Edit Entry #<%= entry/link %></h2>
<%! form journal/store/(entry/link) %><fieldset>
<legend>Edit Journal Entry</legend>
<table><tr>
<th><label for="entry/date">Date:</label></th>
<td><%! field entry/date (form entry/get 'date) %></td>
</tr><tr>
<th><label for="entry/title">Title:</label></th>
<td><%! field entry/title (entry/get 'title) %></td>
</tr><tr>
<th><label for="entry/text">Text:</label></th>
<td><%! area /mine entry/text (entry/get-text) 25x10 %></td>
</tr><tr>
<td></td>
<td><%! submit do "Save" %>
</tr></table>
</fieldset></form>

4.5. views/journal/new.rsp

<%! form %/journal/create %><fieldset>
<legend>Blog Entry</legend>
<table><tr>
<th><label for="entry.date">Date:</label></th>
<td><%! field entry/date (form entry/get 'date) %></td>
</tr><tr>
<td><label for="entry.title">Title:</label></td>
<td><%! field entry/title (entry/get 'title) %></td>
</tr><tr>
<th><label for="entry.text">Text:</label></td>
<td><%! area entry/text (entry/get 'text) 25x10 %></td>
</tr><tr>
<td></td>
<td><%! submit do "Add New Post" %>
</tr></table>
</fieldset></form>

4.6. views/journal/search.rsp

<%! form get %/journal/search %><fieldset>
<legend>Search</legend>
<table><tr><input type="text" size="25" name="q"></tr></table>
</fieldset></form>
 
<p><a href="/journal">Main page</a>
|| <a href="/journal/index">Index</a></p>
 
<% either empty? entries [ %><p>No Matches</p><% ][
%><table class="index"><% foreach entry entries [ %><tr>
<td><%= entry/age? %></td>
<td><%! a journal/show/(entry/link) %><%= entry/title %></a></td>
<td>[<%= entry/link %>]</td>
<td><%= entry/comment-count/short %></td>
</tr><% ] %></table><% ] %>