mvp
This commit is contained in:
parent
2984b4911e
commit
a7ad35249a
@ -4,7 +4,9 @@
|
||||
:min-lein-version "2.0.0"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[compojure "1.6.1"]
|
||||
[ring/ring-defaults "0.3.2"]]
|
||||
[ring/ring-defaults "0.3.2"]
|
||||
[clj-http "3.10.1"]
|
||||
[hickory "0.7.1"]]
|
||||
:plugins [[lein-ring "0.12.5"]]
|
||||
:ring {:handler clojsa.handler/app}
|
||||
:profiles
|
||||
|
1
resources/public/css/bulma.min.css
vendored
Normal file
1
resources/public/css/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
448
resources/public/css/mvp.css
Normal file
448
resources/public/css/mvp.css
Normal file
@ -0,0 +1,448 @@
|
||||
/* MVP.css v1.6.2 - https://github.com/andybrewer/mvp */
|
||||
|
||||
:root {
|
||||
--border-radius: 5px;
|
||||
--box-shadow: 2px 2px 10px;
|
||||
--color: #118bee;
|
||||
--color-accent: #118bee15;
|
||||
--color-bg: #fff;
|
||||
--color-bg-secondary: #e9e9e9;
|
||||
--color-secondary: #920de9;
|
||||
--color-secondary-accent: #920de90b;
|
||||
--color-shadow: #f4f4f4;
|
||||
--color-text: #000;
|
||||
--color-text-secondary: #999;
|
||||
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
--hover-brightness: 1.2;
|
||||
--justify-important: center;
|
||||
--justify-normal: left;
|
||||
--line-height: 1.5;
|
||||
--width-card: 285px;
|
||||
--width-card-medium: 460px;
|
||||
--width-card-wide: 800px;
|
||||
--width-content: 1080px;
|
||||
}
|
||||
|
||||
/*
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color: #0097fc;
|
||||
--color-accent: #0097fc4f;
|
||||
--color-bg: #333;
|
||||
--color-bg-secondary: #555;
|
||||
--color-secondary: #e20de9;
|
||||
--color-secondary-accent: #e20de94f;
|
||||
--color-shadow: #bbbbbb20;
|
||||
--color-text: #f7f7f7;
|
||||
--color-text-secondary: #aaa;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/* Layout */
|
||||
article aside {
|
||||
background: var(--color-secondary-accent);
|
||||
border-left: 4px solid var(--color-secondary);
|
||||
padding: 0.01rem 0.8rem;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-family);
|
||||
line-height: var(--line-height);
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
footer,
|
||||
header,
|
||||
main {
|
||||
margin: 0 auto;
|
||||
max-width: var(--width-content);
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: none;
|
||||
height: 1px;
|
||||
margin: 4rem 0;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: var(--justify-important);
|
||||
}
|
||||
|
||||
section aside {
|
||||
border: 1px solid var(--color-bg-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow) var(--color-shadow);
|
||||
margin: 1rem;
|
||||
padding: 1.25rem;
|
||||
width: var(--width-card);
|
||||
}
|
||||
|
||||
section aside:hover {
|
||||
box-shadow: var(--box-shadow) var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
section aside img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Headers */
|
||||
article header,
|
||||
div header,
|
||||
main header {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: var(--justify-important);
|
||||
}
|
||||
|
||||
header a b,
|
||||
header a em,
|
||||
header a i,
|
||||
header a strong {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
header nav img {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
section header {
|
||||
padding-top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Nav */
|
||||
nav {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 7rem;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
display: inline-block;
|
||||
margin: 0 0.5rem;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Nav Dropdown */
|
||||
nav ul li:hover ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav ul li ul {
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-bg-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow) var(--color-shadow);
|
||||
display: none;
|
||||
height: auto;
|
||||
left: -2px;
|
||||
padding: .5rem 1rem;
|
||||
position: absolute;
|
||||
top: 1.7rem;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
nav ul li ul li,
|
||||
nav ul li ul li a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
code,
|
||||
samp {
|
||||
background-color: var(--color-accent);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text);
|
||||
display: inline-block;
|
||||
margin: 0 0.1rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
details {
|
||||
margin: 1.3rem 0;
|
||||
}
|
||||
|
||||
details summary {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1rem;
|
||||
}
|
||||
|
||||
ol li,
|
||||
ul li {
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.75rem 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 1rem 0;
|
||||
max-width: var(--width-card-wide);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
pre code,
|
||||
pre samp {
|
||||
display: block;
|
||||
max-width: var(--width-card-wide);
|
||||
padding: 0.5rem 2rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
small {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
sup {
|
||||
background-color: var(--color-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-bg);
|
||||
font-size: xx-small;
|
||||
font-weight: bold;
|
||||
margin: 0.2rem;
|
||||
padding: 0.2rem 0.3rem;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: var(--color-secondary);
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
filter: brightness(var(--hover-brightness));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a b,
|
||||
a em,
|
||||
a i,
|
||||
a strong,
|
||||
button {
|
||||
border-radius: var(--border-radius);
|
||||
display: inline-block;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
line-height: var(--line-height);
|
||||
margin: 0.5rem 0;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
cursor: pointer;
|
||||
filter: brightness(var(--hover-brightness));
|
||||
}
|
||||
|
||||
a b,
|
||||
a strong,
|
||||
button {
|
||||
background-color: var(--color);
|
||||
border: 2px solid var(--color);
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
a em,
|
||||
a i {
|
||||
border: 2px solid var(--color);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color);
|
||||
display: inline-block;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
figure {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
figure img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
figure figcaption {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
|
||||
button:disabled,
|
||||
input:disabled {
|
||||
background: var(--color-bg-secondary);
|
||||
border-color: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button[disabled]:hover {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
form {
|
||||
border: 1px solid var(--color-bg-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow) var(--color-shadow);
|
||||
display: block;
|
||||
max-width: var(--width-card-wide);
|
||||
min-width: var(--width-card);
|
||||
padding: 1.5rem;
|
||||
text-align: var(--justify-normal);
|
||||
}
|
||||
|
||||
form header {
|
||||
margin: 1.5rem 0;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
input,
|
||||
label,
|
||||
select,
|
||||
textarea {
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
max-width: var(--width-card-wide);
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[type="checkbox"]+label,
|
||||
input[type="radio"]+label {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
border: 1px solid var(--color-bg-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
}
|
||||
|
||||
input[readonly],
|
||||
textarea[readonly] {
|
||||
background-color: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
border: 1px solid var(--color-bg-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
border-spacing: 0;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table td,
|
||||
table th,
|
||||
table tr {
|
||||
padding: 0.4rem 0.8rem;
|
||||
text-align: var(--justify-important);
|
||||
}
|
||||
|
||||
table thead {
|
||||
background-color: var(--color);
|
||||
border-collapse: collapse;
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-bg);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table thead th:first-child {
|
||||
border-top-left-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
table thead th:last-child {
|
||||
border-top-right-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
table thead th:first-child,
|
||||
table tr td:first-child {
|
||||
text-align: var(--justify-normal);
|
||||
}
|
||||
|
||||
table tr:nth-child(even) {
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Quotes */
|
||||
blockquote {
|
||||
display: block;
|
||||
font-size: x-large;
|
||||
line-height: var(--line-height);
|
||||
margin: 1rem auto;
|
||||
max-width: var(--width-card-medium);
|
||||
padding: 1.5rem 1rem;
|
||||
text-align: var(--justify-important);
|
||||
}
|
||||
|
||||
blockquote footer {
|
||||
color: var(--color-text-secondary);
|
||||
display: block;
|
||||
font-size: small;
|
||||
line-height: var(--line-height);
|
||||
padding: 1.5rem 0;
|
||||
}
|
20
resources/public/css/style.css
Normal file
20
resources/public/css/style.css
Normal file
@ -0,0 +1,20 @@
|
||||
html {
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.box {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
nav.pagination {
|
||||
background-color: #fff;
|
||||
margin-top: 4rem;
|
||||
}
|
22
resources/public/img/bars.svg
Normal file
22
resources/public/img/bars.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<svg width="16" height="16" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#494949">
|
||||
<rect y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height" begin="0.5s" dur="1s" values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear" repeatCount="indefinite"/>
|
||||
<animate attributeName="y" begin="0.5s" dur="1s" values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="30" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height" begin="0.25s" dur="1s" values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear" repeatCount="indefinite"/>
|
||||
<animate attributeName="y" begin="0.25s" dur="1s" values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="60" width="15" height="140" rx="6">
|
||||
<animate attributeName="height" begin="0s" dur="1s" values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear" repeatCount="indefinite"/>
|
||||
<animate attributeName="y" begin="0s" dur="1s" values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="90" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height" begin="0.25s" dur="1s" values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear" repeatCount="indefinite"/>
|
||||
<animate attributeName="y" begin="0.25s" dur="1s" values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="120" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height" begin="0.5s" dur="1s" values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear" repeatCount="indefinite"/>
|
||||
<animate attributeName="y" begin="0.5s" dur="1s" values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
1
resources/public/js/htmx.min.js
vendored
Normal file
1
resources/public/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
resources/public/js/main.js
Normal file
5
resources/public/js/main.js
Normal file
@ -0,0 +1,5 @@
|
||||
document.body.addEventListener('configRequest.htmx', (e) => {
|
||||
if (e.detail.verb != 'get') {
|
||||
e.detail.parameters['__anti-forgery-token'] = __csrfToken;
|
||||
}
|
||||
});
|
@ -1,11 +1,19 @@
|
||||
(ns clojsa.handler
|
||||
(:require [compojure.core :refer :all]
|
||||
(:require [clojsa.views :refer [index-page thread-page]]
|
||||
[compojure.core :refer :all]
|
||||
[compojure.route :as route]
|
||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
|
||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
||||
[ring.middleware.session :refer [wrap-session]]
|
||||
[ring.middleware.session.cookie :refer (cookie-store)]))
|
||||
|
||||
(defroutes app-routes
|
||||
(GET "/" [] "Hello World")
|
||||
(GET "/" request (index-page request))
|
||||
(GET "/thread/:id" [id page]
|
||||
(thread-page (Integer/parseInt id) (Integer/parseInt page)))
|
||||
(route/not-found "Not Found"))
|
||||
|
||||
(def app
|
||||
(wrap-defaults app-routes site-defaults))
|
||||
(-> app-routes
|
||||
(wrap-defaults site-defaults)
|
||||
(wrap-session {:cookie-attrs {:max-age 3600}
|
||||
:store (cookie-store {:key "12345678abcdefgh"})})))
|
||||
|
81
src/clojsa/saclient.clj
Normal file
81
src/clojsa/saclient.clj
Normal file
@ -0,0 +1,81 @@
|
||||
(ns clojsa.saclient
|
||||
(:require [clojure.string :as string]
|
||||
[clj-http.client :as client]
|
||||
[hickory.core :refer :all]
|
||||
[hickory.select :as s]
|
||||
[hickory.convert :refer [hickory-to-hiccup]]))
|
||||
|
||||
(def url "https://forums.somethingawful.com/")
|
||||
(defn thread-url
|
||||
([id]
|
||||
(thread-url id 1))
|
||||
([id page]
|
||||
(let [base-url (str url "showthread.php")
|
||||
query {:threadid id
|
||||
:pagenumber page}]
|
||||
{:href base-url :params query})))
|
||||
|
||||
(defn thread-response [url]
|
||||
(let [resp (client/get (:href url) {:query-params (:params url)})]
|
||||
(:body resp)))
|
||||
|
||||
(def witcher-thread (thread-response (thread-url 3720352 3)))
|
||||
|
||||
(defn hickory-doc [doc]
|
||||
(-> doc parse as-hickory))
|
||||
|
||||
(defn parse-title [htree]
|
||||
(-> (s/select (s/child (s/tag :title)) htree)
|
||||
first :content first))
|
||||
|
||||
(defn parse-pagecount [htree]
|
||||
(-> (s/select (s/descendant
|
||||
(s/class :pages) (s/tag :option)) htree)
|
||||
last :content first Integer/parseInt))
|
||||
|
||||
(defn parse-thread [htree]
|
||||
(-> (s/select (s/descendant
|
||||
(s/id :thread))
|
||||
htree)
|
||||
first))
|
||||
|
||||
(defn select-td [class-key htree]
|
||||
(s/select (s/descendant
|
||||
(s/and (s/tag :td) (s/class class-key))) htree))
|
||||
|
||||
(defn parse-ui [ui]
|
||||
(let [ui (first (s/select (s/descendant (s/tag :dl)) ui))]
|
||||
(hickory-to-hiccup ui)))
|
||||
|
||||
(defn parse-pd [pd]
|
||||
(string/trim (last (hickory-to-hiccup pd))))
|
||||
|
||||
(defn hickory-div [class content]
|
||||
{:type :element,
|
||||
:attrs {:class class},
|
||||
:tag :div,
|
||||
:content content})
|
||||
|
||||
(defn parse-pb [pb]
|
||||
(let [pb (-> pb :content)]
|
||||
(hickory-to-hiccup (hickory-div "postbody" pb))))
|
||||
|
||||
(defn thread-map [id doc]
|
||||
(let [htree (hickory-doc doc)
|
||||
title (parse-title htree)
|
||||
page-count (parse-pagecount htree)
|
||||
thread-tree (parse-thread htree)
|
||||
userinfo (select-td :userinfo thread-tree)
|
||||
postdate (select-td :postdate thread-tree)
|
||||
postbody (select-td :postbody thread-tree)]
|
||||
{:title title
|
||||
:id id
|
||||
:page-count page-count
|
||||
:content
|
||||
(for [[ui pd pb] (partition 3 (interleave userinfo postdate postbody))
|
||||
:when (not= "Adbot" (-> (s/select (s/child (s/class :author)) ui)
|
||||
first :content first))]
|
||||
{:ui (parse-ui ui) :pd (parse-pd pd) :pb (parse-pb pb)})}))
|
||||
|
||||
(defn get-thread [id page]
|
||||
(thread-map id (thread-response (thread-url id page))))
|
77
src/clojsa/views.clj
Normal file
77
src/clojsa/views.clj
Normal file
@ -0,0 +1,77 @@
|
||||
(ns clojsa.views
|
||||
(:use [hiccup core page])
|
||||
(:require [clojsa.saclient :refer [get-thread]]
|
||||
[clojure.string :as string]
|
||||
[clojure.java.io :as io]
|
||||
[cheshire.core :as json]))
|
||||
|
||||
|
||||
(defn header-fragment []
|
||||
(html
|
||||
[:nav]))
|
||||
|
||||
(defn main-template [opts & insert-body-here]
|
||||
(html5
|
||||
{:lang "de"}
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
[:meta {:http-equiv "x-ua-compatible" :content "ie=edge"}]
|
||||
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
|
||||
[:title (get opts :title "clojsa")]
|
||||
(include-css "/css/bulma.min.css")
|
||||
(include-css "/css/style.css")
|
||||
(include-js "/js/htmx.min.js")]
|
||||
[:body
|
||||
{:hx-boost "false"}
|
||||
[:header
|
||||
(header-fragment)]
|
||||
[:main
|
||||
insert-body-here]
|
||||
(include-js "/js/main.js")]))
|
||||
|
||||
(defn index-page [req]
|
||||
(let [thread (get-thread 3720352 3)]
|
||||
(main-template {:title (:title thread)}
|
||||
[:div.container
|
||||
(for [post (:content thread)]
|
||||
[:div.post (:pb post)])])))
|
||||
|
||||
(defn paginate [id cur last]
|
||||
[:nav.container.pagination {:hx-boot "false"}
|
||||
[:a.pagination-previous
|
||||
{:href (format "/thread/%d?page=%d" id (dec cur))} "<"]
|
||||
[:a.pagination-next
|
||||
{:href (format "/thread/%s?page=%d" id (inc cur))} ">"]
|
||||
[:ul.pagination-list
|
||||
[:li
|
||||
[:a.pagination-link
|
||||
{:href (format "/thread/%d?page=%d" id 1)} (str 1)]]
|
||||
[:li
|
||||
[:span.pagination-ellipsis "…"]]
|
||||
(for [i (range (- cur 2) (+ cur 3))]
|
||||
[:li
|
||||
[:a.pagination-link
|
||||
{:href (format "/thread/%d?page=%d" id i)
|
||||
:class (when (= i cur) "is-current")} (str i)]])
|
||||
[:li
|
||||
[:span.pagination-ellipsis "…"]]
|
||||
[:li
|
||||
[:a.pagination-link
|
||||
{:href (format "/thread/%d?page=%d" id last)} (str last)]]]])
|
||||
|
||||
(defn thread-page [id page]
|
||||
(let [thread (get-thread id page)
|
||||
{:keys [id title page-count content]} thread]
|
||||
(main-template
|
||||
{:title title}
|
||||
[:div.container
|
||||
[:h1.is-size-3.mb-4 title]]
|
||||
[:section.thread
|
||||
(for [post content]
|
||||
[:article.container.box.columns
|
||||
[:aside.userinfo.column
|
||||
(:ui post)]
|
||||
[:main.postbody.content.column.is-four-fifths
|
||||
(:pb post)]])]
|
||||
[:section
|
||||
(paginate id page page-count)])))
|
Loading…
Reference in New Issue
Block a user