Browse Source

fix: able to convert an existing page to a property (#11940)

* enhance: use page as property

* fix: ensures block/uuid doesn't change after converting to property
Tienson Qin 4 months ago
parent
commit
88216d2992

+ 4 - 4
deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs

@@ -1,11 +1,11 @@
 (ns logseq.graph-parser.test.docs-graph-helper
   "Helper fns for setting up and running tests against docs graph"
-  (:require ["fs" :as fs]
-            ["child_process" :as child-process]
+  (:require ["child_process" :as child-process]
+            ["fs" :as fs]
             [cljs.test :refer [is testing]]
             [clojure.string :as string]
-            [logseq.common.config :as common-config]
-            [datascript.core :as d]))
+            [datascript.core :as d]
+            [logseq.common.config :as common-config]))
 
 ;; Helper fns for test setup
 ;; =========================

+ 3 - 3
deps/graph-parser/test/logseq/graph_parser/cli_test.cljs

@@ -1,9 +1,9 @@
 (ns ^:node-only logseq.graph-parser.cli-test
   (:require [cljs.test :refer [deftest is testing]]
-            [logseq.graph-parser.cli :as gp-cli]
-            [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
             [clojure.string :as string]
-            [datascript.core :as d]))
+            [datascript.core :as d]
+            [logseq.graph-parser.cli :as gp-cli]
+            [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]))
 
 ;; Integration test that test parsing a large graph like docs
 (deftest ^:integration parse-graph

+ 1 - 1
deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs

@@ -9,8 +9,8 @@
             [logseq.common.graph :as common-graph]
             [logseq.common.util.date-time :as date-time-util]
             [logseq.db :as ldb]
-            [logseq.db.frontend.content :as db-content]
             [logseq.db.common.entity-plus :as entity-plus]
+            [logseq.db.frontend.content :as db-content]
             [logseq.db.frontend.malli-schema :as db-malli-schema]
             [logseq.db.frontend.rules :as rules]
             [logseq.db.frontend.validate :as db-validate]

+ 5 - 6
deps/graph-parser/test/logseq/graph_parser/mldoc_test.cljs

@@ -1,10 +1,10 @@
 (ns logseq.graph-parser.mldoc-test
-  (:require [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.text :as text]
+  (:require [cljs.test :refer [testing deftest are is]]
             [clojure.string :as string]
-            [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
             [logseq.graph-parser.cli :as gp-cli]
-            [cljs.test :refer [testing deftest are is]]))
+            [logseq.graph-parser.mldoc :as gp-mldoc]
+            [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
+            [logseq.graph-parser.text :as text]))
 
 (deftest test-link
   (testing "non-link"
@@ -127,7 +127,7 @@ body"
       line 2
  line 3
 line 4"]
-              (gp-mldoc/remove-indentation-spaces s 2 false)))) 
+              (gp-mldoc/remove-indentation-spaces s 2 false))))
     (is (=  "\t- block 1.1\n  line 1\n    line 2\nline 3\nline 4"
             (let [s "\t- block 1.1
 \t    line 1
@@ -135,7 +135,6 @@ line 4"]
 \t line 3
 \tline 4"]
               (gp-mldoc/remove-indentation-spaces s 3 false))))))
-    
 
 (deftest ^:integration test->edn
   (let [graph-dir "test/resources/docs-0.10.12"

+ 13 - 4
deps/outliner/src/logseq/outliner/property.cljs

@@ -411,7 +411,7 @@
   "Updates property if property-id is given. Otherwise creates a property
    with the given property-id or :property-name option. When a property is created
    it is ensured to have a unique :db/ident"
-  [conn property-id schema {:keys [property-name] :as opts}]
+  [conn property-id schema {:keys [property-name properties] :as opts}]
   (let [db @conn
         db-ident (or property-id
                      (try (db-property/create-user-property-ident-from-name property-name)
@@ -430,9 +430,18 @@
                 (prn "property-id: " property-id ", property-name: " property-name))
         (outliner-validate/validate-page-title k-name {:node {:db/ident db-ident'}})
         (outliner-validate/validate-page-title-characters k-name {:node {:db/ident db-ident'}})
-        (ldb/transact! conn
-                       [(sqlite-util/build-new-property db-ident' schema {:title k-name})]
-                       {:outliner-op :new-property})
+        (let [db-id (:db/id properties)
+              opts (cond-> {:title k-name
+                            :properties properties}
+                     (integer? db-id)
+                     (assoc :block-uuid (:block/uuid (d/entity db db-id))))]
+          (ldb/transact! conn
+                         (concat
+                          [(sqlite-util/build-new-property db-ident' schema opts)]
+                          ;; Convert page to property
+                          (when db-id
+                            [[:db/retract db-id :block/tags :logseq.class/Page]]))
+                         {:outliner-op :upsert-property}))
         (d/entity @conn db-ident')))))
 
 (defn delete-property-value!

+ 69 - 51
src/main/frontend/components/property.cljs

@@ -36,31 +36,31 @@
 
 (defn- <add-property-from-dropdown
   "Adds an existing or new property from dropdown. Used from a block or page context."
-  [entity property-uuid-or-name schema {:keys [class-schema?]}]
+  [entity id-or-name* schema {:keys [class-schema? block-uuid]}]
   (p/let [repo (state/get-current-repo)
+          id-or-name (or block-uuid id-or-name*)
           ;; Both conditions necessary so that a class can add its own page properties
           add-class-property? (and (ldb/class? entity) class-schema?)
-          result (when (uuid? property-uuid-or-name)
-                   (db-async/<get-block repo property-uuid-or-name {:children? false}))
-          ;; In block context result is in :block
-          property (some-> (if (:block result) (:db/id (:block result)) (:db/id result))
-                           db/entity)]
+          property (db-async/<get-block repo id-or-name {:children? false})
+          property? (ldb/property? property)
+          property-title (or (:block/title property) id-or-name)]
     ;; existing property selected or entered
-    (if property
+    (if property?
       (do
         (when (and (not (ldb/public-built-in-property? property))
                    (ldb/built-in? property))
           (notification/show! "This is a private built-in property that can't be used." :error))
         property)
-      ;; new property entered
-      (if (db-property/valid-property-name? property-uuid-or-name)
-        (if add-class-property?
-          (p/let [result (db-property-handler/upsert-property! nil schema {:property-name property-uuid-or-name})
-                  property (db/entity (:db/id result))
-                  _ (pv/<add-property! entity (:db/ident property) "" {:class-schema? class-schema? :exit-edit? false})]
-            property)
-          (p/let [result (db-property-handler/upsert-property! nil schema {:property-name property-uuid-or-name})]
-            (db/entity (:db/id result))))
+      ;; new property entered or converting page to property
+      (if (db-property/valid-property-name? property-title)
+        (p/let [opts (cond-> {:property-name property-title}
+                       (and (not property?) (ldb/internal-page? property))
+                       (assoc :properties {:db/id (:db/id property)}))
+                result (db-property-handler/upsert-property! nil schema opts)
+                property (db/entity (:db/id result))
+                _ (when add-class-property?
+                    (pv/<add-property! entity (:db/ident property) "" {:class-schema? class-schema? :exit-edit? false}))]
+          property)
         (notification/show! "This is an invalid property name. A property name cannot start with page reference characters '#' or '[['." :error)))))
 
 ;; TODO: This component should be cleaned up as it's only used for new properties and used to be used for existing properties
@@ -147,7 +147,8 @@
 
 (rum/defc property-select
   [select-opts]
-  (let [[properties set-properties!] (rum/use-state nil)]
+  (let [[properties set-properties!] (hooks/use-state nil)
+        [q set-q!] (hooks/use-state "")]
     (hooks/use-effect!
      (fn []
        (p/let [repo (state/get-current-repo)
@@ -156,10 +157,24 @@
                             (db-model/get-all-properties repo {:remove-ui-non-suitable-properties? true}))]
          (set-properties! properties)))
      [])
+    (hooks/use-effect!
+     (fn []
+       (p/let [repo (state/get-current-repo)
+               block (when-not (string/blank? q)
+                       (db-async/<get-block repo q {:children? false}))
+               internal-page-exists? (ldb/internal-page? block)]
+         (when internal-page-exists?
+           (set-properties!
+            (cons (assoc block :convert-page-to-property? true) properties)))))
+     [q])
     (let [items (->>
                  (map (fn [x]
-                        {:label (:block/title x)
-                         :value (:block/uuid x)}) properties)
+                        (let [convert? (:convert-page-to-property? x)]
+                          {:label (if convert?
+                                    (util/format "Convert \"%s\" to property" (:block/title x))
+                                    (:block/title x))
+                           :value (:block/uuid x)
+                           :convert-page-to-property? convert?})) properties)
                  (util/distinct-by-last-wins :value))]
       [:div.ls-property-add.flex.flex-row.items-center.property-key
        {:data-keep-selection true}
@@ -173,7 +188,8 @@
                          :new-case-sensitive? true
                          :show-new-when-not-exact-match? true
                          ;; :exact-match-exclude-items (fn [s] (contains? excluded-properties s))
-                         :input-default-placeholder "Add or change property"}
+                         :input-default-placeholder "Add or change property"
+                         :on-input set-q!}
                         select-opts))]])))
 
 (rum/defc property-icon
@@ -201,9 +217,9 @@
 
 (defn- property-input-on-chosen
   [block *property *property-key *show-new-property-config? {:keys [class-schema? remove-property?]}]
-  (fn [{:keys [value label]}]
-    (reset! *property-key (if (uuid? value) label value))
+  (fn [{:keys [value label convert-page-to-property?]}]
     (let [property (when (uuid? value) (db/entity [:block/uuid value]))
+          _ (reset! *property-key (if (uuid? value) (if convert-page-to-property? (:block/title property) label) value))
           batch? (pv/batch-operation?)
           repo (state/get-current-repo)]
       (if (and property remove-property?)
@@ -214,35 +230,37 @@
           (when (and *show-new-property-config? (not (ldb/property? property)))
             (reset! *show-new-property-config? true))
           (reset! *property property)
-          (when property
-            (let [add-class-property? (and (ldb/class? block) class-schema?)
-                  type (:logseq.property/type property)
-                  default-or-url? (and (contains? #{:default :url} type)
-                                       (not (seq (:property/closed-values property))))]
-              (cond
-                add-class-property?
-                (p/do!
-                 (pv/<add-property! block (:db/ident property) "" {:class-schema? class-schema?})
-                 (shui/popup-hide!))
-
-                (and batch? (or (= :checkbox type) (and batch? default-or-url?)))
-                nil
-
-                (= :checkbox type)
-                (p/do!
-                 (ui/hide-popups-until-preview-popup!)
-                 (shui/popup-hide!)
-                 (let [value (if-some [value (:logseq.property/scalar-default-value property)]
-                               value
-                               false)]
-                   (pv/<add-property! block (:db/ident property) value {:exit-edit? true})))
-
-                default-or-url?
-                (pv/<create-new-block! block property "" {:batch-op? true})
-
-                (or (not= :default type)
-                    (and (= :default type) (seq (:property/closed-values property))))
-                (reset! *show-new-property-config? false)))))))))
+          (when-not convert-page-to-property?
+            (let [property' (some-> (:db/id property) db/entity)]
+              (when (and property' (ldb/property? property'))
+                (let [add-class-property? (and (ldb/class? block) class-schema?)
+                      type (:logseq.property/type property')
+                      default-or-url? (and (contains? #{:default :url} type)
+                                           (not (seq (:property/closed-values property'))))]
+                  (cond
+                    add-class-property?
+                    (p/do!
+                     (pv/<add-property! block (:db/ident property') "" {:class-schema? class-schema?})
+                     (shui/popup-hide!))
+
+                    (and batch? (or (= :checkbox type) (and batch? default-or-url?)))
+                    nil
+
+                    (= :checkbox type)
+                    (p/do!
+                     (ui/hide-popups-until-preview-popup!)
+                     (shui/popup-hide!)
+                     (let [value (if-some [value (:logseq.property/scalar-default-value property')]
+                                   value
+                                   false)]
+                       (pv/<add-property! block (:db/ident property') value {:exit-edit? true})))
+
+                    default-or-url?
+                    (pv/<create-new-block! block property' "" {:batch-op? true})
+
+                    (or (not= :default type)
+                        (and (= :default type) (seq (:property/closed-values property'))))
+                    (reset! *show-new-property-config? false)))))))))))
 
 (rum/defc property-key-title
   [block property class-schema?]

+ 1 - 2
src/main/frontend/worker/db/migrate.cljs

@@ -1,7 +1,6 @@
 (ns frontend.worker.db.migrate
   "Handles SQLite and datascript migrations for DB graphs"
-  (:require [cljs-bean.core :as bean]
-            [clojure.walk :as walk]
+  (:require [clojure.walk :as walk]
             [datascript.core :as d]
             [datascript.impl.entity :as de]
             [frontend.worker.util :as worker-util]

+ 11 - 11
src/test/frontend/handler/repo_test.cljs

@@ -1,17 +1,17 @@
 (ns frontend.handler.repo-test
-  (:require [cljs.test :refer [deftest use-fixtures testing is]]
+  (:require ["fs" :as fs]
+            ["path" :as node-path]
+            [cljs.test :refer [deftest use-fixtures testing is]]
+            [clojure.edn :as edn]
+            [datascript.core :as d]
+            [frontend.db.conn :as conn]
+            [frontend.db.model :as model]
             [frontend.handler.file-based.repo :as file-repo-handler]
             [frontend.test.helper :as test-helper :refer [load-test-files]]
-            [logseq.graph-parser.cli :as gp-cli]
-            [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
+            [frontend.worker.state :as worker-state]
             [logseq.common.util.block-ref :as block-ref]
-            [frontend.db.model :as model]
-            [frontend.db.conn :as conn]
-            [datascript.core :as d]
-            [clojure.edn :as edn]
-            ["path" :as node-path]
-            ["fs" :as fs]
-            [frontend.worker.state :as worker-state]))
+            [logseq.graph-parser.cli :as gp-cli]
+            [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]))
 
 (use-fixtures :each test-helper/start-and-destroy-db)
 
@@ -51,7 +51,7 @@
 
 (deftest parse-files-and-load-to-db-with-page-rename
   (testing
-    "Reload a file when the disk contents result in the file having a new page name"
+   "Reload a file when the disk contents result in the file having a new page name"
     (let [test-uuid "16c90195-6a03-4b3f-839d-095a496d9efc"
           target-page-content (str "- target block\n  id:: " test-uuid)
           referring-page-content (str "- " (block-ref/->block-ref test-uuid))