Browse Source

add api to support icon and tag extends

Tienson Qin 1 week ago
parent
commit
73a1722bb5
3 changed files with 123 additions and 48 deletions
  1. 25 19
      src/main/frontend/components/icon.cljs
  2. 4 0
      src/main/logseq/api.cljs
  3. 94 29
      src/main/logseq/api/db_based.cljs

+ 25 - 19
src/main/frontend/components/icon.cljs

@@ -24,25 +24,31 @@
 (defn icon
   [icon' & [opts]]
   (let [icon' (if (or (string? icon') (keyword? icon'))
-                {:type :tabler-icon :id (name icon')} icon')
-        color? (:color? opts)
-        opts (dissoc opts :color?)
-        item (cond
-               (and (= :emoji (:type icon')) (:id icon'))
-               [:span.ui__icon
-                [:em-emoji (merge {:id (:id icon')
-                                   :style {:line-height 1}}
-                                  opts)]]
-
-               (and (= :tabler-icon (:type icon')) (:id icon'))
-               (ui/icon (:id icon') opts)
-
-               :else
-               icon')]
-    (if color?
-      [:span.inline-flex.items-center.ls-icon-color-wrap
-       {:style {:color (or (some-> icon' :color) "inherit")}} item]
-      item)))
+                {:type :tabler-icon :id (name icon')} icon')]
+    (if (and (contains? #{:emoji :tabler-icon} (:type icon'))
+             (string? (:id icon'))
+             (not (string/blank? (:id icon'))))
+      (let [color? (:color? opts)
+            opts (dissoc opts :color?)
+            item (cond
+                   (and (= :emoji (:type icon')) (:id icon'))
+                   [:span.ui__icon
+                    [:em-emoji (merge {:id (:id icon')
+                                       :style {:line-height 1}}
+                                      opts)]]
+
+                   (and (= :tabler-icon (:type icon')) (:id icon'))
+                   (ui/icon (:id icon') opts)
+
+                   :else
+                   icon')]
+        (if color?
+          [:span.inline-flex.items-center.ls-icon-color-wrap
+           {:style {:color (or (some-> icon' :color) "inherit")}} item]
+          item))
+      (do
+        (js/console.error "Invalid icon")
+        [:span]))))
 
 (defn get-node-icon
   [node-entity {:keys [ignore-current-icon?]

+ 4 - 0
src/main/logseq/api.cljs

@@ -203,10 +203,14 @@
 (def ^:export get_tag_objects db-based-api/get-tag-objects)
 (def ^:export create_tag db-based-api/create-tag)
 (def ^:export get_tag db-based-api/get-tag)
+(def ^:export add_tag_extends db-based-api/add-tag-extends)
+(def ^:export remove_tag_extends db-based-api/remove-tag-extends)
 (def ^:export add_block_tag db-based-api/add-block-tag)
 (def ^:export remove_block_tag db-based-api/remove-block-tag)
 (def ^:export add_tag_property db-based-api/tag-add-property)
 (def ^:export remove_tag_property db-based-api/tag-remove-property)
+(def ^:export set_block_icon db-based-api/set-block-icon)
+(def ^:export remove_block_icon db-based-api/remove-block-icon)
 
 ;; Internal db-based CLI APIs
 ;; CLI APIs should use ensure-db-graph unless they have a nested check in cli-common-mcp-tools ns

+ 94 - 29
src/main/logseq/api/db_based.cljs

@@ -1,11 +1,11 @@
 (ns logseq.api.db-based
   "DB version related fns"
-  (:require [cljs-bean.core :as bean]
+  (:require ["@emoji-mart/data" :as emoji-data]
+            [cljs-bean.core :as bean]
             [cljs.reader]
             [clojure.string :as string]
             [clojure.walk :as walk]
             [datascript.core :as d]
-            [logseq.graph-parser.text :as text]
             [frontend.db :as db]
             [frontend.db.async :as db-async]
             [frontend.db.model :as db-model]
@@ -17,9 +17,11 @@
             [frontend.modules.layout.core]
             [frontend.state :as state]
             [frontend.util :as util]
+            [goog.object :as gobj]
             [logseq.api.block :as api-block]
             [logseq.db :as ldb]
             [logseq.db.common.entity-util :as common-entity-util]
+            [logseq.graph-parser.text :as text]
             [logseq.outliner.core :as outliner-core]
             [logseq.sdk.core]
             [logseq.sdk.experiments]
@@ -27,6 +29,10 @@
             [logseq.sdk.utils :as sdk-utils]
             [promesa.core :as p]))
 
+(defonce ^:private name->emoji
+  (->> (vals (bean/->clj (gobj/get emoji-data "emojis")))
+       (group-by :name)))
+
 (defn -get-property
   [^js plugin k]
   (when-let [k' (and (string? k) (api-block/sanitize-user-property-name k))]
@@ -206,36 +212,64 @@
 
 (defn create-tag [title ^js opts]
   (this-as
-    this
-    (when-not (string? title)
-      (throw (ex-info "Tag title should be a string" {:title title})))
-    (when (string/blank? title)
-      (throw (ex-info "Tag title shouldn't be empty" {:title title})))
-    (when (text/namespace-page? title)
-      (throw (ex-info "Tag title shouldn't include forward slash" {:title title})))
-    (let [opts (bean/->clj opts)
-          opts' (assoc opts
-                  :redirect? false
-                  :class-ident-namespace (api-block/resolve-class-prefix-for-db this))]
-      (p/let [tag (db-page-handler/<create-class! title opts')]
-        (sdk-utils/result->js tag)))))
+   this
+   (when-not (string? title)
+     (throw (ex-info "Tag title should be a string" {:title title})))
+   (when (string/blank? title)
+     (throw (ex-info "Tag title shouldn't be empty" {:title title})))
+   (when (text/namespace-page? title)
+     (throw (ex-info "Tag title shouldn't include forward slash" {:title title})))
+   (let [opts (bean/->clj opts)
+         opts' (assoc opts
+                      :redirect? false
+                      :class-ident-namespace (api-block/resolve-class-prefix-for-db this))]
+     (p/let [tag (db-page-handler/<create-class! title opts')]
+       (sdk-utils/result->js tag)))))
+
+(defn- throw-error-if-not-tag!
+  [tag tag-id]
+  (when-not (ldb/class? tag)
+    (throw (ex-info (str "Not a tag: " tag-id)
+                    {:tag tag}))))
+
+(defn add-tag-extends [tag-id extend-id]
+  (let [tag (db-async/<get-block (state/get-current-repo) tag-id)
+        extend (db-async/<get-block (state/get-current-repo) extend-id)]
+    (throw-error-if-not-tag! tag tag-id)
+    (throw-error-if-not-tag! extend extend-id)
+    (when (ldb/built-in? tag)
+      (throw (ex-info "Built-in tag's extends can't be modified" {:tag tag})))
+    (db-property-handler/set-block-property! (:db/id tag)
+                                             :logseq.property.class/extends
+                                             (:db/id extend))))
+
+(defn remove-tag-extends [tag-id extend-id]
+  (let [tag (db-async/<get-block (state/get-current-repo) tag-id)
+        extend (db-async/<get-block (state/get-current-repo) extend-id)]
+    (throw-error-if-not-tag! tag tag-id)
+    (throw-error-if-not-tag! extend extend-id)
+    (when (ldb/built-in? tag)
+      (throw (ex-info "Built-in tag's extends can't be modified" {:tag tag})))
+    (db-property-handler/delete-property-value! (:db/id tag)
+                                                :logseq.property.class/extends
+                                                (:db/id extend))))
 
 (defn get-tag [class-uuid-or-ident-or-title]
   (this-as
-    this
-    (let [title-or-ident (-> (if-not (string? class-uuid-or-ident-or-title)
-                               (str class-uuid-or-ident-or-title)
-                               class-uuid-or-ident-or-title)
-                           (string/replace #"^:+" ""))
-          eid (if (text/namespace-page? title-or-ident)
-                (keyword title-or-ident)
-                (if (util/uuid-string? title-or-ident)
-                  (when-let [id (sdk-utils/uuid-or-throw-error title-or-ident)]
-                    [:block/uuid id])
-                  (keyword (api-block/resolve-class-prefix-for-db this) title-or-ident)))
-          tag (db/entity eid)]
-      (when (ldb/class? tag)
-        (sdk-utils/result->js tag)))))
+   this
+   (let [title-or-ident (-> (if-not (string? class-uuid-or-ident-or-title)
+                              (str class-uuid-or-ident-or-title)
+                              class-uuid-or-ident-or-title)
+                            (string/replace #"^:+" ""))
+         eid (if (text/namespace-page? title-or-ident)
+               (keyword title-or-ident)
+               (if (util/uuid-string? title-or-ident)
+                 (when-let [id (sdk-utils/uuid-or-throw-error title-or-ident)]
+                   [:block/uuid id])
+                 (keyword (api-block/resolve-class-prefix-for-db this) title-or-ident)))
+         tag (db/entity eid)]
+     (when (ldb/class? tag)
+       (sdk-utils/result->js tag)))))
 
 (defn tag-add-property [tag-id property-id-or-name]
   (p/let [tag (db/get-case-page tag-id)
@@ -262,6 +296,9 @@
   (p/let [repo (state/get-current-repo)
           tag (db-async/<get-block repo tag-id)
           block (db-async/<get-block repo id-or-name)]
+    (when-not (ldb/class? tag)
+      (throw (ex-info (str "Not a tag: " tag-id)
+                      {:tag tag})))
     (when (and tag block)
       (db-page-handler/add-tag repo (:db/id block) tag))))
 
@@ -269,6 +306,34 @@
   (p/let [repo (state/get-current-repo)
           block (db-async/<get-block repo id-or-name)
           tag (db-async/<get-block repo tag-id)]
+    (when-not (ldb/class? tag)
+      (throw (ex-info (str "Not a tag: " tag-id)
+                      {:tag tag})))
     (when (and block tag)
       (db-property-handler/delete-property-value!
        (:db/id block) :block/tags (:db/id tag)))))
+
+(defn set-block-icon
+  [block-id icon-type icon-name]
+  (when-not (contains? #{"tabler-icon" "emoji"} icon-type)
+    (throw (ex-info "icon-type should be one of [tabler-icon, emoji]" {:icon-type icon-type})))
+  (when (or (not (string? icon-name))
+            (string/blank? icon-name))
+    (throw (ex-info "icon-name should be a non-blank string" {:icon-name icon-name})))
+  (when (= icon-type "emoji")
+    (when-not (name->emoji icon-name)
+      (throw (ex-info (str "Can't find emoji for " icon-name) {}))))
+  (p/let [repo (state/get-current-repo)
+          block (db-async/<get-block repo block-id)]
+    (db-property-handler/set-block-property! (:db/id block)
+                                             :logseq.property/icon
+                                             {:type (keyword icon-type)
+                                              :id (if (= icon-type "emoji")
+                                                    (:id (first (name->emoji icon-name)))
+                                                    icon-name)})))
+(defn remove-block-icon
+  [block-id]
+  (p/let [repo (state/get-current-repo)
+          block (db-async/<get-block repo block-id)]
+    (db-property-handler/remove-block-property! (:block/uuid block)
+                                                :logseq.property/icon)))