Explorar o código

Merge branch 'master' into enhance/ux-of-file-sync

Andelf %!s(int64=3) %!d(string=hai) anos
pai
achega
64f7168a37
Modificáronse 51 ficheiros con 1032 adicións e 418 borrados
  1. 1 0
      .gitignore
  2. 2 0
      android/app/capacitor.build.gradle
  3. 8 0
      android/app/src/main/assets/capacitor.plugins.json
  4. 6 0
      android/capacitor.settings.gradle
  5. 2 2
      docs/contributing-to-translations.md
  6. 6 6
      ios/App/App.xcodeproj/project.pbxproj
  7. 1 0
      ios/App/Podfile
  8. 3 1
      package.json
  9. 1 1
      resources/css/common.css
  10. 18 17
      src/electron/electron/core.cljs
  11. 47 17
      src/main/frontend/components/block.cljs
  12. 40 1
      src/main/frontend/components/block.css
  13. 4 5
      src/main/frontend/components/editor.cljs
  14. 4 1
      src/main/frontend/components/editor.css
  15. 23 16
      src/main/frontend/components/header.cljs
  16. 9 19
      src/main/frontend/components/header.css
  17. 2 1
      src/main/frontend/components/reference.cljs
  18. 9 0
      src/main/frontend/components/settings.cljs
  19. 26 20
      src/main/frontend/components/sidebar.cljs
  20. 10 0
      src/main/frontend/config.cljs
  21. 4 10
      src/main/frontend/db.cljs
  22. 11 2
      src/main/frontend/dicts.cljc
  23. 23 19
      src/main/frontend/extensions/calc.cljc
  24. 3 0
      src/main/frontend/extensions/code.cljs
  25. 179 124
      src/main/frontend/extensions/html_parser.cljs
  26. 1 1
      src/main/frontend/extensions/zotero/extractor.cljs
  27. 5 3
      src/main/frontend/handler.cljs
  28. 170 9
      src/main/frontend/handler/block.cljs
  29. 4 2
      src/main/frontend/handler/common.cljs
  30. 62 42
      src/main/frontend/handler/editor.cljs
  31. 37 18
      src/main/frontend/handler/events.cljs
  32. 14 9
      src/main/frontend/handler/export.cljs
  33. 6 4
      src/main/frontend/handler/file.cljs
  34. 78 0
      src/main/frontend/mobile/action_bar.cljs
  35. 15 8
      src/main/frontend/mobile/footer.cljs
  36. 15 0
      src/main/frontend/mobile/haptics.cljs
  37. 94 9
      src/main/frontend/mobile/index.css
  38. 1 0
      src/main/frontend/mobile/intent.cljs
  39. 5 3
      src/main/frontend/mobile/mobile_bar.cljs
  40. 7 4
      src/main/frontend/mobile/record.cljs
  41. 11 7
      src/main/frontend/modules/outliner/datascript.cljc
  42. 3 2
      src/main/frontend/modules/outliner/pipeline.cljs
  43. 6 1
      src/main/frontend/modules/shortcut/config.cljs
  44. 4 1
      src/main/frontend/modules/shortcut/dicts.cljc
  45. 1 1
      src/main/frontend/publishing/html.cljs
  46. 7 3
      src/main/frontend/search.cljs
  47. 7 2
      src/main/frontend/state.cljs
  48. 4 1
      src/main/frontend/ui.cljs
  49. 10 5
      src/test/frontend/extensions/calc_test.cljc
  50. 10 10
      src/test/frontend/handler/export_test.cljs
  51. 13 11
      yarn.lock

+ 1 - 0
.gitignore

@@ -44,3 +44,4 @@ ios/App/App/capacitor.config.json
 startup.png
 
 /src/test/docs
+~*~

+ 2 - 0
android/app/capacitor.build.gradle

@@ -11,7 +11,9 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
 dependencies {
     implementation project(':capacitor-app')
     implementation project(':capacitor-camera')
+    implementation project(':capacitor-clipboard')
     implementation project(':capacitor-filesystem')
+    implementation project(':capacitor-haptics')
     implementation project(':capacitor-keyboard')
     implementation project(':capacitor-share')
     implementation project(':capacitor-splash-screen')

+ 8 - 0
android/app/src/main/assets/capacitor.plugins.json

@@ -7,10 +7,18 @@
 		"pkg": "@capacitor/camera",
 		"classpath": "com.capacitorjs.plugins.camera.CameraPlugin"
 	},
+	{
+		"pkg": "@capacitor/clipboard",
+		"classpath": "com.capacitorjs.plugins.clipboard.ClipboardPlugin"
+	},
 	{
 		"pkg": "@capacitor/filesystem",
 		"classpath": "com.capacitorjs.plugins.filesystem.FilesystemPlugin"
 	},
+	{
+		"pkg": "@capacitor/haptics",
+		"classpath": "com.capacitorjs.plugins.haptics.HapticsPlugin"
+	},
 	{
 		"pkg": "@capacitor/keyboard",
 		"classpath": "com.capacitorjs.plugins.keyboard.KeyboardPlugin"

+ 6 - 0
android/capacitor.settings.gradle

@@ -8,9 +8,15 @@ project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/
 include ':capacitor-camera'
 project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
 
+include ':capacitor-clipboard'
+project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android')
+
 include ':capacitor-filesystem'
 project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
 
+include ':capacitor-haptics'
+project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android')
+
 include ':capacitor-keyboard'
 project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
 

+ 2 - 2
docs/contributing-to-translations.md

@@ -14,9 +14,9 @@ In order to run the commands in this doc, you will need to install
 ## Where to Contribute
 
 Language translations are in two files,
-[frontend/dicts.cljs](https://github.com/logseq/logseq/blob/master/src/main/frontend/dicts.cljs)
+[frontend/dicts.cljc](https://github.com/logseq/logseq/blob/master/src/main/frontend/dicts.cljc)
 and
-[shortcut/dict.cljs](https://github.com/logseq/logseq/blob/master/src/main/frontend/modules/shortcut/dicts.cljs).
+[shortcut/dict.cljc](https://github.com/logseq/logseq/blob/master/src/main/frontend/modules/shortcut/dicts.cljc).
 
 ## Language Overview
 

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

@@ -474,7 +474,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
@@ -528,7 +528,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@@ -550,7 +550,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.6.10;
+				MARKETING_VERSION = 0.7.0;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -576,7 +576,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.6.10;
+				MARKETING_VERSION = 0.7.0;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -601,7 +601,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.6.10;
+				MARKETING_VERSION = 0.7.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -628,7 +628,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.6.10;
+				MARKETING_VERSION = 0.7.0;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 1 - 0
ios/App/Podfile

@@ -13,6 +13,7 @@ def capacitor_pods
   pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
   pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
   pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
+  pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
   pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
   pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
   pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'

+ 3 - 1
package.json

@@ -72,6 +72,7 @@
         "@capacitor/clipboard": "^1.0.8",
         "@capacitor/core": "3.2.2",
         "@capacitor/filesystem": "1.0.6",
+        "@capacitor/haptics": "^1.1.4",
         "@capacitor/ios": "3.2.2",
         "@capacitor/keyboard": "^1.2.0",
         "@capacitor/share": "^1.1.2",
@@ -84,6 +85,7 @@
         "@sentry/tracing": "^6.18.2",
         "@tabler/icons": "1.54.0",
         "@tippyjs/react": "4.2.5",
+        "bignumber.js": "^9.0.2",
         "capacitor-voice-recorder": "2.1.0",
         "chokidar": "3.5.1",
         "chrono-node": "2.2.4",
@@ -102,7 +104,7 @@
         "ignore": "5.1.8",
         "is-svg": "4.3.0",
         "jszip": "3.5.0",
-        "mldoc": "1.3.3",
+        "mldoc": "1.3.9",
         "path": "0.12.7",
         "pixi-graph-fork": "0.2.0",
         "pixi.js": "6.2.0",

+ 1 - 1
resources/css/common.css

@@ -12,7 +12,7 @@
   --ls-headbar-height: 3rem;
   --ls-headbar-inner-top-padding: 0px;
   --ls-left-sidebar-width: 240px;
-  --ls-left-sidebar-sm-width: 70%;
+  --ls-left-sidebar-sm-width: 80%;
   --ls-left-sidebar-nav-btn-size: 38px;
 }
 

+ 18 - 17
src/electron/electron/core.cljs

@@ -81,7 +81,7 @@
      (.unregisterProtocol protocol FILE_LSP_SCHEME)
      (.unregisterProtocol protocol "assets")))
 
-(defn- handle-export-publish-assets [_event html custom-css-path repo-path asset-filenames output-path]
+(defn- handle-export-publish-assets [_event html custom-css-path export-css-path repo-path asset-filenames output-path]
   (p/let [app-path (. app getAppPath)
           asset-filenames (js->clj asset-filenames)
           root-dir (or output-path (handler/open-dir-dialog))]
@@ -89,7 +89,8 @@
       (let [static-dir (path/join root-dir "static")
             assets-from-dir (path/join repo-path "assets")
             assets-to-dir (path/join root-dir "assets")
-            index-html-path (path/join root-dir "index.html")]
+            index-html-path (path/join root-dir "index.html")
+            export-or-custom-css-path (if (fs/existsSync export-css-path) export-css-path custom-css-path)]
         (p/let [_ (. fs ensureDir static-dir)
                 _ (. fs ensureDir assets-to-dir)
                 _ (p/all (concat
@@ -99,35 +100,35 @@
                            (. fs copy (path/join app-path "404.html") (path/join root-dir "404.html"))]
 
                           (map
-                            (fn [filename]
-                              (-> (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename))
-                                  (p/catch
-                                      (fn [e]
-                                        (println (str "Failed to copy " (path/join assets-from-dir filename) " to " (path/join assets-to-dir filename)))
-                                        (js/console.error e)))))
-                            asset-filenames)
+                           (fn [filename]
+                             (-> (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename))
+                                 (p/catch
+                                  (fn [e]
+                                    (println (str "Failed to copy " (path/join assets-from-dir filename) " to " (path/join assets-to-dir filename)))
+                                    (js/console.error e)))))
+                           asset-filenames)
 
                           (map
-                            (fn [part]
-                              (. fs copy (path/join app-path part) (path/join static-dir part)))
-                            ["css" "fonts" "icons" "img" "js"])))
-                custom-css (. fs readFile custom-css-path)
-                _ (. fs writeFile (path/join static-dir "css" "custom.css") custom-css)
+                           (fn [part]
+                             (. fs copy (path/join app-path part) (path/join static-dir part)))
+                           ["css" "fonts" "icons" "img" "js"])))
+                export-css (. fs readFile export-or-custom-css-path)
+                _ (. fs writeFile (path/join static-dir "css" "export.css") export-css)
                 js-files ["main.js" "code-editor.js" "excalidraw.js"]
                 _ (p/all (map (fn [file]
                                 (. fs removeSync (path/join static-dir "js" file)))
-                           js-files))
+                              js-files))
                 _ (p/all (map (fn [file]
                                 (. fs moveSync
                                    (path/join static-dir "js" "publishing" file)
                                    (path/join static-dir "js" file)))
-                           js-files))
+                              js-files))
                 _ (. fs removeSync (path/join static-dir "js" "publishing"))
                 ;; remove source map files
                 ;; TODO: ugly, replace with ls-files and filter with ".map"
                 _ (p/all (map (fn [file]
                                 (. fs removeSync (path/join static-dir "js" (str file ".map"))))
-                           ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
+                              ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
           (. dialog showMessageBox (clj->js {:message (str "Export public pages and publish assets to " root-dir " successfully")})))))))
 
 (defn setup-app-manager!

+ 47 - 17
src/main/frontend/components/block.cljs

@@ -12,35 +12,37 @@
             [frontend.commands :as commands]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.lazy-editor :as lazy-editor]
-            [frontend.components.svg :as svg]
             [frontend.components.macro :as macro]
+            [frontend.components.plugins :as plugins]
+            [frontend.components.query-table :as query-table]
+            [frontend.components.svg :as svg]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
-            [frontend.db.utils :as db-utils]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.model :as model]
             [frontend.db.query-dsl :as query-dsl]
+            [frontend.db.utils :as db-utils]
             [frontend.extensions.highlight :as highlight]
             [frontend.extensions.latex :as latex]
-            [frontend.extensions.sci :as sci]
-            [frontend.extensions.pdf.assets :as pdf-assets]
-            [frontend.extensions.zotero :as zotero]
             [frontend.extensions.lightbox :as lightbox]
+            [frontend.extensions.pdf.assets :as pdf-assets]
+            [frontend.extensions.sci :as sci]
             [frontend.extensions.video.youtube :as youtube]
+            [frontend.extensions.zotero :as zotero]
             [frontend.format.block :as block]
             [frontend.format.mldoc :as mldoc]
-            [frontend.components.plugins :as plugins]
-            [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.block :as block-handler]
+            [frontend.handler.common :as common-handler]
             [frontend.handler.dnd :as dnd]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.plugin :as plugin-handler]
+            [frontend.handler.query :as query-handler]
             [frontend.handler.repeated :as repeated]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
-            [frontend.handler.query :as query-handler]
-            [frontend.handler.common :as common-handler]
+            [frontend.mobile.util :as mobile-util]
             [frontend.modules.outliner.tree :as tree]
             [frontend.search :as search]
             [frontend.security :as security]
@@ -50,8 +52,8 @@
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.util.clock :as clock]
-            [frontend.util.property :as property]
             [frontend.util.drawer :as drawer]
+            [frontend.util.property :as property]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.mldoc :as gp-mldoc]
@@ -63,9 +65,7 @@
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
-            [shadow.loader :as loader]
-            [frontend.components.query-table :as query-table]
-            [frontend.mobile.util :as mobile-util]))
+            [shadow.loader :as loader]))
 
 (defn safe-read-string
   ([s]
@@ -256,7 +256,8 @@
 
 (rum/defc audio-cp [src]
   [:audio {:src src
-           :controls true}])
+           :controls true
+           :on-touch-start #(util/stop %)}])
 
 (rum/defcs asset-link < rum/reactive
   (rum/local nil ::src)
@@ -2022,7 +2023,7 @@
                                (util/clear-selection!)))}
        (not slide?)
        (merge attrs))
-
+     
      [:<>
       [:div.flex.flex-row.justify-between
        [:div.flex-1
@@ -2084,6 +2085,21 @@
                        (swap! *hide-block-refs? not)))}
         block-refs-count]])))
 
+(rum/defc block-left-menu < rum/reactive
+  [_config {:block/keys [uuid] :as _block}]
+  [:div.block-left-menu.flex.bg-base-2.rounded-r-md.mr-1
+   [:div.commands-button.w-0.rounded-r-md
+    {:id (str "block-left-menu-" uuid)}
+    [:div.indent (ui/icon "indent-increase" {:style {:fontSize 16}})]]])
+
+(rum/defc block-right-menu < rum/reactive
+  [_config {:block/keys [uuid] :as _block}]
+  [:div.block-right-menu.flex.bg-base-2.rounded-md.ml-1
+   [:div.commands-button.w-0.flex.flew-col.rounded-md
+    {:id (str "block-right-menu-" uuid)}
+    [:div.more (ui/icon "dots-circle-horizontal" {:style {:fontSize 16}})]
+    [:div.outdent (ui/icon "indent-decrease" {:style {:fontSize 16}})]]])
+
 (rum/defcs block-content-or-editor < rum/reactive
   (rum/local true :hide-block-refs?)
   [state config {:block/keys [uuid format] :as block} edit-input-id block-id heading-level edit?]
@@ -2390,6 +2406,8 @@
                      (model/sub-block-direct-children repo uuid))
                    children)
         breadcrumb-show? (:breadcrumb-show? config)
+        *show-left-menu? (::show-block-left-menu? state)
+        *show-right-menu? (::show-block-right-menu? state)
         slide? (boolean (:slide? config))
         doc-mode? (:document/mode? config)
         embed? (:embed? config)
@@ -2443,20 +2461,32 @@
 
      [:div.flex.flex-row.pr-2
       {:class (if (and heading? (seq (:block/title block))) "items-baseline" "")
+       :on-touch-start block-handler/on-touch-start
+       :on-touch-move (fn [event]
+                        (block-handler/on-touch-move event block uuid *show-left-menu? *show-right-menu?))
+       :on-touch-end (fn [event]
+                       (block-handler/on-touch-end event block uuid *show-left-menu? *show-right-menu?))
+       :on-touch-cancel block-handler/on-touch-cancel 
        :on-mouse-over (fn [e]
                         (block-mouse-over uuid e *control-show? block-id doc-mode?))
        :on-mouse-leave (fn [e]
                          (block-mouse-leave e *control-show? block-id doc-mode?))}
       (when (not slide?)
         (block-control config block uuid block-id collapsed? *control-show? edit?))
-
-      (block-content-or-editor config block edit-input-id block-id heading-level edit?)]
+      
+      (when @*show-left-menu?
+        (block-left-menu config block))
+      (block-content-or-editor config block edit-input-id block-id heading-level edit?)
+      (when @*show-right-menu?
+        (block-right-menu config block))]
 
      (block-children config children collapsed?)
 
      (dnd-separator-wrapper block block-id slide? false false)]))
 
 (rum/defcs block-container < rum/reactive
+  (rum/local false ::show-block-left-menu?) 
+  (rum/local false ::show-block-right-menu?)
   {:init (fn [state]
            (let [[config block] (:rum/args state)
                  block-id (:block/uuid block)]

+ 40 - 1
src/main/frontend/components/block.css

@@ -185,6 +185,44 @@
   }
 }
 
+.block-left-menu {
+    background-color: var(--ls-secondary-background-color);
+    background: linear-gradient(90deg, var(--ls-primary-background-color) 0%, var(--ls-secondary-background-color) 100%);
+    
+    .commands-button {
+        overflow: hidden;
+        max-width: 50px;
+        text-align: center;
+        margin: auto 0;
+
+        .indent {
+            opacity: 30%;
+        }
+    }
+}
+
+.block-right-menu {
+    background-color: var(--ls-secondary-background-color);
+    /* background: linear-gradient(-90deg, var(--ls-primary-background-color) 0%, var(--ls-secondary-background-color) 100%); */
+
+    .commands-button {
+        overflow: hidden;
+        max-width: 80px;
+        text-align: center;
+        margin: auto 0;
+
+        .outdent {
+            margin: 0 12px;
+            opacity: 30%;
+        }
+
+        .more {
+            margin: 0 12px;
+            opacity: 30%;
+        }
+    }
+}
+
 .block-ref {
   border-bottom: 0.5px solid;
   border-bottom-color: var(--ls-block-ref-link-text-color);
@@ -586,7 +624,8 @@ a.cloze-revealed {
 
 html.is-native-ios {
     audio {
-        width: 300px;
+        width: 100%;
+        max-width: 350px;
     }
 }
 

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

@@ -281,7 +281,6 @@
         max-width 300
         offset-top 24
         vw-height js/window.innerHeight
-        vw-width js/window.innerWidth
         to-max-height (if (and (seq rect) (> vw-height max-height))
                         (let [delta-height (- vw-height (+ (:top rect) top offset-top))]
                           (if (< delta-height max-height)
@@ -301,15 +300,14 @@
                                    (when (> ofx 0)
                                      (set! (.-transform (.-style el)) (str "translateX(-" (+ ofx 20) "px)")))))))
                            [right-sidebar? editing-key])
-        x-overflow-vw? (when (and (seq rect) (> vw-width max-width))
-                         (let [delta-width (- vw-width (+ (:left rect) left))]
-                           (< delta-width (* max-width 0.5))))
+        y-overflow-vh? (< to-max-height 130)
+        to-max-height (if y-overflow-vh? max-height to-max-height)
         pos-rect (when (and (seq rect) editing-key)
                    (:rect (cursor/get-caret-pos (state/get-input))))
         y-diff (when pos-rect (- (:height pos-rect) (:height rect)))]
     [:div.absolute.rounded-md.shadow-lg.absolute-modal
      {:ref *el
-      :class (if x-overflow-vw? "is-overflow-vw-x" "")
+      :class (if y-overflow-vh? "is-overflow-vh-y" "")
       :on-mouse-down (fn [e]
                        (.stopPropagation e))
       :style (merge
@@ -527,6 +525,7 @@
 
      (when (= (state/sub :editor/record-status) "RECORDING")
        [:div#audio-record-toolbar
+        {:style {:bottom (+ @util/keyboard-height 45)}}
         (footer/audio-record-cp)])
 
      (ui/ls-textarea

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

@@ -1,7 +1,6 @@
 #audio-record-toolbar {
     position: fixed;
     background-color: var(--ls-secondary-background-color);
-    bottom: 45px;
     width: 88px;
     justify-content: left;
     left: 5px;
@@ -36,6 +35,10 @@
   &.is-overflow-vw-x {
     transform: translateX(calc(-100% + 1rem));
   }
+
+  &.is-overflow-vh-y {
+    transform: translateY(calc(-100% - 2rem));
+  }
 }
 
 .is-mobile {

+ 23 - 16
src/main/frontend/components/header.cljs

@@ -144,7 +144,11 @@
                                (or (empty? repos)
                                    (nil? (state/sub :git/current-repo)))
                                (not (mobile-util/native-platform?))
-                               (not config/publishing?))]
+                               (not config/publishing?))
+        left-menu (left-menu-button {:on-click (fn []
+                                       (open-fn)
+                                       (state/set-left-sidebar-open!
+                                        (not (:ui/left-sidebar-open? @state/state))))})]
     [:div.cp__header#head
      {:class           (util/classnames [{:electron-mac   electron-mac?
                                           :native-ios     (mobile-util/native-ios?)
@@ -156,19 +160,23 @@
                              (js/window.apis.toggleMaxOrMinActiveWindow))))
       :style           {:fontSize  50}}
      [:div.l.flex
-      (left-menu-button {:on-click (fn []
-                                     (open-fn)
-                                     (state/set-left-sidebar-open!
-                                      (not (:ui/left-sidebar-open? @state/state))))})
-
-      (when current-repo ;; this is for the Search button
-        (ui/with-shortcut :go/search "right"
-          [:a.button#search-button
-           {:on-click #(do (when (or (mobile-util/native-android?)
-                                     (mobile-util/native-iphone?))
-                             (state/set-left-sidebar-open! false))
-                           (state/pub-event! [:go/search]))}
-           (ui/icon "search" {:style {:fontSize ui/icon-size}})]))]
+      (when-not (mobile-util/native-platform?)
+        [left-menu
+         (when current-repo ;; this is for the Search button
+           (ui/with-shortcut :go/search "right"
+             [:a.button#search-button
+              {:on-click #(do (when (or (mobile-util/native-android?)
+                                        (mobile-util/native-iphone?))
+                                (state/set-left-sidebar-open! false))
+                              (state/pub-event! [:go/search]))}
+              (ui/icon "search" {:style {:fontSize ui/icon-size}})]))])
+      (when (mobile-util/native-platform?)
+        (if (state/home?)
+          left-menu
+          (ui/with-shortcut :go/backward "bottom"
+            [:a.it.navigation.nav-left.button
+             {:title "Go back" :on-click #(js/window.history.back)}
+             (ui/icon "chevron-left" {:style {:fontSize 25}})])))]
 
      [:div.r.flex
       (when-not file-sync-handler/hiding-login&file-sync
@@ -182,8 +190,7 @@
       (when (not= (state/get-current-route) :home)
         (home-button))
 
-      (when (or (util/electron?)
-                (mobile-util/native-ios?))
+      (when (util/electron?)
         (back-and-forward))
 
       (when-not (mobile-util/native-platform?)

+ 9 - 19
src/main/frontend/components/header.css

@@ -1,5 +1,5 @@
 .cp__header {
-  @apply shadow z-10;
+  @apply z-10;  
   -webkit-app-region: drag;
 
   padding-top: var(--ls-headbar-inner-top-padding);
@@ -189,10 +189,6 @@ a.button {
   &:hover, &.active {
     opacity: 1;
     background: none;
-
-    @screen md {
-        background: var(--ls-tertiary-background-color);
-    }
   }
 
   &:active {
@@ -239,8 +235,14 @@ html.is-native-ipad {
          }
     }
 
-    .cp__header > .r {
-        display: flex;
+     .cp__header {
+         > .r {
+             display: flex;
+         }
+
+         a.button {
+             opacity: 1;
+         }
     }
 }
 
@@ -316,10 +318,6 @@ html.is-native-iphone {
     @media (orientation: landscape) {
         --ls-headbar-inner-top-padding: 8px;
         --ls-headbar-height: 2.5rem;
-
-        .cp__header {
-            @apply shadow z-10;
-        }
     }
 }
 
@@ -332,10 +330,6 @@ html.is-native-iphone-without-notch {
 
         --ls-headbar-inner-top-padding: 0px;
         --ls-headbar-height: 2.5rem;
-
-        .cp__header {
-            @apply shadow z-10;
-        }
     }
 }
 
@@ -345,9 +339,5 @@ html.is-zoomed-native-ios {
      @media (orientation: landscape) {
         --ls-headbar-inner-top-padding: 8px;
         --ls-headbar-height: 2.5rem;
-
-        .cp__header {
-            @apply shadow z-10;
-        }
     }
 }

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

@@ -123,7 +123,8 @@
                                   references (->> (concat ref-pages references)
                                                   (remove nil?)
                                                   (distinct))]
-                              (state/set-modal! (filter-dialog filters-atom references page-name))))}
+                              (state/set-modal! (filter-dialog filters-atom references page-name)
+                                                {:center? true})))}
                (ui/icon "filter" {:class (cond
                                            (empty? filter-state)
                                            ""

+ 9 - 0
src/main/frontend/components/settings.cljs

@@ -151,6 +151,14 @@
      :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
      :-for         "customize_css"}))
 
+(defn edit-export-css []
+  (row-with-button-action
+   {:left-label   (t :settings-page/export-theme)
+    :button-label (t :settings-page/edit-export-css)
+    :href         (rfe/href :file {:path (config/get-export-css-path)})
+    :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
+    :-for         "customize_css"}))
+
 (defn show-brackets-row [t show-brackets?]
   [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
    [:label.block.text-sm.font-medium.leading-5.opacity-70
@@ -518,6 +526,7 @@
      (theme-modes-row t switch-theme system-theme? dark?)
      (when current-repo (edit-config-edn))
      (when current-repo (edit-custom-css))
+     (when current-repo (edit-export-css))
      (keyboard-shortcuts-row t)]))
 
 (rum/defcs settings-editor < rum/reactive

+ 26 - 20
src/main/frontend/components/sidebar.cljs

@@ -26,6 +26,7 @@
             [frontend.handler.user :as user-handler]
             [frontend.handler.common :as common-handler]
             [frontend.mixins :as mixins]
+            [frontend.mobile.action-bar :as action-bar]
             [frontend.mobile.footer :as footer]
             [frontend.mobile.util :as mobile-util]
             [frontend.mobile.mobile-bar :refer [mobile-bar]]
@@ -35,8 +36,8 @@
             [frontend.util :as util]
             [goog.dom :as gdom]
             [goog.object :as gobj]
-            [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [reitit.frontend.easy :as rfe]))
 
 (rum/defc nav-content-item
   [name {:keys [class]} child]
@@ -249,16 +250,17 @@
 
       (when (and left-sidebar-open? (not config/publishing?)) (recent-pages t))
 
-      [:nav.px-2 {:aria-label "Sidebar"
-                  :class      "new-page"}
-       (when-not config/publishing?
-         [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
-          {:on-click (fn []
-                       (and (util/sm-breakpoint?)
-                            (state/toggle-left-sidebar!))
-                       (state/pub-event! [:go/search]))}
-          (ui/icon "circle-plus mr-3" {:style {:font-size 20}})
-          [:span.flex-1 (t :right-side-bar/new-page)]])]]]))
+      (when-not (mobile-util/native-platform?)
+       [:nav.px-2 {:aria-label "Sidebar"
+                   :class      "new-page"}
+        (when-not config/publishing?
+          [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
+           {:on-click (fn []
+                        (and (util/sm-breakpoint?)
+                             (state/toggle-left-sidebar!))
+                        (state/pub-event! [:go/search]))}
+           (ui/icon "circle-plus mr-3" {:style {:font-size 20}})
+           [:span.flex-1 (t :right-side-bar/new-page)]])])]]))
 
 (rum/defc left-sidebar < rum/reactive
   [{:keys [left-sidebar-open? route-match]}]
@@ -282,7 +284,7 @@
                                 (editor-handler/upload-asset id files format editor-handler/*asset-uploading? true))))})
                   (common-handler/listen-to-scroll! element))
                 state)}
-  [{:keys [route-match global-graph-pages? route-name indexeddb-support? db-restoring? main-content]}]
+  [{:keys [route-match global-graph-pages? route-name indexeddb-support? db-restoring? main-content show-action-bar?]}]
   (let [left-sidebar-open? (state/sub :ui/left-sidebar-open?)
         onboarding-and-home? (and (or (nil? (state/get-current-repo)) (config/demo-graph?))
                                   (not config/publishing?)
@@ -296,6 +298,9 @@
 
      [:div#main-content-container.scrollbar-spacing.w-full.flex.justify-center.flex-row
 
+      (when show-action-bar?
+        (action-bar/action-bar))
+      
       [:div.cp__sidebar-main-content
        {:data-is-global-graph-pages global-graph-pages?
         :data-is-full-width         (or global-graph-pages?
@@ -307,7 +312,7 @@
        (when (and (not (mobile-util/native-platform?))
                   (contains? #{:page :home} route-name))
          (widgets/demo-graph-alert))
-
+       
        (cond
          (not indexeddb-support?)
          nil
@@ -379,10 +384,9 @@
                                               [page :page])]
                      (state/sidebar-add-block! current-repo db-id block-type)))
                  (reset! sidebar-inited? true))))
-           state)
-   :did-mount (fn [state]
-                (state/set-state! :mobile/show-tabbar? true)
-                state)}
+           (when (state/mobile?)
+                  (state/set-state! :mobile/show-tabbar? true))
+           state)}
   []
   (let [default-home (get-default-home-if-valid)
         current-repo (state/sub :git/current-repo)
@@ -513,7 +517,8 @@
         home? (= :home route-name)
         edit? (:editor/editing? @state/state)
         default-home (get-default-home-if-valid)
-        logged? (user-handler/logged-in?)]
+        logged? (user-handler/logged-in?)
+        show-action-bar? (state/sub :mobile/show-action-bar?)]
     (theme/container
      {:t             t
       :theme         theme
@@ -555,7 +560,8 @@
                :indexeddb-support?  indexeddb-support?
                :light?              light?
                :db-restoring?       db-restoring?
-               :main-content        main-content})]
+               :main-content        main-content
+               :show-action-bar?    show-action-bar?})]
 
        (right-sidebar/sidebar)
 

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

@@ -277,6 +277,7 @@
 (defonce recycle-dir ".recycle")
 (def config-file "config.edn")
 (def custom-css-file "custom.css")
+(def export-css-file "export.css")
 (def custom-js-file "custom.js")
 (def metadata-file "metadata.edn")
 (def pages-metadata-file "pages-metadata.edn")
@@ -393,6 +394,15 @@
      (get-file-path repo
                     (str app-name "/" custom-css-file)))))
 
+(defn get-export-css-path
+  ([]
+   (get-export-css-path (state/get-current-repo)))
+  ([repo]
+   (when repo
+     (get-file-path repo
+                    (str app-name "/" export-css-file)))))
+
+
 (defn get-custom-js-path
   ([]
    (get-custom-js-path (state/get-current-repo)))

+ 4 - 10
src/main/frontend/db.cljs

@@ -116,8 +116,8 @@
 
 ;; only save when user's idle
 
-;; TODO: pass as a parameter
-(defonce *sync-search-indice-f (atom nil))
+(def *db-listener (atom nil))
+
 (defn- repo-listen-to-tx!
   [repo conn]
   (d/listen! conn :persistence
@@ -138,14 +138,8 @@
                      (state/set-last-transact-time! repo (util/time-ms))
                      (persist-if-idle! repo)))
 
-                 ;; rebuild search indices
-                 (let [data (:tx-data tx-report)
-                       datoms (filter
-                               (fn [datom]
-                                 (contains? #{:block/name :block/content} (:a datom)))
-                               data)]
-                   (when-let [f @*sync-search-indice-f]
-                     (f datoms)))))))
+                 (when-let [db-listener @*db-listener]
+                   (db-listener repo tx-report))))))
 
 (defn listen-and-persist!
   [repo]

+ 11 - 2
src/main/frontend/dicts.cljc

@@ -147,8 +147,10 @@
         :settings-page/git-commit-delay "Git auto commit seconds"
         :settings-page/edit-config-edn "Edit config.edn"
         :settings-page/edit-custom-css "Edit custom.css"
+        :settings-page/edit-export-css "Edit export.css"
         :settings-page/custom-configuration "Custom configuration"
         :settings-page/custom-theme "Custom theme"
+        :settings-page/export-theme "Export theme"
         :settings-page/show-brackets "Show brackets"
         :settings-page/spell-checker "Spell checker"
         :settings-page/auto-updater "Auto updater"
@@ -2171,7 +2173,13 @@
            :settings-page/network-proxy "Proxy de Rede"
 
            :file-sync/other-user-graph "O gráfico local atual é obrigado ao gráfico remoto de outro usuário. Portanto, não consigo iniciar a sincronização."
-           :file-sync/graph-deleted "O gráfico remoto atual foi excluído"}
+           :file-sync/graph-deleted "O gráfico remoto atual foi excluído"
+           
+           :page/copy-page-url "Copiar URL da página"
+           :page/file-sync-versions "Versões da página"
+           :plugin/not-installed "Não instalado"
+           :tutorial/dummy-notes "dummy-notes-en.md"
+           :tutorial/text "tutorial-en.md"}
 
    :pt-PT {:on-boarding/demo-graph "Isto é um grafo de demonstração, nenhuma mudança será guardada até abrir uma pasta local."
            :on-boarding/add-graph "Adicionar grafo"
@@ -3390,7 +3398,8 @@
         :select.graph/add-graph "Sì, aggiungi un nuovo grafo"
 
         :file-sync/other-user-graph "Il grafo locale attuale è associato al grafo remoto di un altro utente. Non è quindi possibile avviare la sincronizzazione."
-        :file-sync/graph-deleted "Il grafo attuale è stato eliminato"}
+        :file-sync/graph-deleted "Il grafo attuale è stato eliminato"
+        :settings-page/enable-encryption "Crittografia"}
 
    :tr {:tutorial/text #?(:cljs (rc/inline "tutorial-tr.md")
                                 :default "tutorial-tr.md")

+ 23 - 19
src/main/frontend/extensions/calc.cljc

@@ -3,6 +3,9 @@
   (:require [clojure.edn :as edn]
             [clojure.string :as str]
             [frontend.util :as util]
+
+            [bignumber.js :as bn]
+
             #?(:clj [clojure.java.io :as io])
             #?(:cljs [shadow.resource :as rc])
             #?(:cljs [rum.core :as rum])
@@ -24,35 +27,36 @@
 
 (defn new-env [] (atom {}))
 
+;; TODO: Set DECIMAL_PLACES https://mikemcl.github.io/bignumber.js/#decimal-places
+
 (defn eval* [env ast]
   (insta/transform
-   {:number     (comp edn/read-string #(str/replace % "," ""))
-    :percent    (fn percent [a] (/ a 100.00))
-    :scientific edn/read-string
-    :negterm    (fn neg [a] (- a))
+   {:number     (comp bn/BigNumber #(str/replace % "," ""))
+    :percent    (fn percent [a] (-> a (.dividedBy 100.00)))
+    :scientific (comp bn/BigNumber edn/read-string)
+    :negterm    (fn neg [a] (-> a (.negated)))
     :expr       identity
-    :add        +
-    :sub        -
-    :mul        *
-    :div        /
-    :pow        (fn pow [a b]
-                  #?(:clj (java.lang.Math/pow a b) :cljs (js/Math.pow a b)))
+    :add        (fn add [a b] (-> a (.plus b)))
+    :sub        (fn sub [a b] (-> a (.minus b)))
+    :mul        (fn mul [a b] (-> a (.multipliedBy b)))
+    :div        (fn div [a b] (-> a (.dividedBy b)))
+    :pow        (fn pow [a b] (-> a (.exponentiatedBy b)))
     :log        (fn log [a]
-                  #?(:clj (java.lang.Math/log10 a) :cljs (js/Math.log10 a)))
+                  #?(:clj (java.lang.Math/log10 a) :cljs (bn/BigNumber (js/Math.log10 a))))
     :ln         (fn ln [a]
-                  #?(:clj (java.lang.Math/log a) :cljs (js/Math.log a)))
+                  #?(:clj (java.lang.Math/log a) :cljs (bn/BigNumber (js/Math.log a))))
     :sin        (fn sin [a]
-                  #?(:clj (java.lang.Math/sin a) :cljs (js/Math.sin a)))
+                  #?(:clj (java.lang.Math/sin a) :cljs (bn/BigNumber(js/Math.sin a))))
     :cos        (fn cos [a]
-                  #?(:clj (java.lang.Math/cos a) :cljs (js/Math.cos a)))
+                  #?(:clj (java.lang.Math/cos a) :cljs (bn/BigNumber(js/Math.cos a))))
     :tan        (fn tan [a]
-                  #?(:clj (java.lang.Math/tan a) :cljs (js/Math.tan a)))
+                  #?(:clj (java.lang.Math/tan a) :cljs (bn/BigNumber(js/Math.tan a))))
     :atan       (fn atan [a]
-                  #?(:clj (java.lang.Math/atan a) :cljs (js/Math.atan a)))
+                  #?(:clj (java.lang.Math/atan a) :cljs (bn/BigNumber(js/Math.atan a))))
     :asin       (fn asin [a]
-                  #?(:clj (java.lang.Math/asin a) :cljs (js/Math.asin a)))
+                  #?(:clj (java.lang.Math/asin a) :cljs (bn/BigNumber(js/Math.asin a))))
     :acos       (fn acos [a]
-                  #?(:clj (java.lang.Math/acos a) :cljs (js/Math.acos a)))
+                  #?(:clj (java.lang.Math/acos a) :cljs (bn/BigNumber(js/Math.acos a))))
     :assignment (fn assign! [var val]
                   (swap! env assoc var val)
                   val)
@@ -101,4 +105,4 @@
            [:span (cond
                     (nil? line)           ""
                     (failure? line) "?"
-                    :else                 line)]])])))
+                    :else                 (str line))]])])))

+ 3 - 0
src/main/frontend/extensions/code.cljs

@@ -265,6 +265,9 @@
                              (state/clear-selection!)
                              (when-let [block (and (:block/uuid config) (into {} (db/get-block-by-uuid (:block/uuid config))))]
                                (state/set-editing! id (.getValue editor) block nil false))))
+        (.addEventListener element "touchstart"
+                           (fn [e]
+                             (.stopPropagation e)))
         (.save editor)
         (.refresh editor)
         (when default-open?

+ 179 - 124
src/main/frontend/extensions/html_parser.cljs

@@ -20,152 +20,207 @@
 
                (str (hiccup-without-style hiccup))))
 
+(def allowed-tags
+  #{:address, :article, :aside, :footer, :header,
+    :h1, :h2, :h3, :h4, :h5, :h6, :hgroup,
+    :main, :nav, :section,
+    :blockquote, :dd, :div, :dl, :dt, :figcaption, :figure,
+    :hr, :li, :ol, :p, :pre, :ul,
+    :a, :abbr, :b, :bdi, :bdo, :br, :cite, :code, :data, :dfn,
+    :em, :i, :kbd, :mark, :q,
+    :rb, :rp, :rt, :rtc, :ruby,
+    :s, :samp, :small, :span, :strong, :sub, :sup, :time, :u, :var, :wbr,
+    :caption, :col, :colgroup, :table, :tbody, :td, :tfoot, :th,
+    :thead, :tr
+    :body :html})
+
 (defn ^:large-vars/cleanup-todo hiccup->doc-inner
-  [format hiccup]
-  (let [transform-fn (fn [hiccup]
-                       (hiccup->doc-inner format hiccup))
-        block-pattern (config/get-block-pattern format)
-        map-join (fn [children] (apply str (map transform-fn children)))
+  [format hiccup opts]
+  (let [transform-fn (fn [hiccup opts]
+                       (hiccup->doc-inner format hiccup opts))
+        block-pattern (if (= format :markdown)
+                        "#"
+                        (config/get-block-pattern format))
+        map-join (fn [children] (apply str (map #(transform-fn % opts) children)))
         block-transform (fn [level children]
                           (str (apply str (repeat level block-pattern))
                                " "
-                               (->> (map transform-fn children)
+                               (->> (map #(transform-fn % opts) children)
                                     (string/join " "))
                                "\n"))
-        emphasis-transform (fn [tag _attrs children]
-                             (let [pattern (cond
+        emphasis-transform (fn [tag attrs children]
+                             (let [style (:style attrs)
+                                   [bold? italic? underline? strike-through? mark?]
+                                   (when style
+                                     [(or (string/includes? style "font-weight:700")
+                                          (string/includes? style "font-weight:600"))
+                                      (string/includes? style "font-style:italic")
+                                      (string/includes? style "text-decoration:underline")
+                                      (string/includes? style "text-decoration:line-through")
+                                      (string/includes? style "background-color:#")])
+                                   pattern (cond
                                              (contains? #{:b :strong} tag)
-                                             (config/get-bold format)
+                                             (when-not (and style (string/includes? style "font-weight:normal"))
+                                               (config/get-bold format))
                                              (contains? #{:i :em} tag)
-                                             (config/get-italic format)
+                                             (when-not (and style (string/includes? style "font-style:normal"))
+                                               (config/get-italic format))
                                              (contains? #{:ins} tag)
-                                             (config/get-underline format)
+                                             (when-not (and style (string/includes? style "text-decoration:normal"))
+                                               (config/get-underline format))
                                              (contains? #{:del} tag)
-                                             (config/get-strike-through format)
+                                             (when-not (and style (string/includes? style "text-decoration:normal"))
+                                               (config/get-strike-through format))
                                              (contains? #{:mark} tag)
-                                             (config/get-highlight format)
+                                             (when-not (and style (string/includes? style "background-color:transparent"))
+                                               (config/get-highlight format))
+                                             (and (contains? #{:span} tag)
+                                                  (not (every? string/blank? children)))
+                                             (remove nil?
+                                                     [(when bold? (config/get-bold format))
+                                                      (when italic? (config/get-italic format))
+                                                      (when underline? (config/get-underline format))
+                                                      (when strike-through? (config/get-strike-through format))
+                                                      (when mark? (config/get-highlight format))])
                                              :else
-                                             nil)]
-                               (str pattern (map-join children) pattern)))
+                                             nil)
+                                   children' (map-join children)]
+                               (when-not (string/blank? children')
+                                 (str (if (string? pattern) pattern (apply str pattern))
+                                      children'
+                                      (if (string? pattern) pattern (apply str (reverse pattern)))))))
         wrapper (fn [tag content]
-                  (cond
-                    (contains? #{:p :hr :ul :ol :dl :table :pre :blockquote :aside :canvas
-                                 :center :figure :figcaption :fieldset :div :footer
-                                 :header} tag)
-                    (str "\n\n" content "\n\n")
+                  (let [content (cond
+                                  (not (contains? allowed-tags tag))
+                                  nil
+
+                                  (contains? #{:comment :head :style :xml} tag)
+                                  nil
+
+                                  (and (= tag :p) (:in-table? opts))
+                                  content
 
-                    (contains? #{:thead :tr :li} tag)
-                    (str content "\n")
+                                  (contains? #{:p :hr :ul :ol :dl :table :pre :blockquote :aside :canvas
+                                               :center :figure :figcaption :fieldset :div :footer
+                                               :header} tag)
+                                  (str "\n\n" content "\n\n")
 
-                    :else
-                    content))
+                                  (contains? #{:thead :tr :li} tag)
+                                  (str content "\n")
+
+                                  :else
+                                  content)]
+                    (some-> content
+                            (string/replace "<!--StartFragment-->" "")
+                            (string/replace "<!--EndFragment-->" ""))))
         single-hiccup-transform
         (fn [x]
           (cond
             (vector? x)
             (let [[tag attrs & children] x
                   result (match tag
-                                :head nil
-                                :h1 (block-transform 1 children)
-                                :h2 (block-transform 2 children)
-                                :h3 (block-transform 3 children)
-                                :h4 (block-transform 4 children)
-                                :h5 (block-transform 5 children)
-                                :h6 (block-transform 6 children)
-                                :a (let [href (:href attrs)
-                                         label (map-join children)
-                                         has-img-tag? (util/safe-re-find #"\[:img" (str x))]
-                                     (if has-img-tag?
-                                       (export-hiccup x)
-                                       (case format
-                                         :markdown (util/format "[%s](%s)" label href)
-                                         :org (util/format "[[%s][%s]]" href label)
-                                         nil)))
-                                :img (let [src (:src attrs)
-                                           alt (:alt attrs)]
-                                       (case format
-                                         :markdown (util/format "![%s](%s)" alt src)
-                                         :org (util/format "[[%s][%s]]" src alt)
-                                         nil))
-                                :p (util/format "%s"
-                                                (map-join children))
-
-                                :hr (config/get-hr format)
-
-                                (_ :guard #(contains? #{:b :strong
-                                                        :i :em
-                                                        :ins
-                                                        :del
-                                                        :mark} %))
-                                (emphasis-transform tag attrs children)
-
-                                :code (if @*inside-pre?
-                                        (map-join children)
-                                        (let [pattern (config/get-code format)]
-                                          (str " "
-                                               (str pattern (first children) pattern)
-                                               " ")))
-
-                                :pre
-                                (do
-                                  (reset! *inside-pre? true)
-                                  (let [content (string/trim (doall (map-join children)))]
-                                    (reset! *inside-pre? false)
-                                    (case format
-                                      :markdown (if (util/starts-with? content "```")
-                                                  content
-                                                  (str "```\n" content "\n```"))
-                                      :org (if (util/starts-with? content "#+BEGIN_SRC")
+                           :head nil
+                           :h1 (block-transform 1 children)
+                           :h2 (block-transform 2 children)
+                           :h3 (block-transform 3 children)
+                           :h4 (block-transform 4 children)
+                           :h5 (block-transform 5 children)
+                           :h6 (block-transform 6 children)
+                           :a (let [href (:href attrs)
+                                    label (or (map-join children) "")
+                                    has-img-tag? (util/safe-re-find #"\[:img" (str x))]
+                                (if has-img-tag?
+                                  (export-hiccup x)
+                                  (case format
+                                    :markdown (util/format "[%s](%s)" label href)
+                                    :org (util/format "[[%s][%s]]" href label)
+                                    nil)))
+                           :img (let [src (:src attrs)
+                                      alt (or (:alt attrs) "")]
+                                  (case format
+                                    :markdown (util/format "![%s](%s)" alt src)
+                                    :org (util/format "[[%s][%s]]" src alt)
+                                    nil))
+                           :p (util/format "%s"
+                                           (map-join children))
+
+                           :hr (config/get-hr format)
+
+                           (_ :guard #(contains? #{:b :strong
+                                                   :i :em
+                                                   :ins
+                                                   :del
+                                                   :mark
+                                                   :span} %))
+                           (emphasis-transform tag attrs children)
+
+                           :code (if @*inside-pre?
+                                   (map-join children)
+                                   (let [pattern (config/get-code format)]
+                                     (str " "
+                                          (str pattern (first children) pattern)
+                                          " ")))
+
+                           :pre
+                           (do
+                             (reset! *inside-pre? true)
+                             (let [content (string/trim (doall (map-join children)))]
+                               (reset! *inside-pre? false)
+                               (case format
+                                 :markdown (if (util/starts-with? content "```")
                                              content
-                                             (util/format "#+BEGIN_SRC\n%s\n#+END_SRC" content))
-                                      nil)))
-
-                                :blockquote
-                                (case format
-                                  :markdown (str "> " (map-join children))
-                                  :org (util/format "#+BEGIN_QUOTE\n%s\n#+END_QUOTE" (map-join children))
-                                  nil)
-
-                                :li
-                                (str "- " (map-join children))
-
-                                :dt
-                                (case format
-                                  :org (str "- " (map-join children) " ")
-                                  :markdown (str (map-join children) "\n")
-                                  nil)
-
-                                :dd
-                                (case format
-                                  :markdown (str ": " (map-join children) "\n")
-                                  :org (str ":: " (map-join children) "\n")
-                                  nil)
-
-                                :thead
-                                (case format
-                                  :markdown (let [columns (count (last (first children)))]
-                                              (str
-                                               (map-join children)
-                                               (str "| " (string/join " | "
-                                                                      (repeat columns "----"))
-                                                    " |")))
-                                  :org (let [columns (count (last (first children)))]
+                                             (str "```\n" content "\n```"))
+                                 :org (if (util/starts-with? content "#+BEGIN_SRC")
+                                        content
+                                        (util/format "#+BEGIN_SRC\n%s\n#+END_SRC" content))
+                                 nil)))
+
+                           :blockquote
+                           (case format
+                             :markdown (str "> " (map-join children))
+                             :org (util/format "#+BEGIN_QUOTE\n%s\n#+END_QUOTE" (map-join children))
+                             nil)
+
+                           :li
+                           (str "- " (map-join children))
+
+                           :dt
+                           (case format
+                             :org (str "- " (map-join children) " ")
+                             :markdown (str (map-join children) "\n")
+                             nil)
+
+                           :dd
+                           (case format
+                             :markdown (str ": " (map-join children) "\n")
+                             :org (str ":: " (map-join children) "\n")
+                             nil)
+
+                           :thead
+                           (case format
+                             :markdown (let [columns (count (last (first children)))]
                                          (str
                                           (map-join children)
-                                          (str "|" (string/join "+"
-                                                                (repeat columns "----"))
-                                               "|")))
-                                  nil)
-                                :tr
-                                (str "| "
-                                     (->> (map transform-fn children)
-                                          (string/join " | "))
-                                     " |")
-
-                                (_ :guard #(contains? #{:aside :center :figure :figcaption :fieldset :footer :header} %))
-                                (export-hiccup x)
-
-                                :else (map-join children))]
+                                          (str "| " (string/join " | "
+                                                                 (repeat columns "----"))
+                                               " |")))
+                             :org (let [columns (count (last (first children)))]
+                                    (str
+                                     (map-join children)
+                                     (str "|" (string/join "+"
+                                                           (repeat columns "----"))
+                                          "|")))
+                             nil)
+                           :tr
+                           (str "| "
+                                (->> (map #(transform-fn % (assoc opts :in-table? true)) children)
+                                     (string/join " | "))
+                                " |")
+
+                           (_ :guard #(contains? #{:aside :center :figure :figcaption :fieldset :footer :header} %))
+                           (export-hiccup x)
+
+                           :else (map-join children))]
               (wrapper tag result))
 
             (string? x)
@@ -181,7 +236,7 @@
 
 (defn hiccup->doc
   [format hiccup]
-  (let [s (hiccup->doc-inner format hiccup)]
+  (let [s (hiccup->doc-inner format hiccup {})]
     (if (string/blank? s)
       ""
       (-> s
@@ -196,7 +251,7 @@
                      (goog.string.unescapeEntities f)
                      f)) hiccup))
 
-(defn parse
+(defn convert
   [format html]
   (when-not (string/blank? html)
     (let [hiccup (hickory/as-hiccup (hickory/parse html))

+ 1 - 1
src/main/frontend/extensions/zotero/extractor.cljs

@@ -140,7 +140,7 @@
 (defmethod extract "note"
   [item]
   (let [note-html (-> item :data :note)]
-    (html-parser/parse :markdown note-html)))
+    (html-parser/convert :markdown note-html)))
 
 (defn zotero-imported-file-macro [item-key filename]
   (util/format "{{zotero-imported-file %s, %s}}" item-key (pr-str filename)))

+ 5 - 3
src/main/frontend/handler.cljs

@@ -24,7 +24,6 @@
             [frontend.idb :as idb]
             [frontend.modules.instrumentation.core :as instrument]
             [frontend.modules.shortcut.core :as shortcut]
-            [frontend.search :as search]
             [frontend.state :as state]
             [frontend.storage :as storage]
             [frontend.util :as util]
@@ -32,7 +31,9 @@
             [cljs.reader :refer [read-string]]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [frontend.db.persist :as db-persist]
+            [frontend.modules.outliner.datascript :as outliner-db]))
 
 (defn set-global-error-notification!
   []
@@ -156,6 +157,8 @@
   (state/set-component! :block/linked-references reference/block-linked-references)
   (command-palette/register-global-shortcut-commands))
 
+(reset! db/*db-listener outliner-db/after-transact-pipelines)
+
 (defn start!
   [render]
   (set-global-error-notification!)
@@ -184,7 +187,6 @@
       (when (mobile-util/native-platform?)
         (p/do! (mobile-util/hide-splash))))
 
-    (reset! db/*sync-search-indice-f search/sync-search-indice!)
     (db/run-batch-txs!)
     (file-handler/run-writes-chan!)
     (when config/dev?

+ 170 - 9
src/main/frontend/handler/block.cljs

@@ -1,13 +1,18 @@
 (ns frontend.handler.block
-  (:require [clojure.set :as set]
-            [clojure.walk :as walk]
-            [frontend.db :as db]
-            [frontend.db.model :as db-model]
-            [frontend.db.react :as react]
-            [frontend.state :as state]
-            [logseq.graph-parser.block :as gp-block]
-            [frontend.util :as util]))
-
+  (:require
+   [clojure.set :as set]
+   [clojure.walk :as walk]
+   [frontend.db :as db]
+   [frontend.db.model :as db-model]
+   [frontend.db.react :as react]
+   [frontend.db.utils :as db-utils]
+   [frontend.mobile.haptics :as haptics]
+   [frontend.modules.outliner.core :as outliner-core]
+   [frontend.modules.outliner.transaction :as outliner-tx]
+   [frontend.state :as state]
+   [frontend.util :as util]
+   [goog.dom :as gdom]
+   [logseq.graph-parser.block :as gp-block]))
 
 ;;  Fns
 
@@ -114,3 +119,159 @@
                             (fn [result]
                               (->> (concat result more-data)
                                    (util/distinct-by :db/id))))))
+
+(defn indentable?
+  [{:block/keys [parent] :as block}]
+  (when parent
+    (let [parent-block (db-utils/pull (:db/id parent))
+          first-child (first
+                       (db-model/get-block-immediate-children
+                        (state/get-current-repo)
+                        (:block/uuid parent-block)))]
+      (not= (:db/id block) (:db/id first-child)))))
+
+(defn outdentable?
+  [{:block/keys [level] :as _block}]
+  (not= level 1))
+
+(defn indent-outdent-block!
+  [block direction]
+  (outliner-tx/transact!
+   {:outliner-op :move-blocks}
+   (outliner-core/indent-outdent-blocks! [block] (= direction :right))))
+
+(defn select-block!
+  [block-uuid]
+  (let [blocks (js/document.getElementsByClassName (str block-uuid))]
+    (when (seq blocks)
+      (state/exit-editing-and-set-selected-blocks! blocks))))
+
+(def *swipe (atom nil))
+
+(defn on-touch-start
+  [event]
+  (when-let [touches (.-targetTouches event)]
+    (when (= (.-length touches) 1)
+      (let [touch (aget touches 0)
+            x (.-clientX touch)
+            y (.-clientY touch)]
+        (reset! *swipe {:x0 x :y0 y :xi x :yi y :tx x :ty y :direction nil})))))
+
+(defn on-touch-move
+  [event block uuid *show-left-menu? *show-right-menu?]
+  (when-let [touches (.-targetTouches event)]
+    (when (and (= (.-length touches) 1) @*swipe)
+      (let [{:keys [x0 xi direction]} @*swipe
+            touch (aget touches 0)
+            tx (.-clientX touch)
+            ty (.-clientY touch)
+            direction (if (nil? direction)
+                        (if (> tx x0)
+                          :right
+                          :left)
+                        direction)]
+        (swap! *swipe #(-> %
+                           (assoc :tx tx)
+                           (assoc :ty ty)
+                           (assoc :xi tx)
+                           (assoc :yi ty)
+                           (assoc :direction direction)))
+        (when (< (* (- xi x0) (- tx xi)) 0)
+          (swap! *swipe #(-> %
+                             (assoc :x0 tx)
+                             (assoc :y0 ty))))
+        (let [{:keys [x0 y0]} @*swipe
+              dx (- tx x0)
+              dy (- ty y0)]
+          (when (and (< (. js/Math abs dy) 20)
+                     (> (. js/Math abs dx) 10))
+            (let [left (gdom/getElement (str "block-left-menu-" uuid))
+                  right (gdom/getElement (str "block-right-menu-" uuid))]
+
+              (cond
+                (= direction :right)
+                (do
+                  (reset! *show-left-menu? true)
+                  (when left
+                    (when (>= dx 0)
+                      (set! (.. left -style -width) (str dx "px")))
+                    (when (< dx 0)
+                      (set! (.. left -style -width) (str (max (+ 50 dx) 0) "px")))
+
+                    (let [indent (gdom/getFirstElementChild left)]
+                      (when (indentable? block)
+                        (if (>= (.-clientWidth left) 50)
+                          (set! (.. indent -style -opacity) "100%")
+                          (set! (.. indent -style -opacity) "30%"))))))
+
+                (= direction :left)
+                (do
+                  (reset! *show-right-menu? true)
+                  (when right
+                    (when (<= dx 0)
+                      (set! (.. right -style -width) (str (- dx) "px")))
+                    (when (> dx 0)
+                      (set! (.. right -style -width) (str (max (- 80 dx) 0) "px")))
+
+                    (let [outdent (gdom/getFirstElementChild right)
+                          more (gdom/getLastElementChild right)]
+                      (if (and (>= (.-clientWidth right) 40)
+                               (< (.-clientWidth right) 80))
+                        (set! (.. outdent -style -opacity) "100%")
+                        (set! (.. outdent -style -opacity) "30%"))
+
+                      (when (outdentable? block)
+                        (if (>= (.-clientWidth right) 80)
+                          (set! (.. more -style -opacity) "100%")
+                          (set! (.. more -style -opacity) "30%") 
+                        ;; (set! (.. outdent -style -opacity) "100%")
+                          ;; (set! (.. outdent -style -opacity) "30%")
+                        )))))
+                :else
+                nil))))))))
+
+(defn on-touch-end
+  [_event block uuid *show-left-menu? *show-right-menu?]
+  (when @*swipe
+    (let [left-menu (gdom/getElement (str "block-left-menu-" uuid))
+          right-menu (gdom/getElement (str "block-right-menu-" uuid))
+          {:keys [x0 tx]} @*swipe
+          dx (- tx x0)]
+      (try
+        (when (> (. js/Math abs dx) 10)
+          (cond
+            (and left-menu (>= (.-clientWidth left-menu) 50))
+            (when (indentable? block)
+              (haptics/with-haptics-impact
+                (indent-outdent-block! block :right)
+                :light))
+
+            (and right-menu (< 40 (.-clientWidth right-menu) 80))
+            (haptics/with-haptics-impact
+              (do (state/set-state! :mobile/show-action-bar? true)
+                  (state/set-state! :mobile/actioned-block block)
+                  (select-block! uuid))
+              :light)
+
+            (and right-menu (>= (.-clientWidth right-menu) 80))
+            (when (outdentable? block)
+              (haptics/with-haptics-impact
+                (indent-outdent-block! block :left)
+                :light))
+
+            :else
+            nil))
+        (catch js/Error e
+          (js/console.error e))
+        (finally
+          (reset! *show-left-menu? false)
+          (reset! *show-right-menu? false)
+          (reset! *swipe nil))))))
+
+(defn on-touch-cancel
+  [_event *show-left-menu? *show-right-menu?]
+  (reset! *show-left-menu? false)
+  (reset! *show-right-menu? false)
+  (reset! *swipe nil))
+
+

+ 4 - 2
src/main/frontend/handler/common.cljs

@@ -15,8 +15,10 @@
             [borkdude.rewrite-edn :as rewrite]))
 
 (defn copy-to-clipboard-without-id-property!
-  [format content]
-  (util/copy-to-clipboard! (property/remove-id-property format content)))
+  [format raw-text html]
+  (util/copy-to-clipboard! (property/remove-id-property format raw-text))
+  (when html
+    (util/copy-to-clipboard! html true)))
 
 (defn config-with-document-mode
   [config]

+ 62 - 42
src/main/frontend/handler/editor.cljs

@@ -55,7 +55,8 @@
             [promesa.core :as p]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.block :as gp-block]))
+            [logseq.graph-parser.block :as gp-block]
+            [frontend.extensions.html-parser :as html-parser]))
 
 ;; FIXME: should support multiple images concurrently uploading
 
@@ -962,9 +963,7 @@
 
 (defn select-block!
   [block-uuid]
-  (let [blocks (js/document.getElementsByClassName (str block-uuid))]
-    (when (seq blocks)
-      (state/exit-editing-and-set-selected-blocks! blocks))))
+  (block-handler/select-block! block-uuid))
 
 (defn- compose-copied-blocks-contents
   [repo block-ids]
@@ -977,7 +976,7 @@
      (into [] (state/get-export-block-text-remove-options)))))
 
 (defn copy-selection-blocks
-  []
+  [html?]
   (when-let [blocks (seq (state/get-selection-blocks))]
     (let [repo (state/get-current-repo)
           ids (distinct (keep #(when-let [id (dom/attr % "blockid")]
@@ -985,7 +984,8 @@
           content (compose-copied-blocks-contents repo ids)
           block (db/entity [:block/uuid (first ids)])]
       (when block
-        (common-handler/copy-to-clipboard-without-id-property! (:block/format block) content)
+        (let [html (export/export-blocks-as-html repo ids)]
+          (common-handler/copy-to-clipboard-without-id-property! (:block/format block) content (when html? html)))
         (state/set-copied-blocks content ids)
         (notification/show! "Copied!" :success)))))
 
@@ -1054,7 +1054,7 @@
 
 (defn cut-selection-blocks
   [copy?]
-  (when copy? (copy-selection-blocks))
+  (when copy? (copy-selection-blocks true))
   (when-let [blocks (seq (get-selected-blocks))]
     ;; remove embeds, references and queries
     (let [dom-blocks (remove (fn [block]
@@ -1193,9 +1193,10 @@
   (when-let [block (db/pull [:block/uuid block-id])]
     (let [repo (state/get-current-repo)
           ;; TODO: support org mode
-          md-content (compose-copied-blocks-contents repo [block-id])]
+          md-content (compose-copied-blocks-contents repo [block-id])
+          html (export/export-blocks-as-html repo [block-id])]
       (state/set-copied-full-blocks md-content [block])
-      (common-handler/copy-to-clipboard-without-id-property! (:block/format block) md-content)
+      (common-handler/copy-to-clipboard-without-id-property! (:block/format block) md-content html)
       (delete-block-aux! block true))))
 
 (defn clear-last-selected-block!
@@ -2633,49 +2634,49 @@
         nil
 
         ;; FIXME: On mobile, a backspace click to call keydown-backspace-handler
-        ;; does not work sometimes in an empty block, hence the empty block
+        ;; does not work if cursor is at the beginning of a block, hence the block
         ;; can't be deleted. Need to figure out why and find a better solution.
         (and (mobile-util/native-platform?)
              (= key "Backspace")
-             (= value ""))
+             (zero? pos))
         (do
           (util/stop e)
-          (delete-block! (state/get-current-repo) false))
+          (let [block (state/get-edit-block)
+                top-block? (= (:block/left block) (:block/page block))
+                root-block? (= (:block/container block) (str (:block/uuid block)))
+                repo (state/get-current-repo)]
+           (when (and (if top-block? (string/blank? value) true)
+                      (not root-block?))
+             (delete-block! repo false))))
 
         (and (= key "#")
-             (and
-              (> pos 0)
-              (= "#" (util/nth-safe value (dec pos)))))
+             (and (> pos 0)
+                  (= "#" (util/nth-safe value (dec pos)))))
         (state/set-editor-show-page-search-hashtag! false)
 
-        (and
-         (contains? (set/difference (set (keys reversed-autopair-map))
-                                    #{"`"})
-                    key)
+        (and (contains? (set/difference (set (keys reversed-autopair-map))
+                                        #{"`"})
+                        key)
          (= (get-current-input-char input) key))
-        (do
-          (util/stop e)
-          (cursor/move-cursor-forward input))
+        (do (util/stop e)
+            (cursor/move-cursor-forward input))
 
         (and (autopair-when-selected key) (string/blank? (util/get-selected-text)))
         nil
 
         (and (not (string/blank? (util/get-selected-text)))
              (contains? keycode/left-square-brackets-keys key))
-        (do
-          (autopair input-id "[" format nil)
-          (util/stop e))
+        (do (autopair input-id "[" format nil)
+            (util/stop e))
 
         (and (not (string/blank? (util/get-selected-text)))
              (contains? keycode/left-paren-keys key))
-        (do
-          (util/stop e)
-          (autopair input-id "(" format nil))
+        (do (util/stop e)
+            (autopair input-id "(" format nil))
 
         (contains? (set (keys autopair-map)) key)
-        (do
-          (util/stop e)
-          (autopair input-id key format nil))
+        (do (util/stop e)
+            (autopair input-id key format nil))
 
         hashtag?
         (do
@@ -2887,8 +2888,8 @@
   (let [copied-blocks (state/get-copied-blocks)
         copied-block-ids (:copy/block-ids copied-blocks)
         copied-graph (:copy/graph copied-blocks)
-        input (state/get-input)
-        *stop-event? (atom true)]
+        input (state/get-input)]
+    (util/stop e)
     (cond
       ;; Internal blocks by either copy or cut blocks
       (and
@@ -2916,7 +2917,8 @@
 
       :else
       ;; from external
-      (let [format (or (db/get-page-format (state/get-current-page)) :markdown)]
+      (let [format (or (db/get-page-format (state/get-current-page)) :markdown)
+            text (string/trim text)]
         (match [format
                 (nil? (util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
                 (nil? (util/safe-re-find #"(?m)^\s*\*+\s+" text))
@@ -2928,18 +2930,16 @@
           (paste-text-parseable format text)
 
           [:markdown true _ false]
-          (paste-segmented-text format (string/trim text))
+          (paste-segmented-text format text)
 
           [:markdown true _ true]
-          (reset! *stop-event? false)
+          (commands/simple-insert! (state/get-edit-input-id) text nil)
 
           [:org _ true false]
-          (paste-segmented-text format (string/trim text))
+          (paste-segmented-text format text)
 
           [:org _ true true]
-          (reset! *stop-event? false))))
-    (when @*stop-event?
-      (util/stop e))))
+          (commands/simple-insert! (state/get-edit-input-id) text nil))))))
 
 (defn paste-text-in-one-block-at-point
   []
@@ -2957,7 +2957,14 @@
   [id]
   (fn [e]
     (state/set-state! :editor/on-paste? true)
-    (let [text (.getData (gobj/get e "clipboardData") "text")
+    (let [clipboard-data (gobj/get e "clipboardData")
+          html (.getData clipboard-data "text/html")
+          edit-block (state/get-edit-block)
+          format (or (:block/format edit-block) :markdown)
+          initial-text (.getData clipboard-data "text")
+          text (if-not (string/blank? html)
+                 (html-parser/convert format html)
+                 initial-text)
           input (state/get-input)]
       (if-not (string/blank? text)
         (if (or (thingatpt/markdown-src-at-point input)
@@ -2981,7 +2988,7 @@
 
 (defn shortcut-copy-selection
   [_e]
-  (copy-selection-blocks))
+  (copy-selection-blocks true))
 
 (defn shortcut-cut-selection
   [e]
@@ -3029,6 +3036,19 @@
       :else
       (js/document.execCommand "copy"))))
 
+(defn shortcut-copy-text
+  "shortcut copy action:
+  * when in selection mode, copy selected blocks
+  * when in edit mode with text selected, copy selected text as normal"
+  [_e]
+  (when-not (auto-complete?)
+    (cond
+      (state/selection?)
+      (copy-selection-blocks false)
+
+      :else
+      (js/document.execCommand "copy"))))
+
 (defn shortcut-cut
   "shortcut cut action:
   * when in selection mode, cut selected blocks

+ 37 - 18
src/main/frontend/handler/events.cljs

@@ -2,48 +2,52 @@
   (:refer-clojure :exclude [run!])
   (:require [clojure.core.async :as async]
             [clojure.set :as set]
-            [frontend.context.i18n :refer [t]]
+            [clojure.string :as string]
+            [frontend.commands :as commands]
             [frontend.components.diff :as diff]
-            [frontend.handler.plugin :as plugin-handler]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
-            [frontend.components.plugins :as plugin]
+            [frontend.components.encryption :as encryption]
             [frontend.components.git :as git-component]
-            [frontend.components.shell :as shell]
+            [frontend.components.plugins :as plugin]
             [frontend.components.search :as search]
+            [frontend.components.shell :as shell]
             [frontend.config :as config]
+            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db-schema :as db-schema]
+            [frontend.encrypt :as encrypt]
             [frontend.extensions.srs :as srs]
+            [frontend.fs :as fs]
+            [frontend.fs.capacitor-fs :as capacitor-fs]
             [frontend.fs.nfs :as nfs]
+            [frontend.fs.sync :as sync]
             [frontend.fs.watcher-handler :as fs-watcher]
             [frontend.handler.common :as common-handler]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.file :as file-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
-            [frontend.handler.search :as search-handler]
-            [frontend.handler.ui :as ui-handler]
+            [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.repo :as repo-handler]
-            [frontend.handler.file :as file-handler]
             [frontend.handler.route :as route-handler]
+            [frontend.handler.search :as search-handler]
+            [frontend.handler.ui :as ui-handler]
             [frontend.handler.web.nfs :as nfs-handler]
-            [frontend.modules.shortcut.core :as st]
+            [frontend.mobile.util :as mobile-util]
+            [frontend.modules.instrumentation.posthog :as posthog]
             [frontend.modules.outliner.file :as outliner-file]
-            [frontend.commands :as commands]
+            [frontend.modules.shortcut.core :as st]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
-            [rum.core :as rum]
-            [frontend.modules.instrumentation.posthog :as posthog]
-            [frontend.mobile.util :as mobile-util]
-            [promesa.core :as p]
-            [frontend.fs :as fs]
-            [clojure.string :as string]
             [frontend.util.persist-var :as persist-var]
             [frontend.fs.sync :as sync]
             [frontend.handler.file-sync :as file-sync-handler]
             [frontend.components.file-sync :as file-sync]
             [frontend.components.encryption :as encryption]
-            [frontend.encrypt :as encrypt]))
+            [frontend.encrypt :as encrypt]
+            [goog.dom :as gdom]
+            [promesa.core :as p]
+            [rum.core :as rum]))
 
 ;; TODO: should we move all events here?
 
@@ -312,11 +316,19 @@
   (let [main-node (util/app-scroll-container-node)]
     (state/set-state! :mobile/show-tabbar? false)
     (state/set-state! :mobile/show-toolbar? true)
+    (state/set-state! :mobile/show-action-bar? false)
     (when (mobile-util/native-ios?)
       (reset! util/keyboard-height keyboard-height)
       (set! (.. main-node -style -marginBottom) (str keyboard-height "px"))
+      (when-let [left-sidebar-node (gdom/getElement "left-sidebar")]
+        (set! (.. left-sidebar-node -style -bottom) (str keyboard-height "px")))
+      (when-let [right-sidebar-node (gdom/getElementByClass "sidebar-item-list")]
+        (set! (.. right-sidebar-node -style -paddingBottom) (str (+ 150 keyboard-height) "px")))
       (when-let [card-preview-el (js/document.querySelector ".cards-review")]
         (set! (.. card-preview-el -style -marginBottom) (str keyboard-height "px")))
+      (when (= (state/sub :editor/record-status) "RECORDING")
+        (when-let [record-node (gdom/getElement "audio-record-toolbar")]
+          (set! (.. record-node -style -bottom) (str (+ 45 keyboard-height) "px"))))
       (js/setTimeout (fn []
                        (let [toolbar (.querySelector main-node "#mobile-editor-toolbar")]
                          (set! (.. toolbar -style -bottom) (str keyboard-height "px"))))
@@ -329,7 +341,14 @@
     (when (mobile-util/native-ios?)
       (when-let [card-preview-el (js/document.querySelector ".cards-review")]
         (set! (.. card-preview-el -style -marginBottom) "0px"))
-      (set! (.. main-node -style -marginBottom) "0px"))))
+      (set! (.. main-node -style -marginBottom) "0px")
+      (when-let [left-sidebar-node (gdom/getElement "left-sidebar")]
+        (set! (.. left-sidebar-node -style -bottom) "0px"))
+      (when-let [right-sidebar-node (gdom/getElementByClass "sidebar-item-list")]
+        (set! (.. right-sidebar-node -style -paddingBottom) "150px"))
+      (when (= (state/sub :editor/record-status) "RECORDING")
+        (when-let [record-node (gdom/getElement "audio-record-toolbar")]
+          (set! (.. record-node -style -bottom) "45px"))))))
 
 (defmethod handle :plugin/consume-updates [[_ id pending? updated?]]
   (let [downloading? (:plugin/updates-downloading? @state/state)]

+ 14 - 9
src/main/frontend/handler/export.cljs

@@ -19,7 +19,8 @@
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
             [goog.dom :as gdom]
-            [promesa.core :as p])
+            [promesa.core :as p]
+            [frontend.util.property :as property])
   (:import [goog.string StringBuffer]))
 
 (defn- get-page-content
@@ -94,6 +95,7 @@
         (js/window.apis.exportPublishAssets
          raw-html-str
          (config/get-custom-css-path)
+         (config/get-export-css-path)
          (config/get-repo-dir repo)
          (clj->js asset-filenames)
          (util/mocked-open-dir-path))
@@ -443,14 +445,17 @@
                [?b :block/name]] db)
 
         (map (fn [[{:block/keys [name] :as page}]]
-               (assoc page
-                      :block/children
-                      (outliner-tree/blocks->vec-tree
-                       (db/get-page-blocks-no-cache
-                        (state/get-current-repo)
-                        name
-                        {:transform? false}) name))))
-
+               (let [blocks (db/get-page-blocks-no-cache
+                             (state/get-current-repo)
+                             name
+                             {:transform? false})
+                     blocks' (map (fn [b]
+                                    (if (seq (:block/properties b))
+                                      (update b :block/content
+                                              (fn [content] (property/remove-properties (:block/format b) content)))
+                                      b)) blocks)
+                     children (outliner-tree/blocks->vec-tree blocks' name)]
+                 (assoc page :block/children children))))
         (nested-select-keys
          [:block/id
           :block/page-name

+ 6 - 4
src/main/frontend/handler/file.cljs

@@ -93,8 +93,8 @@
 
 (defn reset-file!
   ([repo-url file content]
-   (reset-file! repo-url file content false))
-  ([repo-url file content new-graph?]
+   (reset-file! repo-url file content {}))
+  ([repo-url file content {:keys [new-graph? from-disk?]}]
    (let [electron-local-repo? (and (util/electron?)
                                    (config/local-db? repo-url))
          file (cond
@@ -163,7 +163,8 @@
                               {:file/path file}
                               new?
                               (assoc :file/created-at t)))])]
-       (db/transact! repo-url tx (when new-graph? {:new-graph? true}))))))
+       (db/transact! repo-url tx {:new-graph? new-graph?
+                                  :from-disk? from-disk?})))))
 
 ;; TODO: Remove this function in favor of `alter-files`
 (defn alter-file
@@ -184,7 +185,8 @@
           (db/transact! repo
             [[:db/retract page-id :block/alias]
              [:db/retract page-id :block/tags]]))
-        (reset-file! repo path content new-graph?))
+        (reset-file! repo path content {:new-graph? new-graph?
+                                        :from-disk? from-disk?}))
       (db/set-file-content! repo path content))
     (util/p-handle (write-file!)
                    (fn [_]

+ 78 - 0
src/main/frontend/mobile/action_bar.cljs

@@ -0,0 +1,78 @@
+(ns frontend.mobile.action-bar
+  (:require
+   [frontend.db :as db]
+   [frontend.extensions.srs :as srs]
+   [frontend.handler.editor :as editor-handler]
+   [frontend.mixins :as mixins]
+   [frontend.state :as state]
+   [frontend.ui :as ui]
+   [frontend.util :as util]
+   [frontend.util.url :as url-util]
+   [goog.dom :as gdom]
+   [goog.object :as gobj]
+   [rum.core :as rum]
+   [frontend.mobile.util :as mobile-util]))
+
+(defn- action-command
+  [icon description command-handler]
+  (let [callback
+        (fn []
+          (state/set-state! :mobile/show-action-bar? false)
+          (editor-handler/clear-selection!))]
+    [:button.bottom-action.flex-row
+     {:on-click (fn [_event]
+                  (command-handler)
+                  (callback))}
+     (ui/icon icon {:style {:fontSize 23}})
+     [:div.description description]]))
+
+(rum/defcs action-bar < rum/reactive
+  (mixins/event-mixin
+   (fn [state]
+     (mixins/hide-when-esc-or-outside
+      state
+      :on-hide (fn []
+                 (editor-handler/clear-selection!)
+                 (state/set-state! :mobile/show-action-bar? false)))))
+  [state]
+  (when-let [block (state/sub :mobile/actioned-block)]
+    (let [{:block/keys [uuid children]} block
+          last-child-block-id (when-not (empty? children)
+                                (-> (db/get-block-children (state/get-current-repo) uuid)
+                                    last
+                                    :block/uuid))]
+      (let [tag-id (or last-child-block-id uuid)
+            bottom-el (gdom/getElement (str "block-content-" tag-id))
+            bottom (gobj/get (.getBoundingClientRect bottom-el) "bottom")
+            vw-height (or (.-height js/window.visualViewport)
+                          (.-clientHeight js/document.documentElement))
+            delta (- vw-height bottom 170)]
+        (when (< delta 0)
+          (.scrollBy (util/app-scroll-container-node) #js {:top (- 10 delta)})))
+      [:div.action-bar
+       [:div.action-bar-commands
+        (when-not (= (:block/format block) :org)
+          (action-command "heading" "Heading"
+                          #(let [properties (:block/properties block)
+                                 heading?   (true? (:heading properties))]
+                             (if heading?
+                               (editor-handler/remove-block-property! uuid :heading)
+                               (editor-handler/set-block-property! uuid :heading true)))))
+        (action-command "infinity" "Card" #(srs/make-block-a-card! (:block/uuid block)))
+        (action-command "copy" "Copy" #(editor-handler/copy-selection-blocks false))
+        (action-command "cut" "Cut" #(editor-handler/cut-selection-blocks true))
+        (action-command "trash" "Delete" #(editor-handler/delete-block-aux! block true))
+        (action-command "registered" "Copy ref"
+                        (fn [_event] (editor-handler/copy-block-ref! uuid #(str "((" % "))"))))
+        (action-command "link" "Copy url"
+                        (fn [_event] (let [current-repo (state/get-current-repo)
+                                           tap-f (fn [block-id]
+                                                   (url-util/get-logseq-graph-uuid-url nil current-repo block-id))]
+                                       (editor-handler/copy-block-ref! uuid tap-f))))
+        (when (mobile-util/native-ipad?)
+          (action-command "text-direction-ltr" "Right sidebar"
+                          (fn [_event]
+                            (let [current-repo (state/get-current-repo)]
+                              (state/sidebar-add-block! current-repo uuid :block-ref)))))]])))
+
+

+ 15 - 8
src/main/frontend/mobile/footer.cljs

@@ -1,13 +1,14 @@
 (ns frontend.mobile.footer
   (:require [clojure.string :as string]
+            [frontend.components.svg :as svg]
             [frontend.date :as date]
             [frontend.handler.editor :as editor-handler]
             [frontend.mobile.record :as record]
+            [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
-            [rum.core :as rum]
-            [frontend.components.svg :as svg]))
+            [rum.core :as rum]))
 
 (rum/defc mobile-bar-command [command-handler icon]
   [:button.bottom-action
@@ -42,20 +43,26 @@
       (reset! *record-start -1)
       (mobile-bar-command record/start-recording "microphone"))
     [:div.flex.flex-row.items-center
-     (mobile-bar-command record/stop-recording "player-stop")
+     (mobile-bar-command #(do
+                            (record/stop-recording)
+                            (reset! *record-start -1))
+                         "player-stop")
      [:div.timer.pl-2
       {:on-click record/stop-recording}
       (seconds->minutes:seconds @*record-start)]]))
 
 (rum/defc footer < rum/reactive
   []
-  (when (and
-         (state/mobile?)
-         (state/sub :mobile/show-tabbar?)
-         (state/get-current-repo))
+  (when (and (state/sub :mobile/show-tabbar?)
+             (state/get-current-repo))
     [:div.cp__footer.w-full.bottom-0.justify-between
      (audio-record-cp)
-     (mobile-bar-command #(state/toggle-document-mode!) "notes")
+     (mobile-bar-command
+      #(do (when-not (mobile-util/native-ipad?)
+             (state/set-left-sidebar-open! false))
+           (state/pub-event! [:go/search]))
+      "search")
+     (mobile-bar-command state/toggle-document-mode! "notes")
      (mobile-bar-command
       #(let [page (or (state/get-current-page)
                       (string/lower-case (date/journal-name)))]

+ 15 - 0
src/main/frontend/mobile/haptics.cljs

@@ -0,0 +1,15 @@
+(ns frontend.mobile.haptics
+  (:require
+   ["@capacitor/haptics" :refer [Haptics ImpactStyle]]
+   [promesa.core :as p]))
+
+(defn with-haptics-impact
+  [fn impact-style]
+  (let [style (cond
+                (= impact-style :light)
+                {:style (.-Light ImpactStyle)}
+
+                (= impact-style :medium)
+                {:style (.-Medium ImpactStyle)})]
+    (p/do! (.impact Haptics (clj->js style))
+           fn)))

+ 94 - 9
src/main/frontend/mobile/index.css

@@ -9,12 +9,16 @@
     flex: 0 0 auto;
     white-space: nowrap;
     height: 80px;
-    /* border-top: 1.5px solid var(--ls-tertiary-border-color); */
-    box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.10);
+    align-items: start;
+    box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.05);
 
+    .bottom-action {
+        width: 23px;
+        height: 23px;
+    }
+                                                             
     .ti, .timer {
         color: var(--ls-primary-text-color);
-        opacity: 0.5;
     }
 
     .timer {
@@ -23,6 +27,42 @@
     }
 }
 
+.action-bar {
+    position: absolute;
+    bottom: 100px;
+    height: 70px;
+    padding: 6px;
+    border-radius: 10px;
+    background-color: var(--ls-secondary-background-color);
+    overflow-x: overlay;
+    box-shadow: rgba(0, 0, 0, 0.02) 0px 1px 1px 0px, rgba(27, 31, 35, 0.10) 0px 0px 0px 1px;
+    z-index: 100;
+        
+    .action-bar-commands {
+        position: relative;
+        display: flex;
+        justify-content: space-around;
+        width: 120%;
+        
+        
+        .ti {
+            color: var(--ls-primary-text-color);
+            font-size: 23px;
+            opacity: 50%;
+        }
+        
+        .description {
+            color: var(--ls-primary-text-color);
+            font-size: 13px;
+            opacity: 60%;
+        }
+        
+        button {
+            padding: 5px 10px
+        }
+    }
+}
+
 #mobile-editor-toolbar {
   position: fixed;
   bottom: 0;
@@ -76,19 +116,49 @@
 html.is-native-ipad {
     .cp__footer {
         height: 55px;
-        /* width: calc(100vw - var(--ls-left-sidebar-width)); */
         right: 0;
         box-shadow: none;
         flex: 1;
         index: 0;
     }
+    
+    .action-bar {
+        width: 70%;
+        min-width:550px;
+        
+        .action-bar-commands {
+            width: 100%;
+        }
+
+        @media (orientation: landscape) {
+            width: 50%;
+        }
+    }
 }
 
 html.is-native-iphone {
+
+    .action-bar {
+        left: 3%;
+        right: 3%;
+    }
+    
     @media (orientation: landscape) {
         .cp__footer {
             height: 45px;
         }
+
+        .action-bar {
+            bottom: 50px;
+            left: 15%;
+            right: 15%;
+            width: 70%;
+            min-width: 450px;
+
+            .action-bar-commands {
+                width: 100%;
+            }
+        }
     }
 }
 
@@ -97,11 +167,30 @@ html.is-native-android {
     .cp__footer {
         height: 45px;
     }
+    
+    .action-bar {
+        left: 5%;
+        right: 5%;
+    }
+
+    @media (orientation: landscape) {
+        
+        .action-bar {
+            bottom: 50px;
+            left: 15%;
+            right: 15%;
+            width: 70%;
+
+            .action-bar-commands {
+                width: 100%;
+            }
+        }
+    }
 }
 
 html.is-zoomed-native-ios {
     .cp__footer {
-        height: 60px;
+        height: 70px;
     }
 
     @media (orientation: landscape) {
@@ -110,7 +199,3 @@ html.is-zoomed-native-ios {
         }
     }
 }
-
-.bottom-action {
-    line-height: 1.15;
-}

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

@@ -80,6 +80,7 @@
                         js/decodeURIComponent
                         util/node-path.name
                         util/file-name-sanity
+                        js/decodeURIComponent
                         (string/replace "." ""))
           path (path/join (config/get-repo-dir (state/get-current-repo))
                           (config/get-pages-directory)

+ 5 - 3
src/main/frontend/mobile/mobile_bar.cljs

@@ -7,6 +7,7 @@
             [frontend.handler.history :as history]
             [frontend.handler.page :as page-handler]
             [frontend.mobile.camera :as mobile-camera]
+            [frontend.mobile.record :as record]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
@@ -14,7 +15,7 @@
             [rum.core :as rum]))
 
 (def ^:private icons-keywords
-  [:checkbox :brackets :parentheses :command :tag :a-b :list :camera
+  [:checkbox :brackets :parentheses :command :tag :a-b :list :camera :microphone
    :brand-youtube :link :rotate :rotate-clockwise :calendar :code :bold :italic :strikethrough :paint])
 
 (def ^:private commands-stats
@@ -102,6 +103,7 @@
              (command editor-handler/cycle-priority! "a-b" true)
              (command editor-handler/toggle-list! "list" true)
              (command #(mobile-camera/embed-photo parent-id) "camera" true)
+             (command record/start-recording "microphone" true)
              (command commands/insert-youtube-timestamp "brand-youtube" true)
              (command editor-handler/html-link-format! "link" true)
              (command history/undo! "rotate" true true)
@@ -127,8 +129,8 @@
                  (state/sub :editor/editing?))
         [:div#mobile-editor-toolbar.bg-base-2
          [:div.toolbar-commands
-          (indent-outdent false "arrow-bar-left")
-          (indent-outdent true "arrow-bar-right")
+          (indent-outdent false "indent-decrease")
+          (indent-outdent true "indent-increase")
           (command (editor-handler/move-up-down true) "arrow-bar-to-up")
           (command (editor-handler/move-up-down false) "arrow-bar-to-down")
           (command #(if (state/sub :document/mode?)

+ 7 - 4
src/main/frontend/mobile/record.cljs

@@ -56,12 +56,15 @@
                    (log/error :file/write-failed {:path path
                                                   :error error})))
           url (util/format "../assets/%s" filename)
-          file-link (editor-handler/get-asset-file-link format url filename true)]
+          file-link (editor-handler/get-asset-file-link format url filename true)
+          args (merge (if (parse-uuid page)
+                        {:block-uuid (uuid page)}
+                        {:page page})
+                      {:edit-block? false
+                       :replace-empty-target? true})]
     (if edit-block
       (state/append-current-edit-content! file-link)
-      (editor-handler/api-insert-new-block! file-link {:page page
-                                                       :edit-block? false
-                                                       :replace-empty-target? true}))))
+      (editor-handler/api-insert-new-block! file-link args))))
 
 (defn stop-recording []
   (p/catch

+ 11 - 7
src/main/frontend/modules/outliner/datascript.cljc

@@ -8,7 +8,8 @@
                      [frontend.state :as state]
                      [frontend.config :as config]
                      [logseq.graph-parser.util :as gp-util]
-                     [lambdaisland.glogi :as log])))
+                     [lambdaisland.glogi :as log]
+                     [frontend.search :as search])))
 
 #?(:cljs
    (defn new-outliner-txs-state [] (atom [])))
@@ -22,9 +23,14 @@
 
 #?(:cljs
    (defn after-transact-pipelines
-     [{:keys [_db-before _db-after _tx-data _tempids _tx-meta] :as tx-report}]
-     (pipelines/invoke-hooks tx-report)
-     (undo-redo/listen-outliner-operation tx-report)))
+     [repo {:keys [_db-before _db-after _tx-data _tempids tx-meta] :as tx-report}]
+     (when-not config/test?
+       (pipelines/invoke-hooks tx-report)
+
+       (when (:outliner/transact? tx-meta)
+         (undo-redo/listen-outliner-operation tx-report))
+
+       (search/sync-search-indice! repo tx-report))))
 
 #?(:cljs
    (defn- remove-nil-from-transaction
@@ -54,7 +60,7 @@
                  conn (conn/get-db repo false)
                  editor-cursor (state/get-current-edit-block-and-position)
                  meta (merge opts {:editor-cursor editor-cursor})
-                 rs (d/transact! conn txs meta)]
+                 rs (d/transact! conn txs (assoc meta :outliner/transact? true))]
              (when true                 ; TODO: add debug flag
                (let [eids (distinct (mapv first (:tx-data rs)))
                      left&parent-list (->>
@@ -66,8 +72,6 @@
                                        (vec)
                                        (map next))]
                  (assert (= (count left&parent-list) (count (distinct left&parent-list))) eids)))
-             (when-not config/test?
-               (after-transact-pipelines rs))
              rs)
            (catch js/Error e
              (log/error :exception e)

+ 3 - 2
src/main/frontend/modules/outliner/pipeline.cljs

@@ -4,13 +4,14 @@
             [frontend.state :as state]))
 
 (defn updated-page-hook
-  [page]
+  [_tx-report page]
   (file/sync-to-file page))
 
 (defn invoke-hooks
   [tx-report]
   (let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)]
-    (doseq [p (seq pages)] (updated-page-hook p))
+    (when-not (:from-disk? (:tx-meta tx-report))
+      (doseq [p (seq pages)] (updated-page-hook tx-report p)))
     (when (and state/lsp-enabled? (seq blocks))
       (state/pub-event! [:plugin/hook-db-tx
                          {:blocks  blocks

+ 6 - 1
src/main/frontend/modules/shortcut/config.cljs

@@ -204,6 +204,9 @@
    :editor/copy                    {:binding "mod+c"
                                     :fn      editor-handler/shortcut-copy}
 
+   :editor/copy-text               {:binding "mod+shift+c"
+                                    :fn      editor-handler/shortcut-copy-text}
+
    :editor/cut                     {:binding "mod+x"
                                     :fn      editor-handler/shortcut-cut}
 
@@ -334,7 +337,7 @@
    :ui/toggle-theme                 {:binding "t t"
                                      :fn      state/toggle-theme!}
 
-   :ui/toggle-contents              {:binding "mod+shift+c"
+   :ui/toggle-contents              {:binding "alt+shift+c"
                                      :fn      ui-handler/toggle-contents!}
 
    :ui/open-new-window              {:binding "mod+n"
@@ -470,6 +473,7 @@
                           :editor/indent
                           :editor/outdent
                           :editor/copy
+                          :editor/copy-text
                           :editor/cut
                           :editor/undo
                           :editor/redo
@@ -541,6 +545,7 @@
     :editor/undo
     :editor/redo
     :editor/copy
+    :editor/copy-text
     :editor/cut]
 
    :shortcut.category/formatting

+ 4 - 1
src/main/frontend/modules/shortcut/dicts.cljc

@@ -65,6 +65,7 @@
    :editor/indent                  "Indent block"
    :editor/outdent                 "Outdent block"
    :editor/copy                    "Copy (copies either selection, or block reference)"
+   :editor/copy-text               "Copy selections as text"
    :editor/cut                     "Cut"
    :editor/undo                    "Undo"
    :editor/redo                    "Redo"
@@ -661,7 +662,9 @@
              :command.graph/save                      "Salvar gráfico atual no computador"
              :command.misc/copy                       "Copiar (copiar seleção ou referência do bloco)"
              :command.ui/goto-plugins                 "Ir para o painel de plugins"
-             :command.ui/open-new-window              "Abra uma nova janela"}
+             :command.ui/open-new-window              "Abra uma nova janela"
+             :command.editor/select-down              "Selecione o conteúdo abaixo"
+             :command.editor/select-up                "Selecione o conteúdo acima"}
 
    :ja      {:shortcut.category/formatting                "フォーマット"
              :shortcut.category/basics                "基本操作"

+ 1 - 1
src/main/frontend/publishing/html.cljs

@@ -19,7 +19,7 @@
              :name "viewport"}]
            [:link {:type "text/css", :href "static/css/tabler-icons.min.css", :rel "stylesheet"}]
            [:link {:type "text/css", :href "static/css/style.css", :rel "stylesheet"}]
-           [:link {:type "text/css", :href "static/css/custom.css", :rel "stylesheet"}]
+           [:link {:type "text/css", :href "static/css/export.css", :rel "stylesheet"}]
            [:link
             {:href icon
              :type "image/png",

+ 7 - 3
src/main/frontend/search.cljs

@@ -162,9 +162,13 @@
          (vec (select-keys templates result)))))))
 
 (defn sync-search-indice!
-  [datoms]
-  (when (seq datoms)
-    (when-let [repo (state/get-current-repo)]
+  [repo tx-report]
+  (let [data (:tx-data tx-report)
+        datoms (filter
+                (fn [datom]
+                  (contains? #{:block/name :block/content} (:a datom)))
+                data)]
+    (when (seq datoms)
       (let [datoms (group-by :a datoms)
             pages (:block/name datoms)
             blocks (:block/content datoms)]

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

@@ -145,6 +145,8 @@
      :electron/user-cfgs                    nil
 
      ;; mobile
+     :mobile/show-action-bar?               false
+     :mobile/actioned-block                 nil
      :mobile/show-toolbar?                  false
      ;;; toolbar icon doesn't update correctly when clicking after separate it from box,
      ;;; add a random in (<= 1000000) to observer its update
@@ -174,7 +176,7 @@
      :plugin/updates-downloading?           false
      :plugin/updates-unchecked              #{}
      :plugin/navs-settings?                 true
-     :plugin/focused-settings               nil            ;; plugin id
+     :plugin/focused-settings               nil ;; plugin id
 
      ;; pdf
      :pdf/current                           nil
@@ -862,7 +864,10 @@
              (util/set-change-value input content))
 
            (when move-cursor?
-             (cursor/move-cursor-to input pos))))))))
+             (cursor/move-cursor-to input pos))
+
+           (when (or (util/mobile?) (mobile-util/native-platform?))
+             (set-state! :mobile/show-action-bar? false))))))))
 
 (defn clear-edit!
   []

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

@@ -905,7 +905,10 @@
     :style {:min-height 24}}
    (if visible?
      (when (fn? content-fn)
-       [:div.fade-in.faster-fade-in (content-fn)])
+       [:div.fade-enter
+        {:ref #(when-let [^js cls (and % (.-classList %))]
+                 (.add cls "fade-enter-active"))}
+        (content-fn)])
      [:div.shadow.rounded-md.p-4.w-full.mx-auto.mb-5.fade-in {:style {:height 88}}
       [:div.animate-pulse.flex.space-x-4
        [:div.flex-1.space-y-3.py-1

+ 10 - 5
src/test/frontend/extensions/calc_test.cljc

@@ -1,10 +1,15 @@
 (ns frontend.extensions.calc-test
   (:require [clojure.test :as test :refer [are deftest testing]]
+            [clojure.edn :as edn]
             [frontend.extensions.calc :as calc]))
 
+(defn convert-bigNum [b]
+  (edn/read-string (str b))
+  )
+
 (defn run [expr]
   {:pre [(string? expr)]}
-  (calc/eval (calc/parse expr)))
+  (convert-bigNum (calc/eval (calc/parse expr))))
 
 (deftest basic-arithmetic
   (testing "numbers are parsed as expected"
@@ -102,7 +107,7 @@
   (testing "variables can be remembered"
     (are [final-env expr] (let [env (calc/new-env)]
                             (calc/eval env (calc/parse expr))
-                            (= final-env @env))
+                            (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
       {"a" 1}        "a = 1"
       {"a" -1}        "a = -1"
       {"variable" 1} "variable = 1 + 0 * 2"
@@ -114,7 +119,7 @@
   (testing "variables can have underscores"
     (are [final-env expr] (let [env (calc/new-env)]
                             (calc/eval env (calc/parse expr))
-                            (= final-env @env))
+                            (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
       {"a_a" 1}         "a_a = 1"
       {"x_yy_zzz" 1}    "x_yy_zzz= 1"
       {"foo_bar_baz" 1} "foo_bar_baz = 1 + -0 * 2"))
@@ -122,7 +127,7 @@
     (are [final-env exprs] (let [env (calc/new-env)]
                              (doseq [expr exprs]
                                (calc/eval env (calc/parse expr)))
-                             (= final-env @env))
+                            (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
       {"a" 1 "b" 2}          ["a = 1" "b = a + 1"]
       {"a" 1 "b" 0}          ["a = 1" "b = -a + 1"]
       {"a" 1 "b" 3}          ["a = 1" "b=a*2+1"]
@@ -133,7 +138,7 @@
     (are [final-env exprs] (let [env (calc/new-env)]
                              (doseq [expr exprs]
                                (calc/eval env (calc/parse expr)))
-                             (= final-env @env))
+                            (= final-env (into {} (for [[k v] @env] [k (convert-bigNum v)]))))
       {"a" 2}              ["a = 1" "a = 2"]
       {"a" 2 "b" 2}        ["a = 1" "b = a + 1" "a = b"]
       {"variable" 1 "x" 0} ["variable = 1 + 0 * 2" "x = log(variable)" "x = variable - 1"])))

+ 10 - 10
src/test/frontend/handler/export_test.cljs

@@ -12,11 +12,11 @@
     :file/content
     "- 1
   id:: 61506710-484c-46d5-9983-3d1651ec02c8
-	- 2
-	  id:: 61506711-5638-4899-ad78-187bdc2eaffc
-		- 3
-		  id:: 61506712-3007-407e-b6d3-d008a8dfa88b
-		- ((61506712-3007-407e-b6d3-d008a8dfa88b))
+        - 2
+          id:: 61506711-5638-4899-ad78-187bdc2eaffc
+                - 3
+                  id:: 61506712-3007-407e-b6d3-d008a8dfa88b
+                - ((61506712-3007-407e-b6d3-d008a8dfa88b))
 - 4
   id:: 61506712-b8a7-491d-ad84-b71651c3fdab"
     }
@@ -24,7 +24,7 @@
     :file/content
     "- 3
   id:: 97a00e55-48c3-48d8-b9ca-417b16e3a616
-	- {{embed [[page1]]}}"}])
+        - {{embed [[page1]]}}"}])
 
 (defn- import-test-data!
   []
@@ -42,20 +42,20 @@
   (are [expect block-uuid-s]
       (= expect
          (export/export-blocks-as-markdown (state/get-current-repo) [(uuid block-uuid-s)] "dashes" []))
-    "- 1\n\t- 2\n\t\t- 3\n\t\t- 3"
+    "- 1  \n\t- 2  \n\t\t- 3  \n\t\t- 3  "
     "61506710-484c-46d5-9983-3d1651ec02c8"
 
-    "- 3\n\t- 1\n\t\t- 2\n\t\t\t- 3\n\t\t\t- 3\n\t- 4"
+    "- 3  \n\t- 1  \n\t\t- 2  \n\t\t\t- 3  \n\t\t\t- 3  \n\t- 4  "
     "97a00e55-48c3-48d8-b9ca-417b16e3a616"))
 
 (deftest export-files-as-markdown
   (are [expect files]
       (= expect
          (@#'export/export-files-as-markdown (state/get-current-repo) files true))
-    [["pages/page1.md" "- 1\n\t- 2\n\t\t- 3\n\t\t- 3\n- 4"]]
+    [["pages/page1.md" "- 1  \n\t- 2  \n\t\t- 3  \n\t\t- 3  \n- 4  "]]
     [{:path "pages/page1.md" :content (:file/content (nth test-files 0)) :names ["page1"] :format :markdown}]
 
-    [["pages/page2.md" "- 3\n\t- 1\n\t\t- 2\n\t\t\t- 3\n\t\t\t- 3\n\t- 4"]]
+    [["pages/page2.md" "- 3  \n\t- 1  \n\t\t- 2  \n\t\t\t- 3  \n\t\t\t- 3  \n\t- 4  "]]
     [{:path "pages/page2.md" :content (:file/content (nth test-files 1)) :names ["page2"] :format :markdown}]))
 
 (deftest export-repo-as-edn-str

+ 13 - 11
yarn.lock

@@ -559,6 +559,10 @@
   resolved "https://registry.yarnpkg.com/@capacitor/filesystem/-/filesystem-1.0.6.tgz#b837585e6b5d48dc705ee89e49cc7c6aeb8874ec"
   integrity sha512-8xqUbDZFGBMhgqoBSn9wEd9OBPdHIRegQ9zCCZcpHNf3FFAIby1ck+aDFnoq+Da49xhD6ks1SKCBSxz/26qWTw==
 
+"@capacitor/haptics@^1.1.4":
+  version "1.1.4"
+  integrity sha512-+pJIb5X7xAcbrWj6rJaV+cwBlv8aFwB1/Ob6EV4atydThuuVSSsAL4hI4ZYlPNOxM6H5s+ZDLj7Pa2os4eFmtg==
+
 "@capacitor/[email protected]":
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/@capacitor/ios/-/ios-3.2.2.tgz#0417cf89df23f651c6a1e20a8fb98294a918ce1f"
@@ -1656,6 +1660,11 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
   integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
 
+bignumber.js@^9.0.2:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673"
+  integrity sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==
+
 binary-extensions@^1.0.0:
   version "1.13.1"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
@@ -5307,10 +5316,10 @@ mkdirp@^1.0.3:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
[email protected].3:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-1.3.3.tgz#b7f39b48eb0ef3558619d3e3522265977bd78fe3"
-  integrity sha512-TzW06GBltdKxwWAxOvflPmIVedu6bzl9T4YoYqnDUyZ3kELFMllEgiYCh65PPW3xsRMA/5OcRQqqGZGiKEJEug==
[email protected].9:
+  version "1.3.9"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-1.3.9.tgz#9e45a25ba79596f3b0b0eace65f651a4c5a0c30a"
+  integrity sha512-UfqNuBphOj7paSBvozTdin5BLB5+W2tr2SGKTfp5eae6VQPx23oICx6RPQprft7KGFtn8T3rpM1YMUN8FaJLhg==
   dependencies:
     yargs "^12.0.2"
 
@@ -6822,11 +6831,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 sha1-oZbjP98eeqof2jrvu2i9rZ6Cp50=
-
 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"
@@ -6836,8 +6840,6 @@ [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-is@^16.13.1, react-is@^16.3.1, react-is@^16.7.0:
   version "16.13.1"