589 lines
19 KiB
Cheetah
589 lines
19 KiB
Cheetah
#? stdtmpl | standard
|
|
#
|
|
#template `!`(idx: untyped): untyped =
|
|
# row[idx]
|
|
#end template
|
|
#
|
|
#
|
|
#proc genThreadsList(c: TForumData, count: var int): string =
|
|
# const queryModAdmin = sql"""select id, name, views, modified from thread
|
|
# where id in (select thread from post where author in
|
|
# (select id from person where status not in ('Spammer') or id = ?))
|
|
# order by modified desc limit ?, ?"""
|
|
# const query = sql"""select id, name, views, modified from thread
|
|
# where id in (select thread from post where author in
|
|
# (select id from person where status not in ('Moderated', 'Spammer') or id = ?))
|
|
# order by modified desc limit ?, ?"""
|
|
# const threadId = 0
|
|
# const name = 1
|
|
# const views = 2
|
|
#
|
|
# result = ""
|
|
# count = 0
|
|
<div id="talk-heads">
|
|
<div class="topic">
|
|
<div>
|
|
Topic
|
|
<a href="${c.req.makeUri("/threadActivity.xml")}">
|
|
<img src="/images/Feed-icon.svg" class="rssfeed">
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="users"><div>Users</div></div>
|
|
<div class="detail"><div>Details</div></div>
|
|
<div class="activity">
|
|
<div>
|
|
Activity
|
|
<a href="${c.req.makeUri("/postActivity.xml")}">
|
|
<img src="/images/Feed-icon.svg" class="rssfeed">
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="talk-threads">
|
|
# for row in rows(db, if c.rank >= Moderator: queryModAdmin else: query,
|
|
# c.userId, $((c.pageNum-1) * ThreadsPerPage), $ThreadsPerPage):
|
|
# inc(count)
|
|
<div>
|
|
<div class="topic">
|
|
<div>
|
|
<a href="${c.genThreadUrl(threadid = !threadid)}"
|
|
title="${xmlEncode(!name)}">${xmlEncode(!name)}</a>
|
|
${genPagenumLocalNav(c, (!threadid).parseInt)}
|
|
</div>
|
|
</div>
|
|
|
|
#let users = getAllRows(db,
|
|
# sql("select distinct name, email from person where id in " &
|
|
# "(select author from post where thread = ?)"), !threadId)
|
|
<div class="users">
|
|
<div>
|
|
#for i in 0 .. min(6, users.len-1):
|
|
<img src="${getGravatarUrl(users[i][1], 20)}" title="${users[i][0]}">
|
|
#end for
|
|
</div>
|
|
</div>
|
|
|
|
#let latestReplyAuthor = getValue(db, sql("select name from person where id = " &
|
|
# "(select author from post where id = " &
|
|
# "(select max(id) from post where thread = ?))"), !threadId)
|
|
|
|
#let replyProfileUrl = c.req.makeUri("profile/", false) &
|
|
# xmlEncode(latestReplyAuthor)
|
|
|
|
# let posts = getValue(db, sql"select count(*) from post where thread = ?", !threadId)
|
|
<div class="detail">
|
|
<div><div title="Views">${xmlEncode(!views)}</div></div>
|
|
<div><div title="Posts">$posts</div></div>
|
|
</div>
|
|
|
|
#let latestReplyDate = getValue(db, sql("SELECT strftime('%s', " &
|
|
# "(select creation from post where id = (select max(id) from post where thread = ?)))"), !threadId)
|
|
#let timeStr = formatTimestamp(latestReplyDate.parseInt())
|
|
<div class="activity">
|
|
<div>
|
|
<a href="$replyProfileUrl">$latestReplyAuthor</a> replied $timeStr
|
|
</div>
|
|
</div>
|
|
</div>
|
|
# end for
|
|
</div>
|
|
|
|
#end proc
|
|
#
|
|
#
|
|
#proc genPostPreview(c: TForumData,
|
|
# title, content, author, date: string): string =
|
|
# result = ""
|
|
<a name="preview"></a>
|
|
<div id="talk-thread">
|
|
<div>
|
|
<div class="author">
|
|
<div>
|
|
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(author)
|
|
<a class="name" href="$profileUrl">${xmlEncode(author)}</a>
|
|
</div>
|
|
</div>
|
|
<div class="topic">
|
|
<div>
|
|
#try:
|
|
${content.rstToHtml}
|
|
#except EParseError:
|
|
# c.errorMsg = getCurrentExceptionMsg()
|
|
#end
|
|
<span class="date">${xmlEncode(date)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>appendRunBtn()</script>
|
|
</div>
|
|
#end proc
|
|
#
|
|
#
|
|
#proc genPostsList(c: TForumData, threadId: string, count: var int): string =
|
|
# let query = sql("""select p.id, u.name, p.header, p.content, p.creation, p.author, u.email from post p,
|
|
# person u
|
|
# where u.id = p.author and p.thread = ? and $#
|
|
# and (u.status <> 'Spammer' or p.author = ?)
|
|
# order by p.id limit ?, ?""" %
|
|
# (if c.rank >= Moderator: "(1 or u.id = ?)" else: "(u.status <> 'Moderated' or p.author = ?)"))
|
|
# const postId = 0
|
|
# const userName = 1
|
|
# const postHeader = 2
|
|
# const postContent = 3
|
|
# const postCreation = 4
|
|
# const postAuthor = 5
|
|
# const userEmail = 6
|
|
# result = ""
|
|
# count = 0
|
|
# let posts = getAllRows(db, query, threadId, c.userId, c.userId, $((c.pageNum-1) * PostsPerPage), $PostsPerPage)
|
|
# if posts.len < 1: return ""
|
|
# end if
|
|
<div id="talk-head">
|
|
<div class="info-post">
|
|
<div>
|
|
<a href="${c.req.makeUri("/")}"><b>forum index</b></a> >
|
|
<a href="${c.req.makeUri("/t/" & $threadId)}">${posts[0][postHeader]}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="talk-thread">
|
|
# for row in posts:
|
|
# inc(count)
|
|
<a name="${!postId}"></a>
|
|
<div id="${!postId}">
|
|
<div class="author">
|
|
<div>
|
|
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(!userName)
|
|
<div class="avatar">${genGravatar(!userEmail)}</div>
|
|
<a class="name" href="$profileUrl">${xmlEncode(!userName)}</a>
|
|
#if c.userId == !postAuthor and c.currentPost.subject.len == 0:
|
|
<hr/><a href="${c.genThreadUrl(!postId, "edit")}">Edit post</a>
|
|
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
|
<hr/><a style="color: red;" href="${c.genThreadUrl(!postId, "edit")}">Edit post</a>
|
|
#end if
|
|
</div>
|
|
</div>
|
|
<div class="topic">
|
|
<div>
|
|
#try:
|
|
${(!postContent).rstToHtml}
|
|
#except EParseError:
|
|
# c.errorMsg = getCurrentExceptionMsg()
|
|
#end
|
|
<span class="date"><a href="${c.genThreadUrl(!postId, "", $threadId, $(c.pageNum))}">${xmlEncode(!postCreation)}</a></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
# end for
|
|
<script>
|
|
// tiny helper that run the code in the playground
|
|
function appendEachRunBtn(element) {
|
|
if (element.getElementsByClassName("runDiv").length>0){
|
|
return; // already has a run btn
|
|
}
|
|
var runDiv = document.createElement("DIV");
|
|
var btn = document.createElement("BUTTON");
|
|
var t = document.createTextNode("Run");
|
|
var playLink = document.createElement("a")
|
|
var code = element.textContent;
|
|
var spinners = [" ◆ "," ◇ "];
|
|
playLink.title = "Edit in playground"
|
|
playLink.textContent = "Edit"
|
|
playLink.setAttribute('href', "https://play.nim-lang.org?code="+encodeURIComponent(code));
|
|
playLink.setAttribute("class", "button");
|
|
playLink.setAttribute("target", "_blank");
|
|
runDiv.setAttribute("class", "runDiv");
|
|
btn.appendChild(t);
|
|
btn.onclick = function(){
|
|
var httpRequest = new XMLHttpRequest();
|
|
var start = null;
|
|
httpRequest.open("POST", "https://play.nim-lang.org/compile", true);
|
|
btn.textContent = spinners[1];
|
|
function step(timestamp) {
|
|
if (!start) start = timestamp;
|
|
if (timestamp - start > 500) {
|
|
if (btn.textContent == spinners[0]) { btn.textContent = spinners[1] }
|
|
else { btn.textContent = spinners[0] }
|
|
start = null;
|
|
}
|
|
if (httpRequest.status != 200){ window.requestAnimationFrame(step) }
|
|
else { btn.textContent = "Run" }
|
|
}
|
|
function displayContents(e){
|
|
if (httpRequest.readyState!=httpRequest.DONE){ return }
|
|
if (httpRequest.status == 200){
|
|
var res = JSON.parse(httpRequest.responseText);
|
|
// this works because only 1 `resDiv` is inside `element`
|
|
var resDiv = element.getElementsByClassName("resDiv")[0];
|
|
if (resDiv == null) {
|
|
resDiv = document.createElement("DIV");
|
|
runDiv.appendChild(resDiv);
|
|
}
|
|
if (res.compileLog.lastIndexOf("[SuccessX]") != -1){
|
|
resDiv.textContent = res.log;
|
|
resDiv.setAttribute("class", "resDiv successComp");
|
|
} else {
|
|
resDiv.textContent = res.compileLog;
|
|
resDiv.setAttribute("class","resDiv failedComp");
|
|
}
|
|
} else {
|
|
console.log("There was a problem with the request.");
|
|
}
|
|
}
|
|
httpRequest.onreadystatechange = displayContents;
|
|
httpRequest.send(JSON.stringify({"code": code, "compilationTarget": "c"}));
|
|
window.requestAnimationFrame(step);
|
|
}
|
|
runDiv.appendChild(document.createElement("HR"));
|
|
runDiv.appendChild(btn);
|
|
runDiv.appendChild(playLink);
|
|
element.appendChild(runDiv);
|
|
}
|
|
function appendRunBtn(){
|
|
var els = Array.prototype.slice.call(document.getElementsByClassName("langNim"));
|
|
els.forEach(appendEachRunBtn, this);
|
|
}
|
|
appendRunBtn()
|
|
</script>
|
|
</div>
|
|
#end proc
|
|
#
|
|
#proc genMarkHelp(): string
|
|
#end proc
|
|
#proc genFormPost(c: TForumData, action: string,
|
|
# topText, title, content: string, isEdit: bool): string =
|
|
# result = ""
|
|
<br />
|
|
<a name="reply"></a>
|
|
<div id="replywrapper">
|
|
<div id="talk-head">
|
|
<div class="info-post">
|
|
<div>
|
|
<a href="${c.req.makeUri("/")}"><b>forum index</b></a> >
|
|
$topText
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<form action="${c.req.makeUri(action, false) & "#preview"}" method="POST">
|
|
#if action == "doreply":
|
|
${hiddenField(c, "subject", title)}
|
|
#else:
|
|
${fieldValid(c, "subject", "Subject:")}
|
|
${textWidget(c, "subject", title, maxlength=100)}
|
|
<br />
|
|
#end if
|
|
${fieldValid(c, "content", "Content:")}<br />
|
|
${textAreaWidget(c, "content", content)}<br />
|
|
${formSession(c, action)}
|
|
|
|
# if isEdit:
|
|
<input type="checkbox" name="delete" value="Delete">Delete Post<br />
|
|
# end if
|
|
#if c.errorMsg != "":
|
|
<div style="float: left; width: 100%;">
|
|
<span class="error">$c.errorMsg</span>
|
|
</div>
|
|
#end if
|
|
<br/>
|
|
|
|
<input type="submit" name="previewBtn" value="Preview" />
|
|
<input type="submit" name="postBtn" value="Submit" />
|
|
|
|
${genMarkHelp()}
|
|
</form>
|
|
</div>
|
|
#end proc
|
|
#
|
|
#
|
|
#proc genFormRegister(c: TForumData): string =
|
|
# result = ""
|
|
<div id="talk-head">
|
|
<div class="info-post">
|
|
<div>
|
|
<a href="${c.req.makeUri("/")}"><b>forum index</b></a> >
|
|
Register
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<form action="${c.req.makeUri("/doregister", false)}" method="POST">
|
|
<table border="0">
|
|
<tr>
|
|
<td>${fieldValid(c, "name", "Username:")}</td>
|
|
<td>${textWidget(c, "name", reuseText, maxlength=20)}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>${fieldValid(c, "new_password", "Password:")}</td>
|
|
<td><input type="password" name="new_password" /></td>
|
|
</tr>
|
|
<tr>
|
|
<td>${fieldValid(c, "email", "E-Mail:")}</td>
|
|
<td>${textWidget(c, "email", reuseText, maxlength=300)}</td>
|
|
</tr>
|
|
#if useCaptcha:
|
|
<tr>
|
|
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
|
<td>${captcha.render(includeNoScript=true)}</td>
|
|
</tr>
|
|
#end if
|
|
</table>
|
|
#if c.errorMsg != "":
|
|
<div style="float: left; width: 100%;">
|
|
<span class="error">$c.errorMsg</span>
|
|
</div>
|
|
#end if
|
|
<input type="submit" value="Register">
|
|
</form>
|
|
#end proc
|
|
#
|
|
#proc genFormSetRank(c: TForumData; ui: TUserInfo): string =
|
|
# result = ""
|
|
<form action="${c.req.makeUri("/dosetrank/" & ui.nick, false)}" method="POST">
|
|
<table border="0">
|
|
<tr>
|
|
<th>Reason</th>
|
|
<td>${textWidget(c, "reason", ui.ban, maxlength=100)}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Rank</th>
|
|
<td><select name = "rank">
|
|
# for i in low(Rank)..high(Rank):
|
|
<option ${if i == ui.rank: "selected" else: ""}>
|
|
$i
|
|
</option>
|
|
# end for
|
|
</td></select>
|
|
</tr>
|
|
</table>
|
|
<input type="submit" value="Change">
|
|
</form>
|
|
#end proc
|
|
#
|
|
#proc genFormLogin(c: TForumData): string =
|
|
# result = ""
|
|
# if not c.loggedIn:
|
|
<form action="${c.req.makeUri("/dologin", false)}" method="POST">
|
|
<table border="0">
|
|
<tr><td>Username:</td><td>
|
|
<input type="text" name="name" maxlength="20"></td></tr>
|
|
<tr><td>Password:</td><td>
|
|
<input type="password" name="password" maxlength="20"></td></tr>
|
|
</table>
|
|
<input type="submit" value="Login">
|
|
</form>
|
|
<span style="color:red">$c.loginErrorMsg</span>
|
|
# else:
|
|
<span style="color:red">You're already logged in!</span>
|
|
# end if
|
|
#end proc
|
|
#
|
|
#
|
|
#proc genListOnline(c: TForumData, stats: TForumStats): string =
|
|
# result = ""
|
|
# var active: seq[string] = @[]
|
|
# for i in stats.activeUsers:
|
|
# active.add(i.nick)
|
|
# end for
|
|
# let profileUrl = c.req.makeUri("profile/", false) &
|
|
# xmlEncode(stats.newestMember.nick)
|
|
<span class="forum-user-info" title="${active.join(", ")}">
|
|
<b>${stats.activeUsers.len}</b> of <b>${stats.totalUsers}</b> users online</span> |
|
|
<b>${stats.totalThreads}</b> threads | <b>${stats.totalPosts}</b> posts |
|
|
newest member: <a href="$profileUrl">${stats.newestMember.nick}</a>
|
|
#end proc
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#proc genSearchResults(c: TForumData,
|
|
# results: iterator: db_sqlite.Row {.closure, tags: [ReadDbEffect].},
|
|
# count: var int): string =
|
|
# const threadId = 0
|
|
# const threadName = 1
|
|
# const postId = 2
|
|
# const postHeader = 3
|
|
# const postContent = 4
|
|
# const userName = 5
|
|
# const postCreation = 6
|
|
# const postAuthor = 7
|
|
# const userEmail = 8
|
|
# const what = 9
|
|
# result = ""
|
|
# count = 0
|
|
# var whCount: array[bool, int]
|
|
<div id="talk-head">
|
|
<div class="info-post">
|
|
<div>
|
|
Search results for: <i style="color: #332299">${xmlEncode(c.search.replace(""","\""))}</i>.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="talk-thread" class="searchResults">
|
|
# for row in results():
|
|
# inc(count)
|
|
# let isThread = !what == "0"
|
|
# inc(whCount[isThread])
|
|
# let postUrl = c.genThreadUrl(!postId,"",!threadId,"")
|
|
# let threadUrl = c.genThreadUrl("","",!threadId)
|
|
# var headersDiffer = false
|
|
<div>
|
|
<div class="author">
|
|
<div>
|
|
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(!userName)
|
|
<div><a href="$profileUrl">${genGravatar(!userEmail, 40)}</a></div>
|
|
<div style="padding: 8px 0"><a href="$profileUrl">${xmlEncode(!userName)}</a></div>
|
|
#if c.userId == !postAuthor and c.currentPost.subject.len == 0:
|
|
<hr/><a href="${c.genThreadUrl(!postId, "edit", !threadId)}">Edit post</a>
|
|
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
|
<hr/><a style="color: red;" href="${c.genThreadUrl(!postId, "edit", !threadId)}">Edit post</a>
|
|
#end if
|
|
</div>
|
|
</div>
|
|
<div class="topic">
|
|
<div>
|
|
#if !postHeader != "":
|
|
<div class="postTitle">
|
|
<span class="titleHeader">Post:</span>
|
|
<a href="${postUrl}">
|
|
<span>${!postHeader}</span>
|
|
</a>
|
|
</div>
|
|
#end if
|
|
#if not isThread:
|
|
#try:
|
|
${(!postContent).rstToHtml}
|
|
#except EParseError:
|
|
# c.errorMsg = getCurrentExceptionMsg()
|
|
${xmlEncode(!postContent)}
|
|
#end
|
|
#end if
|
|
<span class="date">${xmlEncode(!postCreation)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
# end for
|
|
</div>
|
|
# if c.pageNum > 1:
|
|
<form action="/search/${$(c.pageNum-1)}" method="post" class="searchNav">
|
|
<input type="hidden" name="q" value="${c.search}">
|
|
<input type="submit" value="Previous ${ThreadsPerPage} results">
|
|
</form>
|
|
# end if
|
|
# if whCount[true] == ThreadsPerPage or whCount[false] == ThreadsPerPage:
|
|
<form action="/search/${$(c.pageNum+1)}" method="post" class="searchNav">
|
|
<input type="hidden" name="q" value="${c.search}">
|
|
<input type="submit" value="Next ${ThreadsPerPage} results (if any)">
|
|
</form>
|
|
# end if
|
|
#end proc
|
|
#
|
|
#
|
|
#proc genFormResetPassword(c: TForumData): string =
|
|
# result = ""
|
|
<div id="talk-head">
|
|
<div class="info-post">
|
|
<div>
|
|
<a href="${c.req.makeUri("/")}"><b>forum index</b></a> >
|
|
Reset Password
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<form action="${c.req.makeUri("/doresetpassword", false)}" method="POST">
|
|
<table border="0">
|
|
<tr>
|
|
<td>${fieldValid(c, "nick", "Your nickname:")}</td>
|
|
<td><input type="text" name="nick" maxlength="20" /></td>
|
|
</tr>
|
|
#if useCaptcha:
|
|
<tr>
|
|
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
|
<td>${captcha.render(includeNoScript=true)}</td>
|
|
</tr>
|
|
#end if
|
|
</table>
|
|
#if c.errorMsg != "":
|
|
<div style="float: left; width: 100%;">
|
|
<span class="error">$c.errorMsg</span>
|
|
</div>
|
|
#end if
|
|
<input type="submit" value="Email me">
|
|
</form>
|
|
#end proc
|
|
#proc genMarkHelp(): string =
|
|
#result = ""
|
|
<div id="markhelp">
|
|
<p>nimforum uses a slightly-customized version of
|
|
<a href="http://www.sphinx-doc.org/en/stable/rest.html">reStructuredText</a> for formatting. See below for some basics, or check
|
|
<a href="/rst">this link</a> for a more detailed help reference.</p>
|
|
<table class="rst">
|
|
<tbody>
|
|
<tr class="markheading">
|
|
<td><em>you type:</em>
|
|
</td>
|
|
<td><em>you see:</em>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>*italics*</td>
|
|
<td><em>italics</em>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>**bold**</td>
|
|
<td><b>bold</b>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>`nim! <http://nim-lang.org>`_</td>
|
|
<td><a href="http://nim-lang.org">nim!</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>* item 1
|
|
<br>* item 2
|
|
<br>* item 3</td>
|
|
<td>
|
|
<ul>
|
|
<li>item 1</li>
|
|
<li>item 2</li>
|
|
<li>item 3</li>
|
|
</ul>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>> quoted text</td>
|
|
<td>
|
|
<blockquote>quoted text</blockquote>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>The forum supports the Github Markdown syntax
|
|
<br>for code listings:
|
|
<br>
|
|
<br>```nim
|
|
<br>if 1 * 2 < 3:
|
|
<br><span class="spaces"> </span>echo "hello, world!"
|
|
<br>```
|
|
<br>
|
|
</td>
|
|
<td>The forum supports the Github Markdown syntax
|
|
<br>for code listings:
|
|
<br>
|
|
<pre class="listing">
|
|
<span class="Keyword">if</span> <span class="DecNumber">1</span><span class=
|
|
"Operator">*</span><span class="DecNumber">2</span> <span class=
|
|
"Operator"><</span> <span class="DecNumber">3</span><span class=
|
|
"Punctuation">:</span>
|
|
<span class="Identifier">echo</span> <span class=
|
|
"StringLit">"hello, world!"</span>
|
|
</pre>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>A horizontal rule can be created<br/>----<br/>but it needs text after it</td>
|
|
<td>A horizontal rule can be created<hr/>but it needs text after it</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
#end proc
|