From 2f2d83b86c9b7088b30d6f7ecbb0ed7c9b4ff11c Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Tue, 22 May 2018 22:49:34 +0100 Subject: [PATCH 001/175] Change 'scripts' to 'ga' for google analytics specific config. --- public/karax.html | 11 ++++++++++- src/forum.nim | 2 +- src/utils.nim | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/public/karax.html b/public/karax.html index e43498e..0ed0590 100644 --- a/public/karax.html +++ b/public/karax.html @@ -11,12 +11,21 @@ + + + +
- $scripts diff --git a/src/forum.nim b/src/forum.nim index 2c5824f..9d0ba29 100644 --- a/src/forum.nim +++ b/src/forum.nim @@ -267,7 +267,7 @@ proc initialise() = { "title": config.title, "timestamp": encodeUrl(CompileDate & CompileTime), - "scripts": config.scripts + "ga": config.ga }.newStringTable() diff --git a/src/utils.nim b/src/utils.nim index dc50965..c5f053f 100644 --- a/src/utils.nim +++ b/src/utils.nim @@ -28,7 +28,7 @@ type dbPath*: string hostname*: string name*, title*: string - scripts*: string + ga*: string ForumError* = object of Exception data*: PostError @@ -65,7 +65,7 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config = result.hostname = root["hostname"].getStr() result.name = root["name"].getStr() result.title = root["title"].getStr() - result.scripts = root{"scripts"}.getStr() + result.ga = root{"ga"}.getStr() proc processGT(n: XmlNode, tag: string): (int, XmlNode, string) = result = (0, newElement(tag), tag) From 3a87ecb0ab1706c45ed8596e44d7d1c4c913148d Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Tue, 22 May 2018 22:57:28 +0100 Subject: [PATCH 002/175] Adds titles to threads and profiles. --- src/frontend/forum.nim | 5 ++++- src/frontend/postlist.nim | 4 +++- src/frontend/profile.nim | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/frontend/forum.nim b/src/frontend/forum.nim index 5c12905..6aef3b6 100644 --- a/src/frontend/forum.nim +++ b/src/frontend/forum.nim @@ -1,5 +1,5 @@ import strformat, times, options, json, tables, sugar, httpcore, uri -from dom import window, Location +from dom import window, Location, document include karax/prelude import jester/[patterns] @@ -10,6 +10,7 @@ import karaxutils type State = ref object + originalTitle: cstring url: Location profile: ProfileState newThread: NewThread @@ -33,6 +34,7 @@ proc copyLocation(loc: Location): Location = proc newState(): State = State( + originalTitle: document.title, url: copyLocation(window.location), profile: newProfileState(), newThread: newNewThread(), @@ -48,6 +50,7 @@ proc onPopState(event: dom.Event) = # history. I fire it in karaxutils.anchorCB as well to ensure the URL is # always updated. This should be moved into Karax in the future. kout(kstring"New URL: ", window.location.href, " ", state.url.href) + document.title = state.originalTitle if state.url.href != window.location.href: state = newState() # Reload the state to remove stale data. state.url = copyLocation(window.location) diff --git a/src/frontend/postlist.nim b/src/frontend/postlist.nim index 0e998c0..bd10d20 100644 --- a/src/frontend/postlist.nim +++ b/src/frontend/postlist.nim @@ -14,7 +14,7 @@ type posts*: seq[Post] when defined(js): - from dom import nil + from dom import document include karax/prelude import karax / [vstyles, kajax, kdom] @@ -65,6 +65,8 @@ when defined(js): state.list = some(list) + dom.document.title = list.thread.topic & " - " & dom.document.title + # The anchor should be jumped to once all the posts have been loaded. if postId.isSome(): discard setTimeout( diff --git a/src/frontend/profile.nim b/src/frontend/profile.nim index fd2f70c..cfe198c 100644 --- a/src/frontend/profile.nim +++ b/src/frontend/profile.nim @@ -3,6 +3,7 @@ import options, httpcore, json, sugar, times, strformat, strutils import threadlist, post, category, error, user when defined(js): + from dom import document include karax/prelude import karax/[kajax, kdom] import karaxutils, postbutton, delete, profilesettings @@ -37,6 +38,8 @@ when defined(js): state.profile = some(profile) state.settings = some(newProfileSettings(profile)) + dom.document.title = profile.user.name & " - " & dom.document.title + proc genPostLink(link: PostLink): VNode = let url = renderPostUrl(link) result = buildHtml(): From b9df0ec89540d415f4dbc8c56bb058f1b0d273a1 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Tue, 22 May 2018 23:04:08 +0100 Subject: [PATCH 003/175] Fixes newThread regression. --- src/forum.nim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/forum.nim b/src/forum.nim index 9d0ba29..7fad1a9 100644 --- a/src/forum.nim +++ b/src/forum.nim @@ -447,9 +447,7 @@ proc executeReply(c: TForumData, threadId: int, content: string, let isLocked = getValue( db, sql""" - select isLocked from thread where id in ( - select thread from post where id = ? - ) + select isLocked from thread where id = ?; """, threadId ) From b8c1ae1fe0d5423e5164700496b5bee63f1b62ca Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Wed, 23 May 2018 12:48:31 +0100 Subject: [PATCH 004/175] Implements ability to specify the HTTP port. --- src/forum.nim | 3 +++ src/utils.nim | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/forum.nim b/src/forum.nim index 7fad1a9..416436e 100644 --- a/src/forum.nim +++ b/src/forum.nim @@ -750,6 +750,9 @@ include "main.tmpl" initialise() +settings: + port = config.port.Port + routes: get "/threads.json": diff --git a/src/utils.nim b/src/utils.nim index c5f053f..834d30a 100644 --- a/src/utils.nim +++ b/src/utils.nim @@ -29,6 +29,7 @@ type hostname*: string name*, title*: string ga*: string + port*: int ForumError* = object of Exception data*: PostError @@ -66,6 +67,7 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config = result.name = root["name"].getStr() result.title = root["title"].getStr() result.ga = root{"ga"}.getStr() + result.port = root{"port"}.getNum(5000).int proc processGT(n: XmlNode, tag: string): (int, XmlNode, string) = result = (0, newElement(tag), tag) From 6c4fd6f50b6258967fd52057416167a7f5b2223a Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Wed, 23 May 2018 12:56:07 +0100 Subject: [PATCH 005/175] Pin sass package commit. --- nimforum.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimforum.nimble b/nimforum.nimble index a24424e..29df381 100644 --- a/nimforum.nimble +++ b/nimforum.nimble @@ -17,7 +17,7 @@ requires "jester#64295c8" requires "bcrypt#head" requires "hmac#9c61ebe2fd134cf97" requires "recaptcha 1.0.2" -requires "sass" +requires "sass#649e0701fa5c" requires "https://github.com/dom96/karax#7a884fb" From 934dbef44c714695b65e2c81b74eb65ef7962479 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Wed, 23 May 2018 14:00:49 +0100 Subject: [PATCH 006/175] Add recaptcha to reset password modal. --- nimforum.nimble | 8 ++++++-- src/forum.nim | 2 +- src/frontend/header.nim | 3 +-- src/frontend/login.nim | 4 ++-- src/frontend/resetpassword.nim | 11 ++++++++++- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/nimforum.nimble b/nimforum.nimble index 29df381..2cf1271 100644 --- a/nimforum.nimble +++ b/nimforum.nimble @@ -41,14 +41,18 @@ task frontend, "Builds the necessary JS frontend (with CSS)": task minify, "Minifies the JS using Google's closure compiler": exec "closure-compiler public/js/forum.js --js_output_file public/js/forum.js.opt" -task testdb, "Creates a test DB": +task testdb, "Creates a test DB (with admin account!)": exec "nimble c src/setup_nimforum" exec "./src/setup_nimforum --test" -task devdb, "Creates a test DB": +task devdb, "Creates a test DB (with admin account!)": exec "nimble c src/setup_nimforum" exec "./src/setup_nimforum --dev" +task blankdb, "Creates a blank DB": + exec "nimble c src/setup_nimforum" + exec "./src/setup_nimforum --blank" + task test, "Runs tester": exec "nimble c -y src/forum.nim" exec "nimble c -y -r tests/browsertester" diff --git a/src/forum.nim b/src/forum.nim index 416436e..a197fcc 100644 --- a/src/forum.nim +++ b/src/forum.nim @@ -1290,7 +1290,7 @@ routes: if "g-recaptcha-response" notin formData: let err = PostError( errorFields: @[], - message: "Not logged in." + message: "Not logged in and no recaptcha." ) resp Http401, $(%err), "application/json" diff --git a/src/frontend/header.nim b/src/frontend/header.nim index d977771..fc16941 100644 --- a/src/frontend/header.nim +++ b/src/frontend/header.nim @@ -113,7 +113,6 @@ when defined(js): render(state.userMenu, user.get()) # Modals - render(state.loginModal) - if state.data.isSome(): + render(state.loginModal, state.data.get().recaptchaSiteKey) render(state.signupModal, state.data.get().recaptchaSiteKey) \ No newline at end of file diff --git a/src/frontend/login.nim b/src/frontend/login.nim index 2c413dd..f0779c7 100644 --- a/src/frontend/login.nim +++ b/src/frontend/login.nim @@ -53,7 +53,7 @@ when defined(js): if event.key == "Enter": onLogInClick(e, n, state) - proc render*(state: LoginModal): VNode = + proc render*(state: LoginModal, recaptchaSiteKey: Option[string]): VNode = result = buildHtml(tdiv()): tdiv(class=class({"active": state.shown}, "modal modal-sm"), id="login-modal"): @@ -93,4 +93,4 @@ when defined(js): (state.onSignUp(); state.shown = false)): text "Create account" - render(state.resetPasswordModal) \ No newline at end of file + render(state.resetPasswordModal, recaptchaSiteKey) \ No newline at end of file diff --git a/src/frontend/resetpassword.nim b/src/frontend/resetpassword.nim index 1313f20..c2f01a1 100644 --- a/src/frontend/resetpassword.nim +++ b/src/frontend/resetpassword.nim @@ -89,6 +89,8 @@ when defined(js): ajaxPost(uri, @[], cast[cstring](formData), (s: int, r: kstring) => onPost(s, r, state)) + ev.preventDefault() + proc onClose(ev: Event, n: VNode, state: ResetPasswordModal) = state.shown = false ev.preventDefault() @@ -106,7 +108,8 @@ when defined(js): if event.key == "Enter": onClick(e, n, state) - proc render*(state: ResetPasswordModal): VNode = + proc render*(state: ResetPasswordModal, + recaptchaSiteKey: Option[string]): VNode = result = buildHtml(): tdiv(class=class({"active": state.shown}, "modal"), id="resetpassword-modal"): @@ -132,6 +135,11 @@ when defined(js): true, placeholder="Username or email" ) + if recaptchaSiteKey.isSome: + tdiv(id="recaptcha"): + tdiv(class="g-recaptcha", + "data-sitekey"=recaptchaSiteKey.get()) + script(src="https://www.google.com/recaptcha/api.js") tdiv(class="modal-footer"): if state.sent: span(class="text-success"): @@ -142,5 +150,6 @@ when defined(js): {"loading": state.loading}, "btn btn-primary" ), + `type`="button", onClick=(ev: Event, n: VNode) => onClick(ev, n, state)): text "Reset password" \ No newline at end of file From 32dbf3078133957c3c5d0f17e4ce9553afbb6545 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Wed, 23 May 2018 14:27:44 +0100 Subject: [PATCH 007/175] Fixes overflow for
 blocks without `.code`.

---
 public/css/nimforum.scss | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/public/css/nimforum.scss b/public/css/nimforum.scss
index ad1d712..b0af73c 100644
--- a/public/css/nimforum.scss
+++ b/public/css/nimforum.scss
@@ -483,6 +483,12 @@ blockquote {
   display: none;
 }
 
+.post-content {
+  pre:not(.code) {
+    overflow: scroll;
+  }
+}
+
 .information {
   @extend .tile;
   border-top: 1px solid $border-color;

From c566b05347d1c8f26c4698aef0d798391ed96cd7 Mon Sep 17 00:00:00 2001
From: Dominik Picheta 
Date: Thu, 24 May 2018 12:00:06 +0100
Subject: [PATCH 008/175] Update Karax and small frontend fixes.

---
 nimforum.nimble           | 4 ++--
 public/karax.html         | 2 +-
 src/frontend/replybox.nim | 5 ++---
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/nimforum.nimble b/nimforum.nimble
index 2cf1271..392c6a0 100644
--- a/nimforum.nimble
+++ b/nimforum.nimble
@@ -19,7 +19,7 @@ requires "hmac#9c61ebe2fd134cf97"
 requires "recaptcha 1.0.2"
 requires "sass#649e0701fa5c"
 
-requires "https://github.com/dom96/karax#7a884fb"
+requires "karax#9882d1aec2bb"
 
 requires "webdriver#a2be578"
 
@@ -58,4 +58,4 @@ task test, "Runs tester":
   exec "nimble c -y -r tests/browsertester"
 
 task fasttest, "Runs tester without recompiling backend":
-  exec "nimble c -r tests/browsertester"
\ No newline at end of file
+  exec "nimble c -r tests/browsertester"
diff --git a/public/karax.html b/public/karax.html
index 0ed0590..3f7f41b 100644
--- a/public/karax.html
+++ b/public/karax.html
@@ -24,7 +24,7 @@
 
 
 
-  
+
diff --git a/src/frontend/replybox.nim b/src/frontend/replybox.nim index 20e8114..b2e3812 100644 --- a/src/frontend/replybox.nim +++ b/src/frontend/replybox.nim @@ -88,9 +88,8 @@ when defined(js): state.shown = false proc onChange(e: Event, n: VNode, state: ReplyBox) = - # TODO: There should be a karax-way to do this. I guess I can just call - # `value` on the node? We need to document this better :) - state.text = cast[dom.TextAreaElement](n.dom).value + # TODO: Please document this better in Karax. + state.text = n.value proc renderContent*(state: ReplyBox, thread: Option[Thread], post: Option[Post]): VNode = From 7baae0bde0cd3dd366fa1de3e11e471ef3e434bf Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Thu, 24 May 2018 14:07:52 +0100 Subject: [PATCH 009/175] Spammers are now hidden from the thread list properly. --- public/css/nimforum.scss | 4 ---- src/forum.nim | 24 +++++++++++++++++------- src/frontend/user.nim | 1 + 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/public/css/nimforum.scss b/public/css/nimforum.scss index b0af73c..11f411a 100644 --- a/public/css/nimforum.scss +++ b/public/css/nimforum.scss @@ -248,10 +248,6 @@ $threads-meta-color: #545d70; } -#threads-list tr.banned { - display: none; // TODO: Fix server so that it doesn't send banned threads. -} - .posts, .about { @extend .grid-md; @extend .container; diff --git a/src/forum.nim b/src/forum.nim index a197fcc..79e1c72 100644 --- a/src/forum.nim +++ b/src/forum.nim @@ -380,7 +380,7 @@ proc selectThreadAuthor(threadId: int): User = return selectUser(getRow(db, authorQuery, threadId)) -proc selectThread(threadRow: seq[string]): Thread = +proc selectThread(threadRow: seq[string], author: User): Thread = const postsQuery = sql"""select count(*), min(strftime('%s', creation)) from post where thread = ?;""" @@ -418,7 +418,7 @@ proc selectThread(threadRow: seq[string]): Thread = thread.users.add(selectUser(user)) # Grab the author. - thread.author = selectThreadAuthor(thread.id) + thread.author = author return thread @@ -762,9 +762,19 @@ routes: const threadsQuery = sql"""select t.id, t.name, views, strftime('%s', modified), isLocked, - c.id, c.name, c.description, c.color - from thread t, category c - where isDeleted = 0 and category = c.id + c.id, c.name, c.description, c.color, + u.name, u.email, strftime('%s', u.lastOnline), + strftime('%s', u.previousVisitAt), u.status, u.isDeleted + from thread t, category c, person u + where t.isDeleted = 0 and category = c.id and + u.status <> 'Spammer' and u.status <> 'Troll' and + u.status <> 'Banned' and + u.id in ( + select u.id from post p, person u + where p.author = u.id and p.thread = t.id + order by u.id + limit 1 + ) order by modified desc limit ?, ?;""" let thrCount = getValue(db, sql"select count(*) from thread;").parseInt() @@ -772,7 +782,7 @@ routes: var list = ThreadList(threads: @[], moreCount: moreCount) for data in getAllRows(db, threadsQuery, start, count): - let thread = selectThread(data) + let thread = selectThread(data[0 .. 8], selectUser(data[9 .. ^1])) list.threads.add(thread) resp $(%list), "application/json" @@ -793,7 +803,7 @@ routes: where t.id = ? and isDeleted = 0 and category = c.id;""" let threadRow = getRow(db, threadsQuery, id) - let thread = selectThread(threadRow) + let thread = selectThread(threadRow, selectThreadAuthor(id)) let postsQuery = sql( diff --git a/src/frontend/user.nim b/src/frontend/user.nim index 8672953..bbf5782 100644 --- a/src/frontend/user.nim +++ b/src/frontend/user.nim @@ -1,6 +1,7 @@ import times type + # If you add more "Banned" states, be sure to modify forum's threadsQuery too. Rank* {.pure.} = enum ## serialized as 'status' Spammer ## spammer: every post is invisible Troll ## troll: cannot write new posts From dbb3d5c7c1336e8cff7fe571695853e8337ffb0e Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Thu, 24 May 2018 14:15:53 +0100 Subject: [PATCH 010/175] Fixes Ctrl/Cmd not opening links in new tab. --- src/frontend/karaxutils.nim | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/frontend/karaxutils.nim b/src/frontend/karaxutils.nim index 9a0816e..462d000 100644 --- a/src/frontend/karaxutils.nim +++ b/src/frontend/karaxutils.nim @@ -77,13 +77,15 @@ when defined(js): # Fire the popState event. dom.dispatchEvent(dom.window, dom.newEvent("popstate")) - proc anchorCB*(e: kdom.Event, n: VNode) = # TODO: Why does this need disamb? - e.preventDefault() + proc anchorCB*(e: Event, n: VNode) = + let mE = e.MouseEvent + if not (mE.metaKey or mE.ctrlKey): + e.preventDefault() - # TODO: Why does Karax have it's own Node type? That's just silly. - let url = n.getAttr("href") + # TODO: Why does Karax have it's own Node type? That's just silly. + let url = n.getAttr("href") - navigateTo(url) + navigateTo(url) type FormData* = ref object From 2e3f8d4235c3eab5e5e27aef6825a3495d771daf Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Thu, 24 May 2018 14:43:18 +0100 Subject: [PATCH 011/175] Adds rst cheatsheet. --- public/css/nimforum.scss | 2 +- public/rst.rst | 144 +++++++++++++++++--------------------- src/forum.nim | 4 ++ src/frontend/replybox.nim | 2 + 4 files changed, 71 insertions(+), 81 deletions(-) diff --git a/public/css/nimforum.scss b/public/css/nimforum.scss index 11f411a..5a09c44 100644 --- a/public/css/nimforum.scss +++ b/public/css/nimforum.scss @@ -349,7 +349,7 @@ $threads-meta-color: #545d70; } } -.post-content { +.post-content, .about { img { max-width: 100%; } diff --git a/public/rst.rst b/public/rst.rst index 6b584fc..b5db812 100644 --- a/public/rst.rst +++ b/public/rst.rst @@ -1,5 +1,5 @@ -reStructuredText cheat sheet -=========================================================================== +Markdown and RST supported by this forum +======================================== This is a cheat sheet for the *reStructuredText* dialect as implemented by Nim's documentation generator which has been reused for this forum. @@ -11,7 +11,6 @@ for further information. Elements of **markdown** are also supported. - Inline elements --------------- @@ -28,6 +27,18 @@ Plain text Result ``\\escape`` \\escape =============================== ============================================ +Quoting other users can be done by prefixing their message with ``>``:: + + > Hello World + + Hi! + +Which will result in: + +> Hello World + +Hi! + Links ----- @@ -44,33 +55,22 @@ Or like this:: Code blocks ----------- -are done this way:: +The code blocks can be written in the same style as most common Markdown +flavours:: + + ```nim + if x == "abc": + echo "xyz" + ``` + +or using RST syntax:: .. code-block:: nim if x == "abc": echo "xyz" - -Is rendered as: - -.. code-block:: nim - - if x == "abc": - echo "xyz" - - -Except Nim, the programming languages C, C++, Java and C# have highlighting -support. - -An alternative github-like syntax is also supported. This has the advantage -that no excessive indentation is needed:: - - ```nim - if x == "abc": - echo "xyz"``` - -Is rendered as: +Both are rendered as: .. code-block:: nim @@ -78,18 +78,20 @@ Is rendered as: echo "xyz" +Apart from Nim, the programming languages C, C++, Java and C# also +have highlighting support. Literal blocks -------------- -Are introduced by '::' and a newline. The block is indicated by indentation: +These are introduced by '::' and a newline. The block is indicated by indentation: :: :: if x == "abc": echo "xyz" -Is rendered as:: +The above is rendered as:: if x == "abc": echo "xyz" @@ -99,7 +101,7 @@ Is rendered as:: Bullet lists ------------ -look like this:: +Bullet lists look like this:: * Item 1 * Item 2 that @@ -110,7 +112,7 @@ look like this:: - item 3b - valid bullet characters are ``+``, ``*`` and ``-`` -Is rendered as: +The above rendered as: * Item 1 * Item 2 that spans over multiple lines @@ -124,7 +126,7 @@ Is rendered as: Enumerated lists ---------------- -are written like this:: +Enumerated lists are written like this:: 1. This is the first item 2. This is the second item @@ -132,60 +134,13 @@ are written like this:: single letters, or roman numerals #. This item is auto-enumerated -Is rendered as: +They are rendered as: 1. This is the first item 2. This is the second item 3. Enumerators are arabic numbers, single letters, or roman numerals -#. This item is auto-enumerated - - -Quoting someone ---------------- - -quotes are just:: - - **Someone said**: Indented paragraphs, - - and they may nest. - -Is rendered as: - - **Someone said**: Indented paragraphs, - - and they may nest. - - - -Definition lists ----------------- - -are written like this:: - - what - Definition lists associate a term with - a definition. - - how - The term is a one-line phrase, and the - definition is one or more paragraphs or - body elements, indented relative to the - term. Blank lines are not allowed - between term and definition. - -and look like: - -what - Definition lists associate a term with - a definition. - -how - The term is a one-line phrase, and the - definition is one or more paragraphs or - body elements, indented relative to the - term. Blank lines are not allowed - between term and definition. +#. This item is auto-enumerated Tables @@ -221,6 +176,35 @@ Cell 7 Cell 8 Cell 9 Images ------ +Image embedding is supported. This includes GIFs as well as mp4 (for which a +