Skip to content

Instantly share code, notes, and snippets.

@LuisThiamNye
Created March 1, 2021 17:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LuisThiamNye/ec041c7e2b044ad4976e512784cb5554 to your computer and use it in GitHub Desktop.
Save LuisThiamNye/ec041c7e2b044ad4976e512784cb5554 to your computer and use it in GitHub Desktop.
Roam Regex Search & Replace
(ns regex-replace.core
(:require
[roam.block :as block]
[roam.datascript :as d]
[reagent.core :as r]
[clojure.string]))
(defn block-replace-txs [match replacement]
(into []
(comp
(map (fn [datom]
(-> datom :e d/entity (select-keys [:block/string :block/uid])
(as-> b
(assoc b :new-str
(clojure.string/replace (:block/string b)
match
replacement))))))
(filter (fn [b]
(not (identical? (:block/string b)
(:new-str b)))))
(map (fn [b]
{:block {:uid (:block/uid b)
:string (:new-str b)}})))
(d/datoms :aevt :block/string)))
(defn component [_]
(let [match-str (r/atom "")
replacement (r/atom "")
regex? (r/atom false)
default-msg {:error nil
:change-count nil}
msg (r/atom default-msg)
do-replace (fn []
(reset! msg default-msg)
(let [b-txs (block-replace-txs (cond-> @match-str
@regex? re-pattern)
@replacement)]
(if (< 300 (count b-txs))
(swap! msg merge {:change-count 0
:error (str "Matches "
(count b-txs)
" blocks, exceeding the limit of 300.")})
(try
(doseq [b-tx b-txs]
(block/update b-tx))
(catch js/Error e
(swap! msg assoc :error (.-message e)))
(finally
(swap! msg assoc :change-count (count b-txs)))))))]
(fn [_]
(let [change-count (:change-count @msg)
error (:error @msg)]
[:div.rsr-container {:style {:display :flex
:padding "10px"
:border "7px solid red"
:border-radius "7px"
:box-shadow "0px 3px 10px rgb(0 0 0 / 25%)"
:flex-direction :column}}
[:style "
.rsr-container>label, .rsr-container>div {
display: inline-flex;
align-items: center;
}
.rsr-container > label > input[type=text] {
flex-grow: 1;
}
.rsr-container button {
background: red;
}
.rsr-container button[disabled] {
background: grey;
}
"]
[:div {:style {:font-size "1.25em"
:margin-bottom "0.5em"}}
"Regex Search & Replace"]
[:label "Match: "
[:input {:value @match-str
:type :text
:on-change #(reset! match-str (.. % -target -value))}]]
[:label "Regex? "
[:input {:type :checkbox
:value @regex?
:on-change #(swap! regex? not)}]]
[:label "Replacement: "
[:input {:value @replacement
:type :text
:on-change #(reset! replacement (.. % -target -value))}]]
(when (some? error)
[:span {:style {:color :red
:font-weight :bold}}
"ERROR: " error])
(when (some? change-count)
[:span change-count " blocks have been updated."])
[:div {:style {:justify-content :flex-end
:font-size "0.8em"
:margin-top "20px"}}
[:span "Make sure you know what you are doing: "]
[:button {:on-click do-replace
:style {:color :white}
:disabled (some clojure.string/blank? [@match-str @replacement])}
"Replace text"]]]))))
@LuisThiamNye
Copy link
Author

Search and replace text across all blocks in your graph, optionally using regular expressions.

Usage:
{{[[roam/render]]: ((ref-to-clojure-codeblock))}}

Notes:

  • Limitation: The Roam Alpha API only allows a maximum of 300 blocks to be modified per 60 seconds.
  • Cmd/Ctrl-Z is effective in undoing the changes.
  • The search and replace is powered by clojure.string/replace, so supports the same regex patterns.

Screenshot 2021-03-01 at 17 28 54

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment