Browse code
Basic RSS reader implemented
fiddlerwoaroof authored on 01/09/2015 07:26:46
Showing 8 changed files
Showing 8 changed files
- cl-oid-connect.asd
- cl-oid-connect.lisp
- demo.lisp
- lass/main.lass
- package.lisp
- parenscript/fold.js
- static/css/main.css
- static/js/fold.js
... | ... |
@@ -5,15 +5,18 @@ |
5 | 5 |
:author "Ed L <(format nil \"~a@~a\" \"el-projects\" \"howit.is\")>" |
6 | 6 |
:license "2=Clause BSD" |
7 | 7 |
:depends-on (:drakma |
8 |
- :ningle |
|
9 |
- :clack |
|
10 |
- :cljwt |
|
11 |
- :cl-json |
|
12 |
- :anaphora |
|
13 |
- :alexandria |
|
14 |
- :lack-middleware-session |
|
15 |
- :sheeple |
|
16 |
- :cl-who) |
|
8 |
+ :ningle |
|
9 |
+ :clack |
|
10 |
+ :cljwt |
|
11 |
+ :cl-json |
|
12 |
+ :anaphora |
|
13 |
+ :alexandria |
|
14 |
+ :lack-middleware-session |
|
15 |
+ :sheeple |
|
16 |
+ :lass |
|
17 |
+ :lquery |
|
18 |
+ :plump |
|
19 |
+ :cl-who) |
|
17 | 20 |
:serial t |
18 | 21 |
:components ((:file "package") |
19 | 22 |
(:file "cl-oid-connect"))) |
... | ... |
@@ -33,7 +33,6 @@ |
33 | 33 |
|# |
34 | 34 |
|
35 | 35 |
(in-package :cl-oid-connect) |
36 |
-(declaim (optimize (debug 2))) |
|
37 | 36 |
; Should this be here? |
38 | 37 |
(defparameter *oid* (make-instance 'ningle:<app>)) |
39 | 38 |
(setf drakma:*text-content-types* (cons '("application" . "json") drakma:*text-content-types*)) |
... | ... |
@@ -59,6 +58,10 @@ |
59 | 58 |
; goog is well behaved |
60 | 59 |
(defparameter *goog-endpoint-schema* (defobject (=endpoint-schema= *goog-info*))) |
61 | 60 |
|
61 |
+(defun get-base-url (request) (format nil "~a//~a/oidc_callback" |
|
62 |
+ (lack.request:request-query-parameters) |
|
63 |
+ )) |
|
64 |
+ |
|
62 | 65 |
; fbook needs personal attention |
63 | 66 |
(defproto *fbook-endpoint-schema* (=endpoint-schema= *fbook-info*) |
64 | 67 |
((auth-endpoint "https://www.facebook.com/dialog/oauth") |
... | ... |
@@ -190,14 +193,15 @@ |
190 | 193 |
|
191 | 194 |
|
192 | 195 |
(defun oauth2-login-middleware (&key google-info facebook-info) |
193 |
- (lambda (app) |
|
194 |
- (in-package :cl-oid-connect) |
|
195 |
- (load-facebook-info facebook-info) |
|
196 |
- (load-goog-endpoint-schema) |
|
197 |
- (load-google-info google-info) |
|
196 |
+ (let ((clack-env nil)) |
|
197 |
+ (lambda (app) |
|
198 |
+ (in-package :cl-oid-connect) |
|
199 |
+ (load-facebook-info facebook-info) |
|
200 |
+ (load-goog-endpoint-schema) |
|
201 |
+ (load-google-info google-info) |
|
198 | 202 |
|
199 | 203 |
(def-route ("/login/google" (params) :app app) |
200 |
- (with-session (cl-oid-connect:session) |
|
204 |
+ (with-context-variables (session) |
|
201 | 205 |
(let ((state (gen-state 36))) |
202 | 206 |
(setf (gethash :state session) state) |
203 | 207 |
(with-endpoints *goog-endpoint-schema* |
... | ... |
@@ -224,7 +228,7 @@ |
224 | 228 |
(let ((received-state (cdr (string-assoc "state" params))) |
225 | 229 |
(code (cdr (string-assoc "code" params)))) |
226 | 230 |
(check-state received-state |
227 |
- (with-session (cl-oid-connect:session) |
|
231 |
+ (with-context-variables (session) |
|
228 | 232 |
(with-endpoints *goog-endpoint-schema* |
229 | 233 |
(let* ((a-t (get-access-token *goog-endpoint-schema* code)) |
230 | 234 |
(id-token (assoc-cdr :id--token a-t)) |
... | ... |
@@ -239,7 +243,7 @@ |
239 | 243 |
(code (cdr (string-assoc "code" params)))) |
240 | 244 |
(with-endpoints *fbook-endpoint-schema* |
241 | 245 |
(check-state received-state |
242 |
- (with-session (session) |
|
246 |
+ (with-context-variables (session) |
|
243 | 247 |
(let* ((a-t (get-access-token *fbook-endpoint-schema* code)) |
244 | 248 |
(id-token (assoc-cdr :access--token a-t))) |
245 | 249 |
(setf (gethash :userinfo session) (get-user-info *fbook-endpoint-schema* id-token)) |
... | ... |
@@ -247,17 +251,17 @@ |
247 | 251 |
'(403 '() "Out, vile imposter!"))))) |
248 | 252 |
|
249 | 253 |
(def-route ("/userinfo.json" (params) :app app) |
250 |
- (with-session (session) |
|
254 |
+ (with-context-variables (session) |
|
251 | 255 |
(require-login |
252 | 256 |
(with-endpoints (gethash :endpoint-schema session) |
253 | 257 |
(cl-json:encode-json-to-string (gethash :userinfo session)))))) |
254 | 258 |
|
255 | 259 |
(def-route ("/logout" (params) :app app) |
256 |
- (with-session (session) |
|
260 |
+ (with-context-variables (session) |
|
257 | 261 |
(setf (gethash :userinfo session) nil) |
258 | 262 |
'(302 (:location "/")))) |
259 | 263 |
|
260 |
- app)) |
|
264 |
+ app))) |
|
261 | 265 |
|
262 | 266 |
|
263 | 267 |
(defmacro redirect-if-necessary (sessionvar &body body) |
... | ... |
@@ -273,41 +277,3 @@ |
273 | 277 |
|
274 | 278 |
(export '(redirect-if-necessary def-route require-login)) |
275 | 279 |
(export '(oauth2-login-middleware with-session)) |
276 |
- |
|
277 |
-(in-package :cl-user) |
|
278 |
- |
|
279 |
-(defparameter *app* (make-instance 'ningle:<app>)) |
|
280 |
- |
|
281 |
-(cl-oid-connect:def-route ("/login" (params) :app *app*) |
|
282 |
- (cl-who:with-html-output-to-string (s) |
|
283 |
- (:html |
|
284 |
- (:head |
|
285 |
- (:title "Login")) |
|
286 |
- (:body |
|
287 |
- (:div (:a :href "/login/facebook" "Facebook")) |
|
288 |
- (:div (:a :href "/login/google" "Google")))))) |
|
289 |
- |
|
290 |
-(defvar *smession* nil) |
|
291 |
- |
|
292 |
-(cl-oid-connect:def-route ("/" (params) :app *app*) |
|
293 |
- (cl-oid-connect:with-session (*smession*) |
|
294 |
- (cl-oid-connect:redirect-if-necessary *smession* |
|
295 |
- (cl-oid-connect:require-login |
|
296 |
- (anaphora:sunless (gethash :counter *smession*) (setf anaphora:it 0)) |
|
297 |
- (incf (gethash :counter *smession*)) |
|
298 |
- (format nil "~Ath visit<br/>~a<br/><br/>~S<br/>" |
|
299 |
- (gethash :counter *smession*) |
|
300 |
- (alexandria:hash-table-alist *smession*) |
|
301 |
- (alexandria:hash-table-alist (ningle:context :session))))))) |
|
302 |
- |
|
303 |
-(setf *handler* (clack:clackup (lack.builder:builder |
|
304 |
- :backtrace |
|
305 |
- :session |
|
306 |
- (funcall |
|
307 |
- (cl-oid-connect:oauth2-login-middleware |
|
308 |
- :facebook-info |
|
309 |
- (truename "/home/edwlan/github_repos/cl-oid-connect/facebook-secrets.json") |
|
310 |
- :google-info |
|
311 |
- (truename "/home/edwlan/github_repos/cl-oid-connect/google-secrets.json")) |
|
312 |
- *app*)) :port 9090)) |
|
313 |
- |
314 | 280 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,346 @@ |
1 |
+(in-package :cl-user) |
|
2 |
+(ql:quickload :cl-oid-connect) |
|
3 |
+(ql:quickload :plump) |
|
4 |
+(ql:quickload :cl-markup) |
|
5 |
+ |
|
6 |
+(push (cons "application" "rdf+xml") drakma:*text-content-types*) |
|
7 |
+ |
|
8 |
+(defparameter *app* (make-instance 'ningle:<app>)) |
|
9 |
+ |
|
10 |
+(defclass rss-feed () |
|
11 |
+ ((feed :accessor rss-feed-feed |
|
12 |
+ :initarg :feed) |
|
13 |
+ (channel :accessor rss-feed-channel) |
|
14 |
+ (title :accessor rss-feed-title) |
|
15 |
+ (link :accessor rss-feed-link) |
|
16 |
+ (description :accessor rss-feed-description) |
|
17 |
+ (items :accessor rss-feed-items))) |
|
18 |
+ |
|
19 |
+(defclass rss-item () |
|
20 |
+ ((item :accessor rss-item-item :initarg :item) |
|
21 |
+ (title :accessor rss-item-title) |
|
22 |
+ (link :accessor rss-item-link) |
|
23 |
+ (description-raw :accessor rss-item-description-raw) |
|
24 |
+ (description :accessor rss-item-description) |
|
25 |
+ (category :accessor rss-item-category) |
|
26 |
+ (comments :accessor rss-item-comments) |
|
27 |
+ (enclosure :accessor rss-item-enclosure) |
|
28 |
+ (guid :accessor rss-item-guid) |
|
29 |
+ (pub-date :accessor rss-item-pub-date) |
|
30 |
+ (source :accessor rss-item-source))) |
|
31 |
+ |
|
32 |
+(defmacro get-elements (feed &optional (filter nil)) |
|
33 |
+ (let ((feed-sym (gensym)) |
|
34 |
+ (filter-lis `(lambda (x) (and (plump-dom:element-p x) ,@(loop for x in filter |
|
35 |
+ collect `(funcall ,x x)))))) |
|
36 |
+ `(let ((,feed-sym ,feed)) |
|
37 |
+ (remove-if-not ,filter-lis (plump:children ,feed-sym))))) |
|
38 |
+ |
|
39 |
+(defmacro get-elements-by-tagname (feed tagname) |
|
40 |
+ `(get-elements ,feed ((lambda (x) (string= ,tagname (plump:tag-name x)))))) |
|
41 |
+ |
|
42 |
+(defmacro extract-text (selector &optional (default "")) |
|
43 |
+ (alexandria:with-gensyms (selector-s) |
|
44 |
+ `(let ((,selector-s ,selector)) |
|
45 |
+ (if (not (equalp #() (lquery:$ ,selector-s))) |
|
46 |
+ (lquery:$ ,selector-s (text) (node)) |
|
47 |
+ ,default)))) |
|
48 |
+ |
|
49 |
+(defun make-rss-item (item) |
|
50 |
+ (lquery:initialize item) |
|
51 |
+ (flet ((dehtml (h) (plump:text (plump:parse h))) |
|
52 |
+ (get-category-names (it) ;;; TODO: simplify this---Ask Shinmera on IRC |
|
53 |
+ (if (not (equalp #() it)) |
|
54 |
+ (map 'vector |
|
55 |
+ (lambda (x) (plump:text (elt (plump:children x) 0))) |
|
56 |
+ it) |
|
57 |
+ #()))) |
|
58 |
+ (let* ((result (make-instance 'rss-item :item item)) |
|
59 |
+ (title (extract-text "title")) |
|
60 |
+ (link (extract-text "link")) |
|
61 |
+ (description-raw (let ((plump:*html-tags*) |
|
62 |
+ (ss (make-string-output-stream))) |
|
63 |
+ (plump:serialize |
|
64 |
+ (plump:parse (extract-text "description")) |
|
65 |
+ ss) |
|
66 |
+ (get-output-stream-string ss))) |
|
67 |
+ (description-munged (dehtml (extract-text "description"))) |
|
68 |
+ (category (get-category-names (lquery:$ "category"))) |
|
69 |
+ ;(comments) |
|
70 |
+ ;(enclosure) |
|
71 |
+ (guid (extract-text "aaguid")) |
|
72 |
+ (pub-date (extract-text "pubDate")) |
|
73 |
+ (source (extract-text "source"))) |
|
74 |
+ (setf (rss-item-title result) title) |
|
75 |
+ (setf (rss-item-link result) link) |
|
76 |
+ (setf (rss-item-description-raw result) description-raw) |
|
77 |
+ (setf (rss-item-description result) description-munged) |
|
78 |
+ (setf (rss-item-category result) category) |
|
79 |
+ ;(setf (rss-item-comments result) comment) |
|
80 |
+ ;(setf (rss-item-enclosure result) enclosur) |
|
81 |
+ (setf (rss-item-guid result) guid) |
|
82 |
+ (setf (rss-item-pub-date result) pub-date) |
|
83 |
+ (setf (rss-item-source result) source) |
|
84 |
+ result))) |
|
85 |
+ |
|
86 |
+(defun make-rss-feed (feed) |
|
87 |
+ (lquery:initialize feed) |
|
88 |
+ (let* ((result (make-instance 'rss-feed :feed feed)) |
|
89 |
+ (channel (lquery:$ "channel" (text) (node))) |
|
90 |
+ (title (lquery:$ "title" (text) (node))) |
|
91 |
+ (link (lquery:$ "link" (text) (node))) |
|
92 |
+ (description (lquery:$ "description" (text) (node))) |
|
93 |
+ (items (lquery:$ "item"))) |
|
94 |
+ (setf (rss-feed-channel result) channel) |
|
95 |
+ (setf (rss-feed-title result) title) |
|
96 |
+ (setf (rss-feed-link result) link) |
|
97 |
+ (setf (rss-feed-description result) description) |
|
98 |
+ (setf (rss-feed-items result) |
|
99 |
+ (loop for it across items |
|
100 |
+ collect (make-rss-item it))) |
|
101 |
+ result)) |
|
102 |
+ |
|
103 |
+ |
|
104 |
+(cl-oid-connect:def-route ("/login" (params) :app *app*) |
|
105 |
+ (cl-who:with-html-output-to-string (s) |
|
106 |
+ (:html |
|
107 |
+ (:head (:title "Login")) |
|
108 |
+ (:body |
|
109 |
+ (:div |
|
110 |
+ :class "login-button facebook" |
|
111 |
+ (:a :href "/login/facebook" "Facebook")) |
|
112 |
+ (:div |
|
113 |
+ :class "login-button google" |
|
114 |
+ (:a :href "/login/google" "Google")))))) |
|
115 |
+ |
|
116 |
+(defparameter *feed-urls* |
|
117 |
+ #( |
|
118 |
+ "http://www.reddit.com/r/lisp.rss" |
|
119 |
+ "http://www.reddit.com/r/scheme.rss" |
|
120 |
+ "http://www.reddit.com/r/prolog.rss" |
|
121 |
+ "http://www.reddit.com/r/haskell.rss" |
|
122 |
+ "http://www.reddit.com/r/roguelikedev.rss" |
|
123 |
+ "http://www.reddit.com/r/roguelikes.rss" |
|
124 |
+ "http://www.reddit.com/r/talesfromtechsupport.rss" |
|
125 |
+ )) |
|
126 |
+ |
|
127 |
+(let |
|
128 |
+ ((plump-parser:*tag-dispatchers* plump-parser:*xml-tags*)) |
|
129 |
+ (defparameter *docs* (map 'vector |
|
130 |
+ (lambda (x) |
|
131 |
+ (format t "~a~%" x) |
|
132 |
+ (unwind-protect (plump-parser:parse |
|
133 |
+ (drakma:http-request x)))) |
|
134 |
+ *feed-urls*))) |
|
135 |
+ |
|
136 |
+(defparameter *feeds* (map 'vector (lambda (x) (unwind-protect (make-rss-feed x))) *docs*)) |
|
137 |
+ |
|
138 |
+(defclass colorscheme () |
|
139 |
+ ((background :accessor colorscheme-background :initform "#002b36") |
|
140 |
+ (foreground :accessor colorscheme-foreground :initform "#839496") |
|
141 |
+ (accent :accessor colorscheme-accent :initform "#586e75" ) |
|
142 |
+ (base03 :accessor colorscheme-base03 :initform "#002b36") |
|
143 |
+ (base02 :accessor colorscheme-base02 :initform "#073642") |
|
144 |
+ (base01 :accessor colorscheme-base01 :initform "#586e75") |
|
145 |
+ (base00 :accessor colorscheme-base00 :initform "#657b83") |
|
146 |
+ (base0 :accessor colorscheme-base0 :initform "#839496") |
|
147 |
+ (base1 :accessor colorscheme-base1 :initform "#93a1a1") |
|
148 |
+ (base2 :accessor colorscheme-base2 :initform "#eee8d5") |
|
149 |
+ (base3 :accessor colorscheme-base3 :initform "#fdf6e3") |
|
150 |
+ (yellow :accessor colorscheme-yellow :initform "#b58900") |
|
151 |
+ (orange :accessor colorscheme-orange :initform "#cb4b16") |
|
152 |
+ (red :accessor colorscheme-red :initform "#dc322f") |
|
153 |
+ (magenta :accessor colorscheme-magenta :initform "#d33682") |
|
154 |
+ (violet :accessor colorscheme-violet :initform "#6c71c4") |
|
155 |
+ (blue :accessor colorscheme-blue :initform "#268bd2") |
|
156 |
+ (cyan :accessor colorscheme-cyan :initform "#2aa198") |
|
157 |
+ (green :accessor colorscheme-green :initform "#859900"))) |
|
158 |
+ |
|
159 |
+(defgeneric accentize (colorscheme accent)) |
|
160 |
+(defmethod accentize ((colorscheme colorscheme) accent) |
|
161 |
+ (setf (colorscheme-accent colorscheme) (funcall accent colorscheme))) |
|
162 |
+ |
|
163 |
+(defgeneric rebase (colorscheme)) |
|
164 |
+(defmethod rebase ((colorscheme colorscheme)) |
|
165 |
+ (macrolet |
|
166 |
+ ((swap-color (obj slot color1 color2) |
|
167 |
+ `(setf (,slot ,obj) |
|
168 |
+ (if (string= (,slot ,obj) (,color1 ,obj)) |
|
169 |
+ (,color2 ,obj) |
|
170 |
+ (,color1 ,obj))))) |
|
171 |
+ ; Note that swap-color doesn't use gensyms: so don't run functions in invocation |
|
172 |
+ (swap-color colorscheme colorscheme-foreground colorscheme-base0 colorscheme-base0) |
|
173 |
+ (swap-color colorscheme colorscheme-accent colorscheme-base1 colorscheme-base01) |
|
174 |
+ (swap-color colorscheme colorscheme-background colorscheme-base3 colorscheme-base03) |
|
175 |
+ colorscheme)) |
|
176 |
+ |
|
177 |
+ |
|
178 |
+(defparameter *colorscheme* (make-instance 'colorscheme)) |
|
179 |
+(rebase *colorscheme*) |
|
180 |
+(accentize *colorscheme* #'colorscheme-blue) |
|
181 |
+ |
|
182 |
+;rebase $base3, $base2, $base1, $base0,$base00,$base01,$base02,$base03 |
|
183 |
+;rebase $base03,$base02,$base01,$base00 ,$base0 ,$base1 ,$base2 ,$base3 |
|
184 |
+ |
|
185 |
+ |
|
186 |
+(cl-oid-connect:def-route ("/" (params) :app *app*) |
|
187 |
+ (flet ((combine-unit-q (quant unit) (format nil "~d~a" quant unit))) |
|
188 |
+ (let* ((header-height 10) |
|
189 |
+ (height-units "vh") |
|
190 |
+ (ss (lass:compile-and-write |
|
191 |
+ `(* :color ,(colorscheme-foreground *colorscheme*)) |
|
192 |
+ |
|
193 |
+ `(body :background-color ,(colorscheme-background *colorscheme*)) |
|
194 |
+ |
|
195 |
+ `((:or h1 h2 h3 h4 h5 h6) :color ,(colorscheme-accent *colorscheme*)) |
|
196 |
+ |
|
197 |
+ `(header |
|
198 |
+ :border-bottom "thin" "solid" ,(colorscheme-foreground *colorscheme*) |
|
199 |
+ :height ,(combine-unit-q header-height height-units) |
|
200 |
+ :font-size ,(combine-unit-q (* 0.75 header-height) height-units) |
|
201 |
+ :line-height ,(combine-unit-q header-height height-units)) |
|
202 |
+ |
|
203 |
+ `((:or a (:and a :visited) (:and a :active) code.url) |
|
204 |
+ :color ,(colorscheme-accent *colorscheme*)) |
|
205 |
+ |
|
206 |
+ `(section#sidebar |
|
207 |
+ ((ul.menu li a) |
|
208 |
+ ((+ a) |
|
209 |
+ :border-top "thin" "solid" ,(colorscheme-accent *colorscheme*)) |
|
210 |
+ ((:and li :hover) |
|
211 |
+ :background-color ,(colorscheme-foreground *colorscheme*) |
|
212 |
+ :color ,(colorscheme-background *colorscheme*)))) |
|
213 |
+ |
|
214 |
+ `(.feed :border thin solid ,(colorscheme-foreground *colorscheme*)) |
|
215 |
+ `(.link |
|
216 |
+ :border-top thin solid ,(colorscheme-foreground *colorscheme*) |
|
217 |
+ :border-bottom thin solid ,(colorscheme-foreground *colorscheme*) |
|
218 |
+ |
|
219 |
+ (.link-info |
|
220 |
+ :background ,(colorscheme-foreground *colorscheme*) |
|
221 |
+ :color ,(colorscheme-background *colorscheme*) |
|
222 |
+ :border "thin" "solid" ,(colorscheme-foreground *colorscheme*) |
|
223 |
+ |
|
224 |
+ (.link-url |
|
225 |
+ ;:color ,(colorscheme-cyan *colorscheme*) |
|
226 |
+ :color ,(colorscheme-background *colorscheme*)) |
|
227 |
+ (.link-date |
|
228 |
+ :color ,(colorscheme-background *colorscheme*))))))) |
|
229 |
+ |
|
230 |
+ `(200 (:content-type "text/css") ,ss)))) |
|
231 |
+ |
|
232 |
+(defmacro item-markup (item) |
|
233 |
+ (alexandria:with-gensyms (item-s) |
|
234 |
+ `(let ((,item-s ,item)) |
|
235 |
+ (cl-markup:markup |
|
236 |
+ (:li :class "link closed" |
|
237 |
+ (:section :class "link-header" |
|
238 |
+ (:h4 (rss-item-title ,item-s)) |
|
239 |
+ (:p :class "link-info" |
|
240 |
+ (:a :target "_blank" :href (rss-item-link ,item-s) |
|
241 |
+ (:span :class "link-url" (rss-item-link ,item-s))) |
|
242 |
+ (:span :class "link-date") (rss-item-pub-date ,item-s))) |
|
243 |
+ (:section :class "link-content" |
|
244 |
+ (cl-markup:raw (rss-item-description-raw ,item-s)))))))) |
|
245 |
+ |
|
246 |
+(defmacro feed-markup (feed-v fc-v) |
|
247 |
+ (alexandria:with-gensyms (feed fc) |
|
248 |
+ `(let ((,feed ,feed-v) |
|
249 |
+ (,fc ,fc-v)) |
|
250 |
+ (cl-markup:markup |
|
251 |
+ (:section :class "feed" :id (format nil "feed-~a" ,fc) |
|
252 |
+ (:section :class "feed-header" |
|
253 |
+ (:h2 (rss-feed-title ,feed)) |
|
254 |
+ (:h3 (rss-feed-description ,feed))) |
|
255 |
+ (:ul :class "post-list" |
|
256 |
+ (loop for item in (rss-feed-items ,feed) |
|
257 |
+ collect (item-markup item)))))))) |
|
258 |
+ |
|
259 |
+(defmacro feedlist-markup (feedlist-v) |
|
260 |
+ (alexandria:with-gensyms (feedlist) |
|
261 |
+ `(let ((,feedlist ,feedlist-v)) |
|
262 |
+ (cl-markup:markup* |
|
263 |
+ `(:ul :class "menu" |
|
264 |
+ ,@(loop for feed across ,feedlist |
|
265 |
+ count feed into feed-count |
|
266 |
+ collect |
|
267 |
+ (list :li |
|
268 |
+ (list :a |
|
269 |
+ :href (format nil "#feed-~a" feed-count) |
|
270 |
+ (rss-feed-title feed))))))))) |
|
271 |
+ |
|
272 |
+(defmacro base-template () |
|
273 |
+ `(cl-markup:html5 |
|
274 |
+ (:head |
|
275 |
+ (:title "My Feeds") |
|
276 |
+ (:script :src "https://code.jquery.com/jquery-2.1.4.min.js" :type "text/javascript" "") |
|
277 |
+ (:script :src "/static/js/fold.js" :type "text/javascript" "") |
|
278 |
+ (:link :rel "stylesheet" :href "/static/css/main.css") |
|
279 |
+ (:link :rel "stylesheet" :href "/theme.css")) |
|
280 |
+ (:body |
|
281 |
+ (:header |
|
282 |
+ (:h1 "Worricow")) |
|
283 |
+ (:section :id "content" |
|
284 |
+ (:section :id "sidebar" |
|
285 |
+ (cl-markup:raw (feedlist-markup *feeds*))) |
|
286 |
+ (:main |
|
287 |
+ (loop for feed across *feeds* |
|
288 |
+ count feed into feed-count |
|
289 |
+ collect |
|
290 |
+ (feed-markup feed feed-count)))) |
|
291 |
+ (:footer)))) |
|
292 |
+ |
|
293 |
+(defun base-template-f () (base-template)) |
|
294 |
+ |
|
295 |
+;(cl-oid-connect:def-route ("/" (params) :app *app*) |
|
296 |
+; (ningle:with-context-variables (session) |
|
297 |
+; (cl-oid-connect:redirect-if-necessary session |
|
298 |
+; (cl-oid-connect:require-login |
|
299 |
+; (anaphora:sunless (gethash :counter session) (setf anaphora:it 0)) |
|
300 |
+; (incf (gethash :counter session)) |
|
301 |
+; (format nil "~Ath visit<br/>~a<br/><br/>" |
|
302 |
+; (gethash :counter session)))))) |
|
303 |
+ |
|
304 |
+ |
|
305 |
+(cl-oid-connect:def-route ("/feeds/:feeds/html" (params) :app *app*) |
|
306 |
+ (ningle.context:with-context-variables (session) |
|
307 |
+ (cl-oid-connect:require-login |
|
308 |
+ (let* ((feedlist-s (cdr (assoc :feeds params))) |
|
309 |
+ (feedlist (mapcar #'parse-integer (split-sequence:split-sequence #\SPACE feedlist-s))) |
|
310 |
+ (*feeds* (gethash :feeds session *feeds*)) |
|
311 |
+ (*feeds* (make-array (list (length feedlist)) |
|
312 |
+ :initial-contents (loop for x in feedlist |
|
313 |
+ collect (elt *feeds* x))))) |
|
314 |
+ (base-template-f))))) |
|
315 |
+ |
|
316 |
+(cl-oid-connect:def-route ("/" (params) :app *app*) |
|
317 |
+ (ningle.context:with-context-variables (session) |
|
318 |
+ (cl-oid-connect:require-login |
|
319 |
+ (cl-oid-connect:require-login |
|
320 |
+ (let ((*feeds* (gethash :feeds session *feeds*))) |
|
321 |
+ (base-template-f)))))) |
|
322 |
+ |
|
323 |
+(defvar *handler* nil) |
|
324 |
+ |
|
325 |
+ |
|
326 |
+(defun start (tmp) |
|
327 |
+ (let ((server (if (> (length tmp) 1) |
|
328 |
+ (intern (string-upcase (elt tmp 1)) 'keyword) |
|
329 |
+ :hunchentoot))) |
|
330 |
+ (push (clack:clackup |
|
331 |
+ (lack.builder:builder |
|
332 |
+ :backtrace |
|
333 |
+ :session |
|
334 |
+ :csrf |
|
335 |
+ (:static :path "/static/" :root #p"./static/") |
|
336 |
+ (funcall |
|
337 |
+ (cl-oid-connect:oauth2-login-middleware |
|
338 |
+ :facebook-info (truename "~/github_repos/cl-oid-connect/facebook-secrets.json") |
|
339 |
+ :google-info (truename "~/github_repos/cl-oid-connect/google-secrets.json")) |
|
340 |
+ *app*)) :port 9090 :server server) |
|
341 |
+ *handler*)) |
|
342 |
+ (loop (mp:process-wait)) |
|
343 |
+ ) |
|
344 |
+ |
|
345 |
+ |
|
346 |
+ |
0 | 347 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,106 @@ |
1 |
+(:import "url(https://fonts.googleapis.com/css?family=Lato:400,100,300,400italic,300italic,700,700italic,900&subset=latin,latin-ext)") |
|
2 |
+(:import "url(https://fonts.googleapis.com/css?family=Caudex)") |
|
3 |
+ |
|
4 |
+(* |
|
5 |
+ :box-sizing "border-box" |
|
6 |
+ :margin "0px" |
|
7 |
+ :padding "0px" |
|
8 |
+ :font-family "Lato") |
|
9 |
+ |
|
10 |
+((:or ul ol) |
|
11 |
+ :list-style "none") |
|
12 |
+ |
|
13 |
+((:or h1 h2) |
|
14 |
+ :font-size "153.9%") |
|
15 |
+(:h3 |
|
16 |
+ :font-size "146.5%") |
|
17 |
+((:or h4 h5 h6) |
|
18 |
+ :font-size "138.5%") |
|
19 |
+ |
|
20 |
+(((:or ul) + (:or h1 h2 h3 h4 h5 h6)) |
|
21 |
+ :width "initial") |
|
22 |
+ |
|
23 |
+(header |
|
24 |
+ :color "white" |
|
25 |
+ (h1 |
|
26 |
+ :font-family "Caudex" |
|
27 |
+ :font-size inherit |
|
28 |
+ :margin-top "0em" |
|
29 |
+ :padding-left "1em" |
|
30 |
+ :font-weight 200)) |
|
31 |
+ |
|
32 |
+(section#sidebar |
|
33 |
+ :width "20vw" |
|
34 |
+ :height "90vh" |
|
35 |
+ :position "fixed" |
|
36 |
+ :overflow "auto" |
|
37 |
+ (ul.menu |
|
38 |
+ :text-align right |
|
39 |
+ :font-variant small-cap |
|
40 |
+ (li |
|
41 |
+ (a |
|
42 |
+ :width "100%" |
|
43 |
+ :display block |
|
44 |
+ :padding "1em" |
|
45 |
+ :color inherit |
|
46 |
+ :text-decoration none |
|
47 |
+ :font-weight 700 |
|
48 |
+ :font-size "125%" |
|
49 |
+ )))) |
|
50 |
+ |
|
51 |
+(main |
|
52 |
+ :padding-right "20vw" |
|
53 |
+ :width "80vw" |
|
54 |
+ :height "90vh" |
|
55 |
+ :float "right" |
|
56 |
+ :clear "right" |
|
57 |
+ :overflow "auto") |
|
58 |
+ |
|
59 |
+(.feed-header |
|
60 |
+ :padding "1em" |
|
61 |
+ :padding-bottom "0em") |
|
62 |
+ |
|
63 |
+((:and .link :before) |
|
64 |
+ :content "-") |
|
65 |
+ |
|
66 |
+((:and .link.closed :before) |
|
67 |
+ :content "+") |
|
68 |
+ |
|
69 |
+((.link.closed .link-content) |
|
70 |
+ :max-height "0px" |
|
71 |
+ :padding "0em") |
|
72 |
+ |
|
73 |
+(.link |
|
74 |
+ :text-decoration none |
|
75 |
+ :display block |
|
76 |
+ :padding "1em" |
|
77 |
+ :padding-bottom "0em" |
|
78 |
+ :overflow hidden |
|
79 |
+ :font-size "0.8em" |
|
80 |
+ (.link-header |
|
81 |
+ :cursor pointer |
|
82 |
+ (h4 |
|
83 |
+ :margin-bottom "0.5em" |
|
84 |
+ :display "inline-block")) |
|
85 |
+ (.link-info |
|
86 |
+ :margin-left "-1em" |
|
87 |
+ :margin-right "-1em" |
|
88 |
+ :padding-left "1em" |
|
89 |
+ :padding-right "1em" |
|
90 |
+ (.link-url |
|
91 |
+ :float "left" |
|
92 |
+ ) |
|
93 |
+ (.link-date |
|
94 |
+ :float "right" |
|
95 |
+ :display block)) |
|
96 |
+ ((:and .link-info :after) |
|
97 |
+ :content "\" \"" |
|
98 |
+ :display block |
|
99 |
+ :clear both) |
|
100 |
+ (.link-content |
|
101 |
+ :transition "max-height 0.2s ease")) |
|
102 |
+ |
|
103 |
+(.feed |
|
104 |
+ :overflow "hidden" |
|
105 |
+ ) |
|
106 |
+ |
18 | 21 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,17 @@ |
1 |
+(chain ($ document) |
|
2 |
+ (ready |
|
3 |
+ (lambda () |
|
4 |
+ (chain ($ ".link-header") |
|
5 |
+ (click |
|
6 |
+ (lambda () |
|
7 |
+ (chain ($ this) |
|
8 |
+ (siblings ".link-content") |
|
9 |
+ (each (lambda () |
|
10 |
+ (if (= (chain ($ this) (css "max-height")) "0px") |
|
11 |
+ (chain ($ this) |
|
12 |
+ (css "max-height" (@ this scroll-height))) |
|
13 |
+ (chain ($ this) |
|
14 |
+ (css "max-height" "0px")))))) |
|
15 |
+ (chain ($ this) |
|
16 |
+ (parent) |
|
17 |
+ (toggle-class "closed")))))))) |
0 | 18 |
new file mode 100644 |
... | ... |
@@ -0,0 +1 @@ |
1 |
+@import url(https://fonts.googleapis.com/css?family=Lato:400,100,300,400italic,300italic,700,700italic,900&subset=latin,latin-ext);@import url(https://fonts.googleapis.com/css?family=Caudex);*{box-sizing:border-box;margin:0px;padding:0px;font-family:Lato;}ul,ol{list-style:none;}h1,h2{font-size:153.9%;}:h3{font-size:146.5%;}h4,h5,h6{font-size:138.5%;}ul + h1,ul + h2,ul + h3,ul + h4,ul + h5,ul + h6{width:initial;}header{color:white;}header h1{font-family:Caudex;font-size:inherit;margin-top:0em;padding-left:1em;font-weight:200;}section#sidebar{width:20vw;height:90vh;position:fixed;overflow:auto;}section#sidebar ul.menu{text-align:right;font-variant:small-cap;}section#sidebar ul.menu li a{width:100%;display:block;padding:1em;color:inherit;text-decoration:none;font-weight:700;font-size:125%;}main{padding-right:20vw;width:80vw;height:90vh;float:right;clear:right;overflow:auto;}.feed-header{padding:1em;padding-bottom:0em;}.link:before{content:-;}.link.closed:before{content:+;}.link.closed .link-content{max-height:0px;padding:0em;}.link{text-decoration:none;display:block;padding:1em;padding-bottom:0em;overflow:hidden;font-size:0.8em;}.link .link-header{cursor:pointer;}.link .link-header h4{margin-bottom:0.5em;display:inline-block;}.link .link-info{margin-left:-1em;margin-right:-1em;padding-left:1em;padding-right:1em;}.link .link-info .link-url{float:left;}.link .link-info .link-date{float:right;display:block;}.link .link-info:after{content:" ";display:block;clear:both;}.link .link-content{-moz-transition:max-height 0.2s ease;-o-transition:max-height 0.2s ease;-webkit-transition:max-height 0.2s ease;-ms-transition:max-height 0.2s ease;transition:max-height 0.2s ease;}.feed{overflow:hidden;} |
|
0 | 2 |
\ No newline at end of file |
1 | 3 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,9 @@ |
1 |
+$(document).ready(function () { |
|
2 |
+ return $('.link-header').click(function () { |
|
3 |
+ $(this).siblings('.link-content').each(function () { |
|
4 |
+ return $(this).css('max-height') === '0px' ? $(this).css('max-height', this.scrollHeight) : $(this).css('max-height', '0px'); |
|
5 |
+ }); |
|
6 |
+ return $(this).parent().toggleClass('closed'); |
|
7 |
+ }); |
|
8 |
+}); |
|
9 |
+ |