Compare commits

...

3 commits

3 changed files with 60 additions and 41 deletions

View file

@ -18,6 +18,12 @@ After that
npm run build
```
You can upload these files to any static file server, for example using `rsync`:
``` bash
rsync -rsvP --delete-after --exclude js/cljs-runtime/ public/ user@server:some/directory
```
## Development
Just as when building a release, you need to ensure that JS dependencies are installed:

View file

@ -163,6 +163,12 @@
}
@media screen and (min-width: 640px) {
section.posts .controls {
grid-template:
"a a"
"b c";
}
section.posts .controls .post-info {
grid-area: a;
}

View file

@ -9,6 +9,7 @@
(def posts-init-state
{:query nil
:last-update -1
; TODO: pagination
; :page 0
; :max-displayed-id nil
@ -42,6 +43,9 @@
(defn- promise-reject [val]
(js/Promise.reject val))
(defn- now []
(js/Date.now))
(defn- link-header
"Given a JS `Response` object, will parse the `link` header and find a link of
a given `link-type` if present. Useful for paginating API requests."
@ -152,7 +156,6 @@
(obtain-oauth-authorization-code! application))))))
(declare fetch-posts!)
(declare debounced-refresh!)
(defn- fetch-application-settings []
(->> (db/open-cursor! ::db/application ::db/all)
@ -199,7 +202,7 @@
(if (zero? post-count)
(fetch-posts! {:instance-url (:instance_url application)
:bearer-token (:bearer_token application)})
(debounced-refresh! (:section/posts @state))))))))
(swap! state assoc-in [:section/posts :last-update] (now))))))))
;;; views
@ -247,7 +250,7 @@
on-response on-response
on-error on-error}}]
((fn paginate! [url]
(let [req-id (js/Date.now)]
(let [req-id (now)]
(js/console.log :paginate! url :max-id max-id :min-id min-id)
(swap! state update-in [:section/posts :loading] conj req-id)
(-> (mastodon-request! {:url url :bearer-token bearer-token})
@ -271,23 +274,50 @@
always prefer to call the most recent call to `f` as close to the delay
as possible."
[ms f]
(let [prev (volatile! (js/Date.now))
scheduled (volatile! (js/setTimeout (fn dummy []) ms))]
(let [prev (volatile! (now))]
(fn debounced-fn [& args]
(let [now (js/Date.now)]
(js/clearTimeout @scheduled)
(vreset! scheduled (js/setTimeout (fn []
(vreset! prev (js/Date.now))
(apply f args))
(max 0 (- ms (- now @prev)))))))))
(when (< ms (- (now) @prev))
(js/requestAnimationFrame #(apply f args)))
(vreset! prev (now)))))
(defn search []
(defn- refresh-displayed-posts!
[{:keys [per-page query]}]
(let [; this `xform` is responsible for filtering and building the final list
; of results by iterating through the posts in the database.
xform (comp
(filter (query->matching-fn query))
(take per-page)
(map #(js->clj % :keywordize-keys true)))
refresh-id (now)]
(swap! state update-in [:section/posts :loading] conj refresh-id)
(. (promise-all [(db/count! ::db/posts)
(->> (db/open-cursor! ::db/posts ::db/all
{:index ::db/post-created-at
:direction :desc})
(db/transduce-cursor xform))])
(then (fn [[total displayed-posts]]
(swap! state update :section/posts #(-> (assoc % :total total)
(assoc :displayed-posts displayed-posts)
(update :loading disj refresh-id))))))))
; we use reagent's machinery below to define a callback that runs whenever the
; values change that serve as input to the current search reults.
(def debounced-refresh! (debounce 48 refresh-displayed-posts!))
(def search-result-inputs (r/reaction (select-keys (:section/posts @state) [:query :per-page :last-update])))
(defonce update-search-results (r/track! (fn []
(let [inputs @search-result-inputs]
(debounced-refresh! inputs)))))
(defn search [{:keys [query]}]
[:input {:placeholder "Start typing to search…"
:type "search"
:initial-value query
:on-change (fn [e]
(let [query (.. e -target -value)]
(swap! state assoc-in [:section/posts :query] (if (str/blank? query) nil query))
(debounced-refresh! (:section/posts @state))))}])
(swap! state assoc-in [:section/posts :query] (if (str/blank? query) nil query))))}])
(defn- debug
"Implements a lazy pretty-printer for whatever is passed in as `obj`. The
@ -369,29 +399,6 @@
(js/console.log :logging args)
(apply f args)))))
(defn- refresh-displayed-posts!
[posts-section]
(let [{:keys [per-page query]} posts-section
; this `xform` is responsible for filtering and building the final list
; of results by iterating through the posts in the database.
xform (comp
(filter (query->matching-fn query))
(take per-page)
(map #(js->clj % :keywordize-keys true)))
refresh-id (js/Date.now)]
(swap! state update-in [:section/posts :loading] conj refresh-id)
(. (promise-all [(db/count! ::db/posts)
(->> (db/open-cursor! ::db/posts ::db/all
{:index ::db/post-created-at
:direction :desc})
(db/transduce-cursor xform))])
(then (fn [[total displayed-posts]]
(swap! state update :section/posts #(-> (assoc % :total total)
(assoc :displayed-posts displayed-posts)
(update :loading disj refresh-id))))))))
(def debounced-refresh! (debounce 40 refresh-displayed-posts!))
(defn- fetch-posts! [opts]
(let [defaults {:max-id nil
:on-response (fn [response]
@ -410,7 +417,7 @@
(.get url-params "min_id")
(assoc :internal_id (parse-long (.get url-params "min_id")))))))
(debounced-refresh! (:section/posts @state)))}]
(swap! state assoc-in [:section/posts :last-update] (now)))}]
(paginate-posts! (merge defaults opts))))
(defn- internal-post-id
@ -452,7 +459,7 @@
[:path.arc {:d "M50,0 A50,50 180 0,1 100,50"}]]))
(defn posts-section [{:keys [posts]}]
(let [{:keys [per-page query total displayed-posts loading]} posts
(let [{:keys [per-page query total displayed-posts loading query]} posts
n-displayed (count displayed-posts)]
[:section.posts
[:h2 "Favorites"]
@ -465,7 +472,7 @@
" match"
" matches")))))]
[:section.search-form
[search]
[search {:query query}]
[loading-indicator {:loading loading}]
#_(cond (= api-state :loading) " …"
(= api-state :error) " API Error!")]