فهرست منبع

fix: disallow built-in page or property names from being edited

fixes LOG-3049
Tienson Qin 1 سال پیش
والد
کامیت
4c03e58b6c

+ 12 - 3
deps/db/src/logseq/db.cljs

@@ -65,8 +65,8 @@
        (let [f (or @*transact-fn d/transact!)]
        (let [f (or @*transact-fn d/transact!)]
          (f repo-or-conn tx-data tx-meta))))))
          (f repo-or-conn tx-data tx-meta))))))
 
 
-(defn build-default-pages-tx
-  []
+(defn build-pages-tx
+  [pages]
   (let [time (common-util/time-ms)]
   (let [time (common-util/time-ms)]
     (map
     (map
      (fn [m]
      (fn [m]
@@ -74,7 +74,11 @@
            (assoc :block/created-at time)
            (assoc :block/created-at time)
            (assoc :block/updated-at time)
            (assoc :block/updated-at time)
            (assoc :block/format :markdown)))
            (assoc :block/format :markdown)))
-     default-db/built-in-pages)))
+      pages)))
+
+(defn build-default-pages-tx
+  []
+  (build-pages-tx default-db/built-in-pages))
 
 
 (defn create-default-pages!
 (defn create-default-pages!
   "Creates default pages if one of the default pages does not exist. This
   "Creates default pages if one of the default pages does not exist. This
@@ -544,6 +548,11 @@
     (when (seq eids)
     (when (seq eids)
       (d/pull-many db '[*] eids))))
       (d/pull-many db '[*] eids))))
 
 
+(defn built-in?
+  "Built-in page or block"
+  [entity]
+  (:built-in? (:block/metadata entity)))
+
 (comment
 (comment
   (defn db-based-graph?
   (defn db-based-graph?
     "Whether the current graph is db-only"
     "Whether the current graph is db-only"

+ 3 - 0
deps/db/src/logseq/db/frontend/class.cljs

@@ -5,5 +5,8 @@
 (def ^:large-vars/data-var built-in-classes
 (def ^:large-vars/data-var built-in-classes
   {:task {:original-name "Task"
   {:task {:original-name "Task"
           :schema {:properties ["status" "priority"]}}
           :schema {:properties ["status" "priority"]}}
+   :card {:original-name "card"
+          ;; :schema {:property []}
+          }
    ;; TODO: Add more classes such as :book, :paper, :movie, :music, :project
    ;; TODO: Add more classes such as :book, :paper, :movie, :music, :project
    })
    })

+ 8 - 6
deps/db/src/logseq/db/frontend/default.cljs

@@ -15,10 +15,12 @@
    (set built-in-priorities)
    (set built-in-priorities)
    #{"Favorites" "Contents" "card"}))
    #{"Favorites" "Contents" "card"}))
 
 
+(defn page-title->block
+  [title]
+  {:block/name (string/lower-case title)
+   :block/original-name title
+   :block/journal? false
+   :block/uuid (random-uuid)})
+
 (def built-in-pages
 (def built-in-pages
-  (mapv (fn [p]
-          {:block/name (string/lower-case p)
-           :block/original-name p
-           :block/journal? false
-           :block/uuid (random-uuid)})
-        built-in-pages-names))
+  (mapv page-title->block built-in-pages-names))

+ 35 - 25
deps/db/src/logseq/db/sqlite/create_graph.cljs

@@ -7,7 +7,12 @@
             [logseq.db.frontend.property.util :as db-property-util]
             [logseq.db.frontend.property.util :as db-property-util]
             [logseq.common.util :as common-util]
             [logseq.common.util :as common-util]
             [datascript.core :as d]
             [datascript.core :as d]
-            [logseq.db :as ldb]))
+            [logseq.db :as ldb]
+            [logseq.db.frontend.default :as default-db]))
+
+(defn- mark-block-as-built-in
+  [block]
+  (update block :block/metadata assoc :built-in? true))
 
 
 (defn- build-initial-properties
 (defn- build-initial-properties
   []
   []
@@ -21,18 +26,19 @@
                              (into {}))]
                              (into {}))]
     (mapcat
     (mapcat
      (fn [[k-keyword {:keys [schema original-name closed-values db-ident]}]]
      (fn [[k-keyword {:keys [schema original-name closed-values db-ident]}]]
-       (let [k-name (name k-keyword)]
-         (if closed-values
-           (db-property-util/build-closed-values
-            (or original-name k-name)
-            {:block/schema schema :block/uuid (d/squuid) :closed-values closed-values}
-            {:icon-id (get default-property-uuids :icon)
-             :db-ident db-ident})
-           [(sqlite-util/build-new-property
-             (or original-name k-name)
-             schema
-             (get default-property-uuids k-keyword (d/squuid))
-             {:db-ident db-ident})])))
+       (let [k-name (name k-keyword)
+             blocks (if closed-values
+                      (db-property-util/build-closed-values
+                       (or original-name k-name)
+                       {:block/schema schema :block/uuid (d/squuid) :closed-values closed-values}
+                       {:icon-id (get default-property-uuids :icon)
+                        :db-ident db-ident})
+                      [(sqlite-util/build-new-property
+                        (or original-name k-name)
+                        schema
+                        (get default-property-uuids k-keyword (d/squuid))
+                        {:db-ident db-ident})])]
+         (update blocks 0 mark-block-as-built-in)))
      built-in-properties)))
      built-in-properties)))
 
 
 (defn build-db-initial-data
 (defn build-db-initial-data
@@ -51,7 +57,8 @@
                         :file/path (str "logseq/" "custom.js")
                         :file/path (str "logseq/" "custom.js")
                         :file/content ""
                         :file/content ""
                         :file/last-modified-at (js/Date.)}]
                         :file/last-modified-at (js/Date.)}]
