diff --git a/src/cljs/athens/db.cljs b/src/cljs/athens/db.cljs index 9aa39fc43a..5529538731 100644 --- a/src/cljs/athens/db.cljs +++ b/src/cljs/athens/db.cljs @@ -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 diff --git a/src/cljs/athens/events.cljs b/src/cljs/athens/events.cljs index fefaecc52f..7f6dc6cfe5 100644 --- a/src/cljs/athens/events.cljs +++ b/src/cljs/athens/events.cljs @@ -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*(-|\*)?" %)) @@ -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 diff --git a/src/cljs/athens/keybindings.cljs b/src/cljs/athens/keybindings.cljs index 7c5ab06634..b55aecd5d7 100644 --- a/src/cljs/athens/keybindings.cljs +++ b/src/cljs/athens/keybindings.cljs @@ -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)) @@ -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) @@ -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 diff --git a/src/cljs/athens/listeners.cljs b/src/cljs/athens/listeners.cljs index 3174650b59..ba4cc84077 100644 --- a/src/cljs/athens/listeners.cljs +++ b/src/cljs/athens/listeners.cljs @@ -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 @@ -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 @@ -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)))))) @@ -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)) diff --git a/src/cljs/athens/views/blocks.cljs b/src/cljs/athens/views/blocks.cljs index 5bc3c074e7..b4a3af31b1 100644 --- a/src/cljs/athens/views/blocks.cljs +++ b/src/cljs/athens/views/blocks.cljs @@ -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]] @@ -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 @@ -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)