Quellcode durchsuchen

Merge remote-tracking branch 'upstream/master' into whiteboards

Peng Xiao vor 3 Jahren
Ursprung
Commit
c7fe4933b4
38 geänderte Dateien mit 496 neuen und 659 gelöschten Zeilen
  1. 4 0
      ios/App/App/Info.plist
  2. 1 1
      libs/package.json
  3. 15 4
      libs/src/LSPlugin.ts
  4. 12 2
      libs/src/LSPlugin.user.ts
  5. 26 8
      libs/src/modules/LSPlugin.Storage.ts
  6. 1 1
      package.json
  7. 58 383
      resources/css/common.css
  8. 0 0
      resources/js/lsplugin.core.js
  9. 1 1
      resources/package.json
  10. 25 34
      src/electron/electron/file_sync_rsapi.cljs
  11. 10 3
      src/electron/electron/handler.cljs
  12. 4 0
      src/electron/electron/plugin.cljs
  13. 1 1
      src/electron/electron/utils.cljs
  14. 65 40
      src/electron/electron/utils.js
  15. 11 25
      src/main/frontend/components/block.cljs
  16. 0 14
      src/main/frontend/components/block.css
  17. 8 14
      src/main/frontend/components/content.cljs
  18. 4 4
      src/main/frontend/components/encryption.cljs
  19. 2 2
      src/main/frontend/components/file_sync.cljs
  20. 6 0
      src/main/frontend/components/file_sync.css
  21. 2 2
      src/main/frontend/components/page.cljs
  22. 1 6
      src/main/frontend/components/page.css
  23. 2 2
      src/main/frontend/components/page_menu.cljs
  24. 3 3
      src/main/frontend/components/plugins.cljs
  25. 3 3
      src/main/frontend/components/reference.cljs
  26. 7 10
      src/main/frontend/components/svg.cljs
  27. 6 0
      src/main/frontend/config.cljs
  28. 7 0
      src/main/frontend/dicts.cljc
  29. 11 6
      src/main/frontend/extensions/excalidraw.cljs
  30. 10 10
      src/main/frontend/extensions/pdf/pdf.css
  31. 3 3
      src/main/frontend/extensions/zotero.cljs
  32. 9 6
      src/main/frontend/handler/editor.cljs
  33. 1 1
      src/main/frontend/mobile/record.cljs
  34. 15 7
      src/main/frontend/ui.cljs
  35. 101 51
      src/main/logseq/api.cljs
  36. 1 1
      tailwind.all.css
  37. 49 7
      tailwind.config.js
  38. 11 4
      yarn.lock

+ 4 - 0
ios/App/App/Info.plist

@@ -78,6 +78,10 @@
 			<string>ANY</string>
 		</dict>
 	</dict>
+	<key>UIBackgroundModes</key>
+	<array>
+		<string>audio</string>
+	</array>
 	<key>UILaunchStoryboardName</key>
 	<string>LaunchScreen</string>
 	<key>UIMainStoryboardFile</key>

+ 1 - 1
libs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@logseq/libs",
-  "version": "0.0.8",
+  "version": "0.0.10",
   "description": "Logseq SDK libraries",
   "main": "dist/lsplugin.user.js",
   "typings": "index.d.ts",

+ 15 - 4
libs/src/LSPlugin.ts

@@ -3,7 +3,7 @@ import * as CSS from 'csstype'
 import EventEmitter from 'eventemitter3'
 import { LSPluginCaller } from './LSPlugin.caller'
 import { LSPluginExperiments } from './modules/LSPlugin.Experiments'
-import { LSPluginFileStorage } from './modules/LSPlugin.Storage'
+import { IAsyncStorage, LSPluginFileStorage } from './modules/LSPlugin.Storage'
 import { LSPluginRequest } from './modules/LSPlugin.Request'
 
 export type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