-        default-pages (ldb/build-default-pages-tx)
+        default-pages (->> (ldb/build-pages-tx (map default-db/page-title->block ["Contents"]))
+                           (map mark-block-as-built-in))
         default-properties (build-initial-properties)
         default-properties (build-initial-properties)
         name->properties (zipmap
         name->properties (zipmap
                           (map :block/name default-properties)
                           (map :block/name default-properties)
@@ -59,16 +66,19 @@
         default-classes (map
         default-classes (map
                          (fn [[k-keyword {:keys [schema original-name]}]]
                          (fn [[k-keyword {:keys [schema original-name]}]]
                            (let [k-name (name k-keyword)]
                            (let [k-name (name k-keyword)]
-                             (sqlite-util/build-new-class
-                              {:block/original-name (or original-name k-name)
-                               :block/name (common-util/page-name-sanity-lc k-name)
-                               :block/uuid (d/squuid)
-                               :block/schema {:properties
-                                              (mapv
-                                               (fn [property-name]
-                                                 (let [id (:block/uuid (get name->properties property-name))]
-                                                   (assert id (str "Built-in property " property-name " is not defined yet"))
-                                                   id))
-                                               (:properties schema))}})))
+                             (mark-block-as-built-in
+                              (sqlite-util/build-new-class
+                               (let [properties (mapv
+                                                 (fn [property-name]
+                                                   (let [id (:block/uuid (get name->properties property-name))]
+                                                     (assert id (str "Built-in property " property-name " is not defined yet"))
+                                                     id))
+                                                 (:properties schema))]
+                                 (cond->
+                                  {:block/original-name (or original-name k-name)
+                                   :block/name (common-util/page-name-sanity-lc k-name)
+                                   :block/uuid (d/squuid)}
+                                   (seq properties)
+                                   (assoc-in [:block/schema :properties] properties)))))))
                          db-class/built-in-classes)]
                          db-class/built-in-classes)]
     (vec (concat initial-data initial-files default-pages default-classes default-properties))))
     (vec (concat initial-data initial-files default-pages default-classes default-properties))))

+ 1 - 1
deps/outliner/src/logseq/outliner/core.cljs

