Forráskód Böngészése

Merge branch 'feat/db' into chore/whiteboards-cleanup

Konstantinos Kaloutas 1 éve
szülő
commit
8a91a400e5

+ 1 - 12
deps/db/src/logseq/db/frontend/property/util.cljs

@@ -47,16 +47,6 @@
         (assoc :block/type #{"hidden"}
                :block/format :markdown))))
 
-(defn new-property-tx
-  "Provide attributes for a new built-in property given name, schema and uuid.
-   TODO: Merge this with sqlite-util/build-new-property once common-util/page-name-sanity-lc
-   is available to deps/db"
-  [prop-name prop-schema prop-uuid]
-  {:block/uuid prop-uuid
-   :block/schema (merge {:type :default} prop-schema)
-   :block/original-name (name prop-name)
-   :block/name (common-util/page-name-sanity-lc (name prop-name))})
-
 (defn build-closed-values
   "Builds all the tx needed for property with closed values including
    the hidden page and closed value blocks as needed"
@@ -76,8 +66,7 @@
                (:closed-values property)))
         property-schema (assoc (:block/schema property)
                                :values (mapv :block/uuid closed-value-blocks-tx))
-        property-tx (merge (sqlite-util/build-new-property
-                            (new-property-tx prop-name property-schema (:block/uuid property)))
+        property-tx (merge (sqlite-util/build-new-property prop-name property-schema (:block/uuid property))
                            property-attributes)]
     (into [property-tx page-tx]
           (when-not closed-value-page-uuids? closed-value-blocks-tx))))

+ 3 - 4
deps/db/src/logseq/db/sqlite/create_graph.cljs

@@ -22,10 +22,9 @@
             {:block/schema schema :block/uuid (d/squuid) :closed-values closed-values}
             {:icon-id (get default-property-uuids :icon)})
            [(sqlite-util/build-new-property
-             {:block/schema schema
-              :block/original-name (or original-name k-name)
-              :block/name (common-util/page-name-sanity-lc k-name)
-              :block/uuid (get default-property-uuids k-keyword (d/squuid))})])))
+             (or original-name k-name)
+             schema
+             (get default-property-uuids k-keyword (d/squuid)))])))
      db-property/built-in-properties)))
 
 (defn build-db-initial-data

+ 8 - 5
deps/db/src/logseq/db/sqlite/util.cljs

@@ -35,12 +35,15 @@
 
 (defn build-new-property
   "Build a standard new property so that it is is consistent across contexts"
-  [block]
+  [prop-name prop-schema prop-uuid]
   (block-with-timestamps
-   (merge {:block/type "property"
-           :block/journal? false
-           :block/format :markdown}
-          block)))
+   {:block/type "property"
+    :block/journal? false
+    :block/format :markdown
+    :block/uuid prop-uuid
+    :block/schema (merge {:type :default} prop-schema)
+    :block/original-name (name prop-name)
+    :block/name (common-util/page-name-sanity-lc (name prop-name))}))
 
 
 (defn build-new-class

+ 14 - 13
deps/outliner/src/logseq/outliner/core.cljs

@@ -352,18 +352,18 @@
           eid (or db-id (when block-uuid [:block/uuid block-uuid]))
           block-entity (d/entity db eid)
           m' (if (and (:block/content m) db-based?)
-              (update m :block/content
-                      (fn [content]
-                        (db-content/content-without-tags
-                         content
-                         (->>
-                          (map
-                           (fn [tag]
-                             (when (:block/uuid tag)
-                               (str db-content/page-ref-special-chars (:block/uuid tag))))
-                           (:block/tags m))
-                          (remove nil?)))))
-              m)
+               (update m :block/content
+                       (fn [content]
+                         (db-content/content-without-tags
+                          content
+                          (->>
+                           (map
+                            (fn [tag]
+                              (when (:block/uuid tag)
+                                (str db-content/page-ref-special-chars (:block/uuid tag))))
+                            (:block/tags m))
+                           (remove nil?)))))
+               m)
           m (cond->> m'
               db-based?
               (db-marker-handle conn))
@@ -394,7 +394,8 @@
         ;; Remove macros as they are replaced by new ones
         (remove-macros-when-save db txs-state block-entity)
         ;; Remove orphaned refs from block
-        (remove-orphaned-refs-when-save @conn txs-state block-entity m))
+        (when (and (:block/content m) (not= (:block/content m) (:block/content block-entity)))
+          (remove-orphaned-refs-when-save @conn txs-state block-entity m)))
 
       ;; handle others txs
       (let [other-tx (:db/other-tx m)]

+ 1 - 1
package.json

@@ -101,7 +101,7 @@
         "@excalidraw/excalidraw": "0.16.1",
         "@highlightjs/cdn-assets": "10.4.1",
         "@isomorphic-git/lightning-fs": "^4.6.0",
-        "@logseq/capacitor-file-sync": "5.0.1",
+        "@logseq/capacitor-file-sync": "5.0.2",
         "@logseq/diff-merge": "0.2.2",
         "@logseq/react-tweet-embed": "1.3.1-1",
         "@logseq/sqlite-wasm": "=0.1.0",

+ 7 - 7
scripts/src/logseq/tasks/db_graph/create_graph.cljs

@@ -176,13 +176,13 @@
                                   :property-attributes
                                   {:db/id (or (property-db-ids (name prop-name))
                                               (throw (ex-info "No :db/id for property" {:property prop-name})))}})
