mvp
This commit is contained in:
parent
2984b4911e
commit
a7ad35249a
@ -4,7 +4,9 @@
|
|||||||
:min-lein-version "2.0.0"
|
:min-lein-version "2.0.0"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||||
[compojure "1.6.1"]
|
[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"]]
|
:plugins [[lein-ring "0.12.5"]]
|
||||||
:ring {:handler clojsa.handler/app}
|
:ring {:handler clojsa.handler/app}
|
||||||
:profiles
|
: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
|
(ns clojsa.handler
|
||||||
(:require [compojure.core :refer :all]
|
(:require [clojsa.views :refer [index-page thread-page]]
|
||||||
|
[compojure.core :refer :all]
|
||||||
[compojure.route :as route]
|
[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
|
(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"))
|
(route/not-found "Not Found"))
|
||||||
|
|
||||||
(def app
|
(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