diff --git a/deps/common/src/logseq/common/config.cljs b/deps/common/src/logseq/common/config.cljs index 2afc6cd8f..36155782f 100644 --- a/deps/common/src/logseq/common/config.cljs +++ b/deps/common/src/logseq/common/config.cljs @@ -97,6 +97,13 @@ (:date-formatter config) "MMM do, yyyy")) +(defn get-preferred-format + [config] + (or + (when-let [fmt (:preferred-format config)] + (keyword (string/lower-case (name fmt)))) + :markdown)) + (defn get-block-pattern [format] (let [format' (keyword format)] diff --git a/deps/common/src/logseq/common/util/page_ref.cljs b/deps/common/src/logseq/common/util/page_ref.cljs index 127edd93a..f3b43302a 100644 --- a/deps/common/src/logseq/common/util/page_ref.cljs +++ b/deps/common/src/logseq/common/util/page_ref.cljs @@ -1,7 +1,8 @@ (ns logseq.common.util.page-ref "Core vars and util fns for page-ref. Currently this only handles a logseq page-ref e.g. [[page name]]" - (:require [clojure.string :as string])) + (:require [clojure.string :as string] + ["path" :as path])) (def left-brackets "Opening characters for page-ref" "[[") (def right-brackets "Closing characters for page-ref" "]]") @@ -12,6 +13,20 @@ (def page-ref-re "Inner capture and doesn't match nested brackets" #"\[\[(.*?)\]\]") (def page-ref-without-nested-re "Matches most inner nested brackets" #"\[\[([^\[\]]+)\]\]") (def page-ref-any-re "Inner capture that matches anything between brackets" #"\[\[(.*)\]\]") +(def org-page-ref-re #"\[\[(file:.*)\]\[.+?\]\]") +(def markdown-page-ref-re #"\[(.*)\]\(file:.*\)") + +(defn get-file-basename + "Returns the basename of a file path. e.g. /a/b/c.md -> c.md" + [path] + (when-not (string/blank? path) + (.-base (path/parse (string/replace path "+" "/"))))) + +(defn get-file-rootname + "Returns the rootname of a file path. e.g. /a/b/c.md -> c" + [path] + (when-not (string/blank? path) + (.-name (path/parse (string/replace path "+" "/"))))) (defn page-ref? "Determines if string is page-ref. Avoid using with format-specific page-refs e.g. org" @@ -25,12 +40,25 @@ (str left-brackets page-name right-brackets)) (defn get-page-name - "Extracts page-name from page-ref string" + "Extracts page names from format-specific page-refs e.g. org/md specific and + logseq page-refs. Only call in contexts where format-specific page-refs are + used. For logseq page-refs use page-ref/get-page-name" [s] - (second (re-matches page-ref-any-re s))) + (and (string? s) + (or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)] + (string/trim label)) + (when-let [[_ path _label] (re-matches org-page-ref-re s)] + (some-> (get-file-rootname path) + (string/replace "." "/"))) + (-> (re-matches page-ref-re s) + second)))) (defn get-page-name! "Extracts page-name from page-ref and fall back to arg. Useful for when user input may (not) be a page-ref" [s] (or (get-page-name s) s)) + +(defn page-ref-un-brackets! + [s] + (or (get-page-name s) s)) diff --git a/deps/graph-parser/src/logseq/graph_parser/property.cljs b/deps/graph-parser/src/logseq/graph_parser/property.cljs index 9c579a5ea..868fd47be 100644 --- a/deps/graph-parser/src/logseq/graph_parser/property.cljs +++ b/deps/graph-parser/src/logseq/graph_parser/property.cljs @@ -5,7 +5,8 @@ [clojure.set :as set] [goog.string :as gstring] [goog.string.format] - [logseq.graph-parser.mldoc :as gp-mldoc])) + [logseq.graph-parser.mldoc :as gp-mldoc] + [logseq.common.util.page-ref :as page-ref])) (def colons "Property delimiter for markdown mode" "::") (defn colons-org @@ -330,3 +331,21 @@ :else content)) + +(defn insert-properties + [repo format content kvs] + (reduce + (fn [content [k v]] + (let [k (if (string? k) + (keyword (-> (string/lower-case k) + (string/replace " " "-"))) + k) + v (if (coll? v) + (some->> + (seq v) + (distinct) + (map (fn [item] (page-ref/->page-ref (page-ref/page-ref-un-brackets! item)))) + (string/join ", ")) + v)] + (insert-property repo format content k v))) + content kvs)) diff --git a/deps/graph-parser/src/logseq/graph_parser/text.cljs b/deps/graph-parser/src/logseq/graph_parser/text.cljs index 3e8a22b78..702eb22c9 100644 --- a/deps/graph-parser/src/logseq/graph_parser/text.cljs +++ b/deps/graph-parser/src/logseq/graph_parser/text.cljs @@ -1,7 +1,6 @@ (ns logseq.graph-parser.text "Miscellaneous text util fns for the parser" - (:require ["path" :as path] - [goog.string :as gstring] + (:require [goog.string :as gstring] [clojure.string :as string] [clojure.set :as set] [logseq.graph-parser.property :as gp-property] @@ -9,39 +8,11 @@ [logseq.common.util :as common-util] [logseq.common.util.page-ref :as page-ref])) -(defn get-file-basename - "Returns the basename of a file path. e.g. /a/b/c.md -> c.md" - [path] - (when-not (string/blank? path) - (.-base (path/parse (string/replace path "+" "/"))))) +(def get-file-basename page-ref/get-file-basename) -(defn get-file-rootname - "Returns the rootname of a file path. e.g. /a/b/c.md -> c" - [path] - (when-not (string/blank? path) - (.-name (path/parse (string/replace path "+" "/"))))) +(def get-page-name page-ref/get-page-name) -(def page-ref-re-0 #"\[\[(.*)\]\]") -(def org-page-ref-re #"\[\[(file:.*)\]\[.+?\]\]") -(def markdown-page-ref-re #"\[(.*)\]\(file:.*\)") - -(defn get-page-name - "Extracts page names from format-specific page-refs e.g. org/md specific and - logseq page-refs. Only call in contexts where format-specific page-refs are - used. For logseq page-refs use page-ref/get-page-name" - [s] - (and (string? s) - (or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)] - (string/trim label)) - (when-let [[_ path _label] (re-matches org-page-ref-re s)] - (some-> (get-file-rootname path) - (string/replace "." "/"))) - (-> (re-matches page-ref-re-0 s) - second)))) - -(defn page-ref-un-brackets! - [s] - (or (get-page-name s) s)) +(def page-ref-un-brackets! page-ref/page-ref-un-brackets!) (defn get-nested-page-name [page-name] diff --git a/src/main/frontend/db/rtc/core.cljs b/src/main/frontend/db/rtc/core.cljs index 575f89396..4be05b0e6 100644 --- a/src/main/frontend/db/rtc/core.cljs +++ b/src/main/frontend/db/rtc/core.cljs @@ -17,12 +17,15 @@ [logseq.db.frontend.property :as db-property] [datascript.core :as d] [logseq.graph-parser.whiteboard :as gp-whiteboard] + [frontend.worker.handler.page :as worker-page] + [frontend.worker.state :as worker-state] [frontend.db.rtc.const :as rtc-const] [frontend.db.rtc.op-mem-layer :as op-mem-layer] [frontend.db.rtc.ws :as ws] [frontend.handler.page :as page-handler] + [frontend.handler.user :as user])) @@ -323,31 +326,32 @@ (defn apply-remote-update-page-ops [repo conn date-formatter update-page-ops] - (doseq [{:keys [self page-name original-name] :as op-value} update-page-ops] - (let [old-page-original-name (:block/original-name (d/entity @conn [:block/uuid self])) - exist-page (d/entity @conn [:block/name page-name])] - (cond + (let [config (worker-state/get-config repo)] + (doseq [{:keys [self page-name original-name] :as op-value} update-page-ops] + (let [old-page-original-name (:block/original-name (d/entity @conn [:block/uuid self])) + exist-page (d/entity @conn [:block/name page-name]) + create-opts {:create-first-block? false + :uuid self :persist-op? false}] + (cond ;; same name but different uuid ;; remote page has same block/name as local's, but they don't have same block/uuid. ;; 1. rename local page's name to '--Conflict' ;; 2. create page, name=, uuid=remote-uuid - (and exist-page (not= (:block/uuid exist-page) self)) - (do (page-handler/rename! original-name (common-util/format "%s-%s-CONFLICT" original-name (tc/to-long (t/now)))) - (page-handler/create! original-name {:redirect? false :create-first-block? false - :uuid self :persist-op? false})) + (and exist-page (not= (:block/uuid exist-page) self)) + (do (page-handler/rename! original-name (common-util/format "%s-%s-CONFLICT" original-name (tc/to-long (t/now)))) + (worker-page/create! repo conn config original-name create-opts)) ;; a client-page has same uuid as remote but different page-names, ;; then we need to rename the client-page to remote-page-name - (and old-page-original-name (not= old-page-original-name original-name)) - (page-handler/rename! old-page-original-name original-name false false) + (and old-page-original-name (not= old-page-original-name original-name)) + (page-handler/rename! old-page-original-name original-name false false) ;; no such page, name=remote-page-name, OR, uuid=remote-block-uuid ;; just create-page - :else - (page-handler/create! original-name {:redirect? false :create-first-block? false - :uuid self :persist-op? false})) + :else + (worker-page/create! repo conn config original-name create-opts)) - (update-block-attrs repo conn date-formatter self op-value)))) + (update-block-attrs repo conn date-formatter self op-value))))) (defn apply-remote-remove-page-ops [conn remove-page-ops] @@ -832,6 +836,8 @@ {:*rtc-state (atom :closed :validator rtc-state-validator) :*graph-uuid (atom nil) :*repo (atom nil) + :*db-conn (atom nil) + :*date-formatter (atom nil) :data-from-ws-chan data-from-ws-chan :data-from-ws-pub (async/pub data-from-ws-chan :req-id) :toggle-auto-push-client-ops-chan (chan (async/sliding-buffer 1)) diff --git a/src/main/frontend/handler/common/page.cljs b/src/main/frontend/handler/common/page.cljs index f7a861fe9..f6905f3c2 100644 --- a/src/main/frontend/handler/common/page.cljs +++ b/src/main/frontend/handler/common/page.cljs @@ -9,178 +9,44 @@ [frontend.db.utils :as db-utils] [frontend.format.block :as block] [frontend.fs :as fs] - [frontend.handler.common :as common-handler] [frontend.handler.config :as config-handler] [frontend.handler.editor :as editor-handler] [frontend.handler.file-based.editor :as file-editor-handler] - [frontend.handler.file-based.page-property :as file-page-property] [frontend.handler.route :as route-handler] [frontend.handler.ui :as ui-handler] - [frontend.util.fs :as fs-util] [frontend.util :as util] [logseq.db.frontend.schema :as db-schema] - [logseq.graph-parser.block :as gp-block] [logseq.common.util :as common-util] [logseq.graph-parser.text :as text] [lambdaisland.glogi :as log] [medley.core :as medley] [promesa.core :as p] - [clojure.string :as string])) + [clojure.string :as string] + [frontend.worker.handler.page :as worker-page])) -;; create! and its helpers -;; ======================= -(defn- build-title [page] - ;; Don't wrap `\"` anymore, as title property is not effected by `,` now - ;; The previous extract behavior isn't unwrapping the `'"` either. So no need - ;; to maintain the compatibility. - (:block/original-name page)) - -(defn- default-properties-block - ([title format page] - (default-properties-block title format page {})) - ([title format page properties] - (let [repo (state/get-current-repo) - db-based? (config/db-based-graph? repo)] - (when-not db-based? - (let [p (common-handler/get-page-default-properties title) - ps (merge p properties) - content (if db-based? - "" - (file-page-property/insert-properties format "" ps)) - refs (gp-block/get-page-refs-from-properties properties - (db/get-db repo) - (state/get-date-formatter) - (state/get-config))] - {:block/uuid (db/new-block-id) - :block/refs refs - :block/left page - :block/format format - :block/content content - :block/parent page - :block/page page - :block/pre-block? true - :block/properties ps - :block/properties-order (keys ps)}))))) - -(defn- create-title-property? - [repo journal? page-name] - (and (not (config/db-based-graph? repo)) - (not journal?) - (= (state/get-filename-format) :legacy) ;; reduce title computation - (fs-util/create-title-property? page-name))) - -(defn- build-page-tx [repo format properties page journal? {:keys [whiteboard? class? tags]}] - (when (:block/uuid page) - (let [page-entity [:block/uuid (:block/uuid page)] - title (util/get-page-original-name page) - create-title? (create-title-property? repo journal? title) - page (merge page - (when (seq properties) {:block/properties properties}) - (when whiteboard? {:block/type "whiteboard"}) - (when class? {:block/type "class"}) - (when tags {:block/tags (mapv #(hash-map :db/id - (:db/id (db/entity repo [:block/uuid %]))) - tags)})) - page-empty? (db/page-empty? (state/get-current-repo) (:block/name page)) - db-based? (config/db-based-graph? (state/get-current-repo))] - (cond - (not page-empty?) - [page] - - (and create-title? - (not whiteboard?) - (not db-based?)) - (let [properties-block (default-properties-block (build-title page) format page-entity properties)] - [page - properties-block]) - - (and (seq properties) - (not whiteboard?) - (not db-based?)) - [page (file-editor-handler/properties-block repo properties format page-entity)] - - :else - [page])))) - -;; TODO: Move file graph concerns to file-based-handler ns (defn create! "Create page. Has the following options: * :redirect? - when true, redirect to the created page, otherwise return sanitized page name. - * :split-namespace? - when true, split hierarchical namespace into levels. * :create-first-block? - when true, create an empty block if the page is empty. * :uuid - when set, use this uuid instead of generating a new one. * :class? - when true, adds a :block/type 'class' * :whiteboard? - when true, adds a :block/type 'whiteboard' * :tags - tag uuids that are added to :block/tags * :persist-op? - when true, add an update-page op - TODO: Add other options" + " ([title] (create! title {})) - ([title {:keys [redirect? create-first-block? format properties split-namespace? journal? uuid rename? persist-op? whiteboard? class?] - :or {redirect? true - create-first-block? true - rename? false - format nil - properties nil - split-namespace? true - uuid nil - persist-op? true} + ([title {:keys [redirect?] + :or {redirect? true} :as options}] - (let [title (-> (string/trim title) - (text/page-ref-un-brackets!) - ;; remove `#` from tags - (string/replace #"^#+" "")) - title (common-util/remove-boundary-slashes title) - page-name (util/page-name-sanity-lc title) - repo (state/get-current-repo) - with-uuid? (if (uuid? uuid) uuid true)] ;; FIXME: prettier validation - (when (or (db/page-empty? repo page-name) rename?) - (let [pages (if split-namespace? - (common-util/split-namespace-pages title) - [title]) - format (or format (state/get-preferred-format)) - pages (map (fn [page] - ;; only apply uuid to the deepest hierarchy of page to create if provided. - (-> (block/page-name->map page (if (= page title) with-uuid? true)) - (assoc :block/format format))) - pages) - txs (->> pages - ;; for namespace pages, only last page need properties - drop-last - (mapcat #(build-page-tx repo format nil % journal? {})) - (remove nil?)) - txs (map-indexed (fn [i page] - (if (zero? i) - page - (assoc page :block/namespace - [:block/uuid (:block/uuid (nth txs (dec i)))]))) - txs) - last-txs (build-page-tx repo format properties (last pages) journal? (select-keys options [:whiteboard? :class? :tags])) - last-txs (if (seq txs) - (update last-txs 0 - (fn [p] - (assoc p :block/namespace [:block/uuid (:block/uuid (last txs))]))) - last-txs) - txs (concat - (when (and rename? uuid) - (when-let [e (db/entity [:block/uuid uuid])] - [[:db/retract (:db/id e) :block/namespace] - [:db/retract (:db/id e) :block/refs]])) - txs - last-txs)] - (when (seq txs) - (db/transact! repo txs {:persist-op? persist-op?}))) - - (when (and create-first-block? (not (or whiteboard? class?))) - (when (or - (db/page-empty? repo (:db/id (db/entity [:block/name page-name]))) - (create-title-property? repo journal? page-name)) - (editor-handler/api-insert-new-block! "" {:page page-name})))) - - (when redirect? - (route-handler/redirect-to-page! page-name)) - page-name))) + (let [repo (state/get-current-repo) + conn (db/get-db repo false) + config (state/get-config repo)] + (when-let [page-name (worker-page/create! repo conn config title options)] + (when redirect? + (route-handler/redirect-to-page! page-name)) + page-name)))) ;; favorite fns ;; ============ @@ -293,7 +159,7 @@ (defn delete! "Deletes a page and then either calls the ok-handler or the error-handler if unable to delete" [page-name ok-handler & {:keys [delete-file? redirect-to-home? persist-op? error-handler] - :or {delete-file? true + :or {delete-file? true redirect-to-home? true persist-op? true error-handler (fn [{:keys [msg]}] (log/error :msg msg))}}] @@ -332,9 +198,8 @@ (unfavorite-page! page-name) - (when redirect-to-home? (route-handler/redirect-to-home!)) - (when (fn? ok-handler) (ok-handler)) + (when redirect-to-home? (route-handler/redirect-to-home!)) (ui-handler/re-render-root!))))))) diff --git a/src/main/frontend/handler/file_based/property/util.cljs b/src/main/frontend/handler/file_based/property/util.cljs index 744e9b096..a1700c21a 100644 --- a/src/main/frontend/handler/file_based/property/util.cljs +++ b/src/main/frontend/handler/file_based/property/util.cljs @@ -5,9 +5,7 @@ [clojure.set :as set] [frontend.config :as config] [logseq.graph-parser.property :as gp-property :refer [properties-start properties-end]] - [logseq.common.util.page-ref :as page-ref] [frontend.format.mldoc :as mldoc] - [logseq.graph-parser.text :as text] [frontend.db :as db] [frontend.state :as state] [frontend.util.cursor :as cursor])) @@ -165,21 +163,8 @@ (defn insert-properties [format content kvs] - (reduce - (fn [content [k v]] - (let [k (if (string? k) - (keyword (-> (string/lower-case k) - (string/replace " " "-"))) - k) - v (if (coll? v) - (some->> - (seq v) - (distinct) - (map (fn [item] (page-ref/->page-ref (text/page-ref-un-brackets! item)))) - (string/join ", ")) - v)] - (insert-property format content k v))) - content kvs)) + (let [repo (state/get-current-repo)] + (gp-property/insert-properties repo format content kvs))) (def remove-property gp-property/remove-property) diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index 5fb967399..be44fecd1 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -454,10 +454,8 @@ should be done through this fn in order to get global config and config defaults ([repo-url] (keyword (or - (when-let [fmt (:preferred-format (get-config repo-url))] - (string/lower-case (name fmt))) - - (get-in @state [:me :preferred_format] "markdown"))))) + (common-config/get-preferred-format (get-config repo-url)) + (get-in @state [:me :preferred_format] "markdown"))))) (defn markdown? [] diff --git a/src/main/frontend/worker/handler/page.cljs b/src/main/frontend/worker/handler/page.cljs new file mode 100644 index 000000000..5f89e77d6 --- /dev/null +++ b/src/main/frontend/worker/handler/page.cljs @@ -0,0 +1,126 @@ +(ns frontend.worker.handler.page + (:require [logseq.db :as ldb] + [logseq.graph-parser.block :as gp-block] + [logseq.graph-parser.property :as gp-property] + [logseq.db.sqlite.util :as sqlite-util] + [datascript.core :as d] + [clojure.string :as string] + [frontend.worker.date :as date] + [logseq.graph-parser.text :as text] + [logseq.common.util :as common-util] + [logseq.common.config :as common-config])) + +(defn properties-block + [repo conn config date-formatter properties format page] + (let [content (gp-property/insert-properties repo format "" properties) + refs (gp-block/get-page-refs-from-properties properties @conn date-formatter config)] + {:block/pre-block? true + :block/uuid (ldb/new-block-id) + :block/properties properties + :block/properties-order (keys properties) + :block/refs refs + :block/left page + :block/format format + :block/content content + :block/parent page + :block/page page})) + +(defn- build-page-tx [repo conn config date-formatter format properties page {:keys [whiteboard? class? tags]}] + (when (:block/uuid page) + (let [page-entity [:block/uuid (:block/uuid page)] + page (merge page + (when (seq properties) {:block/properties properties}) + (when whiteboard? {:block/type "whiteboard"}) + (when class? {:block/type "class"}) + (when tags {:block/tags (mapv #(hash-map :db/id + (:db/id (d/entity @conn [:block/uuid %]))) + tags)})) + page-empty? (ldb/page-empty? @conn (:block/name page)) + db-based? (sqlite-util/db-based-graph? repo)] + (if (and (seq properties) + (not whiteboard?) + (not db-based?) + page-empty?) + [page (properties-block repo conn config date-formatter properties format page-entity)] + [page])))) + +(defn create! + "Create page. Has the following options: + + * :create-first-block? - when true, create an empty block if the page is empty. + * :uuid - when set, use this uuid instead of generating a new one. + * :class? - when true, adds a :block/type 'class' + * :whiteboard? - when true, adds a :block/type 'whiteboard' + * :tags - tag uuids that are added to :block/tags + * :persist-op? - when true, add an update-page op + TODO: Add other options" + [repo conn config title + & {:keys [create-first-block? format properties uuid rename? persist-op? whiteboard? class?] + :or {create-first-block? true + rename? false + format nil + properties nil + uuid nil + persist-op? true} + :as options}] + (let [date-formatter (common-config/get-date-formatter config) + split-namespace? (not (or (string/starts-with? title "hls__") + (date/valid-journal-title? date-formatter title))) + title (-> (string/trim title) + (text/page-ref-un-brackets!) + ;; remove `#` from tags + (string/replace #"^#+" "")) + title (common-util/remove-boundary-slashes title) + page-name (common-util/page-name-sanity-lc title) + with-uuid? (if (uuid? uuid) uuid true)] ;; FIXME: prettier validation + (when (or (ldb/page-empty? @conn page-name) rename?) + (let [pages (if split-namespace? + (common-util/split-namespace-pages title) + [title]) + format (or format (common-config/get-preferred-format config)) + pages (map (fn [page] + ;; only apply uuid to the deepest hierarchy of page to create if provided. + (-> (gp-block/page-name->map page (if (= page title) with-uuid? true) @conn true date-formatter) + (assoc :block/format format))) + pages) + txs (->> pages + ;; for namespace pages, only last page need properties + drop-last + (mapcat #(build-page-tx repo conn config date-formatter format nil % {})) + (remove nil?)) + txs (map-indexed (fn [i page] + (if (zero? i) + page + (assoc page :block/namespace + [:block/uuid (:block/uuid (nth txs (dec i)))]))) + txs) + last-txs (build-page-tx repo conn config date-formatter format properties (last pages) (select-keys options [:whiteboard? :class? :tags])) + last-txs (if (seq txs) + (update last-txs 0 + (fn [p] + (assoc p :block/namespace [:block/uuid (:block/uuid (last txs))]))) + last-txs) + first-block-tx (when (and + create-first-block? + (not (or whiteboard? class?)) + (ldb/page-empty? @conn (:db/id (d/entity @conn [:block/name page-name]))) + (seq txs)) + (let [page-id [:block/uuid (:block/uuid (last txs))]] + [(sqlite-util/block-with-timestamps + {:block/uuid (ldb/new-block-id) + :block/page page-id + :block/parent page-id + :block/left page-id + :block/content "" + :block/format format})])) + txs (concat + (when (and rename? uuid) + (when-let [e (d/entity @conn [:block/uuid uuid])] + [[:db/retract (:db/id e) :block/namespace] + [:db/retract (:db/id e) :block/refs]])) + txs + last-txs + first-block-tx)] + (when (seq txs) + (d/transact! conn txs {:persist-op? persist-op?}) + page-name))))) diff --git a/src/main/frontend/worker/state.cljs b/src/main/frontend/worker/state.cljs index 9ef8f4cb2..f0db34583 100644 --- a/src/main/frontend/worker/state.cljs +++ b/src/main/frontend/worker/state.cljs @@ -3,7 +3,10 @@ (:require [logseq.common.util :as common-util])) (defonce *state (atom {:db/latest-transact-time {} - :worker/context {}})) + :worker/context {} + + ;; FIXME load graph config when fetch-initial-data + :config {}})) (defonce *sqlite (atom nil)) ;; repo -> {:db conn :search conn} @@ -50,3 +53,7 @@ (defn set-context! [context] (swap! *state assoc :worker/context context)) + +(defn get-config + [repo] + (get-in @*state [:config repo]))