-                                [(sqlite-util/build-new-property
-                                  (merge (db-property-util/new-property-tx prop-name (get-in properties [prop-name :block/schema]) uuid)
-                                         {:db/id (or (property-db-ids (name prop-name))
-                                                     (throw (ex-info "No :db/id for property" {:property prop-name})))}
-                                         (when-let [props (not-empty (get-in properties [prop-name :properties]))]
-                                           {:block/properties (->block-properties-tx props uuid-maps)
-                                            :block/refs (build-property-refs props property-db-ids)})))]))
+                                [(merge
+                                  (sqlite-util/build-new-property prop-name (get-in properties [prop-name :block/schema]) uuid)
+                                  {:db/id (or (property-db-ids (name prop-name))
+                                                      (throw (ex-info "No :db/id for property" {:property prop-name})))}
+                                  (when-let [props (not-empty (get-in properties [prop-name :properties]))]
+                                            {:block/properties (->block-properties-tx props uuid-maps)
+                                             :block/refs (build-property-refs props property-db-ids)}))]))
                             property-uuids))
         pages-and-blocks-tx
         (vec

+ 118 - 38
scripts/src/logseq/tasks/db_graph/create_graph_with_schema_org.cljs

@@ -33,20 +33,20 @@
       (throw (ex-info (str "No :block/uuid for " class-id) {}))))
 
 (defn- get-comment-string
-  [rdfs-comment renamed-classes]
+  [rdfs-comment renamed-pages]
   (let [desc* (if (map? rdfs-comment)
                 (get rdfs-comment "@value")
                 rdfs-comment)
         ;; Update refs to renamed classes
-        regex (re-pattern (str "\\[\\[(" (string/join "|" (keys renamed-classes)) ")\\]\\]"))
-        desc (string/replace desc* regex #(str "[[" (get renamed-classes (second %)) "]]"))]
+        regex (re-pattern (str "\\[\\[(" (string/join "|" (keys renamed-pages)) ")\\]\\]"))
+        desc (string/replace desc* regex #(str "[[" (get renamed-pages (second %)) "]]"))]
     ;; Fix markdown and html links to schema website docs
     (string/replace desc #"(\(|\")/docs" "$1https://schema.org/docs")))
 
 (defn- strip-schema-prefix [s]
   (string/replace-first s "schema:" ""))
 
-(defn- ->class-page [class-m class-db-ids class-uuids class-properties property-uuids {:keys [verbose renamed-classes]}]
+(defn- ->class-page [class-m class-db-ids class-uuids class-properties property-uuids {:keys [verbose renamed-classes renamed-pages]}]
   (let [parent-class* (class-m "rdfs:subClassOf")
         parent-class (cond
                        (map? parent-class*)
@@ -73,7 +73,7 @@
              :db/id (get-class-db-id class-db-ids (class-m "@id"))
              :properties (cond-> {:url url}
                            (class-m "rdfs:comment")
-                           (assoc :description (get-comment-string (class-m "rdfs:comment") renamed-classes)))}
+                           (assoc :description (get-comment-string (class-m "rdfs:comment") renamed-pages)))}
       parent-class
       (assoc :block/namespace {:db/id (get-class-db-id class-db-ids parent-class)})
       (seq properties)
@@ -105,7 +105,7 @@
              (when (class-map %) :page))
         range-includes))
 
-(defn- ->property-page [property-m prop-uuid class-map class-uuids {:keys [verbose renamed-classes]}]
+(defn- ->property-page [property-m prop-uuid class-map class-uuids {:keys [verbose renamed-pages renamed-properties]}]
   (let [range-includes (get-range-includes property-m)
         schema-type (get-schema-type range-includes class-map)
         ;; Pick first range to determine type as only one range is supported currently
@@ -113,12 +113,15 @@
             (println "Picked property type:"
                      {:property (property-m "@id") :type schema-type :range-includes (vec range-includes)}))
         _ (assert schema-type (str "No schema found for property " (property-m "@id")))
+        inverted-renamed-properties (set/map-invert renamed-properties)
+        class-name (strip-schema-prefix (property-m "@id"))
+        url (str "https://schema.org/" (get inverted-renamed-properties class-name class-name))
         schema (cond-> {:type schema-type}
                  ;; This cardinality rule should be adjusted as we use schema.org more
                  (= schema-type :page)
                  (assoc :cardinality :many)
                  (property-m "rdfs:comment")
-                 (assoc :description (get-comment-string (property-m "rdfs:comment") renamed-classes))
+                 (assoc :description (get-comment-string (property-m "rdfs:comment") renamed-pages))
                  (= schema-type :page)
                  (assoc :classes (let [invalid-classes (remove class-uuids range-includes)
                                        _ (when (seq invalid-classes)
@@ -131,7 +134,7 @@
     {(keyword (strip-schema-prefix (property-m "@id")))
      {:block/uuid prop-uuid
       :block/schema schema
-      :properties {:url (string/replace-first (property-m "@id") "schema:" "https://schema.org/")}}}))
+      :properties {:url url}}}))
 
 (defn- get-class-to-properties
   "Given a vec of class ids and a vec of properties map to process, return a map of
@@ -162,21 +165,60 @@
     (and (seq range-includes)
          (every? (fn [x] (contains? unsupported-data-types x)) range-includes))))
 
+(defn- get-vector-conflicts
+  "Given a seq of tuples returns a seq of tuples that conflict i.e. their first element
+   has a case insensitive conflict/duplicate with another. An example conflict:
+   [[\"schema:businessFunction\" :property] [\"schema:BusinessFunction\" :class]]"
+  [tuples-seq]
+  (->> tuples-seq
+       (group-by (comp common-util/page-name-sanity-lc first))
+       (filter #(> (count (val %)) 1))
+       vals))
+
+(defn- detect-final-conflicts
+  "Does one final detection for conflicts after everything has been renamed"
+  [all-properties all-classes page-tuples]
+  (let [property-ids (map #(vector (% "@id") :property) all-properties)
+        class-ids (map #(vector (% "@id") :class) all-classes)
+        existing-conflicts (get-vector-conflicts (concat property-ids class-ids page-tuples))]
+    (when (seq existing-conflicts) (prn :CONFLICTS existing-conflicts))
+    (assert (empty? existing-conflicts)
+            "There are no conflicts between existing pages, schema classes and properties")))
+
+(defn- detect-property-conflicts-and-get-renamed-properties
+  "Detects conflicts between properties and existing pages and returns renamed properties"
+  [property-ids existing-pages {:keys [verbose]}]
+  (let [conflicts (get-vector-conflicts (concat property-ids existing-pages))
+        _ (assert (every? #(= 2 (count %)) conflicts) "All conflicts must only be between two elements")
+        renamed-properties (->> conflicts
+                                (map #(-> % second first))
+                                ;; Renaming properties '_property' suffix guarantees uniqueness
+                                ;; b/c schema.org doesn't use '_' in their names
+                                (map #(vector % (str % "_property")))
+                                (into {}))]
+    (if verbose
+      (println "Renaming the following properties because they have names that conflict with Logseq's built in pages"
+               (keys renamed-properties) "\n")
+      (println "Renaming" (count renamed-properties) "properties due to page name conflicts"))
+    renamed-properties))
+
 (defn- detect-id-conflicts-and-get-renamed-classes
-  "Properties and class names conflict in Logseq because schema.org names are
-  case sensitive whereas Logseq's :block/name is case insensitive. This is dealt
-  with by appending a '_Class' suffix to conflicting classes.  If this strategy
-  changes, be sure to update schema->logseq-data-types"
-  [property-ids class-ids {:keys [verbose]}]
-  (let [conflicts
-        (->> (concat property-ids class-ids)
-             (group-by (comp common-util/page-name-sanity-lc first))
-             (filter #(> (count (val %)) 1))
-             vals)
+  "Detects conflicts between classes AND properties and existing
+  pages. Renames any detected conflicts. Properties and class names conflict in
+  Logseq because schema.org names are case sensitive whereas Logseq's
+  :block/name is case insensitive. This is dealt with by appending a '_Class'
+  suffix to conflicting classes.  If this strategy changes, be sure to update
+  schema->logseq-data-types"
+  [property-ids class-ids existing-pages {:keys [verbose]}]
+  (let [conflicts (get-vector-conflicts (concat property-ids class-ids))
         ;; If this assertion fails then renamed-classes approach to resolving
         ;; conflicts may need to be revisited
-        _ (assert (every? #(= (map second %) [:property :class]) conflicts)
-                  "All conflicts are between a property and class")
+        _ (assert (every? #(= 2 (count %)) conflicts) "All conflicts must only be between two elements")
+        existing-conflicts (get-vector-conflicts (concat class-ids existing-pages))
+        _ (when (seq existing-conflicts) (prn :EXISTING-CLASS-CONFLICTS existing-conflicts))
+        ;; Add existing-conflicts to conflicts if this ever fails
+        _ (assert (empty? existing-conflicts)
+                  "There are no conflicts between existing pages and schema classes and properties")
         renamed-classes (->> conflicts
                              (map #(-> % second first))
                              ;; Renaming classes with '_Class' suffix guarantees uniqueness
@@ -187,7 +229,6 @@
       (println "Renaming the following classes because they have property names that conflict with Logseq's case insensitive :block/name:"
                (keys renamed-classes) "\n")
       (println "Renaming" (count renamed-classes) "classes due to page name conflicts"))
-        ;; Looks for all instances of a renamed class and updates them to the renamed class reference
     renamed-classes))
 
 (defn- get-all-properties [schema-data {:keys [verbose]}]
@@ -232,39 +273,47 @@
 
 (defn- get-all-classes-and-properties
   "Get all classes and properties from raw json file"
-  [schema-data options]
+  [schema-data existing-pages options]
   (let [;; TODO: See if it's worth pulling in non-types like schema:MusicReleaseFormatType
         all-classes* (filter #(contains? (set (as-> (% "@type") type'
                                                 (if (string? type') [type'] type')))
                                          "rdfs:Class")
                              schema-data)
         all-properties* (get-all-properties schema-data options)
+        property-tuples (map #(vector (% "@id") :property) all-properties*)
+        class-tuples (map #(vector (% "@id") :class) all-classes*)
+        page-tuples (map #(vector (str "schema:" %) :page) existing-pages)
         renamed-classes (detect-id-conflicts-and-get-renamed-classes
-                         (map #(vector (% "@id") :property) all-properties*)
-                         (map #(vector (% "@id") :class) all-classes*)
-                         options)
-        rename-class-ids (fn [m]
-                           (w/postwalk (fn [x]
-                                         (if-let [new-class (and (map? x) (renamed-classes (x "@id")))]
-                                           (merge x {"@id" new-class})
-                                           x)) m))
+                         property-tuples class-tuples page-tuples options)
+        renamed-properties (detect-property-conflicts-and-get-renamed-properties
+                            property-tuples page-tuples options)
+        renamed-pages (merge renamed-classes renamed-properties)
         ;; Updates keys like @id, @subClassOf
-        all-classes (map rename-class-ids all-classes*)
+        rename-page-ids (fn [m]
+                          (w/postwalk (fn [x]
+                                        (if-let [new-page (and (map? x) (renamed-pages (x "@id")))]
+                                          (merge x {"@id" new-page})
+                                          x)) m))
         ;; Updates keys like @id, @rangeIncludes, @domainIncludes
-        all-properties (map rename-class-ids all-properties*)]
+        all-classes (map rename-page-ids all-classes*)
+        all-properties (map rename-page-ids all-properties*)]
+    (detect-final-conflicts all-properties all-classes page-tuples)
     {:all-classes all-classes
      :all-properties all-properties
+     :renamed-properties (->> renamed-properties
+                              (map (fn [[k v]] [(strip-schema-prefix k) (strip-schema-prefix v)]))
+                              (into {}))
      :renamed-classes (->> renamed-classes
                            (map (fn [[k v]] [(strip-schema-prefix k) (strip-schema-prefix v)]))
                            (into {}))}))
 
-(defn- create-init-data [options]
+(defn- create-init-data [existing-pages options]
   (let [schema-data (-> (str (fs/readFileSync "resources/schemaorg-current-https.json"))
                         js/JSON.parse
                         (js->clj)
                         (get "@graph"))
-        {:keys [all-classes all-properties renamed-classes]}
-        (get-all-classes-and-properties schema-data options)
+        {:keys [all-classes all-properties renamed-classes renamed-properties]}
+        (get-all-classes-and-properties schema-data existing-pages options)
         ;; Generate data shared across pages and properties
         class-map (->> all-classes
                        (map #(vector (% "@id") %))
@@ -283,13 +332,17 @@
         property-uuids (->> select-properties
                             (map #(vector % (random-uuid)))
                             (into {}))
+        options' (assoc options
+                        :renamed-classes renamed-classes
+                        :renamed-properties renamed-properties
+                        :renamed-pages (merge renamed-properties renamed-classes))
         ;; Generate pages and properties
         properties (generate-properties
                     (filter #(contains? select-properties (% "@id")) all-properties)
-                    property-uuids class-map class-uuids (assoc options :renamed-classes renamed-classes))
+                    property-uuids class-map class-uuids options')
         pages (generate-pages
                (map #(class-map %) select-class-ids)
-               class-uuids class-to-properties property-uuids (assoc options :renamed-classes renamed-classes))]
+               class-uuids class-to-properties property-uuids options')]
     {:pages-and-blocks pages
      :properties properties}))
 
@@ -297,9 +350,34 @@
   "Options spec"
   {:help {:alias :h
           :desc "Print help"}
+   :debug {:alias :d
+           :desc "Prints additional debug info and a schema.edn for debugging"}
    :verbose {:alias :v
              :desc "Verbose mode"}})
 
+(defn- write-debug-file [blocks-tx db]
+  (let [block-uuid->name* (->> (d/q '[:find (pull ?b [:block/name :block/uuid]) :where [?b :block/name]] db)
+                               (map first)
+                               (map (juxt :block/uuid :block/name))
+                               (into {}))
+        block-uuid->name #(or (block-uuid->name* %) (throw (ex-info (str "No entity found for " %) {})))
+        ;; TODO: Figure out why some Thing's properties don't exist
+        block-uuid->name-please-fixme
+        #(or (block-uuid->name* %2) (println "WARNING: Page" (pr-str (:block/original-name %1)) "skipped uuid" %2))]
+    (fs/writeFileSync "schema-org.edn"
+                      (pr-str
+                       (->> blocks-tx
+                            (map (fn [m]
+                                   (cond-> (select-keys m [:block/name :block/type :block/original-name
+                                                           :block/properties :block/schema])
+                                     (seq (:block/properties m))
+                                     (update :block/properties #(update-keys % block-uuid->name))
+                                     (seq (get-in m [:block/schema :properties]))
+                                     (update-in [:block/schema :properties] #(mapv (partial block-uuid->name-please-fixme m) %))
+                                     (seq (get-in m [:block/schema :classes]))
+                                     (update-in [:block/schema :classes] #(mapv block-uuid->name %)))))
+                            set)))))
+
 (defn -main [args]
   (let [[graph-dir] args
         options (cli/parse-opts args {:spec spec})
@@ -311,13 +389,15 @@
                         ((juxt node-path/dirname node-path/basename) graph-dir)
                         [(node-path/join (os/homedir) "logseq" "graphs") graph-dir])
         conn (create-graph/init-conn dir db-name)
-        init-data (create-init-data options)
+        init-data (create-init-data (d/q '[:find [?name ...] :where [?b :block/name ?name]] @conn)
+                                    options)
         blocks-tx (create-graph/create-blocks-tx init-data)]
     (println "Generating" (str (count (filter :block/name blocks-tx)) " pages with "
                                (count (:pages-and-blocks init-data)) " classes and "
                                (count (:properties init-data)) " properties ..."))
     (d/transact! conn blocks-tx)
     (when (:verbose options) (println "Transacted" (count (d/datoms @conn :eavt)) "datoms"))
+    (when (:debug options) (write-debug-file blocks-tx @conn))
     (println "Created graph" (str db-name "!"))))
 
 (when (= nbb/*file* (:file (meta #'-main)))

+ 4 - 1
src/main/frontend/components/block.cljs

@@ -2332,7 +2332,10 @@
       [:div.closed-values-properties.flex.flex-row.items-center.gap-1.select-none.h-full
        (for [pid closed-values-properties]
          (when-let [property (db/entity [:block/uuid pid])]
-           (pv/property-value block property (get (:block/properties block) pid) {:icon? true :page-cp page-cp})))])))
+           (pv/property-value block property (get (:block/properties block) pid)
+                              {:icon? true
+                               :page-cp page-cp
+                               :inline-text inline-text})))])))
 
 (rum/defc ^:large-vars/cleanup-todo block-content < rum/reactive
   [config {:block/keys [uuid content properties scheduled deadline format pre-block?] :as block} edit-input-id block-id slide? selected? *ref]

+ 75 - 28
src/main/frontend/components/imports.cljs

@@ -1,9 +1,11 @@
 (ns frontend.components.imports
   "Import data into Logseq."
-  (:require [cljs.core.async.interop :refer [p->c]]
+  (:require [borkdude.rewrite-edn :as rewrite]
+            [cljs.core.async.interop :refer [p->c]]
             [clojure.core.async :as async]
             [clojure.edn :as edn]
             [clojure.string :as string]
+            [datascript.core :as d]
             [frontend.components.onboarding.setups :as setups]
             [frontend.components.repo :as repo]
             [frontend.components.svg :as svg]
@@ -11,12 +13,15 @@
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.fs :as fs]
-            [frontend.persist-db.browser :as db-browser]
+            [frontend.handler.common.config-edn :as config-edn-common-handler]
             [frontend.handler.db-based.editor :as db-editor-handler]
             [frontend.handler.import :as import-handler]
             [frontend.handler.notification :as notification]
+            [frontend.handler.repo :as repo-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
+            [frontend.modules.outliner.ui :as ui-outliner-tx]
+            [frontend.persist-db.browser :as db-browser]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
@@ -24,13 +29,13 @@
             [goog.functions :refer [debounce]]
             [goog.object :as gobj]
             [logseq.common.path :as path]
+            [logseq.common.util :as common-util]
+            [logseq.db :as ldb]
             [logseq.graph-parser :as graph-parser]
+            [logseq.outliner.core :as outliner-core]
             [medley.core :as medley]
             [promesa.core :as p]
-            [borkdude.rewrite-edn :as rewrite]
-            [rum.core :as rum]
-            [frontend.handler.repo :as repo-handler]
-            [frontend.handler.common.config-edn :as config-edn-common-handler]))
+            [rum.core :as rum]))
 
 ;; Can't name this component as `frontend.components.import` since shadow-cljs
 ;; will complain about it.
@@ -222,30 +227,71 @@
                    (db-editor-handler/save-file! "logseq/config.edn" migrated-content))
                   (edn/read-string migrated-content))))))
 
+(defn- build-hidden-favorites-page-blocks
+  [page-block-uuid-coll]
+  (map
+   (fn [uuid]
+     {:block/link [:block/uuid uuid]
+      :block/content ""
+      :block/format :markdown})
+   page-block-uuid-coll))
+
+(def hidden-favorites-page-name "$$$favorites")
+(def hidden-favorites-page-tx
+  {:block/uuid (d/squuid)
+   :block/name hidden-favorites-page-name
+   :block/original-name hidden-favorites-page-name
+   :block/journal? false
+   :block/type #{"hidden"}
+   :block/format :markdown})
+
+(defn- import-favorites-from-config-edn!
+  [db-conn repo config-file]
+  (let [now (inst-ms (js/Date.))]
+    (p/do!
+     (ldb/transact! repo [(assoc hidden-favorites-page-tx
+                                 :block/created-at now
+                                 :block/updated-at now)])
+     (p/let [content (when config-file (.text config-file))]
+       (when-let [content-edn (try (edn/read-string content)
+                                   (catch :default _ nil))]
+         (when-let [favorites (seq (:favorites content-edn))]
+           (when-let [page-block-uuid-coll
+                      (seq
+                       (keep (fn [page-name]
+                               (some-> (d/entity @db-conn [:block/name (common-util/page-name-sanity-lc page-name)])
+                                       :block/uuid))
+                             favorites))]
+             (let [page-entity (d/entity @db-conn [:block/name hidden-favorites-page-name])]
+               (ui-outliner-tx/transact!
+                {:outliner-op :insert-blocks}
+                (outliner-core/insert-blocks! repo db-conn (build-hidden-favorites-page-blocks page-block-uuid-coll)
+                                              page-entity {}))))))))))
 
-(rum/defc confirm-graph-name-dialog
-  [initial-name on-graph-name-confirmed]
-  (let [[input set-input!] (rum/use-state initial-name)
-        on-submit #(do (on-graph-name-confirmed input)
-                       (state/close-modal!))]
-    [:div.container
-     [:div.sm:flex.sm:items-start
-      [:div.mt-3.text-center.sm:mt-0.sm:text-left
-       [:h3#modal-headline.leading-6.font-medium
-        "Imported new graph name:"]]]
 
-     [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2.mb-4
-      {:auto-focus true
-       :default-value input
-       :on-change (fn [e]
-                    (set-input! (util/evalue e)))
-       :on-key-press (fn [e]
-                       (when (= "Enter" (util/ekey e))
-                         (on-submit)))}]
-
-     [:div.mt-5.sm:mt-4.flex
-      (ui/button "Confirm"
-                 {:on-click on-submit})]]))
+(rum/defc confirm-graph-name-dialog
+          [initial-name on-graph-name-confirmed]
+          (let [[input set-input!] (rum/use-state initial-name)
+                on-submit #(do (on-graph-name-confirmed input)
+                               (state/close-modal!))]
+            [:div.container
+             [:div.sm:flex.sm:items-start
+              [:div.mt-3.text-center.sm:mt-0.sm:text-left
+               [:h3#modal-headline.leading-6.font-medium
+                "Imported new graph name:"]]]
+
+             [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2.mb-4
+              {:auto-focus true
+               :default-value input
+               :on-change (fn [e]
+                            (set-input! (util/evalue e)))
+               :on-key-press (fn [e]
+                               (when (= "Enter" (util/ekey e))
+                                 (on-submit)))}]
+
+             [:div.mt-5.sm:mt-4.flex
+              (ui/button "Confirm"
+                         {:on-click on-submit})]]))
 
 (defn graph-folder-to-db-import-handler
   "Import from a graph folder as a DB-based graph.
@@ -277,6 +323,7 @@
                                 (async/<! (p->c (import-config-file! config-file)))
                                 (async/<! (import-from-asset-files! asset-files))
                                 (async/<! (import-from-doc-files! db-conn repo doc-files))
+                                (async/<! (p->c (import-favorites-from-config-edn! db-conn repo config-file)))
                                 (state/set-state! :graph/importing nil)
                                 (finished-cb)))))]
     (state/set-modal!

+ 39 - 32
src/main/frontend/components/property.cljs

@@ -373,6 +373,10 @@
 
 (rum/defcs property-input < rum/reactive
   (rum/local false ::show-new-property-config?)
+  {:will-unmount (fn [state]
+                   (when-let [*property-key (nth (:rum/args state) 1)]
+                     (reset! *property-key nil))
+                   state)}
   shortcut/disable-all-shortcuts
   [state entity *property-key *property-value {:keys [class-schema? _page-configure? in-block-container?]
                                                :as opts}]
@@ -435,38 +439,40 @@
                              nil))}]
          (property-select exclude-properties on-chosen input-opts)))]))
 
-(rum/defcs new-property < rum/reactive rum/static
+(rum/defcs new-property < rum/reactive
   (rum/local false ::new-property?)
   (rum/local nil ::property-key)
   (rum/local nil ::property-value)
-  [state block id opts]
+  [state block id keyboard-triggered? opts]
   (let [*new-property? (::new-property? state)
         container-id (state/sub :editor/properties-container)
-        new-property? (and @*new-property? (= container-id id))]
-    [:div.ls-new-property
-     (let [*property-key (::property-key state)
-           *property-value (::property-value state)]
-       (cond
-         new-property?
-         (property-input block *property-key *property-value opts)
-
-         (and (or (db-property-handler/block-has-viewable-properties? block)
-                  (:page-configure? opts))
-              (not config/publishing?)
-              (not (:in-block-container? opts)))
-         [:a.fade-link.flex.add-property
-          {:on-click (fn []
-                       (state/set-state! :editor/block block)
-                       (state/set-state! :editor/properties-container id)
-                       (reset! *new-property? true)
-                       (reset! *property-key nil)
-                       (reset! *property-value nil))}
-          [:div.flex.flex-row.items-center {:style {:padding-left 1}}
-           (ui/icon "plus" {:size 15})
-           [:div.ml-1.text-sm {:style {:padding-left 2}} "Add property"]]]
-
-         :else
-         [:div {:style {:height 28}}]))]))
+        new-property? (or keyboard-triggered? (and @*new-property? (= container-id id)))]
+
+    (when-not (and (:in-block-container? opts) (not keyboard-triggered?))
+      [:div.ls-new-property
+       (let [*property-key (::property-key state)
+             *property-value (::property-value state)]
+         (cond
+           new-property?
+           (property-input block *property-key *property-value opts)
+
+           (and (or (db-property-handler/block-has-viewable-properties? block)
+                    (:page-configure? opts))
+                (not config/publishing?)
+                (not (:in-block-container? opts)))
+           [:a.fade-link.flex.add-property
+            {:on-click (fn []
+                         (reset! *property-key nil)
+                         (reset! *property-value nil)
+                         (state/set-state! :editor/block block)
+                         (state/set-state! :editor/properties-container id)
+                         (reset! *new-property? true))}
+            [:div.flex.flex-row.items-center {:style {:padding-left 1}}
+             (ui/icon "plus" {:size 15})
+             [:div.ml-1.text-sm {:style {:padding-left 2}} "Add property"]]]
+
+           :else
+           [:div {:style {:height 28}}]))])))
 
 (defn- property-collapsed?
   [block property]
@@ -626,7 +632,7 @@
 (rum/defcs ^:large-vars/cleanup-todo properties-area < rum/reactive
   {:init (fn [state]
            (assoc state ::id (str (random-uuid))))}
-  [state target-block _edit-input-id {:keys [in-block-container? page-configure? class-schema?] :as opts}]
+  [state target-block edit-input-id {:keys [in-block-container? page-configure? class-schema?] :as opts}]
   (let [id (::id state)
         block (resolve-linked-block-if-exists target-block)
         block-properties (:block/properties block)
@@ -688,10 +694,12 @@
                                 (recur (rest classes)
                                        (set/union properties (set cur-properties))
                                        (conj result [class cur-properties])))
-                              result))]
+                              result))
+        keyboard-triggered? (= (state/sub :editor/new-property-input-id) edit-input-id)]
     (when-not (and (empty? block-own-properties)
                    (empty? class->properties)
-                   (not (:page-configure? opts)))
+                   (not (:page-configure? opts))
+                   (not keyboard-triggered?))
       [:div.ls-properties-area (cond-> (if in-block-container?
                                          {:id id}
                                          {:id id
@@ -703,8 +711,7 @@
        (when (and (seq full-hidden-properties) (not class-schema?) (not config/publishing?))
          (hidden-properties block full-hidden-properties opts))
 
-       (when (not in-block-container?)
-         (rum/with-key (new-property block id opts) (str id "-add-property")))
+       (rum/with-key (new-property block id keyboard-triggered? opts) (str id "-add-property"))
 
        (when (and (seq class->properties) (not one-class?))
          (let [page-cp (:page-cp opts)]

+ 5 - 2
src/main/frontend/components/property/value.cljs

@@ -34,6 +34,7 @@
   ([property-configure-check?]
    (when (or (and property-configure-check? (not (:editor/property-configure? @state/state)))
              (not property-configure-check?))
+     (state/set-state! :editor/new-property-input-id nil)
      (state/clear-edit!))))
 
 (defn set-editing!
@@ -208,7 +209,8 @@
 
                  :else
                  (model/get-all-page-original-names repo))
-               distinct)
+               distinct
+               (remove (fn [p] (util/uuid-string? (str p)))))
         options (map (fn [p] {:value p}) pages)
         string-classes (remove #(= :logseq.class %) classes)
         opts' (cond->
@@ -343,7 +345,8 @@
            esc? (= (util/ekey e) "Escape")
            backspace? (= (util/ekey e) "Backspace")
            new-value (util/evalue e)
-           new-property? (:editor/properties-container @state/state)]
+           new-property? (or @(:editor/properties-container @state/state)
+                             @(:editor/new-property-input-id @state/state))]
        (when (and (or enter? esc? backspace?)
                   (not (state/get-editor-action)))
          (when-not backspace? (util/stop e))

+ 32 - 13
src/main/frontend/db_worker.cljs

@@ -31,6 +31,7 @@
 (defonce *datascript-conns worker-state/*datascript-conns)
 (defonce *opfs-pools worker-state/*opfs-pools)
 (defonce *publishing? (atom false))
+(defonce *store-jobs (atom #{}))
 
 (defn- get-pool-name
   [graph-name]
@@ -104,13 +105,15 @@
   [repo _opts]
   (reify IStorage
     (-store [_ addr+data-seq delete-addrs]
-      (prn :debug (str "SQLite store addr+data count: " (count addr+data-seq)))
       (let [data (map
                   (fn [[addr data]]
                     #js {:$addr addr
                          :$content (pr-str data)})
-                  addr+data-seq)]
-        (upsert-addr-content! repo data delete-addrs)))
+                  addr+data-seq)
+            p (p/do! (upsert-addr-content! repo data delete-addrs))]
+        (swap! *store-jobs conj p)
+        (p/then p (fn [] (swap! *store-jobs disj p)))
+        p))
 
     (-restore [_ addr]
       (restore-data-from-addr repo addr))))
@@ -260,9 +263,16 @@
   (createOrOpenDB
    [_this repo & {:keys [close-other-db?]
                   :or {close-other-db? true}}]
-   (p/let [_ (when close-other-db?
-               (close-other-dbs! repo))]
-     (create-or-open-db! repo)))
+   (p/do!
+    ;; Store the current db if store jobs not finished yet
+    (when (seq @*store-jobs)
+      (-> (p/all @*store-jobs)
+          (p/then (fn [_]
+                    (reset! *store-jobs #{})
+                    (println "DB store job finished")))))
+    (when close-other-db?
+      (close-other-dbs! repo))
+    (create-or-open-db! repo)))
 
   (getMaxTx
    [_this repo]
@@ -318,7 +328,7 @@
          nil)
        (catch :default e
          (prn :debug :error)
-         (js/console.error e)))))
+         (js/console.error e tx-data)))))
 
   (getInitialData
    [_this repo]
@@ -404,12 +414,21 @@
        (bean/->js {:result result}))))
 
   (file-writes-finished?
-   [this]
-   (if (empty? @file/*writes)
-     true
-     (do
-       (js/console.log "Unfinished file writes:" @file/*writes)
-       false)))
+   [this repo]
+   (let [conn (worker-state/get-datascript-conn repo)
+         writes @file/*writes]
+
+     ;; Clean pages that have been deleted
+     (when conn
+       (swap! file/*writes (fn [writes]
+                             (->> writes
+                                  (remove (fn [[_ pid]] (d/entity @conn pid)))
+                                  (into {})))))
+     (if (empty? writes)
+       true
+       (do
+         (prn "Unfinished file writes:" @file/*writes)
+         false))))
 
   (page-file-saved
    [this request-id page-id]

+ 21 - 17
src/main/frontend/handler/common/page.cljs

@@ -20,14 +20,13 @@
             [logseq.db :as ldb]
             [frontend.db.conn :as conn]
             [datascript.core :as d]
-            [frontend.handler.editor :as editor-handler]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [logseq.outliner.core :as outliner-core]))
 
 (defn build-hidden-page-tx-data
   [page-name]
   (let [page-name* (str "$$$" page-name)]
-    (assoc (block/page-name->map page-name* false false)
+    (assoc (block/page-name->map page-name* true false)
            :block/type #{"hidden"}
            :block/format :markdown)))
 
@@ -51,8 +50,7 @@
    (let [repo (state/get-current-repo)
          conn (db/get-db repo false)
          config (state/get-config repo)
-         _ (worker-page/create! repo conn config title options)
-         [_ page-name] (worker-page/get-title-and-pagename title)]
+         [_ page-name] (worker-page/create! repo conn config title options)]
      (when redirect?
        (route-handler/redirect-to-page! page-name))
      (when-let [first-block (first (:block/_left (db/entity [:block/name page-name])))]
@@ -68,8 +66,8 @@
    (p/let [repo (state/get-current-repo)
            conn (db/get-db repo false)
            config (state/get-config repo)
-           _ (worker-page/create! repo conn config title options)
-           [_ page-name] (worker-page/get-title-and-pagename title)]
+           [p page-name] (worker-page/create! repo conn config title options)
+           _result p]
      (when redirect?
        (route-handler/redirect-to-page! page-name))
      (let [page (db/entity [:block/name page-name])]
@@ -113,12 +111,12 @@
 (defn- find-block-in-favorites-page
   [page-block-uuid]
   (let [db (conn/get-db)
-        page-block-uuid-str (str page-block-uuid)
         blocks (ldb/get-page-blocks db favorites-page-name {})]
-    (some (fn [block]
-            (when (= page-block-uuid-str (:block/content block))
-              block))
-          blocks)))
+    (when-let [page-block-entity (d/entity db [:block/uuid page-block-uuid])]
+      (some (fn [block]
+              (when (= (:db/id (:block/link block)) (:db/id page-block-entity))
+                block))
+            blocks))))
 
 (defn favorited?-v2
   [page-block-uuid]
@@ -128,13 +126,19 @@
 (defn <favorite-page!-v2
   [page-block-uuid]
   {:pre [(uuid? page-block-uuid)]}
-  (let [favorites-page (d/entity (conn/get-db) [:block/name favorites-page-name])
+  (let [repo (state/get-current-repo)
+        favorites-page (d/entity (conn/get-db) [:block/name favorites-page-name])
         favorites-page-tx-data (build-hidden-page-tx-data "favorites")]
-    (p/do!
-     (when-not favorites-page (ldb/transact! nil [favorites-page-tx-data]))
-     (editor-handler/api-insert-new-block!
-      (str page-block-uuid)
-      {:page favorites-page-name :edit-block? false}))))
+    (when (d/entity (conn/get-db) [:block/uuid page-block-uuid])
+      (p/do!
+       (when-not favorites-page (ldb/transact! nil [favorites-page-tx-data]))
+       (ui-outliner-tx/transact!
+        {:outliner-op :insert-blocks}
+        (outliner-core/insert-blocks! repo (conn/get-db false) [{:block/link [:block/uuid page-block-uuid]
+                                                                 :block/content ""
+                                                                 :block/format :markdown}]
+                                      (d/entity (conn/get-db) [:block/name favorites-page-name])
+                                      {}))))))
 
 (defn <unfavorite-page!-v2
   [page-block-uuid]

+ 1 - 6
src/main/frontend/handler/db_based/property.cljs

@@ -105,12 +105,7 @@
                             :block/uuid property-uuid
                             :block/type "property"})]
                     {:outliner-op :save-block})
-      (db/transact! repo [(sqlite-util/build-new-property
-                           (cond-> {:block/original-name k-name
-                                    :block/name (util/page-name-sanity-lc k-name)
-                                    :block/uuid property-uuid}
-                             (seq schema)
-                             (assoc :block/schema schema)))]
+      (db/transact! repo [(sqlite-util/build-new-property k-name schema property-uuid)]
                     {:outliner-op :insert-blocks}))))
 
 (defn validate-property-value

+ 2 - 2
src/main/frontend/handler/editor.cljs

@@ -345,9 +345,9 @@
                    :else
                    (not has-children?))]
     (p/do!
-     (save-current-block! {:current-block current-block})
      (ui-outliner-tx/transact!
       {:outliner-op :insert-blocks}
+       (save-current-block! {:current-block current-block})
        (outliner-core/insert-blocks! (state/get-current-repo) (db/get-db false)
                                     [new-block] current-block {:sibling? sibling?
                                                                :keep-uuid? keep-uuid?
@@ -408,7 +408,7 @@
                        (wrap-parse-block))
         sibling? (when block-self? false)]
     (p/let [_ (outliner-insert-block! config current-block next-block {:sibling? sibling?
-                                                              :keep-uuid? true})]
+                                                                       :keep-uuid? true})]
       (util/set-change-value input fst-block-text)
       (assoc next-block :block/content snd-block-text))))
 

+ 12 - 10
src/main/frontend/handler/events.cljs

@@ -189,7 +189,7 @@
 
 (defmethod handle :graph/switch [[_ graph opts]]
   (let [^js sqlite @db-browser/*worker]
-    (p/let [writes-finished? (when sqlite (.file-writes-finished? sqlite))
+    (p/let [writes-finished? (when sqlite (.file-writes-finished? sqlite (state/get-current-repo)))
             request-finished? (ldb/request-finished?)]
       (if (or (not request-finished?) (not writes-finished?)) ; TODO: test (:sync-graph/init? @state/state)
         (do
@@ -369,7 +369,7 @@
 (defmethod handle :file/not-matched-from-disk [[_ path disk-content db-content]]
   (when-let [repo (state/get-current-repo)]
     (let [^js sqlite @db-browser/*worker]
-      (p/let [writes-finished? (when sqlite (.file-writes-finished? sqlite))
+      (p/let [writes-finished? (when sqlite (.file-writes-finished? sqlite (state/get-current-repo)))
               request-finished? (ldb/request-finished?)]
         (prn :debug :writes-finished? writes-finished?
              :request-finished? request-finished?)
@@ -921,14 +921,16 @@
     (editor-handler/toggle-blocks-as-own-order-list! blocks)))
 
 (defmethod handle :editor/new-property [[_]]
-  (when-let [edit-block (state/get-edit-block)]
-    (when-let [block-id (:block/uuid edit-block)]
-      (let [block (db/entity [:block/uuid block-id])
-            collapsed? (or (get-in @state/state [:ui/collapsed-blocks (state/get-current-repo) block-id])
-                           (:block/collapsed? block))]
-        (when collapsed?
-          (editor-handler/set-blocks-collapsed! [block-id] false)))))
-  (property-handler/editing-new-property!))
+  (p/do!
+    (when-let [edit-block (state/get-edit-block)]
+     (when-let [block-id (:block/uuid edit-block)]
+       (let [block (db/entity [:block/uuid block-id])
+             collapsed? (or (get-in @state/state [:ui/collapsed-blocks (state/get-current-repo) block-id])
+                            (:block/collapsed? block))]
+         (when collapsed?
+           (editor-handler/set-blocks-collapsed! [block-id] false)))))
+    (editor-handler/save-current-block!)
+    (property-handler/editing-new-property!)))
 
 (rum/defc multi-tabs-dialog
   []

+ 14 - 11
src/main/frontend/handler/page.cljs

@@ -82,10 +82,12 @@
   (when-let [db (conn/get-db)]
     (let [repo (state/get-current-repo)]
       (if (config/db-based-graph? repo)
-        (let [blocks (ldb/get-page-blocks db page-common-handler/favorites-page-name {})]
+        (let [blocks (ldb/sort-by-left
+                      (ldb/get-page-blocks db page-common-handler/favorites-page-name {})
+                      (d/entity db [:block/name page-common-handler/favorites-page-name]))]
           (keep (fn [block]
-                  (when-let [block-uuid (some-> (:block/content block) parse-uuid)]
-                    (d/entity db [:block/uuid block-uuid]))) blocks))
+                  (when-let [block-db-id (:db/id (:block/link block))]
+                    (d/entity db block-db-id))) blocks))
         (let [page-names (->> (:favorites (state/sub-config))
                               (remove string/blank?)
                               (filter string?)
@@ -135,20 +137,21 @@
   [favorites]
   (let [repo (state/get-current-repo)
         conn (conn/get-db false)]
-    (when (d/entity @conn [:block/name page-common-handler/favorites-page-name])
-      (let [favorite-page-block-uuid-coll
+    (when-let [favorites-page-entity (d/entity @conn [:block/name page-common-handler/favorites-page-name])]
+      (let [favorite-page-block-db-id-coll
             (keep (fn [page-name]
                     (some-> (d/entity @conn [:block/name (common-util/page-name-sanity-lc page-name)])
-                            :block/uuid
-                            str))
+                            :db/id))
                   favorites)
-            current-blocks (ldb/get-page-blocks @conn page-common-handler/favorites-page-name {})]
+            current-blocks (ldb/sort-by-left (ldb/get-page-blocks @conn page-common-handler/favorites-page-name {})
+                                             favorites-page-entity)]
+        (prn :favorite-page-block-db-id-coll favorite-page-block-db-id-coll)
         (ui-outliner-tx/transact!
          {}
-         (doseq [[page-block-uuid block] (zipmap favorite-page-block-uuid-coll current-blocks)]
-           (when (not= page-block-uuid (:block/content block))
+         (doseq [[page-block-db-id block] (zipmap favorite-page-block-db-id-coll current-blocks)]
+           (when (not= page-block-db-id (:db/id (:block/link block)))
              (outliner-core/save-block! repo conn (state/get-date-formatter)
-                                        (assoc block :block/content page-block-uuid)))))))))
+                                        (assoc block :block/link page-block-db-id)))))))))
 
 (defn has-more-journals?
   []

+ 5 - 0
src/main/frontend/handler/property.cljs

@@ -31,8 +31,13 @@
        (set-block-property! repo (:block/uuid page) key value))
       (file-page-property/add-property! page-name key value))))
 
+(defn set-editing-new-property!
+  [value]
+  (state/set-state! :editor/new-property-input-id value))
+
 (defn editing-new-property!
   []
+  (set-editing-new-property! (state/get-edit-input-id))
   (state/clear-edit!))
 
 (defn remove-id-property

+ 17 - 1
src/main/frontend/mobile/mobile_bar.cljs

@@ -13,11 +13,21 @@
             [goog.dom :as gdom]
             [rum.core :as rum]))
 
+
+(defn- blur-if-compositing
+  "Call blur on the textarea if it is in composition mode, let the IME commit the composing text"
+  []
+  (when-let [edit-input-id (and (state/editor-in-composition?)
+                                (state/get-edit-input-id))]
+    (let [textarea-el (gdom/getElement edit-input-id)]
+      (.blur textarea-el))))
+
 (rum/defc indent-outdent [indent? icon]
   [:div
    [:button.bottom-action
     {:on-mouse-down (fn [e]
                       (util/stop e)
+                      (blur-if-compositing)
                       (editor-handler/indent-outdent indent?))}
     (ui/icon icon {:size ui/icon-size})]])
 
@@ -91,7 +101,13 @@
         (command #(if (state/sub :document/mode?)
                     (editor-handler/insert-new-block! nil)
                     (commands/simple-insert! parent-id "\n" {})) {:icon "arrow-back"})
-        (command editor-handler/cycle-todo! {:icon "checkbox"} true)
+        ;; On mobile devies, some IME(keyboard) uses composing mode.
+        ;; The composing text can be committed by losing focus.
+        ;; 100ms is enough to commit the composing text to db.
+        (command #(do
+                    (blur-if-compositing)
+                    (editor-handler/cycle-todo!))
+                 {:icon "checkbox"} true)
         (command #(mobile-camera/embed-photo parent-id) {:icon "camera"} true)
         (command history/undo! {:icon "rotate" :class "rotate-180"} true)
         (command history/redo! {:icon "rotate-clockwise" :class "rotate-180"} true)

+ 2 - 1
src/main/frontend/state.cljs

@@ -130,7 +130,8 @@
       :editor/in-composition?                false
       :editor/content                        (atom {})
       :editor/block                          (atom nil)
-      :editor/properties-container                           (atom nil)
+      :editor/new-property-input-id          (atom nil)
+      :editor/properties-container           (atom nil)
       :editor/block-dom-id                   (atom nil)
       :editor/set-timestamp-block            (atom nil) ;; click rendered block timestamp-cp to set timestamp
       :editor/last-input-time                (atom {})

+ 10 - 6
src/main/frontend/worker/file.cljs

@@ -66,22 +66,26 @@
         blocks-count (ldb/get-page-blocks-count @conn page-db-id)
         blocks-just-deleted? (and (zero? blocks-count)
                                   (contains? #{:delete-blocks :move-blocks} outliner-op))]
-    (when (or (>= blocks-count 1) blocks-just-deleted?)
+    (if (or (>= blocks-count 1) blocks-just-deleted?)
       (if (and (or (> blocks-count 500) whiteboard?)
                (not (worker-state/tx-idle? repo {:diff 3000})))
         (async/put! file-writes-chan [repo page-db-id outliner-op (tc/to-long (t/now)) request-id])
         (let [pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*])
               blocks (ldb/get-page-blocks @conn (:block/name page-block) {:pull-keys pull-keys})
               blocks (if whiteboard? (map cleanup-whiteboard-block blocks) blocks)]
-          (when-not (and (= 1 (count blocks))
-                         (string/blank? (:block/content (first blocks)))
-                         (nil? (:block/file page-block))
-                         (not whiteboard?))
+          (if (and (= 1 (count blocks))
+                   (string/blank? (:block/content (first blocks)))
+                   (nil? (:block/file page-block))
+                   (not whiteboard?))
+            (dissoc-request! request-id)
             (let [tree-or-blocks (if whiteboard? blocks
                                      (otree/blocks->vec-tree repo @conn blocks (:block/name page-block)))]
               (if page-block
                 (file/save-tree! repo conn page-block tree-or-blocks blocks-just-deleted? context request-id)
-                (js/console.error (str "can't find page id: " page-db-id))))))))))
+                (do
+                  (js/console.error (str "can't find page id: " page-db-id))
+                  (dissoc-request! request-id)))))))
+      (dissoc-request! request-id))))
 
 (defn write-files!
   [conn pages context]

+ 6 - 1
src/main/frontend/worker/file/core.cljs

@@ -21,7 +21,12 @@
 
 (defn dissoc-request!
   [request-id]
-  (swap! *writes dissoc request-id))
+  (when-let [page-id (get @*writes request-id)]
+    (let [old-page-request-ids (keep (fn [[r p]]
+                                       (when (and (= p page-id) (<= r request-id))
+                                         r)) @*writes)]
+      (when (seq old-page-request-ids)
+        (swap! *writes (fn [x] (apply dissoc x old-page-request-ids)))))))
 
 (defn- indented-block-content
   [content spaces-tabs]

+ 48 - 47
src/main/frontend/worker/handler/page.cljs

@@ -81,56 +81,57 @@
                                   (date/valid-journal-title? date-formatter title)))
 
         [title page-name] (get-title-and-pagename title)
-        with-uuid? (if (uuid? uuid) uuid true)] ;; FIXME: prettier validation
-    (when (ldb/page-empty? @conn page-name)
-      (let [pages    (if split-namespace?
-                       (common-util/split-namespace-pages title)
-                       [title])
-            format   (or format (common-config/get-preferred-format config))
-            pages    (map (fn [page]
+        with-uuid? (if (uuid? uuid) uuid true)
+        result (when (ldb/page-empty? @conn page-name)
+                 (let [pages    (if split-namespace?
+                                  (common-util/split-namespace-pages title)
+                                  [title])
+                       format   (or format (common-config/get-preferred-format config))
+                       pages    (map (fn [page]
                              ;; only apply uuid to the deepest hierarchy of page to create if provided.
-                            (-> (gp-block/page-name->map page (if (= page title) with-uuid? true) @conn true date-formatter)
-                                (assoc :block/format format)))
-                          pages)
-            txs      (->> pages
+                                       (-> (gp-block/page-name->map page (if (= page title) with-uuid? true) @conn true date-formatter)
+                                           (assoc :block/format format)))
+                                     pages)
+                       txs      (->> pages
                            ;; for namespace pages, only last page need properties
-                          drop-last
-                          (mapcat #(build-page-tx repo conn config date-formatter format nil % {}))
-                          (remove nil?))
-            txs      (map-indexed (fn [i page]
-                                    (if (zero? i)
-                                      page
-                                      (assoc page :block/namespace
-                                             [:block/uuid (:block/uuid (nth txs (dec i)))])))
-                                  txs)
-            page-txs (build-page-tx repo conn config date-formatter format properties (last pages) (select-keys options [:whiteboard? :class? :tags]))
-            page-txs (if (seq txs)
-                       (update page-txs 0
-                               (fn [p]
-                                 (assoc p :block/namespace [:block/uuid (:block/uuid (last txs))])))
-                       page-txs)
-            first-block-tx (when (and
-                                  create-first-block?
-                                  (not (or whiteboard? class?))
-                                  (ldb/page-empty? @conn (:db/id (d/entity @conn [:block/name page-name])))
+                                     drop-last
+                                     (mapcat #(build-page-tx repo conn config date-formatter format nil % {}))
+                                     (remove nil?))
+                       txs      (map-indexed (fn [i page]
+                                               (if (zero? i)
+                                                 page
+                                                 (assoc page :block/namespace
+                                                        [:block/uuid (:block/uuid (nth txs (dec i)))])))
+                                             txs)
+                       page-txs (build-page-tx repo conn config date-formatter format properties (last pages) (select-keys options [:whiteboard? :class? :tags]))
+                       page-txs (if (seq txs)
+                                  (update page-txs 0
+                                          (fn [p]
+                                            (assoc p :block/namespace [:block/uuid (:block/uuid (last txs))])))
                                   page-txs)
-                             (let [page-id [:block/uuid (:block/uuid (first page-txs))]]
-                               [(sqlite-util/block-with-timestamps
-                                 {:block/uuid (ldb/new-block-id)
-                                  :block/page page-id
-                                  :block/parent page-id
-                                  :block/left page-id
-                                  :block/content ""
-                                  :block/format format})]))
-            txs      (concat
-                      txs
-                      page-txs
-                      first-block-tx)]
-        (when (seq txs)
-          (ldb/transact! conn txs (cond-> {:persist-op? persist-op?}
-                                    today-journal?
-                                    (assoc :create-today-journal? true
-                                           :today-journal-name page-name))))))))
+                       first-block-tx (when (and
+                                             create-first-block?
+                                             (not (or whiteboard? class?))
+                                             (ldb/page-empty? @conn (:db/id (d/entity @conn [:block/name page-name])))
+                                             page-txs)
+                                        (let [page-id [:block/uuid (:block/uuid (first page-txs))]]
+                                          [(sqlite-util/block-with-timestamps
+                                            {:block/uuid (ldb/new-block-id)
+                                             :block/page page-id
+                                             :block/parent page-id
+                                             :block/left page-id
+                                             :block/content ""
+                                             :block/format format})]))
+                       txs      (concat
+                                 txs
+                                 page-txs
+                                 first-block-tx)]
+                   (when (seq txs)
+                     (ldb/transact! conn txs (cond-> {:persist-op? persist-op?}
+                                               today-journal?
+                                               (assoc :create-today-journal? true
+                                                      :today-journal-name page-name))))))] ;; FIXME: prettier validation
+    [result page-name]))
 
 (defn db-refs->page
   "Replace [[page name]] with page name"

+ 2 - 1
src/main/frontend/worker/pipeline.cljs

@@ -82,7 +82,8 @@
               _ (when (sqlite-util/local-file-based-graph? repo)
                   (let [page-ids (distinct (map :db/id pages))]
                     (doseq [page-id page-ids]
-                      (file/sync-to-file repo page-id tx-meta))))
+                      (when (d/entity @conn page-id)
+                        (file/sync-to-file repo page-id tx-meta)))))
               deleted-block-uuids (set (outliner-pipeline/filter-deleted-blocks (:tx-data tx-report)))
               replace-tx (concat
                           ;; block path refs

+ 4 - 4
yarn.lock

@@ -537,10 +537,10 @@
     "@jridgewell/resolve-uri" "^3.1.0"
     "@jridgewell/sourcemap-codec" "^1.4.14"
 
-"@logseq/[email protected].1":
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-5.0.1.tgz#e154f715597785518ccd7d058f353acb67fbc2b8"
-  integrity sha512-C1fLSS53orxsUWBsNb6LKwuOdlEU9ZhxkweMjNKG9VaSkLFTFqpjFG36OTso23WQ7hC5e45jjXq79aoWuqJaKA==
+"@logseq/[email protected].2":
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-5.0.2.tgz#10c56e35b41b1a0afd293c9b045fbcfe150c3477"
+  integrity sha512-FymsTeRtF66zG+oeO+ohZxWICMQMC8An4n9pdI0zz1WaGLer4oWC/lUghlC2DpztRLA32p0CH28tEzF5+2jARg==
 
 "@logseq/[email protected]":
   version "0.2.2"