mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
Merge branch 'master' into feat/custom-children-list-style
This commit is contained in:
@@ -2932,7 +2932,8 @@
|
||||
custom-query? (boolean (:custom-query? config))]
|
||||
(if (and (or ref? custom-query?) (not (:ref-query-child? config)))
|
||||
(ui/lazy-visible
|
||||
(fn [] (block-container-inner state repo config block)))
|
||||
(fn [] (block-container-inner state repo config block))
|
||||
{:debug-id (str "block-container-ref " (:db/id block))})
|
||||
(block-container-inner state repo config block))))
|
||||
|
||||
(defn divide-lists
|
||||
@@ -3360,7 +3361,7 @@
|
||||
"More"
|
||||
|
||||
@*loading?
|
||||
(ui/lazy-loading-placeholder)
|
||||
(ui/lazy-loading-placeholder 88)
|
||||
|
||||
:else
|
||||
"")})]))))
|
||||
@@ -3452,8 +3453,7 @@
|
||||
(rum/with-key
|
||||
(breadcrumb-with-container blocks (assoc config :top-level? top-level?))
|
||||
(:db/id parent)))))
|
||||
{:debug-id page
|
||||
:trigger-once? false})])))))]
|
||||
{:debug-id page})])))))]
|
||||
|
||||
(and (:ref? config) (:group-by-page? config))
|
||||
[:div.flex.flex-col
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
[:span.px-3.flex (ui/icon "key")]
|
||||
[:p.dark:text-gray-100
|
||||
[:span "Please make sure you "]
|
||||
"remember the password you have set, "
|
||||
"remember the password you have set, as we are unable to reset or retrieve it in case you forget it, "
|
||||
[:span "and we recommend you "]
|
||||
"keep a secure backup "
|
||||
[:span "of the password."]]]
|
||||
|
||||
@@ -59,8 +59,7 @@
|
||||
(blocks-cp repo page)
|
||||
(ui/lazy-visible
|
||||
(fn [] (blocks-cp repo page))
|
||||
{:trigger-once? false
|
||||
:debug-id (str "journal-blocks " page)}))
|
||||
{:debug-id (str "journal-blocks " page)}))
|
||||
|
||||
{})
|
||||
|
||||
|
||||
@@ -253,5 +253,4 @@
|
||||
(ui/lazy-visible
|
||||
(fn []
|
||||
(custom-query* config q (::query-triggered? state)))
|
||||
{:debug-id q
|
||||
:trigger-once? false})))
|
||||
{:debug-id q})))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[frontend.format.block :as block]
|
||||
[medley.core :as medley]
|
||||
[rum.core :as rum]
|
||||
[logseq.graph-parser.text :as text]
|
||||
[frontend.modules.outliner.tree :as tree]))
|
||||
|
||||
;; Util fns
|
||||
@@ -27,7 +28,7 @@
|
||||
(map #(medley/dissoc-in % ks) result)
|
||||
result)))
|
||||
|
||||
(defn- sort-by-fn [sort-by-column item]
|
||||
(defn- sort-by-fn [sort-by-column item {:keys [page?]}]
|
||||
(case sort-by-column
|
||||
:created-at
|
||||
(:block/created-at item)
|
||||
@@ -36,7 +37,7 @@
|
||||
:block
|
||||
(:block/content item)
|
||||
:page
|
||||
(:block/name item)
|
||||
(if page? (:block/name item) (get-in item [:block/page :block/name]))
|
||||
(get-in item [:block/properties sort-by-column])))
|
||||
|
||||
(defn- locale-compare
|
||||
@@ -46,11 +47,12 @@
|
||||
(< x y)
|
||||
(.localeCompare (str x) (str y) (state/sub :preferred-language) #js {:numeric true})))
|
||||
|
||||
(defn- sort-result [result {:keys [sort-by-column sort-desc? sort-nlp-date?]}]
|
||||
(defn- sort-result [result {:keys [sort-by-column sort-desc? sort-nlp-date? page?]}]
|
||||
(if (some? sort-by-column)
|
||||
(let [comp-fn (if sort-desc? #(locale-compare %2 %1) locale-compare)]
|
||||
(sort-by (fn [item]
|
||||
(block/normalize-block (sort-by-fn sort-by-column item) sort-nlp-date?))
|
||||
(block/normalize-block (sort-by-fn sort-by-column item {:page? page?})
|
||||
sort-nlp-date?))
|
||||
comp-fn
|
||||
result))
|
||||
result))
|
||||
@@ -115,7 +117,7 @@
|
||||
(defn- build-column-value
|
||||
"Builds a column's tuple value for a query table given a row, column and
|
||||
options"
|
||||
[row column {:keys [page? ->elem map-inline config]}]
|
||||
[row column {:keys [page? ->elem map-inline config comma-separated-property?]}]
|
||||
(case column
|
||||
:page
|
||||
[:string (if page?
|
||||
@@ -143,9 +145,13 @@
|
||||
[:string (when-let [updated-at (:block/updated-at row)]
|
||||
(date/int->local-time-2 updated-at))]
|
||||
|
||||
[:string (or (get-in row [:block/properties-text-values column])
|
||||
;; Fallback to property relationships for page blocks
|
||||
(get-in row [:block/properties column]))]))
|
||||
[:string (if comma-separated-property?
|
||||
;; Return original properties since comma properties need to
|
||||
;; return collections for display purposes
|
||||
(get-in row [:block/properties column])
|
||||
(or (get-in row [:block/properties-text-values column])
|
||||
;; Fallback to original properties for page blocks
|
||||
(get-in row [:block/properties column])))]))
|
||||
|
||||
(rum/defcs result-table < rum/reactive
|
||||
(rum/local false ::select?)
|
||||
@@ -165,7 +171,8 @@
|
||||
;; Sort state needs to be in sync between final result and sortable title
|
||||
;; as user needs to know if there result is sorted
|
||||
sort-state (get-sort-state current-block)
|
||||
result' (sort-result result sort-state)]
|
||||
result' (sort-result result (assoc sort-state :page? page?))
|
||||
property-separated-by-commas? (partial text/separated-by-commas? (state/get-config))]
|
||||
[:div.overflow-x-auto {:on-mouse-down (fn [e] (.stopPropagation e))
|
||||
:style {:width "100%"}
|
||||
:class (when-not page? "query-table")}
|
||||
@@ -188,7 +195,8 @@
|
||||
{:page? page?
|
||||
:->elem ->elem
|
||||
:map-inline map-inline
|
||||
:config config})]
|
||||
:config config
|
||||
:comma-separated-property? (property-separated-by-commas? column)})]
|
||||
[:td.whitespace-nowrap {:on-mouse-down (fn []
|
||||
(reset! *mouse-down? true)
|
||||
(reset! select? false))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
(not (true? (state/scheduled-deadlines-disabled?)))
|
||||
(= (string/lower-case page-name) (string/lower-case (date/journal-name)))))
|
||||
|
||||
(rum/defc scheduled-and-deadlines < rum/reactive db-mixins/query
|
||||
(rum/defc scheduled-and-deadlines-inner < rum/reactive db-mixins/query
|
||||
[page-name]
|
||||
(let [scheduled-or-deadlines (when (scheduled-or-deadlines? page-name)
|
||||
(db/get-date-scheduled-or-deadlines (string/capitalize page-name)))]
|
||||
@@ -33,3 +33,9 @@
|
||||
{})]
|
||||
(content/content page-name {:hiccup ref-hiccup}))]
|
||||
{:title-trigger? true})])))
|
||||
|
||||
(rum/defc scheduled-and-deadlines
|
||||
[page-name]
|
||||
(ui/lazy-visible
|
||||
(fn [] (scheduled-and-deadlines-inner page-name))
|
||||
{:debug-id "scheduled-and-deadlines"}))
|
||||
|
||||
@@ -51,8 +51,12 @@
|
||||
|
||||
(defonce query-state (atom {}))
|
||||
|
||||
;; Current dynamic component
|
||||
(def ^:dynamic *query-component*)
|
||||
|
||||
;; Which reactive queries are triggered by the current component
|
||||
(def ^:dynamic *reactive-queries*)
|
||||
|
||||
;; component -> query-key
|
||||
(defonce query-components (atom {}))
|
||||
|
||||
@@ -156,11 +160,14 @@
|
||||
transform-fn identity}} query & inputs]
|
||||
{:pre [(s/valid? ::react-query-keys k)]}
|
||||
(let [kv? (and (vector? k) (= :kv (first k)))
|
||||
origin-key k
|
||||
k (vec (cons repo k))]
|
||||
(when-let [db (conn/get-db repo)]
|
||||
(let [result-atom (get-query-cached-result k)]
|
||||
(when-let [component *query-component*]
|
||||
(add-query-component! k component))
|
||||
(when-let [queries *reactive-queries*]
|
||||
(swap! queries conj origin-key))
|
||||
(if (and use-cache? result-atom)
|
||||
result-atom
|
||||
(let [{:keys [result time]} (util/with-time
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
(:require [frontend.db.react :as react]))
|
||||
|
||||
(def query
|
||||
{:wrap-render
|
||||
{:init
|
||||
(fn [state]
|
||||
(assoc state :reactive-queries (atom #{})))
|
||||
:wrap-render
|
||||
(fn [render-fn]
|
||||
(fn [state]
|
||||
(binding [react/*query-component* (:rum/react-component state)]
|
||||
(binding [react/*query-component* (:rum/react-component state)
|
||||
react/*reactive-queries* (:reactive-queries state)]
|
||||
(render-fn state))))
|
||||
:will-unmount
|
||||
(fn [state]
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
"")
|
||||
(string/replace (re-pattern (str "(?i)" (gstring/regExpEscape (str "." ext)) "$"))
|
||||
""))]
|
||||
(util/safe-path-join repo-dir (str bak-dir "/" relative-path))))
|
||||
(path/path-join repo-dir bak-dir relative-path)))
|
||||
|
||||
(defn- truncate-old-versioned-files!
|
||||
"reserve the latest 6 version files"
|
||||
@@ -174,7 +174,8 @@
|
||||
dir (case dir
|
||||
:backup-dir (get-backup-dir repo-dir path backup-dir ext)
|
||||
:version-file-dir (get-backup-dir repo-dir path version-file-dir ext))
|
||||
new-path (util/safe-path-join dir (str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) "." ext))]
|
||||
new-path (path/path-join dir (str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) "." ext))]
|
||||
|
||||
(<write-file-with-utf8 new-path content)
|
||||
(truncate-old-versioned-files! dir)))
|
||||
|
||||
@@ -187,14 +188,14 @@
|
||||
(string/includes? repo-dir divider-schema))
|
||||
repo-dir (if-not dir-schema?
|
||||
(str file-schema divider-schema repo-dir) repo-dir)
|
||||
backup-root (util/safe-path-join repo-dir backup-dir)
|
||||
backup-root (path/path-join repo-dir backup-dir)
|
||||
backup-dir-parent (util/node-path.dirname file-path)
|
||||
backup-dir-parent (string/replace backup-dir-parent repo-dir "")
|
||||
backup-dir-name (util/node-path.name file-path)
|
||||
file-extname (.extname util/node-path file-path)
|
||||
file-root (util/safe-path-join backup-root backup-dir-parent backup-dir-name)
|
||||
file-path (util/safe-path-join file-root
|
||||
(str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) file-extname))]
|
||||
file-root (path/path-join backup-root backup-dir-parent backup-dir-name)
|
||||
file-path (path/path-join file-root
|
||||
(str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) file-extname))]
|
||||
(<write-file-with-utf8 file-path content)
|
||||
(truncate-old-versioned-files! file-root)))
|
||||
|
||||
|
||||
@@ -677,6 +677,7 @@
|
||||
[:editor/bold
|
||||
:editor/insert-link
|
||||
:editor/italics
|
||||
:editor/strike-through
|
||||
:editor/highlight]
|
||||
|
||||
:shortcut.category/navigating
|
||||
|
||||
@@ -1065,8 +1065,8 @@
|
||||
(progress-bar width)])
|
||||
|
||||
(rum/defc lazy-loading-placeholder
|
||||
[]
|
||||
[:div.shadow.rounded-md.p-4.w-full.mx-auto.mb-5.fade-in {:style {:height 88}}
|
||||
[height]
|
||||
[:div.shadow.rounded-md.p-4.w-full.mx-auto.mb-5.fade-in {:style {:height height}}
|
||||
[:div.animate-pulse.flex.space-x-4
|
||||
[:div.flex-1.space-y-3.py-1
|
||||
[:div.h-2.bg-base-4.rounded]
|
||||
@@ -1076,37 +1076,37 @@
|
||||
[:div.h-2.bg-base-4.rounded.col-span-1]]
|
||||
[:div.h-2.bg-base-4.rounded]]]]])
|
||||
|
||||
(rum/defcs lazy-visible-inner
|
||||
[state visible? content-fn ref]
|
||||
[:div.lazy-visibility
|
||||
{:ref ref
|
||||
:style {:min-height 24}}
|
||||
(if visible?
|
||||
(when (fn? content-fn)
|
||||
[:div.fade-enter
|
||||
{:ref #(when-let [^js cls (and % (.-classList %))]
|
||||
(.add cls "fade-enter-active"))}
|
||||
(content-fn)])
|
||||
(lazy-loading-placeholder))])
|
||||
(rum/defc lazy-visible-inner
|
||||
[visible? content-fn ref]
|
||||
(let [[set-ref rect] (r/use-bounding-client-rect)
|
||||
placeholder-height (or (when rect (.-height rect)) 88)]
|
||||
[:div.lazy-visibility {:ref ref}
|
||||
[:div {:ref set-ref}
|
||||
(if visible?
|
||||
(when (fn? content-fn)
|
||||
[:div.fade-enter
|
||||
{:ref #(when-let [^js cls (and % (.-classList %))]
|
||||
(.add cls "fade-enter-active"))}
|
||||
(content-fn)])
|
||||
(lazy-loading-placeholder placeholder-height))]]))
|
||||
|
||||
(rum/defc lazy-visible
|
||||
([content-fn]
|
||||
(lazy-visible content-fn nil))
|
||||
([content-fn {:keys [trigger-once? _debug-id]
|
||||
:or {trigger-once? true}}]
|
||||
(if (or (util/mobile?) (mobile-util/native-platform?))
|
||||
(content-fn)
|
||||
(let [[visible? set-visible!] (rum/use-state false)
|
||||
inViewState (useInView #js {:rootMargin "100px"
|
||||
:triggerOnce trigger-once?
|
||||
:onChange (fn [in-view? entry]
|
||||
(let [self-top (.-top (.-boundingClientRect entry))]
|
||||
(when (or (and (not visible?) in-view?)
|
||||
;; hide only the components below the current top for better ux
|
||||
(and visible? (not in-view?) (> self-top 0)))
|
||||
(set-visible! in-view?))))})
|
||||
ref (.-ref inViewState)]
|
||||
(lazy-visible-inner visible? content-fn ref)))))
|
||||
:or {trigger-once? false}}]
|
||||
(let [[visible? set-visible!] (rum/use-state false)
|
||||
root-margin 100
|
||||
inViewState (useInView #js {:rootMargin (str root-margin "px")
|
||||
:triggerOnce trigger-once?
|
||||
:onChange (fn [in-view? entry]
|
||||
(let [self-top (.-top (.-boundingClientRect entry))]
|
||||
(when (or (and (not visible?) in-view?)
|
||||
;; hide only the components below the current top for better ux
|
||||
(and visible? (not in-view?) (> self-top root-margin)))
|
||||
(set-visible! in-view?))))})
|
||||
ref (.-ref inViewState)]
|
||||
(lazy-visible-inner visible? content-fn ref))))
|
||||
|
||||
(rum/defc portal
|
||||
([children]
|
||||
|
||||
@@ -528,13 +528,6 @@
|
||||
(if (string? s)
|
||||
(string/lower-case s) s))
|
||||
|
||||
#?(:cljs
|
||||
(defn safe-path-join [prefix & paths]
|
||||
(let [path (apply node-path.join (cons prefix paths))]
|
||||
(if (and (electron?) (gstring/caseInsensitiveStartsWith path "file://"))
|
||||
(gp-util/safe-decode-uri-component (subs path 7))
|
||||
path))))
|
||||
|
||||
(defn trim-safe
|
||||
[s]
|
||||
(when s
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
(when-let [result (query-dsl/query test-helper/test-db s)]
|
||||
(map first (deref result))))
|
||||
|
||||
(deftest sort-result
|
||||
(deftest sort-result-for-standard-columns
|
||||
;; Define since it's not defined
|
||||
(state/set-preferred-language! "en")
|
||||
|
||||
(testing "sort by block content"
|
||||
(testing "sort by block column"
|
||||
(are [sort-state result sorted-result]
|
||||
(= (mapv #(hash-map :block/content %) sorted-result)
|
||||
(#'query-table/sort-result (mapv #(hash-map :block/content %) result) sort-state))
|
||||
@@ -30,6 +30,24 @@
|
||||
{:sort-desc? false :sort-by-column :block}
|
||||
["abc" "cde"] ["abc" "cde"]))
|
||||
|
||||
(testing "sort by page column"
|
||||
(are [sort-options result sorted-result]
|
||||
(= sorted-result
|
||||
(#'query-table/sort-result result sort-options))
|
||||
;; on page queries
|
||||
{:sort-desc? true :sort-by-column :page :page? true}
|
||||
(map #(hash-map :block/name %) ["abc" "cde"])
|
||||
(map #(hash-map :block/name %) ["cde" "abc"])
|
||||
|
||||
;; on block queries
|
||||
{:sort-desc? true :sort-by-column :page :page? false}
|
||||
(map #(hash-map :block/page {:block/name %}) ["abc" "cde"])
|
||||
(map #(hash-map :block/page {:block/name %}) ["cde" "abc"]))))
|
||||
|
||||
(deftest sort-result-for-property-columns
|
||||
;; Define since it's not defined
|
||||
(state/set-preferred-language! "en")
|
||||
|
||||
(testing "sort by integer block property"
|
||||
(are [sort-state result sorted-result]
|
||||
(= (mapv #(hash-map :block/properties %) sorted-result)
|
||||
@@ -86,17 +104,17 @@
|
||||
[{:title 1.1} {:title 1.2} {:title 1.11}]
|
||||
[{:title 1.1} {:title 1.11} {:title 1.2}]))
|
||||
|
||||
(testing "sort by semver-style string block property"
|
||||
(are [sort-state result sorted-result]
|
||||
(= (mapv #(hash-map :block/properties %) sorted-result)
|
||||
(#'query-table/sort-result (mapv #(hash-map :block/properties %) result) sort-state))
|
||||
{:sort-desc? true :sort-by-column :title}
|
||||
[{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}]
|
||||
[{:title "1.11.1"} {:title "1.11.0"} {:title "1.2.1"} {:title "1.2.0"} {:title "1.1.1"} {:title "1.1.0"}]
|
||||
(testing "sort by semver-style string block property"
|
||||
(are [sort-state result sorted-result]
|
||||
(= (mapv #(hash-map :block/properties %) sorted-result)
|
||||
(#'query-table/sort-result (mapv #(hash-map :block/properties %) result) sort-state))
|
||||
{:sort-desc? true :sort-by-column :title}
|
||||
[{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}]
|
||||
[{:title "1.11.1"} {:title "1.11.0"} {:title "1.2.1"} {:title "1.2.0"} {:title "1.1.1"} {:title "1.1.0"}]
|
||||
|
||||
{:sort-desc? false :sort-by-column :title}
|
||||
[{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}]
|
||||
[{:title "1.1.0"} {:title "1.1.1"} {:title "1.2.0"} {:title "1.2.1"} {:title "1.11.0"} {:title "1.11.1"}]))
|
||||
{:sort-desc? false :sort-by-column :title}
|
||||
[{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}]
|
||||
[{:title "1.1.0"} {:title "1.1.1"} {:title "1.2.0"} {:title "1.2.1"} {:title "1.11.0"} {:title "1.11.1"}]))
|
||||
|
||||
(testing "sort by string block property for specific locale"
|
||||
(state/set-preferred-language! "zh-CN")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
(ns frontend.db.query-dsl-test
|
||||
(:require [cljs.test :refer [are deftest testing use-fixtures is]]
|
||||
[clojure.string :as str]
|
||||
[logseq.graph-parser.util.page-ref :as page-ref]
|
||||
[frontend.db :as db]
|
||||
[frontend.util :as util]
|
||||
[frontend.db.query-dsl :as query-dsl]
|
||||
[frontend.test.helper :as test-helper :include-macros true :refer [load-test-files]]))
|
||||
|
||||
@@ -142,6 +144,21 @@ prop-d:: nada"}])
|
||||
(test-helper/with-config {}
|
||||
(block-property-queries-test))))
|
||||
|
||||
(deftest block-property-query-performance
|
||||
(let [pages (->> (repeat 10 {:tags ["tag1" "tag2"]})
|
||||
(map-indexed (fn [idx {:keys [tags]}]
|
||||
{:file/path (str "pages/page" idx ".md")
|
||||
:file/content (if (seq tags)
|
||||
(str "tags:: " (str/join ", " (map page-ref/->page-ref tags)))
|
||||
"")})))
|
||||
_ (load-test-files pages)
|
||||
{:keys [result time]}
|
||||
(util/with-time (dsl-query "(and (property tags tag1) (property tags tag2))"))]
|
||||
;; Specific number isn't as important as ensuring query doesn't take orders
|
||||
;; of magnitude longer
|
||||
(is (> 25.0 time) "multi property query perf is reasonable")
|
||||
(is (= 10 (count result)))))
|
||||
|
||||
(defn- page-property-queries-test
|
||||
[]
|
||||
(load-test-files [{:file/path "pages/page1.md"
|
||||
|
||||
@@ -22,17 +22,6 @@
|
||||
(is (= 2 (util/safe-inc-current-pos-from-start "abcde" 1)))
|
||||
(is (= 1 (util/safe-inc-current-pos-from-start "中文" 0)))))
|
||||
|
||||
(deftest test-safe-path-join
|
||||
(testing "safe path join with custom schema"
|
||||
(is (= (util/node-path.join "a/b" "c/d.md") "a/b/c/d.md"))
|
||||
(is (= (util/node-path.join "a/b/c" "../../d.md") "a/d.md"))
|
||||
(is (= (util/node-path.join "file:///a/b" "c/d.md") "file:///a/b/c/d.md"))
|
||||
(is (= (util/node-path.join "file:///a/b" "../d.md") "file:///a/d.md"))
|
||||
(is (= (util/node-path.join "file:///a a2/b" "c/d.md") "file:///a a2/b/c/d.md"))
|
||||
(is (= (util/node-path.join "C:/a2/b" "c/d.md") "C:/a2/b/c/d.md"))
|
||||
(is (= (util/node-path.join "content://a/b" "../d.md") "content://a/d.md"))
|
||||
(is (= (util/node-path.join "https://logseq.com/a/b" "c/d.md") "https://logseq.com/a/b/c/d.md"))))
|
||||
|
||||
(deftest test-memoize-last
|
||||
(testing "memoize-last add test"
|
||||
(let [actual-ops (atom 0)
|
||||
@@ -120,4 +109,4 @@
|
||||
(is (= (config/ext-of-video? "file.mp3") false))
|
||||
(is (= (config/ext-of-image? "file.svg") true))
|
||||
(is (= (config/ext-of-image? "a.file.png") true))
|
||||
(is (= (config/ext-of-image? "file.tiff") false))))
|
||||
(is (= (config/ext-of-image? "file.tiff") false))))
|
||||
|
||||
Reference in New Issue
Block a user