|
@@ -3,189 +3,34 @@
|
|
|
exclude some fns which produce lazy-seq, which can cause strange behaviors
|
|
|
when use together with dynamic var."
|
|
|
(:refer-clojure :exclude [map filter mapcat concat remove])
|
|
|
- (:require [cljs.core.match :refer [match]]
|
|
|
- [clojure.string :as string]
|
|
|
- [frontend.common.file.core :as common-file]
|
|
|
- [frontend.db :as db]
|
|
|
- [frontend.format.mldoc :as mldoc]
|
|
|
- [frontend.modules.file.core :as outliner-file]
|
|
|
- [frontend.modules.outliner.tree :as outliner-tree]
|
|
|
+ (:require [clojure.string :as string]
|
|
|
+ [frontend.db.conn :as conn]
|
|
|
[frontend.state :as state]
|
|
|
- [frontend.util :as util :refer [concatv mapcatv removev]]
|
|
|
- [malli.core :as m]
|
|
|
- [malli.util :as mu]
|
|
|
+ [logseq.cli.common.export.common :as cli-export-common]
|
|
|
[promesa.core :as p]))
|
|
|
|
|
|
-;;; TODO: split frontend.handler.export.text related states
|
|
|
-(def ^:dynamic *state*
|
|
|
- "dynamic var, state used for exporting"
|
|
|
- {;; current level of Heading, start from 1(same as mldoc), use when `block-ast->simple-ast`
|
|
|
- :current-level 1
|
|
|
- ;; emphasis symbol (use when `block-ast->simple-ast`)
|
|
|
- :outside-em-symbol nil
|
|
|
- ;; (use when `block-ast->simple-ast`)
|
|
|
- :indent-after-break-line? false
|
|
|
- ;; TODO: :last-empty-heading? false
|
|
|
- ;; current: | want:
|
|
|
- ;; - | - xxx
|
|
|
- ;; xxx | yyy
|
|
|
- ;; yyy |
|
|
|
-
|
|
|
- ;; this submap is used when replace block-reference, block-embed, page-embed
|
|
|
- :replace-ref-embed
|
|
|
- {;; start from 1
|
|
|
- :current-level 1
|
|
|
- :block-ref-replaced? false
|
|
|
- :block&page-embed-replaced? false}
|
|
|
-
|
|
|
- ;; submap for :newline-after-block internal state
|
|
|
- :newline-after-block
|
|
|
- {:current-block-is-first-heading-block? true}
|
|
|
-
|
|
|
- ;; export-options submap
|
|
|
- :export-options
|
|
|
- {;; dashes, spaces, no-indent
|
|
|
- :indent-style "dashes"
|
|
|
- :remove-page-ref-brackets? false
|
|
|
- :remove-emphasis? false
|
|
|
- :remove-tags? false
|
|
|
- :remove-properties? true
|
|
|
- :keep-only-level<=N :all
|
|
|
- :newline-after-block false}})
|
|
|
-
|
|
|
-;;; internal utils
|
|
|
-(defn- get-blocks-contents
|
|
|
- [repo root-block-uuid & {:keys [init-level]
|
|
|
- :or {init-level 1}}]
|
|
|
- (let [block (db/entity [:block/uuid root-block-uuid])
|
|
|
- link (:block/link block)
|
|
|
- block' (or link block)
|
|
|
- root-id (:block/uuid block')
|
|
|
- blocks (db/get-block-and-children repo root-id)]
|
|
|
- (-> (outliner-tree/blocks->vec-tree repo blocks root-id {:link link})
|
|
|
- (outliner-file/tree->file-content {:init-level init-level
|
|
|
- :link link}))))
|
|
|
+(defn get-content-config []
|
|
|
+ {:export-bullet-indentation (state/get-export-bullet-indentation)})
|
|
|
|
|
|
(defn root-block-uuids->content
|
|
|
+ "Converts given block uuids to content for given repo"
|
|
|
[repo root-block-uuids]
|
|
|
- (let [contents (mapv (fn [id]
|
|
|
- (get-blocks-contents repo id)) root-block-uuids)]
|
|
|
- (string/join "\n" (mapv string/trim-newline contents))))
|
|
|
-
|
|
|
-(declare remove-block-ast-pos Properties-block-ast?)
|
|
|
-
|
|
|
-(defn- block-uuid->ast
|
|
|
- [block-uuid]
|
|
|
- (let [block (into {} (db/get-block-by-uuid block-uuid))
|
|
|
- content (outliner-file/tree->file-content [block] {:init-level 1})
|
|
|
- format :markdown]
|
|
|
- (when content
|
|
|
- (removev Properties-block-ast?
|
|
|
- (mapv remove-block-ast-pos
|
|
|
- (mldoc/->edn content format))))))
|
|
|
-
|
|
|
-(defn- block-uuid->ast-with-children
|
|
|
- [block-uuid]
|
|
|
- (let [content (get-blocks-contents (state/get-current-repo) block-uuid)
|
|
|
- format :markdown]
|
|
|
- (when content
|
|
|
- (removev Properties-block-ast?
|
|
|
- (mapv remove-block-ast-pos
|
|
|
- (mldoc/->edn content format))))))
|
|
|
+ (binding [cli-export-common/*current-repo* repo
|
|
|
+ cli-export-common/*current-db* (conn/get-db repo)
|
|
|
+ cli-export-common/*content-config* (get-content-config)]
|
|
|
+ (let [contents (mapv (fn [id]
|
|
|
+ (cli-export-common/get-blocks-contents repo id)) root-block-uuids)]
|
|
|
+ (string/join "\n" (mapv string/trim-newline contents)))))
|
|
|
|
|
|
(defn get-page-content
|
|
|
+ "Gets page content for current repo, db and state"
|
|
|
[page-uuid]
|
|
|
- (let [repo (state/get-current-repo)
|
|
|
- db (db/get-db repo)]
|
|
|
- (common-file/block->content repo db page-uuid
|
|
|
- nil
|
|
|
- {:export-bullet-indentation (state/get-export-bullet-indentation)})))
|
|
|
-
|
|
|
-(defn- page-name->ast
|
|
|
- [page-name]
|
|
|
- (let [page (db/get-page page-name)]
|
|
|
- (when-let [content (get-page-content (:block/uuid page))]
|
|
|
- (when content
|
|
|
- (let [format :markdown]
|
|
|
- (removev Properties-block-ast?
|
|
|
- (mapv remove-block-ast-pos
|
|
|
- (mldoc/->edn content format))))))))
|
|
|
-
|
|
|
-(defn- update-level-in-block-ast-coll
|
|
|
- [block-ast-coll origin-level]
|
|
|
- (mapv
|
|
|
- (fn [block-ast]
|
|
|
- (let [[ast-type ast-content] block-ast]
|
|
|
- (if (= ast-type "Heading")
|
|
|
- [ast-type (update ast-content :level #(+ (dec %) origin-level))]
|
|
|
- block-ast)))
|
|
|
- block-ast-coll))
|
|
|
-
|
|
|
-(defn- plain-indent-inline-ast
|
|
|
- [level & {:keys [spaces] :or {spaces " "}}]
|
|
|
- ["Plain" (str (reduce str (repeat (dec level) "\t")) spaces)])
|
|
|
-
|
|
|
-(defn- mk-paragraph-ast
|
|
|
- [inline-coll meta]
|
|
|
- (with-meta ["Paragraph" inline-coll] meta))
|
|
|
-
|
|
|
-;;; internal utils (ends)
|
|
|
-
|
|
|
-;;; utils
|
|
|
-
|
|
|
-(defn priority->string
|
|
|
- [priority]
|
|
|
- (str "[#" priority "]"))
|
|
|
-
|
|
|
-(defn- repetition-to-string
|
|
|
- [[[kind] [duration] n]]
|
|
|
- (let [kind (case kind
|
|
|
- "Dotted" "."
|
|
|
- "Plus" "+"
|
|
|
- "DoublePlus" "++")]
|
|
|
- (str kind n (string/lower-case (str (first duration))))))
|
|
|
-
|
|
|
-(defn timestamp-to-string
|
|
|
- [{:keys [date time repetition wday active]}]
|
|
|
- (let [{:keys [year month day]} date
|
|
|
- {:keys [hour min]} time
|
|
|
- [open close] (if active ["<" ">"] ["[" "]"])
|
|
|
- repetition (if repetition
|
|
|
- (str " " (repetition-to-string repetition))
|
|
|
- "")
|
|
|
- hour (when hour (util/zero-pad hour))
|
|
|
- min (when min (util/zero-pad min))
|
|
|
- time (cond
|
|
|
- (and hour min)
|
|
|
- (util/format " %s:%s" hour min)
|
|
|
- hour
|
|
|
- (util/format " %s" hour)
|
|
|
- :else
|
|
|
- "")]
|
|
|
- (util/format "%s%s-%s-%s %s%s%s%s"
|
|
|
- open
|
|
|
- (str year)
|
|
|
- (util/zero-pad month)
|
|
|
- (util/zero-pad day)
|
|
|
- wday
|
|
|
- time
|
|
|
- repetition
|
|
|
- close)))
|
|
|
-(defn hashtag-value->string
|
|
|
- [inline-coll]
|
|
|
- (reduce str
|
|
|
- (mapv
|
|
|
- (fn [inline]
|
|
|
- (let [[ast-type ast-content] inline]
|
|
|
- (case ast-type
|
|
|
- "Nested_link"
|
|
|
- (:content ast-content)
|
|
|
- "Link"
|
|
|
- (:full_text ast-content)
|
|
|
- "Plain"
|
|
|
- ast-content)))
|
|
|
- inline-coll)))
|
|
|
+ (binding [cli-export-common/*current-repo* (state/get-current-repo)
|
|
|
+ cli-export-common/*current-db* (conn/get-db (state/get-current-repo))
|
|
|
+ cli-export-common/*content-config* (get-content-config)]
|
|
|
+ (cli-export-common/get-page-content page-uuid)))
|
|
|
|
|
|
+;; Utils
|
|
|
(defn <get-all-pages
|
|
|
[repo]
|
|
|
(state/<invoke-db-worker :thread-api/export-get-all-pages repo))
|
|
@@ -209,625 +54,19 @@
|
|
|
:format :markdown})
|
|
|
page->content)))
|
|
|
|
|
|
-;;; utils (ends)
|
|
|
-
|
|
|
-;;; replace block-ref, block-embed, page-embed
|
|
|
-
|
|
|
-(defn- replace-block-reference-in-heading
|
|
|
- [{:keys [title] :as ast-content}]
|
|
|
- (let [inline-coll title
|
|
|
- inline-coll*
|
|
|
- (mapcatv
|
|
|
- #(match [%]
|
|
|
- [["Link" {:url ["Block_ref" block-uuid]}]]
|
|
|
- (let [[[_ {title-inline-coll :title}]]
|
|
|
- (block-uuid->ast (uuid block-uuid))]
|
|
|
- (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
|
|
|
- title-inline-coll)
|
|
|
-
|
|
|
- :else [%])
|
|
|
- inline-coll)]
|
|
|
- (assoc ast-content :title inline-coll*)))
|
|
|
-
|
|
|
-(defn- replace-block-reference-in-paragraph
|
|
|
- [inline-coll]
|
|
|
- (mapcatv
|
|
|
- #(match [%]
|
|
|
- [["Link" {:url ["Block_ref" block-uuid]}]]
|
|
|
- (let [[[_ {title-inline-coll :title}]]
|
|
|
- (block-uuid->ast (uuid block-uuid))]
|
|
|
- (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
|
|
|
- title-inline-coll)
|
|
|
- :else [%])
|
|
|
- inline-coll))
|
|
|
-
|
|
|
-(declare replace-block-references)
|
|
|
-
|
|
|
-(defn- replace-block-reference-in-list
|
|
|
- [list-items]
|
|
|
- (mapv
|
|
|
- (fn [{block-ast-coll :content sub-items :items :as item}]
|
|
|
- (assoc item
|
|
|
- :content (mapv replace-block-references block-ast-coll)
|
|
|
- :items (replace-block-reference-in-list sub-items)))
|
|
|
- list-items))
|
|
|
-
|
|
|
-(defn- replace-block-reference-in-quote
|
|
|
- [block-ast-coll]
|
|
|
- (mapv replace-block-references block-ast-coll))
|
|
|
-
|
|
|
-(defn- replace-block-reference-in-table
|
|
|
- [{:keys [header groups] :as table}]
|
|
|
- (let [header*
|
|
|
- (mapv
|
|
|
- (fn [col]
|
|
|
- (mapcatv
|
|
|
- #(match [%]
|
|
|
- [["Link" {:url ["Block_ref" block-uuid]}]]
|
|
|
- (let [[[_ {title-inline-coll :title}]]
|
|
|
- (block-uuid->ast (uuid block-uuid))]
|
|
|
- (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
|
|
|
- title-inline-coll)
|
|
|
- :else [%])
|
|
|
- col))
|
|
|
- header)
|
|
|
- groups*
|
|
|
- (mapv
|
|
|
- (fn [group]
|
|
|
- (mapv
|
|
|
- (fn [row]
|
|
|
- (mapv
|
|
|
- (fn [col]
|
|
|
- (mapcatv
|
|
|
- #(match [%]
|
|
|
- [["Link" {:url ["Block_ref" block-uuid]}]]
|
|
|
- (let [[[_ {title-inline-coll :title}]]
|
|
|
- (block-uuid->ast (uuid block-uuid))]
|
|
|
- (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] true))
|
|
|
- title-inline-coll)
|
|
|
- :else [%])
|
|
|
- col))
|
|
|
- row))
|
|
|
- group))
|
|
|
- groups)]
|
|
|
- (assoc table :header header* :groups groups*)))
|
|
|
-
|
|
|
-(defn- replace-block-references
|
|
|
- [block-ast]
|
|
|
- (let [[ast-type ast-content] block-ast]
|
|
|
- (case ast-type
|
|
|
- "Heading"
|
|
|
- [ast-type (replace-block-reference-in-heading ast-content)]
|
|
|
-
|
|
|
- "Paragraph"
|
|
|
- (mk-paragraph-ast (replace-block-reference-in-paragraph ast-content) (meta block-ast))
|
|
|
-
|
|
|
- "List"
|
|
|
- [ast-type (replace-block-reference-in-list ast-content)]
|
|
|
-
|
|
|
- "Quote"
|
|
|
- [ast-type (replace-block-reference-in-quote ast-content)]
|
|
|
-
|
|
|
- "Table"
|
|
|
- [ast-type (replace-block-reference-in-table ast-content)]
|
|
|
- ;; else
|
|
|
- block-ast)))
|
|
|
-
|
|
|
-(defn- replace-block-references-until-stable
|
|
|
- [block-ast]
|
|
|
- (binding [*state* *state*]
|
|
|
- (loop [block-ast block-ast]
|
|
|
- (let [block-ast* (replace-block-references block-ast)]
|
|
|
- (if (get-in *state* [:replace-ref-embed :block-ref-replaced?])
|
|
|
- (do (set! *state* (assoc-in *state* [:replace-ref-embed :block-ref-replaced?] false))
|
|
|
- (recur block-ast*))
|
|
|
- block-ast*)))))
|
|
|
-
|
|
|
-(defn- replace-block-embeds-helper
|
|
|
- [current-paragraph-inlines block-uuid blocks-tcoll level]
|
|
|
- (let [block-uuid* (subs block-uuid 2 (- (count block-uuid) 2))
|
|
|
- ast-coll (update-level-in-block-ast-coll
|
|
|
- (block-uuid->ast-with-children (uuid block-uuid*))
|
|
|
- level)]
|
|
|
- (cond-> blocks-tcoll
|
|
|
- (seq current-paragraph-inlines)
|
|
|
- (conj! ["Paragraph" current-paragraph-inlines])
|
|
|
- true
|
|
|
- (#(reduce conj! % ast-coll)))))
|
|
|
-
|
|
|
-(defn- replace-page-embeds-helper
|
|
|
- [current-paragraph-inlines page-name blocks-tcoll level]
|
|
|
- (let [page-name* (subs page-name 2 (- (count page-name) 2))
|
|
|
- ast-coll (update-level-in-block-ast-coll
|
|
|
- (page-name->ast page-name*)
|
|
|
- level)]
|
|
|
- (cond-> blocks-tcoll
|
|
|
- (seq current-paragraph-inlines)
|
|
|
- (conj! ["Paragraph" current-paragraph-inlines])
|
|
|
- true
|
|
|
- (#(reduce conj! % ast-coll)))))
|
|
|
-
|
|
|
-(defn- replace-block&page-embeds-in-heading
|
|
|
- [{inline-coll :title origin-level :level :as ast-content}]
|
|
|
- (set! *state* (assoc-in *state* [:replace-ref-embed :current-level] origin-level))
|
|
|
- (if (empty? inline-coll)
|
|
|
- ;; it's just a empty Heading, return itself
|
|
|
- [["Heading" ast-content]]
|
|
|
- (loop [[inline & other-inlines] inline-coll
|
|
|
- heading-exist? false
|
|
|
- current-paragraph-inlines []
|
|
|
- r (transient [])]
|
|
|
- (if-not inline
|
|
|
- (persistent!
|
|
|
- (if (seq current-paragraph-inlines)
|
|
|
- (conj! r (if heading-exist?
|
|
|
- ["Paragraph" current-paragraph-inlines]
|
|
|
- ["Heading" (assoc ast-content :title current-paragraph-inlines)]))
|
|
|
- r))
|
|
|
- (match [inline]
|
|
|
- [["Macro" {:name "embed" :arguments [block-uuid-or-page-name]}]]
|
|
|
- (cond
|
|
|
- (and (string/starts-with? block-uuid-or-page-name "((")
|
|
|
- (string/ends-with? block-uuid-or-page-name "))"))
|
|
|
- (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
|
|
|
- (recur other-inlines true []
|
|
|
- (replace-block-embeds-helper
|
|
|
- current-paragraph-inlines block-uuid-or-page-name r origin-level)))
|
|
|
- (and (string/starts-with? block-uuid-or-page-name "[[")
|
|
|
- (string/ends-with? block-uuid-or-page-name "]]"))
|
|
|
- (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
|
|
|
- (recur other-inlines true []
|
|
|
- (replace-page-embeds-helper
|
|
|
- current-paragraph-inlines block-uuid-or-page-name r origin-level)))
|
|
|
- :else ;; not ((block-uuid)) or [[page-name]], just drop the original ast
|
|
|
- (recur other-inlines heading-exist? current-paragraph-inlines r))
|
|
|
-
|
|
|
- :else
|
|
|
- (let [current-paragraph-inlines*
|
|
|
- (if (and (empty? current-paragraph-inlines)
|
|
|
- heading-exist?)
|
|
|
- (conj current-paragraph-inlines (plain-indent-inline-ast origin-level))
|
|
|
- current-paragraph-inlines)]
|
|
|
- (recur other-inlines heading-exist? (conj current-paragraph-inlines* inline) r)))))))
|
|
|
-
|
|
|
-(defn- replace-block&page-embeds-in-paragraph
|
|
|
- [inline-coll meta]
|
|
|
- (let [current-level (get-in *state* [:replace-ref-embed :current-level])]
|
|
|
- (loop [[inline & other-inlines] inline-coll
|
|
|
- current-paragraph-inlines []
|
|
|
- just-after-embed? false
|
|
|
- blocks (transient [])]
|
|
|
- (if-not inline
|
|
|
- (let [[first-block & other-blocks] (persistent!
|
|
|
- (if (seq current-paragraph-inlines)
|
|
|
- (conj! blocks ["Paragraph" current-paragraph-inlines])
|
|
|
- blocks))]
|
|
|
- (if first-block
|
|
|
- (apply vector (with-meta first-block meta) other-blocks)
|
|
|
- []))
|
|
|
- (match [inline]
|
|
|
- [["Macro" {:name "embed" :arguments [block-uuid-or-page-name]}]]
|
|
|
- (cond
|
|
|
- (and (string/starts-with? block-uuid-or-page-name "((")
|
|
|
- (string/ends-with? block-uuid-or-page-name "))"))
|
|
|
- (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
|
|
|
- (recur other-inlines [] true
|
|
|
- (replace-block-embeds-helper
|
|
|
- current-paragraph-inlines block-uuid-or-page-name blocks current-level)))
|
|
|
- (and (string/starts-with? block-uuid-or-page-name "[[")
|
|
|
- (string/ends-with? block-uuid-or-page-name "]]"))
|
|
|
- (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] true))
|
|
|
- (recur other-inlines [] true
|
|
|
- (replace-page-embeds-helper
|
|
|
- current-paragraph-inlines block-uuid-or-page-name blocks current-level)))
|
|
|
- :else ;; not ((block-uuid)) or [[page-name]], just drop the original ast
|
|
|
- (recur other-inlines current-paragraph-inlines false blocks))
|
|
|
-
|
|
|
- :else
|
|
|
- (let [current-paragraph-inlines*
|
|
|
- (if just-after-embed?
|
|
|
- (conj current-paragraph-inlines (plain-indent-inline-ast current-level))
|
|
|
- current-paragraph-inlines)]
|
|
|
- (recur other-inlines (conj current-paragraph-inlines* inline) false blocks)))))))
|
|
|
-
|
|
|
-(declare replace-block&page-embeds)
|
|
|
-
|
|
|
-(defn- replace-block&page-embeds-in-list-helper
|
|
|
- [list-items]
|
|
|
- (binding [*state* (update-in *state* [:replace-ref-embed :current-level] inc)]
|
|
|
- (mapv
|
|
|
- (fn [{block-ast-coll :content sub-items :items :as item}]
|
|
|
- (assoc item
|
|
|
- :content (mapcatv replace-block&page-embeds block-ast-coll)
|
|
|
- :items (replace-block&page-embeds-in-list-helper sub-items)))
|
|
|
- list-items)))
|
|
|
-
|
|
|
-(defn- replace-block&page-embeds-in-list
|
|
|
- [list-items]
|
|
|
- [["List" (replace-block&page-embeds-in-list-helper list-items)]])
|
|
|
-
|
|
|
-(defn- replace-block&page-embeds-in-quote
|
|
|
- [block-ast-coll]
|
|
|
- (->> block-ast-coll
|
|
|
- (mapcatv replace-block&page-embeds)
|
|
|
- (vector "Quote")
|
|
|
- vector))
|
|
|
-
|
|
|
-(defn- replace-block&page-embeds
|
|
|
- [block-ast]
|
|
|
- (let [[ast-type ast-content] block-ast]
|
|
|
- (case ast-type
|
|
|
- "Heading"
|
|
|
- (replace-block&page-embeds-in-heading ast-content)
|
|
|
- "Paragraph"
|
|
|
- (replace-block&page-embeds-in-paragraph ast-content (meta block-ast))
|
|
|
- "List"
|
|
|
- (replace-block&page-embeds-in-list ast-content)
|
|
|
- "Quote"
|
|
|
- (replace-block&page-embeds-in-quote ast-content)
|
|
|
- "Table"
|
|
|
- ;; TODO: block&page embeds in table are not replaced yet
|
|
|
- [block-ast]
|
|
|
- ;; else
|
|
|
- [block-ast])))
|
|
|
-
|
|
|
-(defn replace-block&page-reference&embed
|
|
|
- "add meta :embed-depth to the embed replaced block-ast,
|
|
|
- to avoid too deep block-ref&embed (or maybe it's a cycle)"
|
|
|
- [block-ast-coll]
|
|
|
- (loop [block-ast-coll block-ast-coll
|
|
|
- result-block-ast-tcoll (transient [])
|
|
|
- block-ast-coll-to-replace-references []
|
|
|
- block-ast-coll-to-replace-embeds []]
|
|
|
- (cond
|
|
|
- (seq block-ast-coll-to-replace-references)
|
|
|
- (let [[block-ast-to-replace-ref & other-block-asts-to-replace-ref]
|
|
|
- block-ast-coll-to-replace-references
|
|
|
- embed-depth (:embed-depth (meta block-ast-to-replace-ref) 0)
|
|
|
- block-ast-replaced (-> (replace-block-references-until-stable block-ast-to-replace-ref)
|
|
|
- (with-meta {:embed-depth embed-depth}))]
|
|
|
- (if (>= embed-depth 5)
|
|
|
- ;; if :embed-depth >= 5, dont replace embed for this block anymore
|
|
|
- ;; there is too deep, or maybe it just a ref/embed cycle
|
|
|
- (recur block-ast-coll (conj! result-block-ast-tcoll block-ast-replaced)
|
|
|
- (vec other-block-asts-to-replace-ref) block-ast-coll-to-replace-embeds)
|
|
|
- (recur block-ast-coll result-block-ast-tcoll (vec other-block-asts-to-replace-ref)
|
|
|
- (conj block-ast-coll-to-replace-embeds block-ast-replaced))))
|
|
|
-
|
|
|
- (seq block-ast-coll-to-replace-embeds)
|
|
|
- (let [[block-ast-to-replace-embed & other-block-asts-to-replace-embed]
|
|
|
- block-ast-coll-to-replace-embeds
|
|
|
- embed-depth (:embed-depth (meta block-ast-to-replace-embed) 0)
|
|
|
- block-ast-coll-replaced (->> (replace-block&page-embeds block-ast-to-replace-embed)
|
|
|
- (mapv #(with-meta % {:embed-depth (inc embed-depth)})))]
|
|
|
- (if (get-in *state* [:replace-ref-embed :block&page-embed-replaced?])
|
|
|
- (do (set! *state* (assoc-in *state* [:replace-ref-embed :block&page-embed-replaced?] false))
|
|
|
- (recur block-ast-coll result-block-ast-tcoll
|
|
|
- (concatv block-ast-coll-to-replace-references block-ast-coll-replaced)
|
|
|
- (vec other-block-asts-to-replace-embed)))
|
|
|
- (recur block-ast-coll (reduce conj! result-block-ast-tcoll block-ast-coll-replaced)
|
|
|
- (vec block-ast-coll-to-replace-references) (vec other-block-asts-to-replace-embed))))
|
|
|
-
|
|
|
- :else
|
|
|
- (let [[block-ast & other-block-ast] block-ast-coll]
|
|
|
- (if-not block-ast
|
|
|
- (persistent! result-block-ast-tcoll)
|
|
|
- (recur other-block-ast result-block-ast-tcoll
|
|
|
- (conj block-ast-coll-to-replace-references block-ast)
|
|
|
- (vec block-ast-coll-to-replace-embeds)))))))
|
|
|
-
|
|
|
-;;; replace block-ref, block-embed, page-embed (ends)
|
|
|
-
|
|
|
-(def remove-block-ast-pos
|
|
|
- "[[ast-type ast-content] _pos] -> [ast-type ast-content]"
|
|
|
- first)
|
|
|
-
|
|
|
-(defn Properties-block-ast?
|
|
|
- [[tp _]]
|
|
|
- (= tp "Properties"))
|
|
|
-
|
|
|
-(defn replace-Heading-with-Paragraph
|
|
|
- "works on block-ast
|
|
|
- replace all heading with paragraph when indent-style is no-indent"
|
|
|
- [heading-ast]
|
|
|
- (let [[heading-type {:keys [title marker priority size]}] heading-ast]
|
|
|
- (if (= heading-type "Heading")
|
|
|
- (let [inline-coll
|
|
|
- (cond->> title
|
|
|
- priority (cons ["Plain" (str (priority->string priority) " ")])
|
|
|
- marker (cons ["Plain" (str marker " ")])
|
|
|
- size (cons ["Plain" (str (reduce str (repeat size "#")) " ")])
|
|
|
- true vec)]
|
|
|
- (mk-paragraph-ast inline-coll {:origin-ast heading-ast}))
|
|
|
- heading-ast)))
|
|
|
-
|
|
|
-(defn keep-only-level<=n
|
|
|
- [block-ast-coll n]
|
|
|
- (-> (reduce
|
|
|
- (fn [{:keys [result-ast-tcoll accepted-heading] :as r} ast]
|
|
|
- (let [[heading-type {level :level}] ast
|
|
|
- is-heading? (= heading-type "Heading")]
|
|
|
- (cond
|
|
|
- (and (not is-heading?) accepted-heading)
|
|
|
- {:result-ast-tcoll (conj! result-ast-tcoll ast) :accepted-heading accepted-heading}
|
|
|
-
|
|
|
- (and (not is-heading?) (not accepted-heading))
|
|
|
- r
|
|
|
-
|
|
|
- (and is-heading? (<= level n))
|
|
|
- {:result-ast-tcoll (conj! result-ast-tcoll ast) :accepted-heading true}
|
|
|
-
|
|
|
- (and is-heading? (> level n))
|
|
|
- {:result-ast-tcoll result-ast-tcoll :accepted-heading false})))
|
|
|
- {:result-ast-tcoll (transient []) :accepted-heading false}
|
|
|
- block-ast-coll)
|
|
|
- :result-ast-tcoll
|
|
|
- persistent!))
|
|
|
-
|
|
|
-;;; inline transformers
|
|
|
-
|
|
|
-(defn remove-emphasis
|
|
|
- ":mapcat-fns-on-inline-ast"
|
|
|
- [inline-ast]
|
|
|
- (let [[ast-type ast-content] inline-ast]
|
|
|
- (case ast-type
|
|
|
- "Emphasis"
|
|
|
- (let [[_ inline-coll] ast-content]
|
|
|
- inline-coll)
|
|
|
- ;; else
|
|
|
- [inline-ast])))
|
|
|
-
|
|
|
-(defn remove-page-ref-brackets
|
|
|
- ":map-fns-on-inline-ast"
|
|
|
- [inline-ast]
|
|
|
- (let [[ast-type ast-content] inline-ast]
|
|
|
- (case ast-type
|
|
|
- "Link"
|
|
|
- (let [{:keys [url label]} ast-content]
|
|
|
- (if (and (= "Page_ref" (first url))
|
|
|
- (or (empty? label)
|
|
|
- (= label [["Plain" ""]])))
|
|
|
- ["Plain" (second url)]
|
|
|
- inline-ast))
|
|
|
- ;; else
|
|
|
- inline-ast)))
|
|
|
-
|
|
|
-(defn remove-tags
|
|
|
- ":mapcat-fns-on-inline-ast"
|
|
|
- [inline-ast]
|
|
|
- (let [[ast-type _ast-content] inline-ast]
|
|
|
- (case ast-type
|
|
|
- "Tag"
|
|
|
- []
|
|
|
- ;; else
|
|
|
- [inline-ast])))
|
|
|
-
|
|
|
-(defn remove-prefix-spaces-in-Plain
|
|
|
- [inline-coll]
|
|
|
- (:r
|
|
|
- (reduce
|
|
|
- (fn [{:keys [r after-break-line?]} ast]
|
|
|
- (let [[ast-type ast-content] ast]
|
|
|
- (case ast-type
|
|
|
- "Plain"
|
|
|
- (let [trimmed-content (string/triml ast-content)]
|
|
|
- (if after-break-line?
|
|
|
- (if (empty? trimmed-content)
|
|
|
- {:r r :after-break-line? false}
|
|
|
- {:r (conj r ["Plain" trimmed-content]) :after-break-line? false})
|
|
|
- {:r (conj r ast) :after-break-line? false}))
|
|
|
- ("Break_Line" "Hard_Break_Line")
|
|
|
- {:r (conj r ast) :after-break-line? true}
|
|
|
- ;; else
|
|
|
- {:r (conj r ast) :after-break-line? false})))
|
|
|
- {:r [] :after-break-line? true}
|
|
|
- inline-coll)))
|
|
|
-
|
|
|
-;;; inline transformers (ends)
|
|
|
-
|
|
|
-;;; walk on block-ast, apply inline transformers
|
|
|
-
|
|
|
-(defn- walk-block-ast-helper
|
|
|
- [inline-coll map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll]
|
|
|
- (->>
|
|
|
- (reduce (fn [inline-coll f] (f inline-coll)) inline-coll fns-on-inline-coll)
|
|
|
- (mapv #(reduce (fn [inline-ast f] (f inline-ast)) % map-fns-on-inline-ast))
|
|
|
- (mapcatv #(reduce
|
|
|
- (fn [inline-ast-coll f] (mapcatv f inline-ast-coll)) [%] mapcat-fns-on-inline-ast))))
|
|
|
-
|
|
|
-(declare walk-block-ast)
|
|
|
-
|
|
|
-(defn- walk-block-ast-for-list
|
|
|
- [list-items map-fns-on-inline-ast mapcat-fns-on-inline-ast]
|
|
|
- (mapv
|
|
|
- (fn [{block-ast-coll :content sub-items :items :as item}]
|
|
|
- (assoc item
|
|
|
- :content
|
|
|
- (mapv
|
|
|
- (partial walk-block-ast
|
|
|
- {:map-fns-on-inline-ast map-fns-on-inline-ast
|
|
|
- :mapcat-fns-on-inline-ast mapcat-fns-on-inline-ast})
|
|
|
- block-ast-coll)
|
|
|
- :items
|
|
|
- (walk-block-ast-for-list sub-items map-fns-on-inline-ast mapcat-fns-on-inline-ast)))
|
|
|
- list-items))
|
|
|
-
|
|
|
-(defn walk-block-ast
|
|
|
- [{:keys [map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll] :as fns}
|
|
|
- block-ast]
|
|
|
- (let [[ast-type ast-content] block-ast]
|
|
|
- (case ast-type
|
|
|
- "Paragraph"
|
|
|
- (mk-paragraph-ast
|
|
|
- (walk-block-ast-helper ast-content map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll)
|
|
|
- (meta block-ast))
|
|
|
- "Heading"
|
|
|
- (let [{:keys [title]} ast-content]
|
|
|
- ["Heading"
|
|
|
- (assoc ast-content
|
|
|
- :title
|
|
|
- (walk-block-ast-helper title map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll))])
|
|
|
- "List"
|
|
|
- ["List" (walk-block-ast-for-list ast-content map-fns-on-inline-ast mapcat-fns-on-inline-ast)]
|
|
|
- "Quote"
|
|
|
- ["Quote" (mapv (partial walk-block-ast fns) ast-content)]
|
|
|
- "Footnote_Definition"
|
|
|
- (let [[name contents] (rest block-ast)]
|
|
|
- ["Footnote_Definition"
|
|
|
- name (walk-block-ast-helper contents map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll)])
|
|
|
- "Table"
|
|
|
- (let [{:keys [header groups]} ast-content
|
|
|
- header* (mapv
|
|
|
- #(walk-block-ast-helper % map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll)
|
|
|
- header)
|
|
|
- groups* (mapv
|
|
|
- (fn [group]
|
|
|
- (mapv
|
|
|
- (fn [row]
|
|
|
- (mapv
|
|
|
- (fn [col]
|
|
|
- (walk-block-ast-helper col map-fns-on-inline-ast mapcat-fns-on-inline-ast fns-on-inline-coll))
|
|
|
- row))
|
|
|
- group))
|
|
|
- groups)]
|
|
|
- ["Table" (assoc ast-content :header header* :groups groups*)])
|
|
|
-
|
|
|
- ;; else
|
|
|
- block-ast)))
|
|
|
-
|
|
|
-;;; walk on block-ast, apply inline transformers (ends)
|
|
|
-
|
|
|
-;;; simple ast
|
|
|
-(def simple-ast-malli-schema
|
|
|
- (mu/closed-schema
|
|
|
- [:or
|
|
|
- [:map
|
|
|
- [:type [:= :raw-text]]
|
|
|
- [:content :string]]
|
|
|
- [:map
|
|
|
- [:type [:= :space]]]
|
|
|
- [:map
|
|
|
- [:type [:= :newline]]
|
|
|
- [:line-count :int]]
|
|
|
- [:map
|
|
|
- [:type [:= :indent]]
|
|
|
- [:level :int]
|
|
|
- [:extra-space-count :int]]]))
|
|
|
-
|
|
|
-(defn raw-text [& contents]
|
|
|
- {:type :raw-text :content (reduce str contents)})
|
|
|
-(def space {:type :space})
|
|
|
-(defn newline* [line-count]
|
|
|
- {:type :newline :line-count line-count})
|
|
|
-(defn indent [level extra-space-count]
|
|
|
- {:type :indent :level level :extra-space-count extra-space-count})
|
|
|
-
|
|
|
-(defn- simple-ast->string
|
|
|
- [simple-ast]
|
|
|
- {:pre [(m/validate simple-ast-malli-schema simple-ast)]}
|
|
|
- (case (:type simple-ast)
|
|
|
- :raw-text (:content simple-ast)
|
|
|
- :space " "
|
|
|
- :newline (reduce str (repeat (:line-count simple-ast) "\n"))
|
|
|
- :indent (reduce str (concatv (repeat (:level simple-ast) "\t")
|
|
|
- (repeat (:extra-space-count simple-ast) " ")))))
|
|
|
-
|
|
|
-(defn- merge-adjacent-spaces&newlines
|
|
|
- [simple-ast-coll]
|
|
|
- (loop [r (transient [])
|
|
|
- last-ast nil
|
|
|
- last-raw-text-space-suffix? false
|
|
|
- last-raw-text-newline-suffix? false
|
|
|
- [simple-ast & other-ast-coll] simple-ast-coll]
|
|
|
- (if (nil? simple-ast)
|
|
|
- (persistent! (if last-ast (conj! r last-ast) r))
|
|
|
- (let [tp (:type simple-ast)
|
|
|
- last-ast-type (:type last-ast)]
|
|
|
- (case tp
|
|
|
- :space
|
|
|
- (if (or (contains? #{:space :newline :indent} last-ast-type)
|
|
|
- last-raw-text-space-suffix?
|
|
|
- last-raw-text-newline-suffix?)
|
|
|
- ;; drop this :space
|
|
|
- (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
|
|
|
- (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll))
|
|
|
-
|
|
|
- :newline
|
|
|
- (case last-ast-type
|
|
|
- (:space :indent) ;; drop last-ast
|
|
|
- (recur r simple-ast false false other-ast-coll)
|
|
|
- :newline
|
|
|
- (let [last-newline-count (:line-count last-ast)
|
|
|
- current-newline-count (:line-count simple-ast)
|
|
|
- kept-ast (if (> last-newline-count current-newline-count) last-ast simple-ast)]
|
|
|
- (recur r kept-ast false false other-ast-coll))
|
|
|
- :raw-text
|
|
|
- (if last-raw-text-newline-suffix?
|
|
|
- (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
|
|
|
- (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll))
|
|
|
- ;; no-last-ast
|
|
|
- (recur r simple-ast false false other-ast-coll))
|
|
|
-
|
|
|
- :indent
|
|
|
- (case last-ast-type
|
|
|
- (:space :indent) ; drop last-ast
|
|
|
- (recur r simple-ast false false other-ast-coll)
|
|
|
- :newline
|
|
|
- (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll)
|
|
|
- :raw-text
|
|
|
- (if last-raw-text-space-suffix?
|
|
|
- ;; drop this :indent
|
|
|
- (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
|
|
|
- (recur (if last-ast (conj! r last-ast) r) simple-ast false false other-ast-coll))
|
|
|
- ;; no-last-ast
|
|
|
- (recur r simple-ast false false other-ast-coll))
|
|
|
-
|
|
|
- :raw-text
|
|
|
- (let [content (:content simple-ast)
|
|
|
- empty-content? (empty? content)
|
|
|
- first-ch (first content)
|
|
|
- last-ch (let [num (count content)]
|
|
|
- (when (pos? num)
|
|
|
- (nth content (dec num))))
|
|
|
- newline-prefix? (some-> first-ch #{"\r" "\n"} boolean)
|
|
|
- newline-suffix? (some-> last-ch #{"\n"} boolean)
|
|
|
- space-prefix? (some-> first-ch #{" "} boolean)
|
|
|
- space-suffix? (some-> last-ch #{" "} boolean)]
|
|
|
- (cond
|
|
|
- empty-content? ;drop this raw-text
|
|
|
- (recur r last-ast last-raw-text-space-suffix? last-raw-text-newline-suffix? other-ast-coll)
|
|
|
- newline-prefix?
|
|
|
- (case last-ast-type
|
|
|
- (:space :indent :newline) ;drop last-ast
|
|
|
- (recur r simple-ast space-suffix? newline-suffix? other-ast-coll)
|
|
|
- :raw-text
|
|
|
- (recur (if last-ast (conj! r last-ast) r) simple-ast space-suffix? newline-suffix? other-ast-coll)
|
|
|
- ;; no-last-ast
|
|
|
- (recur r simple-ast space-suffix? newline-suffix? other-ast-coll))
|
|
|
- space-prefix?
|
|
|
- (case last-ast-type
|
|
|
- (:space :indent) ;drop last-ast
|
|
|
- (recur r simple-ast space-suffix? newline-suffix? other-ast-coll)
|
|
|
- (:newline :raw-text)
|
|
|
- (recur (if last-ast (conj! r last-ast) r) simple-ast space-suffix? newline-suffix? other-ast-coll)
|
|
|
- ;; no-last-ast
|
|
|
- (recur r simple-ast space-suffix? newline-suffix? other-ast-coll))
|
|
|
- :else
|
|
|
- (recur (if last-ast (conj! r last-ast) r) simple-ast space-suffix? newline-suffix? other-ast-coll))))))))
|
|
|
-
|
|
|
-(defn simple-asts->string
|
|
|
- [simple-ast-coll]
|
|
|
- (->> simple-ast-coll
|
|
|
- merge-adjacent-spaces&newlines
|
|
|
- merge-adjacent-spaces&newlines
|
|
|
- (mapv simple-ast->string)
|
|
|
- string/join))
|
|
|
-
|
|
|
-;;; simple ast (ends)
|
|
|
-
|
|
|
-;;; TODO: walk the hiccup tree,
|
|
|
-;;; and call escape-html on all its contents
|
|
|
-;;;
|
|
|
-
|
|
|
-;;; walk the hiccup tree,
|
|
|
-;;; and call escape-html on all its contents (ends)
|
|
|
+;; Aliased fns requiring cli-export-common dynamic bindings e.g. cli-export-common/*current-repo*
|
|
|
+(def replace-block&page-reference&embed cli-export-common/replace-block&page-reference&embed)
|
|
|
+(def replace-Heading-with-Paragraph cli-export-common/replace-Heading-with-Paragraph)
|
|
|
+
|
|
|
+;; Aliased fns
|
|
|
+(def priority->string cli-export-common/priority->string)
|
|
|
+(def timestamp-to-string cli-export-common/timestamp-to-string)
|
|
|
+(def hashtag-value->string cli-export-common/hashtag-value->string)
|
|
|
+(def remove-block-ast-pos cli-export-common/remove-block-ast-pos)
|
|
|
+(def Properties-block-ast? cli-export-common/Properties-block-ast?)
|
|
|
+(def keep-only-level<=n cli-export-common/keep-only-level<=n)
|
|
|
+(def remove-emphasis cli-export-common/remove-emphasis)
|
|
|
+(def remove-page-ref-brackets cli-export-common/remove-page-ref-brackets)
|
|
|
+(def remove-tags cli-export-common/remove-tags)
|
|
|
+(def remove-prefix-spaces-in-Plain cli-export-common/remove-prefix-spaces-in-Plain)
|
|
|
+(def walk-block-ast cli-export-common/walk-block-ast)
|