diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index fde1b09..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,80 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: CI - -# Controls when the action will run. -on: - # Triggers the workflow on push or pull request events but only for the master branch - push: - branches: [ master ] - pull_request: - branches: [ master ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - test_stable: - runs-on: ubuntu-latest - strategy: - matrix: - firefox: [ '73.0' ] - include: - - nim-version: 'stable' - cache-key: 'stable' - steps: - - uses: actions/checkout@v2 - - name: Checkout submodules - run: git submodule update --init --recursive - - - name: Setup firefox - uses: browser-actions/setup-firefox@latest - with: - firefox-version: ${{ matrix.firefox }} - - - name: Get Date - id: get-date - run: echo "::set-output name=date::$(date "+%Y-%m-%d")" - shell: bash - - - name: Cache choosenim - uses: actions/cache@v2 - with: - path: ~/.choosenim - key: ${{ runner.os }}-choosenim-${{ matrix.cache-key }} - - - name: Cache nimble - uses: actions/cache@v2 - with: - path: ~/.nimble - key: ${{ runner.os }}-nimble-${{ hashFiles('*.nimble') }} - - - uses: jiro4989/setup-nim-action@v1 - with: - nim-version: "${{ matrix.nim-version }}" - - - name: Install geckodriver - run: | - sudo apt-get -qq update - sudo apt-get install autoconf libtool libsass-dev - - wget https://github.com/mozilla/geckodriver/releases/download/v0.29.1/geckodriver-v0.29.1-linux64.tar.gz - mkdir geckodriver - tar -xzf geckodriver-v0.29.1-linux64.tar.gz -C geckodriver - export PATH=$PATH:$PWD/geckodriver - - - name: Install choosenim - run: | - export CHOOSENIM_CHOOSE_VERSION="stable" - curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh - sh init.sh -y - export PATH=$HOME/.nimble/bin:$PATH - nimble refresh -y - - - name: Run tests - run: | - export MOZ_HEADLESS=1 - nimble -y install - nimble -y test - diff --git a/.gitignore b/.gitignore deleted file mode 100644 index fe26a8a..0000000 --- a/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Wildcard patterns. -*.swp -nimcache/ -*.db* - -# Specific paths -/createdb -/forum -/nimforum.db - -# Binaries -forum -createdb -editdb - -.vscode -forum.json* -browsertester -setup_nimforum -buildcss -nimforum.css - -/src/frontend/forum.js diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6ea9ea9..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "frontend/spectre"] - path = frontend/spectre - url = https://github.com/picturepan2/spectre -[submodule "public/css/spectre"] - path = public/css/spectre - url = https://github.com/picturepan2/spectre diff --git a/README.md b/README.md index d7dedb4..abacfe4 100644 --- a/README.md +++ b/README.md @@ -1,131 +1,11 @@ -# nimforum +nimforum +======== -NimForum is a light-weight forum implementation -with many similarities to Discourse. It is implemented in -the [Nim](https://nim-lang.org) programming -language and uses SQLite for its database. +This is Nimrod's forum. The code is not nice and depends on the RST parser of +the Nimrod compiler. -## Examples in the wild - -[![forum.nim-lang.org](https://i.imgur.com/hdIF5Az.png)](https://forum.nim-lang.org) - -

forum.nim-lang.org

- -## Features - -* Efficient, type safe and clean **single-page application** developed using the - [Karax](https://github.com/pragmagic/karax) and - [Jester](https://github.com/dom96/jester) frameworks. -* **Utilizes SQLite** making set up much easier. -* Endlessly **customizable** using SASS. -* Spam blocking via new user sandboxing with great tools for moderators. -* reStructuredText enriched by Markdown to make formatting your posts a breeze. -* Search powered by SQLite's full-text search. -* Context-aware replies. -* Last visit tracking. -* Gravatar support. -* And much more! - -## Setup - -[See this document.](https://github.com/nim-lang/nimforum/blob/master/setup.md) - -## Dependencies - -The following lists the dependencies which you may need to install manually -in order to get NimForum running, compiled*, or tested†. - -* libsass -* SQLite -* pcre -* Nim (and the Nimble package manager)* -* [geckodriver](https://github.com/mozilla/geckodriver)† - * Firefox† - -[*] Build time dependencies - -[†] Test time dependencies - -## Development - -Check out the tasks defined by this project's ``nimforum.nimble`` file by -running ``nimble tasks``, as of writing they are: - -``` -backend Compiles and runs the forum backend -runbackend Runs the forum backend -frontend Builds the necessary JS frontend (with CSS) -minify Minifies the JS using Google's closure compiler -testdb Creates a test DB (with admin account!) -devdb Creates a test DB (with admin account!) -blankdb Creates a blank DB -test Runs tester -fasttest Runs tester without recompiling backend -``` - -To get up and running: - -```bash -git clone https://github.com/nim-lang/nimforum -cd nimforum -git submodule update --init --recursive - -# Setup the db with user: admin, pass: admin and some other users -nimble devdb - -# Run this again if frontend code changes -nimble frontend - -# Will start a server at localhost:5000 -nimble backend -``` - -Development typically involves running `nimble devdb` which sets up the -database for development and testing, then `nimble backend` -which compiles and runs the forum's backend, and `nimble frontend` -separately to build the frontend. When making changes to the frontend it -should be enough to simply run `nimble frontend` again to rebuild. This command -will also build the SASS ``nimforum.scss`` file in the `public/css` directory. - -### With docker - -You can easily launch site on localhost if you have `docker` and `docker-compose`. -You don't have to setup dependencies (libsass, sglite, pcre, etc...) on you host PC. - -To get up and running: - -```bash -cd docker -docker-compose build -docker-compose up -``` - -And you can access local NimForum site. -Open http://localhost:5000 . - -# Troubleshooting - -You might have to run `nimble install karax@#5f21dcd`, if setup fails -with: - -``` -andinus@circinus ~/projects/forks/nimforum> nimble --verbose devdb -[...] - Installing karax@#5f21dcd - Tip: 24 messages have been suppressed, use --verbose to show them. - Error: No binaries built, did you specify a valid binary name? -[...] - Error: Exception raised during nimble script execution -``` - -The hash needs to be replaced with the one specified in output. - -# Copyright - -Copyright (c) 2012-2018 Andreas Rumpf, Dominik Picheta. +Copyright (c) 2012 Andreas Rumpf. All rights reserved. -# License -NimForum is licensed under the MIT license. diff --git a/captchas.nim b/captchas.nim new file mode 100644 index 0000000..0d56022 --- /dev/null +++ b/captchas.nim @@ -0,0 +1,39 @@ +# +# +# The Nimrod Forum +# (c) Copyright 2012 Andreas Rumpf +# +# All rights reserved. +# + +import cairo, os, strutils, jester + +proc getCaptchaFilename*(i: int): string {.inline.} = + result = "public/captchas/capture_" & $i & ".png" + +proc getCaptchaUrl*(req: var TRequest, i: int): string = + result = req.makeUri("/captchas/capture_" & $i & ".png", absolute = false) + +proc createCaptcha*(file, text: string) = + var surface = imageSurfaceCreate(FORMAT_ARGB32, 10*text.len, 10) + var cr = create(surface) + + selectFontFace(cr, "serif", FONT_SLANT_NORMAL, FONT_WEIGHT_BOLD) + setFontSize(cr, 12.0) + + setSourceRgb(cr, 1.0, 0.5, 0.0) + moveTo(cr, 0.0, 10.0) + showText(cr, repeatChar(text.len, 'O')) + + setSourceRgb(cr, 0.0, 0.0, 1.0) + moveTo(cr, 0.0, 10.0) + showText(cr, text) + + destroy(cr) + discard writeToPng(surface, file) + destroy(surface) + +when isMainModule: + createCapture("test.png", "1+33") + + diff --git a/createdb.nim b/createdb.nim new file mode 100644 index 0000000..8cd3289 --- /dev/null +++ b/createdb.nim @@ -0,0 +1,92 @@ +# +# +# Nimrod Forum +# (c) Copyright 2012 Andreas Rumpf +# +# All rights reserved. +# + +import strutils, db_sqlite + +var db = Open(connection="nimforum.db", user="postgres", password="", + database="nimforum") + +const + TUserName = "varchar(20)" + TPassword = "varchar(32)" + TEmail = "varchar(30)" + +db.Exec(sql""" +create table if not exists thread( + id integer primary key, + name varchar(100) not null, + views integer not null, + modified timestamp not null default (DATETIME('now')) +);""", []) + +db.Exec(sql""" +create unique index if not exists ThreadNameIx on thread (name); +""", []) + +db.Exec(sql(""" +create table if not exists person( + id integer primary key, + name $# not null, + password $# not null, + email $# not null, + creation timestamp not null default (DATETIME('now')), + salt varbin(128) not null, + status integer not null, + admin bool default false +);""" % [TUserName, TPassword, TEmail]), []) +# echo "person table already exists" + +db.Exec(sql""" +create unique index if not exists UserNameIx on person (name); +""", []) + +# ----------------------- Forum ------------------------------------------------ + + +if not db.TryExec(sql""" +create table if not exists post( + id integer primary key, + author integer not null, + ip inet not null, + header varchar(100) not null, + content varchar(1000) not null, + thread integer not null, + creation timestamp not null default (DATETIME('now')), + + foreign key (thread) references thread(id), + foreign key (author) references person(id) +);""", []): + echo "post table already exists" + +# -------------------- Session ------------------------------------------------- + +if not db.TryExec(sql(""" +create table if not exists session( + id integer primary key, + ip inet not null, + password $# not null, + userid integer not null, + lastModified timestamp not null default (DATETIME('now')), + foreign key (userid) references person(id) +);""" % [TPassword]), []): + echo "session table already exists" + +if not db.TryExec(sql""" +create table if not exists antibot( + id integer primary key, + ip inet not null, + answer varchar(30) not null, + created timestamp not null default (DATETIME('now')) +);""", []): + echo "antibot table already exists" + +#discard stdin.readline() + +Close(db) + + diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index cb3191a..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM nimlang/nim:1.2.6-ubuntu - -RUN apt-get update -yqq \ - && apt-get install -y --no-install-recommends \ - libsass-dev \ - sqlite3 \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app -COPY . /app - -# install dependencies -RUN nimble install -Y diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 8657235..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: "3.7" - -services: - forum: - build: - context: ../ - dockerfile: ./docker/Dockerfile - volumes: - - "../:/app" - ports: - - "5000:5000" - entrypoint: "/app/docker/entrypoint.sh" diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100755 index d8f5923..0000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -eu - -git submodule update --init --recursive - -# setup -nimble c -d:release src/setup_nimforum.nim -./src/setup_nimforum --dev - -# build frontend -nimble c -r src/buildcss -nimble js -d:release src/frontend/forum.nim -mkdir -p public/js -cp src/frontend/forum.js public/js/forum.js - -# build backend -nimble c src/forum.nim -./src/forum diff --git a/editdb.nim b/editdb.nim new file mode 100644 index 0000000..ea28f93 --- /dev/null +++ b/editdb.nim @@ -0,0 +1,11 @@ + +import strutils, db_sqlite + +var db = Open(connection="nimforum.db", user="postgres", password="", + database="nimforum") + +db.exec(sql"""ALTER TABLE person add column + lastOnline timestamp +""", []) + +close(db) \ No newline at end of file diff --git a/forms.tmpl b/forms.tmpl new file mode 100644 index 0000000..039b435 --- /dev/null +++ b/forms.tmpl @@ -0,0 +1,218 @@ +#! stdtmpl +# +#template `%`(idx: expr): expr {.immediate.} = +# row[idx] +#end template +# +# +#proc genThreadsList(c: var TForumData): string = +# const query = sql"select id, name, views, modified from thread order by modified desc" +# const threadId = 0 +# const name = 1 +# const views = 2 +# +# result = "" + + + + + + + + +# for row in Rows(db, query): + + + #let authorName = getValue(db, sql("select name from person where id = " & + # "(select author from post where id = " & + # "(select min(id) from post where thread = ?))"), %threadId) + +# let posts = GetValue(db, sql"select count(*) from post where thread = ?", %threadId) + + + #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 latestReplyDate = getValue(db, sql("SELECT strftime('%s', " & + # "(select creation from post where id = (select max(id) from post where thread = ?)))"), %threadId) + + +# end for +
TopicsAuthorPostsViewsLast reply
${UrlButton(c, XMLencode(%name), c.genThreadUrl(threadid = %threadid))}${authorName}$posts${XMLencode(%views)} + ${formatTimestamp(latestReplyDate.parseInt())}
+ ${latestReplyAuthor} +
+#end proc +# +# +#proc genPostPreview(c: var TForumData, +# title, content, author, date: string): string = +# result = "" + + + + + + + + +
+ ${XMLEncode(title)} + ${XMLencode(date)} +
+ ${XMLencode(author)} + + #try: + ${content.rstToHtml} + #except EParseError: + # c.errorMsg = getCurrentExceptionMsg() + #end +
+#end proc +# +# +#proc genPostsList(c: var TForumData, threadId: string): string = +# const 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 = ? order by p.id" +# const postId = 0 +# const userName = 1 +# const postHeader = 2 +# const postContent = 3 +# const postCreation = 4 +# const postAuthor = 5 +# const userEmail = 6 +# result = "" +# for row in FastRows(db, query, threadId): + + + + + + + + +
+ ${XMLencode(%postHeader)} + ${XMLencode(%postCreation)} +
+ ${XMLencode(%userName)} +
+ ${genGravatar(%userEmail)} + #if c.userId == %postAuthor and c.currentPost.subject.len == 0: +
${UrlButton(c, "Edit post", c.genThreadUrl(%postId, "edit"))} + #elif c.isAdmin and c.currentPost.subject.len == 0: +
+ ${UrlButton(c, "Edit post", c.genThreadUrl(%postId, "edit"))} + #end if +
+ #try: + ${(%postContent).rstToHtml} + #except EParseError: + # c.errorMsg = getCurrentExceptionMsg() + #end +
+# end for +#end proc +# +# +#proc genFormPost(c: var TForumData, action: string, +# topText, title, content: string, isEdit: bool): string = +# result = "" +
+ +
+
+ ${topText} +
+
+ ${FieldValid(c, "subject", "Subject:")} + ${TextWidget(c, "subject", title, maxlength=100)} +
+ ${FieldValid(c, "content", "Content:")}
+ ${TextAreaWidget(c, "content", content, width=100, height=20)}
+ ${FormSession(c, action)} + + # if isEdit: + Delete Post
+ # end if +
+ + + + Syntax Cheatsheet +
+
+#end proc +# +# +#proc genFormRegister(c: var TForumData): string = +# result = "" +
+ Register
+ + + + + + + + + + + + + + + + + +
${FieldValid(c, "name", "Username:")}${TextWidget(c, "name", reuseText, maxlength=20)}
${FieldValid(c, "new_password", "Password:")}
${FieldValid(c, "email", "E-Mail:")}${TextWidget(c, "email", reuseText, maxlength=30)}
${FieldValid(c, "antibot", "What is " & antibot(c) & "?")}${TextWidget(c, "antibot", "", maxlength=4)}
+ +
+#end proc +# +#proc genFormLogin(c: var TForumData): string = +# result = "" +# if not c.loggedIn: +
+ + + +
Username: +
Password: +
+ +
+ $c.loginErrorMsg +# else: + You're already logged in! +# end if +#end proc +# +# +#proc genListOnline(c: var TForumData): string = +# result = "" +# let stats = c.getStats() +
+
+ Who is online? +
+
+ Out of ${stats.totalUsers} users ${stats.activeUsers.len} are online${if stats.activeUsers.len == 0: "." else: ":"} + #for index, usr in stats.activeUsers: + # if usr.isAdmin: + #if index != 0: result.add ',' + #end if + #result.add(""" """ & usr.nick & """""") + # else: + #if index != 0: result.add ',' + #end if + #result.add(""" """ & usr.nick & """""") + # end if + #end for + +
+ Total threads: ${stats.totalThreads} | Total posts: ${stats.totalPosts} | Newest member: ${stats.newestMember.nick} +
+
+ +#end proc diff --git a/forum.nim b/forum.nim new file mode 100644 index 0000000..1055b43 --- /dev/null +++ b/forum.nim @@ -0,0 +1,580 @@ +# +# +# The Nimrod Forum +# (c) Copyright 2012 Andreas Rumpf +# +# All rights reserved. +# + +import + os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers, + rst, rstgen, captchas, sockets, scgi, jester + +const + unselectedThread = -1 + transientThread = 0 + +type + TCrud = enum crCreate, crRead, crUpdate, crDelete + + TSession = object of TObject + threadid: int + postid: int + userName, userPass, email: string + isAdmin: bool + + TPost = tuple[subject, content: string] + + TForumData = object of TSession + req: TRequest + userid: string + actionContent: string + errorMsg, loginErrorMsg: string + invalidField: string + currentPost: TPost + startTime: float + + TStyledButton = tuple[text: string, link: string] + + TForumStats = object + totalUsers: int + totalPosts: int + totalThreads: int + newestMember: tuple[nick: string, id: int, isAdmin: bool] + activeUsers: seq[tuple[nick: string, id: int, isAdmin: bool]] + +var + db: TDbConn + docConfig: PStringTable + +proc init(c: var TForumData) = + c.userPass = "" + c.userName = "" + c.threadId = unselectedThread + c.postId = -1 + + c.userid = "" + c.actionContent = "" + c.errorMsg = "" + c.loginErrorMsg = "" + c.invalidField = "" + c.currentPost = (subject: "", content: "") + +proc loggedIn(c: TForumData): bool = + result = c.userName.len > 0 + +# --------------- HTML widgets ------------------------------------------------ + +# for widgets "" means the empty string as usual; should the old value be +# used again, pass `reuseText` instead: +const + reuseText = "\1" + +proc TextWidget(c: TForumData, name, defaultText: string, + maxlength = 30, size = -1): string = + let x = if defaultText != reuseText: defaultText + else: XMLencode(c.req.params[name]) + return """""" % [ + name, $maxlength, x, if size != -1: "size=\"" & $size & "\"" else: ""] + +proc TextAreaWidget(c: TForumData, name, defaultText: string, + width = 80, height = 20): string = + let x = if defaultText != reuseText: defaultText + else: XMLencode(c.req.params[name]) + return """""" % [ + name, $width, $height, x] + +proc FieldValid(c: TForumData, name, text: string): string = + if name == c.invalidField: + result = """$1""" % text + else: + result = text + +proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = ""): string = + result = "/t/" & (if threadid == "": $c.threadId else: threadid) + if action != "": + result.add("?action=" & action) + if postId != "": + result.add("&postid=" & postid) + result = c.req.makeUri(result, absolute = false) + +proc FormSession(c: var TForumData, nextAction: string): string = + return """ + """ % [ + $c.threadId, $c.postid] + +proc UrlButton(c: var TForumData, text, url: string): string = + return ("""$2""") % [ + url, text] + +proc genButtons(c: var TForumData, btns: seq[TStyledButton]): string = + if btns.len == 1: + var anchor = "" + + result = ("""$2""") % [ + btns[0].link, btns[0].text, anchor] + else: + result = "" + for i, btn in pairs(btns): + var anchor = "" + + var class = "" + if i == 0: class = "left " + elif i == btns.len()-1: class = "right " + else: class = "middle " + result.add(("""$2""") % [ + btns[i].link, btns[i].text, class, anchor]) + +proc formatTimestamp(t: int): string = + let t2 = getGMTime(TTime(t)) + return t2.format("ddd',' d MMM yyyy HH':'mm 'UTC'") + +proc genGravatar(email: string, size: int = 80): string = + let emailMD5 = email.toLower.toMD5 + result = "" % + ("http://www.gravatar.com/avatar/" & $emailMD5 & "?s=" & $size & + "&d=identicon") + +proc randomSalt(): string = + result = "" + for i in 0..127: + var r = random(225) + if r >= 32 and r <= 126: + result.add(chr(random(225))) + +proc devRandomSalt(): string = + when defined(posix): + result = "" + var f = open("/dev/urandom") + var randomBytes: array[0..127, char] + discard f.readBuffer(addr(randomBytes), 128) + for i in 0..127: + if ord(randomBytes[i]) >= 32 and ord(randomBytes[i]) <= 126: + result.add(randomBytes[i]) + f.close() + else: + result = randomSalt() + +proc makeSalt(): string = + ## Creates a salt using a cryptographically secure random number generator. + try: + result = devRandomSalt() + except EIO: + result = randomSalt() + +proc makePassword(password, salt: string): string = + ## Creates an MD5 hash by combining password and salt. + result = getMD5(salt & getMD5(password)) + +# ----------------------------------------------------------------------------- +template `||`(x: expr): expr = (if not isNil(x): x else: "") + +proc validThreadId(c: TForumData): bool = + result = GetValue(db, sql"select id from thread where id = ?", + $c.threadId).len > 0 + +proc antibot(c: var TForumData): string = + let a = math.random(10)+1 + let b = math.random(1000)+1 + let answer = $(a+b) + + Exec(db, sql"delete from antibot where ip = ?", c.req.ip) + let CaptchaId = TryInsertID(db, + sql"insert into antibot(ip, answer) values (?, ?)", c.req.ip, + answer).int mod 10_000 + let CaptchaFile = getCaptchaFilename(CaptchaId) + createCaptcha(CaptchaFile, $a & "+" & $b) + result = """""" % c.req.getCaptchaUrl(captchaId) + +const + SecureChars = {'A'..'Z', 'a'..'z', '0'..'9', '_', '\128'..'\255'} + +proc setError(c: var TForumData, field, msg: string): bool {.inline.} = + c.invalidField = field + c.errorMsg = "Error: " & msg + return false + +proc register(c: var TForumData, name, pass, antibot, email: string): bool = + # Username validation: + if name.len == 0 or not allCharsInSet(name, SecureChars): + return setError(c, "name", "Invalid username!") + if GetValue(db, sql"select name from person where name = ?", name).len > 0: + return setError(c, "name", "Username already exists!") + + # Password validation: + if pass.len < 4: + return setError(c, "new_password", "Invalid password!") + + # antibot validation: + let correctRes = GetValue(db, + sql"select answer from antibot where ip = ?", c.req.ip) + if antibot != correctRes: + return setError(c, "antibot", "You seem to be a bot!") + + # email validation + if not validEmailAddress(email): + return setError(c, "email", "Invalid email address") + + # perform registration: + var salt = makeSalt() + Exec(db, sql("INSERT INTO person(name, password, email, salt, status, lastOnline) " & + "VALUES (?, ?, ?, ?, 'user', DATETIME('now'))"), name, + makePassword(pass, salt), email, salt) + # return setError(c, "", "Could not create your account!") + return true + +proc checkLoggedIn(c: var TForumData) = + let pass = c.req.cookies["sid"] + if pass.len == 0: return + if ExecAffectedRows(db, + sql("update session set lastModified = DATETIME('now') " & + "where ip = ? and password = ?"), + c.req.ip, pass) > 0: + c.userpass = pass + c.userid = GetValue(db, + sql"select userid from session where ip = ? and password = ?", + c.req.ip, pass) + + let row = getRow(db, + sql"select name, email, admin from person where id = ?", c.userid) + c.username = ||row[0] + c.email = ||row[1] + c.isAdmin = parseBool(||row[2]) + # Update lastOnline + db.exec(sql"update person set lastOnline = DATETIME('now') where id = ?", + c.userid) + + else: + echo("SID not found in sessions. Assuming logged out.") + +proc logout(c: var TForumData) = + const query = sql"delete from session where ip = ? and password = ?" + c.username = "" + c.userpass = "" + Exec(db, query, c.req.ip, c.req.cookies["sid"]) + +proc incrementViews(c: var TForumData) = + const query = sql"update thread set views = views + 1 where id = ?" + Exec(db, query, $c.threadId) + +proc isPreview(c: TForumData): bool = + result = c.req.params["previewBtn"].len > 0 # TODO: Could be wrong? + +proc isDelete(c: TForumData): bool = + result = c.req.params["delete"].len > 0 + +proc rstToHtml(content: string): string = + result = rstgen.rstToHtml(content, {roSupportSmilies, roSupportMarkdown}, + docConfig) + +proc validateRst(c: var TForumData, content: string): bool = + result = true + try: + discard rstToHtml(content) + except EParseError: + result = setError(c, "", getCurrentExceptionMsg()) + +proc crud(c: TCrud, table: string, data: openArray[string]): TSqlQuery = + case c + of crCreate: + var fields = "insert into " & table & "(" + var vals = "" + for i, d in data: + if i > 0: + fields.add(", ") + vals.add(", ") + fields.add(d) + vals.add('?') + result = sql(fields & ") values (" & vals & ")") + of crRead: + var res = "select " + for i, d in data: + if i > 0: res.add(", ") + res.add(d) + result = sql(res & " from " & table) + of crUpdate: + var res = "update " & table & " set " + for i, d in data: + if i > 0: res.add(", ") + res.add(d) + res.add(" = ?") + result = sql(res & " where id = ?") + of crDelete: + result = sql("delete from " & table & " where id = ?") + +template retrSubject(c: expr) = + let subject = c.req.params["subject"] + if subject.len < 3: return setError(c, "subject", "Subject not long enough") + +template retrContent(c: expr) = + let content = c.req.params["content"] + if not validateRst(c, content): return false + +template retrPost(c: expr) = + retrSubject(c) + retrContent(c) + +template checkLogin(c: expr) = + if not loggedIn(c): return setError(c, "", "User is not logged in") + +template checkOwnership(c, postId: expr) = + if not c.isAdmin: + let x = getValue(db, sql"select author from post where id = ?", + postId) + if x != c.userId: + return setError(c, "", "You are not the owner of this post") + +template setPreviewData(c: expr) = + c.currentPost.subject = subject + c.currentPost.content = content + +template writeToDb(c, cr, postId: expr) = + exec(db, crud(cr, "post", "author", "ip", "header", "content", "thread"), + c.userId, c.req.ip, subject, content, $c.threadId, postId) + +proc edit(c: var TForumData, postId: int): bool = + checkLogin(c) + if c.isPreview: + retrPost(c) + setPreviewData(c) + elif c.isDelete: + checkOwnership(c, $postId) + if not TryExec(db, crud(crDelete, "post"), $postId): + return setError(c, "", "database error") + # delete corresponding thread: + if ExecAffectedRows(db, + sql"delete from thread where id not in (select thread from post)") > 0: + # whole thread has been deleted, so: + c.threadId = unselectedThread + result = true + else: + checkOwnership(c, $postId) + retrPost(c) + exec(db, crud(crUpdate, "post", "header", "content"), + subject, content, $postId) + result = true + +proc reply(c: var TForumData): bool = + checkLogin(c) + retrPost(c) + if c.isPreview: + setPreviewData(c) + else: + writeToDb(c, crCreate, "") + exec(db, sql"update thread set modified = DATETIME('now') where id = ?", + $c.threadId) + result = true + +proc newThread(c: var TForumData): bool = + const query = sql"insert into thread(name, views, modified) values (?, 0, DATETIME('now'))" + checkLogin(c) + retrPost(c) + if c.isPreview: + setPreviewData(c) + c.threadID = transientThread + else: + c.threadID = TryInsertID(db, query, c.req.params["subject"]).int + if c.threadID < 0: return setError(c, "subject", "Subject already exists") + writeToDb(c, crCreate, "") + result = true + +proc login(c: var TForumData, name, pass: string): bool = + # get form data: + const query = + sql"select id, name, password, email, salt, admin from person where name = ?" + if name.len == 0: + return c.setError("name", "Username cannot be nil.") + var success = false + for row in FastRows(db, query, name): + if row[2] == makePassword(pass, row[4]): + c.userid = row[0] + c.username = row[1] + c.userpass = row[2] + c.email = row[3] + c.isAdmin = row[5].parseBool + success = true + break + if success: + # create session: + Exec(db, + sql"insert into session (ip, password, userid) values (?, ?, ?)", + c.req.ip, c.userpass, c.userid) + return true + else: + return c.setError("password", "Login failed!") + +proc genActionMenu(c: var TForumData): string = + result = "" + var btns: seq[TStyledButton] = @[] + # TODO: Make this detection better? + if c.req.pathInfo notin ["/", "/login", "/register", "/dologin", "/doregister"]: + btns.add(("Thread List", c.req.makeUri("/", false))) + if c.loggedIn: + let hasReplyBtn = c.req.pathInfo != "/donewthread" and c.req.pathInfo != "/doreply" + if c.threadId >= 0 and hasReplyBtn: + let replyUrl = c.genThreadUrl("", "reply") & "#reply" + btns.add(("Reply", replyUrl)) + btns.add(("New Thread", c.req.makeUri("/newthread", false))) + result = c.genButtons(btns) + +proc getStats(c: var TForumData): TForumStats = + const totalUsersQuery = + sql"select count(*) from person" + result.totalUsers = getValue(db, totalUsersQuery).parseInt + const totalPostsQuery = + sql"select count(*) from post" + result.totalPosts = getValue(db, totalPostsQuery).parseInt + const totalThreadsQuery = + sql"select count(*) from thread" + result.totalThreads = getValue(db, totalThreadsQuery).parseInt + + var newestMemberCreation = 0 + result.activeUsers = @[] + const getUsersQuery = + sql"select id, name, admin, strftime('%s', lastOnline), strftime('%s', creation) from person" + for row in fastRows(db, getUsersQuery): + let secs = if row[3] == "": 0 else: row[3].parseint + let lastOnlineSeconds = getTime() - TTime(secs) + if lastOnlineSeconds < (60 * 5): # 5 minutes + result.activeUsers.add((row[1], row[0].parseInt, row[2].parseBool)) + if row[4].parseInt > newestMemberCreation: + result.newestMember = (row[1], row[0].parseInt, row[2].parseBool) + newestMemberCreation = row[4].parseInt + +include "forms.tmpl" +include "main.tmpl" + +proc prependRe(s: string): string = + result = if s.len == 0: + "" + elif s.startswith("Re:"): s + else: "Re: " & s + +template createTFD(): stmt = + var c: TForumData + init(c) + c.req = request + c.startTime = epochTime() + if request.cookies.len > 0: + checkLoggedIn(c) + +get "/": + createTFD() + resp genMain(c, genThreadsList(c), true) + +get "/t/@threadid/?": + createTFD() + parseInt(@"threadid", c.threadId, -1..1000_000) + if (@"postid").len > 0: + parseInt(@"postid", c.postId, -1..1000_000) + + if (@"action").len > 0: + case @"action" + of "reply": + let subject = GetValue(db, + sql"select header from post where id = (select max(id) from post where thread = ?)", + $c.threadId).prependRe + body = genPostsList(c, $c.threadId) + echo(c.threadId) + body.add genFormPost(c, "doreply", "Reply", subject, "", false) + of "edit": + cond c.postId != -1 + const query = sql"select header, content from post where id = ?" + let row = getRow(db, query, $c.postId) + let header = ||row[0] + let content = ||row[1] + body = genFormPost(c, "doedit", "Edit", header, content, true) + resp c.genMain(body) + else: + cond validThreadId(c) + incrementViews(c) + resp genMain(c, genPostsList(c, $c.threadId)) + +get "/login/?": + createTFD() + resp genMain(c, genFormLogin(c)) + +get "/logout/?": + createTFD() + logout(c) + redirect(uri("/")) + +get "/register/?": + createTFD() + resp genMain(c, genFormRegister(c)) + +template readIDs(): stmt = + # Retrieve the threadid and postid + if (@"threadid").len > 0: + parseInt(@"threadid", c.threadId, -1..1000_000) + if (@"postid").len > 0: + parseInt(@"postid", c.postId, -1..1000_000) + +template finishLogin(): stmt = + setCookie("sid", c.userpass, daysForward(7)) + redirect(uri("/")) + +template handleError(action: string, topText: string, isEdit: bool): stmt = + if c.isPreview: + body.add genPostPreview(c, @"subject", @"content", + c.userName, $getGMTime(getTime())) + body.add genFormPost(c, action, topText, reuseText, reuseText, isEdit) + resp genMain(c, body) + +post "/dologin": + createTFD() + if login(c, @"name", @"password"): + finishLogin() + else: + resp c.genMain(genFormLogin(c)) + +post "/doregister": + createTFD() + if c.register(@"name", @"new_password", @"antibot", @"email"): + discard c.login(@"name", @"new_password") + finishLogin() + else: + resp c.genMain(genFormRegister(c)) + +post "/donewthread": + createTFD() + if newThread(c): + redirect(uri("/")) + else: + body = "" + handleError("donewthread", "New thread", false) + +post "/doreply": + createTFD() + readIDs() + if reply(c): + redirect(c.genThreadUrl()) + else: + body = genPostsList(c, $c.threadId) + handleError("doreply", "Reply", false) + +post "/doedit": + createTFD() + readIDs() + if edit(c, c.postId): + redirect(c.genThreadUrl()) + else: + body = "" + handleError("doedit", "Edit", true) + +get "/newthread/?": + createTFD() + resp genMain(c, genFormPost(c, "donewthread", "New thread", "", "", false)) + +when isMainModule: + docConfig = rstgen.defaultConfig() + math.randomize() + db = Open(connection="nimforum.db", user="postgres", password="", + database="nimforum") + var http = true + if paramCount() > 0: + if paramStr(1) == "scgi": + http = false + run("", port = TPort(9000), http = http) + db.close() + diff --git a/license.txt b/license.txt deleted file mode 100644 index 3eb168b..0000000 --- a/license.txt +++ /dev/null @@ -1,18 +0,0 @@ -Copyright (C) 2018 Andreas Rumpf, Dominik Picheta - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/localhost.local/public/css/custom-style.scss b/localhost.local/public/css/custom-style.scss deleted file mode 100644 index 6c9e50c..0000000 --- a/localhost.local/public/css/custom-style.scss +++ /dev/null @@ -1,16 +0,0 @@ -// Use this to customise the styles of your forum. -$primary-color: #6577ac; -$body-font-color: #292929; -$dark-color: #505050; -$label-color: #7cd2ff; -$secondary-btn-color: #f1f1f1; - -// Define nav bar colours. -$body-bg: #ffffff; -$navbar-color: $body-bg; -$navbar-border-color-dark: $body-bg; -$navbar-primary-color: #e80080; - -#main-navbar input#search-box { - border: 1px solid #e6e6e6; -} \ No newline at end of file diff --git a/localhost.local/public/images/logo.png b/localhost.local/public/images/logo.png deleted file mode 100644 index 4ac52b7..0000000 Binary files a/localhost.local/public/images/logo.png and /dev/null differ diff --git a/main.tmpl b/main.tmpl new file mode 100644 index 0000000..964f1a0 --- /dev/null +++ b/main.tmpl @@ -0,0 +1,53 @@ +#! stdtmpl +#proc genMain(c: var TForumData, content: string, mainPage = false): string = +# result = "" + + + + Nimrod Forum + + + + +
+
+ Homepage +
+ + + +
+ ${c.genActionMenu} +
+ +
+ $content + $c.errorMsg +
+
+ ${c.genActionMenu} +
+ + #if mainPage: + ${c.genListOnline} + #end if + +
+ + + diff --git a/mockup/index.html b/mockup/index.html deleted file mode 100644 index b836674..0000000 --- a/mockup/index.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - The Nim programming language forum - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TopicCategoryUsersRepliesViewsActivity
Few mixed up questions
help
-
- -
-
- -
-
-
-
-
-
-
-
554745m
Lexers and parsers in Nim
community
-
-
-
01444m
I need help 2
help
-
- -
-
- -
-
-
-
41d
- - last visit - -
Nim v1.0 is here!
announcement
-
- -
-
- -
-
44d
- - load more threads - -
-
- - - - \ No newline at end of file diff --git a/mockup/thread.html b/mockup/thread.html deleted file mode 100644 index 298bb80..0000000 --- a/mockup/thread.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - The Nim programming language forum - - - - - - - - - -
-
-

Lexers and parsers in nim

-
community -
-
-
-
-
- Avatar -
-
-
-
-
- ErikCampobadal -
-
Jan 2015
-
-
-

Hey! I'm willing to create a programming language using nim.

- -

It's an educational project. Been reading about compilers for weeks now and I started using tools like flex and bison for lexer and parser. I know nim have a parsing library but nowhere near that level.

- -

There is an old post (2014) with a similar question so I'm bringing that back a few years later. Is there anything anyone know that could speed up the process of developing a programing language using nim? (I can have c code if needed ofc)

-
-
- -
- -
-
- -
-
-
-
- -
-
-
- Avatar -
-
-
-
-
- twetzel59 -
-
Jan 2015
-
-
-

Wow, I was just reading about the compilation pipeline today!

- -

I suppose you could use at least the lexing part from a generator like flex, not so sure about using AST generators easily (it's possible).

- -

Is your language complicated enough to warrant a parser generator or could you just use a custom parser?

-
-
- -
- -
-
- -
-
-
-
- -
-
- -
-
-
- 3 years later -
-
-
- -
-
-
- Avatar -
-
-
-
-
- dom96 -
-
32m
-
-
-

Let us test this new design a bit, shall we?

-
proc hello(x: int) =
-  echo("Hello ", x)
-
-42.hello()
Output
Hello 42
- -

The greatest function ever written is hello.

-
-

Designing websites is often a pain.

-
Multi-level baby!
-

True that.

-

I also want to be able to support more detailed quoting:

-
-
-
- Avatar -
- Araq: - -
- Unix is a cancer. -
-

We also want to be able to highlight user mentions:

-

Please let - - @Araq - - know that this forum is awesome.

-
-
- -
- -
-
- -
-
-
-
- - - -
-
- -
-
-
- Replying to "Lexers and parsers in nim" -
-
- -
-
-
- -
-
- - - - \ No newline at end of file diff --git a/nimforum.nimble b/nimforum.nimble deleted file mode 100644 index 58a22f7..0000000 --- a/nimforum.nimble +++ /dev/null @@ -1,64 +0,0 @@ -# Package -version = "2.1.0" -author = "Dominik Picheta" -description = "The Nim forum" -license = "MIT" - -srcDir = "src" - -bin = @["forum"] - -skipExt = @["nim"] - -# Dependencies - -requires "nim >= 1.0.6" -requires "jester#405be2e" -requires "bcrypt#440c5676ff6" -requires "hmac#9c61ebe2fd134cf97" -requires "recaptcha#d06488e" -requires "sass#649e0701fa5c" - -requires "karax#5f21dcd" - -requires "webdriver#429933a" - -# Tasks - -task backend, "Compiles and runs the forum backend": - exec "nimble c src/forum.nim" - exec "./src/forum" - -task runbackend, "Runs the forum backend": - exec "./src/forum" - -task testbackend, "Runs the forum backend in test mode": - exec "nimble c -r -d:skipRateLimitCheck src/forum.nim" - -task frontend, "Builds the necessary JS frontend (with CSS)": - exec "nimble c -r src/buildcss" - exec "nimble js -d:release src/frontend/forum.nim" - mkDir "public/js" - cpFile "src/frontend/forum.js", "public/js/forum.js" - -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 (with admin account!)": - exec "nimble c src/setup_nimforum" - exec "./src/setup_nimforum --test" - -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 -d:actionDelayMs=0 tests/browsertester" - -task fasttest, "Runs tester without recompiling backend": - exec "nimble c -r -d:actionDelayMs=0 tests/browsertester" diff --git a/nimrod.cfg b/nimrod.cfg new file mode 100644 index 0000000..3ed332c --- /dev/null +++ b/nimrod.cfg @@ -0,0 +1,6 @@ + +# we need the documentation generator of the compiler: +--path:"$nimrod/packages/docutils" + +--path:"$nimrod" +--path:"/home/dominik/code/nimrod/jester" \ No newline at end of file diff --git a/public/captchas/.gitignore b/public/captchas/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/public/captchas/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/public/css/nimforum.scss b/public/css/nimforum.scss deleted file mode 100644 index 2daecdb..0000000 --- a/public/css/nimforum.scss +++ /dev/null @@ -1,781 +0,0 @@ -@import "custom-style"; - -// Import full Spectre source code -@import "spectre/src/spectre"; - -// Global styles. -// - TODO: Make these non-global. -.btn, .form-input { - margin-right: $control-padding-x; -} - -table th { - font-size: 0.65rem; -} - -// Spectre fixes. -// - Weird avatar outline. -.avatar { - background: transparent; -} - -// Custom styles. -// - Navigation bar. -$navbar-height: 60px; -$default-category-color: #a3a3a3; -$logo-height: $navbar-height - 20px; - -.navbar-button { - border-color: $navbar-border-color-dark; - background-color: $navbar-primary-color; - color: $navbar-color; - - &:focus { - box-shadow: none; - } - - &:hover { - background-color: darken($navbar-primary-color, 20%); - color: $navbar-color; - border-color: $navbar-border-color-dark; - } -} - -#main-navbar { - background-color: $navbar-color; - - .navbar { - height: $navbar-height; - } - - // Unfortunately we must colour the controls in the navbar manually. - .search-input { - @extend .form-input; - min-width: 120px; - border-color: $navbar-border-color-dark; - } - - .search-input:focus { - box-shadow: none; - border-color: $navbar-border-color-dark; - } - - .btn-primary { - @extend .navbar-button; - } - -} - -#img-logo { - vertical-align: middle; - height: $logo-height; -} - -.menu-right { - // To make sure the user menu doesn't move off the screen. - @media (max-width: 1600px) { - left: auto; - right: 0; - } - position: absolute; -} - -// - Main buttons -.btn-secondary { - background: $secondary-btn-color; - border-color: darken($secondary-btn-color, 5%); - color: invert($secondary-btn-color); - - margin-right: $control-padding-x*2; - - &:hover, &:focus { - background: darken($secondary-btn-color, 5%); - border-color: darken($secondary-btn-color, 10%); - - color: invert($secondary-btn-color); - } - - &:focus { - @include control-shadow(darken($secondary-btn-color, 40%)); - } -} - -#main-buttons { - margin-top: $control-padding-y*2; - margin-bottom: $control-padding-y*2; - - .dropdown > .btn { - @extend .btn-secondary; - } -} - -#category-selection { - .dropdown { - .btn { - margin-right: 0px; - } - } - .plus-btn { - margin-right: 0px; - i { - margin-right: 0px; - } - } -} - -.category-description { - opacity: 0.6; - font-size: small; -} - -.category-status { - font-size: small; - font-weight: bold; - - .topic-count { - margin-left: 5px; - opacity: 0.7; - font-size: small; - } -} - -.category { - white-space: nowrap; -} - -#new-thread { - .modal-container .modal-body { - max-height: none; - } - - .panel-body { - padding-top: $control-padding-y*2; - padding-bottom: $control-padding-y*2; - } - - .form-input[name='subject'] { - margin-bottom: $control-padding-y*2; - } - - textarea.form-input, .panel-body > div { - min-height: 40vh; - } - - .footer { - float: right; - margin-top: $control-padding-y*2; - } -} - -// - Thread table -.thread-title { - a, a:hover { - color: $body-font-color; - text-decoration: none; - } - - a.visited, a:visited { - color: lighten($body-font-color, 40%); - } - - i { - // Icon - margin-right: $control-padding-x-sm; - } -} - -.thread-list { - @extend .container; - @extend .grid-xl; -} - -.category-list { - @extend .thread-list; - - - .category-title { - @extend .thread-title; - a, a:hover { - color: lighten($body-font-color, 10%); - text-decoration: none; - } - } - - .category-description { - opacity: 0.6; - } -} - -#categories-list .category { - border-left: 6px solid; - border-left-color: $default-category-color; -} - -$super-popular-color: #f86713; -$popular-color: darken($super-popular-color, 25%); -$threads-meta-color: #545d70; - -.super-popular-text { - color: $super-popular-color; -} - -.popular-text { - color: $popular-color; -} - -.views-text { - color: $threads-meta-color; -} - -.label-custom { - color: white; - background-color: $label-color; - - font-size: 0.6rem; - padding-left: 0.3rem; - padding-right: 0.3rem; - border-radius: 5rem; -} - -.last-visit-separator { - td { - border-bottom: 1px solid $super-popular-color; - line-height: 0.1rem; - padding: 0; - text-align: center; - } - - span { - color: $super-popular-color; - padding: 0 8px; - font-size: 0.7rem; - background-color: $body-bg; - } -} - -.no-border { - td { - border: none; - } -} - -.category-color { - width: 0; - height: 0; - border: 0.25rem solid $default-category-color; - display: inline-block; - margin-right: 5px; -} - -.load-more-separator { - text-align: center; - color: darken($label-color, 35%); - background-color: lighten($label-color, 15%); - text-transform: uppercase; - font-weight: bold; - font-size: 80%; - cursor: pointer; - - td { - border: none; - padding: $control-padding-x $control-padding-y/2; - } -} - -// - Thread view -.title { - margin-top: $control-padding-y*2; - margin-bottom: $control-padding-y*2; - - p { - font-size: 1.4rem; - font-weight: bold; - - color: darken($dark-color, 20%); - - margin: 0; - } - - i.fas { - margin-right: $control-padding-x-sm; - } -} - -.thread-replies, .thread-time, .views-text, .popular-text, .centered-header { - text-align: center; -} - -.thread-users { - text-align: left; -} - -.thread-time { - color: $threads-meta-color; - - &.is-new { - @extend .text-success; - } - - &.is-old { - @extend .text-gray; - } - -} - -// Hide all the avatars but the first on small screens. -@media screen and (max-width: 600px) { - #threads-list a:not(:first-child) > .avatar { - display: none; - } -} - -.posts, .about { - @extend .grid-md; - @extend .container; - margin: 0; - padding: 0; - - margin-bottom: 10rem; // Just some empty space at the bottom. -} - -.post { - @extend .tile; - border-top: 1px solid $border-color; - padding-top: $control-padding-y-lg; - - &:target .post-main, &.highlight .post-main { - animation: highlight 2000ms ease-out; - } -} - -@keyframes highlight { - 0% { - background-color: lighten($primary-color, 20%); - } - 100% { - background-color: inherit; - } -} - -.post-icon { - @extend .tile-icon; -} - -.post-avatar { - @extend .avatar; - font-size: 1.6rem; - height: 2.5rem; - width: 2.5rem; -} - -.post-main { - @extend .tile-content; - - margin-bottom: $control-padding-y-lg*2; - // https://stackoverflow.com/a/41675912/492186 - flex: 1; - min-width: 0; -} - -.post-title { - margin-bottom: $control-padding-y*2; - - &, a, a:visited, a:hover { - color: lighten($body-font-color, 20%); - text-decoration: none; - } - - - .thread-title { - width: 100%; - - a > div { - display: inline-block; - } - } - - .post-username { - font-weight: bold; - display: inline-block; - - i { - margin-left: $control-padding-x; - } - } - - .post-metadata { - float: right; - - .post-replyingTo { - display: inline-block; - margin-right: $control-padding-x; - - i.fa-reply { - transform: rotate(180deg); - } - } - - .post-history { - display: inline-block; - margin-right: $control-padding-x; - - i { - font-size: 90%; - } - - .edit-count { - margin-right: $control-padding-x-sm/2; - } - } - } -} - -.post-content, .about { - img { - max-width: 100%; - } -} - -.post-buttons { - float: right; - - > div { - display: inline-block; - } - - .btn { - background: transparent; - border-color: transparent; - color: darken($secondary-btn-color, 40%); - - margin: 0; - margin-left: $control-padding-y-sm; - } - - .btn:hover { - background: $secondary-btn-color; - border-color: darken($secondary-btn-color, 5%); - color: invert($secondary-btn-color); - } - - .btn:focus { - @include control-shadow(darken($secondary-btn-color, 50%)); - } - - .btn:active { - box-shadow: inset 0 0 .4rem .01rem darken($secondary-btn-color, 80%); - } - - .like-button i:hover, .like-button i.fas { - color: #f783ac; - } - - .like-count { - margin-right: $control-padding-x-sm; - } -} - -#thread-buttons { - border-top: 1px solid $border-color; - width: 100%; - padding-top: $control-padding-y; - padding-bottom: $control-padding-y; - @extend .clearfix; - - .btn { - float: right; - margin-right: 0; - margin-left: $control-padding-x; - } -} - -blockquote { - border-left: 0.2rem solid darken($bg-color, 10%); - background-color: $bg-color; - - .detail { - margin-bottom: $control-padding-y; - color: lighten($body-font-color, 20%); - } -} - -.quote-avatar { - @extend .avatar; - @extend .avatar-sm; -} - -.quote-link { - float: right; -} - -.user-mention { - @extend .chip; - vertical-align: initial; - font-weight: bold; - display: inline-block; - font-size: 85%; - height: inherit; - padding: 0.08rem 0.4rem; - background-color: darken($bg-color-dark, 5%); - - img { - @extend .avatar; - @extend .avatar-sm; - } -} - -.code-buttons { - position: absolute; - bottom: 0; - right: 0; - - .btn-primary { - margin-bottom: $control-padding-y; - } -} - -.execution-result { - @extend .toast; - - h6 { - font-family: $base-font-family; - } -} - -.execution-success { - @extend .toast-success; -} - -.code { - // Don't show the "none". - &[data-lang="none"]::before { - content: ""; - } - - // &:not([data-lang="Nim"]) > .code-buttons { - // display: none; - // } -} -.code-buttons { - display: none; -} - -.post-content { - pre:not(.code) { - overflow: scroll; - } -} - -.information { - @extend .tile; - border-top: 1px solid $border-color; - padding-top: $control-padding-y-lg*2; - padding-bottom: $control-padding-y-lg*2; - color: lighten($body-font-color, 20%); - .information-title { - font-weight: bold; - } - - &.no-border { - border: none; - } -} - -.information-icon { - @extend .tile-icon; - - i { - width: $unit-16; - text-align: center; - font-size: 1rem; - - } -} - -.time-passed { - text-transform: uppercase; -} - -.load-more-posts { - text-align: center; - color: darken($label-color, 35%); - background-color: lighten($label-color, 15%); - border: none; - text-transform: uppercase; - font-weight: bold; - cursor: pointer; - - .information-main { - width: 100%; - text-align: left; - } - - .more-post-count { - color: rgba(darken($label-color, 35%), 0.5); - margin-right: $control-padding-x*2; - float: right; - } -} - -.form-input.post-text-area { - margin-top: $control-padding-y*2; - resize: vertical; -} - -#reply-box { - .panel { - margin-top: $control-padding-y*2; - } -} - -code { - color: $body-font-color; - background-color: $bg-color; -} - -tt { - @extend code; -} - -hr { - background: $border-color; - height: $border-width; - margin: $unit-2 0; - border: 0; -} - -.edit-box { - .edit-buttons { - margin-top: $control-padding-y*2; - - float: right; - - > div { - display: inline-block; - } - } - - .text-error { - margin-top: $control-padding-y*3; - display: inline-block; - } - - .form-input.post-text-area { - margin-bottom: $control-padding-y*2; - } -} - -@import "syntax.scss"; - -// - Profile view - -.profile { - @extend .tile; - margin-top: $control-padding-y*5; -} - -.profile-icon { - @extend .tile-icon; - margin-right: $control-padding-x; -} - -.profile-avatar { - @extend .avatar; - @extend .avatar-xl; - - height: 6.2rem; - width: 6.2rem; -} - -.profile-content { - @extend .tile-content; - padding: $control-padding-x $control-padding-y; -} - -.profile-title { - @extend .tile-title; -} - -.profile-stats { - dl { - border-top: 1px solid $border-color; - border-bottom: 1px solid $border-color; - padding: $control-padding-x $control-padding-y; - } - - dt { - font-weight: normal; - color: lighten($dark-color, 15%); - } - - dt, dd { - display: inline-block; - margin: 0; - margin-right: $control-padding-x; - } - - dd { - margin-right: $control-padding-x-lg; - } -} - -.profile-tabs { - margin-bottom: $control-padding-y-lg*2; -} - -.profile-post { - @extend .post; - - .profile-post-main { - flex: 1; - } - - .profile-post-time { - float: right; - } -} - -.spoiler { - text-shadow: gray 0px 0px 15px; - color: transparent; - -moz-user-select: none; - user-select: none; - cursor: normal; - - &:hover, &:focus { - text-shadow: $body-font-color 0px 0px 0px; - } -} - -.profile-post-title { - @extend .thread-title; -} - -// - Sign up modal - -#signup-modal { - .modal-container .modal-body { - max-height: 60vh; - } -} - -.license-text { - text-align: left; - font-size: 80%; -} - -// - Reset password -#resetpassword { - @extend .grid-sm; - @extend .container; - - .form-input { - display: inline-block; - width: 15rem; - margin-bottom: $control-padding-y*2; - } - - .footer { - margin-top: $control-padding-y*2; - } -} diff --git a/public/css/normalize.css b/public/css/normalize.css new file mode 100644 index 0000000..7dbb346 --- /dev/null +++ b/public/css/normalize.css @@ -0,0 +1,284 @@ +/* + * HTML5 Boilerplate + * + * What follows is the result of much research on cross-browser styling. + * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, + * Kroc Camen, and the H5BP dev community and team. + * + * Detailed information about this CSS: h5bp.com/css + * + * ==|== normalize ========================================================== + */ + + +/* ============================================================================= + HTML5 display definitions + ========================================================================== */ + +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } +audio:not([controls]) { display: none; } +[hidden] { display: none; } + + +/* ============================================================================= + Base + ========================================================================== */ + +/* + * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units + * 2. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g + */ + +html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + +html, button, input, select, textarea { font-family: sans-serif; color: #222; } + +body { margin: 0; font-size: 1em; line-height: 1.4; } + + +/* ============================================================================= + Links + ========================================================================== */ + +a { color: #00e; } +a:visited { color: #551a8b; } +a:hover { color: #06e; } +a:focus { outline: thin dotted; } + +/* Improve readability when focused and hovered in all browsers: h5bp.com/h */ +a:hover, a:active { outline: 0; } + + +/* ============================================================================= + Typography + ========================================================================== */ + +abbr[title] { border-bottom: 1px dotted; } + +b, strong { font-weight: bold; } + +blockquote { margin: 1em 40px; } + +dfn { font-style: italic; } + +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } + +ins { background: #ff9; color: #000; text-decoration: none; } + +mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } + +/* Redeclare monospace font family: h5bp.com/j */ +pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; } + +/* Improve readability of pre-formatted text in all browsers */ +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } + +q { quotes: none; } +q:before, q:after { content: ""; content: none; } + +small { font-size: 85%; } + +/* Position subscript and superscript content without affecting line-height: h5bp.com/k */ +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + + +/* ============================================================================= + Lists + ========================================================================== */ + +ul, ol { margin: 1em 0; padding: 0 0 0 40px; } +dd { margin: 0 0 0 40px; } +nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; } + + +/* ============================================================================= + Embedded content + ========================================================================== */ + +/* + * 1. Improve image quality when scaled in IE7: h5bp.com/d + * 2. Remove the gap between images and borders on image containers: h5bp.com/i/440 + */ + +img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } + +/* + * Correct overflow not hidden in IE9 + */ + +svg:not(:root) { overflow: hidden; } + + +/* ============================================================================= + Figures + ========================================================================== */ + +figure { margin: 0; } + + +/* ============================================================================= + Forms + ========================================================================== */ + +form { margin: 0; } +fieldset { border: 0; margin: 0; padding: 0; } + +/* Indicate that 'label' will shift focus to the associated form element */ +label { cursor: pointer; } + +/* + * 1. Correct color not inheriting in IE6/7/8/9 + * 2. Correct alignment displayed oddly in IE6/7 + */ + +legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; } + +/* + * 1. Correct font-size not inheriting in all browsers + * 2. Remove margins in FF3/4 S5 Chrome + * 3. Define consistent vertical alignment display in all browsers + */ + +button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } + +/* + * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet) + */ + +button, input { line-height: normal; } + +/* + * 1. Display hand cursor for clickable form elements + * 2. Allow styling of clickable form elements in iOS + * 3. Correct inner spacing displayed oddly in IE7 (doesn't effect IE6) + */ + +button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } + +/* + * Re-set default cursor for disabled elements + */ + +button[disabled], input[disabled] { cursor: default; } + +/* + * Consistent box sizing and appearance + */ + +input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; } +input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } + +/* + * Remove inner padding and border in FF3/4: h5bp.com/l + */ + +button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } + +/* + * 1. Remove default vertical scrollbar in IE6/7/8/9 + * 2. Allow only vertical resizing + */ + +textarea { overflow: auto; vertical-align: top; resize: vertical; } + +/* Colors for form validity */ +input:valid, textarea:valid { } +input:invalid, textarea:invalid { background-color: #f0dddd; } + + +/* ============================================================================= + Tables + ========================================================================== */ + +table { border-collapse: collapse; border-spacing: 0; } +td { vertical-align: top; } + + +/* ============================================================================= + Chrome Frame Prompt + ========================================================================== */ + +.chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; } + + +/* ==|== primary styles ===================================================== + Author: + ========================================================================== */ + + + + + + + + + + + + + + + + +/* ==|== media queries ====================================================== + EXAMPLE Media Query for Responsive Design. + This example overrides the primary ('mobile first') styles + Modify as content requires. + ========================================================================== */ + +@media only screen and (min-width: 35em) { + /* Style adjustments for viewports that meet the condition */ +} + + + +/* ==|== non-semantic helper classes ======================================== + Please define your styles before this section. + ========================================================================== */ + +/* For image replacement */ +.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } +.ir br { display: none; } + +/* Hide from both screenreaders and browsers: h5bp.com/u */ +.hidden { display: none !important; visibility: hidden; } + +/* Hide only visually, but have it available for screenreaders: h5bp.com/v */ +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } + +/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */ +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } + +/* Hide visually and from screenreaders, but maintain layout */ +.invisible { visibility: hidden; } + +/* Contain floats: h5bp.com/q */ +.clearfix:before, .clearfix:after { content: ""; display: table; } +.clearfix:after { clear: both; } +.clearfix { *zoom: 1; } + + + +/* ==|== print styles ======================================================= + Print styles. + Inlined to avoid required HTTP connection: h5bp.com/r + ========================================================================== */ + +@media print { + * { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */ + a, a:visited { text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } /* h5bp.com/t */ + tr, img { page-break-inside: avoid; } + img { max-width: 100% !important; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3 { page-break-after: avoid; } +} diff --git a/public/css/spectre b/public/css/spectre deleted file mode 160000 index 7a6af53..0000000 --- a/public/css/spectre +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a6af53bca72b549b2cbf1948763348bd5b30dcd diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..888f8f2 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,308 @@ +html, body { + height: 100%; +} + +#wrapper { + min-height: 100%; +} + +div#header { + background-color: #3d3d3d; + color: #ffffff; + padding-top: 3pt; + padding-bottom: 3pt; +} + +div#header span#welcome { + float: right; + padding: 0; + padding-right: 7pt; +} +div#header img { + float: right; + margin-top: -2pt; + padding-right: 7pt; +} + +div#header span, #nimbtn span { + margin: 0; + padding: 5pt; +} + +div#header a.right, #nimbtn a { + float: right; + + color: #ffffff; + margin-right: 6pt; +} + +div#header a { + color: #ffffff; +} + +div#header a:visited, #nimbtn a:visited { + color: #ffffff; +} + +div#header a:hover, #nimbtn a:hover { + text-decoration: none; +} + +#nimbtn a { + margin-left: 6pt; +} + +#nimbtn { + float: left; + background-color: #2a2a2a; + color: #ffffff; + padding-top: 3pt; + padding-bottom: 3pt; +} + +#content { + margin: 5pt; +} + +#content table#threads { + width: 100%; + border-collapse: separate; + text-align: center; + border: #ffffff solid 1px; + font-size: 10pt; +} + +#content table#threads th { + background-color: #5D5D5D; + background: -moz-linear-gradient(top, #5D5D5D, #4D4D4D); + background: -webkit-linear-gradient(top, #5D5D5D, #4d4d4d); + background: -o-linear-gradient(top, #5D5D5D, #4d4d4d); + color: #ffffff; + border-bottom: #2d2d2d solid 2px; + border-right: #2d2d2d solid 1px; +} + +#content table#threads tr:nth-child(even) { + background-color: #eee; +} + +#content table#threads td { + vertical-align: middle; + border-right: #9d9d9d solid 1px; + border-bottom: #9d9d9d solid 1px; +} + +#content table#threads>tbody>tr>td:first-child { + border-left: #9d9d9d solid 1px; + +} + +#content table#threads>tbody>tr>td:last-child { + border-right: #9d9d9d solid 1px; +} + +#content table#threads td:hover { + border-right-color: #9d9d9d; +} + +#content table#threads td.topic { + text-align: left; + padding: 5pt; +} +#content table#threads td.author { + width: 10%; +} +#content table#threads td.posts { + width: 10%; +} +#content table#threads td.views { + width: 10%; +} +#content table#threads td.lastreply { + width: 15%; +} + +#whoisonline { + margin: 5pt; + font-size: 9pt; +} + +#whoisonline .wioHeader { + background-color: #5D5D5D; + background: -moz-linear-gradient(top, #5D5D5D, #4D4D4D); + background: -webkit-linear-gradient(top, #5D5D5D, #4d4d4d); + background: -o-linear-gradient(top, #5D5D5D, #4d4d4d); + color: #ffffff; + border-bottom: #2d2d2d solid 1px; + padding: 3px; + padding-left: 5pt; +} + +#whoisonline .content { + border: #9d9d9d solid 1px; + border-top: #2D2D2D solid 1px; + padding: 5pt; +} + +#whoisonline .content hr { + margin-top: 5pt; + margin-bottom: 5pt; +} + +#footer { + background-color: #5D5D5D; + background: -moz-linear-gradient(top, #5D5D5D, #4D4D4D); + background: -webkit-linear-gradient(top, #5D5D5D, #4d4d4d); + background: -o-linear-gradient(top, #5D5D5D, #4d4d4d); + color: #ffffff; + + padding-left: 5px; + padding-right: 5px; +} + +#footer a:link, #footer a:visited { + color: #ffffff; +} + +#footer a:hover { + text-decoration: none; +} + +#topbar { + margin: 5pt; +} + +#content .post { + border: #4d4d4d solid 2px; + width: 100%; + margin-bottom: 5pt; +} + +#content .post th { + background-color: #5D5D5D; + background: -moz-linear-gradient(top, #5D5D5D, #4D4D4D); + background: -webkit-linear-gradient(top, #5D5D5D, #4d4d4d); + background: -o-linear-gradient(top, #5D5D5D, #4d4d4d); + color: #ffffff; + padding-left: 5pt; + padding-right: 5pt; + padding-top: 3pt; + padding-bottom: 3pt; + text-align: left; + font-size: 9pt; +} + +#content .post .left { + border-left: #4d4d4d solid 2px; + background-color: #eee; + padding: 7pt; + width: 15%; + height: auto; +} + +#content .post .left hr { + margin: 0; + margin-bottom: 5pt; + margin-top: 2pt; +} + +#content .post .content { + padding: 6pt; +} + +div#replywrapper { + width: 70%; + margin-left: auto; + margin-right: auto; + border: #4d4d4d solid 2px; +} + +div#replywrapper div#replytop { + background-color: #5D5D5D; + background: -moz-linear-gradient(top, #5D5D5D, #4D4D4D); + background: -webkit-linear-gradient(top, #5D5D5D, #4d4d4d); + background: -o-linear-gradient(top, #5D5D5D, #4d4d4d); + font-size: 9pt; + color: #ffffff; + padding: 5pt; + +} + +div#replywrapper form textarea { + width: 99%; +} + +div#replywrapper form > input:first-child { + width: 80%; +} + +div#replywrapper form { + padding: 8pt; +} + +/* For RST nimrod syntax highlighter */ +span.DecNumber {color: blue} +span.BinNumber {color: blue} +span.HexNumber {color: blue} +span.OctNumber {color: blue} +span.FloatNumber {color: blue} +span.Identifier {color: black} +span.Keyword {font-weight: bold} +span.StringLit {color: blue} +span.LongStringLit {color: blue} +span.CharLit {color: blue} +span.EscapeSequence {color: black} +span.Operator {color: black} +span.Punctation {color: black} +span.Comment, span.LongComment {font-style:italic; color: green} +span.RegularExpression {color: DarkViolet} +span.TagStart {color: DarkViolet} +span.TagEnd {color: DarkViolet} +span.Key {color: blue} +span.Value {color: black} +span.RawData {color: blue} +span.Assembler {color: blue} +span.Preprocessor {color: DarkViolet} +span.Directive {color: DarkViolet} +span.Command, span.Rule, span.Hyperlink, span.Label, span.Reference, +span.Other {color: black} + +/* Buttons */ +a.button { + border-radius: 2px 2px 2px 2px; + background: -moz-linear-gradient(top, #f7f7f7, #ebebeb); + background: -webkit-linear-gradient(top, #f7f7f7, #ebebeb); + background: -o-linear-gradient(top, #f7f7f7, #ebebeb); + text-decoration: none; + color: #3d3d3d; + padding: 5px; + border: solid 1px #9d9d9d; + display: inline-block; + position: relative; + text-align: center; + font-size: small; +} + +a.button.left { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +a.button.middle { + border-radius: 0; + border-left: 0; +} + +a.button.right { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 0; +} + +a.button:hover { + background: -moz-linear-gradient(top, #0099c7, #0294C1); + background: -webkit-linear-gradient(top, #0099c7, #0294C1); + background: -o-linear-gradient(top, #0099c7, #0294C1); + border: solid 1px #077A9C; + color: #ffffff; +} \ No newline at end of file diff --git a/public/css/syntax.scss b/public/css/syntax.scss deleted file mode 100644 index 14dfa49..0000000 --- a/public/css/syntax.scss +++ /dev/null @@ -1,13 +0,0 @@ -pre .Comment { color:#618f0b; font-style:italic; } -pre .Keyword { color:rgb(39, 141, 182); font-weight:bold; } -pre .Type { color:#128B7D; font-weight:bold; } -pre .Operator { font-weight: bold; } -pre .atr { color:#128B7D; font-weight:bold; font-style:italic; } -pre .def { color:#CAD6E4; font-weight:bold; font-style:italic; } -pre .StringLit { color:rgb(190, 15, 15); font-weight:bold; } -pre .DecNumber, pre .FloatNumber { color:#8AB647; } -pre .tab { border-left:1px dotted rgba(67,168,207,0.4); } -pre .EscapeSequence -{ - color: #C08D12; -} \ No newline at end of file diff --git a/public/karax.html b/public/karax.html deleted file mode 100644 index 3f7f41b..0000000 --- a/public/karax.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - $title - - - - - - - - - - - -
- - - - diff --git a/public/license.rst b/public/license.rst deleted file mode 100644 index beebae3..0000000 --- a/public/license.rst +++ /dev/null @@ -1,38 +0,0 @@ -Content license -=============== - -All the content contributed to $hostname is `cc-wiki (aka cc-by-sa) -`_ licensed, intended to be -**shared and remixed**. - -The cc-wiki licensing, while intentionally permissive, does require -attribution: - -**Attribution** — You must attribute the work in the manner specified by -the author or licensor (but not in any way that suggests that they endorse -you or your use of the work). - -This means that if you republish this content, you are -required to: - -* **Visually indicate that the content is from the $name**. It doesn’t - have to be obnoxious; a discreet text blurb is fine. -* **Hyperlink directly to the original post** (e.g., - https://$hostname/t/186/1#908) -* **Show the author names** for every post. -* **Hyperlink each author name** directly back to their user profile page - (e.g., http://$hostname/profile/Araq) - -To be more specific, each hyperlink must -point directly to the $hostname domain in -standard HTML visible even with JavaScript disabled, and not use a tinyurl or -any other form of obfuscation or redirection. Furthermore, the links must not -be `nofollowed -`_. - -This is about the spirit of fair **attribution**. Attribution to the website, -and more importantly, to the individuals who so generously contributed their -time to create that content in the first place! - -Feel free to remix and reuse to your heart’s content, as long as a good faith -effort is made to attribute the content! diff --git a/public/search-help.rst b/public/search-help.rst deleted file mode 100644 index 8e7fc80..0000000 --- a/public/search-help.rst +++ /dev/null @@ -1,35 +0,0 @@ -Full-text search for Nim forum -============================== - -Syntax (using *SQLite* dll compiled without *Enhanced Query Syntax* support): ------------------------------------------------------------------------------ - -- Only alphanumeric characters are searched. -- Only full words and words beginnings (e.g. ``Nim*`` for both ``Nimrod`` and ``Nim``) are searched -- All words are joined with implicit **AND** operator; there's no explicit one -- There's explicit **OR** operator (upper-case) and it has higher priority -- Words can be prepended with **-** to be excluded from search -- No parentheses support -- Quotes for phrases search, e.g. ``"programming language"`` -- Distances between words/phrases can be specified putting ``NEAR`` or ``NEAR/some_number`` between them - -Syntax - differences in *Enhanced Query Syntax* (should be enabled in *SQLite* dll): ------------------------------------------------------------------------------------- - -- **AND** and **NOT** logical operators available -- Precedence of operators is, from highest to lowest: **NOT**, **AND**, **OR** -- Parentheses for grouping are supported - -Where search is performed: --------------------------- - -- **Threads' titles** - these results are outputed first -- **Posts' titles** - middle precedence -- **Posts' contents** - the latest - -How results are shown: ----------------------- - -- All results are ordered by date (posts' edits don't affect) -- Matched tokens in text are marked (bold or dotted underline) -- Threads title is the link to the thread and posts title is the link to the post diff --git a/public/rst.rst b/rst.txt similarity index 62% rename from public/rst.rst rename to rst.txt index b5db812..15813a5 100644 --- a/public/rst.rst +++ b/rst.txt @@ -1,8 +1,9 @@ -Markdown and RST supported by this forum -======================================== +=========================================================================== + reStructuredText cheat sheet +=========================================================================== This is a cheat sheet for the *reStructuredText* dialect as implemented by -Nim's documentation generator which has been reused for this forum. +Nimrod's documentation generator which has been reused for this forum. :-) See also the `official RST cheat sheet `_ @@ -11,8 +12,9 @@ for further information. Elements of **markdown** are also supported. + Inline elements ---------------- +=============== Ordinary text may contain *inline elements*: @@ -27,71 +29,68 @@ 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 ------ +===== -Links are either direct URLs like ``https://nim-lang.org`` or written like +Links are either direct URLs like ``http://nimrod-code.org`` or written like this:: - `Nim `_ + `Nimrod `_ Or like this:: - ``_ + ``_ Code blocks ------------ +=========== -The code blocks can be written in the same style as most common Markdown -flavours:: +are done this way:: - ```nim - if x == "abc": - echo "xyz" - ``` - -or using RST syntax:: - - .. code-block:: nim + .. code-block:: nimrod if x == "abc": echo "xyz" -Both are rendered as: -.. code-block:: nim +Is rendered as: + +.. code-block:: nimrod + + if x == "abc": + echo "xyz" + + +Except Nimrod, 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:: + + ```nimrod + if x == "abc": + echo "xyz"``` + +Is rendered as: + +.. code-block:: nimrod if x == "abc": echo "xyz" -Apart from Nim, the programming languages C, C++, Java and C# also -have highlighting support. Literal blocks --------------- +============== -These are introduced by '::' and a newline. The block is indicated by indentation: +Are introduced by '::' and a newline. The block is indicated by indentation: :: :: if x == "abc": echo "xyz" -The above is rendered as:: +Is rendered as:: if x == "abc": echo "xyz" @@ -99,9 +98,9 @@ The above is rendered as:: Bullet lists ------------- +============ -Bullet lists look like this:: +look like this:: * Item 1 * Item 2 that @@ -112,7 +111,7 @@ Bullet lists look like this:: - item 3b - valid bullet characters are ``+``, ``*`` and ``-`` -The above rendered as: +Is rendered as: * Item 1 * Item 2 that spans over multiple lines @@ -124,9 +123,9 @@ The above rendered as: Enumerated lists ----------------- +================ -Enumerated lists are written like this:: +are written like this:: 1. This is the first item 2. This is the second item @@ -134,17 +133,64 @@ Enumerated lists are written like this:: single letters, or roman numerals #. This item is auto-enumerated -They are rendered as: +Is 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 +#. 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. Tables ------- +====== Only *simple tables* are supported. They are of the form:: @@ -172,39 +218,3 @@ Cell 4 Cell 5; any Cell 6 multiple lines Cell 7 Cell 8 Cell 9 ================== =============== =================== - -Images ------- - -Image embedding is supported. This includes GIFs as well as mp4 (for which a -