Add "load more" button and fix pagination
This commit is contained in:
parent
80d0a6e258
commit
f984e9b14d
2 changed files with 170 additions and 87 deletions
|
|
@ -86,6 +86,11 @@
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button[disabled] {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
section.login label,
|
section.login label,
|
||||||
section.login input {
|
section.login input {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
@ -100,6 +105,11 @@
|
||||||
|
|
||||||
section.posts .controls {
|
section.posts .controls {
|
||||||
padding: 0 0 36px;
|
padding: 0 0 36px;
|
||||||
|
display: grid;
|
||||||
|
grid-template:
|
||||||
|
"a"
|
||||||
|
"b"
|
||||||
|
"c";
|
||||||
}
|
}
|
||||||
|
|
||||||
section.posts .controls .search-form input {
|
section.posts .controls .search-form input {
|
||||||
|
|
@ -137,14 +147,19 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 640px) {
|
section.posts .controls .post-info {
|
||||||
section.posts .controls {
|
grid-area: b;
|
||||||
display: grid;
|
}
|
||||||
grid-template:
|
|
||||||
"a a"
|
|
||||||
"b c";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
section.posts .controls .search-form {
|
||||||
|
grid-area: c;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.posts .controls .buttons {
|
||||||
|
grid-area: a;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 640px) {
|
||||||
section.posts .controls .post-info {
|
section.posts .controls .post-info {
|
||||||
grid-area: a;
|
grid-area: a;
|
||||||
}
|
}
|
||||||
|
|
@ -159,14 +174,12 @@
|
||||||
|
|
||||||
section.posts .controls .buttons {
|
section.posts .controls .buttons {
|
||||||
grid-area: c;
|
grid-area: c;
|
||||||
display: flex;
|
|
||||||
justify-content: end;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section.posts .controls .control-button {
|
section.posts .controls .control-button {
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
margin: 6px 0;
|
margin: 6px 0 6px 12px;
|
||||||
background: #f5e6ab;
|
background: #f5e6ab;
|
||||||
color: #444;
|
color: #444;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -212,14 +225,12 @@
|
||||||
margin: 24px 0 0;
|
margin: 24px 0 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 640px) {
|
@media screen and (min-width: 640px) {
|
||||||
section.posts .post .controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: end
|
|
||||||
}
|
|
||||||
|
|
||||||
section.posts .post .controls .control-element a {
|
section.posts .post .controls .control-element a {
|
||||||
margin: 0 0 0 12px;
|
margin: 0 0 0 12px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.pprint :as pprint]
|
[clojure.pprint :as pprint]
|
||||||
[computersandblues.lodestone.database :as db]
|
[computersandblues.lodestone.database :as db]
|
||||||
[computersandblues.lodestone.match :refer [query->matching-fn]]))
|
[computersandblues.lodestone.match :refer [query->matching-fn]]
|
||||||
|
[applied-science.js-interop :as j]))
|
||||||
|
|
||||||
(def posts-init-state
|
(def posts-init-state
|
||||||
{:query nil
|
{:query nil
|
||||||
|
|
@ -27,13 +28,21 @@
|
||||||
:section/posts posts-init-state}))
|
:section/posts posts-init-state}))
|
||||||
|
|
||||||
; TODO Ensure that cached data is up to date
|
; TODO Ensure that cached data is up to date
|
||||||
; TODO Manually fetch older / newer favorites
|
|
||||||
; TODO Handle 429
|
; TODO Handle 429
|
||||||
; TODO Search for tags (`#foo`) and handles (`@bar`)
|
; TODO Search for tags (`#foo`) and handles (`@bar`)
|
||||||
; TODO Explain which kind of search currently is possible
|
; TODO Explain which kind of search currently is possible
|
||||||
|
|
||||||
;; Mastodon API helpers
|
;; Mastodon API helpers
|
||||||
|
|
||||||
|
(defn- promise-all [xs]
|
||||||
|
(js/Promise.all (clj->js xs)))
|
||||||
|
|
||||||
|
(defn- promise-resolve [val]
|
||||||
|
(js/Promise.resolve val))
|
||||||
|
|
||||||
|
(defn- promise-reject [val]
|
||||||
|
(js/Promise.reject val))
|
||||||
|
|
||||||
(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."
|
||||||
|
|
@ -59,26 +68,27 @@
|
||||||
"Small helper function to send authorized requests to mastodon-compatible APIs"
|
"Small helper function to send authorized requests to mastodon-compatible APIs"
|
||||||
[{:keys [url method bearer-token payload]
|
[{:keys [url method bearer-token payload]
|
||||||
:or {method :get}}]
|
:or {method :get}}]
|
||||||
(js/Promise.
|
(. (js/fetch url
|
||||||
(fn [resolve reject]
|
(clj->js (cond-> {:method (str/upper-case (name method))}
|
||||||
(. (js/fetch url
|
bearer-token (assoc-in [:headers :authorization] (str "Bearer " bearer-token))
|
||||||
(clj->js (cond-> {:method (str/upper-case (name method))}
|
payload (->
|
||||||
bearer-token (assoc-in [:headers :authorization] (str "Bearer " bearer-token))
|
(assoc-in [:headers :content-type] "application/json; charset=utf-8")
|
||||||
payload (->
|
(assoc :body (js/JSON.stringify (clj->js payload)))))))
|
||||||
(assoc-in [:headers :content-type] "application/json; charset=utf-8")
|
(then (fn [res]
|
||||||
(assoc :body (js/JSON.stringify (clj->js payload)))))))
|
(if (.-ok res)
|
||||||
(then (fn [res]
|
(-> (.json res)
|
||||||
(if (.-ok res)
|
(.then
|
||||||
(-> (.json res)
|
(fn [body]
|
||||||
(.then
|
(promise-resolve {:raw res
|
||||||
(fn [body]
|
:body (js->clj body {:keywordize-keys true})}))))
|
||||||
(resolve {:raw res
|
(promise-reject res))))))
|
||||||
:body (js->clj body {:keywordize-keys true})}))))
|
|
||||||
(reject res))))))))
|
|
||||||
|
|
||||||
(defn- search-params [params]
|
(defn- ->search-params [params]
|
||||||
(js/URLSearchParams. (clj->js params)))
|
(js/URLSearchParams. (clj->js params)))
|
||||||
|
|
||||||
|
(defn- url->search-params [url]
|
||||||
|
(.-searchParams (js/URL. url)))
|
||||||
|
|
||||||
;; all of the app's sections (i.e. different views / pieces of functionality)
|
;; all of the app's sections (i.e. different views / pieces of functionality)
|
||||||
|
|
||||||
;; login & application setup
|
;; login & application setup
|
||||||
|
|
@ -99,24 +109,26 @@
|
||||||
(set! (.-location js/window)
|
(set! (.-location js/window)
|
||||||
(str (:instance_url application)
|
(str (:instance_url application)
|
||||||
"/oauth/authorize?"
|
"/oauth/authorize?"
|
||||||
(search-params {:response_type "code"
|
(->search-params {:response_type "code"
|
||||||
:client_id (:client_id application)
|
:client_id (:client_id application)
|
||||||
:redirect_uri (:redirect_uri application) ; TODO handle multiple reidrect uris?
|
:redirect_uri (:redirect_uri application) ; TODO handle multiple reidrect uris?
|
||||||
:scope "read:favourites"}))))
|
:scope "read:favourites"}))))
|
||||||
|
|
||||||
(defn oauth-authorization-code [location]
|
(defn oauth-authorization-code [location]
|
||||||
(.get (js/URLSearchParams. (.-search location)) "code"))
|
(-> (.-search location)
|
||||||
|
(js/URLSearchParams.)
|
||||||
|
(.get "code")))
|
||||||
|
|
||||||
(defn handle-oauth-authorization-code! [{:keys [application code]}]
|
(defn handle-oauth-authorization-code! [{:keys [application code]}]
|
||||||
(->
|
(->
|
||||||
(mastodon-request! {:method :post
|
(mastodon-request! {:method :post
|
||||||
:url (str (:instance_url application)
|
:url (str (:instance_url application)
|
||||||
"/oauth/token?"
|
"/oauth/token?"
|
||||||
(search-params {:grant_type "authorization_code"
|
(->search-params {:grant_type "authorization_code"
|
||||||
:code code
|
:code code
|
||||||
:client_id (:client_id application)
|
:client_id (:client_id application)
|
||||||
:client_secret (:client_secret application)
|
:client_secret (:client_secret application)
|
||||||
:redirect_uri (:redirect_uri application)}))})
|
:redirect_uri (:redirect_uri application)}))})
|
||||||
(.then (fn [res]
|
(.then (fn [res]
|
||||||
(let [bearer-token (-> res :body :access_token)
|
(let [bearer-token (-> res :body :access_token)
|
||||||
application (assoc application :bearer_token bearer-token)]
|
application (assoc application :bearer_token bearer-token)]
|
||||||
|
|
@ -143,6 +155,12 @@
|
||||||
(declare fetch-posts!)
|
(declare fetch-posts!)
|
||||||
(declare refresh-displayed-posts!)
|
(declare refresh-displayed-posts!)
|
||||||
|
|
||||||
|
(defn- fetch-application-settings []
|
||||||
|
(-> (db/open-cursor! ::db/application db/all)
|
||||||
|
(db/transduce-cursor (comp (take 1)
|
||||||
|
(map #(js->clj % :keywordize-keys true)))
|
||||||
|
(fn [_ x] x))))
|
||||||
|
|
||||||
(defn setup-application!
|
(defn setup-application!
|
||||||
"Handles Mastodon application setup on the client side"
|
"Handles Mastodon application setup on the client side"
|
||||||
[]
|
[]
|
||||||
|
|
@ -156,14 +174,11 @@
|
||||||
; the last case is not handled in this function, but is handled by the
|
; the last case is not handled in this function, but is handled by the
|
||||||
; `create-remote-application!` function that is called once the user submits
|
; `create-remote-application!` function that is called once the user submits
|
||||||
; the form with their instance URL.
|
; the form with their instance URL.
|
||||||
(-> (db/open-cursor! ::db/application db/all)
|
(-> (fetch-application-settings)
|
||||||
(db/transduce-cursor (comp (take 1)
|
(.then (fn [application]
|
||||||
(map #(js->clj % :keywordize-keys true))))
|
|
||||||
(.then (fn [[application]]
|
|
||||||
(let [code (oauth-authorization-code (.-location js/window))]
|
(let [code (oauth-authorization-code (.-location js/window))]
|
||||||
(cond
|
(cond
|
||||||
(:bearer_token application)
|
(:bearer_token application) (js/Promise.resolve application)
|
||||||
(js/Promise.resolve application)
|
|
||||||
|
|
||||||
(and application code)
|
(and application code)
|
||||||
(handle-oauth-authorization-code!
|
(handle-oauth-authorization-code!
|
||||||
|
|
@ -181,16 +196,12 @@
|
||||||
(.then (fn [application]
|
(.then (fn [application]
|
||||||
(when application
|
(when application
|
||||||
(swap! state assoc :section :posts)
|
(swap! state assoc :section :posts)
|
||||||
(js/Promise.all #js [application (db/count! ::db/posts)]))))
|
(promise-all [application (db/count! ::db/posts)]))))
|
||||||
(.then (fn [[application post-count]]
|
(.then (fn [[application post-count]]
|
||||||
(when post-count
|
(when post-count
|
||||||
(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)})
|
||||||
:continue?
|
|
||||||
(fn [response]
|
|
||||||
(and (seq (:body response))
|
|
||||||
(< (count (:favorites @state)) 500)))})
|
|
||||||
(refresh-displayed-posts! (:section/posts @state))))))))
|
(refresh-displayed-posts! (:section/posts @state))))))))
|
||||||
|
|
||||||
;;; views
|
;;; views
|
||||||
|
|
@ -223,34 +234,36 @@
|
||||||
|
|
||||||
;;; api interaction
|
;;; api interaction
|
||||||
|
|
||||||
(defn- favorites-url [{:keys [instance-url limit max-id]
|
(defn- favorites-url [{:keys [instance-url limit max-id min-id]
|
||||||
:or {limit 40}}]
|
:or {limit 40}}]
|
||||||
(let [params (search-params (cond-> {:limit limit}
|
(let [params (->search-params (cond-> {:limit limit}
|
||||||
max-id (assoc :max_id max-id)))]
|
max-id (assoc :max_id max-id)
|
||||||
|
min-id (assoc :min_id min-id)))]
|
||||||
(str instance-url "/api/v1/favourites?" params)))
|
(str instance-url "/api/v1/favourites?" params)))
|
||||||
|
|
||||||
(defn fetch-favorites!
|
(defn paginate-posts!
|
||||||
[{:keys [instance-url bearer-token max-id
|
[{:keys [instance-url bearer-token
|
||||||
|
max-id min-id
|
||||||
on-response on-error continue?]
|
on-response on-error continue?]
|
||||||
:or {continue? (fn [response]
|
:or {continue? (fn [response]
|
||||||
(seq (:body response)))
|
(seq (:body response)))
|
||||||
on-response on-response
|
on-response on-response
|
||||||
on-error on-error}}]
|
on-error on-error}}]
|
||||||
(js/console.log 'fetch-favorites! instance-url max-id bearer-token)
|
((fn paginate! [url]
|
||||||
((fn fetch-favorites' [url]
|
|
||||||
(let [req-id (js/Date.now)]
|
(let [req-id (js/Date.now)]
|
||||||
(println :calling url)
|
(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})
|
||||||
(.then (fn [response]
|
(.then (fn [response]
|
||||||
(on-response response)
|
(let [next-url (link-header "next" (:raw response))]
|
||||||
(if (continue? response)
|
(swap! state update-in [:section/posts :loading] disj req-id)
|
||||||
(js/setTimeout #(fetch-favorites' (link-header "next" (:raw response))) 500)
|
(on-response response)
|
||||||
(swap! state update-in [:section/posts :loading] disj req-id))))
|
(when (and (continue? response) next-url)
|
||||||
|
(js/setTimeout #(paginate! next-url) 500)))))
|
||||||
(.catch (fn [response]
|
(.catch (fn [response]
|
||||||
(swap! state update-in [:section/posts :loading] disj req-id)
|
(swap! state update-in [:section/posts :loading] disj req-id)
|
||||||
(on-error response))))))
|
(on-error response))))))
|
||||||
(favorites-url {:instance-url instance-url :max-id max-id})))
|
(favorites-url {:instance-url instance-url :max-id max-id :min-id min-id})))
|
||||||
|
|
||||||
;;; views
|
;;; views
|
||||||
|
|
||||||
|
|
@ -341,6 +354,14 @@
|
||||||
(js/navigator.clipboard.writeText (:url post)))} "◎ Copy URL to clipboard"]]]]
|
(js/navigator.clipboard.writeText (:url post)))} "◎ Copy URL to clipboard"]]]]
|
||||||
#_[debug post]])
|
#_[debug post]])
|
||||||
|
|
||||||
|
#_(defn logging [f]
|
||||||
|
(let [n (volatile! 0)]
|
||||||
|
(fn [& args]
|
||||||
|
(when (< @n 10)
|
||||||
|
(vswap! n inc)
|
||||||
|
(js/console.log :logging args)
|
||||||
|
(apply f args)))))
|
||||||
|
|
||||||
(defn- refresh-displayed-posts!
|
(defn- refresh-displayed-posts!
|
||||||
[posts-section]
|
[posts-section]
|
||||||
(let [{:keys [per-page query]} posts-section
|
(let [{:keys [per-page query]} posts-section
|
||||||
|
|
@ -352,42 +373,90 @@
|
||||||
(map #(js->clj % :keywordize-keys true)))
|
(map #(js->clj % :keywordize-keys true)))
|
||||||
refresh-id (js/Date.now)]
|
refresh-id (js/Date.now)]
|
||||||
(swap! state update-in [:section/posts :loading] conj refresh-id)
|
(swap! state update-in [:section/posts :loading] conj refresh-id)
|
||||||
(-> (js/Promise.all #js [(db/count! ::db/posts)
|
(. (promise-all [(db/count! ::db/posts)
|
||||||
(-> (db/open-cursor! ::db/posts ::db/post-created-at db/all "prev")
|
(-> (db/open-cursor! ::db/posts ::db/post-created-at db/all "prev")
|
||||||
(db/transduce-cursor xform))])
|
(db/transduce-cursor xform))])
|
||||||
(.then (fn [[total displayed-posts]]
|
(then (fn [[total displayed-posts]]
|
||||||
(swap! state update :section/posts #(-> (assoc % :total total)
|
(swap! state update :section/posts #(-> (assoc % :total total)
|
||||||
(assoc :displayed-posts displayed-posts)
|
(assoc :displayed-posts displayed-posts)
|
||||||
(update :loading disj refresh-id))))))))
|
(update :loading disj refresh-id))))))))
|
||||||
|
|
||||||
(def debounced-refresh! (debounce 20 (partial refresh-displayed-posts!)))
|
(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]
|
||||||
(doseq [post (:body response)]
|
(let [url-params (url->search-params (.-url (:raw response)))]
|
||||||
(db/put! ::db/posts post))
|
(js/console.log :on-response
|
||||||
|
:max_id (.get url-params "max_id")
|
||||||
|
:min_id (.get url-params "min_id"))
|
||||||
|
(doseq [post (:body response)]
|
||||||
|
(db/put! ::db/posts (cond-> post
|
||||||
|
; these IDs are internal server ids and it looks like
|
||||||
|
; they are not returned in any response; they are
|
||||||
|
; required for pagination, so we're storing them to be
|
||||||
|
; able to abort and continue pagination if we want or
|
||||||
|
; if outer circumstances decide so (for example if the
|
||||||
|
; tab is closed)
|
||||||
|
(.get url-params "max_id")
|
||||||
|
(assoc :internal_id (.get url-params "max_id"))
|
||||||
|
|
||||||
|
(.get url-params "min_id")
|
||||||
|
(assoc :internal_id (.get url-params "min_id"))))))
|
||||||
(debounced-refresh! (:section/posts @state)))}]
|
(debounced-refresh! (:section/posts @state)))}]
|
||||||
(fetch-favorites! (merge defaults opts))))
|
(paginate-posts! (merge defaults opts))))
|
||||||
|
|
||||||
|
(defn- internal-post-id [max-or-min]
|
||||||
|
(-> (db/open-cursor! ::db/posts ::db/post-created-at db/all (if (= max-or-min :min)
|
||||||
|
"next"
|
||||||
|
"prev"))
|
||||||
|
(db/transduce-cursor (comp (keep (j/get :internal_id))
|
||||||
|
(take 1))
|
||||||
|
(fn [_ x] x))))
|
||||||
|
|
||||||
|
(defn fetch-more-posts! [e]
|
||||||
|
(.preventDefault e)
|
||||||
|
(. (promise-all [(fetch-application-settings) (internal-post-id :min) (internal-post-id :max)])
|
||||||
|
(then (fn [[application min-id max-id]]
|
||||||
|
(when min-id
|
||||||
|
(fetch-posts! {:instance-url (:instance_url application)
|
||||||
|
:bearer-token (:bearer_token application)
|
||||||
|
:min-id min-id}))
|
||||||
|
(when max-id
|
||||||
|
(fetch-posts! {:instance-url (:instance_url application)
|
||||||
|
:bearer-token (:bearer_token application)
|
||||||
|
:max-id max-id}))
|
||||||
|
(when-not (or min-id max-id)
|
||||||
|
(fetch-posts! {:instance-url (:instance_url application)
|
||||||
|
:bearer-token (:bearer_token application)}))))))
|
||||||
|
|
||||||
(defn- disconnect-account! [e]
|
(defn- disconnect-account! [e]
|
||||||
(.preventDefault e)
|
(.preventDefault e)
|
||||||
(when (js/confirm "Are you sure? This will log you out and clear your local cache.")
|
(when (js/confirm "Are you sure? This will log you out and clear your local cache.")
|
||||||
(. (js/Promise.all #js [(db/clear! ::db/posts)
|
(. (promise-all [(db/clear! ::db/posts) (db/clear! ::db/application)])
|
||||||
(db/clear! ::db/application)])
|
|
||||||
(then (fn [_]
|
(then (fn [_]
|
||||||
(swap! state #(-> (assoc % :section :login)
|
(swap! state #(-> (assoc % :section :login)
|
||||||
(assoc :section/posts posts-init-state))))))))
|
(assoc :section/posts posts-init-state))))))))
|
||||||
|
|
||||||
|
(defn loading-indicator [{:keys [loading]}]
|
||||||
|
(when (seq loading)
|
||||||
|
; see https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/d#elliptical_arc_curve
|
||||||
|
[:svg.loading-indicator {:viewBox "-10 -10 120 120" :xmlns "http://www.w3.org/2000/svg"}
|
||||||
|
[: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]} posts
|
||||||
|
n-displayed (count displayed-posts)]
|
||||||
[:section.posts
|
[:section.posts
|
||||||
[:h2 "Favorites"]
|
[:h2 "Favorites"]
|
||||||
[:header.controls
|
[:header.controls
|
||||||
[:p.display-info
|
[:p.display-info
|
||||||
(str "Loaded " total " posts"
|
(str "Loaded " total " posts"
|
||||||
(when (or query (> total per-page))
|
(when (or query (> total per-page))
|
||||||
(str ", displaying " (count displayed-posts) (when query " matches"))))]
|
(str ", displaying " n-displayed (when query
|
||||||
|
(if (= 1 n-displayed)
|
||||||
|
" match"
|
||||||
|
" matches")))))]
|
||||||
[:section.search-form
|
[:section.search-form
|
||||||
[search]
|
[search]
|
||||||
[loading-indicator (select-keys posts [:loading])]
|
[loading-indicator (select-keys posts [:loading])]
|
||||||
|
|
@ -443,7 +512,7 @@
|
||||||
|
|
||||||
;; database
|
;; database
|
||||||
|
|
||||||
(def db-version 2)
|
(def db-version 3)
|
||||||
|
|
||||||
(def migrations
|
(def migrations
|
||||||
{1 (fn migration-0001 [db _]
|
{1 (fn migration-0001 [db _]
|
||||||
|
|
@ -451,7 +520,10 @@
|
||||||
(db/create-object-store! db ::db/posts {:keyPath "id"}))
|
(db/create-object-store! db ::db/posts {:keyPath "id"}))
|
||||||
2 (fn migration-0002 [_ txn]
|
2 (fn migration-0002 [_ txn]
|
||||||
(-> (db/open-store txn ::db/posts "readwrite")
|
(-> (db/open-store txn ::db/posts "readwrite")
|
||||||
(db/create-index! ::db/post-created-at "created_at" {:unique false})))})
|
(db/create-index! ::db/post-created-at "created_at" {:unique false})))
|
||||||
|
3 (fn migration-0003 [_ txn]
|
||||||
|
(-> (db/open-store txn ::db/posts "readwrite")
|
||||||
|
(db/create-index! ::db/post-internal-id "internal_id" {:unique false})))})
|
||||||
|
|
||||||
;; go go go
|
;; go go go
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue