Skip to content

Commit

Permalink
feat(copy+paste): respect h1/h2/h3 markdown (#901)
Browse files Browse the repository at this point in the history
  • Loading branch information
tangjeff0 authored Apr 2, 2021
1 parent 2e0d15d commit a2bcef5
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 66 deletions.
2 changes: 1 addition & 1 deletion src/cljs/athens/db.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@


(def block-document-pull-vector
'[:db/id :block/uid :block/string :block/open :block/order {:block/children ...} :block/refs :block/_refs])
'[:db/id :block/uid :block/string :block/open :block/order :block/header {:block/children ...} :block/refs :block/_refs])


(def node-document-pull-vector
Expand Down
19 changes: 14 additions & 5 deletions src/cljs/athens/events.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,8 @@
(defn text-to-blocks
[text uid root-order]
(let [;; Split raw text by line
lines (clojure.string/split-lines text)
lines (->> (clojure.string/split-lines text)
(filter (comp not clojure.string/blank?)))
;; Count left offset
left-counts (->> lines
(map #(re-find #"^\s*(-|\*)?" %))
Expand All @@ -1468,10 +1469,18 @@
lines)
;; Generate blocks with tempids
blocks (map-indexed (fn [idx x]
{:db/id (dec (* -1 idx))
:block/string x
:block/open true
:block/uid (gen-block-uid)}) sanitize)
(let [h1-re #"^#{1}\s"
h2-re #"^#{2}\s"
h3-re #"^#{3}\s"]
(cond->
{:db/id (dec (* -1 idx))
:block/string x
:block/open true
:block/uid (gen-block-uid)}
(re-find h1-re x) (assoc :block/header 1 :block/string (string/replace x h1-re ""))
(re-find h2-re x) (assoc :block/header 2 :block/string (string/replace x h2-re ""))
(re-find h3-re x) (assoc :block/header 3 :block/string (string/replace x h3-re "")))))
sanitize)
;; Count blocks
n (count blocks)
;; Assign parents
Expand Down
16 changes: 10 additions & 6 deletions src/cljs/athens/keybindings.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
start? (block-start? e)
end? (block-end? e)
{:search/keys [results type index] caret-position :caret-position} @state
textarea-height (.. target -offsetHeight)
textarea-height (.. target -offsetHeight) ;; this height is accurate, but caret-position height is not updating
{:keys [top height]} caret-position
rows (js/Math.round (/ textarea-height height))
row (js/Math.ceil (/ top height))
Expand All @@ -317,7 +317,8 @@
up? (= key-code KeyCodes.UP)
down? (= key-code KeyCodes.DOWN)
left? (= key-code KeyCodes.LEFT)
right? (= key-code KeyCodes.RIGHT)]
right? (= key-code KeyCodes.RIGHT)
header (db/v-by-ea (db/e-by-av :block/uid uid) :block/header)]

(cond
;; Shift: select block if leaving block content boundaries (top or bottom rows). Otherwise select textarea text (default)
Expand Down Expand Up @@ -357,12 +358,15 @@
selection? nil

;; Else: navigate across blocks
;; FIX: always navigates up or down for header because get-caret-position for some reason returns the wrong value for top
(or (and up? top-row?)
(and left? start?)) (do (.. e preventDefault)
(dispatch [:up uid]))
(and left? start?)
(and up? header)) (do (.. e preventDefault)
(dispatch [:up uid]))
(or (and down? bottom-row?)
(and right? end?)) (do (.. e preventDefault)
(dispatch [:down uid])))))
(and right? end?)
(and down? header)) (do (.. e preventDefault)
(dispatch [:down uid])))))


