Ver Fonte

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

Peng Xiao há 3 anos atrás
pai
commit
d2c3b88865

+ 7 - 3
.github/workflows/build-android.yml

@@ -7,10 +7,14 @@ on:
   workflow_dispatch:
     inputs:
       build-target:
-        description: 'Build Target ("nightly"/"beta"/"non-release")'
-        type: string
+        description: 'Build Target (Release Type)'
+        type: choice
         required: true
-        default: "beta"
+        options:
+          - beta
+          - nightly
+          - non-release
+        default: "non-release"
       git-ref:
         description: "Build from Git Ref(master)"
         required: true

+ 96 - 20
android/app/src/main/java/com/logseq/app/FileUtil.java

@@ -9,8 +9,15 @@ import android.os.Build;
 import android.os.Environment;
 import android.provider.DocumentsContract;
 import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+
+// The following refs
 // https://stackoverflow.com/questions/29713587/how-to-get-the-real-path-with-action-open-document-tree-intent
 // https://gist.github.com/asifmujteba/d89ba9074bc941de1eaa#file-asfurihelper
+// with bug fixes and patches.
 public class FileUtil {
     @TargetApi(Build.VERSION_CODES.KITKAT)
     public static String getPath(final Context context, final Uri uri) {
@@ -24,24 +31,64 @@ public class FileUtil {
                 final String docId = DocumentsContract.getDocumentId(uri);
                 final String[] split = docId.split(":");
                 final String type = split[0];
+                // NOTE: It's not a good idea to use storage root as Graph root.
+                String remain = "";
+                if (split.length == 2) {
+                    remain = split[1];
+                }
 
                 if ("primary".equalsIgnoreCase(type)) {
-                    return Environment.getExternalStorageDirectory() + "/" + split[1];
+                    return Environment.getExternalStorageDirectory() + "/" + remain;
+                } else if ("home".equalsIgnoreCase(type)) {
+                    return Environment.getExternalStorageDirectory() + "/Documents/" + remain;
+                } else if ("downloads".equalsIgnoreCase(type)) {
+                    return Environment.getExternalStorageDirectory() + "/Download/" + remain; // No 's' here
                 }
 
-                // TODO handle non-primary volumes
-            }
-            // DownloadsProvider
-            else if (isDownloadsDocument(uri)) {
+                File dir = null;
+                File[] mdirs = context.getExternalMediaDirs();
+                for (File mdir : mdirs) {
+                    String extPath = mdir.getAbsolutePath();
+
+                    if (extPath.contains("/" + type + "/")) {
+                        dir = new File(extPath.substring(0, extPath.indexOf("/Android")) + "/" + remain);
+                        if (dir.exists()) {
+                            return dir.getAbsolutePath();
+                        }
+                    }
+                }
+                // FIXME: The following attempt cannot handle same directory name on different devices!
+                // attempt 1
+                dir = new File("/storage/" + type + "/" + remain);
+                if (dir.exists()) {
+                    return dir.getAbsolutePath();
+                }
+                // attempt 3
+                dir = new File("/mnt/media_rw/" + type + "/" + remain);
+                if (dir.exists()) {
+                    return dir.getAbsolutePath();
+                }
+                // attempt 3
+                dir = new File("/mnt/" + type + "/" + remain);
+                if (dir.exists()) {
+                    return dir.getAbsolutePath();
+                }
 
+                // TODO: other cases
+            } else if (isDownloadsDocument(uri)) {
                 final String id = DocumentsContract.getDocumentId(uri);
-                final Uri contentUri = ContentUris.withAppendedId(
-                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
-
-                return getDataColumn(context, contentUri, null, null);
-            }
-            // MediaProvider
-            else if (isMediaDocument(uri)) {
+                if (!TextUtils.isEmpty(id)) {
+                    if (id.startsWith("raw:")) {
+                        return id.replaceFirst("raw:", "");
+                    }
+                    try {
+                        final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+                        return getDataColumn(context, contentUri, null, null);
+                    } catch (NumberFormatException e) {
+                        return null;
+                    }
+                }
+            } else if (isMediaDocument(uri)) {
                 final String docId = DocumentsContract.getDocumentId(uri);
                 final String[] split = docId.split(":");
                 final String type = split[0];
@@ -56,24 +103,49 @@ public class FileUtil {
                 }
 
                 final String selection = "_id=?";
-                final String[] selectionArgs = new String[] {
+                final String[] selectionArgs = new String[]{
                         split[1]
                 };
 
                 return getDataColumn(context, contentUri, selection, selectionArgs);
+            } else if (isTermuxDocument(uri)) {
+                String docId = DocumentsContract.getDocumentId(uri);
+
+                // Ref: https://github.com/termux/termux-app/blob/master/app/src/main/java/com/termux/app/TermuxInstaller.java
+                if (docId.startsWith("/")) {
+                    if (docId.contains("/com.termux/files/home/storage/")) {
+                        String remain = docId.replaceFirst("^.*?com\\.termux/files/home/storage/[^/]+/", "");
+                        if (docId.contains("/storage/external-1")) { // TODO: Support external-2 or more
+                            File[] dirs = context.getExternalFilesDirs(remain);
+                            if (dirs != null && dirs.length >= 2) {
+                                docId = dirs[1].getAbsolutePath();
+                            }
+                        } else if (docId.contains("/storage/media-1")) {
+                            File[] dirs = context.getExternalMediaDirs();
+                            if (dirs != null && dirs.length >= 2) {
+                                docId = dirs[1].getAbsolutePath() + "/" + remain;
+                            }
+                        } else if (docId.contains("/storage/downloads")) {
+                            docId = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + remain;
+                        } else if (docId.contains("/storage/shared")) {
+                            docId = Environment.getExternalStorageDirectory() + "/" + remain;
+                        }
+                    }
+                    File dir = new File(docId);
+                    if (dir.exists()) {
+                        return dir.getAbsolutePath();
+                    }
+                    Log.e("Logseq/FileUtil", "Handle termux content url failed: " + docId);
+                }
+                // FIXME: Are there any other cases?
             }
-        }
-        // MediaStore (and general)
-        else if ("content".equalsIgnoreCase(uri.getScheme())) {
-
+        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
             // Return the remote address
             if (isGooglePhotosUri(uri))
                 return uri.getLastPathSegment();
 
             return getDataColumn(context, uri, null, null);
-        }
-        // File
-        else if ("file".equalsIgnoreCase(uri.getScheme())) {
+        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
             return uri.getPath();
         }
 
@@ -135,4 +207,8 @@ public class FileUtil {
     public static boolean isGooglePhotosUri(Uri uri) {
         return "com.google.android.apps.photos.content".equals(uri.getAuthority());
     }
+
+    public static boolean isTermuxDocument(Uri uri) {
+        return "com.termux.documents".equals(uri.getAuthority());
+    }
 }

+ 10 - 2
android/app/src/main/java/com/logseq/app/FolderPicker.java

@@ -8,6 +8,7 @@ import android.os.Environment;
 import android.os.Build;
 import android.provider.DocumentsContract;
 import android.provider.Settings;
+import android.util.Log;
 
 import androidx.activity.result.ActivityResult;
 import androidx.documentfile.provider.DocumentFile;
@@ -56,7 +57,14 @@ public class FolderPicker extends Plugin {
         Uri treeUri = result.getData().getData();
         Uri docUri = DocumentsContract.buildDocumentUriUsingTree(treeUri,
                 DocumentsContract.getTreeDocumentId(treeUri));
-        ret.put("path", FileUtil.getPath(context, docUri));
-        call.resolve(ret);
+        Log.i("Logseq/FolderPicker", "Got uri " + docUri);
+        String path = FileUtil.getPath(context, docUri);
+        Log.i("Logseq/FolderPicker", "Convert to path " + FileUtil.getPath(context, docUri));
+        if (path == null || path.isEmpty()) {
+            call.reject("Cannot support this directory type: " + docUri);
+        } else {
+            ret.put("path", path);
+            call.resolve(ret);
+        }
     }
 }

+ 7 - 13
src/main/frontend/components/block.cljs

@@ -1533,16 +1533,8 @@
       (util/stop e))
     (route-handler/redirect-to-page! uuid)))
 
