Merge branch 'feature/4' into develop

This commit is contained in:
simon 2016-09-11 17:40:04 +01:00
commit ad152b336a
17 changed files with 457 additions and 216 deletions

View file

@ -1 +1 @@
{:admin {:password "admin", :email "admin@localhost"}}
{:admin {:admin true, :email "info@weft.scot", :password "admin"}}

View file

@ -1 +1 @@
This is the page linked to from the left bar.
This is the page linked to from the [[Introduction]] page.

View file

@ -8,18 +8,16 @@ Smeagol is now a fully working small Wiki engine, and meets my own immediate nee
things which could be improved - see **TODO** list below - but it works now and doesn't seem to have any major problems.
## Markup syntax
Smeagol uses the Markdown format as provided by [markdown-clj](https://github.com/yogthos/markdown-clj), with the addition that anything enclosed in double square brackets, \[\[like this\]\], will be treated as a link into the wiki itself.
Smeagol uses the Markdown format as provided by [markdown-clj](https://github.com/yogthos/markdown-clj), with the addition that anything enclosed in double square brackets, \[\[like this\]\], will be treated as a link into the wiki itself. Here's an example [[Internal Link]].
## Security and authentication
Security is now greatly improved. There is a file called *passwd* in the *resources* directory, which contains a clojure map which maps usernames to maps with plain-text passwords and emails thus:
{:admin {:password "admin" :email "admin@localhost"}
{:admin {:password "admin" :email "admin@localhost" :admin true}
:adam {:password "secret" :email "adam@localhost"}}
that is to say, the username is a keyword and the corresponding password is a string. However, since version 0.5.0, users can now change their own passwords, and when the user changes their password their new password is encrypted using the [scrypt](http://www.tarsnap.com/scrypt.html) one-way encryption scheme. The password file is now no longer either in the *resources/public* directory so cannot be downloaded through the browser, nor in the git archive to which the Wiki content is stored, so that even if that git archive is remotely clonable an attacker cannot get the password file that way.
There's still no mechanism to add a new user to the system through the user interface; you do still have to do that by editing the password file in an editor.
## Images
Smeagol does not currently have any mechanism to upload images. You can, however, link to images already available on the web, like this:

View file

@ -20,6 +20,8 @@
USA.
# The Stylesheet
## html elements generally in alphabetic order
*/
body {
@ -28,6 +30,18 @@ body {
font-family: sans-serif;
}
del {
color: red;
}
div.content, form, p, pre, h1, h2, h3, h4, h5 {
padding: 0.25em 5%;
}
dl, menu, ol, table, ul {
margin: 0.25em 5%;
}
/* footer of the page - not-editable, provided by Smeagol */
footer {
border-top: thin solid gray;
@ -52,6 +66,9 @@ footer div {
padding: 0.1em;
}
form {
border: thin solid silver;
}
/* header for all pages in the Wiki - editable, provided by users. */
header {
@ -69,7 +86,66 @@ header img {
float: right;
}
/* ids generally in document order */
input {
background-color: white;
}
input.action {
background-color: green;
}
input.action-dangerous {
color: white;
background-color: red;
}
input.required:after {
content: " \*";
color: red;
}
ins {
color: green;
}
label {
width: 20%;
min-width: 20em;
border-right: thin solid gray;
display: inline-block;
}
menu li {
display: inline;
}
menu li::before {
content: "|| ";
}
table {
border: 2px solid black;
border-collapse: collapse;
}
table.music-ruled tr:nth-child(odd) {
background-color: silver;
}
th, td {
text-align: left;
vertical-align: top;
padding: 0.15em 1.5em;
border: 1px solid gray;
}
th {
background-color: silver;
}
/*
## ids generally in document order
*/
/* top-of-page navigation, not editable, provided by Smeagol */
#nav{
@ -180,22 +256,22 @@ li.nav-item a:active { background: gray; color: white; }
display: block;
}
.change {
background-color: rgb( 223, 223, 223);
border: thin solid silver;
}
.error {
width: 100%;
background-color: red;
color: white;
border: thin solid maroon;
}
.message {
border: thin solid red;
color: darkgreen;
background-color: silver;
border: thin solid lime;
}
.minor-controls {
@ -233,75 +309,3 @@ li.nav-item a:active { background: gray; color: white; }
margin: 0;
}
form {
border: thin solid silver;
}
del {
color: red;
}
div.content, form, p, pre, h1, h2, h3, h4, h5 {
padding: 0.25em 5%;
}
dl, menu, ol, table, ul {
margin: 0.25em 5%;
}
input {
background-color: white;
}
input.action {
background-color: green;
}
input.action-dangerous {
color: white;
background-color: red;
}
input.required:after {
content: " \*";
color: red;
}
ins {
color: green;
}
label {
width: 20%;
min-width: 20em;
border-right: thin solid gray;
}
menu li {
display: inline;
}
menu li::before {
content: "|| ";
}
table {
border: 2px solid black;
border-collapse: collapse;
}
table.music-ruled tr:nth-child(odd) {
background-color: silver;
}
th, td {
text-align: left;
vertical-align: top;
padding: 0.15em 1.5em;
border: 1px solid gray;
}
th {
background-color: silver;
}

1
resources/public/vendor/README.md vendored Normal file
View file

@ -0,0 +1 @@
This folder must exist in order that the Bower package manager can deploy JavaScript packages to it.

View file

@ -18,6 +18,9 @@
<img id="nav-icon" src="{{servlet-context}}/img/threelines.png" alt="Menu"/>
<ul id="nav-menu" class="nav">
<li class="{{wiki-selected}}"><a href="{{servlet-context}}/">Home</a></li>
{% if admin %}
<li class="{{admin-selected}}"><a href="{{servlet-context}}/edit-users">Edit users</a></li>
{% endif %}
<li class="{{auth-selected}}"><a href="{{servlet-context}}/auth">
{% if user %}
Log out
@ -28,12 +31,6 @@
</div>
<h1>{{title}}</h1>
{{header|safe}}
</header>
<div id="side-bar" class="wiki">
{{side-bar|safe}}
</div>
<div id="main-container" class="container">
{% if message %}
<div id="message">
<p class="message">{{message}}</p>
@ -44,7 +41,12 @@
<p class="error">{{error}}</p>
</div>
{% endif %}
</header>
<div id="side-bar" class="wiki">
{{side-bar|safe}}
</div>
<div id="main-container" class="container">
{% block content %}
{% endblock %}
</div>
@ -62,8 +64,8 @@
<footer>
<div id="credits">
<div>
One Wiki to rule them all ||
<img height="16" width="16" alt="one wiki to rule them all" src="img/smeagol.png"/>Smeagol wiki engine {{version}} ||
<img height="16" width="16" alt="one wiki to rule them all" src="img/smeagol.png"/>One Wiki to rule them all ||
Smeagol wiki engine {{version}} ||
<img height="16" width="16" alt="The Web Engineering Factory &amp; Toolworks" src="http://www.weft.scot/images/weft.logo.64.png"> Developed by <a href="http://www.weft.scot/">WEFT</a>
</div>
<div>

View file

@ -0,0 +1,32 @@
{% extends "templates/base.html" %}
{% block content %}
<div id="content" class="edit">
<form action="{{servlet-context}}/edit-user" method="POST">
<p class="widget">
<label for="target">Username</label>
<input type="text" name="target" id="target" value="{{target}}" required/>
</p>
<p class="widget">
<label for="pass1">New password</label>
<input name="pass1" id="pass1" type="password"/>
</p>
<p class="widget">
<label for="pass2">And again</label>
<input name="pass2" id="pass2" type="password"/>
</p>
<p class="widget">
<label for="email">Email address</label>
<input name="email" id="email" type="text" value="{{details.email}}" required/>
</p>
<p class="widget">
<label for="admin">Is administrator?</label>
<input name="admin" id="admin" type="checkbox" {% if details.admin %}checked{% endif %}/>
</p>
<p class="widget">
<label for="submit">When you have finished editing</label>
<input name="submit" id="submit" type="submit" class="action" value="Save!"/>
</p>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends "templates/base.html" %}
{% block content %}
<div id="content">
<table>
<tr>
<th>Edit</th><th>Delete</th>
</tr>
{% for user in users %}
<tr>
<td><a href="edit-user?target={{user}}">Edit {{user}}</a></td>
<td><a href="delete-user?target={{user}}">Delete {{user}}</a></td>
</tr>
{% endfor %}
<tr>
<td><a href="edit-user">Add new user</a></td>
<td></td>
</tr>
</table>
</div>
{% endblock %}

View file

@ -3,20 +3,20 @@
<div id="content" class="auth">
<form action="{{servlet-context}}/passwd" method="POST">
<p class="widget">
<label for="password">Your password</label>
<label for="oldpass">Your password</label>
<input name="oldpass" id="oldpass" type="password" required/>
</p>
<p class="widget">
<label for="password">New password</label>
<label for="pass1">New password</label>
<input name="pass1" id="pass1" type="password" required/>
</p>
<p class="widget">
<label for="password">And again</label>
<label for="pass2">And again</label>
<input name="pass2" id="pass2" type="password" required/>
</p>
<p class="widget">
<label for="submit">To edit this wiki</label>
<input name="action" id="action" type="submit" class="action" value="Login!"/>
<input name="action" id="action" type="submit" class="action" value="Change password!"/>
</p>
</form>
</div>

View file

@ -2,10 +2,12 @@
{% block content %}
<div id="content" class="wiki">
{% if editable %}
<ul class="minor-controls">
<li><a href="{{servlet-context}}/edit?page={{title}}">Edit this page</a></li>
<li><a href="history?page={{page}}">History</a></li>
</ul>
{% endif %}
{{content|safe}}
</div>
{% endblock %}

View file

@ -33,28 +33,50 @@
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; the relative path to the password file.
(def password-file-path (str (io/resource-path) "../passwd"))
(defn- get-users
"Get the whole content of the password file as a clojure map"
[]
(read-string (slurp password-file-path)))
(defn authenticate
"Return `true` if this `username`/`password` pair match, `false` otherwise"
[username password]
(let [path (str (io/resource-path) "../passwd")
users (read-string (slurp path))
user ((keyword username) users)]
(timbre/info (str "Authenticating " username " against " path))
(let [user ((keyword username) (get-users))]
(timbre/info (str "Authenticating " username " against " password-file-path))
(and user
(:password user)
(or
(.equals (:password user) password)
(password/check password (:password user))))))
(defn get-email
"Return the email address associated with this `username`."
[username]
(let [path (str (io/resource-path) "../passwd")
users (read-string (slurp path))
user ((keyword username) users)]
(if user (:email user))))
(if username
(let [user ((keyword username) (get-users))]
(:email user))))
(defn get-admin
"Return a flag indicating whether the user with this username is an administrator."
[username]
(if username
(let [user ((keyword username) (get-users))]
(:admin user))))
(defn evaluate-password
"Evaluate whether this proposed password is suitable for use."
([pass1 pass2]
(and pass1 (>= (count pass1) 8) (.equals pass1 pass2)))
([password]
(evaluate-password password password)))
;;; TODO: worth locking the passwd file to prevent corruption if two simultaneous threads
;;; try to write it. See http://stackoverflow.com/questions/6404717/idiomatic-file-locking-in-clojure
(defn change-pass
"Change the password for the user with this `username` and `oldpass` to this `newpass`.
@ -62,10 +84,9 @@
password will be encrypted."
[username oldpass newpass]
(timbre/info (format "Changing password for user %s" username))
(let [path (str (io/resource-path) "../passwd")
users (read-string (slurp path))
(let [users (get-users)
keywd (keyword username)
user (if users (keywd users))
user (keywd users)
email (:email user)]
(try
(cond
@ -74,12 +95,78 @@
(.equals (:password user) oldpass)
(password/check oldpass (:password user))))
(do
(spit path
(assoc (dissoc users keywd) keywd
{:password (password/encrypt newpass) :email email}))
(locking password-file-path
(spit password-file-path
(merge users
{keywd
(merge user
{:password (password/encrypt newpass)})})))
(timbre/info (str "Successfully changed password for user " username))
true))
(catch Exception any
(timbre/error
(format "Changing password failed for user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any)))
false))))
(defn list-users
"Return, as strings, the names of the currently known users."
[]
(map name (keys (get-users))))
(defn fetch-user-details
"Return the map of features of this user, if any."
[username]
(if
(and username (> (count (str username)) 0))
((keyword username) (get-users))))
(defn add-user
"Add a user to the passwd file with this username, initial password and email address and admin flag."
[username newpass email admin]
(let [users (get-users)
user ((keyword username) users)
password (if
(and newpass (evaluate-password newpass))
(password/encrypt newpass))
details {:email email
:admin (if
(and (string? admin) (> (count admin) 0))
true
false)}
;; if we have a valid password we want to include it in the details to update.
full-details (if password
(merge details {:password password})
details)]
(try
(locking password-file-path
(spit password-file-path
(merge users
{(keyword username) (merge user full-details)}))
(timbre/info (str "Successfully added user " username))
true)
(catch Exception any
(timbre/error
(format "Adding user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any)))
false))))
(defn delete-user
"Delete the user with this `username` from the password file."
[username]
(let [users (get-users)]
(try
(locking password-file-path
(spit password-file-path
(dissoc users (keyword username)))
(timbre/info (str "Successfully deleted user " username))
true)
(catch Exception any
(timbre/error
(format "Deleting user %s failed: %s (%s)"
username (.getName (.getClass any)) (.getMessage any)))
false))))

View file

@ -54,10 +54,6 @@
an app server such as Tomcat
put any initialization code here"
[]
(timbre/set-config!
{:min-level :debug
:enabled? true
:output-fn timbre/default-output-fn})
(timbre/merge-config!
{:appenders
{:rotor (rotor/rotor-appender

View file

@ -0,0 +1,84 @@
(ns ^{:doc "Render all the main pages of a very simple Wiki engine."
:author "Simon Brooke"}
smeagol.routes.admin
(:require [clojure.walk :refer :all]
[noir.session :as session]
[taoensso.timbre :as timbre]
[smeagol.authenticate :as auth]
[smeagol.layout :as layout]
[smeagol.util :as util]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Smeagol: a very simple Wiki engine.
;;;;
;;;; 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; if not, write to the Free Software
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
;;;; USA.
;;;;
;;;; Copyright (C) 2016 Simon Brooke
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn edit-users
"Put a list of users on-screen for editing."
[request]
(let [params (keywordize-keys (:params request))
user (session/get :user)]
(layout/render "edit-users.html"
(merge (util/standard-params request)
{:title "Select user to edit"
:users (auth/list-users)}))))
(defn delete-user
"Delete a user."
[request]
(let [params (keywordize-keys (:params request))
target (:target params)
deleted (auth/delete-user target)
message (if deleted (str "Successfully deleted user " target))
error (if (not deleted) (str "Could not delete user " target))]
(layout/render "edit-users.html"
(merge (util/standard-params request)
{:title "Select user to edit"
:message message
:error error
:users (auth/list-users)}))))
(defn edit-user
"Put an individual user's details on screen for editing."
[request]
(let [params (keywordize-keys (:params request))
target (:target params)
pass1 (:pass1 params)
password (if (and pass1 (auth/evaluate-password pass1 (:pass2 params))) pass1)
stored (if (:email params)
(auth/add-user target password (:email params) (:admin params)))
message (if stored (str "User " target " was stored successfully."))
error (if (and (:email params) (not stored))
(str "User " target " was not stored."))
details (auth/fetch-user-details target)]
(if message
(timbre/info message))
(if error
(timbre/warn error))
(layout/render "edit-user.html"
(merge (util/standard-params request)
{:title (str "Edit user " target)
:message message
:error error
:target target
:details details}))))

View file

View file

@ -3,10 +3,9 @@
smeagol.routes.wiki
(:require [clojure.walk :refer :all]
[clojure.java.io :as cjio]
[clojure.string :as cs]
[cemerick.url :refer (url url-encode url-decode)]
[compojure.core :refer :all]
[clj-jgit.porcelain :as git]
[cemerick.url :refer (url url-encode url-decode)]
[markdown.core :as md]
[noir.io :as io]
[noir.response :as response]
@ -17,7 +16,8 @@
[smeagol.diff2html :as d2h]
[smeagol.layout :as layout]
[smeagol.util :as util]
[smeagol.history :as hist]))
[smeagol.history :as hist]
[smeagol.routes.admin :as admin]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@ -42,19 +42,6 @@
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn local-links
"Rewrite text in `html-src` surrounded by double square brackets as a local link into this wiki."
[^String html-src]
(cs/replace html-src #"\[\[[^\[\]]*\]\]"
#(let [text (clojure.string/replace %1 #"[\[\]]" "")
encoded (url-encode text)
;; I use '\_' to represent '_' in wiki markup, because
;; '_' is meaningful in Markdown. However, this needs to
;; be stripped out when interpreting local links.
munged (cs/replace encoded #"%26%2395%3B" "_")]
(format "<a href='wiki?page=%s'>%s</a>" munged text))))
(defn get-git-repo
"Get the git repository for my content, creating it if necessary"
[]
@ -100,18 +87,20 @@
src-text (:src params)
page (or (:page params) default)
file-path (str (io/resource-path) "content/" page suffix)
exists? (.exists (cjio/as-file file-path))]
(if (not exists?) (timbre/info (format "File '%s' not found; creating a new file" file-path)))
exists? (.exists (cjio/as-file file-path))
user (session/get :user)]
(if (not exists?)
(timbre/info (format "File '%s' not found; creating a new file" file-path))
(timbre/info (format "Opening '%s' for editing" file-path)))
(cond src-text (process-source params suffix)
true
(layout/render template
(merge (util/standard-params request)
{:title (str "Edit " page)
:page page
:side-bar (local-links (util/md->html side-bar))
:header (local-links (util/md->html "/content/_header.md"))
:side-bar (util/local-links (util/md->html side-bar))
:content (if exists? (io/slurp-resource (str "/content/" page suffix)) "")
:user (session/get :user)
:exists exists?})))))
:exists exists?}))))))
(defn edit-css-page
@ -129,15 +118,15 @@
file-path (str (io/resource-path) file-name)
exists? (.exists (clojure.java.io/as-file file-path))]
(cond exists?
(do
(timbre/info (format "Showing page '%s'" page))
(layout/render "wiki.html"
(merge (util/standard-params request)
{:title page
:page page
:side-bar (local-links (util/md->html "/content/_side-bar.md"))
:header (local-links (util/md->html "/content/_header.md"))
:content (local-links (util/md->html file-name))
:user (session/get :user)
:version (System/getProperty "smeagol.version")})
true (response/redirect (str "/edit?page=" page)))))
:content (util/local-links (util/md->html file-name))
:editable true})))
true (response/redirect (str "edit?page=" page)))))
(defn history-page
@ -149,11 +138,10 @@
file-name (str page ".md")
repo-path (str (io/resource-path) "/content/")]
(layout/render "history.html"
(merge (util/standard-params request)
{:title (str "History of " page)
:page page
:side-bar (local-links (util/md->html "/content/_side-bar.md"))
:header (local-links (util/md->html "/content/_header.md"))
:history (hist/find-history repo-path file-name)})))
:history (hist/find-history repo-path file-name)}))))
(defn version-page
@ -165,17 +153,13 @@
file-name (str page ".md")
repo-path (str (io/resource-path) "/content/")]
(layout/render "wiki.html"
(merge (util/standard-params request)
{:title (str "Version " version " of " page)
:page page
:side-bar (local-links
(util/md->html "/content/_side-bar.md"))
:header (local-links
(util/md->html "/content/_header.md"))
:content (local-links
:content (util/local-links
(md/md-to-html-string
(hist/fetch-version
repo-path file-name version)))
:user (session/get :user)})))
repo-path file-name version)))}))))
(defn diff-page
@ -187,14 +171,10 @@
file-name (str page ".md")
repo-path (str (io/resource-path) "/content/")]
(layout/render "wiki.html"
(merge (util/standard-params request)
{:title (str "Changes since version " version " of " page)
:page page
:side-bar (local-links
(util/md->html "/content/_side-bar.md"))
:header (local-links
(util/md->html "/content/_header.md"))
:content (d2h/diff2html (hist/diff repo-path file-name version))
:user (session/get :user)})))
:content (d2h/diff2html (hist/diff repo-path file-name version))}))))
(defn auth-page
@ -218,11 +198,12 @@
(response/redirect redirect-to))
true
(layout/render "auth.html"
(merge (util/standard-params request)
{:title (if user (str "Logout " user) "Log in")
:redirect-to ((:headers request) "referer")
:side-bar (local-links (util/md->html "/content/_side-bar.md"))
:header (local-links (util/md->html "/content/_header.md"))
:user user}))))
:side-bar (util/local-links (util/md->html "/content/_side-bar.md"))
:header (util/local-links (util/md->html "/content/_header.md"))
:user user})))))
(defn passwd-page
@ -233,28 +214,32 @@
pass1 (:pass1 params)
pass2 (:pass2 params)
user (session/get :user)
length (if pass1 (count pass1) 0)
message (cond
(nil? oldpass) nil
(and pass1 (>= length 8) (.equals pass1 pass2) (auth/change-pass user oldpass pass2))
(and (auth/evaluate-password pass1 pass2) (auth/change-pass user oldpass pass2))
"Your password was changed"
(< length 8) "You proposed password wasn't long enough: 8 characters required"
(< (count pass1) 8) "You proposed password wasn't long enough: 8 characters required"
(not (= pass1 pass2)) "Your proposed passwords don't match"
true "Your password was not changed")] ;; but I don't know why...
(layout/render "passwd.html"
(merge (util/standard-params request)
{:title (str "Change passord for " user)
:side-bar (local-links (util/md->html "/content/_side-bar.md"))
:header (local-links (util/md->html "/content/_header.md"))
:message message})))
:side-bar (util/local-links (util/md->html "/content/_side-bar.md"))
:header (util/local-links (util/md->html "/content/_header.md"))
:message message}))))
(defroutes wiki-routes
(GET "/wiki" request (wiki-page request))
(GET "/" request (wiki-page request))
(GET "/delete-user" request (route/restricted (admin/delete-user request)))
(GET "/edit" request (route/restricted (edit-page request)))
(POST "/edit" request (route/restricted (edit-page request)))
(GET "/edit-css" request (route/restricted (edit-css-page request)))
(POST "/edit-css" request (route/restricted (edit-css-page request)))
(GET "/edit-users" request (route/restricted (admin/edit-users request)))
(GET "/edit-user" request (route/restricted (admin/edit-user request)))
(POST "/edit-user" request (route/restricted (admin/edit-user request)))
(GET "/history" request (history-page request))
(GET "/version" request (version-page request))
(GET "/changes" request (diff-page request))

View file

@ -1,8 +1,12 @@
(ns ^{:doc "Miscellaneous utility functions supporting Smeagol."
:author "Simon Brooke"}
smeagol.util
(:require [noir.io :as io]
[markdown.core :as md]))
(:require [clojure.string :as cs]
[cemerick.url :refer (url url-encode url-decode)]
[noir.io :as io]
[noir.session :as session]
[markdown.core :as md]
[smeagol.authenticate :as auth]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@ -31,3 +35,28 @@
"reads a markdown file from public/md and returns an HTML string"
[filename]
(md/md-to-html-string (io/slurp-resource filename)))
(defn local-links
"Rewrite text in `html-src` surrounded by double square brackets as a local link into this wiki."
[^String html-src]
(cs/replace html-src #"\[\[[^\[\]]*\]\]"
#(let [text (clojure.string/replace %1 #"[\[\]]" "")
encoded (url-encode text)
;; I use '\_' to represent '_' in wiki markup, because
;; '_' is meaningful in Markdown. However, this needs to
;; be stripped out when interpreting local links.
munged (cs/replace encoded #"%26%2395%3B" "_")]
(format "<a href='wiki?page=%s'>%s</a>" munged text))))
(defn standard-params
"Return a map of standard parameters to pass to the template renderer."
[request]
(let [user (session/get :user)]
{:user user
:admin (auth/get-admin user)
:side-bar (local-links (md->html "/content/_side-bar.md"))
:header (local-links (md->html "/content/_header.md"))
:version (System/getProperty "smeagol.version")}))