Compare commits
3 commits
7d359c2ed7
...
de2cf7f9c2
| Author | SHA1 | Date | |
|---|---|---|---|
| de2cf7f9c2 | |||
| 09407c3231 | |||
| a9e4d2aa38 |
3 changed files with 60 additions and 41 deletions
|
|
@ -18,6 +18,12 @@ After that
|
||||||
npm run build
|
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
|
## Development
|
||||||
|
|
||||||
Just as when building a release, you need to ensure that JS dependencies are installed:
|
Just as when building a release, you need to ensure that JS dependencies are installed:
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 640px) {
|
@media screen and (min-width: 640px) {
|
||||||
|
section.posts .controls {
|
||||||
|
grid-template:
|
||||||
|
"a a"
|
||||||
|
"b c";
|
||||||
|
}
|
||||||
|
|
||||||
section.posts .controls .post-info {
|
section.posts .controls .post-info {
|
||||||
grid-area: a;
|
grid-area: a;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
(def posts-init-state
|
(def posts-init-state
|
||||||
{:query nil
|
{:query nil
|
||||||
|
:last-update -1
|
||||||
; TODO: pagination
|
; TODO: pagination
|
||||||
; :page 0
|
; :page 0
|
||||||
; :max-displayed-id nil
|
; :max-displayed-id nil
|
||||||
|
|
@ -42,6 +43,9 @@
|
||||||
(defn- promise-reject [val]
|
(defn- promise-reject [val]
|
||||||
(js/Promise.reject val))
|
(js/Promise.reject val))
|
||||||
|
|
||||||
|
(defn- now []
|
||||||
|
(js/Date.now))
|
||||||
|
|
||||||
(defn- link-header
|
(defn- link-header
|
||||||
"Given a JS `Response` object, will parse the `link` header and find a link of
|
"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."
|
a given `link-type` if present. Useful for paginating API requests."
|
||||||
|
|
@ -152,7 +156,6 @@
|
||||||
(obtain-oauth-authorization-code! application))))))
|
(obtain-oauth-authorization-code! application))))))
|
||||||
|
|
||||||
(declare fetch-posts!)
|
(declare fetch-posts!)
|
||||||
(declare debounced-refresh!)
|
|
||||||
|
|
||||||
(defn- fetch-application-settings []
|
(defn- fetch-application-settings []
|
||||||
(->> (db/open-cursor! ::db/application ::db/all)
|
(->> (db/open-cursor! ::db/application ::db/all)
|
||||||
|
|
@ -199,7 +202,7 @@
|
||||||
(if (zero? post-count)
|
(if (zero? post-count)
|
||||||
(fetch-posts! {:instance-url (:instance_url application)
|
(fetch-posts! {:instance-url (:instance_url application)
|
||||||
:bearer-token (:bearer_token application)})
|
:bearer-token (:bearer_token application)})
|
||||||
(debounced-refresh! (:section/posts @state))))))))
|
(swap! state assoc-in [:section/posts :last-update] (now))))))))
|
||||||
|
|
||||||
;;; views
|
;;; views
|
||||||
|
|
||||||
|
|
@ -247,7 +250,7 @@
|
||||||
on-response on-response
|
on-response on-response
|
||||||
on-error on-error}}]
|
on-error on-error}}]
|
||||||
((fn paginate! [url]
|
((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)
|
(js/console.log :paginate! url :max-id max-id :min-id min-id)
|
||||||
(swap! state update-in [:section/posts :loading] conj req-id)
|
(swap! state update-in [:section/posts :loading] conj req-id)
|
||||||
(-> (mastodon-request! {:url url :bearer-token bearer-token})
|
(-> (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
|
always prefer to call the most recent call to `f` as close to the delay
|
||||||
as possible."
|
as possible."
|
||||||
[ms f]
|
[ms f]
|
||||||
(let [prev (volatile! (js/Date.now))
|
(let [prev (volatile! (now))]
|
||||||
scheduled (volatile! (js/setTimeout (fn dummy []) ms))]
|
|
||||||
(fn debounced-fn [& args]
|
(fn debounced-fn [& args]
|
||||||
(let [now (js/Date.now)]
|
(when (< ms (- (now) @prev))
|
||||||
(js/clearTimeout @scheduled)
|
(js/requestAnimationFrame #(apply f args)))
|
||||||
(vreset! scheduled (js/setTimeout (fn []
|
(vreset! prev (now)))))
|
||||||
(vreset! prev (js/Date.now))
|
|
||||||
(apply f args))
|
|
||||||
(max 0 (- ms (- now @prev)))))))))
|
|
||||||
|
|
||||||
(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…"
|
[:input {:placeholder "Start typing to search…"
|
||||||
:type "search"
|
:type "search"
|
||||||
|
:initial-value query
|
||||||
:on-change (fn [e]
|
:on-change (fn [e]
|
||||||
(let [query (.. e -target -value)]
|
(let [query (.. e -target -value)]
|
||||||
(swap! state assoc-in [:section/posts :query] (if (str/blank? query) nil query))
|
(swap! state assoc-in [:section/posts :query] (if (str/blank? query) nil query))))}])
|
||||||
(debounced-refresh! (:section/posts @state))))}])
|
|
||||||
|
|
||||||
(defn- debug
|
(defn- debug
|
||||||
"Implements a lazy pretty-printer for whatever is passed in as `obj`. The
|
"Implements a lazy pretty-printer for whatever is passed in as `obj`. The
|
||||||
|
|
@ -369,29 +399,6 @@
|
||||||
(js/console.log :logging args)
|
(js/console.log :logging args)
|
||||||
(apply f 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]
|
(defn- fetch-posts! [opts]
|
||||||
(let [defaults {:max-id nil
|
(let [defaults {:max-id nil
|
||||||
:on-response (fn [response]
|
:on-response (fn [response]
|
||||||
|
|
@ -410,7 +417,7 @@
|
||||||
|
|
||||||
(.get url-params "min_id")
|
(.get url-params "min_id")
|
||||||
(assoc :internal_id (parse-long (.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))))
|
(paginate-posts! (merge defaults opts))))
|
||||||
|
|
||||||
(defn- internal-post-id
|
(defn- internal-post-id
|
||||||
|
|
@ -452,7 +459,7 @@
|
||||||
[:path.arc {:d "M50,0 A50,50 180 0,1 100,50"}]]))
|
[:path.arc {:d "M50,0 A50,50 180 0,1 100,50"}]]))
|
||||||
|
|
||||||
(defn posts-section [{:keys [posts]}]
|
(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)]
|
n-displayed (count displayed-posts)]
|
||||||
[:section.posts
|
[:section.posts
|
||||||
[:h2 "Favorites"]
|
[:h2 "Favorites"]
|
||||||
|
|
@ -465,7 +472,7 @@
|
||||||
" match"
|
" match"
|
||||||
" matches")))))]
|
" matches")))))]
|
||||||
[:section.search-form
|
[:section.search-form
|
||||||
[search]
|
[search {:query query}]
|
||||||
[loading-indicator {:loading loading}]
|
[loading-indicator {:loading loading}]
|
||||||
#_(cond (= api-state :loading) " …"
|
#_(cond (= api-state :loading) " …"
|
||||||
(= api-state :error) " API Error!")]
|
(= api-state :error) " API Error!")]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue