(page "index.html" (:import goog.html.sanitizer.HtmlSanitizer goog.html.sanitizer.HtmlSanitizer.Builder goog.html.sanitizer.unsafe goog.html.sanitizer.AttributeWhitelist goog.string.Const goog.functions goog.html.SafeUrl goog.html.SafeHtml) (:require [cljs.pprint :refer [cl-format] :rename {cl-format format}]) (:require-macros [feed-archive.let-promise :refer [let-promise]])) (defc state {"base-url" "http://roachnotes.com" "pull-time" "" "feeds" [] :items {}}) (defc= base-url (get state "base-url") #(swap! state assoc-in ["base-url"] %)) (defc= pull-time (get state "pull-time")) (defc= feeds (get state "feeds")) (defn setup-state [base-url] (let-promise [[resp (js/fetch (str base-url "/current"))] [data (.json resp)]] (swap! state merge (js->clj data)))) (cell= (when base-url (setup-state base-url))) (defn make-feed-getter [out-cell] (fn [base-url path] (let-promise [[resp (js/fetch (str base-url "/" path))] [data (.json resp)]] (reset! out-cell (js->clj data))))) (defn get-feed-entry-cells [base-url feed] (let [feed-cell (cell {}) items (cell= (get feed-cell "items")) item-count (cell= (count items)) get-feed (make-feed-getter feed-cell) path (cell= (get feed "path" "")) url (cell= (get feed "url" "")) title (cell= (get feed "title" ""))] (cell= (get-feed base-url path)) [url title path feed-cell items item-count])) (defn get-sanitizer [] (let [r (Builder.) justification (.from Const "Because images are ok, silly")] (.alsoAllowAttributes unsafe justification r #js [#js {:tagName "img" :attributeName "src" :policy nil}]) (.build r))) (defn sanitize-html [html] (let [result (.sanitize (get-sanitizer) html)] (.unwrap SafeHtml result))) (defn make-item-getter [out-cell content] (fn [base-url feed-path item-path] (let-promise [[resp (js/fetch (str base-url "/" feed-path item-path))] [data (.json resp)]] (let [data (js->clj data)] (reset! content (sanitize-html (get data "content"))) (reset! out-cell data))))) (defn get-item-cells [item content] (let* [key (get @item "path") item-cell (cell= (get (:items state) key) #(swap! state assoc-in [:items key] %)) get-item (make-item-getter item-cell content)] [get-item (cell= (get item "title")) (cell= (get item "path")) (cell= (get item-cell "link")) (cell= (sanitize-html (get item-cell "content")))])) (cell= (.log js/console (clj->js base-url))) (defn column-width [len] (letfn [(divisible-by [x] (= 0 (mod len x)))] (format nil "~d%" (- (cond (divisible-by 5) 20 (divisible-by 4) 25 (divisible-by 3) 33 (> len 30) 20 (> len 20) 25 (> len 10) 33 true 50) 0.5)))) (defelem feed-item [{item :item path :path css :my/css overlay :overlay content-cell :content-cell :as attrs} _] (let [[get-item title item-path link content] (get-item-cells item content-cell) toggled (cell false) leftover-attrs (dissoc attrs :item :path :my/css :overlay :content-cell)] (cell= (get-item base-url path item-path)) (article leftover-attrs :css css :class "summary" (div :html content) (header (h3 (a :href link title)) " " (button :class "show-article" :click #(dosync (get-item @base-url @path @item-path) (reset! overlay true) (swap! toggled not)) ">>"))))) (defelem feed-view [{feed :feed} _] (let [[url title path feed-cell items item-count] (get-feed-entry-cells base-url feed) child-width (cell= (column-width item-count)) overlay-visible (cell false) content-cell (cell "")] (section :class (cell= {"displayed" overlay-visible}) (div :class (cell= {"article-overlay" true "displayed" overlay-visible}) (header (button :click #(reset! overlay-visible false) "X")) (article :html content-cell)) (if-tpl (cell= (> item-count 0)) (div (header (h2 title) (p url)) (loop-tpl :bindings [item items] (feed-item :path path :item item :overlay overlay-visible :content-cell content-cell :my/css (cell= {:width child-width})))))))) (html (head (link :href "app.css" :rel "stylesheet" :type "text/css")) (body (header (h1 "Roach Notes " (div :class "beta" :html "β")) (input :type "text" :value base-url :change #(reset! base-url (.-value (.-target %))))) (main (loop-tpl :bindings [feed feeds] (feed-view :feed feed)))))