| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545 |
- (ns frontend.util
- "Main ns for utility fns. This ns should be split up into more focused namespaces"
- #?(:clj (:refer-clojure :exclude [format]))
- #?(:cljs (:require-macros [frontend.util]))
- #?(:cljs (:require
- ["/frontend/selection" :as selection]
- ["/frontend/utils" :as utils]
- ["@capacitor/status-bar" :refer [^js StatusBar Style]]
- ["@capgo/capacitor-navigation-bar" :refer [^js NavigationBar]]
- ["grapheme-splitter" :as GraphemeSplitter]
- ["sanitize-filename" :as sanitizeFilename]
- ["check-password-strength" :refer [passwordStrength]]
- ["path-complete-extname" :as pathCompleteExtname]
- ["semver" :as semver]
- [frontend.loader :refer [load]]
- [cljs-bean.core :as bean]
- [cljs-time.coerce :as tc]
- [cljs-time.core :as t]
- [clojure.pprint]
- [dommy.core :as d]
- [frontend.mobile.util :as mobile-util]
- [logseq.common.util :as common-util]
- [goog.dom :as gdom]
- [goog.object :as gobj]
- [goog.string :as gstring]
- [goog.functions :as gfun]
- [goog.userAgent]
- [promesa.core :as p]
- [rum.core :as rum]
- [clojure.core.async :as async]
- [frontend.pubsub :as pubsub]
- [datascript.impl.entity :as de]
- [logseq.common.config :as common-config]))
- #?(:cljs (:import [goog.async Debouncer]))
- (:require
- [clojure.pprint]
- [clojure.string :as string]
- [clojure.walk :as walk]))
- #?(:cljs (goog-define NODETEST false)
- :clj (def NODETEST false))
- (defonce node-test? NODETEST)
- #?(:cljs
- (extend-protocol IPrintWithWriter
- symbol
- (-pr-writer [sym writer _]
- (-write writer (str "\"" (.toString sym) "\"")))))
- #?(:cljs
- (extend-protocol INamed
- UUID
- (-name [this] (str this))
- (-namespace [_] nil)))
- #?(:cljs (defonce ^js node-path utils/nodePath))
- #?(:cljs (defonce ^js sem-ver semver))
- #?(:cljs (defonce ^js full-path-extname pathCompleteExtname))
- #?(:cljs (defn app-scroll-container-node
- ([]
- (gdom/getElement "main-content-container"))
- ([el]
- (if (.closest el "#main-content-container")
- (app-scroll-container-node)
- (or
- (gdom/getElementByClass "sidebar-item-list")
- (app-scroll-container-node))))))
- #?(:cljs (defonce el-visible-in-viewport? utils/elementIsVisibleInViewport))
- #?(:cljs (defonce convert-to-roman utils/convertToRoman))
- #?(:cljs (defonce convert-to-letters utils/convertToLetters))
- #?(:cljs (defonce hsl2hex utils/hsl2hex))
- #?(:cljs (def string-join-path common-util/string-join-path))
- #?(:cljs
- (do
- (def safe-re-find common-util/safe-re-find)
- (defn safe-keyword
- [s]
- (when (string? s)
- (keyword (string/replace s " " "_"))))))
- #?(:cljs
- (do
- (def uuid-string? common-util/uuid-string?)
- (defn check-password-strength
- {:malli/schema [:=> [:cat :string] [:maybe
- [:map
- [:contains [:sequential :string]]
- [:length :int]
- [:id :int]
- [:value :string]]]]}
- [input]
- (when-let [^js ret (and (string? input)
- (not (string/blank? input))
- (passwordStrength input))]
- (bean/->clj ret)))
- (defn safe-sanitize-file-name
- {:malli/schema [:=> [:cat :string] :string]}
- [s]
- (sanitizeFilename (str s)))))
- #?(:cljs
- (do
- (defn- ios*?
- []
- (utils/ios))
- (def ios? (memoize ios*?))))
- #?(:cljs
- (do
- (defn- safari*?
- []
- (let [ua (string/lower-case js/navigator.userAgent)]
- (and (string/includes? ua "webkit")
- (not (string/includes? ua "chrome")))))
- (def safari? (memoize safari*?))))
- #?(:cljs
- (do
- (defn- mobile*?
- "Triggering condition: Mobile phones
- *** Warning!!! ***
- For UX logic only! Don't use for FS logic
- iPad / Android Pad doesn't trigger!"
- []
- (when-not node-test?
- (safe-re-find #"Mobi" js/navigator.userAgent)))
- (def mobile? (memoize mobile*?))))
- #?(:cljs
- (do
- (defn- electron*?
- []
- (when (and js/window (gobj/get js/window "navigator"))
- (gstring/caseInsensitiveContains js/navigator.userAgent " electron")))
- (def electron? (memoize electron*?))))
- #?(:cljs
- (defn mocked-open-dir-path
- "Mocked open DIR path for by-passing open dir in electron during testing. Nil if not given"
- []
- (when (electron?) (. js/window -__MOCKED_OPEN_DIR_PATH__))))
- ;; #?(:cljs
- ;; (defn ci?
- ;; []
- ;; (boolean (. js/window -__E2E_TESTING__))))
- #?(:cljs
- (do
- (def nfs? (and (not (electron?))
- (not (mobile-util/native-platform?))))
- (def web-platform? nfs?)
- (def plugin-platform? (or (and web-platform? (not common-config/PUBLISHING)) (electron?)))))
- #?(:cljs
- (defn file-protocol?
- []
- (string/starts-with? js/window.location.href "file://")))
- #?(:cljs
- (def format common-util/format))
- #?(:clj
- (defn format
- [fmt & args]
- (apply clojure.core/format fmt args)))
- #?(:cljs
- (defn evalue
- [event]
- (gobj/getValueByKeys event "target" "value")))
- #?(:cljs
- (defn ekey [event]
- (gobj/getValueByKeys event "key")))
- #?(:cljs
- (defn echecked? [event]
- (gobj/getValueByKeys event "target" "checked")))
- #?(:cljs
- (defn set-change-value
- "compatible change event for React"
- [node value]
- (utils/triggerInputChange node value)))
- #?(:cljs
- (defn p-handle
- ([p ok-handler]
- (p-handle p ok-handler (fn [error]
- (js/console.error error))))
- ([p ok-handler error-handler]
- (-> p
- (p/then (fn [result]
- (ok-handler result)))
- (p/catch (fn [error]
- (error-handler error)))))))
- #?(:cljs
- (defn get-width
- []
- (gobj/get js/window "innerWidth")))
- ;; Keep the following colors in sync with common.css
- #?(:cljs
- (defn- get-computed-bg-color
- []
- ;; window.getComputedStyle(document.body, null).getPropertyValue('background-color');
- (let [styles (js/window.getComputedStyle js/document.body)
- bg-color (gobj/get styles "background-color")
- ;; convert rgb(r,g,b) to #rrggbb
- rgb2hex (fn [rgb]
- (->> rgb
- (map (comp #(.toString % 16) parse-long string/trim))
- (map #(if (< (count %) 2)
- (str "0" %)
- %))
- (string/join)
- (str "#")))]
- (when (string/starts-with? bg-color "rgb")
- (let [rgb (-> bg-color
- (string/replace #"^rgb[^\d]+" "")
- (string/replace #"\)$" "")
- (string/split #","))
- rgb (take 3 rgb)]
- (rgb2hex rgb))))))
- #?(:cljs
- (defn set-android-theme
- []
- (let [f #(when (mobile-util/native-android?)
- (when-let [bg-color (try (get-computed-bg-color)
- (catch :default _
- nil))]
- (.setNavigationBarColor NavigationBar (clj->js {:color bg-color}))
- (.setBackgroundColor StatusBar (clj->js {:color bg-color}))))]
- (js/setTimeout f 32))))
- #?(:cljs
- (defn set-theme-light
- []
- (p/do!
- (.setStyle StatusBar (clj->js {:style (.-Light Style)}))
- (set-android-theme))))
- #?(:cljs
- (defn set-theme-dark
- []
- (p/do!
- (.setStyle StatusBar (clj->js {:style (.-Dark Style)}))
- (set-android-theme))))
- (defn find-first
- [pred coll]
- (first (filter pred coll)))
- (defn find-index
- "Find first index of an element in list"
- [pred-or-val coll]
- (let [pred (if (fn? pred-or-val) pred-or-val #(= pred-or-val %))]
- (reduce-kv #(if (pred %3) (reduced %2) %1) -1
- (cond-> coll (list? coll) (vec)))))
- ;; ".lg:absolute.lg:inset-y-0.lg:right-0.lg:w-1/2"
- (defn hiccup->class
- [class']
- (some->> (string/split class' #"\.")
- (string/join " ")
- (string/trim)))
- #?(:cljs
- (defn fetch
- ([url on-ok on-failed]
- (fetch url {} on-ok on-failed))
- ([url opts on-ok on-failed]
- (-> (js/fetch url (bean/->js opts))
- (.then (fn [resp]
- (if (>= (.-status resp) 400)
- (on-failed resp)
- (if (.-ok resp)
- (-> (.json resp)
- (.then bean/->clj)
- (.then #(on-ok %)))
- (on-failed resp)))))))))
- (defn zero-pad
- [n]
- (if (< n 10)
- (str "0" n)
- (str n)))
- #?(:cljs
- (defn safe-parse-int
- "Use if arg could be an int or string. If arg is only a string, use `parse-long`."
- {:malli/schema [:=> [:cat [:or :int :string]] :int]}
- [x]
- (if (string? x)
- (parse-long x)
- x)))
- #?(:cljs
- (defn safe-parse-float
- "Use if arg could be a float or string. If arg is only a string, use `parse-double`"
- {:malli/schema [:=> [:cat [:or :double :string]] :double]}
- [x]
- (if (string? x)
- (parse-double x)
- x)))
- #?(:cljs
- (def debounce gfun/debounce))
- #?(:cljs
- (defn cancelable-debounce
- "Create a stateful debounce function with specified interval
- Returns [fire-fn, cancel-fn]
- Use `fire-fn` to call the function(debounced)
- Use `cancel-fn` to cancel pending callback if there is"
- [f interval]
- (let [debouncer (Debouncer. f interval)]
- [(fn [& args] (.apply (.-fire debouncer) debouncer (to-array args)))
- (fn [] (.stop debouncer))])))
- (defn nth-safe [c i]
- (if (or (< i 0) (>= i (count c)))
- nil
- (nth c i)))
- #?(:cljs
- (when-not node-test?
- (extend-type js/NodeList
- ISeqable
- (-seq [arr] (array-seq arr 0)))))
- ;; Caret
- #?(:cljs
- (defn caret-range [node]
- (when-let [doc (or (gobj/get node "ownerDocument")
- (gobj/get node "document"))]
- (let [win (or (gobj/get doc "defaultView")
- (gobj/get doc "parentWindow"))
- selection (.getSelection win)]
- (if selection
- (let [range-count (gobj/get selection "rangeCount")]
- (when (> range-count 0)
- (let [range (-> (.getSelection win)
- (.getRangeAt 0))
- pre-caret-range (.cloneRange range)]
- (.selectNodeContents pre-caret-range node)
- (.setEnd pre-caret-range
- (gobj/get range "endContainer")
- (gobj/get range "endOffset"))
- (let [contents (.cloneContents pre-caret-range)
- html (some-> (first (.-childNodes contents))
- (gobj/get "innerHTML")
- str)
- ;; FIXME: this depends on the dom structure,
- ;; need a converter from html to text includes newlines
- br-ended? (and html
- (or
- ;; first line with a new line
- (string/ends-with? html "<div class=\"is-paragraph\"></div></div></span></div></div></div>")
- ;; multiple lines with a new line
- (string/ends-with? html "<br></div></div></span></div></div></div>")))
- value (.toString pre-caret-range)]
- (if br-ended?
- (str value "\n")
- value)))))
- (when-let [selection (gobj/get doc "selection")]
- (when (not= "Control" (gobj/get selection "type"))
- (let [text-range (.createRange selection)
- pre-caret-text-range (.createTextRange (gobj/get doc "body"))]
- (.moveToElementText pre-caret-text-range node)
- (.setEndPoint pre-caret-text-range "EndToEnd" text-range)
- (gobj/get pre-caret-text-range "text")))))))))
- (defn get-selection-start
- [input]
- (when input
- (.-selectionStart input)))
- (defn get-selection-end
- [input]
- (when input
- (.-selectionEnd input)))
- (defn input-text-selected?
- [input]
- (not= (get-selection-start input)
- (get-selection-end input)))
- (defn get-selection-direction
- [input]
- (when input
- (.-selectionDirection input)))
- #?(:cljs
- (defn split-graphemes
- [s]
- (let [^js splitter (GraphemeSplitter.)]
- (.splitGraphemes splitter s))))
- #?(:cljs
- (defn get-graphemes-pos
- "Return the length of the substrings in s between start and from-index.
- multi-char count as 1, like emoji characters"
- [s from-index]
- (let [^js splitter (GraphemeSplitter.)]
- (.countGraphemes splitter (subs s 0 from-index)))))
- #?(:cljs
- (defn get-line-pos
- "Return the length of the substrings in s between the last index of newline
- in s searching backward from from-newline-index and from-newline-index.
- multi-char count as 1, like emoji characters"
- [s from-newline-index]
- (let [^js splitter (GraphemeSplitter.)
- last-newline-pos (string/last-index-of s \newline (dec from-newline-index))
- before-last-newline-length (or last-newline-pos -1)
- last-newline-content (subs s (inc before-last-newline-length) from-newline-index)]
- (.countGraphemes splitter last-newline-content))))
- #?(:cljs
- (defn get-text-range
- "Return the substring of the first grapheme-num characters of s if first-line? is true,
- otherwise return the substring of s before the last \n and the first grapheme-num characters.
- grapheme-num treats multi-char as 1, like emoji characters"
- [s grapheme-num first-line?]
- (let [newline-pos (if first-line?
- 0
- (inc (or (string/last-index-of s \newline) -1)))
- ^js splitter (GraphemeSplitter.)
- ^js newline-graphemes (.splitGraphemes splitter (subs s newline-pos))
- ^js newline-graphemes (.slice newline-graphemes 0 grapheme-num)
- content (.join newline-graphemes "")]
- (subs s 0 (+ newline-pos (count content))))))
- #?(:cljs
- (defn stop [e]
- (when e (doto e (.preventDefault) (.stopPropagation)))))
- #?(:cljs
- (defn stop-propagation [e]
- (when e (.stopPropagation e))))
- #?(:cljs
- (defn nearest-scrollable-container [^js/HTMLElement element]
- (some #(when-let [overflow-y (.-overflowY (js/window.getComputedStyle %))]
- (when (contains? #{"auto" "scroll" "overlay"} overflow-y)
- %))
- (take-while (complement nil?) (iterate #(.-parentElement %) element)))))
- #?(:cljs
- (defn element-visible?
- [element]
- (when element
- (when-let [r (.getBoundingClientRect element)]
- (and (>= (.-top r) 0)
- (<= (+ (.-bottom r) 64)
- (or (.-innerHeight js/window)
- (js/document.documentElement.clientHeight))))))))
- #?(:cljs
- (defn element-top [elem top]
- (when elem
- (if (.-offsetParent elem)
- (let [client-top (or (.-clientTop elem) 0)
- offset-top (.-offsetTop elem)]
- (+ top client-top offset-top (element-top (.-offsetParent elem) top)))
- top))))
- #?(:cljs
- (defn scroll-to-element
- [elem-id]
- (when-not (safe-re-find #"^/\d+$" elem-id)
- (when elem-id
- (when-let [elem (gdom/getElement elem-id)]
- (.scroll (app-scroll-container-node)
- #js {:top (let [top (element-top elem 0)]
- (if (< top 256)
- 0
- (- top 80)))
- :behavior "smooth"}))))))
- #?(:cljs
- (defn scroll-to
- ([pos]
- (scroll-to (app-scroll-container-node) pos))
- ([node pos]
- (scroll-to node pos true))
- ([node pos animate?]
- (when node
- (.scroll node
- #js {:top pos
- :behavior (if animate? "smooth" "auto")})))))
- #?(:cljs
- (defn scroll-top
- "Returns the scroll top position of the `node`. If `node` is not specified,
- returns the scroll top position of the `app-scroll-container-node`."
- ([]
- (scroll-top (app-scroll-container-node)))
- ([node]
- (when node (.-scrollTop node)))))
- #?(:cljs
- (defn scroll-to-top
- ([]
- (scroll-to (app-scroll-container-node) 0 false))
- ([animate?]
- (scroll-to (app-scroll-container-node) 0 animate?))))
- #?(:cljs
- (defn scroll-to-block
- "Scroll into the view to vertically align a non-visible block to the centre
- of the visible area"
- ([block]
- (scroll-to-block block true))
- ([block animate?]
- (when block
- (when-not (element-visible? block)
- (.scrollIntoView block
- #js {:behavior (if animate? "smooth" "auto")
- :block "center"}))))))
- #?(:cljs
- (defn link?
- [node]
- (contains?
- #{"A" "BUTTON"}
- (gobj/get node "tagName"))))
- #?(:cljs
- (defn time?
- [node]
- (contains?
- #{"TIME"}
- (gobj/get node "tagName"))))
- #?(:cljs
- (defn audio?
- [node]
- (contains?
- #{"AUDIO"}
- (gobj/get node "tagName"))))
- #?(:cljs
- (defn video?
- [node]
- (contains?
- #{"VIDEO"}
- (gobj/get node "tagName"))))
- #?(:cljs
- (defn sup?
- [node]
- (contains?
- #{"SUP"}
- (gobj/get node "tagName"))))
- #?(:cljs
- (defn input?
- [node]
- (when node
- (contains?
- #{"INPUT" "TEXTAREA"}
- (gobj/get node "tagName")))))
- #?(:cljs
- (defn details-or-summary?
- [node]
- (when node
- (contains?
- #{"DETAILS" "SUMMARY"}
- (gobj/get node "tagName")))))
- ;; Debug
- (defn starts-with?
- [s substr]
- (string/starts-with? s substr))
- #?(:cljs
- (def distinct-by common-util/distinct-by))
- #?(:cljs
- (def distinct-by-last-wins common-util/distinct-by-last-wins))
- (defn get-git-owner-and-repo
- [repo-url]
- (take-last 2 (string/split repo-url #"/")))
- (defn safe-lower-case
- [s]
- (if (string? s)
- (string/lower-case s) s))
- (defn trim-safe
- [s]
- (if (string? s)
- (string/trim s) s))
- (defn trimr-without-newlines
- [s]
- (.replace s #"[ \t\r]+$" ""))
- (defn triml-without-newlines
- [s]
- (.replace s #"^[ \t\r]+" ""))
- (defn concat-without-spaces
- [left right]
- (when (and (string? left)
- (string? right))
- (let [left (trimr-without-newlines left)
- not-space? (or
- (string/blank? left)
- (= "\n" (last left)))]
- (str left
- (when-not not-space? " ")
- (triml-without-newlines right)))))
- (defn cjk-string?
- [s]
- (re-find #"[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f]" s))
- ;; Add documentation
- (defn replace-first [pattern s new-value]
- (if-let [first-index (string/index-of s pattern)]
- (str new-value (subs s (+ first-index (count pattern))))
- s))
- (defn replace-last
- ([pattern s new-value]
- (replace-last pattern s new-value true))
- ([pattern s new-value space?]
- (if-let [last-index (string/last-index-of s pattern)]
- (let [prefix (subs s 0 last-index)]
- (if space?
- (concat-without-spaces prefix new-value)
- (str prefix new-value)))
- s)))
- ;; copy from https://stackoverflow.com/questions/18735665/how-can-i-get-the-positions-of-regex-matches-in-clojurescript
- #?(:cljs
- (defn re-pos [re s]
- (let [re (js/RegExp. (.-source re) "g")]
- (loop [res []]
- (if-let [m (.exec re s)]
- (recur (conj res [(.-index m) (first m)]))
- res)))))
- #?(:cljs
- (defn safe-set-range-text!
- ([input text start end]
- (try
- (.setRangeText input text start end)
- (catch :default _e
- nil)))
- ([input text start end select-mode]
- (try
- (.setRangeText input text start end select-mode)
- (catch :default _e
- nil)))))
- #?(:cljs
- ;; for widen char
- (defn safe-dec-current-pos-from-end
- [input current-pos]
- (if-let [len (and (number? current-pos) (string? input) (.-length input))]
- (if-let [input (and (>= len 2) (<= current-pos len)
- (.substring input (max (- current-pos 20) 0) current-pos))]
- (try
- (let [^js splitter (GraphemeSplitter.)
- ^js input' (.splitGraphemes splitter input)]
- (- current-pos (.-length (.pop input'))))
- (catch :default e
- (js/console.error e)
- (dec current-pos)))
- (dec current-pos))
- current-pos)))
- #?(:cljs
- ;; for widen char
- (defn safe-inc-current-pos-from-start
- [input current-pos]
- (if-let [len (and (number? current-pos) (string? input) (.-length input))]
- (if-let [input (and (>= len 2) (<= current-pos len)
- (.substr input current-pos 20))]
- (try
- (let [^js splitter (GraphemeSplitter.)
- ^js input (.splitGraphemes splitter input)]
- (+ current-pos (.-length (.shift input))))
- (catch :default e
- (js/console.error e)
- (inc current-pos)))
- (inc current-pos))
- current-pos)))
- #?(:cljs
- (defn kill-line-before!
- [input]
- (let [val (.-value input)
- end (get-selection-start input)
- n-pos (string/last-index-of val \newline (dec end))
- start (if n-pos (inc n-pos) 0)]
- (safe-set-range-text! input "" start end))))
- #?(:cljs
- (defn kill-line-after!
- [input]
- (let [val (.-value input)
- start (get-selection-start input)
- end (or (string/index-of val \newline start)
- (count val))]
- (safe-set-range-text! input "" start end))))
- #?(:cljs
- (defn insert-at-current-position!
- [input text]
- (let [start (get-selection-start input)
- end (get-selection-end input)]
- (safe-set-range-text! input text start end "end"))))
- (defn safe-subvec [xs start end]
- (if (or (neg? start)
- (> start end)
- (> end (count xs)))
- []
- (subvec xs start end)))
- #?(:cljs
- (defn get-nodes-between-two-nodes
- [id1 id2 class]
- (when-let [nodes (array-seq (js/document.getElementsByClassName class))]
- (let [node-1 (gdom/getElement id1)
- node-2 (gdom/getElement id2)
- idx-1 (.indexOf nodes node-1)
- idx-2 (.indexOf nodes node-2)
- start (min idx-1 idx-2)
- end (inc (max idx-1 idx-2))]
- (safe-subvec (vec nodes) start end)))))
- #?(:cljs
- (defn get-direction-between-two-nodes
- [id1 id2 class]
- (when-let [nodes (array-seq (js/document.getElementsByClassName class))]
- (let [node-1 (gdom/getElement id1)
- node-2 (gdom/getElement id2)
- idx-1 (.indexOf nodes node-1)
- idx-2 (.indexOf nodes node-2)]
- (if (>= idx-1 idx-2)
- :up
- :down)))))
- #?(:cljs
- (defn rec-get-node
- [node class]
- (if (and node (d/has-class? node class))
- node
- (and node
- (rec-get-node (gobj/get node "parentNode") class)))))
- #?(:cljs
- (defn rec-get-blocks-container
- [node]
- (rec-get-node node "blocks-container")))
- #?(:cljs
- (defn rec-get-blocks-content-section
- [node]
- (rec-get-node node "content")))
- #?(:cljs
- (defn get-blocks-noncollapse
- ([]
- (->> (d/sel "div .ls-block")
- (filter (fn [b] (some? (gobj/get b "offsetParent"))))))
- ([blocks-container]
- (->> (d/sel blocks-container "div .ls-block")
- (filter (fn [b] (some? (gobj/get b "offsetParent"))))))))
- #?(:cljs
- (defn remove-embedded-blocks [blocks]
- (->> blocks
- (remove (fn [b] (= "true" (d/attr b "data-embed")))))))
- #?(:cljs
- (defn remove-property-value-blocks [blocks]
- (->> blocks
- (remove (fn [b] (d/has-class? b "property-value-container"))))))
- #?(:cljs
- (defn get-selected-text
- []
- (utils/getSelectionText)))
- #?(:cljs (def clear-selection! selection/clearSelection))
- #?(:cljs
- (defn copy-to-clipboard!
- [text & {:keys [graph html blocks embed-block? owner-window]}]
- (let [blocks (map (fn [block] (if (de/entity? block)
- (-> (into {} block)
- ;; FIXME: why :db/id is not included?
- (assoc :db/id (:db/id block)))
- block)) blocks)
- data (clj->js
- (common-util/remove-nils-non-nested
- {:text text
- :html html
- :blocks (when (and graph (seq blocks))
- (pr-str
- {:graph graph
- :embed-block? embed-block?
- :blocks (mapv #(dissoc % :block.temp/fully-loaded? %) blocks)}))}))]
- (if owner-window
- (utils/writeClipboard data owner-window)
- (utils/writeClipboard data)))))
- (defn drop-nth [n coll]
- (keep-indexed #(when (not= %1 n) %2) coll))
- #?(:cljs
- (defn atom? [v]
- (instance? Atom v)))
- #?(:cljs
- (defn react
- [ref]
- (when ref
- (if rum/*reactions*
- (rum/react ref)
- @ref))))
- #?(:cljs
- (def time-ms common-util/time-ms))
- (defn d
- [k f]
- (let [result (atom nil)]
- (println (str "Debug " k))
- (time (reset! result (doall (f))))
- @result))
- #?(:cljs
- (def concat-without-nil common-util/concat-without-nil))
- #?(:cljs
- (defn set-title!
- [title]
- (set! (.-title js/document) title)))
- #?(:cljs
- (defn get-block-container
- [block-element]
- (when block-element
- (when-let [section (some-> (rec-get-blocks-content-section block-element)
- (d/parent))]
- (when section
- (gdom/getElement section "id"))))))
- #?(:cljs
- (defn- skip-same-top-blocks
- [blocks block]
- (let [property? (= (d/attr block "data-is-property") "true")
- properties-area (rec-get-node block "ls-properties-area")]
- (remove (fn [b]
- (and
- (not= b block)
- (or (= (when b (.-top (.getBoundingClientRect b)))
- (when block (.-top (.getBoundingClientRect block))))
- (when property?
- (and (not= (d/attr b "data-is-property") "true")
- (gdom/contains properties-area b)))))) blocks))))
- #?(:cljs
- (defn get-prev-block-non-collapsed
- "Gets previous non-collapsed block. If given a container
- looks up blocks in that container e.g. for embed"
- ([block] (get-prev-block-non-collapsed block {}))
- ([block {:keys [container up-down? exclude-property?]}]
- (when-let [blocks (if container
- (get-blocks-noncollapse container)
- (get-blocks-noncollapse))]
- (let [blocks (cond->>
- (if up-down?
- (skip-same-top-blocks blocks block)
- blocks)
- exclude-property?
- (remove (fn [node] (d/has-class? node "property-value-container"))))]
- (when-let [index (.indexOf blocks block)]
- (let [idx (dec index)]
- (when (>= idx 0)
- (nth-safe blocks idx)))))))))
- #?(:cljs
- (defn get-prev-block-non-collapsed-non-embed
- [block]
- (when-let [blocks (->> (get-blocks-noncollapse)
- remove-embedded-blocks
- remove-property-value-blocks)]
- (when-let [index (.indexOf blocks block)]
- (let [idx (dec index)]
- (when (>= idx 0)
- (nth-safe blocks idx)))))))
- #?(:cljs
- (defn get-next-block-non-collapsed
- [block {:keys [up-down? exclude-property?]}]
- (when-let [blocks (and block (get-blocks-noncollapse))]
- (let [blocks (cond->>
- (if up-down?
- (skip-same-top-blocks blocks block)
- blocks)
- exclude-property?
- (remove (fn [node] (d/has-class? node "property-value-container"))))]
- (when-let [index (.indexOf blocks block)]
- (let [idx (inc index)]
- (when (>= (count blocks) idx)
- (nth-safe blocks idx))))))))
- #?(:cljs
- (defn get-next-block-non-collapsed-skip
- [block]
- (when-let [blocks (get-blocks-noncollapse)]
- (when-let [index (.indexOf blocks block)]
- (loop [idx (inc index)]
- (when (>= (count blocks) idx)
- (let [block (nth-safe blocks idx)
- nested? (->> (array-seq (gdom/getElementsByClass "selected"))
- (some (fn [dom] (.contains dom block))))]
- (if nested?
- (recur (inc idx))
- block))))))))
- (defn rand-str
- [n]
- #?(:cljs (-> (.toString (js/Math.random) 36)
- (.substr 2 n))
- :clj (->> (repeatedly #(Integer/toString (rand 36) 36))
- (take n)
- (apply str))))
- (defn unique-id
- []
- (str (rand-str 6) (rand-str 3)))
- (defn pp-str [x]
- #_:clj-kondo/ignore
- (with-out-str (clojure.pprint/pprint x)))
- (defn hiccup-keywordize
- [hiccup]
- (walk/postwalk
- (fn [f]
- (if (and (vector? f) (string? (first f)))
- (update f 0 keyword)
- f))
- hiccup))
- #?(:cljs
- (defn chrome?
- []
- (let [user-agent js/navigator.userAgent
- vendor js/navigator.vendor]
- (boolean (and (safe-re-find #"Chrome" user-agent)
- (safe-re-find #"Google Inc" vendor))))))
- #?(:cljs
- (defn indexeddb-check?
- "Check if indexedDB support is available, reject if not"
- []
- (let [db-name "logseq-indexeddb-check"]
- (if js/window.indexedDB
- (js/Promise. (fn [resolve reject]
- (let [req (js/window.indexedDB.open db-name)]
- (set! (.-onerror req) reject)
- (set! (.-onsuccess req)
- (fn [_event]
- (.close (.-result req))
- (let [req (js/window.indexedDB.deleteDatabase db-name)]
- (set! (.-onerror req) reject)
- (set! (.-onsuccess req) (fn [_event]
- (resolve true)))))))))
- (p/rejected "no indexeddb defined")))))
- (defonce mac? #?(:cljs goog.userAgent/MAC
- :clj nil))
- (defonce win32? #?(:cljs goog.userAgent/WINDOWS
- :clj nil))
- (defonce linux? #?(:cljs goog.userAgent/LINUX
- :clj nil))
- #?(:cljs
- (defn get-blocks-by-id
- [block-id]
- (when (uuid-string? (str block-id))
- (d/sel (format "[blockid='%s']" (str block-id))))))
- #?(:cljs
- (defn get-first-block-by-id
- [block-id]
- (first (get-blocks-by-id block-id))))
- #?(:cljs
- (defn url-encode
- [string]
- (some-> string str (js/encodeURIComponent) (.replace "+" "%20"))))
- #?(:cljs
- (def page-name-sanity-lc
- "Delegate to common-util to loosely couple app usages to graph-parser"
- common-util/page-name-sanity-lc))
- #?(:cljs
- (def safe-page-name-sanity-lc common-util/safe-page-name-sanity-lc))
- #?(:cljs
- (def get-page-title common-util/get-page-title))
- #?(:cljs
- (defn add-style!
- [style]
- (when (some? style)
- (let [parent-node (d/sel1 :head)
- id "logseq-custom-theme-id"
- old-link-element (d/sel1 (str "#" id))
- style (if (string/starts-with? style "http")
- style
- (str "data:text/css;charset=utf-8," (js/encodeURIComponent style)))]
- (when old-link-element
- (d/remove! old-link-element))
- (let [link (->
- (d/create-element :link)
- (d/set-attr! :id id)
- (d/set-attr! :rel "stylesheet")
- (d/set-attr! :type "text/css")
- (d/set-attr! :href style)
- (d/set-attr! :media "all"))]
- (d/append! parent-node link))
- (set-android-theme)))))
- (defn remove-common-preceding
- [col1 col2]
- (if (and (= (first col1) (first col2))
- (seq col1))
- (recur (rest col1) (rest col2))
- [col1 col2]))
- ;; fs
- #?(:cljs
- (defn get-file-ext
- [file]
- (and
- (string? file)
- (string/includes? file ".")
- (some-> (common-util/path->file-ext file) string/lower-case))))
- #?(:cljs
- (defn get-dir-and-basename
- [path]
- (let [parts (string/split path "/")
- basename (last parts)
- dir (->> (butlast parts)
- string-join-path)]
- [dir basename])))
- #?(:cljs
- (defn get-relative-path
- [current-file-path another-file-path]
- (let [directories-f #(butlast (string/split % "/"))
- parts-1 (directories-f current-file-path)
- parts-2 (directories-f another-file-path)
- [parts-1 parts-2] (remove-common-preceding parts-1 parts-2)
- another-file-name (last (string/split another-file-path "/"))]
- (->> (concat
- (if (seq parts-1)
- (repeat (count parts-1) "..")
- ["."])
- parts-2
- [another-file-name])
- string-join-path))))
- #?(:clj
- (defmacro profile
- [k & body]
- `(if goog.DEBUG
- (let [k# ~k]
- (.time js/console k#)
- (let [res# (do ~@body)]
- (.timeEnd js/console k#)
- res#))
- (do ~@body))))
- #?(:clj
- (defmacro with-time
- "Evaluates expr and prints the time it took.
- Returns the value of expr and the spent time of float number in msecs."
- [expr]
- `(let [start# (cljs.core/system-time)
- ret# ~expr]
- {:result ret#
- :time (- (cljs.core/system-time) start#)})))
- ;; TODO: profile and profileEnd
- (comment
- (= (get-relative-path "journals/2020_11_18.org" "pages/grant_ideas.org")
- "../pages/grant_ideas.org")
- (= (get-relative-path "journals/2020_11_18.org" "journals/2020_11_19.org")
- "./2020_11_19.org")
- (= (get-relative-path "a/b/c/d/g.org" "a/b/c/e/f.org")
- "../e/f.org"))
- (defn keyname [key] (str (namespace key) "/" (name key)))
- ;; FIXME: drain-chan was copied from frontend.worker.util due to shadow-cljs compile bug
- #?(:cljs
- (defn drain-chan
- "drop all stuffs in CH, and return all of them"
- [ch]
- (->> (repeatedly #(async/poll! ch))
- (take-while identity))))
- #?(:cljs
- (defn trace!
- []
- (js/console.trace)))
- #?(:cljs
- (def remove-first common-util/remove-first))
- #?(:cljs
- (defn backward-kill-word
- [input]
- (let [val (.-value input)
- current (get-selection-start input)
- prev (or
- (->> [(string/last-index-of val \space (dec current))
- (string/last-index-of val \newline (dec current))]
- (remove nil?)
- (apply max))
- 0)
- idx (if (zero? prev)
- 0
- (->
- (loop [idx prev]
- (if (#{\space \newline} (nth-safe val idx))
- (recur (dec idx))
- idx))
- inc))]
- (safe-set-range-text! input "" idx current))))
- #?(:cljs
- (defn forward-kill-word
- [input]
- (let [val (.-value input)
- current (get-selection-start input)
- current (loop [idx current]
- (if (#{\space \newline} (nth-safe val idx))
- (recur (inc idx))
- idx))
- idx (or (->> [(string/index-of val \space current)
- (string/index-of val \newline current)]
- (remove nil?)
- (apply min))
- (count val))]
- (safe-set-range-text! input "" current (inc idx)))))
- #?(:cljs
- (defn fix-open-external-with-shift!
- [^js/MouseEvent e]
- (when (and (.-shiftKey e) win32? (electron?)
- (= (string/lower-case (.. e -target -nodeName)) "a")
- (string/starts-with? (.. e -target -href) "file:"))
- (.preventDefault e))))
- (defn classnames
- "Like react classnames utility:
- ```
- [:div {:class (classnames [:a :b {:c true}])}
- ```
- "
- [args]
- (into #{} (mapcat
- #(if (map? %)
- (for [[k v] %]
- (when v (name k)))
- (when-not (nil? %) [(name %)]))
- args)))
- #?(:cljs
- (defn- get-dom-top
- [node]
- (when node
- (gobj/get (.getBoundingClientRect node) "top"))))
- #?(:cljs
- (defn sort-by-height
- [elements]
- (sort (fn [x y]
- (< (get-dom-top x) (get-dom-top y)))
- (remove nil? elements))))
- #?(:cljs
- (defn calc-delta-rect-offset
- [^js/HTMLElement target ^js/HTMLElement container]
- (let [target-rect (bean/->clj (.toJSON (.getBoundingClientRect target)))
- viewport-rect {:width (.-clientWidth container)
- :height (.-clientHeight container)}]
- {:y (- (:height viewport-rect) (:bottom target-rect))
- :x (- (:width viewport-rect) (:right target-rect))})))
- (def regex-char-esc-smap
- (let [esc-chars "{}[]()&^%$#!?*.+|\\"]
- (zipmap esc-chars
- (map #(str "\\" %) esc-chars))))
- (defn regex-escape
- "Escape all regex meta chars in text."
- [text]
- (string/join (replace regex-char-esc-smap text)))
- (comment
- (re-matches (re-pattern (regex-escape "$u^8(d)+w.*[dw]d?")) "$u^8(d)+w.*[dw]d?"))
- #?(:cljs
- (defn meta-key? [e]
- (if mac?
- (gobj/get e "metaKey")
- (gobj/get e "ctrlKey"))))
- #?(:cljs
- (defn shift-key? [e]
- (gobj/get e "shiftKey")))
- #?(:cljs
- (defn right-click?
- [e]
- (let [which (gobj/get e "which")
- button (gobj/get e "button")]
- (or (= which 3)
- (= button 2)))))
- (def keyboard-height (atom nil))
- #?(:cljs
- (defn scroll-editor-cursor
- [^js/HTMLElement el & {:keys [to-vw-one-quarter?]}]
- (when (and el (mobile?))
- (let [box-rect (.getBoundingClientRect el)
- box-top (.-top box-rect)
- box-bottom (.-bottom box-rect)
- header-height (-> (gdom/getElementByClass "cp__header")
- .-clientHeight)
- main-node (app-scroll-container-node el)
- scroll-top' (.-scrollTop main-node)
- current-pos (get-selection-start el)
- grapheme-pos (get-graphemes-pos (.-value el) current-pos)
- mock-text (some-> (gdom/getElement "mock-text")
- gdom/getChildren
- array-seq
- (nth-safe grapheme-pos))
- offset-top (and mock-text (.-offsetTop mock-text))
- offset-height (and mock-text (.-offsetHeight mock-text))
- cursor-y (if offset-top (+ offset-top box-top offset-height 2) box-bottom)
- vw-height (or (.-height js/window.visualViewport)
- (.-clientHeight js/document.documentElement))
- ;; mobile toolbar height: 40px
- scroll (- cursor-y (- vw-height (+ @keyboard-height (+ 40 4))))]
- (cond
- (and to-vw-one-quarter? (> cursor-y (* vw-height 0.4)))
- (set! (.-scrollTop main-node) (+ scroll-top' (- cursor-y (/ vw-height 4))))
- (and (< cursor-y (+ header-height offset-height 4)) ;; 4 is top+bottom padding for per line
- (>= cursor-y header-height))
- (.scrollBy main-node (bean/->js {:top (- (+ offset-height 4))}))
- (< cursor-y header-height)
- (let [_ (.scrollIntoView el true)
- main-node (app-scroll-container-node el)
- scroll-top' (.-scrollTop main-node)]
- (set! (.-scrollTop main-node) (- scroll-top' (/ vw-height 4))))
- (> scroll 0)
- (set! (.-scrollTop main-node) (+ scroll-top' scroll))
- :else
- nil)))))
- #?(:cljs
- (do
- (defn breakpoint?
- [size]
- (< (.-offsetWidth js/document.documentElement) size))
- (defn sm-breakpoint?
- [] (breakpoint? 640))))
- #?(:cljs
- (do
- (defn goog-event?
- [^js e]
- (and e (fn? (gobj/get e "getBrowserEvent"))))
- (defn goog-event-is-composing?
- "Check if keydown event is a composing (IME) event.
- Ignore the IME process by default."
- ([^js e]
- (goog-event-is-composing? e false))
- ([^js e include-process?]
- (when (goog-event? e)
- (let [event-composing? (some-> (.getBrowserEvent e) (.-isComposing))]
- (if include-process?
- (or event-composing?
- (= (gobj/get e "keyCode") 229)
- (= (gobj/get e "key") "Process"))
- event-composing?)))))))
- #?(:cljs
- (defn native-event-is-composing?
- "Check if onchange event of Input is a composing (IME) event.
- Always ignore the IME process."
- [^js e]
- (when-let [^js native-event
- (and e (cond
- (goog-event? e)
- (.getBrowserEvent e)
- (js-in "_reactName" e)
- (.-nativeEvent e)
- :else e))]
- (.-isComposing native-event))))
- #?(:cljs
- (defn open-url
- [url]
- (let [route? (or (string/starts-with? url
- (string/replace js/location.href js/location.hash ""))
- (string/starts-with? url "#"))]
- (if (and (not route?) (electron?))
- (js/window.apis.openExternal url)
- (set! (.-href js/window.location) url)))))
- (defn collapsed?
- [block]
- (:block/collapsed? block))
- ;; https://stackoverflow.com/questions/32511405/how-would-time-ago-function-implementation-look-like-in-clojure
- #?(:cljs
- (defn human-time
- "time: inst-ms or js/Date"
- [time & {:keys [ago? after?]
- :or {ago? true
- after? false}}]
- (let [ago? (if after? false ago?)
- units [{:name "second" :limit 60 :in-second 1}
- {:name "minute" :limit 3600 :in-second 60}
- {:name "hour" :limit 86400 :in-second 3600}
- {:name "day" :limit 604800 :in-second 86400}
- {:name "week" :limit 2629743 :in-second 604800}
- {:name "month" :limit 31556926 :in-second 2629743}
- {:name "year" :limit js/Number.MAX_SAFE_INTEGER :in-second 31556926}]
- time' (if (instance? js/Date time) time (js/Date. time))
- now (t/now)
- diff (t/in-seconds (if ago? (t/interval time' now) (t/interval now time')))]
- (if (< diff 5)
- (if ago? "just now" (str diff "seconds"))
- (let [unit (first (drop-while #(or (>= diff (:limit %))
- (not (:limit %)))
- units))]
- (-> (/ diff (:in-second unit))
- Math/floor
- int
- (#(str % " " (:name unit) (when (> % 1) "s")
- (when ago? " ago")
- (when after? " later")))))))))
- #?(:cljs
- (def JS_ROOT
- (when-not node-test?
- (if (= js/location.protocol "file:")
- "./js"
- "./static/js"))))
- #?(:cljs
- (defn js-load$
- [url]
- (p/create
- (fn [resolve]
- (load url resolve)))))
- #?(:cljs
- (defn css-load$
- ([url] (css-load$ url nil))
- ([url id]
- (p/create
- (fn [resolve reject]
- (let [id (str "css-load-" (or id url))]
- (if-not (gdom/getElement id)
- (let [^js link (js/document.createElement "link")]
- (set! (.-id link) id)
- (set! (.-rel link) "stylesheet")
- (set! (.-href link) url)
- (set! (.-onload link) resolve)
- (set! (.-onerror link) reject)
- (.append (.-head js/document) link))
- (resolve))))))))
- #?(:cljs
- (defn image-blob->png
- [blob cb]
- (let [image (js/Image.)
- off-canvas (js/document.createElement "canvas")
- data-url (js/URL.createObjectURL blob)
- ctx (.getContext off-canvas "2d")]
- (set! (.-onload image)
- #(let [width (.-width image)
- height (.-height image)]
- (set! (.-width off-canvas) width)
- (set! (.-height off-canvas) height)
- (.drawImage ctx image 0 0 width height)
- (.toBlob off-canvas cb)))
- (set! (.-src image) data-url))))
- #?(:cljs
- (defn write-blob-to-clipboard
- [blob]
- (->> blob
- (js-obj (.-type blob))
- (js/ClipboardItem.)
- (array)
- (js/navigator.clipboard.write))))
- #?(:cljs
- (defn copy-image-to-clipboard
- [src]
- (-> (js/fetch src)
- (.then (fn [data]
- (-> (.blob data)
- (.then (fn [blob]
- (if (= (.-type blob) "image/png")
- (write-blob-to-clipboard blob)
- (image-blob->png blob write-blob-to-clipboard))))
- (.catch js/console.error)))))))
- (defn memoize-last
- "Different from core.memoize, it only cache the last result.
- Returns a memoized version of a referentially transparent function. The
- memoized version of the function cache the the last result, and replay when calls
- with the same arguments, or update cache when with different arguments."
- [f]
- (let [last-mem (atom nil)
- last-args (atom nil)]
- (fn [& args]
- (if (or (nil? @last-mem)
- (not= @last-args args))
- (let [ret (apply f args)]
- (reset! last-args args)
- (reset! last-mem ret)
- ret)
- @last-mem))))
- #?(:cljs
- (do
- (defn <app-wake-up-from-sleep-loop
- "start a async/go-loop to check the app awake from sleep.
- Use (async/tap `pubsub/app-wake-up-from-sleep-mult`) to receive messages.
- Arg *stop: atom, reset to true to stop the loop"
- [*stop]
- (let [*last-activated-at (volatile! (tc/to-epoch (t/now)))]
- (async/go-loop []
- (if @*stop
- (println :<app-wake-up-from-sleep-loop :stop)
- (let [now-epoch (tc/to-epoch (t/now))]
- (when (< @*last-activated-at (- now-epoch 10))
- (async/>! pubsub/app-wake-up-from-sleep-ch {:last-activated-at @*last-activated-at :now now-epoch}))
- (vreset! *last-activated-at now-epoch)
- (async/<! (async/timeout 5000))
- (recur))))))))
- (defmacro concatv
- "Vector version of concat. non-lazy"
- [& args]
- `(vec (concat ~@args)))
- (defmacro mapcatv
- "Vector version of mapcat. non-lazy"
- [f coll & colls]
- `(vec (mapcat ~f ~coll ~@colls)))
- (defmacro removev
- "Vector version of remove. non-lazy"
- [pred coll]
- `(vec (remove ~pred ~coll)))
- ;; from rum
- #?(:cljs
- (def schedule
- (or (and (exists? js/window)
- (or js/window.requestAnimationFrame
- js/window.webkitRequestAnimationFrame
- js/window.mozRequestAnimationFrame
- js/window.msRequestAnimationFrame))
- #(js/setTimeout % 16))))
- #?(:cljs
- (defn parse-params
- "Parse URL parameters in hash(fragment) into a hashmap"
- []
- (if node-test?
- {}
- (when-let [fragment (-> js/window
- (.-location)
- (.-hash)
- not-empty)]
- (when (string/starts-with? fragment "#/?")
- (->> (subs fragment 2)
- (new js/URLSearchParams)
- (seq)
- (js->clj)
- (into {})
- (walk/keywordize-keys)))))))
- #?(:cljs
- (defn get-cm-instance
- [^js target]
- (when target
- (some-> target (.querySelector ".CodeMirror") (.-CodeMirror)))))
|