|
@@ -4,7 +4,8 @@
|
|
|
[clojure.string :as string]
|
|
[clojure.string :as string]
|
|
|
[clojure.set :as set]
|
|
[clojure.set :as set]
|
|
|
[goog.string :as gstring]
|
|
[goog.string :as gstring]
|
|
|
- [goog.string.format]))
|
|
|
|
|
|
|
+ [goog.string.format]
|
|
|
|
|
+ [logseq.graph-parser.mldoc :as gp-mldoc]))
|
|
|
|
|
|
|
|
(def colons "Property delimiter for markdown mode" "::")
|
|
(def colons "Property delimiter for markdown mode" "::")
|
|
|
(defn colons-org
|
|
(defn colons-org
|
|
@@ -158,3 +159,174 @@
|
|
|
(string/join "\n" lines))
|
|
(string/join "\n" lines))
|
|
|
content))
|
|
content))
|
|
|
content))
|
|
content))
|
|
|
|
|
+
|
|
|
|
|
+(defn- build-properties-str
|
|
|
|
|
+ [format properties]
|
|
|
|
|
+ (when (seq properties)
|
|
|
|
|
+ (let [org? (= format :org)
|
|
|
|
|
+ kv-format (if org? ":%s: %s" (str "%s" colons " %s"))
|
|
|
|
|
+ full-format (if org? ":PROPERTIES:\n%s\n:END:" "%s\n")
|
|
|
|
|
+ properties-content (->> (map (fn [[k v]] (common-util/format kv-format (name k) v)) properties)
|
|
|
|
|
+ (string/join "\n"))]
|
|
|
|
|
+ (common-util/format full-format properties-content))))
|
|
|
|
|
+
|
|
|
|
|
+(defn simplified-property?
|
|
|
|
|
+ [line]
|
|
|
|
|
+ (boolean
|
|
|
|
|
+ (and (string? line)
|
|
|
|
|
+ (re-find (re-pattern (str "^\\s?[^ ]+" colons)) line))))
|
|
|
|
|
+
|
|
|
|
|
+(defn- front-matter-property?
|
|
|
|
|
+ [line]
|
|
|
|
|
+ (boolean
|
|
|
|
|
+ (and (string? line)
|
|
|
|
|
+ (common-util/safe-re-find #"^\s*[^ ]+:" line))))
|
|
|
|
|
+
|
|
|
|
|
+(defn insert-property
|
|
|
|
|
+ "Only accept nake content (without any indentation)"
|
|
|
|
|
+ ([repo format content key value]
|
|
|
|
|
+ (insert-property repo format content key value false))
|
|
|
|
|
+ ([repo format content key value front-matter?]
|
|
|
|
|
+ (when (string? content)
|
|
|
|
|
+ (let [ast (gp-mldoc/->edn repo content format)
|
|
|
|
|
+ title? (gp-mldoc/block-with-title? (ffirst (map first ast)))
|
|
|
|
|
+ has-properties? (or (and title?
|
|
|
|
|
+ (or (gp-mldoc/properties? (second ast))
|
|
|
|
|
+ (gp-mldoc/properties? (second
|
|
|
|
|
+ (remove
|
|
|
|
|
+ (fn [[x _]]
|
|
|
|
|
+ (contains? #{"Hiccup" "Raw_Html"} (first x)))
|
|
|
|
|
+ ast)))))
|
|
|
|
|
+ (gp-mldoc/properties? (first ast)))
|
|
|
|
|
+ lines (string/split-lines content)
|
|
|
|
|
+ [title body] (gp-mldoc/get-title&body repo content format)
|
|
|
|
|
+ scheduled (filter #(string/starts-with? % "SCHEDULED") lines)
|
|
|
|
|
+ deadline (filter #(string/starts-with? % "DEADLINE") lines)
|
|
|
|
|
+ body-without-timestamps (filter
|
|
|
|
|
+ #(not (or (string/starts-with? % "SCHEDULED")
|
|
|
|
|
+ (string/starts-with? % "DEADLINE")))
|
|
|
|
|
+ (string/split-lines body))
|
|
|
|
|
+ org? (= :org format)
|
|
|
|
|
+ key (string/lower-case (name key))
|
|
|
|
|
+ value (string/trim (str value))
|
|
|
|
|
+ start-idx (.indexOf lines properties-start)
|
|
|
|
|
+ end-idx (.indexOf lines properties-end)
|
|
|
|
|
+ result (cond
|
|
|
|
|
+ (and org? (not has-properties?))
|
|
|
|
|
+ (let [properties (build-properties-str format {key value})]
|
|
|
|
|
+ (if title
|
|
|
|
|
+ (string/join "\n" (concat [title] scheduled deadline [properties] body-without-timestamps))
|
|
|
|
|
+ (str properties "\n" content)))
|
|
|
|
|
+
|
|
|
|
|
+ (and has-properties? (>= start-idx 0) (> end-idx 0) (> end-idx start-idx))
|
|
|
|
|
+ (let [exists? (atom false)
|
|
|
|
|
+ before (subvec lines 0 start-idx)
|
|
|
|
|
+ middle (doall
|
|
|
|
|
+ (->> (subvec lines (inc start-idx) end-idx)
|
|
|
|
|
+ (mapv (fn [text]
|
|
|
|
|
+ (let [[k v] (common-util/split-first ":" (subs text 1))]
|
|
|
|
|
+ (if (and k v)
|
|
|
|
|
+ (let [key-exists? (= k key)
|
|
|
|
|
+ _ (when key-exists? (reset! exists? true))
|
|
|
|
|
+ v (if key-exists? value v)]
|
|
|
|
|
+ (str ":" k ": " (string/trim v)))
|
|
|
|
|
+ text))))))
|
|
|
|
|
+ middle (if @exists? middle (conj middle (str ":" key ": " value)))
|
|
|
|
|
+ after (subvec lines (inc end-idx))
|
|
|
|
|
+ lines (concat before [properties-start] middle [properties-end] after)]
|
|
|
|
|
+ (string/join "\n" lines))
|
|
|
|
|
+
|
|
|
|
|
+ (not org?)
|
|
|
|
|
+ (let [exists? (atom false)
|
|
|
|
|
+ sym (if front-matter? ": " (str colons " "))
|
|
|
|
|
+ new-property-s (str key sym value)
|
|
|
|
|
+ property-f (if front-matter? front-matter-property? simplified-property?)
|
|
|
|
|
+ groups (partition-by property-f lines)
|
|
|
|
|
+ compose-lines (fn []
|
|
|
|
|
+ (mapcat (fn [lines]
|
|
|
|
|
+ (if (property-f (first lines))
|
|
|
|
|
+ (let [lines (doall
|
|
|
|
|
+ (mapv (fn [text]
|
|
|
|
|
+ (let [[k v] (common-util/split-first sym text)]
|
|
|
|
|
+ (if (and k v)
|
|
|
|
|
+ (let [key-exists? (= k key)
|
|
|
|
|
+ _ (when key-exists? (reset! exists? true))
|
|
|
|
|
+ v (if key-exists? value v)]
|
|
|
|
|
+ (str k sym (string/trim v)))
|
|
|
|
|
+ text)))
|
|
|
|
|
+ lines))
|
|
|
|
|
+ lines (if @exists? lines (conj lines new-property-s))]
|
|
|
|
|
+ lines)
|
|
|
|
|
+ lines))
|
|
|
|
|
+ groups))
|
|
|
|
|
+ lines (cond
|
|
|
|
|
+ has-properties?
|
|
|
|
|
+ (compose-lines)
|
|
|
|
|
+
|
|
|
|
|
+ title?
|
|
|
|
|
+ (cons (first lines) (cons new-property-s (rest lines)))
|
|
|
|
|
+
|
|
|
|
|
+ :else
|
|
|
|
|
+ (cons new-property-s lines))]
|
|
|
|
|
+ (string/join "\n" lines))
|
|
|
|
|
+
|
|
|
|
|
+ :else
|
|
|
|
|
+ content)]
|
|
|
|
|
+ (string/trimr result)))))
|
|
|
|
|
+
|
|
|
|
|
+(defn remove-property
|
|
|
|
|
+ ([format key content]
|
|
|
|
|
+ (remove-property format key content true))
|
|
|
|
|
+ ([format key content first?]
|
|
|
|
|
+ (when (not (string/blank? (name key)))
|
|
|
|
|
+ (let [format (or format :markdown)
|
|
|
|
|
+ key (string/lower-case (name key))
|
|
|
|
|
+ remove-f (if first? common-util/remove-first remove)]
|
|
|
|
|
+ (if (and (= format :org) (not (contains-properties? content)))
|
|
|
|
|
+ content
|
|
|
|
|
+ (let [lines (->> (string/split-lines content)
|
|
|
|
|
+ (remove-f (fn [line]
|
|
|
|
|
+ (let [s (string/triml (string/lower-case line))]
|
|
|
|
|
+ (or (string/starts-with? s (str ":" key ":"))
|
|
|
|
|
+ (string/starts-with? s (str key colons " ")))))))]
|
|
|
|
|
+ (string/join "\n" lines)))))))
|
|
|
|
|
+
|
|
|
|
|
+(defn remove-properties
|
|
|
|
|
+ [format content]
|
|
|
|
|
+ (cond
|
|
|
|
|
+ (contains-properties? content)
|
|
|
|
|
+ (let [lines (string/split-lines content)
|
|
|
|
|
+ [title-lines properties&body] (split-with #(-> (string/triml %)
|
|
|
|
|
+ string/upper-case
|
|
|
|
|
+ (string/starts-with? properties-start)
|
|
|
|
|
+ not)
|
|
|
|
|
+ lines)
|
|
|
|
|
+ body (drop-while #(-> (string/trim %)
|
|
|
|
|
+ string/upper-case
|
|
|
|
|
+ (string/starts-with? properties-end)
|
|
|
|
|
+ not
|
|
|
|
|
+ (or (string/blank? %)))
|
|
|
|
|
+ properties&body)
|
|
|
|
|
+ body (if (and (seq body)
|
|
|
|
|
+ (-> (first body)
|
|
|
|
|
+ string/triml
|
|
|
|
|
+ string/upper-case
|
|
|
|
|
+ (string/starts-with? properties-end)))
|
|
|
|
|
+ (let [line (string/replace (first body) #"(?i):END:\s?" "")]
|
|
|
|
|
+ (if (string/blank? line)
|
|
|
|
|
+ (rest body)
|
|
|
|
|
+ (cons line (rest body))))
|
|
|
|
|
+ body)]
|
|
|
|
|
+ (->> (concat title-lines body)
|
|
|
|
|
+ (string/join "\n")))
|
|
|
|
|
+
|
|
|
|
|
+ (not= format :org)
|
|
|
|
|
+ (let [lines (string/split-lines content)
|
|
|
|
|
+ lines (if (simplified-property? (first lines))
|
|
|
|
|
+ (drop-while simplified-property? lines)
|
|
|
|
|
+ (cons (first lines)
|
|
|
|
|
+ (drop-while simplified-property? (rest lines))))]
|
|
|
|
|
+ (string/join "\n" lines))
|
|
|
|
|
+
|
|
|
|
|
+ :else
|
|
|
|
|
+ content))
|