Browse code
saving work ...
fiddlerwoaroof authored on 23/05/2016 20:44:22
Showing 2 changed files
Showing 2 changed files
... | ... |
@@ -10,9 +10,10 @@ |
10 | 10 |
(ql:quickload :spinneret) |
11 | 11 |
(ql:quickload :clack) |
12 | 12 |
(ql:quickload :clack-errors) |
13 |
+(ql:quickload :cl-markdown) |
|
13 | 14 |
|
14 | 15 |
(defpackage :blogerate |
15 |
- (:use :cl :alexandria :ningle :anaphora :ningle)) |
|
16 |
+ (:use :cl :alexandria :ningle :anaphora :ningle :spinneret)) |
|
16 | 17 |
|
17 | 18 |
(in-package :blogerate) |
18 | 19 |
(ubiquitous:restore 'blogerate) |
... | ... |
@@ -20,18 +21,30 @@ |
20 | 21 |
(manardb:use-mmap-dir "/tmp/manardb-blogerate") |
21 | 22 |
|
22 | 23 |
(manardb:defmmclass blog () |
23 |
- ((id :initarg :id :accessor blog-id) |
|
24 |
- (posts :initarg :posts :accessor blog-posts :initform nil) |
|
25 |
- (tags :initform (make-hash-table :test #'equal) :accessor blog-tags) |
|
26 |
- (authors :initform (make-hash-table :test #'equal) :accessor blog-authors) |
|
27 |
- (sets :initform (make-hash-table :test #'equal) :accessor blog-sets))) |
|
24 |
+ ((id :initarg :id :accessor blog-id) |
|
25 |
+ (posts :initarg :posts :accessor blog-posts :initform nil) |
|
26 |
+ (tags :initform nil :accessor blog-tags) |
|
27 |
+ (authors :initform nil :accessor blog-authors) |
|
28 |
+ (sets :initform nil :accessor blog-sets) |
|
29 |
+ (seed :initarg :seed |
|
30 |
+ :initform (make-array 10 |
|
31 |
+ :element-type '(unsigned-byte 8) |
|
32 |
+ :initial-contents (loop for x from 1 to 10 collect (random 256))) |
|
33 |
+ :accessor blog-seed))) |
|
34 |
+ |
|
35 |
+(manardb:defmmclass post-set () |
|
36 |
+ ((id :initarg :id :accessor set-id :initform nil) |
|
37 |
+ (name :initarg :name :accessor set-name) |
|
38 |
+ (posts :initarg :posts :accessor set-posts :initform nil) |
|
39 |
+ (seed :initarg :seed |
|
40 |
+ :initform (make-array 10 |
|
41 |
+ :element-type '(unsigned-byte 8) |
|
42 |
+ :initial-contents (loop for x from 1 to 10 collect (random 256))) |
|
43 |
+ :accessor set-seed))) |
|
28 | 44 |
|
29 | 45 |
(manardb:defmmclass post () |
30 | 46 |
((id :initarg :id :accessor post-id :initform (with-output-to-string (s) |
31 |
- (uuid:print-bytes s |
|
32 |
- (uuid:make-v5-uuid |
|
33 |
- uuid:+namespace-oid+ |
|
34 |
- "blump")))) |
|
47 |
+ (uuid:print-bytes s (uuid:make-v4-uuid)))) |
|
35 | 48 |
(title :initarg :title :accessor post-title :initform "") |
36 | 49 |
(text :initarg :text :accessor post-text :initform "") |
37 | 50 |
(author :initarg :author :accessor post-author :initform "") |
... | ... |
@@ -39,15 +52,44 @@ |
39 | 52 |
(tags :initarg :tags :initform '() :accessor post-tags))) |
40 | 53 |
|
41 | 54 |
(defun make-post (blog title text author set &key (tags nil tags-p)) |
42 |
- (let ((result (make-instance 'post :title title :text text :author author :set set :tags tags))) |
|
55 |
+ (let ((result (make-instance 'post :title title :text text :author author :set set :tags tags)) |
|
56 |
+ (set-obj (get-set :name set :author author))) |
|
43 | 57 |
(push result (blog-posts blog)) |
44 | 58 |
(when tags-p |
45 | 59 |
(mapcar (lambda (x) (push (cons x result) (blog-tags blog))) |
46 | 60 |
(post-tags result))) |
47 |
- (push (cons (post-set result) result) (blog-sets blog)) |
|
61 |
+ (push result (set-posts set-obj)) |
|
62 |
+ (unless (member set (blog-sets blog) :key #'car :test #'string=) |
|
63 |
+ (push (cons set set-obj) (blog-sets blog))) |
|
48 | 64 |
(push (cons (post-author result) result) (blog-authors blog)) |
49 | 65 |
result)) |
50 | 66 |
|
67 |
+(defparameter *blog* (get-blog "my-blog9")) |
|
68 |
+(defvar *app* (make-instance 'ningle:<app>)) |
|
69 |
+ |
|
70 |
+ |
|
71 |
+(defun get-set-by-id (id &optional (blog *blog*)) |
|
72 |
+ (awhen (blog-sets blog) |
|
73 |
+ (awhen (assoc id it :test #'string=) |
|
74 |
+ (cdr it)))) |
|
75 |
+ |
|
76 |
+(defun get-set-by-name-and-author (name author) |
|
77 |
+ (flet ((new-set () |
|
78 |
+ (let ((new-set (make-instance 'post-set :name name))) |
|
79 |
+ (setf (set-id new-set) (get-set-id (set-seed new-set) name author)) |
|
80 |
+ new-set))) |
|
81 |
+ (aif (manardb:retrieve-all-instances 'post-set) |
|
82 |
+ (aif (car (remove-if-not (lambda (x) (equal (get-set-id (set-seed x) name author) (set-id x))) |
|
83 |
+ it)) |
|
84 |
+ it |
|
85 |
+ (new-set)) |
|
86 |
+ (new-set)))) |
|
87 |
+ |
|
88 |
+(defun get-set (&key id name author) |
|
89 |
+ (if id |
|
90 |
+ (get-set-by-id id) |
|
91 |
+ (get-set-by-name-and-author name author))) |
|
92 |
+ |
|
51 | 93 |
(defun get-blog (id) |
52 | 94 |
(flet ((new-blog () (make-instance 'blog :id id))) |
53 | 95 |
(aif (manardb:retrieve-all-instances 'blog) |
... | ... |
@@ -56,9 +98,6 @@ |
56 | 98 |
(new-blog)) |
57 | 99 |
(new-blog)))) |
58 | 100 |
|
59 |
-(defparameter *blog* (get-blog "my-blog3")) |
|
60 |
-(defparameter *app* (make-instance 'ningle:<app>)) |
|
61 |
- |
|
62 | 101 |
(defmacro with-page ((&key title) &body body) |
63 | 102 |
`(spinneret:with-html-string |
64 | 103 |
(:doctype) |
... | ... |
@@ -66,67 +105,141 @@ |
66 | 105 |
(:head |
67 | 106 |
(:link :rel "stylesheet" :href "/static/main.css") |
68 | 107 |
(:title ,title)) |
69 |
- (:body ,@body)))) |
|
108 |
+ (:body |
|
109 |
+ (:main |
|
110 |
+ ,@body))))) |
|
111 |
+ |
|
112 |
+(defmacro format-post (item) |
|
113 |
+ (alexandria:once-only (item) |
|
114 |
+ `(spinneret:with-html |
|
115 |
+ (:section :class "post-show" |
|
116 |
+ (:h2 |
|
117 |
+ (post-title ,item)) |
|
118 |
+ (:article |
|
119 |
+ (:raw |
|
120 |
+ (nth-value 1 (cl-markdown:markdown (post-text ,item) :stream nil)))) |
|
121 |
+ (:span :class "author" |
|
122 |
+ (post-author ,item)) |
|
123 |
+ (:span :class "set" |
|
124 |
+ (post-set ,item)) |
|
125 |
+ (:ul.tags |
|
126 |
+ (loop for tag in (post-tags ,item) |
|
127 |
+ collect (:li tag))))))) |
|
128 |
+ |
|
129 |
+(defun get-set-posts (set-name) |
|
130 |
+ (car |
|
131 |
+ (loop for (set . post) in (blog-sets *blog*) |
|
132 |
+ if (string= set set-name) |
|
133 |
+ collect post))) |
|
134 |
+ |
|
135 |
+(defun get-tag (tag-name) |
|
136 |
+ (mapcar #'cdr |
|
137 |
+ (remove-if-not (lambda (x) (string= x tag-name)) |
|
138 |
+ (blog-tags *blog*) |
|
139 |
+ :key #'car))) |
|
140 |
+ |
|
141 |
+(setf (route *app* "/posts/:set" :method :GET) |
|
142 |
+ (lambda (params) |
|
143 |
+ (declare (ignorable params)) |
|
144 |
+ (let* ((set-name (cdr (assoc :set params))) |
|
145 |
+ (set-title (format nil "Set ~a" set-name)) |
|
146 |
+ (set (get-set :id set-name))) |
|
147 |
+ (format nil "~s" (get-set :id set-name)) |
|
148 |
+ (with-page (:title set-title) |
|
149 |
+ (:header |
|
150 |
+ (:h1 (set-name set))) |
|
151 |
+ (loop for post in (set-posts set) |
|
152 |
+ collect (format nil "~s" (format-post post))))))) |
|
70 | 153 |
|
154 |
+(setf (route *app* "/posts/:set/:tag" :method :GET) |
|
155 |
+ (lambda (params) |
|
156 |
+ (let* ((set-id (cdr (assoc :set params))) |
|
157 |
+ (tag-name (cdr (assoc :tag params))) |
|
158 |
+ (set (get-set :id set-id)) |
|
159 |
+ (set-posts (set-posts set)) |
|
160 |
+ (tag-posts (get-tag tag-name)) |
|
161 |
+ (display-posts (intersection set-posts tag-posts |
|
162 |
+ :key #'post-id |
|
163 |
+ :test #'string=))) |
|
164 |
+ (with-page (:title (format nil "Set: ~a Tag: ~a" (set-name set) tag-name)) |
|
165 |
+ (:header (:h1 (format nil "Set: ~a Tag: ~a" (set-name set) tag-name))) |
|
166 |
+ (mapcar (lambda (post) (format nil "~s" (format-post post))) |
|
167 |
+ display-posts))))) |
|
168 |
+ |
|
169 |
+(spinneret:deftag form-input (body attrs &key name label (type "text")) |
|
170 |
+ (declare (ignore body)) |
|
171 |
+ (alexandria:once-only (name) |
|
172 |
+ `(:div.input-group :class ,name ,@attrs |
|
173 |
+ ;(:label :for ,name ,label) |
|
174 |
+ (:input :placeholder ,label :type ,type :name ,name)))) |
|
71 | 175 |
|
72 | 176 |
(setf (route *app* "/post/new" :method :GET) |
73 | 177 |
(lambda (params) |
74 | 178 |
(declare (ignorable params)) |
75 |
- (with-page (:title "new post") |
|
76 |
- (:header |
|
77 |
- (:h1 "New Post:")) |
|
78 |
- (:section :class "post-new" |
|
79 |
- (:form :action "/post/new" :method "POST" |
|
80 |
- (:input :type "text" :name "title") |
|
81 |
- (:input :type "text" :name "author") |
|
82 |
- (:textarea :name "post") |
|
83 |
- (:input :type "text" :name "set") |
|
84 |
- (:input :type "text" :name "tags") |
|
85 |
- (:input :type "submit")))))) |
|
179 |
+ (with-page (:title "new post") |
|
180 |
+ (:header |
|
181 |
+ (:h1 "New Post:")) |
|
182 |
+ (:section :class "post-new" |
|
183 |
+ (:form :action "/post/new" :method "POST" |
|
184 |
+ (form-input :name "title" :label "Title") |
|
185 |
+ (form-input :name "author" :label "Author") |
|
186 |
+ (:div.input-group.post |
|
187 |
+ ;(:label :for "post" "Post Content") |
|
188 |
+ (:textarea :placeholder "Content" :name "post")) |
|
189 |
+ (form-input :name "set" :label "Set") |
|
190 |
+ (form-input :name "tags" :label "Comma-separated Tags") |
|
191 |
+ (form-input :type "submit")))))) |
|
86 | 192 |
|
87 | 193 |
(defun str-assoc-cdr (key alist) |
88 | 194 |
(cdr (assoc key alist :test #'equal))) |
89 | 195 |
|
90 | 196 |
(defun split-tags (str) |
91 |
- (mapcar (lambda (x) (string-trim " " x)) (split-sequence:split-sequence #\, str))) |
|
197 |
+ (mapcar (lambda (x) (string-trim " " x)) |
|
198 |
+ (split-sequence:split-sequence #\, str))) |
|
199 |
+ |
|
200 |
+(defun get-set-id (blog-seed set-name author-name) |
|
201 |
+ (let* ((o nil) |
|
202 |
+ (digester (ironclad:make-digest :tiger)) |
|
203 |
+ (digest (progn |
|
204 |
+ (ironclad:update-digest digester blog-seed) |
|
205 |
+ (ironclad:update-digest digester (ironclad:ascii-string-to-byte-array set-name)) |
|
206 |
+ (ironclad:update-digest digester (ironclad:ascii-string-to-byte-array author-name)) |
|
207 |
+ (ironclad:byte-array-to-hex-string |
|
208 |
+ (ironclad:produce-digest digester)))) |
|
209 |
+ (digest-length (length digest))) |
|
210 |
+ (dotimes (x (ceiling (/ digest-length 5))) |
|
211 |
+ (push (subseq digest |
|
212 |
+ (* x 5) |
|
213 |
+ (min digest-length (* (1+ x) 5))) |
|
214 |
+ o)) |
|
215 |
+ (format nil "~{~a~^-~}" o))) |
|
92 | 216 |
|
93 | 217 |
(setf (route *app* "/post/new" :method :POST) |
94 | 218 |
(lambda (params) |
95 | 219 |
(format nil "~s" params) |
96 |
- (let ((new-post |
|
220 |
+ (let* ((author (str-assoc-cdr "author" params)) |
|
221 |
+ (set (str-assoc-cdr "set" params)) |
|
222 |
+ (new-post |
|
97 | 223 |
(make-post *blog* |
98 | 224 |
(str-assoc-cdr "title" params) |
99 | 225 |
(str-assoc-cdr "post" params) |
100 |
- (str-assoc-cdr "author" params) |
|
101 |
- (str-assoc-cdr "set" params) |
|
226 |
+ author |
|
227 |
+ set |
|
102 | 228 |
:tags (split-tags (str-assoc-cdr "tags" params))))) |
103 | 229 |
(with-page (:title (post-title new-post)) |
104 | 230 |
(:header |
105 |
- (:h1 (post-title new-post))) |
|
106 |
- (:section :class "post-show" |
|
107 |
- (:article |
|
108 |
- (post-text new-post)) |
|
109 |
- (:span :class "author" |
|
110 |
- (post-author new-post)) |
|
111 |
- (:span :class "set" |
|
112 |
- (post-set new-post))))))) |
|
231 |
+ (:h1 "What a Piece of Blog is This?")) |
|
232 |
+ (format-post new-post))))) |
|
113 | 233 |
|
114 | 234 |
(setf (route *app* "/" :method :GET) |
115 | 235 |
(lambda (params) |
116 |
- (with-page (:title "Blog") |
|
236 |
+ (declare (ignore params)) |
|
237 |
+ (with-page (:title "What a Piece of Blog is This?") |
|
117 | 238 |
(:header |
118 |
- (:h1 "Blog")) |
|
239 |
+ (:h1 "What a Piece of Blog is This?")) |
|
119 | 240 |
(loop for new-post in (blog-posts *blog*) |
120 | 241 |
collect |
121 |
- (:section :class "post-show" |
|
122 |
- (:h2 |
|
123 |
- (post-title new-post)) |
|
124 |
- (:article |
|
125 |
- (post-text new-post)) |
|
126 |
- (:span :class "author" |
|
127 |
- (post-author new-post)) |
|
128 |
- (:span :class "set" |
|
129 |
- (post-set new-post))))))) |
|
242 |
+ (format-post new-post))))) |
|
130 | 243 |
|
131 | 244 |
|
132 | 245 |
(defparameter *handler* |
... | ... |
@@ -136,4 +249,4 @@ |
136 | 249 |
;:csrf |
137 | 250 |
;clack-errors:*clack-error-middleware* |
138 | 251 |
(:static :path "/static/" :root #p"./static/") |
139 |
- *app*) :port 5000)) |
|
252 |
+ *app*) :port 5050)) |
... | ... |
@@ -5,29 +5,77 @@ |
5 | 5 |
} |
6 | 6 |
|
7 | 7 |
body { |
8 |
- background: hsl(44, 87%, 94%); |
|
8 |
+ background: #fdf6e3; |
|
9 | 9 |
color: hsl(194, 14%, 40%); |
10 |
- text-rendering: geometric-precision; |
|
10 |
+ text-rendering: geometricPrecision; |
|
11 | 11 |
font-family: Caudex; |
12 |
+ padding-top: 10vh; |
|
13 |
+} |
|
14 |
+ |
|
15 |
+main { |
|
16 |
+ background: #eee8d5; |
|
17 |
+ width: 66vw; |
|
18 |
+ margin-left: 16.5vw; |
|
19 |
+ display:block; |
|
12 | 20 |
} |
13 | 21 |
|
14 | 22 |
header { |
15 |
- padding: 2em; |
|
23 |
+ /*padding: 2em;*/ |
|
16 | 24 |
font-size: 2vmin; |
17 | 25 |
height: 10vh; |
18 | 26 |
width: 66vw; |
19 |
- margin-left: 16.5vw; |
|
20 | 27 |
border: thin solid black; |
28 |
+ position: fixed; |
|
29 |
+ top: 0px; |
|
30 |
+ line-height: 10vh; |
|
31 |
+ text-align: center; |
|
32 |
+ z-index: 100; |
|
33 |
+} |
|
34 |
+ |
|
35 |
+header h1 { |
|
36 |
+ text-shadow: 0px 0px 0.5em hsl(194, 14%, 40%); |
|
37 |
+ color: hsl(44, 87%, 94%); |
|
38 |
+ padding: 0px; |
|
39 |
+ margin: 0px; |
|
40 |
+} |
|
41 |
+ |
|
42 |
+h1, h2 { |
|
43 |
+ text-align: center; |
|
44 |
+ margin-top: 1em; |
|
45 |
+ margin-bottom: 0.5em; |
|
46 |
+} |
|
47 |
+ |
|
48 |
+ |
|
49 |
+ |
|
50 |
+header::before { |
|
51 |
+ position: absolute; |
|
52 |
+ top: 0px; |
|
53 |
+ bottom: 0px; |
|
54 |
+ left: 0px; |
|
55 |
+ right: 0px; |
|
56 |
+ content: " "; |
|
57 |
+ display: block; |
|
58 |
+ background: hsla(194, 14%, 40%, 0.7); |
|
59 |
+ z-index: -1; |
|
60 |
+ -webkit-backdrop-filter: blur(2px); |
|
21 | 61 |
} |
22 | 62 |
|
23 | 63 |
section.post-new, section.post-show { |
24 | 64 |
padding: 2em; |
25 |
- width: 66vw; |
|
26 |
- margin-left: 16.5vw; |
|
27 |
- border: thin solid black; |
|
65 |
+ overflow: hidden; |
|
66 |
+ border: thin solid hsl(194, 14%, 40%); |
|
67 |
+ border-top: none; |
|
68 |
+ border-bottom-width: medium; |
|
69 |
+ border-bottom-style: double; |
|
28 | 70 |
} |
71 |
+ |
|
29 | 72 |
section.post-new { |
30 |
- height: 90vh; |
|
73 |
+ overflow-y: auto; |
|
74 |
+ min-height: 90vh; |
|
75 |
+} |
|
76 |
+ |
|
77 |
+form { |
|
78 |
+ position: relative; |
|
31 | 79 |
} |
32 | 80 |
|
33 | 81 |
input[type=text], textarea { |
... | ... |
@@ -36,11 +84,39 @@ input[type=text], textarea { |
36 | 84 |
|
37 | 85 |
input, textarea { |
38 | 86 |
padding: 0.5em 1em; |
39 |
- border: thin solid black; |
|
40 |
- background: hsl(44, 87%, 94%); |
|
87 |
+ border: thin solid #fdf6e3; |
|
88 |
+ border-radius: 0.5rem; |
|
89 |
+ background: #fdf6e3; |
|
41 | 90 |
color: hsl(194, 14%, 40%); |
42 | 91 |
} |
43 | 92 |
|
93 |
+input[type=submit], button { |
|
94 |
+ color: hsl(44, 87%, 94%); |
|
95 |
+ text-shadow: 0px 0px 0.2em hsl(194, 14%, 40%), 0px 0px 0.2em hsl(44, 87%, 94%); |
|
96 |
+ background-image: linear-gradient( |
|
97 |
+ hsla(194, 14%, 40%, 0.25), |
|
98 |
+ hsla(194, 14%, 40%, 0.5), |
|
99 |
+ hsl(194, 14%, 40%) |
|
100 |
+ ); |
|
101 |
+} |
|
102 |
+ |
|
103 |
+div.input-group { |
|
104 |
+ display: inline-block; |
|
105 |
+} |
|
106 |
+ |
|
107 |
+input[type=submit]:hover, button:hover { |
|
108 |
+ background-image: linear-gradient( |
|
109 |
+ hsl(194, 14%, 40%), |
|
110 |
+ hsla(194, 14%, 40%, 0.5), |
|
111 |
+ hsla(194, 14%, 40%, 0.25) |
|
112 |
+ ); |
|
113 |
+} |
|
114 |
+ |
|
115 |
+input[type=text], textarea { |
|
116 |
+ box-shadow: inset 0em 0em 1em #eee8d5, |
|
117 |
+ 0em 0em 1em #eee8d5; |
|
118 |
+} |
|
119 |
+ |
|
44 | 120 |
section.post-new textarea { |
45 | 121 |
display: block; |
46 | 122 |
width: 100%; |
... | ... |
@@ -48,29 +124,63 @@ section.post-new textarea { |
48 | 124 |
margin-bottom: 1em; |
49 | 125 |
} |
50 | 126 |
|
51 |
-input[name=title] { |
|
127 |
+.input-group.title { |
|
52 | 128 |
width: 100%; |
129 |
+} |
|
130 |
+ |
|
131 |
+.input-group.title label, |
|
132 |
+.input-group.title input { |
|
53 | 133 |
font-size: 150%; |
54 | 134 |
font-weight: bolder; |
55 | 135 |
margin-bottom: 1em; |
56 | 136 |
} |
57 |
-input[name=author] { |
|
137 |
+ |
|
138 |
+.input-group.title label, .input-group.author label { |
|
139 |
+ width: 33%; |
|
140 |
+ text-align: right; |
|
141 |
+ display: inline-block; |
|
142 |
+} |
|
143 |
+ |
|
144 |
+.input-group label { |
|
145 |
+ text-transform: uppercase; |
|
146 |
+} |
|
147 |
+ |
|
148 |
+.input-group.post { |
|
149 |
+ width: 100%; |
|
150 |
+} |
|
151 |
+ |
|
152 |
+.input-group.title input, .input-group.author input { |
|
153 |
+ display: inline-block; |
|
58 | 154 |
width: 66%; |
59 |
- margin-left: 34%; |
|
155 |
+} |
|
156 |
+ |
|
157 |
+.input-group.author { |
|
158 |
+ width: 100%; |
|
60 | 159 |
margin-bottom: 1em; |
61 | 160 |
} |
62 | 161 |
|
63 |
-input[name=set] { |
|
162 |
+.input-group.set { |
|
64 | 163 |
width: 24%; |
65 | 164 |
margin-bottom: 1em; |
66 | 165 |
} |
67 | 166 |
|
68 |
-input[name=tags] { |
|
167 |
+input[type=text] { |
|
168 |
+ width: 100%; |
|
169 |
+} |
|
170 |
+ |
|
171 |
+.input-group.tags { |
|
69 | 172 |
width: 74%; |
70 | 173 |
margin-left: 1%; |
71 | 174 |
margin-bottom: 1em; |
72 | 175 |
clear: both; |
73 | 176 |
} |
74 | 177 |
|
75 |
-input[type=submit] { |
|
178 |
+input[type=submit], button { |
|
179 |
+ cursor: pointer; |
|
180 |
+ position: absolute; |
|
181 |
+ right: 0px; |
|
182 |
+} |
|
183 |
+ |
|
184 |
+ul, ol { |
|
185 |
+ margin-left: 1.5em; |
|
76 | 186 |
} |