浏览代码

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

Tienson Qin 2 年之前
父节点
当前提交
898f10b84d
共有 41 个文件被更改,包括 389 次插入436 次删除
  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
     id: problem
     attributes:
-      label: What happened?
+      label: What Happened?
       description: |
         Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
     validations:
@@ -41,7 +41,7 @@ body:
   - type: textarea
     id: platform
     attributes:
-      label: Desktop or mobile Platform Information
+      label: Desktop or Mobile Platform Information
       description: |
         Would you mind to tell us the system information about your desktop or mobile platform?
       placeholder: |

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

@@ -107,8 +107,8 @@ jobs:
         run: |
           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)
         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
         run: |
           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
       - name: Ensure static yarn.lock is up to date
@@ -194,3 +194,4 @@ jobs:
         with:
           name: e2e-test-report
           path: e2e-dump/*
+          retention-days: 1

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

@@ -146,9 +146,10 @@ jobs:
           DEBUG: "pw:api"
           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
 
-- 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
 - Forum: https://discuss.logseq.com - Where we answer questions, discuss workflows and share tips
 - Discord: https://discord.gg/KpN4eHY

+ 2 - 2
android/app/build.gradle

@@ -6,8 +6,8 @@ android {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 46
-        versionName "0.8.12"
+        versionCode 47
+        versionName "0.8.13"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // 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 fs from 'fs'
+
+const version = fs.readFileSync('static/package.json', 'utf8').match(/"version": "(.*?)"/)?.at(1) ?? '0.0.0'
 
 const config: CapacitorConfig = {
   appId: 'com.logseq.app',
@@ -18,8 +21,12 @@ const config: CapacitorConfig = {
       resize: 'none'
     }
   },
+  android: {
+    appendUserAgent: `Logseq/${version} (Android)`
+  },
   ios: {
-    scheme: 'Logseq'
+    scheme: 'Logseq',
+    appendUserAgent: `Logseq/${version} (iOS)`
   },
   cordova: {
     staticPlugins: [

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

@@ -172,7 +172,6 @@
   [format]
   (case (keyword format)
     :md :markdown
-    :asciidoc :adoc
     ;; default
     (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 }) => {
   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)
+
   if (IsMac) {
     await page.keyboard.press('Meta+c')
   } 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 block.waitForBlocks(2)
+
   if (IsMac) {
     await page.keyboard.press('Meta+v')
   } else {
@@ -184,54 +187,58 @@ test('copy & paste block ref and replace its content', async ({ page, block }) =
   }
   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
-  await expect(blockRef).toHaveCount(1);
+  await expect(page.locator('.block-ref >> text="Some random text"')).toHaveCount(1);
 
   // Move cursor into the block ref
   for (let i = 0; i < 4; i++) {
     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
   if (IsMac) {
     await page.keyboard.press('Meta+Shift+r')
   } 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 }) => {
   await createRandomPage(page)
 
   // 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.waitForTimeout(100)
+  await expect(page.locator('.ls-block.selected')).toHaveCount(1)
+
   if (IsMac) {
-    await page.keyboard.press('Meta+c')
+    await page.keyboard.press('Meta+c', { delay: 10 })
   } 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.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 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) {
     await page.keyboard.press('Meta+v')
   } else {
     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 }) => {
@@ -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 }) => {
-  const delay = 100
+  const delay = 300
   await createRandomPage(page)
 
   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()
   expect(await block.indent()).toBe(true)
   await block.mustType('Child text')
-  await page.waitForTimeout(delay)
 
   // collapse
   await page.click('.block-control >> nth=0')
-  await page.waitForTimeout(delay)
+  await block.waitForBlocks(1)
 
   // select the block that has the soft return
   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.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
-  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
   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.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 }) => {

+ 12 - 4
e2e-tests/fixtures.ts

@@ -118,11 +118,13 @@ base.beforeEach(async () => {
     await page.keyboard.press('Escape')
     await page.keyboard.press('Escape')
 
+    /*
     const locator = page.locator('.notification-close-button').first()
     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')
     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 })
       },
       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> => {
         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[] ) {
+  await createRandomPage(page)
+
   const rand = randomString(10)
   let target_name = page_name + ' target ' + 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_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 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_2, and
   //   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 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
-  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.keyboard.press(hotkeyOpenLink)
-  await page.waitForTimeout(100) // await navigation
+  page.keyboard.press(hotkeyOpenLink)
+  await page.waitForNavigation()
+  await block.escapeEditing()
 
   // shortcut opening test
-  await lastBlock(page)
+  await block.activeEditing(1)
   expect(await page.inputValue('textarea >> nth=0')).toBe(alias_test_content_1)
 
   await enterNextBlock(page)
   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
-  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', '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)
   await newInnerBlock(page)
   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
+  await block.activeEditing(1)
   await block.enterNext()
   await page.waitForSelector('.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.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)
 
     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)
     page.keyboard.press("Enter")
     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)
     await page.click('#search-button')
     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)
-    page.click(":nth-match(.menu-link, 2)")
+    page.click(":nth-match(.search-result, 3)")
     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)
   }
 
   // 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, "ü", ["ü", "ü", "Ü"])
 })

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

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

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

@@ -1,4 +1,4 @@
-/*** 
+/***
  * Author: Junyi Du <[email protected]>
  * References:
  * 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,
       "isComposing": true
     },
-    "latency": 627
+    "latency": 200
   },
   {
     "event_type": "keyup",
@@ -353,7 +353,7 @@ export let macos_pinyin_selecting_candidate_double_left_square_bracket: Recorded
   {
     "event_type": "compositionend",
     "event": {},
-    "latency": 968
+    "latency": 200
   }
 ]
 
@@ -426,7 +426,7 @@ export let win10_RIME_selecting_candidate_double_left_square_bracket: RecordedEv
       "repeat": false,
       "isComposing": true
     },
-    "latency": 237
+    "latency": 200
   },
   {
     "event_type": "keyup",
@@ -461,6 +461,6 @@ export let win10_RIME_selecting_candidate_double_left_square_bracket: RecordedEv
   {
     "event_type": "compositionend",
     "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) {
   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.click(`[data-page-ref="${pageTitle}"]`)
+  page.click(`[data-page-ref="${pageTitle}"]`)
+  await page.waitForNavigation()
   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
   let locator = page.locator('.notification-close-button').first()
   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)
 
     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 { 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;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				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\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -542,7 +542,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				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_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -567,7 +567,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				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_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -594,7 +594,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				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;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 6 - 1
libs/src/LSPlugin.ts

@@ -634,10 +634,15 @@ export interface IEditorProxy extends Record<string, any> {
     }>
   ) => 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: (
     srcBlock: BlockIdentity,
     batch: IBatchBlock | Array<IBatchBlock>,
-    opts?: Partial<{ before: boolean; sibling: boolean }>
+    opts?: Partial<{ before: boolean; sibling: boolean, keepUUID: boolean }>
   ) => Promise<Array<BlockEntity> | null>
 
   updateBlock: (

+ 1 - 8
package.json

@@ -39,7 +39,6 @@
         "app-watch": "run-p gulp:watch cljs:app-watch",
         "release": "run-s gulp:build cljs:release",
         "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-electron-app": "gulp electron",
         "release-electron": "run-s gulp:build && gulp electronMaker",
@@ -61,7 +60,6 @@
         "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-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:test": "clojure -M:test compile test",
         "cljs:run-test": "node static/tests.js",
@@ -91,13 +89,12 @@
         "@capawesome/capacitor-background-task": "^2.0.0",
         "@excalidraw/excalidraw": "0.12.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",
         "@sentry/react": "^6.18.2",
         "@sentry/tracing": "^6.18.2",
         "@tabler/icons": "^1.96.0",
         "@tippyjs/react": "4.2.5",
-        "aes-js": "3.1.2",
         "bignumber.js": "^9.0.2",
         "capacitor-voice-recorder": "4.0.0",
         "check-password-strength": "2.0.7",
@@ -114,10 +111,8 @@
         "fuse.js": "6.4.6",
         "grapheme-splitter": "1.0.4",
         "graphology": "0.20.0",
-        "gulp-cached": "1.1.1",
         "highlight.js": "10.4.1",
         "ignore": "5.1.8",
-        "is-svg": "4.3.0",
         "jszip": "3.7.0",
         "mldoc": "^1.5.1",
         "path": "0.12.7",
@@ -128,8 +123,6 @@
         "react": "17.0.2",
         "react-dom": "17.0.2",
         "react-grid-layout": "0.16.6",
-        "react-icon-base": "^2.1.2",
-        "react-icons": "2.2.7",
         "react-intersection-observer": "^9.3.5",
         "react-resize-context": "3.0.0",
         "react-textarea-autosize": "8.3.3",

+ 1 - 1
resources/css/common.css

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

+ 2 - 3
resources/package.json

@@ -1,7 +1,7 @@
 {
   "name": "Logseq",
   "productName": "Logseq",
-  "version": "0.8.12",
+  "version": "0.8.13",
   "main": "electron.js",
   "author": "Logseq",
   "license": "AGPL-3.0",
@@ -13,7 +13,6 @@
     "electron:make": "electron-forge make",
     "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
     "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",
     "postinstall": "install-app-deps"
   },
@@ -38,7 +37,7 @@
     "https-proxy-agent": "5.0.0",
     "@sentry/electron": "2.5.1",
     "posthog-js": "1.10.2",
-    "@logseq/rsapi": "0.0.50",
+    "@logseq/rsapi": "0.0.54",
     "electron-deeplink": "1.0.10",
     "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] === '') {
   const today = new Date()
   console.log(
-    ver + '+nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
+    ver + '-nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
   )
 } else {
   console.log(ver)

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

@@ -16,13 +16,13 @@
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.notification :as notification]
+            [frontend.handler.page :as page-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user]
             [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.handler.page :as page-handler]))
+            [frontend.ui :as ui]))
 
 
 (defn persist-dbs!
@@ -161,8 +161,8 @@
                                                    [:quick-capture-options :insert-today?]
                                                    false)
                              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?)
                                           (string/lower-case (date/today)))
                              page (if (or (= page "TODAY")
@@ -197,11 +197,14 @@
                              (editor-handler/insert (str "\n" content)))
 
                            (do
+                             (editor-handler/escape-editing)
                              (when (not= page (state/get-current-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"
                      ;; 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.page-menu :as page-menu]
             [frontend.components.export :as export]
-            [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.extensions.srs :as srs]
-            [frontend.format :as format]
-            [frontend.format.protocol :as protocol]
             [frontend.handler.common :as common-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.image :as image-handler]
@@ -31,29 +28,6 @@
 
 ;; 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
   []
   [:.menu-links-wrapper
@@ -420,17 +394,13 @@
 
 (rum/defc non-hiccup-content < rum/reactive
   [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?
       (editor/box {:on-hide on-hide
                    :format format}
                   id
                   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"))
                          (util/stop e)
                          (editor-handler/reset-cursor-range! (gdom/getElement (str id)))
@@ -438,17 +408,12 @@
                          (state/set-edit-input-id! id)
                          (when on-click
                            (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!
   []
@@ -463,16 +428,12 @@
           (d/set-style! draw :margin-left (str (- (/ (- width 570) 2)) "px")))))))
 
 (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!)
                 (image-handler/render-local-images!)
                 state)
    :did-update (fn [state]
                  (set-draw-iframe-style!)
-                 (lazy-load-js state)
                  (image-handler/render-local-images!)
                  state)}
   [state id {:keys [format

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

@@ -20,7 +20,8 @@
             [frontend.util :as util]
             [frontend.version :refer [version]]
             [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [clojure.string :as string]))
 
 (rum/defc home-button
   < {:key-fn #(identity "home-button")}
@@ -59,8 +60,11 @@
      (ui/icon "menu-2" {:size ui/icon-size})]))
 
 (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))]
     (str "https://github.com/logseq/logseq/issues/new?"
          "title=&"

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

@@ -18,7 +18,9 @@
     (let [repo (state/get-current-repo)
           aliases (db/get-page-alias-names repo 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)
               parent-routes (db-model/get-page-namespace-routes repo page)
               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 html-render-formats
-  #{:adoc :asciidoc})
-
 (defn extname-of-supported?
   ([input] (extname-of-supported?
             input
             [image-formats doc-formats audio-formats
-             video-formats markup-formats html-render-formats
+             video-formats markup-formats
              (gp-config/text-formats)]))
   ([input formats]
    (when-let [input (some->

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

@@ -759,7 +759,7 @@
   (let [*doc-ref       (rum/use-ref 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})
-        [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-hls-state! #(merge % {:initial-hls [] :latest-hls latest-hls})))
         set-hls-extra! (fn [extra]
@@ -771,7 +771,7 @@
        (p/catch
         (p/let [data (pdf-assets/load-hls-data$ pdf-current)
                 {: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}))
 
         ;; 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)))}
                     {:content
                      (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

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

@@ -1,15 +1,13 @@
 (ns frontend.format
   "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]
-            [frontend.format.adoc :refer [->AdocMode]]
             [frontend.format.protocol :as protocol]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
             [clojure.string :as string]))
 
 (defonce mldoc-record (->MldocMode))
-(defonce adoc-record (->AdocMode))
 
 (defn get-format-record
   [format]
@@ -18,8 +16,6 @@ and edn. Can handle org, markdown and adoc formats"
     mldoc-record
     :markdown
     mldoc-record
-    :adoc
-    adoc-record
     nil))
 
 ;; html
@@ -48,8 +44,3 @@ and edn. Can handle org, markdown and adoc formats"
      (if-let [record (get-format-record format)]
        (protocol/toEdn record content config)
        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))
   (toHtml [_this content config references]
     (export "html" content config references))
-  (loaded? [_this]
-    true)
-  (lazyLoad [_this _ok-handler]
-    true)
   (exportMarkdown [_this content config references]
     (parse-export-markdown content config references))
   (exportOPML [_this content config title references]

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

@@ -3,7 +3,5 @@
 (defprotocol Format
   (toEdn [this content config])
   (toHtml [this content config references])
-  (loaded? [this])
-  (lazyLoad [this ok-handler])
   (exportMarkdown [this content config 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)))
 
 (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
         (if content-update-fn
           (content-update-fn (:block/content block))
           (:block/content block))
         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
                    :block/pre-block?
                    :block/meta)
            {:block/page {:db/id (:db/id page)}
             :block/format format
             :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})))
 
 (defn- edit-last-block-after-inserted!
@@ -1962,7 +1964,7 @@
       (when target-block'
         (let [format (or (:block/format target-block') (state/get-preferred-format))
               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)
               result (outliner-core/insert-blocks! blocks' target-block' {:sibling? sibling?
                                                                           :outliner-op :paste
@@ -2007,9 +2009,10 @@
 (defn insert-block-tree-after-target
   "`tree-vec`: a vector of blocks.
    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
                      {:target-block (db/pull target-block-id)
+                      :keep-uuid?   keep-uuid?
                       :sibling?     sibling?}))
 
 (defn insert-template!
@@ -2050,7 +2053,7 @@
              page (if (:block/name block) block
                       (when target (:block/page (db/entity (:db/id target)))))
              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)
              sibling? (:sibling? opts)
              sibling?' (cond

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

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

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

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

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

@@ -51,7 +51,7 @@
                         (let [target (gdom/getNextElementSibling (gdom/getParentElement (.-target event)))]
                           (dom/add-class! target "show-submenu")))}
       (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}}
       (command-cp #(let [today (page-handler/get-page-ref-text (date/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]
             [medley.core :as medley]
             [promesa.core :as p]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [logseq.graph-parser.log :as log]))
 
 ;; Stores main application state
 (defonce ^:large-vars/data-var state
@@ -44,7 +45,6 @@
      :indexeddb/support?      true
      :me                      nil
      :git/current-repo        current-graph
-     :format/loading          {}
      :draw?                   false
      :db/restoring?           nil
 
@@ -323,11 +323,11 @@
   which are merged."
   [& configs]
   (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
   "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)))
   ([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))
 
@@ -551,9 +560,15 @@ Similar to re-frame subscriptions"
   ([] (sub-config (get-current-repo)))
   ([repo]
    (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?
   []

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

@@ -113,10 +113,10 @@
      []
      (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
    (do

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

@@ -1,3 +1,3 @@
 (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 [bb (bean/->clj batch-blocks)]
         (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
-                  (:db/id block) sibling bb (:block/format block))]
+                 (:db/id block) sibling bb (:block/format block) keep-uuid?)]
           nil)))))
 
 (def ^:export remove_block

+ 8 - 62
yarn.lock

@@ -487,10 +487,10 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@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]":
   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"
   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:
   version "3.1.0"
   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==
 
 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:
   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"
   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:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/fastdom/-/fastdom-1.0.11.tgz#f22984f9df6b9a6081e5ce2e49cfb5525daf198a"
@@ -3393,14 +3381,6 @@ [email protected]:
     events "^3.3.0"
     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:
   version "4.3.0"
   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:
     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:
   version "1.0.4"
   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"
   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:
   version "4.5.0"
   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-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:
   version "9.4.0"
   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:
     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:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
@@ -7166,7 +7112,7 @@ [email protected]:
   dependencies:
     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"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
   integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==