@@ -294,7 +294,7 @@
   [conn m]
   [conn m]
   (or
   (or
    (let [marker (:block/marker m)
    (let [marker (:block/marker m)
-         property (db-property/get-property @conn "status")
+         property (d/entity @conn :task/status)
          matched-status-id (when marker
          matched-status-id (when marker
                              (->> (get-in property [:block/schema :values])
                              (->> (get-in property [:block/schema :values])
                                  (some (fn [id]
                                  (some (fn [id]

+ 28 - 27
src/main/frontend/components/page.cljs

@@ -50,7 +50,8 @@
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
             [rum.core :as rum]
             [frontend.extensions.graph.pixi :as pixi]
             [frontend.extensions.graph.pixi :as pixi]
-            [frontend.db.async :as db-async]))
+            [frontend.db.async :as db-async]
+            [logseq.db :as ldb]))
 
 
 (defn- get-page-name
 (defn- get-page-name
   [state]
   [state]
@@ -302,13 +303,13 @@
                   (when untitled? (reset! *title-value "")))}]))
                   (when untitled? (reset! *title-value "")))}]))
 
 
 (rum/defcs ^:large-vars/cleanup-todo page-title < rum/reactive
 (rum/defcs ^:large-vars/cleanup-todo page-title < rum/reactive
-                                                  (rum/local false ::edit?)
-                                                  (rum/local "" ::input-value)
-                                                  {:init (fn [state]
-                                                           (let [page-name (first (:rum/args state))
-                                                                 original-name (:block/original-name (db/entity [:block/name (util/page-name-sanity-lc page-name)]))
-                                                                 *title-value (atom original-name)]
-                                                             (assoc state ::title-value *title-value)))}
+  (rum/local false ::edit?)
+  (rum/local "" ::input-value)
+  {:init (fn [state]
+           (let [page-name (first (:rum/args state))
+                 original-name (:block/original-name (db/entity [:block/name (util/page-name-sanity-lc page-name)]))
+                 *title-value (atom original-name)]
+             (assoc state ::title-value *title-value)))}
   [state page-name {:keys [fmt-journal? preview? *hover?]}]
   [state page-name {:keys [fmt-journal? preview? *hover?]}]
   (when page-name
   (when page-name
     (let [page (when page-name (db/entity [:block/name page-name]))
     (let [page (when page-name (db/entity [:block/name page-name]))
@@ -338,13 +339,13 @@
               {:on-mouse-down util/stop-propagation}
               {:on-mouse-down util/stop-propagation}
               (if (and (map? icon) db-based?)
               (if (and (map? icon) db-based?)
                 (icon-component/icon-picker icon
                 (icon-component/icon-picker icon
-                  {:on-chosen (fn [_e icon]
-                                (let [icon-property-id (db-pu/get-built-in-property-uuid :icon)]
-                                  (db-property-handler/<update-property!
-                                    repo
-                                    (:block/uuid page)
-                                    {:properties {icon-property-id icon}})))
-                   :icon-props {:size 38}})
+                                            {:on-chosen (fn [_e icon]
+                                                          (let [icon-property-id (db-pu/get-built-in-property-uuid :icon)]
+                                                            (db-property-handler/<update-property!
+                                                             repo
+                                                             (:block/uuid page)
+                                                             {:properties {icon-property-id icon}})))
+                                             :icon-props {:size 38}})
                 icon)])
                 icon)])
            [:h1.page-title.flex-1.cursor-pointer.gap-1
            [:h1.page-title.flex-1.cursor-pointer.gap-1
             {:class (when-not whiteboard-page? "title")
             {:class (when-not whiteboard-page? "title")
@@ -357,16 +358,16 @@
                            (if (gobj/get e "shiftKey")
                            (if (gobj/get e "shiftKey")
                              (when-let [page (db/pull repo '[*] [:block/name page-name])]
                              (when-let [page (db/pull repo '[*] [:block/name page-name])]
                                (state/sidebar-add-block!
                                (state/sidebar-add-block!
-                                 repo
-                                 (:db/id page)
-                                 :page))
-                             (when (and (not hls-page?)
-                                     (not fmt-journal?)
-                                     (not config/publishing?)
-                                     (not (and (contains? (:block/type page) "property")
-                                            (contains? db-property/built-in-properties-keys-str page-name))))
-                               (reset! *input-value (if untitled? "" old-name))
-                               (reset! *edit? true)))))}
+                                repo
+                                (:db/id page)
+                                :page))
+                             (do
+                               (when (and (not hls-page?)
+                                          (not fmt-journal?)
+                                          (not config/publishing?)
+                                          (not (ldb/built-in? page)))
+                                 (reset! *input-value (if untitled? "" old-name))
+                                 (reset! *edit? true))))))}
 
 
             (if @*edit?
             (if @*edit?
               (page-title-editor page {:*title-value *title-value
               (page-title-editor page {:*title-value *title-value
@@ -385,10 +386,10 @@
                 :data-ref   page-name
                 :data-ref   page-name
                 :style      {:opacity (when @*edit? 0)}}
                 :style      {:opacity (when @*edit? 0)}}
                (let [nested? (and (string/includes? title page-ref/left-brackets)
                (let [nested? (and (string/includes? title page-ref/left-brackets)
-                               (string/includes? title page-ref/right-brackets))]
+                                  (string/includes? title page-ref/right-brackets))]
                  (cond untitled? [:span.opacity-50 (t :untitled)]
                  (cond untitled? [:span.opacity-50 (t :untitled)]
                        nested? (component-block/map-inline {} (gp-mldoc/inline->edn title (mldoc/get-default-config
                        nested? (component-block/map-inline {} (gp-mldoc/inline->edn title (mldoc/get-default-config
-                                                                                            (:block/format page))))
+                                                                                           (:block/format page))))
                        :else title))])]])))))
                        :else title))])]])))))
 
 
 (defn- page-mouse-over
 (defn- page-mouse-over

+ 201 - 197
src/main/frontend/components/property.cljs

@@ -30,7 +30,8 @@
             [frontend.components.dnd :as dnd]
             [frontend.components.dnd :as dnd]
             [frontend.components.property.closed-value :as closed-value]
             [frontend.components.property.closed-value :as closed-value]
             [frontend.components.property.util :as components-pu]
             [frontend.components.property.util :as components-pu]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [logseq.db :as ldb]))
 
 
 (defn- <create-class-if-not-exists!
 (defn- <create-class-if-not-exists!
   [value]
   [value]
@@ -153,215 +154,218 @@
   (let [values (rum/react (::values state))]
   (let [values (rum/react (::values state))]
     (when-not (= :loading values)
     (when-not (= :loading values)
       (let [*property-name (::property-name state)
       (let [*property-name (::property-name state)
-           *property-schema (::property-schema state)
-           built-in-property? (contains? db-property/built-in-properties-keys-str (:block/original-name property))
-           property (db/sub-block (:db/id property))
-           disabled? (or built-in-property? config/publishing?)
-           hide-delete? (or (= (:db/id block) (:db/id property)) ; property page
-                            add-new-property?)
-           class? (contains? (:block/type block) "class")
-           property-type (get-in property [:block/schema :type])
-           save-property-fn (fn [] (components-pu/update-property! property @*property-name @*property-schema))
-           enable-closed-values? (contains? db-property-type/closed-value-property-types (or property-type :default))]
-       [:div.property-configure.flex.flex-1.flex-col
-        {:on-mouse-down #(state/set-state! :editor/mouse-down-from-property-configure? true)
-         :on-mouse-up #(state/set-state! :editor/mouse-down-from-property-configure? nil)}
-        [:div.grid.gap-2.p-1
-         [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-          [:label.col-span-1 "Name:"]
-          (shui/input
-           {:class         "col-span-2 !px-2 !py-0 !h-8"
-            :auto-focus    (not add-new-property?)
-            :on-change     #(reset! *property-name (util/evalue %))
-            :on-blur       save-property-fn
-            :on-key-press  (fn [e]
-                             (when (= "Enter" (util/ekey e))
-                               (save-property-fn)))
-            :disabled      disabled?
-            :default-value @*property-name})]
-
-         [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-          [:label.col-span-1 "Icon:"]
-          (let [icon-value (pu/get-block-property-value property :icon)]
-            [:div.col-span-3.flex.flex-row.items-center.gap-2
-             (icon-component/icon-picker icon-value
-                                         {:disabled? disabled?
-                                          :on-chosen (fn [_e icon]
-                                                       (let [icon-property-id (db-pu/get-built-in-property-uuid :icon)]
-                                                         (db-property-handler/<update-property!
-                                                          (state/get-current-repo)
-                                                          (:block/uuid property)
-                                                          {:properties {icon-property-id icon}})))})
-
-             (when (and icon-value (not disabled?))
-               [:a.fade-link.flex {:on-click (fn [_e]
-                                               (db-property-handler/remove-block-property!
-                                                (state/get-current-repo)
-                                                (:block/uuid property)
-                                                (db-pu/get-built-in-property-uuid :icon)))
-                                   :title "Delete this icon"}
-                (ui/icon "X")])])]
-
-         [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-          [:label.col-span-1 "Schema type:"]
-          (let [schema-types (->> (concat db-property-type/user-built-in-property-types
-                                          (when built-in-property?
-                                            db-property-type/internal-built-in-property-types))
-                                  (map (fn [type]
-                                         {:label (property-type-label type)
-                                          :disabled disabled?
-                                          :value type
-                                          :selected (= type (:type @*property-schema))})))]
-            (if (and property-type
-                     (seq values))
-              [:div.flex.items-center.col-span-2
-               (property-type-label property-type)
-               (ui/tippy {:html        "The type of this property is locked once you start using it. This is to make sure all your existing information stays correct if the property type is changed later. To unlock, all uses of a property must be deleted."
-                          :class       "tippy-hover ml-2"
-                          :interactive true
-                          :disabled    false}
-                         (svg/help-circle))]
-              [:div.flex.items-center.col-span-2
-               (shui/select
+            *property-schema (::property-schema state)
+            built-in-property? (contains? db-property/built-in-properties-keys-str (:block/original-name property))
+            property (db/sub-block (:db/id property))
+            built-in? (ldb/built-in? property)
+            disabled? (or built-in-property? config/publishing?)
+            hide-delete? (or (= (:db/id block) (:db/id property)) ; property page
+                             add-new-property?)
+            class? (contains? (:block/type block) "class")
+            property-type (get-in property [:block/schema :type])
+            save-property-fn (fn [] (components-pu/update-property! property @*property-name @*property-schema))
+            enable-closed-values? (contains? db-property-type/closed-value-property-types (or property-type :default))]
+        [:div.property-configure.flex.flex-1.flex-col
+         {:on-mouse-down #(state/set-state! :editor/mouse-down-from-property-configure? true)
+          :on-mouse-up #(state/set-state! :editor/mouse-down-from-property-configure? nil)}
+         [:div.grid.gap-2.p-1
+          [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+           [:label.col-span-1 "Name:"]
+           (if built-in?
+             [:div.col-span-2 (:block/original-name property)]
+             (shui/input
+              {:class         "col-span-2 !px-2 !py-0 !h-8"
+               :auto-focus    (not add-new-property?)
+               :on-change     #(reset! *property-name (util/evalue %))
+               :on-blur       save-property-fn
+               :on-key-press  (fn [e]
+                                (when (= "Enter" (util/ekey e))
+                                  (save-property-fn)))
+               :disabled      disabled?
+               :default-value @*property-name}))]
+
+          [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+           [:label.col-span-1 "Icon:"]
+           (let [icon-value (pu/get-block-property-value property :icon)]
+             [:div.col-span-3.flex.flex-row.items-center.gap-2
+              (icon-component/icon-picker icon-value
+                                          {:disabled? disabled?
+                                           :on-chosen (fn [_e icon]
+                                                        (let [icon-property-id (db-pu/get-built-in-property-uuid :icon)]
+                                                          (db-property-handler/<update-property!
+                                                           (state/get-current-repo)
+                                                           (:block/uuid property)
+                                                           {:properties {icon-property-id icon}})))})
+
+              (when (and icon-value (not disabled?))
+                [:a.fade-link.flex {:on-click (fn [_e]
+                                                (db-property-handler/remove-block-property!
+                                                 (state/get-current-repo)
+                                                 (:block/uuid property)
+                                                 (db-pu/get-built-in-property-uuid :icon)))
+                                    :title "Delete this icon"}
+                 (ui/icon "X")])])]
+
+          [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+           [:label.col-span-1 "Schema type:"]
+           (let [schema-types (->> (concat db-property-type/user-built-in-property-types
+                                           (when built-in-property?
+                                             db-property-type/internal-built-in-property-types))
+                                   (map (fn [type]
+                                          {:label (property-type-label type)
+                                           :disabled disabled?
+                                           :value type
+                                           :selected (= type (:type @*property-schema))})))]
+             (if (or (ldb/built-in? property)
+                     (and property-type (seq values)))
+               [:div.flex.items-center.col-span-2
+                (property-type-label property-type)
+                (ui/tippy {:html        "The type of this property is locked once you start using it. This is to make sure all your existing information stays correct if the property type is changed later. To unlock, all uses of a property must be deleted."
+                           :class       "tippy-hover ml-2"
+                           :interactive true
+                           :disabled    false}
+                          (svg/help-circle))]
+               [:div.flex.items-center.col-span-2
+                (shui/select
                  {:on-value-change
                  {:on-value-change
                   (fn [v]
                   (fn [v]
                     (let [type (keyword (string/lower-case v))
                     (let [type (keyword (string/lower-case v))
                           update-schema-fn (apply comp
                           update-schema-fn (apply comp
-                                             #(assoc % :type type)
+                                                  #(assoc % :type type)
                                              ;; always delete previous closed values as they
                                              ;; always delete previous closed values as they
                                              ;; are not valid for the new type
                                              ;; are not valid for the new type
-                                             #(dissoc % :values)
-                                             (keep
-                                               (fn [attr]
-                                                 (when-not (db-property-type/property-type-allows-schema-attribute? type attr)
-                                                   #(dissoc % attr)))
-                                               [:cardinality :classes :position]))]
+                                                  #(dissoc % :values)
+                                                  (keep
+                                                   (fn [attr]
+                                                     (when-not (db-property-type/property-type-allows-schema-attribute? type attr)
+                                                       #(dissoc % attr)))
+                                                   [:cardinality :classes :position]))]
                       (swap! *property-schema update-schema-fn)
                       (swap! *property-schema update-schema-fn)
                       (components-pu/update-property! property @*property-name @*property-schema)))
                       (components-pu/update-property! property @*property-name @*property-schema)))
                   :default-value :default}
                   :default-value :default}
                  (shui/select-trigger
                  (shui/select-trigger
-                   {:class "!px-2 !py-0 !h-8"}
-                   (shui/select-value
-                     {:placeholder "Select a schema type"})
-                   (shui/tabler-icon "selector" {:class "opacity-30"}))
+                  {:class "!px-2 !py-0 !h-8"}
+                  (shui/select-value
+                   {:placeholder "Select a schema type"})
+                  (shui/tabler-icon "selector" {:class "opacity-30"}))
                  (shui/select-content
                  (shui/select-content
-                   (shui/select-group
-                     (for [{:keys [label value disabled]} schema-types]
-                       (shui/select-item {:value value :disabled disabled} label)))))
-
-               (ui/tippy {:html        "Changing the property type clears some property configurations."
-                          :class       "tippy-hover ml-2"
-                          :interactive true
-                          :disabled    false}
-                         (svg/info))]))]
-
-         (when (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :cardinality)
-           [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-            [:label "Multiple values:"]
-            (let [many? (boolean (= :many (:cardinality @*property-schema)))]
-              (shui/checkbox
-               {:checked           many?
-                :disabled          disabled?
-                :on-checked-change (fn []
-                                     (swap! *property-schema assoc :cardinality (if many? :one :many))
-                                     (save-property-fn))}))])
-
-         (when (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :classes)
-           (case (:type @*property-schema)
-             :page
-             (when (empty? (:values @*property-schema))
-               [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-                [:label "Specify classes:"]
-                (class-select *property-schema
-                              (:classes @*property-schema)
-                              (assoc opts
-                                     :disabled? disabled?
-                                     :save-property-fn save-property-fn))])
-
-             :template
-             [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-              [:label "Specify template:"]
-              (class-select *property-schema (:classes @*property-schema)
-                            (assoc opts
-                                   :multiple-choices? false
-                                   :disabled? disabled?
-                                   :save-property-fn save-property-fn))]
-
-             nil))
-
-         (when (and enable-closed-values? (empty? (:classes @*property-schema)))
-           [:div.grid.grid-cols-4.gap-1.items-start.leading-8
-            [:label.col-span-1 "Available choices:"]
-            [:div.col-span-3
-             (closed-value/choices property *property-name *property-schema opts)]])
-
-         (when (and enable-closed-values?
-                    (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :position)
-                    (seq (:values @*property-schema)))
-           (let [position (:position @*property-schema)
-                 choices (map
-                          (fn [item]
-                            (assoc item :selected
-                                   (or (and position (= (:value item) position))
-                                       (and (nil? position) (= (:value item) "properties")))))
-                          [{:label "Block properties"
-                            :value "properties"}
-                           {:label "Beginning of the block"
-                            :value "block-beginning"}
+                  (shui/select-group
+                   (for [{:keys [label value disabled]} schema-types]
+                     (shui/select-item {:value value :disabled disabled} label)))))
+
+                (ui/tippy {:html        "Changing the property type clears some property configurations."
+                           :class       "tippy-hover ml-2"
+                           :interactive true
+                           :disabled    false}
+                          (svg/info))]))]
+
+          (when (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :cardinality)
+            [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+             [:label "Multiple values:"]
+             (let [many? (boolean (= :many (:cardinality @*property-schema)))]
+               (shui/checkbox
+                {:checked           many?
+                 :disabled          disabled?
+                 :on-checked-change (fn []
+                                      (swap! *property-schema assoc :cardinality (if many? :one :many))
+                                      (save-property-fn))}))])
+
+          (when (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :classes)
+            (case (:type @*property-schema)
+              :page
+              (when (empty? (:values @*property-schema))
+                [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+                 [:label "Specify classes:"]
+                 (class-select *property-schema
+                               (:classes @*property-schema)
+                               (assoc opts
+                                      :disabled? disabled?
+                                      :save-property-fn save-property-fn))])
+
+              :template
+              [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+               [:label "Specify template:"]
+               (class-select *property-schema (:classes @*property-schema)
+                             (assoc opts
+                                    :multiple-choices? false
+                                    :disabled? disabled?
+                                    :save-property-fn save-property-fn))]
+
+              nil))
+
+          (when (and enable-closed-values? (empty? (:classes @*property-schema)))
+            [:div.grid.grid-cols-4.gap-1.items-start.leading-8
+             [:label.col-span-1 "Available choices:"]
+             [:div.col-span-3
+              (closed-value/choices property *property-name *property-schema opts)]])
+
+          (when (and enable-closed-values?
+                     (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :position)
+                     (seq (:values @*property-schema)))
+            (let [position (:position @*property-schema)
+                  choices (map
+                           (fn [item]
+                             (assoc item :selected
+                                    (or (and position (= (:value item) position))
+                                        (and (nil? position) (= (:value item) "properties")))))
+                           [{:label "Block properties"
+                             :value "properties"}
+                            {:label "Beginning of the block"
+                             :value "block-beginning"}
                         ;; {:label "Ending of the block"
                         ;; {:label "Ending of the block"
                         ;;  :value "block-ending"}
                         ;;  :value "block-ending"}
-                           ])]
-             [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-              [:label.col-span-1 "UI position:"]
-              [:div.col-span-2
-               (shui/select
-                (cond-> {:on-value-change (fn [v]
-                                            (swap! *property-schema assoc :position v)
-                                            (save-property-fn))}
-                  (string? position)
-                  (assoc :default-value position))
-                (shui/select-trigger
-                 {:class "!px-2 !py-0 !h-8"}
-                 (shui/select-value
-                  {:placeholder "Select a position mode"})
-                 (shui/tabler-icon "selector" {:class "opacity-30"}))
-                (shui/select-content
-                 (shui/select-group
-                  (for [{:keys [label value]} choices]
-                    (shui/select-item {:value value} label)))))]]))
-
-         (let [hide? (:hide? @*property-schema)]
-           [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-            [:label "Hide by default:"]
-            (shui/checkbox
-             {:checked           hide?
-              :disabled          disabled?
-              :on-checked-change (fn []
-                                   (swap! *property-schema assoc :hide? (not hide?))
-                                   (save-property-fn))})])
-
-         [:div.grid.grid-cols-4.gap-1.items-start.leading-8
-          [:label "Description:"]
-          [:div.col-span-3
-           (if (and disabled? inline-text)
-             (inline-text {} :markdown (:description @*property-schema))
-             [:div.mt-1
-              (shui/textarea
-               {:on-change     (fn [e]
-                                 (swap! *property-schema assoc :description (util/evalue e)))
-                :on-blur       save-property-fn
-                :disabled      disabled?
-                :default-value (:description @*property-schema)})])]]
-
-         (when-not hide-delete?
-           (shui/button
-            {:variant :secondary
-             :class "mt-4 hover:text-red-700"
-             :on-click (fn [e]
-                         (util/stop e)
-                         (handle-delete-property! block property {:class? class? :class-schema? class-schema?})
-                         (when toggle-fn (toggle-fn)))}
-            "Delete property from this block"))]]))))
+                            ])]
+              [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+               [:label.col-span-1 "UI position:"]
+               [:div.col-span-2
+                (shui/select
+                 (cond-> {:on-value-change (fn [v]
+                                             (swap! *property-schema assoc :position v)
+                                             (save-property-fn))}
+                   (string? position)
+                   (assoc :default-value position))
+                 (shui/select-trigger
+                  {:class "!px-2 !py-0 !h-8"}
+                  (shui/select-value
+                   {:placeholder "Select a position mode"})
+                  (shui/tabler-icon "selector" {:class "opacity-30"}))
+                 (shui/select-content
+                  (shui/select-group
+                   (for [{:keys [label value]} choices]
+                     (shui/select-item {:value value} label)))))]]))
+
+          (let [hide? (:hide? @*property-schema)]
+            [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+             [:label "Hide by default:"]
+             (shui/checkbox
+              {:checked           hide?
+               :disabled          disabled?
+               :on-checked-change (fn []
+                                    (swap! *property-schema assoc :hide? (not hide?))
+                                    (save-property-fn))})])
+
+          [:div.grid.grid-cols-4.gap-1.items-start.leading-8
+           [:label "Description:"]
+           [:div.col-span-3
+            (if (and disabled? inline-text)
+              (inline-text {} :markdown (:description @*property-schema))
+              [:div.mt-1
+               (shui/textarea
+                {:on-change     (fn [e]
+                                  (swap! *property-schema assoc :description (util/evalue e)))
+                 :on-blur       save-property-fn
+                 :disabled      disabled?
+                 :default-value (:description @*property-schema)})])]]
+
+          (when-not hide-delete?
+            (shui/button
+             {:variant :secondary
+              :class "mt-4 hover:text-red-700"
+              :on-click (fn [e]
+                          (util/stop e)
+                          (handle-delete-property! block property {:class? class? :class-schema? class-schema?})
+                          (when toggle-fn (toggle-fn)))}
+             "Delete property from this block"))]]))))
 
 
 (defn- get-property-from-db [name]
 (defn- get-property-from-db [name]
   (when-not (string/blank? name)
   (when-not (string/blank? name)

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

@@ -184,8 +184,8 @@
   [property
   [property
    {:keys [block classes multiple-choices? dropdown? input-opts on-chosen] :as opts}]
    {:keys [block classes multiple-choices? dropdown? input-opts on-chosen] :as opts}]
   (let [repo (state/get-current-repo)
   (let [repo (state/get-current-repo)
-        tags? (= "tags" (:block/name property))
-        alias? (= "alias" (:block/name property))
+        tags? (= :tags (:db/ident property))
+        alias? (= :alias (:db/ident property))
         tags-or-alias? (or tags? alias?)
         tags-or-alias? (or tags? alias?)
         selected-choices (when block
         selected-choices (when block
                            (->>
                            (->>

+ 1 - 1
src/main/frontend/db.cljs

@@ -58,7 +58,7 @@
  [frontend.db.query-react
  [frontend.db.query-react
   react-query custom-query-result-transform]
   react-query custom-query-result-transform]
 
 
- [logseq.db.frontend.default built-in-pages-names built-in-pages])
+ [logseq.db.frontend.default built-in-pages-names built-in-pages page-title->block])
 
 
 (defn start-db-conn!
 (defn start-db-conn!
   ([repo]
   ([repo]

+ 4 - 3
src/main/frontend/handler/db_based/property.cljs

@@ -20,7 +20,8 @@
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property :as db-property]
             [frontend.handler.property.util :as pu]
             [frontend.handler.property.util :as pu]
             [promesa.core :as p]
             [promesa.core :as p]
-            [frontend.db.async :as db-async]))
+            [frontend.db.async :as db-async]
+            [logseq.db :as ldb]))
 
 
 ;; schema -> type, cardinality, object's class
 ;; schema -> type, cardinality, object's class
 ;;           min, max -> string length, number range, cardinality size limit
 ;;           min, max -> string length, number range, cardinality size limit
@@ -228,7 +229,7 @@
                 (if-let [msg (validate-property-value schema v*)]
                 (if-let [msg (validate-property-value schema v*)]
                   (let [msg' (str "\"" k-name "\"" " " (if (coll? msg) (first msg) msg))]
                   (let [msg' (str "\"" k-name "\"" " " (if (coll? msg) (first msg) msg))]
                     (notification/show! msg' :warning))
                     (notification/show! msg' :warning))
-                  (do
+                  (p/do!
                     (upsert-property! repo k-name (assoc property-schema :type property-type)
                     (upsert-property! repo k-name (assoc property-schema :type property-type)
                                       {:property-uuid property-uuid})
                                       {:property-uuid property-uuid})
                     (let [status? (= "status" (string/lower-case k-name))
                     (let [status? (= "status" (string/lower-case k-name))
@@ -312,7 +313,7 @@
             property-values (db-async/<get-block-property-values repo property-uuid)]
             property-values (db-async/<get-block-property-values repo property-uuid)]
       (when (or (not type-changed?)
       (when (or (not type-changed?)
                 ;; only change type if property hasn't been used yet
                 ;; only change type if property hasn't been used yet
-                (empty? property-values))
+                (and (not (ldb/built-in? property)) (empty? property-values)))
         (when (not= ::skip-transact (handle-cardinality-changes repo property-uuid property property-schema property-values))
         (when (not= ::skip-transact (handle-cardinality-changes repo property-uuid property property-schema property-values))
           (let [tx-data (cond-> {:block/uuid property-uuid}
           (let [tx-data (cond-> {:block/uuid property-uuid}
                           property-name (merge
                           property-name (merge

+ 2 - 0
src/main/frontend/handler/page.cljs

@@ -131,6 +131,8 @@
             result (.page-rename worker repo old-name new-name)
             result (.page-rename worker repo old-name new-name)
             result' (:result (bean/->clj result))]
             result' (:result (bean/->clj result))]
       (case result'
       (case result'
+        :built-in-page
+        (notification/show! "Built-in page's name cann't be modified" :error)
         :invalid-empty-name
         :invalid-empty-name
         (notification/show! "Please use a valid name, empty name is not allowed!" :error)
         (notification/show! "Please use a valid name, empty name is not allowed!" :error)
         :merge-whiteboard-pages
         :merge-whiteboard-pages

+ 37 - 36
src/main/frontend/worker/handler/page.cljs

@@ -210,41 +210,42 @@
                                      [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
                                      [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
                                    blocks)
                                    blocks)
           db-based? (sqlite-util/db-based-graph? repo)]
           db-based? (sqlite-util/db-based-graph? repo)]
-      (if-let [msg (and db-based? (page-unable-to-delete conn page))]
-        (do
-          (ldb/transact! conn truncate-blocks-tx-data
-                         {:outliner-op :truncate-page-blocks :persist-op? persist-op?})
-          (error-handler msg))
-        (let [file (ldb/get-page-file @conn page-name)
-              file-path (:file/path file)
-              delete-file-tx (when file
-                               [[:db.fn/retractEntity [:file/path file-path]]])
+      (when-not (ldb/built-in? page)
+        (if-let [msg (and db-based? (page-unable-to-delete conn page))]
+          (do
+            (ldb/transact! conn truncate-blocks-tx-data
+                           {:outliner-op :truncate-page-blocks :persist-op? persist-op?})
+            (error-handler msg))
+          (let [file (ldb/get-page-file @conn page-name)
+                file-path (:file/path file)
+                delete-file-tx (when file
+                                 [[:db.fn/retractEntity [:file/path file-path]]])
               ;; if other page alias this pagename,
               ;; if other page alias this pagename,
               ;; then just remove some attrs of this entity instead of retractEntity
               ;; then just remove some attrs of this entity instead of retractEntity
-              delete-page-tx (cond
-                               (or (and db-based? (not (:block/_namespace page)))
-                                   (not db-based?))
-                               (if (and db-based? (ldb/get-alias-source-page @conn page-name))
-                                 (when-let [id (:db/id (d/entity @conn [:block/name page-name]))]
-                                   (mapv (fn [attribute]
-                                           [:db/retract id attribute])
-                                         db-schema/retract-page-attributes))
-                                 (concat (db-refs->page repo page)
-                                         [[:db.fn/retractEntity [:block/name page-name]]]))
-
-                               :else
-                               nil)
-              tx-data (concat truncate-blocks-tx-data delete-page-tx delete-file-tx)]
-
-          (ldb/transact! conn tx-data
-                         (cond-> {:outliner-op :delete-page
-                                  :deleted-page page-name
-                                  :persist-op? persist-op?}
-                           rename?
-                           (assoc :real-outliner-op :rename-page)
-                           file-path
-                           (assoc :file-path file-path)))
-
-          (when (fn? ok-handler) (ok-handler))
-
-          true)))))
+                delete-page-tx (cond
+                                 (or (and db-based? (not (:block/_namespace page)))
+                                     (not db-based?))
+                                 (if (and db-based? (ldb/get-alias-source-page @conn page-name))
+                                   (when-let [id (:db/id (d/entity @conn [:block/name page-name]))]
+                                     (mapv (fn [attribute]
+                                             [:db/retract id attribute])
+                                           db-schema/retract-page-attributes))
+                                   (concat (db-refs->page repo page)
+                                           [[:db.fn/retractEntity [:block/name page-name]]]))
+
+                                 :else
+                                 nil)
+                tx-data (concat truncate-blocks-tx-data delete-page-tx delete-file-tx)]
+
+            (ldb/transact! conn tx-data
+                           (cond-> {:outliner-op :delete-page
+                                    :deleted-page page-name
+                                    :persist-op? persist-op?}
+                             rename?
+                             (assoc :real-outliner-op :rename-page)
+                             file-path
+                             (assoc :file-path file-path)))
+
+            (when (fn? ok-handler) (ok-handler))
+
+            true))))))

+ 3 - 0
src/main/frontend/worker/handler/page/rename.cljs

@@ -254,6 +254,9 @@
         new-page-e (d/entity db [:block/name new-page-name])
         new-page-e (d/entity db [:block/name new-page-name])
         name-changed? (not= old-name new-name)]
         name-changed? (not= old-name new-name)]
     (cond
     (cond
+      (ldb/built-in? page-e)
+      :built-in-page
+
       (string/blank? new-name)
       (string/blank? new-name)
       :invalid-empty-name
       :invalid-empty-name