;;; Tab
Expand Down
61 changes: 53 additions & 8 deletions src/cljs/athens/listeners.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[athens.util :as util]
[cljsjs.react]
[cljsjs.react.dom]
[clojure.string :as string]
[goog.events :as events]
[re-frame.core :refer [dispatch subscribe]])
(:import
Expand Down Expand Up @@ -110,13 +111,57 @@

;; -- Clipboard ----------------------------------------------------------

(defn walk-str
(defn unformat-double-brackets
"https://github.com/ryanguill/roam-tools/blob/eda72040622555b52e40f7a28a14744bce0496e5/src/index.js#L336-L345"
[s]
(-> s
(string/replace #"\[([^\[\]]+)\]\((\[\[|\(\()([^\[\]]+)(\]\]|\)\))\)" "$1")
(string/replace #"\[\[([^\[\]]+)\]\]" "$1")))


(defn block-refs-to-plain-text
"If there is a valid ((uid)), find the original block's string.
If invalid ((uid)), no-op.
TODO: If deep block ref, convert deep block ref to plain-text.
Want to put this in athens.util, but circular dependency from athens.db"
[s]
(let [replacements (->> s
(re-seq #"\(\(([^\(\)]+)\)\)")
(map (fn [[orig-str match-str]]
(let [eid (db/e-by-av :block/uid match-str)]
(if eid
[orig-str (db/v-by-ea eid :block/string)]
[orig-str (str "((" match-str "))")])))))]
(loop [replacements replacements
s s]
(let [orig-str (first (first replacements))
replace-str (second (first replacements))]
(if (empty? replacements)
s
(recur (rest replacements)
(clojure.string/replace s orig-str replace-str)))))))


(defn blocks-to-clipboard-data
"Four spaces per depth level."
[depth node]
(let [{:block/keys [string children]} node
left-offset (apply str (repeat depth " "))
walk-children (apply str (map #(walk-str (inc depth) %) children))]
(str left-offset "- " string "\n" walk-children)))
([depth node]
(blocks-to-clipboard-data depth node false))
([depth node unformat?]
(let [{:block/keys [string children header]} node
left-offset (apply str (repeat depth " "))
walk-children (apply str (map #(blocks-to-clipboard-data (inc depth) % unformat?) children))
string (let [header-to-str (case header
1 "# "
2 "## "
3 "### "
"")]
(str header-to-str string))
string (if unformat?
(-> string unformat-double-brackets athens.listeners/block-refs-to-plain-text)
string)
dash (if unformat? "" "- ")]
(str left-offset dash string "\n" walk-children))))


(defn copy
Expand All @@ -126,7 +171,7 @@
(let [uids @(subscribe [:selected/items])]
(when (not-empty uids)
(let [copy-data (->> (map #(db/get-block-document [:block/uid %]) uids)
(map #(walk-str 0 %))
(map #(blocks-to-clipboard-data 0 %))
(apply str))]
(.. e preventDefault)
(.. e -event_ -clipboardData (setData "text/plain" copy-data))))))
Expand All @@ -138,7 +183,7 @@
(let [uids @(subscribe [:selected/items])]
(when (not-empty uids)
(let [copy-data (->> (map #(db/get-block-document [:block/uid %]) uids)
(map #(walk-str 0 %))
(map #(blocks-to-clipboard-data 0 %))
(apply str))]
(.. e preventDefault)
(.. e -event_ -clipboardData (setData "text/plain" copy-data))
Expand Down
59 changes: 13 additions & 46 deletions src/cljs/athens/views/blocks.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
auto-complete-inline
auto-complete-slash
textarea-key-down]]
[athens.listeners :as listeners]
[athens.parse-renderer :refer [parse-and-render]]
[athens.router :refer [navigate-uid]]
[athens.style :refer [color DEPTH-SHADOWS OPACITIES ZINDICES]]
Expand Down Expand Up @@ -618,11 +619,16 @@
is-editing can be used for shift up/down, so it is used in both editing and selection."
[_ _]
(fn [block state]
(let [{:block/keys [uid original-uid]} block
(let [{:block/keys [uid original-uid header]} block
{:string/keys [local]} @state
is-editing @(subscribe [:editing/is-editing uid])
selected-items @(subscribe [:selected/items])]
[:div {:class "block-content"}
selected-items @(subscribe [:selected/items])
font-size (case header
1 "2.1em"
2 "1.7em"
3 "1.3em"
"1em")]
[:div {:class "block-content" :style {:font-size font-size}}
[autosize/textarea {:value (:string/local @state)
:class ["textarea" (when (and (empty? selected-items) is-editing) "is-editing")]
;;:auto-focus true
Expand Down Expand Up @@ -711,55 +717,16 @@
(swap! state assoc :context-menu/show false)))


(defn unformat-double-brackets
"https://github.com/ryanguill/roam-tools/blob/eda72040622555b52e40f7a28a14744bce0496e5/src/index.js#L336-L345"
[s]
(-> s
(str/replace #"\[([^\[\]]+)\]\((\[\[|\(\()([^\[\]]+)(\]\]|\)\))\)" "$1")
(str/replace #"\[\[([^\[\]]+)\]\]" "$1")))


(defn block-refs-to-plain-text
"If there is a valid ((uid)), find the original block's string.
If invalid ((uid)), no-op.
TODO: If deep block ref, convert deep block ref to plain-text."
[s]
(let [replacements (->> s
(re-seq #"\(\(([^\(\)]+)\)\)")
(map (fn [[orig-str match-str]]
(let [eid (db/e-by-av :block/uid match-str)]
(if eid
[orig-str (db/v-by-ea eid :block/string)]
[orig-str (str "((" match-str "))")])))))]
(loop [replacements replacements
s s]
(let [orig-str (first (first replacements))
replace-str (second (first replacements))]
(if (empty? replacements)
s
(recur (rest replacements)
(str/replace s orig-str replace-str)))))))


(defn unformat-walk-str
"Same as walk-str in athens.listeners, except unformats double brackets, turns block refs into plain text, and does not add hyphens."
[depth node]
(let [{:block/keys [string children]} node
left-offset (apply str (repeat depth " "))
walk-children (apply str (map #(unformat-walk-str (inc depth) %) children))
unformatted-string (-> string unformat-double-brackets block-refs-to-plain-text)]
(str left-offset unformatted-string "\n" walk-children)))


(defn handle-copy-unformatted
"If copying only a single block, dissoc children to not copy subtree."
[^js e uid state]
(let [uids @(subscribe [:selected/items])]
(if (empty? uids)
(let [block (db/get-block-document [:block/uid uid])
data (unformat-walk-str 0 block)]
(let [block (dissoc (db/get-block-document [:block/uid uid]) :block/children)
data (listeners/blocks-to-clipboard-data 0 block true)]
(.. js/navigator -clipboard (writeText data)))
(let [data (->> (map #(db/get-block-document [:block/uid %]) uids)
(map #(unformat-walk-str 0 %))
(map #(listeners/blocks-to-clipboard-data 0 % true))
(apply str))]
(.. js/navigator -clipboard (writeText data)))))
(.. e preventDefault)
Expand Down

0 comments on commit a2bcef5

Please sign in to comment.