(ns outliner.model.core.tree) (def root-id "root") (defn root-children [doc] (get-in doc [:nodes root-id :children])) (defn make-doc [root-children-ids nodes] (let [root-node {:id root-id :text "" :parent nil :children (vec root-children-ids) :annotations []} nodes-with-root (assoc nodes root-id root-node) nodes-fixed (reduce (fn [n id] (assoc-in n [id :parent] root-id)) nodes-with-root root-children-ids)] {:nodes nodes-fixed})) (defn- fix-root-children-parents [doc] (let [children (get-in doc [:nodes root-id :children])] (reduce (fn [d id] (if (and (get-in d [:nodes id]) (nil? (get-in d [:nodes id :parent]))) (assoc-in d [:nodes id :parent] root-id) d)) doc children))) (defn migrate-doc [doc] (if (get-in doc [:nodes root-id]) (-> doc (dissoc :root) fix-root-children-parents) (let [old-root (vec (remove #{root-id} (or (:root doc) []))) top-level (if (seq old-root) old-root (vec (keep (fn [[id node]] (when (or (nil? (:parent node)) (= root-id (:parent node))) id)) (:nodes doc)))) root-node {:id root-id :text "" :parent nil :children top-level :annotations []} nodes (reduce (fn [n id] (if (get n id) (assoc-in n [id :parent] root-id) n)) (assoc (:nodes doc) root-id root-node) top-level)] {:nodes nodes :metadata (:metadata doc)}))) (defn index-of [coll x] #?(:cljs (.indexOf (to-array coll) x) :clj (.indexOf ^java.util.List coll x))) (defn get-node [doc id] (get-in doc [:nodes id])) (defn get-parent-id [doc id] (:parent (get-node doc id))) (defn get-siblings-path [doc id] (when-let [parent-id (get-parent-id doc id)] [:nodes parent-id :children])) (defn get-siblings [doc id] (when-let [path (get-siblings-path doc id)] (get-in doc path))) (defn detach-node [doc id] (update-in doc (get-siblings-path doc id) (fn [sib] (vec (filter #(not= % id) sib))))) (defn delete-node-recursive [doc id] (let [node (get-in doc [:nodes id]) children (:children node) doc (reduce delete-node-recursive doc children)] (update doc :nodes dissoc id))) (defn insert-into-siblings [siblings id target-id position] (let [siblings (vec (filter #(not= % id) siblings))] (case position :before (let [idx (index-of siblings target-id)] (if (neg? idx) (vec (cons id siblings)) (vec (concat (take idx siblings) [id] (drop idx siblings))))) :after (let [idx (index-of siblings target-id)] (if (neg? idx) (vec (cons id siblings)) (vec (concat (take (inc idx) siblings) [id] (drop (inc idx) siblings))))) :first-child (vec (cons id siblings)) :last-child (conj (vec siblings) id)))) (defn attach-node ([doc id target-id position] (attach-node doc id target-id position true)) ([doc id target-id position force-expand?] (let [path (case position (:before :after) (get-siblings-path doc target-id) (:first-child :last-child) [:nodes target-id :children]) parent-id (case position (:before :after) (get-parent-id doc target-id) (:first-child :last-child) target-id)] (-> doc (update-in path insert-into-siblings id target-id position) (assoc-in [:nodes id :parent] parent-id) (cond-> (and force-expand? (#{:first-child :last-child} position)) (assoc-in [:nodes target-id :expanded?] true)))))) (defn insert-node ([doc node] (insert-node doc node true)) ([doc node force-expand?] (let [id (:id node) parent-id (or (:parent node) root-id) doc (update-in doc [:nodes id] merge {:children [] :parent nil} node)] (attach-node doc id parent-id :last-child force-expand?)))) (defn find-last-descendant [doc id] (let [node (get-node doc id) children (:children node) expanded? (:expanded? node true)] (if (and expanded? (seq children)) (find-last-descendant doc (last children)) id))) (defn find-prev-id ([doc id] (find-prev-id doc id nil)) ([doc id zoom-id] (if (= id zoom-id) nil (let [siblings (get-siblings doc id) index (index-of siblings id)] (if (and index (> index 0)) (find-last-descendant doc (nth siblings (dec index))) (let [parent-id (get-parent-id doc id)] (when (and parent-id (not= parent-id root-id)) parent-id))))))) (defn find-next-id ([doc id] (find-next-id doc id nil)) ([doc id zoom-id] (let [node (get-node doc id) children (:children node) expanded? (or (= id zoom-id) (:expanded? node true))] (if (and expanded? (seq children)) (first children) (loop [curr-id id] (if (= curr-id zoom-id) nil (let [siblings (get-siblings doc curr-id) index (index-of siblings curr-id)] (if (and (>= index 0) (< index (dec (count siblings)))) (nth siblings (inc index)) (let [parent-id (get-parent-id doc curr-id)] (if (and parent-id (not= parent-id zoom-id) (not= parent-id root-id)) (recur parent-id) nil)))))))))) (defn is-ancestor? [doc potential-ancestor-id id] (loop [curr-id id] (if-let [parent-id (get-parent-id doc curr-id)] (cond (= parent-id potential-ancestor-id) true :else (recur parent-id)) false))) (defn get-path [doc id] (loop [curr-id id path []] (if-let [node (get-node doc curr-id)] (if (= curr-id root-id) (reverse path) (recur (:parent node) (conj path node))) (reverse path))))