Browse Source

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

Tienson Qin 2 years ago
parent
commit
898f10b84d
41 changed files with 389 additions and 436 deletions
  1. 2 2
      .github/ISSUE_TEMPLATE/bug_report.yaml
  2. 2 2
      .github/workflows/build-android.yml
  3. 2 1
      .github/workflows/build.yml
  4. 7 6
      .github/workflows/e2e.yml
  5. 1 1
      README.md
  6. 2 2
      android/app/build.gradle
  7. 8 1
      capacitor.config.ts
  8. 0 1
      deps/graph-parser/src/logseq/graph_parser/util.cljs
  9. 33 30
      e2e-tests/editor.spec.ts
  10. 12 4
      e2e-tests/fixtures.ts
  11. 41 33
      e2e-tests/page-search.spec.ts
  12. 1 0
      e2e-tests/sidebar.spec.ts
  13. 6 6
      e2e-tests/util/keyboard-events.ts
  14. 6 3
      e2e-tests/utils.ts
  15. 140 110
      e2e-tests/whiteboards.spec.ts
  16. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  17. 6 1
      libs/src/LSPlugin.ts
  18. 1 8
      package.json
  19. 1 1
      resources/css/common.css
  20. 2 3
      resources/package.json
  21. 1 1
      scripts/get-pkg-version.js
  22. 10 7
      src/main/electron/listener.cljs
  23. 9 48
      src/main/frontend/components/content.cljs
  24. 7 3
      src/main/frontend/components/header.cljs
  25. 3 1
      src/main/frontend/components/hierarchy.cljs
  26. 1 4
      src/main/frontend/config.cljs
  27. 2 2
      src/main/frontend/extensions/pdf/highlights.cljs
  28. 2 1
      src/main/frontend/extensions/srs.cljs
  29. 1 10
      src/main/frontend/format.cljs
  30. 0 30
      src/main/frontend/format/adoc.cljs
  31. 0 4
      src/main/frontend/format/mldoc.cljs
  32. 0 2
      src/main/frontend/format/protocol.cljs
  33. 12 9
      src/main/frontend/handler/editor.cljs
  34. 12 10
      src/main/frontend/handler/events.cljs
  35. 0 1
      src/main/frontend/mobile/camera.cljs
  36. 1 1
      src/main/frontend/mobile/mobile_bar.cljs
  37. 29 14
      src/main/frontend/state.cljs
  38. 4 4
      src/main/frontend/util.cljc
  39. 1 1
      src/main/frontend/version.cljs
  40. 9 2
      src/main/logseq/api.cljs
  41. 8 62
      yarn.lock

+ 2 - 2
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -4,7 +4,7 @@ body:
   - type: textarea
   - type: textarea
     id: problem
     id: problem
     attributes:
     attributes:
-      label: What happened?
+      label: What Happened?
       description: |
       description: |
         Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
         Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
     validations:
     validations:
@@ -41,7 +41,7 @@ body:
   - type: textarea
   - type: textarea
     id: platform
     id: platform
     attributes:
     attributes:
-      label: Desktop or mobile Platform Information
+      label: Desktop or Mobile Platform Information
       description: |
       description: |
         Would you mind to tell us the system information about your desktop or mobile platform?
         Would you mind to tell us the system information about your desktop or mobile platform?
       placeholder: |
       placeholder: |

+ 2 - 2
.github/workflows/build-android.yml

@@ -107,8 +107,8 @@ jobs:
         run: |
         run: |
           echo "ENABLE_FILE_SYNC_PRODUCTION=${{ inputs.enable-file-sync-production || github.event.inputs.enable-file-sync-production || inputs.build-target == '' }}" >> $GITHUB_ENV
           echo "ENABLE_FILE_SYNC_PRODUCTION=${{ inputs.enable-file-sync-production || github.event.inputs.enable-file-sync-production || inputs.build-target == '' }}" >> $GITHUB_ENV
 
 
-      - name: Compile CLJS - android variant, use es6 instead of es-next
-        run: yarn install && yarn release-android-app
+      - name: Compile CLJS - app variant, use es6 instead of es-next
+        run: yarn install && yarn release-app
 
 
       - name: Upload Sentry Sourcemaps (beta only)
       - name: Upload Sentry Sourcemaps (beta only)
         if: ${{ github.repository == 'logseq/logseq' && (inputs.build-target == 'beta' || github.event.inputs.build-target == 'beta') }}
         if: ${{ github.repository == 'logseq/logseq' && (inputs.build-target == 'beta' || github.event.inputs.build-target == 'beta') }}

+ 2 - 1
.github/workflows/build.yml

@@ -168,7 +168,7 @@ jobs:
       - name: Prepare E2E test build
       - name: Prepare E2E test build
         run: |
         run: |
           yarn gulp:build && clojure -M:cljs compile app publishing electron
           yarn gulp:build && clojure -M:cljs compile app publishing electron
-          (cd static && yarn install && yarn rebuild:better-sqlite3)
+          (cd static && yarn install && yarn rebuild:all)
 
 
       # Exits with 0 if yarn.lock is up to date or 1 if we forgot to update it
       # Exits with 0 if yarn.lock is up to date or 1 if we forgot to update it
       - name: Ensure static yarn.lock is up to date
       - name: Ensure static yarn.lock is up to date
@@ -194,3 +194,4 @@ jobs:
         with:
         with:
           name: e2e-test-report
           name: e2e-test-report
           path: e2e-dump/*
           path: e2e-dump/*
+          retention-days: 1

+ 7 - 6
.github/workflows/e2e.yml

@@ -146,9 +146,10 @@ jobs:
           DEBUG: "pw:api"
           DEBUG: "pw:api"
           RELEASE: true # skip dev only test
           RELEASE: true # skip dev only test
 
 
-      # - name: Save test artifacts
-      #   if: ${{ failure() }}
-      #   uses: actions/upload-artifact@v2
-      #   with:
-      #     name: e2e-test-report
-      #     path: artifacts.zip
+      - name: Save e2e artifacts
+        if: ${{ failure() }}
+        uses: actions/upload-artifact@v3
+        with:
+          name: e2e-repeat-report-${{ matrix.shard}}-${{ matrix.repeat }}
+          path: e2e-dump/*
+          retention-days: 1

+ 1 - 1
README.md

@@ -67,7 +67,7 @@ Logseq is also made possible by the following projects:
 
 
 ## Learn more
 ## Learn more
 
 
-- Our blog: https://logseq.com/blog - Please be sure to visit our [About page](https://logseq.com/blog/about) for the latest updates of the app
+- Our blog: [https://blog.logseq.com/](https://blog.logseq.com) - Please be sure to visit our [About page](https://blog.logseq.com/about) for the latest updates of the app
 - Twitter: https://twitter.com/logseq
 - Twitter: https://twitter.com/logseq
 - Forum: https://discuss.logseq.com - Where we answer questions, discuss workflows and share tips
 - Forum: https://discuss.logseq.com - Where we answer questions, discuss workflows and share tips
 - Discord: https://discord.gg/KpN4eHY
 - Discord: https://discord.gg/KpN4eHY

+ 2 - 2
android/app/build.gradle

@@ -6,8 +6,8 @@ android {
         applicationId "com.logseq.app"
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 46
-        versionName "0.8.12"
+        versionCode 47
+        versionName "0.8.13"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 8 - 1
capacitor.config.ts

@@ -1,4 +1,7 @@
 import { CapacitorConfig } from '@capacitor/cli'
 import { CapacitorConfig } from '@capacitor/cli'
+import fs from 'fs'
+
+const version = fs.readFileSync('static/package.json', 'utf8').match(/"version": "(.*?)"/)?.at(1) ?? '0.0.0'
 
 
 const config: CapacitorConfig = {
 const config: CapacitorConfig = {
   appId: 'com.logseq.app',
   appId: 'com.logseq.app',
@@ -18,8 +21,12 @@ const config: CapacitorConfig = {
       resize: 'none'
       resize: 'none'
     }
     }
   },
   },
+  android: {
+    appendUserAgent: `Logseq/${version} (Android)`
+  },
   ios: {
   ios: {
-    scheme: 'Logseq'
+    scheme: 'Logseq',
+    appendUserAgent: `Logseq/${version} (iOS)`
   },
   },
   cordova: {
   cordova: {
     staticPlugins: [
     staticPlugins: [

+ 0 - 1
deps/graph-parser/src/logseq/graph_parser/util.cljs

@@ -172,7 +172,6 @@
   [format]
   [format]
   (case (keyword format)
   (case (keyword format)
     :md :markdown
     :md :markdown
-    :asciidoc :adoc
     ;; default
     ;; default
     (keyword format)))
     (keyword format)))
 
 

+ 33 - 30
e2e-tests/editor.spec.ts

@@ -167,9 +167,10 @@ test(
 test('copy & paste block ref and replace its content', async ({ page, block }) => {
 test('copy & paste block ref and replace its content', async ({ page, block }) => {
   await createRandomPage(page)
   await createRandomPage(page)
 
 
-  await block.mustFill('Some random text')
-  // FIXME: copy instantly will make content disappear
+  await block.mustType('Some random text')
+  // FIXME: https://github.com/logseq/logseq/issues/7541
   await page.waitForTimeout(1000)
   await page.waitForTimeout(1000)
+
   if (IsMac) {
   if (IsMac) {
     await page.keyboard.press('Meta+c')
     await page.keyboard.press('Meta+c')
   } else {
   } else {
@@ -177,6 +178,8 @@ test('copy & paste block ref and replace its content', async ({ page, block }) =
   }
   }
 
 
   await page.press('textarea >> nth=0', 'Enter')
   await page.press('textarea >> nth=0', 'Enter')
+  await block.waitForBlocks(2)
+
   if (IsMac) {
   if (IsMac) {
     await page.keyboard.press('Meta+v')
     await page.keyboard.press('Meta+v')
   } else {
   } else {
@@ -184,54 +187,58 @@ test('copy & paste block ref and replace its content', async ({ page, block }) =
   }
   }
   await page.keyboard.press('Enter')
   await page.keyboard.press('Enter')
 
 
-  const blockRef = page.locator('.block-ref >> text="Some random text"');
-
   // Check if the newly created block-ref has the same referenced content
   // Check if the newly created block-ref has the same referenced content
-  await expect(blockRef).toHaveCount(1);
+  await expect(page.locator('.block-ref >> text="Some random text"')).toHaveCount(1);
 
 
   // Move cursor into the block ref
   // Move cursor into the block ref
   for (let i = 0; i < 4; i++) {
   for (let i = 0; i < 4; i++) {
     await page.press('textarea >> nth=0', 'ArrowLeft')
     await page.press('textarea >> nth=0', 'ArrowLeft')
   }
   }
 
 
+  await expect(page.locator('textarea >> nth=0')).not.toHaveValue('Some random text')
+
   // Trigger replace-block-reference-with-content-at-point
   // Trigger replace-block-reference-with-content-at-point
   if (IsMac) {
   if (IsMac) {
     await page.keyboard.press('Meta+Shift+r')
     await page.keyboard.press('Meta+Shift+r')
   } else {
   } else {
-    await page.keyboard.press('Control+Shift+v')
+    await page.keyboard.press('Control+Shift+r')
   }
   }
+  await expect(page.locator('textarea >> nth=0')).toHaveValue('Some random text')
+  await block.escapeEditing()
+
+  await expect(page.locator('.block-ref >> text="Some random text"')).toHaveCount(0);
+  await expect(page.locator('text="Some random text"')).toHaveCount(2);
 })
 })
 
 
 test('copy and paste block after editing new block #5962', async ({ page, block }) => {
 test('copy and paste block after editing new block #5962', async ({ page, block }) => {
   await createRandomPage(page)
   await createRandomPage(page)
 
 
   // Create a block and copy it in block-select mode
   // Create a block and copy it in block-select mode
-  await block.mustFill('Block being copied')
-  await page.waitForTimeout(100)
+  await block.mustType('Block being copied')
   await page.keyboard.press('Escape')
   await page.keyboard.press('Escape')
-  await page.waitForTimeout(100)
+  await expect(page.locator('.ls-block.selected')).toHaveCount(1)
+
   if (IsMac) {
   if (IsMac) {
-    await page.keyboard.press('Meta+c')
+    await page.keyboard.press('Meta+c', { delay: 10 })
   } else {
   } else {
-    await page.keyboard.press('Control+c')
+    await page.keyboard.press('Control+c', { delay: 10 })
   }
   }
-  // await page.waitForTimeout(100)
+
   await page.keyboard.press('Enter')
   await page.keyboard.press('Enter')
-  await page.waitForTimeout(100)
+  await expect(page.locator('.ls-block.selected')).toHaveCount(0)
+  await expect(page.locator('textarea >> nth=0')).toBeVisible()
   await page.keyboard.press('Enter')
   await page.keyboard.press('Enter')
+  await block.waitForBlocks(2)
 
 
-  await page.waitForTimeout(100)
-  // Create a new block with some text
-  await page.keyboard.insertText("Typed block")
+  await block.mustType('Typed block')
 
 
-  // Quickly paste the copied block
   if (IsMac) {
   if (IsMac) {
     await page.keyboard.press('Meta+v')
     await page.keyboard.press('Meta+v')
   } else {
   } else {
     await page.keyboard.press('Control+v')
     await page.keyboard.press('Control+v')
   }
   }
-
-  await expect(page.locator('text="Typed block"')).toHaveCount(1);
+  await expect(page.locator('text="Typed block"')).toHaveCount(1)
+  await block.waitForBlocks(3)
 })
 })
 
 
 test('undo and redo after starting an action should not destroy text #6267', async ({ page, block }) => {
 test('undo and redo after starting an action should not destroy text #6267', async ({ page, block }) => {
@@ -516,7 +523,7 @@ test('press escape when link/image dialog is open, should restore focus to input
 })
 })
 
 
 test('should show text after soft return when node is collapsed #5074', async ({ page, block }) => {
 test('should show text after soft return when node is collapsed #5074', async ({ page, block }) => {
-  const delay = 100
+  const delay = 300
   await createRandomPage(page)
   await createRandomPage(page)
 
 
   await page.type('textarea >> nth=0', 'Before soft return', { delay: 10 })
   await page.type('textarea >> nth=0', 'Before soft return', { delay: 10 })
@@ -526,11 +533,10 @@ test('should show text after soft return when node is collapsed #5074', async ({
   await block.enterNext()
   await block.enterNext()
   expect(await block.indent()).toBe(true)
   expect(await block.indent()).toBe(true)
   await block.mustType('Child text')
   await block.mustType('Child text')
-  await page.waitForTimeout(delay)
 
 
   // collapse
   // collapse
   await page.click('.block-control >> nth=0')
   await page.click('.block-control >> nth=0')
-  await page.waitForTimeout(delay)
+  await block.waitForBlocks(1)
 
 
   // select the block that has the soft return
   // select the block that has the soft return
   await page.keyboard.press('ArrowDown')
   await page.keyboard.press('ArrowDown')
@@ -538,13 +544,12 @@ test('should show text after soft return when node is collapsed #5074', async ({
   await page.keyboard.press('Enter')
   await page.keyboard.press('Enter')
   await page.waitForTimeout(delay)
   await page.waitForTimeout(delay)
 
 
-  expect(await page.inputValue('textarea >> nth=0')).toBe(
-    'Before soft return\nAfter soft return'
-  )
+  await expect(page.locator('textarea >> nth=0')).toHaveText('Before soft return\nAfter soft return')
 
 
   // zoom into the block
   // zoom into the block
-  await page.click('a.block-control + a')
-  await page.waitForTimeout(delay)
+  page.click('a.block-control + a')
+  await page.waitForNavigation()
+  await page.waitForTimeout(delay * 3)
 
 
   // select the block that has the soft return
   // select the block that has the soft return
   await page.keyboard.press('ArrowDown')
   await page.keyboard.press('ArrowDown')
@@ -552,9 +557,7 @@ test('should show text after soft return when node is collapsed #5074', async ({
   await page.keyboard.press('Enter')
   await page.keyboard.press('Enter')
   await page.waitForTimeout(delay)
   await page.waitForTimeout(delay)
 
 
-  expect(await page.inputValue('textarea >> nth=0')).toBe(
-    'Before soft return\nAfter soft return'
-  )
+  await expect(page.locator('textarea >> nth=0')).toHaveText('Before soft return\nAfter soft return')
 })
 })
 
 
 test('should not erase typed text when expanding block quickly after typing #3891', async ({ page, block }) => {
 test('should not erase typed text when expanding block quickly after typing #3891', async ({ page, block }) => {

+ 12 - 4
e2e-tests/fixtures.ts

@@ -118,11 +118,13 @@ base.beforeEach(async () => {
     await page.keyboard.press('Escape')
     await page.keyboard.press('Escape')
     await page.keyboard.press('Escape')
     await page.keyboard.press('Escape')
 
 
+    /*
     const locator = page.locator('.notification-close-button').first()
     const locator = page.locator('.notification-close-button').first()
     while (await locator.isVisible()) {
     while (await locator.isVisible()) {
-      await locator.click()
-      expect(locator.isVisible()).resolves.toBe(false)
+      locator.click() // ignore error
     }
     }
+    */
+    await expect(page.locator('.notification-close-button')).not.toBeVisible()
 
 
     const rightSidebar = page.locator('.cp__right-sidebar-inner')
     const rightSidebar = page.locator('.cp__right-sidebar-inner')
     if (await rightSidebar.isVisible()) {
     if (await rightSidebar.isVisible()) {
@@ -203,8 +205,14 @@ export const test = base.extend<LogseqFixtures>({
         await page.waitForSelector(`.ls-block.selected >> nth=${total - 1}`, { timeout: 1000 })
         await page.waitForSelector(`.ls-block.selected >> nth=${total - 1}`, { timeout: 1000 })
       },
       },
       escapeEditing: async (): Promise<void> => {
       escapeEditing: async (): Promise<void> => {
-        await page.keyboard.press('Escape')
-        await page.keyboard.press('Escape')
+        const blockEdit = page.locator('.ls-block textarea >> nth=0')
+        while (await blockEdit.isVisible()) {
+          await page.keyboard.press('Escape')
+        }
+        const blockSelect = page.locator('.ls-block.selected')
+        while (await blockSelect.isVisible()) {
+          await page.keyboard.press('Escape')
+        }
       },
       },
       activeEditing: async (nth: number): Promise<void> => {
       activeEditing: async (nth: number): Promise<void> => {
         await page.waitForSelector(`.ls-block >> nth=${nth}`, { timeout: 1000 })
         await page.waitForSelector(`.ls-block >> nth=${nth}`, { timeout: 1000 })

+ 41 - 33
e2e-tests/page-search.spec.ts

@@ -83,6 +83,8 @@ test('Search CJK', async ({ page, block }) => {
 })
 })
 
 
 async function alias_test( block: Block, page: Page, page_name: string, search_kws: string[] ) {
 async function alias_test( block: Block, page: Page, page_name: string, search_kws: string[] ) {
+  await createRandomPage(page)
+
   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
@@ -90,10 +92,7 @@ async function alias_test( block: Block, page: Page, page_name: string, search_k
   let alias_test_content_2 = randomString(20)
   let alias_test_content_2 = randomString(20)
   let alias_test_content_3 = randomString(20)
   let alias_test_content_3 = randomString(20)
 
 
-  // shortcut opening test
-  let parent_title = await createRandomPage(page)
-
-  await page.fill('textarea >> nth=0', '[[' + target_name + ']]')
+  await page.type('textarea >> nth=0', '[[' + target_name)
   await page.keyboard.press(hotkeyOpenLink)
   await page.keyboard.press(hotkeyOpenLink)
 
 
   await lastBlock(page)
   await lastBlock(page)
@@ -103,46 +102,57 @@ async function alias_test( block: Block, page: Page, page_name: string, search_k
   //   alias_test_content_1,
   //   alias_test_content_1,
   //   alias_test_content_2, and
   //   alias_test_content_2, and
   //   alias_test_content_3 sequentialy, to validate the target page state
   //   alias_test_content_3 sequentialy, to validate the target page state
-  await page.type('textarea >> nth=0', 'alias:: [[' + alias_name)
-  await page.press('textarea >> nth=0', 'Enter') // Enter for finishing selection
-  await page.press('textarea >> nth=0', 'Enter') // double Enter for exit property editing
-  await page.press('textarea >> nth=0', 'Enter') // double Enter for exit property editing
-  await lastBlock(page)
+  await page.type('textarea >> nth=0', 'alias:: [[' + alias_name, {delay: 10})
+  await page.keyboard.press('Enter', {delay: 200}) // Enter for finishing selection
+  await page.keyboard.press('Enter', {delay: 200}) // double Enter for exit property editing
+  await page.keyboard.press('Enter', {delay: 200}) // double Enter for exit property editing
+  await page.waitForTimeout(200)
+  await block.activeEditing(1)
   await page.type('textarea >> nth=0', alias_test_content_1)
   await page.type('textarea >> nth=0', alias_test_content_1)
   await lastBlock(page)
   await lastBlock(page)
-  await page.keyboard.press(hotkeyBack)
+  page.keyboard.press(hotkeyBack)
 
 
-  await page.waitForTimeout(100) // await navigation
+  await page.waitForNavigation()
+  await block.escapeEditing()
   // create alias ref in origin Page
   // create alias ref in origin Page
-  await newBlock(page)
-  await page.type('textarea >> nth=0', '[[' + alias_name)
-  await page.press('textarea >> nth=0', 'Enter') // Enter for finishing selection
+  await block.activeEditing(0)
+  await block.enterNext()
+  await page.type('textarea >> nth=0', '[[' + alias_name, {delay: 20})
+  await page.keyboard.press('Enter') // Enter for finishing selection
   await page.waitForTimeout(100)
   await page.waitForTimeout(100)
 
 
-  await page.keyboard.press(hotkeyOpenLink)
-  await page.waitForTimeout(100) // await navigation
+  page.keyboard.press(hotkeyOpenLink)
+  await page.waitForNavigation()
+  await block.escapeEditing()
 
 
   // shortcut opening test
   // shortcut opening test
-  await lastBlock(page)
+  await block.activeEditing(1)
   expect(await page.inputValue('textarea >> nth=0')).toBe(alias_test_content_1)
   expect(await page.inputValue('textarea >> nth=0')).toBe(alias_test_content_1)
 
 
   await enterNextBlock(page)
   await enterNextBlock(page)
   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)
+  page.keyboard.press(hotkeyBack)
 
 
+  await page.waitForNavigation()
+  await block.escapeEditing()
   // pressing enter on alias opening test
   // pressing enter on alias opening test
-  await lastBlock(page)
+  await block.activeEditing(1)
   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')
   await page.press('textarea >> nth=0', 'ArrowLeft')
   await page.press('textarea >> nth=0', 'ArrowLeft')
-  await page.press('textarea >> nth=0', 'Enter')
-  await lastBlock(page)
+  page.press('textarea >> nth=0', 'Enter')
+  await page.waitForNavigation()
+  await block.escapeEditing()
+  await block.activeEditing(2)
   expect(await page.inputValue('textarea >> nth=0')).toBe(alias_test_content_2)
   expect(await page.inputValue('textarea >> nth=0')).toBe(alias_test_content_2)
   await newInnerBlock(page)
   await newInnerBlock(page)
   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)
-
+  page.keyboard.press(hotkeyBack)
+  
+  await page.waitForNavigation()
+  await block.escapeEditing()
   // clicking alias ref opening test
   // clicking alias ref opening test
+  await block.activeEditing(1)
   await block.enterNext()
   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')
@@ -157,7 +167,7 @@ async function alias_test( block: Block, page: Page, page_name: string, search_k
 
 
     await page.click('#search-button')
     await page.click('#search-button')
     await page.waitForSelector('[placeholder="Search or create page"]')
     await page.waitForSelector('[placeholder="Search or create page"]')
-    await page.fill('[placeholder="Search or create page"]', kw_name)
+    await page.type('[placeholder="Search or create page"]', kw_name)
     await page.waitForTimeout(500)
     await page.waitForTimeout(500)
 
 
     const results = await page.$$('#ui__ac-inner>div')
     const results = await page.$$('#ui__ac-inner>div')
@@ -172,26 +182,24 @@ async function alias_test( block: Block, page: Page, page_name: string, search_k
     // test search entering (page)
     // test search entering (page)
     page.keyboard.press("Enter")
     page.keyboard.press("Enter")
     await page.waitForNavigation()
     await page.waitForNavigation()
-    await page.waitForTimeout(100)
-    await lastBlock(page)
-    expect(await page.inputValue('textarea >> nth=0')).toBe(alias_test_content_3)
+    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)
     // test search clicking (block)
     await page.click('#search-button')
     await page.click('#search-button')
     await page.waitForSelector('[placeholder="Search or create page"]')
     await page.waitForSelector('[placeholder="Search or create page"]')
-    await page.fill('[placeholder="Search or create page"]', kw_name)
+    await page.type('[placeholder="Search or create page"]', kw_name)
     await page.waitForTimeout(500)
     await page.waitForTimeout(500)
-    page.click(":nth-match(.menu-link, 2)")
+    page.click(":nth-match(.search-result, 3)")
     await page.waitForNavigation()
     await page.waitForNavigation()
-    await page.waitForTimeout(500)
-    await lastBlock(page)
-    expect(await page.inputValue('textarea >> nth=0')).toBe("[[" + alias_name + "]]")
+    await page.waitForSelector('.selected a.page-ref')
+    expect(await page.locator('.selected a.page-ref').innerHTML()).toBe(alias_name)
     await page.keyboard.press(hotkeyBack)
     await page.keyboard.press(hotkeyBack)
   }
   }
 
 
   // TODO: search clicking (alias property)
   // TODO: search clicking (alias property)
 }
 }
 
 
-test.skip('page diacritic alias', async ({ block, page }) => {
+test('page diacritic alias', async ({ block, page }) => {
   await alias_test(block, page, "ü", ["ü", "ü", "Ü"])
   await alias_test(block, page, "ü", ["ü", "ü", "Ü"])
 })
 })

+ 1 - 0
e2e-tests/sidebar.spec.ts

@@ -49,6 +49,7 @@ test('recent is updated #4320', async ({ page }) => {
 
 
   // then jump back
   // then jump back
   await searchAndJumpToPage(page, page1)
   await searchAndJumpToPage(page, page1)
+  await page.waitForTimeout(500)
   expect(await firstRecent.textContent()).toContain(page1)
   expect(await firstRecent.textContent()).toContain(page1)
   expect(await secondRecent.textContent()).toContain(page2)
   expect(await secondRecent.textContent()).toContain(page2)
 })
 })

+ 6 - 6
e2e-tests/util/keyboard-events.ts

@@ -1,4 +1,4 @@
-/*** 
+/***
  * Author: Junyi Du <[email protected]>
  * Author: Junyi Du <[email protected]>
  * References:
  * References:
  * https://stackoverflow.com/questions/8892238/detect-keyboard-layout-with-javascript
  * https://stackoverflow.com/questions/8892238/detect-keyboard-layout-with-javascript
@@ -243,7 +243,7 @@ export let macos_pinyin_selecting_candidate_double_left_square_bracket: Recorded
       "repeat": false,
       "repeat": false,
       "isComposing": true
       "isComposing": true
     },
     },
-    "latency": 627
+    "latency": 200
   },
   },
   {
   {
     "event_type": "keyup",
     "event_type": "keyup",
@@ -353,7 +353,7 @@ export let macos_pinyin_selecting_candidate_double_left_square_bracket: Recorded
   {
   {
     "event_type": "compositionend",
     "event_type": "compositionend",
     "event": {},
     "event": {},
-    "latency": 968
+    "latency": 200
   }
   }
 ]
 ]
 
 
@@ -426,7 +426,7 @@ export let win10_RIME_selecting_candidate_double_left_square_bracket: RecordedEv
       "repeat": false,
       "repeat": false,
       "isComposing": true
       "isComposing": true
     },
     },
-    "latency": 237
+    "latency": 200
   },
   },
   {
   {
     "event_type": "keyup",
     "event_type": "keyup",
@@ -461,6 +461,6 @@ export let win10_RIME_selecting_candidate_double_left_square_bracket: RecordedEv
   {
   {
     "event_type": "compositionend",
     "event_type": "compositionend",
     "event": {},
     "event": {},
-    "latency": 1479
+    "latency": 200
   }
   }
-]
+]

+ 6 - 3
e2e-tests/utils.ts

@@ -65,9 +65,10 @@ export async function createPage(page: Page, page_name: string) {// Click #searc
 
 
 export async function searchAndJumpToPage(page: Page, pageTitle: string) {
 export async function searchAndJumpToPage(page: Page, pageTitle: string) {
   await page.click('#search-button')
   await page.click('#search-button')
-  await page.fill('[placeholder="Search or create page"]', pageTitle)
+  await page.type('[placeholder="Search or create page"]', pageTitle)
   await page.waitForSelector(`[data-page-ref="${pageTitle}"]`, { state: 'visible' })
   await page.waitForSelector(`[data-page-ref="${pageTitle}"]`, { state: 'visible' })
-  await page.click(`[data-page-ref="${pageTitle}"]`)
+  page.click(`[data-page-ref="${pageTitle}"]`)
+  await page.waitForNavigation()
   return pageTitle;
   return pageTitle;
 }
 }
 
 
@@ -213,7 +214,9 @@ export async function loadLocalGraph(page: Page, path: string): Promise<void> {
   // close it first so it doesn't cover up the UI
   // close it first so it doesn't cover up the UI
   let locator = page.locator('.notification-close-button').first()
   let locator = page.locator('.notification-close-button').first()
   while (await locator?.isVisible()) {
   while (await locator?.isVisible()) {
-    await locator.click()
+    try { // don't fail if unable to click (likely disappeared already)
+      await locator.click()
+    } catch (error) {}
     await page.waitForTimeout(250)
     await page.waitForTimeout(250)
 
 
     expect(locator.isVisible()).resolves.toBe(false)
     expect(locator.isVisible()).resolves.toBe(false)

+ 140 - 110
e2e-tests/whiteboards.spec.ts

@@ -2,147 +2,177 @@ import { expect } from '@playwright/test'
 import { test } from './fixtures'
 import { test } from './fixtures'
 import { IsMac } from './utils'
 import { IsMac } from './utils'
 
 
-test.skip('enable whiteboards', async ({ page }) => {
-    await page.evaluate(() => {
-        window.localStorage.removeItem('ls-onboarding-whiteboard?')
-    })
-
-    await expect(page.locator('.nav-header .whiteboard')).toBeHidden()
-    await page.click('#head .toolbar-dots-btn')
-    await page.click('#head .dropdown-wrapper >> text=Settings')
-    await page.click('.settings-modal a[data-id=features]')
-    await page.click('text=Whiteboards >> .. >> .ui__toggle')
-    await page.keyboard.press('Escape')
-    await expect(page.locator('.nav-header .whiteboard')).toBeVisible()
+test('enable whiteboards', async ({ page }) => {
+  await page.evaluate(() => {
+    window.localStorage.removeItem('ls-onboarding-whiteboard?')
+  })
+
+  await expect(page.locator('.nav-header .whiteboard')).toBeHidden()
+  await page.click('#head .toolbar-dots-btn')
+  await page.click('#head .dropdown-wrapper >> text=Settings')
+  await page.click('.settings-modal a[data-id=features]')
+  await page.click('text=Whiteboards >> .. >> .ui__toggle')
+  await page.keyboard.press('Escape')
+  await expect(page.locator('.nav-header .whiteboard')).toBeVisible()
 })
 })
 
 
-test.skip('create new whiteboard', async ({ page }) => {
-    await page.click('.nav-header .whiteboard')
-    await page.click('#tl-create-whiteboard')
-    await expect(page.locator('.logseq-tldraw')).toBeVisible()
+test('create new whiteboard', async ({ page }) => {
+  await page.click('.nav-header .whiteboard')
+  await page.click('#tl-create-whiteboard')
+  await expect(page.locator('.logseq-tldraw')).toBeVisible()
 })
 })
 
 
-test.skip('check if the page contains the onboarding whiteboard', async ({ page }) => {
-    await expect(page.locator('.tl-text-shape-wrapper >> text=Welcome to')).toHaveCount(1)
+test('check if the page contains the onboarding whiteboard', async ({
+  page,
+}) => {
+  await expect(
+    page.locator('.tl-text-shape-wrapper >> text=Welcome to')
+  ).toHaveCount(1)
 })
 })
 
 
-test.skip('cleanup the shapes', async ({ page }) => {
-    if (IsMac) {
-        await page.keyboard.press('Meta+a')
-    } else {
-        await page.keyboard.press('Control+a')
-    }
-    await page.keyboard.press('Delete')
-    await expect(page.locator('[data-type=Shape]')).toHaveCount(0)
+test('cleanup the shapes', async ({ page }) => {
+  if (IsMac) {
+    await page.keyboard.press('Meta+a')
+  } else {
+    await page.keyboard.press('Control+a')
+  }
+  await page.keyboard.press('Delete')
+  await expect(page.locator('[data-type=Shape]')).toHaveCount(0)
 })
 })
 
 
-test.skip('can right click title to show context menu', async ({ page }) => {
-    await page.click('.whiteboard-page-title', {
-        button: 'right',
-    })
+test('can right click title to show context menu', async ({ page }) => {
+  await page.click('.whiteboard-page-title', {
+    button: 'right',
+  })
 
 
-    await expect(page.locator('#custom-context-menu')).toBeVisible()
+  await expect(page.locator('#custom-context-menu')).toBeVisible()
 
 
-    await page.keyboard.press('Escape')
+  await page.keyboard.press('Escape')
 
 
-    await expect(page.locator('#custom-context-menu')).toHaveCount(0)
+  await expect(page.locator('#custom-context-menu')).toHaveCount(0)
 })
 })
 
 
-test.skip('set whiteboard title', async ({ page }) => {
-    const title = "my-whiteboard"
-    // Newly created whiteboard should have a default title
-    await expect(page.locator('.whiteboard-page-title .title')).toContainText("Untitled");
-
-    await page.click('.whiteboard-page-title')
-    await page.fill('.whiteboard-page-title input', title)
-    await page.keyboard.press('Enter')
-    await expect(page.locator('.whiteboard-page-title .title')).toContainText(title);
-
-    await page.click('.whiteboard-page-title')
-    await page.fill('.whiteboard-page-title input', title + "-2")
-    await page.keyboard.press('Enter')
-
-    // Updating non-default title should pop up a confirmation dialog
-    await expect(page.locator('.ui__confirm-modal >> .headline')).toContainText(`Do you really want to change the page name to “${title}-2”?`);
-    await page.click('.ui__confirm-modal button')
-    await expect(page.locator('.whiteboard-page-title .title')).toContainText(title + "-2");
+test('set whiteboard title', async ({ page }) => {
+  const title = 'my-whiteboard'
+  // Newly created whiteboard should have a default title
+  await expect(page.locator('.whiteboard-page-title .title')).toContainText(
+    'Untitled'
+  )
+
+  await page.click('.whiteboard-page-title')
+  await page.fill('.whiteboard-page-title input', title)
+  await page.keyboard.press('Enter')
+  await expect(page.locator('.whiteboard-page-title .title')).toContainText(
+    title
+  )
+
+  await page.click('.whiteboard-page-title')
+  await page.fill('.whiteboard-page-title input', title + '-2')
+  await page.keyboard.press('Enter')
+
+  // Updating non-default title should pop up a confirmation dialog
+  await expect(page.locator('.ui__confirm-modal >> .headline')).toContainText(
+    `Do you really want to change the page name to “${title}-2”?`
+  )
+  await page.click('.ui__confirm-modal button')
+  await expect(page.locator('.whiteboard-page-title .title')).toContainText(
+    title + '-2'
+  )
 })
 })
 
 
-test.skip('select rectangle tool', async ({ page }) => {
-    await page.keyboard.press('7')
-    await expect(page.locator('.tl-geometry-tools-pane-anchor [title*="Rectangle"]')).toHaveAttribute('data-selected', 'true')
+test('select rectangle tool', async ({ page }) => {
+  await page.keyboard.press('7')
+  await expect(
+    page.locator('.tl-geometry-tools-pane-anchor [title*="Rectangle"]')
+  ).toHaveAttribute('data-selected', 'true')
 })
 })
 
 
-test.skip('draw a rectangle', async ({ page }) => {
-    const canvas = await page.waitForSelector('.logseq-tldraw');
-    const bounds = (await canvas.boundingBox())!;
+test('draw a rectangle', async ({ page }) => {
+  const canvas = await page.waitForSelector('.logseq-tldraw')
+  const bounds = (await canvas.boundingBox())!
 
 
-    await page.keyboard.press('7')
+  await page.keyboard.press('7')
 
 
-    await page.mouse.move(bounds.x + 5, bounds.y + 5);
-    await page.mouse.down();
+  await page.mouse.move(bounds.x + 5, bounds.y + 5)
+  await page.mouse.down()
 
 
-    await page.mouse.move(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
-    await page.mouse.up();
+  await page.mouse.move(
+    bounds.x + bounds.width / 2,
+    bounds.y + bounds.height / 2
+  )
+  await page.mouse.up()
 
 
-    await expect(page.locator('.logseq-tldraw .tl-positioned-svg rect')).not.toHaveCount(0);
+  await expect(
+    page.locator('.logseq-tldraw .tl-positioned-svg rect')
+  ).not.toHaveCount(0)
 })
 })
 
 
-test.skip('zoom in', async ({ page }) => {
-    await page.click('#tl-zoom-in')
-    await expect(page.locator('#tl-zoom')).toContainText('125%');
+test('zoom in', async ({ page }) => {
+  await page.click('#tl-zoom-in')
+  await expect(page.locator('#tl-zoom')).toContainText('125%')
 })
 })
 
 
-test.skip('zoom out', async ({ page }) => {
-    await page.click('#tl-zoom-out')
-    await expect(page.locator('#tl-zoom')).toContainText('100%');
+test('zoom out', async ({ page }) => {
+  await page.click('#tl-zoom-out')
+  await expect(page.locator('#tl-zoom')).toContainText('100%')
 })
 })
 
 
-test.skip('open context menu', async ({ page }) => {
-    await page.locator('.logseq-tldraw').click({ button: "right" })
-    await expect(page.locator('.tl-context-menu')).toBeVisible()
+test('open context menu', async ({ page }) => {
+  await page.locator('.logseq-tldraw').click({ button: 'right' })
+  await expect(page.locator('.tl-context-menu')).toBeVisible()
 })
 })
 
 
-test.skip('close context menu on esc', async ({ page }) => {
-    await page.keyboard.press('Escape')
-    await expect(page.locator('.tl-context-menu')).toBeHidden()
+test('close context menu on esc', async ({ page }) => {
+  await page.keyboard.press('Escape')
+  await expect(page.locator('.tl-context-menu')).toBeHidden()
 })
 })
 
 
-test.skip('quick add another whiteboard', async ({ page }) => {
-    // create a new board first
-    await page.click('.nav-header .whiteboard')
-    await page.click('#tl-create-whiteboard')
-
-    await page.click('.whiteboard-page-title')
-    await page.fill('.whiteboard-page-title input', "my-whiteboard-3")
-    await page.keyboard.press('Enter')
-
-    const canvas = await page.waitForSelector('.logseq-tldraw');
-    await canvas.dblclick({
-        position: {
-            x: 100,
-            y: 100
-        }
-    })
-
-    const quickAdd$ = page.locator('.tl-quick-search')
-    await expect(quickAdd$).toBeVisible()
-
-    await page.fill('.tl-quick-search input', 'my-whiteboard')
-    await quickAdd$.locator('.tl-quick-search-option >> text=my-whiteboard-2').first().click()
-
-    await expect(quickAdd$).toBeHidden()
-    await expect(page.locator('.tl-logseq-portal-container >> text=my-whiteboard-2')).toBeVisible()
+test('quick add another whiteboard', async ({ page }) => {
+  // create a new board first
+  await page.click('.nav-header .whiteboard')
+  await page.click('#tl-create-whiteboard')
+
+  await page.click('.whiteboard-page-title')
+  await page.fill('.whiteboard-page-title input', 'my-whiteboard-3')
+  await page.keyboard.press('Enter')
+
+  const canvas = await page.waitForSelector('.logseq-tldraw')
+  await canvas.dblclick({
+    position: {
+      x: 100,
+      y: 100,
+    },
+  })
+
+  const quickAdd$ = page.locator('.tl-quick-search')
+  await expect(quickAdd$).toBeVisible()
+
+  await page.fill('.tl-quick-search input', 'my-whiteboard')
+  await quickAdd$
+    .locator('.tl-quick-search-option >> text=my-whiteboard-2')
+    .first()
+    .click()
+
+  await expect(quickAdd$).toBeHidden()
+  await expect(
+    page.locator('.tl-logseq-portal-container >> text=my-whiteboard-2')
+  ).toBeVisible()
 })
 })
 
 
-test.skip('go to another board and check reference', async ({ page }) => {
-    await page.locator('.tl-logseq-portal-container >> text=my-whiteboard-2').click()
-    await expect(page.locator('.whiteboard-page-title .title')).toContainText("my-whiteboard-2");
-
-    const pageRefCount$ = page.locator('.whiteboard-page-refs-count')
-    await expect(pageRefCount$.locator('.open-page-ref-link')).toContainText('1')
-
-    await pageRefCount$.click()
-    await expect(page.locator('.references-blocks')).toBeVisible()
-    await expect(page.locator('.references-blocks >> .page-ref >> text=my-whiteboard-3')).toBeVisible()
+test('go to another board and check reference', async ({ page }) => {
+  await page
+    .locator('.tl-logseq-portal-container >> text=my-whiteboard-2')
+    .click()
+  await expect(page.locator('.whiteboard-page-title .title')).toContainText(
+    'my-whiteboard-2'
+  )
+
+  const pageRefCount$ = page.locator('.whiteboard-page-refs-count')
+  await expect(pageRefCount$.locator('.open-page-ref-link')).toContainText('1')
+
+  await pageRefCount$.click()
+  await expect(page.locator('.references-blocks')).toBeVisible()
+  await expect(
+    page.locator('.references-blocks >> .page-ref >> text=my-whiteboard-3')
+  ).toBeVisible()
 })
 })

+ 4 - 4
ios/App/App.xcodeproj/project.pbxproj

@@ -515,7 +515,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.8.12;
+				MARKETING_VERSION = 0.8.13;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -542,7 +542,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.8.12;
+				MARKETING_VERSION = 0.8.13;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -567,7 +567,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.8.12;
+				MARKETING_VERSION = 0.8.13;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -594,7 +594,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.8.12;
+				MARKETING_VERSION = 0.8.13;
 				MTL_FAST_MATH = YES;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 6 - 1
libs/src/LSPlugin.ts

@@ -634,10 +634,15 @@ export interface IEditorProxy extends Record<string, any> {
     }>
     }>
   ) => Promise<BlockEntity | null>
   ) => Promise<BlockEntity | null>
 
 
+  /**
+   * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news
+   * 
+   * `keepUUID` will allow you to set a custom UUID for blocks by setting their properties.id
+   */
   insertBatchBlock: (
   insertBatchBlock: (
     srcBlock: BlockIdentity,
     srcBlock: BlockIdentity,
     batch: IBatchBlock | Array<IBatchBlock>,
     batch: IBatchBlock | Array<IBatchBlock>,
-    opts?: Partial<{ before: boolean; sibling: boolean }>
+    opts?: Partial<{ before: boolean; sibling: boolean, keepUUID: boolean }>
   ) => Promise<Array<BlockEntity> | null>
   ) => Promise<Array<BlockEntity> | null>
 
 
   updateBlock: (
   updateBlock: (

+ 1 - 8
package.json

@@ -39,7 +39,6 @@
         "app-watch": "run-p gulp:watch cljs:app-watch",
         "app-watch": "run-p gulp:watch cljs:app-watch",
         "release": "run-s gulp:build cljs:release",
         "release": "run-s gulp:build cljs:release",
         "release-app": "run-s gulp:build cljs:release-app",
         "release-app": "run-s gulp:build cljs:release-app",
-        "release-android-app": "run-s gulp:build cljs:release-android-app",
         "dev-release-app": "run-s gulp:build cljs:dev-release-app",
         "dev-release-app": "run-s gulp:build cljs:dev-release-app",
         "dev-electron-app": "gulp electron",
         "dev-electron-app": "gulp electron",
         "release-electron": "run-s gulp:build && gulp electronMaker",
         "release-electron": "run-s gulp:build && gulp electronMaker",
@@ -61,7 +60,6 @@
         "cljs:release": "clojure -M:cljs release app publishing electron",
         "cljs:release": "clojure -M:cljs release app publishing electron",
         "cljs:release-electron": "clojure -M:cljs release app electron --debug && clojure -M:cljs release publishing",
         "cljs:release-electron": "clojure -M:cljs release app electron --debug && clojure -M:cljs release publishing",
         "cljs:release-app": "clojure -M:cljs release app --config-merge \"{:compiler-options {:output-feature-set :es6}}\"",
         "cljs:release-app": "clojure -M:cljs release app --config-merge \"{:compiler-options {:output-feature-set :es6}}\"",
-        "cljs:release-android-app": "clojure -M:cljs release app --config-merge \"{:compiler-options {:output-feature-set :es6}}\"",
         "cljs:release-publishing": "clojure -M:cljs release publishing",
         "cljs:release-publishing": "clojure -M:cljs release publishing",
         "cljs:test": "clojure -M:test compile test",
         "cljs:test": "clojure -M:test compile test",
         "cljs:run-test": "node static/tests.js",
         "cljs:run-test": "node static/tests.js",
@@ -91,13 +89,12 @@
         "@capawesome/capacitor-background-task": "^2.0.0",
         "@capawesome/capacitor-background-task": "^2.0.0",
         "@excalidraw/excalidraw": "0.12.0",
         "@excalidraw/excalidraw": "0.12.0",
         "@hugotomazi/capacitor-navigation-bar": "^2.0.0",
         "@hugotomazi/capacitor-navigation-bar": "^2.0.0",
-        "@logseq/capacitor-file-sync": "0.0.14",
+        "@logseq/capacitor-file-sync": "0.0.15",
         "@logseq/react-tweet-embed": "1.3.1-1",
         "@logseq/react-tweet-embed": "1.3.1-1",
         "@sentry/react": "^6.18.2",
         "@sentry/react": "^6.18.2",
         "@sentry/tracing": "^6.18.2",
         "@sentry/tracing": "^6.18.2",
         "@tabler/icons": "^1.96.0",
         "@tabler/icons": "^1.96.0",
         "@tippyjs/react": "4.2.5",
         "@tippyjs/react": "4.2.5",
-        "aes-js": "3.1.2",
         "bignumber.js": "^9.0.2",
         "bignumber.js": "^9.0.2",
         "capacitor-voice-recorder": "4.0.0",
         "capacitor-voice-recorder": "4.0.0",
         "check-password-strength": "2.0.7",
         "check-password-strength": "2.0.7",
@@ -114,10 +111,8 @@
         "fuse.js": "6.4.6",
         "fuse.js": "6.4.6",
         "grapheme-splitter": "1.0.4",
         "grapheme-splitter": "1.0.4",
         "graphology": "0.20.0",
         "graphology": "0.20.0",
-        "gulp-cached": "1.1.1",
         "highlight.js": "10.4.1",
         "highlight.js": "10.4.1",
         "ignore": "5.1.8",
         "ignore": "5.1.8",
-        "is-svg": "4.3.0",
         "jszip": "3.7.0",
         "jszip": "3.7.0",
         "mldoc": "^1.5.1",
         "mldoc": "^1.5.1",
         "path": "0.12.7",
         "path": "0.12.7",
@@ -128,8 +123,6 @@
         "react": "17.0.2",
         "react": "17.0.2",
         "react-dom": "17.0.2",
         "react-dom": "17.0.2",
         "react-grid-layout": "0.16.6",
         "react-grid-layout": "0.16.6",
-        "react-icon-base": "^2.1.2",
-        "react-icons": "2.2.7",
         "react-intersection-observer": "^9.3.5",
         "react-intersection-observer": "^9.3.5",
         "react-resize-context": "3.0.0",
         "react-resize-context": "3.0.0",
         "react-textarea-autosize": "8.3.3",
         "react-textarea-autosize": "8.3.3",

+ 1 - 1
resources/css/common.css

@@ -669,7 +669,7 @@ img.small {
 }
 }
 
 
 a.tag {
 a.tag {
-  font-size: 13px;
+  font-size: 0.9em;
   text-align: center;
   text-align: center;
   text-decoration: none;
   text-decoration: none;
   display: inline-block;
   display: inline-block;

+ 2 - 3
resources/package.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "Logseq",
   "name": "Logseq",
   "productName": "Logseq",
   "productName": "Logseq",
-  "version": "0.8.12",
+  "version": "0.8.13",
   "main": "electron.js",
   "main": "electron.js",
   "author": "Logseq",
   "author": "Logseq",
   "license": "AGPL-3.0",
   "license": "AGPL-3.0",
@@ -13,7 +13,6 @@
     "electron:make": "electron-forge make",
     "electron:make": "electron-forge make",
     "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
     "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
     "electron:publish:github": "electron-forge publish",
     "electron:publish:github": "electron-forge publish",
-    "rebuild:better-sqlite3": "electron-rebuild -v 19.1.8 -f -w better-sqlite3",
     "rebuild:all": "electron-rebuild -v 19.1.8 -f",
     "rebuild:all": "electron-rebuild -v 19.1.8 -f",
     "postinstall": "install-app-deps"
     "postinstall": "install-app-deps"
   },
   },
@@ -38,7 +37,7 @@
     "https-proxy-agent": "5.0.0",
     "https-proxy-agent": "5.0.0",
     "@sentry/electron": "2.5.1",
     "@sentry/electron": "2.5.1",
     "posthog-js": "1.10.2",
     "posthog-js": "1.10.2",
-    "@logseq/rsapi": "0.0.50",
+    "@logseq/rsapi": "0.0.54",
     "electron-deeplink": "1.0.10",
     "electron-deeplink": "1.0.10",
     "abort-controller": "3.0.0"
     "abort-controller": "3.0.0"
   },
   },

+ 1 - 1
scripts/get-pkg-version.js

@@ -21,7 +21,7 @@ if (match) {
 if (process.argv[2] === 'nightly' || process.argv[2] === '') {
 if (process.argv[2] === 'nightly' || process.argv[2] === '') {
   const today = new Date()
   const today = new Date()
   console.log(
   console.log(
-    ver + '+nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
+    ver + '-nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
   )
   )
 } else {
 } else {
   console.log(ver)
   console.log(ver)

+ 10 - 7
src/main/electron/listener.cljs

@@ -16,13 +16,13 @@
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
+            [frontend.handler.page :as page-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user]
             [frontend.handler.user :as user]
             [frontend.state :as state]
             [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.handler.page :as page-handler]))
+            [frontend.ui :as ui]))
 
 
 
 
 (defn persist-dbs!
 (defn persist-dbs!
@@ -161,8 +161,8 @@
                                                    [:quick-capture-options :insert-today?]
                                                    [:quick-capture-options :insert-today?]
                                                    false)
                                                    false)
                              redirect-page? (get-in (state/get-config)
                              redirect-page? (get-in (state/get-config)
-                                                   [:quick-capture-options :redirect-page?]
-                                                   false)
+                                                    [:quick-capture-options :redirect-page?]
+                                                    false)
                              today-page (when (state/enable-journals?)
                              today-page (when (state/enable-journals?)
                                           (string/lower-case (date/today)))
                                           (string/lower-case (date/today)))
                              page (if (or (= page "TODAY")
                              page (if (or (= page "TODAY")
@@ -197,11 +197,14 @@
                              (editor-handler/insert (str "\n" content)))
                              (editor-handler/insert (str "\n" content)))
 
 
                            (do
                            (do
+                             (editor-handler/escape-editing)
                              (when (not= page (state/get-current-page))
                              (when (not= page (state/get-current-page))
                                (page-handler/create! page {:redirect? redirect-page?}))
                                (page-handler/create! page {:redirect? redirect-page?}))
-                             (editor-handler/api-insert-new-block! content {:page page
-                                                                            :edit-block? true
-                                                                            :replace-empty-target? true}))))))
+                             ;; Or else this will clear the newly inserted content
+                             (js/setTimeout #(editor-handler/api-insert-new-block! content {:page page
+                                                                                            :edit-block? true
+                                                                                            :replace-empty-target? true})
+                                            100))))))
 
 
   (js/window.apis.on "openNewWindowOfGraph"
   (js/window.apis.on "openNewWindowOfGraph"
                      ;; Handle open new window in renderer, until the destination graph doesn't rely on setting local storage
                      ;; Handle open new window in renderer, until the destination graph doesn't rely on setting local storage

+ 9 - 48
src/main/frontend/components/content.cljs

@@ -6,12 +6,9 @@
             [frontend.components.editor :as editor]
             [frontend.components.editor :as editor]
             [frontend.components.page-menu :as page-menu]
             [frontend.components.page-menu :as page-menu]
             [frontend.components.export :as export]
             [frontend.components.export :as export]
-            [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.extensions.srs :as srs]
             [frontend.extensions.srs :as srs]
-            [frontend.format :as format]
-            [frontend.format.protocol :as protocol]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.image :as image-handler]
             [frontend.handler.image :as image-handler]
@@ -31,29 +28,6 @@
 
 
 ;; TODO i18n support
 ;; TODO i18n support
 
 
-(defn- set-format-js-loading!
-  [format value]
-  (when format
-    (swap! state/state assoc-in [:format/loading format] value)))
-
-(defn- lazy-load
-  [format]
-  (let [format (gp-util/normalize-format format)]
-    (when-let [record (format/get-format-record format)]
-      (when-not (protocol/loaded? record)
-        (set-format-js-loading! format true)
-        (protocol/lazyLoad record
-                           (fn [_result]
-                             (set-format-js-loading! format false)))))))
-
-(defn lazy-load-js
-  [state]
-  (when-let [format (:format (last (:rum/args state)))]
-    (let [loader? (contains? config/html-render-formats format)]
-      (when loader?
-        (when-not (format/loaded? format)
-          (lazy-load format))))))
-
 (rum/defc custom-context-menu-content
 (rum/defc custom-context-menu-content
   []
   []
   [:.menu-links-wrapper
   [:.menu-links-wrapper
@@ -420,17 +394,13 @@
 
 
 (rum/defc non-hiccup-content < rum/reactive
 (rum/defc non-hiccup-content < rum/reactive
   [id content on-click on-hide config format]
   [id content on-click on-hide config format]
-  (let [edit? (state/sub [:editor/editing? id])
-        loading (state/sub :format/loading)]
+  (let [edit? (state/sub [:editor/editing? id])]
     (if edit?
     (if edit?
       (editor/box {:on-hide on-hide
       (editor/box {:on-hide on-hide
                    :format format}
                    :format format}
                   id
                   id
                   config)
                   config)
-      (let [format (gp-util/normalize-format format)
-            loading? (get loading format)
-            markup? (contains? config/html-render-formats format)
-            on-click (fn [e]
+      (let [on-click (fn [e]
                        (when-not (util/link? (gobj/get e "target"))
                        (when-not (util/link? (gobj/get e "target"))
                          (util/stop e)
                          (util/stop e)
                          (editor-handler/reset-cursor-range! (gdom/getElement (str id)))
                          (editor-handler/reset-cursor-range! (gdom/getElement (str id)))
@@ -438,17 +408,12 @@
                          (state/set-edit-input-id! id)
                          (state/set-edit-input-id! id)
                          (when on-click
                          (when on-click
                            (on-click e))))]
                            (on-click e))))]
-        (cond
-          (and markup? loading?)
-          [:div "loading ..."]
-
-          :else                       ; other text formats
-          [:pre.cursor.content.pre-white-space
-           {:id id
-            :on-click on-click}
-           (if (string/blank? content)
-             [:div.cursor "Click to edit"]
-             content)])))))
+        [:pre.cursor.content.pre-white-space
+         {:id id
+          :on-click on-click}
+         (if (string/blank? content)
+           [:div.cursor "Click to edit"]
+           content)]))))
 
 
 (defn- set-draw-iframe-style!
 (defn- set-draw-iframe-style!
   []
   []
@@ -463,16 +428,12 @@
           (d/set-style! draw :margin-left (str (- (/ (- width 570) 2)) "px")))))))
           (d/set-style! draw :margin-left (str (- (/ (- width 570) 2)) "px")))))))
 
 
 (rum/defcs content < rum/reactive
 (rum/defcs content < rum/reactive
-  {:will-mount (fn [state]
-                 (lazy-load-js state)
-                 state)
-   :did-mount (fn [state]
+  {:did-mount (fn [state]
                 (set-draw-iframe-style!)
                 (set-draw-iframe-style!)
                 (image-handler/render-local-images!)
                 (image-handler/render-local-images!)
                 state)
                 state)
    :did-update (fn [state]
    :did-update (fn [state]
                  (set-draw-iframe-style!)
                  (set-draw-iframe-style!)
-                 (lazy-load-js state)
                  (image-handler/render-local-images!)
                  (image-handler/render-local-images!)
                  state)}
                  state)}
   [state id {:keys [format
   [state id {:keys [format

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

@@ -20,7 +20,8 @@
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.version :refer [version]]
             [frontend.version :refer [version]]
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [clojure.string :as string]))
 
 
 (rum/defc home-button
 (rum/defc home-button
   < {:key-fn #(identity "home-button")}
   < {:key-fn #(identity "home-button")}
@@ -59,8 +60,11 @@
      (ui/icon "menu-2" {:size ui/icon-size})]))
      (ui/icon "menu-2" {:size ui/icon-size})]))
 
 
 (def bug-report-url
 (def bug-report-url
-  (let [platform (str "App Version: " version "\n"
-                      "Platform: " (.-userAgent js/navigator) "\n"
+  (let [ua (.-userAgent js/navigator)
+        safe-ua (string/replace ua #"[^_/a-zA-Z0-9\.\(\)]+" " ")
+        platform (str "App Version: " version "\n"
+                      "Git Revision: " config/REVISION "\n"
+                      "Platform: " safe-ua "\n"
                       "Language: " (.-language js/navigator))]
                       "Language: " (.-language js/navigator))]
     (str "https://github.com/logseq/logseq/issues/new?"
     (str "https://github.com/logseq/logseq/issues/new?"
          "title=&"
          "title=&"

+ 3 - 1
src/main/frontend/components/hierarchy.cljs

@@ -18,7 +18,9 @@
     (let [repo (state/get-current-repo)
     (let [repo (state/get-current-repo)
           aliases (db/get-page-alias-names repo page)
           aliases (db/get-page-alias-names repo page)
           all-page-names (conj aliases page)]
           all-page-names (conj aliases page)]
-      (when-let [page (first (filter text/namespace-page? all-page-names))]
+      (when-let [page (or (first (filter text/namespace-page? all-page-names))
+                          (when (:block/_namespace (db/entity [:block/name (util/page-name-sanity-lc page)]))
+                            page))]
         (let [namespace-pages (db/get-namespace-pages repo page)
         (let [namespace-pages (db/get-namespace-pages repo page)
               parent-routes (db-model/get-page-namespace-routes repo page)
               parent-routes (db-model/get-page-namespace-routes repo page)
               pages (->> (concat namespace-pages parent-routes)
               pages (->> (concat namespace-pages parent-routes)

+ 1 - 4
src/main/frontend/config.cljs

@@ -104,14 +104,11 @@
 
 
 (def media-formats (set/union (gp-config/img-formats) audio-formats))
 (def media-formats (set/union (gp-config/img-formats) audio-formats))
 
 
-(def html-render-formats
-  #{:adoc :asciidoc})
-
 (defn extname-of-supported?
 (defn extname-of-supported?
   ([input] (extname-of-supported?
   ([input] (extname-of-supported?
             input
             input
             [image-formats doc-formats audio-formats
             [image-formats doc-formats audio-formats
-             video-formats markup-formats html-render-formats
+             video-formats markup-formats
              (gp-config/text-formats)]))
              (gp-config/text-formats)]))
   ([input formats]
   ([input formats]
    (when-let [input (some->
    (when-let [input (some->

+ 2 - 2
src/main/frontend/extensions/pdf/highlights.cljs

@@ -759,7 +759,7 @@
   (let [*doc-ref       (rum/use-ref nil)
   (let [*doc-ref       (rum/use-ref nil)
         [loader-state, set-loader-state!] (rum/use-state {:error nil :pdf-document nil :status nil})
         [loader-state, set-loader-state!] (rum/use-state {:error nil :pdf-document nil :status nil})
         [hls-state, set-hls-state!] (rum/use-state {:initial-hls nil :latest-hls nil :extra nil :loaded false})
         [hls-state, set-hls-state!] (rum/use-state {:initial-hls nil :latest-hls nil :extra nil :loaded false})
-        [initial-page, set-initial-page!] (rum/use-state 0)
+        [initial-page, set-initial-page!] (rum/use-state 1)
         set-dirty-hls! (fn [latest-hls]  ;; TODO: incremental
         set-dirty-hls! (fn [latest-hls]  ;; TODO: incremental
                          (set-hls-state! #(merge % {:initial-hls [] :latest-hls latest-hls})))
                          (set-hls-state! #(merge % {:initial-hls [] :latest-hls latest-hls})))
         set-hls-extra! (fn [extra]
         set-hls-extra! (fn [extra]
@@ -771,7 +771,7 @@
        (p/catch
        (p/catch
         (p/let [data (pdf-assets/load-hls-data$ pdf-current)
         (p/let [data (pdf-assets/load-hls-data$ pdf-current)
                 {:keys [highlights extra]} data]
                 {:keys [highlights extra]} data]
-          (set-initial-page! (util/safe-parse-int (:page extra)))
+          (set-initial-page! (or (util/safe-parse-int (:page extra)) 1))
           (set-hls-state! {:initial-hls highlights :latest-hls highlights :extra extra :loaded true}))
           (set-hls-state! {:initial-hls highlights :latest-hls highlights :extra extra :loaded true}))
 
 
         ;; error
         ;; error

+ 2 - 1
src/main/frontend/extensions/srs.cljs

@@ -370,7 +370,8 @@
                      (util/format "Remembered:   %d (%d%%)" score-remembered-count (* 100 (/ score-remembered-count review-count)))}
                      (util/format "Remembered:   %d (%d%%)" score-remembered-count (* 100 (/ score-remembered-count review-count)))}
                     {:content
                     {:content
                      (util/format "Forgotten :   %d (%d%%)" score-forgotten-count (* 100 (/ score-forgotten-count review-count)))}]}]
                      (util/format "Forgotten :   %d (%d%%)" score-forgotten-count (* 100 (/ score-forgotten-count review-count)))}]}]
-       (:block/format card-query-block)))))
+       (:block/format card-query-block)
+       false))))
 
 
 ;;; ================================================================
 ;;; ================================================================
 ;;; UI
 ;;; UI

+ 1 - 10
src/main/frontend/format.cljs

@@ -1,15 +1,13 @@
 (ns frontend.format
 (ns frontend.format
   "Main ns for providing common operations on file content like conversion to html
   "Main ns for providing common operations on file content like conversion to html
-and edn. Can handle org, markdown and adoc formats"
+and edn. Can handle org-mode and markdown formats"
   (:require [frontend.format.mldoc :refer [->MldocMode] :as mldoc]
   (:require [frontend.format.mldoc :refer [->MldocMode] :as mldoc]
-            [frontend.format.adoc :refer [->AdocMode]]
             [frontend.format.protocol :as protocol]
             [frontend.format.protocol :as protocol]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util :as gp-util]
             [clojure.string :as string]))
             [clojure.string :as string]))
 
 
 (defonce mldoc-record (->MldocMode))
 (defonce mldoc-record (->MldocMode))
-(defonce adoc-record (->AdocMode))
 
 
 (defn get-format-record
 (defn get-format-record
   [format]
   [format]
@@ -18,8 +16,6 @@ and edn. Can handle org, markdown and adoc formats"
     mldoc-record
     mldoc-record
     :markdown
     :markdown
     mldoc-record
     mldoc-record
-    :adoc
-    adoc-record
     nil))
     nil))
 
 
 ;; html
 ;; html
@@ -48,8 +44,3 @@ and edn. Can handle org, markdown and adoc formats"
      (if-let [record (get-format-record format)]
      (if-let [record (get-format-record format)]
        (protocol/toEdn record content config)
        (protocol/toEdn record content config)
        nil))))
        nil))))
-
-(defn loaded?
-  [format]
-  (when-let [record (get-format-record format)]
-    (protocol/loaded? record)))

+ 0 - 30
src/main/frontend/format/adoc.cljs

@@ -1,30 +0,0 @@
-(ns frontend.format.adoc
-  "Partial implementation of format protocol for adoc that uses asciidoctor"
-  (:require [frontend.format.protocol :as protocol]
-            [frontend.loader :as loader]))
-
-(defn loaded? []
-  js/window.Asciidoctor)
-
-(defrecord AdocMode []
-  protocol/Format
-  (toEdn [_this _content _config]
-    nil)
-  (toHtml [_this content _config _references]
-    (when (loaded?)
-      (let [config {:attributes {:showTitle false
-                                 :hardbreaks true
-                                 :icons "font"
-                                 ;; :source-highlighter "pygments"
-                                 }}]
-        (.convert (js/window.Asciidoctor) content (clj->js config)))))
-  (loaded? [_this]
-    (some? (loaded?)))
-  (lazyLoad [_this ok-handler]
-    (loader/load
-     "https://cdnjs.cloudflare.com/ajax/libs/asciidoctor.js/1.5.9/asciidoctor.min.js"
-     ok-handler))
-  (exportMarkdown [_this _content _config _references]
-    (throw "not support"))
-  (exportOPML [_this _content _config _title _references]
-    (throw "not support")))

+ 0 - 4
src/main/frontend/format/mldoc.cljs

@@ -62,10 +62,6 @@
     (->edn content config))
     (->edn content config))
   (toHtml [_this content config references]
   (toHtml [_this content config references]
     (export "html" content config references))
     (export "html" content config references))
-  (loaded? [_this]
-    true)
-  (lazyLoad [_this _ok-handler]
-    true)
   (exportMarkdown [_this content config references]
   (exportMarkdown [_this content config references]
     (parse-export-markdown content config references))
     (parse-export-markdown content config references))
   (exportOPML [_this content config title references]
   (exportOPML [_this content config title references]

+ 0 - 2
src/main/frontend/format/protocol.cljs

@@ -3,7 +3,5 @@
 (defprotocol Format
 (defprotocol Format
   (toEdn [this content config])
   (toEdn [this content config])
   (toHtml [this content config references])
   (toHtml [this content config references])
-  (loaded? [this])
-  (lazyLoad [this ok-handler])
   (exportMarkdown [this content config references])
   (exportMarkdown [this content config references])
   (exportOPML [this content config title references]))
   (exportOPML [this content config title references]))

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

@@ -1875,23 +1875,25 @@
     (cursor/move-cursor-forward input 2)))
     (cursor/move-cursor-forward input 2)))
 
 
 (defn- paste-block-cleanup
 (defn- paste-block-cleanup
-  [block page exclude-properties format content-update-fn]
+  [block page exclude-properties format content-update-fn keep-uuid?]
   (let [new-content
   (let [new-content
         (if content-update-fn
         (if content-update-fn
           (content-update-fn (:block/content block))
           (content-update-fn (:block/content block))
           (:block/content block))
           (:block/content block))
         new-content
         new-content
-        (->> new-content
-             (property/remove-property format "id")
-             (property/remove-property format "custom_id"))]
+        (cond->> new-content
+             (not keep-uuid?) (property/remove-property format "id")
+             true             (property/remove-property format "custom_id"))]
     (merge (dissoc block
     (merge (dissoc block
                    :block/pre-block?
                    :block/pre-block?
                    :block/meta)
                    :block/meta)
            {:block/page {:db/id (:db/id page)}
            {:block/page {:db/id (:db/id page)}
             :block/format format
             :block/format format
             :block/properties (apply dissoc (:block/properties block)
             :block/properties (apply dissoc (:block/properties block)
-                                (concat [:id :custom_id :custom-id]
-                                        exclude-properties))
+                                (concat 
+                                  (when (not keep-uuid?) [:id])
+                                  [:custom_id :custom-id]
+                                  exclude-properties))
             :block/content new-content})))
             :block/content new-content})))
 
 
 (defn- edit-last-block-after-inserted!
 (defn- edit-last-block-after-inserted!
@@ -1962,7 +1964,7 @@
       (when target-block'
       (when target-block'
         (let [format (or (:block/format target-block') (state/get-preferred-format))
         (let [format (or (:block/format target-block') (state/get-preferred-format))
               blocks' (map (fn [block]
               blocks' (map (fn [block]
-                             (paste-block-cleanup block page exclude-properties format content-update-fn))
+                             (paste-block-cleanup block page exclude-properties format content-update-fn keep-uuid?))
                            blocks)
                            blocks)
               result (outliner-core/insert-blocks! blocks' target-block' {:sibling? sibling?
               result (outliner-core/insert-blocks! blocks' target-block' {:sibling? sibling?
                                                                           :outliner-op :paste
                                                                           :outliner-op :paste
@@ -2007,9 +2009,10 @@
 (defn insert-block-tree-after-target
 (defn insert-block-tree-after-target
   "`tree-vec`: a vector of blocks.
   "`tree-vec`: a vector of blocks.
    A block element: {:content :properties :children [block-1, block-2, ...]}"
    A block element: {:content :properties :children [block-1, block-2, ...]}"
-  [target-block-id sibling? tree-vec format]
+  [target-block-id sibling? tree-vec format keep-uuid?]
   (insert-block-tree tree-vec format
   (insert-block-tree tree-vec format
                      {:target-block (db/pull target-block-id)
                      {:target-block (db/pull target-block-id)
+                      :keep-uuid?   keep-uuid?
                       :sibling?     sibling?}))
                       :sibling?     sibling?}))
 
 
 (defn insert-template!
 (defn insert-template!
@@ -2050,7 +2053,7 @@
              page (if (:block/name block) block
              page (if (:block/name block) block
                       (when target (:block/page (db/entity (:db/id target)))))
                       (when target (:block/page (db/entity (:db/id target)))))
              blocks' (map (fn [block]
              blocks' (map (fn [block]
-                            (paste-block-cleanup block page exclude-properties format content-update-fn))
+                            (paste-block-cleanup block page exclude-properties format content-update-fn false))
                           blocks)
                           blocks)
              sibling? (:sibling? opts)
              sibling? (:sibling? opts)
              sibling?' (cond
              sibling?' (cond

+ 12 - 10
src/main/frontend/handler/events.cljs

@@ -366,16 +366,18 @@
         (state/pub-event! [:graph/dir-gone dir]))))
         (state/pub-event! [:graph/dir-gone dir]))))
   ;; FIXME: an ugly implementation for redirecting to page on new window is restored
   ;; FIXME: an ugly implementation for redirecting to page on new window is restored
   (repo-handler/graph-ready! repo)
   (repo-handler/graph-ready! repo)
-  (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)))
+  ;; TODO: 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))
+  )
 
 
 (defmethod handle :notification/show [[_ {:keys [content status clear?]}]]
 (defmethod handle :notification/show [[_ {:keys [content status clear?]}]]
   (notification/show! content status clear?))
   (notification/show! content status clear?))

+ 0 - 1
src/main/frontend/mobile/camera.cljs

@@ -22,7 +22,6 @@
       (p/catch (fn [error]
       (p/catch (fn [error]
                  (log/error :photo/get-failed {:error error})))
                  (log/error :photo/get-failed {:error error})))
       (p/then (fn [photo]
       (p/then (fn [photo]
-                (prn ::debug-photo photo)
                 (if (nil? photo)
                 (if (nil? photo)
                   (p/resolved nil)
                   (p/resolved nil)
                   ;; NOTE: For iOS and Android, only jpeg format will be returned as base64 string.
                   ;; NOTE: For iOS and Android, only jpeg format will be returned as base64 string.

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

@@ -51,7 +51,7 @@
                         (let [target (gdom/getNextElementSibling (gdom/getParentElement (.-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})]
       (ui/icon "calendar" {:size ui/icon-size})]
-     [:div.submenu.fixed.left-0.hidden.w-full.flex-row.justify-evenly.items-center
+     [:div.submenu.fixed.left-0.bottom-0.hidden.w-full.flex-row.justify-evenly.items-center
       {:style {:bottom @util/keyboard-height}}
       {:style {:bottom @util/keyboard-height}}
       (command-cp #(let [today (page-handler/get-page-ref-text (date/today))]
       (command-cp #(let [today (page-handler/get-page-ref-text (date/today))]
                      (commands/simple-insert! parent-id today {}))
                      (commands/simple-insert! parent-id today {}))

+ 29 - 14
src/main/frontend/state.cljs

@@ -17,7 +17,8 @@
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.config :as gp-config]
             [medley.core :as medley]
             [medley.core :as medley]
             [promesa.core :as p]
             [promesa.core :as p]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [logseq.graph-parser.log :as log]))
 
 
 ;; Stores main application state
 ;; Stores main application state
 (defonce ^:large-vars/data-var state
 (defonce ^:large-vars/data-var state
@@ -44,7 +45,6 @@
      :indexeddb/support?      true
      :indexeddb/support?      true
      :me                      nil
      :me                      nil
      :git/current-repo        current-graph
      :git/current-repo        current-graph
-     :format/loading          {}
      :draw?                   false
      :draw?                   false
      :db/restoring?           nil
      :db/restoring?           nil
 
 
@@ -323,11 +323,11 @@
   which are merged."
   which are merged."
   [& configs]
   [& configs]
   (apply merge-with
   (apply merge-with
-    (fn merge-config [current new]
-      (if (and (map? current) (map? new))
-        (merge current new)
-        new))
-    configs))
+         (fn merge-config [current new]
+           (if (and (map? current) (map? new))
+             (merge current new)
+             new))
+         configs))
 
 
 (defn get-config
 (defn get-config
   "User config for the given repo or current repo if none given. All config fetching
   "User config for the given repo or current repo if none given. All config fetching
@@ -335,10 +335,19 @@ should be done through this fn in order to get global config and config defaults
   ([]
   ([]
    (get-config (get-current-repo)))
    (get-config (get-current-repo)))
   ([repo-url]
   ([repo-url]
-   (merge-configs
-    default-config
-    (get-in @state [:config ::global-config])
-    (get-in @state [:config repo-url]))))
+   (try
+     (merge-configs
+      default-config
+      (get-in @state [:config ::global-config])
+      (get-in @state [:config repo-url]))
+     (catch :default e
+       (do
+         (log/error "Cannot parse global config file" e)
+         (log/error "Restore repo config")
+         ;; NOTE: Since repo config is guarded by a try-catch, we can safely failback to it
+         (merge-configs
+          default-config
+          (get-in @state [:config repo-url])))))))
 
 
 (defonce publishing? (atom nil))
 (defonce publishing? (atom nil))
 
 
@@ -551,9 +560,15 @@ Similar to re-frame subscriptions"
   ([] (sub-config (get-current-repo)))
   ([] (sub-config (get-current-repo)))
   ([repo]
   ([repo]
    (let [config (sub :config)]
    (let [config (sub :config)]
-     (merge-configs default-config
-                    (get config ::global-config)
-                    (get config repo)))))
+     (try
+       (merge-configs default-config
+                      (get config ::global-config)
+                      (get config repo))
+       (catch :default e
+         (do
+           (log/error "Cannot parse config files" e)
+           (log/error "Restore default config")
+           default-config))))))
 
 
 (defn enable-grammarly?
 (defn enable-grammarly?
   []
   []

+ 4 - 4
src/main/frontend/util.cljc

@@ -113,10 +113,10 @@
      []
      []
      (when (electron?) (. js/window -__MOCKED_OPEN_DIR_PATH__))))
      (when (electron?) (. js/window -__MOCKED_OPEN_DIR_PATH__))))
 
 
-#?(:cljs
-   (defn ci?
-     []
-     (boolean (. js/window -__E2E_TESTING__))))
+;; #?(:cljs
+;;    (defn ci?
+;;      []
+;;      (boolean (. js/window -__E2E_TESTING__))))
 
 
 #?(:cljs
 #?(:cljs
    (do
    (do

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

@@ -1,3 +1,3 @@
 (ns ^:no-doc frontend.version)
 (ns ^:no-doc frontend.version)
 
 
-(defonce version "0.8.12")
+(defonce version "0.8.13")

+ 9 - 2
src/main/logseq/api.cljs

@@ -621,9 +621,16 @@
     (when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
     (when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
       (when-let [bb (bean/->clj batch-blocks)]
       (when-let [bb (bean/->clj batch-blocks)]
         (let [bb (if-not (vector? bb) (vector bb) bb)
         (let [bb (if-not (vector? bb) (vector bb) bb)
-              {:keys [sibling]} (bean/->clj opts)
+              {:keys [sibling keepUUID]} (bean/->clj opts)
+              keep-uuid? (or keepUUID false)
+              _ (when keep-uuid? (doseq 
+                  [block (outliner/tree-vec-flatten bb :children)]
+                  (let [uuid (:id (:properties block))] 
+                    (when (and uuid (db-model/query-block-by-uuid (uuid-or-throw-error uuid)))
+                      (throw (js/Error.
+                              (util/format "Custom block UUID already exists (%s)." uuid)))))))
               _ (editor-handler/insert-block-tree-after-target
               _ (editor-handler/insert-block-tree-after-target
-                  (:db/id block) sibling bb (:block/format block))]
+                 (:db/id block) sibling bb (:block/format block) keep-uuid?)]
           nil)))))
           nil)))))
 
 
 (def ^:export remove_block
 (def ^:export remove_block

+ 8 - 62
yarn.lock

@@ -487,10 +487,10 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/sourcemap-codec" "^1.4.10"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
 
-"@logseq/[email protected]4":
-  version "0.0.14"
-  resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-0.0.14.tgz#f358f42e95e0578c2853477a66491c8f221a7c15"
-  integrity sha512-M8UBSRMErWEc5OJOOnYZTYQU1/OSZNbLHsaJEgEuFlVra9g5l2jtAoeVB7Ekn2vtkJu9odHWyJnnDr04pFVSKQ==
+"@logseq/[email protected]5":
+  version "0.0.15"
+  resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-0.0.15.tgz#246512a4d2d74b561470abcda76acfe4d653000d"
+  integrity sha512-wD/9W8Jvzvf2+BE0vRgWx6CmLUjXXA/pqgpksW1IrEmfHc20+6fai79qw6L8fQHk/skslFs/EjezAyo4/cN9Ew==
 
 
 "@logseq/[email protected]":
 "@logseq/[email protected]":
   version "1.3.1-1"
   version "1.3.1-1"
@@ -1011,11 +1011,6 @@ acorn@^7.0.0:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
 
[email protected]:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
-  integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
-
 aggregate-error@^3.0.0:
 aggregate-error@^3.0.0:
   version "3.1.0"
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
   resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -2236,9 +2231,9 @@ decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0:
   integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
   integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
 
 
 decode-uri-component@^0.2.0:
 decode-uri-component@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
-  integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
+  integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
 
 
 decompress-response@^3.3.0:
 decompress-response@^3.3.0:
   version "3.3.0"
   version "3.3.0"
@@ -2853,13 +2848,6 @@ fast-sort@^2.2.0:
   resolved "https://registry.yarnpkg.com/fast-sort/-/fast-sort-2.2.0.tgz#20903763531fbcbb41c9df5ab1bf5f2cefc8476a"
   resolved "https://registry.yarnpkg.com/fast-sort/-/fast-sort-2.2.0.tgz#20903763531fbcbb41c9df5ab1bf5f2cefc8476a"
   integrity sha512-W7zqnn2zsYoQA87FKmYtgOsbJohOrh7XrtZrCVHN5XZKqTBTv5UG+rSS3+iWbg/nepRQUOu+wnas8BwtK8kiCg==
   integrity sha512-W7zqnn2zsYoQA87FKmYtgOsbJohOrh7XrtZrCVHN5XZKqTBTv5UG+rSS3+iWbg/nepRQUOu+wnas8BwtK8kiCg==
 
 
-fast-xml-parser@^3.19.0:
-  version "3.21.1"
-  resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz#152a1d51d445380f7046b304672dd55d15c9e736"
-  integrity sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==
-  dependencies:
-    strnum "^1.0.4"
-
 fastdom@^1.0.9:
 fastdom@^1.0.9:
   version "1.0.11"
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/fastdom/-/fastdom-1.0.11.tgz#f22984f9df6b9a6081e5ce2e49cfb5525daf198a"
   resolved "https://registry.yarnpkg.com/fastdom/-/fastdom-1.0.11.tgz#f22984f9df6b9a6081e5ce2e49cfb5525daf198a"
@@ -3393,14 +3381,6 @@ [email protected]:
     events "^3.3.0"
     events "^3.3.0"
     obliterator "^1.6.1"
     obliterator "^1.6.1"
 
 
[email protected]:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/gulp-cached/-/gulp-cached-1.1.1.tgz#fe7cd4f87f37601e6073cfedee5c2bdaf8b6acce"
-  integrity sha512-OEGsICR6Vmx0VK3nhpy5MGPzAjeDYC3+NKxNtJAu4DW8L15oy8tCe2WuD6HDEj9BsbSopnOBiXPK95YHvO0DpA==
-  dependencies:
-    lodash.defaults "^4.2.0"
-    through2 "^2.0.1"
-
 gulp-clean-css@^4.3.0:
 gulp-clean-css@^4.3.0:
   version "4.3.0"
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/gulp-clean-css/-/gulp-clean-css-4.3.0.tgz#5b1e73f2fca46703eb636014cdd4553cea65146d"
   resolved "https://registry.yarnpkg.com/gulp-clean-css/-/gulp-clean-css-4.3.0.tgz#5b1e73f2fca46703eb636014cdd4553cea65146d"
@@ -4021,13 +4001,6 @@ is-string@^1.0.5, is-string@^1.0.7:
   dependencies:
   dependencies:
     has-tostringtag "^1.0.0"
     has-tostringtag "^1.0.0"
 
 
[email protected]:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.0.tgz#3e46a45dcdb2780e42a3c8538154d7f7bfc07216"
-  integrity sha512-Np3TOGLVr0J27VDaS/gVE7bT45ZcSmX4pMmMTsPjqO8JY383fuPIcWmZr3QsHVWhqhZWxSdmW+tkkl3PWOB0Nw==
-  dependencies:
-    fast-xml-parser "^3.19.0"
-
 is-symbol@^1.0.2, is-symbol@^1.0.3:
 is-symbol@^1.0.2, is-symbol@^1.0.3:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
   resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
@@ -4338,11 +4311,6 @@ lodash.castarray@^4.4.0:
   resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
   resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
   integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
   integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
 
 
-lodash.defaults@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
-  integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
-
 lodash.isequal@^4.0.0:
 lodash.isequal@^4.0.0:
   version "4.5.0"
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
   resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@@ -6030,23 +5998,6 @@ [email protected]:
     react-draggable "3.x"
     react-draggable "3.x"
     react-resizable "1.x"
     react-resizable "1.x"
 
 
[email protected]:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d"
-  integrity sha512-9wwKJa2LB8ujtJB5MAXYYEM7JfYThZTj0YnfGxzLLWkifaLIGc7iTde2EpJ7ka5MjneRHnlxbIn5VV9k2WjUVA==
-
-react-icon-base@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.2.tgz#a17101dad9c1192652356096860a9ab43a0766c7"
-  integrity sha512-NRlRo0RPxWRMQT7osj8UCBSSXsGOxhF1pre84ildhuft5S2U382NOs7tg29osWSjbO90L2a3VTCqadA/LnAzHQ==
-
[email protected]:
-  version "2.2.7"
-  resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-2.2.7.tgz#d7860826b258557510dac10680abea5ca23cf650"
-  integrity sha512-0n4lcGqzJFcIQLoQytLdJCE0DKSA9dkwEZRYoGrIDJZFvIT6Hbajx5mv9geqhqFiNjUgtxg8kPyDfjlhymbGFg==
-  dependencies:
-    react-icon-base "2.1.0"
-
 react-intersection-observer@^9.3.5:
 react-intersection-observer@^9.3.5:
   version "9.4.0"
   version "9.4.0"
   resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.0.tgz#f6b6e616e625f9bf255857c5cba9dbf7b1825ec7"
   resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.0.tgz#f6b6e616e625f9bf255857c5cba9dbf7b1825ec7"
@@ -6940,11 +6891,6 @@ strip-indent@^3.0.0:
   dependencies:
   dependencies:
     min-indent "^1.0.0"
     min-indent "^1.0.0"
 
 
-strnum@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
-  integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
-
 style-search@^0.1.0:
 style-search@^0.1.0:
   version "0.1.0"
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
   resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
@@ -7166,7 +7112,7 @@ [email protected]:
   dependencies:
   dependencies:
     readable-stream "2 || 3"
     readable-stream "2 || 3"
 
 
-through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0:
+through2@^2.0.0, through2@^2.0.3, through2@~2.0.0:
   version "2.0.5"
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
   integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
   integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==