-(defn- toggle-block-children
-  [_e children]
-  (let [block-ids (map :block/uuid children)]
-    (dorun
-     (if (some editor-handler/collapsable? block-ids)
-       (map editor-handler/collapse-block! block-ids)
-       (map editor-handler/expand-block! block-ids)))))
-
 (rum/defc block-children < rum/reactive
-  [config children collapsed?]
+  [config block children collapsed?]
   (let [ref? (:ref? config)
         query? (:custom-query? config)
         children (when (coll? children)
@@ -1552,7 +1544,9 @@
                (not collapsed?))
       (let [doc-mode? (state/sub :document/mode?)]
         [:div.block-children-container.flex {:style {:margin-left (if doc-mode? 18 29)}}
-         [:div.block-children-left-border {:on-click (fn [event] (toggle-block-children event children))}]
+         [:div.block-children-left-border
+          {:on-click (fn [_]
+                       (editor-handler/toggle-open-block-children! (:block/uuid block)))}]
          [:div.block-children.w-full {:style    {:display     (if collapsed? "none" "")}}
           (for [child children]
             (when (map? child)
@@ -2469,7 +2463,7 @@
         *navigating-block (get state ::navigating-block)
         navigating-block (rum/react *navigating-block)
         navigated? (and (not= (:block/uuid block) navigating-block) navigating-block)
-        block (if navigated?
+        block (if (or navigated? custom-query?)
                 (let [block (db/pull [:block/uuid navigating-block])
                       blocks (db/get-paginated-blocks repo (:db/id block)
                                                       {:scoped-block-id (:db/id block)})
@@ -2574,7 +2568,7 @@
       (when @*show-right-menu?
         (block-right-menu config block edit?))]
 
-     (block-children config children collapsed?)
+     (block-children config block children collapsed?)
 
      (dnd-separator-wrapper block block-id slide? false false)]))
 
@@ -2590,7 +2584,7 @@
 
                (or (:ref? config) (:custom-query? config))
                (state/set-collapsed-block! block-id
-                                           (editor-handler/block-default-collapsed? block config))
+                                           (boolean (editor-handler/block-default-collapsed? block config)))
 
                :else
                nil)

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

@@ -261,7 +261,7 @@
           (ui/menu-link
            {:key      "Collapse all"
             :on-click (fn [_e]
-                        (editor-handler/collapse-all! block-id))}
+                        (editor-handler/collapse-all! block-id {}))}
            "Collapse all")
 
           (when (state/sub [:plugin/simple-commands])

+ 6 - 4
src/main/frontend/fs/capacitor_fs.cljs

@@ -307,10 +307,12 @@
         result)))
   (open-dir [_this _ok-handler]
     (p/let [_    (when (= (mobile-util/platform) "android") (check-permission-android))
-            {:keys [path localDocumentsPath]} (p/chain
-                                               (.pickFolder mobile-util/folder-picker)
-                                               #(js->clj % :keywordize-keys true))
-            _ (when (and (mobile-util/native-ios?) 
+            {:keys [path localDocumentsPath]} (-> (.pickFolder mobile-util/folder-picker)
+                                                  (p/then #(js->clj % :keywordize-keys true))
+                                                  (p/catch (fn [e]
+                                                             (js/alert (str e))
+                                                             nil))) ;; NOTE: Can not pick folder, let it crash
+            _ (when (and (mobile-util/native-ios?)
                          (not (or (local-container-path? path localDocumentsPath)
                                   (mobile-util/iCloud-container-path? path))))
                 (state/pub-event! [:modal/show-instruction]))

+ 13 - 4
src/main/frontend/handler/block.cljs

@@ -13,7 +13,9 @@
    [frontend.state :as state]
    [frontend.util :as util]
    [goog.dom :as gdom]
-   [logseq.graph-parser.block :as gp-block]))
+   [logseq.graph-parser.block :as gp-block]
+   [frontend.modules.instrumentation.posthog :as posthog]
+   [cljs-bean.core :as bean]))
 
 ;;  Fns
 
@@ -283,6 +285,13 @@
 
 (defn get-filtered-ref-blocks
   [ref-blocks filters ref-pages]
-  (let [ref-blocks' (mapcat second ref-blocks)
-        filtered-blocks (filter-blocks ref-blocks' filters ref-pages)]
-    (group-by :block/page filtered-blocks)))
+  (try
+    (let [ref-blocks' (doall (mapcat second ref-blocks))
+          filtered-blocks (filter-blocks ref-blocks' filters ref-pages)]
+      (group-by :block/page filtered-blocks))
+    (catch :default e
+      (js/console.error e)
+      (posthog/capture :bad-ref-blocks (bean/->js
+                                        {:ref-blocks ref-blocks
+                                         :filters filters
+                                         :ref-pages ref-pages})))))

+ 39 - 28
src/main/frontend/handler/editor.cljs

@@ -3133,13 +3133,16 @@
 (defn shortcut-left-right [direction]
   (fn [e]
     (when-not (auto-complete?)
-      (util/stop e)
       (cond
         (state/editing?)
-        (keydown-arrow-handler direction)
+        (do
+          (util/stop e)
+          (keydown-arrow-handler direction))
 
         (state/selection?)
-        (open-selected-block! direction e)
+        (do
+          (util/stop e)
+          (open-selected-block! direction e))
 
         :else
         nil))))
@@ -3198,14 +3201,12 @@
               :or {semantic? false}}]
    (when block-id
      (if-let [block (db-model/query-block-by-uuid block-id)]
-       (and
-        (not (util/collapsed? block))
-        (or (db-model/has-children? block-id)
-            (and
-             (:outliner/block-title-collapse-enabled? (state/get-config))
-             (block-with-title? (:block/format block)
-                                (:block/content block)
-                                semantic?))))
+       (or (db-model/has-children? block-id)
+           (and
+            (:outliner/block-title-collapse-enabled? (state/get-config))
+            (block-with-title? (:block/format block)
+                               (:block/content block)
+                               semantic?)))
        false))))
 
 (defn all-blocks-with-level
@@ -3284,14 +3285,16 @@
         value (boolean value)]
     (when repo
       (outliner-tx/transact!
-       {:outliner-op :collapse-expand-blocks}
-       (doseq [block-id block-ids]
-         (when-let [block (db/entity [:block/uuid block-id])]
-           (let [current-value (:block/collapsed? block)]
-             (when-not (= current-value value)
-               (let [block {:block/uuid block-id
-                            :block/collapsed? value}]
-                 (outliner-core/save-block! block)))))))
+        {:outliner-op :collapse-expand-blocks}
+        (doseq [block-id block-ids]
+          (when-let [block (db/entity [:block/uuid block-id])]
+            (let [current-value (:block/collapsed? block)]
+              (when-not (= current-value value)
+                (let [block {:block/uuid block-id
+                             :block/collapsed? value}]
+                  (outliner-core/save-block! block)))))))
+      (doseq [block-id block-ids]
+        (state/set-collapsed-block! block-id value))
       (let [block-id (first block-ids)
             input-pos (or (state/get-edit-pos) :max)]
         ;; update editing input content
@@ -3304,13 +3307,11 @@
 (defn collapse-block! [block-id]
   (when (collapsable? block-id)
     (when-not (skip-collapsing-in-db?)
-      (set-blocks-collapsed! [block-id] true)))
-  (state/set-collapsed-block! block-id true))
+      (set-blocks-collapsed! [block-id] true))))
 
 (defn expand-block! [block-id]
   (when-not (skip-collapsing-in-db?)
-    (set-blocks-collapsed! [block-id] false)
-    (state/set-collapsed-block! block-id false)))
+    (set-blocks-collapsed! [block-id] false)))
 
 (defn expand!
   ([e] (expand! e false))
@@ -3383,12 +3384,15 @@
 
 (defn collapse-all!
   ([]
-   (collapse-all! nil))
-  ([block-id]
+   (collapse-all! nil {}))
+  ([block-id {:keys [collapse-self?]
+              :or {collapse-self? true}}]
    (let [blocks (all-blocks-with-level {:incremental? false
                                         :expanded? true
                                         :root-block block-id})
-         block-ids (map :block/uuid blocks)]
+         block-ids (cond->> (mapv :block/uuid blocks)
+                     (not collapse-self?)
+                     (remove #{block-id}))]
      (set-blocks-collapsed! block-ids true))))
 
 (defn expand-all!
@@ -3408,6 +3412,14 @@
       (collapse-all!)
       (expand-all!))))
 
+(defn toggle-open-block-children! [block-id]
+  (let [all-expanded? (empty? (all-blocks-with-level {:incremental? false
+                                                      :collapse? true
+                                                      :root-block block-id}))]
+    (if all-expanded?
+      (collapse-all! block-id {:collapse-self? false})
+      (expand-all! block-id))))
+
 (defn select-all-blocks!
   []
   (if-let [current-input-id (state/get-edit-input-id)]
@@ -3494,8 +3506,7 @@
   (or
    (and
     (or (:ref? config) (:custom-query? config))
-    (>= (inc (:block/level block))
-        (state/get-ref-open-blocks-level))
+    (>= (:block/level block) (state/get-ref-open-blocks-level))
     ;; has children
     (first (:block/_parent (db/entity (:db/id block)))))
    (util/collapsed? block)))

+ 14 - 10
src/main/frontend/modules/outliner/tree.cljs

@@ -68,14 +68,15 @@
 
 (defn- tree [flat-nodes root-id]
   (let [children (group-by :block/parent flat-nodes)
-        nodes (fn nodes [parent-id]
-                (map (fn [b]
-                       (let [children (nodes (:db/id b))]
-                         (if (seq children)
-                           (assoc b :block/children children)
-                           b)))
-                  (children {:db/id parent-id})))]
-    (nodes root-id)))
+        nodes (fn nodes [parent-id level]
+                (mapv (fn [b]
+                        (let [b' (assoc b :block/level (inc level))
+                              children (nodes (:db/id b) (inc level))]
+                          (if (seq children)
+                            (assoc b' :block/children children)
+                            b')))
+                      (get children {:db/id parent-id})))]
+    (nodes root-id 1)))
 
 (defn non-consecutive-blocks->vec-tree
   "`blocks` need to be in the same page."
@@ -88,12 +89,15 @@
         id->parent (zipmap (map :db/id blocks)
                            (map (comp :db/id :block/parent) blocks))
         top-level-ids (set (remove #(id->parent (id->parent %)) (map :db/id blocks)))
+        ;; Separate blocks into parent and children groups [parent-children, parent-children]
         blocks' (loop [blocks blocks
                        result []]
                   (if-let [block (first blocks)]
                     (if (top-level-ids (:db/id block))
-                      (recur (rest blocks) (conj result [block]))
-                      (recur (rest blocks) (conj (vec (butlast result)) (conj (last result) block))))
+                      (let [block' (assoc block :block/level 1)]
+                        (recur (rest blocks) (conj result [block'])))
+                      (recur (rest blocks) (conj (vec (butlast result))
+                                                 (conj (last result) block))))
                     result))]
     (map (fn [[parent & children]]
            (if (seq children)

+ 61 - 0
src/test/frontend/modules/outliner/core_test.cljs

@@ -622,6 +622,67 @@
                                                                            :use-cache? false})))
                total))))))
 
+(deftest test-non-consecutive-blocks->vec-tree
+  (let [blocks [{:block/page #:db{:id 2313},
+                 :block/uuid #uuid "62f49b4c-f9f0-4739-9985-8bd55e4c68d4",
+                 :block/parent #:db{:id 2313},
+                 :db/id 2315}
+                {:block/page #:db{:id 2313},
+                 :block/uuid #uuid "62f49b4c-aa84-416e-9554-b486b4e59b1b",
+                 :block/parent #:db{:id 2315},
+                 :db/id 2316}
+                {:block/page #:db{:id 2313},
+                 :block/uuid #uuid "62f49b4c-f80c-49b4-ae83-f78c4520c071",
+                 :block/parent #:db{:id 2316},
+                 :db/id 2317}
+                {:block/page #:db{:id 2313},
+                 :block/uuid #uuid "62f49b4c-8f5b-4a04-b749-68d34b28bcf2",
+                 :block/parent #:db{:id 2317},
+                 :db/id 2318}
+                {:block/page #:db{:id 2313},
+                 :block/uuid #uuid "62f4b8c1-a99b-434f-84c3-011d6afc48ba",
+                 :block/parent #:db{:id 2315},
+                 :db/id 2333}
+                {:block/page #:db{:id 2313},
+                 :block/uuid #uuid "62f4b8c6-072e-4133-90e2-0591021a7fea",
+                 :block/parent #:db{:id 2333},
+                 :db/id 2334}]]
+    (= (tree/non-consecutive-blocks->vec-tree blocks)
+       '({:db/id 2315,
+          :block/uuid #uuid "62f49b4c-f9f0-4739-9985-8bd55e4c68d4",
+          :block/parent #:db{:id 2313},
+          :block/page #:db{:id 2313},
+          :block/level 1,
+          :block/children
+          [{:db/id 2316,
+            :block/uuid #uuid "62f49b4c-aa84-416e-9554-b486b4e59b1b",
+            :block/parent #:db{:id 2315},
+            :block/page #:db{:id 2313},
+            :block/level 2,
+            :block/children
+            [{:db/id 2317,
+              :block/uuid #uuid "62f49b4c-f80c-49b4-ae83-f78c4520c071",
+              :block/parent #:db{:id 2316},
+              :block/page #:db{:id 2313},
+              :block/level 3,
+              :block/children
+              [{:db/id 2318,
+                :block/uuid #uuid "62f49b4c-8f5b-4a04-b749-68d34b28bcf2",
+                :block/parent #:db{:id 2317},
+                :block/page #:db{:id 2313},
+                :block/level 4}]}]}
+           {:db/id 2333,
+            :block/uuid #uuid "62f4b8c1-a99b-434f-84c3-011d6afc48ba",
+            :block/parent #:db{:id 2315},
+            :block/page #:db{:id 2313},
+            :block/level 2,
+            :block/children
+            [{:db/id 2334,
+              :block/uuid #uuid "62f4b8c6-072e-4133-90e2-0591021a7fea",
+              :block/parent #:db{:id 2333},
+              :block/page #:db{:id 2313},
+              :block/level 3}]}]}))))
+
 (comment
   (dotimes [i 5]
     (do