mirror of
https://github.com/logseq/logseq.git
synced 2026-05-01 17:36:33 +00:00
refactor: remove web
This commit is contained in:
406
src/main/frontend/format/block.cljs
Normal file
406
src/main/frontend/format/block.cljs
Normal file
@@ -0,0 +1,406 @@
|
||||
(ns frontend.format.block
|
||||
(:require [frontend.util :as util :refer-macros [profile]]
|
||||
[clojure.walk :as walk]
|
||||
[clojure.string :as string]
|
||||
[frontend.format :as format]
|
||||
[frontend.utf8 :as utf8]
|
||||
[medley.core :as medley]
|
||||
[frontend.config :as config]
|
||||
[datascript.core :as d]
|
||||
[clojure.set :as set]
|
||||
[frontend.date :as date]
|
||||
[frontend.format.mldoc :as mldoc]
|
||||
[medley.core :as medley]))
|
||||
|
||||
(defn heading-block?
|
||||
[block]
|
||||
(and
|
||||
(vector? block)
|
||||
(= "Heading" (first block))))
|
||||
|
||||
(defn get-tag
|
||||
[block]
|
||||
(and (vector? block)
|
||||
(= "Tag" (first block))
|
||||
(second block)))
|
||||
|
||||
(defn get-page-reference
|
||||
[block]
|
||||
(cond
|
||||
(and (vector? block) (= "Link" (first block)))
|
||||
(let [typ (first (:url (second block)))]
|
||||
(or
|
||||
(and
|
||||
(= typ "Search")
|
||||
(not (contains? #{\# \* \/ \( \[} (first (second (:url (second block))))))
|
||||
(let [page (second (:url (second block)))]
|
||||
(when (and (not (util/starts-with? page "http"))
|
||||
(not (util/starts-with? page "file"))
|
||||
(not (string/ends-with? page ".html")))
|
||||
page)))
|
||||
|
||||
(and
|
||||
(= typ "Complex")
|
||||
(= (:protocol (second (:url (second block)))) "file")
|
||||
(:link (second (:url (second block)))))))
|
||||
|
||||
(and (vector? block) (= "Nested_link" (first block)))
|
||||
(let [content (:content (last block))]
|
||||
(subs content 2 (- (count content) 2)))
|
||||
|
||||
:else
|
||||
nil))
|
||||
|
||||
(defn get-block-reference
|
||||
[block]
|
||||
(when-let [block-id (and (vector? block)
|
||||
(= "Block_reference" (first block))
|
||||
(last block))]
|
||||
(when (util/uuid-string? block-id)
|
||||
block-id)))
|
||||
|
||||
;; FIXME:
|
||||
(defn extract-title
|
||||
[block]
|
||||
(-> (:title (second block))
|
||||
first
|
||||
second))
|
||||
|
||||
(defn paragraph-block?
|
||||
[block]
|
||||
(and
|
||||
(vector? block)
|
||||
(= "Paragraph" (first block))))
|
||||
|
||||
(defn hiccup-block?
|
||||
[block]
|
||||
(and
|
||||
(vector? block)
|
||||
(= "Hiccup" (first block))))
|
||||
|
||||
(defn- timestamp-block?
|
||||
[block]
|
||||
(and
|
||||
(vector? block)
|
||||
(= "Timestamp" (first block))))
|
||||
|
||||
(defn properties-block?
|
||||
[block]
|
||||
(and
|
||||
(vector? block)
|
||||
(= "Property_Drawer" (first block))))
|
||||
|
||||
(defn definition-list-block?
|
||||
[block]
|
||||
(and
|
||||
(vector? block)
|
||||
(= "List" (first block))
|
||||
(:name (first (second block)))))
|
||||
|
||||
(defn extract-properties
|
||||
[[_ properties] start-pos end-pos]
|
||||
(let [properties (->> (into {} properties)
|
||||
(medley/map-kv (fn [k v]
|
||||
(let [k' (and k (string/trim (string/lower-case k)))
|
||||
v' (and v (string/trim v))
|
||||
v' (if (and k' v'
|
||||
(contains? config/markers k')
|
||||
(util/safe-parse-int v'))
|
||||
(util/safe-parse-int v')
|
||||
v')]
|
||||
[k' v']))))]
|
||||
{:properties properties
|
||||
:start-pos start-pos
|
||||
:end-pos end-pos}))
|
||||
|
||||
(defn- paragraph-timestamp-block?
|
||||
[block]
|
||||
(and (paragraph-block? block)
|
||||
(timestamp-block? (first (second block)))))
|
||||
|
||||
(defn extract-timestamps
|
||||
[block]
|
||||
(some->>
|
||||
(second block)
|
||||
(filter timestamp-block?)
|
||||
(map last)
|
||||
(into {})))
|
||||
|
||||
;; {"Deadline" {:date {:year 2020, :month 10, :day 20}, :wday "Tue", :time {:hour 8, :min 0}, :repetition [["DoublePlus"] ["Day"] 1], :active true}}
|
||||
(defn timestamps->scheduled-and-deadline
|
||||
[timestamps]
|
||||
(let [timestamps (medley/map-keys (comp keyword string/lower-case) timestamps)
|
||||
m (some->> (select-keys timestamps [:scheduled :deadline])
|
||||
(map (fn [[k v]]
|
||||
(let [{:keys [date repetition]} v
|
||||
{:keys [year month day]} date
|
||||
day (js/parseInt (str year month day))]
|
||||
(cond->
|
||||
(case k
|
||||
:scheduled
|
||||
{:scheduled day
|
||||
:scheduled-ast v}
|
||||
:deadline
|
||||
{:deadline day
|
||||
:deadline-ast v})
|
||||
repetition
|
||||
(assoc :repeated? true))))))]
|
||||
(apply merge m)))
|
||||
|
||||
(defn with-page-refs
|
||||
[{:keys [title body tags] :as block}]
|
||||
(let [tags (mapv :tag/name (util/->tags (map :tag/name tags)))
|
||||
ref-pages (atom tags)]
|
||||
(walk/postwalk
|
||||
(fn [form]
|
||||
(when-let [page (get-page-reference form)]
|
||||
(swap! ref-pages conj page))
|
||||
(when-let [tag (get-tag form)]
|
||||
(when (util/tag-valid? tag)
|
||||
(swap! ref-pages conj tag)))
|
||||
form)
|
||||
(concat title body))
|
||||
(let [ref-pages (remove string/blank? @ref-pages)]
|
||||
(assoc block :ref-pages (vec ref-pages)))))
|
||||
|
||||
(defn with-block-refs
|
||||
[{:keys [title body] :as block}]
|
||||
(let [ref-blocks (atom nil)]
|
||||
(walk/postwalk
|
||||
(fn [form]
|
||||
(when-let [block (get-block-reference form)]
|
||||
(swap! ref-blocks conj block))
|
||||
form)
|
||||
(concat title body))
|
||||
(let [ref-blocks (remove string/blank? @ref-blocks)]
|
||||
(assoc block :ref-blocks (map
|
||||
(fn [id]
|
||||
[:block/uuid (medley/uuid id)])
|
||||
ref-blocks)))))
|
||||
|
||||
(defn update-src-pos-meta!
|
||||
[{:keys [body] :as block}]
|
||||
(let [body (walk/postwalk
|
||||
(fn [form]
|
||||
(if (and (vector? form)
|
||||
(= (first form) "Src")
|
||||
(map? (:pos_meta (second form))))
|
||||
(let [{:keys [start_pos end_pos]} (:pos_meta (second form))
|
||||
new_start_pos (- start_pos (get-in block [:meta :start-pos]))]
|
||||
["Src" (assoc (second form)
|
||||
:pos_meta {:start_pos new_start_pos
|
||||
:end_pos (+ new_start_pos (- end_pos start_pos))})])
|
||||
form))
|
||||
body)]
|
||||
(assoc block :body body)))
|
||||
|
||||
(defn block-keywordize
|
||||
[block]
|
||||
(medley/map-keys
|
||||
(fn [k] (keyword "block" k))
|
||||
block))
|
||||
|
||||
(defn safe-blocks
|
||||
[blocks]
|
||||
(map (fn [block]
|
||||
(block-keywordize (util/remove-nils block)))
|
||||
blocks))
|
||||
|
||||
(defn collect-block-tags
|
||||
[{:keys [title body tags] :as block}]
|
||||
(cond-> block
|
||||
(seq tags)
|
||||
(assoc :tags (util/->tags tags))))
|
||||
|
||||
(defn extract-blocks
|
||||
[blocks last-pos encoded-content]
|
||||
(let [[block-refs blocks]
|
||||
(loop [block-refs []
|
||||
headings []
|
||||
block-body []
|
||||
blocks (reverse blocks)
|
||||
timestamps {}
|
||||
properties {}
|
||||
last-pos last-pos
|
||||
last-level 1000
|
||||
children []]
|
||||
(if (seq blocks)
|
||||
(let [[block {:keys [start_pos end_pos]}] (first blocks)
|
||||
level (:level (second block))]
|
||||
(cond
|
||||
(paragraph-timestamp-block? block)
|
||||
(let [timestamps (extract-timestamps block)
|
||||
timestamps' (merge timestamps timestamps)
|
||||
other-body (->> (remove timestamp-block? (second block))
|
||||
(drop-while #(= ["Break_Line"] %)))]
|
||||
(recur block-refs headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children))
|
||||
|
||||
(properties-block? block)
|
||||
(let [properties (extract-properties block start_pos end_pos)]
|
||||
(recur block-refs headings block-body (rest blocks) timestamps properties last-pos last-level children))
|
||||
|
||||
(heading-block? block)
|
||||
(let [id (or (when-let [custom-id (get-in properties [:properties "custom_id"])]
|
||||
(let [custom-id (string/trim custom-id)]
|
||||
(when (util/uuid-string? custom-id)
|
||||
(uuid custom-id))))
|
||||
(d/squuid))
|
||||
block (second block)
|
||||
level (:level block)
|
||||
[children current-block-children]
|
||||
(cond
|
||||
(>= level last-level)
|
||||
[(conj children [id level])
|
||||
#{}]
|
||||
|
||||
(< level last-level)
|
||||
(let [current-block-children (set (->> (filter #(< level (second %)) children)
|
||||
(map first)))
|
||||
others (vec (remove #(< level (second %)) children))]
|
||||
[(conj others [id level])
|
||||
current-block-children]))
|
||||
block (-> (assoc block
|
||||
:uuid id
|
||||
:body (vec (reverse block-body))
|
||||
:properties (:properties properties)
|
||||
:children (or current-block-children []))
|
||||
(assoc-in [:meta :start-pos] start_pos)
|
||||
(assoc-in [:meta :end-pos] last-pos))
|
||||
block (if (seq timestamps)
|
||||
(merge block (timestamps->scheduled-and-deadline timestamps))
|
||||
block)
|
||||
block (collect-block-tags block)
|
||||
block (with-page-refs block)
|
||||
block (with-block-refs block)
|
||||
block (update-src-pos-meta! block)
|
||||
block-refs (into block-refs (:ref-blocks block))
|
||||
last-pos' (get-in block [:meta :start-pos])]
|
||||
(recur block-refs (conj headings block) [] (rest blocks) {} {} last-pos' (:level block) children))
|
||||
|
||||
:else
|
||||
(let [block-body' (conj block-body block)]
|
||||
(recur block-refs headings block-body' (rest blocks) timestamps properties last-pos last-level children))))
|
||||
[(->> block-refs
|
||||
(remove nil?)
|
||||
(distinct))
|
||||
(-> (reverse headings)
|
||||
safe-blocks)]))]
|
||||
(let [first-block (first blocks)
|
||||
first-block-start-pos (get-in first-block [:block/meta :start-pos])
|
||||
blocks (if (and
|
||||
(not (string/blank? encoded-content))
|
||||
(or (empty? blocks)
|
||||
(> first-block-start-pos 1)))
|
||||
(cons
|
||||
(merge
|
||||
(let [content (utf8/substring encoded-content 0 first-block-start-pos)
|
||||
uuid (d/squuid)]
|
||||
(->
|
||||
{:uuid uuid
|
||||
:content content
|
||||
:anchor (str uuid)
|
||||
:level 2
|
||||
:meta {:start-pos 0
|
||||
:end-pos (or first-block-start-pos
|
||||
(utf8/length encoded-content))}
|
||||
:body (take-while (fn [block] (not (heading-block? block))) blocks)
|
||||
:pre-block? true}
|
||||
(block-keywordize)))
|
||||
(select-keys first-block [:block/file :block/format :block/page]))
|
||||
blocks)
|
||||
blocks)
|
||||
block-refs (mapv
|
||||
(fn [[_ block-uuid]]
|
||||
{:block/uuid block-uuid})
|
||||
block-refs)]
|
||||
[block-refs blocks])))
|
||||
|
||||
(defn- page-with-journal
|
||||
[original-page-name]
|
||||
(when original-page-name
|
||||
(let [page-name (string/lower-case original-page-name)]
|
||||
(if-let [d (date/journal-title->int (string/capitalize page-name))]
|
||||
{:page/name page-name
|
||||
:page/original-name original-page-name
|
||||
:page/journal? true
|
||||
:page/journal-day d}
|
||||
{:page/name page-name
|
||||
:page/original-name original-page-name}))))
|
||||
|
||||
(defn parse-block
|
||||
([block format]
|
||||
(parse-block block format nil))
|
||||
([{:block/keys [uuid content meta file page pre-block?] :as block} format ast]
|
||||
(when-not (string/blank? content)
|
||||
(let [ast (or ast (format/to-edn content format nil))
|
||||
start-pos (:start-pos meta)
|
||||
encoded-content (utf8/encode content)
|
||||
content-length (utf8/length encoded-content)
|
||||
[block-refs blocks] (extract-blocks ast content-length encoded-content)
|
||||
ref-pages-atom (atom [])
|
||||
blocks (doall
|
||||
(map-indexed
|
||||
(fn [idx {:block/keys [ref-pages ref-blocks meta] :as block}]
|
||||
(let [block (collect-block-tags block)
|
||||
block (merge
|
||||
block
|
||||
{:block/meta meta
|
||||
:block/marker (get block :block/marker "nil")
|
||||
:block/properties (get block :block/properties [])
|
||||
:block/file file
|
||||
:block/format format
|
||||
:block/page page
|
||||
:block/content (utf8/substring encoded-content
|
||||
(:start-pos meta)
|
||||
(:end-pos meta))}
|
||||
;; Preserve the original block id
|
||||
(when (and (zero? idx)
|
||||
;; not custom-id
|
||||
(not (get-in block [:block/properties "custom_id"])))
|
||||
{:block/uuid uuid})
|
||||
(when (seq ref-pages)
|
||||
{:block/ref-pages
|
||||
(mapv
|
||||
(fn [page]
|
||||
(let [page (page-with-journal page)]
|
||||
(swap! ref-pages-atom conj page)
|
||||
page))
|
||||
ref-pages)}))]
|
||||
(-> block
|
||||
(assoc-in [:block/meta :start-pos] (+ (:start-pos meta) start-pos))
|
||||
(assoc-in [:block/meta :end-pos] (+ (:end-pos meta) start-pos)))))
|
||||
blocks))
|
||||
pages (vec (distinct @ref-pages-atom))]
|
||||
{:block-refs block-refs
|
||||
:blocks blocks
|
||||
:pages pages
|
||||
:start-pos start-pos
|
||||
:end-pos (+ start-pos content-length)}))))
|
||||
|
||||
(defn with-levels
|
||||
[text format {:block/keys [level pre-block?]}]
|
||||
(let [pattern (config/get-block-pattern format)
|
||||
prefix (if pre-block? "" (str (apply str (repeat level pattern)) " "))]
|
||||
(str prefix (string/triml text))))
|
||||
|
||||
(defn macro-subs
|
||||
[macro-content arguments]
|
||||
(loop [s macro-content
|
||||
args arguments
|
||||
n 1]
|
||||
(if (seq args)
|
||||
(recur
|
||||
(string/replace s (str "$" n) (first args))
|
||||
(rest args)
|
||||
(inc n))
|
||||
s)))
|
||||
|
||||
(defn break-line-paragraph?
|
||||
[[typ break-lines]]
|
||||
(and (= typ "Paragraph")
|
||||
(every? #(= % ["Break_Line"]) break-lines)))
|
||||
|
||||
(defn trim-break-lines!
|
||||
[ast]
|
||||
(->> (drop-while break-line-paragraph? ast)
|
||||
(take-while (complement break-line-paragraph?))))
|
||||
Reference in New Issue
Block a user