Prechádzať zdrojové kódy

Merge branch 'master' into feat/db

Tienson Qin 1 rok pred
rodič
commit
ee3442a44b
100 zmenil súbory, kde vykonal 5192 pridanie a 2377 odobranie
  1. 13 1
      .github/workflows/build-stage.yml
  2. 52 0
      .github/workflows/deploy-stage-to-master.yml
  3. 207 3
      deps/shui/shui-graph/pages/contents.md
  4. 2 0
      deps/shui/shui-graph/pages/shui___components___button.md
  5. 1 0
      deps/shui/shui-graph/pages/shui___components___properties.md
  6. 1 0
      deps/shui/shui-graph/pages/shui___components___toggle.md
  7. 53 0
      deps/shui/src/logseq/shui/button/v2.cljs
  8. 18 23
      deps/shui/src/logseq/shui/context.cljs
  9. 28 2
      deps/shui/src/logseq/shui/core.cljs
  10. 68 0
      deps/shui/src/logseq/shui/dialog/v1.cljs
  11. 104 0
      deps/shui/src/logseq/shui/icon/v2.cljs
  12. 142 0
      deps/shui/src/logseq/shui/list_item/v1.cljs
  13. 101 0
      deps/shui/src/logseq/shui/shortcut/v1.cljs
  14. 8 6
      deps/shui/src/logseq/shui/table/v2.cljs
  15. 9 7
      e2e-tests/fs.spec.ts
  16. 2 2
      e2e-tests/hotkey.spec.ts
  17. 3 3
      e2e-tests/page-rename.spec.ts
  18. 0 13
      e2e-tests/page-search.spec.ts
  19. 14 17
      e2e-tests/util/search-modal.ts
  20. 1 1
      e2e-tests/whiteboards.spec.ts
  21. 1 4
      libs/src/LSPlugin.ts
  22. 2 1
      package.json
  23. 17 0
      postcss.config.js
  24. 173 0
      resources/css/codemirror.lsradix.css
  25. 1379 0
      resources/css/radix.css
  26. 316 0
      resources/css/shui.css
  27. BIN
      resources/img/dark-theme.png
  28. BIN
      resources/img/file-sync-unavailale-nonbacker-dark.png
  29. BIN
      resources/img/file-sync-unavailale-nonbacker-light.png
  30. BIN
      resources/img/file-sync-welcome-backer-dark.png
  31. BIN
      resources/img/file-sync-welcome-backer-light.png
  32. BIN
      resources/img/light-theme.png
  33. BIN
      resources/img/system-theme.png
  34. BIN
      resources/img/whiteboard-welcome-dark.png
  35. BIN
      resources/img/whiteboard-welcome-light.png
  36. 1 1
      resources/package.json
  37. 2 2
      scripts/src/logseq/tasks/lang.clj
  38. 75 0
      src/main/frontend/colors.cljs
  39. 24 12
      src/main/frontend/common.css
  40. 166 168
      src/main/frontend/components/block.cljs
  41. 13 9
      src/main/frontend/components/block.css
  42. 781 0
      src/main/frontend/components/cmdk.cljs
  43. 0 57
      src/main/frontend/components/command_palette.cljs
  44. 7 2
      src/main/frontend/components/command_palette.css
  45. 7 3
      src/main/frontend/components/container.cljs
  46. 61 28
      src/main/frontend/components/container.css
  47. 4 3
      src/main/frontend/components/editor.cljs
  48. 1 1
      src/main/frontend/components/export.cljs
  49. 1 1
      src/main/frontend/components/header.css
  50. 1 1
      src/main/frontend/components/journal.css
  51. 9 3
      src/main/frontend/components/page.css
  52. 107 107
      src/main/frontend/components/plugins.cljs
  53. 1 4
      src/main/frontend/components/plugins.css
  54. 2 2
      src/main/frontend/components/query/builder.cljs
  55. 1 1
      src/main/frontend/components/reference.cljs
  56. 4 0
      src/main/frontend/components/reference.css
  57. 6 3
      src/main/frontend/components/repo.cljs
  58. 40 16
      src/main/frontend/components/right_sidebar.cljs
  59. 6 1
      src/main/frontend/components/right_sidebar.css
  60. 2 552
      src/main/frontend/components/search.cljs
  61. 0 58
      src/main/frontend/components/search.css
  62. 0 43
      src/main/frontend/components/search/highlight.cljs
  63. 94 62
      src/main/frontend/components/settings.cljs
  64. 35 3
      src/main/frontend/components/settings.css
  65. 470 212
      src/main/frontend/components/shortcut.cljs
  66. 16 13
      src/main/frontend/components/shortcut.css
  67. 0 480
      src/main/frontend/components/shortcut2.cljs
  68. 105 0
      src/main/frontend/components/shortcut_help.cljs
  69. 0 8
      src/main/frontend/components/svg.cljs
  70. 4 4
      src/main/frontend/components/theme.css
  71. 9 9
      src/main/frontend/components/whiteboard.cljs
  72. 15 3
      src/main/frontend/components/whiteboard.css
  73. 1 1
      src/main/frontend/db.cljs
  74. 17 9
      src/main/frontend/db/model.cljs
  75. 20 20
      src/main/frontend/db/react.cljs
  76. 17 2
      src/main/frontend/extensions/code.cljs
  77. 4 0
      src/main/frontend/extensions/code.css
  78. 1 1
      src/main/frontend/extensions/handbooks/handbooks.css
  79. 0 2
      src/main/frontend/extensions/pdf/pdf.css
  80. 24 8
      src/main/frontend/extensions/pdf/toolbar.cljs
  81. 24 18
      src/main/frontend/extensions/srs.cljs
  82. 3 3
      src/main/frontend/extensions/tldraw.cljs
  83. 3 2
      src/main/frontend/extensions/zotero.cljs
  84. 2 1
      src/main/frontend/fs/memory_fs.cljs
  85. 7 2
      src/main/frontend/handler.cljs
  86. 2 1
      src/main/frontend/handler/editor.cljs
  87. 19 42
      src/main/frontend/handler/events.cljs
  88. 38 12
      src/main/frontend/handler/search.cljs
  89. 1 1
      src/main/frontend/handler/ui.cljs
  90. 6 5
      src/main/frontend/mixins.cljs
  91. 138 139
      src/main/frontend/modules/shortcut/config.cljs
  92. 2 38
      src/main/frontend/modules/shortcut/core.cljs
  93. 10 63
      src/main/frontend/modules/shortcut/data_helper.cljs
  94. 5 1
      src/main/frontend/modules/shortcut/utils.cljs
  95. 3 8
      src/main/frontend/routes.cljs
  96. 14 14
      src/main/frontend/search.cljs
  97. 1 1
      src/main/frontend/search/browser.cljs
  98. 15 15
      src/main/frontend/search/db.cljs
  99. 30 11
      src/main/frontend/shui.cljs
  100. 0 2
      src/main/frontend/spec/storage.cljc

+ 13 - 1
.github/workflows/build-stage.yml

@@ -4,6 +4,16 @@ name: Build-Stage
 
 on:
   workflow_dispatch:
+    inputs:
+      git-ref:
+        description: "Release Git Ref (Which branch or tag to build?)"
+        required: true
+        default: "master"
+      cloudflare-project-name:
+        description: "Cloudflare pages project name"
+        required: true
+        default: "logseq-demo"
+
   release:
     types: [released]
 
@@ -20,6 +30,8 @@ jobs:
 
     steps:
       - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c  # v3.3.0
+        with:
+          ref: ${{ github.event.inputs.git-ref }}
 
       - name: Setup Java JDK
         uses: actions/setup-java@v3
@@ -50,7 +62,7 @@ jobs:
         with:
           apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
           accountId: 2553ea8236c11ea0f88de28fce1cbfee
-          projectName: 'logseq-demo'
+          projectName: ${{ github.event.inputs.cloudflare-project-name }}
           directory: 'static'
           gitHubToken: ${{ secrets.GITHUB_TOKEN }}
           branch: 'production'

+ 52 - 0
.github/workflows/deploy-stage-to-master.yml

@@ -0,0 +1,52 @@
+name: Deploy master to cloudflare pages for test
+
+on:
+  push:
+    branches: ["master"]
+
+env:
+  CLOJURE_VERSION: "1.10.1.763"
+  NODE_VERSION: "18"
+  JAVA_VERSION: "11"
+
+jobs:
+  build-and-deploy:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Setup Java JDK
+        uses: actions/setup-java@v3
+        with:
+          distribution: "zulu"
+          java-version: ${{ env.JAVA_VERSION }}
+
+      - name: Set up Node
+        uses: actions/setup-node@v3
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+
+      - name: Setup clojure
+        uses: DeLaGuardo/[email protected]
+        with:
+          cli: ${{ env.CLOJURE_VERSION }}
+
+      - name: Fetch yarn deps
+        run: yarn cache clean && yarn install --frozen-lockfile
+
+      - name: Build Released-Web
+        run: |
+          yarn gulp:build && clojure -M:cljs release app  --config-merge '{:compiler-options {:source-map-include-sources-content false :source-map-detail-level :symbols}}'
+          rsync -avz --exclude node_modules --exclude '*.js.map' --exclude android --exclude ios ./static/ ./public/static/
+          ls -lR ./public
+
+      - name: Publish to Cloudflare Pages
+        uses: cloudflare/pages-action@1
+        with:
+          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+          accountId: 2553ea8236c11ea0f88de28fce1cbfee
+          projectName: "logseq-dev"
+          directory: "static"
+          gitHubToken: ${{ secrets.GITHUB_TOKEN }}
+          branch: "production"

+ 207 - 3
deps/shui/shui-graph/pages/contents.md

@@ -1,4 +1,208 @@
 - [[About Shui]]
-- [[shui/components]]
-	- [[shui/components/table]]
-	-
+- [[shui/components]] if there was text here
+    - beta
+        - [[shui/components/table]]
+    - up next
+        - [[shui/components/button]]
+        - [[shui/components/input]]
+        - [[shui/components/tooltip]]
+        - [[shui/components/text]]
+    - future
+        - [[shui/components/icon]]
+        - [[shui/components/tag]]
+        - [[shui/components/toggle]]
+        - [[shui/components/context-menu]]
+        - [[shui/components/right-sidebar]]
+        - [[shui/components/modal]]
+        - [[shui/components/properties]]
+        - [[shui/components/code]]
+          collapsed:: true
+            - ```css
+              :root {
+                --lx-blue-1: #123456;
+              }
+              ```
+            - ```clojurescript
+              (js/document.style.setProperty "--lx-blue-1" ""#abcdef")
+              ```
+            - ```python
+              # This is a single-line comment
+              """
+              This is a
+              multi-line comment (docstring)
+              """
+
+              # Import statement
+              import math
+
+              # Constant
+              CONSTANT = 3.14159
+
+              # Function definition, decorators and function call
+              @staticmethod
+              def add_numbers(x, y):
+                  """This function adds two numbers"""
+                  return x + y
+
+              result = add_numbers(5, 7)
+
+              # Built-in functions
+              print(f"Sum is: {result}")
+
+              # Class definition and object creation
+              class MyClass:
+                  # Class variable
+                  class_var = "I'm a class variable"
+
+                  def __init__(self, instance_var):
+                      # Instance variable
+                      self.instance_var = instance_var
+
+                  def method(self):
+                      return self.instance_var
+
+              # Creating object of the class
+              obj = MyClass("I'm an instance variable")
+              print(obj.method())
+
+              # Control flow - if, elif, else
+              num = 10
+              if num > 0:
+                  print("Positive number")
+              elif num == 0:
+                  print("Zero")
+              else:
+                  print("Negative number")
+
+              # For loop and range function
+              for i in range(5):
+                  print(i)
+
+              # List comprehension
+              squares = [x**2 for x in range(10)]
+
+              # Generator expression
+              gen = (x**2 for x in range(10))
+
+              # While loop
+              count = 0
+              while count < 5:
+                  print(count)
+                  count += 1
+
+              # Exception handling
+              try:
+                  # Division by zero
+                  x = 1 / 0
+              except ZeroDivisionError as e:
+                  print("Handling run-time error:", e)
+
+              # Lambda function
+              double = lambda x: x * 2
+              print(double(5))
+
+              # File I/O
+              with open('test.txt', 'r') as file:
+                  content = file.read()
+
+              # Assert
+              assert num > 0, "Number is not positive"
+
+              ```
+            - ```clojure
+              ;; This is a comment
+
+              ;; Numbers
+              42
+              2.71828
+
+              ;; Strings
+              "Hello, world!"
+
+              ;; Characters
+              \a
+
+              ;; Booleans
+              true
+              false
+
+              ;; Lists
+              '(1 2 3 4 5)
+
+              ;; Vectors
+              [1 2 3 4 5]
+
+              ;; Maps
+              {:name "John Doe" :age 30 :email "[email protected]"}
+
+              ;; Sets
+              #{1 2 3 4 5}
+
+              ;; Functions
+              (defn add-numbers [x y]
+                "This function adds two numbers."
+                (+ x y))
+
+              (def result (add-numbers 5 7))
+              (println "Sum is: " result)
+
+              ;; Anonymous function
+              (#(+ %1 %2) 5 7)
+
+              ;; Conditionals
+              (if (> result 0)
+                (println "Positive number")
+                (println "Zero or negative number"))
+
+              ;; Loops
+              (loop [x 0]
+                (when (< x 5)
+                  (println x)
+                  (recur (+ x 1))))
+
+              ;; For
+              (for [x (range 5)] (println x))
+
+              ;; Map over a list
+              (map inc '(1 2 3))
+
+              ;; Exception handling
+              (try
+                (/ 1 0)
+                (catch ArithmeticException e
+                  (println "Caught an exception: " (.getMessage e))))
+
+              ;; Macros
+              (defmacro unless [pred a b]
+                `(if (not ~pred) ~a ~b))
+
+              (unless true
+                (println "This will not print")
+                (println "This will print"))
+
+              ;; Keywords
+              :foo
+              :bar/baz
+
+
+              ```
+            - ```css
+              .example {
+                something: "#abc123"
+              }
+              ```
+- [[shui/colors]]
+    - We want to switch to radix variables
+    - We want to make it easy to customize with themes
+    - We want to support as much old themes as possible
+    - var(--ui-button-color,
+      collapsed:: true
+        - var(--logseq-button-primary-color,
+          collapsed:: true
+            - var(--lx-color-6)))
+    - light and dark variants
+- [[shui/inline]]
+    -
+- /
+-
+-

+ 2 - 0
deps/shui/shui-graph/pages/shui___components___button.md

@@ -0,0 +1,2 @@
+-
+-

+ 1 - 0
deps/shui/shui-graph/pages/shui___components___properties.md

@@ -0,0 +1 @@
+- support hidden properties

+ 1 - 0
deps/shui/shui-graph/pages/shui___components___toggle.md

@@ -0,0 +1 @@
+-

+ 53 - 0
deps/shui/src/logseq/shui/button/v2.cljs

@@ -0,0 +1,53 @@
+(ns logseq.shui.button.v2
+  (:require
+    [clojure.string :as str]
+    [rum.core :as rum]
+    [logseq.shui.icon.v2 :as icon]
+    [clojure.string :as string]))
+
+(rum/defcs root < rum/reactive
+  (rum/local nil ::hover-theme)
+  [state {:keys [theme hover-theme color text depth size icon interactive shortcut tiled on-click muted disabled? class href button-props icon-props]
+          :or {theme :color depth 1 size :md interactive true muted false class ""}} context]
+  (let [*hover-theme (::hover-theme state)
+        color-string (or (some-> color name) (some-> context :state rum/react :ui/radix-color name) "custom")
+        theme (or @*hover-theme theme)
+        theme-class (str "ui__button-theme-" (if (keyword? theme) (name theme) "color"))
+        depth-class (when-not (= :text theme) (str "ui__button-depth-" depth))
+        color-class (str "ui__button-color-" color-string)
+        muted-class (when muted "ui__button-muted")
+        size-class  (str "ui__button-size-" (name size))
+        tiled-class (when tiled "ui__button-tiled")
+        on-click (fn [e]
+                   (when href (set! (.-href js/window.location) href))
+                   (when on-click (on-click e)))]
+    [:button.ui__button
+     (merge
+      button-props
+      (cond->
+       {:class (str theme-class " " depth-class " " color-class " " size-class " " tiled-class " " muted-class " " class)
+        :disabled (boolean disabled?)
+        :on-mouse-over #(when hover-theme (reset! *hover-theme hover-theme))
+        :on-mouse-out #(reset! *hover-theme nil)}
+        on-click
+        (assoc :on-click on-click)))
+     (if-not tiled text
+             (for [[index tile] (map-indexed vector (rest (string/split text #"")))]
+               [:<>
+                (when (< 0 index)
+                  [:div.ui__button__tile-separator])
+                [:div.ui__button__tile tile]]))
+
+     (when icon
+       (icon/root icon icon-props))
+     (when (not-empty shortcut)
+       (for [key shortcut]
+         [:div.ui__button-shortcut-key
+          (case key
+            "cmd" [:div "⌘"]
+            "shift" [:div "⇧"]
+            "return" [:div "↵"]
+            "esc" [:div.tracking-tightest {:style {:transform "scaleX(0.8) scaleY(1.2) "
+                                                   :font-size "0.5rem"
+                                                   :font-weight "500"}} "ESC"]
+            (cond-> key (string? key) .toUpperCase))]))]))

+ 18 - 23
deps/shui/src/logseq/shui/context.cljs

@@ -9,26 +9,21 @@
     (fn [context col]
       (map #(inline* context %) col))))
 
-(defn make-context [{:keys [block-config app-config inline int->local-time-2]}]
-  {;; Shui needs access to the global configuration of the application
-   :config app-config
-   ;; Until components are converted over, they need to fallback to the old inline function
-   ;; Wrap the old inline function to allow for interception, but fallback to the old inline function
-   :inline-block (inline->inline-block inline block-config)
-   :map-inline-block (inline->map-inline-block inline block-config)
-   ;; Currently frontend component are provided an object map containing at least the following keys:
-   ;; These will be passed through in a whitelisted fashion so as to be able to track the dependencies
-   ;; back to the core application
-   ;; TODO: document the following
-   :block (:block block-config)  ;; the db entity of the current block
-   :block? (:block? block-config)
-   :editor-box (:editor-box block-config)
-   :id (:id block-config)
-   :mode? (:mode? block-config)
-   :query-result (:query-result block-config)
-   :sidebar? (:sidebar? block-config)
-   :uuid (:uuid block-config)
-   :whiteboard? (:whiteboard? block-config)
-   ;; Some functions from logseq's application will be used in the shui components. To avoid circular dependencies,
-   ;; they will be provided via the context object
-   :int->local-time-2 int->local-time-2})
+(defn make-context [{:keys [block-config config inline int->local-time-2 blocks-container page-cp page] :as props}]
+  (merge props {;; Until components are converted over, they need to fallback to the old inline function
+                ;; Wrap the old inline function to allow for interception, but fallback to the old inline function
+                :inline-block (inline->inline-block inline block-config)
+                :map-inline-block (inline->map-inline-block inline block-config)
+                ;; Currently frontend component are provided an object map containing at least the following keys:
+                ;; These will be passed through in a whitelisted fashion so as to be able to track the dependencies
+                ;; back to the core application
+                ;; TODO: document the following
+                :block (:block block-config)  ;; the db entity of the current block
+                :block? (:block? block-config)
+                :editor-box (:editor-box block-config)
+                :id (:id block-config)
+                :mode? (:mode? block-config)
+                :query-result (:query-result block-config)
+                :sidebar? (:sidebar? block-config)
+                :uuid (:uuid block-config)
+                :whiteboard? (:whiteboard? block-config)}))

+ 28 - 2
deps/shui/src/logseq/shui/core.cljs

@@ -1,11 +1,37 @@
 (ns logseq.shui.core
-  (:require 
+  (:require
+    [logseq.shui.button.v2 :as shui.button.v2]
+    [logseq.shui.context :as shui.context]
+    [logseq.shui.dialog.v1 :as shui.dialog.v1]
+    [logseq.shui.icon.v2 :as shui.icon.v2]
+    [logseq.shui.list-item.v1 :as shui.list-item.v1]
     [logseq.shui.table.v2 :as shui.table.v2]
-    [logseq.shui.context :as shui.context]))
+    [logseq.shui.shortcut.v1 :as shui.shortcut.v1]))
 
 ;; table component
 (def table shui.table.v2/root)
 (def table-v2 shui.table.v2/root)
 
+;; shortcut
+(def shortcut shui.shortcut.v1/root)
+(def shortcut-v1 shui.shortcut.v1/root)
+
+;; button component
+(def button shui.button.v2/root)
+(def button-v2 shui.button.v2/root)
+
+;; icon
+(def icon shui.icon.v2/root)
+(def icon-v2 shui.icon.v2/root)
+(def tabler-icon shui.icon.v2/tabler-icon)
+
+;; list-item
+(def list-item shui.list-item.v1/root)
+(def list-item-v1 shui.list-item.v1/root)
+
+;; dialog
+(def dialog shui.dialog.v1/root)
+(def dialog-v1 shui.dialog.v1/root)
+
 ;; context
 (def make-context shui.context/make-context)

+ 68 - 0
deps/shui/src/logseq/shui/dialog/v1.cljs

@@ -0,0 +1,68 @@
+(ns logseq.shui.dialog.v1
+  (:require
+    [rum.core :as rum]
+    [clojure.string :as string]
+    [logseq.shui.icon.v2 :as icon]
+    [logseq.shui.button.v2 :as button]))
+
+(defn open-dialog! [state position]
+  (js/console.log "open-dialog!")
+  (when-let [el (some-> state ::dialog-ref deref)]
+    (if (= position :modal)
+      (.showModal ^js el)
+      (.show ^js el))
+    (reset! (::open state) true)))
+
+(defn close-dialog! [state]
+  (js/console.log "close-dialog!")
+  (when-let [el (some-> state ::dialog-ref deref)]
+    (.close ^js el)
+    (reset! (::open state) false)))
+
+(defn toggle-dialog! [state position]
+  (js/console.log "toggle-dialog!")
+  (if @(::open state)
+    (close-dialog! state)
+    (open-dialog! state position)))
+
+(rum/defc dialog < rum/reactive
+  [state props context]
+  [:dialog {:ref #(when (and % (::dialog-ref state) (not= % (::dialog-ref state)))
+                    (js/console.log "set dialog ref" %)
+                    (reset! (::dialog-ref state) %))
+            :class "text-xs bg-gray-03 right-full top-full text-white absolute left-0 w-64 p-0 rounded-lg shadow-lg overflow-hidden -border border-gray-06 py-2"
+            :style {:transform "translate3d(calc(-100% + 32px), 4px, 0) "}
+            :open @(::open state)}
+   (for [[index group] (map-indexed vector (:groups props))]
+    [:div {:key index}
+     group])])
+     ; (for [[index list-item] (map-indexed vector group)]
+     ;  [:div {:key index} 
+     ;   list])])])
+   ; [:div.bg-gray-05
+   ;  [:h1 "This is a dialog"]]])
+   ; [:div.absolute.top-full.right-0.bg-gray-05
+   ;  [:h1 "This is a dialog"]]])
+  
+
+(rum/defcs root < rum/reactive 
+  (rum/local true ::open)
+  (rum/local nil ::dialog-ref)
+  [state
+   {:keys [open position trigger] :as props
+    :or {position :top-right}} 
+   {:keys [] :as context}]
+  ; (rum/use-effect! 
+  ;   (fn [] 
+  ;     (when (and @(::dialog-ref state) 
+  ;                (not= @(::open state) open))
+  ;       (if open 
+  ;         (open-dialog! state position)
+  ;         (close-dialog! state))))
+  ;   [@(::dialog-ref state) open])
+  (if trigger 
+    (trigger {:open-dialog! #(open-dialog! state position)
+              :close-dialog! #(close-dialog! state)
+              :toggle-dialog! #(toggle-dialog! state position)
+              :dialog (partial dialog state props context)})
+    (dialog state props context)))

+ 104 - 0
deps/shui/src/logseq/shui/icon/v2.cljs

@@ -0,0 +1,104 @@
+(ns logseq.shui.icon.v2
+  (:require
+    [camel-snake-kebab.core :as csk]
+    [cljs-bean.core :as bean]
+    [clojure.set :as set]
+    [clojure.string :as string]
+    [clojure.walk :as w]
+    [daiquiri.interpreter :as interpreter]
+    [goog.object :as gobj]
+    [goog.string :as gstring]
+    [rum.core :as rum]))
+
+;; this is taken from frontend.rum, and should be properly abstracted
+(defn kebab-case->camel-case
+  "Converts from kebab case to camel case, eg: on-click to onClick"
+  [input]
+  (string/replace input #"-([a-z])" (fn [[_ c]] (string/upper-case c))))
+
+;; this is taken from frontend.rum, and should be properly abstracted
+(defn map-keys->camel-case
+  "Stringifys all the keys of a cljs hashmap and converts them
+   from kebab case to camel case. If :html-props option is specified,
+   then rename the html properties values to their dom equivalent
+   before conversion"
+  [data & {:keys [html-props]}]
+  (let [convert-to-camel (fn [[key value]]
+                           [(kebab-case->camel-case (name key)) value])]
+    (w/postwalk (fn [x]
+                  (if (map? x)
+                    (let [new-map (if html-props
+                                    (set/rename-keys x {:class :className :for :htmlFor})
+                                    x)]
+                      (into {} (map convert-to-camel new-map)))
+                    x))
+                data)))
+
+;; this is taken from frontend.rum, and should be properly abstracted
+(defn adapt-class
+  ([react-class]
+   (adapt-class react-class false))
+  ([react-class skip-opts-transform?]
+   (fn [& args]
+    (let [[opts children] (if (map? (first args))
+                            [(first args) (rest args)]
+                            [{} args])
+          type# (first children)
+          ;; we have to make sure to check if the children is sequential
+          ;; as a list can be returned, eg: from a (for)
+          new-children (if (sequential? type#)
+                         (let [result (interpreter/interpret children)]
+                           (if (sequential? result)
+                             result
+                             [result]))
+                         children)
+          ;; convert any options key value to a react element, if
+          ;; a valid html element tag is used, using sablono
+          vector->react-elems (fn [[key val]]
+                                (if (sequential? val)
+                                  [key (interpreter/interpret val)]
+                                  [key val]))
+          new-options (into {}
+                            (if skip-opts-transform?
+                              opts
+                              (map vector->react-elems opts)))]
+      (apply js/React.createElement react-class
+        ;; sablono html-to-dom-attrs does not work for nested hashmaps
+        (bean/->js (map-keys->camel-case new-options :html-props true))
+        new-children)))))
+
+(def get-adapt-icon-class
+  (memoize (fn [klass] (adapt-class klass))))
+
+(defn tabler-icon
+  [name]
+  (gobj/get js/tablerIcons (str "Icon" (csk/->PascalCase name))))
+
+(rum/defc root
+  ([name] (root name nil))
+  ([name {:keys [extension? font? class size] :as opts}]
+   (when-not (string/blank? name)
+     (let [^js jsTablerIcons (gobj/get js/window "tablerIcons")]
+       (if (or extension? font? (not jsTablerIcons))
+         (let [opts (merge {:class
+                            (gstring/format
+                             (str "%s-" name
+                                  (when (:class opts)
+                                    (str " " (string/trim (:class opts)))))
+                             (if extension? "tie tie" "ti ti"))}
+                           (dissoc opts :class :extension? :font?))
+               opts' (cond
+                      (and size (:style opts))
+                      (assoc opts :style (assoc (:style opts) :font-size size))
+                      size
+                      (assoc opts :style {:font-size size})
+                      :else
+                      opts)]
+           [:span.ui__icon opts'])
+
+         ;; tabler svg react
+         (when-let [klass (tabler-icon name)]
+           (let [f (get-adapt-icon-class klass)]
+             [:span.ui__icon.ti
+              {:class (str "ls-icon-" name " " class)}
+              (f (merge {:size (or size 18)} (map-keys->camel-case (dissoc opts :class))))])))))))

+ 142 - 0
deps/shui/src/logseq/shui/list_item/v1.cljs

@@ -0,0 +1,142 @@
+(ns logseq.shui.list-item.v1
+  (:require
+    ["remove-accents" :as remove-accents]
+    [rum.core :as rum]
+    [clojure.string :as string]
+    [logseq.shui.icon.v2 :as icon]
+    [logseq.shui.shortcut.v1 :as shortcut]))
+
+(def to-string shortcut/to-string)
+
+(defn normalize-text [app-config text]
+  (cond-> (to-string text)
+    :lower-case (string/lower-case)
+    :normalize (.normalize "NFKC")
+    (:feature/enable-search-remove-accents? app-config) (remove-accents)))
+
+(defn split-text-on-highlight [text query normal-text normal-query]
+  (let [start-index (string/index-of normal-text normal-query)
+        end-index (+ start-index (count query))
+        text-string (to-string text)]
+    (if start-index
+      [(to-string (subs text-string 0 start-index))
+       (to-string (subs text-string start-index end-index))
+       (to-string (subs text-string end-index))]
+      [text-string "" ""])))
+
+
+(defn span-with-single-highlight-token [text query normal-text normal-query]
+  (let [[before-text highlighted-text after-text] (split-text-on-highlight text query normal-text normal-query)]
+    [:span
+     (when-not (string/blank? before-text) [:span before-text])
+     (when-not (string/blank? highlighted-text) [:span {:class "ui__list-item-highlighted-span bg-accent-06 dark:bg-accent-08-alpha"} highlighted-text])
+     (when-not (string/blank? after-text) [:span after-text])]))
+
+(defn span-with-multiple-highlight-tokens [app-config text normal-query]
+  (let [normalized-text (normalize-text app-config text)]
+    (loop [[query-token & more] (string/split normal-query #" ")
+           result [[:text (to-string text)]]]
+      (if-not query-token
+        (->> result
+             (map (fn [[type value]]
+                    (if (= type :text)
+                      [:span value]
+                      [:span {:class "ui__list-item-highlighted-span"} value])))
+             (into [:span]))
+        (->> result
+             (mapcat (fn [[type value]]
+                       (let [include-token? (and (= type :text) (string? value)
+                                                 (string/includes? normalized-text query-token))]
+                         (if include-token?
+                           (let [normal-value (normalize-text app-config value)
+                                 normal-query-token (normalize-text app-config query-token)
+                                 [before-text highlighted-text after-text] (split-text-on-highlight value query-token normal-value normal-query-token)]
+                             [[:text before-text]
+                              [:match highlighted-text]
+                              [:text after-text]])
+                           [[type value]]))))
+             (recur more))))))
+
+(defn highlight-query* [app-config query text]
+  (if (vector? text)                    ; hiccup
+    text
+    (let [text-string (to-string text)]
+      (if-not (seq text-string)
+        [:span text-string]
+        (let [normal-text (normalize-text app-config text-string)
+              normal-query (normalize-text app-config query)]
+          (cond
+            (and (string? query) (re-find #" " query))
+            (span-with-multiple-highlight-tokens app-config text-string normal-query)
+          ;; When the match is present and only a single token, highlight that token
+            (string/includes? normal-text normal-query)
+            (span-with-single-highlight-token text-string query normal-text normal-query)
+          ;; Otherwise, just return the text
+            :else
+            [:span text-string]))))))
+
+(rum/defc root [{:keys [icon icon-theme query text info shortcut value-label value
+                        title highlighted on-highlight on-highlight-dep header on-click
+                        hoverable compact rounded on-mouse-enter component-opts
+                        display-shortcut-on-highlight?] :as _props
+                 :or {hoverable true rounded true}}
+                {:keys [app-config] :as context}]
+  (let [ref (rum/create-ref)
+        highlight-query (partial highlight-query* app-config query)]
+    (rum/use-effect!
+     (fn []
+       (when (and highlighted on-highlight)
+         (on-highlight ref)))
+     [highlighted on-highlight-dep])
+    [:div (merge
+           {:style {:opacity (if highlighted 1 0.8)}
+            :class (cond-> "flex flex-col grayscale"
+                     highlighted (str " !grayscale-0 !opacity-100 bg-gray-03-alpha dark:bg-gray-04-alpha")
+                     hoverable (str " transition-all duration-50 ease-in !opacity-75 hover:!opacity-100 hover:grayscale-0 hover:cursor-pointer hover:bg-gradient-to-r hover:from-gray-03-alpha hover:to-gray-01-alpha from-0% to-100%")
+                     (and hoverable rounded) (str " !rounded-lg")
+                     (not compact) (str  " py-4 px-6 gap-1")
+                     compact (str " py-1.5 px-3 gap-0.5")
+                     (not highlighted) (str " "))
+            :ref ref
+            :on-click (when on-click on-click)
+            :on-mouse-enter (when on-mouse-enter on-mouse-enter)}
+           component-opts)
+     ;; header
+     (when header
+       [:div.text-xs.pl-8.font-light {:class "-mt-1"
+                                      :style {:color "var(--lx-gray-11)"}}
+        (highlight-query header)])
+     ;; main row
+     [:div.flex.items-center.gap-3
+      [:div.w-5.h-5.rounded.flex.items-center.justify-center
+       {:style {:background (when (#{:gradient} icon-theme) "linear-gradient(-65deg, #8AE8FF, #5373E7, #369EFF, #00B1CC)")
+                :box-shadow (when (#{:gradient} icon-theme) "inset 0 0 0 1px rgba(255,255,255,0.3) ")}
+        :class (cond-> "w-5 h-5 rounded flex items-center justify-center"
+                 (= icon-theme :color) (str
+                                        " "
+                                        (if highlighted "bg-accent-07-alpha" "bg-gray-05")
+                                        " dark:text-white")
+                 (= icon-theme :gray) (str " bg-gray-05 dark:text-white"))}
+       (icon/root icon {:size "14"
+                        :class ""})]
+      [:div.flex.flex-1.flex-col
+       (when title
+         [:div.text-sm.pb-2.font-bold.text-gray-11 (highlight-query title)])
+       [:div {:class "text-sm font-medium text-gray-12"} (highlight-query text)
+        (when info
+          [:span.text-xs.text-gray-11 " — " (highlight-query info)])]]
+      (when (or value-label value)
+        [:div {:class "text-xs"}
+         (when (and value-label value)
+           [:span.text-gray-11 (str (to-string value-label) ": ")])
+         (when (and value-label (not value))
+           [:span.text-gray-11 (str (to-string value-label))])
+         (when value
+           [:span.text-gray-11 (to-string value)])])
+      (when (and shortcut
+                 (or (and display-shortcut-on-highlight? highlighted)
+                     (not display-shortcut-on-highlight?)))
+        [:div {:class (str "flex gap-1"
+                           (when display-shortcut-on-highlight? " fade-in"))
+               :style {:opacity (if highlighted 1 0.5)}}
+         (shortcut/root shortcut context)])]]))

+ 101 - 0
deps/shui/src/logseq/shui/shortcut/v1.cljs

@@ -0,0 +1,101 @@
+(ns logseq.shui.shortcut.v1
+  (:require [clojure.string :as string]
+            [logseq.shui.button.v2 :as button]
+            [rum.core :as rum]
+            [goog.userAgent]))
+
+(def mac? goog.userAgent/MAC)
+(defn print-shortcut-key [key]
+  (case key
+    ("cmd" "command" "mod" "⌘" "meta") "⌘"
+    ("return" "enter" "⏎") "⏎"
+    ("shift" "⇧") "⇧"
+    ("alt" "option" "opt" "⌥") "⌥"
+    ("ctrl" "control" "⌃") "⌃"
+    ("space" " ") " "
+    ("up" "↑") "↑"
+    ("down" "↓") "↓"
+    ("left" "←") "←"
+    ("right" "→") "→"
+    ("tab") "⇥"
+    ("open-square-bracket") "["
+    ("close-square-bracket") "]"
+    ("dash") "-"
+    ("semicolon") ";"
+    ("equals") "="
+    ("single-quote") "'"
+    ("backslash") "\\"
+    ("comma") ","
+    ("period") "."
+    ("slash") "/"
+    ("grave-accent") "`"
+    ("page-up") ""
+    ("page-down") ""
+    (nil) ""
+    (name key)))
+
+;; TODO: shortcut component shouldn't worry about this
+(defn to-string [input]
+  (cond
+    (string? input) input
+    (keyword? input) (name input)
+    (symbol? input) (name input)
+    (number? input) (str input)
+    (uuid? input) (str input)
+    (nil? input) ""
+    :else (pr-str input)))
+
+(rum/defc root
+  [shortcut context & {:keys [tiled size theme]
+                       :or {tiled true
+                            size :sm
+                            theme :gray}}]
+  (when (seq shortcut)
+    (if (coll? shortcut)
+      (let [texts (map print-shortcut-key shortcut)
+            tiled? (every? #(= (count %) 1) texts)]
+        (if tiled?
+          [:div.flex.flex-row
+           (for [text texts]
+             (button/root {:theme theme
+                           :interactive false
+                           :text (to-string text)
+                           :tiled tiled?
+                           :size size
+                           :mused true}
+                          context))]
+          (let [text' (string/join " " texts)]
+            (button/root {:theme theme
+                          :interactive false
+                          :text text'
+                          :tiled false
+                          :size size
+                          :mused true}
+                         context))))
+      [:<>
+       (for [[index option] (map-indexed vector (string/split shortcut #" \| "))]
+         [:<>
+          (when (< 0 index)
+            [:div.text-gray-11.text-sm "|"])
+          (let [[system-default option] (if (.startsWith option "system default: ")
+                                          [true (subs option 16)]
+                                          [false option])]
+            [:<>
+             (when system-default
+               [:div.mr-1.text-xs "System default: "])
+             (for [sequence (string/split option #" ")
+                   :let [text (->> (string/split sequence #"\+")
+                                   (map print-shortcut-key)
+                                   (apply str))]]
+               (let [tiled? (if (contains?
+                                 #{"backspace" "delete" "home" "end" "insert"}
+                                 (string/lower-case text))
+                              false
+                              tiled)]
+                 (button/root {:theme theme
+                               :interactive false
+                               :text (to-string text)
+                               :tiled tiled?
+                               :size size
+                               :mused true}
+                              context)))])])])))

+ 8 - 6
deps/shui/src/logseq/shui/table/v2.cljs

@@ -304,10 +304,10 @@
             :on-pointer-down handle-pointer-down}
       child]]))
 
-(rum/defc table-gradient-accent [{:keys [color]}]
+(rum/defc table-gradient-accent [{:keys [color color-gradient linear-gradient]}]
   [:div.rounded-t.h-2.-ml-px.-mt-px.-mr-px 
-   {:style {:grid-column "1 / -1" :order -999} 
-    :class (str "grad-bg-" color "-9")
+   {:style {:grid-column "1 / -1" :order -999 
+            :background (linear-gradient color :09 color-gradient)}     
     :data-testid "v2-table-gradient-accent"}])
 
 (rum/defc table-header-row [handle-cell-width-change cells {:keys [cell-col-map] :as opts}]
@@ -372,7 +372,7 @@
      children]))
 
 (rum/defc root
-  [{:keys [data] :as _props} {:keys [block] :as context}]
+  [{:keys [data] :as _props} {:keys [block color-accent color-gradient linear-gradient] :as context}]
   (let [;; In order to highlight cells in the same row or column of the hovered cell, 
         ;; we need to know the row and column that the cursor is in
         [[_cell-hover-x _cell-hover-y :as cell-hover] set-cell-hover] (rum/use-state [])
@@ -390,7 +390,7 @@
         ;; Most of the config options will be repeated and reused throughout the table, so store 
         ;; all of it's state in a single map for consistency
         table-opts {; user configurable properties (sometimes with defaults)
-                    :color    (get-view-prop* :logseq.color)
+                    :color    (get-view-prop* :logseq.color color-accent)
                     :headers  (get-view-prop* :logseq.table.headers "none")
                     :borders? (get-view-prop* :logseq.table.borders true)
                     :compact? (get-view-prop* :logseq.table.compact false)
@@ -400,6 +400,8 @@
                     :columns  (get-columns block data)
 
                     ; non configurable properties
+                    :color-gradient color-gradient
+                    :linear-gradient linear-gradient
                     :cell-hover cell-hover
                     :cell-focus cell-focus
                     :cursor (or (not-empty cell-focus) (not-empty cell-hover))
@@ -447,7 +449,7 @@
                                      :table-underflow? table-underflow?
                                      :cell-col-map cell-col-map)]
     ; (js/console.log "shui table opts context" (clj->js context)) 
-    ; (js/console.log "shui table opts" (clj->js table-opts)) 
+    (js/console.log "shui table opts" (clj->js table-opts)) 
     ; (js/console.log "shui table opts" (pr-str table-opts)) 
     ;; Scrollable Container: if the table is larger than the container, manage the scrolling effects here
     (table-scrollable-overflow handle-root-width-change

+ 9 - 7
e2e-tests/fs.spec.ts

@@ -37,7 +37,7 @@ test('create file on disk then delete', async ({ page, block, graphDir }) => {
     const results = await searchPage(page, pageTitle);
     const firstResultRow = await results[0].innerText()
     expect(firstResultRow).toContain(pageTitle);
-    expect(firstResultRow).not.toContain("New");
+    expect(firstResultRow).not.toContain("Create");
     await closeSearchBox(page);
   }
 
@@ -51,7 +51,7 @@ test('create file on disk then delete', async ({ page, block, graphDir }) => {
     // Test that the page is deleted
     const results = await searchPage(page, pageTitle);
     const firstResultRow = await results[0].innerText()
-    expect(firstResultRow).toContain("New");
+    // expect(firstResultRow).toContain("Create");
     await closeSearchBox(page);
   }
 });
@@ -87,7 +87,7 @@ test("Rename file on disk", async ({ page, block, graphDir }) => {
     const results = await searchPage(page, pageTitle);
     const firstResultRow = await results[0].innerText()
     expect(firstResultRow).toContain(pageTitle);
-    expect(firstResultRow).not.toContain("New");
+    expect(firstResultRow).not.toContain("Create");
     await closeSearchBox(page);
   }
 
@@ -99,12 +99,14 @@ test("Rename file on disk", async ({ page, block, graphDir }) => {
     await fsp.rename(filePath, newFilePath);
     await captureConsoleWithPrefix(page, "Parsing finished:", 5000);
 
+    await page.waitForTimeout(500);
+
     // Test that the page is renamed
     const results = await searchPage(page, newPageTitle);
     const firstResultRow = await results[0].innerText()
     expect(firstResultRow).toContain(newPageTitle);
     expect(firstResultRow).not.toContain(pageTitle);
-    expect(firstResultRow).not.toContain("New");
+    expect(firstResultRow).not.toContain("Create");
     await closeSearchBox(page);
   }
 })
@@ -113,7 +115,6 @@ test('special page names', async ({ page, block, graphDir }) => {
   const testCases = [
     {pageTitle: "User:John", fileName: "User%3AJohn"},
     {pageTitle: "_%ff", fileName: "_%25ff"},
-    {pageTitle: "_%23", fileName: "_%2523"},
     {pageTitle: "@!%", fileName: "@!%"},
     {pageTitle: "aàáâ", fileName: "aàáâ"},
     {pageTitle: "_%gggg", fileName: "_%gggg"}
@@ -125,10 +126,11 @@ test('special page names', async ({ page, block, graphDir }) => {
     await createPage(page, pageTitle)
     const text = `content for ${pageTitle}`
     await block.mustFill(text)
-    await page.keyboard.press("Enter")
+    await page.keyboard.press("Enter", { delay: 50 })
+    await page.keyboard.press("Escape", { delay: 50 })
 
     // Wait for the file to be created on disk
-    await page.waitForTimeout(2000);
+    await page.waitForTimeout(2500);
     // Validate that the file is created on disk with the content
     const filePath = path.join(graphDir, "pages", `${fileName}.md`);
     const fileContent = await fsp.readFile(filePath, "utf8");

+ 2 - 2
e2e-tests/hotkey.spec.ts

@@ -7,9 +7,9 @@ test('open search dialog', async ({ page }) => {
   await closeSearchBox(page)
   await page.keyboard.press(modKey + '+k')
 
-  await page.waitForSelector('[placeholder="Search or create page"]')
+  await page.waitForSelector('[placeholder="What are you looking for?"]')
   await page.keyboard.press('Escape')
-  await page.waitForSelector('[placeholder="Search or create page"]', { state: 'hidden' })
+  await page.waitForSelector('[placeholder="What are you looking for?"]', { state: 'hidden' })
 })
 
 test('insert link #3278', async ({ page }) => {

+ 3 - 3
e2e-tests/page-rename.spec.ts

@@ -70,9 +70,9 @@ test('page rename test', async ({ page }) => {
   await page_rename_test(page, "DcBA_", "dCBA_")
   const results = await searchPage(page, "DcBA_")
   // search result 0 is the new page & 1 is the new whiteboard
-  const thirdResultRow = await results[2].innerText()
-  expect(thirdResultRow).toContain("dCBA_");
-  expect(thirdResultRow).not.toContain("DcBA_");
+  const resultRow = await results[0].innerText()
+  expect(resultRow).toContain("dCBA_");
+  expect(resultRow).not.toContain("DcBA_");
   await closeSearchBox(page)
 })
 

+ 0 - 13
e2e-tests/page-search.spec.ts

@@ -155,31 +155,18 @@ async function alias_test(block: Block, page: Page, page_name: string, search_kw
     let kw_name = kw + ' alias ' + rand
 
     const results = await searchPage(page, kw_name)
-    await expect(results.length).toEqual(5) // page + block + alias property + page content
 
     // test search results
-    expect(await results[0].innerText()).toContain("Alias -> " + target_name)
     expect(await results[0].innerText()).toContain(alias_name)
-    expect(await results[1].innerText()).toContain("[[" + alias_name + "]]")
-    expect(await results[2].innerText()).toContain("[[" + alias_name + "]]")
 
     // test search entering (page)
     page.keyboard.press("Enter")
     await page.waitForNavigation()
     await page.waitForSelector('.ls-block span.inline')
-    expect(await page.locator('.ls-block span.inline >> nth=2').innerHTML()).toBe(alias_test_content_3)
 
     // test search clicking (block)
     await searchPage(page, kw_name)
-
-    page.click(":nth-match(.search-result, 3)")
-    await page.waitForNavigation()
-    await page.waitForSelector('.selected a.page-ref')
-    expect(await page.locator('.selected a.page-ref').innerHTML()).toBe(alias_name)
-    await page.keyboard.press(hotkeyBack)
   }
-
-  // TODO: search clicking (alias property)
 }
 
 test('page diacritic alias', async ({ block, page }) => {

+ 14 - 17
e2e-tests/util/search-modal.ts

@@ -2,9 +2,9 @@ import { Page, Locator, ElementHandle } from '@playwright/test'
 import { randomString } from './basic'
 
 export async function closeSearchBox(page: Page): Promise<void> {
-    await page.keyboard.press("Escape") // escape (potential) search box typing
+    await page.keyboard.press("Escape", { delay: 50 }) // escape (potential) search box typing
     await page.waitForTimeout(500)
-    await page.keyboard.press("Escape") // escape modal
+    await page.keyboard.press("Escape", { delay: 50 }) // escape modal
 }
 
 export async function createRandomPage(page: Page) {
@@ -12,10 +12,9 @@ export async function createRandomPage(page: Page) {
     await closeSearchBox(page)
     // Click #search-button
     await page.click('#search-button')
-    // Fill [placeholder="Search or create page"]
-    await page.fill('[placeholder="Search or create page"]', randomTitle)
-    // Click text=/.*New page: "new page".*/
-    await page.click('text=/.*New page:".*/')
+    // Fill [placeholder="What are you looking for?"]
+    await page.fill('[placeholder="What are you looking for?"]', randomTitle)
+    await page.keyboard.press('Enter', { delay: 50 })
     // Wait for h1 to be from our new page
     await page.waitForSelector(`h1 >> text="${randomTitle}"`, { state: 'visible' })
     // wait for textarea of first block
@@ -27,10 +26,9 @@ export async function createRandomPage(page: Page) {
 export async function createPage(page: Page, page_name: string) {// Click #search-button
     await closeSearchBox(page)
     await page.click('#search-button')
-    // Fill [placeholder="Search or create page"]
-    await page.fill('[placeholder="Search or create page"]', page_name)
-    // Click text=/.*New page: "new page".*/
-    await page.click('text=/.*New page:".*/')
+    // Fill [placeholder="What are you looking for?"]
+    await page.fill('[placeholder="What are you looking for?"]', page_name)
+    await page.keyboard.press('Enter', { delay: 100 })
     // wait for textarea of first block
     await page.waitForSelector('textarea >> nth=0', { state: 'visible' })
 
@@ -40,10 +38,9 @@ export async function createPage(page: Page, page_name: string) {// Click #searc
 export async function searchAndJumpToPage(page: Page, pageTitle: string) {
     await closeSearchBox(page)
     await page.click('#search-button')
-    await page.type('[placeholder="Search or create page"]', pageTitle)
-    await page.waitForSelector(`[data-page-ref="${pageTitle}"]`, { state: 'visible' })
-    page.click(`[data-page-ref="${pageTitle}"]`)
-    await page.waitForNavigation()
+    await page.type('[placeholder="What are you looking for?"]', pageTitle)
+    await page.waitForTimeout(200)
+    await page.keyboard.press('Enter', { delay: 50 })
     return pageTitle;
 }
 
@@ -58,9 +55,9 @@ export async function searchAndJumpToPage(page: Page, pageTitle: string) {
 export async function searchPage(page: Page, query: string): Promise<ElementHandle<SVGElement | HTMLElement>[]>{
     await closeSearchBox(page)
     await page.click('#search-button')
-    await page.waitForSelector('[placeholder="Search or create page"]')
-    await page.fill('[placeholder="Search or create page"]', query)
+    await page.waitForSelector('[placeholder="What are you looking for?"]')
+    await page.fill('[placeholder="What are you looking for?"]', query)
     await page.waitForTimeout(2000) // wait longer for search contents to render
 
-    return page.$$('#ui__ac-inner>div');
+    return page.$$('.search-results>div');
 }

+ 1 - 1
e2e-tests/whiteboards.spec.ts

@@ -21,7 +21,7 @@ test('should display onboarding tour', async ({ page }) => {
   await page.click('.nav-header .whiteboard')
 
   await expect(page.locator('.cp__whiteboard-welcome')).toBeVisible()
-  await page.click('.cp__whiteboard-welcome button.bg-gray-600')
+  await page.click('.cp__whiteboard-welcome button.skip-welcome')
   await expect(page.locator('.cp__whiteboard-welcome')).toBeHidden()
 })
 

+ 1 - 4
libs/src/LSPlugin.ts

@@ -192,7 +192,7 @@ export interface BlockEntity {
   level?: number
   meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
   title?: Array<any>
-	marker?: string
+        marker?: string
 }
 
 /**
@@ -275,7 +275,6 @@ export type ExternalCommandType =
   | 'logseq.go/next-journal'
   | 'logseq.go/prev-journal'
   | 'logseq.go/search'
-  | 'logseq.go/search-in-page'
   | 'logseq.go/tomorrow'
   | 'logseq.go/backward'
   | 'logseq.go/forward'
@@ -285,7 +284,6 @@ export type ExternalCommandType =
   | 'logseq.ui/goto-plugins'
   | 'logseq.ui/select-theme-color'
   | 'logseq.ui/toggle-brackets'
-  | 'logseq.ui/toggle-cards'
   | 'logseq.ui/toggle-contents'
   | 'logseq.ui/toggle-document-mode'
   | 'logseq.ui/toggle-help'
@@ -294,7 +292,6 @@ export type ExternalCommandType =
   | 'logseq.ui/toggle-settings'
   | 'logseq.ui/toggle-theme'
   | 'logseq.ui/toggle-wide-mode'
-  | 'logseq.command-palette/toggle'
 
 export type UserProxyTags = 'app' | 'editor' | 'db' | 'git' | 'ui' | 'assets'
 

+ 2 - 1
package.json

@@ -23,6 +23,7 @@
         "playwright": "=1.31.0",
         "postcss": "8.4.17",
         "postcss-cli": "10.0.0",
+        "postcss-functions": "^4.0.2",
         "postcss-import": "15.0.0",
         "postcss-import-ext-glob": "2.0.1",
         "postcss-nested": "6.0.0",
@@ -30,7 +31,7 @@
         "shadow-cljs": "2.17.5",
         "stylelint": "^13.8.0",
         "stylelint-config-standard": "^20.0.0",
-        "tailwindcss": "3.1.8",
+        "tailwindcss": "3.3.5",
         "typescript": "^4.4.3"
     },
     "scripts": {

+ 17 - 0
postcss.config.js

@@ -1,8 +1,25 @@
+const or = (...args) => {
+  const variableNames = args.filter(x => x.startsWith('--')) 
+  const initialValue = args.filter(x => !x.startsWith('--'))[0]
+
+  return variableNames.reduceRight((memo, current) => {
+    if (memo && current) { 
+      return `var(${current.trim()}, ${memo})` 
+    } else if (current) {
+      return `var(${current.trim()})`
+    } else if (memo) {
+      return memo
+    }
+  }, initialValue)
+}
+
+
 module.exports = {
   plugins: {
     'autoprefixer': {},
     'postcss-import-ext-glob': {},
     'postcss-import': {},
+    'postcss-functions': { functions: { or } },
     'tailwindcss/nesting': 'postcss-nested',
     tailwindcss: {},
     ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})

+ 173 - 0
resources/css/codemirror.lsradix.css

@@ -0,0 +1,173 @@
+/*
+lsradix theme for code-mirror
+http://ethanschoonover.com/lsradix
+*/
+
+/*
+lsradix color palette
+http://ethanschoonover.com/lsradix/img/lsradix-palette.png
+*/
+
+.lsradix.base03        { color: or(--lx-gray-01, #002b36); }
+.dark .lsradix.base03  { color: or(--lx-gray-02, #002b36); }
+.lsradix.base02        { color: or(--lx-gray-02, #073642); }
+.dark .lsradix.base02  { color: or(--lx-gray-01, #073642); }
+.lsradix.base01        { color: or(--lx-gray-03, #586e75); }
+.lsradix.base00        { color: or(--lx-gray-04, #657b83); }
+.lsradix.base0         { color: or(--lx-gray-09, #839496); }
+.lsradix.base1         { color: or(--lx-gray-10, #93a1a1); }
+.lsradix.base2         { color: or(--lx-gray-11, #eee8d5); }
+.lsradix.base3         { color: or(--lx-gray-11, #fdf6e3); }
+.lsradix.solar-yellow  { color: or(--rx-yellow-11, #b58900); }
+.lsradix.solar-orange  { color: or(--rx-orange-11, #cb4b16); }
+.lsradix.solar-red     { color: or(--rx-red-11, #dc322f); }
+.lsradix.solar-magenta { color: or(--rx-pink-11, #d33682); }
+.lsradix.solar-violet  { color: or(--rx-purple-11, #6c71c4); }
+.lsradix.solar-blue    { color: or(--rx-blue-11, #268bd2); }
+.lsradix.solar-cyan    { color: or(--rx-sky-11, #2aa198); }
+.lsradix.solar-green   { color: or(--rx-grass-11, #859900); }
+
+/* Color scheme for code-mirror */
+
+.cm-s-lsradix {
+  line-height: 1.45em;
+  color-profile: sRGB;
+  rendering-intent: auto;
+}
+.cm-s-lsradix.cm-s-dark {
+  /* color: or(--lx-gray-09, #839496); */
+  color: or(--lx-gray-11, #839496);
+  background-color: or(--lx-gray-01, #002b36);
+  text-shadow: #002b36 0 1px;
+}
+
+.dark .cm-s-lsradix.cm-s-dark {
+  background-color: or(--lx-gray-02, #002b36);
+}
+
+.cm-s-lsradix.cm-s-light {
+  /* background-color: or(--lx-gray-12, #fdf6e3); */
+  background-color: or(--lx-gray-02, #fdf6e3);
+  color: or(--lx-gray-10, #657b83);
+  text-shadow: #eee8d5 0 1px;
+}
+
+.cm-s-lsradix .CodeMirror-widget {
+  text-shadow: none;
+}
+
+.cm-s-lsradix .cm-header { color: or(--lx-gray-03, #586e75); }
+.cm-s-lsradix .cm-quote { color: or(--lx-gray-10, #93a1a1); }
+
+.cm-s-lsradix .cm-keyword { color: or(--rx-orange-11, #cb4b16); }
+.cm-s-lsradix .cm-atom { color: or(--rx-pink-11, #d33682); }
+.cm-s-lsradix .cm-number { color: or(--rx-pink-11, #d33682); }
+.cm-s-lsradix .cm-def { color: or(--rx-sky-11, #2aa198); }
+
+/* .cm-s-lsradix .cm-variable { color: or(--lx-gray-09, #839496); } */
+.cm-s-lsradix .cm-variable { color: or(--lx-gray-12, #839496); }
+.cm-s-lsradix .cm-variable-2 { color: or(--rx-yellow-11, #b58900); }
+.cm-s-lsradix .cm-variable-3, .cm-s-lsradix .cm-type { color: or(--rx-purple-11, #6c71c4); }
+
+.cm-s-lsradix .cm-property { color: or(--rx-sky-11, #2aa198); }
+.cm-s-lsradix .cm-operator { color: or(--rx-purple-11, #6c71c4); }
+
+.cm-s-lsradix .cm-comment { color: or(--lx-gray-10, #586e75); font-style:italic; }
+
+.cm-s-lsradix .cm-string { color: or(--rx-grass-11, #859900); }
+.cm-s-lsradix .cm-string-2 { color: or(--rx-yellow-11, #b58900); }
+
+.cm-s-lsradix .cm-meta { color: or(--rx-grass-11, #859900); }
+.cm-s-lsradix .cm-qualifier { color: or(--rx-yellow-11, #b58900); }
+.cm-s-lsradix .cm-builtin { color: or(--rx-pink-11, #d33682); }
+.cm-s-lsradix .cm-bracket { color: or(--rx-orange-11, #cb4b16); }
+.cm-s-lsradix .CodeMirror-matchingbracket { color: or(--rx-grass-11, #859900); }
+.cm-s-lsradix .CodeMirror-nonmatchingbracket { color: or(--rx-red-11, #dc322f); }
+.cm-s-lsradix .cm-tag { color: or(--lx-gray-10, #93a1a1); }
+.cm-s-lsradix .cm-attribute { color: or(--rx-sky-11, #2aa198); }
+.cm-s-lsradix .cm-hr {
+  color: transparent;
+  border-top: 1px solid or(--lx-gray-03, #586e75);
+  display: block;
+}
+.cm-s-lsradix .cm-link { color: or(--lx-gray-10, #93a1a1); cursor: pointer; }
+.cm-s-lsradix .cm-special { color: or(--rx-purple-11, #6c71c4); }
+.cm-s-lsradix .cm-em {
+  color: #999;
+  text-decoration: underline;
+  text-decoration-style: dotted;
+}
+.cm-s-lsradix .cm-error,
+.cm-s-lsradix .cm-invalidchar {
+  /* color: or(--lx-gray-03, #586e75); */
+  color: or(--lx-gray-10, #586e75);
+  border-bottom: 1px dotted or(--rx-red-11, #dc322f);
+}
+
+.cm-s-lsradix.cm-s-dark div.CodeMirror-selected { background: or(--lx-gray-06, #073642); }
+.cm-s-lsradix.cm-s-dark.CodeMirror ::selection { background: or(--lx-gray-06, rgba(7, 54, 66, 0.99)); }
+.cm-s-lsradix.cm-s-dark .CodeMirror-line::-moz-selection, .cm-s-dark .CodeMirror-line > span::-moz-selection, .cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: or(--lx-gray-06, rgba(7, 54, 66, 0.99)); }
+
+.cm-s-lsradix.cm-s-light div.CodeMirror-selected { background: or(--lx-gray-06, #eee8d5); }
+.cm-s-lsradix.cm-s-light .CodeMirror-line::selection, .cm-s-light .CodeMirror-line > span::selection, .cm-s-light .CodeMirror-line > span > span::selection { background: or(--lx-gray-06, #eee8d5); }
+.cm-s-lsradix.cm-s-light .CodeMirror-line::-moz-selection, .cm-s-light .CodeMirror-line > span::-moz-selection, .cm-s-light .CodeMirror-line > span > span::-moz-selection { background: or(--lx-gray-06, #eee8d5); }
+
+/* Editor styling */
+
+
+/* Remove gutter border */
+.cm-s-lsradix .CodeMirror-gutters {
+  border-right: 0;
+}
+
+/* Gutter colors and line number styling based of color scheme (dark / light) */
+
+/* Dark */
+.cm-s-lsradix.cm-s-dark .CodeMirror-gutters {
+  background-color: or(--lx-gray-01, #073642);
+}
+
+.cm-s-lsradix.cm-s-dark .CodeMirror-linenumber {
+  color: or(--lx-gray-09, #586e75);
+  /* color: or(--lx-gray-03, #586e75); */
+  text-shadow: #021014 0 -1px;
+}
+
+/* Light */
+.cm-s-lsradix.cm-s-light .CodeMirror-gutters {
+  background-color: or(--lx-gray-03, #eee8d5);
+  /* background-color: or(--lx-gray-11, #eee8d5); */
+}
+
+.cm-s-lsradix.cm-s-light .CodeMirror-linenumber {
+  color: or(--lx-gray-09, #839496);
+}
+
+/* Common */
+.cm-s-lsradix .CodeMirror-linenumber {
+  padding: 0 5px;
+}
+.cm-s-lsradix .CodeMirror-guttermarker-subtle { color: or(--lx-gray-03, #586e75); }
+.cm-s-lsradix.cm-s-dark .CodeMirror-guttermarker { color: #ddd; }
+.cm-s-lsradix.cm-s-light .CodeMirror-guttermarker { color: or(--rx-orange-11, #cb4b16); }
+
+.cm-s-lsradix .CodeMirror-gutter .CodeMirror-gutter-text {
+  color: or(--lx-gray-03, #586e75);
+}
+
+/* Cursor */
+.cm-s-lsradix .CodeMirror-cursor { border-left: 1px solid #819090; }
+
+/* Fat cursor */
+.cm-s-lsradix.cm-s-light.cm-fat-cursor .CodeMirror-cursor { background: #77ee77; }
+.cm-s-lsradix.cm-s-light .cm-animate-fat-cursor { background-color: #77ee77; }
+.cm-s-lsradix.cm-s-dark.cm-fat-cursor .CodeMirror-cursor { background: or(--lx-gray-03, #586e75); }
+.cm-s-lsradix.cm-s-dark .cm-animate-fat-cursor { background-color: or(--lx-gray-03, #586e75); }
+
+/* Active line */
+.cm-s-lsradix.cm-s-dark .CodeMirror-activeline-background {
+  background: rgba(255, 255, 255, 0.06);
+}
+.cm-s-lsradix.cm-s-light .CodeMirror-activeline-background {
+  background: rgba(0, 0, 0, 0.06);
+}

+ 1379 - 0
resources/css/radix.css

@@ -0,0 +1,1379 @@
+:root {
+  --rx-amber-01: hsl(39, 70.0%, 99.0%);
+  --rx-amber-02: hsl(40, 100%, 96.5%);
+  --rx-amber-03: hsl(44, 100%, 91.7%);
+  --rx-amber-04: hsl(43, 100%, 86.8%);
+  --rx-amber-05: hsl(42, 100%, 81.8%);
+  --rx-amber-06: hsl(38, 99.7%, 76.3%);
+  --rx-amber-07: hsl(36, 86.1%, 67.1%);
+  --rx-amber-08: hsl(35, 85.2%, 55.1%);
+  --rx-amber-09: hsl(39, 100%, 57.0%);
+  --rx-amber-10: hsl(35, 100%, 55.5%);
+  --rx-amber-11: hsl(30, 100%, 34.0%);
+  --rx-amber-12: hsl(20, 80.0%, 17.0%);
+  --rx-blue-01: hsl(206, 100%, 99.2%);
+  --rx-blue-02: hsl(210, 100%, 98.0%);
+  --rx-blue-03: hsl(209, 100%, 96.5%);
+  --rx-blue-04: hsl(210, 98.8%, 94.0%);
+  --rx-blue-05: hsl(209, 95.0%, 90.1%);
+  --rx-blue-06: hsl(209, 81.2%, 84.5%);
+  --rx-blue-07: hsl(208, 77.5%, 76.9%);
+  --rx-blue-08: hsl(206, 81.9%, 65.3%);
+  --rx-blue-09: hsl(206, 100%, 50.0%);
+  --rx-blue-10: hsl(208, 100%, 47.3%);
+  --rx-blue-11: hsl(211, 100%, 43.2%);
+  --rx-blue-12: hsl(211, 100%, 15.0%);
+  --rx-bronze-01: hsl(15, 30.0%, 99.1%);
+  --rx-bronze-02: hsl(17, 63.6%, 97.8%);
+  --rx-bronze-03: hsl(17, 42.1%, 95.2%);
+  --rx-bronze-04: hsl(17, 35.2%, 92.1%);
+  --rx-bronze-05: hsl(17, 31.5%, 88.2%);
+  --rx-bronze-06: hsl(17, 29.0%, 83.0%);
+  --rx-bronze-07: hsl(17, 26.9%, 75.6%);
+  --rx-bronze-08: hsl(17, 25.1%, 66.5%);
+  --rx-bronze-09: hsl(17, 20.0%, 54.0%);
+  --rx-bronze-10: hsl(17, 18.1%, 50.1%);
+  --rx-bronze-11: hsl(15, 20.0%, 43.1%);
+  --rx-bronze-12: hsl(12, 22.0%, 21.5%);
+  --rx-brown-01: hsl(30, 40.0%, 99.1%);
+  --rx-brown-02: hsl(30, 50.0%, 97.6%);
+  --rx-brown-03: hsl(30, 52.5%, 94.6%);
+  --rx-brown-04: hsl(30, 53.0%, 91.2%);
+  --rx-brown-05: hsl(29, 52.9%, 86.8%);
+  --rx-brown-06: hsl(29, 52.5%, 80.9%);
+  --rx-brown-07: hsl(29, 51.5%, 72.8%);
+  --rx-brown-08: hsl(28, 50.0%, 63.1%);
+  --rx-brown-09: hsl(28, 34.0%, 51.0%);
+  --rx-brown-10: hsl(27, 31.8%, 47.6%);
+  --rx-brown-11: hsl(25, 30.0%, 41.0%);
+  --rx-brown-12: hsl(20, 30.0%, 19.0%);
+  --rx-crimson-01: hsl(332, 100%, 99.4%);
+  --rx-crimson-02: hsl(330, 100%, 98.4%);
+  --rx-crimson-03: hsl(331, 85.6%, 96.6%);
+  --rx-crimson-04: hsl(331, 78.1%, 94.2%);
+  --rx-crimson-05: hsl(332, 72.1%, 91.1%);
+  --rx-crimson-06: hsl(333, 67.0%, 86.7%);
+  --rx-crimson-07: hsl(335, 63.5%, 80.4%);
+  --rx-crimson-08: hsl(336, 62.3%, 72.9%);
+  --rx-crimson-09: hsl(336, 80.0%, 57.8%);
+  --rx-crimson-10: hsl(336, 73.7%, 53.5%);
+  --rx-crimson-11: hsl(336, 75.0%, 47.2%);
+  --rx-crimson-12: hsl(340, 65.0%, 14.5%);
+  --rx-cyan-01: hsl(185, 60.0%, 98.7%);
+  --rx-cyan-02: hsl(185, 73.3%, 97.1%);
+  --rx-cyan-03: hsl(186, 70.2%, 94.4%);
+  --rx-cyan-04: hsl(186, 63.8%, 90.6%);
+  --rx-cyan-05: hsl(187, 58.3%, 85.4%);
+  --rx-cyan-06: hsl(188, 54.6%, 78.4%);
+  --rx-cyan-07: hsl(189, 53.7%, 68.7%);
+  --rx-cyan-08: hsl(189, 60.3%, 52.5%);
+  --rx-cyan-09: hsl(190, 95.0%, 39.0%);
+  --rx-cyan-10: hsl(191, 91.2%, 36.8%);
+  --rx-cyan-11: hsl(192, 85.0%, 31.0%);
+  --rx-cyan-12: hsl(192, 88.0%, 12.5%);
+  --rx-gold-01: hsl(50, 20.0%, 99.1%);
+  --rx-gold-02: hsl(47, 52.9%, 96.7%);
+  --rx-gold-03: hsl(46, 38.2%, 93.7%);
+  --rx-gold-04: hsl(44, 32.7%, 90.1%);
+  --rx-gold-05: hsl(43, 29.9%, 85.7%);
+  --rx-gold-06: hsl(41, 28.3%, 79.8%);
+  --rx-gold-07: hsl(39, 27.6%, 71.9%);
+  --rx-gold-08: hsl(36, 27.2%, 61.8%);
+  --rx-gold-09: hsl(36, 20.0%, 49.5%);
+  --rx-gold-10: hsl(36, 19.8%, 45.7%);
+  --rx-gold-11: hsl(36, 20.0%, 39.0%);
+  --rx-gold-12: hsl(36, 16.0%, 20.0%);
+  --rx-grass-01: hsl(116, 50.0%, 98.9%);
+  --rx-grass-02: hsl(120, 60.0%, 97.1%);
+  --rx-grass-03: hsl(120, 53.6%, 94.8%);
+  --rx-grass-04: hsl(121, 47.5%, 91.4%);
+  --rx-grass-05: hsl(122, 42.6%, 86.5%);
+  --rx-grass-06: hsl(124, 39.0%, 79.7%);
+  --rx-grass-07: hsl(126, 37.1%, 70.2%);
+  --rx-grass-08: hsl(131, 38.1%, 56.3%);
+  --rx-grass-09: hsl(131, 41.0%, 46.5%);
+  --rx-grass-10: hsl(132, 43.1%, 42.2%);
+  --rx-grass-11: hsl(133, 50.0%, 32.5%);
+  --rx-grass-12: hsl(130, 30.0%, 14.9%);
+  --rx-gray-01: hsl(0, 0%, 99.0%);
+  --rx-gray-02: hsl(0, 0%, 97.3%);
+  --rx-gray-03: hsl(0, 0%, 95.1%);
+  --rx-gray-04: hsl(0, 0%, 93.0%);
+  --rx-gray-05: hsl(0, 0%, 90.9%);
+  --rx-gray-06: hsl(0, 0%, 88.7%);
+  --rx-gray-07: hsl(0, 0%, 85.8%);
+  --rx-gray-08: hsl(0, 0%, 78.0%);
+  --rx-gray-09: hsl(0, 0%, 56.1%);
+  --rx-gray-10: hsl(0, 0%, 52.3%);
+  --rx-gray-11: hsl(0, 0%, 43.5%);
+  --rx-gray-12: hsl(0, 0%, 9.0%);
+  --rx-green-01: hsl(136, 50.0%, 98.9%);
+  --rx-green-02: hsl(138, 62.5%, 96.9%);
+  --rx-green-03: hsl(139, 55.2%, 94.5%);
+  --rx-green-04: hsl(140, 48.7%, 91.0%);
+  --rx-green-05: hsl(141, 43.7%, 86.0%);
+  --rx-green-06: hsl(143, 40.3%, 79.0%);
+  --rx-green-07: hsl(146, 38.5%, 69.0%);
+  --rx-green-08: hsl(151, 40.2%, 54.1%);
+  --rx-green-09: hsl(151, 55.0%, 41.5%);
+  --rx-green-10: hsl(152, 57.5%, 37.6%);
+  --rx-green-11: hsl(153, 67.0%, 28.5%);
+  --rx-green-12: hsl(155, 40.0%, 14.0%);
+  --rx-indigo-01: hsl(225, 60.0%, 99.4%);
+  --rx-indigo-02: hsl(223, 100%, 98.6%);
+  --rx-indigo-03: hsl(223, 98.4%, 97.1%);
+  --rx-indigo-04: hsl(223, 92.9%, 95.0%);
+  --rx-indigo-05: hsl(224, 87.1%, 92.0%);
+  --rx-indigo-06: hsl(224, 81.9%, 87.8%);
+  --rx-indigo-07: hsl(225, 77.4%, 82.1%);
+  --rx-indigo-08: hsl(226, 75.4%, 74.5%);
+  --rx-indigo-09: hsl(226, 70.0%, 55.5%);
+  --rx-indigo-10: hsl(226, 58.6%, 51.3%);
+  --rx-indigo-11: hsl(226, 55.0%, 45.0%);
+  --rx-indigo-12: hsl(226, 62.0%, 17.0%);
+  --rx-lime-01: hsl(85, 50.0%, 98.7%);
+  --rx-lime-02: hsl(85, 66.7%, 96.5%);
+  --rx-lime-03: hsl(85, 76.0%, 92.3%);
+  --rx-lime-04: hsl(84, 75.3%, 87.5%);
+  --rx-lime-05: hsl(84, 71.5%, 81.9%);
+  --rx-lime-06: hsl(82, 65.0%, 74.6%);
+  --rx-lime-07: hsl(79, 53.2%, 61.8%);
+  --rx-lime-08: hsl(76, 61.7%, 45.1%);
+  --rx-lime-09: hsl(81, 67.0%, 50.0%);
+  --rx-lime-10: hsl(80, 68.3%, 46.9%);
+  --rx-lime-11: hsl(75, 80.0%, 26.0%);
+  --rx-lime-12: hsl(78, 70.0%, 11.5%);
+  --rx-mauve-01: hsl(300, 20.0%, 99.0%);
+  --rx-mauve-02: hsl(300, 7.7%, 97.5%);
+  --rx-mauve-03: hsl(294, 5.5%, 95.3%);
+  --rx-mauve-04: hsl(289, 4.7%, 93.3%);
+  --rx-mauve-05: hsl(283, 4.4%, 91.3%);
+  --rx-mauve-06: hsl(278, 4.1%, 89.1%);
+  --rx-mauve-07: hsl(271, 3.9%, 86.3%);
+  --rx-mauve-08: hsl(255, 3.7%, 78.8%);
+  --rx-mauve-09: hsl(252, 4.0%, 57.3%);
+  --rx-mauve-10: hsl(253, 3.5%, 53.5%);
+  --rx-mauve-11: hsl(252, 4.0%, 44.8%);
+  --rx-mauve-12: hsl(260, 25.0%, 11.0%);
+  --rx-mint-01: hsl(165, 80.0%, 98.8%);
+  --rx-mint-02: hsl(164, 88.2%, 96.7%);
+  --rx-mint-03: hsl(164, 76.6%, 93.3%);
+  --rx-mint-04: hsl(165, 68.8%, 89.5%);
+  --rx-mint-05: hsl(165, 60.6%, 84.5%);
+  --rx-mint-06: hsl(165, 53.5%, 76.9%);
+  --rx-mint-07: hsl(166, 50.7%, 66.1%);
+  --rx-mint-08: hsl(168, 52.8%, 51.0%);
+  --rx-mint-09: hsl(167, 65.0%, 66.0%);
+  --rx-mint-10: hsl(167, 59.3%, 63.1%);
+  --rx-mint-11: hsl(172, 72.0%, 28.5%);
+  --rx-mint-12: hsl(172, 70.0%, 12.0%);
+  --rx-olive-01: hsl(110, 20.0%, 99.0%);
+  --rx-olive-02: hsl(120, 16.7%, 97.6%);
+  --rx-olive-03: hsl(119, 10.1%, 95.2%);
+  --rx-olive-04: hsl(118, 8.1%, 93.0%);
+  --rx-olive-05: hsl(117, 7.1%, 90.8%);
+  --rx-olive-06: hsl(115, 6.4%, 88.5%);
+  --rx-olive-07: hsl(114, 5.9%, 85.4%);
+  --rx-olive-08: hsl(110, 5.2%, 77.3%);
+  --rx-olive-09: hsl(110, 3.5%, 55.5%);
+  --rx-olive-10: hsl(111, 2.8%, 51.7%);
+  --rx-olive-11: hsl(110, 3.0%, 43.0%);
+  --rx-olive-12: hsl(110, 25.0%, 9.5%);
+  --rx-orange-01: hsl(24, 70.0%, 99.0%);
+  --rx-orange-02: hsl(24, 83.3%, 97.6%);
+  --rx-orange-03: hsl(24, 100%, 95.3%);
+  --rx-orange-04: hsl(25, 100%, 92.2%);
+  --rx-orange-05: hsl(25, 100%, 88.2%);
+  --rx-orange-06: hsl(25, 100%, 82.8%);
+  --rx-orange-07: hsl(24, 100%, 75.3%);
+  --rx-orange-08: hsl(24, 94.5%, 64.3%);
+  --rx-orange-09: hsl(24, 94.0%, 50.0%);
+  --rx-orange-10: hsl(24, 100%, 46.5%);
+  --rx-orange-11: hsl(24, 100%, 37.0%);
+  --rx-orange-12: hsl(15, 60.0%, 17.0%);
+  --rx-pink-01: hsl(322, 100%, 99.4%);
+  --rx-pink-02: hsl(323, 100%, 98.4%);
+  --rx-pink-03: hsl(323, 86.3%, 96.5%);
+  --rx-pink-04: hsl(323, 78.7%, 94.2%);
+  --rx-pink-05: hsl(323, 72.2%, 91.1%);
+  --rx-pink-06: hsl(323, 66.3%, 86.6%);
+  --rx-pink-07: hsl(323, 62.0%, 80.1%);
+  --rx-pink-08: hsl(323, 60.3%, 72.4%);
+  --rx-pink-09: hsl(322, 65.0%, 54.5%);
+  --rx-pink-10: hsl(322, 63.9%, 50.7%);
+  --rx-pink-11: hsl(322, 75.0%, 46.0%);
+  --rx-pink-12: hsl(320, 70.0%, 13.5%);
+  --rx-plum-01: hsl(292, 90.0%, 99.4%);
+  --rx-plum-02: hsl(300, 100%, 98.6%);
+  --rx-plum-03: hsl(299, 71.2%, 96.4%);
+  --rx-plum-04: hsl(299, 62.0%, 93.8%);
+  --rx-plum-05: hsl(298, 56.1%, 90.5%);
+  --rx-plum-06: hsl(296, 51.3%, 85.8%);
+  --rx-plum-07: hsl(295, 48.2%, 78.9%);
+  --rx-plum-08: hsl(292, 47.7%, 70.8%);
+  --rx-plum-09: hsl(292, 45.0%, 51.0%);
+  --rx-plum-10: hsl(292, 50.2%, 46.9%);
+  --rx-plum-11: hsl(292, 60.0%, 42.5%);
+  --rx-plum-12: hsl(291, 66.0%, 14.0%);
+  --rx-purple-01: hsl(280, 65.0%, 99.4%);
+  --rx-purple-02: hsl(276, 100%, 99.0%);
+  --rx-purple-03: hsl(276, 83.1%, 97.0%);
+  --rx-purple-04: hsl(275, 76.4%, 94.7%);
+  --rx-purple-05: hsl(275, 70.8%, 91.8%);
+  --rx-purple-06: hsl(274, 65.4%, 87.8%);
+  --rx-purple-07: hsl(273, 61.0%, 81.7%);
+  --rx-purple-08: hsl(272, 60.0%, 73.5%);
+  --rx-purple-09: hsl(272, 51.0%, 54.0%);
+  --rx-purple-10: hsl(272, 46.8%, 50.3%);
+  --rx-purple-11: hsl(272, 50.0%, 45.8%);
+  --rx-purple-12: hsl(272, 66.0%, 16.0%);
+  --rx-red-01: hsl(359, 100%, 99.4%);
+  --rx-red-02: hsl(359, 100%, 98.6%);
+  --rx-red-03: hsl(360, 100%, 96.8%);
+  --rx-red-04: hsl(360, 97.9%, 94.8%);
+  --rx-red-05: hsl(360, 90.2%, 91.9%);
+  --rx-red-06: hsl(360, 81.7%, 87.8%);
+  --rx-red-07: hsl(359, 74.2%, 81.7%);
+  --rx-red-08: hsl(359, 69.5%, 74.3%);
+  --rx-red-09: hsl(358, 75.0%, 59.0%);
+  --rx-red-10: hsl(358, 69.4%, 55.2%);
+  --rx-red-11: hsl(358, 65.0%, 48.7%);
+  --rx-red-12: hsl(354, 50.0%, 14.6%);
+  --rx-sage-01: hsl(155, 30.0%, 98.8%);
+  --rx-sage-02: hsl(150, 16.7%, 97.6%);
+  --rx-sage-03: hsl(151, 10.6%, 95.2%);
+  --rx-sage-04: hsl(151, 8.8%, 93.0%);
+  --rx-sage-05: hsl(151, 7.8%, 90.8%);
+  --rx-sage-06: hsl(152, 7.2%, 88.4%);
+  --rx-sage-07: hsl(153, 6.7%, 85.3%);
+  --rx-sage-08: hsl(154, 6.1%, 77.5%);
+  --rx-sage-09: hsl(155, 3.5%, 55.5%);
+  --rx-sage-10: hsl(154, 2.8%, 51.7%);
+  --rx-sage-11: hsl(155, 3.0%, 43.0%);
+  --rx-sage-12: hsl(155, 24.0%, 9.0%);
+  --rx-sand-01: hsl(50, 20.0%, 99.0%);
+  --rx-sand-02: hsl(60, 7.7%, 97.5%);
+  --rx-sand-03: hsl(59, 6.5%, 95.1%);
+  --rx-sand-04: hsl(58, 6.1%, 92.9%);
+  --rx-sand-05: hsl(57, 6.0%, 90.7%);
+  --rx-sand-06: hsl(56, 5.9%, 88.4%);
+  --rx-sand-07: hsl(55, 5.9%, 85.2%);
+  --rx-sand-08: hsl(51, 6.0%, 77.1%);
+  --rx-sand-09: hsl(50, 2.0%, 55.7%);
+  --rx-sand-10: hsl(55, 1.7%, 51.9%);
+  --rx-sand-11: hsl(50, 2.0%, 43.1%);
+  --rx-sand-12: hsl(50, 6.0%, 10.0%);
+  --rx-sky-01: hsl(193, 100%, 98.8%);
+  --rx-sky-02: hsl(193, 100%, 97.3%);
+  --rx-sky-03: hsl(193, 99.0%, 94.7%);
+  --rx-sky-04: hsl(193, 91.4%, 91.4%);
+  --rx-sky-05: hsl(194, 82.0%, 86.6%);
+  --rx-sky-06: hsl(194, 74.1%, 79.5%);
+  --rx-sky-07: hsl(194, 72.3%, 69.6%);
+  --rx-sky-08: hsl(193, 77.9%, 53.9%);
+  --rx-sky-09: hsl(193, 98.0%, 70.0%);
+  --rx-sky-10: hsl(193, 87.0%, 66.5%);
+  --rx-sky-11: hsl(195, 100%, 31.5%);
+  --rx-sky-12: hsl(195, 100%, 13.0%);
+  --rx-slate-01: hsl(206, 30.0%, 98.8%);
+  --rx-slate-02: hsl(210, 16.7%, 97.6%);
+  --rx-slate-03: hsl(209, 13.3%, 95.3%);
+  --rx-slate-04: hsl(209, 12.2%, 93.2%);
+  --rx-slate-05: hsl(208, 11.7%, 91.1%);
+  --rx-slate-06: hsl(208, 11.3%, 88.9%);
+  --rx-slate-07: hsl(207, 11.1%, 85.9%);
+  --rx-slate-08: hsl(205, 10.7%, 78.0%);
+  --rx-slate-09: hsl(206, 6.0%, 56.1%);
+  --rx-slate-10: hsl(206, 5.8%, 52.3%);
+  --rx-slate-11: hsl(206, 6.0%, 43.5%);
+  --rx-slate-12: hsl(206, 24.0%, 9.0%);
+  --rx-teal-01: hsl(165, 60.0%, 98.8%);
+  --rx-teal-02: hsl(169, 64.7%, 96.7%);
+  --rx-teal-03: hsl(169, 59.8%, 94.0%);
+  --rx-teal-04: hsl(169, 53.1%, 90.2%);
+  --rx-teal-05: hsl(170, 47.1%, 85.0%);
+  --rx-teal-06: hsl(170, 42.6%, 77.9%);
+  --rx-teal-07: hsl(170, 39.9%, 68.1%);
+  --rx-teal-08: hsl(172, 42.1%, 52.5%);
+  --rx-teal-09: hsl(173, 80.0%, 36.0%);
+  --rx-teal-10: hsl(173, 83.4%, 32.5%);
+  --rx-teal-11: hsl(174, 90.0%, 25.2%);
+  --rx-teal-12: hsl(170, 50.0%, 12.5%);
+  --rx-tomato-01: hsl(10, 100%, 99.4%);
+  --rx-tomato-02: hsl(8, 100%, 98.4%);
+  --rx-tomato-03: hsl(8, 100%, 96.6%);
+  --rx-tomato-04: hsl(8, 100%, 94.3%);
+  --rx-tomato-05: hsl(8, 92.8%, 91.0%);
+  --rx-tomato-06: hsl(9, 84.7%, 86.3%);
+  --rx-tomato-07: hsl(10, 77.3%, 79.5%);
+  --rx-tomato-08: hsl(10, 71.6%, 71.0%);
+  --rx-tomato-09: hsl(10, 78.0%, 54.0%);
+  --rx-tomato-10: hsl(10, 71.5%, 50.0%);
+  --rx-tomato-11: hsl(10, 82.0%, 43.5%);
+  --rx-tomato-12: hsl(10, 50.0%, 13.5%);
+  --rx-violet-01: hsl(255, 65.0%, 99.4%);
+  --rx-violet-02: hsl(252, 100%, 99.0%);
+  --rx-violet-03: hsl(252, 96.9%, 97.4%);
+  --rx-violet-04: hsl(252, 91.5%, 95.5%);
+  --rx-violet-05: hsl(252, 85.1%, 93.0%);
+  --rx-violet-06: hsl(252, 77.8%, 89.4%);
+  --rx-violet-07: hsl(252, 71.0%, 83.7%);
+  --rx-violet-08: hsl(252, 68.6%, 76.3%);
+  --rx-violet-09: hsl(252, 56.0%, 57.5%);
+  --rx-violet-10: hsl(251, 48.1%, 53.5%);
+  --rx-violet-11: hsl(250, 43.0%, 48.0%);
+  --rx-violet-12: hsl(254, 60.0%, 18.5%);
+  --rx-yellow-01: hsl(60, 54.0%, 98.5%);
+  --rx-yellow-02: hsl(52, 100%, 95.5%);
+  --rx-yellow-03: hsl(55, 100%, 90.9%);
+  --rx-yellow-04: hsl(54, 100%, 86.6%);
+  --rx-yellow-05: hsl(52, 97.9%, 82.0%);
+  --rx-yellow-06: hsl(50, 89.4%, 76.1%);
+  --rx-yellow-07: hsl(47, 80.4%, 68.0%);
+  --rx-yellow-08: hsl(48, 100%, 46.1%);
+  --rx-yellow-09: hsl(53, 92.0%, 50.0%);
+  --rx-yellow-10: hsl(50, 100%, 48.5%);
+  --rx-yellow-11: hsl(42, 100%, 29.0%);
+  --rx-yellow-12: hsl(40, 55.0%, 13.5%);
+} 
+
+:root {
+  --rx-amber-01-alpha: hsla(40, 94.9%, 38.7%, 0.016);
+  --rx-amber-02-alpha: hsla(40, 100%, 50.3%, 0.071);
+  --rx-amber-03-alpha: hsla(44, 100%, 50.1%, 0.165);
+  --rx-amber-04-alpha: hsla(43, 100%, 50.0%, 0.263);
+  --rx-amber-05-alpha: hsla(42, 100%, 50.0%, 0.365);
+  --rx-amber-06-alpha: hsla(38, 100%, 50.1%, 0.475);
+  --rx-amber-07-alpha: hsla(36, 99.9%, 46.2%, 0.612);
+  --rx-amber-08-alpha: hsla(35, 99.8%, 46.0%, 0.832);
+  --rx-amber-09-alpha: hsla(39, 100%, 50.0%, 0.859);
+  --rx-amber-10-alpha: hsla(35, 100%, 50.0%, 0.891);
+  --rx-amber-11-alpha: hsla(29, 100%, 33.6%, 0.980);
+  --rx-amber-12-alpha: hsla(20, 99.8%, 14.1%, 0.965);
+  --rx-black-01-alpha: hsla(0, 0%, 0%, 0.012);
+  --rx-black-02-alpha: hsla(0, 0%, 0%, 0.027);
+  --rx-black-03-alpha: hsla(0, 0%, 0%, 0.047);
+  --rx-black-04-alpha: hsla(0, 0%, 0%, 0.071);
+  --rx-black-05-alpha: hsla(0, 0%, 0%, 0.090);
+  --rx-black-06-alpha: hsla(0, 0%, 0%, 0.114);
+  --rx-black-07-alpha: hsla(0, 0%, 0%, 0.141);
+  --rx-black-08-alpha: hsla(0, 0%, 0%, 0.220);
+  --rx-black-09-alpha: hsla(0, 0%, 0%, 0.439);
+  --rx-black-10-alpha: hsla(0, 0%, 0%, 0.478);
+  --rx-black-11-alpha: hsla(0, 0%, 0%, 0.565);
+  --rx-black-12-alpha: hsla(0, 0%, 0%, 0.910);
+  --rx-blue-01-alpha: hsla(210, 100%, 51.0%, 0.016);
+  --rx-blue-02-alpha: hsla(210, 100%, 51.0%, 0.040);
+  --rx-blue-03-alpha: hsla(210, 100%, 50.3%, 0.071);
+  --rx-blue-04-alpha: hsla(210, 100%, 50.1%, 0.118);
+  --rx-blue-05-alpha: hsla(208, 99.1%, 47.1%, 0.189);
+  --rx-blue-06-alpha: hsla(209, 99.5%, 45.3%, 0.283);
+  --rx-blue-07-alpha: hsla(208, 99.9%, 43.8%, 0.412);
+  --rx-blue-08-alpha: hsla(206, 99.8%, 45.1%, 0.632);
+  --rx-blue-09-alpha: hsla(206, 100%, 50.0%, 0.980);
+  --rx-blue-10-alpha: hsla(208, 100%, 47.2%, 0.980);
+  --rx-blue-11-alpha: hsla(212, 100%, 43.0%, 0.980);
+  --rx-blue-12-alpha: hsla(213, 100%, 14.4%, 0.980);
+  --rx-bronze-01-alpha: hsla(0, 89.3%, 18.3%, 0.012);
+  --rx-bronze-02-alpha: hsla(17, 95.1%, 40.1%, 0.036);
+  --rx-bronze-03-alpha: hsla(18, 98.3%, 29.8%, 0.067);
+  --rx-bronze-04-alpha: hsla(17, 99.6%, 26.0%, 0.106);
+  --rx-bronze-05-alpha: hsla(19, 99.6%, 23.8%, 0.157);
+  --rx-bronze-06-alpha: hsla(17, 99.2%, 22.5%, 0.220);
+  --rx-bronze-07-alpha: hsla(18, 99.7%, 21.6%, 0.310);
+  --rx-bronze-08-alpha: hsla(17, 99.5%, 20.2%, 0.420);
+  --rx-bronze-09-alpha: hsla(18, 99.9%, 16.7%, 0.553);
+  --rx-bronze-10-alpha: hsla(17, 99.2%, 15.4%, 0.589);
+  --rx-bronze-11-alpha: hsla(15, 99.9%, 13.2%, 0.655);
+  --rx-bronze-12-alpha: hsla(12, 98.7%, 5.7%, 0.832);
+  --rx-brown-01-alpha: hsla(30, 94.3%, 34.6%, 0.012);
+  --rx-brown-02-alpha: hsla(30, 94.3%, 34.6%, 0.036);
+  --rx-brown-03-alpha: hsla(30, 97.7%, 33.9%, 0.083);
+  --rx-brown-04-alpha: hsla(31, 98.5%, 34.2%, 0.134);
+  --rx-brown-05-alpha: hsla(29, 100%, 34.3%, 0.200);
+  --rx-brown-06-alpha: hsla(28, 99.2%, 34.6%, 0.291);
+  --rx-brown-07-alpha: hsla(29, 99.8%, 33.8%, 0.412);
+  --rx-brown-08-alpha: hsla(28, 100%, 33.3%, 0.553);
+  --rx-brown-09-alpha: hsla(28, 99.9%, 25.5%, 0.655);
+  --rx-brown-10-alpha: hsla(27, 99.7%, 22.4%, 0.675);
+  --rx-brown-11-alpha: hsla(25, 99.8%, 17.3%, 0.714);
+  --rx-brown-12-alpha: hsla(21, 99.4%, 6.6%, 0.867);
+  --rx-crimson-01-alpha: hsla(340, 100%, 51.0%, 0.012);
+  --rx-crimson-02-alpha: hsla(330, 100%, 51.0%, 0.032);
+  --rx-crimson-03-alpha: hsla(332, 99.1%, 47.1%, 0.063);
+  --rx-crimson-04-alpha: hsla(331, 99.9%, 44.3%, 0.102);
+  --rx-crimson-05-alpha: hsla(333, 99.9%, 42.3%, 0.153);
+  --rx-crimson-06-alpha: hsla(333, 99.5%, 40.5%, 0.224);
+  --rx-crimson-07-alpha: hsla(335, 99.7%, 39.1%, 0.322);
+  --rx-crimson-08-alpha: hsla(336, 99.5%, 38.5%, 0.440);
+  --rx-crimson-09-alpha: hsla(336, 99.9%, 44.3%, 0.761);
+  --rx-crimson-10-alpha: hsla(336, 100%, 42.5%, 0.808);
+  --rx-crimson-11-alpha: hsla(336, 99.8%, 40.3%, 0.883);
+  --rx-crimson-12-alpha: hsla(340, 99.0%, 10.0%, 0.950);
+  --rx-cyan-01-alpha: hsla(195, 95.2%, 41.2%, 0.020);
+  --rx-cyan-02-alpha: hsla(185, 99.9%, 42.3%, 0.051);
+  --rx-cyan-03-alpha: hsla(186, 97.8%, 42.2%, 0.095);
+  --rx-cyan-04-alpha: hsla(186, 99.9%, 38.5%, 0.153);
+  --rx-cyan-05-alpha: hsla(187, 99.3%, 36.6%, 0.232);
+  --rx-cyan-06-alpha: hsla(188, 99.4%, 35.4%, 0.334);
+  --rx-cyan-07-alpha: hsla(189, 99.6%, 35.0%, 0.483);
+  --rx-cyan-08-alpha: hsla(189, 99.9%, 37.6%, 0.761);
+  --rx-cyan-09-alpha: hsla(190, 100%, 37.8%, 0.980);
+  --rx-cyan-10-alpha: hsla(191, 99.9%, 34.6%, 0.969);
+  --rx-cyan-11-alpha: hsla(192, 100%, 27.6%, 0.953);
+  --rx-cyan-12-alpha: hsla(192, 100%, 11.0%, 0.980);
+  --rx-gold-01-alpha: hsla(60, 89.3%, 18.3%, 0.012);
+  --rx-gold-02-alpha: hsla(47, 99.9%, 34.6%, 0.051);
+  --rx-gold-03-alpha: hsla(45, 97.0%, 27.9%, 0.087);
+  --rx-gold-04-alpha: hsla(46, 98.0%, 25.4%, 0.134);
+  --rx-gold-05-alpha: hsla(43, 98.4%, 22.6%, 0.185);
+  --rx-gold-06-alpha: hsla(41, 99.7%, 22.0%, 0.259);
+  --rx-gold-07-alpha: hsla(38, 99.8%, 21.5%, 0.357);
+  --rx-gold-08-alpha: hsla(36, 99.3%, 21.5%, 0.487);
+  --rx-gold-09-alpha: hsla(36, 99.9%, 16.2%, 0.604);
+  --rx-gold-10-alpha: hsla(36, 99.2%, 14.6%, 0.636);
+  --rx-gold-11-alpha: hsla(35, 99.1%, 11.2%, 0.687);
+  --rx-gold-12-alpha: hsla(38, 98.0%, 3.8%, 0.832);
+  --rx-grass-01-alpha: hsla(120, 94.9%, 38.7%, 0.016);
+  --rx-grass-02-alpha: hsla(120, 94.9%, 38.7%, 0.048);
+  --rx-grass-03-alpha: hsla(120, 98.0%, 35.5%, 0.079);
+  --rx-grass-04-alpha: hsla(120, 98.7%, 31.5%, 0.126);
+  --rx-grass-05-alpha: hsla(122, 98.5%, 29.9%, 0.193);
+  --rx-grass-06-alpha: hsla(125, 99.2%, 27.9%, 0.283);
+  --rx-grass-07-alpha: hsla(125, 99.9%, 27.0%, 0.408);
+  --rx-grass-08-alpha: hsla(131, 100%, 27.6%, 0.604);
+  --rx-grass-09-alpha: hsla(131, 99.7%, 26.3%, 0.726);
+  --rx-grass-10-alpha: hsla(132, 99.9%, 24.0%, 0.761);
+  --rx-grass-11-alpha: hsla(133, 99.5%, 19.5%, 0.840);
+  --rx-grass-12-alpha: hsla(128, 98.0%, 4.9%, 0.895);
+  --rx-gray-01-alpha: hsla(0, 0%, 0%, 0.012);
+  --rx-gray-02-alpha: hsla(0, 0%, 0%, 0.027);
+  --rx-gray-03-alpha: hsla(0, 0%, 0%, 0.047);
+  --rx-gray-04-alpha: hsla(0, 0%, 0%, 0.071);
+  --rx-gray-05-alpha: hsla(0, 0%, 0%, 0.090);
+  --rx-gray-06-alpha: hsla(0, 0%, 0%, 0.114);
+  --rx-gray-07-alpha: hsla(0, 0%, 0%, 0.141);
+  --rx-gray-08-alpha: hsla(0, 0%, 0%, 0.220);
+  --rx-gray-09-alpha: hsla(0, 0%, 0%, 0.439);
+  --rx-gray-10-alpha: hsla(0, 0%, 0%, 0.478);
+  --rx-gray-11-alpha: hsla(0, 0%, 0%, 0.565);
+  --rx-gray-12-alpha: hsla(0, 0%, 0%, 0.910);
+  --rx-green-01-alpha: hsla(140, 94.9%, 38.7%, 0.016);
+  --rx-green-02-alpha: hsla(138, 99.9%, 38.5%, 0.051);
+  --rx-green-03-alpha: hsla(139, 97.7%, 36.9%, 0.087);
+  --rx-green-04-alpha: hsla(139, 98.5%, 32.7%, 0.134);
+  --rx-green-05-alpha: hsla(141, 100%, 30.4%, 0.200);
+  --rx-green-06-alpha: hsla(142, 99.0%, 28.9%, 0.295);
+  --rx-green-07-alpha: hsla(146, 99.5%, 27.6%, 0.428);
+  --rx-green-08-alpha: hsla(151, 99.5%, 28.8%, 0.644);
+  --rx-green-09-alpha: hsla(151, 99.9%, 28.0%, 0.812);
+  --rx-green-10-alpha: hsla(152, 99.6%, 25.8%, 0.840);
+  --rx-green-11-alpha: hsla(153, 99.9%, 21.0%, 0.906);
+  --rx-green-12-alpha: hsla(155, 99.4%, 6.2%, 0.918);
+  --rx-indigo-01-alpha: hsla(240, 92.6%, 26.5%, 0.008);
+  --rx-indigo-02-alpha: hsla(223, 100%, 51.0%, 0.028);
+  --rx-indigo-03-alpha: hsla(224, 100%, 50.1%, 0.059);
+  --rx-indigo-04-alpha: hsla(223, 98.0%, 48.5%, 0.099);
+  --rx-indigo-05-alpha: hsla(225, 98.6%, 46.4%, 0.150);
+  --rx-indigo-06-alpha: hsla(224, 99.5%, 44.9%, 0.224);
+  --rx-indigo-07-alpha: hsla(225, 99.7%, 43.9%, 0.318);
+  --rx-indigo-08-alpha: hsla(226, 99.5%, 43.1%, 0.448);
+  --rx-indigo-09-alpha: hsla(226, 100%, 41.2%, 0.757);
+  --rx-indigo-10-alpha: hsla(226, 99.8%, 37.1%, 0.773);
+  --rx-indigo-11-alpha: hsla(226, 99.6%, 31.1%, 0.797);
+  --rx-indigo-12-alpha: hsla(226, 99.3%, 11.4%, 0.938);
+  --rx-lime-01-alpha: hsla(80, 93.8%, 31.4%, 0.020);
+  --rx-lime-02-alpha: hsla(85, 99.3%, 40.2%, 0.059);
+  --rx-lime-03-alpha: hsla(84, 98.7%, 43.2%, 0.138);
+  --rx-lime-04-alpha: hsla(84, 99.6%, 43.0%, 0.220);
+  --rx-lime-05-alpha: hsla(85, 99.8%, 41.8%, 0.310);
+  --rx-lime-06-alpha: hsla(82, 99.8%, 39.3%, 0.420);
+  --rx-lime-07-alpha: hsla(79, 99.7%, 34.6%, 0.585);
+  --rx-lime-08-alpha: hsla(76, 99.8%, 33.7%, 0.828);
+  --rx-lime-09-alpha: hsla(81, 99.8%, 40.2%, 0.836);
+  --rx-lime-10-alpha: hsla(80, 100%, 37.6%, 0.851);
+  --rx-lime-11-alpha: hsla(75, 99.5%, 22.0%, 0.950);
+  --rx-lime-12-alpha: hsla(78, 99.6%, 8.4%, 0.965);
+  --rx-mauve-01-alpha: hsla(300, 89.3%, 18.3%, 0.012);
+  --rx-mauve-02-alpha: hsla(300, 78.1%, 9.0%, 0.028);
+  --rx-mauve-03-alpha: hsla(300, 99.5%, 7.7%, 0.051);
+  --rx-mauve-04-alpha: hsla(270, 90.5%, 6.1%, 0.071);
+  --rx-mauve-05-alpha: hsla(270, 83.0%, 5.2%, 0.091);
+  --rx-mauve-06-alpha: hsla(300, 93.5%, 3.7%, 0.114);
+  --rx-mauve-07-alpha: hsla(270, 82.6%, 3.3%, 0.142);
+  --rx-mauve-08-alpha: hsla(255, 95.2%, 3.7%, 0.220);
+  --rx-mauve-09-alpha: hsla(255, 94.8%, 3.7%, 0.444);
+  --rx-mauve-10-alpha: hsla(253, 96.5%, 3.8%, 0.483);
+  --rx-mauve-11-alpha: hsla(247, 97.9%, 3.2%, 0.569);
+  --rx-mauve-12-alpha: hsla(261, 98.7%, 3.0%, 0.918);
+  --rx-mint-01-alpha: hsla(168, 95.4%, 42.8%, 0.024);
+  --rx-mint-02-alpha: hsla(164, 99.1%, 47.1%, 0.063);
+  --rx-mint-03-alpha: hsla(164, 99.3%, 43.5%, 0.118);
+  --rx-mint-04-alpha: hsla(164, 99.3%, 41.3%, 0.177);
+  --rx-mint-05-alpha: hsla(165, 99.0%, 37.5%, 0.248);
+  --rx-mint-06-alpha: hsla(165, 100%, 35.0%, 0.353);
+  --rx-mint-07-alpha: hsla(166, 99.9%, 33.5%, 0.510);
+  --rx-mint-08-alpha: hsla(168, 99.6%, 34.6%, 0.750);
+  --rx-mint-09-alpha: hsla(167, 99.9%, 39.5%, 0.561);
+  --rx-mint-10-alpha: hsla(167, 99.7%, 37.4%, 0.589);
+  --rx-mint-11-alpha: hsla(172, 99.8%, 22.4%, 0.922);
+  --rx-mint-12-alpha: hsla(172, 99.7%, 8.8%, 0.965);
+  --rx-olive-01-alpha: hsla(120, 89.3%, 18.3%, 0.012);
+  --rx-olive-02-alpha: hsla(120, 87.7%, 16.0%, 0.028);
+  --rx-olive-03-alpha: hsla(120, 99.5%, 7.7%, 0.051);
+  --rx-olive-04-alpha: hsla(120, 92.3%, 8.5%, 0.075);
+  --rx-olive-05-alpha: hsla(120, 86.0%, 6.9%, 0.099);
+  --rx-olive-06-alpha: hsla(120, 94.8%, 6.8%, 0.122);
+  --rx-olive-07-alpha: hsla(120, 99.3%, 5.2%, 0.153);
+  --rx-olive-08-alpha: hsla(110, 93.8%, 5.2%, 0.240);
+  --rx-olive-09-alpha: hsla(111, 98.7%, 3.0%, 0.459);
+  --rx-olive-10-alpha: hsla(111, 93.5%, 2.9%, 0.499);
+  --rx-olive-11-alpha: hsla(111, 95.2%, 2.5%, 0.585);
+  --rx-olive-12-alpha: hsla(110, 97.6%, 2.6%, 0.930);
+  --rx-orange-01-alpha: hsla(20, 94.9%, 38.7%, 0.016);
+  --rx-orange-02-alpha: hsla(24, 95.8%, 46.5%, 0.044);
+  --rx-orange-03-alpha: hsla(25, 100%, 50.5%, 0.095);
+  --rx-orange-04-alpha: hsla(26, 100%, 50.0%, 0.157);
+  --rx-orange-05-alpha: hsla(25, 100%, 50.1%, 0.236);
+  --rx-orange-06-alpha: hsla(25, 100%, 50.1%, 0.346);
+  --rx-orange-07-alpha: hsla(24, 100%, 50.1%, 0.495);
+  --rx-orange-08-alpha: hsla(24, 99.7%, 48.7%, 0.695);
+  --rx-orange-09-alpha: hsla(24, 99.9%, 48.4%, 0.969);
+  --rx-orange-10-alpha: hsla(23, 100%, 46.4%, 0.980);
+  --rx-orange-11-alpha: hsla(23, 100%, 36.8%, 0.980);
+  --rx-orange-12-alpha: hsla(15, 99.4%, 11.0%, 0.934);
+  --rx-pink-01-alpha: hsla(320, 100%, 51.0%, 0.012);
+  --rx-pink-02-alpha: hsla(323, 100%, 51.0%, 0.032);
+  --rx-pink-03-alpha: hsla(323, 98.9%, 47.3%, 0.067);
+  --rx-pink-04-alpha: hsla(323, 99.9%, 44.3%, 0.102);
+  --rx-pink-05-alpha: hsla(324, 99.9%, 42.3%, 0.153);
+  --rx-pink-06-alpha: hsla(323, 99.5%, 39.6%, 0.224);
+  --rx-pink-07-alpha: hsla(323, 99.7%, 38.5%, 0.322);
+  --rx-pink-08-alpha: hsla(323, 99.5%, 37.7%, 0.444);
+  --rx-pink-09-alpha: hsla(322, 99.7%, 39.3%, 0.750);
+  --rx-pink-10-alpha: hsla(322, 100%, 39.1%, 0.808);
+  --rx-pink-11-alpha: hsla(322, 99.8%, 39.0%, 0.887);
+  --rx-pink-12-alpha: hsla(321, 99.8%, 10.0%, 0.961);
+  --rx-plum-01-alpha: hsla(280, 100%, 51.0%, 0.012);
+  --rx-plum-02-alpha: hsla(300, 100%, 51.0%, 0.028);
+  --rx-plum-03-alpha: hsla(300, 99.0%, 40.9%, 0.063);
+  --rx-plum-04-alpha: hsla(300, 99.9%, 38.5%, 0.102);
+  --rx-plum-05-alpha: hsla(298, 98.2%, 35.9%, 0.150);
+  --rx-plum-06-alpha: hsla(297, 99.6%, 33.7%, 0.216);
+  --rx-plum-07-alpha: hsla(295, 99.7%, 32.6%, 0.314);
+  --rx-plum-08-alpha: hsla(292, 99.6%, 32.4%, 0.432);
+  --rx-plum-09-alpha: hsla(292, 99.9%, 31.0%, 0.710);
+  --rx-plum-10-alpha: hsla(292, 99.9%, 30.8%, 0.765);
+  --rx-plum-11-alpha: hsla(292, 99.8%, 30.7%, 0.832);
+  --rx-plum-12-alpha: hsla(291, 99.9%, 9.7%, 0.953);
+  --rx-purple-01-alpha: hsla(300, 94.3%, 34.6%, 0.012);
+  --rx-purple-02-alpha: hsla(276, 100%, 51.0%, 0.020);
+  --rx-purple-03-alpha: hsla(277, 99.6%, 46.5%, 0.055);
+  --rx-purple-04-alpha: hsla(274, 97.9%, 44.3%, 0.095);
+  --rx-purple-05-alpha: hsla(276, 98.6%, 42.0%, 0.142);
+  --rx-purple-06-alpha: hsla(275, 100%, 39.2%, 0.200);
+  --rx-purple-07-alpha: hsla(273, 99.2%, 38.2%, 0.295);
+  --rx-purple-08-alpha: hsla(272, 99.7%, 37.6%, 0.424);
+  --rx-purple-09-alpha: hsla(272, 99.6%, 34.0%, 0.695);
+  --rx-purple-10-alpha: hsla(272, 99.7%, 32.0%, 0.730);
+  --rx-purple-11-alpha: hsla(272, 99.8%, 29.7%, 0.773);
+  --rx-purple-12-alpha: hsla(272, 99.2%, 11.3%, 0.946);
+  --rx-red-01-alpha: hsla(0, 100%, 51.0%, 0.012);
+  --rx-red-02-alpha: hsla(0, 100%, 51.0%, 0.032);
+  --rx-red-03-alpha: hsla(0, 100%, 50.2%, 0.063);
+  --rx-red-04-alpha: hsla(0, 100%, 50.0%, 0.102);
+  --rx-red-05-alpha: hsla(0, 99.9%, 47.5%, 0.153);
+  --rx-red-06-alpha: hsla(0, 99.5%, 44.9%, 0.224);
+  --rx-red-07-alpha: hsla(359, 99.7%, 42.7%, 0.318);
+  --rx-red-08-alpha: hsla(359, 99.6%, 41.1%, 0.436);
+  --rx-red-09-alpha: hsla(358, 99.9%, 42.9%, 0.718);
+  --rx-red-10-alpha: hsla(358, 99.9%, 41.0%, 0.761);
+  --rx-red-11-alpha: hsla(358, 99.8%, 38.3%, 0.832);
+  --rx-red-12-alpha: hsla(355, 99.3%, 7.9%, 0.926);
+  --rx-sage-01-alpha: hsla(150, 92.6%, 26.5%, 0.016);
+  --rx-sage-02-alpha: hsla(150, 87.7%, 16.0%, 0.028);
+  --rx-sage-03-alpha: hsla(160, 98.4%, 10.9%, 0.055);
+  --rx-sage-04-alpha: hsla(140, 92.3%, 8.5%, 0.075);
+  --rx-sage-05-alpha: hsla(160, 86.0%, 6.9%, 0.099);
+  --rx-sage-06-alpha: hsla(156, 95.1%, 8.2%, 0.126);
+  --rx-sage-07-alpha: hsla(156, 98.6%, 6.3%, 0.157);
+  --rx-sage-08-alpha: hsla(154, 94.6%, 6.0%, 0.240);
+  --rx-sage-09-alpha: hsla(154, 98.7%, 3.0%, 0.459);
+  --rx-sage-10-alpha: hsla(154, 93.5%, 2.9%, 0.499);
+  --rx-sage-11-alpha: hsla(154, 95.2%, 2.5%, 0.585);
+  --rx-sage-12-alpha: hsla(158, 97.0%, 2.4%, 0.934);
+  --rx-sand-01-alpha: hsla(60, 89.3%, 18.3%, 0.012);
+  --rx-sand-02-alpha: hsla(60, 78.1%, 9.0%, 0.028);
+  --rx-sand-03-alpha: hsla(60, 99.0%, 3.9%, 0.051);
+  --rx-sand-04-alpha: hsla(60, 88.9%, 5.9%, 0.075);
+  --rx-sand-05-alpha: hsla(60, 86.0%, 6.9%, 0.099);
+  --rx-sand-06-alpha: hsla(60, 93.2%, 5.2%, 0.122);
+  --rx-sand-07-alpha: hsla(60, 98.3%, 5.1%, 0.157);
+  --rx-sand-08-alpha: hsla(51, 94.1%, 6.0%, 0.244);
+  --rx-sand-09-alpha: hsla(60, 99.8%, 1.7%, 0.451);
+  --rx-sand-10-alpha: hsla(60, 90.7%, 1.8%, 0.491);
+  --rx-sand-11-alpha: hsla(45, 93.7%, 1.5%, 0.577);
+  --rx-sand-12-alpha: hsla(60, 98.0%, 0.7%, 0.906);
+  --rx-sky-01-alpha: hsla(190, 100%, 51.0%, 0.024);
+  --rx-sky-02-alpha: hsla(193, 100%, 50.1%, 0.055);
+  --rx-sky-03-alpha: hsla(193, 100%, 50.1%, 0.106);
+  --rx-sky-04-alpha: hsla(194, 99.6%, 47.7%, 0.165);
+  --rx-sky-05-alpha: hsla(194, 99.2%, 45.4%, 0.244);
+  --rx-sky-06-alpha: hsla(194, 99.9%, 42.3%, 0.357);
+  --rx-sky-07-alpha: hsla(194, 99.8%, 42.2%, 0.526);
+  --rx-sky-08-alpha: hsla(193, 99.9%, 43.8%, 0.820);
+  --rx-sky-09-alpha: hsla(193, 99.7%, 49.4%, 0.593);
+  --rx-sky-10-alpha: hsla(193, 99.8%, 46.6%, 0.628);
+  --rx-sky-11-alpha: hsla(196, 100%, 31.2%, 0.980);
+  --rx-sky-12-alpha: hsla(196, 100%, 12.2%, 0.980);
+  --rx-slate-01-alpha: hsla(210, 92.6%, 26.5%, 0.016);
+  --rx-slate-02-alpha: hsla(210, 87.7%, 16.0%, 0.028);
+  --rx-slate-03-alpha: hsla(210, 98.8%, 14.4%, 0.055);
+  --rx-slate-04-alpha: hsla(210, 94.1%, 11.1%, 0.075);
+  --rx-slate-05-alpha: hsla(216, 91.1%, 10.9%, 0.099);
+  --rx-slate-06-alpha: hsla(206, 96.4%, 11.3%, 0.126);
+  --rx-slate-07-alpha: hsla(210, 99.1%, 10.1%, 0.157);
+  --rx-slate-08-alpha: hsla(205, 96.5%, 10.0%, 0.244);
+  --rx-slate-09-alpha: hsla(206, 98.8%, 5.9%, 0.467);
+  --rx-slate-10-alpha: hsla(206, 99.6%, 5.4%, 0.506);
+  --rx-slate-11-alpha: hsla(206, 97.0%, 4.8%, 0.593);
+  --rx-slate-12-alpha: hsla(202, 97.0%, 2.4%, 0.934);
+  --rx-teal-01-alpha: hsla(165, 95.2%, 41.2%, 0.020);
+  --rx-teal-02-alpha: hsla(169, 99.5%, 39.4%, 0.055);
+  --rx-teal-03-alpha: hsla(167, 97.6%, 38.1%, 0.095);
+  --rx-teal-04-alpha: hsla(168, 98.1%, 34.6%, 0.150);
+  --rx-teal-05-alpha: hsla(170, 99.4%, 32.3%, 0.220);
+  --rx-teal-06-alpha: hsla(170, 99.7%, 30.1%, 0.314);
+  --rx-teal-07-alpha: hsla(170, 99.3%, 28.7%, 0.448);
+  --rx-teal-08-alpha: hsla(172, 99.8%, 29.7%, 0.675);
+  --rx-teal-09-alpha: hsla(173, 99.8%, 31.1%, 0.930);
+  --rx-teal-10-alpha: hsla(173, 99.7%, 28.7%, 0.946);
+  --rx-teal-11-alpha: hsla(174, 99.8%, 23.3%, 0.977);
+  --rx-teal-12-alpha: hsla(171, 98.8%, 6.8%, 0.938);
+  --rx-tomato-01-alpha: hsla(0, 100%, 51.0%, 0.012);
+  --rx-tomato-02-alpha: hsla(8, 100%, 51.0%, 0.032);
+  --rx-tomato-03-alpha: hsla(7, 100%, 50.2%, 0.067);
+  --rx-tomato-04-alpha: hsla(8, 100%, 50.1%, 0.114);
+  --rx-tomato-05-alpha: hsla(7, 99.5%, 47.9%, 0.173);
+  --rx-tomato-06-alpha: hsla(9, 99.9%, 46.2%, 0.255);
+  --rx-tomato-07-alpha: hsla(10, 99.8%, 43.6%, 0.365);
+  --rx-tomato-08-alpha: hsla(10, 99.5%, 41.8%, 0.499);
+  --rx-tomato-09-alpha: hsla(10, 99.9%, 43.8%, 0.820);
+  --rx-tomato-10-alpha: hsla(10, 100%, 41.8%, 0.859);
+  --rx-tomato-11-alpha: hsla(10, 99.9%, 38.8%, 0.922);
+  --rx-tomato-12-alpha: hsla(10, 99.0%, 7.4%, 0.934);
+  --rx-violet-01-alpha: hsla(270, 94.3%, 34.6%, 0.012);
+  --rx-violet-02-alpha: hsla(252, 100%, 51.0%, 0.020);
+  --rx-violet-03-alpha: hsla(254, 100%, 50.0%, 0.051);
+  --rx-violet-04-alpha: hsla(251, 98.3%, 48.2%, 0.087);
+  --rx-violet-05-alpha: hsla(252, 99.0%, 45.7%, 0.130);
+  --rx-violet-06-alpha: hsla(251, 99.1%, 44.0%, 0.189);
+  --rx-violet-07-alpha: hsla(252, 99.5%, 41.7%, 0.279);
+  --rx-violet-08-alpha: hsla(252, 100%, 40.7%, 0.400);
+  --rx-violet-09-alpha: hsla(252, 99.9%, 35.8%, 0.663);
+  --rx-violet-10-alpha: hsla(251, 99.6%, 32.5%, 0.691);
+  --rx-violet-11-alpha: hsla(250, 99.8%, 28.4%, 0.726);
+  --rx-violet-12-alpha: hsla(254, 99.5%, 11.9%, 0.926);
+  --rx-white-01-alpha: hsla(0, 0%, 100%, 0);
+  --rx-white-02-alpha: hsla(0, 0%, 100%, 0.013);
+  --rx-white-03-alpha: hsla(0, 0%, 100%, 0.034);
+  --rx-white-04-alpha: hsla(0, 0%, 100%, 0.056);
+  --rx-white-05-alpha: hsla(0, 0%, 100%, 0.086);
+  --rx-white-06-alpha: hsla(0, 0%, 100%, 0.124);
+  --rx-white-07-alpha: hsla(0, 0%, 100%, 0.176);
+  --rx-white-08-alpha: hsla(0, 0%, 100%, 0.249);
+  --rx-white-09-alpha: hsla(0, 0%, 100%, 0.386);
+  --rx-white-10-alpha: hsla(0, 0%, 100%, 0.446);
+  --rx-white-11-alpha: hsla(0, 0%, 100%, 0.592);
+  --rx-white-12-alpha: hsla(0, 0%, 100%, 0.923);
+  --rx-yellow-01-alpha: hsla(60, 94.3%, 34.6%, 0.024);
+  --rx-yellow-02-alpha: hsla(52, 100%, 50.4%, 0.091);
+  --rx-yellow-03-alpha: hsla(55, 100%, 50.2%, 0.181);
+  --rx-yellow-04-alpha: hsla(54, 100%, 50.1%, 0.267);
+  --rx-yellow-05-alpha: hsla(52, 99.9%, 49.5%, 0.357);
+  --rx-yellow-06-alpha: hsla(50, 100%, 47.4%, 0.451);
+  --rx-yellow-07-alpha: hsla(47, 99.8%, 44.6%, 0.577);
+  --rx-yellow-08-alpha: hsla(48, 100%, 46.0%, 0.980);
+  --rx-yellow-09-alpha: hsla(53, 100%, 48.0%, 0.961);
+  --rx-yellow-10-alpha: hsla(50, 100%, 48.4%, 0.980);
+  --rx-yellow-11-alpha: hsla(42, 100%, 28.6%, 0.980);
+  --rx-yellow-12-alpha: hsla(41, 98.9%, 8.0%, 0.942);
+}
+
+html.dark, html[data-theme=dark] {
+  --rx-amber-01: hsl(36, 100%, 6.1%);
+  --rx-amber-02: hsl(35, 100%, 7.6%);
+  --rx-amber-03: hsl(32, 100%, 10.2%);
+  --rx-amber-04: hsl(32, 100%, 12.4%);
+  --rx-amber-05: hsl(33, 100%, 14.6%);
+  --rx-amber-06: hsl(35, 100%, 17.1%);
+  --rx-amber-07: hsl(35, 91.0%, 21.6%);
+  --rx-amber-08: hsl(36, 100%, 25.5%);
+  --rx-amber-09: hsl(39, 100%, 57.0%);
+  --rx-amber-10: hsl(43, 100%, 64.0%);
+  --rx-amber-11: hsl(39, 90.0%, 49.8%);
+  --rx-amber-12: hsl(39, 97.0%, 93.2%);
+  --rx-blue-01: hsl(212, 35.0%, 9.2%);
+  --rx-blue-02: hsl(216, 50.0%, 11.8%);
+  --rx-blue-03: hsl(214, 59.4%, 15.3%);
+  --rx-blue-04: hsl(214, 65.8%, 17.9%);
+  --rx-blue-05: hsl(213, 71.2%, 20.2%);
+  --rx-blue-06: hsl(212, 77.4%, 23.1%);
+  --rx-blue-07: hsl(211, 85.1%, 27.4%);
+  --rx-blue-08: hsl(211, 89.7%, 34.1%);
+  --rx-blue-09: hsl(206, 100%, 50.0%);
+  --rx-blue-10: hsl(209, 100%, 60.6%);
+  --rx-blue-11: hsl(210, 100%, 66.1%);
+  --rx-blue-12: hsl(206, 98.0%, 95.8%);
+  --rx-bronze-01: hsl(17, 10.0%, 8.8%);
+  --rx-bronze-02: hsl(15, 14.8%, 10.6%);
+  --rx-bronze-03: hsl(15, 16.3%, 14.3%);
+  --rx-bronze-04: hsl(16, 17.1%, 17.2%);
+  --rx-bronze-05: hsl(16, 17.6%, 19.6%);
+  --rx-bronze-06: hsl(16, 18.1%, 22.9%);
+  --rx-bronze-07: hsl(17, 18.8%, 28.8%);
+  --rx-bronze-08: hsl(17, 19.6%, 38.0%);
+  --rx-bronze-09: hsl(17, 20.0%, 54.0%);
+  --rx-bronze-10: hsl(18, 24.0%, 59.0%);
+  --rx-bronze-11: hsl(18, 35.0%, 68.5%);
+  --rx-bronze-12: hsl(18, 57.0%, 94.1%);
+  --rx-brown-01: hsl(22, 15.0%, 8.7%);
+  --rx-brown-02: hsl(20, 28.3%, 10.4%);
+  --rx-brown-03: hsl(20, 28.0%, 14.0%);
+  --rx-brown-04: hsl(21, 28.4%, 16.5%);
+  --rx-brown-05: hsl(22, 28.7%, 18.9%);
+  --rx-brown-06: hsl(23, 29.0%, 22.3%);
+  --rx-brown-07: hsl(25, 29.5%, 27.8%);
+  --rx-brown-08: hsl(27, 30.1%, 35.9%);
+  --rx-brown-09: hsl(28, 34.0%, 51.0%);
+  --rx-brown-10: hsl(28, 41.4%, 55.8%);
+  --rx-brown-11: hsl(28, 60.0%, 64.5%);
+  --rx-brown-12: hsl(30, 67.0%, 94.0%);
+  --rx-crimson-01: hsl(335, 20.0%, 9.6%);
+  --rx-crimson-02: hsl(335, 32.2%, 11.6%);
+  --rx-crimson-03: hsl(335, 42.5%, 16.5%);
+  --rx-crimson-04: hsl(335, 47.2%, 19.3%);
+  --rx-crimson-05: hsl(335, 50.9%, 21.8%);
+  --rx-crimson-06: hsl(335, 55.7%, 25.3%);
+  --rx-crimson-07: hsl(336, 62.9%, 30.8%);
+  --rx-crimson-08: hsl(336, 74.9%, 39.0%);
+  --rx-crimson-09: hsl(336, 80.0%, 57.8%);
+  --rx-crimson-10: hsl(339, 84.1%, 62.6%);
+  --rx-crimson-11: hsl(341, 90.0%, 67.3%);
+  --rx-crimson-12: hsl(332, 87.0%, 96.0%);
+  --rx-cyan-01: hsl(192, 60.0%, 7.2%);
+  --rx-cyan-02: hsl(192, 71.4%, 8.2%);
+  --rx-cyan-03: hsl(192, 75.9%, 10.8%);
+  --rx-cyan-04: hsl(192, 79.3%, 12.8%);
+  --rx-cyan-05: hsl(192, 82.5%, 14.6%);
+  --rx-cyan-06: hsl(192, 86.6%, 16.9%);
+  --rx-cyan-07: hsl(192, 92.6%, 20.1%);
+  --rx-cyan-08: hsl(192, 100%, 24.5%);
+  --rx-cyan-09: hsl(190, 95.0%, 39.0%);
+  --rx-cyan-10: hsl(188, 100%, 40.0%);
+  --rx-cyan-11: hsl(186, 100%, 42.2%);
+  --rx-cyan-12: hsl(185, 73.0%, 93.2%);
+  --rx-gold-01: hsl(44, 9.0%, 8.3%);
+  --rx-gold-02: hsl(43, 14.3%, 9.6%);
+  --rx-gold-03: hsl(42, 15.5%, 13.0%);
+  --rx-gold-04: hsl(41, 16.4%, 15.6%);
+  --rx-gold-05: hsl(41, 16.9%, 17.8%);
+  --rx-gold-06: hsl(40, 17.6%, 20.8%);
+  --rx-gold-07: hsl(38, 18.5%, 26.4%);
+  --rx-gold-08: hsl(36, 19.6%, 35.1%);
+  --rx-gold-09: hsl(36, 20.0%, 49.5%);
+  --rx-gold-10: hsl(36, 22.3%, 54.5%);
+  --rx-gold-11: hsl(35, 30.0%, 64.0%);
+  --rx-gold-12: hsl(49, 52.0%, 93.8%);
+  --rx-grass-01: hsl(146, 30.0%, 7.4%);
+  --rx-grass-02: hsl(136, 33.3%, 8.8%);
+  --rx-grass-03: hsl(137, 36.0%, 11.4%);
+  --rx-grass-04: hsl(137, 37.6%, 13.7%);
+  --rx-grass-05: hsl(136, 38.7%, 16.0%);
+  --rx-grass-06: hsl(135, 39.6%, 19.1%);
+  --rx-grass-07: hsl(134, 40.3%, 23.8%);
+  --rx-grass-08: hsl(131, 40.1%, 30.8%);
+  --rx-grass-09: hsl(131, 41.0%, 46.5%);
+  --rx-grass-10: hsl(131, 39.0%, 52.1%);
+  --rx-grass-11: hsl(131, 43.0%, 57.2%);
+  --rx-grass-12: hsl(137, 72.0%, 94.0%);
+  --rx-gray-01: hsl(0, 0%, 8.5%);
+  --rx-gray-02: hsl(0, 0%, 11.0%);
+  --rx-gray-03: hsl(0, 0%, 13.6%);
+  --rx-gray-04: hsl(0, 0%, 15.8%);
+  --rx-gray-05: hsl(0, 0%, 17.9%);
+  --rx-gray-06: hsl(0, 0%, 20.5%);
+  --rx-gray-07: hsl(0, 0%, 24.3%);
+  --rx-gray-08: hsl(0, 0%, 31.2%);
+  --rx-gray-09: hsl(0, 0%, 43.9%);
+  --rx-gray-10: hsl(0, 0%, 49.4%);
+  --rx-gray-11: hsl(0, 0%, 62.8%);
+  --rx-gray-12: hsl(0, 0%, 93.0%);
+  --rx-green-01: hsl(146, 30.0%, 7.4%);
+  --rx-green-02: hsl(155, 44.2%, 8.4%);
+  --rx-green-03: hsl(155, 46.7%, 10.9%);
+  --rx-green-04: hsl(154, 48.4%, 12.9%);
+  --rx-green-05: hsl(154, 49.7%, 14.9%);
+  --rx-green-06: hsl(154, 50.9%, 17.6%);
+  --rx-green-07: hsl(153, 51.8%, 21.8%);
+  --rx-green-08: hsl(151, 51.7%, 28.4%);
+  --rx-green-09: hsl(151, 55.0%, 41.5%);
+  --rx-green-10: hsl(151, 49.3%, 46.5%);
+  --rx-green-11: hsl(151, 50.0%, 53.2%);
+  --rx-green-12: hsl(137, 72.0%, 94.0%);
+  --rx-indigo-01: hsl(229, 24.0%, 10.0%);
+  --rx-indigo-02: hsl(230, 36.4%, 12.9%);
+  --rx-indigo-03: hsl(228, 43.3%, 17.5%);
+  --rx-indigo-04: hsl(227, 47.2%, 21.0%);
+  --rx-indigo-05: hsl(227, 50.0%, 24.1%);
+  --rx-indigo-06: hsl(226, 52.9%, 28.2%);
+  --rx-indigo-07: hsl(226, 56.0%, 34.5%);
+  --rx-indigo-08: hsl(226, 58.2%, 44.1%);
+  --rx-indigo-09: hsl(226, 70.0%, 55.5%);
+  --rx-indigo-10: hsl(227, 75.2%, 61.6%);
+  --rx-indigo-11: hsl(228, 100%, 75.9%);
+  --rx-indigo-12: hsl(226, 83.0%, 96.3%);
+  --rx-lime-01: hsl(75, 55.0%, 6.0%);
+  --rx-lime-02: hsl(74, 56.8%, 7.3%);
+  --rx-lime-03: hsl(78, 50.2%, 9.9%);
+  --rx-lime-04: hsl(79, 50.3%, 12.1%);
+  --rx-lime-05: hsl(79, 52.6%, 14.2%);
+  --rx-lime-06: hsl(78, 55.7%, 16.7%);
+  --rx-lime-07: hsl(77, 59.7%, 20.1%);
+  --rx-lime-08: hsl(75, 64.8%, 24.5%);
+  --rx-lime-09: hsl(81, 67.0%, 50.0%);
+  --rx-lime-10: hsl(75, 85.0%, 60.0%);
+  --rx-lime-11: hsl(81, 70.0%, 43.8%);
+  --rx-lime-12: hsl(84, 79.0%, 92.6%);
+  --rx-mauve-01: hsl(246, 6.0%, 9.0%);
+  --rx-mauve-02: hsl(240, 5.1%, 11.6%);
+  --rx-mauve-03: hsl(241, 5.0%, 14.3%);
+  --rx-mauve-04: hsl(242, 4.9%, 16.5%);
+  --rx-mauve-05: hsl(243, 4.9%, 18.8%);
+  --rx-mauve-06: hsl(244, 4.9%, 21.5%);
+  --rx-mauve-07: hsl(245, 4.9%, 25.4%);
+  --rx-mauve-08: hsl(247, 4.8%, 32.5%);
+  --rx-mauve-09: hsl(252, 4.0%, 45.2%);
+  --rx-mauve-10: hsl(247, 3.4%, 50.7%);
+  --rx-mauve-11: hsl(253, 4.0%, 63.7%);
+  --rx-mauve-12: hsl(256, 6.0%, 93.2%);
+  --rx-mint-01: hsl(173, 50.0%, 6.6%);
+  --rx-mint-02: hsl(176, 73.0%, 7.3%);
+  --rx-mint-03: hsl(175, 79.3%, 8.9%);
+  --rx-mint-04: hsl(174, 84.8%, 10.3%);
+  --rx-mint-05: hsl(174, 90.2%, 11.9%);
+  --rx-mint-06: hsl(173, 96.0%, 13.8%);
+  --rx-mint-07: hsl(172, 100%, 16.8%);
+  --rx-mint-08: hsl(170, 100%, 21.4%);
+  --rx-mint-09: hsl(167, 65.0%, 66.0%);
+  --rx-mint-10: hsl(163, 80.0%, 77.0%);
+  --rx-mint-11: hsl(167, 70.0%, 48.0%);
+  --rx-mint-12: hsl(165, 80.0%, 94.8%);
+  --rx-olive-01: hsl(110, 5.0%, 8.6%);
+  --rx-olive-02: hsl(105, 7.4%, 10.6%);
+  --rx-olive-03: hsl(106, 6.4%, 13.1%);
+  --rx-olive-04: hsl(106, 5.8%, 15.3%);
+  --rx-olive-05: hsl(107, 5.3%, 17.4%);
+  --rx-olive-06: hsl(107, 4.9%, 19.9%);
+  --rx-olive-07: hsl(108, 4.4%, 23.6%);
+  --rx-olive-08: hsl(110, 3.8%, 30.6%);
+  --rx-olive-09: hsl(110, 6.0%, 42.5%);
+  --rx-olive-10: hsl(111, 4.8%, 48.2%);
+  --rx-olive-11: hsl(110, 5.0%, 61.8%);
+  --rx-olive-12: hsl(110, 6.0%, 93.0%);
+  --rx-orange-01: hsl(30, 70.0%, 7.2%);
+  --rx-orange-02: hsl(28, 100%, 8.4%);
+  --rx-orange-03: hsl(26, 91.1%, 11.6%);
+  --rx-orange-04: hsl(25, 88.3%, 14.1%);
+  --rx-orange-05: hsl(24, 87.6%, 16.6%);
+  --rx-orange-06: hsl(24, 88.6%, 19.8%);
+  --rx-orange-07: hsl(24, 92.4%, 24.0%);
+  --rx-orange-08: hsl(25, 100%, 29.0%);
+  --rx-orange-09: hsl(24, 94.0%, 50.0%);
+  --rx-orange-10: hsl(24, 100%, 58.5%);
+  --rx-orange-11: hsl(24, 100%, 62.2%);
+  --rx-orange-12: hsl(24, 97.0%, 93.2%);
+  --rx-pink-01: hsl(318, 25.0%, 9.6%);
+  --rx-pink-02: hsl(319, 32.2%, 11.6%);
+  --rx-pink-03: hsl(319, 41.0%, 16.0%);
+  --rx-pink-04: hsl(320, 45.4%, 18.7%);
+  --rx-pink-05: hsl(320, 49.0%, 21.1%);
+  --rx-pink-06: hsl(321, 53.6%, 24.4%);
+  --rx-pink-07: hsl(321, 61.1%, 29.7%);
+  --rx-pink-08: hsl(322, 74.9%, 37.5%);
+  --rx-pink-09: hsl(322, 65.0%, 54.5%);
+  --rx-pink-10: hsl(323, 72.8%, 59.2%);
+  --rx-pink-11: hsl(325, 90.0%, 66.4%);
+  --rx-pink-12: hsl(322, 90.0%, 95.8%);
+  --rx-plum-01: hsl(301, 20.0%, 9.4%);
+  --rx-plum-02: hsl(300, 29.8%, 11.2%);
+  --rx-plum-03: hsl(298, 34.4%, 15.3%);
+  --rx-plum-04: hsl(297, 36.8%, 18.3%);
+  --rx-plum-05: hsl(296, 38.5%, 21.1%);
+  --rx-plum-06: hsl(295, 40.4%, 24.7%);
+  --rx-plum-07: hsl(294, 42.7%, 30.6%);
+  --rx-plum-08: hsl(292, 45.1%, 40.0%);
+  --rx-plum-09: hsl(292, 45.0%, 51.0%);
+  --rx-plum-10: hsl(295, 50.0%, 55.4%);
+  --rx-plum-11: hsl(300, 60.0%, 62.0%);
+  --rx-plum-12: hsl(296, 74.0%, 95.7%);
+  --rx-purple-01: hsl(284, 20.0%, 9.6%);
+  --rx-purple-02: hsl(283, 30.0%, 11.8%);
+  --rx-purple-03: hsl(281, 37.5%, 16.5%);
+  --rx-purple-04: hsl(280, 41.2%, 20.0%);
+  --rx-purple-05: hsl(279, 43.8%, 23.3%);
+  --rx-purple-06: hsl(277, 46.4%, 27.5%);
+  --rx-purple-07: hsl(275, 49.3%, 34.6%);
+  --rx-purple-08: hsl(272, 52.1%, 45.9%);
+  --rx-purple-09: hsl(272, 51.0%, 54.0%);
+  --rx-purple-10: hsl(273, 57.3%, 59.1%);
+  --rx-purple-11: hsl(275, 80.0%, 71.0%);
+  --rx-purple-12: hsl(279, 75.0%, 95.7%);
+  --rx-red-01: hsl(353, 23.0%, 9.8%);
+  --rx-red-02: hsl(357, 34.4%, 12.0%);
+  --rx-red-03: hsl(356, 43.4%, 16.4%);
+  --rx-red-04: hsl(356, 47.6%, 19.2%);
+  --rx-red-05: hsl(356, 51.1%, 21.9%);
+  --rx-red-06: hsl(356, 55.2%, 25.9%);
+  --rx-red-07: hsl(357, 60.2%, 31.8%);
+  --rx-red-08: hsl(358, 65.0%, 40.4%);
+  --rx-red-09: hsl(358, 75.0%, 59.0%);
+  --rx-red-10: hsl(358, 85.3%, 64.0%);
+  --rx-red-11: hsl(358, 100%, 69.5%);
+  --rx-red-12: hsl(351, 89.0%, 96.0%);
+  --rx-sage-01: hsl(155, 7.0%, 8.4%);
+  --rx-sage-02: hsl(150, 7.4%, 10.6%);
+  --rx-sage-03: hsl(150, 6.7%, 13.1%);
+  --rx-sage-04: hsl(150, 6.4%, 15.3%);
+  --rx-sage-05: hsl(150, 6.1%, 17.4%);
+  --rx-sage-06: hsl(150, 5.8%, 19.9%);
+  --rx-sage-07: hsl(150, 5.5%, 23.6%);
+  --rx-sage-08: hsl(150, 5.1%, 30.6%);
+  --rx-sage-09: hsl(155, 6.0%, 42.5%);
+  --rx-sage-10: hsl(153, 4.8%, 48.2%);
+  --rx-sage-11: hsl(155, 5.0%, 61.8%);
+  --rx-sage-12: hsl(155, 6.0%, 93.0%);
+  --rx-sand-01: hsl(61, 2.0%, 8.3%);
+  --rx-sand-02: hsl(60, 3.7%, 10.6%);
+  --rx-sand-03: hsl(58, 3.7%, 13.1%);
+  --rx-sand-04: hsl(57, 3.6%, 15.3%);
+  --rx-sand-05: hsl(56, 3.7%, 17.4%);
+  --rx-sand-06: hsl(55, 3.7%, 19.9%);
+  --rx-sand-07: hsl(53, 3.7%, 23.6%);
+  --rx-sand-08: hsl(50, 3.8%, 30.6%);
+  --rx-sand-09: hsl(50, 4.0%, 42.7%);
+  --rx-sand-10: hsl(52, 3.1%, 48.3%);
+  --rx-sand-11: hsl(50, 4.0%, 61.8%);
+  --rx-sand-12: hsl(56, 4.0%, 92.8%);
+  --rx-sky-01: hsl(205, 45.0%, 8.6%);
+  --rx-sky-02: hsl(202, 71.4%, 9.6%);
+  --rx-sky-03: hsl(201, 74.6%, 12.2%);
+  --rx-sky-04: hsl(201, 77.4%, 14.4%);
+  --rx-sky-05: hsl(200, 80.3%, 16.5%);
+  --rx-sky-06: hsl(200, 84.1%, 18.9%);
+  --rx-sky-07: hsl(199, 90.2%, 22.1%);
+  --rx-sky-08: hsl(198, 100%, 26.1%);
+  --rx-sky-09: hsl(193, 98.0%, 70.0%);
+  --rx-sky-10: hsl(192, 100%, 77.0%);
+  --rx-sky-11: hsl(192, 85.0%, 55.8%);
+  --rx-sky-12: hsl(198, 98.0%, 95.8%);
+  --rx-slate-01: hsl(200, 7.0%, 8.8%);
+  --rx-slate-02: hsl(195, 7.1%, 11.0%);
+  --rx-slate-03: hsl(197, 6.8%, 13.6%);
+  --rx-slate-04: hsl(198, 6.6%, 15.8%);
+  --rx-slate-05: hsl(199, 6.4%, 17.9%);
+  --rx-slate-06: hsl(201, 6.2%, 20.5%);
+  --rx-slate-07: hsl(203, 6.0%, 24.3%);
+  --rx-slate-08: hsl(207, 5.6%, 31.6%);
+  --rx-slate-09: hsl(206, 6.0%, 43.9%);
+  --rx-slate-10: hsl(206, 5.2%, 49.5%);
+  --rx-slate-11: hsl(206, 6.0%, 63.0%);
+  --rx-slate-12: hsl(210, 6.0%, 93.0%);
+  --rx-teal-01: hsl(168, 48.0%, 6.5%);
+  --rx-teal-02: hsl(169, 77.8%, 7.1%);
+  --rx-teal-03: hsl(170, 76.1%, 9.2%);
+  --rx-teal-04: hsl(171, 75.8%, 11.0%);
+  --rx-teal-05: hsl(171, 75.7%, 12.8%);
+  --rx-teal-06: hsl(172, 75.8%, 15.1%);
+  --rx-teal-07: hsl(172, 76.7%, 18.6%);
+  --rx-teal-08: hsl(173, 80.2%, 23.7%);
+  --rx-teal-09: hsl(173, 80.0%, 36.0%);
+  --rx-teal-10: hsl(174, 83.9%, 38.2%);
+  --rx-teal-11: hsl(174, 90.0%, 40.7%);
+  --rx-teal-12: hsl(166, 73.0%, 93.1%);
+  --rx-tomato-01: hsl(10, 23.0%, 9.4%);
+  --rx-tomato-02: hsl(9, 44.8%, 11.4%);
+  --rx-tomato-03: hsl(8, 52.0%, 15.3%);
+  --rx-tomato-04: hsl(7, 56.3%, 18.0%);
+  --rx-tomato-05: hsl(7, 60.1%, 20.6%);
+  --rx-tomato-06: hsl(8, 64.8%, 24.0%);
+  --rx-tomato-07: hsl(8, 71.2%, 29.1%);
+  --rx-tomato-08: hsl(10, 80.2%, 35.7%);
+  --rx-tomato-09: hsl(10, 78.0%, 54.0%);
+  --rx-tomato-10: hsl(10, 81.7%, 59.0%);
+  --rx-tomato-11: hsl(10, 85.0%, 62.8%);
+  --rx-tomato-12: hsl(10, 89.0%, 96.0%);
+  --rx-violet-01: hsl(250, 20.0%, 10.2%);
+  --rx-violet-02: hsl(255, 30.3%, 12.9%);
+  --rx-violet-03: hsl(253, 37.0%, 18.4%);
+  --rx-violet-04: hsl(252, 40.1%, 22.5%);
+  --rx-violet-05: hsl(252, 42.2%, 26.2%);
+  --rx-violet-06: hsl(251, 44.3%, 31.1%);
+  --rx-violet-07: hsl(250, 46.8%, 38.9%);
+  --rx-violet-08: hsl(250, 51.8%, 51.2%);
+  --rx-violet-09: hsl(252, 56.0%, 57.5%);
+  --rx-violet-10: hsl(251, 63.2%, 63.2%);
+  --rx-violet-11: hsl(250, 95.0%, 76.8%);
+  --rx-violet-12: hsl(252, 87.0%, 96.4%);
+  --rx-yellow-01: hsl(45, 100%, 5.5%);
+  --rx-yellow-02: hsl(46, 100%, 6.7%);
+  --rx-yellow-03: hsl(45, 100%, 8.7%);
+  --rx-yellow-04: hsl(45, 100%, 10.4%);
+  --rx-yellow-05: hsl(47, 100%, 12.1%);
+  --rx-yellow-06: hsl(49, 100%, 14.3%);
+  --rx-yellow-07: hsl(49, 90.3%, 18.4%);
+  --rx-yellow-08: hsl(50, 100%, 22.0%);
+  --rx-yellow-09: hsl(53, 92.0%, 50.0%);
+  --rx-yellow-10: hsl(54, 100%, 68.0%);
+  --rx-yellow-11: hsl(48, 100%, 47.0%);
+  --rx-yellow-12: hsl(53, 100%, 91.0%);
+}
+
+html.dark, html[data-theme=dark] {
+  --rx-amber-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-amber-02-alpha: hsla(31, 100%, 49.7%, 0.036);
+  --rx-amber-03-alpha: hsla(27, 100%, 49.9%, 0.094);
+  --rx-amber-04-alpha: hsla(29, 100%, 50.0%, 0.143);
+  --rx-amber-05-alpha: hsla(31, 100%, 50.0%, 0.192);
+  --rx-amber-06-alpha: hsla(35, 100%, 50.0%, 0.250);
+  --rx-amber-07-alpha: hsla(34, 99.6%, 52.9%, 0.331);
+  --rx-amber-08-alpha: hsla(36, 100%, 50.0%, 0.442);
+  --rx-amber-09-alpha: hsla(40, 100%, 57.2%, 0.980);
+  --rx-amber-10-alpha: hsla(44, 100%, 64.2%, 0.980);
+  --rx-amber-11-alpha: hsla(39, 99.9%, 52.7%, 0.938);
+  --rx-amber-12-alpha: hsla(45, 100%, 94.2%, 0.980);
+  --rx-blue-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-blue-02-alpha: hsla(221, 97.8%, 52.4%, 0.059);
+  --rx-blue-03-alpha: hsla(215, 99.3%, 54.2%, 0.135);
+  --rx-blue-04-alpha: hsla(215, 99.3%, 53.8%, 0.198);
+  --rx-blue-05-alpha: hsla(213, 99.4%, 52.8%, 0.252);
+  --rx-blue-06-alpha: hsla(212, 99.9%, 51.7%, 0.323);
+  --rx-blue-07-alpha: hsla(211, 100%, 50.7%, 0.435);
+  --rx-blue-08-alpha: hsla(211, 99.8%, 50.9%, 0.597);
+  --rx-blue-09-alpha: hsla(205, 100%, 50.0%, 0.980);
+  --rx-blue-10-alpha: hsla(208, 100%, 60.7%, 0.980);
+  --rx-blue-11-alpha: hsla(209, 100%, 66.3%, 0.980);
+  --rx-blue-12-alpha: hsla(196, 100%, 96.8%, 0.980);
+  --rx-bronze-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-bronze-02-alpha: hsla(20, 88.2%, 74.2%, 0.027);
+  --rx-bronze-03-alpha: hsla(10, 99.4%, 83.0%, 0.074);
+  --rx-bronze-04-alpha: hsla(18, 96.0%, 81.1%, 0.114);
+  --rx-bronze-05-alpha: hsla(18, 99.4%, 81.7%, 0.148);
+  --rx-bronze-06-alpha: hsla(15, 98.1%, 82.4%, 0.192);
+  --rx-bronze-07-alpha: hsla(16, 99.2%, 82.9%, 0.270);
+  --rx-bronze-08-alpha: hsla(18, 99.5%, 82.6%, 0.396);
+  --rx-bronze-09-alpha: hsla(18, 99.3%, 85.0%, 0.592);
+  --rx-bronze-10-alpha: hsla(18, 99.6%, 85.2%, 0.657);
+  --rx-bronze-11-alpha: hsla(17, 99.9%, 86.1%, 0.774);
+  --rx-bronze-12-alpha: hsla(20, 99.8%, 96.4%, 0.974);
+  --rx-brown-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-brown-02-alpha: hsla(22, 99.6%, 53.6%, 0.035);
+  --rx-brown-03-alpha: hsla(18, 97.8%, 69.0%, 0.088);
+  --rx-brown-04-alpha: hsla(21, 98.2%, 71.0%, 0.123);
+  --rx-brown-05-alpha: hsla(25, 98.4%, 72.1%, 0.158);
+  --rx-brown-06-alpha: hsla(25, 98.7%, 73.5%, 0.206);
+  --rx-brown-07-alpha: hsla(25, 99.0%, 74.6%, 0.289);
+  --rx-brown-08-alpha: hsla(28, 99.2%, 75.3%, 0.407);
+  --rx-brown-09-alpha: hsla(28, 100%, 74.8%, 0.642);
+  --rx-brown-10-alpha: hsla(28, 99.9%, 74.9%, 0.712);
+  --rx-brown-11-alpha: hsla(28, 99.9%, 74.9%, 0.843);
+  --rx-brown-12-alpha: hsla(32, 98.2%, 95.7%, 0.979);
+  --rx-crimson-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-crimson-02-alpha: hsla(336, 96.8%, 53.2%, 0.045);
+  --rx-crimson-03-alpha: hsla(335, 98.7%, 59.3%, 0.138);
+  --rx-crimson-04-alpha: hsla(336, 99.1%, 59.9%, 0.191);
+  --rx-crimson-05-alpha: hsla(335, 99.4%, 59.4%, 0.244);
+  --rx-crimson-06-alpha: hsla(335, 99.4%, 59.4%, 0.315);
+  --rx-crimson-07-alpha: hsla(336, 99.5%, 57.8%, 0.439);
+  --rx-crimson-08-alpha: hsla(336, 99.9%, 55.4%, 0.642);
+  --rx-crimson-09-alpha: hsla(336, 99.9%, 62.8%, 0.903);
+  --rx-crimson-10-alpha: hsla(339, 99.9%, 66.3%, 0.934);
+  --rx-crimson-11-alpha: hsla(341, 99.9%, 69.5%, 0.965);
+  --rx-crimson-12-alpha: hsla(327, 100%, 97.1%, 0.980);
+  --rx-cyan-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-cyan-02-alpha: hsla(196, 100%, 50.0%, 0.031);
+  --rx-cyan-03-alpha: hsla(192, 98.0%, 50.9%, 0.085);
+  --rx-cyan-04-alpha: hsla(194, 99.6%, 51.3%, 0.133);
+  --rx-cyan-05-alpha: hsla(192, 99.5%, 51.3%, 0.173);
+  --rx-cyan-06-alpha: hsla(193, 99.7%, 50.4%, 0.226);
+  --rx-cyan-07-alpha: hsla(192, 100%, 50.0%, 0.310);
+  --rx-cyan-08-alpha: hsla(193, 100%, 50.0%, 0.425);
+  --rx-cyan-09-alpha: hsla(190, 99.8%, 50.8%, 0.731);
+  --rx-cyan-10-alpha: hsla(188, 100%, 50.0%, 0.775);
+  --rx-cyan-11-alpha: hsla(186, 100%, 49.9%, 0.824);
+  --rx-cyan-12-alpha: hsla(185, 99.8%, 95.1%, 0.978);
+  --rx-gold-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-gold-02-alpha: hsla(40, 93.7%, 70.6%, 0.022);
+  --rx-gold-03-alpha: hsla(40, 97.5%, 80.6%, 0.065);
+  --rx-gold-04-alpha: hsla(40, 95.9%, 80.8%, 0.100);
+  --rx-gold-05-alpha: hsla(38, 97.3%, 82.1%, 0.130);
+  --rx-gold-06-alpha: hsla(39, 97.2%, 82.5%, 0.169);
+  --rx-gold-07-alpha: hsla(37, 99.3%, 82.4%, 0.246);
+  --rx-gold-08-alpha: hsla(35, 98.7%, 82.2%, 0.363);
+  --rx-gold-09-alpha: hsla(36, 99.7%, 82.8%, 0.552);
+  --rx-gold-10-alpha: hsla(35, 99.2%, 83.7%, 0.613);
+  --rx-gold-11-alpha: hsla(35, 99.3%, 85.3%, 0.725);
+  --rx-gold-12-alpha: hsla(49, 98.6%, 96.7%, 0.966);
+  --rx-grass-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-grass-02-alpha: hsla(107, 97.2%, 61.9%, 0.022);
+  --rx-grass-03-alpha: hsla(128, 96.5%, 69.8%, 0.066);
+  --rx-grass-04-alpha: hsla(130, 100%, 70.2%, 0.100);
+  --rx-grass-05-alpha: hsla(130, 98.2%, 69.1%, 0.140);
+  --rx-grass-06-alpha: hsla(132, 99.9%, 69.3%, 0.187);
+  --rx-grass-07-alpha: hsla(132, 99.9%, 69.8%, 0.261);
+  --rx-grass-08-alpha: hsla(130, 99.6%, 70.5%, 0.370);
+  --rx-grass-09-alpha: hsla(130, 99.7%, 70.6%, 0.618);
+  --rx-grass-10-alpha: hsla(131, 100%, 73.5%, 0.674);
+  --rx-grass-11-alpha: hsla(130, 99.7%, 75.6%, 0.731);
+  --rx-grass-12-alpha: hsla(137, 100%, 95.8%, 0.980);
+  --rx-gray-01-alpha: hsla(0, 0%, 100%, 0);
+  --rx-gray-02-alpha: hsla(0, 0%, 100%, 0.026);
+  --rx-gray-03-alpha: hsla(0, 0%, 100%, 0.056);
+  --rx-gray-04-alpha: hsla(0, 0%, 100%, 0.077);
+  --rx-gray-05-alpha: hsla(0, 0%, 100%, 0.103);
+  --rx-gray-06-alpha: hsla(0, 0%, 100%, 0.129);
+  --rx-gray-07-alpha: hsla(0, 0%, 100%, 0.172);
+  --rx-gray-08-alpha: hsla(0, 0%, 100%, 0.249);
+  --rx-gray-09-alpha: hsla(0, 0%, 100%, 0.386);
+  --rx-gray-10-alpha: hsla(0, 0%, 100%, 0.446);
+  --rx-gray-11-alpha: hsla(0, 0%, 100%, 0.592);
+  --rx-gray-12-alpha: hsla(0, 0%, 100%, 0.923);
+  --rx-green-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-green-02-alpha: hsla(169, 100%, 48.5%, 0.027);
+  --rx-green-03-alpha: hsla(162, 98.7%, 57.9%, 0.070);
+  --rx-green-04-alpha: hsla(158, 98.6%, 59.7%, 0.105);
+  --rx-green-05-alpha: hsla(158, 98.6%, 60.7%, 0.140);
+  --rx-green-06-alpha: hsla(156, 99.9%, 62.0%, 0.187);
+  --rx-green-07-alpha: hsla(154, 99.5%, 63.1%, 0.257);
+  --rx-green-08-alpha: hsla(152, 99.7%, 64.2%, 0.370);
+  --rx-green-09-alpha: hsla(151, 99.7%, 63.8%, 0.605);
+  --rx-green-10-alpha: hsla(152, 99.9%, 66.5%, 0.661);
+  --rx-green-11-alpha: hsla(151, 99.7%, 69.2%, 0.740);
+  --rx-green-12-alpha: hsla(137, 100%, 95.8%, 0.980);
+  --rx-indigo-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-indigo-02-alpha: hsla(234, 97.4%, 59.9%, 0.059);
+  --rx-indigo-03-alpha: hsla(228, 99.2%, 61.7%, 0.144);
+  --rx-indigo-04-alpha: hsla(227, 99.7%, 62.0%, 0.211);
+  --rx-indigo-05-alpha: hsla(227, 99.2%, 62.3%, 0.270);
+  --rx-indigo-06-alpha: hsla(226, 99.9%, 62.1%, 0.350);
+  --rx-indigo-07-alpha: hsla(226, 99.9%, 62.0%, 0.471);
+  --rx-indigo-08-alpha: hsla(226, 99.9%, 62.1%, 0.655);
+  --rx-indigo-09-alpha: hsla(226, 99.9%, 63.6%, 0.848);
+  --rx-indigo-10-alpha: hsla(227, 99.8%, 67.7%, 0.893);
+  --rx-indigo-11-alpha: hsla(227, 100%, 76.3%, 0.980);
+  --rx-indigo-12-alpha: hsla(226, 100%, 97.5%, 0.980);
+  --rx-lime-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-lime-02-alpha: hsla(75, 96.4%, 59.6%, 0.022);
+  --rx-lime-03-alpha: hsla(88, 98.0%, 70.4%, 0.061);
+  --rx-lime-04-alpha: hsla(81, 97.8%, 67.4%, 0.096);
+  --rx-lime-05-alpha: hsla(82, 98.4%, 65.6%, 0.135);
+  --rx-lime-06-alpha: hsla(79, 99.7%, 64.3%, 0.182);
+  --rx-lime-07-alpha: hsla(77, 99.1%, 62.1%, 0.252);
+  --rx-lime-08-alpha: hsla(75, 100%, 60.0%, 0.342);
+  --rx-lime-09-alpha: hsla(81, 99.8%, 59.7%, 0.819);
+  --rx-lime-10-alpha: hsla(75, 99.8%, 63.7%, 0.936);
+  --rx-lime-11-alpha: hsla(81, 99.9%, 58.7%, 0.719);
+  --rx-lime-12-alpha: hsla(83, 100%, 94.2%, 0.980);
+  --rx-mauve-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-mauve-02-alpha: hsla(240, 76.7%, 91.2%, 0.031);
+  --rx-mauve-03-alpha: hsla(240, 86.0%, 95.8%, 0.061);
+  --rx-mauve-04-alpha: hsla(240, 91.8%, 94.7%, 0.087);
+  --rx-mauve-05-alpha: hsla(240, 91.5%, 95.8%, 0.113);
+  --rx-mauve-06-alpha: hsla(240, 92.0%, 93.8%, 0.148);
+  --rx-mauve-07-alpha: hsla(240, 94.8%, 95.3%, 0.191);
+  --rx-mauve-08-alpha: hsla(249, 98.1%, 95.2%, 0.273);
+  --rx-mauve-09-alpha: hsla(248, 97.6%, 96.2%, 0.416);
+  --rx-mauve-10-alpha: hsla(248, 95.5%, 96.6%, 0.477);
+  --rx-mauve-11-alpha: hsla(250, 98.0%, 98.0%, 0.615);
+  --rx-mauve-12-alpha: hsla(240, 93.9%, 99.6%, 0.931);
+  --rx-mint-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-mint-02-alpha: hsla(180, 100%, 49.2%, 0.031);
+  --rx-mint-03-alpha: hsla(176, 100%, 49.7%, 0.070);
+  --rx-mint-04-alpha: hsla(173, 100%, 49.7%, 0.105);
+  --rx-mint-05-alpha: hsla(173, 100%, 49.8%, 0.144);
+  --rx-mint-06-alpha: hsla(172, 100%, 49.8%, 0.192);
+  --rx-mint-07-alpha: hsla(171, 100%, 49.9%, 0.266);
+  --rx-mint-08-alpha: hsla(169, 100%, 49.9%, 0.366);
+  --rx-mint-09-alpha: hsla(167, 99.8%, 75.0%, 0.870);
+  --rx-mint-10-alpha: hsla(163, 99.9%, 80.7%, 0.948);
+  --rx-mint-11-alpha: hsla(167, 99.9%, 58.7%, 0.796);
+  --rx-mint-12-alpha: hsla(169, 100%, 96.2%, 0.980);
+  --rx-olive-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-olive-02-alpha: hsla(91, 97.0%, 84.0%, 0.026);
+  --rx-olive-03-alpha: hsla(101, 87.4%, 87.7%, 0.057);
+  --rx-olive-04-alpha: hsla(92, 91.8%, 94.1%, 0.078);
+  --rx-olive-05-alpha: hsla(101, 92.6%, 93.5%, 0.104);
+  --rx-olive-06-alpha: hsla(102, 91.1%, 94.6%, 0.130);
+  --rx-olive-07-alpha: hsla(102, 92.5%, 95.9%, 0.173);
+  --rx-olive-08-alpha: hsla(107, 100%, 96.5%, 0.250);
+  --rx-olive-09-alpha: hsla(110, 98.3%, 94.1%, 0.397);
+  --rx-olive-10-alpha: hsla(109, 99.6%, 95.3%, 0.457);
+  --rx-olive-11-alpha: hsla(113, 95.3%, 97.2%, 0.600);
+  --rx-olive-12-alpha: hsla(120, 93.5%, 99.6%, 0.927);
+  --rx-orange-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-orange-02-alpha: hsla(13, 100%, 49.7%, 0.054);
+  --rx-orange-03-alpha: hsla(20, 100%, 49.7%, 0.117);
+  --rx-orange-04-alpha: hsla(23, 100%, 49.8%, 0.166);
+  --rx-orange-05-alpha: hsla(23, 99.4%, 50.1%, 0.215);
+  --rx-orange-06-alpha: hsla(23, 99.8%, 51.1%, 0.286);
+  --rx-orange-07-alpha: hsla(23, 99.7%, 50.6%, 0.389);
+  --rx-orange-08-alpha: hsla(24, 100%, 49.9%, 0.523);
+  --rx-orange-09-alpha: hsla(24, 99.9%, 51.6%, 0.965);
+  --rx-orange-10-alpha: hsla(25, 100%, 58.6%, 0.980);
+  --rx-orange-11-alpha: hsla(24, 100%, 62.4%, 0.980);
+  --rx-orange-12-alpha: hsla(26, 100%, 94.2%, 0.980);
+  --rx-pink-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-pink-02-alpha: hsla(320, 98.1%, 64.1%, 0.036);
+  --rx-pink-03-alpha: hsla(320, 99.1%, 63.1%, 0.121);
+  --rx-pink-04-alpha: hsla(320, 99.5%, 62.7%, 0.170);
+  --rx-pink-05-alpha: hsla(319, 99.7%, 61.5%, 0.219);
+  --rx-pink-06-alpha: hsla(322, 99.4%, 60.8%, 0.291);
+  --rx-pink-07-alpha: hsla(321, 99.6%, 58.7%, 0.407);
+  --rx-pink-08-alpha: hsla(322, 99.7%, 55.4%, 0.608);
+  --rx-pink-09-alpha: hsla(322, 100%, 64.6%, 0.817);
+  --rx-pink-10-alpha: hsla(323, 100%, 66.3%, 0.875);
+  --rx-pink-11-alpha: hsla(325, 99.9%, 68.6%, 0.960);
+  --rx-pink-12-alpha: hsla(314, 100%, 96.9%, 0.980);
+  --rx-plum-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-plum-02-alpha: hsla(300, 96.4%, 58.4%, 0.036);
+  --rx-plum-03-alpha: hsla(300, 99.4%, 67.1%, 0.102);
+  --rx-plum-04-alpha: hsla(295, 99.8%, 66.3%, 0.155);
+  --rx-plum-05-alpha: hsla(295, 99.4%, 67.1%, 0.204);
+  --rx-plum-06-alpha: hsla(294, 99.0%, 67.8%, 0.262);
+  --rx-plum-07-alpha: hsla(294, 99.9%, 67.7%, 0.363);
+  --rx-plum-08-alpha: hsla(292, 99.8%, 67.5%, 0.527);
+  --rx-plum-09-alpha: hsla(292, 99.9%, 69.2%, 0.695);
+  --rx-plum-10-alpha: hsla(295, 99.9%, 70.8%, 0.748);
+  --rx-plum-11-alpha: hsla(300, 99.8%, 72.9%, 0.828);
+  --rx-plum-12-alpha: hsla(300, 100%, 97.1%, 0.980);
+  --rx-purple-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-purple-02-alpha: hsla(280, 96.5%, 57.5%, 0.045);
+  --rx-purple-03-alpha: hsla(279, 98.7%, 62.8%, 0.129);
+  --rx-purple-04-alpha: hsla(279, 99.1%, 64.0%, 0.191);
+  --rx-purple-05-alpha: hsla(278, 99.8%, 64.2%, 0.248);
+  --rx-purple-06-alpha: hsla(276, 99.6%, 64.6%, 0.328);
+  --rx-purple-07-alpha: hsla(274, 99.9%, 64.6%, 0.456);
+  --rx-purple-08-alpha: hsla(272, 99.7%, 64.6%, 0.660);
+  --rx-purple-09-alpha: hsla(272, 99.9%, 69.1%, 0.748);
+  --rx-purple-10-alpha: hsla(273, 100%, 71.3%, 0.801);
+  --rx-purple-11-alpha: hsla(275, 99.9%, 75.3%, 0.934);
+  --rx-purple-12-alpha: hsla(286, 100%, 97.1%, 0.980);
+  --rx-red-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-red-02-alpha: hsla(5, 98.5%, 53.8%, 0.045);
+  --rx-red-03-alpha: hsla(359, 99.1%, 61.1%, 0.130);
+  --rx-red-04-alpha: hsla(358, 98.8%, 61.0%, 0.184);
+  --rx-red-05-alpha: hsla(357, 99.6%, 60.3%, 0.237);
+  --rx-red-06-alpha: hsla(358, 99.6%, 60.3%, 0.322);
+  --rx-red-07-alpha: hsla(357, 100%, 59.5%, 0.442);
+  --rx-red-08-alpha: hsla(358, 99.8%, 59.1%, 0.621);
+  --rx-red-09-alpha: hsla(358, 100%, 65.5%, 0.884);
+  --rx-red-10-alpha: hsla(358, 100%, 67.5%, 0.942);
+  --rx-red-11-alpha: hsla(358, 100%, 69.7%, 0.980);
+  --rx-red-12-alpha: hsla(352, 100%, 97.1%, 0.980);
+  --rx-sage-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-sage-02-alpha: hsla(123, 94.4%, 91.4%, 0.026);
+  --rx-sage-03-alpha: hsla(123, 82.9%, 91.0%, 0.057);
+  --rx-sage-04-alpha: hsla(124, 97.9%, 94.5%, 0.082);
+  --rx-sage-05-alpha: hsla(125, 90.0%, 95.2%, 0.104);
+  --rx-sage-06-alpha: hsla(142, 95.1%, 94.8%, 0.134);
+  --rx-sage-07-alpha: hsla(143, 92.8%, 95.7%, 0.173);
+  --rx-sage-08-alpha: hsla(146, 94.7%, 95.3%, 0.255);
+  --rx-sage-09-alpha: hsla(151, 98.2%, 94.4%, 0.397);
+  --rx-sage-10-alpha: hsla(148, 99.5%, 95.5%, 0.457);
+  --rx-sage-11-alpha: hsla(152, 95.1%, 97.3%, 0.600);
+  --rx-sage-12-alpha: hsla(149, 93.3%, 99.6%, 0.927);
+  --rx-sand-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-sand-02-alpha: hsla(60, 89.8%, 91.4%, 0.026);
+  --rx-sand-03-alpha: hsla(60, 95.5%, 92.5%, 0.056);
+  --rx-sand-04-alpha: hsla(60, 75.6%, 96.4%, 0.078);
+  --rx-sand-05-alpha: hsla(60, 81.9%, 95.2%, 0.104);
+  --rx-sand-06-alpha: hsla(41, 87.6%, 94.8%, 0.134);
+  --rx-sand-07-alpha: hsla(60, 95.4%, 96.2%, 0.172);
+  --rx-sand-08-alpha: hsla(49, 93.5%, 95.7%, 0.254);
+  --rx-sand-09-alpha: hsla(52, 97.3%, 96.2%, 0.391);
+  --rx-sand-10-alpha: hsla(52, 97.8%, 96.7%, 0.451);
+  --rx-sand-11-alpha: hsla(51, 97.0%, 97.8%, 0.597);
+  --rx-sand-12-alpha: hsla(60, 88.7%, 99.8%, 0.923);
+  --rx-sky-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-sky-02-alpha: hsla(208, 100%, 49.8%, 0.045);
+  --rx-sky-03-alpha: hsla(201, 100%, 49.8%, 0.099);
+  --rx-sky-04-alpha: hsla(201, 100%, 50.0%, 0.148);
+  --rx-sky-05-alpha: hsla(200, 100%, 49.8%, 0.198);
+  --rx-sky-06-alpha: hsla(199, 100%, 49.9%, 0.256);
+  --rx-sky-07-alpha: hsla(199, 100%, 49.9%, 0.337);
+  --rx-sky-08-alpha: hsla(199, 100%, 50.0%, 0.453);
+  --rx-sky-09-alpha: hsla(192, 100%, 70.8%, 0.980);
+  --rx-sky-10-alpha: hsla(190, 100%, 77.6%, 0.980);
+  --rx-sky-11-alpha: hsla(192, 99.9%, 59.6%, 0.924);
+  --rx-sky-12-alpha: hsla(189, 100%, 96.8%, 0.980);
+  --rx-slate-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-slate-02-alpha: hsla(181, 98.9%, 91.8%, 0.026);
+  --rx-slate-03-alpha: hsla(182, 86.7%, 91.4%, 0.057);
+  --rx-slate-04-alpha: hsla(209, 86.7%, 93.9%, 0.083);
+  --rx-slate-05-alpha: hsla(200, 90.3%, 93.4%, 0.109);
+  --rx-slate-06-alpha: hsla(209, 95.3%, 93.5%, 0.139);
+  --rx-slate-07-alpha: hsla(204, 98.5%, 93.9%, 0.182);
+  --rx-slate-08-alpha: hsla(209, 94.0%, 94.7%, 0.265);
+  --rx-slate-09-alpha: hsla(207, 97.3%, 94.0%, 0.412);
+  --rx-slate-10-alpha: hsla(209, 99.4%, 95.2%, 0.472);
+  --rx-slate-11-alpha: hsla(208, 98.7%, 96.8%, 0.615);
+  --rx-slate-12-alpha: hsla(211, 86.7%, 99.6%, 0.927);
+  --rx-teal-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-teal-02-alpha: hsla(171, 100%, 49.2%, 0.031);
+  --rx-teal-03-alpha: hsla(172, 100%, 49.7%, 0.070);
+  --rx-teal-04-alpha: hsla(175, 100%, 49.7%, 0.105);
+  --rx-teal-05-alpha: hsla(174, 98.9%, 50.1%, 0.140);
+  --rx-teal-06-alpha: hsla(174, 100%, 51.8%, 0.187);
+  --rx-teal-07-alpha: hsla(173, 99.6%, 53.2%, 0.257);
+  --rx-teal-08-alpha: hsla(174, 99.6%, 53.3%, 0.366);
+  --rx-teal-09-alpha: hsla(173, 99.9%, 54.6%, 0.609);
+  --rx-teal-10-alpha: hsla(174, 99.9%, 53.8%, 0.670);
+  --rx-teal-11-alpha: hsla(174, 100%, 52.0%, 0.748);
+  --rx-teal-12-alpha: hsla(166, 98.6%, 95.0%, 0.979);
+  --rx-tomato-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-tomato-02-alpha: hsla(5, 100%, 49.6%, 0.058);
+  --rx-tomato-03-alpha: hsla(6, 99.6%, 54.9%, 0.133);
+  --rx-tomato-04-alpha: hsla(6, 99.2%, 55.4%, 0.191);
+  --rx-tomato-05-alpha: hsla(6, 99.5%, 55.8%, 0.244);
+  --rx-tomato-06-alpha: hsla(7, 99.7%, 55.9%, 0.319);
+  --rx-tomato-07-alpha: hsla(8, 99.8%, 54.8%, 0.434);
+  --rx-tomato-08-alpha: hsla(10, 99.8%, 53.5%, 0.598);
+  --rx-tomato-09-alpha: hsla(10, 100%, 59.7%, 0.885);
+  --rx-tomato-10-alpha: hsla(10, 100%, 63.6%, 0.916);
+  --rx-tomato-11-alpha: hsla(10, 99.7%, 66.4%, 0.939);
+  --rx-tomato-12-alpha: hsla(12, 100%, 97.1%, 0.980);
+  --rx-violet-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-violet-02-alpha: hsla(258, 98.2%, 61.0%, 0.054);
+  --rx-violet-03-alpha: hsla(252, 98.8%, 65.8%, 0.148);
+  --rx-violet-04-alpha: hsla(253, 99.7%, 65.7%, 0.219);
+  --rx-violet-05-alpha: hsla(252, 99.7%, 66.4%, 0.286);
+  --rx-violet-06-alpha: hsla(251, 99.7%, 66.2%, 0.371);
+  --rx-violet-07-alpha: hsla(250, 99.7%, 66.3%, 0.514);
+  --rx-violet-08-alpha: hsla(250, 99.7%, 66.1%, 0.733);
+  --rx-violet-09-alpha: hsla(252, 99.9%, 70.3%, 0.786);
+  --rx-violet-10-alpha: hsla(251, 99.9%, 72.9%, 0.844);
+  --rx-violet-11-alpha: hsla(250, 100%, 77.9%, 0.980);
+  --rx-violet-12-alpha: hsla(254, 100%, 97.5%, 0.980);
+  --rx-yellow-01-alpha: hsla(0, 0%, 0%, 0);
+  --rx-yellow-02-alpha: hsla(49, 100%, 49.1%, 0.027);
+  --rx-yellow-03-alpha: hsla(45, 100%, 49.7%, 0.071);
+  --rx-yellow-04-alpha: hsla(46, 100%, 49.7%, 0.111);
+  --rx-yellow-05-alpha: hsla(47, 100%, 49.9%, 0.150);
+  --rx-yellow-06-alpha: hsla(51, 100%, 49.8%, 0.199);
+  --rx-yellow-07-alpha: hsla(51, 99.8%, 53.6%, 0.269);
+  --rx-yellow-08-alpha: hsla(51, 100%, 49.9%, 0.371);
+  --rx-yellow-09-alpha: hsla(53, 100%, 52.0%, 0.956);
+  --rx-yellow-10-alpha: hsla(56, 100%, 68.4%, 0.980);
+  --rx-yellow-11-alpha: hsla(48, 100%, 50.0%, 0.934);
+  --rx-yellow-12-alpha: hsla(60, 100%, 91.8%, 0.980);
+}

+ 316 - 0
resources/css/shui.css

@@ -0,0 +1,316 @@
+.ui__button {
+  @apply font-medium relative flex items-center justify-center gap-1;
+  border-radius: 0.25rem;
+  /* box-shadow: inset 0 2px 0 0px rgba(255, 255, 255, 0.2), */
+  /*             inset 0 -2px 0 0px rgba(0, 0, 0, 0.1); */
+  /* background-image: linear-gradient(white, white), */
+  /*                   linear-gradient(to bottom, green, gold); */
+  /* background-origin: border-box; */
+  /* background-clip: content-box, border-box; */
+}
+
+.ui__button-size-sm {
+  @apply text-xs py-1 px-2;
+}
+
+.ui__button-size-md {
+  @apply text-sm py-1 px-3;
+}
+
+.ui__button-tiled {
+  padding: 0 !important;
+  gap: 0 !important;
+}
+
+.ui__button-tiled .ui__button__tile {
+  @apply flex items-center justify-center text-center;
+}
+
+.ui__button-tiled.ui__button-size-md .ui__button__tile {
+  @apply h-6 w-6;
+}
+
+.ui__button-tiled.ui__button-size-sm .ui__button__tile {
+    @apply h-4 w-4;
+}
+
+.ui__button__tile-separator {
+  @apply w-px h-full bg-gray-08-alpha;
+}
+
+.ui__button-theme-text {
+  background: none;
+  box-shadow: none;
+}
+
+.ui__button-theme-gray {
+  background: or(--lx-gray-06, --ls-quaternary-background);
+}
+
+.ui__button-theme-gray:hover {
+  background: or(--lx-gray-05, --ls-quaternary-background);
+}
+
+.ui__button-theme-gray:active {
+  background: or(--lx-gray-04, --ls-quaternary-background);
+}
+
+.dark .ui__button-theme,
+.dark .ui__button-theme-color {
+  background: or(--lx-accent-09, --rx-blue-09);
+}
+
+.dark .ui__button-theme:hover,
+.dark .ui__button-theme-color:hover {
+  background: or(--lx-accent-10, --rx-blue-10);
+}
+
+.dark .ui__button-theme:active,
+.dark .ui__button-theme-color:active {
+  background: or(--lx-accent-08, --rx-blue-08);
+}
+
+.dark .ui__button-theme-gray {
+  background: or(--lx-gray-05, --ls-quaternary-background);
+}
+
+.dark .ui__button-theme-gray:hover {
+  background: or(--lx-gray-06, --ls-quaternary-background);
+}
+
+.dark .ui__button-theme-gray:active {
+  background: or(--lx-gray-04, --ls-quaternary-background);
+}
+
+.ui__button-theme-gradient {
+  --depth-shadow-from: rgba(2, 23, 53, 0.70);
+  --depth-shadow-to: rgba(2, 23, 53, 0.00);
+}
+
+.ui__button-theme-gradient:hover {
+  --depth-shadow-from: rgba(2, 23, 53, 0.30);
+  --depth-shadow-to: rgba(2, 23, 53, 0.00);
+}
+
+.ui__button-theme-gradient:active {
+  --depth-shadow-from: rgba(0, 0, 0, 0);
+  --depth-shadow-to: rgba(0, 0, 0, 0);
+}
+
+.ui__button-theme-gradient.ui__button-,
+.ui__button-theme-gradient.ui__button-custom,
+.ui__button-theme-gradient.ui__button-indigo,
+.ui__button-theme-gradient.ui__button-blue,
+.ui__button-theme-gradient.ui__button-sky,
+.ui__button-theme-gradient.ui__button-cyan {
+  background: linear-gradient(37deg, var(--depth-shadow-from) 0%, var(--depth-shadow-to) 100%),
+              linear-gradient(135deg, var(--rx-indigo-09) 0%, var(--rx-blue-09) 33.85%, var(--rx-sky-09) 64.06%, var(--rx-cyan-09) 100%);
+}
+
+.ui__button-theme-gradient.ui__button-tomato,
+.ui__button-theme-gradient.ui__button-red,
+.ui__button-theme-gradient.ui__button-crimson,
+.ui__button-theme-gradient.ui__button-pink,
+.ui__button-theme-gradient.ui__button-plum,
+.ui__button-theme-gradient.ui__button-purple,
+.ui__button-theme-gradient.ui__button-violet {
+  background: linear-gradient(37deg, var(--depth-shadow-from) 0%, var(--depth-shadow-to) 100%),
+              linear-gradient(135deg, var(--rx-tomato-09) 0%, var(--rx-red-09) 16.66%, var(--rx-crimson-09) 33.33%, var(--rx-pink-09) 50%, var(--rx-plum-09) 66.66%, var(--rx-purple-09) 83.33%, var(--rx-violet-09) 100%);
+}
+
+.ui__button-theme-gradient.ui__button-green,
+.ui__button-theme-gradient.ui__button-mint,
+.ui__button-theme-gradient.ui__button-teal {
+  background: linear-gradient(37deg, var(--depth-shadow-from) 0%, var(--depth-shadow-to) 100%),
+              linear-gradient(135deg, var(--rx-teal-09) 0%, var(--rx-mint-09) 50%, var(--rx-green-09) 100%);
+}
+
+.ui__button-theme-gradient.ui__button-grass,
+.ui__button-theme-gradient.ui__button-lime {
+  background: linear-gradient(37deg, var(--depth-shadow-from) 0%, var(--depth-shadow-to) 100%),
+              linear-gradient(135deg, var(--rx-grass-09) 0%, var(--rx-lime-09) 100%);
+}
+
+.ui__button-theme-gradient.ui__button-yellow,
+.ui__button-theme-gradient.ui__button-amber,
+.ui__button-theme-gradient.ui__button-orange,
+.ui__button-theme-gradient.ui__button-brown {
+  background: linear-gradient(37deg, var(--depth-shadow-from) 0%, var(--depth-shadow-to) 100%),
+              linear-gradient(135deg, var(--rx-yellow-09) 0%, var(--rx-amber-09) 33.33%, var(--rx-orange-09) 66.66%, var(--rx-brown-09) 100%);
+}
+
+/* .ui__button-theme-gradient:hover { */
+  /* background: linear-gradient(37deg, rgba(2, 23, 53, 0.30) 0%, rgba(2, 23, 53, 0.00) 100%), linear-gradient(135deg, #8AE8FF 0%, #5373E7 33.85%, #369EFF 64.06%, #00B1CC 100%); */
+/* } */
+
+/* Shadow/xs */
+/* box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); */
+/* } */
+
+.ui__button-depth-1 {
+  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.15),
+              inset 0 -1px 0 0 rgba(0, 0, 0, 0.15);
+}
+
+.ui__button-depth-1:hover {
+  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.15),
+              inset 0 -2px 0 0 rgba(0, 0, 0, 0.15);
+}
+
+.ui__button-depth-2 {
+  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.2),
+              inset 0 -1px 0 0 rgba(0, 0, 0, 0.2);
+}
+
+/* .ui__button-depth-1:before { */
+/*   @apply absolute inset-0; */
+/*   border-radius: 0.25rem; */
+/*   content: ""; */
+/*   padding: 1px; */
+/*   background: linear-gradient(to bottom, rgba(255,255,255,0.3), transparent); */
+/*   -webkit-mask: linear-gradient(#fff 0 0) content-box, */
+/*                 linear-gradient(#fff 0 0); */
+/*   -webkit-mask-composite: xor; */
+/*   mask-composite: exclude; */
+/* } */
+
+/* .ui__button-depth-1:after { */
+/*   @apply absolute inset-0; */
+/*   border-radius: 0.25rem; */
+/*   content: ""; */
+/*   padding: 1px; */
+/*   background: linear-gradient(to top, rgba(0,0,0,0.2), transparent); */
+/*   -webkit-mask: linear-gradient(#fff 0 0) content-box, */
+/*                 linear-gradient(#fff 0 0); */
+/*   -webkit-mask-composite: xor; */
+/*   mask-composite: exclude; */
+/* } */
+
+/* .ui__button-depth-2:before { */
+/*   @apply absolute inset-0; */
+/*   border-radius: 0.25rem; */
+/*   content: ""; */
+/*   padding: 1px; */
+/*   background: linear-gradient(to bottom, rgba(255,255,255,0.6), transparent); */
+/*   -webkit-mask: linear-gradient(#fff 0 0) content-box, */
+/*                 linear-gradient(#fff 0 0); */
+/*   -webkit-mask-composite: xor; */
+/*   mask-composite: exclude; */
+/* } */
+
+/* .ui__button-depth-2:after { */
+/*   @apply absolute inset-0; */
+/*   border-radius: 0.25rem; */
+/*   content: ""; */
+/*   padding: 1px; */
+/*   background: linear-gradient(to top, rgba(0,0,0,0.4), transparent); */
+/*   -webkit-mask: linear-gradient(#fff 0 0) content-box, */
+/*                 linear-gradient(#fff 0 0); */
+/*   -webkit-mask-composite: xor; */
+/*   mask-composite: exclude; */
+/* } */
+
+.ui__button-shortcut-key:first-of-type {
+  @apply ml-2;
+}
+
+.ui__button-shortcut-key {
+  @apply text-xs font-normal h-5 w-5 flex items-center justify-center rounded bg-gray-06-alpha;
+}
+
+.ui__cmdk-quick-capture-glow::before {
+  @apply absolute inset-0;
+  pointer-events: none;
+  border-radius: 0.25rem;
+  content: "";
+  padding: 1px;
+  background: linear-gradient(to bottom, var(--lx-accent-10), transparent);
+  -webkit-mask: linear-gradient(#fff 0 0) content-box,
+                linear-gradient(#fff 0 0);
+  -webkit-mask-composite: xor;
+  mask-composite: exclude;
+}
+
+.ui__button-muted {
+  transition: opacity 200 ease-in;
+  opacity: 0.4;
+}
+
+.ui__button-muted:hover {
+  opacity: 1;
+}
+
+.ui__button-theme-gray.ui__button-color-custom  { background-color: var(--ls-tertiary-background-color);
+                                                      color: var(--ls-secondary-text-color, white); }
+.ui__button-theme-color.ui__button-color-custom  { background-color: hsl(var(--ls-button-background-hsl) / 0.9);
+                                                       color: white; }
+
+.ui__button-theme-color.ui__button-color-custom:hover {
+    background: var(--ls-button-background);
+}
+
+.ui__button-theme-color.ui__button-color-lime    { color: white; background-color: var(--rx-lime-09); &:hover { background-color: var(--rx-lime-10); } &:active { background-color: var(--rx-lime-08); }}
+.ui__button-theme-color.ui__button-color-orange  { color: white; background-color: var(--rx-orange-09); &:hover { background-color: var(--rx-orange-10); } &:active { background-color: var(--rx-orange-08); }}
+.ui__button-theme-color.ui__button-color-gray    { color: white; background-color: var(--rx-gray-09); &:hover { background-color: var(--rx-gray-10); } &:active { background-color: var(--rx-gray-08); }}
+.ui__button-theme-color.ui__button-color-sand    { color: white; background-color: var(--rx-sand-09); &:hover { background-color: var(--rx-sand-10); } &:active { background-color: var(--rx-sand-08); }}
+.ui__button-theme-color.ui__button-color-crimson { color: white; background-color: var(--rx-crimson-09); &:hover { background-color: var(--rx-crimson-10); } &:active { background-color: var(--rx-crimson-08); }}
+.ui__button-theme-color.ui__button-color-yellow  { color: white; background-color: var(--rx-yellow-09); &:hover { background-color: var(--rx-yellow-10); } &:active { background-color: var(--rx-yellow-08); }}
+.ui__button-theme-color.ui__button-color-green   { color: white; background-color: var(--rx-green-09); &:hover { background-color: var(--rx-green-10); } &:active { background-color: var(--rx-green-08); }}
+.ui__button-theme-color.ui__button-color-indigo  { color: white; background-color: var(--rx-indigo-09); &:hover { background-color: var(--rx-indigo-10); } &:active { background-color: var(--rx-indigo-08); }}
+.ui__button-theme-color.ui__button-color-cyan    { color: white; background-color: var(--rx-cyan-09); &:hover { background-color: var(--rx-cyan-10); } &:active { background-color: var(--rx-cyan-08); }}
+.ui__button-theme-color.ui__button-color-violet  { color: white; background-color: var(--rx-violet-09); &:hover { background-color: var(--rx-violet-10); } &:active { background-color: var(--rx-violet-08); }}
+.ui__button-theme-color.ui__button-color-bronze  { color: white; background-color: var(--rx-bronze-09); &:hover { background-color: var(--rx-bronze-10); } &:active { background-color: var(--rx-bronze-08); }}
+.ui__button-theme-color.ui__button-color-slate   { color: white; background-color: var(--rx-slate-09); &:hover { background-color: var(--rx-slate-10); } &:active { background-color: var(--rx-slate-08); }}
+.ui__button-theme-color.ui__button-color-gold    { color: white; background-color: var(--rx-gold-09); &:hover { background-color: var(--rx-gold-10); } &:active { background-color: var(--rx-gold-08); }}
+.ui__button-theme-color.ui__button-color-sage    { color: white; background-color: var(--rx-sage-09); &:hover { background-color: var(--rx-sage-10); } &:active { background-color: var(--rx-sage-08); }}
+.ui__button-theme-color.ui__button-color-mauve   { color: white; background-color: var(--rx-mauve-09); &:hover { background-color: var(--rx-mauve-10); } &:active { background-color: var(--rx-mauve-08); }}
+.ui__button-theme-color.ui__button-color-mint    { color: white; background-color: var(--rx-mint-09); &:hover { background-color: var(--rx-mint-10); } &:active { background-color: var(--rx-mint-08); }}
+.ui__button-theme-color.ui__button-color-red     { color: white; background-color: var(--rx-red-09); &:hover { background-color: var(--rx-red-10); } &:active { background-color: var(--rx-red-08); }}
+.ui__button-theme-color.ui__button-color-blue    { color: white; background-color: var(--rx-blue-09); &:hover { background-color: var(--rx-blue-10); } &:active { background-color: var(--rx-blue-08); }}
+.ui__button-theme-color.ui__button-color-grass   { color: white; background-color: var(--rx-grass-09); &:hover { background-color: var(--rx-grass-10); } &:active { background-color: var(--rx-grass-08); }}
+.ui__button-theme-color.ui__button-color-plum    { color: white; background-color: var(--rx-plum-09); &:hover { background-color: var(--rx-plum-10); } &:active { background-color: var(--rx-plum-08); }}
+.ui__button-theme-color.ui__button-color-pink    { color: white; background-color: var(--rx-pink-09); &:hover { background-color: var(--rx-pink-10); } &:active { background-color: var(--rx-pink-08); }}
+.ui__button-theme-color.ui__button-color-teal    { color: white; background-color: var(--rx-teal-09); &:hover { background-color: var(--rx-teal-10); } &:active { background-color: var(--rx-teal-08); }}
+.ui__button-theme-color.ui__button-color-amber   { color: white; background-color: var(--rx-amber-09); &:hover { background-color: var(--rx-amber-10); } &:active { background-color: var(--rx-amber-08); }}
+.ui__button-theme-color.ui__button-color-purple  { color: white; background-color: var(--rx-purple-09); &:hover { background-color: var(--rx-purple-10); } &:active { background-color: var(--rx-purple-08); }}
+.ui__button-theme-color.ui__button-color-brown   { color: white; background-color: var(--rx-brown-09); &:hover { background-color: var(--rx-brown-10); } &:active { background-color: var(--rx-brown-08); }}
+.ui__button-theme-color.ui__button-color-sky     { color: white; background-color: var(--rx-sky-09); &:hover { background-color: var(--rx-sky-10); } &:active { background-color: var(--rx-sky-08); }}
+.ui__button-theme-color.ui__button-color-olive   { color: white; background-color: var(--rx-olive-09); &:hover { background-color: var(--rx-olive-10); } &:active { background-color: var(--rx-olive-08); }}
+.ui__button-theme-color.ui__button-color-tomato  { color: white; background-color: var(--rx-tomato-09); &:hover { background-color: var(--rx-tomato-10); } &:active { background-color: var(--rx-tomato-08); }}
+
+.dark .ui__button-theme-color.ui__button-color-lime    { background-color: var(--rx-lime-09); &:hover { background-color: var(--rx-lime-08); } &:active { background-color: var(--rx-lime-07); }}
+.dark .ui__button-theme-color.ui__button-color-orange  { background-color: var(--rx-orange-09); &:hover { background-color: var(--rx-orange-08); } &:active { background-color: var(--rx-orange-07); }}
+.dark .ui__button-theme-color.ui__button-color-gray    { background-color: var(--rx-gray-09); &:hover { background-color: var(--rx-gray-08); } &:active { background-color: var(--rx-gray-07); }}
+.dark .ui__button-theme-color.ui__button-color-sand    { background-color: var(--rx-sand-09); &:hover { background-color: var(--rx-sand-08); } &:active { background-color: var(--rx-sand-07); }}
+.dark .ui__button-theme-color.ui__button-color-crimson { background-color: var(--rx-crimson-09); &:hover { background-color: var(--rx-crimson-08); } &:active { background-color: var(--rx-crimson-07); }}
+.dark .ui__button-theme-color.ui__button-color-yellow  { background-color: var(--rx-yellow-09); &:hover { background-color: var(--rx-yellow-08); } &:active { background-color: var(--rx-yellow-07); }}
+.dark .ui__button-theme-color.ui__button-color-green   { background-color: var(--rx-green-09); &:hover { background-color: var(--rx-green-08); } &:active { background-color: var(--rx-green-07); }}
+.dark .ui__button-theme-color.ui__button-color-indigo  { background-color: var(--rx-indigo-09); &:hover { background-color: var(--rx-indigo-08); } &:active { background-color: var(--rx-indigo-07); }}
+.dark .ui__button-theme-color.ui__button-color-cyan    { background-color: var(--rx-cyan-09); &:hover { background-color: var(--rx-cyan-08); } &:active { background-color: var(--rx-cyan-07); }}
+.dark .ui__button-theme-color.ui__button-color-violet  { background-color: var(--rx-violet-09); &:hover { background-color: var(--rx-violet-08); } &:active { background-color: var(--rx-violet-07); }}
+.dark .ui__button-theme-color.ui__button-color-bronze  { background-color: var(--rx-bronze-09); &:hover { background-color: var(--rx-bronze-08); } &:active { background-color: var(--rx-bronze-07); }}
+.dark .ui__button-theme-color.ui__button-color-slate   { background-color: var(--rx-slate-09); &:hover { background-color: var(--rx-slate-08); } &:active { background-color: var(--rx-slate-07); }}
+.dark .ui__button-theme-color.ui__button-color-gold    { background-color: var(--rx-gold-09); &:hover { background-color: var(--rx-gold-08); } &:active { background-color: var(--rx-gold-07); }}
+.dark .ui__button-theme-color.ui__button-color-sage    { background-color: var(--rx-sage-09); &:hover { background-color: var(--rx-sage-08); } &:active { background-color: var(--rx-sage-07); }}
+.dark .ui__button-theme-color.ui__button-color-mauve   { background-color: var(--rx-mauve-09); &:hover { background-color: var(--rx-mauve-08); } &:active { background-color: var(--rx-mauve-07); }}
+.dark .ui__button-theme-color.ui__button-color-mint    { background-color: var(--rx-mint-09); &:hover { background-color: var(--rx-mint-08); } &:active { background-color: var(--rx-mint-07); }}
+.dark .ui__button-theme-color.ui__button-color-red     { background-color: var(--rx-red-09); &:hover { background-color: var(--rx-red-08); } &:active { background-color: var(--rx-red-07); }}
+.dark .ui__button-theme-color.ui__button-color-blue    { background-color: var(--rx-blue-09); &:hover { background-color: var(--rx-blue-08); } &:active { background-color: var(--rx-blue-07); }}
+.dark .ui__button-theme-color.ui__button-color-grass   { background-color: var(--rx-grass-09); &:hover { background-color: var(--rx-grass-08); } &:active { background-color: var(--rx-grass-07); }}
+.dark .ui__button-theme-color.ui__button-color-plum    { background-color: var(--rx-plum-09); &:hover { background-color: var(--rx-plum-08); } &:active { background-color: var(--rx-plum-07); }}
+.dark .ui__button-theme-color.ui__button-color-pink    { background-color: var(--rx-pink-09); &:hover { background-color: var(--rx-pink-08); } &:active { background-color: var(--rx-pink-07); }}
+.dark .ui__button-theme-color.ui__button-color-teal    { background-color: var(--rx-teal-09); &:hover { background-color: var(--rx-teal-08); } &:active { background-color: var(--rx-teal-07); }}
+.dark .ui__button-theme-color.ui__button-color-amber   { background-color: var(--rx-amber-09); &:hover { background-color: var(--rx-amber-08); } &:active { background-color: var(--rx-amber-07); }}
+.dark .ui__button-theme-color.ui__button-color-purple  { background-color: var(--rx-purple-09); &:hover { background-color: var(--rx-purple-08); } &:active { background-color: var(--rx-purple-07); }}
+.dark .ui__button-theme-color.ui__button-color-brown   { background-color: var(--rx-brown-09); &:hover { background-color: var(--rx-brown-08); } &:active { background-color: var(--rx-brown-07); }}
+.dark .ui__button-theme-color.ui__button-color-sky     { background-color: var(--rx-sky-09); &:hover { background-color: var(--rx-sky-08); } &:active { background-color: var(--rx-sky-07); }}
+.dark .ui__button-theme-color.ui__button-color-olive   { background-color: var(--rx-olive-09); &:hover { background-color: var(--rx-olive-08); } &:active { background-color: var(--rx-olive-07); }}
+.dark .ui__button-theme-color.ui__button-color-tomato  { background-color: var(--rx-tomato-09); &:hover { background-color: var(--rx-tomato-08); } &:active { background-color: var(--rx-tomato-07); }}
+
+.ui__list-item-highlighted-span {
+  background-color: or(--lx-accent-06, --color-level-4);
+}
+
+.dark .ui__list-item-highlighted-span {
+  background-color: or(--lx-accent-08-alpha, --color-level-4);
+}

BIN
resources/img/dark-theme.png


BIN
resources/img/file-sync-unavailale-nonbacker-dark.png


BIN
resources/img/file-sync-unavailale-nonbacker-light.png


BIN
resources/img/file-sync-welcome-backer-dark.png


BIN
resources/img/file-sync-welcome-backer-light.png


BIN
resources/img/light-theme.png


BIN
resources/img/system-theme.png


BIN
resources/img/whiteboard-welcome-dark.png


BIN
resources/img/whiteboard-welcome-light.png


+ 1 - 1
resources/package.json

@@ -38,7 +38,7 @@
     "socks-proxy-agent": "8.0.2",
     "@sentry/electron": "2.5.1",
     "posthog-js": "1.10.2",
-    "@logseq/rsapi": "0.0.75",
+    "@logseq/rsapi": "0.0.76",
     "electron-deeplink": "1.0.10",
     "abort-controller": "3.0.0",
     "fastify": "latest",

+ 2 - 2
scripts/src/logseq/tasks/lang.clj

@@ -161,10 +161,10 @@
   {:fr #{:port :type :help/docs :search-item/page :shortcut.category/navigating :text/image
          :settings-of-plugins :code :on-boarding/section-pages :paginates/pages :right-side-bar/history-global
          :shortcut.category/plugins :whiteboard/rectangle :whiteboard/triangle}
-   :de #{:graph :host :plugins :port :right-side-bar/whiteboards :search-item/block
+   :de #{:graph :host :plugins :port :right-side-bar/whiteboards
          :settings-of-plugins :search-item/whiteboard :shortcut.category/navigating
          :settings-page/enable-tooltip :settings-page/enable-whiteboards :settings-page/plugin-system}
-   :es #{:settings-page/tab-general :settings-page/tab-editor :whiteboard/color :search/command-palette-tip-1 :right-side-bar/history-global}
+   :es #{:settings-page/tab-general :settings-page/tab-editor :whiteboard/color :right-side-bar/history-global}
    :it #{:plugins}
    :nl #{:plugins :type :left-side-bar/nav-recent-pages :plugin/update}
    :pl #{:port}

+ 75 - 0
src/main/frontend/colors.cljs

@@ -0,0 +1,75 @@
+(ns frontend.colors
+  "Colors used"
+  (:require [clojure.string :as str]))
+
+(def color-list [:tomato :red :crimson :pink :plum :purple :violet :indigo :blue :cyan :teal :green :grass :orange :brown])
+
+(def gray-pairing-map {:tomato :mauve :red :mauve :crimson :mauve :pink :mauve :plum :mauve :purple :mauve :violet :mauve
+                       :indigo :slate :blue :slate :sky :slate :cyan :slate
+                       :teal :sage :mint :sage :green :sage
+                       :grass :olive :lime :olive
+                       :yellow :sand :amber :sand :orange :sand :brown :sand})
+
+(defn variable
+  ; ([value])
+  ([color value] (variable color value false))
+  ([color value alpha?]
+   (str "var(--rx-" (name color) "-" (cond-> value keyword? name) (if alpha? "-alpha" "") ")")))
+
+(defn set-radix [color]
+  (let [style-tag (or (js/document.querySelector "style#color-variables")
+                      (js/document.createElement "style"))
+        steps ["01" "02" "03" "04" "05" "06" "07" "08" "09" "10" "11" "12" "01-alpha" "02-alpha" "03-alpha" "04-alpha" "05-alpha" "06-alpha" "07-alpha" "08-alpha" "09-alpha" "10-alpha" "11-alpha" "12-alpha"]
+        gray (get gray-pairing-map color)
+        accents (map #(str "--lx-accent-" % ": var(--rx-" (name color) "-" % "); ") steps)
+        grays   (map #(str "--lx-gray-" % ": var(--rx-" (name gray) "-" % "); ") steps)
+        translations (str "--ls-primary-background-color: var(--rx-" (name gray) "-01); "
+                          "--ls-secondary-background-color: var(--rx-" (name gray) "-02); "
+                          "--ls-tertiary-background-color: var(--rx-" (name gray) "-03); "
+                          "--ls-quaternary-background-color: var(--rx-" (name gray) "-04); "
+                          "--ls-link-text-color: var(--rx-" (name color) "-11); "
+                          "--ls-link-text-hover-color: var(--rx-" (name color) "-12); "
+                          "--ls-secondary-text-color: var(--rx-" (name gray) "-12); "
+                          "--ls-primary-text-color: var(--rx-" (name gray) "-11); "
+                          "--ls-border-color: var(--rx-" (name gray) "-05); "
+                          "--ls-secondary-border-color: var(--rx-" (name color) "-05); "
+                          "--ls-page-checkbox-color: var(--rx-" (name gray) "-07); "
+                          "--ls-selection-background-color: var(--rx-" (name gray) "-04-alpha); "
+                          "--ls-block-highlight-color: var(--rx-" (name gray) "-04); "
+                          "--ls-focus-ring-color: var(--rx-" (name color) "-09); "
+                          "--ls-table-tr-even-background-color: var(--rx-" (name gray) "-04); "
+                          "--ls-page-properties-background-color: var(--rx-" (name gray) "-04); "
+                          "--ls-cloze-text-color: var(--rx-" (name color) "-08); "
+                          "--ls-wb-stroke-color-default: var(--rx-" (name color) "-07); "
+                          "--ls-wb-background-color-default: var(--rx-" (name color) "-04); "
+                          "--ls-wb-text-color-default: var(--rx-" (name gray) "-12); "
+                          "--ls-a-chosen-bg: var(--rx-" (name gray) "-01); ")
+                          ; "--tl-selectStroke: var(--rx-" (name color) "-08); ")
+        tl-translations (str "[class^=\"tl-\"] { --tl-selectStroke: var(--rx-" (name color) "-09); }")]
+    (set! (.-id style-tag) "color-variables")
+    ; (set! (.-innerHTML style-tag) (str/join "\n" (flatten [":root {" accent gray "}"])))
+    (->> [":root { " accents grays translations " } body, .dark-theme, .light-theme {" accents grays translations "} " tl-translations]
+         (flatten)
+         (str/join "\n")
+         (set! (.-innerHTML style-tag)))
+    (js/document.head.appendChild style-tag)))
+
+(defn unset-radix []
+  (when-let [style-tag (js/document.querySelector "style#color-variables")]
+    (js/document.head.removeChild style-tag)))
+
+
+(defn linear-gradient [color-name color-stop gradient-level]
+  (let [color-index (.indexOf color-list color-name)
+        step (fn [dist]
+               (str "var(--rx-"
+                 (name (nth color-list (mod (+ color-index dist) (count color-list))))
+                 "-" (name color-stop) ")"))]
+    (case gradient-level
+      2 (str "linear-gradient(-45deg, " (step -1) " -50%, " (step 0) " 50%, " (step 1) " 150%)")
+      3 (str "linear-gradient(-45deg, " (step -1) " 0%, " (step 0) " 50%, " (step 1) " 100%)")
+      4 (str "linear-gradient(-45deg, " (step -2) " -16.66%, " (step -1) " 16.66%, " (step 0) " 50%, " (step 1) " 83.33%, " (step 2) " 116.66%)")
+      5 (str "linear-gradient(-45deg, " (step -2) " 0%, " (step -1) " 25%, " (step 0) " 50%, " (step 1) " 75%, " (step 2) " 100%)")
+      6 (str "linear-gradient(-45deg, " (step -3) " -10%, " (step -2) " 10%, " (step -1) " 30%, " (step 0) " 50%, " (step 1) " 70%, " (step 2) " 90%, " (step 3) " 110%)")
+      7 (str "linear-gradient(-45deg, " (step -3) " 0%, " (step -2) " 16.66%, " (step -1) " 33.33%, " (step 0) " 50%, " (step 1) " 66.66%, " (step 2) " 83.33%, " (step 3) " 100%)")
+      (str "linear-gradient(90deg, " (step 0) ", " (step 0) ")"))))

+ 24 - 12
src/main/frontend/common.css

@@ -103,6 +103,9 @@ html[data-theme='dark'] {
   --ls-success-background-color: var(--color-green-900);
   --ls-focus-ring-color: rgba(18, 98, 119, 0.5);
   --ls-header-button-background: #dee4ea;
+  --ls-left-sidebar-text-color: var(--lx-gray-11);
+  --ls-button-background-hsl: 200 98% 35%;
+  --ls-button-background: hsl(var(--ls-button-background-hsl));
   --color-level-1: var(--ls-secondary-background-color);
   --color-level-2: var(--ls-tertiary-background-color);
   --color-level-3: var(--ls-quaternary-background-color);
@@ -181,6 +184,9 @@ html[data-theme='light'] {
   --ls-success-background-color: var(--color-green-100);
   --ls-focus-ring-color: rgba(66, 133, 244, 0.5);
   --ls-header-button-background: rgba(15, 20, 25, 1);
+  --ls-left-sidebar-text-color: var(--lx-gray-12);
+  --ls-button-background-hsl: 200 98% 35%;
+  --ls-button-background: hsl(var(--ls-button-background-hsl));
   --color-level-1: var(--ls-secondary-background-color);
   --color-level-2: var(--ls-tertiary-background-color);
   --color-level-3: var(--ls-quaternary-background-color);
@@ -201,7 +207,7 @@ html {
 }
 
 body {
-  color: var(--ls-primary-text-color);
+  color: or(--ls-default-text-color, --lx-gray-12, --ls-primary-text-color);
   line-height: 1.5;
   background-color: transparent;
   min-height: 100%;
@@ -265,12 +271,12 @@ pre {
 
 a {
   cursor: pointer;
-  color: var(--ls-link-text-color, #045591);
+  color: or(--ls-anchor-link-text-color, --lx-accent-11, --ls-link-text-color, #045591);
   text-decoration: none;
 }
 
 a:hover {
-  color: var(--ls-link-text-hover-color, #000);
+  color: or(--ls-anchor-link-text-color-hover, --lx-accent-12, --ls-link-text-hover-color, #000);
 }
 
 code {
@@ -523,7 +529,7 @@ i.ti {
 /* region FIXME: override elements (?) */
 h1.title {
   margin-bottom: 1.5rem;
-  color: var(--ls-title-text-color, #222);
+  color: or(--ls-journal-title-color, --lx-gray-12, --ls-title-text-color, #222);
   font-size: var(--ls-page-title-size, 36px);
   font-weight: 500;
 }
@@ -569,7 +575,7 @@ button.menu {
 .menu-link:hover,
 button.pull:hover,
 button.menu:focus {
-  background-color: var(--ls-menu-hover-color, #f4f5f7);
+  background-color: or(--ls-settings-list-item-hover-background-color, --lx-gray-05, --ls-menu-hover-color, #f4f5f7);
 }
 
 .menu-links-wrapper,
@@ -577,7 +583,7 @@ button.menu:focus {
   @apply py-2 rounded-md shadow-lg overflow-y-auto;
 
   max-height: calc(100vh - 100px) !important;
-  background-color: var(--ls-primary-background-color, #fff);
+  background-color: or(--ls-settings-dropdown-background, --lx-gray-03, --ls-primary-background-color, #fff);
   min-width: 12rem;
 }
 
@@ -588,8 +594,8 @@ button.menu:focus {
 }
 
 .menu-link {
-  background-color: var(--ls-primary-background-color, #fff);
-  color: var(--ls-primary-text-color);
+  background-color: or(--ls-settings-dropdown-link-item-background, --lx-gray-03, --ls-primary-background-color, #fff);
+  color: or(--ls-settings-dropdown-link-text-color, --lx-gray-11, --ls-primary-text-color);
   user-select: none;
 }
 
@@ -680,13 +686,17 @@ a.tag {
   text-decoration: none;
   display: inline-block;
   cursor: pointer;
-  color: var(--ls-tag-text-color, #045591);
+  padding: 0 2px;
+  border-radius: 4px;
+  background: or(--ls-tag-background, --lx-accent-05, transparent);
+  color: or(--ls-tag-text, --lx-accent-11, --ls-tag-text-color, #045591);
   opacity: var(--ls-tag-text-opacity, 0.8);
 }
 
 a.tag:hover {
   opacity: var(--ls-tag-text-hover-opacity, 1);
-  color: var(--ls-tag-text-hover-color, #045591);
+  background: or(--ls-tag-background-hover, --lx-accent-06, transparent);
+  color: or(--ls-tag-text-hvoer, --lx-accent-12, --ls-tag-text-hover-color, #045591);
 }
 
 svg.note {
@@ -792,6 +802,8 @@ mark {
   font-family: MonoLisa, 'Fira Code', Monaco, Menlo, Consolas, 'COURIER NEW',
     monospace;
   letter-spacing: 0;
+  background-color: or(--ls-inline-code-background, --lx-gray-06, --ls-page-inline-code-bg-color, #eee);
+  color: or(--ls-inline-code-text, --lx-gray-11, --ls-page-inline-code-color);
   background-color: var(--ls-page-inline-code-bg-color, #eee);
   color: var(--ls-page-inline-code-color);
   text-rendering: optimizeSpeed;
@@ -828,11 +840,11 @@ a.tooltip-priority {
 }
 
 .page-reference:hover {
-  background: var(--ls-secondary-background-color);
+  background: or(--ls-page-reference-hover-background, --lx-accent-04-alpha, --ls-secondary-background-color);
 }
 
 .references-blocks .page-reference:hover {
-  background: var(--ls-tertiary-background-color);
+  background: or(--ls-page-reference-block-hover-background, --lx-accent-04-alpha, --ls-tertiary-background-color);
 }
 
 #head .fade-link {

+ 166 - 168
src/main/frontend/components/block.cljs

@@ -84,8 +84,6 @@
             [shadow.loader :as loader]
             [logseq.common.path :as path]))
 
-
-
 ;; local state
 (defonce *dragging?
   (atom false))
@@ -239,7 +237,7 @@
                 (and exist? (not loading?)))
           (content-fn)
           [:p.text-error.text-xs [:small.opacity-80
-                                    (util/format "%s not found!" (string/capitalize type))]])))))
+                                  (util/format "%s not found!" (string/capitalize type))]])))))
 
 (defn open-lightbox
   [e]
@@ -292,8 +290,8 @@
                          (js/setTimeout #(reset! *resizing-image? false) 200)))
           :onClick (fn [e]
                      (when @*resizing-image? (util/stop e)))}
-         (and (:width metadata) (not (util/mobile?)))
-         (assoc :style {:width (:width metadata)}))
+          (and (:width metadata) (not (util/mobile?)))
+          (assoc :style {:width (:width metadata)}))
         {})
       [:div.asset-container {:key "resize-asset-container"}
        [:img.rounded-sm.relative
@@ -330,19 +328,19 @@
                   (fn [e]
                     (when-let [block-id (:block/uuid config)]
                       (let [confirm-fn (ui/make-confirm-modal
-                                         {:title         (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
-                                          :sub-title     (if local? :asset/physical-delete "")
-                                          :sub-checkbox? local?
-                                          :on-confirm    (fn [_e {:keys [close-fn sub-selected]}]
-                                                           (close-fn)
-                                                           (editor-handler/delete-asset-of-block!
-                                                             {:block-id      block-id
-                                                              :local?        local?
-                                                              :delete-local? (and sub-selected (first sub-selected))
-                                                              :repo          (state/get-current-repo)
-                                                              :href          src
-                                                              :title         title
-                                                              :full-text     full-text}))})]
+                                        {:title         (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
+                                         :sub-title     (if local? :asset/physical-delete "")
+                                         :sub-checkbox? local?
+                                         :on-confirm    (fn [_e {:keys [close-fn sub-selected]}]
+                                                          (close-fn)
+                                                          (editor-handler/delete-asset-of-block!
+                                                           {:block-id      block-id
+                                                            :local?        local?
+                                                            :delete-local? (and sub-selected (first sub-selected))
+                                                            :repo          (state/get-current-repo)
+                                                            :href          src
+                                                            :title         title
+                                                            :full-text     full-text}))})]
                         (util/stop e)
                         (state/set-modal! confirm-fn))))}
                  (ui/icon "trash")])
@@ -461,7 +459,6 @@
                       (get-file-absolute-path config href)))]
          (resizable-image config title href metadata full_text false))))))
 
-
 (def timestamp-to-string export-common-handler/timestamp-to-string)
 
 (defn timestamp [{:keys [active _date _time _repetition _wday] :as t} kind]
@@ -700,7 +697,8 @@
           inner (page-inner config
                             page-name-in-block
                             page-name
-                            redirect-page-name page-entity contents-page? children html-export? label whiteboard-page?)]
+                            redirect-page-name page-entity contents-page? children html-export? label whiteboard-page?)
+          modal? (:modal/show? @state/state)]
       (cond
         (:breadcrumb? config)
         (or (:block/original-name page)
@@ -708,7 +706,8 @@
 
         (and (not (util/mobile?))
              (not preview?)
-             (not disable-preview?))
+             (not disable-preview?)
+             (not modal?))
         (page-preview-trigger (assoc config :children inner) page-name)
 
         :else
@@ -939,7 +938,10 @@
                           ;; default open block page
                           :else (route-handler/redirect-to-page! id))))))}
 
-               (if (and (not (util/mobile?)) (not (:preview? config)) (nil? block-type))
+               (if (and (not (util/mobile?))
+                        (not (:preview? config))
+                        (not (:modal/show? @state/state))
+                        (nil? block-type))
                  (ui/tippy {:html        (fn []
                                            [:div.tippy-wrapper.overflow-y-auto.p-4
                                             {:style {:width      735
@@ -955,8 +957,9 @@
                  inner)])
             [:span.warning.mr-1 {:title "Block ref invalid"}
              (block-ref/->block-ref id)]))))
+
     [:span.warning.mr-1 {:title "Block ref invalid"}
-      (block-ref/->block-ref id)]))
+     (block-ref/->block-ref id)]))
 
 (defn inline-text
   ([format v]
@@ -1070,10 +1073,12 @@
       (cond
         (util/electron?)
         [:a.asset-ref.is-pdf
-         {:on-click (fn [event]
-                      (when-let [current (pdf-assets/inflate-asset s)]
-                        (state/set-current-pdf! current)
-                        (util/stop event)))
+         {:data-href s
+          :on-click (fn [^js e]
+                      (when-let [s (some-> (.-target e) (.-dataset) (.-href))]
+                        (when-let [current (pdf-assets/inflate-asset s)]
+                          (state/set-current-pdf! current)
+                          (util/stop e))))
           :draggable true
           :on-drag-start #(.setData (gobj/get % "dataTransfer") "file" s)}
          (or label-text
@@ -1137,8 +1142,8 @@
         {:href      (path/path-join "file://" path)
          :data-href path
          :target    "_blank"}
-        title
-        (assoc :title title))
+         title
+         (assoc :title title))
        (map-inline config label)))
 
     :else
@@ -1231,8 +1236,8 @@
            (cond->
             {:href (ar-url->http-url href)
              :target "_blank"}
-            title
-            (assoc :title title))
+             title
+             (assoc :title title))
            (map-inline config label))
 
           :else
@@ -1241,8 +1246,8 @@
            (cond->
             {:href href
              :target "_blank"}
-            title
-            (assoc :title title))
+             title
+             (assoc :title title))
            (map-inline config label)))))))
 
 (declare ->hiccup inline)
@@ -1567,111 +1572,111 @@
                     [:div.warning {:title "Invalid hiccup"}
                      s])]
     (-> result'
-       (hiccups.core/html)
-       (security/sanitize-html))))
+        (hiccups.core/html)
+        (security/sanitize-html))))
 
 (defn ^:large-vars/cleanup-todo inline
   [{:keys [html-export?] :as config} item]
   (match item
-         [(:or "Plain" "Spaces") s]
-         s
-
-         ["Superscript" l]
-         (->elem :sup (map-inline config l))
-         ["Subscript" l]
-         (->elem :sub (map-inline config l))
-
-         ["Tag" _]
-         (when-let [s (gp-block/get-tag item)]
-           (let [s (text/page-ref-un-brackets! s)]
-             (page-cp (assoc config
-                             :tag? true
-                             :hide-close-button? true) {:block/name s})))
-
-         ["Emphasis" [[kind] data]]
-         (emphasis-cp config kind data)
-
-         ["Entity" e]
-         [:span {:dangerouslySetInnerHTML
-                 {:__html (security/sanitize-html (:html e))}}]
-
-         ["Latex_Fragment" [display s]] ;display can be "Displayed" or "Inline"
-         (if html-export?
-           (latex/html-export s false true)
-           (latex/latex (str (d/squuid)) s false (not= display "Inline")))
-
-         [(:or "Target" "Radio_Target") s]
-         [:a {:id s} s]
-
-         ["Email" address]
-         (let [{:keys [local_part domain]} address
-               address (str local_part "@" domain)]
-           [:a {:href (str "mailto:" address)} address])
-
-         ["Nested_link" link]
-         (nested-link config html-export? link)
-
-         ["Link" link]
-         (link-cp config html-export? link)
-
-         [(:or "Verbatim" "Code") s]
-         [:code s]
-
-         ["Inline_Source_Block" x]
-         [:code (:code x)]
-
-         ["Export_Snippet" "html" s]
-         (when (not html-export?)
-           [:span {:dangerouslySetInnerHTML
-                   {:__html (security/sanitize-html s)}}])
-
-         ["Inline_Hiccup" s] ;; String to hiccup
-         (ui/catch-error
-          [:div.warning {:title "Invalid hiccup"} s]
-          [:span {:dangerouslySetInnerHTML
-                  {:__html (hiccup->html s)}}])
-
-         ["Inline_Html" s]
-         (when (not html-export?)
+    [(:or "Plain" "Spaces") s]
+    s
+
+    ["Superscript" l]
+    (->elem :sup (map-inline config l))
+    ["Subscript" l]
+    (->elem :sub (map-inline config l))
+
+    ["Tag" _]
+    (when-let [s (gp-block/get-tag item)]
+      (let [s (text/page-ref-un-brackets! s)]
+        (page-cp (assoc config
+                        :tag? true
+                        :hide-close-button? true) {:block/name s})))
+
+    ["Emphasis" [[kind] data]]
+    (emphasis-cp config kind data)
+
+    ["Entity" e]
+    [:span {:dangerouslySetInnerHTML
+            {:__html (security/sanitize-html (:html e))}}]
+
+    ["Latex_Fragment" [display s]] ;display can be "Displayed" or "Inline"
+    (if html-export?
+      (latex/html-export s false true)
+      (latex/latex (str (d/squuid)) s false (not= display "Inline")))
+
+    [(:or "Target" "Radio_Target") s]
+    [:a {:id s} s]
+
+    ["Email" address]
+    (let [{:keys [local_part domain]} address
+          address (str local_part "@" domain)]
+      [:a {:href (str "mailto:" address)} address])
+
+    ["Nested_link" link]
+    (nested-link config html-export? link)
+
+    ["Link" link]
+    (link-cp config html-export? link)
+
+    [(:or "Verbatim" "Code") s]
+    [:code s]
+
+    ["Inline_Source_Block" x]
+    [:code (:code x)]
+
+    ["Export_Snippet" "html" s]
+    (when (not html-export?)
+      [:span {:dangerouslySetInnerHTML
+              {:__html (security/sanitize-html s)}}])
+
+    ["Inline_Hiccup" s] ;; String to hiccup
+    (ui/catch-error
+     [:div.warning {:title "Invalid hiccup"} s]
+     [:span {:dangerouslySetInnerHTML
+             {:__html (hiccup->html s)}}])
+
+    ["Inline_Html" s]
+    (when (not html-export?)
            ;; TODO: how to remove span and only export the content of `s`?
-           [:span {:dangerouslySetInnerHTML {:__html (security/sanitize-html s)}}])
-
-         [(:or "Break_Line" "Hard_Break_Line")]
-         [:br]
-
-         ["Timestamp" [(:or "Scheduled" "Deadline") _timestamp]]
-         nil
-         ["Timestamp" ["Date" t]]
-         (timestamp t "Date")
-         ["Timestamp" ["Closed" t]]
-         (timestamp t "Closed")
-         ["Timestamp" ["Range" t]]
-         (range t false)
-         ["Timestamp" ["Clock" ["Stopped" t]]]
-         (range t true)
-         ["Timestamp" ["Clock" ["Started" t]]]
-         (timestamp t "Started")
-
-         ["Cookie" ["Percent" n]]
-         [:span {:class "cookie-percent"}
-          (util/format "[%d%%]" n)]
-         ["Cookie" ["Absolute" current total]]
-         [:span {:class "cookie-absolute"}
-          (util/format "[%d/%d]" current total)]
-
-         ["Footnote_Reference" options]
-         (let [{:keys [name]} options
-               encode-name (util/url-encode name)]
-           [:sup.fn
-            [:a {:id (str "fnr." encode-name)
-                 :class "footref"
-                 :on-click #(route-handler/jump-to-anchor! (str "fn." encode-name))}
-             name]])
-
-         ["Macro" options]
-         (macro-cp config options)
-
-         :else ""))
+      [:span {:dangerouslySetInnerHTML {:__html (security/sanitize-html s)}}])
+
+    [(:or "Break_Line" "Hard_Break_Line")]
+    [:br]
+
+    ["Timestamp" [(:or "Scheduled" "Deadline") _timestamp]]
+    nil
+    ["Timestamp" ["Date" t]]
+    (timestamp t "Date")
+    ["Timestamp" ["Closed" t]]
+    (timestamp t "Closed")
+    ["Timestamp" ["Range" t]]
+    (range t false)
+    ["Timestamp" ["Clock" ["Stopped" t]]]
+    (range t true)
+    ["Timestamp" ["Clock" ["Started" t]]]
+    (timestamp t "Started")
+
+    ["Cookie" ["Percent" n]]
+    [:span {:class "cookie-percent"}
+     (util/format "[%d%%]" n)]
+    ["Cookie" ["Absolute" current total]]
+    [:span {:class "cookie-absolute"}
+     (util/format "[%d/%d]" current total)]
+
+    ["Footnote_Reference" options]
+    (let [{:keys [name]} options
+          encode-name (util/url-encode name)]
+      [:sup.fn
+       [:a {:id (str "fnr." encode-name)
+            :class "footref"
+            :on-click #(route-handler/jump-to-anchor! (str "fn." encode-name))}
+        name]])
+
+    ["Macro" options]
+    (macro-cp config options)
+
+    :else ""))
 
 (rum/defc block-child
   [block]
@@ -1686,9 +1691,9 @@
   (let [selected (set (map #(.-id %) (state/get-selection-blocks)))
         selected? (contains? selected block-id)]
     (when-not selected?
-    (util/clear-selection!)
-    (state/conj-selection-block! (gdom/getElement block-id) :down)
-    (editor-handler/highlight-block! uuid)))
+      (util/clear-selection!)
+      (state/conj-selection-block! (gdom/getElement block-id) :down)
+      (editor-handler/highlight-block! uuid)))
 
   (editor-handler/block->data-transfer! uuid event)
   (.setData (gobj/get event "dataTransfer")
@@ -1732,8 +1737,8 @@
                (not collapsed?))
       [:div.block-children-container.flex
        [:div.block-children-left-border
-         {:on-click (fn [_]
-                      (editor-handler/toggle-open-block-children! (:block/uuid block)))}]
+        {:on-click (fn [_]
+                     (editor-handler/toggle-open-block-children! (:block/uuid block)))}]
        [:div.block-children.w-full {:style {:display (if collapsed? "none" "")}}
         (let [config' (cond-> (dissoc config :breadcrumb-show? :embed-parent)
                         (or ref? query?)
@@ -2307,10 +2312,10 @@
   [block]
   (let [closed-values-properties (property-handler/get-block-other-position-properties (:db/id block))]
     (when (seq closed-values-properties)
-    [: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})))])))
+      [: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})))])))
 
 (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]
@@ -2853,20 +2858,20 @@
   [sub-id *ref {:keys [initial-value]}]
   (let [*latest-value (atom nil)
         *hidden? (rum/derived-atom [(:ui/main-container-scroll-top @state/state)] [::lazy-display sub-id]
-                                   (fn [_top]
-                                     (if (false? @*latest-value)
-                                       @*latest-value
-                                       (let [value (cond
-                                                     (some? initial-value)
-                                                     initial-value
-
-                                                     @*ref
-                                                     (hide-block? @*ref)
-
-                                                     :else
-                                                     true)]
-                                         (reset! *latest-value value)
-                                         value))))]
+                   (fn [_top]
+                     (if (false? @*latest-value)
+                       @*latest-value
+                       (let [value (cond
+                                     (some? initial-value)
+                                     initial-value
+
+                                     @*ref
+                                     (hide-block? @*ref)
+
+                                     :else
+                                     true)]
+                         (reset! *latest-value value)
+                         value))))]
     *hidden?))
 
 (rum/defcs ^:large-vars/cleanup-todo block-container-inner < rum/reactive db-mixins/query
@@ -3097,7 +3102,6 @@
                                (merge opts {:navigating-block navigating-block :navigated? navigated?}))
         (str "block-inner" (:block/uuid block))))))
 
-
 (defn divide-lists
   [[f & l]]
   (loop [l        l
@@ -3162,8 +3166,8 @@
          :li
          (cond->
           {:checked checked?}
-          number
-          (assoc :value number))
+           number
+           (assoc :value number))
          (vec-cat
           [(->elem
             :p
@@ -3571,9 +3575,7 @@
                    page (db/entity (:db/id page))
                    blocks (tree/non-consecutive-blocks->vec-tree blocks)
                    parent-blocks (group-by :block/parent blocks)]
-               [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
-                            (:ref? config)
-                            (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
+               [:div.custom-query-page-result.color-level {:key (str "page-" (:db/id page))}
                 (ui/foldable
                  [:div
                   (page-cp config page)
@@ -3602,9 +3604,7 @@
                    page (db/entity (:db/id page))
                    ;; FIXME: parents need to be sorted
                    parent-blocks (group-by :block/parent page-blocks)]
-               [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
-                            (:ref? config)
-                            (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
+               [:div.my-2 {:key (str "page-" (:db/id page))}
                 (ui/foldable
                  [:div
                   (page-cp config page)
@@ -3633,9 +3633,7 @@
               (let [alias? (:block/alias? page)
                     page (db/entity (:db/id page))
                     whiteboard? (model/whiteboard-page? page)]
-                [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
-                             (:ref? config)
-                             (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
+                [:div.my-2 {:key (str "page-" (:db/id page))}
                  (ui/foldable
                   [:div
                    (page-cp config page)

+ 13 - 9
src/main/frontend/components/block.css

@@ -180,12 +180,12 @@
 }
 
 .block-children-left-border:hover {
-  background-color: var(--ls-primary-text-color);
+  background-color: or(--ls-block-left-color, --lx-gray-11, --ls-primary-text-color);
 }
 
 .block-children {
   border-left: 1px solid;
-  border-left-color: var(--ls-guideline-color, #ddd);
+  border-left-color: or(--ls-guideline-color, --lx-gray-04-alpha, --ls-guideline-color, #ddd) !important;
 
   padding-top: 2px;
   padding-bottom: 3px;
@@ -297,10 +297,10 @@
 }
 
 .page-ref {
-  color: var(--ls-link-ref-text-color);
+  color: or(--ls-page-ref-text-color, --lx-accent-11, --ls-link-ref-text-color);
 
   &:hover {
-    color: var(--ls-link-ref-text-hover-color);
+    color: or(--ls-page-ref-text-color-hover, --lx-accent-12, --ls-link-ref-text-hover-color);
   }
 }
 
@@ -478,7 +478,10 @@
 }
 
 .color-level {
-  background-color: var(--color-level-1);
+  background-color: or(--ls-right-sidebar-content-background, --lx-gray-02, --color-level-1);
+  .dark & {
+    background-color: or(--ls-right-sidebar-content-background, --lx-gray-01, --color-level-1);
+  }
 
   & .color-level {
     background-color: var(--color-level-2);
@@ -566,7 +569,7 @@
 
     font-size: 14px;
 
-    background-color: var(--ls-block-bullet-color, #394b59);
+    background-color: or(--ls-block-bullet-color, --lx-gray-07, --ls-block-bullet-color, #394b59);
     transition: transform 0.2s;
 
     > * {
@@ -576,7 +579,7 @@
 
   &:not(.typed-list) {
     &.bullet-closed {
-      background-color: var(--ls-block-bullet-border-color, #ced9e0);
+      background-color: or(--ls-bullet-closed-background, --lx-gray-04-alpha, --ls-block-bullet-border-color, #ced9e0);
     }
   }
 
@@ -594,11 +597,12 @@
   color: var(--ls-primary-text-color);
 
   &:hover > .bullet-container .bullet {
-    transform: scale(1.4);
+    transform: scale(1.2);
+    background-color: or(--ls-buller-border-color-typed-list, --lx-gray-08, inherit) !important;
   }
 
   &:hover > .bullet-container:not(.typed-list) {
-    background-color: var(--ls-block-bullet-border-color, #ced9e0);
+    background-color: or(--ls-bullet-border-color, --lx-gray-04-alpha, --ls-block-bullet-border-color, #ced9e0);
   }
 }
 

+ 781 - 0
src/main/frontend/components/cmdk.cljs

@@ -0,0 +1,781 @@
+(ns frontend.components.cmdk
+  (:require
+    [clojure.string :as string]
+    [frontend.components.block :as block]
+    [frontend.context.i18n :refer [t]]
+    [frontend.db :as db]
+    [frontend.db.model :as model]
+    [frontend.handler.command-palette :as cp-handler]
+    [frontend.handler.editor :as editor-handler]
+    [frontend.handler.page :as page-handler]
+    [frontend.handler.route :as route-handler]
+    [frontend.handler.whiteboard :as whiteboard-handler]
+    [frontend.modules.shortcut.core :as shortcut]
+    [frontend.search :as search]
+    [frontend.shui :refer [make-shui-context]]
+    [frontend.state :as state]
+    [frontend.ui :as ui]
+    [frontend.util :as util]
+    [frontend.util.page :as page-util]
+    [goog.functions :as gfun]
+    [goog.object :as gobj]
+    [logseq.shui.core :as shui]
+    [promesa.core :as p]
+    [rum.core :as rum]
+    [frontend.mixins :as mixins]
+    [logseq.graph-parser.util.block-ref :as block-ref]
+    [logseq.graph-parser.util :as gp-util]
+    [logseq.shui.button.v2 :as button]
+    [frontend.modules.shortcut.utils :as shortcut-utils]))
+
+(defn translate [t {:keys [id desc]}]
+  (when id
+    (let [desc-i18n (t (shortcut-utils/decorate-namespace id))]
+      (if (string/starts-with? desc-i18n "{Missing key")
+        desc
+        desc-i18n))))
+
+(def GROUP-LIMIT 5)
+
+(def search-actions
+  [{:text "Search only pages"        :info "Add filter to search" :icon-theme :gray :icon "page" :filter {:mode "search"
+                                                                                                          :group :pages}}
+   {:text "Search only current page" :info "Add filter to search" :icon-theme :gray :icon "page" :filter {:mode "search"
+                                                                                                          :group :current-page}}
+   {:text "Search only blocks"       :info "Add filter to search" :icon-theme :gray :icon "block" :filter {:mode "search"
+                                                                                                           :group :blocks}}
+   {:text "Search only commands"     :info "Add filter to search" :icon-theme :gray :icon "command" :filter {:mode "search"
+                                                                                                             :group :commands}}
+   {:text "Search only files"        :info "Add filter to search" :icon-theme :gray :icon "file" :filter {:mode "search"
+                                                                                                          :group :files}}])
+
+(def filters search-actions)
+
+;; The results are separated into groups, and loaded/fetched/queried separately
+(def default-results
+  {:commands       {:status :success :show :less :items nil}
+   :favorites      {:status :success :show :less :items nil}
+   :current-page   {:status :success :show :less :items nil}
+   :pages          {:status :success :show :less :items nil}
+   :blocks         {:status :success :show :less :items nil}
+   :files          {:status :success :show :less :items nil}
+   :filters        {:status :success :show :less :items nil}})
+
+(defn create-items [q]
+  (when-not (string/blank? q)
+    [{:text "Create page"       :icon "new-page"
+      :icon-theme :gray
+      :info (str "Create page called '" q "'") :source-create :page}
+     {:text "Create whiteboard" :icon "new-whiteboard"
+      :icon-theme :gray
+      :info (str "Create whiteboard called '" q "'") :source-create :whiteboard}]))
+
+;; Take the results, decide how many items to show, and order the results appropriately
+(defn state->results-ordered [state search-mode]
+  (let [sidebar? (:sidebar? (last (:rum/args state)))
+        results @(::results state)
+        input @(::input state)
+        filter @(::filter state)
+        filter-group (:group filter)
+        index (volatile! -1)
+        visible-items (fn [group]
+                        (let [{:keys [items show]} (get results group)]
+                          (cond
+                            (or sidebar? (= group filter-group))
+                            items
+
+                            (= :more show)
+                            items
+
+                            :else
+                            (take 5 items))))
+        page-exists? (when-not (string/blank? input)
+                       (db/entity [:block/name (string/trim input)]))
+        filter-mode? (or (string/includes? input " /")
+                         (string/starts-with? input "/"))
+        order* (cond
+                 (= search-mode :graph)
+                 [["Pages"          :pages          (visible-items :pages)]]
+
+                 filter-mode?
+                 [["Filters"        :filters        (visible-items :filters)]
+                  ["Pages"          :pages          (visible-items :pages)]
+                  (when-not page-exists?
+                    ["Create"         :create         (create-items input)])]
+
+                 filter-group
+                 [(when (= filter-group :blocks)
+                    ["Current page"   :current-page   (visible-items :current-page)])
+                  [(if (= filter-group :current-page) "Current page" (name filter-group))
+                   filter-group
+                   (visible-items filter-group)]
+                  (when (= filter-group :pages)
+                    (when-not page-exists?
+                      ["Create"         :create         (create-items input)]))]
+
+                 :else
+                 (->>
+                  [["Pages"          :pages          (visible-items :pages)]
+                   (when-not page-exists?
+                     ["Create"         :create         (create-items input)])
+                   ["Commands"       :commands       (visible-items :commands)]
+                   ["Current page"   :current-page   (visible-items :current-page)]
+                   ["Blocks"         :blocks         (visible-items :blocks)]
+                   ["Files"          :files          (visible-items :files)]]
+                  (remove nil?)))
+        order (remove nil? order*)]
+    (for [[group-name group-key group-items] order]
+      [group-name
+       group-key
+       (if (= group-key :create)
+         (count group-items)
+         (count (get-in results [group-key :items])))
+       (mapv #(assoc % :item-index (vswap! index inc)) group-items)])))
+
+(defn state->highlighted-item [state]
+  (or (some-> state ::highlighted-item deref)
+      (some->> (state->results-ordered state (:search/mode @state/state))
+               (mapcat last)
+               (first))))
+
+(defn state->action [state]
+  (let [highlighted-item (state->highlighted-item state)]
+    (cond (:source-page highlighted-item) :open
+          (:source-block highlighted-item) :open
+          (:file-path highlighted-item) :open
+          (:source-search highlighted-item) :search
+          (:source-command highlighted-item) :trigger
+          (:source-create highlighted-item) :create
+          (:filter highlighted-item) :filter
+          :else nil)))
+
+;; Each result group has it's own load-results function
+(defmulti load-results (fn [group _state] group))
+
+(defmethod load-results :initial [_ state]
+  (let [!results (::results state)
+        command-items (->> (cp-handler/top-commands 1000)
+                           (remove (fn [c] (= :window/close (:id c))))
+                           (map #(hash-map :icon "command"
+                                           :icon-theme :gray
+                                           :text (translate t %)
+                                           :shortcut (:shortcut %)
+                                           :source-command %)))]
+    (reset! !results (assoc-in default-results [:commands :items] command-items))))
+
+;; The commands search uses the command-palette handler
+(defmethod load-results :commands [group state]
+  (let [!input (::input state)
+        !results (::results state)]
+    (swap! !results assoc-in [group :status] :loading)
+    (let [commands (->> (cp-handler/top-commands 1000)
+                        (map #(assoc % :t (translate t %))))
+          search-results (if (string/blank? @!input)
+                           commands
+                           (search/fuzzy-search commands @!input {:extract-fn :t}))]
+      (->> search-results
+           (map #(hash-map :icon "command"
+                           :icon-theme :gray
+                           :text (translate t %)
+                           :shortcut (:shortcut %)
+                           :source-command %))
+           (hash-map :status :success :items)
+           (swap! !results update group merge)))))
+
+;; The pages search action uses an existing handler
+(defmethod load-results :pages [group state]
+  (let [!input (::input state)
+        !results (::results state)]
+    (swap! !results assoc-in [group :status] :loading)
+    (p/let [pages (search/page-search @!input)
+            items (map
+                   (fn [page]
+                     (let [entity (db/entity [:block/name (util/page-name-sanity-lc page)])
+                           whiteboard? (= (:block/type entity) "whiteboard")]
+                       (hash-map :icon (if whiteboard? "whiteboard" "page")
+                                 :icon-theme :gray
+                                 :text page
+                                 :source-page page)))
+                   pages)]
+      (swap! !results update group        merge {:status :success :items items}))))
+
+;; The blocks search action uses an existing handler
+(defmethod load-results :blocks [group state]
+  (let [!input (::input state)
+        !results (::results state)
+        repo (state/get-current-repo)
+        current-page (page-util/get-current-page-id)
+        opts {:limit 100}]
+    (swap! !results assoc-in [group :status] :loading)
+    (swap! !results assoc-in [:current-page :status] :loading)
+    (p/let [blocks (search/block-search repo @!input opts)
+            blocks (remove nil? blocks)
+            items (map (fn [block]
+                         (let [id (if (uuid? (:block/uuid block))
+                                    (:block/uuid block)
+                                    (uuid (:block/uuid block)))]
+                           {:icon "block"
+                            :icon-theme :gray
+                            :text (:block/content block)
+                            :header (block/breadcrumb {:search? true} repo id {})
+                            :current-page? (some-> block :block/page #{current-page})
+                            :source-block block})) blocks)
+            items-on-other-pages (remove :current-page? items)
+            items-on-current-page (filter :current-page? items)]
+      (swap! !results update group         merge {:status :success :items items-on-other-pages})
+      (swap! !results update :current-page merge {:status :success :items items-on-current-page}))))
+
+(defmethod load-results :files [group state]
+  (let [!input (::input state)
+        !results (::results state)]
+    (swap! !results assoc-in [group :status] :loading)
+    (p/let [files (search/file-search @!input 99)
+            items (map
+                   (fn [file]
+                     (hash-map :icon "file"
+                               :icon-theme :gray
+                               :text file
+                               :file-path file))
+                   files)]
+      (swap! !results update group        merge {:status :success :items items}))))
+
+(defn- get-filter-q
+  [input]
+  (or (when (string/starts-with? input "/")
+        (subs input 1))
+      (last (gp-util/split-last " /" input))))
+
+(defmethod load-results :filters [group state]
+  (let [!results (::results state)
+        !input (::input state)
+        input @!input
+        q (or (get-filter-q input) "")
+        matched-items (if (string/blank? q)
+                        filters
+                        (search/fuzzy-search filters q {:extract-fn :text}))]
+    (swap! !results update group merge {:status :success :items matched-items})))
+
+;; The default load-results function triggers all the other load-results function
+(defmethod load-results :default [_ state]
+  (js/console.log "load-results/default" @(::input state))
+  (if-not (some-> state ::input deref seq)
+    (load-results :initial state)
+    (do
+      (load-results :commands state)
+      (load-results :blocks state)
+      (load-results :pages state)
+      (load-results :filters state)
+      (load-results :files state))))
+
+(defn close-unless-alt! [state]
+  (when-not (some-> state ::alt? deref)
+    (state/close-modal!)))
+
+(defn- copy-block-ref [state]
+  (when-let [block-uuid (some-> state state->highlighted-item :source-block :block/uuid uuid)]
+    (editor-handler/copy-block-ref! block-uuid block-ref/->block-ref)
+    (close-unless-alt! state)))
+
+(defmulti handle-action (fn [action _state _event] action))
+
+(defmethod handle-action :open-page [_ state _event]
+  (when-let [page-name (some-> state state->highlighted-item :source-page)]
+    (let [page (db/entity [:block/name (util/page-name-sanity-lc page-name)])]
+      (if (= (:block/type page) "whiteboard")
+        (route-handler/redirect-to-whiteboard! page-name)
+        (route-handler/redirect-to-page! page-name)))
+    (close-unless-alt! state)))
+
+(defmethod handle-action :open-block [_ state _event]
+  (let [block-id (some-> state state->highlighted-item :source-block :block/uuid uuid)
+        get-block-page (partial model/get-block-page (state/get-current-repo))]
+    (when-let [page (some-> block-id get-block-page)]
+      (let [page-name (:block/name page)]
+        (if (= (:block/type page) "whiteboard")
+          (route-handler/redirect-to-whiteboard! page-name {:block-id block-id})
+          (route-handler/redirect-to-page! page-name {:anchor (str "ls-block-" block-id)})))
+      (close-unless-alt! state))))
+
+(defmethod handle-action :open-page-right [_ state _event]
+  (when-let [page-name (some-> state state->highlighted-item :source-page)]
+    (when-let [page (db/entity [:block/name (util/page-name-sanity-lc page-name)])]
+      (editor-handler/open-block-in-sidebar! (:block/uuid page)))
+    (close-unless-alt! state)))
+
+(defmethod handle-action :open-block-right [_ state _event]
+  (when-let [block-uuid (some-> state state->highlighted-item :source-block :block/uuid uuid)]
+    (editor-handler/open-block-in-sidebar! block-uuid)
+    (close-unless-alt! state)))
+
+(defmethod handle-action :open [_ state event]
+  (when-let [item (some-> state state->highlighted-item)]
+    (let [page? (boolean (:source-page item))
+          block? (boolean (:source-block item))
+          shift?  @(::shift? state)
+          shift-or-sidebar? (or shift? (boolean (:open-sidebar? (:opts state))))
+          search-mode (:search/mode @state/state)
+          graph-view? (= search-mode :graph)]
+      (cond
+        (:file-path item) (do
+                            (route-handler/redirect! {:to :file
+                                                      :path-params {:path (:file-path item)}})
+                            (state/close-modal!))
+        (and graph-view? page? (not shift?)) (do
+                                               (state/add-graph-search-filter! @(::input state))
+                                               (reset! (::input state) ""))
+        (and shift-or-sidebar? block?) (handle-action :open-block-right state event)
+        (and shift-or-sidebar? page?) (handle-action :open-page-right state event)
+        block? (handle-action :open-block state event)
+        page? (handle-action :open-page state event)))))
+
+(defmethod handle-action :search [_ state _event]
+  (when-let [item (some-> state state->highlighted-item)]
+    (let [search-query (:source-search item)]
+      (reset! (::input state) search-query))))
+
+(defmethod handle-action :trigger [_ state _event]
+  (let [command (some-> state state->highlighted-item :source-command)]
+    (when-let [action (:action command)]
+      (action)
+      (when-not (contains? #{:graph/open :graph/remove :ui/toggle-settings :go/flashcards} (:id command))
+        (close-unless-alt! state)))))
+
+(defmethod handle-action :create [_ state _event]
+  (let [item (state->highlighted-item state)
+        create-whiteboard? (= :whiteboard (:source-create item))
+        create-page? (= :page (:source-create item))
+        alt? (some-> state ::alt deref)
+        !input (::input state)]
+    (cond
+      (and create-whiteboard? alt?) (whiteboard-handler/create-new-whiteboard-page! @!input)
+      (and create-whiteboard? (not alt?)) (whiteboard-handler/create-new-whiteboard-and-redirect! @!input)
+      (and create-page? alt?) (page-handler/create! @!input {:redirect? false})
+      (and create-page? (not alt?)) (page-handler/create! @!input {:redirect? true}))
+    (close-unless-alt! state)))
+
+(defn- get-filter-user-input
+  [input]
+  (cond
+    (string/includes? input " /")
+    (first (gp-util/split-last " /" input))
+    (string/starts-with? input "/")
+    ""
+    :else
+    input))
+
+(defmethod handle-action :filter [_ state _event]
+  (let [item (some-> state state->highlighted-item)
+        !input (::input state)]
+    (reset! !input (get-filter-user-input @!input))
+    (let [!filter (::filter state)
+          group (get-in item [:filter :group])]
+      (swap! !filter assoc :group group)
+      (load-results group state))))
+
+(defmethod handle-action :default [_ state event]
+  (when-let [action (state->action state)]
+    (handle-action action state event)))
+
+(defn- scroll-into-view-when-invisible
+  [state target]
+  (let [*container-ref (::scroll-container-ref state)
+        container-rect (.getBoundingClientRect @*container-ref)
+        t1 (.-top container-rect)
+        b1 (.-bottom container-rect)
+        target-rect (.getBoundingClientRect target)
+        t2 (.-top target-rect)
+        b2 (.-bottom target-rect)]
+    (when-not (<= t1 t2 b2 b1)          ; not visible
+      (.scrollIntoView target
+                       #js {:inline "nearest"
+                            :behavior "smooth"}))))
+
+(rum/defc mouse-active-effect!
+  [*mouse-active? deps]
+  (rum/use-effect!
+    #(reset! *mouse-active? false)
+    deps)
+  nil)
+
+(rum/defcs result-group
+  < rum/reactive
+  (rum/local false ::mouse-active?)
+  [state' state title group visible-items first-item sidebar?]
+  (let [{:keys [show items]} (some-> state ::results deref group)
+        highlighted-item (or @(::highlighted-item state) first-item)
+        highlighted-group @(::highlighted-group state)
+        *mouse-active? (::mouse-active? state')
+        filter @(::filter state)
+        can-show-less? (< GROUP-LIMIT (count visible-items))
+        can-show-more? (< (count visible-items) (count items))
+        show-less #(swap! (::results state) assoc-in [group :show] :less)
+        show-more #(swap! (::results state) assoc-in [group :show] :more)
+        context (make-shui-context)]
+    [:<>
+     (mouse-active-effect! *mouse-active? [highlighted-item])
+     [:div {:class         "border-b border-gray-06 pb-1 last:border-b-0"
+            :on-mouse-move #(reset! *mouse-active? true)}
+      [:div {:class "text-xs py-1.5 px-3 flex justify-between items-center gap-2 text-gray-11 bg-gray-02"}
+       [:div {:class "font-bold text-gray-11 pl-0.5"} title]
+       (when (not= group :create)
+         [:div {:class "pl-1.5 text-gray-12 rounded-full"
+                :style {:font-size "0.7rem"}}
+          (if (<= 100 (count items))
+            (str "99+")
+            (count items))])
+
+       [:div {:class "flex-1"}]
+
+       (when (and (= group highlighted-group)
+               (or can-show-more? can-show-less?)
+               (empty? filter)
+               (not sidebar?))
+         [:a.text-link.select-node.opacity-50.hover:opacity-90
+          {:on-click (if (= show :more) show-less show-more)}
+          (if (= show :more)
+            [:div.flex.flex-row.gap-1.items-center
+             "Show less"
+             (shui/shortcut "mod up" context)]
+            [:div.flex.flex-row.gap-1.items-center
+             "Show more"
+             (shui/shortcut "mod down" context)])])]
+
+      [:div.search-results
+       (for [item visible-items
+             :let [highlighted? (= item highlighted-item)]]
+         (let [item (shui/list-item (assoc item
+                                      :query (when-not (= group :create) @(::input state))
+                                      :compact true
+                                      :rounded false
+                                      :hoverable @*mouse-active?
+                                      :highlighted highlighted?
+                                      :display-shortcut-on-highlight? true
+                                      ;; for some reason, the highlight effect does not always trigger on a
+                                      ;; boolean value change so manually pass in the dep
+                                      :on-highlight-dep highlighted-item
+                                      :on-click (fn [e]
+                                                  (reset! (::highlighted-item state) item)
+                                                  (handle-action :default state item)
+                                                  (when-let [on-click (:on-click item)]
+                                                    (on-click e)))
+                                      ;; :on-mouse-enter (fn [e]
+                                      ;;                   (when (not highlighted?)
+                                      ;;                     (reset! (::highlighted-item state) (assoc item :mouse-enter-triggered-highlight true))))
+                                      :on-highlight (fn [ref]
+                                                      (reset! (::highlighted-group state) group)
+                                                      (when (and ref (.-current ref)
+                                                              (not (:mouse-enter-triggered-highlight @(::highlighted-item state))))
+                                                        (scroll-into-view-when-invisible state (.-current ref)))))
+                      context)]
+           (if (= group :blocks)
+             (ui/lazy-visible (fn [] item) {:trigger-once? true})
+             item)))]]]))
+
+(defn move-highlight [state n]
+  (let [items (mapcat last (state->results-ordered state (:search/mode @state/state)))
+        highlighted-item (some-> state ::highlighted-item deref (dissoc :mouse-enter-triggered-highlight))
+        current-item-index (some->> highlighted-item (.indexOf items))
+        next-item-index (some-> (or current-item-index 0) (+ n) (mod (count items)))]
+    (if-let [next-highlighted-item (nth items next-item-index nil)]
+      (reset! (::highlighted-item state) next-highlighted-item)
+      (reset! (::highlighted-item state) nil))))
+
+(defn handle-input-change
+  ([state e] (handle-input-change state e (.. e -target -value)))
+  ([state e input]
+   (let [composing? (util/onchange-event-is-composing? e)
+         e-type (gobj/getValueByKeys e "type")
+         !input (::input state)
+         !load-results-throttled (::load-results-throttled state)]
+     ;; update the input value in the UI
+     (reset! !input input)
+
+       ;; ensure that there is a throttled version of the load-results function
+     (when-not @!load-results-throttled
+       (reset! !load-results-throttled (gfun/throttle load-results 50)))
+
+       ;; retrieve the load-results function and update all the results
+     (when (or (not composing?) (= e-type "compositionend"))
+       (when-let [load-results-throttled @!load-results-throttled]
+         (load-results-throttled :default state))))))
+
+(defn- keydown-handler
+  [state e]
+  (let [shift? (.-shiftKey e)
+        meta? (.-metaKey e)
+        alt? (.-altKey e)
+        ctrl? (.-ctrlKey e)
+        keyname (.-key e)
+        enter? (= keyname "Enter")
+        esc? (= keyname "Escape")
+        highlighted-group @(::highlighted-group state)
+        show-less (fn [] (swap! (::results state) assoc-in [highlighted-group :show] :less))
+        show-more (fn [] (swap! (::results state) assoc-in [highlighted-group :show] :more))
+        input @(::input state)
+        as-keydown? (or (= keyname "ArrowDown") (and ctrl? (= keyname "n")))
+        as-keyup? (or (= keyname "ArrowUp") (and ctrl? (= keyname "p")))]
+    (reset! (::shift? state) shift?)
+    (reset! (::meta? state) meta?)
+    (reset! (::alt? state) alt?)
+    (when (or as-keydown? as-keyup?)
+      (.preventDefault e))
+    (when-not esc? (util/stop-propagation e))
+
+    (cond
+      (and meta? enter?
+           (not (string/blank? input)))
+      (let [repo (state/get-current-repo)]
+        (state/close-modal!)
+        (state/sidebar-add-block! repo input :search))
+
+      as-keydown? (if meta?
+                    (show-more)
+                    (move-highlight state 1))
+      as-keyup? (if meta?
+                  (show-less)
+                  (move-highlight state -1))
+      enter? (handle-action :default state e)
+      esc? (let [filter @(::filter state)]
+             (when (or filter (not (string/blank? input)))
+               (util/stop e)
+               (reset! (::filter state) nil)
+               (when-not filter (handle-input-change state nil ""))))
+      (= keyname "c") (copy-block-ref state)
+      :else nil)))
+
+(defn keyup-handler
+  [state e]
+  (let [shift? (.-shiftKey e)
+        meta? (.-metaKey e)
+        alt? (.-altKey e)]
+    (reset! (::shift? state) shift?)
+    (reset! (::alt? state) alt?)
+    (reset! (::meta? state) meta?)))
+
+(defn- input-placeholder
+  [sidebar?]
+  (let [search-mode (:search/mode @state/state)]
+    (cond
+      (and (= search-mode :graph) (not sidebar?))
+      "Add graph filter"
+
+      :else
+      "What are you looking for?")))
+
+(rum/defc input-row
+  [state all-items opts]
+  (let [highlighted-item @(::highlighted-item state)
+        input @(::input state)
+        input-ref (::input-ref state)]
+    ;; use-effect [results-ordered input] to check whether the highlighted item is still in the results,
+    ;; if not then clear that puppy out!
+    ;; This was moved to a functional component
+    (rum/use-effect! (fn []
+                       (when (and highlighted-item (= -1 (.indexOf all-items (dissoc highlighted-item :mouse-enter-triggered-highlight))))
+                         (reset! (::highlighted-item state) nil)))
+                     [all-items])
+    (rum/use-effect! (fn [] (load-results :default state)) [])
+    [:div {:class "bg-gray-02 border-b border-1 border-gray-07"}
+     [:input#search
+      {:class "text-xl bg-transparent border-none w-full outline-none px-3 py-3"
+       :auto-focus true
+       :autoComplete "off"
+       :placeholder (input-placeholder false)
+       :ref #(when-not @input-ref (reset! input-ref %))
+       :on-change (fn [e]
+                    (handle-input-change state e)
+                    (when-let [on-change (:on-input-change opts)]
+                      (on-change (.-value (.-target e)))))
+       :on-blur (fn [_e]
+                  (when-let [on-blur (:on-input-blur opts)]
+                    (on-blur input)))
+       :on-composition-end (fn [e] (handle-input-change state e))
+       :on-key-down (fn [e]
+                      (let [value (.-value @input-ref)
+                            last-char (last value)]
+                        (when (and (some? @(::filter state))
+                                   (or (= (util/ekey e) "/")
+                                       (and (= (util/ekey e) "Backspace")
+                                            (= last-char "/"))))
+                          (reset! (::filter state) nil))))
+       :value input}]]))
+
+(defn rand-tip
+  [context]
+  (rand-nth
+   [[:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
+     [:div "Type"]
+     (shui/shortcut "/" context)
+     [:div "to filter search results"]]
+    [:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
+     (shui/shortcut "mod enter" context)
+     [:div "to open search in the sidebar"]]]))
+
+(rum/defcs tip <
+  {:init (fn [state]
+           (assoc state ::rand-tip (rand-tip (last (:rum/args state)))))}
+  [inner-state state context]
+  (let [filter @(::filter state)]
+    (cond
+      filter
+      [:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
+       [:div "Type"]
+       (shui/shortcut "esc" context {:tiled false})
+       [:div "to clear search filter"]]
+
+      :else
+      (::rand-tip inner-state))))
+
+(rum/defc hints
+  [state]
+  (let [context (make-shui-context)
+        action (state->action state)
+        button-fn (fn [text shortcut & {:as opts}]
+                    (shui/button {:text text
+                                  :theme :text
+                                  :hover-theme :gray
+                                  :on-click #(handle-action action (assoc state :opts opts) %)
+                                  :shortcut shortcut
+                                  :muted true}
+                      context))]
+    (when action
+      [:div {:class "flex w-full px-3 py-2 gap-2 justify-between"
+             :style {:background "var(--lx-gray-03)"
+                     :border-top "1px solid var(--lx-gray-07)"}}
+       [:div.text-sm.leading-6
+        [:div.flex.flex-row.gap-1.items-center
+         [:div.font-medium.text-gray-12 "Tip:"]
+         (tip state context)]]
+
+       [:div.gap-2.hidden.md:flex {:style {:margin-right -6}}
+        (case action
+          :open
+          [:<>
+           (button-fn "Open" ["return"])
+           (button-fn "Open in sidebar" ["shift" "return"] {:open-sidebar? true})
+           (when (:source-block @(::highlighted-item state)) (button-fn "Copy ref" ["⌘" "c"]))]
+
+          :search
+          [:<>
+           (button-fn "Search" ["return"])]
+
+          :trigger
+          [:<>
+           (button-fn "Trigger" ["return"])]
+
+          :create
+          [:<>
+           (button-fn "Create" ["return"])]
+
+          :filter
+          [:<>
+           (button-fn "Filter" ["return"])]
+
+          nil)]])))
+
+(rum/defc search-only
+  [state group-name]
+  [:div.flex.flex-row.gap-1.items-center
+   [:div "Search only:"]
+   [:div group-name]
+   (button/root {:icon "x"
+                 :theme :text
+                 :hover-theme :gray
+                 :size :sm
+                 :on-click (fn []
+                             (reset! (::filter state) nil))}
+                (make-shui-context))])
+
+(rum/defcs cmdk < rum/static
+  rum/reactive
+  {:will-mount
+   (fn [state]
+     (when-not (:sidebar? (last (:rum/args state)))
+       (shortcut/unlisten-all!))
+     state)
+
+   :will-unmount
+   (fn [state]
+     (when-not (:sidebar? (last (:rum/args state)))
+       (shortcut/listen-all!))
+     state)}
+  {:init (fn [state]
+           (let [search-mode (:search/mode @state/state)
+                 opts (last (:rum/args state))]
+             (assoc state
+                    ::ref (atom nil)
+                    ::filter (if (and search-mode
+                                      (not (contains? #{:global :graph} search-mode))
+                                      (not (:sidebar? opts)))
+                               (atom {:group search-mode})
+                               (atom nil))
+                    ::input (atom (or (:initial-input opts) "")))))
+   :will-unmount (fn [state]
+                   (state/set-state! :search/mode nil)
+                   state)}
+  (mixins/event-mixin
+   (fn [state]
+     (let [ref @(::ref state)]
+       (mixins/on-key-down state {}
+                           {:target ref
+                            :all-handler (fn [e _key] (keydown-handler state e))})
+       (mixins/on-key-up state {}
+                         {:target ref
+                          :all-handler (fn [e _key] (keyup-handler state e))}))))
+  (rum/local false ::shift?)
+  (rum/local false ::meta?)
+  (rum/local false ::alt?)
+  (rum/local nil ::highlighted-group)
+  (rum/local nil ::highlighted-item)
+  (rum/local default-results ::results)
+  (rum/local nil ::load-results-throttled)
+  (rum/local nil ::scroll-container-ref)
+  (rum/local nil ::input-ref)
+  [state {:keys [sidebar?] :as opts}]
+  (let [*input (::input state)
+        _input (rum/react *input)
+        search-mode (:search/mode @state/state)
+        group-filter (:group (rum/react (::filter state)))
+        results-ordered (state->results-ordered state search-mode)
+        all-items (mapcat last results-ordered)
+        first-item (first all-items)]
+    [:div.cp__cmdk {:ref #(when-not @(::ref state) (reset! (::ref state) %))
+                    :class (cond-> "w-full h-full relative flex flex-col justify-start"
+                             (not sidebar?) (str " rounded-lg"))}
+     (input-row state all-items opts)
+     [:div {:class (cond-> "w-full flex-1 overflow-y-auto min-h-[65dvh] max-h-[65dvh]"
+                     (not sidebar?) (str " pb-14"))
+            :ref #(let [*ref (::scroll-container-ref state)]
+                    (when-not @*ref (reset! *ref %)))
+            :style {:background "var(--lx-gray-02)"}}
+
+      (when group-filter
+        [:div.flex.flex-col.p-3.opacity-50.text-sm
+         (search-only state (name group-filter))])
+
+      (let [items (filter
+                   (fn [[_group-name group-key group-count _group-items]]
+                     (and (not= 0 group-count)
+                          (if-not group-filter true
+                                  (or (= group-filter group-key)
+                                      (and (= group-filter :blocks)
+                                           (= group-key :current-page))
+                                      (and (contains? #{:pages :create} group-filter)
+                                           (= group-key :create))))))
+                   results-ordered)]
+        (if (seq items)
+          (for [[group-name group-key _group-count group-items] items]
+            (let [title group-name]
+              (result-group state title group-key group-items first-item sidebar?)))
+          [:div.flex.flex-col.p-4.opacity-50
+           (when-not (string/blank? (rum/react *input))
+             "No matched results")]))]
+     (when-not sidebar? (hints state))]))
+
+(rum/defc cmdk-modal [props]
+  [:div {:class "cp__cmdk__modal rounded-lg w-[90dvw] max-w-4xl relative"}
+   (cmdk props)])
+
+(rum/defc cmdk-block [props]
+  [:div {:class "cp__cmdk__block rounded-md"}
+   (cmdk props)])

+ 0 - 57
src/main/frontend/components/command_palette.cljs

@@ -1,57 +0,0 @@
-(ns frontend.components.command-palette
-  (:require [frontend.handler.command-palette :as cp]
-            [frontend.modules.shortcut.core :as shortcut]
-            [frontend.modules.shortcut.utils :as shortcut-utils]
-            [frontend.context.i18n :refer [t]]
-            [frontend.search :as search]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [rum.core :as rum]
-            [clojure.string :as string]))
-
-(defn translate [t {:keys [id desc]}]
-  (when id
-    (let [desc-i18n (t (shortcut-utils/decorate-namespace id))]
-      (if (string/starts-with? desc-i18n "{Missing key")
-        desc
-        desc-i18n))))
-
-(defn get-matched-commands [commands input limit t]
-  (search/fuzzy-search commands input :limit limit :extract-fn (partial translate t)))
-
-(rum/defc render-command
-  [{:keys [id shortcut] :as cmd} chosen?]
-  (let [first-shortcut (first (string/split shortcut #" \| "))
-        desc (translate t cmd)]
-    [:div.inline-grid.grid-cols-4.items-center.w-full
-     {:class (when chosen? "chosen")}
-     [:span.col-span-3 desc]
-     [:div.col-span-1.flex.justify-end.tip
-      (when (and (keyword? id) (namespace id))
-        [:code.opacity-40.bg-transparent (namespace id)])
-      (when-not (string/blank? first-shortcut)
-        [:code.ml-1 first-shortcut])]]))
-
-(rum/defcs command-palette <
-  shortcut/disable-all-shortcuts
-  (rum/local "" ::input)
-  [state {:keys [commands limit]
-          :or {limit 100}}]
-  (let [input (::input state)]
-    [:div.cp__palette.cp__palette-main
-     [:div.input-wrap
-      [:input.cp__palette-input.w-full.h-full
-       {:type        "text"
-        :placeholder (t :command-palette/prompt)
-        :auto-focus  true
-        :value       @input
-        :on-change   (fn [e] (reset! input (util/evalue e)))}]]
-
-     [:div.command-results-wrap
-      (ui/auto-complete
-       (if (string/blank? @input)
-         (cp/top-commands limit)
-         (get-matched-commands commands @input limit t))
-       {:item-render render-command
-        :class       "cp__palette-results"
-        :on-chosen   (fn [cmd] (cp/invoke-command cmd))})]]))

+ 7 - 2
src/main/frontend/components/command_palette.css

@@ -1,4 +1,4 @@
-.cp__palette, .cp__select {
+ .cp__select {
   --palettle-input-height: 64px;
   --palettle-container-height: 75vh;
 
@@ -40,10 +40,15 @@
 
       &.chosen,
       &.chosen p {
-        background-color: var(--ls-a-chosen-bg);
+        background-color: or(--ls-cp-chosen, --lx-gray-03, --ls-a-chosen-bg);
         color: var(--ls-secondary-text-color);
       }
 
+      .dark &.chosen,
+      .dark &.chosen p {
+        background-color: or(--ls-cp-chosen, --lx-gray-02, --ls-a-chosen-bg);
+      }
+
       &:hover p {
         color: var(--ls-secondary-text-color);
       }

+ 7 - 3
src/main/frontend/components/container.cljs

@@ -164,7 +164,9 @@
                    (state/pub-event! [:modal/show-cards]))}
      (ui/icon "infinity")
      [:span.flex-1 (t :right-side-bar/flashcards)]
-     [:span.ml-1 (ui/render-keyboard-shortcut (ui/keyboard-shortcut-from-config :go/flashcards))]
+     [:span.ml-1 (ui/render-keyboard-shortcut
+                  (ui/keyboard-shortcut-from-config :go/flashcards
+                                                    {:pick-first? true}))]
      (when (and num (not (zero? num)))
        [:span.ml-1.inline-block.py-0.5.px-3.text-xs.font-medium.rounded-full.fade-in num])]))
 
@@ -715,7 +717,10 @@
       [:div.inner
        {:title    (t :help-shortcut-title)
         :on-click #(state/toggle! :ui/help-open?)}
-       "?"]]
+       [:svg.scale-125 {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "24", :viewbox "0 0 24 24", :xmlns "http://www.w3.org/2000/svg", :stroke-linecap "round", :stroke-width "2", :class "icon icon-tabler icon-tabler-help-small", :height "24"}
+        [:path {:stroke "none", :d "M0 0h24v24H0z", :fill "none"}]
+        [:path {:d "M12 16v.01"}]
+        [:path {:d "M12 13a2 2 0 0 0 .914 -3.782a1.98 1.98 0 0 0 -2.414 .483"}]]]]
 
      (when help-open?
        (help-menu-popup))
@@ -812,7 +817,6 @@
                         :route-match    route-match
                         :default-home   default-home
                         :new-block-mode new-block-mode})
-
         (when (util/electron?)
           (find-in-page/search))
 

+ 61 - 28
src/main/frontend/components/container.css

@@ -6,13 +6,17 @@
 }
 
 #app-container {
-  background-color: var(--ls-primary-background-color, #fff);
+  background-color: or(--ls-top-bar-background, --lx-gray-01, --ls-primary-background-color, #fff);
   position: relative;
 }
 
+.dark #app-container {
+  background-color: or(--ls-top-bar-background, --lx-gray-02, --ls-primary-background-color, #fff);
+}
+
 #root {
   > div {
-    color: var(--ls-primary-text-color, #24292e);
+    color: or(--ls-document-text-color, --lx-gray-12, --ls-primary-text-color, #24292e);
     font-size: var(--ls-page-text-size);
   }
 }
@@ -75,6 +79,10 @@
   overflow: auto;
 }
 
+.dark .left-sidebar-inner {
+  background-color: or(--ls-left-sidebar-background-color, --lx-gray-01, --ls-primary-background);
+}
+
 .left-sidebar-inner {
   position: relative;
   height: 100%;
@@ -82,8 +90,8 @@
   width: var(--ls-left-sidebar-sm-width);
   overflow-y: auto;
   overflow-x: hidden;
-  background-color: var(--ls-primary-background-color);
-  border-right: 1px solid var(--ls-tertiary-background-color);
+  background-color: or(--ls-left-sidebar-background-color, --lx-gray-02, --ls-primary-background-color);
+  border-right: 1px solid or(--ls-left-sidebar-border-color, --lx-gray-03, --ls-tertiary-background-color);
   transition: transform .3s;
   transform: translate3d(-100%, 0, 0);
   z-index: 3;
@@ -119,12 +127,15 @@
 
   .nav-header a {
     .keyboard-shortcut {
-      @apply w-0 opacity-0;
-      transition: opacity 0.3s;
+        @apply w-0 opacity-0;
+        visibility: hidden;
     }
 
     &:hover {
       .keyboard-shortcut {
+        visibility: visible;
+        transition: opacity 1s;
+        transition-delay: 2s;
         width: auto;
         opacity: 1;
       }
@@ -156,16 +167,13 @@
     }
 
     &:hover {
-      background-color: var(--ls-tertiary-background-color);
-
-      .ui__icon {
-        opacity: .9;
-      }
+      background-color: or(--ls-left-sidebar-hover-background, --lx-gray-04, --ls-primary-background-color);
+      color: or(--ls-left-sidebar-text-color-hover, --lx-gray-12);
     }
 
-    &.active, &:active {
-      background-color: var(--ls-quaternary-background-color);
-
+    &.active {
+      background-color: or(--ls-left-sidebar-active-background, --lx-gray-04, --color-level-3);
+      color: or(--ls-left-sidebar-active-text-color, --lx-gray-12);
       .ui__icon {
         opacity: .9;
       }
@@ -208,17 +216,13 @@
         width: 20px;
       }
 
-      a {
-        opacity: .7;
-      }
-
       .more {
         display: none;
         transition: .15s transform;
       }
 
       &:hover {
-        background-color: var(--ls-tertiary-background-color);
+        background-color: or(--ls-nav-item-hover, --lx-gray-04, --ls-tertiary-background-color);
 
         * {
           opacity: 1 !important;
@@ -273,6 +277,7 @@
           }
 
           &:hover {
+            background-color: or(--ls-recessed-nav-item-hover, --lx-gray-04, --ls-quaternary-background-color);
             opacity: 1;
             background-color: var(--ls-quaternary-background-color);
           }
@@ -296,6 +301,14 @@
     background-image: linear-gradient(transparent, var(--ls-primary-background-color));
     user-select: none;
 
+    @screen sm {
+      background-image: linear-gradient(transparent, or(--ls-left-sidebar-bottom-gradient, --lx-gray-02, --ls-secondary-background-color));
+
+      .dark & {
+        background-image: linear-gradient(transparent, or(--ls-left-sidebar-bottom-gradient, --lx-gray-01, --ls-secondary-background-color));
+      }
+    }
+
     &-link {
       background-color: var(--ls-primary-background-color);
       box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
@@ -316,13 +329,22 @@
 
     #create-button {
       @apply flex items-center justify-center p-2 text-sm font-medium rounded-md w-full border;
-      background-color: var(--ls-secondary-background-color) !important;
+      background-color: or(--ls-create-button-color, --lx-gray-03, --ls-secondary-background-color) !important;
       border-color: transparent;
 
       &:hover,
       &:focus {
         border-color: var(--ls-border-color);
-        background-color: var(--ls-tertiary-background-color) !important;
+        background-color: or(--ls-create-button-color-focus, --lx-gray-03, --ls-primary-background-color) !important;
+      }
+
+      @screen sm {
+        background-color: or(--ls-create-button-color-sm, --lx-gray-03, --ls-primary-background-color) !important;
+
+        &:hover,
+        &:focus {
+          background-color: or(--ls-create-button-color-sm-focus, --lx-gray-04, --ls-secondary-background-color) !important;
+        }
       }
     }
   }
@@ -330,6 +352,11 @@
   @screen sm {
     padding-top: 0;
     width: var(--ls-left-sidebar-width);
+    background-color: or(--ls-left-sidebar-background, --lx-gray-02, --ls-secondary-background-color);
+
+    .dark & {
+      background-color: or(--ls-left-sidebar-background, --lx-gray-01, --ls-secondary-background-color);
+    }
 
     > .wrap {
       margin-top: 52px;
@@ -353,8 +380,9 @@
 
   a {
     @apply opacity-90 hover:opacity-100;
+    transition: all 120ms ease-out;
 
-    color: var(--ls-header-button-background);
+    color: or(--ls-left-sidebar-text-color, --ls-header-button-background);
   }
 
   > .left-sidebar-inner {
@@ -431,7 +459,7 @@
     transition: width .3s;
 
     &:before {
-      background-color: var(--ls-secondary-background-color);
+      background-color: or(--ls-left-sidebar-container-sm, --lx-gray-02, --ls-secondary-background-color);
       width: 0;
       overflow: hidden;
     }
@@ -493,7 +521,11 @@
 }
 
 .cp__sidebar-main-layout {
-  background-color: var(--ls-primary-background-color);
+  background-color: or(--ls-main-content-background, --lx-gray-01, --ls-primary-background-color);
+}
+
+.dark .cp__sidebar-main-layout {
+  background-color: or(--ls-main-content-background, --lx-gray-02, --ls-primary-background-color);
 }
 
 .cp__sidebar-main-content {
@@ -516,13 +548,13 @@
   }
 
   &-btn {
-    @apply fixed bottom-4 right-4 sm:right-8;
+    @apply fixed bottom-4 right-4 sm:right-8 opacity-70 hover:opacity-100;
 
     > .inner {
       @apply rounded-full h-8 w-8 flex items-center justify-center
-      font-bold select-none cursor-help;
+      font-bold select-none cursor-pointer;
 
-      background-color: var(--ls-secondary-background-color);
+      background-color: or(--ls-left-sidebar-help-background, --lx-gray-01, --ls-secondary-background-color);
     }
   }
 
@@ -571,7 +603,7 @@
     &:hover,
     &:focus,
     &:active {
-      background-color: var(--ls-active-primary-color);
+      background-color: or(--ls-right-sidebar-resizer-color, --lx-gray-08-alpha, --ls-active-primary-color);
     }
   }
 
@@ -614,6 +646,7 @@
     top: 0;
     left: 0;
     right: 0;
+    background-color: or(--ls-right-sidebar-topbar-color, --lx-gray-01, --ls-secondary-background-color, #d8e1e8);
     z-index: 999;
     user-select: none;
     -webkit-app-region: drag;

+ 4 - 3
src/main/frontend/components/editor.cljs

@@ -4,9 +4,8 @@
              :refer [*first-command-group *matched-block-commands *matched-commands]]
             [frontend.components.block :as block]
             [frontend.components.datetime :as datetime-comp]
-            [frontend.components.search :as search]
-            [frontend.components.search.highlight :as highlight]
             [frontend.components.svg :as svg]
+            [frontend.components.search :as search]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
@@ -16,6 +15,7 @@
             [frontend.handler.page :as page-handler]
             [frontend.handler.paste :as paste-handler]
             [frontend.handler.property.util :as pu]
+            [frontend.handler.search :as search-handler]
             [frontend.search :refer [fuzzy-search]]
             [frontend.mixins :as mixins]
             [frontend.modules.shortcut.core :as shortcut]
@@ -198,7 +198,7 @@
                                   (when (db-model/whiteboard-page? page-name) [:span.mr-1 (ui/icon "whiteboard" {:extension? true})])
                                   [:div.flex.space-x-1
                                    [:div (when-not (db/page-exists? page-name) (t :new-page))]
-                                   (highlight/highlight-exact-query page-name q)]]
+                                   (search-handler/highlight-exact-query page-name q)]]
                                  :open?           chosen?
                                  :manual?         true
                                  :fixed-position? true
@@ -208,6 +208,7 @@
                :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 "Search for a page"]
                :class       "black"})]))))))
 
+
 (defn- search-blocks!
   [state result]
   (let [[edit-block _ _ q] (:rum/args state)]

+ 1 - 1
src/main/frontend/components/export.cljs

@@ -275,7 +275,7 @@
               [:option {:key n :value n} n])]]]))
 
      (when @*content
-       [:div.mt-4
+       [:div.mt-4.flex.flex-row.gap-2
         (ui/button (if @*copied? (t :export-copied-to-clipboard) (t :export-copy-to-clipboard))
                    :class "mr-4"
                    :on-click (fn []

+ 1 - 1
src/main/frontend/components/header.css

@@ -187,7 +187,7 @@
     background: none;
 
     @screen md {
-      background: var(--ls-tertiary-background-color);
+      background: or(--ls-header-button-hover, --lx-gray-04, --ls-tertiary-background-color);
     }
   }
 

+ 1 - 1
src/main/frontend/components/journal.css

@@ -7,7 +7,7 @@
 
   .journal-item {
     border-top: 1px solid;
-    border-top-color: var(--ls-border-color, #738694);
+    border-top-color: or(--ls-journal-page-rule, --lx-gray-07, --ls-border-color, #738694);
     margin: 24px 0;
     padding: 24px 0;
     min-height: 250px;

+ 9 - 3
src/main/frontend/components/page.css

@@ -16,7 +16,7 @@
 
     > .it {
       display: flex;
-      justify-content: center;
+      justify-content: flex-start;
       padding-top: 5px;
       padding-bottom: 5px;
 
@@ -74,7 +74,9 @@
       }
 
       > span {
-        @apply text-gray-500 text-sm;
+        @apply text-sm;
+
+        color: or(--ls-all-pages-table-text, --lx-gray-11, rgb(115, 115, 115));
 
         padding: 6px 8px;
       }
@@ -99,12 +101,16 @@
 
   .actions {
     position: sticky;
-    background-color: var(--ls-primary-background-color);
+    background-color: or(--ls-all-pages-table, --lx-gray-01, --ls-primary-background-color);
     white-space: nowrap;
     top: -18px;
     padding-bottom: 10px;
     z-index: 1;
 
+    .dark & {
+        background-color: or(--ls-all-pages-table, --lx-gray-02, --ls-primary-background-color);
+    }
+
     @screen md {
       display: flex;
       align-items: center;

+ 107 - 107
src/main/frontend/components/plugins.cljs

@@ -149,14 +149,14 @@
      [:span.flex.items-center
       (ui/icon "puzzle")
       (t :plugins) (when (vector? total-nums) (str " (" (first total-nums) ")"))]
-     :intent "logseq"
+     :intent "link"
      :on-click #(on-action :plugins)
      :class (if (= category :plugins) "active" ""))
    (ui/button
      [:span.flex.items-center
       (ui/icon "palette")
       (t :themes) (when (vector? total-nums) (str " (" (last total-nums) ")"))]
-     :intent "logseq"
+     :intent "link"
      :on-click #(on-action :themes)
      :class (if (= category :themes) "active" ""))])
 
@@ -382,7 +382,7 @@
     (t :plugin/contribute)
     :href "https://github.com/logseq/marketplace"
     :class "contribute"
-    :intent "logseq"
+    :intent "link"
     :target "_blank"))
 
 (rum/defc user-proxy-settings-panel
@@ -492,11 +492,11 @@
          (ui/tippy {:html  [:div (t :plugin/unpacked-tips)]
                     :arrow true}
                    (ui/button
-                     [:span.flex.items-center
-                      (ui/icon "upload") (t :plugin/load-unpacked)]
-                     :intent "logseq"
+                    (t :plugin/load-unpacked)
+                    {:icon "upload"
+                     :intent "link"
                      :class "load-unpacked"
-                     :on-click plugin-handler/load-unpacked-plugin))
+                     :on-click plugin-handler/load-unpacked-plugin}))
 
          (unpacked-plugin-loader selected-unpacked-pkg)])]
 
@@ -504,11 +504,11 @@
       ;; extra info
       (when-let [proxy-val (state/http-proxy-enabled-or-val?)]
         (ui/button
-          [:span.flex.items-center.text-indigo-500
-           (ui/icon "world-download") proxy-val]
-          :small? true
-          :intent "link"
-          :on-click #(state/pub-event! [:go/proxy-settings agent-opts])))
+         [:span.flex.items-center.text-indigo-500
+          (ui/icon "world-download") proxy-val]
+         :small? true
+         :intent "link"
+         :on-click #(state/pub-event! [:go/proxy-settings agent-opts])))
 
       ;; search
       (panel-tab-search search-key *search-key *search-ref)
@@ -516,105 +516,105 @@
       ;; sorter & filter
       (let [aim-icon #(if (= filter-by %) "check" "circle")]
         (ui/dropdown-with-links
-          (fn [{:keys [toggle-fn]}]
-            (ui/button
-              (ui/icon "filter")
-              :class (str (when-not (contains? #{:default} filter-by) "picked ") "sort-or-filter-by")
-              :on-click toggle-fn
-              :intent "link"))
-
-          (if market?
-            [{:title   (t :plugin/all)
-              :options {:on-click #(reset! *filter-by :default)}
-              :icon    (ui/icon (aim-icon :default))}
-
-             {:title   (t :plugin/installed)
-              :options {:on-click #(reset! *filter-by :installed)}
-              :icon    (ui/icon (aim-icon :installed))}
-
-             {:title   (t :plugin/not-installed)
-              :options {:on-click #(reset! *filter-by :not-installed)}
-              :icon    (ui/icon (aim-icon :not-installed))}]
-
-            [{:title   (t :plugin/all)
-              :options {:on-click #(reset! *filter-by :default)}
-              :icon    (ui/icon (aim-icon :default))}
-
-             {:title   (t :plugin/enabled)
-              :options {:on-click #(reset! *filter-by :enabled)}
-              :icon    (ui/icon (aim-icon :enabled))}
-
-             {:title   (t :plugin/disabled)
-              :options {:on-click #(reset! *filter-by :disabled)}
-              :icon    (ui/icon (aim-icon :disabled))}
-
-             {:title   (t :plugin/unpacked)
-              :options {:on-click #(reset! *filter-by :unpacked)}
-              :icon    (ui/icon (aim-icon :unpacked))}
-
-             {:title   (t :plugin/update-available)
-              :options {:on-click #(reset! *filter-by :update-available)}
-              :icon    (ui/icon (aim-icon :update-available))}])
-          nil))
+         (fn [{:keys [toggle-fn]}]
+           (ui/button
+            (ui/icon "filter")
+            :class (str (when-not (contains? #{:default} filter-by) "picked ") "sort-or-filter-by")
+            :on-click toggle-fn
+            :intent "link"))
+
+         (if market?
+           [{:title   (t :plugin/all)
+             :options {:on-click #(reset! *filter-by :default)}
+             :icon    (ui/icon (aim-icon :default))}
+
+            {:title   (t :plugin/installed)
+             :options {:on-click #(reset! *filter-by :installed)}
+             :icon    (ui/icon (aim-icon :installed))}
+
+            {:title   (t :plugin/not-installed)
+             :options {:on-click #(reset! *filter-by :not-installed)}
+             :icon    (ui/icon (aim-icon :not-installed))}]
+
+           [{:title   (t :plugin/all)
+             :options {:on-click #(reset! *filter-by :default)}
+             :icon    (ui/icon (aim-icon :default))}
+
+            {:title   (t :plugin/enabled)
+             :options {:on-click #(reset! *filter-by :enabled)}
+             :icon    (ui/icon (aim-icon :enabled))}
+
+            {:title   (t :plugin/disabled)
+             :options {:on-click #(reset! *filter-by :disabled)}
+             :icon    (ui/icon (aim-icon :disabled))}
+
+            {:title   (t :plugin/unpacked)
+             :options {:on-click #(reset! *filter-by :unpacked)}
+             :icon    (ui/icon (aim-icon :unpacked))}
+
+            {:title   (t :plugin/update-available)
+             :options {:on-click #(reset! *filter-by :update-available)}
+             :icon    (ui/icon (aim-icon :update-available))}])
+         nil))
 
       (when market?
         (ui/dropdown-with-links
-          (fn [{:keys [toggle-fn]}]
-            (ui/button
-              (ui/icon "arrows-sort")
-              :class (str (when-not (contains? #{:default :downloads} sort-by) "picked ") "sort-or-filter-by")
-              :on-click toggle-fn
-              :intent "link"))
-          (let [aim-icon #(if (= sort-by %) "check" "circle")]
-            [{:title   (t :plugin/downloads)
-              :options {:on-click #(reset! *sort-by :downloads)}
-              :icon    (ui/icon (aim-icon :downloads))}
-
-             {:title   (t :plugin/stars)
-              :options {:on-click #(reset! *sort-by :stars)}
-              :icon    (ui/icon (aim-icon :stars))}
-
-             {:title   (t :plugin/title "A - Z")
-              :options {:on-click #(reset! *sort-by :letters)}
-              :icon    (ui/icon (aim-icon :letters))}])
-          {}))
-
-      ;; more - updater
-      (ui/dropdown-with-links
-        (fn [{:keys [toggle-fn]}]
-          (ui/button
-            (ui/icon "dots-vertical")
-            :class "more-do"
+         (fn [{:keys [toggle-fn]}]
+           (ui/button
+            (ui/icon "arrows-sort")
+            :class (str (when-not (contains? #{:default :downloads} sort-by) "picked ") "sort-or-filter-by")
             :on-click toggle-fn
             :intent "link"))
+         (let [aim-icon #(if (= sort-by %) "check" "circle")]
+           [{:title   (t :plugin/downloads)
+             :options {:on-click #(reset! *sort-by :downloads)}
+             :icon    (ui/icon (aim-icon :downloads))}
 
-        (concat (if market?
-                  [{:title   [:span.flex.items-center (ui/icon "rotate-clockwise") (t :plugin/refresh-lists)]
-                    :options {:on-click #(reload-market-fn)}}]
-                  [{:title   [:span.flex.items-center (ui/icon "rotate-clockwise") (t :plugin/check-all-updates)]
-                    :options {:on-click #(plugin-handler/user-check-enabled-for-updates! (not= :plugins category))}}])
-
-                [{:title   [:span.flex.items-center (ui/icon "world") (t :settings-page/network-proxy)]
-                  :options {:on-click #(state/pub-event! [:go/proxy-settings agent-opts])}}]
-
-                [{:title   [:span.flex.items-center (ui/icon "arrow-down-circle") (t :plugin.install-from-file/menu-title)]
-                  :options {:on-click plugin-config-handler/open-replace-plugins-modal}}]
-
-                (when (state/developer-mode?)
-                  [{:hr true}
-                   {:title   [:span.flex.items-center (ui/icon "file-code") (t :plugin/open-preferences)]
-                    :options {:on-click
-                              #(p/let [root (plugin-handler/get-ls-dotdir-root)]
-                                 (js/apis.openPath (str root "/preferences.json")))}}
-                   {:title   [:span.flex.items-center.whitespace-nowrap.space-x-1 (ui/icon "bug") (t :plugin/open-logseq-dir) [:code "~/.logseq"]]
-                    :options {:on-click
-                              #(p/let [root (plugin-handler/get-ls-dotdir-root)]
-                                 (js/apis.openPath root))}}])
-
-                [{:hr true :key "dropdown-more"}
-                 {:title   (auto-check-for-updates-control)
-                  :options {:no-padding? true}}])
-        {})
+            {:title   (t :plugin/stars)
+             :options {:on-click #(reset! *sort-by :stars)}
+             :icon    (ui/icon (aim-icon :stars))}
+
+            {:title   (t :plugin/title "A - Z")
+             :options {:on-click #(reset! *sort-by :letters)}
+             :icon    (ui/icon (aim-icon :letters))}])
+         {}))
+
+      ;; more - updater
+      (ui/dropdown-with-links
+       (fn [{:keys [toggle-fn]}]
+         (ui/button
+          (ui/icon "dots-vertical")
+          :class "more-do"
+          :on-click toggle-fn
+          :intent "link"))
+
+       (concat (if market?
+                 [{:title   [:span.flex.items-center (ui/icon "rotate-clockwise") (t :plugin/refresh-lists)]
+                   :options {:on-click #(reload-market-fn)}}]
+                 [{:title   [:span.flex.items-center (ui/icon "rotate-clockwise") (t :plugin/check-all-updates)]
+                   :options {:on-click #(plugin-handler/user-check-enabled-for-updates! (not= :plugins category))}}])
+
+               [{:title   [:span.flex.items-center (ui/icon "world") (t :settings-page/network-proxy)]
+                 :options {:on-click #(state/pub-event! [:go/proxy-settings agent-opts])}}]
+
+               [{:title   [:span.flex.items-center (ui/icon "arrow-down-circle") (t :plugin.install-from-file/menu-title)]
+                 :options {:on-click plugin-config-handler/open-replace-plugins-modal}}]
+
+               (when (state/developer-mode?)
+                 [{:hr true}
+                  {:title   [:span.flex.items-center (ui/icon "file-code") (t :plugin/open-preferences)]
+                   :options {:on-click
+                             #(p/let [root (plugin-handler/get-ls-dotdir-root)]
+                                (js/apis.openPath (str root "/preferences.json")))}}
+                  {:title   [:span.flex.items-center.whitespace-nowrap.space-x-1 (ui/icon "bug") (t :plugin/open-logseq-dir) [:code "~/.logseq"]]
+                   :options {:on-click
+                             #(p/let [root (plugin-handler/get-ls-dotdir-root)]
+                                (js/apis.openPath root))}}])
+
+               [{:hr true :key "dropdown-more"}
+                {:title   (auto-check-for-updates-control)
+                 :options {:no-padding? true}}])
+       {})
 
       ;; developer
       (panel-tab-developer)]]))
@@ -1226,11 +1226,11 @@
       [:div.tabs-inner.flex.items-center
        (ui/button [:span.it (t :plugin/installed)]
                   :on-click #(set-active! :installed)
-                  :intent "logseq" :class (if-not market? "active" ""))
+                  :intent (if-not market? "" "link"))
 
        (ui/button [:span.mk (svg/apps 16) (t :plugin/marketplace)]
                   :on-click #(set-active! :marketplace)
-                  :intent "logseq" :class (if market? "active" ""))]]
+                  :intent (if market? "" "link"))]]
 
      [:div.panels
       (if market?

+ 1 - 4
src/main/frontend/components/plugins.css

@@ -30,7 +30,6 @@
 
     .tabs {
       .ui__button {
-        background-color: transparent;
         margin: 0 8px;
 
         > span {
@@ -44,9 +43,7 @@
           }
         }
 
-        &.active {
-          background-color: var(--ls-tertiary-background-color);
-        }
+        &.active {}
       }
     }
 

+ 2 - 2
src/main/frontend/components/query/builder.cljs

@@ -454,8 +454,8 @@
                                                  block (db/pull [:block/uuid (:block/uuid block)])]
                                              (when block
                                                (let [content (string/replace (:block/content block)
-                                                                             (util/format "{{query %s" q-str)
-                                                                             (util/format "{{query %s" q))]
+                                                                             #"\{\{query[^}]+\}\}"
+                                                                             (util/format "{{query %s}}" q))]
                                                  (editor-handler/save-block! repo (:block/uuid block) content)))))))
              (assoc state ::tree *tree)))
    :will-mount (fn [state]

+ 1 - 1
src/main/frontend/components/reference.cljs

@@ -23,7 +23,7 @@
 
 (defn filtered-refs
   [page-name filters filters-atom filtered-references]
-  [:div.flex.gap-1.flex-wrap
+  [:div.flex.gap-2.flex-wrap
    (for [[ref-name ref-count] filtered-references]
      (when ref-name
        (let [lc-reference (string/lower-case ref-name)]

+ 4 - 0
src/main/frontend/components/reference.css

@@ -18,3 +18,7 @@
 .ls-filters {
     max-width: 704px;
 }
+
+.custom-query-page-result {
+    @apply p-2 my-2 rounded;
+}

+ 6 - 3
src/main/frontend/components/repo.cljs

@@ -18,7 +18,8 @@
             [goog.object :as gobj]
             [cljs.core.async :as async :refer [go <!]]
             [frontend.handler.file-sync :as file-sync]
-            [reitit.frontend.easy :as rfe]))
+            [reitit.frontend.easy :as rfe]
+            [logseq.shui.core :as shui]))
 
 (rum/defc add-repo
   [args]
@@ -254,10 +255,12 @@
                                  {:style {:top 1}}
                                  (ui/icon (if logged-in?
                                             (let [icon (str "letter-" (first (user-handler/email)))]
-                                              (if (ui/tabler-icon icon) icon "user"))
+                                              (if (shui/tabler-icon icon) icon "user"))
                                             "database") {:size (if logged-in? 12 16)
                                                          :id "database-icon"
-                                                         :class (when logged-in? "p-1 rounded color-level-5")})]
+                                                         :class (when logged-in? "p-1 rounded")
+                                                         :style {:background-color "var(--lx-gray-06-alpha, var(--color-level-5))"
+                                                                 :padding 3}})]
                                 [:div.graphs
                                  [:span#repo-switch.block.pr-2.whitespace-nowrap
                                   [:span [:span#repo-name.font-medium

+ 40 - 16
src/main/frontend/components/right_sidebar.cljs

@@ -4,7 +4,8 @@
             [frontend.components.block :as block]
             [frontend.components.onboarding :as onboarding]
             [frontend.components.page :as page]
-            [frontend.components.shortcut :as shortcut]
+            [frontend.components.shortcut-help :as shortcut-help]
+            [frontend.components.cmdk :as cmdk]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
@@ -57,7 +58,7 @@
 (rum/defc shortcut-settings
   []
   [:div.contents.flex-col.flex.ml-3
-   (shortcut/shortcut-page {:show-title? false})])
+   (shortcut-help/shortcut-page {:show-title? false})])
 
 (defn- block-with-breadcrumb
   [repo block idx sidebar-key ref?]
@@ -114,7 +115,7 @@
                 (history-stack (t :right-side-bar/history-redos) (rum/react (:redo-stack state)))]]]))
 
 (defn build-sidebar-item
-  [repo idx db-id block-type]
+  [repo idx db-id block-type *db-id init-key]
   (case (keyword block-type)
     :contents
     [[:.flex.items-center (ui/icon "list-details" {:class "text-md mr-2"}) (t :right-side-bar/contents)]
@@ -155,6 +156,22 @@
         [:span.overflow-hidden.text-ellipsis (db-model/get-page-original-name page-name)]]
        (page-cp repo page-name)])
 
+    :search
+    [[:.flex.items-center.page-title
+      (ui/icon "search" {:class "text-md mr-2"})
+      (let [input (rum/react *db-id)
+            input' (if (string/blank? input) "Blank input" input)]
+        [:span.overflow-hidden.text-ellipsis input'])]
+     (rum/with-key
+       (cmdk/cmdk-block {:initial-input db-id
+                         :sidebar? true
+                         :on-input-change (fn [new-value]
+                                            (reset! *db-id new-value))
+                         :on-input-blur (fn [new-value]
+                                            (state/sidebar-replace-block! [repo db-id block-type]
+                                                                          [repo new-value block-type]))})
+       (str init-key))]
+
     :page-slide-view
     (let [page-name (:block/name (db/entity db-id))]
       [[:a.page-title {:href (rfe/href :page {:name page-name})}
@@ -221,11 +238,16 @@
   [component _should-update?]
   component)
 
-(rum/defc sidebar-item < rum/reactive
-  [repo idx db-id block-type block-count]
+(rum/defcs sidebar-item < rum/reactive
+  {:init (fn [state] (assoc state
+                            ::db-id (atom (nth (:rum/args state) 2))
+                            ::init-key (random-uuid)))}
+  [state repo idx db-id block-type block-count]
   (let [drag-from (rum/react *drag-from)
         drag-to (rum/react *drag-to)
-        item (build-sidebar-item repo idx db-id block-type)]
+        item (build-sidebar-item repo idx db-id block-type
+                                 (::db-id state)
+                                 (::init-key state))]
     (when item
       (let [collapsed? (state/sub [:ui/sidebar-collapsed-blocks db-id])]
         [:<>
@@ -272,12 +294,14 @@
                               (context-menu-content db-id idx block-type collapsed? block-count close-fn)))
                [:button.button.close {:title (t :right-side-bar/pane-close)
                                       :on-click #(state/sidebar-remove-block! idx)} (ui/icon "x")]]]
-             [:div.pt-4.p-1 {:role "region"
-                             :id (str "sidebar-panel-content-" idx)
-                             :aria-labelledby (str "sidebar-panel-header-" idx)
-                             :class (util/classnames [{:hidden collapsed?
-                                                       :initial (not collapsed?)
-                                                       :p-4 (not (contains? #{:page :block :contents} block-type))}])}
+             [:div {:role "region"
+                    :id (str "sidebar-panel-content-" idx)
+                    :aria-labelledby (str "sidebar-panel-header-" idx)
+                    :class (util/classnames [{:hidden collapsed?
+                                              :initial (not collapsed?)
+                                              :p-4 (not (contains? #{:page :block :contents :search :shortcut-settings} block-type))
+                                              :pt-4 (not (contains? #{:search :shortcut-settings} block-type))
+                                              :p-1 (not (contains? #{:search :shortcut-settings} block-type))}])}
               (ui/catch-error
                [:span.warning "Something wrong happens"]
                (inner-component component (not drag-from)))]
@@ -435,12 +459,12 @@
             (t :right-side-bar/history)]])
         ]]
 
-      [:.sidebar-item-list.flex-1.scrollbar-spacing.ml-2.pr-3
+      [:.sidebar-item-list.flex-1.scrollbar-spacing.ml-2
        (if @*anim-finished?
          (for [[idx [repo db-id block-type]] (medley/indexed blocks)]
-           (rum/with-key
-             (sidebar-item repo idx db-id block-type block-count)
-             (str "sidebar-block-" db-id)))
+            (rum/with-key
+              (sidebar-item repo idx db-id block-type block-count)
+              (str "sidebar-block-" db-id)))
          [:div.p-4
           [:span.font-medium.opacity-50 "Loading ..."]])]]]))
 

+ 6 - 1
src/main/frontend/components/right_sidebar.css

@@ -19,6 +19,7 @@ html[data-theme=light] {
   margin-top: -8px;
   padding-bottom: 150px;
   height: calc(100vh - 48px);
+  background-color: or(--ls-right-ridebar-color, --lx-gray-01, --ls-secondary-background-color, #d8e1e8);
 }
 
 
@@ -28,7 +29,11 @@ html[data-theme=light] a.toggle:hover {
 
 .cp__header a, .cp__header button {
     opacity: 1;
-    color: var(--ls-header-button-background);
+    color: or(--ls-header-button-text-color, --lx-gray-11, --ls-header-button-background);
+}
+
+.cp__header a:hover, .cp__header button:hover, .cp__right-sidebar-topbar button:hover {
+    color: or(--ls-header-button-text-color-hover, --lx-gray-12, --ls-header-button-background);
 }
 
 .cp__right-sidebar-topbar {

+ 2 - 552
src/main/frontend/components/search.cljs

@@ -1,68 +1,7 @@
 (ns frontend.components.search
   (:require [rum.core :as rum]
-            [lambdaisland.glogi :as log]
-            [frontend.util :as util]
-            [frontend.components.block :as block]
-            [frontend.components.svg :as svg]
-            [frontend.components.search.highlight :as highlight]
-            [frontend.handler.route :as route-handler]
-            [frontend.handler.editor :as editor-handler]
-            [frontend.handler.property :as property-handler]
-            [frontend.handler.page :as page-handler]
-            [frontend.handler.notification :as notification]
-            [frontend.db :as db]
-            [frontend.db.model :as model]
             [frontend.handler.search :as search-handler]
-            [frontend.handler.whiteboard :as whiteboard-handler]
-            [frontend.handler.recent :as recent-handler]
-            [frontend.extensions.pdf.utils :as pdf-utils]
-            [frontend.ui :as ui]
-            [frontend.state :as state]
-            [frontend.mixins :as mixins]
-            [frontend.config :as config]
-            [clojure.string :as string]
-            [frontend.context.i18n :refer [t]]
-            [frontend.date :as date]
-            [reitit.frontend.easy :as rfe]
-            [frontend.modules.shortcut.core :as shortcut]
-            [frontend.util.text :as text-util]))
-
-(defn highlight-page-content-query
-  "Return hiccup of highlighted page content FTS result"
-  [content q]
-  (when-not (or (string/blank? content) (string/blank? q))
-    [:div (loop [content content ;; why recur? because there might be multiple matches
-                 result  []]
-            (let [[b-cut hl-cut e-cut] (text-util/cut-by content "$pfts_2lqh>$" "$<pfts_2lqh$")
-                  hiccups-add [(when-not (string/blank? b-cut)
-                                 [:span b-cut])
-                               (when-not (string/blank? hl-cut)
-                                 [:mark.p-0.rounded-none hl-cut])]
-                  hiccups-add (remove nil? hiccups-add)
-                  new-result (concat result hiccups-add)]
-              (if-not (string/blank? e-cut)
-                (recur e-cut new-result)
-                new-result)))]))
-
-(rum/defc search-result-item
-  [icon content]
-  [:.search-result
-   (ui/type-icon icon)
-   [:.self-center content]])
-
-(rum/defc page-content-search-result-item
-  [repo uuid format snippet q search-mode]
-  [:div
-   (when (not= search-mode :page)
-     [:div {:class "mb-1" :key "parents"}
-      (block/breadcrumb {:id "block-search-block-parent"
-                         :block? true
-                         :search? true}
-                        repo
-                        (clojure.core/uuid uuid)
-                        {:indent? false})])
-   [:div {:class "font-medium" :key "content"}
-    (highlight-page-content-query (search-handler/sanity-search-content format snippet) q)]])
+            [frontend.components.block :as block]))
 
 (rum/defc block-search-result-item
   [repo uuid format content q search-mode]
@@ -77,493 +16,4 @@
                           (clojure.core/uuid uuid)
                           {:indent? false})])
      [:div {:class "font-medium" :key "content"}
-      (highlight/highlight-exact-query content q)]]))
-
-(defonce search-timeout (atom nil))
-
-(defn- search-on-chosen-open-link
-  [repo search-q {:keys [data type alias]}]
-  (search-handler/add-search-to-recent! repo search-q)
-  (search-handler/clear-search!)
-  (case type
-    :block
-    ;; Open the first link in a block's content
-    (let [block-uuid (uuid (:block/uuid data))
-          block (:block/content (db/entity [:block/uuid block-uuid]))
-          link (re-find editor-handler/url-regex block)]
-      (if link
-        (js/window.open link)
-        (notification/show! "No link found on this block." :warning)))
-
-    :page
-    ;; Open the first link found in a page's properties
-    (let [data (or alias data)
-          page (when data (db/entity [:block/name (util/page-name-sanity-lc data)]))
-          link (some #(re-find editor-handler/url-regex (val %)) (:block/properties page))]
-      (if link
-        (js/window.open link)
-        (notification/show! "No link found on this page's properties." :warning)))
-
-    nil)
-  (state/close-modal!))
-
-(defn- search-on-chosen
-  [repo search-q {:keys [type data alias]}]
-  (search-handler/add-search-to-recent! repo search-q)
-  (search-handler/clear-search!)
-  (case type
-    :graph-add-filter
-    (state/add-graph-search-filter! search-q)
-
-    ;; If it's a journal title or journal file name, translate the title
-    :new-page
-    (if-let [journal-title (date/journal-title->custom-format search-q)]
-        (page-handler/create! journal-title {:redirect? true :journal? true})
-        (page-handler/create! search-q {:redirect? true}))
-
-    :new-class
-    (let [search-q' (subs search-q 1)]
-      (page-handler/create! search-q' {:class? true
-                                       :redirect? false})
-      (state/pub-event! [:class/configure (db/entity [:block/name (util/page-name-sanity-lc search-q')])]))
-
-    :new-whiteboard
-    (whiteboard-handler/create-new-whiteboard-and-redirect! search-q)
-
-    :page
-    (let [data (or alias data)]
-      (cond
-        (model/whiteboard-page? data)
-        (route-handler/redirect-to-whiteboard! data)
-        :else
-        (route-handler/redirect-to-page! data)))
-
-    :file
-    (route-handler/redirect! {:to :file
-                              :path-params {:path data}})
-
-    :block
-    (let [block-uuid (uuid (:block/uuid data))
-          block-uuid (or
-                      (some-> (property-handler/get-property-block-created-block [:block/uuid block-uuid])
-                              db/entity
-                              :block/uuid)
-                      block-uuid)
-          collapsed? (db/parents-collapsed? repo block-uuid)
-          page (:block/page (db/entity [:block/uuid block-uuid]))
-          page-name (:block/name page)]
-      (if page
-        (cond
-          (model/whiteboard-page? page-name)
-          (route-handler/redirect-to-whiteboard! page-name {:block-id block-uuid})
-
-          collapsed?
-          (route-handler/redirect-to-page! block-uuid)
-
-          :else
-          (route-handler/redirect-to-page! (:block/name page) {:anchor (str "ls-block-" (:block/uuid data))}))
-        ;; search indice outdated
-        (println "[Error] Block page missing: "
-                 {:block-id block-uuid
-                  :block (db/pull [:block/uuid block-uuid])})))
-
-    :page-content
-    (let [page-uuid (uuid (:block/uuid data))
-          page (model/get-block-by-uuid page-uuid)
-          page-name (:block/name page)]
-      (if page
-        (cond
-          (model/whiteboard-page? page-name)
-          (route-handler/redirect-to-whiteboard! page-name)
-          :else
-          (route-handler/redirect-to-page! page-name))
-        ;; search indice outdated
-        (println "[Error] page missing: "
-                 {:page-uuid page-uuid
-                  :page page})))
-    nil)
-  (state/close-modal!))
-
-(defn- search-on-shift-chosen
-  [repo search-q {:keys [type data alias]}]
-  (search-handler/add-search-to-recent! repo search-q)
-  (case type
-    :page
-    (let [data (or alias data)
-          page (when data (db/entity [:block/name (util/page-name-sanity-lc data)]))]
-      (when page
-        (state/sidebar-add-block!
-         repo
-         (:db/id page)
-         :page)))
-
-    :page-content
-    (let [page-uuid (uuid (:block/uuid data))
-          page (model/get-block-by-uuid page-uuid)]
-      (if page
-        (state/sidebar-add-block!
-         repo
-         (:db/id page)
-         :page)
-        ;; search indice outdated
-        (println "[Error] page missing: "
-                 {:page-uuid page-uuid
-                  :page page})))
-
-    :block
-    (let [block-uuid (uuid (:block/uuid data))
-          block (db/entity [:block/uuid block-uuid])]
-      (state/sidebar-add-block!
-       repo
-       (:db/id block)
-       :block))
-
-    :new-page
-    (page-handler/create! search-q)
-
-    :new-class
-    (page-handler/create! search-q {:class? true
-                                    :redirect? false})
-
-    :file
-    (route-handler/redirect! {:to :file
-                      :path-params {:path data}})
-
-    nil)
-  (state/close-modal!))
-
-(defn- create-item-render
-  [icon label name]
-  (search-result-item
-   {:name icon
-    :class "highlight"
-    :extension? true}
-   [:div.text.font-bold label
-    [:span.ml-2 name]]))
-
-(defn- search-item-render
-  [search-q {:keys [type data alias]}]
-  (let [search-mode (state/get-search-mode)
-        data (if (string? data) (pdf-utils/fix-local-asset-pagename data) data)]
-    [:div {:class "py-2"}
-     (case type
-       :graph-add-filter
-       [:b search-q]
-
-       :new-page
-       (create-item-render "new-page" (t :new-page) (str "\"" (string/trim search-q) "\""))
-
-       :new-class
-       ;; TODO: Add icon for new-class
-       (create-item-render "new-page" (t :new-class) (str "\"" (string/trim (subs search-q 1)) "\""))
-
-       :new-whiteboard
-       (create-item-render "new-whiteboard" (t :new-whiteboard) (str "\"" (string/trim search-q) "\""))
-
-       :page
-       [:span {:data-page-ref data}
-        (when alias
-          (let [target-original-name (model/get-page-original-name alias)]
-            [:span.mr-2.text-sm.font-medium.mb-2 (str "Alias -> " target-original-name)]))
-        (search-result-item {:name (if (model/whiteboard-page? data) "whiteboard" "page")
-                             :extension? true
-                             :title (t (if (model/whiteboard-page? data) :search-item/whiteboard :search-item/page))}
-                            (highlight/highlight-exact-query data search-q))]
-
-       :file
-       (search-result-item {:name "file"
-                            :title (t :search-item/file)}
-                           (highlight/highlight-exact-query data search-q))
-
-       :block
-       (let [{:block/keys [page uuid content]} data  ;; content here is normalized
-             page (util/get-page-original-name page)
-             repo (state/sub :git/current-repo)
-             format (db/get-page-format page)
-             block (when-not (string/blank? uuid)
-                     (model/query-block-by-uuid uuid))
-             content' (if block (:block/content block) content)]
-         [:span {:data-block-ref uuid}
-          (search-result-item {:name "block"
-                               :title (t :search-item/block)
-                               :extension? true}
-
-                              (cond
-                                (some? block)
-                                (block-search-result-item repo uuid format content' search-q search-mode)
-
-                                (not (string/blank? content'))
-                                content'
-
-                                :else
-                                (do (log/error "search result with non-existing uuid: " data)
-                                    (t :search/cache-outdated))))])
-
-       :page-content
-       (let [{:block/keys [snippet uuid]} data  ;; content here is normalized
-             repo (state/sub :git/current-repo)
-             page (when uuid (model/query-block-by-uuid uuid))  ;; it's actually a page
-             format (db/get-page-format page)]
-         (when page
-           [:span {:data-block-ref uuid}
-            (search-result-item {:name "page"
-                                 :title (t :search-item/page)
-                                 :extension? true}
-                                (if page
-                                  (page-content-search-result-item repo uuid format snippet search-q search-mode)
-                                  (do (log/error "search result with non-existing uuid: " data)
-                                      (t :search/cache-outdated))))]))
-
-       nil)]))
-
-(rum/defc search-auto-complete
-  "has-more? - if the result is truncated
-   all? - if true, in show-more mode"
-  [{:keys [engine pages files pages-content blocks has-more?] :as result} search-q all?]
-  (let [pages (when-not all? (map (fn [page]
-                                    (let [alias (model/get-redirect-page-name page)]
-                                      (cond->
-                                       {:type :page
-                                        :data page}
-                                        (and alias
-                                             (not= (util/page-name-sanity-lc page)
-                                                   (util/page-name-sanity-lc alias)))
-                                        (assoc :alias alias))))
-                                  (remove nil? pages)))
-        files (when-not all? (map (fn [file] {:type :file :data file}) files))
-        blocks (map (fn [block] {:type :block :data block}) blocks)
-        pages-content (map (fn [pages-content] {:type :page-content :data pages-content}) pages-content)
-        search-mode (state/sub :search/mode)
-        tag-search? (= \# (first search-q))
-        new-page (if (or
-                      (some? engine)
-                      (let [search-q' (util/safe-page-name-sanity-lc search-q)
-                            first-matched-item (util/safe-page-name-sanity-lc (:data (first pages)))]
-                        (and (seq pages)
-                             (or (= search-q' first-matched-item)
-                                 (and tag-search? (= search-q' (str "#" first-matched-item))))))
-                      (nil? result)
-                      all?)
-                   []
-                   (if (state/enable-whiteboards?)
-                     [{:type (if tag-search? :new-class :new-page)} {:type :new-whiteboard}]
-                     [{:type :new-page}]))
-        result (cond
-                 config/publishing?
-                 (concat pages files blocks) ;; Browser doesn't have page content FTS
-
-                 (= :whiteboard/link search-mode)
-                 (concat pages blocks pages-content)
-
-                 :else
-                 (concat new-page pages files blocks pages-content))
-        result (if (= search-mode :graph)
-                 [{:type :graph-add-filter}]
-                 result)
-        repo (state/get-current-repo)]
-    [:div.results-inner
-     (ui/auto-complete
-      result
-      {:class "search-results"
-       :on-chosen #(search-on-chosen repo search-q %)
-       :on-shift-chosen #(search-on-shift-chosen repo search-q %)
-       :item-render #(search-item-render search-q %)
-       :on-chosen-open-link #(search-on-chosen-open-link repo search-q %)})
-     (when (and has-more? (not all?))
-       [:div.px-2.py-4.search-more
-        [:a.text-sm.font-medium {:href (rfe/href :search {:q search-q})
-                                 :on-click (fn []
-                                             (when-not (string/blank? search-q)
-                                               (state/close-modal!)
-                                               (search-handler/search (state/get-current-repo) search-q {:limit 1000
-                                                                                                         :more? true})
-                                               (search-handler/clear-search!)))}
-         (t :more)]])]))
-
-(rum/defc recent-search-and-pages
-  [in-page-search?]
-  [:div.recent-search
-   [:div.wrap.px-4.pb-2.text-sm.opacity-70.flex.flex-row.justify-between.align-items.mx-1.sm:mx-0
-    [:div (t :search/recent)]
-    [:div.hidden.md:flex
-     (ui/with-shortcut :go/search-in-page "bottom"
-       [:div.flex-row.flex.align-items
-        [:div.mr-3.flex (t :search/blocks-in-page)]
-        [:div.flex.items-center
-         (ui/toggle in-page-search?
-                    (fn [_value]
-                      (state/set-search-mode! (if in-page-search? :global :page)))
-                    true)]
-        (ui/tippy {:html [:div
-                          ;; TODO: fetch from config
-                          (t :search/command-palette-tip-1) [:code (util/->platform-shortcut "Ctrl + Shift + p")] (t :search/command-palette-tip-2)]
-                   :interactive     true
-                   :arrow           true
-                   :theme       "monospace"}
-                  [:a.flex.fade-link.items-center
-                   {:style {:margin-left 12}
-                    :on-click #(state/pub-event! [:modal/command-palette])}
-                   (ui/icon "command" {:style {:font-size 20}})])])]]
-   (let [recent-search (mapv (fn [q] {:type :search :data q})
-                             (if (config/db-based-graph? (state/get-current-repo))
-                               (state/get-recent-search)
-                               (db/get-key-value :recent/search)))
-         pages (->> (recent-handler/get-recent-pages)
-                    (mapv (fn [page] {:type :page :data page})))
-         result (concat (take 5 recent-search) pages)]
-     (ui/auto-complete
-      result
-      {:on-chosen (fn [{:keys [type data]}]
-                    (case type
-                      :page
-                      (do (route-handler/redirect-to-page! data)
-                          (state/close-modal!))
-                      :search
-                      (let [q data]
-                        (state/set-q! q)
-                        (let [search-mode (state/get-search-mode)
-                              opts (if (= :page search-mode)
-                                     (let [current-page (or (state/get-current-page)
-                                                            (date/today))]
-                                       {:page-db-id (:db/id (db/entity [:block/name (util/page-name-sanity-lc current-page)]))})
-                                     {})]
-                          (if (= :page search-mode)
-                            (search-handler/search (state/get-current-repo) q opts)
-                            (search-handler/search (state/get-current-repo) q))))
-
-                      nil))
-       :on-shift-chosen (fn [{:keys [type data]}]
-                          (case type
-                            :page
-                            (let [page data]
-                              (when (string? page)
-                                (when-let [page (db/pull [:block/name (util/page-name-sanity-lc page)])]
-                                 (state/sidebar-add-block!
-                                  (state/get-current-repo)
-                                  (:db/id page)
-                                  :page))
-                                (state/close-modal!)))
-
-                            nil))
-       :item-render (fn [{:keys [type data]}]
-                      (case type
-                        :search [:div.flex-row.flex.search-item.font-medium
-                                 svg/search
-                                 [:span.ml-2 data]]
-                        :page (when-let [original-name (model/get-page-original-name data)] ;; might be block reference
-                                (search-result-item {:name "page"
-                                                     :extension? true}
-                                                    original-name))
-                        nil))}))])
-
-(defn default-placeholder
-  [search-mode]
-  (cond
-    config/publishing?
-    (t :search/publishing)
-
-    (= search-mode :whiteboard/link)
-    (t :whiteboard/link-whiteboard-or-block)
-
-    :else
-    (t :search)))
-
-(rum/defcs search-modal < rum/reactive
-  shortcut/disable-all-shortcuts
-  (mixins/event-mixin
-   (fn [state]
-     (mixins/hide-when-esc-or-outside
-      state
-      :on-hide (fn []
-                 (search-handler/clear-search!)))))
-  (rum/local nil ::active-engine-tab)
-  [state]
-  (let [search-result (state/sub :search/result)
-        search-q (state/sub :search/q)
-        search-mode (state/sub :search/mode)
-        engines (state/sub :search/engines)
-        *active-engine-tab (::active-engine-tab state)
-        timeout 300
-        in-page-search? (= search-mode :page)]
-    [:div.cp__palette.cp__palette-main
-     [:div.ls-search.p-2.md:p-0
-      [:div.input-wrap
-      [:input.cp__palette-input.w-full.h-full
-       {:type          "text"
-        :auto-focus    true
-        :placeholder   (case search-mode
-                         :graph
-                         (t :graph-search)
-                         :page
-                         (t :page-search)
-                         (default-placeholder search-mode))
-        :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
-        :value         search-q
-        :on-key-down   (fn [^js e]
-                         (when (= 27 (.-keyCode e))
-                           (when-not (string/blank? search-q)
-                             (util/stop e)
-                             (search-handler/clear-search!))))
-        :on-change     (fn [^js e]
-                         (when @search-timeout
-                           (js/clearTimeout @search-timeout))
-                         (let [value (util/evalue e)
-                               is-composing? (util/onchange-event-is-composing? e)] ;; #3199
-                           (if (and (string/blank? value) (not is-composing?))
-                             (search-handler/clear-search! false)
-                             (let [search-mode (state/get-search-mode)
-                                   opts (if (= :page search-mode)
-                                          (when-let [current-page (or (state/get-current-page)
-                                                                      (date/today))]
-                                            {:page-db-id (:db/id (db/entity [:block/name (util/page-name-sanity-lc current-page)]))})
-                                          {})]
-                               (state/set-q! value)
-                               (reset! search-timeout
-                                       (js/setTimeout
-                                        (fn []
-                                          (if (= :page search-mode)
-                                            (search-handler/search (state/get-current-repo) value opts)
-                                            (search-handler/search (state/get-current-repo) value)))
-                                        timeout))))))}]]
-      [:div.search-results-wrap
-        ;; list registered search engines
-       (when (seq engines)
-         [:ul.search-results-engines-tabs
-          [:li
-           {:class (when-not @*active-engine-tab "is-active")}
-           (ui/button
-            [:span.flex.items-center
-             (svg/logo 14) [:span.pl-2 "Default"]]
-            :background "orange"
-            :on-click #(reset! *active-engine-tab nil))]
-
-          (for [[k v] engines]
-            [:li
-             {:key k
-              :class (if (= k @*active-engine-tab) "is-active" "")}
-             (ui/button [:span.flex.items-center
-                         [:span.pr-2 (ui/icon "puzzle")]
-                         (:name v)
-                         (when-let [result (and v (:result v))]
-                           (str " (" (apply + (map count ((juxt :blocks :pages :files) result))) ")"))]
-                        :on-click #(reset! *active-engine-tab k))])])
-
-       (if-not (nil? @*active-engine-tab)
-         (let [active-engine-result (get-in engines [@*active-engine-tab :result])]
-           (search-auto-complete
-            (merge active-engine-result {:engine @*active-engine-tab}) search-q false))
-         (if (seq search-result)
-           (search-auto-complete search-result search-q false)
-           (recent-search-and-pages in-page-search?)))]]]))
-
-(rum/defc more < rum/reactive
-  [route]
-  (let [search-q (get-in route [:path-params :q])
-        search-result (state/sub :search/more-result)]
-    [:div#search.flex-1.flex
-     [:div.inner
-      [:h1.title (t :search/result-for) [:i search-q]]
-      [:p.font-medium.tx-sm (str (count (:blocks search-result)) " " (t :search/items))]
-      [:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
-       (when-not (string/blank? search-q)
-         (search-auto-complete search-result search-q true))]]]))
+      (search-handler/highlight-exact-query content q)]]))

+ 0 - 58
src/main/frontend/components/search.css

@@ -1,58 +0,0 @@
-#search {
-  > .inner {
-    width: 100%;
-    max-width: 100%;
-
-    border-radius: 4px;
-  }
-
-  .search-result {
-    @apply flex;
-  }
-}
-
-#search-wrapper {
-  transition: .3s;
-  padding-right: 12px;
-}
-
-.ls-search {
-  @apply flex flex-col overflow-auto;
-
-  > .search-results-wrap {
-    @apply flex flex-col flex-1 overflow-hidden h-full;
-
-    > .results-inner {
-      @apply h-full overflow-y-auto;
-    }
-  }
-}
-
-.search-item svg {
-  transform: scale(0.8);
-}
-
-.search-results-engines {
-  &-tabs {
-    @apply flex list-none -mx-2 mb-2 px-2;
-
-    background: var(--ls-primary-background-color);
-
-    > li {
-      @apply p-0 m-0 min-w-[150px] flex-1;
-
-      .ui__button {
-        @apply w-full h-full justify-center border;
-
-        background: transparent;
-        border-radius: 0;
-        line-height: 1;
-        color: var(--ls-primary-text-color);
-      }
-
-      &.is-active .ui__button {
-        background: var(--ls-quaternary-background-color);
-      }
-    }
-  }
-}

+ 0 - 43
src/main/frontend/components/search/highlight.cljs

@@ -1,43 +0,0 @@
-(ns frontend.components.search.highlight
-  "Search highlight component"
-  (:require [frontend.util :as util]
-            [frontend.state :as state]
-            [clojure.string :as string]))
-
-(defn highlight-exact-query
-  [content q]
-  (if (or (string/blank? content) (string/blank? q))
-    content
-    (when (and content q)
-      (let [q-words (string/split q #" ")
-            lc-content (util/search-normalize content (state/enable-search-remove-accents?))
-            lc-q (util/search-normalize q (state/enable-search-remove-accents?))]
-        (if (and (string/includes? lc-content lc-q)
-                 (not (util/safe-re-find #" " q)))
-          (let [i (string/index-of lc-content lc-q)
-                [before after] [(subs content 0 i) (subs content (+ i (count q)))]]
-            [:div
-             (when-not (string/blank? before)
-               [:span before])
-             [:mark.p-0.rounded-none (subs content i (+ i (count q)))]
-             (when-not (string/blank? after)
-               [:span after])])
-          (let [elements (loop [words q-words
-                                content content
-                                result []]
-                           (if (and (seq words) content)
-                             (let [word (first words)
-                                   lc-word (util/search-normalize word (state/enable-search-remove-accents?))
-                                   lc-content (util/search-normalize content (state/enable-search-remove-accents?))]
-                               (if-let [i (string/index-of lc-content lc-word)]
-                                 (recur (rest words)
-                                        (subs content (+ i (count word)))
-                                        (vec
-                                         (concat result
-                                                 [[:span (subs content 0 i)]
-                                                  [:mark.p-0.rounded-none (subs content i (+ i (count word)))]])))
-                                 (recur nil
-                                        content
-                                        result)))
-                             (conj result [:span content])))]
-            [:p {:class "m-0"} elements]))))))

+ 94 - 62
src/main/frontend/components/settings.cljs

@@ -1,6 +1,9 @@
 (ns frontend.components.settings
   (:require [clojure.string :as string]
             [electron.ipc :as ipc]
+            [logseq.shui.core :as shui]
+            [frontend.shui :refer [make-shui-context]]
+            [frontend.colors :as colors]
             [frontend.components.assets :as assets]
             [frontend.components.conversion :as conversion-component]
             [frontend.components.file-sync :as fs]
@@ -23,7 +26,7 @@
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.instrumentation.core :as instrument]
             [frontend.modules.shortcut.data-helper :as shortcut-helper]
-            [frontend.components.shortcut2 :as shortcut2]
+            [frontend.components.shortcut :as shortcut]
             [frontend.spec.storage :as storage-spec]
             [frontend.state :as state]
             [frontend.storage :as storage]
@@ -38,7 +41,7 @@
 
 (defn toggle
   [label-for name state on-toggle & [detail-text]]
-  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
    [:label.block.text-sm.font-medium.leading-5.opacity-70
     {:for label-for}
     name]
@@ -148,64 +151,64 @@
            :height 500}]]])
 
 (defn row-with-button-action
-  [{:keys [left-label action button-label href on-click desc -for]}]
-  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
-
+  [{:keys [left-label description action button-label href on-click desc -for stretch center?]
+    :or {center? true}}]
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4
+   {:class (if center? "sm:items-center" "sm:items-start")}
    ;; left column
-   [:label.block.text-sm.font-medium.leading-5.opacity-70
-    {:for -for}
-    left-label]
+   [:div.flex.flex-col
+    [:label.block.text-sm.font-medium.leading-5.opacity-70
+     {:for -for}
+     left-label]
+    (when description
+      [:div.text-xs.text-gray-10 description])]
 
    ;; right column
    [:div.mt-1.sm:mt-0.sm:col-span-2.flex.items-center
-    {:style {:gap "0.5rem"}}
-    [:div (cond
-            action
-            action
-            button-label
-            (ui/button
-             button-label
-             :class    "text-sm p-1"
-             :href     href
-             :on-click on-click))]
+    {:style {:display "flex" :gap "0.5rem" :align-items "center"}}
+    [:div {:style (when stretch {:width "100%"})}
+     (if action action (shui/button {:text button-label
+                                     :href href
+                                     :on-click on-click}
+                         (make-shui-context)))]
     (when-not (or (util/mobile?)
                   (mobile-util/native-platform?))
       [:div.text-sm.flex desc])]])
 
 (defn edit-config-edn []
   (row-with-button-action
-    {:left-label   (t :settings-page/custom-configuration)
-     :button-label (t :settings-page/edit-config-edn)
-     :href         (rfe/href :file {:path (config/get-repo-config-path)})
-     :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
-     :-for         "config_edn"}))
+   {:left-label   (t :settings-page/custom-configuration)
+    :button-label (t :settings-page/edit-config-edn)
+    :href         (rfe/href :file {:path (config/get-repo-config-path)})
+    :on-click     ui-handler/toggle-settings-modal!
+    :-for         "config_edn"}))
 
 (defn edit-global-config-edn []
   (row-with-button-action
     {:left-label   (t :settings-page/custom-global-configuration)
      :button-label (t :settings-page/edit-global-config-edn)
      :href         (rfe/href :file {:path (global-config-handler/global-config-path)})
-     :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
+     :on-click     ui-handler/toggle-settings-modal!
      :-for         "global_config_edn"}))
 
 (defn edit-custom-css []
   (row-with-button-action
-    {:left-label   (t :settings-page/custom-theme)
-     :button-label (t :settings-page/edit-custom-css)
-     :href         (rfe/href :file {:path (config/get-custom-css-path)})
-     :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
-     :-for         "customize_css"}))
+   {:left-label   (t :settings-page/custom-theme)
+    :button-label (t :settings-page/edit-custom-css)
+    :href         (rfe/href :file {:path (config/get-custom-css-path)})
+    :on-click     ui-handler/toggle-settings-modal!
+    :-for         "customize_css"}))
 
 (defn edit-export-css []
   (row-with-button-action
    {:left-label   (t :settings-page/export-theme)
     :button-label (t :settings-page/edit-export-css)
     :href         (rfe/href :file {:path (config/get-export-css-path)})
-    :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
-    :-for         "customize_css"}))
+    :on-click     ui-handler/toggle-settings-modal!
+    :-for         "export_css"}))
 
 (defn show-brackets-row [t show-brackets?]
-  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
    [:label.block.text-sm.font-medium.leading-5.opacity-70
     {:for "show_brackets"}
     (t :settings-page/show-brackets)]
@@ -221,7 +224,7 @@
 (rum/defcs switch-spell-check-row < rum/reactive
   [state t]
   (let [enabled? (state/sub [:electron/user-cfgs :spell-check])]
-    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
      [:label.block.text-sm.font-medium.leading-5.opacity-70
       (t :settings-page/spell-checker)]
      [:div
@@ -238,7 +241,7 @@
 (rum/defcs switch-git-auto-commit-row < rum/reactive
   [state t]
   (let [enabled? (state/get-git-auto-commit-enabled?)]
-    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
      [:label.block.text-sm.font-medium.leading-5.opacity-70
       (t :settings-page/git-switcher-label)]
      [:div
@@ -253,7 +256,7 @@
 (rum/defcs git-auto-commit-seconds < rum/reactive
   [state t]
   (let [secs (or (state/sub [:electron/user-cfgs :git/auto-commit-seconds]) 60)]
-    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
      [:label.block.text-sm.font-medium.leading-5.opacity-70
       (t :settings-page/git-commit-delay)]
      [:div.mt-1.sm:mt-0.sm:col-span-2
@@ -299,20 +302,54 @@
                              :action     action})))
 
 (defn theme-modes-row [t switch-theme system-theme? dark?]
-  (let [pick-theme [:ul.theme-modes-options
+  (let [color-accent (state/sub :ui/radix-color)
+        pick-theme [:ul.theme-modes-options
                     [:li {:on-click (partial state/use-theme-mode! "light")
-                          :class    (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light] [:strong (t :settings-page/theme-light)]]
+                          :class    (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-light)]]
                     [:li {:on-click (partial state/use-theme-mode! "dark")
-                          :class    (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark] [:strong (t :settings-page/theme-dark)]]
+                          :class    (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-dark)]]
                     [:li {:on-click (partial state/use-theme-mode! "system")
-                          :class    (classnames [{:active system-theme?}])} [:i.mode-system] [:strong (t :settings-page/theme-system)]]]]
+                          :class    (classnames [{:active system-theme?}])} [:i.mode-system {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-system)]]]]
     (row-with-button-action {:left-label (t :right-side-bar/switch-theme (string/capitalize switch-theme))
                              :-for       "toggle_theme"
                              :action     pick-theme
                              :desc       (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-theme))})))
 
+(defn accent-color-row []
+  (let [color-accent (state/sub :ui/radix-color)
+        pick-theme [:div.grid {:style {:grid-template-columns "repeat(5, 1fr)"
+                                       :gap "0.75rem"
+                                       :width "100%"
+                                       :max-width "16rem"}}
+                    (for [color colors/color-list
+                          :let [active? (= color color-accent)]]
+                      [:div.flex.items-center {:style {:height 28}}
+                       [:div {:class "w-5 h-5 rounded-full flex justify-center items-center transition ease-in duration-100 hover:cursor-pointer hover:opacity-100"
+                              :style {:background-color (colors/variable color :09)
+                                      :outline-color (colors/variable color (if active? :07 :06))
+                                      :outline-width (if active? "4px" "1px")
+                                      :outline-style :solid
+                                      :opacity (if active? 1 0.5)}
+                              :on-click (fn [_e] (state/set-color-accent! color))}
+                        [:div {:class "w-2 h-2 rounded-full transition ease-in duration-100"
+                               :style {:background-color (str "var(--rx-" (name color) "-07)")
+                                       :opacity (if active? 1 0)}}]]])
+                    (when color-accent
+                      [:div.col-span-5
+                       (shui/button {:text "Back to default color"
+                                     :theme :gray
+                                     :on-click (fn [_e] (state/unset-color-accent!))}
+                                    (make-shui-context nil nil))])]]
+
+    [:<>
+     (row-with-button-action {:left-label "Accent color"
+                              :description "Choosing an accent color will override any theme you have selected."
+                              :-for       "toggle_radix_theme"
+                              :stretch    true
+                              :action     pick-theme})]))
+
 (defn file-format-row [t preferred-format]
-  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
    [:label.block.text-sm.font-medium.leading-5.opacity-70
     {:for "preferred_format"}
     (t :settings-page/preferred-file-format)]
@@ -329,7 +366,7 @@
         [:option {:key format :value format} (string/capitalize format)])]]]])
 
 (defn date-format-row [t preferred-date-format]
-  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
    [:label.block.text-sm.font-medium.leading-5.opacity-70
     {:for "custom_date_format"}
     (t :settings-page/custom-date-format)
@@ -355,7 +392,7 @@
         [:option {:key format} format])]]]])
 
 (defn workflow-row [t preferred-workflow]
-  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
    [:label.block.text-sm.font-medium.leading-5.opacity-70
     {:for "preferred_workflow"}
     (t :settings-page/preferred-workflow)]
@@ -469,7 +506,7 @@
               (config-handler/set-config! :publishing/all-pages-public? value)))))
 
 (defn zotero-settings-row []
-  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
    [:label.block.text-sm.font-medium.leading-5.opacity-70
     {:for "zotero_settings"}
     "Zotero"]
@@ -528,15 +565,12 @@
         on-toggle #(let [v (not on?)]
                      (set-on? v)
                      (storage/set ::storage-spec/lsp-core-enabled v))]
-    [:div.flex.items-center
+    [:div.flex.items-center.gap-2
      (ui/toggle on? on-toggle true)
      (when (not= (boolean value) on?)
-       [:div.relative.opacity-70
-        [:span.absolute.whitespace-nowrap
-         {:style {:top -18 :left 10}}
-         (ui/button (t :plugin/restart)
-                    :on-click #(js/logseq.api.relaunch)
-           :small? true :intent "logseq")]])]))
+       (ui/button (t :plugin/restart)
+                  :on-click #(js/logseq.api.relaunch)
+                  :small? true :intent "logseq"))]))
 
 (rum/defc http-server-enabled-switcher
   [t]
@@ -545,15 +579,12 @@
         on-toggle #(let [v (not on?)]
                      (set-on? v)
                      (storage/set ::storage-spec/http-server-enabled v))]
-    [:div.flex.items-center
+    [:div.flex.items-center.gap-2
      (ui/toggle on? on-toggle true)
      (when (not= (boolean value) on?)
-       [:div.relative.opacity-70
-        [:span.absolute.whitespace-nowrap
-         {:style {:top -18 :left 10}}
-         (ui/button (t :plugin/restart)
-                    :on-click #(js/logseq.api.relaunch)
-                    :small? true :intent "logseq")]])]))
+       (ui/button (t :plugin/restart)
+                  :on-click #(js/logseq.api.relaunch)
+                  :small? true :intent "logseq"))]))
 
 (rum/defc flashcards-enabled-switcher
   [enable-flashcards?]
@@ -637,6 +668,7 @@
   (let [preferred-language (state/sub [:preferred-language])
         theme (state/sub :ui/theme)
         dark? (= "dark" theme)
+        show-radix-themes? true
         system-theme? (state/sub :ui/system-theme?)
         switch-theme (if dark? "light" "dark")]
     [:div.panel-wrap.is-general
@@ -644,6 +676,7 @@
      (language-row t preferred-language)
      (theme-modes-row t switch-theme system-theme? dark?)
      (when (and (util/electron?) (not util/mac?)) (native-titlebar-row t))
+     (when show-radix-themes? (accent-color-row))
      (when (config/global-config-enabled?) (edit-global-config-edn))
      (when current-repo (edit-config-edn))
      (when current-repo (edit-custom-css))
@@ -991,7 +1024,7 @@
     [:div.panel-wrap.is-features.mb-8
      (journal-row enable-journals?)
      (when (not enable-journals?)
-       [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+       [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
         [:label.block.text-sm.font-medium.leading-5.opacity-70
          {:for "default page"}
          (t :settings-page/home-default-page)]
@@ -1032,7 +1065,7 @@
 
      (when-not web-platform?
        [:<>
-        [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+        [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
          [:label.flex.font-medium.leading-5.self-start.mt-1
           (ui/icon  (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/beta-features)]]
         [:div.flex.flex-col.gap-4
@@ -1050,7 +1083,7 @@
      ;; (when-not web-platform?
      ;;   [:<>
      ;;    [:hr]
-     ;;    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+;;    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
      ;;     [:label.flex.font-medium.leading-5.self-start.mt-1 (ui/icon  (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/alpha-features)]]
      ;;    [:div.flex.flex-col.gap-4
      ;;     {:class (when-not user-handler/alpha-user? "opacity-50 pointer-events-none cursor-not-allowed")}
@@ -1105,7 +1138,6 @@
      [:div.cp__settings-inner
       [:aside.md:w-64 {:style {:min-width "10rem"}}
        [:header.cp__settings-header
-        (ui/icon "settings")
         [:h1.cp__settings-modal-title (t :settings)]]
        [:ul.settings-menu
         (for [[label id text icon]
@@ -1158,7 +1190,7 @@
          (settings-editor current-repo)
 
          :keymap
-         (shortcut2/shortcut-keymap-x)
+         (shortcut/shortcut-keymap-x)
 
          :version-control
          (settings-git)

+ 35 - 3
src/main/frontend/components/settings.css

@@ -2,7 +2,18 @@
   &-inner {
     @apply flex flex-col md:flex-row;
 
-    > aside {
+    > header {
+      padding: 10px;
+      padding-top: 0;
+      border-bottom: 1px solid or(--ls-settings-header-border-color, --lx-gray-06, --ls-quaternary-background-color);
+
+      h1 {
+        font-size: 22px;
+        margin: 0;
+      }
+    }
+
+    aside {
       @apply bg-gray-400/5 p-4;
 
       > ul > li {
@@ -95,9 +106,10 @@
 
         margin-bottom: 0;
         padding-bottom: 12px;
-        align-items: center;
 
         label {
+          min-height: 28px;
+          line-height: 28px;
           display: flex;
           align-items: center;
 
@@ -210,6 +222,18 @@
           &.mode-system {
             background-position-x: -194px;
           }
+
+          &.mode-dark.radix {
+            background: url('../img/dark-theme.png') no-repeat center / cover;
+          }
+
+          &.mode-light.radix {
+            background: url('../img/light-theme.png') no-repeat center / cover;
+          }
+
+          &.mode-system.radix {
+            background: url('../img/system-theme.png') no-repeat center / cover;
+          }
         }
 
         > strong {
@@ -435,6 +459,14 @@ html.is-native-iphone-without-notch {
       padding-bottom: 0;
     }
   }
+
+  .theme-row--swatch {
+    @apply w-5 h-5 rounded-full flex justify-center items-center;
+  }
+
+  .theme-row--swatch-active {
+    @apply w-5 h-5 rounded-full flex justify-center items-center;
+  }
 }
 
 svg.git {
@@ -456,4 +488,4 @@ body[data-settings-tab=keymap] {
       }
     }
   }
-}
+}

+ 470 - 212
src/main/frontend/components/shortcut.cljs

@@ -1,220 +1,478 @@
 (ns frontend.components.shortcut
-  (:require [clojure.string :as str]
+  (:require [clojure.string :as string]
+            [rum.core :as rum]
             [frontend.context.i18n :refer [t]]
+            [cljs-bean.core :as bean]
+            [frontend.state :as state]
+            [frontend.search :as search]
+            [frontend.ui :as ui]
+            [frontend.rum :as r]
+            [goog.events :as events]
+            [promesa.core :as p]
+            [frontend.handler.notification :as notification]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.modules.shortcut.data-helper :as dh]
+            [frontend.util :as util]
             [frontend.modules.shortcut.utils :as shortcut-utils]
-            [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.extensions.latex :as latex]
-            [frontend.extensions.highlight :as highlight]
-            [logseq.graph-parser.util.block-ref :as block-ref]
-            [logseq.graph-parser.util.page-ref :as page-ref]
-            [rum.core :as rum]))
-
-(rum/defcs customize-shortcut-dialog-inner <
-  (rum/local "")
-  (rum/local nil :rum/action)
-  (shortcut/record!)
-  [state k action-name current-binding]
-  (let [*keypress         (:rum/local state)
-        *action           (:rum/action state)
-        keypressed?       (not= "" @*keypress)
-        keyboard-shortcut (if-not keypressed? current-binding @*keypress)]
-    [:<>
-     [:div.sm:w-lsm
-      [:p.mb-4 "Press any sequence of keys to set the shortcut for the " [:b action-name] " action."]
-      [:p.mb-4.mt-4
-       (ui/render-keyboard-shortcut (-> keyboard-shortcut
-                                        (str/trim)
-                                        (str/lower-case)
-                                        (str/split  #" |\+")))
-       " "
+            [frontend.modules.shortcut.config :as shortcut-config]
+            [logseq.shui.core :as shui]
+            [frontend.shui :refer [make-shui-context]])
+  (:import [goog.events KeyHandler]))
+
+(defonce categories
+         (vector :shortcut.category/basics
+                 :shortcut.category/navigating
+                 :shortcut.category/block-editing
+                 :shortcut.category/block-command-editing
+                 :shortcut.category/block-selection
+                 :shortcut.category/formatting
+                 :shortcut.category/toggle
+                 :shortcut.category/whiteboard
+                 :shortcut.category/plugins
+                 :shortcut.category/others))
+
+(defonce *refresh-sentry (atom 0))
+(defn refresh-shortcuts-list! [] (reset! *refresh-sentry (inc @*refresh-sentry)))
+(defonce *global-listener-setup? (atom false))
+(defonce *customize-modal-life-sentry (atom 0))
+
+(defn- to-vector [v]
+  (when-not (nil? v)
+    (if (sequential? v) (vec v) [v])))
+
+(declare customize-shortcut-dialog-inner)
+
+(rum/defc keyboard-filter-record-inner
+  [keystroke set-keystroke! close-fn]
+
+  (let [keypressed? (not= "" keystroke)]
+
+    (rum/use-effect!
+      (fn []
+        (let [key-handler (KeyHandler. js/document)]
+          ;; setup
+          (util/profile
+            "[shortcuts] unlisten*"
+            (shortcut/unlisten-all! true))
+          (events/listen key-handler "key"
+                         (fn [^js e]
+                           (.preventDefault e)
+                           (set-keystroke! #(util/trim-safe (str % (shortcut/keyname e))))))
+
+          ;; teardown
+          #(do
+             (util/profile
+               "[shortcuts] listen*"
+               (shortcut/listen-all!))
+             (.dispose key-handler))))
+      [])
+
+    [:div.keyboard-filter-record
+     [:h2
+      [:strong (t :keymap/keystroke-filter)]
+      [:span.flex.space-x-2
        (when keypressed?
-         [:a.text-sm
-          {:style    {:margin-left "12px"}
-           :on-click (fn []
-                       (dh/remove-shortcut k)
-                       (shortcut/refresh!)
-                       (swap! *keypress (constantly ""))          ;; Clear local state
-                       )}
-          "Reset"])]]
-     [:div.cancel-save-buttons.text-right.mt-4
-      (ui/button "Save" :on-click (fn []
-                                    (reset! *action :save)
-                                    (state/close-modal!)))
-      [:a.ml-4
-       {:on-click (fn []
-                    (reset! *keypress (dh/binding-for-storage current-binding))
-                    (reset! *action :cancel)
-                    (state/close-modal!))} "Cancel"]]]))
-
-(defn customize-shortcut-dialog [k action-name displayed-binding]
-  (fn [_]
-    (customize-shortcut-dialog-inner k action-name displayed-binding)))
-
-(rum/defc shortcut-col [_category k binding configurable? action-name]
-  (let [conflict?         (dh/potential-conflict? k)
-        displayed-binding (dh/binding-for-display k binding)
-        disabled?         (str/includes? displayed-binding "system default")]
-    (if (not configurable?)
-      [:td.text-right displayed-binding]
-      [:td.text-right
+         [:a.flex.items-center
+          {:on-click #(set-keystroke! "")} (ui/icon "zoom-reset" {:size 12})])
+       [:a.flex.items-center
+        {:on-click #(do (close-fn) (set-keystroke! ""))} (ui/icon "x" {:size 12})]]]
+     [:div.wrap.p-2
+      (if-not keypressed?
+        [:small (t :keymap/keystroke-record-desc)]
+        (when-not (string/blank? keystroke)
+          (ui/render-keyboard-shortcut [keystroke])))]]))
+
+(rum/defc pane-controls
+  [q set-q! filters set-filters! keystroke set-keystroke! toggle-categories-fn]
+  (let [*search-ref (rum/use-ref nil)]
+    [:div.cp__shortcut-page-x-pane-controls
+     [:a.flex.items-center.icon-link
+      {:on-click toggle-categories-fn
+       :title "Toggle categories pane"}
+      (ui/icon "fold")]
+
+     [:a.flex.items-center.icon-link
+      {:on-click refresh-shortcuts-list!
+       :title "Refresh all"}
+      (ui/icon "refresh")]
+
+     [:span.search-input-wrap
+      [:input.form-input.is-small
+       {:placeholder (t :keymap/search)
+        :ref         *search-ref
+        :value       (or q "")
+        :auto-focus  true
+        :on-key-down #(when (= 27 (.-keyCode %))
+                        (util/stop %)
+                        (if (string/blank? q)
+                          (some-> (rum/deref *search-ref) (.blur))
+                          (set-q! "")))
+        :on-change   #(let [v (util/evalue %)]
+                        (set-q! v))}]
+
+      (when-not (string/blank? q)
+        [:a.x
+         {:on-click (fn []
+                      (set-q! "")
+                      (js/setTimeout #(some-> (rum/deref *search-ref) (.focus)) 50))}
+         (ui/icon "x" {:size 14})])]
+
+     ;; keyboard filter
+     (ui/dropdown
+       (fn [{:keys [toggle-fn]}]
+         [:a.flex.items-center.icon-link
+          {:on-click toggle-fn} (ui/icon "keyboard")
+
+          (when-not (string/blank? keystroke)
+            (ui/point "bg-red-600.absolute" 4 {:style {:right -2 :top -2}}))])
+       (fn [{:keys [close-fn]}]
+         (keyboard-filter-record-inner keystroke set-keystroke! close-fn))
+       {:outside?      true
+        :trigger-class "keyboard-filter"})
+
+     ;; other filter
+     (ui/dropdown-with-links
+       (fn [{:keys [toggle-fn]}]
+         [:a.flex.items-center.icon-link.relative
+          {:on-click toggle-fn}
+          (ui/icon "filter")
+
+          (when (seq filters)
+            (ui/point "bg-red-600.absolute" 4 {:style {:right -2 :top -2}}))])
+
+       (for [k [:All :Disabled :Unset :Custom]
+             :let [all? (= k :All)
+                   checked? (or (contains? filters k) (and all? (nil? (seq filters))))]]
+
+         {:title   (if all? (t :keymap/all) (t (keyword :keymap (string/lower-case (name k)))))
+          :icon    (ui/icon (if checked? "checkbox" "square"))
+          :options {:on-click #(set-filters! (if all? #{} (let [f (if checked? disj conj)] (f filters k))))}})
+
+       nil)]))
+
+(rum/defc shortcut-desc-label
+  [id binding-map]
+  (when-let [id' (and id binding-map (some-> (str id) (string/replace "plugin." "")))]
+    [:span {:title (str id' "#" (some-> (:handler-id binding-map) (name)))}
+     [:span.pl-1 (dh/get-shortcut-desc (assoc binding-map :id id))]
+     [:small.pl-1 [:code.text-xs (str id')]]]))
+
+(defn- open-customize-shortcut-dialog!
+  [id]
+  (when-let [{:keys [binding user-binding] :as m} (dh/shortcut-item id)]
+    (let [binding (to-vector binding)
+          user-binding (and user-binding (to-vector user-binding))
+          modal-id (str :customize-shortcut id)
+          label (shortcut-desc-label id m)
+          args [id label binding user-binding
+                {:saved-cb (fn [] (-> (p/delay 500) (p/then refresh-shortcuts-list!)))
+                 :modal-id modal-id}]]
+      (state/set-sub-modal!
+        (fn [] (apply customize-shortcut-dialog-inner args))
+        {:center? true
+         :id      modal-id
+         :payload args}))))
+
+(rum/defc shortcut-conflicts-display
+  [_k conflicts-map]
+
+  [:div.cp__shortcut-conflicts-list-wrap
+   (for [[g ks] conflicts-map]
+     [:section.relative
+      [:h2 (ui/icon "alert-triangle" {:size 15})
+       [:span (t :keymap/conflicts-for-label)]
+       [:code (shortcut-utils/decorate-binding g)]]
+      [:ul
+       (for [v (vals ks)
+             :let [k (first v)
+                   vs (second v)]]
+         (for [[id' handler-id] vs
+               :let [m (dh/shortcut-item id')]
+               :when (not (nil? m))]
+           [:li
+            {:key (str id')}
+            [:a.select-none.hover:underline
+             {:on-click #(open-customize-shortcut-dialog! id')
+              :title (str handler-id)}
+             [:code.inline-block.mr-1.text-xs
+              (shortcut-utils/decorate-binding k)]
+             [:span
+              (dh/get-shortcut-desc m)
+              (ui/icon "external-link" {:size 18})]
+             [:code [:small (str id')]]]]))]])])
+
+(rum/defc ^:large-vars/cleanup-todo customize-shortcut-dialog-inner
+  [k action-name binding user-binding {:keys [saved-cb modal-id]}]
+  (let [*ref-el (rum/use-ref nil)
+        [modal-life _] (r/use-atom *customize-modal-life-sentry)
+        [keystroke set-keystroke!] (rum/use-state "")
+        [current-binding set-current-binding!] (rum/use-state (or user-binding binding))
+        [key-conflicts set-key-conflicts!] (rum/use-state nil)
+
+        handler-id (rum/use-memo #(dh/get-group k))
+        dirty? (not= (or user-binding binding) current-binding)
+        keypressed? (not= "" keystroke)
+
+        save-keystroke-fn!
+        (fn []
+          ;; parse current binding conflicts
+          (if-let [current-conflicts (seq (dh/parse-conflicts-from-binding current-binding keystroke))]
+            (notification/show!
+             (str "Shortcut conflicts from existing binding: "
+                  (pr-str (some->> current-conflicts (map #(shortcut-utils/decorate-binding %)))))
+             :error true :shortcut-conflicts/warning 5000)
+
+            ;; get conflicts from the existed bindings map
+            (let [conflicts-map (dh/get-conflicts-by-keys keystroke handler-id)]
+              (if-not (seq conflicts-map)
+                (do (set-current-binding! (conj current-binding keystroke))
+                    (set-keystroke! "")
+                    (set-key-conflicts! nil))
+
+                ;; show conflicts
+                (set-key-conflicts! conflicts-map)))))]
+
+    (rum/use-effect!
+     (fn []
+       (let [mid (state/sub :modal/id)
+             mid' (some-> (state/sub :modal/subsets) (last) (:modal/id))
+             el (rum/deref *ref-el)]
+         (when (or (and (not mid') (= mid modal-id))
+                   (= mid' modal-id))
+           (some-> el (.focus))
+           (js/setTimeout
+            #(some-> (.querySelector el ".shortcut-record-control a.submit")
+                     (.click)) 200))))
+     [modal-life])
+
+    (rum/use-effect!
+     (fn []
+       (let [^js el (rum/deref *ref-el)
+             key-handler (KeyHandler. el)
+
+             teardown-global!
+             (when-not @*global-listener-setup?
+               (shortcut/unlisten-all! true)
+               (reset! *global-listener-setup? true)
+               (fn []
+                 (shortcut/listen-all!)
+                 (reset! *global-listener-setup? false)))]
+
+          ;; setup
+         (events/listen key-handler "key"
+                        (fn [^js e]
+                          (.preventDefault e)
+                          (set-key-conflicts! nil)
+                          (set-keystroke! #(util/trim-safe (str % (shortcut/keyname e))))))
+
+          ;; active
+         (.focus el)
+
+          ;; teardown
+         #(do (some-> teardown-global! (apply nil))
+              (.dispose key-handler)
+              (swap! *customize-modal-life-sentry inc))))
+     [])
+
+    [:div.cp__shortcut-page-x-record-dialog-inner
+     {:class     (util/classnames [{:keypressed keypressed? :dirty dirty?}])
+      :tab-index -1
+      :ref       *ref-el}
+     [:div.sm:w-lsm
+      [:h1.text-2xl.pb-2
+       (t :keymap/customize-for-label)]
+
+      [:p.mb-4.text-md [:b action-name]]
+
+      [:div.shortcuts-keys-wrap
+       [:span.keyboard-shortcut.flex.flex-wrap.mr-2.space-x-2
+        (for [x current-binding
+              :when (string? x)]
+          [:code.tracking-wider
+           (-> x (string/trim) (string/lower-case) (shortcut-utils/decorate-binding))
+           [:a.x {:on-click (fn [] (set-current-binding!
+                                    (->> current-binding (remove #(= x %)) (into []))))}
+            (ui/icon "x" {:size 12})]])]
+
+       ;; add shortcut
+       [:div.shortcut-record-control
+        ;; keypressed state
+        (if keypressed?
+          [:<>
+           (when-not (string/blank? keystroke)
+             (ui/render-keyboard-shortcut [keystroke]))
+
+           [:a.flex.items-center.active:opacity-90.submit
+            {:on-click save-keystroke-fn!}
+            (ui/icon "check" {:size 14})]
+           [:a.flex.items-center.text-red-600.hover:text-red-700.active:opacity-90.cancel
+            {:on-click (fn []
+                         (set-keystroke! "")
+                         (set-key-conflicts! nil))}
+            (ui/icon "x" {:size 14})]]
+
+          [:code.flex.items-center
+           [:small.pr-1 (t :keymap/keystroke-record-setup-label)] (ui/icon "keyboard" {:size 14})])]]]
+
+     ;; conflicts results
+     (when (seq key-conflicts)
+       (shortcut-conflicts-display k key-conflicts))
+
+     [:div.action-btns.text-right.mt-6.flex.justify-between.items-center
+      ;; restore default
+      (if (and dirty? (or user-binding binding))
+        [:a.flex.items-center.space-x-1.text-sm.fade-link
+         {:on-click #(set-current-binding! (or user-binding binding))}
+         (t :keymap/restore-to-default)
+         (for [it (some->> (or binding user-binding) (map #(some->> % (dh/mod-key) (shortcut-utils/decorate-binding))))]
+           [:span.keyboard-shortcut.ml-1 [:code it]])]
+        [:div])
+
+      [:div.flex.flex-row.items-center.gap-2
        (ui/button
-        displayed-binding
-        :class "text-sm p-1"
-        :style {:cursor (if disabled? "not-allowed" "pointer")}
-        :title (if conflict?
-                 "Shortcut conflict!"
-                 (if disabled? "Cannot override system default" "Click to modify"))
-        :background (if conflict? "pink" (when disabled? "gray"))
-        :on-click (when-not disabled?
-                    #(state/set-sub-modal!
-                       (customize-shortcut-dialog k action-name displayed-binding)
-                       {:center? true})))])))
-
-(rum/defcs shortcut-table
-  < rum/reactive
-    (rum/local true ::folded?)
-    {:will-mount (fn [state]
-                   (let [name (first (:rum/args state))]
-                     (cond-> state
-                             (contains? #{:shortcut.category/basics}
-                                        name)
-                             (-> ::folded? (reset! false) (do state)))))}
-  [state category configurable?]
-  (let [*folded? (::folded? state)
-        plugin?  (= category :shortcut.category/plugins)
-        _        (state/sub [:config (state/get-current-repo) :shortcuts])]
-    [:div.cp__shortcut-table-wrap
-     [:a.fold
-      {:on-click #(reset! *folded? (not @*folded?))}
-      (ui/icon (if @*folded? "chevron-left" "chevron-down"))]
-     [:table
-      [:thead
-       [:tr
-        [:th.text-left [:b (t category)]]
-        [:th.text-right]]]
-      (when-not @*folded?
-        [:tbody
-         (map (fn [[k {:keys [binding]}]]
-                (let [cmd   (dh/shortcut-cmd k)
-                      label (cond
-                              (string? (:desc cmd))
-                              [:<>
-                               [:code.text-xs (namespace k)]
-                               [:small.pl-1 (:desc cmd)]]
-
-                              (not plugin?) (-> k (shortcut-utils/decorate-namespace) (t))
-                              :else (str k))]
-                  [:tr {:key (str k)}
-                   [:td.text-left.flex.items-center label]
-                   (shortcut-col category k binding configurable? label)]))
-              (dh/binding-by-category category))])]]))
-
-(rum/defc trigger-table []
-  [:table
-   [:thead
-    [:tr
-     [:th.text-left [:b (t :help/shortcuts-triggers)]]
-     [:th.text-right [:b (t :help/shortcut)]]]]
-   [:tbody
-    [:tr
-     [:td.text-left (t :help/slash-autocomplete)]
-     [:td.text-right [:code "/"]]]
-    [:tr
-     [:td.text-left (t :help/block-content-autocomplete)]
-     [:td.text-right [:code "<"]]]
-    [:tr
-     [:td.text-left (t :help/reference-autocomplete)]
-     [:td.text-right [:code page-ref/left-and-right-brackets]]]
-    [:tr
-     [:td.text-left (t :help/block-reference)]
-     [:td.text-right [:code block-ref/left-and-right-parens]]]
-    [:tr
-     [:td.text-left (t :help/open-link-in-sidebar)]
-     [:td.text-right (ui/render-keyboard-shortcut ["shift" "click"])]]
-    [:tr
-     [:td.text-left (t :help/context-menu)]
-     [:td.text-right (ui/render-keyboard-shortcut ["right" "click"])]]]])
-
-(defn markdown-and-orgmode-syntax []
-  (let [list [:bold :italics :del :mark :latex :code :link :pre :img]
-
-        preferred-format (state/get-preferred-format) ; markdown/org
-
-        title (case preferred-format
-                :markdown (t :help/markdown-syntax)
-                :org (t :help/org-mode-syntax))
-
-        learn-more (case preferred-format
-                     :markdown "https://www.markdownguide.org/basic-syntax"
-                     :org "https://orgmode.org/worg/dev/org-syntax.html")
-
-        raw (case preferred-format
-              :markdown {:bold (str "**" (t :bold) "**")
-                         :italics (str "_" (t :italics) "_")
-                         :link "[Link](https://www.example.com)"
-                         :del (str "~~" (t :strikethrough) "~~")
-                         :mark (str "^^" (t :highlight) "^^")
-                         :latex "$$E = mc^2$$"
-                         :code (str "`" (t :code) "`")
-                         :pre "```clojure\n  (println \"Hello world!\")\n```"
-                         :img "![image](https://asset.logseq.com/static/img/logo.png)"}
-              :org {:bold (str "*" (t :bold) "*")
-                    :italics (str "/" (t :italics) "/")
-                    :del (str "+" (t :strikethrough) "+")
-                    :pre [:pre "#+BEGIN_SRC clojure\n  (println \"Hello world!\")\n#+END_SRC"]
-                    :link "[[https://www.example.com][Link]]"
-                    :mark (str "^^" (t :highlight) "^^")
-                    :latex "$$E = mc^2$$"
-                    :code "~Code~"
-                    :img "[[https://asset.logseq.com/static/img/logo.png][image]]"})
-
-        rendered {:italics [:i (t :italics)]
-                  :bold [:b (t :bold)]
-                  :link [:a {:href "https://www.example.com"} "Link"]
-                  :del [:del (t :strikethrough)]
-                  :mark [:mark (t :highlight)]
-                  :latex (latex/latex "help-latex" "E = mc^2" true false)
-                  :code [:code (t :code)]
-                  :pre (highlight/highlight "help-highlight" {:data-lang "clojure"} "(println \"Hello world!\")")
-                  :img [:img {:style {:float "right" :width 32 :height 32}
-                              :src "https://asset.logseq.com/static/img/logo.png"
-                              :alt "image"}]}]
-
-    [:table
-     [:thead
-      [:tr
-       [:th.text-left [:b title]]
-       [:th.text-right [:a {:href learn-more} "Learn more →"]]]]
-     [:tbody
-      (map (fn [name]
-             [:tr
-              [:td.text-left [(if (= :pre name) :pre :code) (get raw name)]]
-              [:td.text-right (get rendered name)]])
-        list)]]))
-
-(rum/defc keymap-tables
+        (t :save)
+        :disabled (not dirty?)
+        :on-click (fn []
+                     ;; TODO: check conflicts for the single same leader key
+                    (let [binding' (if (nil? current-binding) [] current-binding)
+                          conflicts (dh/get-conflicts-by-keys binding' handler-id {:exclude-ids #{k}})]
+                      (if (seq conflicts)
+                        (set-key-conflicts! conflicts)
+                        (let [binding' (if (= binding binding') nil binding')]
+                          (shortcut/persist-user-shortcut! k binding')
+                           ;(notification/show! "Saved!" :success)
+                          (state/close-modal!)
+                          (saved-cb))))))]]]))
+
+(defn build-categories-map
+  []
+  (->> categories
+       (map #(vector % (into (sorted-map) (dh/binding-by-category %))))))
+
+(rum/defc ^:large-vars/cleanup-todo shortcut-keymap-x
   []
-  [:div.cp__keymap-tables
-   (shortcut-table :shortcut.category/basics true)
-   (shortcut-table :shortcut.category/navigating true)
-   (shortcut-table :shortcut.category/block-editing true)
-   (shortcut-table :shortcut.category/block-command-editing true)
-   (shortcut-table :shortcut.category/block-selection true)
-   (shortcut-table :shortcut.category/formatting true)
-   (shortcut-table :shortcut.category/toggle true)
-   (when (state/enable-whiteboards?)
-     (shortcut-table :shortcut.category/whiteboard true))
-   (shortcut-table :shortcut.category/plugins true)
-   (shortcut-table :shortcut.category/others true)])
-
-(rum/defc shortcut-page
-  [{:keys [show-title?]
-    :or {show-title? true}}]
-  [:div.cp__shortcut-page
-   (when show-title? [:h1.title (t :help/shortcut-page-title)])
-   (trigger-table)
-   (markdown-and-orgmode-syntax)
-   (keymap-tables)])
+  (let [_ (r/use-atom shortcut-config/*category)
+        _ (r/use-atom *refresh-sentry)
+        [ready?, set-ready!] (rum/use-state false)
+        [filters, set-filters!] (rum/use-state #{})
+        [keystroke, set-keystroke!] (rum/use-state "")
+        [q set-q!] (rum/use-state nil)
+
+        categories-list-map (build-categories-map)
+        all-categories (into #{} (map first categories-list-map))
+        in-filters? (boolean (seq filters))
+        in-query? (not (string/blank? (util/trim-safe q)))
+        in-keystroke? (not (string/blank? keystroke))
+
+        [folded-categories set-folded-categories!] (rum/use-state #{})
+
+        matched-list-map
+        (when (and in-query? (not in-keystroke?))
+          (->> categories-list-map
+               (map (fn [[c binding-map]]
+                      [c (search/fuzzy-search
+                           binding-map q
+                           :extract-fn
+                           #(let [[id m] %]
+                              (str (name id) " " (dh/get-shortcut-desc (assoc m :id id)))))]))))
+
+        result-list-map (or matched-list-map categories-list-map)
+        toggle-categories! #(if (= folded-categories all-categories)
+                              (set-folded-categories! #{})
+                              (set-folded-categories! all-categories))]
+
+    (rum/use-effect!
+      (fn []
+        (js/setTimeout #(set-ready! true) 100))
+      [])
+
+    [:div.cp__shortcut-page-x
+     [:header.relative
+      [:h2.text-xs.opacity-70
+       (str (t :keymap/total)
+            " "
+            (if ready?
+              (apply + (map #(count (second %)) result-list-map))
+              " ..."))]
+
+      (pane-controls q set-q! filters set-filters! keystroke set-keystroke! toggle-categories!)]
+
+     [:article
+      (when-not ready?
+        [:p.py-8.flex.justify-center (ui/loading "")])
+
+      (when ready?
+        [:ul.list-none.m-0.py-3
+         (for [[c binding-map] result-list-map
+               :let [folded? (contains? folded-categories c)]]
+           [:<>
+            ;; category row
+            (when (and (not in-query?)
+                       (not in-filters?)
+                       (not in-keystroke?))
+              [:li.flex.justify-between.th
+               {:key      (str c)
+                :on-click #(let [f (if folded? disj conj)]
+                             (set-folded-categories! (f folded-categories c)))}
+               [:strong.font-semibold (t c)]
+               [:i.flex.items-center
+                (ui/icon (if folded? "chevron-left" "chevron-down"))]])
+
+            ;; binding row
+            (when (or in-query? in-filters? (not folded?))
+              (for [[id {:keys [binding user-binding] :as m}] binding-map
+                    :let [binding (to-vector binding)
+                          user-binding (and user-binding (to-vector user-binding))
+                          label (shortcut-desc-label id m)
+                          custom? (not (nil? user-binding))
+                          disabled? (or (false? user-binding)
+                                        (false? (first binding)))
+                          unset? (and (not disabled?)
+                                      (or (= user-binding [])
+                                          (and (= binding [])
+                                               (nil? user-binding))))]]
+
+                (when (or (nil? (seq filters))
+                          (when (contains? filters :Custom) custom?)
+                          (when (contains? filters :Disabled) disabled?)
+                          (when (contains? filters :Unset) unset?))
+
+                  ;; keystrokes filter
+                  (when (or (not in-keystroke?)
+                            (and (not disabled?)
+                                 (not unset?)
+                                 (let [binding' (or user-binding binding)
+                                       keystroke' (some-> (shortcut-utils/safe-parse-string-binding keystroke) (bean/->clj))]
+                                   (when (sequential? binding')
+                                     (some #(when-let [s (some-> % (dh/mod-key) (shortcut-utils/safe-parse-string-binding) (bean/->clj))]
+                                              (or (= s keystroke')
+                                                  (and (sequential? s) (sequential? keystroke')
+                                                       (apply = (map first [s keystroke']))))) binding')))))
+
+                    [:li.flex.items-center.justify-between.text-sm
+                     {:key (str id)}
+                     [:span.label-wrap label]
+
+                     [:a.action-wrap
+                      {:class    (util/classnames [{:disabled disabled?}])
+                       :on-click (when (and id (not disabled?))
+                                   #(open-customize-shortcut-dialog! id))}
+
+                      (cond
+                        (or unset? user-binding (false? user-binding))
+                        [:code.dark:bg-green-800.bg-green-300
+                         (if unset?
+                           (t :keymap/unset)
+                           (str (t :keymap/custom) ": "
+                                (if disabled?
+                                  (t :keymap/disabled)
+                                  (bean/->js
+                                    (map #(if (false? %)
+                                            (t :keymap/disabled)
+                                            (shortcut-utils/decorate-binding %)) user-binding)))))]
+
+                        (not unset?)
+                        [:code.flex.items-center.bg-transparent
+                         (shui/shortcut-v1 (string/join " | " (map #(dh/binding-for-display id %) binding))
+                                           (make-shui-context)
+                                           {:size :md})])]]))))])])]]))

+ 16 - 13
src/main/frontend/components/shortcut.css

@@ -163,31 +163,34 @@
         @apply rounded-[3px];
       }
     }
-
-    .reset-btn {
-      @apply ml-4 opacity-50 cursor-default;
-    }
-
-    &.dirty {
-      .reset-btn {
-        @apply opacity-100 cursor-pointer;
-      }
-    }
   }
 }
 
 .cp__shortcut-conflicts-list {
   &-wrap {
     > section {
-      @apply bg-gray-3 border-[2px] mb-3 dark:bg-transparent;
+      @apply bg-gray-03 border-[2px] mb-3 dark:bg-transparent;
 
       > ul {
         @apply px-2 pb-2 m-0 list-none;
       }
 
       > h2 {
-        @apply flex items-center p-2 text-red-9 text-sm space-x-1 font-extrabold;
+        @apply flex items-center p-2 text-red-600 text-sm space-x-1 font-extrabold;
       }
     }
   }
-}
+}
+
+.sidebar-item .cp__shortcut-page-x {
+    padding: 12px 0 0 0;
+    background-color: var(--color-level-2);
+}
+
+.sidebar-item article {
+    max-height: unset;
+}
+
+.keyboard-shortcut {
+    @apply inline-flex;
+}

+ 0 - 480
src/main/frontend/components/shortcut2.cljs

@@ -1,480 +0,0 @@
-(ns frontend.components.shortcut2
-  (:require [clojure.string :as string]
-            [rum.core :as rum]
-            [frontend.context.i18n :refer [t]]
-            [cljs-bean.core :as bean]
-            [frontend.state :as state]
-            [frontend.search :as search]
-            [frontend.ui :as ui]
-            [frontend.rum :as r]
-            [goog.events :as events]
-            [promesa.core :as p]
-            [frontend.handler.notification :as notification]
-            [frontend.modules.shortcut.core :as shortcut]
-            [frontend.modules.shortcut.data-helper :as dh]
-            [frontend.util :as util]
-            [frontend.modules.shortcut.utils :as shortcut-utils]
-            [frontend.modules.shortcut.config :as shortcut-config])
-  (:import [goog.events KeyHandler]))
-
-(defonce categories
-         (vector :shortcut.category/basics
-                 :shortcut.category/navigating
-                 :shortcut.category/block-editing
-                 :shortcut.category/block-command-editing
-                 :shortcut.category/block-selection
-                 :shortcut.category/formatting
-                 :shortcut.category/toggle
-                 :shortcut.category/whiteboard
-                 :shortcut.category/plugins
-                 :shortcut.category/others))
-
-(defonce *refresh-sentry (atom 0))
-(defn refresh-shortcuts-list! [] (reset! *refresh-sentry (inc @*refresh-sentry)))
-(defonce *global-listener-setup? (atom false))
-(defonce *customize-modal-life-sentry (atom 0))
-
-(defn- to-vector [v]
-  (when-not (nil? v)
-    (if (sequential? v) (vec v) [v])))
-
-(declare customize-shortcut-dialog-inner)
-
-(rum/defc keyboard-filter-record-inner
-  [keystroke set-keystroke! close-fn]
-
-  (let [keypressed? (not= "" keystroke)]
-
-    (rum/use-effect!
-      (fn []
-        (let [key-handler (KeyHandler. js/document)]
-          ;; setup
-          (util/profile
-            "[shortcuts] unlisten*"
-            (shortcut/unlisten-all! true))
-          (events/listen key-handler "key"
-                         (fn [^js e]
-                           (.preventDefault e)
-                           (set-keystroke! #(util/trim-safe (str % (shortcut/keyname e))))))
-
-          ;; teardown
-          #(do
-             (util/profile
-               "[shortcuts] listen*"
-               (shortcut/listen-all!))
-             (.dispose key-handler))))
-      [])
-
-    [:div.keyboard-filter-record
-     [:h2
-      [:strong (t :keymap/keystroke-filter)]
-      [:span.flex.space-x-2
-       (when keypressed?
-         [:a.flex.items-center
-          {:on-click #(set-keystroke! "")} (ui/icon "zoom-reset" {:size 12})])
-       [:a.flex.items-center
-        {:on-click #(do (close-fn) (set-keystroke! ""))} (ui/icon "x" {:size 12})]]]
-     [:div.wrap.p-2
-      (if-not keypressed?
-        [:small (t :keymap/keystroke-record-desc)]
-        (when-not (string/blank? keystroke)
-          (ui/render-keyboard-shortcut [keystroke])))]]))
-
-(rum/defc pane-controls
-  [q set-q! filters set-filters! keystroke set-keystroke! toggle-categories-fn]
-  (let [*search-ref (rum/use-ref nil)]
-    [:div.cp__shortcut-page-x-pane-controls
-     [:a.flex.items-center.icon-link
-      {:on-click toggle-categories-fn
-       :title "Toggle categories pane"}
-      (ui/icon "fold")]
-
-     [:a.flex.items-center.icon-link
-      {:on-click refresh-shortcuts-list!
-       :title "Refresh all"}
-      (ui/icon "refresh")]
-
-     [:span.search-input-wrap
-      [:input.form-input.is-small
-       {:placeholder (t :keymap/search)
-        :ref         *search-ref
-        :value       (or q "")
-        :auto-focus  true
-        :on-key-down #(when (= 27 (.-keyCode %))
-                        (util/stop %)
-                        (if (string/blank? q)
-                          (some-> (rum/deref *search-ref) (.blur))
-                          (set-q! "")))
-        :on-change   #(let [v (util/evalue %)]
-                        (set-q! v))}]
-
-      (when-not (string/blank? q)
-        [:a.x
-         {:on-click (fn []
-                      (set-q! "")
-                      (js/setTimeout #(some-> (rum/deref *search-ref) (.focus)) 50))}
-         (ui/icon "x" {:size 14})])]
-
-     ;; keyboard filter
-     (ui/dropdown
-       (fn [{:keys [toggle-fn]}]
-         [:a.flex.items-center.icon-link
-          {:on-click toggle-fn} (ui/icon "keyboard")
-
-          (when-not (string/blank? keystroke)
-            (ui/point "bg-red-600.absolute" 4 {:style {:right -2 :top -2}}))])
-       (fn [{:keys [close-fn]}]
-         (keyboard-filter-record-inner keystroke set-keystroke! close-fn))
-       {:outside?      true
-        :trigger-class "keyboard-filter"})
-
-     ;; other filter
-     (ui/dropdown-with-links
-       (fn [{:keys [toggle-fn]}]
-         [:a.flex.items-center.icon-link.relative
-          {:on-click toggle-fn}
-          (ui/icon "filter")
-
-          (when (seq filters)
-            (ui/point "bg-red-600.absolute" 4 {:style {:right -2 :top -2}}))])
-
-       (for [k [:All :Disabled :Unset :Custom]
-             :let [all? (= k :All)
-                   checked? (or (contains? filters k) (and all? (nil? (seq filters))))]]
-
-         {:title   (if all? (t :keymap/all) (t (keyword :keymap (string/lower-case (name k)))))
-          :icon    (ui/icon (if checked? "checkbox" "square"))
-          :options {:on-click #(set-filters! (if all? #{} (let [f (if checked? disj conj)] (f filters k))))}})
-
-       nil)]))
-
-(rum/defc shortcut-desc-label
-  [id binding-map]
-  (when-let [id' (and id binding-map (some-> (str id) (string/replace "plugin." "")))]
-    [:span {:title (str id' "#" (some-> (:handler-id binding-map) (name)))}
-     [:span.pl-1 (dh/get-shortcut-desc (assoc binding-map :id id))]
-     [:small.pl-1 [:code.text-xs (str id')]]]))
-
-(defn- open-customize-shortcut-dialog!
-  [id]
-  (when-let [{:keys [binding user-binding] :as m} (dh/shortcut-item id)]
-    (let [binding (to-vector binding)
-          user-binding (and user-binding (to-vector user-binding))
-          modal-id (str :customize-shortcut id)
-          label (shortcut-desc-label id m)
-          args [id label binding user-binding
-                {:saved-cb (fn [] (-> (p/delay 500) (p/then refresh-shortcuts-list!)))
-                 :modal-id modal-id}]]
-      (state/set-sub-modal!
-        (fn [] (apply customize-shortcut-dialog-inner args))
-        {:center? true
-         :id      modal-id
-         :payload args}))))
-
-(rum/defc shortcut-conflicts-display
-  [_k conflicts-map]
-
-  [:div.cp__shortcut-conflicts-list-wrap
-   (for [[g ks] conflicts-map]
-     [:section.relative
-      [:h2 (ui/icon "alert-triangle" {:size 15})
-       [:span (t :keymap/conflicts-for-label)]
-       [:code (shortcut-utils/decorate-binding g)]]
-      [:ul
-       (for [v (vals ks)
-             :let [k (first v)
-                   vs (second v)]]
-         (for [[id' handler-id] vs
-               :let [m (dh/shortcut-item id')]
-               :when (not (nil? m))]
-           [:li
-            {:key (str id')}
-            [:a.select-none.hover:underline
-             {:on-click #(open-customize-shortcut-dialog! id')
-              :title (str handler-id)}
-             [:code.inline-block.mr-1.text-xs
-              (shortcut-utils/decorate-binding k)]
-             [:span
-              (dh/get-shortcut-desc m)
-              (ui/icon "external-link" {:size 18})]
-             [:code [:small (str id')]]]]))]])])
-
-(rum/defc ^:large-vars/cleanup-todo customize-shortcut-dialog-inner
-  [k action-name binding user-binding {:keys [saved-cb modal-id]}]
-  (let [*ref-el (rum/use-ref nil)
-        [modal-life _] (r/use-atom *customize-modal-life-sentry)
-        [keystroke set-keystroke!] (rum/use-state "")
-        [current-binding set-current-binding!] (rum/use-state (or user-binding binding))
-        [key-conflicts set-key-conflicts!] (rum/use-state nil)
-
-        handler-id (rum/use-memo #(dh/get-group k))
-        dirty? (not= (or user-binding binding) current-binding)
-        keypressed? (not= "" keystroke)
-
-        save-keystroke-fn!
-        (fn []
-          ;; parse current binding conflicts
-          (if-let [current-conflicts (seq (dh/parse-conflicts-from-binding current-binding keystroke))]
-            (notification/show!
-              (str "Shortcut conflicts from existing binding: "
-                   (pr-str (some->> current-conflicts (map #(shortcut-utils/decorate-binding %)))))
-              :error true :shortcut-conflicts/warning 5000)
-
-            ;; get conflicts from the existed bindings map
-            (let [conflicts-map (dh/get-conflicts-by-keys keystroke handler-id)]
-              (if-not (seq conflicts-map)
-                (do (set-current-binding! (conj current-binding keystroke))
-                    (set-keystroke! "")
-                    (set-key-conflicts! nil))
-
-                ;; show conflicts
-                (set-key-conflicts! conflicts-map)))))]
-
-    (rum/use-effect!
-      (fn []
-        (let [mid (state/sub :modal/id)
-              mid' (some-> (state/sub :modal/subsets) (last) (:modal/id))
-              el (rum/deref *ref-el)]
-          (when (or (and (not mid') (= mid modal-id))
-                    (= mid' modal-id))
-            (some-> el (.focus))
-            (js/setTimeout
-              #(some-> (.querySelector el ".shortcut-record-control a.submit")
-                       (.click)) 200))))
-      [modal-life])
-
-    (rum/use-effect!
-      (fn []
-        (let [^js el (rum/deref *ref-el)
-              key-handler (KeyHandler. el)
-
-              teardown-global!
-              (when-not @*global-listener-setup?
-                (shortcut/unlisten-all! true)
-                (reset! *global-listener-setup? true)
-                (fn []
-                  (shortcut/listen-all!)
-                  (reset! *global-listener-setup? false)))]
-
-          ;; setup
-          (events/listen key-handler "key"
-                         (fn [^js e]
-                           (.preventDefault e)
-                           (set-key-conflicts! nil)
-                           (set-keystroke! #(util/trim-safe (str % (shortcut/keyname e))))))
-
-          ;; active
-          (.focus el)
-
-          ;; teardown
-          #(do (some-> teardown-global! (apply nil))
-               (.dispose key-handler)
-               (swap! *customize-modal-life-sentry inc))))
-      [])
-
-    [:div.cp__shortcut-page-x-record-dialog-inner
-     {:class     (util/classnames [{:keypressed keypressed? :dirty dirty?}])
-      :tab-index -1
-      :ref       *ref-el}
-     [:div.sm:w-lsm
-      [:h1.text-2xl.pb-2
-       (t :keymap/customize-for-label)]
-
-      [:p.mb-4.text-md [:b action-name]]
-
-      [:div.shortcuts-keys-wrap
-       [:span.keyboard-shortcut.flex.flex-wrap.mr-2.space-x-2
-        (for [x current-binding
-              :when (string? x)]
-          [:code.tracking-wider
-           (-> x (string/trim) (string/lower-case) (shortcut-utils/decorate-binding))
-           [:a.x {:on-click (fn [] (set-current-binding!
-                                     (->> current-binding (remove #(= x %)) (into []))))}
-            (ui/icon "x" {:size 12})]])]
-
-       ;; add shortcut
-       [:div.shortcut-record-control
-        ;; keypressed state
-        (if keypressed?
-          [:<>
-           (when-not (string/blank? keystroke)
-             (ui/render-keyboard-shortcut [keystroke]))
-
-           [:a.flex.items-center.active:opacity-90.submit
-            {:on-click save-keystroke-fn!}
-            (ui/icon "check" {:size 14})]
-           [:a.flex.items-center.text-red-600.hover:text-red-700.active:opacity-90.cancel
-            {:on-click (fn []
-                         (set-keystroke! "")
-                         (set-key-conflicts! nil))}
-            (ui/icon "x" {:size 14})]]
-
-          [:code.flex.items-center
-           [:small.pr-1 (t :keymap/keystroke-record-setup-label)] (ui/icon "keyboard" {:size 14})])]]]
-
-     ;; conflicts results
-     (when (seq key-conflicts)
-       (shortcut-conflicts-display k key-conflicts))
-
-     [:div.action-btns.text-right.mt-6.flex.justify-between.items-center
-      ;; restore default
-      (when (sequential? binding)
-        [:a.flex.items-center.space-x-1.text-sm.opacity-70.hover:opacity-100
-         {:on-click #(set-current-binding! binding)}
-         (t :keymap/restore-to-default)
-         (for [it (some->> binding (map #(some->> % (dh/mod-key) (shortcut-utils/decorate-binding))))]
-           [:span.keyboard-shortcut.ml-1 [:code it]])])
-
-      [:span
-       (ui/button
-         (t :save)
-         :disabled (not dirty?)
-         :on-click (fn []
-                     ;; TODO: check conflicts for the single same leader key
-                     (let [binding' (if (nil? current-binding) [] current-binding)
-                           conflicts (dh/get-conflicts-by-keys binding' handler-id {:exclude-ids #{k}})]
-                       (if (seq conflicts)
-                         (set-key-conflicts! conflicts)
-                         (let [binding' (if (= binding binding') nil binding')]
-                           (shortcut/persist-user-shortcut! k binding')
-                           ;(notification/show! "Saved!" :success)
-                           (state/close-modal!)
-                           (saved-cb))))))
-
-       [:a.reset-btn
-        {:on-click (fn [] (set-current-binding! (or user-binding binding)))}
-        (t :reset)]]]]))
-
-(defn build-categories-map
-  []
-  (->> categories
-       (map #(vector % (into (sorted-map) (dh/binding-by-category %))))))
-
-(rum/defc ^:large-vars/cleanup-todo shortcut-keymap-x
-  []
-  (let [_ (r/use-atom shortcut-config/*category)
-        _ (r/use-atom *refresh-sentry)
-        [ready?, set-ready!] (rum/use-state false)
-        [filters, set-filters!] (rum/use-state #{})
-        [keystroke, set-keystroke!] (rum/use-state "")
-        [q set-q!] (rum/use-state nil)
-
-        categories-list-map (build-categories-map)
-        all-categories (into #{} (map first categories-list-map))
-        in-filters? (boolean (seq filters))
-        in-query? (not (string/blank? (util/trim-safe q)))
-        in-keystroke? (not (string/blank? keystroke))
-
-        [folded-categories set-folded-categories!] (rum/use-state #{})
-
-        matched-list-map
-        (when (and in-query? (not in-keystroke?))
-          (->> categories-list-map
-               (map (fn [[c binding-map]]
-                      [c (search/fuzzy-search
-                           binding-map q
-                           :extract-fn
-                           #(let [[id m] %]
-                              (str (name id) " " (dh/get-shortcut-desc (assoc m :id id)))))]))))
-
-        result-list-map (or matched-list-map categories-list-map)
-        toggle-categories! #(if (= folded-categories all-categories)
-                              (set-folded-categories! #{})
-                              (set-folded-categories! all-categories))]
-
-    (rum/use-effect!
-      (fn []
-        (js/setTimeout #(set-ready! true) 100))
-      [])
-
-    [:div.cp__shortcut-page-x
-     [:header.relative
-      [:h2.text-xs.opacity-70
-       (str (t :keymap/total)
-            " "
-            (if ready?
-              (apply + (map #(count (second %)) result-list-map))
-              " ..."))]
-
-      (pane-controls q set-q! filters set-filters! keystroke set-keystroke! toggle-categories!)]
-
-     [:article
-      (when-not ready?
-        [:p.py-8.flex.justify-center (ui/loading "")])
-
-      (when ready?
-        [:ul.list-none.m-0.py-3
-         (for [[c binding-map] result-list-map
-               :let [folded? (contains? folded-categories c)]]
-           [:<>
-            ;; category row
-            (when (and (not in-query?)
-                       (not in-filters?)
-                       (not in-keystroke?))
-              [:li.flex.justify-between.th
-               {:key      (str c)
-                :on-click #(let [f (if folded? disj conj)]
-                             (set-folded-categories! (f folded-categories c)))}
-               [:strong.font-semibold (t c)]
-               [:i.flex.items-center
-                (ui/icon (if folded? "chevron-left" "chevron-down"))]])
-
-            ;; binding row
-            (when (or in-query? in-filters? (not folded?))
-              (for [[id {:keys [binding user-binding] :as m}] binding-map
-                    :let [binding (to-vector binding)
-                          user-binding (and user-binding (to-vector user-binding))
-                          label (shortcut-desc-label id m)
-                          custom? (not (nil? user-binding))
-                          disabled? (or (false? user-binding)
-                                        (false? (first binding)))
-                          unset? (and (not disabled?)
-                                      (or (= user-binding [])
-                                          (and (= binding [])
-                                               (nil? user-binding))))]]
-
-                (when (or (nil? (seq filters))
-                          (when (contains? filters :Custom) custom?)
-                          (when (contains? filters :Disabled) disabled?)
-                          (when (contains? filters :Unset) unset?))
-
-                  ;; keystrokes filter
-                  (when (or (not in-keystroke?)
-                            (and (not disabled?)
-                                 (not unset?)
-                                 (let [binding' (or user-binding binding)
-                                       keystroke' (some-> (shortcut-utils/safe-parse-string-binding keystroke) (bean/->clj))]
-                                   (when (sequential? binding')
-                                     (some #(when-let [s (some-> % (dh/mod-key) (shortcut-utils/safe-parse-string-binding) (bean/->clj))]
-                                              (or (= s keystroke')
-                                                  (and (sequential? s) (sequential? keystroke')
-                                                       (apply = (map first [s keystroke']))))) binding')))))
-
-                    [:li.flex.items-center.justify-between.text-sm
-                     {:key (str id)}
-                     [:span.label-wrap label]
-
-                     [:a.action-wrap
-                      {:class    (util/classnames [{:disabled disabled?}])
-                       :on-click (when (and id (not disabled?))
-                                   #(open-customize-shortcut-dialog! id))}
-
-                      (cond
-                        (or unset? user-binding (false? user-binding))
-                        [:code.dark:bg-green-800.bg-green-300
-                         (if unset?
-                           (t :keymap/unset)
-                           (str (t :keymap/custom) ": "
-                                (if disabled?
-                                  (t :keymap/disabled)
-                                  (bean/->js
-                                    (map #(if (false? %)
-                                            (t :keymap/disabled)
-                                            (shortcut-utils/decorate-binding %)) user-binding)))))]
-
-                        (not unset?)
-                        (for [x binding]
-                          [:code.tracking-wide
-                           {:key (str x)}
-                           (dh/binding-for-display id x)]))
-                      ]]))))])])]]))

+ 105 - 0
src/main/frontend/components/shortcut_help.cljs

@@ -0,0 +1,105 @@
+(ns frontend.components.shortcut-help
+  "Shortcut help"
+  (:require [frontend.context.i18n :refer [t]]
+            [frontend.state :as state]
+            [frontend.extensions.latex :as latex]
+            [frontend.extensions.highlight :as highlight]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [logseq.graph-parser.util.page-ref :as page-ref]
+            [rum.core :as rum]
+            [frontend.components.shortcut :as shortcut]
+            [logseq.shui.core :as shui]))
+
+(rum/defc trigger-table []
+  [:table
+   [:thead
+    [:tr
+     [:th.text-left [:b (t :help/shortcuts-triggers)]]
+     [:th.text-right [:b (t :help/shortcut)]]]]
+   [:tbody
+    [:tr
+     [:td.text-left (t :help/slash-autocomplete)]
+     [:td.text-right [:code "/"]]]
+    [:tr
+     [:td.text-left (t :help/search)]
+     [:td.text-right [:div.float-right
+                      (shui/shortcut ["mod" "k"] (shui/make-context))]]]
+    [:tr
+     [:td.text-left (t :help/reference-autocomplete)]
+     [:td.text-right [:code page-ref/left-and-right-brackets]]]
+    [:tr
+     [:td.text-left (t :help/block-reference)]
+     [:td.text-right [:code block-ref/left-and-right-parens]]]
+    [:tr
+     [:td.text-left (t :help/open-link-in-sidebar)]
+     [:td.text-right [:code "Shift click reference"]]]
+    [:tr
+     [:td.text-left (t :help/context-menu)]
+     [:td.text-right [:code "Right click bullet"]]]]])
+
+(defn markdown-and-orgmode-syntax []
+  (let [list [:bold :italics :del :mark :latex :code :link :pre :img]
+
+        preferred-format (state/get-preferred-format) ; markdown/org
+
+        title (case preferred-format
+                :markdown (t :help/markdown-syntax)
+                :org (t :help/org-mode-syntax))
+
+        learn-more (case preferred-format
+                     :markdown "https://www.markdownguide.org/basic-syntax"
+                     :org "https://orgmode.org/worg/dev/org-syntax.html")
+
+        raw (case preferred-format
+              :markdown {:bold (str "**" (t :bold) "**")
+                         :italics (str "_" (t :italics) "_")
+                         :link "[Link](https://www.example.com)"
+                         :del (str "~~" (t :strikethrough) "~~")
+                         :mark (str "^^" (t :highlight) "^^")
+                         :latex "$$E = mc^2$$"
+                         :code (str "`" (t :code) "`")
+                         :pre "```clojure\n  (println \"Hello world!\")\n```"
+                         :img "![image](https://asset.logseq.com/static/img/logo.png)"}
+              :org {:bold (str "*" (t :bold) "*")
+                    :italics (str "/" (t :italics) "/")
+                    :del (str "+" (t :strikethrough) "+")
+                    :pre [:pre "#+BEGIN_SRC clojure\n  (println \"Hello world!\")\n#+END_SRC"]
+                    :link "[[https://www.example.com][Link]]"
+                    :mark (str "^^" (t :highlight) "^^")
+                    :latex "$$E = mc^2$$"
+                    :code "~Code~"
+                    :img "[[https://asset.logseq.com/static/img/logo.png][image]]"})
+
+        rendered {:italics [:i (t :italics)]
+                  :bold [:b (t :bold)]
+                  :link [:a {:href "https://www.example.com"} "Link"]
+                  :del [:del (t :strikethrough)]
+                  :mark [:mark (t :highlight)]
+                  :latex (latex/latex "help-latex" "E = mc^2" true false)
+                  :code [:code (t :code)]
+                  :pre (highlight/highlight "help-highlight" {:data-lang "clojure"} "(println \"Hello world!\")")
+                  :img [:img {:style {:float "right" :width 32 :height 32}
+                              :src "https://asset.logseq.com/static/img/logo.png"
+                              :alt "image"}]}]
+
+    [:table
+     [:thead
+      [:tr
+       [:th.text-left [:b title]]
+       [:th.text-right [:a {:href learn-more} "Learn more →"]]]]
+     [:tbody
+      (map (fn [name]
+             [:tr
+              [:td.text-left [(if (= :pre name) :pre :code) (get raw name)]]
+              [:td.text-right (get rendered name)]])
+        list)]]))
+
+
+(rum/defc shortcut-page
+  [{:keys [show-title?]
+    :or {show-title? true}}]
+  [:div.cp__shortcut-page
+   (when show-title? [:h1.title (t :help/shortcut-page-title)])
+   (trigger-table)
+   (markdown-and-orgmode-syntax)
+   (shortcut/shortcut-keymap-x)])

+ 0 - 8
src/main/frontend/components/svg.cljs

@@ -193,14 +193,6 @@
      "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z",
      :fill-rule "evenodd"}]])
 
-(def search
-  [:svg.h-5.w-5
-   {:view-box "0 0 20 20", :fill "currentColor"}
-   [:path
-    {:d "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
-     :clip-rule "evenodd"
-     :fill-rule "evenodd"}]])
-
 (def edit
   [:svg.h-6.w-6
    {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}

+ 4 - 4
src/main/frontend/components/theme.css

@@ -1,6 +1,6 @@
 :root {
   scrollbar-width: thin;
-  scrollbar-color: var(--ls-scrollbar-foreground-color) var(--ls-scrollbar-background-color);
+  scrollbar-color: or(--lx-gray-05, --ls-scrollbar-foreground-color) or(--lx-gray-02, --ls-scrollbar-background-color);
 
   --ls-z-index-level-0: 0;
   --ls-z-index-level-1: 9;
@@ -12,17 +12,17 @@
 
 html {
   ::-webkit-scrollbar-thumb {
-    background-color: var(--ls-scrollbar-foreground-color);
+    background-color: or(--ls-scrollbar-thumb-color, --lx-gray-05, --ls-scrollbar-foreground-color);
   }
 
   ::-webkit-scrollbar {
-    background-color: var(--ls-scrollbar-background-color);
+    background-color: or(--ls-scrollbar-color, --lx-gray-02, --ls-scrollbar-background-color);
     width: var(--ls-scrollbar-width);
     height: 8px;
   }
 
   ::-webkit-scrollbar-thumb:active {
-    background-color: var(--ls-scrollbar-thumb-hover-color);
+    background-color: or(--ls-scrollbar-thumb-color-active, --lx-gray-06, --ls-scrollbar-thumb-hover-color);
   }
 
   ::-webkit-scrollbar-corner {

+ 9 - 9
src/main/frontend/components/whiteboard.cljs

@@ -216,19 +216,16 @@
           (str " · " total-whiteboards)]]
         [:div.flex-1]
         (when has-checked?
-          [:button.ui__button.m-0.py-1.inline-flex.items-center.bg-red-800
-           {:on-click
+          (ui/button
+           (count checked-page-names)
+           {:icon "trash"
+            :on-click
             (fn []
               (state/set-modal! (page/batch-delete-dialog
                                  (map (fn [name]
                                         (some (fn [w] (when (= (:block/name w) name) w)) whiteboards))
                                       checked-page-names)
-                                 false route-handler/redirect-to-whiteboard-dashboard!)))}
-           [:span.flex.gap-2.items-center
-            [:span.opacity-50 (ui/icon "trash" {:style {:font-size 15}})]
-            (t :delete)
-            [:span.opacity-50
-             (str " · " (count checked-page-names))]]])]
+                                 false route-handler/redirect-to-whiteboard-dashboard!)))}))]
        [:div
         {:ref ref}
         [:div.gap-8.grid.grid-rows-auto
@@ -303,7 +300,10 @@
    [:p (t :on-boarding/welcome-whiteboard-modal-description)]
 
    [:div.pt-6.flex.justify-center.space-x-2.sm:justify-end
-    (ui/button (t :on-boarding/welcome-whiteboard-modal-skip) :on-click close-fn :background "gray" :class "opacity-60")
+    (ui/button (t :on-boarding/welcome-whiteboard-modal-skip)
+               :on-click close-fn
+               :background "gray"
+               :class "opacity-60 skip-welcome")
     (ui/button (t :on-boarding/welcome-whiteboard-modal-start)
                :on-click (fn []
                            (quick-tour/ready

+ 15 - 3
src/main/frontend/components/whiteboard.css

@@ -50,11 +50,15 @@ h1.title.whiteboard-dashboard-title {
 
 .dashboard-create-card {
   @apply items-center justify-center relative;
-  background-color: var(--ls-secondary-background-color);
+  background-color: or(--ls-create-whiteboard-background, --lx-gray-02, --ls-secondary-background-color);
   box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
   border: 1px solid transparent;
 }
 
+.dark .dashboard-create-card {
+  background-color: or(--ls-create-whiteboard-background, --lx-gray-03, --ls-secondary-background-color);
+}
+
 .dashboard-create-card i {
   font-size: 24px;
 }
@@ -79,10 +83,14 @@ h1.title.whiteboard-dashboard-title {
   @apply px-4 py-3 flex flex-col;
   gap: 4px;
   border-bottom: 1px solid var(--ls-border-color);
-  background-color: var(--ls-secondary-background-color);
+  background-color: or(--ls-whiteboard-card-title-background, --lx-gray-02, --ls-secondary-background-color);
   font-size: 16px;
 }
 
+.dark .dashboard-card-title {
+  background-color: or(--ls-whiteboard-card-title-background, --lx-gray-03, --ls-secondary-background-color);
+}
+
 .dashboard-card-title-name {
   @apply truncate;
   color: var(--ls-primary-text-color);
@@ -138,12 +146,16 @@ input.tl-text-input {
   position: absolute;
   left: 2.5rem;
   top: 0;
-  background: var(--ls-primary-background-color);
   padding: 4px;
   border-radius: 0 0 12px 12px;
   z-index: 2000;
   gap: 4px;
   line-height: 1.4;
+  background: or(--ls-whiteboard-title-background, --lx-gray-01, --ls-primary-background-color);
+}
+
+.dark .whiteboard-page-title-root {
+  background: or(--ls-whiteboard-title-background, --lx-gray-02, --ls-primary-background-color);
 }
 
 .whiteboard-page-title {

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

@@ -30,7 +30,7 @@
 
  [frontend.db.model
   blocks-count blocks-count-cache delete-blocks get-pre-block
-  delete-files delete-pages-by-files get-all-block-contents get-all-tagged-pages
+  delete-files delete-pages-by-files get-all-block-contents get-all-block-avets get-all-tagged-pages get-single-block-contents
   get-all-templates get-block-and-children get-block-by-uuid get-block-children sort-by-left
   get-block-parent get-block-parents parents-collapsed? get-block-referenced-blocks get-all-referenced-blocks-uuid
   get-block-immediate-children get-block-page

+ 17 - 9
src/main/frontend/db/model.cljs

@@ -696,6 +696,7 @@ independent of format as format specific heading characters are stripped"
 
 (defn get-block-page
   [repo block-uuid]
+  (assert (uuid? block-uuid) "get-block-page requires block-uuid to be of type uuid")
   (when-let [block (db-utils/entity repo [:block/uuid block-uuid])]
     (db-utils/entity repo (:db/id (:block/page block)))))
 
@@ -1330,22 +1331,29 @@ independent of format as format specific heading characters are stripped"
            [?referee-b :block/refs ?refed-b]] db)))
 
 ;; block/uuid and block/content
+(defn get-single-block-contents [id]
+  (let [e (db-utils/entity [:block/uuid id])]
+    (when (and (not (:block/name e))
+               (not (string/blank? (:block/content e))))
+      {:db/id (:db/id e)
+       :block/uuid id
+       :block/page (:db/id (:block/page e))
+       :block/content (:block/content e)
+       :block/format (:block/format e)})))
+
 (defn get-all-block-contents
   []
   (when-let [db (conn/get-db)]
     (->> (d/datoms db :avet :block/uuid)
          (map :v)
-         (map (fn [id]
-                (let [e (db-utils/entity [:block/uuid id])]
-                  (when (and (not (:block/name e))
-                             (not (string/blank? (:block/content e))))
-                    {:db/id (:db/id e)
-                     :block/uuid id
-                     :block/page (:db/id (:block/page e))
-                     :block/content (:block/content e)
-                     :block/format (:block/format e)}))))
+         (map get-single-block-contents)
          (remove nil?))))
 
+(defn get-all-block-avets
+  []
+  (when-let [db (conn/get-db)]
+    (->> (d/datoms db :avet :block/uuid))))
+
 ;; Deprecated?
 (defn delete-blocks
   [repo-url files _delete-page?]

+ 20 - 20
src/main/frontend/db/react.cljs

@@ -269,26 +269,26 @@
       ;; (or skip-query-time-check?
       ;;       (<= (or query-time 0) 80))
     (let [new-result (->
-                     (cond
-                       query-fn
-                       (let [result (query-fn db tx result)]
-                         (if (coll? result)
-                           (doall result)
-                           result))
-
-                       inputs-fn
-                       (let [inputs (inputs-fn)]
-                         (apply d/q query db inputs))
-
-                       (keyword? query)
-                       (db-utils/get-key-value graph query)
-
-                       (seq inputs)
-                       (apply d/q query db inputs)
-
-                       :else
-                       (d/q query db))
-                     transform-fn)]
+                      (cond
+                        query-fn
+                        (let [result (query-fn db tx result)]
+                          (if (coll? result)
+                            (doall result)
+                            result))
+
+                        inputs-fn
+                        (let [inputs (inputs-fn)]
+                          (apply d/q query db inputs))
+
+                        (keyword? query)
+                        (db-utils/get-key-value graph query)
+
+                        (seq inputs)
+                        (apply d/q query db inputs)
+
+                        :else
+                        (d/q query db))
+                      transform-fn)]
      (when-not (= new-result result)
        (set-new-result! k new-result)))))
 

+ 17 - 2
src/main/frontend/extensions/code.cljs

@@ -387,7 +387,10 @@
         lisp-like? (contains? #{"scheme" "lisp" "clojure" "edn"} mode)
         config-edit? (and (:file? config) (string/ends-with? (:file-path config) "config.edn"))
         textarea (gdom/getElement id)
-        default-cm-options {:theme (str "solarized " theme)
+        radix-color (state/sub :ui/radix-color)
+        default-cm-options {:theme (if radix-color 
+                                     (str "lsradix " theme)
+                                     (str "solarized " theme))
                             :autoCloseBrackets true
                             :lineNumbers true
                             :matchBrackets lisp-like?
@@ -470,17 +473,29 @@
       (let [editor (render! state)]
         (reset! editor-atom editor)))))
 
+(defn get-theme! []
+  (if (state/sub :ui/radix-color)
+    (str "lsradix " (state/sub :ui/theme))
+    (str "solarized " (state/sub :ui/theme))))
+
 (rum/defcs editor < rum/reactive
   {:init (fn [state]
            (let [[_ _ _ code _ options] (:rum/args state)]
              (assoc state
                     :editor-atom (atom nil)
                     :calc-atom (atom (calc/eval-lines code))
-                    :code-options (atom options))))
+                    :code-options (atom options)
+                    :last-theme (atom (get-theme!)))))
    :did-mount (fn [state]
                 (load-and-render! state)
                 state)
    :did-update (fn [state]
+                 (let [next-theme (get-theme!)
+                       last-theme @(:last-theme state)
+                       editor (some-> state :editor-atom deref)]
+                   (when (and editor (not= next-theme last-theme)) 
+                     (reset! (:last-theme state) next-theme)
+                     (.setOption editor "theme" next-theme)))
                  (reset! (:code-options state) (last (:rum/args state)))
                  (when-not (:file? (first (:rum/args state)))
                    (let [code (nth (:rum/args state) 3)

+ 4 - 0
src/main/frontend/extensions/code.css

@@ -69,3 +69,7 @@
     cursor: pointer;
   }
 }
+
+.cm-s-solarized.CodeMirror {
+    box-shadow: none;
+}

+ 1 - 1
src/main/frontend/extensions/handbooks/handbooks.css

@@ -95,7 +95,7 @@
         @apply leading-none flex-1;
 
         > strong {
-          @apply font-medium text-sm pt-0.5 pb-[1px] opacity-90 leading-5 dark:text-gray-5;
+          @apply font-medium text-sm pt-0.5 pb-[1px] opacity-90 leading-5 dark:text-gray-50;
         }
 
         > span {

+ 0 - 2
src/main/frontend/extensions/pdf/pdf.css

@@ -375,8 +375,6 @@ input::-webkit-inner-spin-button {
 
         .ui__button {
           margin-top: unset;
-          border-radius: 4px;
-          color: black;
           padding: 4px;
 
           .ti {

+ 24 - 8
src/main/frontend/extensions/pdf/toolbar.cljs

@@ -244,14 +244,30 @@
                            :dune))}]
 
         (when entered-active0?
-          (ui/button (ui/icon "arrow-back") :title "Enter to search" :class "icon-enter" :intent "true" :small? true))]
-
-       (ui/button (ui/icon "letter-case")
-                  :class (string/join " " (util/classnames [{:active case-sensitive?}]))
-                  :intent "true" :small? true :on-click #(set-case-sensitive? (not case-sensitive?)))
-       (ui/button (ui/icon "chevron-up") :intent "true" :small? true :on-click #(do (do-find! {:type :again :prev? true}) (util/stop %)))
-       (ui/button (ui/icon "chevron-down") :intent "true" :small? true :on-click #(do (do-find! {:type :again}) (util/stop %)))
-       (ui/button (ui/icon "x") :intent "true" :small? true :on-click close-finder!)]
+          (ui/button {:icon "arrow-back"
+                      :intent "link"
+                      :title "Enter to search"
+                      :class "icon-enter"
+                      :small? true}))]
+
+       (ui/button {:icon "letter-case"
+                   :intent "link"
+                   :class (string/join " " (util/classnames [{:active case-sensitive?}]))
+                   :small? true :on-click #(set-case-sensitive? (not case-sensitive?))})
+
+       (ui/button {:icon "chevron-up"
+                   :intent "link"
+                   :small? true :on-click #(do (do-find! {:type :again :prev? true}) (util/stop %))})
+
+       (ui/button
+         {:icon "chevron-down"
+          :intent "link"
+          :small? true :on-click #(do (do-find! {:type :again}) (util/stop %))})
+
+       (ui/button
+         {:icon "x"
+          :intent "link"
+          :small? true :on-click close-finder!})]
 
       [:div.result-inner
        (when-let [status (and entered-active?

+ 24 - 18
src/main/frontend/extensions/srs.cljs

@@ -418,7 +418,7 @@
 (defn- btn-with-shortcut [{:keys [shortcut id btn-text background on-click class]}]
   (ui/button
    [:span btn-text (when-not (util/sm-breakpoint?)
-                     [" " (ui/render-keyboard-shortcut shortcut)])]
+                     [" " (ui/render-keyboard-shortcut shortcut {:theme :text})])]
    :id id
    :class (str id " " class)
    :background background
@@ -647,14 +647,14 @@
            (if @*preview-mode?
              (ui/tippy {:html [:div.text-sm (t :flashcards/modal-current-total)]
                         :interactive true}
-                       [:div.opacity-60.text-sm.mr-3
+                       [:div.opacity-60.text-sm.mr-2
                         @*card-index
                         [:span "/"]
                         total])
              (ui/tippy {:html [:div.text-sm (t :flashcards/modal-overdue-total)]
                         ;; :class "tippy-hover"
                         :interactive true}
-                       [:div.opacity-60.text-sm.mr-3
+                       [:div.opacity-60.text-sm.mr-2
                         (max 0 (- filtered-total @*card-index))
                         [:span "/"]
                         total]))
@@ -665,29 +665,35 @@
              :class "tippy-hover"
              :interactive true
              :disabled false}
-            [:a.opacity-60.hover:opacity-100.svg-small.inline.font-bold
-             {:id "preview-all-cards"
-              :style (when @*preview-mode? {:color "orange"})
-              :on-click (fn [e]
+
+            (ui/button
+             (merge
+              {:icon "letter-a"
+               :intent "link"
+               :on-click (fn [e]
                           (util/stop e)
                           (swap! *preview-mode? not)
-                          (reset! *card-index 0))}
-             "A"])
+                           (reset! *card-index 0))
+               :button-props {:id "preview-all-cards"}
+               :small? true}
+              (when @*preview-mode?
+                {:icon-props {:style {:color "var(--ls-button-background)"}}}))))
 
            (ui/tippy
             {:html [:div.text-sm (t :flashcards/modal-toggle-random-mode)]
              :delay [1000, 100]
              :class "tippy-hover"
              :interactive true}
-            [:a.mt-1.ml-2.block.opacity-60.hover:opacity-100
-             {:on-mouse-down (fn [e]
-                               (util/stop e)
-                               (swap! *random-mode? not))}
-             (ui/icon "arrows-shuffle" {:style (cond->
-                                                {:font-size 18
-                                                 :font-weight 600}
-                                                 @*random-mode?
-                                                 (assoc :color "orange"))})])]]
+            (ui/button
+             (merge
+              {:icon "arrows-shuffle"
+               :intent "link"
+               :on-click (fn [e]
+                           (util/stop e)
+                           (swap! *random-mode? not))
+               :small? true}
+              (when @*random-mode?
+                {:icon-props {:style {:color "var(--ls-button-background)"}}}))))]]
          [:div.px-1
           (when (and (not modal?) (not @*preview-mode?))
             {:on-click (fn []

+ 3 - 3
src/main/frontend/extensions/tldraw.cljs

@@ -163,9 +163,9 @@
 
        (when
         (and populate-onboarding? (not loaded-app))
-         [:div.absolute.inset-0.flex.items-center.justify-center
-          {:style {:z-index 200}}
-          (ui/loading "Loading onboarding whiteboard ...")])
+        [:div.absolute.inset-0.flex.items-center.justify-center
+         {:style {:z-index 200}}
+         (ui/loading "Loading onboarding whiteboard ...")])
        (tldraw {:renderers tldraw-renderers
                 :handlers (get-tldraw-handlers page-name)
                 :onMount on-mount

+ 3 - 2
src/main/frontend/extensions/zotero.cljs

@@ -355,8 +355,9 @@
   [:div.flex.flex-row.mb-4.items-center
    [:label.title.mr-32 {:for "profile-select"} "Choose a profile:"]
    [:div.flex.flex-row.ml-4
-    [:select.ml-1
-     {:value @profile*
+    [:select.ml-1.rounded
+     {:style {:padding "0px 36px 0px 8px"}
+      :value @profile*
       :on-change
       (fn [e]
         (when-let [profile (util/evalue e)]

+ 2 - 1
src/main/frontend/fs/memory_fs.cljs

@@ -82,7 +82,8 @@
             _ (<ensure-dir! containing-dir)
             _ (js/window.pfs.writeFile fpath content)]
       (db/set-file-content! repo rpath content)
-      (db/set-file-last-modified-at! repo rpath (js/Date.))))
+      (db/set-file-last-modified-at! repo rpath (js/Date.))
+      ))
   (rename! [_this _repo old-path new-path]
     (let [old-path (path/url-to-path old-path)
           new-path (path/url-to-path new-path)]

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

@@ -12,6 +12,7 @@
             [frontend.components.whiteboard :as whiteboard]
             [frontend.config :as config]
             [frontend.context.i18n :as i18n :refer [t]]
+            [frontend.colors :as colors]
             [frontend.db :as db]
             [frontend.db.restore :as db-restore]
             [frontend.db.conn :as conn]
@@ -54,13 +55,13 @@
   (set! js/window.onerror
         (fn [message, _source, _lineno, _colno, error]
           (when-not (error/ignored? message)
-            (log/error :exception error)
+            (log/error :exception error)))))
             ;; (notification/show!
             ;;  (str "message=" message "\nsource=" source "\nlineno=" lineno "\ncolno=" colno "\nerror=" error)
             ;;  :error
             ;;  ;; Don't auto-hide
             ;;  false)
-            ))))
+            
 
 
 (defn- watch-for-date!
@@ -200,6 +201,8 @@
   (state/set-component! :block/linked-references reference/block-linked-references)
   (state/set-component! :whiteboard/tldraw-preview whiteboard/tldraw-preview)
   (state/set-component! :block/single-block block/single-block-cp)
+  (state/set-component! :block/container block/block-container)
+  (state/set-component! :block/embed block/block-embed)
   (state/set-component! :editor/box editor/box)
   (command-palette/register-global-shortcut-commands))
 
@@ -228,6 +231,8 @@
   (instrument/init)
   (state/set-online! js/navigator.onLine)
   (set-network-watcher!)
+  (when-let [radix-color (state/get-color-accent)]
+    (colors/set-radix radix-color))
 
   (-> (util/indexeddb-check?)
       (p/catch (fn [_e]

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

@@ -205,6 +205,7 @@
 
 (defn open-block-in-sidebar!
   [block-id]
+  ; (assert (uuid? block-id) "frontend.handler.editor/open-block-in-sidebar! expects block-id to be of type uuid")
   (when block-id
     (when-let [block (db/entity (if (number? block-id) block-id [:block/uuid block-id]))]
       (let [page? (nil? (:block/page block))]
@@ -1259,7 +1260,7 @@
 
 (defn save-block!
   ([repo block-or-uuid content]
-    (save-block! repo block-or-uuid content {}))
+   (save-block! repo block-or-uuid content {}))
   ([repo block-or-uuid content {:keys [properties] :as opts}]
    (let [block (if (or (uuid? block-or-uuid)
                        (string? block-or-uuid))

+ 19 - 42
src/main/frontend/handler/events.cljs

@@ -12,14 +12,13 @@
             [clojure.string :as string]
             [frontend.commands :as commands]
             [frontend.components.class :as class-component]
-            [frontend.components.command-palette :as command-palette]
+            [frontend.components.cmdk :as cmdk]
             [frontend.components.conversion :as conversion-component]
             [frontend.components.diff :as diff]
             [frontend.components.encryption :as encryption]
             [frontend.components.file-sync :as file-sync]
             [frontend.components.git :as git-component]
             [frontend.components.plugins :as plugin]
-            [frontend.components.search :as component-search]
             [frontend.components.shell :as shell]
             [frontend.components.whiteboard :as whiteboard]
             [frontend.components.user.login :as login]
@@ -37,7 +36,6 @@
             [frontend.fs.nfs :as nfs]
             [frontend.fs.sync :as sync]
             [frontend.fs.watcher-handler :as fs-watcher]
-            [frontend.handler.command-palette :as cp]
             [frontend.handler.common :as common-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.file :as file-handler]
@@ -280,18 +278,18 @@
   (when
    (and (not (util/electron?))
         (not (mobile-util/native-platform?)))
-    (fn [close-fn]
-      [:div
-       ;; TODO: fn translation with args
-       [:p
-        "Grant native filesystem permission for directory: "
-        [:b (config/get-local-dir repo)]]
-       (ui/button
-        (t :settings-permission/start-granting)
-        :class "ui__modal-enter"
-        :on-click (fn []
-                    (nfs/check-directory-permission! repo)
-                    (close-fn)))])))
+   (fn [close-fn]
+     [:div
+      ;; TODO: fn translation with args
+      [:p
+       "Grant native filesystem permission for directory: "
+       [:b (config/get-local-dir repo)]]
+      (ui/button
+       (t :settings-permission/start-granting)
+       :class "ui__modal-enter"
+       :on-click (fn []
+                   (nfs/check-directory-permission! repo)
+                   (close-fn)))])))
 
 (defmethod handle :modal/nfs-ask-permission []
   (when-let [repo (get-local-repo)]
@@ -410,20 +408,7 @@
           ;; FIXME: an ugly implementation for redirecting to page on new window is restored
           _ (repo-handler/graph-ready! repo)
           _ (when-not (config/db-based-graph? repo)
-              (fs-watcher/load-graph-files! repo loaded-homepage-files))]
-
-  ;; TODO(junyi): Notify user to update filename format when the UX is smooth enough
-  ;; (when-not config/test?
-  ;;   (js/setTimeout
-  ;;    (fn []
-  ;;      (let [filename-format (state/get-filename-format repo)]
-  ;;        (when (and (util/electron?)
-  ;;                   (not (util/ci?))
-  ;;                   (not (config/demo-graph?))
-  ;;                   (not= filename-format :triple-lowbar))
-  ;;          (state/pub-event! [:ui/notify-outdated-filename-format []]))))
-  ;;    3000))
-    ))
+              (fs-watcher/load-graph-files! repo loaded-homepage-files))]))
 
 (defmethod handle :notification/show [[_ {:keys [content status clear?]}]]
   (notification/show! content status clear?))
@@ -433,9 +418,10 @@
     (state/set-modal! shell/shell)))
 
 (defmethod handle :go/search [_]
-  (state/set-modal! component-search/search-modal
-                    {:fullscreen? false
+  (state/set-modal! cmdk/cmdk-modal
+                    {:fullscreen? true
                      :close-btn?  false
+                     :panel?      false
                      :label "ls-modal-search"}))
 
 (defmethod handle :go/plugins [_]
@@ -666,8 +652,7 @@
       (t :yes)
       :autoFocus "on"
       :class "ui__modal-enter"
-      :large? true
-      :on-click (fn []
+       :on-click (fn []
                   (state/close-modal!)
                   (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
 
@@ -716,7 +701,6 @@
          (t :yes)
          :autoFocus "on"
          :class "ui__modal-enter"
-         :large? true
          :on-click (fn []
                      (state/close-modal!)
                      (state/pub-event! [:graph/re-index])))]])))
@@ -731,12 +715,6 @@
                   opts))
    {:center? true :close-btn? false :close-backdrop? false}))
 
-(defmethod handle :modal/command-palette [_]
-  (state/set-modal!
-   #(command-palette/command-palette {:commands (cp/get-commands)})
-   {:fullscreen? false
-    :close-btn?  false}))
-
 (defmethod handle :journal/insert-template [[_ page-name]]
   (let [page-name (util/page-name-sanity-lc page-name)]
     (when-let [page (db/pull [:block/name page-name])]
@@ -1043,5 +1021,4 @@
   (def deprecated-repo (state/get-current-repo))
   (def new-repo (string/replace deprecated-repo deprecated-app-id current-app-id))
 
-  (update-file-path deprecated-repo new-repo deprecated-app-id current-app-id)
-  )
+  (update-file-path deprecated-repo new-repo deprecated-app-id current-app-id))

+ 38 - 12
src/main/frontend/handler/search.cljs

@@ -12,18 +12,6 @@
             [electron.ipc :as ipc]
             [dommy.core :as dom]))
 
-(defn add-search-to-recent!
-  [repo q]
-  (when-not (string/blank? q)
-    (if (config/db-based-graph? repo)
-      (let [items (state/get-recent-search)
-            new-items (vec (take 10 (distinct (cons q items))))]
-        (state/set-recent-search! new-items))
-      (let [items (or (db/get-key-value repo :recent/search)
-                      '())
-            new-items (take 10 (distinct (cons q items)))]
-        (db/set-key-value repo :recent/search new-items)))))
-
 (defn sanity-search-content
   "Convert a block to the display contents for searching"
   [format content]
@@ -130,3 +118,41 @@
        (notification/show!
         "Search indices rebuilt successfully!"
         :success)))))
+
+(defn highlight-exact-query
+  [content q]
+  (if (or (string/blank? content) (string/blank? q))
+    content
+    (when (and content q)
+      (let [q-words (string/split q #" ")
+            lc-content (util/search-normalize content (state/enable-search-remove-accents?))
+            lc-q (util/search-normalize q (state/enable-search-remove-accents?))]
+        (if (and (string/includes? lc-content lc-q)
+                 (not (util/safe-re-find #" " q)))
+          (let [i (string/index-of lc-content lc-q)
+                [before after] [(subs content 0 i) (subs content (+ i (count q)))]]
+            [:div
+             (when-not (string/blank? before)
+               [:span before])
+             [:mark.p-0.rounded-none (subs content i (+ i (count q)))]
+             (when-not (string/blank? after)
+               [:span after])])
+          (let [elements (loop [words q-words
+                                content content
+                                result []]
+                           (if (and (seq words) content)
+                             (let [word (first words)
+                                   lc-word (util/search-normalize word (state/enable-search-remove-accents?))
+                                   lc-content (util/search-normalize content (state/enable-search-remove-accents?))]
+                               (if-let [i (string/index-of lc-content lc-word)]
+                                 (recur (rest words)
+                                        (subs content (+ i (count word)))
+                                        (vec
+                                         (concat result
+                                                 [[:span (subs content 0 i)]
+                                                  [:mark.p-0.rounded-none (subs content i (+ i (count word)))]])))
+                                 (recur nil
+                                        content
+                                        result)))
+                             (conj result [:span content])))]
+            [:p {:class "m-0"} elements]))))))

+ 1 - 1
src/main/frontend/handler/ui.cljs

@@ -286,7 +286,7 @@
 
 (defn toggle-cards!
   []
-  (if (:modal/show? @state/state)
+  (if (and (= :srs (:modal/id @state/state)) (:modal/show? @state/state))
     (state/close-modal!)
     (state/pub-event! [:modal/show-cards])))
 

+ 6 - 5
src/main/frontend/mixins.cljs

@@ -79,16 +79,17 @@
 (defn on-key-down
   ([state keycode-map]
    (on-key-down state keycode-map {}))
-  ([state keycode-map {:keys [not-matched-handler all-handler target]}]
+  ([state keycode-map {:keys [not-matched-handler all-handler target keycode?]
+                       :or {keycode? true}}]
    (listen state (or target js/window) "keydown"
            (fn [e]
-             (let [key-code (.-keyCode e)]
-               (if-let [f (get keycode-map key-code)]
+             (let [key (if keycode? (.-keyCode e) (.-key e))]
+               (if-let [f (get keycode-map key)]
                  (f state e)
                  (when (and not-matched-handler (fn? not-matched-handler))
-                   (not-matched-handler e key-code)))
+                   (not-matched-handler e key)))
                (when (and all-handler (fn? all-handler))
-                 (all-handler e key-code)))))))
+                 (all-handler e key)))))))
 
 (defn event-mixin
   ([attach-listeners]

+ 138 - 139
src/main/frontend/modules/shortcut/config.cljs

@@ -29,6 +29,11 @@
             [clojure.data :as data]
             [medley.core :as medley]))
 
+(defn- search
+  [mode]
+  (editor-handler/escape-editing false)
+  (route-handler/go-to-search! mode))
+
 ;; TODO: Namespace all-default-keyboard-shortcuts keys with `:command` e.g.
 ;; `:command.date-picker/complete`. They are namespaced in translation but
 ;; almost everywhere else they are not which could cause needless conflicts
@@ -352,15 +357,12 @@
    :ui/toggle-brackets                      {:binding "mod+c mod+b"
                                              :fn      config-handler/toggle-ui-show-brackets!}
 
-   :go/search-in-page                       {:binding "mod+shift+k"
-                                             :fn      #(do
-                                                         (editor-handler/escape-editing)
-                                                         (route-handler/go-to-search! :page))}
-
    :go/search                               {:binding "mod+k"
-                                             :fn      #(do
-                                                         (editor-handler/escape-editing false)
-                                                         (route-handler/go-to-search! :global))}
+                                             :fn      #(search :global)}
+   :command-palette/toggle                  {:binding "mod+shift+p"
+                                             :fn      #(search :commands)}
+   :go/search-in-page                       {:binding "mod+shift+k"
+                                             :fn      #(search :current-page)}
 
    :go/electron-find-in-page                {:binding  "mod+f"
                                              :inactive (not (util/electron?))
@@ -400,13 +402,8 @@
    :misc/copy                               {:binding "mod+c"
                                              :fn      (fn [] (js/document.execCommand "copy"))}
 
-   :command-palette/toggle                  {:binding "mod+shift+p"
-                                             :fn      #(do
-                                                         (editor-handler/escape-editing)
-                                                         (state/pub-event! [:modal/command-palette]))}
-
    :graph/export-as-html                    {:fn      #(export-handler/download-repo-as-html!
-                                                         (state/get-current-repo))
+                                                        (state/get-current-repo))
                                              :binding []}
 
    :graph/open                              {:fn      #(do
@@ -468,11 +465,8 @@
    :go/prev-journal                         {:binding "g p"
                                              :fn      journal-handler/go-to-prev-journal!}
 
-   :go/flashcards                           {:binding "g f"
-                                             :fn      (fn []
-                                                        (if (state/modal-opened?)
-                                                          (state/close-modal!)
-                                                          (state/pub-event! [:modal/show-cards])))}
+   :go/flashcards                           {:binding ["g f" "t c"]
+                                             :fn      ui-handler/toggle-cards!}
 
    :ui/toggle-document-mode                 {:binding "t d"
                                              :fn      state/toggle-document-mode!}
@@ -538,8 +532,11 @@
    :editor/toggle-open-blocks               {:binding "t o"
                                              :fn      editor-handler/toggle-open!}
 
-   :ui/toggle-cards                         {:binding "t c"
-                                             :fn      ui-handler/toggle-cards!}
+   :ui/cycle-color-off                      {:binding "c o"
+                                             :fn      state/unset-color-accent!}
+
+   :ui/cycle-color                          {:binding "c c"
+                                             :fn      state/cycle-color!}
 
    :git/commit                              {:binding  "mod+g c"
                                              :inactive (not (util/electron?))
@@ -620,134 +617,135 @@
 
     :shortcut.handler/block-editing-only
     (-> (build-category-map
-          [:editor/escape-editing
-           :editor/backspace
-           :editor/delete
-           :editor/zoom-in
-           :editor/zoom-out
-           :editor/new-block
-           :editor/new-line
-           :editor/follow-link
-           :editor/open-link-in-sidebar
-           :editor/bold
-           :editor/italics
-           :editor/highlight
-           :editor/strike-through
-           :editor/clear-block
-           :editor/kill-line-before
-           :editor/kill-line-after
-           :editor/beginning-of-block
-           :editor/end-of-block
-           :editor/forward-word
-           :editor/backward-word
-           :editor/forward-kill-word
-           :editor/backward-kill-word
-           :editor/replace-block-reference-at-point
-           :editor/copy-embed
-           :editor/paste-text-in-one-block-at-point
-           :editor/insert-youtube-timestamp])
+         [:editor/escape-editing
+          :editor/backspace
+          :editor/delete
+          :editor/zoom-in
+          :editor/zoom-out
+          :editor/new-block
+          :editor/new-line
+          :editor/follow-link
+          :editor/open-link-in-sidebar
+          :editor/bold
+          :editor/italics
+          :editor/highlight
+          :editor/strike-through
+          :editor/clear-block
+          :editor/kill-line-before
+          :editor/kill-line-after
+          :editor/beginning-of-block
+          :editor/end-of-block
+          :editor/forward-word
+          :editor/backward-word
+          :editor/forward-kill-word
+          :editor/backward-kill-word
+          :editor/replace-block-reference-at-point
+          :editor/copy-embed
+          :editor/paste-text-in-one-block-at-point
+          :editor/insert-youtube-timestamp])
         (with-meta {:before m/enable-when-editing-mode!}))
 
     :shortcut.handler/editor-global
     (-> (build-category-map
-          [:graph/export-as-html
-           :graph/open
-           :graph/remove
-           :graph/add
-           :graph/db-add
-           :graph/save
-           :graph/re-index
-           :editor/cycle-todo
-           :editor/up
-           :editor/down
-           :editor/left
-           :editor/right
-           :editor/select-up
-           :editor/select-down
-           :editor/move-block-up
-           :editor/move-block-down
-           :editor/open-edit
-           :editor/select-block-up
-           :editor/select-block-down
-           :editor/select-parent
-           :editor/delete-selection
-           :editor/expand-block-children
-           :editor/collapse-block-children
-           :editor/indent
-           :editor/outdent
-           :editor/copy
-           :editor/copy-text
-           :editor/cut
-           :command/toggle-favorite])
+         [:graph/export-as-html
+          :graph/open
+          :graph/remove
+          :graph/add
+          :graph/db-add
+          :graph/save
+          :graph/re-index
+          :editor/cycle-todo
+          :editor/up
+          :editor/down
+          :editor/left
+          :editor/right
+          :editor/select-up
+          :editor/select-down
+          :editor/move-block-up
+          :editor/move-block-down
+          :editor/open-edit
+          :editor/select-block-up
+          :editor/select-block-down
+          :editor/select-parent
+          :editor/delete-selection
+          :editor/expand-block-children
+          :editor/collapse-block-children
+          :editor/indent
+          :editor/outdent
+          :editor/copy
+          :editor/copy-text
+          :editor/cut
+          :command/toggle-favorite])
         (with-meta {:before m/enable-when-not-component-editing!}))
 
     :shortcut.handler/global-prevent-default
     (-> (build-category-map
-          [:editor/insert-link
-           :editor/select-all-blocks
-           :editor/toggle-undo-redo-mode
-           :editor/toggle-number-list
-           :editor/undo
-           :editor/redo
-           :ui/toggle-brackets
-           :go/search-in-page
-           :go/search
-           :go/electron-find-in-page
-           :go/electron-jump-to-the-next
-           :go/electron-jump-to-the-previous
-           :go/backward
-           :go/forward
-           :search/re-index
-           :sidebar/open-today-page
-           :sidebar/clear
-           :command/run
-           :command-palette/toggle
-           :editor/add-property
-           :window/close])
+         [:editor/insert-link
+          :editor/select-all-blocks
+          :editor/toggle-undo-redo-mode
+          :editor/toggle-number-list
+          :editor/undo
+          :editor/redo
+          :ui/toggle-brackets
+          :go/search-in-page
+          :go/search
+          :go/electron-find-in-page
+          :go/electron-jump-to-the-next
+          :go/electron-jump-to-the-previous
+          :go/backward
+          :go/forward
+          :search/re-index
+          :sidebar/open-today-page
+          :sidebar/clear
+          :command/run
+          :command-palette/toggle
+          :editor/add-property
+          :window/close])
         (with-meta {:before m/prevent-default-behavior}))
 
     :shortcut.handler/global-non-editing-only
     (-> (build-category-map
-          [:go/home
-           :go/journals
-           :go/all-pages
-           :go/flashcards
-           :go/graph-view
-           :go/all-graphs
-           :go/whiteboards
-           :go/keyboard-shortcuts
-           :go/tomorrow
-           :go/next-journal
-           :go/prev-journal
-           :ui/toggle-document-mode
-           :ui/toggle-settings
-           :ui/toggle-right-sidebar
-           :ui/toggle-left-sidebar
-           :ui/toggle-help
-           :ui/toggle-theme
-           :ui/toggle-contents
-           :editor/open-file-in-default-app
-           :editor/open-file-in-directory
-           :editor/copy-current-file
-           :editor/copy-page-url
-           :editor/new-whiteboard
-           :ui/toggle-wide-mode
-           :ui/select-theme-color
-           :ui/goto-plugins
-           :ui/install-plugins-from-file
-           :editor/toggle-open-blocks
-           :ui/toggle-cards
-           :ui/clear-all-notifications
-           :git/commit
-           :sidebar/close-top
-           :dev/show-block-data
-           :dev/show-block-ast
-           :dev/show-page-data
-           :dev/show-page-ast])
+         [:go/home
+          :go/journals
+          :go/all-pages
+          :go/flashcards
+          :go/graph-view
+          :go/all-graphs
+          :go/whiteboards
+          :go/keyboard-shortcuts
+          :go/tomorrow
+          :go/next-journal
+          :go/prev-journal
+          :ui/toggle-document-mode
+          :ui/toggle-settings
+          :ui/toggle-right-sidebar
+          :ui/toggle-left-sidebar
+          :ui/toggle-help
+          :ui/toggle-theme
+          :ui/toggle-contents
+          :editor/open-file-in-default-app
+          :editor/open-file-in-directory
+          :editor/copy-current-file
+          :editor/copy-page-url
+          :editor/new-whiteboard
+          :ui/toggle-wide-mode
+          :ui/select-theme-color
+          :ui/goto-plugins
+          :ui/install-plugins-from-file
+          :editor/toggle-open-blocks
+          :ui/clear-all-notifications
+          :git/commit
+          :sidebar/close-top
+          :dev/show-block-data
+          :dev/show-block-ast
+          :dev/show-page-data
+          :dev/show-page-ast
+          :ui/cycle-color
+          :ui/cycle-color-off])
         (with-meta {:before m/enable-when-not-editing-mode!}))
 
     :shortcut.handler/misc
-    ;; always overrides the copy due to "mod+c mod+s"
+            ;; always overrides the copy due to "mod+c mod+s"
     {:misc/copy (:misc/copy all-built-in-keyboard-shortcuts)}}))
 
 ;; To add a new entry to this map, first add it here and then
@@ -756,14 +754,15 @@
 (defonce ^:large-vars/data-var *category
   (atom
    {:shortcut.category/basics
-    [:editor/new-block
+    [:go/search
+     :editor/new-block
      :editor/new-line
      :editor/indent
      :editor/outdent
      :editor/select-all-blocks
      :editor/select-parent
-     :go/search
      :go/search-in-page
+     :command-palette/toggle
      :go/electron-find-in-page
      :go/electron-jump-to-the-next
      :go/electron-jump-to-the-previous
@@ -850,14 +849,15 @@
      :editor/toggle-undo-redo-mode
      :editor/toggle-number-list
      :ui/toggle-wide-mode
-     :ui/toggle-cards
      :ui/toggle-document-mode
      :ui/toggle-brackets
      :ui/toggle-theme
      :ui/toggle-left-sidebar
      :ui/toggle-right-sidebar
      :ui/toggle-settings
-     :ui/toggle-contents]
+     :ui/toggle-contents
+     :ui/cycle-color-off
+     :ui/cycle-color]
 
     :shortcut.category/whiteboard
     [:editor/new-whiteboard
@@ -893,7 +893,6 @@
      :pdf/find
      :command/toggle-favorite
      :command/run
-     :command-palette/toggle
      :graph/export-as-html
      :graph/open
      :graph/remove

+ 2 - 38
src/main/frontend/modules/shortcut/core.cljs

@@ -13,7 +13,7 @@
             [goog.ui.KeyboardShortcutHandler.EventType :as EventType]
             [lambdaisland.glogi :as log]
             [goog.functions :refer [debounce]])
-  (:import [goog.events KeyCodes KeyHandler KeyNames]
+  (:import [goog.events KeyCodes KeyNames]
            [goog.ui KeyboardShortcutHandler]))
 
 (defonce *installed-handlers (atom {}))
@@ -272,42 +272,6 @@
       ("ctrl" "shift" "alt" "esc") nil
       (str " " (name-with-meta e)))))
 
-(defn record! []
-  {:did-mount
-   (fn [state]
-     (let [handler (KeyHandler. js/document)
-           keystroke (:rum/local state)]
-
-       (doseq [id (keys @*installed-handlers)]
-         (uninstall-shortcut-handler! id))
-
-       (events/listen handler "key"
-                      (fn [e]
-                        (.preventDefault e)
-                        (swap! keystroke #(str % (keyname e)))))
-
-       (assoc state ::key-record-handler handler)))
-
-   :will-unmount
-   (fn [{:rum/keys [args local action] :as state}]
-     (let [k (first args)
-           keystroke (str/trim @local)]
-       (when (and (= @action :save)
-                  (seq keystroke))
-         (config-handler/set-config!
-           :shortcuts
-           (merge
-             (:shortcuts (state/get-config))
-             {k keystroke}))))
-
-     (when-let [^js handler (::key-record-handler state)]
-       (.dispose handler))
-
-     ;; force re-install shortcut handlers
-     (js/setTimeout #(refresh!) 500)
-
-     (dissoc state ::key-record-handler))})
-
 (defn persist-user-shortcut!
   [id binding]
   (let [graph-shortcuts (or (:shortcuts (state/get-graph-config)) {})
@@ -326,4 +290,4 @@
       ;; TODO: exclude current graph config shortcuts
       (when (nil? binding)
         (config-handler/set-config! :shortcuts (into-shortcuts graph-shortcuts)))
-      (global-config-handler/set-global-config-kv! :shortcuts (into-shortcuts global-shortcuts)))))
+      (global-config-handler/set-global-config-kv! :shortcuts (into-shortcuts global-shortcuts)))))

+ 10 - 63
src/main/frontend/modules/shortcut/data_helper.cljs

@@ -1,20 +1,13 @@
 (ns frontend.modules.shortcut.data-helper
-  (:require [borkdude.rewrite-edn :as rewrite]
-            [clojure.set :refer [rename-keys] :as set]
+  (:require [clojure.set :refer [rename-keys] :as set]
             [clojure.string :as str]
             [cljs-bean.core :as bean]
             [frontend.context.i18n :refer [t]]
-            [frontend.config :as config]
-            [frontend.db :as db]
-            [frontend.handler.file :as file]
             [frontend.modules.shortcut.config :as shortcut-config]
             [frontend.modules.shortcut.utils :as shortcut-utils]
             [frontend.state :as state]
             [frontend.util :as util]
-            [lambdaisland.glogi :as log]
-            [frontend.handler.repo-config :as repo-config-handler]
-            [frontend.handler.config :as config-handler])
-  (:import [goog.ui KeyboardShortcutHandler]))
+            [lambdaisland.glogi :as log]))
 
 (declare get-group)
 
@@ -100,10 +93,6 @@
           shortcut)
         (mapv mod-key)))))
 
-(defn shortcut-cmd
-  [id]
-  (get @shortcut-config/*shortcut-cmds id))
-
 (defn shortcut-item
   [id]
   (get (get-bindings-ids-map) id))
@@ -151,10 +140,10 @@
   (let [tmp (cond
               (false? binding)
               (cond
-                (and util/mac? (= k :editor/kill-line-after)) "system default: ctrl+k"
-                (and util/mac? (= k :editor/beginning-of-block)) "system default: ctrl+a"
-                (and util/mac? (= k :editor/end-of-block)) "system default: ctrl+e"
-                (and util/mac? (= k :editor/backward-kill-word)) "system default: opt+delete"
+                (and util/mac? (= k :editor/kill-line-after)) "system default: ctrl k"
+                (and util/mac? (= k :editor/beginning-of-block)) "system default: ctrl a"
+                (and util/mac? (= k :editor/end-of-block)) "system default: ctrl e"
+                (and util/mac? (= k :editor/backward-kill-word)) "system default: opt delete"
                 :else (t :keymap/disabled))
 
               (string? binding)
@@ -169,24 +158,6 @@
     ;; mod key, because that's what the Mac keyboards actually say.
     (str/replace tmp "meta" "cmd")))
 
-;; Given the displayed binding, prepare it to be put back into config.edn
-(defn binding-for-storage [binding]
-  (str/replace binding "cmd" "meta"))
-
-(defn remove-shortcut [k]
-  (let [repo (state/get-current-repo)
-        path (config/get-repo-config-path)]
-    (when-let [result (some-> (db/get-file path)
-                              (config-handler/parse-repo-config))]
-      (when-let [new-content (and (:shortcuts result)
-                                  (-> (rewrite/update
-                                        result
-                                        :shortcuts
-                                        #(dissoc (rewrite/sexpr %) k))
-                                      (str)))]
-        (repo-config-handler/set-repo-config-state! repo new-content)
-        (file/set-file-content! repo path new-content)))))
-
 (defn get-group
   "Given shortcut key, return handler group
   eg: :editor/new-line -> :shortcut.handler/block-editing-only"
@@ -236,15 +207,15 @@
                                                             (and (set? handler-ids) (contains? handler-ids handler-id'))
                                                             (and global? (contains? global-handlers handler-id'))))
                                                     (assoc r id handler-id')
-                                                    r)
-                                                  ) {} refs)]]))]
+                                                    r))
+                                                {} refs)]]))]
 
                      [k' (->> ks-bindings
                               (filterv same-leading-key?)
                               (mapv into-conflict-refs)
                               (remove #(empty? (second (second %1))))
-                              (into {}))]
-                     ))))
+                              (into {}))]))))
+
           (remove #(empty? (vals (second %1))))
           (into {})))))
 
@@ -262,30 +233,6 @@
                              (= (count target) 1))
                          (= (first target) (first from))))))))))
 
-(defn potential-conflict? [shortcut-id]
-  (if-not (shortcut-binding shortcut-id)
-    false
-    (let [handler-id (get-group shortcut-id)
-          shortcut-m (shortcut-map handler-id)
-          parse-shortcut #(try
-                            (KeyboardShortcutHandler/parseStringShortcut %)
-                            (catch :default e
-                              (js/console.error "[shortcut/parse-error]" (str % " - " (.-message e)))))
-          bindings (->> (shortcut-binding shortcut-id)
-                        (map mod-key)
-                        (map parse-shortcut)
-                        (map js->clj))
-          rest-bindings (->> (map key shortcut-m)
-                             (remove #{shortcut-id})
-                             (map shortcut-binding)
-                             (filter vector?)
-                             (mapcat identity)
-                             (map mod-key)
-                             (map parse-shortcut)
-                             (map js->clj))]
-
-      (some? (some (fn [b] (some #{b} rest-bindings)) bindings)))))
-
 (defn shortcut-data-by-id [id]
   (let [binding (shortcut-binding id)
         data (->> (vals @shortcut-config/*config)

+ 5 - 1
src/main/frontend/modules/shortcut/utils.cljs

@@ -28,7 +28,9 @@
                     "~" "shift+`"
                     "⇧" "shift"
                     "←" "left"
-                    "→" "right"}]
+                    "→" "right"
+                    "↑"  "up"
+                    "↓"  "down"}]
       (-> binding
           (str/replace #"[;=-\[\]'\(\)\~\→\←\⇧]" #(get keynames %))
           (str/replace #"\s+" " ")
@@ -50,6 +52,8 @@
         (str/replace "shift+/" "?")
         (str/replace "left" "←")
         (str/replace "right" "→")
+        (str/replace "up" "↑")
+        (str/replace "down" "↓")
         (str/replace "shift" "⇧")
         (str/replace "open-square-bracket" "[")
         (str/replace "close-square-bracket" "]")

+ 3 - 8
src/main/frontend/routes.cljs

@@ -7,9 +7,8 @@
             [frontend.components.page :as page]
             [frontend.components.plugins :as plugins]
             [frontend.components.repo :as repo]
-            [frontend.components.search :as search]
             [frontend.components.settings :as settings]
-            [frontend.components.whiteboard :as whiteboard] 
+            [frontend.components.whiteboard :as whiteboard]
             [frontend.extensions.zotero :as zotero]
             [frontend.components.bug-report :as bug-report]
             [frontend.components.user.login :as login]))
@@ -44,10 +43,6 @@
     {:name :file
      :view file/file}]
 
-   ["/search/:q"
-    {:name :search
-     :view search/more}]
-
    ["/page/:name"
     {:name :page
      :view page/page}]
@@ -81,8 +76,8 @@
      :view bug-report/bug-report}]
 
    ["/bug-report-tool/:tool"
-     {:name :bug-report-tools
-      :view bug-report/bug-report-tool-route}]
+    {:name :bug-report-tools
+     :view bug-report/bug-report-tool-route}]
 
    ["/all-journals"
     {:name :all-journals

+ 14 - 14
src/main/frontend/search.cljs

@@ -67,16 +67,16 @@
                       (if (<= 0 (.indexOf ostr oquery)) MAX-STRING-LENGTH 0))
         (empty? s) 0
         :else (if (= (first q) (first s))
-                   (recur (rest q)
-                          (rest s)
-                          (inc mult) ;; increase the multiplier as more query chars are matched
-                          (dec idx) ;; decrease idx so score gets lowered the further into the string we match
-                          (+ mult score)) ;; score for this match is current multiplier * idx
-                   (recur q
-                          (rest s)
-                          1 ;; when there is no match, reset multiplier to one
-                          (dec idx)
-                          score))))))
+                  (recur (rest q)
+                         (rest s)
+                         (inc mult) ;; increase the multiplier as more query chars are matched
+                         (dec idx) ;; decrease idx so score gets lowered the further into the string we match
+                         (+ mult score)) ;; score for this match is current multiplier * idx
+                  (recur q
+                         (rest s)
+                         1 ;; when there is no match, reset multiplier to one
+                         (dec idx)
+                         score))))))
 
 (defn fuzzy-search
   [data query & {:keys [limit extract-fn]
@@ -148,11 +148,11 @@
                           (search-db/make-pages-title-indice!))
                result (->> (.search indice q (clj->js {:limit limit}))
                            (bean/->clj))]
-           ;; TODO: add indexes for highlights
-           (->> (map
+           (->> result
+                (util/distinct-by (fn [i] (string/trim (get-in i [:item :name]))))
+                (map
                  (fn [{:keys [item]}]
-                   (:original-name item))
-                 result)
+                   (:original-name item)))
                 (remove nil?)
                 (map string/trim)
                 (distinct)

+ 1 - 1
src/main/frontend/search/browser.cljs

@@ -40,7 +40,7 @@
     (let [indice (search-db/make-blocks-indice! repo)]
       (p/promise indice)))
   (transact-blocks! [_this {:keys [blocks-to-remove-set
-                                  blocks-to-add]}]
+                                   blocks-to-add]}]
     (swap! search-db/indices update-in [repo :blocks]
            (fn [indice]
              (when indice

+ 15 - 15
src/main/frontend/search/db.cljs

@@ -11,15 +11,15 @@
 
 (defonce indices (atom nil))
 
+(defn- max-len
+  []
+  (state/block-content-max-length (state/get-current-repo)))
+
 (defn- sanitize
   [content]
   (some-> content
           (util/search-normalize (state/enable-search-remove-accents?))))
 
-(defn- max-len
-  []
-  (state/block-content-max-length (state/get-current-repo)))
-
 (defn block->index
   "Convert a block to the index for searching"
   [{:block/keys [uuid page content] :as block}]
@@ -62,17 +62,17 @@
        (bean/->js)))
 
 (defn make-blocks-indice!
-  [repo]
-  (let [blocks (build-blocks-indice repo)
-        indice (fuse. blocks
-                      (clj->js {:keys ["uuid" "content" "page"]
-                                :shouldSort true
-                                :tokenize true
-                                :minMatchCharLength 1
-                                :distance 1000
-                                :threshold 0.35}))]
-    (swap! indices assoc-in [repo :blocks] indice)
-    indice))
+  ([repo] (make-blocks-indice! repo (build-blocks-indice repo)))
+  ([repo blocks]
+   (let [indice (fuse. blocks
+                       (clj->js {:keys ["uuid" "content" "page"]
+                                 :shouldSort true
+                                 :tokenize true
+                                 :minMatchCharLength 1
+                                 :distance 1000
+                                 :threshold 0.35}))]
+     (swap! indices assoc-in [repo :blocks] indice)
+     indice)))
 
 (defn original-page-name->index
   [p]

+ 30 - 11
src/main/frontend/shui.cljs

@@ -1,15 +1,20 @@
 (ns frontend.shui
   "Glue between frontend code and deps/shui for convenience"
   (:require
-   [frontend.date :refer [int->local-time-2]]
-   [frontend.state :as state]
-   [frontend.handler.property.util :as pu]
-   [logseq.shui.context :refer [make-context]]))
+    [frontend.colors :as colors]
+    [frontend.date :refer [int->local-time-2]]
+    [frontend.db :as db]
+    [frontend.db.utils :as db-utils]
+    [frontend.handler.search :as search-handler]
+    [frontend.state :as state]
+    [frontend.handler.property.util :as pu]
+    [logseq.shui.context :refer [make-context]]))
+
 
 (def default-versions {:logseq.table.version 1})
 
-(defn get-shui-component-version 
-  "Returns the version of the shui component, checking first 
+(defn get-shui-component-version
+  "Returns the version of the shui component, checking first
   the block properties, then the global config, then the defaults."
   [component-name block-config]
   (let [version-key (keyword (str "logseq." (name component-name) ".version"))]
@@ -19,8 +24,22 @@
           (get-in default-versions [version-key])
           1))))
 
-(defn make-shui-context [block-config inline]
-  (make-context {:block-config block-config 
-                 :app-config (state/get-config) 
-                 :inline inline 
-                 :int->local-time-2 int->local-time-2}))
+(defn make-shui-context
+  ([] (make-shui-context nil nil))
+  ([block-config inline]
+   (make-context {:block-config block-config
+                  :config (state/get-config)
+                  :inline inline
+                  :int->local-time-2 int->local-time-2
+                  :search search-handler/search
+                  ;; We need some variable from the state to carry over
+                  :state state/state
+                  :get-current-repo state/get-current-repo
+                  :color-accent (state/get-color-accent)
+                  ;; Pass over ability to look up entities
+                  :entity db-utils/entity
+                  :get-block-and-children db/get-block-and-children
+                  :get-block-children db/get-block-children
+                  :get-page-blocks-no-cache db/get-page-blocks-no-cache
+                  :get-page db/get-page
+                  :linear-gradient colors/linear-gradient})))

+ 0 - 2
src/main/frontend/spec/storage.cljc

@@ -24,7 +24,6 @@
 (s/def :document/mode? boolean?)
 (s/def :ui/shortcut-tooltip? boolean?)
 (s/def :ui/recent-pages map?)
-(s/def :ui/recent-search map?)
 (s/def :copy/export-block-text-indent-style string?)
 (s/def :copy/export-block-text-remove-options set?)
 (s/def :copy/export-block-text-other-options map?)
@@ -49,7 +48,6 @@
             :ui/theme
             :ui/system-theme?
             :ui/recent-pages
-            :ui/recent-search
             ::lsp-core-enabled
             ::instrument-disabled
             ::ls-pdf-area-is-dashed

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov