Browse Source

Merge branch 'master' into enhance/mobile-ux-2

charlie 2 years ago
parent
commit
ee7029c5fc

+ 8 - 17
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -31,33 +31,24 @@ body:
     validations:
     validations:
       required: false
       required: false
   - type: textarea
   - type: textarea
-    id: Screenshots
+    id: screenshots
     attributes:
     attributes:
       label: Screenshots
       label: Screenshots
       description: |
       description: |
-        If applicable, add screenshots to help explain your problem.
+        If applicable, add screenshots or screen recordings to help explain your problem.
     validations:
     validations:
       required: false
       required: false
   - type: textarea
   - type: textarea
-    id: desktop
+    id: platform
     attributes:
     attributes:
-      label: Desktop Platform Information
+      label: Desktop or mobile Platform Information
       description: |
       description: |
-        Would you mind to tell us the system information about your desktop platform?
+        Would you mind to tell us the system information about your desktop or mobile platform?
       placeholder: |
       placeholder: |
         OS version, Browser or App, Logseq App version
         OS version, Browser or App, Logseq App version
-        example: MacOS 12.2, App, v0.5.9
-    validations:
-      required: false
-  - type: textarea
-    id: mobile
-    attributes:
-      label: Mobile Platform Information
-      description: |
-        Would you mind to tell us the system information about your mobile platform?
-      placeholder: |
-        Device, OS version, Browser or App, Logseq App version
-        example: iPhone6, iOS8.1, App, v0.5.9
+        example: MacOS 12.2, Desktop App v0.5.9
+        example: iPhone 12, iOS8.1, v0.5.9
+        example: Pixel XL, Android 12, v0.5.9
     validations:
     validations:
       required: false
       required: false
   - type: textarea
   - type: textarea

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
+/e2e-dump
 /target
 /target
 /classes
 /classes
 /checkouts
 /checkouts

+ 7 - 0
docs/dev-practices.md

@@ -94,6 +94,13 @@ If e2e failed after first running:
 - `rm -rdf <repo dir>/tmp/`  
 - `rm -rdf <repo dir>/tmp/`  
 - `rm -rdf <appData dir>/Electron`  (Reference: https://www.electronjs.org/de/docs/latest/api/app#appgetpathname)
 - `rm -rdf <appData dir>/Electron`  (Reference: https://www.electronjs.org/de/docs/latest/api/app#appgetpathname)
 
 
+If e2e tests fail, they can be debugged by examining a trace dump with [the
+playwright trace
+viewer](https://playwright.dev/docs/trace-viewer#recording-a-trace). Locally
+this will get dumped into e2e-dump/. On CI the trace file will be under
+Artifacts at the bottom of a run page e.g.
+https://github.com/logseq/logseq/actions/runs/3574600322.
+
 ### Unit Testing
 ### Unit Testing
 
 
 Our unit tests use the [shadow-cljs test-runner](https://shadow-cljs.github.io/docs/UsersGuide.html#_testing). To run them:
 Our unit tests use the [shadow-cljs test-runner](https://shadow-cljs.github.io/docs/UsersGuide.html#_testing). To run them:

+ 45 - 24
e2e-tests/page-search.spec.ts

@@ -1,5 +1,6 @@
 import { expect, Page } from '@playwright/test'
 import { expect, Page } from '@playwright/test'
 import { test } from './fixtures'
 import { test } from './fixtures'
+import { Block } from './types'
 import { IsMac, createRandomPage, newBlock, newInnerBlock, randomString, lastBlock, enterNextBlock } from './utils'
 import { IsMac, createRandomPage, newBlock, newInnerBlock, randomString, lastBlock, enterNextBlock } from './utils'
 
 
 /***
 /***
@@ -8,14 +9,14 @@ import { IsMac, createRandomPage, newBlock, newInnerBlock, randomString, lastBlo
  * Consider diacritics
  * Consider diacritics
  ***/
  ***/
 
 
-test('Search page and blocks (diacritics)', async ({ page, block }) => {
-  let hotkeyOpenLink = 'Control+o'
-  let hotkeyBack = 'Control+['
-  if (IsMac) {
-    hotkeyOpenLink = 'Meta+o'
-    hotkeyBack = 'Meta+['
-  }
+ let hotkeyOpenLink = 'Control+o'
+ let hotkeyBack = 'Control+['
+ if (IsMac) {
+   hotkeyOpenLink = 'Meta+o'
+   hotkeyBack = 'Meta+['
+ }
 
 
+test('Search page and blocks (diacritics)', async ({ page, block }) => {
   const rand = randomString(20)
   const rand = randomString(20)
 
 
   // diacritic opening test
   // diacritic opening test
@@ -45,21 +46,43 @@ test('Search page and blocks (diacritics)', async ({ page, block }) => {
   await page.waitForTimeout(2000) // wait longer for search contents to render
   await page.waitForTimeout(2000) // wait longer for search contents to render
   // 2 blocks + 1 page + 1 page content
   // 2 blocks + 1 page + 1 page content
   const searchResults = page.locator('#ui__ac-inner>div')
   const searchResults = page.locator('#ui__ac-inner>div')
-  await expect(searchResults).toHaveCount(4)
+  await expect(searchResults).toHaveCount(5) // 1 page + 2 block + 2 page content
 
 
   await page.keyboard.press("Escape") // escape search box typing
   await page.keyboard.press("Escape") // escape search box typing
   await page.waitForTimeout(500)
   await page.waitForTimeout(500)
   await page.keyboard.press("Escape") // escape modal
   await page.keyboard.press("Escape") // escape modal
 })
 })
 
 
-async function alias_test(page: Page, page_name: string, search_kws: string[]) {
-  let hotkeyOpenLink = 'Control+o'
-  let hotkeyBack = 'Control+['
-  if (IsMac) {
-    hotkeyOpenLink = 'Meta+o'
-    hotkeyBack = 'Meta+['
-  }
+test('Search CJK', async ({ page, block }) => {
+  const rand = randomString(20)
+
+  // diacritic opening test
+  await createRandomPage(page)
 
 
+  await block.mustType('[[今日daytime进度条' + rand + ']] diacritic-block-1', { delay: 10 })
+  await page.keyboard.press(hotkeyOpenLink)
+
+  const pageTitle = page.locator('.page-title').first()
+  expect(await pageTitle.innerText()).toEqual('今日daytime进度条' + rand)
+
+  await page.waitForTimeout(500)
+
+  // check if diacritics are indexed
+  await page.click('#search-button')
+  await page.waitForSelector('[placeholder="Search or create page"]')
+  await page.type('[placeholder="Search or create page"]', '进度', { delay: 10 })
+
+  await page.waitForTimeout(2000) // wait longer for search contents to render
+  // 2 blocks + 1 page + 1 page content
+  const searchResults = page.locator('#ui__ac-inner>div')
+  await expect(searchResults).toHaveCount(4) // 1 new page + 1 page + 1 block + 1 page content
+
+  await page.keyboard.press("Escape") // escape search box typing
+  await page.waitForTimeout(500)
+  await page.keyboard.press("Escape") // escape modal
+})
+
+async function alias_test( block: Block, page: Page, page_name: string, search_kws: string[] ) {
   const rand = randomString(10)
   const rand = randomString(10)
   let target_name = page_name + ' target ' + rand
   let target_name = page_name + ' target ' + rand
   let alias_name = page_name + ' alias ' + rand
   let alias_name = page_name + ' alias ' + rand
@@ -107,7 +130,7 @@ async function alias_test(page: Page, page_name: string, search_kws: string[]) {
   await page.type('textarea >> nth=0', alias_test_content_2)
   await page.type('textarea >> nth=0', alias_test_content_2)
   await page.keyboard.press(hotkeyBack)
   await page.keyboard.press(hotkeyBack)
 
 
-  // pressing enter opening test
+  // pressing enter on alias opening test
   await lastBlock(page)
   await lastBlock(page)
   await page.press('textarea >> nth=0', 'ArrowLeft')
   await page.press('textarea >> nth=0', 'ArrowLeft')
   await page.press('textarea >> nth=0', 'ArrowLeft')
   await page.press('textarea >> nth=0', 'ArrowLeft')
@@ -119,8 +142,8 @@ async function alias_test(page: Page, page_name: string, search_kws: string[]) {
   await page.type('textarea >> nth=0', alias_test_content_3)
   await page.type('textarea >> nth=0', alias_test_content_3)
   await page.keyboard.press(hotkeyBack)
   await page.keyboard.press(hotkeyBack)
 
 
-  // clicking opening test
-  await newBlock(page)
+  // clicking alias ref opening test
+  await block.enterNext()
   await page.waitForSelector('.page-blocks-inner .ls-block .page-ref >> nth=-1')
   await page.waitForSelector('.page-blocks-inner .ls-block .page-ref >> nth=-1')
   await page.click('.page-blocks-inner .ls-block .page-ref >> nth=-1')
   await page.click('.page-blocks-inner .ls-block .page-ref >> nth=-1')
   await lastBlock(page)
   await lastBlock(page)
@@ -138,15 +161,13 @@ async function alias_test(page: Page, page_name: string, search_kws: string[]) {
     await page.waitForTimeout(500)
     await page.waitForTimeout(500)
 
 
     const results = await page.$$('#ui__ac-inner>div')
     const results = await page.$$('#ui__ac-inner>div')
-    expect(results.length).toEqual(3) // page + block + alias property
+    expect(results.length).toEqual(5) // page + block + alias property + page content
 
 
     // test search results
     // test search results
     expect(await results[0].innerText()).toContain("Alias -> " + target_name)
     expect(await results[0].innerText()).toContain("Alias -> " + target_name)
     expect(await results[0].innerText()).toContain(alias_name)
     expect(await results[0].innerText()).toContain(alias_name)
-    expect(await results[1].innerText()).toContain(parent_title)
     expect(await results[1].innerText()).toContain("[[" + alias_name + "]]")
     expect(await results[1].innerText()).toContain("[[" + alias_name + "]]")
-    expect(await results[2].innerText()).toContain(target_name)
-    expect(await results[2].innerText()).toContain("alias:: [[" + alias_name + "]]")
+    expect(await results[2].innerText()).toContain("[[" + alias_name + "]]")
 
 
     // test search entering (page)
     // test search entering (page)
     page.keyboard.press("Enter")
     page.keyboard.press("Enter")
@@ -171,6 +192,6 @@ async function alias_test(page: Page, page_name: string, search_kws: string[]) {
   // TODO: search clicking (alias property)
   // TODO: search clicking (alias property)
 }
 }
 
 
-test.skip('page diacritic alias', async ({ page }) => {
-  await alias_test(page, "ü", ["ü", "ü", "Ü"])
+test.skip('page diacritic alias', async ({ block, page }) => {
+  await alias_test(block, page, "ü", ["ü", "ü", "Ü"])
 })
 })

+ 1 - 0
e2e-tests/utils.ts

@@ -115,6 +115,7 @@ export async function newInnerBlock(page: Page): Promise<Locator> {
   return page.locator('textarea >> nth=0')
   return page.locator('textarea >> nth=0')
 }
 }
 
 
+// Deprecated by block.enterNext
 export async function newBlock(page: Page): Promise<Locator> {
 export async function newBlock(page: Page): Promise<Locator> {
   let blockNumber = await page.locator('.page-blocks-inner .ls-block').count()
   let blockNumber = await page.locator('.page-blocks-inner .ls-block').count()
   await lastBlock(page)
   await lastBlock(page)

+ 4 - 2
shadow-cljs.edn

@@ -33,10 +33,12 @@
                                                 "externs.js"]
                                                 "externs.js"]
                            :warnings           {:fn-deprecated false
                            :warnings           {:fn-deprecated false
                                                 :redef false}}
                                                 :redef false}}
+        :build-hooks      [(shadow.hooks/git-revision-hook "--long --always --dirty")]
         :closure-defines  {goog.debug.LOGGING_ENABLED       true
         :closure-defines  {goog.debug.LOGGING_ENABLED       true
-                           frontend.config/ENABLE-PLUGINS   #shadow/env ["ENABLE_PLUGINS"   :as :bool :default true]
+                           frontend.config/ENABLE-PLUGINS #shadow/env ["ENABLE_PLUGINS"   :as :bool :default true]
                            frontend.config/ENABLE-FILE-SYNC-PRODUCTION #shadow/env ["ENABLE_FILE_SYNC_PRODUCTION" :as :bool :default true]
                            frontend.config/ENABLE-FILE-SYNC-PRODUCTION #shadow/env ["ENABLE_FILE_SYNC_PRODUCTION" :as :bool :default true]
-                           frontend.config/TEST #shadow/env ["LOGSEQ_CI" :as :bool :default false]}
+                           frontend.config/TEST #shadow/env ["LOGSEQ_CI" :as :bool :default false]
+                           frontend.config/REVISION #shadow/env ["LOGSEQ_REVISION" :default "dev"]} ;; set by git-revision-hook
 
 
         ;; NOTE: electron, browser/mobile-app use different asset-paths.
         ;; NOTE: electron, browser/mobile-app use different asset-paths.
         ;;   For browser/mobile-app devs, assets are located in /static/js(via HTTP root).
         ;;   For browser/mobile-app devs, assets are located in /static/js(via HTTP root).

+ 19 - 6
src/dev-cljs/shadow/hooks.clj

@@ -1,15 +1,15 @@
 (ns shadow.hooks
 (ns shadow.hooks
   (:require [clojure.java.shell :refer [sh]]
   (:require [clojure.java.shell :refer [sh]]
-            [clojure.string :as str]))
+            [clojure.string :as string]))
 
 
 ;; copied from https://gist.github.com/mhuebert/ba885b5e4f07923e21d1dc4642e2f182
 ;; copied from https://gist.github.com/mhuebert/ba885b5e4f07923e21d1dc4642e2f182
 (defn exec [& cmd]
 (defn exec [& cmd]
-  (let [cmd (str/split (str/join " " (flatten cmd)) #"\s+")
-        _ (println (str/join " " cmd))
+  (let [cmd (string/split (string/join " " (flatten cmd)) #"\s+")
+        _ (println (string/join " " cmd))
         {:keys [exit out err]} (apply sh cmd)]
         {:keys [exit out err]} (apply sh cmd)]
     (if (zero? exit)
     (if (zero? exit)
-      (when-not (str/blank? out)
-        (println out))
+      (when-not (string/blank? out)
+        (string/trim out))
       (println err))))
       (println err))))
 
 
 (defn purge-css
 (defn purge-css
@@ -27,5 +27,18 @@
     :dev
     :dev
     (do
     (do
       (exec "mkdir -p" public-dir)
       (exec "mkdir -p" public-dir)
-      (exec "cp" css-source (str public-dir "/" (last (str/split css-source #"/"))))))
+      (exec "cp" css-source (str public-dir "/" (last (string/split css-source #"/"))))))
   state)
   state)
+
+(defn git-revision-hook
+  {:shadow.build/stage :configure}
+  [build-state & args]
+  (let [defines-in-config (get-in build-state [:shadow.build/config :closure-defines])
+        defines-in-options (get-in build-state [:compiler-options :closure-defines])
+        revision (exec "git" "describe" args)]
+    (prn ::git-revision-hook revision)
+    (-> build-state
+        (assoc-in [:shadow.build/config :closure-defines]
+                  (assoc defines-in-config 'frontend.config/REVISION revision))
+        (assoc-in [:compiler-options :closure-defines]
+                  (assoc defines-in-options 'frontend.config/REVISION revision)))))

+ 23 - 3
src/electron/electron/search.cljs

@@ -273,17 +273,33 @@
         (->>
         (->>
          (concat matched-result
          (concat matched-result
                  (search-blocks-aux database non-match-sql non-match-input page limit))
                  (search-blocks-aux database non-match-sql non-match-input page limit))
-         (distinct-by :id)
+         (distinct-by :rowid)
          (take limit)
          (take limit)
          (vec))))))
          (vec))))))
 
 
+(defn- snippet-by
+  [content length]
+  (str (subs content 0 length) (when (> (count content) 250) "...")))
+
 (defn- search-pages-res-unpack
 (defn- search-pages-res-unpack
   [arr]
   [arr]
   (let [[rowid uuid content snippet] arr]
   (let [[rowid uuid content snippet] arr]
     {:id      rowid
     {:id      rowid
      :uuid    uuid
      :uuid    uuid
      :content content
      :content content
-     :snippet snippet}))
+     ;; post processing
+     :snippet (let [;; Remove title from snippet
+                    flag-title " $<pfts_f6ld$ "
+                    flag-title-pos (string/index-of snippet flag-title)
+                    snippet (if flag-title-pos
+                              (subs snippet (+ flag-title-pos (count flag-title)))
+                              snippet)
+                    ;; Cut snippet to 250 chars for non-matched results
+                    flag-highlight "$pfts_2lqh>$ "
+                    snippet (if (string/includes? snippet flag-highlight)
+                              snippet
+                              (snippet-by snippet 250))]
+                snippet)}))
 
 
 (defn- search-pages-aux
 (defn- search-pages-aux
   [database sql input limit]
   [database sql input limit]
@@ -301,6 +317,7 @@
   (when-let [database (get-db repo)]
   (when-let [database (get-db repo)]
     (when-not (string/blank? q)
     (when-not (string/blank? q)
       (let [match-inputs (get-match-inputs q)
       (let [match-inputs (get-match-inputs q)
+            non-match-input (str "%" (string/replace q #"\s+" "%") "%")
             limit  (or limit 20)
             limit  (or limit 20)
             ;; https://www.sqlite.org/fts5.html#the_highlight_function
             ;; https://www.sqlite.org/fts5.html#the_highlight_function
             ;; the 2nd column in pages_fts (content)
             ;; the 2nd column in pages_fts (content)
@@ -310,6 +327,8 @@
             select (str "select rowid, uuid, content, " snippet-aux " from pages_fts where ")
             select (str "select rowid, uuid, content, " snippet-aux " from pages_fts where ")
             match-sql (str select
             match-sql (str select
                            " content match ? order by rank limit ?")
                            " content match ? order by rank limit ?")
+            non-match-sql (str select
+                               " content like ? limit ?")
             matched-result (->>
             matched-result (->>
                             (map
                             (map
                               (fn [match-input]
                               (fn [match-input]
@@ -317,7 +336,8 @@
                               match-inputs)
                               match-inputs)
                             (apply concat))]
                             (apply concat))]
         (->>
         (->>
-         matched-result
+         (concat matched-result
+                 (search-pages-aux database non-match-sql non-match-input limit))
          (distinct-by :id)
          (distinct-by :id)
          (take limit)
          (take limit)
          (vec))))))
          (vec))))))

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

@@ -14,7 +14,7 @@
   [*show-password?]
   [*show-password?]
   [:div.flex.flex-row.items-center
   [:div.flex.flex-row.items-center
    [:label.px-1 {:for "show-password"}
    [:label.px-1 {:for "show-password"}
-    (ui/checkbox {:checked?  @*show-password?
+    (ui/checkbox {:checked   @*show-password?
                   :on-change (fn [e]
                   :on-change (fn [e]
                                (reset! *show-password? (util/echecked? e)))
                                (reset! *show-password? (util/echecked? e)))
                   :id        "show-password"})
                   :id        "show-password"})

+ 18 - 0
src/main/frontend/components/header.cljs

@@ -18,6 +18,7 @@
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.util :as util]
+            [frontend.version :refer [version]]
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]))
             [rum.core :as rum]))
 
 
@@ -57,6 +58,16 @@
       :on-click on-click}
       :on-click on-click}
      (ui/icon "menu-2" {:size ui/icon-size})]))
      (ui/icon "menu-2" {:size ui/icon-size})]))
 
 
+(def bug-report-url
+  (let [platform (str "App Version: " version "\n"
+                      "Platform: " (.-userAgent js/navigator) "\n"
+                      "Language: " (.-language js/navigator))]
+    (str "https://github.com/logseq/logseq/issues/new?"
+         "title=&"
+         "template=bug_report.yaml&"
+         "platform="
+         (js/encodeURIComponent platform))))
+
 (rum/defc dropdown-menu < rum/reactive
 (rum/defc dropdown-menu < rum/reactive
   < {:key-fn #(identity "repos-dropdown-menu")}
   < {:key-fn #(identity "repos-dropdown-menu")}
   [{:keys [current-repo t]}]
   [{:keys [current-repo t]}]
@@ -101,6 +112,13 @@
                   :title (t :discourse-title)
                   :title (t :discourse-title)
                   :target "_blank"}
                   :target "_blank"}
         :icon (ui/icon "brand-discord")}
         :icon (ui/icon "brand-discord")}
+       
+       {:title [:div.flex-row.flex.justify-between.items-center
+                [:span "Bug report"]]
+        :options {:href bug-report-url
+                  :title "Fire a bug report on Github"
+                  :target "_blank"}
+        :icon (ui/icon "bug")} 
 
 
        (when (and (state/sub :auth/id-token) (user-handler/logged-in?))
        (when (and (state/sub :auth/id-token) (user-handler/logged-in?))
          {:title (str (t :logout) " (" (user-handler/email) ")")
          {:title (str (t :logout) " (" (user-handler/email) ")")

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

@@ -287,7 +287,8 @@
                   (if fmt-journal? (date/journal-title->custom-format title) title))
                   (if fmt-journal? (date/journal-title->custom-format title) title))
           old-name (or title page-name)]
           old-name (or title page-name)]
       [:h1.page-title.flex.cursor-pointer.gap-1.w-full
       [:h1.page-title.flex.cursor-pointer.gap-1.w-full
-       {:on-mouse-down (fn [e]
+       {:class (when-not whiteboard-page? "title")
+        :on-mouse-down (fn [e]
                          (when (util/right-click? e)
                          (when (util/right-click? e)
                            (state/set-state! :page-title/context {:page page-name})))
                            (state/set-state! :page-title/context {:page page-name})))
         :on-click (fn [e]
         :on-click (fn [e]
@@ -415,8 +416,8 @@
                                    (page-mouse-leave e *control-show?))}
                                    (page-mouse-leave e *control-show?))}
                 (page-blocks-collapse-control title *control-show? *all-collapsed?)])
                 (page-blocks-collapse-control title *control-show? *all-collapsed?)])
              (when-not whiteboard?
              (when-not whiteboard?
-               [:div.flex-1.flex-row.w-full
-                [:h1.title.ls-page-title (page-title page-name icon title format fmt-journal?)]])
+               [:div.ls-page-title.flex-1.flex-row.w-full
+                (page-title page-name icon title format fmt-journal?)])
              (when (not config/publishing?)
              (when (not config/publishing?)
                (when config/lsp-enabled?
                (when config/lsp-enabled?
                  [:div.flex.flex-row
                  [:div.flex.flex-row

+ 11 - 2
src/main/frontend/components/settings.cljs

@@ -77,7 +77,16 @@
                :else
                :else
                nil)]
                nil)]
 
 
-       [:div.text-sm version]
+       [:div.text-sm.cursor
+        {:title (str "Revision: " config/revison)
+         :on-click (fn []
+                     (notification/show! [:div "Current Revision: "
+                                          [:a {:target "_blank"
+                                               :href (str "https://github.com/logseq/logseq/commit/" config/revison)}
+                                           config/revison]]
+                                         :info
+                                         false))}
+        version]
 
 
        [:a.text-sm.fade-link.underline.inline
        [:a.text-sm.fade-link.underline.inline
         {:target "_blank"
         {:target "_blank"
@@ -306,7 +315,7 @@
    [:label.block.text-sm.font-medium.leading-5.opacity-70
    [:label.block.text-sm.font-medium.leading-5.opacity-70
     {:for "custom_date_format"}
     {:for "custom_date_format"}
     (t :settings-page/custom-date-format)
     (t :settings-page/custom-date-format)
-    (ui/tippy {:html        (t :settings-page/custom-date-format-warning)     
+    (ui/tippy {:html        (t :settings-page/custom-date-format-warning)
                :class       "tippy-hover ml-2"
                :class       "tippy-hover ml-2"
                :interactive true
                :interactive true
                :disabled    false}
                :disabled    false}

+ 3 - 0
src/main/frontend/config.cljs

@@ -16,6 +16,9 @@
 (goog-define PUBLISHING false)
 (goog-define PUBLISHING false)
 (defonce publishing? PUBLISHING)
 (defonce publishing? PUBLISHING)
 
 
+(goog-define REVISION "unknown")
+(defonce revison REVISION)
+
 (reset! state/publishing? publishing?)
 (reset! state/publishing? publishing?)
 
 
 (goog-define TEST false)
 (goog-define TEST false)

+ 4 - 2
src/main/frontend/fs/sync.cljs

@@ -2794,7 +2794,7 @@
 
 
   (idle [this]
   (idle [this]
     (go
     (go
-      (let [{:keys [stop remote->local local->remote local->remote-full-sync remote->local-full-sync pause] :as result}
+      (let [{:keys [stop remote->local local->remote local->remote-full-sync remote->local-full-sync pause resume] :as result}
             (<! ops-chan)]
             (<! ops-chan)]
         (cond
         (cond
           (or stop (nil? result))
           (or stop (nil? result))
@@ -2809,6 +2809,8 @@
           (<! (.schedule this ::remote->local-full-sync nil nil))
           (<! (.schedule this ::remote->local-full-sync nil nil))
           pause
           pause
           (<! (.schedule this ::pause nil nil))
           (<! (.schedule this ::pause nil nil))
+          resume
+          (<! (.schedule this ::idle nil nil))
           :else
           :else
           (do
           (do
             (state/pub-event! [:capture-error {:error (js/Error. "sync/wrong-ops-chan-when-idle")
             (state/pub-event! [:capture-error {:error (js/Error. "sync/wrong-ops-chan-when-idle")
@@ -2816,7 +2818,7 @@
                                                          :ops-chan-result result
                                                          :ops-chan-result result
                                                          :user-id user-uuid
                                                          :user-id user-uuid
                                                          :graph-id graph-uuid}}])
                                                          :graph-id graph-uuid}}])
-            nil)))))
+            (<! (.schedule this ::idle nil nil)))))))
 
 
   (full-sync [this]
   (full-sync [this]
     (go
     (go

+ 9 - 6
src/main/frontend/handler/command_palette.cljs

@@ -79,12 +79,15 @@
   To add i18n support, prefix `id` with command and put that item in dict.
   To add i18n support, prefix `id` with command and put that item in dict.
   Example: {:zh-CN {:command.document/open-logseq-doc \"打开文档\"}}"
   Example: {:zh-CN {:command.document/open-logseq-doc \"打开文档\"}}"
   [{:keys [id] :as command}]
   [{:keys [id] :as command}]
-  (spec/validate :command/command command)
-  (let [cmds (get-commands)]
-    (if (some (fn [existing-cmd] (= (:id existing-cmd) id)) cmds)
-      (log/error :command/register {:msg "Failed to register command. Command with same id already exist"
-                                    :id  id})
-      (state/set-state! :command-palette/commands (conj cmds command)))))
+  (if (:command/shortcut command)
+    (log/error :shortcut/missing (str "Shortcut is missing for " (:id command)))
+    (do
+      (spec/validate :command/command command)
+      (let [cmds (get-commands)]
+        (if (some (fn [existing-cmd] (= (:id existing-cmd) id)) cmds)
+          (log/error :command/register {:msg "Failed to register command. Command with same id already exist"
+                                        :id  id})
+          (state/set-state! :command-palette/commands (conj cmds command)))))))
 
 
 (defn unregister
 (defn unregister
   [id]
   [id]

+ 12 - 15
src/main/frontend/handler/editor.cljs

@@ -1,6 +1,5 @@
 (ns ^:no-doc frontend.handler.editor
 (ns ^:no-doc frontend.handler.editor
-  (:require ["path" :as path]
-            [clojure.set :as set]
+  (:require [clojure.set :as set]
             [clojure.string :as string]
             [clojure.string :as string]
             [clojure.walk :as w]
             [clojure.walk :as w]
             [dommy.core :as dom]
             [dommy.core :as dom]
@@ -1329,20 +1328,18 @@
              (util/format "[[%s][%s]]" url file-name))
              (util/format "[[%s][%s]]" url file-name))
       nil)))
       nil)))
 
 
-(defn ensure-assets-dir!
+(defn- ensure-assets-dir!
   [repo]
   [repo]
-  (let [repo-dir (config/get-repo-dir repo)
-        assets-dir "assets"]
-    (p/then
-     (fs/mkdir-if-not-exists (str repo-dir "/" assets-dir))
-     (fn [] [repo-dir assets-dir]))))
-
-(defn get-asset-path [filename]
-  (p/let [[repo-dir assets-dir] (ensure-assets-dir! (state/get-current-repo))
-          path (path/join repo-dir assets-dir filename)]
-    (if (mobile-util/native-android?)
-      path
-      (js/encodeURI (js/decodeURI path)))))
+  (p/let [repo-dir (config/get-repo-dir repo)
+          assets-dir "assets"
+          _ (fs/mkdir-if-not-exists (str repo-dir "/" assets-dir))]
+    [repo-dir assets-dir]))
+
+(defn get-asset-path
+  "Get asset path from filename, ensure assets dir exists"
+  [filename]
+  (p/let [[repo-dir assets-dir] (ensure-assets-dir! (state/get-current-repo))]
+    (util/safe-path-join repo-dir assets-dir filename)))
 
 
 (defn save-assets!
 (defn save-assets!
   ([_ repo files]
   ([_ repo files]

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

@@ -86,7 +86,7 @@
         nil
         nil
         (map? result)
         (map? result)
         (do
         (do
-          (state/set-state! :user/info result)
+          (state/set-user-info! result)
           (let [status (if (user-handler/alpha-or-beta-user?) :welcome :unavailable)]
           (let [status (if (user-handler/alpha-or-beta-user?) :welcome :unavailable)]
             (when (and (= status :welcome) (user-handler/logged-in?))
             (when (and (= status :welcome) (user-handler/logged-in?))
               (when-not (false? (state/enable-sync?)) ; user turns it off
               (when-not (false? (state/enable-sync?)) ; user turns it off

+ 33 - 30
src/main/frontend/mobile/camera.cljs

@@ -6,32 +6,35 @@
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.date :as date]
             [frontend.date :as date]
-            [frontend.util :as util]
             [frontend.commands :as commands]
             [frontend.commands :as commands]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [frontend.util.cursor :as cursor]))
             [frontend.util.cursor :as cursor]))
 
 
-(defn- save-photo []
-  (p/let [photo (p/catch
-                    (.getPhoto Camera (clj->js
-                                       {:allowEditing (get-in
-                                                       (state/get-config)
-                                                       [:mobile/photo :allow-editing?])
-                                        :saveToGallery true
-                                        :resultType (.-Base64 CameraResultType)}))
-                    (fn [error]
-                      (log/error :photo/get-failed {:error error})))
-          filename (str (date/get-date-time-string-2) ".jpeg")
-          path (editor-handler/get-asset-path filename)
-          _file (when photo
-                  (p/catch
-                     (.writeFile Filesystem (clj->js {:data (.-base64String photo)
-                                                      :path path
-                                                      :recursive true}))
-                     (fn [error]
-                       (log/error :file/write-failed {:path path
-                                                      :error error}))))]
-    (p/resolved filename)))
+(defn- take-or-choose-photo []
+  (-> (.getPhoto Camera (clj->js
+                         {:allowEditing (get-in
+                                         (state/get-config)
+                                         [:mobile/photo :allow-editing?])
+                          :quality (get-in (state/get-config)
+                                           [:mobile/photo :quality] 80)
+                          :saveToGallery true
+                          :resultType (.-Base64 CameraResultType)}))
+      (p/catch (fn [error]
+                 (log/error :photo/get-failed {:error error})))
+      (p/then (fn [photo]
+                (prn ::debug-photo photo)
+                (if (nil? photo)
+                  (p/resolved nil)
+                  ;; NOTE: For iOS and Android, only jpeg format will be returned as base64 string.
+                  ;; See-also: https://capacitorjs.com/docs/apis/camera#galleryphoto
+                  (p/let [filename (str (date/get-date-time-string-2) ".jpeg")
+                          image-path (editor-handler/get-asset-path filename)
+                          _ret (.writeFile Filesystem (clj->js {:data (.-base64String photo)
+                                                                :path image-path
+                                                                :recursive true}))]
+                    filename))))
+      (p/catch (fn [error]
+                 (log/error :file/write-failed {:error error})))))
 
 
 (defn embed-photo [id]
 (defn embed-photo [id]
   (let [block (state/get-edit-block)
   (let [block (state/get-edit-block)
@@ -48,11 +51,11 @@
 
 
                        :else " ")
                        :else " ")
         format (:block/format block)]
         format (:block/format block)]
-    (p/let [filename (save-photo)
-            url (util/format "../assets/%s" filename)]
-      (commands/simple-insert!
-       id
-       (str left-padding
-            (editor-handler/get-asset-file-link format url filename true)
-        " ")
-       {}))))
+    (p/let [filename (take-or-choose-photo)]
+      (when (not-empty filename)
+        (commands/simple-insert!
+         id
+         (str left-padding
+              (editor-handler/get-asset-file-link format (str "../assets/" filename) filename true)
+              " ")
+         {})))))

+ 16 - 16
src/main/frontend/mobile/mobile_bar.cljs

@@ -48,23 +48,23 @@
      [:button.bottom-action
      [:button.bottom-action
       {:on-mouse-down (fn [event]
       {:on-mouse-down (fn [event]
                         (util/stop event)
                         (util/stop event)
-                        (let [target (gdom/getNextElementSibling (.-target event))]
+                        (let [target (gdom/getNextElementSibling (gdom/getParentElement (.-target event)))]
                           (dom/add-class! target "show-submenu")))}
                           (dom/add-class! target "show-submenu")))}
-      (ui/icon "calendar" {:size ui/icon-size})
-      [:div.submenu.fixed.hidden.flex.flex-col.w-full.justify-evenly
-       {:style {:bottom @util/keyboard-height}}
-       (command-cp #(let [today (page-handler/get-page-ref-text (date/today))]
-                      (commands/simple-insert! parent-id today {}))
-                   "Today")
-       (command-cp #(let [tomorrow (page-handler/get-page-ref-text (date/tomorrow))]
-                      (commands/simple-insert! parent-id tomorrow {}))
-                   "Tomorrow")
-       (command-cp #(let [yesterday (page-handler/get-page-ref-text (date/yesterday))]
-                      (commands/simple-insert! parent-id yesterday {}))
-                   "Yesterday")
-       (command-cp #(let [timestamp (date/get-current-time)]
-                      (commands/simple-insert! parent-id timestamp {}))
-                   "Time")]]]))
+      (ui/icon "calendar" {:size ui/icon-size})]
+     [:div.submenu.fixed.left-0.hidden.w-full.flex-row.justify-evenly.items-center
+      {:style {:bottom @util/keyboard-height}}
+      (command-cp #(let [today (page-handler/get-page-ref-text (date/today))]
+                     (commands/simple-insert! parent-id today {}))
+                  "Today")
+      (command-cp #(let [tomorrow (page-handler/get-page-ref-text (date/tomorrow))]
+                     (commands/simple-insert! parent-id tomorrow {}))
+                  "Tomorrow")
+      (command-cp #(let [yesterday (page-handler/get-page-ref-text (date/yesterday))]
+                     (commands/simple-insert! parent-id yesterday {}))
+                  "Yesterday")
+      (command-cp #(let [timestamp (date/get-current-time)]
+                     (commands/simple-insert! parent-id timestamp {}))
+                  "Time")]]))
 
 
 (defn commands
 (defn commands
   [parent-id]
   [parent-id]

+ 8 - 5
src/main/frontend/modules/instrumentation/sentry.cljs

@@ -14,11 +14,14 @@
                          version)
                          version)
    :environment (if config/dev? "development" "production")
    :environment (if config/dev? "development" "production")
    :initialScope {:tags
    :initialScope {:tags
-                  {:platform (cond
-                               (util/electron?) "electron"
-                               (mobile-util/native-platform?) "mobile"
-                               :else "web")
-                   :publishing config/publishing?}}
+                  (merge
+                   (when (not-empty config/revison)
+                     {:revision config/revison})
+                   {:platform (cond
+                                (util/electron?) "electron"
+                                (mobile-util/native-platform?) "mobile"
+                                :else "web")
+                    :publishing config/publishing?})}
    ;; :integrations [(new posthog/SentryIntegration posthog "logseq" 5311485)
    ;; :integrations [(new posthog/SentryIntegration posthog "logseq" 5311485)
    ;;                (new BrowserTracing)]
    ;;                (new BrowserTracing)]
    :debug config/dev?
    :debug config/dev?

+ 3 - 2
src/main/frontend/search/db.cljs

@@ -30,13 +30,14 @@
 (defn page->index
 (defn page->index
   "Convert a page name to the index for searching (page content level)
   "Convert a page name to the index for searching (page content level)
    Generate index based on the DB content AT THE POINT OF TIME"
    Generate index based on the DB content AT THE POINT OF TIME"
-  [{:block/keys [uuid _original-name] :as page}]
+  [{:block/keys [uuid original-name] :as page}]
   (when-let [content (some-> (:block/file page)
   (when-let [content (some-> (:block/file page)
                              (:file/content))]
                              (:file/content))]
     (when-not (> (count content) (* (max-len) 10))
     (when-not (> (count content) (* (max-len) 10))
       {:id   (:db/id page)
       {:id   (:db/id page)
        :uuid (str uuid)
        :uuid (str uuid)
-       :content (sanitize content)})))
+       ;; Add page name to the index
+       :content (sanitize (str "$pfts_f6ld>$ " original-name " $<pfts_f6ld$ " content))})))
 
 
 (defn build-blocks-indice
 (defn build-blocks-indice
   ;; TODO: Remove repo effects fns further up the call stack. db fns need standardization on taking connection
   ;; TODO: Remove repo effects fns further up the call stack. db fns need standardization on taking connection

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

@@ -263,7 +263,7 @@
      :file-sync/graph-state                 {:current-graph-uuid nil
      :file-sync/graph-state                 {:current-graph-uuid nil
                                              ;; graph-uuid -> ...
                                              ;; graph-uuid -> ...
                                              }
                                              }
-
+     :user/info                             {:UserGroups (storage/get :user-groups)}
      :encryption/graph-parsing?             false
      :encryption/graph-parsing?             false
 
 
      :ui/loading?                           {}
      :ui/loading?                           {}
@@ -2069,3 +2069,11 @@ Similar to re-frame subscriptions"
      (when (and shape-id (parse-uuid shape-id))
      (when (and shape-id (parse-uuid shape-id))
        (. api selectShapes shape-id)
        (. api selectShapes shape-id)
        (. api zoomToSelection)))))
        (. api zoomToSelection)))))
+
+(defn set-user-info!
+  [info]
+  (when info
+    (set-state! :user/info info)
+    (let [groups (:UserGroups info)]
+      (when (seq groups)
+        (storage/set :user-groups groups)))))