@@ -169,6 +169,7 @@ export interface BlockEntity {
   unordered: boolean
   content: string
   page: IEntityID
+  properties?: Record<string, any>
 
   // optional fields in dummy page
   anchor?: string
@@ -196,9 +197,12 @@ export interface PageEntity {
   file?: IEntityID
   namespace?: IEntityID
   children?: Array<PageEntity>
+  properties?: Record<string, any>
   format?: 'markdown' | 'org'
   journalDay?: number
   updatedAt?: number
+
+  [key: string]: any
 }
 
 export type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
@@ -412,9 +416,9 @@ export interface IAppProxy {
 
   // hook events
   onCurrentGraphChanged: IUserHook
-  onGraphAfterIndexed: IUserHook<{repo: string}>
+  onGraphAfterIndexed: IUserHook<{ repo: string }>
   onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }>
-  onThemeChanged: IUserHook<Partial<{name: string, mode: string, pid: string, url: string}>>
+  onThemeChanged: IUserHook<Partial<{ name: string, mode: string, pid: string, url: string }>>
   onBlockRendererSlotted: IUserSlotHook<{ uuid: BlockUUID }>
 
   /**
@@ -786,7 +790,7 @@ export interface IAssetsProxy {
    * @added 0.0.2
    * @param exts
    */
-  listFilesOfCurrentGraph(exts: string | string[]): Promise<{
+  listFilesOfCurrentGraph(exts?: string | string[]): Promise<{
     path: string
     size: number
     accessTime: number
@@ -794,6 +798,12 @@ export interface IAssetsProxy {
     changeTime: number
     birthTime: number
   }>
+
+  /**
+   * @example https://github.com/logseq/logseq/pull/6488
+   * @added 0.0.10
+   */
+  makeSandboxStorage(): IAsyncStorage
 }
 
 export interface ILSPluginThemeManager {
@@ -963,6 +973,7 @@ export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
   DB: IDBProxy
   Git: IGitProxy
   UI: IUIProxy
+  Assets: IAssetsProxy
 
   Request: LSPluginRequest
   FileStorage: LSPluginFileStorage

+ 12 - 2
libs/src/LSPlugin.user.ts

@@ -38,7 +38,7 @@ import {
 import Debug from 'debug'
 import * as CSS from 'csstype'
 import EventEmitter from 'eventemitter3'
-import { LSPluginFileStorage } from './modules/LSPlugin.Storage'
+import { IAsyncStorage, LSPluginFileStorage } from './modules/LSPlugin.Storage'
 import { LSPluginExperiments } from './modules/LSPlugin.Experiments'
 import { LSPluginRequest } from './modules/LSPlugin.Request'
 
@@ -311,8 +311,18 @@ const db: Partial<IDBProxy> = {
 }
 
 const git: Partial<IGitProxy> = {}
+
 const ui: Partial<IUIProxy> = {}
-const assets: Partial<IAssetsProxy> = {}
+
+const assets: Partial<IAssetsProxy> = {
+  makeSandboxStorage(
+    this: LSPluginUser
+  ): IAsyncStorage {
+    return new LSPluginFileStorage(
+      this, { assets: true }
+    )
+  }
+}
 
 type uiState = {
   key?: number

+ 26 - 8
libs/src/modules/LSPlugin.Storage.ts

@@ -9,6 +9,8 @@ export interface IAsyncStorage {
 
   hasItem(key: string): Promise<boolean>
 
+  allKeys(): Promise<Array<string>>
+
   clear(): Promise<void>
 }
 
@@ -18,8 +20,14 @@ export interface IAsyncStorage {
 class LSPluginFileStorage implements IAsyncStorage {
   /**
    * @param ctx
+   * @param opts
    */
-  constructor(private ctx: LSPluginUser) {}
+  constructor(
+    private ctx: LSPluginUser,
+    private opts?: {
+      assets: boolean
+    }
+  ) {}
 
   /**
    * plugin id
@@ -32,20 +40,20 @@ class LSPluginFileStorage implements IAsyncStorage {
    * @param key A string as file name that support nested directory
    * @param value Storage value
    */
-  setItem(key: string, value: string): Promise<void> {
+  setItem(key: string, value: string | any): Promise<void> {
     return this.ctx.caller.callAsync(`api:call`, {
       method: 'write-plugin-storage-file',
-      args: [this.ctxId, key, value],
+      args: [this.ctxId, key, value, this.opts?.assets],
     })
   }
 
   /**
    * @param key
    */
-  getItem(key: string): Promise<string | undefined> {
+  getItem(key: string): Promise<string | any> {
     return this.ctx.caller.callAsync(`api:call`, {
       method: 'read-plugin-storage-file',
-      args: [this.ctxId, key],
+      args: [this.ctxId, key, this.opts?.assets],
     })
   }
 
@@ -55,7 +63,17 @@ class LSPluginFileStorage implements IAsyncStorage {
   removeItem(key: string): Promise<void> {
     return this.ctx.caller.call(`api:call`, {
       method: 'unlink-plugin-storage-file',
-      args: [this.ctxId, key],
+      args: [this.ctxId, key, this.opts?.assets],
+    })
+  }
+
+  /**
+   * Get all path file keys
+   */
+  allKeys(): Promise<Array<string>> {
+    return this.ctx.caller.callAsync(`api:call`, {
+      method: 'list-plugin-storage-files',
+      args: [this.ctxId, this.opts?.assets]
     })
   }
 
@@ -65,7 +83,7 @@ class LSPluginFileStorage implements IAsyncStorage {
   clear(): Promise<void> {
     return this.ctx.caller.call(`api:call`, {
       method: 'clear-plugin-storage-files',
-      args: [this.ctxId],
+      args: [this.ctxId, this.opts?.assets],
     })
   }
 
@@ -75,7 +93,7 @@ class LSPluginFileStorage implements IAsyncStorage {
   hasItem(key: string): Promise<boolean> {
     return this.ctx.caller.callAsync(`api:call`, {
       method: 'exist-plugin-storage-file',
-      args: [this.ctxId, key],
+      args: [this.ctxId, key, this.opts?.assets],
     })
   }
 }

+ 1 - 1
package.json

@@ -83,7 +83,7 @@
         "@capacitor/share": "^4.0.0",
         "@capacitor/splash-screen": "^4.0.0",
         "@capacitor/status-bar": "^4.0.0",
-        "@excalidraw/excalidraw": "0.10.0",
+        "@excalidraw/excalidraw": "0.12.0",
         "@kanru/rage-wasm": "^0.3.0",
         "@logseq/capacitor-file-sync": "0.0.11",
         "@logseq/react-tweet-embed": "1.3.1-1",

+ 58 - 383
resources/css/common.css

@@ -14,9 +14,9 @@
   --ls-left-sidebar-width: 246px;
   --ls-left-sidebar-sm-width: 70%;
   --ls-left-sidebar-nav-btn-size: 38px;
-  --ls-color-file-sync-error: #ff0000;
-  --ls-color-file-sync-pending: #ffbb4d;
-  --ls-color-file-sync-idle: #04b404;
+  --ls-error-color: var(--color-red-500);
+  --ls-warning-color: var(--color-orange-500);
+  --ls-success-color: var(--color-green-500);
 }
 
 @media (prefers-color-scheme: dark) {
@@ -91,6 +91,17 @@ html[data-theme='dark'] {
   --ls-right-sidebar-code-bg-color: #04303c;
   --ls-pie-bg-color: #01303b;
   --ls-pie-fg-color: #0b5869;
+  --ls-highlight-color-gray: var(--color-gray-900);
+  --ls-highlight-color-red: var(--color-red-900);
+  --ls-highlight-color-yellow: var(--color-yellow-900);
+  --ls-highlight-color-green: var(--color-green-900);
+  --ls-highlight-color-blue: var(--color-blue-900);
+  --ls-highlight-color-purple: var(--color-purple-900);
+  --ls-highlight-color-pink: var(--color-pink-900);
+  --ls-error-text-color: var(--color-red-100);
+  --ls-error-background-color: var(--color-red-900);
+  --ls-warning-text-color: var(--color-yellow-100);
+  --ls-warning-background-color: var(--color-yellow-900);
   --ls-focus-ring-color: rgba(18, 98, 119, 0.5);
   --color-level-1: var(--ls-secondary-background-color);
   --color-level-2: var(--ls-tertiary-background-color);
@@ -154,6 +165,17 @@ html[data-theme='light'] {
   --ls-right-sidebar-code-bg-color: var(--ls-secondary-background-color);
   --ls-pie-bg-color: #e1e1e1;
   --ls-pie-fg-color: #0a4a5d;
+  --ls-highlight-color-gray: var(--color-gray-100);
+  --ls-highlight-color-red: var(--color-red-100);
+  --ls-highlight-color-yellow: var(--color-yellow-100);
+  --ls-highlight-color-green: var(--color-green-100);
+  --ls-highlight-color-blue: var(--color-blue-100);
+  --ls-highlight-color-purple: var(--color-purple-100);
+  --ls-highlight-color-pink: var(--color-pink-100);
+  --ls-error-text-color: var(--color-red-800);
+  --ls-error-background-color: var(--color-red-100);
+  --ls-warning-text-color: var(--color-yellow-800);
+  --ls-warning-background-color: var(--color-yellow-100);
   --ls-focus-ring-color: rgba(66, 133, 244, 0.5);
   --color-level-1: var(--ls-secondary-background-color);
   --color-level-2: var(--ls-tertiary-background-color);
@@ -311,373 +333,6 @@ video {
 
 /* endregion */
 
-/** region Common utilities **/
-.w10 {
-  max-width: 10%;
-}
-
-.w20 {
-  max-width: 20%;
-}
-
-.w30 {
-  max-width: 30%;
-}
-
-.w40 {
-  max-width: 40%;
-}
-
-.w50 {
-  max-width: 50%;
-}
-
-.w60 {
-  max-width: 60%;
-}
-
-.w70 {
-  max-width: 70%;
-}
-
-.w80 {
-  max-width: 80%;
-}
-
-.w90 {
-  max-width: 90%;
-}
-
-.w100 {
-  max-width: 100%;
-}
-
-.bg-black {
-  background-color: rgba(0, 0, 0);
-}
-
-.bg-white {
-  background-color: rgba(255, 255, 255);
-}
-
-.bg-gray-50 {
-  background-color: rgba(249, 250, 251);
-}
-
-.bg-gray-100 {
-  background-color: rgba(243, 244, 246);
-}
-
-.bg-gray-200 {
-  background-color: rgba(229, 231, 235);
-}
-
-.bg-gray-300 {
-  background-color: rgba(209, 213, 219);
-}
-
-.bg-gray-400 {
-  background-color: rgba(156, 163, 175);
-}
-
-.bg-gray-500 {
-  background-color: rgba(107, 114, 128);
-}
-
-.bg-gray-600 {
-  background-color: rgba(75, 85, 99);
-}
-
-.bg-gray-700 {
-  background-color: rgba(55, 65, 81);
-}
-
-.bg-gray-800 {
-  background-color: rgba(31, 41, 55);
-}
-
-.bg-gray-900 {
-  background-color: rgba(17, 24, 39);
-}
-
-.bg-red-50 {
-  background-color: rgba(254, 242, 242);
-}
-
-.bg-red-100 {
-  background-color: rgba(254, 226, 226);
-}
-
-.bg-red-200 {
-  background-color: rgba(254, 202, 202);
-}
-
-.bg-red-300 {
-  background-color: rgba(252, 165, 165);
-}
-
-.bg-red-400 {
-  background-color: rgba(248, 113, 113);
-}
-
-.bg-red-500 {
-  background-color: rgba(239, 68, 68);
-}
-
-.bg-red-600 {
-  background-color: rgba(220, 38, 38);
-}
-
-.bg-red-700 {
-  background-color: rgba(185, 28, 28);
-}
-
-.bg-red-800 {
-  background-color: rgba(153, 27, 27);
-}
-
-.bg-red-900 {
-  background-color: rgba(127, 29, 29);
-}
-
-.bg-yellow-50 {
-  background-color: rgba(255, 251, 235);
-}
-
-.bg-yellow-100 {
-  background-color: rgba(254, 243, 199);
-}
-
-.bg-yellow-200 {
-  background-color: rgba(253, 230, 138);
-}
-
-.bg-yellow-300 {
-  background-color: rgba(252, 211, 77);
-}
-
-.bg-yellow-400 {
-  background-color: rgba(251, 191, 36);
-}
-
-.bg-yellow-500 {
-  background-color: rgba(245, 158, 11);
-}
-
-.bg-yellow-600 {
-  background-color: rgba(217, 119, 6);
-}
-
-.bg-yellow-700 {
-  background-color: rgba(180, 83, 9);
-}
-
-.bg-yellow-800 {
-  background-color: rgba(146, 64, 14);
-}
-
-.bg-yellow-900 {
-  background-color: rgba(120, 53, 15);
-}
-
-.bg-green-50 {
-  background-color: rgba(236, 253, 245);
-}
-
-.bg-green-100 {
-  background-color: rgba(209, 250, 229);
-}
-
-.bg-green-200 {
-  background-color: rgba(167, 243, 208);
-}
-
-.bg-green-300 {
-  background-color: rgba(110, 231, 183);
-}
-
-.bg-green-400 {
-  background-color: rgba(52, 211, 153);
-}
-
-.bg-green-500 {
-  background-color: rgba(16, 185, 129);
-}
-
-.bg-green-600 {
-  background-color: rgba(5, 150, 105);
-}
-
-.bg-green-700 {
-  background-color: rgba(4, 120, 87);
-}
-
-.bg-green-800 {
-  background-color: rgba(6, 95, 70);
-}
-
-.bg-green-900 {
-  background-color: rgba(6, 78, 59);
-}
-
-.bg-blue-50 {
-  background-color: rgba(239, 246, 255);
-}
-
-.bg-blue-100 {
-  background-color: rgba(219, 234, 254);
-}
-
-.bg-blue-200 {
-  background-color: rgba(191, 219, 254);
-}
-
-.bg-blue-300 {
-  background-color: rgba(147, 197, 253);
-}
-
-.bg-blue-400 {
-  background-color: rgba(96, 165, 250);
-}
-
-.bg-blue-500 {
-  background-color: rgba(59, 130, 246);
-}
-
-.bg-blue-600 {
-  background-color: rgba(37, 99, 235);
-}
-
-.bg-blue-700 {
-  background-color: rgba(29, 78, 216);
-}
-
-.bg-blue-800 {
-  background-color: rgba(30, 64, 175);
-}
-
-.bg-blue-900 {
-  background-color: rgba(30, 58, 138);
-}
-
-.bg-indigo-50 {
-  background-color: rgba(238, 242, 255);
-}
-
-.bg-indigo-100 {
-  background-color: rgba(224, 231, 255);
-}
-
-.bg-indigo-200 {
-  background-color: rgba(199, 210, 254);
-}
-
-.bg-indigo-300 {
-  background-color: rgba(165, 180, 252);
-}
-
-.bg-indigo-400 {
-  background-color: rgba(129, 140, 248);
-}
-
-.bg-indigo-500 {
-  background-color: rgba(99, 102, 241);
-}
-
-.bg-indigo-600 {
-  background-color: rgba(79, 70, 229);
-}
-
-.bg-indigo-700 {
-  background-color: rgba(67, 56, 202);
-}
-
-.bg-indigo-800 {
-  background-color: rgba(55, 48, 163);
-}
-
-.bg-indigo-900 {
-  background-color: rgba(49, 46, 129);
-}
-
-.bg-purple-50 {
-  background-color: rgba(245, 243, 255);
-}
-
-.bg-purple-100 {
-  background-color: rgba(237, 233, 254);
-}
-
-.bg-purple-200 {
-  background-color: rgba(221, 214, 254);
-}
-
-.bg-purple-300 {
-  background-color: rgba(196, 181, 253);
-}
-
-.bg-purple-400 {
-  background-color: rgba(167, 139, 250);
-}
-
-.bg-purple-500 {
-  background-color: rgba(139, 92, 246);
-}
-
-.bg-purple-600 {
-  background-color: rgba(124, 58, 237);
-}
-
-.bg-purple-700 {
-  background-color: rgba(109, 40, 217);
-}
-
-.bg-purple-800 {
-  background-color: rgba(91, 33, 182);
-}
-
-.bg-purple-900  {
-  background-color: rgba(76, 29, 149);
-}
-
-.bg-pink-100 {
-  background-color: #fff5f7;
-}
-
-.bg-pink-200 {
-  background-color: #fed7e2;
-}
-
-.bg-pink-300 {
-  background-color: #fbb6ce;
-}
-
-.bg-pink-400 {
-  background-color: #f687b3;
-}
-
-.bg-pink-500 {
-  background-color: #ed64a6;
-}
-
-.bg-pink-600 {
-  background-color: #d53f8c;
-}
-
-.bg-pink-700 {
-  background-color: #b83280;
-}
-
-.bg-pink-800 {
-  background-color: #97266d;
-}
-
-.bg-pink-900 {
-  background-color: #702459;
-}
-
-/** endregion **/
-
 /** region App utilities **/
 .ls-center {
   position: absolute;
@@ -782,11 +437,6 @@ li p:last-child,
   opacity: 0.7;
 }
 
-.svg-shadow {
-  -webkit-filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.7));
-  filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.5));
-}
-
 .tip-shadow {
   -webkit-filter: drop-shadow(1px 1px 2px rgba(155, 155, 0, 0.8));
   filter: drop-shadow(1px 1px 2px rgba(155, 155, 0, 0.8));
@@ -847,8 +497,8 @@ i.ti {
 
 .heading-bg {
   border-radius: 50%;
-  width: 14px;
-  height: 14px;
+  width: 18px;
+  height: 18px;
 
   &.remove {
     @apply border flex items-center justify-center;
@@ -857,6 +507,10 @@ i.ti {
   }
 }
 
+.to-heading-button {
+  @apply px-1 !important;
+}
+
 /** endregion **/
 
 /* region FIXME: override elements (?) */
@@ -964,24 +618,45 @@ a.chosen {
 
 a.warning,
 span.warning, div.warning:not(.admonitionblock), p.warning {
-  background: #f56565;
+  background: var(--ls-warning-background-color);
   padding: 0.1em 0.4em;
   border-radius: var(--ls-border-radius-low);
-  color: #fff;
+  color: var(--ls-warning-text-color);
+}
+
+.text-warning {
+  color: var(--ls-warning-text-color);
 }
 
-.warning-text {
-  color: #f56565;
+.bg-warning {
+  background: var(--ls-waring-background-color);
 }
 
 a.error,
 span.error {
-    background: red;
+    background: var(--ls-error-background-color);
     padding: 0.1em 0.4em;
     border-radius: var(--ls-border-radius-low);
-    color: #fff;
+    color: var(--ls-error-text-color);
+}
+
+.text-error {
+  color: var(--ls-error-text-color);
+}
+
+.bg-error {
+  background: var(--ls-error-background-color);
+}
+
+.text-success {
+  color: var(--ls-success-text-color);
 }
 
+.bg-success {
+  background: var(--ls-success-background-color);
+}
+
+
 img.small {
   display: inline;
   width: 20px;

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
resources/js/lsplugin.core.js


+ 1 - 1
resources/package.json

@@ -37,7 +37,7 @@
     "https-proxy-agent": "5.0.0",
     "@sentry/electron": "2.5.1",
     "posthog-js": "1.10.2",
-    "@logseq/rsapi": "0.0.48",
+    "@logseq/rsapi": "0.0.50",
     "electron-deeplink": "1.0.10",
     "abort-controller": "3.0.0"
   },

+ 25 - 34
src/electron/electron/file_sync_rsapi.cljs

@@ -1,69 +1,60 @@
 (ns electron.file-sync-rsapi
-  (:require ["os" :as os]
+  (:require ["@logseq/rsapi" :as rsapi]
             [electron.window :as window]
-            [cljs-bean.core :as bean]
-            [clojure.string :as string]))
+            [cljs-bean.core :as bean]))
 
-(if (and (= (.platform os) "win32")
-         (string/starts-with? (.release os) "6."))
-  (defonce rsapi nil)
-  (defonce rsapi (js/require "@logseq/rsapi")))
-
-(defn key-gen []
-  (.keygen rsapi))
+(defn key-gen [] (rsapi/keygen))
 
 (defn set-env [graph-uuid env private-key public-key]
-  (.setEnv rsapi graph-uuid env private-key public-key))
+  (rsapi/setEnv graph-uuid env private-key public-key))
 
 (defn set-progress-callback [callback]
-  (.setProgressCallback rsapi callback))
+  (rsapi/setProgressCallback callback))
 
 (defn get-local-files-meta [graph-uuid base-path file-paths]
-  (.getLocalFilesMeta rsapi graph-uuid base-path (clj->js file-paths)))
+  (rsapi/getLocalFilesMeta graph-uuid base-path (clj->js file-paths)))
 
 (defn get-local-all-files-meta [graph-uuid base-path]
-  (.getLocalAllFilesMeta rsapi graph-uuid base-path))
+  (rsapi/getLocalAllFilesMeta graph-uuid base-path))
 
 (defn rename-local-file [graph-uuid base-path from to]
-  (.renameLocalFile rsapi graph-uuid base-path from to))
+  (rsapi/renameLocalFile graph-uuid base-path from to))
 
 (defn delete-local-files [graph-uuid base-path file-paths]
-  (.deleteLocalFiles rsapi graph-uuid base-path (clj->js file-paths)))
+  (rsapi/deleteLocalFiles graph-uuid base-path (clj->js file-paths)))
 
 (defn update-local-files [graph-uuid base-path file-paths token]
-  (.updateLocalFiles rsapi graph-uuid base-path (clj->js file-paths) token))
+  (rsapi/updateLocalFiles graph-uuid base-path (clj->js file-paths) token))
 
 (defn download-version-files [graph-uuid base-path file-paths token]
-  (.updateLocalVersionFiles rsapi graph-uuid base-path (clj->js file-paths) token))
+  (rsapi/updateLocalVersionFiles graph-uuid base-path (clj->js file-paths) token))
 
 (defn delete-remote-files [graph-uuid base-path file-paths txid token]
-  (.deleteRemoteFiles rsapi graph-uuid base-path (clj->js file-paths) txid token))
+  (rsapi/deleteRemoteFiles graph-uuid base-path (clj->js file-paths) txid token))
 
 (defn update-remote-files [graph-uuid base-path file-paths txid token]
-  (.updateRemoteFiles rsapi graph-uuid base-path (clj->js file-paths) txid token true))
+  (rsapi/updateRemoteFiles graph-uuid base-path (clj->js file-paths) txid token true))
 
 (defn encrypt-fnames [graph-uuid fnames]
-  (.encryptFnames rsapi graph-uuid (clj->js fnames)))
+  (rsapi/encryptFnames graph-uuid (clj->js fnames)))
 
 (defn decrypt-fnames [graph-uuid fnames]
-  (.decryptFnames rsapi graph-uuid (clj->js fnames)))
+  (rsapi/decryptFnames graph-uuid (clj->js fnames)))
 
 (defn encrypt-with-passphrase [passphrase data]
-  (.ageEncryptWithPassphrase rsapi passphrase data))
+  (rsapi/ageEncryptWithPassphrase passphrase data))
 
 (defn decrypt-with-passphrase [passphrase data]
-  (.ageDecryptWithPassphrase rsapi passphrase data))
+  (rsapi/ageDecryptWithPassphrase passphrase data))
 
 (defn cancel-all-requests []
-  (.cancelAllRequests rsapi))
+  (rsapi/cancelAllRequests))
 
 (defonce progress-notify-chan "file-sync-progress")
-
-(when rsapi
-  (set-progress-callback (fn [error progress-info]
-                           (when-not error
-                             (doseq [^js win (window/get-all-windows)]
-                               (when-not (.isDestroyed win)
-                                 (.. win -webContents
-                                     (send progress-notify-chan (bean/->js progress-info)))))))))
-
+(set-progress-callback (fn [error progress-info]
+                         (when-not error
+                           (doseq [^js win (window/get-all-windows)]
+                             (when-not (.isDestroyed win)
+                               (.. win -webContents
+                                   (send progress-notify-chan (bean/->js progress-info))))))))
+                                   

+ 10 - 3
src/electron/electron/handler.cljs

@@ -55,8 +55,13 @@
 (defmethod handle :readdir [_window [_ dir]]
   (readdir dir))
 
+(defmethod handle :listdir [_window [_ dir flat?]]
+  (when (and dir (fs-extra/pathExistsSync dir))
+    (js-utils/deepReadDir dir (if (boolean? flat?) flat? true))))
+
 (defmethod handle :unlink [_window [_ repo-dir path]]
-  (if (plugin/dotdir-file? path)
+  (if (or (plugin/dotdir-file? path)
+          (plugin/assetsdir-file? path))
     (fs/unlinkSync path)
     (try
       (logger/info ::unlink {:path path})
@@ -374,8 +379,10 @@
 
 (defmethod handle :getAssetsFiles [^js win [_ {:keys [exts]}]]
   (when-let [graph-path (state/get-window-graph-path win)]
-    (p/let [^js files (js-utils/getAllFiles (.join path graph-path "assets") (clj->js exts))]
-      files)))
+    (when-let [assets-path (.join path graph-path "assets")]
+      (when (fs-extra/pathExistsSync assets-path)
+        (p/let [^js files (js-utils/getAllFiles assets-path (clj->js exts))]
+          files)))))
 
 (defn close-watcher-when-orphaned!
   "When it's the last window for the directory, close the watcher."

+ 4 - 0
src/electron/electron/plugin.cljs

@@ -23,6 +23,10 @@
   [file]
   (and file (string/starts-with? (path/normalize file) cfgs/dot-root)))
 
+(defn assetsdir-file?
+  [file]
+  (and (string? file) (string/includes? file "assets/storages")))
+
 ;; Get a release by tag name: /repos/{owner}/{repo}/releases/tags/{tag}
 ;; Get the latest release: /repos/{owner}/{repo}/releases/latest
 ;; Zipball https://api.github.com/repos/{owner}/{repo}/zipball

+ 1 - 1
src/electron/electron/utils.cljs

@@ -35,7 +35,7 @@
 
 (defn get-ls-dotdir-root
   []
-  (let [lg-dir (str (.getPath app "home") "/.logseq")]
+  (let [lg-dir (path/join (.getPath app "home") ".logseq")]
     (if-not (fs/existsSync lg-dir)
       (do (fs/mkdirSync lg-dir) lg-dir)
       lg-dir)))

+ 65 - 40
src/electron/electron/utils.js

@@ -1,5 +1,5 @@
-import path from "path";
-import fs from 'fs';
+import path from 'path'
+import fse from 'fs-extra'
 
 // workaround from https://github.com/electron/electron/issues/426#issuecomment-658901422
 // We set an intercept on incoming requests to disable x-frame-options
@@ -7,36 +7,35 @@ import fs from 'fs';
 
 // Should we do this? Does this make evil sites doing danagerous things?
 export const disableXFrameOptions = (win) => {
-  win.webContents.session.webRequest.onHeadersReceived(
-    (d, c) => {
-      if (d.responseHeaders['X-Frame-Options']) {
-        delete d.responseHeaders['X-Frame-Options']
-      } else if (d.responseHeaders['x-frame-options']) {
-        delete d.responseHeaders['x-frame-options']
-      }
-      
-      if (d.responseHeaders['Content-Security-Policy']) {
-        delete d.responseHeaders['Content-Security-Policy']
-      }
+  win.webContents.session.webRequest.onHeadersReceived((d, c) => {
+    if (d.responseHeaders['X-Frame-Options']) {
+      delete d.responseHeaders['X-Frame-Options']
+    }
 
-      if (d.responseHeaders['content-security-policy']) {
-        delete d.responseHeaders['content-security-policy']
-      }
+    if (d.responseHeaders['x-frame-options']) {
+      delete d.responseHeaders['x-frame-options']
+    }
 
+    if (d.responseHeaders['Content-Security-Policy']) {
+      delete d.responseHeaders['Content-Security-Policy']
+    }
 
-      c({ cancel: false, responseHeaders: d.responseHeaders })
+    if (d.responseHeaders['content-security-policy']) {
+      delete d.responseHeaders['content-security-policy']
     }
-  )
+
+    c({ cancel: false, responseHeaders: d.responseHeaders })
+  })
 }
 
 export async function getAllFiles(dir, exts) {
-  const dirents = await readdir(dir, { withFileTypes: true })
+  const dirents = await fse.readdir(dir, { withFileTypes: true })
 
-  if (exts) {
+  if (exts != null) {
     !Array.isArray(exts) && (exts = [exts])
 
-    exts = exts.map(it => {
-      if (it && !it.startsWith('.')) {
+    exts = exts.map((it) => {
+      if (typeof it === 'string' && it !== '' && !it.startsWith('.')) {
         it = '.' + it
       }
 
@@ -44,23 +43,49 @@ export async function getAllFiles(dir, exts) {
     })
   }
 
-  const files = await Promise.all(dirents.map(async (dirent) => {
-    if (exts && !exts.includes(path.extname(dirent.name))) {
-      return null
-    }
+  const files = await Promise.all(
+    dirents.map(async (dirent) => {
+      const filePath = path.resolve(dir, dirent.name)
 
-    const filePath = path.resolve(dir, dirent.name)
-    const fileStats = await lstat(filePath)
-    const stats = {
-      size: fileStats.size,
-      accessTime: fileStats.atimeMs,
-      modifiedTime: fileStats.mtimeMs,
-      changeTime: fileStats.ctimeMs,
-      birthTime: fileStats.birthtimeMs
-    }
-    return dirent.isDirectory() ? getAllFiles(filePath) : {
-      path: filePath, ...stats
-    }
-  }))
-  return files.flat().filter(it => it != null)
+      if (dirent.isDirectory()) {
+        return getAllFiles(filePath, exts)
+      }
+
+      if (exts && !exts.includes(path.extname(dirent.name)?.toLowerCase())) {
+        return null
+      }
+
+      const fileStats = await fse.lstat(filePath)
+
+      const stats = {
+        size: fileStats.size,
+        accessTime: fileStats.atimeMs,
+        modifiedTime: fileStats.mtimeMs,
+        changeTime: fileStats.ctimeMs,
+        birthTime: fileStats.birthtimeMs,
+      }
+
+      return { path: filePath, ...stats }
+    })
+  )
+  return files.flat().filter((it) => it != null)
+}
+
+export async function deepReadDir(dirPath, flat = true) {
+  const ret = await Promise.all(
+    (
+      await fse.readdir(dirPath)
+    ).map(async (entity) => {
+      const root = path.join(dirPath, entity)
+      return (await fse.lstat(root)).isDirectory()
+        ? await deepReadDir(root)
+        : root
+    })
+  )
+
+  if (flat) {
+    return ret?.flat()
+  }
+
+  return ret
 }

+ 11 - 25
src/main/frontend/components/block.cljs

@@ -240,7 +240,7 @@
         (if (or (not asset-file?)
                 (and exist? (not loading?)))
           (content-fn)
-          [:p.text-red-500.text-xs [:small.opacity-80
+          [:p.text-error.text-xs [:small.opacity-80
                                     (util/format "%s not found!" (string/capitalize type))]])))))
 
 (defn open-lightbox
@@ -1916,8 +1916,10 @@
                  (not= "nil" marker))
         {:class (str (string/lower-case marker))})
       (when bg-color
-        {:style {:background-color bg-color}
-         :class "with-bg-color"}))
+        {:style {:background-color (if (some #{bg-color} ui/block-background-colors) 
+                                     (str "var(--ls-highlight-color-" bg-color ")")
+                                     bg-color)}
+         :class "px-1 with-bg-color"}))
      (remove-nils
       (concat
        [(when-not slide? checkbox)
@@ -3174,22 +3176,6 @@
     {:debug-id q
      :trigger-once? false})))
 
-(defn admonition
-  [config type result]
-  (when-let [icon (case (string/lower-case (name type))
-                    "note" svg/note
-                    "tip" svg/tip
-                    "important" svg/important
-                    "caution" svg/caution
-                    "warning" svg/warning
-                    "pinned" svg/pinned
-                    nil)]
-    [:div.flex.flex-row.admonitionblock.align-items {:class type}
-     [:div.pr-4.admonition-icon.flex.flex-col.justify-center
-      {:title (string/upper-case type)} (icon)]
-     [:div.ml-4.text-lg
-      (markup-elements-cp config result)]]))
-
 ;; TODO: move to mldoc
 ;; (defn- convert-md-src-to-custom-block
 ;;   [item]
@@ -3333,22 +3319,22 @@
           (ui/block-error "Invalid query:" {:content content})))
 
       ["Custom" "note" _options result _content]
-      (admonition config "note" result)
+      (ui/admonition "note" (markup-elements-cp config result))
 
       ["Custom" "tip" _options result _content]
-      (admonition config "tip" result)
+      (ui/admonition "tip" (markup-elements-cp config result))
 
       ["Custom" "important" _options result _content]
-      (admonition config "important" result)
+      (ui/admonition "important" (markup-elements-cp config result))
 
       ["Custom" "caution" _options result _content]
-      (admonition config "caution" result)
+      (ui/admonition "caution" (markup-elements-cp config result))
 
       ["Custom" "warning" _options result _content]
-      (admonition config "warning" result)
+      (ui/admonition "warning" (markup-elements-cp config result))
 
       ["Custom" "pinned" _options result _content]
-      (admonition config "pinned" result)
+      (ui/admonition "pinned" (markup-elements-cp config result))
 
       ["Custom" "center" _options l _content]
       (->elem

+ 0 - 14
src/main/frontend/components/block.css

@@ -305,20 +305,6 @@
   }
 }
 
-.with-bg-color {
-  @apply px-1;
-
-  color: #fff;
-
-  a, .page-reference:not(:hover) {
-    color: #aacece;
-
-    .bracket {
-      color: #aacece;
-    }
-  }
-}
-
 .block-properties {
   margin: 4px 0;
   padding: 4px 8px;

+ 8 - 14
src/main/frontend/components/content.cljs

@@ -93,15 +93,6 @@
     "Cycle todos"
     nil)])
 
-;; FIXME: Make it configurable
-(def block-background-colors
-  ["#533e7d"
-   "#497d46"
-   "#787f97"
-   "#978626"
-   "#49767b"
-   "#264c9b"])
-
 (defonce *template-including-parent? (atom nil))
 
 (rum/defc template-checkbox
@@ -164,13 +155,14 @@
     (when-let [block (db/entity [:block/uuid block-id])]
       (let [format (:block/format block)]
         [:.menu-links-wrapper
-         [:div.flex.flex-row.justify-between.pb-2.pt-1.px-2.items-center
+         [:div.flex.flex-row.justify-between.py-1.px-2.items-center
           [:div.flex.flex-row.justify-between.flex-1
-           (for [color block-background-colors]
+           (for [color ui/block-background-colors]
              [:a.m-2.shadow-sm
-              {:on-click (fn [_e]
+              {:title (t (keyword "color" color))
+               :on-click (fn [_e]
                            (editor-handler/set-block-property! block-id "background-color" color))}
-              [:div.heading-bg {:style {:background-color color}}]])
+              [:div.heading-bg {:style {:background-color (str "var(--color-" color "-500)")}}]])
            [:a.m-2.shadow-sm
             {:title    (t :remove-background)
              :on-click (fn [_e]
@@ -178,16 +170,18 @@
             [:div.heading-bg.remove "-"]]]]
 
          [:div.flex.flex-row.justify-between.pb-2.pt-1.px-2.items-center
-          [:div.flex.flex-row.justify-between.flex-1
+          [:div.flex.flex-row.justify-between.flex-1.px-1
            (for [i (range 1 7)]
              (ui/button
               (str "H" i)
+              :class "to-heading-button"
               :on-click (fn [_e]
                           (editor-handler/set-heading! block-id format i))
               :intent "link"
               :small? true))
            (ui/button
             "H-"
+            :class "to-heading-button"
             :title (t :remove-heading)
             :on-click (fn [_e]
                         (editor-handler/remove-heading! block-id format))

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

@@ -170,8 +170,8 @@
          [:div.input-hints.text-sm.py-2.px-3.rounded.mb-2.mt-2.flex.items-center
           (if-let [display-str (:fail set-remote-graph-pwd-result)]
             [:<>
-             [:span.flex.pr-1.text-red-600 (ui/icon "alert-circle" {:class "text-md mr-1"})]
-             [:span.text-red-600 display-str]]
+             [:span.flex.pr-1.text-error (ui/icon "alert-circle" {:class "text-md mr-1"})]
+             [:span.text-error display-str]]
             [:<>
              [:span.flex.pr-1 (ui/icon "bulb" {:class "text-md mr-1"})]
              [:span "Please enter the password for this graph to continue syncing."]])]])
@@ -187,8 +187,8 @@
                     (not (string/blank? @*pw-confirm)))
               (if (or (not (pattern-ok?))
                       (not= @*password @*pw-confirm))
-                [:span.flex.pr-1.text-red-600 (ui/icon "alert-circle" {:class "text-md mr-1"})]
-                [:span.flex.pr-1.text-green-600 (ui/icon "circle-check" {:class "text-md mr-1"})])
+                [:span.flex.pr-1.text-error (ui/icon "alert-circle" {:class "text-md mr-1"})]
+                [:span.flex.pr-1.text-success (ui/icon "circle-check" {:class "text-md mr-1"})])
               [:span.flex.pr-1 (ui/icon "bulb" {:class "text-md mr-1"})])
 
             (if (not (string/blank? @*password))

+ 2 - 2
src/main/frontend/components/file_sync.cljs

@@ -85,8 +85,8 @@
       (when (not (string/blank? selected-path))
         [:h5.text-xs.pt-1.-mb-1.flex.items-center.leading-none
          (if (mobile-util/iCloud-container-path? selected-path)
-           [:span.inline-block.pr-1.text-red-600.scale-75 (ui/icon "alert-circle")]
-           [:span.inline-block.pr-1.text-green-600.scale-75 (ui/icon "circle-check")])
+           [:span.inline-block.pr-1.text-error.scale-75 (ui/icon "alert-circle")]
+           [:span.inline-block.pr-1.text-success.scale-75 (ui/icon "circle-check")])
          selected-path])
 
       [:div.out-icloud

+ 6 - 0
src/main/frontend/components/file_sync.css

@@ -1,3 +1,9 @@
+:root {
+  --ls-color-file-sync-error: var(--ls-error-color);
+  --ls-color-file-sync-pending: var(--color-yellow-500);
+  --ls-color-file-sync-idle: var(--ls-success-color);
+}
+
 .cp__file-sync {
   &-indicator {
     a.cloud {

+ 2 - 2
src/main/frontend/components/page.cljs

@@ -778,8 +778,8 @@
   (fn [close-fn]
     [:div
      [:div.sm:flex.items-center
-      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
-       [:span.text-red-600.text-xl
+      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-error.sm:mx-0.sm:h-10.sm:w-10
+       [:span.text-error.text-xl
         (ui/icon "alert-triangle")]]
       [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
        [:h3#modal-headline.text-lg.leading-6.font-medium

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

@@ -133,7 +133,7 @@
 
     .r {
       @apply text-base space-x-2;
-      
+
       a.button {
         color: var(--ls-primary-text-color);
         margin-top: 1px;
@@ -364,11 +364,6 @@ html.is-native-ios {
   }
  }
 
- .text-2xs {
-  font-size: 0.625rem; /* 10px */
-  line-height: 0.875rem; /* 14px */
- }
-
  .toned-down {
    opacity: 0.5;
   color: var(--ls-secondary-text-color);

+ 2 - 2
src/main/frontend/components/page_menu.cljs

@@ -33,8 +33,8 @@
   (fn [close-fn]
     [:div
      [:div.sm:flex.items-center
-      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
-       [:span.text-red-600.text-xl
+      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-error.sm:mx-0.sm:h-10.sm:w-10
+       [:span.text-error.text-xl
         (ui/icon "alert-triangle")]]
       [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
        [:h3#modal-headline.text-lg.leading-6.font-medium

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

@@ -292,7 +292,7 @@
         svg/folder)
 
       (when (and (not market?) unpacked?)
-        [:span.flex.justify-center.text-xs.text-red-500.pt-2 (t :plugin/unpacked)])]
+        [:span.flex.justify-center.text-xs.text-error.pt-2 (t :plugin/unpacked)])]
 
      [:div.r
       [:h3.head.text-xl.font-bold.pt-1.5
@@ -1004,7 +1004,7 @@
   [:div
    [:span.block.whitespace-normal
     "This plugin "
-    [:strong.text-red-500 "#" name]
+    [:strong.text-error "#" name]
     " takes too long to load, affecting the application startup time and
      potentially causing other plugins to fail to load."]
 
@@ -1021,7 +1021,7 @@
                                 (notification/clear! pid)
                                 (notification/show!
                                  [:span "The plugin "
-                                  [:strong.text-red-500 "#" name]
+                                  [:strong.text-error "#" name]
                                   " is disabled."] :success
                                  true nil 3000)))
                      (p/catch #(js/console.error %)))))]])

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

@@ -148,11 +148,11 @@
                                    (empty? filter-state)
                                    ""
                                    (every? true? (vals filter-state))
-                                   "text-green-400"
+                                   "text-success"
                                    (every? false? (vals filter-state))
-                                   "text-red-400"
+                                   "text-error"
                                    :else
-                                   "text-yellow-400")
+                                   "text-warning")
                           :style {:fontSize 24}})]]
 
      (fn []

+ 7 - 10
src/main/frontend/components/svg.cljs

@@ -70,7 +70,7 @@
      :d               "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"}]])
 (rum/defc note
   []
-  [:svg.h-8.w-8.svg-shadow.note
+  [:svg.h-8.w-8.note
    {:view-box "0 0 512 512"
     :fill     "currentColor"}
    [:path
@@ -88,20 +88,18 @@
 
 (rum/defc important
   []
-  [:svg.h-8.w-8.svg-shadow.important
+  [:svg.h-8.w-8.important
    {:view-box "0 0 512 512"
-    :fill     "currentColor"
-    :color    "#bf0000"}
+    :fill     "var(--ls-error-color)"}
    [:path
     {:d
      "M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"}]])
 
 (rum/defc caution
   []
-  [:svg.h-8.w-8.svg-shadow.caution
+  [:svg.h-8.w-8.caution
    {:view-box "0 0 384 512"
-    :fill     "currentColor"
-    :color    "#bf3400"}
+    :fill     "var(--ls-warning-color)"}
    [:path
     {:d
      "M216 23.86c0-23.8-30.65-32.77-44.15-13.04C48 191.85 224 200 224 288c0 35.63-29.11 64.46-64.85 63.99-35.17-.45-63.15-29.77-63.15-64.94v-85.51c0-21.7-26.47-32.23-41.43-16.5C27.8 213.16 0 261.33 0 320c0 105.87 86.13 192 192 192s192-86.13 192-192c0-170.29-168-193-168-296.14z"}]])
@@ -110,11 +108,10 @@
   ([]
    (warning nil))
   ([opts]
-   [:svg.h-8.w-8.svg-shadow.warning
+   [:svg.h-8.w-8.warning
     (merge
       {:view-box "0 0 576 512"
-       :fill     "currentColor"
-       :color    "#bf6900"}
+       :fill     "var(--ls-warning-color)"}
       opts)
     [:path
      {:d

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

@@ -425,6 +425,12 @@
     (string/replace
      source "../assets" (util/format "%s://%s/assets" protocol (get-repo-dir (state/get-current-repo))))))
 
+(defn get-current-repo-assets-root
+  []
+  (when-let [repo-root (and (local-db? (state/get-current-repo))
+                            (get-repo-dir (state/get-current-repo)))]
+    (util/node-path.join repo-root "assets")))
+
 (defn get-custom-js-path
   ([]
    (get-custom-js-path (state/get-current-repo)))

+ 7 - 0
src/main/frontend/dicts.cljc

@@ -148,6 +148,13 @@
         :asset/maximize "Maximize image"
         :asset/confirm-delete "Are you sure you want to delete this {1}?"
         :asset/physical-delete "Remove the file too (notice it can't be restored)"
+        :color/gray "Gray"
+        :color/red "Red"
+        :color/yellow "Yellow"
+        :color/green "Green"
+        :color/blue "Blue"
+        :color/purple "Purple"
+        :color/pink "Pink"
         :content/copy "Copy"
         :content/cut "Cut"
         :content/make-todos "Make {1}s"

+ 11 - 6
src/main/frontend/extensions/excalidraw.cljs

@@ -3,7 +3,7 @@
             [clojure.string :as string]
             ;; NOTE: Always use production build of excalidraw
             ;; See-also: https://github.com/excalidraw/excalidraw/pull/3330
-            ["@excalidraw/excalidraw/dist/excalidraw.production.min" :as Excalidraw]
+            ["@excalidraw/excalidraw/dist/excalidraw.production.min" :refer [Excalidraw serializeAsJSON]]
             [frontend.config :as config]
             [frontend.db :as db]
             [frontend.handler.editor :as editor-handler]
@@ -15,11 +15,11 @@
             [frontend.ui :as ui]
             [frontend.util :as util]
             [goog.object :as gobj]
+            [goog.functions :refer [debounce]]
             [rum.core :as rum]
             [frontend.mobile.util :as mobile-util]))
 
-(def excalidraw (r/adapt-class (gobj/get Excalidraw "default")))
-(def serialize-as-json (gobj/get Excalidraw "serializeAsJSON"))
+(def excalidraw (r/adapt-class Excalidraw))
 
 (defn from-json
   [text]
@@ -64,8 +64,13 @@
   (rum/local false ::view-mode?)
   (rum/local false ::grid-mode?)
   (rum/local nil ::elements)
-  {:did-mount update-draw-content-width
-   :did-update update-draw-content-width}
+  (rum/local nil ::resize-observer)
+  {:did-mount (fn [state]
+                (reset! (::resize-observer state) (js/ResizeObserver. (debounce #(reset! (::draw-width state) 0) 300)))
+                (.observe @(::resize-observer state) (ui/main-node))
+                (update-draw-content-width state))
+   :did-update update-draw-content-width
+   :will-unmount (fn [state] (.disconnect @(::resize-observer state)))}
   [state data option]
   (let [*draw-width (get state ::draw-width)
         *zen-mode? (get state ::zen-mode?)
@@ -109,7 +114,7 @@
                               (reset! *elements elements->clj)
                               (draw/save-excalidraw!
                                file
-                               (serialize-as-json elements app-state))))))
+                               (serializeAsJSON elements app-state))))))
 
            :zen-mode-enabled @*zen-mode?
            :view-mode-enabled @*view-mode?

+ 10 - 10
src/main/frontend/extensions/pdf/pdf.css

@@ -1,11 +1,11 @@
 @import "_viewer.css";
 
 :root {
-  --ph-highlight-color-blue: #55b8fe;
-  --ph-highlight-color-green: #46da48;
-  --ph-highlight-color-red: #fd709a;
-  --ph-highlight-color-purple: #ac8cf1;
-  --ph-highlight-color-yellow: #fcd713;
+  --ph-highlight-color-blue: var(--color-blue-100);
+  --ph-highlight-color-green: var(--color-green-100);
+  --ph-highlight-color-red: var(--color-red-100);
+  --ph-highlight-color-purple: var(--color-purple-100);
+  --ph-highlight-color-yellow: var(--color-yellow-100);
 
   --ph-highlight-scroll-into-color: rgba(255, 75, 93, 0.67);
 
@@ -385,23 +385,23 @@ input::-webkit-inner-spin-button {
           }
 
           &[data-color=yellow] {
-            background-color: var(--ph-highlight-color-yellow);
+            background-color: var(--color-yellow-500);
           }
 
           &[data-color=blue] {
-            background-color: var(--ph-highlight-color-blue);
+            background-color: var(--color-blue-500);
           }
 
           &[data-color=green] {
-            background-color: var(--ph-highlight-color-green);
+            background-color: var(--color-green-500);
           }
 
           &[data-color=red] {
-            background-color: var(--ph-highlight-color-red);
+            background-color: var(--color-red-500);
           }
 
           &[data-color=purple] {
-            background-color: var(--ph-highlight-color-purple);
+            background-color: var(--color-purple-500);
           }
         }
       }

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

@@ -95,7 +95,7 @@
 
        [:span.animate-spin-reverse {:style {:visibility (if is-searching "visible"  "hidden")}}  svg/refresh]]]
 
-     [:div.h-2.text-sm.text-red-400.mb-2 (if search-error (str "Search error: " search-error) "")]
+     [:div.h-2.text-sm.text-error.mb-2 (if search-error (str "Search error: " search-error) "")]
 
      [:div
       (map
@@ -154,7 +154,7 @@
          (not (re-matches #"^\d+$" (str @(::type-id state)))))
      (ui/admonition
       :warning
-      [:p.text-red-500
+      [:p.text-error
        "User ID is different from username and can be found on the "
        [:a {:href "https://www.zotero.org/settings/keys" :target "_blank"}
         "https://www.zotero.org/settings/keys"]
@@ -176,7 +176,7 @@
    (when (setting/setting :overwrite-mode?)
      (ui/admonition
       :warning
-      [:p.text-red-500
+      [:p.text-error
        "Dangerous! This will delete and recreate Zotero existing page! Make sure to backup your notes first in case something goes wrong. Make sure you don't put any personal item in previous Zotero page and it's OK to overwrite the page!"]))])
 
 (rum/defc attachment-setting <

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

@@ -315,12 +315,12 @@
   (let [block (or (and (:db/id block) (db/pull (:db/id block))) block)
         block (merge block
                      (block/parse-title-and-body uuid format pre-block? (:block/content block)))
-        properties (:block/properties block)
+        properties (-> (:block/properties block)
+                       (dissoc :heading))
         real-content (:block/content block)
-        content (let [properties (if (= format :markdown) (dissoc properties :heading) properties)]
-                  (if (and (seq properties) real-content (not= real-content content))
-                   (property/with-built-in-properties properties content format)
-                   content))
+        content (if (and (seq properties) real-content (not= real-content content))
+                  (property/with-built-in-properties properties content format)
+                  content)
         content (drawer/with-logbook block content)
         content (with-timetracking block content)
         first-block? (= left page)
@@ -370,6 +370,7 @@
     (profile
      "Save block: "
      (let [block' (wrap-parse-block block)]
+       (util/pprint block')
        (outliner-tx/transact!
          {:outliner-op :save-block}
          (outliner-core/save-block! block'))
@@ -3495,7 +3496,9 @@
           block (db/entity [:block/uuid block-id])
           content' (commands/set-markdown-heading (:block/content block) heading)]
       (save-block! repo block-id content'))
-    (set-block-property! block-id "heading" heading)))
+    (do
+      (save-current-block!)
+      (set-block-property! block-id "heading" heading))))
 
 (defn remove-heading!
   [block-id format]

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

@@ -44,7 +44,7 @@
 
 (defn- embed-audio [database64]
   (p/let [page (or (state/get-current-page) (string/lower-case (date/journal-name)))
-          filename (str (date/get-date-time-string-2) ".mp3")
+          filename (str (date/get-date-time-string-2) ".m4a")
           edit-block (state/get-edit-block)
           format (or (:block/format edit-block) (db/get-page-format page))
           path (editor-handler/get-asset-path filename)

+ 15 - 7
src/main/frontend/ui.cljs

@@ -52,6 +52,14 @@
 
 (defonce icon-size (if (mobile-util/native-platform?) 26 20))
 
+(def block-background-colors
+  ["gray"
+   "red"
+   "yellow"
+   "green"
+   "blue"
+   "purple"])
+
 (rum/defc ls-textarea
   < rum/reactive
   {:did-mount (fn [state]
@@ -207,7 +215,7 @@
           (case status
             :success
             [:svg.h-6.w-6.text-green-400
-             {:stroke "currentColor", :viewBox "0 0 24 24", :fill "none"}
+             {:stroke "var(--ls-success-color)", :viewBox "0 0 24 24", :fill "none"}
              [:path
               {:d               "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
                :stroke-width    "2"
@@ -215,7 +223,7 @@
                :stroke-linecap  "round"}]]
             :warning
             [:svg.h-6.w-6.text-yellow-500
-             {:stroke "currentColor", :viewBox "0 0 24 24", :fill "none"}
+             {:stroke "var(--ls-warning-color)", :viewBox "0 0 24 24", :fill "none"}
              [:path
               {:d               "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
                :stroke-width    "2"
@@ -223,7 +231,7 @@
                :stroke-linecap  "round"}]]
 
             [:svg.h-6.w-6.text-red-500
-             {:view-box "0 0 20 20", :fill "currentColor"}
+             {:view-box "0 0 20 20", :fill "var(--ls-error-color)"}
              [:path
               {:clip-rule "evenodd"
                :d
@@ -602,8 +610,8 @@
       [:div.ui__confirm-modal
        {:class (str "is-" tag)}
        [:div.sm:flex.sm:items-start
-        [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
-         [:svg.h-6.w-6.text-red-600
+        [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-error.sm:mx-0.sm:h-10.sm:w-10
+         [:svg.h-6.w-6.text-error
           {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
           [:path
            {:d
@@ -741,7 +749,7 @@
                       nil)]
       [:div.flex.flex-row.admonitionblock.align-items {:class type}
        [:div.pr-4.admonition-icon.flex.flex-col.justify-center
-        {:title (string/upper-case type)} (icon)]
+        {:title (string/capitalize type)} (icon)]
        [:div.ml-4.text-lg
         content]])))
 
@@ -774,7 +782,7 @@
   [:section.border.mt-1.p-1.cursor-pointer.block-content-fallback-ui
    section-attrs
    [:div.flex.justify-between.items-center.px-1
-    [:h5.text-red-600.pb-1 title]
+    [:h5.text-error.pb-1 title]
     [:a.text-xs.opacity-50.hover:opacity-80
      {:href "https://github.com/logseq/logseq/issues/new?labels=from:in-app&template=bug_report.yaml"
       :target "_blank"} "report issue"]]

+ 101 - 51
src/main/logseq/api.cljs

@@ -147,86 +147,136 @@
           path (util/node-path.join path "package.json")]
       (fs/write-file! repo "" path (js/JSON.stringify data nil 2) {:skip-compare? true}))))
 
+(defn ^:private write_rootdir_file
+  [file content sub-root root-dir]
+  (p/let [repo           ""
+          path           (util/node-path.join root-dir sub-root)
+          exist?         (fs/file-exists? path "")
+          _              (when-not exist? (fs/mkdir-recur! path))
+          user-path      (util/node-path.join path file)
+          sub-dir?       (string/starts-with? user-path path)
+          _              (when-not sub-dir?
+                           (log/info :debug user-path)
+                           (throw "write file denied"))
+          user-path-root (util/node-path.dirname user-path)
+          exist?         (fs/file-exists? user-path-root "")
+          _              (when-not exist? (fs/mkdir-recur! user-path-root))
+          _              (fs/write-file! repo "" user-path content {:skip-compare? true})]
+    user-path))
+
 (defn ^:private write_dotdir_file
   [file content sub-root]
-  (p/let [repo ""
-          path (plugin-handler/get-ls-dotdir-root)
-          path (util/node-path.join path sub-root)
-          exist? (fs/file-exists? path "")
-          _ (when-not exist? (fs/mkdir-recur! path))
+  (some-> (plugin-handler/get-ls-dotdir-root)
+          (p/then #(write_rootdir_file file content sub-root %))))
+
+(defn ^:private write_assetsdir_file
+  [file content sub-root]
+  (if-let [assets-dir (config/get-current-repo-assets-root)]
+    (write_rootdir_file file content sub-root assets-dir)
+    false))
+
+(defn ^:private read_rootdir_file
+  [file sub-root root-dir]
+  (p/let [path      (util/node-path.join root-dir sub-root)
           user-path (util/node-path.join path file)
-          sub-dir? (string/starts-with? user-path path)
-          _ (when-not sub-dir?
-              (log/info :debug user-path)
-              (throw "write file denied"))
-          user-path-root (util/node-path.dirname user-path)
-          exist? (fs/file-exists? user-path-root "")
-          _ (when-not exist? (fs/mkdir-recur! user-path-root))
-          _ (fs/write-file! repo "" user-path content {:skip-compare? true})]
-    user-path))
+          sub-dir?  (string/starts-with? user-path path)
+          _         (when-not sub-dir? (log/info :debug user-path) (throw "read file denied"))
+          exist?    (fs/file-exists? "" user-path)
+          _         (when-not exist? (log/info :debug user-path) (throw "file not existed"))
+          content   (fs/read-file "" user-path)]
+    content))
 
 (defn ^:private read_dotdir_file
   [file sub-root]
-  (p/let [path (plugin-handler/get-ls-dotdir-root)
-          path (util/node-path.join path sub-root)
+  (some-> (plugin-handler/get-ls-dotdir-root)
+          (p/then #(read_rootdir_file file sub-root %))))
+
+(defn ^:private read_assetsdir_file
+  [file sub-root]
+  (when-let [root-dir (config/get-current-repo-assets-root)]
+    (read_rootdir_file file sub-root root-dir)))
+
+(defn ^:private unlink_rootdir_file!
+  [file sub-root root-dir]
+  (p/let [repo      ""
+          path      (util/node-path.join root-dir sub-root)
           user-path (util/node-path.join path file)
-          sub-dir? (string/starts-with? user-path path)
-          _ (when-not sub-dir? (log/info :debug user-path) (throw "read file denied"))
-          exist? (fs/file-exists? "" user-path)
-          _ (when-not exist? (log/info :debug user-path) (throw "file not existed"))
-          content (fs/read-file "" user-path)]
-    content))
+          sub-dir?  (string/starts-with? user-path path)
+          _         (when-not sub-dir? (log/info :debug user-path) (throw "access file denied"))
+          exist?    (fs/file-exists? "" user-path)
+          _         (when-not exist? (log/info :debug user-path) (throw "file not existed"))
+          _         (fs/unlink! repo user-path {})]))
 
 (defn ^:private unlink_dotdir_file!
   [file sub-root]
-  (p/let [repo ""
-          path (plugin-handler/get-ls-dotdir-root)
-          path (util/node-path.join path sub-root)
-          user-path (util/node-path.join path file)
-          sub-dir? (string/starts-with? user-path path)
-          _ (when-not sub-dir? (log/info :debug user-path) (throw "access file denied"))
-          exist? (fs/file-exists? "" user-path)
-          _ (when-not exist? (log/info :debug user-path) (throw "file not existed"))
-          _ (fs/unlink! repo user-path {})]))
+  (some-> (plugin-handler/get-ls-dotdir-root)
+          (p/then #(unlink_rootdir_file! file sub-root %))))
+
+(defn ^:private unlink_assetsdir_file!
+  [file sub-root]
+  (when-let [root-dir (config/get-current-repo-assets-root)]
+    (unlink_rootdir_file! file sub-root root-dir)))
 
 (def ^:export write_user_tmp_file
   (fn [file content]
     (write_dotdir_file file content "tmp")))
 
 (def ^:export write_plugin_storage_file
-  (fn [plugin-id file content]
-    (write_dotdir_file
-      file content
-      (let [plugin-id (util/node-path.basename plugin-id)]
-        (util/node-path.join "storages" plugin-id)))))
+  (fn [plugin-id file content assets?]
+    (let [plugin-id (util/node-path.basename plugin-id)
+          sub-root  (util/node-path.join "storages" plugin-id)]
+      (if (true? assets?)
+        (write_assetsdir_file file content sub-root)
+        (write_dotdir_file file content sub-root)))))
 
 (def ^:export read_plugin_storage_file
-  (fn [plugin-id file]
-    (let [plugin-id (util/node-path.basename plugin-id)]
-      (read_dotdir_file
-        file (util/node-path.join "storages" plugin-id)))))
+  (fn [plugin-id file assets?]
+    (let [plugin-id (util/node-path.basename plugin-id)
+          sub-root (util/node-path.join "storages" plugin-id)]
+      (if (true? assets?)
+        (read_assetsdir_file file sub-root)
+        (read_dotdir_file file sub-root)))))
 
 (def ^:export unlink_plugin_storage_file
-  (fn [plugin-id file]
-    (let [plugin-id (util/node-path.basename plugin-id)]
-      (unlink_dotdir_file!
-        file (util/node-path.join "storages" plugin-id)))))
+  (fn [plugin-id file assets?]
+    (let [plugin-id (util/node-path.basename plugin-id)
+          sub-root (util/node-path.join "storages" plugin-id)]
+      (if (true? assets?)
+        (unlink_assetsdir_file! file sub-root)
+        (unlink_dotdir_file! file sub-root)))))
 
 (def ^:export exist_plugin_storage_file
-  (fn [plugin-id file]
-    (p/let [root (plugin-handler/get-ls-dotdir-root)
+  (fn [plugin-id file assets?]
+    (p/let [root      (if (true? assets?)
+                        (config/get-current-repo-assets-root)
+                        (plugin-handler/get-ls-dotdir-root))
             plugin-id (util/node-path.basename plugin-id)
-            exist? (fs/file-exists?
-                     (util/node-path.join root "storages" plugin-id)
-                     file)]
+            exist?    (fs/file-exists?
+                       (util/node-path.join root "storages" plugin-id)
+                       file)]
       exist?)))
 
 (def ^:export clear_plugin_storage_files
-  (fn [plugin-id]
-    (p/let [root (plugin-handler/get-ls-dotdir-root)
+  (fn [plugin-id assets?]
+    (p/let [root      (if (true? assets?)
+                        (config/get-current-repo-assets-root)
+                        (plugin-handler/get-ls-dotdir-root))
             plugin-id (util/node-path.basename plugin-id)]
       (fs/rmdir! (util/node-path.join root "storages" plugin-id)))))
 
+(def ^:export list_plugin_storage_files
+  (fn [plugin-id assets?]
+    (p/let [root       (if (true? assets?)
+                         (config/get-current-repo-assets-root)
+                         (plugin-handler/get-ls-dotdir-root))
+            plugin-id  (util/node-path.basename plugin-id)
+            files-path (util/node-path.join root "storages" plugin-id)
+            ^js files  (ipc/ipc :listdir files-path)]
+      (when (js-iterable? files)
+        (bean/->js
+         (map #(some-> (string/replace-first % files-path "")
+                       (string/replace #"^/+" "")) files))))))
+
 (def ^:export load_user_preferences
   (fn []
     (p/let [repo ""

+ 1 - 1
tailwind.all.css

@@ -1,6 +1,7 @@
 @charset "utf-8";
 @import "tailwindcss/base";
 @import "tailwindcss/components";
+@import "tailwindcss/utilities";
 @import "resources/css/inter.css";
 @import "resources/css/reveal.min.css";
 @import "resources/css/reveal_black.min.css";
@@ -21,4 +22,3 @@
 @import "resources/css/srs_cards.css";
 @import "resources/css/tabler-extension.css";
 @import-glob "src/main/frontend/**/[!_]*.css";
-@import "tailwindcss/utilities";

+ 49 - 7
tailwind.config.js

@@ -1,16 +1,56 @@
 const colors = require('tailwindcss/colors')
 
+function exposeColorsToCssVars({ addBase, theme }) {
+  function extractColorVars(colorObj, colorGroup = '') {
+    return Object.keys(colorObj).reduce((vars, colorKey) => {
+      const value = colorObj[colorKey];
+
+      const newVars =
+        typeof value === 'string'
+          ? { [`--color${colorGroup}-${colorKey}`]: value }
+          : extractColorVars(value, `-${colorKey}`);
+
+      return { ...vars, ...newVars };
+    }, {});
+  }
+
+  addBase({
+    ':root': extractColorVars(theme('colors')),
+  });
+}
+
 module.exports = {
   mode: 'jit',
-  purge: [
-    './src/**/*.js',
-    './src/**/*.cljs',
-    './resources/**/*.html',
-  ],
-  plugins: [require('@tailwindcss/ui')],
+  purge: {
+    content: [
+      './src/**/*.js',
+      './src/**/*.cljs',
+      './resources/**/*.html'
+    ],
+    safelist: [
+      "bg-black",
+      "bg-white",
+      "bg-gray-50",  "bg-red-50",  "bg-yellow-50",  "bg-green-50",  "bg-blue-50",  "bg-indigo-50",  "bg-orange-50",  "bg-rose-50",  "bg-purple-50",
+      "bg-gray-100", "bg-red-100", "bg-yellow-100", "bg-green-100", "bg-blue-100", "bg-indigo-100", "bg-orange-100", "bg-rose-100", "bg-purple-100", "bg-pink-100",
+      "bg-gray-200", "bg-red-200", "bg-yellow-200", "bg-green-200", "bg-blue-200", "bg-indigo-200", "bg-orange-200", "bg-rose-200", "bg-purple-200", "bg-pink-200",
+      "bg-gray-300", "bg-red-300", "bg-yellow-300", "bg-green-300", "bg-blue-300", "bg-indigo-300", "bg-orange-300", "bg-rose-300", "bg-purple-300", "bg-pink-300",
+      "bg-gray-400", "bg-red-400", "bg-yellow-400", "bg-green-400", "bg-blue-400", "bg-indigo-400", "bg-orange-400", "bg-rose-400", "bg-purple-400", "bg-pink-400",
+      "bg-gray-500", "bg-red-500", "bg-yellow-500", "bg-green-500", "bg-blue-500", "bg-indigo-500", "bg-orange-500", "bg-rose-500", "bg-purple-500", "bg-pink-500",
+      "bg-gray-600", "bg-red-600", "bg-yellow-600", "bg-green-600", "bg-blue-600", "bg-indigo-600", "bg-orange-600", "bg-rose-600", "bg-purple-600", "bg-pink-600",
+      "bg-gray-700", "bg-red-700", "bg-yellow-700", "bg-green-700", "bg-blue-700", "bg-indigo-700", "bg-orange-700", "bg-rose-700", "bg-purple-700", "bg-pink-700",
+      "bg-gray-800", "bg-red-800", "bg-yellow-800", "bg-green-800", "bg-blue-800", "bg-indigo-800", "bg-orange-800", "bg-rose-800", "bg-purple-800", "bg-pink-800",
+      "bg-gray-900", "bg-red-900", "bg-yellow-900", "bg-green-900", "bg-blue-900", "bg-indigo-900", "bg-orange-900", "bg-rose-900", "bg-purple-900", "bg-pink-900",
+    ]
+  },
+  plugins: [
+    require('@tailwindcss/ui'),
+    exposeColorsToCssVars],
   darkMode: 'class',
   theme: {
     extend: {
+      fontSize: {
+        '2xs': ['0.625rem', '0.875rem']
+      },
       animation: {
         'spin-reverse': 'spin 2s linear infinite reverse',
       },
@@ -48,7 +88,9 @@ module.exports = {
       red: colors.red,
       yellow: colors.amber,
       orange: colors.orange,
-      rose: colors.rose
+      rose: colors.rose,
+      purple: colors.purple,
+      pink: colors.pink
     }
   }
 }

+ 11 - 4
yarn.lock

@@ -346,10 +346,12 @@
     global-agent "^3.0.0"
     global-tunnel-ng "^2.7.1"
 
-"@excalidraw/[email protected]":
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.10.0.tgz#5329d6fb1b0cca068e2cd34da6d648dd6d0d3fec"
-  integrity sha512-0fHc/oX394dHAT7LEacwZ0vh8aeI179plYnfaeLeRHBtHARgmtlmvxcnxd2pxJ0Z1Uj/Cy76oK9MVw/y8P1HhQ==
+"@excalidraw/[email protected]":
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.12.0.tgz#0c281624f29aa7834a015035b21dfd928be861d4"
+  integrity sha512-xMPmKmOEgKij43k5m6Koaevb+SBw6La7MT9UDY8Iq7nQCMhA1HQwcUURfSkZ3ERibdQmMsAGtjSLbkX7hrA3+A==
+  dependencies:
+    dotenv "10.0.0"
 
 "@ionic/cli-framework-output@^2.2.1", "@ionic/cli-framework-output@^2.2.5":
   version "2.2.5"
@@ -2466,6 +2468,11 @@ domutils@^2.8.0:
     domelementtype "^2.2.0"
     domhandler "^4.2.0"
 
[email protected]:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
+  integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
+
 duplexer3@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.