Merge pull request #11866 from logseq/refactor/page-parent

feat: Library
This commit is contained in:
Tienson Qin
2025-06-18 08:32:46 +08:00
committed by GitHub
106 changed files with 1147 additions and 739 deletions

View File

@@ -7,17 +7,17 @@
[logseq.e2e.fixtures :as fixtures]
[logseq.e2e.graph :as graph]
[logseq.e2e.keyboard :as k]
[logseq.e2e.locator :as loc]
[logseq.e2e.multi-tabs-basic-test]
[logseq.e2e.outliner-basic-test]
[logseq.e2e.plugins-basic-test]
[logseq.e2e.property-basic-test]
[logseq.e2e.reference-basic-test]
[logseq.e2e.rtc-basic-test]
[logseq.e2e.rtc-extra-test]
[logseq.e2e.property-basic-test]
[logseq.e2e.util :as util]
[wally.main :as w]
[wally.repl :as repl]
[logseq.e2e.locator :as loc]))
[wally.repl :as repl]))
;; Use port 3001 for local testing
(reset! config/*port 3001)

View File

@@ -1,7 +1,6 @@
(ns logseq.common.config
"Common config constants and fns that are shared between deps and app"
(:require [clojure.string :as string]
[goog.object :as gobj]))
(:require [clojure.string :as string]))
(goog-define PUBLISHING false)
@@ -30,15 +29,12 @@
(def app-name "logseq")
(defonce asset-protocol "assets://")
(defonce capacitor-protocol "capacitor://")
(defonce capacitor-prefix "_capacitor_file_")
(defonce capacitor-protocol-with-prefix (str capacitor-protocol "localhost/" capacitor-prefix))
(defonce capacitor-x-protocol-with-prefix (str (gobj/getValueByKeys js/globalThis "location" "href") capacitor-prefix))
(defonce local-assets-dir "assets")
(defonce favorites-page-name "$$$favorites")
(defonce views-page-name "$$$views")
(defonce library-page-name "Library")
(defn local-asset?
[s]
@@ -48,17 +44,13 @@
(defn local-protocol-asset?
[s]
(when (string? s)
(or (string/starts-with? s asset-protocol)
(string/starts-with? s capacitor-protocol)
(string/starts-with? s capacitor-x-protocol-with-prefix))))
(string/starts-with? s asset-protocol)))
(defn remove-asset-protocol
[s]
(if (local-protocol-asset? s)
(-> s
(string/replace-first asset-protocol "file://")
(string/replace-first capacitor-protocol-with-prefix "file://")
(string/replace-first capacitor-x-protocol-with-prefix "file://"))
(string/replace-first asset-protocol "file://"))
s))
(defonce default-draw-directory "draws")
@@ -154,4 +146,4 @@
:favorites
"is not stored in config for DB graphs"
:default-templates
"is replaced by #Template and the `Apply template to tags` property"}))
"is replaced by #Template and the `Apply template to tags` property"}))

View File

@@ -1,10 +1,10 @@
(ns logseq.common.config-test
(:require [cljs.test :refer [deftest is]]
[clojure.string :as string]
[logseq.common.config :as common-config]
(:require ["fs" :as fs]
["path" :as node-path]
#?(:org.babashka/nbb [nbb.classpath :as cp])
["fs" :as fs]
["path" :as node-path]))
[cljs.test :refer [deftest is]]
[clojure.string :as string]
[logseq.common.config :as common-config]))
(deftest remove-hidden-files
(let [files ["pages/foo.md" "pages/bar.md"

View File

@@ -41,7 +41,7 @@
(deftest escape-regex-chars
(testing "ensure the result is a valid regex string"
(are [x]
(some? (re-find (re-pattern (common-util/escape-regex-chars x)) x))
(some? (re-find (re-pattern (common-util/escape-regex-chars x)) x))
"[[page-name]]"
"end-with-backslash\\"
"\\[]{}().+*?|$^")))

View File

@@ -6,12 +6,12 @@
[babashka.cli :as cli]
[clojure.edn :as edn]
[datascript.core :as d]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.sqlite.export :as sqlite-export]
[logseq.outliner.cli :as outliner-cli]
[nbb.classpath :as cp]
[nbb.core :as nbb]
[validate-db]
[logseq.db.common.sqlite-cli :as sqlite-cli]))
[validate-db]))
(defn- resolve-path
"If relative path, resolve with $ORIGINAL_PWD"

View File

@@ -4,9 +4,9 @@
(:require [babashka.cli :as cli]
[cljs.pprint :as pprint]
[datascript.core :as d]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.frontend.malli-schema :as db-malli-schema]
[logseq.db.frontend.validate :as db-validate]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[malli.core :as m]
[malli.error :as me]
[nbb.core :as nbb]))

View File

@@ -7,6 +7,7 @@
[clojure.walk :as walk]
[datascript.core :as d]
[datascript.impl.entity :as de]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
[logseq.common.uuid :as common-uuid]
[logseq.db.common.delete-blocks :as delete-blocks] ;; Load entity extensions
@@ -22,6 +23,14 @@
[logseq.db.sqlite.util :as sqlite-util])
(:refer-clojure :exclude [object?]))
(def built-in? entity-util/built-in?)
(def built-in-class-property? db-db/built-in-class-property?)
(def private-built-in-page? db-db/private-built-in-page?)
(def write-transit-str sqlite-util/write-transit-str)
(def read-transit-str sqlite-util/read-transit-str)
(def build-favorite-tx db-db/build-favorite-tx)
(defonce *transact-fn (atom nil))
(defn register-transact-fn!
[f]
@@ -242,6 +251,17 @@
(d/entity db [:block/uuid id])
(d/entity db (get-first-page-by-name db (name page-id-name-or-uuid)))))))
(defn get-built-in-page
[db title]
(when db
(let [id (common-uuid/gen-uuid :builtin-block-uuid title)]
(d/entity db [:block/uuid id]))))
(defn library?
[page]
(and (built-in? page)
(= common-config/library-page-name (:block/title page))))
(defn get-case-page
"Case sensitive version of get-page. For use with DB graphs"
[db page-name-or-uuid]
@@ -470,14 +490,6 @@
(when-not (hidden-or-internal-tag? e)
e))))))
(def built-in? entity-util/built-in?)
(def built-in-class-property? db-db/built-in-class-property?)
(def private-built-in-page? db-db/private-built-in-page?)
(def write-transit-str sqlite-util/write-transit-str)
(def read-transit-str sqlite-util/read-transit-str)
(def build-favorite-tx db-db/build-favorite-tx)
(defn get-key-value
[db key-ident]
(:kv/value (d/entity db key-ident)))
@@ -499,7 +511,7 @@
(when db (get-key-value db :logseq.kv/remote-schema-version)))
(def get-all-properties db-db/get-all-properties)
(def get-page-parents db-db/get-page-parents)
(def get-class-extends db-db/get-class-extends)
(def get-classes-parents db-db/get-classes-parents)
(def get-title-with-parents db-db/get-title-with-parents)
(def class-instance? db-db/class-instance?)

View File

@@ -1,6 +1,7 @@
(ns logseq.db.common.initial-data
"Provides db helper fns for graph initialization and lazy loading entities"
(:require [datascript.core :as d]
(:require [clojure.string :as string]
[datascript.core :as d]
[datascript.impl.entity :as de]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
@@ -329,7 +330,9 @@
rseq
(keep (fn [datom]
(let [e (d/entity db (:e datom))]
(when (and (common-entity-util/page? e) (not (entity-util/hidden? e)))
(when (and (common-entity-util/page? e)
(not (entity-util/hidden? e))
(not (string/blank? (:block/title e))))
e))))
(take 30)))

View File

@@ -27,12 +27,12 @@
:logseq.class/Journal
{:title "Journal"
:properties {:logseq.property/parent :logseq.class/Page
:properties {:logseq.property.class/extends :logseq.class/Page
:logseq.property.journal/title-format "MMM do, yyyy"}}
:logseq.class/Whiteboard
{:title "Whiteboard"
:properties {:logseq.property/parent :logseq.class/Page}}
:properties {:logseq.property.class/extends :logseq.class/Page}}
:logseq.class/Task
{:title "Task"
@@ -50,7 +50,7 @@
:logseq.class/Cards
{:title "Cards"
:properties {:logseq.property/icon {:type :tabler-icon :id "search"}
:logseq.property/parent :logseq.class/Query}}
:logseq.property.class/extends :logseq.class/Query}}
:logseq.class/Asset
{:title "Asset"
@@ -95,7 +95,7 @@
"Children of :logseq.class/Page"
(set
(keep (fn [[class-ident m]]
(when (= (get-in m [:properties :logseq.property/parent]) :logseq.class/Page) class-ident))
(when (= (get-in m [:properties :logseq.property.class/extends]) :logseq.class/Page) class-ident))
built-in-classes)))
(def page-classes
@@ -121,13 +121,13 @@
(defn get-structured-children
[db eid]
(->>
(d/q '[:find [?children ...]
:in $ ?parent %
(d/q '[:find [?c ...]
:in $ ?p %
:where
(parent ?parent ?children)]
(class-extends ?p ?c)]
db
eid
(:parent rules/rules))
(:class-extends rules/rules))
(remove #{eid})))
;; Helper fns

View File

@@ -3,6 +3,7 @@
(:require [clojure.set :as set]
[clojure.string :as string]
[datascript.core :as d]
[logseq.common.config :as common-config]
[logseq.common.util.namespace :as ns-util]
[logseq.common.util.page-ref :as page-ref]
[logseq.db.frontend.class :as db-class]
@@ -42,34 +43,60 @@
(->> (d/datoms db :avet :block/tags :logseq.class/Property)
(map (fn [d] (d/entity db (:e d))))))
(defn get-page-parents
[node & {:keys [node-class?]}]
(when-let [parent (:logseq.property/parent node)]
(defn get-class-extends
"Returns all parents of a class"
[node]
(when-let [parent (:logseq.property.class/extends node)]
(loop [current-parent parent
parents' []]
(if (and
current-parent
(if node-class? (entity-util/class? current-parent) true)
(not (contains? parents' current-parent)))
(recur (:logseq.property/parent current-parent)
(if (and current-parent
(not (contains? parents' current-parent)))
(recur (:logseq.property.class/extends current-parent)
(conj parents' current-parent))
(vec (reverse parents'))))))
(defn get-page-parents
[node]
(when-let [parent (:block/parent node)]
(loop [current-parent parent
parents' []]
(if (and current-parent
(not (contains? parents' current-parent)))
(recur (:block/parent current-parent)
(conj parents' current-parent))
(vec (reverse parents'))))))
(defn- get-class-title-with-extends
[entity]
(let [parents' (->> (get-class-extends entity)
(remove (fn [e] (= :logseq.class/Root (:db/ident e))))
vec)]
(string/join
ns-util/parent-char
(map :block/title (conj (vec parents') entity)))))
(defn get-title-with-parents
[entity]
(if (or (entity-util/class? entity) (entity-util/internal-page? entity))
(cond
(entity-util/class? entity)
(get-class-title-with-extends entity)
(entity-util/page? entity)
(let [parents' (->> (get-page-parents entity)
(remove (fn [e] (= :logseq.class/Root (:db/ident e))))
vec)]
(remove (fn [e]
(and (:logseq.property/built-in? e) (= common-config/library-page-name (:block/title e))))))]
(string/join
ns-util/parent-char
(map :block/title (conj (vec parents') entity))))
:else
(:block/title entity)))
(defn get-classes-parents
"Returns all parents of all classes. Like get-class-extends but for multiple classes"
[tags]
(let [tags' (filter entity-util/class? tags)
result (mapcat #(get-page-parents % {:node-class? true}) tags')]
result (mapcat get-class-extends tags')]
(set result)))
(defn class-instance?

View File

@@ -282,7 +282,9 @@
(concat
[:map
;; journal-day is only set for journal pages
[:block/journal-day {:optional true} :int]]
[:block/journal-day {:optional true} :int]
[:block/parent {:optional true} :int]
[:block/order {:optional true} block-order]]
page-attrs
page-or-block-attrs)))
@@ -290,7 +292,8 @@
(vec
(concat
[:map
[:db/ident class-ident]]
[:db/ident class-ident]
[:logseq.property.class/extends {:optional true} :int]]
page-attrs
page-or-block-attrs)))

View File

@@ -148,13 +148,6 @@
:hide? true
:view-context :block}
:queryable? true}
:logseq.property/parent {:title "Parent"
:schema {:type :node
:public? true
:view-context :page}
:queryable? true
:properties
{:logseq.property/description "Provides parent-child relationships between nodes. For tags this enables inheritance and for pages this enables namespaces."}}
:logseq.property/default-value {:title "Default value"
:schema {:type :entity
:public? false
@@ -165,6 +158,14 @@
:public? false
:hide? true
:view-context :property}}
;; TODO: support cardinality many for extends
:logseq.property.class/extends {:title "Extends"
:schema {:type :class
:public? true
:view-context :class}
:queryable? true
:properties
{:logseq.property/description "This enables tags to inherit properties from other tags"}}
:logseq.property.class/properties {:title "Tag Properties"
:schema {:type :property
:cardinality :many

View File

@@ -8,11 +8,18 @@
;; rule "parent" is optimized for parent node -> child node nesting queries
{:parent
'[[(parent ?p ?c)
[?c :logseq.property/parent ?p]]
[?c :block/parent ?p]]
[(parent ?p ?c)
[?t :logseq.property/parent ?p]
[?t :block/parent ?p]
(parent ?t ?c)]]
:class-extends
'[[(class-extends ?p ?c)
[?c :logseq.property.class/extends ?p]]
[(class-extends ?p ?c)
[?t :logseq.property.class/extends ?p]
(class-extends ?t ?c)]]
:alias
'[[(alias ?e2 ?e1)
[?e2 :block/alias ?e1]]
@@ -136,7 +143,7 @@
[?b :block/tags ?tc]
(or
[(= ?t ?tc)]
(parent ?t ?tc))]
(class-extends ?t ?tc))]
:has-property-or-object-property
'[(has-property-or-object-property? ?b ?prop)
@@ -236,7 +243,7 @@
:priority #{:simple-query-property}
:property-missing-value #{:object-has-class-property}
:has-property-or-object-property #{:object-has-class-property}
:object-has-class-property #{:parent}
:object-has-class-property #{:class-extends}
:has-simple-query-property #{:has-property-or-object-property}
:has-private-simple-query-property #{:has-property-or-object-property}
:property-default-value #{:existing-property-value :property-missing-value}

View File

@@ -37,7 +37,7 @@
(map (juxt :major :minor)
[(parse-schema-version x) (parse-schema-version y)])))
(def version (parse-schema-version "65.0"))
(def version (parse-schema-version "65.1"))
(defn major-version
"Return a number.

View File

@@ -118,7 +118,7 @@
{:entities (count entities)
:pages (count (filter :block/name entities))
;; Nodes that aren't pages
:blocks (count (filter :block/parent entities))
:blocks (count (filter :block/page entities))
:classes classes-count
:properties properties-count
;; Objects that aren't classes or properties

View File

@@ -282,7 +282,7 @@
(->block-properties (merge props (db-property-build/build-properties-with-ref-values pvalue-tx-m))
uuid-maps all-idents options))
(when class-parent
{:logseq.property/parent
{:logseq.property.class/extends
(or (class-db-ids class-parent)
(if (db-malli-schema/class? class-parent)
class-parent

View File

@@ -13,7 +13,7 @@
[logseq.db.frontend.schema :as db-schema]
[logseq.db.sqlite.util :as sqlite-util]))
(defn- mark-block-as-built-in [block]
(defn mark-block-as-built-in [block]
(assoc block :logseq.property/built-in? true))
(defn- schema->qualified-property-keyword
@@ -128,7 +128,8 @@
:properties (filter entity-util/property? properties-tx)}))
(def built-in-pages-names
#{"Contents"})
#{common-config/library-page-name
"Contents"})
(defn- validate-tx-for-duplicate-idents [tx]
(when-let [conflicting-idents
@@ -232,7 +233,7 @@
(map mark-block-as-built-in))
hidden-pages (concat (build-initial-views) (build-favorites-page))
;; These classes bootstrap our tags and properties as they depend on each other e.g.
;; Root <-> Tag, classes-tx depends on logseq.property/parent, properties-tx depends on Property
;; Root <-> Tag, classes-tx depends on logseq.property.class/extends, properties-tx depends on Property
bootstrap-class? (fn [c] (contains? #{:logseq.class/Root :logseq.class/Property :logseq.class/Tag :logseq.class/Template} (:db/ident c)))
bootstrap-classes (filter bootstrap-class? default-classes)
bootstrap-class-ids (map #(select-keys % [:db/ident :block/uuid]) bootstrap-classes)

View File

@@ -7,16 +7,16 @@
[datascript.core :as d]
[datascript.impl.entity :as de]
[logseq.db :as ldb]
[logseq.db.common.entity-plus :as entity-plus]
[logseq.db.frontend.class :as db-class]
[logseq.db.frontend.content :as db-content]
[logseq.db.frontend.db :as db-db]
[logseq.db.common.entity-plus :as entity-plus]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.property :as db-property]
[logseq.db.sqlite.build :as sqlite-build]
[medley.core :as medley]
[logseq.db.frontend.property.type :as db-property-type]
[logseq.db.frontend.schema :as db-schema]))
[logseq.db.frontend.schema :as db-schema]
[logseq.db.sqlite.build :as sqlite-build]
[medley.core :as medley]))
;; Export fns
;; ==========
@@ -166,7 +166,7 @@
(map (fn [[ent build-property]]
(let [ent-properties (apply dissoc (db-property/properties ent)
;; For overlapping class properties, these would be built in :classes
:logseq.property/parent :logseq.property.class/properties
:logseq.property.class/extends :logseq.property.class/properties
(into db-property/schema-properties db-property/public-db-attribute-properties))]
[(:db/ident ent)
(cond-> build-property
@@ -191,10 +191,10 @@
(assoc :block/alias (set (map #(vector :block/uuid (:block/uuid %)) (:block/alias class-ent))))
;; It's caller's responsibility to ensure parent is included in final export
(and (not shallow-copy?)
(:logseq.property/parent class-ent)
(not= :logseq.class/Root (:db/ident (:logseq.property/parent class-ent))))
(:logseq.property.class/extends class-ent)
(not= :logseq.class/Root (:db/ident (:logseq.property.class/extends class-ent))))
(assoc :build/class-parent
(:db/ident (:logseq.property/parent class-ent)))))
(:db/ident (:logseq.property.class/extends class-ent)))))
(defn- build-node-classes
[db build-block block-tags properties]
@@ -589,7 +589,7 @@
classes
(->> class-ents
(map (fn [ent]
(let [ent-properties (apply dissoc (db-property/properties ent) :logseq.property/parent db-property/public-db-attribute-properties)]
(let [ent-properties (apply dissoc (db-property/properties ent) :logseq.property.class/extends db-property/public-db-attribute-properties)]
(vector (:db/ident ent)
(cond-> (build-export-class ent options)
(seq ent-properties)

View File

@@ -98,16 +98,16 @@
(cond-> (merge block
{:block/tags (set (conj (:block/tags block) :logseq.class/Tag))})
(and (not= (:db/ident block) :logseq.class/Root)
(nil? (:logseq.property/parent block)))
(assoc :logseq.property/parent :logseq.class/Root))))
(nil? (:logseq.property.class/extends block)))
(assoc :logseq.property.class/extends :logseq.class/Root))))
(defn build-new-page
"Builds a basic page to be transacted. A minimal version of gp-block/page-name->map"
[page-name]
[title]
(block-with-timestamps
{:block/name (common-util/page-name-sanity-lc page-name)
:block/title page-name
:block/uuid (common-uuid/gen-uuid :builtin-block-uuid page-name)
{:block/name (common-util/page-name-sanity-lc title)
:block/title title
:block/uuid (common-uuid/gen-uuid :builtin-block-uuid title)
:block/tags #{:logseq.class/Page}}))
(defn kv

View File

@@ -12,7 +12,7 @@
(deftest get-full-deps
(let [default-value-deps #{:property-default-value :property-missing-value :existing-property-value
:object-has-class-property :parent}
:object-has-class-property :class-extends}
property-value-deps (conj default-value-deps :property-value :property-scalar-default-value)
property-deps (conj property-value-deps :simple-query-property)
task-deps (conj property-deps :task)

View File

@@ -84,9 +84,9 @@
(is (= (count (dissoc db-class/built-in-classes :logseq.class/Root))
(count (->> (d/datoms @conn :avet :block/tags :logseq.class/Tag)
(map #(d/entity @conn (:e %)))
(mapcat :logseq.property/_parent)
(mapcat :logseq.property.class/_extends)
set)))
"Reverse lookup of :logseq.property/parent correctly fetches number of child classes")))
"Reverse lookup of :logseq.property.class/extends correctly fetches number of child classes")))
(deftest new-graph-initializes-default-properties-correctly
(let [conn (db-test/create-conn)]

View File

@@ -695,6 +695,8 @@
{:page {:block/uuid property-uuid}
:blocks [{:block/title "property block1"}]}
;; built-in pages
{:page {:block/title "Library" :build/properties {:logseq.property/built-in? true}}
:blocks []}
{:page {:block/title "Contents" :build/properties {:logseq.property/built-in? true}}
:blocks [{:block/title "right sidebar"}]}
{:page {:block/title common-config/favorites-page-name
@@ -882,9 +884,9 @@
(deftest build-import-can-import-existing-page-with-different-uuid
(testing "By default any properties passed to an existing page are upserted"
(test-import-existing-page {}
{:logseq.property/description "second description"
:logseq.property/exclude-from-graph-view true}))
{:logseq.property/description "second description"
:logseq.property/exclude-from-graph-view true}))
(testing "With ::existing-pages-keep-properties?, existing properties on existing pages are not overwritten by imported data"
(test-import-existing-page {:existing-pages-keep-properties? true}
{:logseq.property/description "first description"
:logseq.property/exclude-from-graph-view true})))
:logseq.property/exclude-from-graph-view true})))

View File

@@ -30,18 +30,18 @@
:block/title "y"
:block/name "y"
:block/uuid #uuid "7008db08-ba0c-4aa9-afc6-7e4783e40a99"
:logseq.property/parent [:block/uuid #uuid "6c353967-f79b-4785-b804-a39b81d72461"]}
:logseq.property.class/extends [:block/uuid #uuid "6c353967-f79b-4785-b804-a39b81d72461"]}
{:block/tags :logseq.class/Tag
:block/title "z"
:block/name "z"
:block/uuid #uuid "d95f2912-a7af-41b9-8ed5-28861f7fc0be"
:logseq.property/parent [:block/uuid #uuid "7008db08-ba0c-4aa9-afc6-7e4783e40a99"]}])
:logseq.property.class/extends [:block/uuid #uuid "7008db08-ba0c-4aa9-afc6-7e4783e40a99"]}])
(deftest get-page-parents
(deftest get-class-extends
(let [conn (db-test/create-conn)]
(d/transact! conn class-parents-data)
(is (= #{"x" "y"}
(->> (ldb/get-page-parents (ldb/get-page @conn "z") {:node-class? true})
(->> (ldb/get-class-extends (ldb/get-page @conn "z"))
(map :block/title)
set)))))

View File

@@ -10,6 +10,7 @@
[clojure.set :as set]
[clojure.string :as string]
[datascript.core :as d]
[logseq.common.config :as common-config]
[logseq.common.graph :as common-graph]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.frontend.asset :as db-asset]
@@ -18,8 +19,7 @@
[logseq.outliner.pipeline :as outliner-pipeline]
[nbb.classpath :as cp]
[nbb.core :as nbb]
[promesa.core :as p]
[logseq.common.config :as common-config]))
[promesa.core :as p]))
(def tx-queue (atom cljs.core/PersistentQueue.EMPTY))
(def original-transact! d/transact!)
@@ -209,4 +209,4 @@
(println "Created graph" (str db-name "!")))))
(when (= nbb/*file* (nbb/invoked-file))
(-main *command-line-args*))
(-main *command-line-args*))

View File

@@ -63,10 +63,10 @@
(merge ex-data' {:page-name page-name
:page-names (sort (keys @page-names-to-uuids))})))))
(defn- replace-namespace-with-parent [block page-names-to-uuids]
(defn- replace-namespace-with-parent [block page-names-to-uuids parent-k]
(if (:block/namespace block)
(-> (dissoc block :block/namespace)
(assoc :logseq.property/parent
(assoc parent-k
{:block/uuid (get-page-uuid page-names-to-uuids
(get-in block [:block/namespace :block/name])
{:block block :block/namespace (:block/namespace block)})}))
@@ -130,7 +130,7 @@
db
(ns-util/get-last-part full-name))
(map #(d/entity db %))
(some #(let [parents (->> (ldb/get-page-parents %)
(some #(let [parents (->> (ldb/get-class-extends %)
(remove (fn [e] (= :logseq.class/Root (:db/ident e))))
vec)]
(when (= full-name (string/join ns-util/namespace-char (map :block/name (conj parents %))))
@@ -175,7 +175,7 @@
(dissoc :block/created-at :block/updated-at)
(merge (add-missing-timestamps
(select-keys tag-block [:block/created-at :block/updated-at])))
(replace-namespace-with-parent page-names-to-uuids))]
(replace-namespace-with-parent page-names-to-uuids :logseq.property.class/extends))]
(when (:new-class? (meta class-m)) (swap! classes-tx conj class-m'))
(assert (:block/uuid class-m') "Class must have a :block/uuid")
[:block/uuid (:block/uuid class-m')]))))))
@@ -427,9 +427,12 @@
(def all-built-in-names
"All built-in properties and classes as a set of keywords"
(set/union all-built-in-property-file-ids
;; This should list all new pages introduced with db graph
(set (->> db-class/built-in-classes
vals
(map #(-> % :title string/lower-case keyword))))))
(map :title)
(concat [common-config/library-page-name])
(map #(-> % string/lower-case keyword))))))
(def file-built-in-property-names
"File-graph built-in property names that are supported. Expressed as set of keywords"
@@ -739,7 +742,7 @@
(update :block dissoc :block/properties :block/properties-text-values :block/properties-order :block/invalid-properties)))
(defn- handle-page-properties
"Adds page properties including special handling for :logseq.property/parent"
"Adds page properties including special handling for :logseq.property.class/extends or :block/parent"
[{:block/keys [properties] :as block*} db {:keys [page-names-to-uuids classes-tx]} refs
{:keys [user-options log-fn import-state] :as options}]
(let [{:keys [block properties-tx]} (handle-page-and-block-properties block* db page-names-to-uuids refs options)
@@ -752,7 +755,8 @@
class-m (find-or-create-class db ((some-fn ::original-title :block/title) block) (:all-idents import-state) block)
class-m' (-> block
(merge class-m)
(assoc :logseq.property/parent
(dissoc :block/namespace)
(assoc :logseq.property.class/extends
(let [new-class (first parent-classes-from-properties)
class-m (find-or-create-class db new-class (:all-idents import-state))
class-m' (merge class-m
@@ -762,9 +766,8 @@
(when (:new-class? (meta class-m)) (swap! classes-tx conj class-m'))
[:block/uuid (:block/uuid class-m')])))]
class-m')
block)
block'' (replace-namespace-with-parent block' page-names-to-uuids)]
{:block block'' :properties-tx properties-tx}))
(replace-namespace-with-parent block page-names-to-uuids :block/parent))]
{:block block' :properties-tx properties-tx}))
(defn- pretty-print-dissoc
"Remove list of keys from a given map string while preserving whitespace"
@@ -918,10 +921,10 @@
"\\)(\\{[^}]*\\})?"))
(page-ref/->page-ref asset-uuid))]
(when (string/includes? new-title asset-name)
(swap! ignored-assets conj
{:reason "Some asset links were not updated to block references"
:path asset-name
:location {:block new-title}}))
(swap! ignored-assets conj
{:reason "Some asset links were not updated to block references"
:path asset-name
:location {:block new-title}}))
new-title))
block-title
asset-name-to-uuids))
@@ -1025,10 +1028,11 @@
"Like ldb/get-page-parents but using all-existing-page-uuids"
[node all-existing-page-uuids]
(let [get-parent (fn get-parent [n]
(when (:block/uuid (:logseq.property/parent n))
(or (get all-existing-page-uuids (:block/uuid (:logseq.property/parent n)))
(throw (ex-info (str "No parent page found for " (pr-str (:block/uuid (:logseq.property/parent n))))
{:node n})))))]
(let [parent (or (:logseq.property.class/extends n) (:block/parent n))]
(when-let [parent-id (:block/uuid parent)]
(or (get all-existing-page-uuids parent-id)
(throw (ex-info (str "No parent page found for " (pr-str (:block/uuid parent)))
{:node n}))))))]
(when-let [parent (get-parent node)]
(loop [current-parent parent
parents' []]
@@ -1063,7 +1067,7 @@
(let [;; These attributes are not allowed to be transacted because they must not change across files
disallowed-attributes [:block/name :block/uuid :block/format :block/title :block/journal-day
:block/created-at :block/updated-at]
allowed-attributes (into [:block/tags :block/alias :logseq.property/parent :db/ident]
allowed-attributes (into [:block/tags :block/alias :block/parent :logseq.property.class/extends :db/ident]
(keep #(when (db-malli-schema/user-property? (key %)) (key %))
m))
block-changes (select-keys m allowed-attributes)]
@@ -1267,6 +1271,15 @@
(set (map (comp keyword string/lower-case) (:property-parent-classes user-options)))
file-built-in-property-names)})}))
(defn- retract-parent-and-page-tag
[col]
(vec
(mapcat (fn [b]
(let [eid [:block/uuid (:block/uuid b)]]
[[:db/retract eid :block/parent]
[:db/retract eid :block/tags :logseq.class/Page]]))
col)))
(defn- split-pages-and-properties-tx
"Separates new pages from new properties tx in preparation for properties to
be transacted separately. Also builds property pages tx and converts existing
@@ -1295,8 +1308,7 @@
{:block/uuid existing-page-uuid})))
(set/intersection new-properties (set (map keyword (keys existing-pages)))))
;; Could do this only for existing pages but the added complexity isn't worth reducing the tx noise
retract-page-tag-from-properties-tx (map #(vector :db/retract [:block/uuid (:block/uuid %)] :block/tags :logseq.class/Page)
(concat property-pages-tx converted-property-pages-tx))
retract-page-tag-from-properties-tx (retract-parent-and-page-tag (concat property-pages-tx converted-property-pages-tx))
;; Save properties on new property pages separately as they can contain new properties and thus need to be
;; transacted separately the property pages
property-page-properties-tx (keep (fn [b]
@@ -1420,17 +1432,18 @@
(->> pages-tx'
;; Existing pages that have converted to property or class
(filter #(and (:db/ident %) (get existing-pages' (:block/uuid %))))
(mapv #(vector :db/retract [:block/uuid (:block/uuid %)] :block/tags :logseq.class/Page)))]
retract-parent-and-page-tag)]
{:pages-tx
(mapv (fn [page]
(if (or (contains? classes (:block/uuid page))
(contains? existing-properties (:block/uuid page)))
(update page :block/tags (fn [tags] (vec (remove #(= % :logseq.class/Page) tags))))
(-> page
(update :block/tags (fn [tags] (vec (remove #(= % :logseq.class/Page) tags))))
(dissoc :block/parent))
page))
pages-tx')
:retract-page-tags-tx
(into (mapv #(vector :db/retract [:block/uuid (:block/uuid %)] :block/tags :logseq.class/Page)
classes-tx)
(into (retract-parent-and-page-tag classes-tx)
retract-page-tag-from-existing-pages)}))
(defn- save-from-tx
@@ -1730,6 +1743,26 @@
:macros (or (:macros options) (:macros config))}
(merge (select-keys options [:set-ui-state :export-file :notify-user]))))
(defn- move-top-parent-pages-to-library
[conn repo-or-conn]
(let [db @conn
library-page (ldb/get-built-in-page db common-config/library-page-name)
library-id (:block/uuid library-page)
top-parent-pages (->> (d/datoms db :avet :block/parent)
(keep (fn [d]
(let [child (d/entity db (:e d))
parent (d/entity db (:v d))]
(when (and (nil? (:block/parent parent)) (ldb/page? child) (ldb/page? parent))
parent))))
(common-util/distinct-by :block/uuid))
tx-data (map
(fn [parent]
{:db/id (:db/id parent)
:block/parent [:block/uuid library-id]
:block/order (db-order/gen-key)})
top-parent-pages)]
(ldb/transact! repo-or-conn tx-data)))
(defn export-file-graph
"Main fn which exports a file graph given its files and imports them
into a DB graph. Files is expected to be a seq of maps with a :path key.
@@ -1781,6 +1814,7 @@
(select-keys options [:notify-user :set-ui-state]))
(export-favorites-from-config-edn conn repo-or-conn config {})
(export-class-properties conn repo-or-conn)
(move-top-parent-pages-to-library conn repo-or-conn)
{:import-state (-> (:import-state doc-options)
;; don't leak full asset content (which could be large) out of this ns
(dissoc :assets))

View File

@@ -364,7 +364,7 @@
(:block/alias (db-test/readable-properties (db-test/find-page-by-title @conn "chat-gpt"))))
"alias set correctly")
(is (= ["y"]
(->> (d/q '[:find [?b ...] :where [?b :block/title "y"] [?b :logseq.property/parent]]
(->> (d/q '[:find [?b ...] :where [?b :block/title "y"] [?b :block/parent]]
@conn)
first
(d/entity @conn)
@@ -440,14 +440,15 @@
(testing "namespaces"
(let [expand-children (fn expand-children [ent parent]
(if-let [children (:logseq.property/_parent ent)]
(if-let [children (:block/_parent ent)]
(cons {:parent (:block/title parent) :child (:block/title ent)}
(mapcat #(expand-children % ent) children))
[{:parent (:block/title parent) :child (:block/title ent)}]))]
;; check pages only
(is (= [{:parent "n1" :child "x"}
{:parent "x" :child "z"}
{:parent "x" :child "y"}]
(rest (expand-children (db-test/find-page-by-title @conn "n1") nil)))
(take 3 (rest (expand-children (db-test/find-page-by-title @conn "n1") nil))))
"First namespace tests duplicate parent page name")
(is (= [{:parent "n2" :child "x"}
{:parent "x" :child "z"}
@@ -640,7 +641,8 @@
(p/let [file-graph-dir "test/resources/exporter-test-graph"
files (mapv #(node-path/join file-graph-dir %)
["journals/2024_02_23.md" "pages/url.md" "pages/Whiteboard___Tool.md"
"pages/Whiteboard___Arrow_head_toggle.md"])
"pages/Whiteboard___Arrow_head_toggle.md"
"pages/Library.md"])
conn (db-test/create-conn)
_ (import-files-to-db files conn {:property-classes ["type"]})
_ (@#'gp-exporter/export-class-properties conn conn)]
@@ -725,9 +727,9 @@
set))
"All classes are correctly defined by :type")
(is (= "CreativeWork" (get-in (d/entity @conn :user.class/Movie) [:logseq.property/parent :block/title]))
(is (= "CreativeWork" (get-in (d/entity @conn :user.class/Movie) [:logseq.property.class/extends :block/title]))
"Existing page correctly set as class parent")
(is (= "Thing" (get-in (d/entity @conn :user.class/CreativeWork) [:logseq.property/parent :block/title]))
(is (= "Thing" (get-in (d/entity @conn :user.class/CreativeWork) [:logseq.property.class/extends :block/title]))
"New page correctly set as class parent")))
(deftest-async export-files-with-property-pages-disabled

View File

@@ -0,0 +1 @@
type:: [[Class]]

View File

@@ -2,8 +2,8 @@
"This script generically runs transactions against the queried blocks"
(:require [clojure.edn :as edn]
[datascript.core :as d]
[logseq.db.frontend.rules :as rules]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.frontend.rules :as rules]
[logseq.outliner.db-pipeline :as db-pipeline]
[nbb.core :as nbb]))

View File

@@ -73,4 +73,4 @@
(def build-blocks-tx
"An alias for build-blocks-tx to specify default options for this ns"
sqlite-build/build-blocks-tx)
sqlite-build/build-blocks-tx)

View File

@@ -261,7 +261,7 @@
(not= block-title (:block/title block-entity)))
_ (when (and db-based? page? block-title)
(outliner-validate/validate-page-title-characters block-title {:node m*}))
m* (if (and db-based? page-title-changed?)
m* (if (and db-based? page-title-changed? (not (and page? (:block/parent block-entity))))
(let [_ (outliner-validate/validate-page-title (:block/title m*) {:node m*})
page-name (common-util/page-name-sanity-lc (:block/title m*))]
(assoc m* :block/name page-name))
@@ -334,7 +334,7 @@
txs (map (fn [id] [:db.fn/retractEntity [:block/uuid id]]) ids)
page-tx (let [block (d/entity db [:block/uuid block-id])]
(when (:block/pre-block? block)
(let [id (:db/id (:block/page block))]
(when-let [id (:db/id (:block/page block))]
[[:db/retract id :block/properties]
[:db/retract id :block/properties-order]
[:db/retract id :block/properties-text-values]
@@ -503,12 +503,21 @@
{}
block)))
(defn- get-target-block-page
[target-block]
(or
(:db/id (:block/page target-block))
;; target parent is a page
(when-let [parent (:block/parent target-block)]
(when (ldb/page? parent)
(:db/id parent)))
;; target-block is a page itself
(:db/id target-block)))
(defn- build-insert-blocks-tx
[db target-block blocks uuids get-new-id {:keys [sibling? outliner-op replace-empty-target? insert-template? keep-block-order?]}]
(let [block-ids (set (map :block/uuid blocks))
target-page (or (:db/id (:block/page target-block))
;; target block is a page itself
(:db/id target-block))
target-page (get-target-block-page target-block)
orders (get-block-orders blocks target-block sibling? keep-block-order?)]
(map-indexed (fn [idx {:block/keys [parent] :as block}]
(when-let [uuid' (get uuids (:block/uuid block))]
@@ -522,11 +531,13 @@
(let [ref-ids (set (map :block/uuid (:block/refs block)))]
(->> (set/intersection block-ids ref-ids)
(remove #{(:block/uuid block)})))))
m {:db/id (:db/id block)
:block/uuid uuid'
:block/page target-page
:block/parent parent
:block/order order}
m (cond->
{:db/id (:db/id block)
:block/uuid uuid'
:block/parent parent
:block/order order}
(not (ldb/page? block))
(assoc :block/page target-page))
result (->
(if (de/entity? block)
(assoc m :block/level (:block/level block))
@@ -610,7 +621,6 @@
:else
[block sibling?])
sibling? (if (ldb/page? block) false sibling?)
block (if (de/entity? block) block (d/entity db (:db/id block)))]
[block sibling?])))
@@ -684,7 +694,6 @@
blocks)
[target-block sibling?] (get-target-block db blocks target-block opts)
_ (assert (some? target-block) (str "Invalid target: " target-block))
sibling? (if (ldb/page? target-block) false sibling?)
replace-empty-target? (if (and (some? replace-empty-target?)
(:block/title target-block)
(string/blank? (:block/title target-block)))
@@ -773,8 +782,7 @@
[db blocks]
(let [top-level-blocks (filter-top-level-blocks db blocks)
non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks db top-level-blocks)))
top-level-blocks* (->> (get-top-level-blocks top-level-blocks non-consecutive?)
(remove ldb/page?))
top-level-blocks* (get-top-level-blocks top-level-blocks non-consecutive?)
top-level-blocks (remove :logseq.property/built-in? top-level-blocks*)
txs-state (ds/new-outliner-txs-state)
block-ids (map (fn [b] [:block/uuid (:block/uuid b)]) top-level-blocks)
@@ -824,10 +832,8 @@
(let [target-block (d/entity db (:db/id target-block))
block (d/entity db (:db/id block))
first-block-page (:db/id (:block/page block))
target-page (or (:db/id (:block/page target-block))
(:db/id target-block))
target-page (get-target-block-page target-block)
not-same-page? (not= first-block-page target-page)
block-order (if sibling?
(db-order/gen-key (:block/order target-block)
(:block/order (ldb/get-right-sibling target-block)))
@@ -840,12 +846,15 @@
(:db/id (:block/parent target-block))
(:db/id target-block))
:block/order block-order}
not-same-page?
(not (ldb/page? block))
(assoc :block/page target-page))]
children-page-tx (when not-same-page?
(let [children-ids (ldb/get-block-children-ids db (:block/uuid block))]
(map (fn [id] {:block/uuid id
:block/page target-page}) children-ids)))
(keep (fn [id]
(let [child (d/entity db [:block/uuid id])]
(when-not (ldb/page? child)
{:block/uuid id
:block/page target-page}))) children-ids)))
target-from-property (:logseq.property/created-from-property target-block)
block-from-property (:logseq.property/created-from-property block)
property-tx (let [retract-property-tx (when block-from-property
@@ -985,7 +994,7 @@
:sibling? true
:indent? false})))
(when (and parent (not (ldb/page? (d/entity db (:db/id parent)))))
(when parent
(let [blocks' (take-while (fn [b]
(not= (:db/id (:block/parent b))
(:db/id (:block/parent parent))))

View File

@@ -314,8 +314,8 @@
_ (when (= property-id :block/tags)
(outliner-validate/validate-tags-property @conn block-eids v))
property (d/entity @conn property-id)
_ (when (= (:db/ident property) :logseq.property/parent)
(outliner-validate/validate-parent-property
_ (when (= (:db/ident property) :logseq.property.class/extends)
(outliner-validate/validate-extends-property
(if (number? v) (d/entity @conn v) v)
(map #(d/entity @conn %) block-eids)))
_ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
@@ -359,9 +359,9 @@
property-id :logseq.property/empty-placeholder}]
{:outliner-op :save-block})
(and (ldb/class? block) (= property-id :logseq.property/parent))
(and (ldb/class? block) (= property-id :logseq.property.class/extends))
(ldb/transact! conn
[[:db/add (:db/id block) :logseq.property/parent :logseq.class/Root]]
[[:db/add (:db/id block) :logseq.property.class/extends :logseq.class/Root]]
{:outliner-op :save-block})
(contains? db-property/db-attribute-properties property-id)
@@ -387,8 +387,8 @@
db-attribute? (some? (db-schema/schema property-id))]
(when (= property-id :block/tags)
(outliner-validate/validate-tags-property @conn [block-eid] v))
(when (= property-id :logseq.property/parent)
(outliner-validate/validate-parent-property v [block]))
(when (= property-id :logseq.property.class/extends)
(outliner-validate/validate-extends-property v [block]))
(cond
db-attribute?
(when-not (and (= property-id :block/alias) (= v (:db/id block))) ; alias can't be itself

View File

@@ -46,59 +46,74 @@
:payload {:message "Built-in pages can't be edited"
:type :warning}}))))
(defn- validate-unique-by-parent-and-name [db entity new-title]
(defn- validate-unique-by-extends-and-name [db entity new-title]
(when-let [_res (seq (d/q '[:find [?b ...]
:in $ ?eid ?type ?title
:where
[?b :block/title ?title]
[?b :logseq.property/parent ?type]
[?b :logseq.property.class/extends ?type]
[(not= ?b ?eid)]]
db
(:db/id entity)
(:db/id (:logseq.property/parent entity))
(:db/id (:logseq.property.class/extends entity))
new-title))]
(throw (ex-info "Duplicate page by parent"
{:type :notification
:payload {:message (str "Another page named " (pr-str new-title) " already exists for parents "
(pr-str (->> (ldb/get-page-parents entity)
(pr-str (->> (ldb/get-class-extends entity)
(map :block/title)
(string/join ns-util/parent-char))))
:type :warning}}))))
(defn- another-id-q
[entity]
(cond
(ldb/property? entity)
;; Property names are unique in that they can
;; have the same names as built-in property names
'[:find [?b ...]
:in $ ?eid ?title [?tag-id ...]
:where
[?b :block/title ?title]
[?b :block/tags ?tag-id]
[(missing? $ ?b :logseq.property/built-in?)]
[(not= ?b ?eid)]]
(:logseq.property.class/extends entity)
'[:find [?b ...]
:in $ ?eid ?title [?tag-id ...]
:where
[?b :block/title ?title]
[?b :block/tags ?tag-id]
[(not= ?b ?eid)]
;; same extends
[?b :logseq.property.class/extends ?bp]
[?eid :logseq.property.class/extends ?ep]
[(= ?bp ?ep)]]
(:block/parent entity)
'[:find [?b ...]
:in $ ?eid ?title [?tag-id ...]
:where
[?b :block/title ?title]
[?b :block/tags ?tag-id]
[(not= ?b ?eid)]
;; same parent
[?b :block/parent ?bp]
[?eid :block/parent ?ep]
[(= ?bp ?ep)]]
:else
'[:find [?b ...]
:in $ ?eid ?title [?tag-id ...]
:where
[?b :block/title ?title]
[?b :block/tags ?tag-id]
[(not= ?b ?eid)]]))
(defn- validate-unique-for-page
[db new-title {:block/keys [tags] :as entity}]
(cond
(seq tags)
(when-let [another-id (first
(d/q (cond
(ldb/property? entity)
;; Property names are unique in that they can
;; have the same names as built-in property names
'[:find [?b ...]
:in $ ?eid ?title [?tag-id ...]
:where
[?b :block/title ?title]
[?b :block/tags ?tag-id]
[(missing? $ ?b :logseq.property/built-in?)]
[(not= ?b ?eid)]]
(:logseq.property/parent entity)
'[:find [?b ...]
:in $ ?eid ?title [?tag-id ...]
:where
[?b :block/title ?title]
[?b :block/tags ?tag-id]
[(not= ?b ?eid)]
;; same parent
[?b :logseq.property/parent ?bp]
[?eid :logseq.property/parent ?ep]
[(= ?bp ?ep)]]
:else
'[:find [?b ...]
:in $ ?eid ?title [?tag-id ...]
:where
[?b :block/title ?title]
[?b :block/tags ?tag-id]
[(not= ?b ?eid)]])
(d/q (another-id-q entity)
db
(:db/id entity)
new-title
@@ -117,13 +132,13 @@
(map (fn [id] (str "#" (:block/title (d/entity db id)))) common-tag-ids)))
:type :warning}})))))
(:logseq.property/parent entity)
(validate-unique-by-parent-and-name db entity new-title)))
(:logseq.property.class/extends entity)
(validate-unique-by-extends-and-name db entity new-title)))
(defn ^:api validate-unique-by-name-tag-and-block-type
"Validates uniqueness of nodes for the following cases:
- Page names are unique for a tag e.g. their can be Apple #Company and Apple #Fruit
- Page names are unique for a :logseq.property/parent"
- Page names are unique for a :logseq.property.class/extends"
[db new-title entity]
(when (entity-util/page? entity)
(validate-unique-for-page db new-title entity)))
@@ -154,32 +169,29 @@
:payload {:message "This is an invalid property name. A property name cannot start with page reference characters '#' or '[['."
:type :error}}))))
(defn- validate-parent-property-have-same-type
"Validates whether given parent and children are valid. Allows 'class' and
'page' types to have a relationship with their own type. May consider allowing more
page types if they don't cause systemic bugs"
(defn- validate-extends-property-have-correct-type
"Validates whether given parent and children are classes"
[parent-ent child-ents]
(when (or (and (ldb/class? parent-ent) (not (every? ldb/class? child-ents)))
(and (ldb/internal-page? parent-ent) (not (every? ldb/internal-page? child-ents)))
(not ((some-fn ldb/class? ldb/internal-page?) parent-ent)))
(throw (ex-info "Can't set this page as a parent because the child page is a different type"
(when (or (not (ldb/class? parent-ent))
(not (every? ldb/class? child-ents)))
(throw (ex-info "Can't extend this page since either it is not a tag or is extending from a page that is not a tag"
{:type :notification
:payload {:message "Can't set this page as a parent because the child page is a different type"
:type :warning}
:payload {:message "Can't extend this page since either it is not a tag or is extending from a page that is not a tag"
:type :error}
:blocks (map #(select-keys % [:db/id :block/title]) (remove ldb/class? child-ents))}))))
(defn- disallow-built-in-class-parent-change
(defn- disallow-built-in-class-extends-change
[_parent-ent child-ents]
(when (some #(get db-class/built-in-classes (:db/ident %)) child-ents)
(throw (ex-info "Can't change the parent of a built-in tag"
{:type :notification
:payload {:message "Can't change the parent of a built-in tag"
:type :warning}}))))
:type :error}}))))
(defn validate-parent-property
(defn validate-extends-property
[parent-ent child-ents]
(disallow-built-in-class-parent-change parent-ent child-ents)
(validate-parent-property-have-same-type parent-ent child-ents))
(disallow-built-in-class-extends-change parent-ent child-ents)
(validate-extends-property-have-correct-type parent-ent child-ents))
(defn- disallow-node-cant-tag-with-built-in-non-tags
[db _block-eids v]
@@ -230,4 +242,4 @@
"Validates deleting a property value from :block/tags for given blocks"
[db block-eids v]
(disallow-tagging-a-built-in-entity db block-eids {:delete? true})
(disallow-node-cant-tag-with-private-tags db block-eids v {:delete? true}))
(disallow-node-cant-tag-with-private-tags db block-eids v {:delete? true}))

View File

@@ -65,7 +65,7 @@
(db-test/find-page-by-title @conn "Fruit")))
"Allow class to have same name as a page")))
(deftest validate-parent-property
(deftest validate-extends-property
(let [conn (db-test/create-conn-with-blocks
{:properties {:prop1 {:logseq.property/type :default}}
:classes {:Class1 {} :Class2 {}}
@@ -73,34 +73,30 @@
[{:page {:block/title "page1"}}
{:page {:block/title "page2"}}]})
page1 (db-test/find-page-by-title @conn "page1")
page2 (db-test/find-page-by-title @conn "page2")
class1 (db-test/find-page-by-title @conn "Class1")
class2 (db-test/find-page-by-title @conn "Class2")
property (db-test/find-page-by-title @conn "prop1")]
(testing "valid parent and child combinations"
(is (nil? (outliner-validate/validate-parent-property page1 [page2]))
"parent page to child page is valid")
(is (nil? (outliner-validate/validate-parent-property class1 [class2]))
(is (nil? (outliner-validate/validate-extends-property class1 [class2]))
"parent class to child class is valid"))
(testing "invalid parent and child combinations"
(are [parent child]
(thrown-with-msg?
js/Error
#"Can't set"
(outliner-validate/validate-parent-property parent [child]))
#"Can't extend"
(outliner-validate/validate-extends-property parent [child]))
class1 page1
page1 class1
property page1
property class1))
(testing "built-in tag can't have parent changed"
(is (thrown-with-msg?
js/Error
#"Can't change.*built-in"
(outliner-validate/validate-parent-property (entity-plus/entity-memoized @conn :logseq.class/Task)
(outliner-validate/validate-extends-property (entity-plus/entity-memoized @conn :logseq.class/Task)
[(entity-plus/entity-memoized @conn :logseq.class/Cards)]))))))
(deftest validate-tags-property
@@ -168,9 +164,9 @@
(testing "Validate property relationships"
(let [parent-child-pairs (d/q '[:find ?parent ?child
:where [?child :logseq.property/parent ?parent]] @conn)]
:where [?child :logseq.property.class/extends ?parent]] @conn)]
(doseq [[parent-id child-id] parent-child-pairs]
(let [parent (d/entity @conn parent-id)
child (d/entity @conn child-id)]
(is (nil? (#'outliner-validate/validate-parent-property-have-same-type parent [child]))
(is (nil? (#'outliner-validate/validate-extends-property-have-correct-type parent [child]))
(str "Parent and child page is valid: " (pr-str (:block/title parent)) " " (pr-str (:block/title child))))))))))

View File

@@ -2,10 +2,10 @@
"This frontend only ns builds the publishing html including doing all the
necessary db filtering"
(:require [clojure.string :as string]
[datascript.core :as d]
[datascript.transit :as dt]
[goog.string :as gstring]
[goog.string.format]
[datascript.transit :as dt]
[datascript.core :as d]
[logseq.publishing.db :as db]))
;; Copied from hiccup but tweaked for publish usage

View File

@@ -44,13 +44,12 @@
(apply button-base props' children)))
(defn button-icon
[variant icon-name {:keys [icon-props size] :as props} child]
[variant icon-name {:keys [icon-props button-size] :as props} child]
(button
(merge (dissoc props :icon-props :size)
(merge (dissoc props :icon-props :button-size)
{:variant variant
:data-button :icon
:style (when size {:width size :height size})})
:style (when button-size {:width button-size :height button-size})})
(tabler-icon/root (name icon-name) (merge {:size 20} icon-props))
child))

View File

@@ -14,11 +14,11 @@
[logseq.common.util :as common-util]
[logseq.common.util.date-time :as date-time-util]
[logseq.common.util.page-ref :as page-ref]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.frontend.property.type :as db-property-type]
[logseq.outliner.cli :as outliner-cli]
[nbb.classpath :as cp]
[nbb.core :as nbb]
[logseq.db.common.sqlite-cli :as sqlite-cli]))
[nbb.core :as nbb]))
(defn- date-journal-title [date]
(date-time-util/int->journal-title (date-time-util/date->int date) "MMM do, yyyy"))

View File

@@ -17,12 +17,12 @@
[clojure.string :as string]
[clojure.walk :as w]
[datascript.core :as d]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.frontend.malli-schema :as db-malli-schema]
[logseq.db.frontend.property :as db-property]
[logseq.outliner.cli :as outliner-cli]
[nbb.classpath :as cp]
[nbb.core :as nbb]
[logseq.db.common.sqlite-cli :as sqlite-cli]))
[nbb.core :as nbb]))
(defn- get-comment-string
[rdfs-comment renamed-pages]
@@ -364,14 +364,14 @@
(d/q '[:find [(pull ?b [*
{:logseq.property.class/properties [:block/title]}
{:logseq.property/classes [:block/title]}
{:logseq.property/parent [:block/title]}
{:logseq.property.class/extends [:block/title]}
{:block/tags [:block/title]}
{:block/refs [:block/title]}]) ...]
:in $
:where [?b :db/ident ?ident]]
db))
top-level-properties [:logseq.property/type :logseq.property.class/properties :logseq.property/classes
:logseq.property/parent :block/tags]
:logseq.property.class/extends :block/tags]
debug-attributes (into [:block/name :block/title :db/cardinality :db/ident :block/refs]
top-level-properties)]
(fs/writeFileSync "schema-org.edn"
@@ -388,8 +388,8 @@
v)))))
(seq (:logseq.property.class/properties m))
(update :logseq.property.class/properties #(set (map :block/title %)))
(some? (:logseq.property/parent m))
(update :logseq.property/parent :block/title)
(some? (:logseq.property.class/extends m))
(update :logseq.property.class/extends :block/title)
(seq (:logseq.property/classes m))
(update :logseq.property/classes #(set (map :block/title %)))
(seq (:block/tags m))

View File

@@ -1,6 +1,6 @@
(ns logseq.tasks.dev.lint
(:require [clojure.string :as string]
[babashka.process :refer [shell]]))
(:require [babashka.process :refer [shell]]
[clojure.string :as string]))
(defn dev
"Run all lint tasks
@@ -34,14 +34,14 @@
;; remove files that aren't in a kondo dir
((fn [x] (dissoc x nil))))]
(if (seq dir-to-files)
(doseq [[dir* files*] dir-to-files]
(let [dir (if (= dir* "src") "." dir*)
files (mapv #(string/replace-first % (str dir "/") "") files*)
cmd (str "cd " dir " && clj-kondo --lint " (string/join " " files))
_ (println cmd)
res (apply shell {:dir dir :continue :true} "clj-kondo --lint" files)]
(when (pos? (:exit res)) (System/exit (:exit res)))))
(println "No clj* files have changed to lint."))))
(doseq [[dir* files*] dir-to-files]
(let [dir (if (= dir* "src") "." dir*)
files (mapv #(string/replace-first % (str dir "/") "") files*)
cmd (str "cd " dir " && clj-kondo --lint " (string/join " " files))
_ (println cmd)
res (apply shell {:dir dir :continue :true} "clj-kondo --lint" files)]
(when (pos? (:exit res)) (System/exit (:exit res)))))
(println "No clj* files have changed to lint."))))
(defn- validate-frontend-not-in-worker
[]

View File

@@ -1,13 +1,13 @@
(ns logseq.tasks.lang
"Tasks related to language translations"
(:require [clojure.set :as set]
(:require [babashka.cli :as cli]
[babashka.fs :as fs]
[babashka.process :refer [shell]]
[borkdude.rewrite-edn :as rewrite]
[clojure.set :as set]
[clojure.string :as string]
[frontend.dicts :as dicts]
[logseq.tasks.util :as task-util]
[babashka.cli :as cli]
[babashka.process :refer [shell]]
[babashka.fs :as fs]
[borkdude.rewrite-edn :as rewrite]))
[logseq.tasks.util :as task-util]))
(defn- get-dicts
[]

View File

@@ -11,7 +11,6 @@
:kv/value {:doc "Used to store key-value, the value could be anything,
e.g. {:db/ident :logseq.kv/xxx :kv/value value}"}
;; :block.temp/xxx keywords
;; :block.temp/xxx keywords
:block.temp/load-status {:doc "`:full` means the block and its children have been loaded,
`:self` means the block itself has been loaded."})

View File

@@ -663,8 +663,9 @@
All page-names are sanitized except page-name-in-block"
[state
{:keys [contents-page? whiteboard-page? other-position? show-unique-title? stop-click-event?
on-context-menu]
:or {stop-click-event? true}
on-context-menu with-parent?]
:or {stop-click-event? true
with-parent? true}
:as config}
page-entity children label]
(let [*hover? (::hover? state)
@@ -672,9 +673,10 @@
tag? (:tag? config)
page-name (when (:block/title page-entity)
(util/page-name-sanity-lc (:block/title page-entity)))
breadcrumb? (:breadcrumb? config)
config (assoc config :whiteboard-page? whiteboard-page?)
untitled? (when page-name (model/untitled-page? (:block/title page-entity)))
untitled? (when page-name
(or (model/untitled-page? (:block/title page-entity))
(and (ldb/page? page-entity) (string/blank? (:block/title page-entity)))))
show-icon? (:show-icon? config)]
[:a.relative
(cond->
@@ -706,9 +708,6 @@
(and other-position? (not (util/shift-key? e)))
(some-> (.-target e) (.closest ".jtrigger") (.click))
breadcrumb?
(.preventDefault e)
:else
(do
(.preventDefault e)
@@ -734,6 +733,12 @@
:own-icon? true})]
[:span {:class (str "icon-emoji-wrap " (when emoji? "as-emoji"))}
icon])))
(when (and (ldb/page? page-entity) with-parent?)
(when-let [parent (:block/parent page-entity)]
(when-not (ldb/library? parent)
[:span.select-none (str (:block/title parent) "/")])))
[:span
(if (and (coll? children) (seq children))
(for [child children]
@@ -793,25 +798,27 @@
(let [*el-trigger (hooks/use-ref nil)]
(hooks/use-effect!
(fn []
(when (true? visible?)
(shui/popup-show!
(hooks/deref *el-trigger) render
{:root-props {:onOpenChange (fn [v] (set-visible! v))
:modal false}
:content-props {:class "ls-preview-popup"
:onInteractOutside (fn [^js e] (.preventDefault e))
:onEscapeKeyDown (fn [^js e]
(when (state/editing?)
(.preventDefault e)
(some-> (hooks/deref *el-popup) (.focus))))}
:as-dropdown? false}))
(when-not (state/editing?)
(when (true? visible?)
(shui/popup-show!
(hooks/deref *el-trigger) render
{:root-props {:onOpenChange (fn [v] (set-visible! v))
:modal false}
:content-props {:class "ls-preview-popup"
:onInteractOutside (fn [^js e] (.preventDefault e))
:onEscapeKeyDown (fn [^js e]
(when (state/editing?)
(.preventDefault e)
(some-> (hooks/deref *el-popup) (.focus))))}
:as-dropdown? false}))
(when (false? visible?)
(shui/popup-hide!)
(when (state/get-edit-block)
(state/clear-edit!)))
(hooks/set-ref! *timer nil)
(hooks/set-ref! *timer1 nil))
(when (false? visible?)
(shui/popup-hide!)
(when (state/get-edit-block)
(state/clear-edit!)))
(hooks/set-ref! *timer nil)
(hooks/set-ref! *timer1 nil)
;; teardown
(fn []
(when visible?
@@ -2119,12 +2126,15 @@
[config block children collapsed?]
(let [ref? (:ref? config)
query? (:custom-query? config)
library? (:library? config)
children (when (coll? children)
(let [ref-matched-children-ids (:ref-matched-children-ids config)]
(cond->> (remove nil? children)
ref-matched-children-ids
;; Block children will not be rendered if the filters do not match them
(filter (fn [b] (ref-matched-children-ids (:db/id b)))))))]
(filter (fn [b] (ref-matched-children-ids (:db/id b))))
library?
(filter (fn [b] (and (ldb/page? b) (not (or (ldb/class? b) (ldb/property? b)))))))))]
(when (and (coll? children)
(seq children)
(not collapsed?))
@@ -2163,9 +2173,10 @@
:ignore-children? page-title?})
link? (boolean (:original-block config))
icon-size (if collapsed? 12 14)
icon (icon-component/get-node-icon-cp block {:size icon-size :color? true})
icon (icon-component/get-node-icon-cp block {:size icon-size :color? true :link? link?})
with-icon? (and (some? icon)
(or (db/page? block)
(or (and (db/page? block)
(not (:library? config)))
(:logseq.property/icon block)
link?
(some :logseq.property/icon (:block/tags block))
@@ -2449,6 +2460,9 @@
db (db/get-db)
query? (ldb/class-instance? (entity-plus/entity-memoized db :logseq.class/Query) block')]
(cond
(and (:page-title? config) (ldb/page? block) (string/blank? (:block/title block)))
[:div.opacity-75 "Untitled"]
(:raw-title? config)
(text-block-title (dissoc config :raw-title?) block)
@@ -3209,42 +3223,30 @@
(rum/defc breadcrumb-separator
[]
(ui/icon "chevron-right" {:style {:font-size 20}
:class "opacity-50 mx-1"}))
[:span.opacity-50.px-1
"/"])
;; "block-id - uuid of the target block of breadcrumb. page uuid is also acceptable"
(rum/defc breadcrumb-aux < rum/reactive
[config repo block-id {:keys [show-page? indent? end-separator? level-limit _navigating-block]
:or {show-page? true
level-limit 3}
[config repo block-id {:keys [show-page? indent? end-separator? _navigating-block]
:or {show-page? true}
:as opts}]
(let [from-property (when (and block-id (config/db-based-graph? repo))
(:logseq.property/created-from-property (db/entity [:block/uuid block-id])))
parents (db/get-block-parents repo block-id {:depth (inc level-limit)})
parents (remove nil? (concat parents [from-property]))
page (or (db/get-block-page repo block-id) ;; only return for block uuid
(model/query-block-by-uuid block-id)) ;; return page entity when received page uuid
page-name (:block/name page)
page-title (:block/title page)
show? (or (seq parents) show-page? page-name)
parents (if (= page-name (:block/name (first parents)))
(rest parents)
parents)
more? (> (count parents) level-limit)
parents (if more? (take-last level-limit parents) parents)
parents (db/get-block-parents repo block-id {:depth 1000})
parents (cond-> (remove nil? (concat parents [from-property]))
(not show-page?)
rest)
config (assoc config
:breadcrumb? true
:disable-preview? true
:stop-click-event? false)]
(when show?
(let [page-name-props (when show-page?
[page
(page-cp (dissoc config :breadcrumb? true) page)
{:block/name (or page-title page-name)}])
parents-props (doall
(when (seq parents)
(let [parents-props (doall
(for [{:block/keys [uuid name title] :as block} parents]
(if name
[block (page-cp {} block)]
[block (page-cp {:disable-preview? true
:with-parent? false} block) true]
(let [result (block/parse-title-and-body
uuid
(get block :block/format :markdown)
@@ -3257,16 +3259,15 @@
(when ast-title
(if (seq ast-title)
(->elem :span (map-inline config ast-title))
(->elem :div (markup-elements-cp config ast-body))))]))))
breadcrumbs (->> (into [] parents-props)
(concat [page-name-props] (when more? [:more]))
(filterv identity)
(->elem :div (markup-elements-cp config ast-body))))
false]))))
breadcrumbs (->> parents-props
(map (fn [x]
(if (and (vector? x) (second x))
(let [[block label] x]
(rum/with-key (breadcrumb-fragment config block label opts)
(str (:block/uuid block))))
[:span.opacity-70 {:key "dots"} "⋯"])))
(let [[block label page?] x
label' (if page?
label
(breadcrumb-fragment config block label opts))]
(rum/with-key label' (str (:block/uuid block))))))
(interpose (breadcrumb-separator)))]
(when (seq breadcrumbs)
[:div.breadcrumb.block-parents
@@ -3282,10 +3283,9 @@
(when end-separator? (breadcrumb-separator))])))))
(rum/defc breadcrumb
[config repo block-id {:keys [_show-page? _indent? _end-separator? level-limit _navigating-block]
:or {level-limit 3}
[config repo block-id {:keys [_show-page? _indent? _end-separator? _navigating-block]
:as opts}]
(let [[block set-block!] (hooks/use-state (db/entity [:block/uuid block-id]))]
(let [[block set-block!] (hooks/use-state nil)]
(hooks/use-effect!
(fn []
(p/let [block (db-async/<get-block (state/get-current-repo)
@@ -3293,7 +3293,7 @@
{:children? false
:skip-refresh? true})
_ (when-let [id (:db/id block)]
(db-async/<get-block-parents (state/get-current-repo) id level-limit))]
(db-async/<get-block-parents (state/get-current-repo) id 9))]
(set-block! block)))
[])
(when block
@@ -3513,9 +3513,7 @@
show-query? (rum/react *show-query?)
*refs-count (get state ::refs-count)
hide-block-refs? (rum/react *hide-block-refs?)
refs-count (if (seq (:block/_refs block))
(count (remove :logseq.property/view-for (:block/_refs block)))
(rum/react *refs-count))
refs-count (rum/react *refs-count)
[original-block block] (build-block config* block {:navigating-block navigating-block :navigated? navigated?})
config* (if original-block
(assoc config* :original-block original-block)
@@ -3702,6 +3700,7 @@
(block-right-menu config block editing?))])
(when (and db-based?
(not (:library? config))
(or (:tag-dialog? config)
(and
(not collapsed?)

View File

@@ -378,7 +378,7 @@
@apply rounded transition-[background];
.bracket {
@apply opacity-30;
@apply opacity-30 inline-flex;
}
&:hover {

View File

@@ -7,14 +7,14 @@
(defn class-children-aux
[class {:keys [default-collapsed?] :as opts}]
(let [children (->> (:logseq.property/_parent class)
(let [children (->> (:logseq.property.class/_extends class)
;; Disallow parent cycles
(remove #(= (:db/id class) (:db/id %))))]
(when (seq children)
[:ul
(for [child (sort-by :block/title children)]
(let [title [:li.ml-2 (block/page-reference {:show-brackets? false} (:block/uuid child) nil)]]
(if (seq (:logseq.property/_parent child))
(if (seq (:logseq.property.class/_extends child))
(ui/foldable
title
(class-children-aux child opts)
@@ -23,7 +23,7 @@
(rum/defc class-children
[class]
(when (seq (:logseq.property/_parent class))
(when (seq (:logseq.property.class/_extends class))
(let [children-pages (set (model/get-structured-children (state/get-current-repo) (:db/id class)))
;; Expand children if there are about a pageful of total blocks to display
default-collapsed? (> (count children-pages) 30)]

View File

@@ -127,14 +127,15 @@
(or (let [page (some-> (text/get-namespace-last-part input)
string/trim
db/get-page)
parent-title (:block/title (:logseq.property/parent page))
parent-title (:block/title (:block/parent page))
namespace? (string/includes? input "/")]
(and page
(or (not namespace?)
(and
parent-title
(= (util/page-name-sanity-lc parent-title)
(util/page-name-sanity-lc (nth (reverse (string/split input "/")) 1)))))))
(some-> (util/nth-safe (reverse (string/split input "/")) 1)
util/page-name-sanity-lc))))))
(some (fn [block]
(and
(:block/tags block)
@@ -294,13 +295,16 @@
source-page (or (model/get-alias-source-page repo (:db/id entity))
(:alias page))
icon (get-page-icon entity)
title (block-handler/block-unique-title page)
title (block-handler/block-unique-title (or entity page))
title' (if source-page (str title " -> alias: " (:block/title source-page)) title)]
(hash-map :icon icon
:icon-theme :gray
:text title'
:source-page (or source-page page)
:alias (:alias page))))
:header (when (:block/parent entity)
(block/breadcrumb {:disable-preview? true
:search? true} repo (:block/uuid page) {}))
:alias (:alias page)
:source-page (or source-page page))))
(defn- block-item
[repo block current-page !input]
@@ -310,7 +314,8 @@
{:icon icon
:icon-theme :gray
:text (highlight-content-query text @!input)
:header (when-not (db/page? block) (block/breadcrumb {:search? true} repo id {}))
:header (block/breadcrumb {:disable-preview? true
:search? true} repo id {})
:current-page? (when-let [page-id (:block/page block)]
(= page-id (:block/uuid current-page)))
:source-block block}))

View File

@@ -40,7 +40,7 @@
highlighted-text (string/replace normal-text query-re "<:hlmarker>$1<:hlmarker>")
segs (string/split highlighted-text #"<:hlmarker>")]
(if (seq segs)
(into [:span {:data-testid text-string}]
(into [:span {"data-testid" text-string}]
(map-indexed (fn [i seg]
(if (even? i)
[:span seg]
@@ -82,7 +82,7 @@
:style {:color "var(--lx-gray-11)"}}
(highlight-query header)])
;; main row
[:div.flex.items-center.gap-3
[:div.flex.items-start.gap-3
[:div.w-5.h-5.rounded.flex.items-center.justify-center
{:style {:background (when (#{:gradient} icon-theme) "linear-gradient(-65deg, #8AE8FF, #5373E7, #369EFF, #00B1CC)")
:box-shadow (when (#{:gradient} icon-theme) "inset 0 0 0 1px rgba(255,255,255,0.3) ")}

View File

@@ -48,6 +48,7 @@
[frontend.version :refer [version]]
[goog.dom :as gdom]
[goog.object :as gobj]
[logseq.common.config :as common-config]
[logseq.common.path :as path]
[logseq.common.util.namespace :as ns-util]
[logseq.db :as ldb]
@@ -155,8 +156,12 @@
(block/inline-text :markdown (:block/title page))
untitled? (t :untitled)
:else (let [title' (pdf-utils/fix-local-asset-pagename title)
parent (:logseq.property/parent page)]
(if (and parent (not (ldb/class? page)))
parent (:block/parent page)]
(if (and parent
(not (or (ldb/class? page)
(and (:logseq.property/built-in? parent)
(= (:block/title parent)
common-config/library-page-name)))))
(str (:block/title parent) ns-util/parent-char title')
title')))]

View File

@@ -123,7 +123,8 @@
(state/set-edit-content! (.-id input) value')
(state/clear-editor-action!)
(p/let [page (db/get-page chosen-item)
_ (when-not page (page-handler/<create! chosen-item {:redirect? false}))
_ (when-not page (page-handler/<create! chosen-item {:redirect? false
:reference? true}))
page' (db/get-page chosen-item)
current-block (state/get-edit-block)]
(editor-handler/api-insert-new-block! chosen-item
@@ -204,14 +205,13 @@
block)
block)]
[:div.flex.flex-col
(when (and (not (or db-tag? (:page? block) (ldb/page? block)))
(:block/uuid block'))
(when (and (:block/uuid block') (or (:block/parent block') (not (:page? block))))
(when-let [breadcrumb (state/get-component :block/breadcrumb)]
[:div.text-xs.opacity-70.mb-1 {:style {:margin-left 3}}
(breadcrumb {:search? true} (state/get-current-repo) (:block/uuid block') {})]))
[:div.flex.flex-row.items-center.gap-1
[:div.flex.flex-row.items-start
(when-not (or db-tag? (not db-based?))
[:div.flex.items-center
[:div.flex.items-center.h-5.mr-1.opacity-50
(cond
(:nlp-date? block')
(ui/icon "calendar" {:size 14})
@@ -225,7 +225,7 @@
(db-model/whiteboard-page? block')
(ui/icon "writing" {:size 14})
(or (ldb/page? block') (:page? block'))
(or (ldb/page? block') (:page? block))
(ui/icon "file" {:size 14})
(or (string/starts-with? (str (:block/title block')) (t :new-tag))

View File

@@ -5,6 +5,7 @@
[clojure.string :as string]
[dommy.core :as d]
[frontend.common.missionary :as c.m]
[frontend.components.block :as component-block]
[frontend.components.export :as export]
[frontend.components.file-sync :as fs-sync]
[frontend.components.page-menu :as page-menu]
@@ -29,6 +30,7 @@
[frontend.ui :as ui]
[frontend.util :as util]
[frontend.version :refer [version]]
[logseq.common.util :as common-util]
[logseq.db :as ldb]
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
@@ -66,7 +68,7 @@
(let [rtc-graph-id (ldb/get-graph-rtc-uuid (db/get-db))
online-users @(::online-users state)]
(when rtc-graph-id
[:div.rtc-collaborators.flex.gap-1.text-sm.py-2.bg-gray-01.items-center
[:div.rtc-collaborators.flex.gap-1.text-sm.bg-gray-01.items-center
(shui/button-ghost-icon :user-plus
{:on-click #(shui/dialog-open!
(fn []
@@ -333,6 +335,18 @@
(when (state/sub :ui/toggle-highlight-recent-blocks?)
(recent-slider-inner)))
(rum/defc block-breadcrumb
[page-name]
[:div.ls-block-breadcrumb
(when-let [page (when (and page-name (common-util/uuid-string? page-name))
(db/entity [:block/uuid (uuid page-name)]))]
(when (:block/parent page)
[:div.text-sm
(component-block/breadcrumb {}
(state/get-current-repo)
(:block/uuid page)
{:header? true})]))])
(rum/defc ^:large-vars/cleanup-todo header-aux < rum/reactive
[{:keys [current-repo default-home new-block-mode]}]
(let [electron-mac? (and util/mac? (util/electron?))
@@ -358,7 +372,7 @@
[:div.l.flex.items-center.drag-region
[left-menu
(if (mobile-util/native-platform?)
;; back button for mobile
;; back button for mobile
(when-not (or (state/home?) custom-home-page? (state/whiteboard-dashboard?))
(ui/with-shortcut :go/backward "bottom"
[:button.it.navigation.nav-left.button.icon.opacity-70
@@ -375,58 +389,61 @@
(state/pub-event! [:go/search]))}
(ui/icon "search" {:size ui/icon-size})])))]]
[:div.r.flex.drag-region
(when (and current-repo
(ldb/get-graph-rtc-uuid (db/get-db))
(user-handler/logged-in?)
(config/db-based-graph? current-repo)
(user-handler/team-member?))
[:<>
(recent-slider)
(rum/with-key (rtc-collaborators)
(str "collab-" current-repo))
(rtc-indicator/indicator)])
[:div.r.flex.drag-region.justify-between.items-center.gap-2.overflow-x-hidden.w-full
[:div.flex.flex-1
(block-breadcrumb (state/get-current-page))]
[:div.flex
(when (and current-repo
(ldb/get-graph-rtc-uuid (db/get-db))
(user-handler/logged-in?)
(config/db-based-graph? current-repo)
(user-handler/team-member?))
[:<>
(recent-slider)
(rum/with-key (rtc-collaborators)
(str "collab-" current-repo))
(rtc-indicator/indicator)])
(when (user-handler/logged-in?)
(rtc-indicator/downloading-detail))
(when (user-handler/logged-in?)
(rtc-indicator/uploading-detail))
(when (user-handler/logged-in?)
(rtc-indicator/downloading-detail))
(when (user-handler/logged-in?)
(rtc-indicator/uploading-detail))
(when (and current-repo
(not (config/demo-graph? current-repo))
(not (config/db-based-graph? current-repo))
(user-handler/alpha-or-beta-user?))
(fs-sync/indicator))
(when (and current-repo
(not (config/demo-graph? current-repo))
(not (config/db-based-graph? current-repo))
(user-handler/alpha-or-beta-user?))
(fs-sync/indicator))
(when (and (not= (state/get-current-route) :home)
(not custom-home-page?))
(home-button))
(when (and (not= (state/get-current-route) :home)
(not custom-home-page?))
(home-button))
(when config/lsp-enabled?
[:<>
(plugins/hook-ui-items :toolbar)
(plugins/updates-notifications)])
(when config/lsp-enabled?
[:<>
(plugins/hook-ui-items :toolbar)
(plugins/updates-notifications)])
(when (state/feature-http-server-enabled?)
(server/server-indicator (state/sub :electron/server)))
(when (state/feature-http-server-enabled?)
(server/server-indicator (state/sub :electron/server)))
(when (util/electron?)
(back-and-forward))
(when (util/electron?)
(back-and-forward))
(when-not (mobile-util/native-platform?)
(new-block-mode))
(when-not (mobile-util/native-platform?)
(new-block-mode))
(when config/publishing?
[:a.text-sm.font-medium.button {:href (rfe/href :graph)}
(t :graph)])
(when config/publishing?
[:a.text-sm.font-medium.button {:href (rfe/href :graph)}
(t :graph)])
(toolbar-dots-menu {:t t
:current-repo current-repo
:default-home default-home})
(toolbar-dots-menu {:t t
:current-repo current-repo
:default-home default-home})
(sidebar/toggle)
(sidebar/toggle)
(updater-tips-new-version t)]]))
(updater-tips-new-version t)]]]))
(def ^:private header-related-flow
(m/latest

View File

@@ -314,3 +314,31 @@ html.is-zoomed-native-ios {
min-width: auto;
}
}
.cp__header {
> .r > div:not(.ui__dropdown-trigger) a, button {
@apply opacity-70 text-[inherit];
&:hover {
@apply opacity-100;
}
}
}
.cp__header {
.ls-block-breadcrumb {
@apply text-sm pl-4;
}
.breadcrumb {
@apply flex items-center;
}
.breadcrumb a {
@apply !opacity-75 hover:!opacity-100;
span {
@apply whitespace-nowrap text-ellipsis inline-block overflow-x-hidden align-middle;
max-width: 34ch;
}
}
}

View File

@@ -65,8 +65,12 @@
(defn get-node-icon-cp
[node-entity opts]
(let [opts' (merge {:size 14} opts)
node-icon (if (:own-icon? opts)
node-icon (cond
(:own-icon? opts)
(get node-entity (pu/get-pid :logseq.property/icon))
(:link? opts)
"arrow-narrow-right"
:else
(get-node-icon node-entity))]
(when-not (or (string/blank? node-icon) (and (contains? #{"letter-n" "file"} node-icon) (:not-text-or-page? opts)))
[:div.icon-cp-container.flex.items-center

View File

@@ -0,0 +1,75 @@
(ns frontend.components.library
"Library page"
(:require [clojure.string :as string]
[frontend.components.select :as components-select]
[frontend.db :as db]
[frontend.handler.editor :as editor-handler]
[frontend.search :as search]
[frontend.state :as state]
[frontend.ui :as ui]
[logseq.db :as ldb]
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
[promesa.core :as p]
[rum.core :as rum]))
(rum/defc select-pages
[library-page]
(let [[result set-result!] (hooks/use-state nil)
[input set-input!] (hooks/use-state "")
[selected-choices set-selected-choices!] (hooks/use-state #{})
items (map (fn [block]
{:value (:db/id block)
:label (:block/title block)})
result)]
(hooks/use-effect!
(fn []
(if (string/blank? input)
(set-result! nil)
(p/let [result (search/block-search (state/get-current-repo) input {:enable-snippet? false
:built-in? false
:page-only? true
:library-page-search? true})]
(set-result! result))))
[(hooks/use-debounced-value input 200)])
(components-select/select
{:items items
:extract-fn :label
:extract-chosen-fn :value
:selected-choices selected-choices
:on-chosen (fn [chosen selected?]
(if selected?
(let [last-child (->> (:block/_parent (db/entity (:db/id library-page)))
ldb/sort-by-order
last)
target (or last-child library-page)
sibling? (some? last-child)]
(editor-handler/move-blocks! [{:db/id chosen}] target sibling?)
(set-selected-choices! (conj selected-choices chosen)))
(do
(db/transact! (state/get-current-repo)
[[:db/retract chosen :block/parent]]
{:outliner-op :save-block})
(set-selected-choices! (disj selected-choices chosen)))))
:multiple-choices? true
:input-default-placeholder "Add pages"
:show-new-when-not-exact-match? false
:on-input set-input!
:input-opts {:class "!p-1 !text-sm"}})))
(rum/defc add-pages
[library-page]
[:div.ls-add-pages.px-1
(shui/button
{:variant :secondary
:size :sm
:class "text-muted-foreground hover:text-foreground"
:on-click (fn [e]
(shui/popup-show!
(.-target e)
(fn []
[:div {:style {:min-height 120}}
(select-pages library-page)])
{:align :start}))}
(ui/icon "plus" {:size 16})
"Add pages to Library")])

View File

@@ -8,6 +8,7 @@
[frontend.components.db-based.page :as db-page]
[frontend.components.editor :as editor]
[frontend.components.file-based.hierarchy :as hierarchy]
[frontend.components.library :as library]
[frontend.components.objects :as objects]
[frontend.components.plugins :as plugins]
[frontend.components.property.config :as property-config]
@@ -100,11 +101,12 @@
(declare page-cp)
(rum/defc add-button
[block container-id]
[block {:keys [container-id] :as config*}]
(let [*ref (rum/use-ref nil)
has-children? (:block/_parent block)
page? (ldb/page? block)
opacity-class (if has-children? "opacity-0" "opacity-50")]
opacity-class (if has-children? "opacity-0" "opacity-50")
config (dissoc config* :page)]
(when page?
[:div.ls-block.block-add-button.flex-1.flex-col.rounded-sm.cursor-text.transition-opacity.ease-in.duration-100.!py-0
{:class opacity-class
@@ -113,8 +115,8 @@
:on-click (fn [e]
(util/stop e)
(state/set-state! :editor/container-id container-id)
(editor-handler/api-insert-new-block! ""
{:block-uuid (:block/uuid block)}))
(editor-handler/api-insert-new-block! "" (merge config
{:block-uuid (:block/uuid block)})))
:on-mouse-over (fn []
(let [ref (rum/deref *ref)
prev-block (util/get-prev-block-non-collapsed (rum/deref *ref) {:up-down? true})]
@@ -132,7 +134,7 @@
(util/stop e)
(when (= "Enter" (util/ekey e))
(state/set-state! :editor/container-id container-id)
(editor-handler/api-insert-new-block! "" block)))
(editor-handler/api-insert-new-block! "" (merge config block))))
:tab-index 0}
[:div.flex.flex-row
[:div.flex.items-center {:style {:height 28
@@ -165,13 +167,14 @@
(remove (fn [b] (some? (get b (:db/ident block)))) children)
:else
children)]
children)
config (assoc config :library? (ldb/library? block))]
(cond
(and
(not block?)
(not config/publishing?)
(empty? children) block)
(add-button block (:container-id config))
(add-button block config)
:else
(let [document-mode? (state/sub :document/mode?)
@@ -186,7 +189,7 @@
blocks (if block? [block] (db/sort-by-order children block))]
[:div.relative
(page-blocks-inner block blocks config sidebar? whiteboard? block-id)
(add-button block (:container-id config))])))))
(add-button block config)])))))
(rum/defc today-queries < rum/reactive
[repo today? sidebar?]
@@ -407,7 +410,7 @@
(let [with-actions? (not config/publishing?)]
[:div.ls-page-title.flex.flex-1.w-full.content.items-start.title
{:class (when-not whiteboard-page? "title")
:data-testid "page title"
"data-testid" "page title"
:on-pointer-down (fn [e]
(when (util/right-click? e)
(state/set-state! :page-title/context {:page (:block/title page)
@@ -572,7 +575,6 @@
(let [current-repo (state/sub :git/current-repo)
*tabs-rendered? (::tabs-rendered? state)
repo (or repo current-repo)
block-id (:block/uuid page)
block? (some? (:block/page page))
class-page? (ldb/class? page)
property-page? (ldb/property? page)
@@ -629,22 +631,21 @@
:preview? preview?})))
(lsp-pagebar-slot)])
(when (and db-based? (ldb/library? page))
(library/add-pages page))
(when (and db-based? sidebar? (ldb/page? page))
[:div.-mb-8
(sidebar-page-properties config page)])
(when (and block? (not sidebar?) (not whiteboard?))
(let [config (merge config {:id "block-parent"
:block? true})]
[:div.mb-4
(component-block/breadcrumb config repo block-id {:level-limit 3})]))
(when show-tabs?
(tabs page {:current-page? option :sidebar? sidebar? :*tabs-rendered? *tabs-rendered?}))
(when (and (or (not show-tabs?) tabs-rendered?) (not tag-dialog?))
[:div.ls-page-blocks
{:style {:margin-left (if whiteboard? 0 -20)}}
{:style {:margin-left (if whiteboard? 0 -20)}
:class (when-not sidebar?
"mt-4")}
(page-blocks-cp page (merge option {:sidebar? sidebar?
:container-id (:container-id state)
:whiteboard? whiteboard?}))])])
@@ -660,7 +661,7 @@
(when (and (not block?) (not db-based?))
(tagged-pages repo page title))
(when (and (ldb/page? page) (:logseq.property/_parent page))
(when (and (ldb/page? page) (:logseq.property.class/_extends page))
(class-component/class-children page))
;; referenced blocks

View File

@@ -151,7 +151,7 @@
(when (and db-based? (ldb/internal-page? page))
{:title (t :page/convert-to-tag)
:options {:on-click (fn []
(db-page-handler/convert-to-tag! page))}})
(db-page-handler/convert-page-to-tag! page))}})
(when (and db-based? (ldb/class? page) (not (:logseq.property/built-in? page)))
{:title (t :page/convert-tag-to-page)

View File

@@ -418,6 +418,7 @@
(shui/popup-hide!)))}]
(property-select (merge (:select-opts opts) {:on-chosen on-chosen
:input-opts input-opts
:block block
:class-schema? class-schema?}))))]))
(rum/defcs new-property < rum/reactive

View File

@@ -615,7 +615,7 @@
(property-type-sub-pane property ops))}))
(when (and (= property-type :node)
(not (contains? #{:logseq.property/parent} (:db/ident property))))
(not (contains? #{:logseq.property.class/extends} (:db/ident property))))
(dropdown-editor-menuitem {:icon :hash
:disabled? disabled?
:title "Specify node tags"
@@ -666,7 +666,7 @@
(when (and (not= :logseq.property/enable-history? (:db/ident property))
(not special-built-in-prop?))
(let [property-type (:logseq.property/type property)
group' (->> [(when (and (not (contains? #{:logseq.property/parent :logseq.property.class/properties} (:db/ident property)))
group' (->> [(when (and (not (contains? #{:logseq.property.class/extends :logseq.property.class/properties} (:db/ident property)))
(contains? #{:default :number :date :checkbox :node} property-type)
(not
(and (= :default property-type)
@@ -678,13 +678,13 @@
:disabled? config/publishing?
:submenu-content (fn [ops] (ui-position-sub-pane property (assoc ops :ui-position position)))})))
(when (not (contains? #{:logseq.property/parent :logseq.property.class/properties} (:db/ident property)))
(when (not (contains? #{:logseq.property.class/extends :logseq.property.class/properties} (:db/ident property)))
(dropdown-editor-menuitem {:icon :eye-off :title "Hide by default" :toggle-checked? (boolean (:logseq.property/hide? property))
:disabled? config/publishing?
:on-toggle-checked-change #(db-property-handler/set-block-property! (:db/id property)
:logseq.property/hide?
%)}))
(when (not (contains? #{:logseq.property/parent :logseq.property.class/properties} (:db/ident property)))
(when (not (contains? #{:logseq.property.class/extends :logseq.property.class/properties} (:db/ident property)))
(dropdown-editor-menuitem
{:icon :eye-off :title "Hide empty value"
:toggle-checked? (boolean (:logseq.property/hide-empty-value property))
@@ -731,7 +731,7 @@
;; Any property should be removable from Tag Properties
(if class-schema?
(contains? (set (map :db/id (:logseq.property.class/properties owner-block))) (:db/id property))
(not (contains? #{:logseq.property/parent} (:db/ident property)))))
(not (contains? #{:logseq.property.class/extends} (:db/ident property)))))
(dropdown-editor-menuitem
{:id :delete-property :icon :x

View File

@@ -561,7 +561,7 @@
page-entity (ldb/get-case-page (db/get-db) page)
id (:db/id page-entity)
class? (or (= :block/tags (:db/ident property))
(and (= :logseq.property/parent (:db/ident property))
(and (= :logseq.property.class/extends (:db/ident property))
(ldb/class? block)))
;; Note: property and other types shouldn't be converted to class
page? (ldb/internal-page? page-entity)]
@@ -585,7 +585,7 @@
(:db/id page)))
(and class? page? id)
(p/let [_ (db-page-handler/convert-to-tag! page-entity)]
(p/let [_ (db-page-handler/convert-page-to-tag! page-entity)]
id)
:else
@@ -614,7 +614,7 @@
items' (->>
(if (and (seq selected-choices)
(not multiple-choices?)
(not (and (ldb/class? block) (= (:db/ident property) :logseq.property/parent)))
(not (and (ldb/class? block) (= (:db/ident property) :logseq.property.class/extends)))
(not= (:db/ident property) :logseq.property.view/type))
(concat sorted-items
[{:value clear-value
@@ -674,11 +674,11 @@
(if (every? entity-map? v)
(map :db/id v)
[(:db/id v)])))
parent-property? (= (:db/ident property) :logseq.property/parent)
children-pages (when parent-property? (model/get-structured-children repo (:db/id block)))
extends-property? (= (:db/ident property) :logseq.property.class/extends)
children-pages (when extends-property? (model/get-structured-children repo (:db/id block)))
property-type (:logseq.property/type property)
nodes (cond
parent-property?
extends-property?
(let [;; Disallows cyclic hierarchies
exclude-ids (-> (set (map (fn [id] (:block/uuid (db/entity id))) children-pages))
(conj (:block/uuid block))) ; break cycle
@@ -779,7 +779,7 @@
"Set alias"
:else
(str "Set " (:block/title property)))
:show-new-when-not-exact-match? (if (or (and parent-property? (contains? (set children-pages) (:db/id block)))
:show-new-when-not-exact-match? (if (or (and extends-property? (contains? (set children-pages) (:db/id block)))
;; Don't allow creating private tags
(and (= :block/tags (:db/ident property))
(seq (set/intersection (set (map :db/ident classes'))
@@ -869,20 +869,20 @@
non-root-classes (cond-> (remove (fn [c] (= (:db/ident c) :logseq.class/Root)) classes)
class?
(conj (frontend.db/entity :logseq.class/Tag)))
parent-property? (= (:db/ident property) :logseq.property/parent)]
extends-property? (= (:db/ident property) :logseq.property.class/extends)]
;; effect runs once
(hooks/use-effect!
(fn []
(cond
(and parent-property? (not (ldb/class? block))
(and extends-property? (not (ldb/class? block))
(ldb/internal-page? block))
(p/let [result (db-async/<get-tag-pages repo (:db/id (db/entity :logseq.class/Page)))
result' (->> result
(remove ldb/built-in?))]
(set-result-and-initial-choices! result'))
parent-property?
extends-property?
nil
(seq non-root-classes)
@@ -1499,15 +1499,15 @@
(multiple-values block property opts)
:else
(let [parent? (= property-ident :logseq.property/parent)
(let [extends? (= property-ident :logseq.property.class/extends)
value-cp (property-scalar-value block property v
(merge
opts
{:editor-id editor-id
:dom-id dom-id}))
page-ancestors (when parent?
page-ancestors (when extends?
(let [ancestor-pages (loop [parents [block]]
(if-let [parent (:logseq.property/parent (last parents))]
(if-let [parent (:logseq.property.class/extends (last parents))]
(when-not (contains? (set parents) parent)
(recur (conj parents parent)))
parents))]

View File

@@ -69,7 +69,7 @@
:let [only-cloud? (and remote? (nil? root))
db-based? (config/db-based-graph? url)]]
[:div.flex.justify-between.mb-4.items-center.group {:key (or url GraphUUID)
:data-testid url}
"data-testid" url}
[:div
[:span.flex.items-center.gap-1
(normalized-graph-label repo

View File

@@ -21,16 +21,6 @@ html[data-theme=light] {
height: calc(100vh - 48px);
}
.cp__header {
> .r > div:not(.ui__dropdown-trigger) a, button {
@apply opacity-70 text-[inherit];
&:hover {
@apply opacity-100;
}
}
}
.cp__right-sidebar-topbar {
@apply px-1 h-12;

View File

@@ -2,6 +2,7 @@
"RTC state indicator"
(:require [clojure.pprint :as pprint]
[frontend.common.missionary :as c.m]
[frontend.config :as config]
[frontend.db :as db]
[frontend.flows :as flows]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
@@ -11,8 +12,7 @@
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
[missionary.core :as m]
[rum.core :as rum]
[frontend.config :as config]))
[rum.core :as rum]))
(comment
(def rtc-state-schema
@@ -147,7 +147,7 @@
unpushed-block-update-count (:pending-local-ops detail-info)
{:keys [local-tx remote-tx]} detail-info]
[:div.cp__rtc-sync
[:div.hidden {:data-testid "rtc-tx"} (pr-str {:local-tx local-tx :remote-tx remote-tx})]
[:div.hidden {"data-testid" "rtc-tx"} (pr-str {:local-tx local-tx :remote-tx remote-tx})]
[:div.cp__rtc-sync-indicator.flex.flex-row.items-center.gap-1
(shui/button-ghost-icon :cloud
{:on-click #(shui/popup-show! (.-target %)

View File

@@ -529,10 +529,10 @@ independent of format as format specific heading characters are stripped"
view-context (get m :logseq.property/view-context :all)]
(or (contains? #{:logseq.property/query} (:db/ident m))
(and (not block-page?) (contains? #{:block/alias} (:db/ident m)))
;; Filters out properties from being in wrong :view-context and :never view-contexts
;; Filters out properties from being in wrong :view-context and :never view-contexts
(and (not= view-context :all) (not (contains? block-types view-context)))
(and (ldb/built-in? block) (contains? #{:logseq.property/parent} (:db/ident m)))
;; Filters out adding buggy class properties e.g. Alias and Parent
(and (ldb/built-in? block) (contains? #{:logseq.property.class/extends} (:db/ident m)))
;; Filters out adding buggy class properties e.g. Alias and Parent
(and class-schema? (ldb/public-built-in-property? m) (:logseq.property/view-context m))))))
(defn get-all-properties
@@ -577,7 +577,7 @@ independent of format as format specific heading characters are stripped"
[repo class-id]
(when-let [class (db-utils/entity repo class-id)]
(->>
(if (first (:logseq.property/_parent class)) ; has children classes
(if (first (:logseq.property.class/_extends class)) ; has children classes
(let [all-classes (conj (->> (get-structured-children repo class-id)
(map #(db-utils/entity repo %)))
class)]

View File

@@ -176,8 +176,8 @@
(if-not id
;; add highlight
(let [highlight (merge highlight
{:id (pdf-utils/gen-uuid)
:properties properties})]
{:id (pdf-utils/gen-uuid)
:properties properties})]
(p/let [highlight' (add-hl! highlight)]
(pdf-utils/clear-all-selection owner-win)
(pdf-assets/copy-hl-ref! highlight' viewer)))
@@ -569,15 +569,15 @@
(set-highlights! highlights')
(if-let [vw-pos (and (pdf-assets/area-highlight? hl)
(pdf-utils/scaled-to-vw-pos viewer (:position hl)))]
(pdf-utils/scaled-to-vw-pos viewer (:position hl)))]
(-> (p/let [result (pdf-assets/persist-hl-area-image$ viewer (:pdf/current @state/state)
hl nil (:bounding vw-pos))]
hl nil (:bounding vw-pos))]
(if (de/entity? result)
(let [hl' (assoc-in hl [:content :image] (:db/id result))]
(set-highlights! (map (fn [hl] (if (= (:id hl) (:id hl')) hl' hl)) highlights'))
hl')
hl))
(p/catch (fn [e] (js/console.error e))))
(p/catch (fn [e] (js/console.error e))))
hl))))
upd-hl! (fn [hl]
(let [highlights (pdf-utils/fix-nested-js highlights)]

View File

@@ -53,8 +53,7 @@
(block/breadcrumb {:preview? true}
(state/get-current-repo)
(uuid (gobj/get props "blockId"))
{:end-separator? (gobj/get props "endSeparator")
:level-limit (gobj/get props "levelLimit" 3)}))
{:end-separator? (gobj/get props "endSeparator")})) -
(rum/defc tweet
[props]

View File

@@ -11,10 +11,10 @@
[logseq.common.config :as common-config]
[logseq.common.path :as path]
[logseq.common.util :as common-util]
[logseq.db.frontend.asset :as db-asset]
[medley.core :as medley]
[missionary.core :as m]
[promesa.core :as p]
[logseq.db.frontend.asset :as db-asset])
[promesa.core :as p])
(:import [missionary Cancelled]))
(defn alias-enabled?
@@ -50,15 +50,6 @@
(medley/find-first #(= name (:name (second %1)))
(medley/indexed alias-dirs))))
(defn- convert-platform-protocol
[full-path]
(cond-> full-path
(and (string? full-path)
(mobile-util/native-platform?))
(string/replace-first
#"^(file://|assets://)" common-config/capacitor-protocol-with-prefix)))
(defn resolve-asset-real-path-url
[repo rpath]
(when-let [rpath (and (string? rpath)
@@ -87,7 +78,7 @@
(if has-schema?
(path/path-join graph-root rpath)
(path/prepend-protocol "file:" (path/path-join graph-root rpath)))))]
(convert-platform-protocol ret)))))
ret))))
(defn normalize-asset-resource-url
"try to convert resource file to url asset link"

View File

@@ -77,14 +77,13 @@
(= type order-list-type)))
prev-block-fn #(some-> (db/entity (:db/id %)) ldb/get-left-sibling)
prev-block (prev-block-fn block)]
(letfn [(page-fn? [b] (some-> b :block/name some?))
(order-sibling-list [b]
(letfn [(order-sibling-list [b]
(lazy-seq
(when (and (not (page-fn? b)) (order-block-fn? b))
(when (order-block-fn? b)
(cons b (order-sibling-list (prev-block-fn b))))))
(order-parent-list [b]
(lazy-seq
(when (and (not (page-fn? b)) (order-block-fn? b))
(when (order-block-fn? b)
(cons b (order-parent-list (db-model/get-block-parent (:block/uuid b)))))))]
(let [idx (if prev-block
(count (order-sibling-list block)) 1)

View File

@@ -1,14 +1,14 @@
(ns frontend.handler.common.config-edn
"Common fns related to config.edn - global and repo"
(:require [malli.error :as me]
[malli.core :as m]
(:require [clojure.edn :as edn]
[clojure.string :as string]
[clojure.edn :as edn]
[lambdaisland.glogi :as log]
[frontend.handler.notification :as notification]
[goog.string :as gstring]
[reitit.frontend.easy :as rfe]
[logseq.common.config :as common-config]))
[lambdaisland.glogi :as log]
[logseq.common.config :as common-config]
[malli.core :as m]
[malli.error :as me]
[reitit.frontend.easy :as rfe]))
(defn- humanize-more
"Make error maps from me/humanize more readable for users. Doesn't try to handle
@@ -77,7 +77,7 @@ nested keys or positional errors e.g. tuples"
(do
(config-notification-show! [:<> "Failed to read file " (file-link path)]
"Make sure your config is wrapped in {}. Also make sure that the characters '( { [' have their corresponding closing character ') } ]'.")
false)
false)
;; Custom error message is better than malli's "invalid type" error
(not (map? parsed-body))
(do
@@ -91,7 +91,7 @@ nested keys or positional errors e.g. tuples"
"Detects config keys that will or have been deprecated"
[path content {:keys [db-graph?]}]
(let [body (try (edn/read-string content)
(catch :default _ ::failed-to-detect))
(catch :default _ ::failed-to-detect))
warnings (cond->
{:editor/command-trigger
"is no longer supported. Please use '/' and report bugs on it."

View File

@@ -42,43 +42,50 @@
(when (valid-tag? repo (db/entity repo [:block/uuid block-id]) tag-entity)
(db-property-handler/set-block-property! block-id :block/tags (:db/id tag-entity)))))
(defn convert-to-tag!
(defn convert-page-to-tag!
"Converts a Page to a Tag"
[page-entity]
(if (db/page-exists? (:block/title page-entity) #{:logseq.class/Tag})
(notification/show! (str "A tag with the name \"" (:block/title page-entity) "\" already exists.") :warning false)
(let [txs [(db-class/build-new-class (db/get-db)
{:db/id (:db/id page-entity)
:block/title (:block/title page-entity)
:block/created-at (:block/created-at page-entity)})
[:db/retract (:db/id page-entity) :block/tags :logseq.class/Page]]]
(cond (db/page-exists? (:block/title page-entity) #{:logseq.class/Tag})
(notification/show! (str "A tag with the name \"" (:block/title page-entity) "\" already exists.") :warning false)
(:block/parent page-entity)
(notification/show! "Namespaced pages can't be tags" :error false)
:else
(let [txs [(db-class/build-new-class (db/get-db)
{:db/id (:db/id page-entity)
:block/title (:block/title page-entity)
:block/created-at (:block/created-at page-entity)})
[:db/retract (:db/id page-entity) :block/tags :logseq.class/Page]]]
(db/transact! (state/get-current-repo) txs {:outliner-op :save-block}))))
(db/transact! (state/get-current-repo) txs {:outliner-op :save-block}))))
(defn convert-tag-to-page!
[page-entity]
(if (db/page-exists? (:block/title page-entity) #{:logseq.class/Page})
(notification/show! (str "A page with the name \"" (:block/title page-entity) "\" already exists.") :warning false)
(when-not (:logseq.property/built-in? page-entity)
(p/let [objects (db-async/<get-tag-objects (state/get-current-repo) (:db/id page-entity))]
(let [convert-fn
(fn convert-fn []
(let [page-txs [[:db/retract (:db/id page-entity) :db/ident]
[:db/retract (:db/id page-entity) :block/tags :logseq.class/Tag]
[:db/add (:db/id page-entity) :block/tags :logseq.class/Page]]
obj-txs (mapcat (fn [obj]
(let [tags (map #(db/entity (state/get-current-repo) (:db/id %)) (:block/tags obj))]
[{:db/id (:db/id obj)
:block/title (db-content/replace-tag-refs-with-page-refs (:block/title obj) tags)}
[:db/retract (:db/id obj) :block/tags (:db/id page-entity)]]))
objects)
txs (concat page-txs obj-txs)]
(db/transact! (state/get-current-repo) txs {:outliner-op :save-block})))]
(-> (shui/dialog-confirm!
"Converting a tag to page also removes tags from any nodes that have that tag. Are you ok with that?"
{:id :convert-tag-to-page
:data-reminder :ok})
(p/then convert-fn)))))))
[entity]
(if (db/page-exists? (:block/title entity) #{:logseq.class/Page})
(notification/show! (str "A page with the name \"" (:block/title entity) "\" already exists.") :warning false)
(when-not (:logseq.property/built-in? entity)
(if (seq (:logseq.property.class/_extends entity))
(notification/show! "This tag cannot be converted because it has tag children. All tag children must be removed or converted before converting this tag." :error false)
(p/let [objects (db-async/<get-tag-objects (state/get-current-repo) (:db/id entity))]
(let [convert-fn
(fn convert-fn []
(let [page-txs [[:db/retract (:db/id entity) :db/ident]
[:db/retract (:db/id entity) :block/tags :logseq.class/Tag]
[:db/retract (:db/id entity) :logseq.property.class/extends]
[:db/retract (:db/id entity) :logseq.property.class/properties]
[:db/add (:db/id entity) :block/tags :logseq.class/Page]]
obj-txs (mapcat (fn [obj]
(let [tags (map #(db/entity (state/get-current-repo) (:db/id %)) (:block/tags obj))]
[{:db/id (:db/id obj)
:block/title (db-content/replace-tag-refs-with-page-refs (:block/title obj) tags)}
[:db/retract (:db/id obj) :block/tags (:db/id entity)]]))
objects)
txs (concat page-txs obj-txs)]
(db/transact! (state/get-current-repo) txs {:outliner-op :save-block})))]
(-> (shui/dialog-confirm!
"Converting a tag to page also removes its tag properties and its tag from all nodes tagged with it. Are you ok with that?"
{:id :convert-tag-to-page
:data-reminder :ok})
(p/then convert-fn))))))))
(defn <create-class!
"Creates a class page and provides class-specific error handling"

View File

@@ -1,6 +1,7 @@
(ns frontend.handler.db-based.recent
"Fns related to recent pages feature"
(:require [frontend.db :as db]
(:require [clojure.string :as string]
[frontend.db :as db]
[frontend.state :as state]
[logseq.db :as ldb]))
@@ -9,11 +10,12 @@
(assert db-id (number? db-id))
(when-not (:db/restoring? @state/state)
(when-let [page (db/entity db-id)]
(let [pages (state/get-recent-pages)]
(when-not (or (ldb/hidden? page)
((set pages) db-id))
(let [new-pages (vec (take 15 (distinct (cons db-id pages))))]
(state/set-recent-pages! new-pages)))))))
(when-not (string/blank? (:block/title page))
(let [pages (state/get-recent-pages)]
(when-not (or (ldb/hidden? page)
((set pages) db-id))
(let [new-pages (vec (take 15 (distinct (cons db-id pages))))]
(state/set-recent-pages! new-pages))))))))
(defn get-recent-pages
[]
@@ -24,5 +26,6 @@
(filter db/page?)
(remove ldb/hidden?)
(remove (fn [e]
(and (ldb/property? e)
(true? (:logseq.property/hide? e)))))))
(or (and (ldb/property? e)
(true? (:logseq.property/hide? e)))
(string/blank? (:block/title e)))))))

View File

@@ -338,14 +338,21 @@
true
:else
(not has-children?))]
(not has-children?))
library? (:library? config)
new-block' (if library?
(-> new-block
(-> (assoc :block/tags #{:logseq.class/Page}
:block/name (util/page-name-sanity-lc (:block/title new-block)))
(dissoc :block/page)))
new-block)]
(ui-outliner-tx/transact!
{:outliner-op :insert-blocks}
(save-current-block! {:current-block current-block})
(outliner-op/insert-blocks! [new-block] current-block {:sibling? sibling?
:keep-uuid? keep-uuid?
:ordered-list? ordered-list?
:replace-empty-target? replace-empty-target?}))))
(outliner-op/insert-blocks! [new-block'] current-block {:sibling? sibling?
:keep-uuid? keep-uuid?
:ordered-list? ordered-list?
:replace-empty-target? replace-empty-target?}))))
(defn- block-self-alone-when-insert?
[config uuid]
@@ -528,7 +535,8 @@
custom-uuid replace-empty-target? edit-block? ordered-list? other-attrs]
:or {sibling? false
before? false
edit-block? true}}]
edit-block? true}
:as config}]
(when (or page block-uuid)
(let [repo (state/get-current-repo)
db-based? (config/db-based-graph? repo)
@@ -589,10 +597,11 @@
(p/do!
(ui-outliner-tx/transact!
{:outliner-op :insert-blocks}
(outliner-insert-block! {} block-m new-block {:sibling? sibling?
:keep-uuid? true
:ordered-list? ordered-list?
:replace-empty-target? replace-empty-target?})
(outliner-insert-block! config block-m new-block
{:sibling? sibling?
:keep-uuid? true
:ordered-list? ordered-list?
:replace-empty-target? replace-empty-target?})
(when (and db-based? (seq properties))
(property-handler/set-block-properties! repo (:block/uuid new-block) properties)))
(when edit-block?
@@ -835,6 +844,13 @@
(delete-block-aux! block)
(when edit-block-f (edit-block-f))))))))))))
(defn move-blocks!
[blocks target sibling?]
(when (seq blocks)
(ui-outliner-tx/transact!
{:outliner-op :move-blocks}
(outliner-op/move-blocks! blocks target sibling?))))
(defn delete-block!
[repo]
(delete-block-inner! repo (get-state)))
@@ -1048,14 +1064,24 @@
(let [repo (state/get-current-repo)
block-uuids (distinct (map #(uuid (dom/attr % "blockid")) dom-blocks))
lookup-refs (map (fn [id] [:block/uuid id]) block-uuids)
blocks (->> (map db/entity lookup-refs)
(remove ldb/page?))
top-level-blocks (when (seq blocks) (block-handler/get-top-level-blocks blocks))
sorted-blocks (mapcat (fn [block]
(tree/get-sorted-block-and-children repo (:db/id block)))
top-level-blocks)]
(when (seq sorted-blocks)
(delete-blocks! repo (map :block/uuid sorted-blocks) sorted-blocks dom-blocks)))))))
blocks (map db/entity lookup-refs)
pages (filter ldb/page? blocks)
pages-with-parent (filter (fn [page] (and (:block/parent page) (not (string/blank? (:block/title page))))) pages)]
(ui-outliner-tx/transact!
{:outliner-op :delete-blocks}
(doseq [page pages-with-parent]
(outliner-op/remove-block-property! (:db/id page) :block/parent))
(let [blocks' (if (seq pages-with-parent)
(let [ids (set (map :db/id pages-with-parent))]
(remove (fn [b] (ids (:db/id b))) blocks))
blocks)]
(when (seq blocks')
(let [top-level-blocks (block-handler/get-top-level-blocks blocks')
sorted-blocks (mapcat (fn [block]
(tree/get-sorted-block-and-children repo (:db/id block)))
top-level-blocks)]
(when (seq sorted-blocks)
(delete-blocks! repo (map :block/uuid sorted-blocks) sorted-blocks dom-blocks)))))))))))
(def url-regex
"Didn't use link/plain-link as it is incorrectly detects words as urls."

View File

@@ -216,7 +216,7 @@
(p/let [_ (when (:convert-page-to-tag? chosen-result)
(let [entity (db/entity (:db/id chosen-result))]
(when (and (ldb/page? entity) (not (ldb/class? entity)))
(db-page-handler/convert-to-tag! entity))))
(db-page-handler/convert-page-to-tag! entity))))
chosen-result (if (:block/uuid chosen-result)
(db/entity [:block/uuid (:block/uuid chosen-result)])
chosen-result)

View File

@@ -73,7 +73,7 @@
:logseq.property/icon :block/alias :logseq.property/enable-history?
:logseq.property/exclude-from-graph-view :logseq.property/template-applied-to
:logseq.property/hide-empty-value :logseq.property.class/hide-from-node
:logseq.property/page-tags :logseq.property/parent
:logseq.property/page-tags :logseq.property.class/extends
:logseq.property/publishing-public? :logseq.property.user/avatar
:logseq.property.user/email :logseq.property.user/name})

View File

@@ -354,6 +354,9 @@
(gobj/get range "endContainer")
(gobj/get range "endOffset"))
(let [contents (.cloneContents pre-caret-range)
;; Remove all `.select-none` nodes
_ (doseq [el (.querySelectorAll contents ".select-none")]
(.remove el))
html (some-> (first (.-childNodes contents))
(gobj/get "innerHTML")
str)
@@ -365,7 +368,7 @@
(string/ends-with? html "<div class=\"is-paragraph\"></div></div></span></div></div></div>")
;; multiple lines with a new line
(string/ends-with? html "<br></div></div></span></div></div></div>")))
value (.toString pre-caret-range)]
value (.-textContent contents)]
(if br-ended?
(str value "\n")
value)))))

View File

@@ -1,10 +1,14 @@
(ns frontend.worker.db.migrate
"Handles SQLite and datascript migrations for DB graphs"
(:require [clojure.walk :as walk]
(:require [clojure.string :as string]
[clojure.walk :as walk]
[datascript.core :as d]
[datascript.impl.entity :as de]
[frontend.worker.util :as worker-util]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
[logseq.db :as ldb]
[logseq.db.common.order :as db-order]
[logseq.db.frontend.class :as db-class]
[logseq.db.frontend.property :as db-property]
[logseq.db.frontend.schema :as db-schema]
@@ -15,121 +19,121 @@
;; Frontend migrations
;; ===================
(comment
(defn- rename-properties-aux
[db props-to-rename]
(let [property-tx (map
(fn [[old new]]
(let [e-new (d/entity db new)
e-old (d/entity db old)]
(if e-new
(when e-old
[:db/retractEntity (:db/id e-old)])
(merge {:db/id (:db/id (d/entity db old))
:db/ident new}
(when-let [new-title (get-in db-property/built-in-properties [new :title])]
{:block/title new-title
:block/name (common-util/page-name-sanity-lc new-title)})))))
props-to-rename)
titles-tx (->> (d/datoms db :avet :block/title)
(keep (fn [d]
(let [title (:v d)]
(if (string? title)
(when-let [props (seq (filter (fn [[old _new]] (string/includes? (:v d) (str old))) props-to-rename))]
(let [title' (reduce (fn [title [old new]]
(string/replace title (str old) (str new))) title props)]
[:db/add (:e d) :block/title title']))
[:db/retract (:e d) :block/title])))))
sorting-tx (->> (d/datoms db :avet :logseq.property.table/sorting)
(keep (fn [d]
(when (coll? (:v d))
(when-let [props (seq (filter (fn [[old _new]]
(some (fn [item] (= old (:id item))) (:v d))) props-to-rename))]
(let [value (reduce
(fn [sorting [old new]]
(mapv
(fn [item]
(if (= old (:id item))
(assoc item :id new)
item))
sorting))
(:v d)
props)]
[:db/add (:e d) :logseq.property.table/sorting value]))))))
sized-columns-tx (->> (d/datoms db :avet :logseq.property.table/sized-columns)
(defn- rename-properties-aux
[db props-to-rename]
(let [property-tx (map
(fn [[old new]]
(let [e-new (d/entity db new)
e-old (d/entity db old)]
(if e-new
(when e-old
[:db/retractEntity (:db/id e-old)])
(merge {:db/id (:db/id (d/entity db old))
:db/ident new}
(when-let [new-title (get-in db-property/built-in-properties [new :title])]
{:block/title new-title
:block/name (common-util/page-name-sanity-lc new-title)})))))
props-to-rename)
titles-tx (->> (d/datoms db :avet :block/title)
(keep (fn [d]
(let [title (:v d)]
(if (string? title)
(when-let [props (seq (filter (fn [[old _new]] (string/includes? (:v d) (str old))) props-to-rename))]
(let [title' (reduce (fn [title [old new]]
(string/replace title (str old) (str new))) title props)]
[:db/add (:e d) :block/title title']))
[:db/retract (:e d) :block/title])))))
sorting-tx (->> (d/datoms db :avet :logseq.property.table/sorting)
(keep (fn [d]
(when (coll? (:v d))
(when-let [props (seq (filter (fn [[old _new]]
(some (fn [item] (= old (:id item))) (:v d))) props-to-rename))]
(let [value (reduce
(fn [sorting [old new]]
(mapv
(fn [item]
(if (= old (:id item))
(assoc item :id new)
item))
sorting))
(:v d)
props)]
[:db/add (:e d) :logseq.property.table/sorting value]))))))
sized-columns-tx (->> (d/datoms db :avet :logseq.property.table/sized-columns)
(keep (fn [d]
(when (map? (:v d))
(when-let [props (seq (filter (fn [[old _new]] (get (:v d) old)) props-to-rename))]
(let [value (reduce
(fn [sizes [old new]]
(if-let [size (get sizes old)]
(-> sizes
(dissoc old)
(assoc new size))
sizes))
(:v d)
props)]
[:db/add (:e d) :logseq.property.table/sized-columns value]))))))
hidden-columns-tx (mapcat
(fn [[old new]]
(->> (d/datoms db :avet :logseq.property.table/hidden-columns old)
(mapcat (fn [d]
[[:db/retract (:e d) :logseq.property.table/hidden-columns old]
[:db/add (:e d) :logseq.property.table/hidden-columns new]]))))
props-to-rename)
ordered-columns-tx (->> (d/datoms db :avet :logseq.property.table/ordered-columns)
(keep (fn [d]
(when (map? (:v d))
(when-let [props (seq (filter (fn [[old _new]] (get (:v d) old)) props-to-rename))]
(when (coll? (:v d))
(when-let [props (seq (filter (fn [[old _new]] ((set (:v d)) old)) props-to-rename))]
(let [value (reduce
(fn [sizes [old new]]
(if-let [size (get sizes old)]
(-> sizes
(dissoc old)
(assoc new size))
sizes))
(fn [col [old new]]
(mapv (fn [v] (if (= old v) new v)) col))
(:v d)
props)]
[:db/add (:e d) :logseq.property.table/sized-columns value]))))))
hidden-columns-tx (mapcat
(fn [[old new]]
(->> (d/datoms db :avet :logseq.property.table/hidden-columns old)
(mapcat (fn [d]
[[:db/retract (:e d) :logseq.property.table/hidden-columns old]
[:db/add (:e d) :logseq.property.table/hidden-columns new]]))))
props-to-rename)
ordered-columns-tx (->> (d/datoms db :avet :logseq.property.table/ordered-columns)
(keep (fn [d]
(when (coll? (:v d))
(when-let [props (seq (filter (fn [[old _new]] ((set (:v d)) old)) props-to-rename))]
(let [value (reduce
(fn [col [old new]]
(mapv (fn [v] (if (= old v) new v)) col))
(:v d)
props)]
[:db/add (:e d) :logseq.property.table/ordered-columns value]))))))
filters-tx (->> (d/datoms db :avet :logseq.property.table/filters)
(keep (fn [d]
(let [filters (:filters (:v d))]
(when (coll? filters)
(when-let [props (seq (filter (fn [[old _new]]
(some (fn [item] (and (vector? item)
(= old (first item)))) filters)) props-to-rename))]
(let [value (update (:v d) :filters
(fn [col]
(reduce
(fn [col [old new]]
(mapv (fn [item]
(if (and (vector? item) (= old (first item)))
(vec (cons new (rest item)))
item))
col))
col
props)))]
[:db/add (:e d) :logseq.property.table/filters value])))))))]
(concat property-tx
titles-tx
sorting-tx
sized-columns-tx
hidden-columns-tx
ordered-columns-tx
filters-tx)))
[:db/add (:e d) :logseq.property.table/ordered-columns value]))))))
filters-tx (->> (d/datoms db :avet :logseq.property.table/filters)
(keep (fn [d]
(let [filters (:filters (:v d))]
(when (coll? filters)
(when-let [props (seq (filter (fn [[old _new]]
(some (fn [item] (and (vector? item)
(= old (first item)))) filters)) props-to-rename))]
(let [value (update (:v d) :filters
(fn [col]
(reduce
(fn [col [old new]]
(mapv (fn [item]
(if (and (vector? item) (= old (first item)))
(vec (cons new (rest item)))
item))
col))
col
props)))]
[:db/add (:e d) :logseq.property.table/filters value])))))))]
(concat property-tx
titles-tx
sorting-tx
sized-columns-tx
hidden-columns-tx
ordered-columns-tx
filters-tx)))
(defn- rename-properties
[props-to-rename]
(fn [conn _search-db]
(when (ldb/db-based-graph? @conn)
(let [props-tx (rename-properties-aux @conn props-to-rename)]
;; Property changes need to be in their own tx for subsequent uses of properties to take effect
(ldb/transact! conn props-tx {:db-migrate? true})
(mapcat (fn [[old new]]
;; can't use datoms b/c user properties aren't indexed
(->> (d/q '[:find ?b ?prop-v :in $ ?prop :where [?b ?prop ?prop-v]] @conn old)
(mapcat (fn [[id prop-value]]
[[:db/retract id old]
[:db/add id new prop-value]]))))
props-to-rename)))))
(defn rename-properties
[props-to-rename & {:keys [replace-fn]}]
(fn [conn]
(when (ldb/db-based-graph? @conn)
(let [props-tx (rename-properties-aux @conn props-to-rename)
fix-tx (mapcat (fn [[old new]]
;; can't use datoms b/c user properties aren't indexed
(->> (d/q '[:find ?b ?prop-v :in $ ?prop :where [?b ?prop ?prop-v]] @conn old)
(mapcat (fn [[id prop-value]]
(if (fn? replace-fn)
(replace-fn id prop-value)
[[:db/retract id old]
[:db/add id new prop-value]])))))
props-to-rename)]
(concat props-tx fix-tx)))))
(comment
(defn- rename-classes
[classes-to-rename]
(fn [conn _search-db]
@@ -142,6 +146,53 @@
:block/name (common-util/page-name-sanity-lc new-title)})))
classes-to-rename)))))
(defn fix-rename-parent-to-extends
[conn _search-db]
(let [db @conn
parent-entity (d/entity db :logseq.property/parent)]
(when parent-entity
(let [old-p :logseq.property/parent
new-p :logseq.property.class/extends
f (rename-properties
{old-p new-p}
{:replace-fn (fn [id prop-value]
(let [page (d/entity db id)
new-p' (if (ldb/class? page) new-p :block/parent)]
[[:db/retract id old-p]
[:db/add id new-p' prop-value]]))})
rename-property-tx (f conn)
library-page (if-let [page (ldb/get-built-in-page db common-config/library-page-name)]
page
(-> (sqlite-util/build-new-page common-config/library-page-name)
sqlite-create-graph/mark-block-as-built-in))
library-id (:block/uuid library-page)
library-page-tx (when-not (de/entity? library-page)
[library-page])
pages-with-parent (->> (d/datoms db :avet :logseq.property/parent)
(keep (fn [d]
(let [e (d/entity db (:e d))]
(when-not (ldb/class? e)
e)))))
parents (->> pages-with-parent
(map :logseq.property/parent)
(common-util/distinct-by :db/id))
top-parents (remove :logseq.property/parent parents)
top-parent-ids (set (map :db/id top-parents))
move-top-parents-to-library (map (fn [parent]
{:db/id (:db/id parent)
:block/parent [:block/uuid library-id]
:block/order (db-order/gen-key)}) top-parents)
update-children-parent-and-order (->> pages-with-parent
(remove (fn [page] (top-parent-ids (:db/id page))))
(map (fn [page]
{:db/id (:db/id page)
:block/order (db-order/gen-key)})))]
(concat
rename-property-tx
library-page-tx
move-top-parents-to-library
update-children-parent-and-order)))))
(defn separate-classes-and-properties
[conn _sqlite-db]
;; find all properties that're classes, create new properties to separate them
@@ -198,7 +249,8 @@
"A vec of tuples defining datascript migrations. Each tuple consists of the
schema version integer and a migration map. A migration map can have keys of :properties, :classes
and :fix."
[["65.0" {:fix separate-classes-and-properties}]])
[["65.0" {:fix separate-classes-and-properties}]
["65.1" {:fix fix-rename-parent-to-extends}]])
(let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
schema-version->updates)))]
@@ -302,7 +354,6 @@
"Migrate 'frontend' datascript schema and data. To add a new migration,
add an entry to schema-version->updates and bump db-schema/version"
[conn search-db]
(prn :debug :migrate)
(when (ldb/db-based-graph? @conn)
(let [db @conn
version-in-db (db-schema/parse-schema-version (or (:kv/value (d/entity db :logseq.kv/schema-version)) 0))

View File

@@ -94,10 +94,10 @@
delete-property-tx
[[:db.fn/retractEntity (:db/id page)]])
restore-class-parent-tx (when db-based?
(->> (filter (fn [p] (ldb/class? p)) (:logseq.property/_parent page))
(->> (filter ldb/class? (:logseq.property.class/_extends page))
(map (fn [p]
{:db/id (:db/id p)
:logseq.property/parent :logseq.class/Root}))))
:logseq.property.class/extends :logseq.class/Root}))))
tx-data (concat truncate-blocks-tx-data
restore-class-parent-tx
delete-page-tx

View File

@@ -3,10 +3,12 @@
(:require [clojure.string :as string]
[datascript.core :as d]
[datascript.impl.entity :as de]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
[logseq.common.util.namespace :as ns-util]
[logseq.db :as ldb]
[logseq.db.common.entity-plus :as entity-plus]
[logseq.db.common.order :as db-order]
[logseq.db.frontend.class :as db-class]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.malli-schema :as db-malli-schema]
@@ -74,23 +76,34 @@
title))
(defn- get-page-by-parent-name
[db parent-title child-title]
[db parent-title child-title class?]
(some->>
(d/q
'[:find [?b ...]
:in $ ?parent-name ?child-name
:in $ ?attribute ?parent-name ?child-name
:where
[?b :logseq.property/parent ?p]
[?b ?attribute ?p]
[?b :block/name ?child-name]
[?p :block/name ?parent-name]]
db
(if class? :logseq.property.class/extends :block/parent)
(common-util/page-name-sanity-lc parent-title)
(common-util/page-name-sanity-lc child-title))
first
(d/entity db)))
(defn- page-with-parent-and-order
"Apply to namespace pages"
[db page & {:keys [parent]}]
(let [library (ldb/get-built-in-page db common-config/library-page-name)]
(when (nil? library)
(throw (ex-info "Library page doesn't exist" {})))
(assoc page
:block/parent (or parent (:db/id library))
:block/order (db-order/gen-key))))
(defn- split-namespace-pages
[db page date-formatter]
[db page date-formatter create-class?]
(let [{:block/keys [title] block-uuid :block/uuid} page]
(->>
(if (and (or (entity-util/class? page)
@@ -100,20 +113,19 @@
parts (->> (string/split title ns-util/parent-re)
(map string/trim)
(remove string/blank?))
pages (doall
(map-indexed
(fn [idx part]
(let [last-part? (= idx (dec (count parts)))
page (if (zero? idx)
(ldb/get-page db part)
(get-page-by-parent-name db (nth parts (dec idx)) part))
result (or page
(gp-block/page-name->map part db true date-formatter
{:page-uuid (when last-part? block-uuid)
:skip-existing-page-check? true
:class? class?}))]
result))
parts))]
pages (map-indexed
(fn [idx part]
(let [last-part? (= idx (dec (count parts)))
page (if (zero? idx)
(ldb/get-page db part)
(get-page-by-parent-name db (nth parts (dec idx)) part create-class?))
result (or page
(gp-block/page-name->map part db true date-formatter
{:page-uuid (when last-part? block-uuid)
:skip-existing-page-check? true
:class? class?}))]
result))
parts)]
(cond
(and (not class?) (not (every? ldb/internal-page? pages)))
(throw (ex-info "Cannot create this page unless all parents are pages"
@@ -136,7 +148,7 @@
(if class?
(cond
(and (de/entity? page) (ldb/class? page))
(assoc page :logseq.property/parent parent-eid)
(assoc page :logseq.property.class/extends parent-eid)
(de/entity? page) ; page exists but not a class, avoid converting here because this could be troublesome.
nil
@@ -145,10 +157,10 @@
(db-class/build-new-class db page)
:else
(db-class/build-new-class db (assoc page :logseq.property/parent parent-eid)))
(if (or (de/entity? page) (zero? idx))
(db-class/build-new-class db (assoc page :logseq.property.class/extends parent-eid)))
(if (de/entity? page)
page
(assoc page :logseq.property/parent parent-eid)))))
(page-with-parent-and-order db page {:parent parent-eid})))))
pages)))
[page])
(remove nil?))))
@@ -188,12 +200,12 @@
[:db/retract [:block/uuid (:block/uuid existing-page)] :block/tags :logseq.class/Page]]]
{:tx-meta tx-meta
:tx-data tx-data})))
(let [page (gp-block/page-name->map title db true date-formatter
{:class? class?
:page-uuid (when (uuid? uuid) uuid)
:skip-existing-page-check? true})
(let [page (gp-block/page-name->map title db true date-formatter
{:class? class?
:page-uuid (when (uuid? uuid) uuid)
:skip-existing-page-check? true})
[page parents] (if (and (text/namespace-page? title) split-namespace?)
(let [pages (split-namespace-pages db page date-formatter)]
(let [pages (split-namespace-pages db page date-formatter class?)]
[(last pages) (butlast pages)])
[page nil])]
(when (and page (or (nil? (:db/ident page))

View File

@@ -66,7 +66,7 @@
(let [object (d/entity db e)
template-blocks (->> (mapcat (fn [id]
(let [tag (d/entity db id)
parents (ldb/get-page-parents tag {:node-class? true})
parents (ldb/get-class-extends tag)
templates (mapcat :logseq.property/_template-applied-to (conj parents tag))]
(cond->> templates
journal-page

View File

@@ -273,7 +273,7 @@ DROP TRIGGER IF EXISTS blocks_au;
* :limit - Number of result to limit search results. Defaults to 100
* :dev? - Allow all nodes to be seen for development. Defaults to false
* :built-in? - Whether to return public built-in nodes for db graphs. Defaults to false"
[repo conn search-db q {:keys [limit page enable-snippet? built-in? dev? page-only?]
[repo conn search-db q {:keys [limit page enable-snippet? built-in? dev? page-only? library-page-search?]
:as option
:or {enable-snippet? true}}]
(when-not (string/blank? q)
@@ -308,26 +308,29 @@ DROP TRIGGER IF EXISTS blocks_au;
(let [{:keys [id page title snippet]} result
block-id (uuid id)]
(when-let [block (d/entity @conn [:block/uuid block-id])]
(when (if dev?
true
(if built-in?
(or (not (ldb/built-in? block))
(not (ldb/private-built-in-page? block))
(ldb/class? block))
(or (not (ldb/built-in? block))
(ldb/class? block))))
{:db/id (:db/id block)
:block/uuid block-id
:block/title (if (ldb/page? block)
(ldb/get-title-with-parents block)
(or snippet title))
:block/page (if (common-util/uuid-string? page)
(uuid page)
nil)
:block/tags (seq (map :db/id (:block/tags block)))
:page? (ldb/page? block)
:alias (some-> (first (:block/_alias block))
(select-keys [:block/uuid :block/title]))}))))))
(when-not (and library-page-search?
(or (:block/parent block)
(not (ldb/internal-page? block)))) ; remove pages that already have parents
(when (if dev?
true
(if built-in?
(or (not (ldb/built-in? block))
(not (ldb/private-built-in-page? block))
(ldb/class? block))
(or (not (ldb/built-in? block))
(ldb/class? block))))
{:db/id (:db/id block)
:block/uuid block-id
:block/title (if (ldb/page? block)
(ldb/get-title-with-parents block)
(or snippet title))
:block/page (if (common-util/uuid-string? page)
(uuid page)
nil)
:block/tags (seq (map :db/id (:block/tags block)))
:page? (ldb/page? block)
:alias (some-> (first (:block/_alias block))
(select-keys [:block/uuid :block/title]))})))))))
page-or-object-result (filter (fn [b] (or (:page? b) (:block/tags result))) result)]
(->>
(concat page-or-object-result

View File

@@ -22,7 +22,6 @@
:right-side-bar/switch-theme "Skakel oor na die {1} tema"
:right-side-bar/contents "Inhoud"
:right-side-bar/block-ref "Blok verwysing"
:page/delete-confirmation "Is jy seker jy wil die bladsy uitvee?"
:file/name "Lêer naam"
:file/last-modified-at "Laas verander op"
:file/no-data "Geen data"

View File

@@ -419,7 +419,6 @@
:page/copy-page-url "Copiar URL de la pàgina"
:page/created-at "Creada el"
:page/delete "Eliminar pàgina"
:page/delete-confirmation "Està segur que desitja eliminar aquesta pàgina i el seu arxiu?"
:page/illegal-page-name "¡Nom de pàgina no permès!"
:page/logseq-is-having-a-problem "Logseq està tenint un problema. Per intentar tornar a un estat de treball, intenti el següent procediment en ordre:"
:page/make-private "Fer privada"

View File

@@ -130,7 +130,6 @@
:page/logseq-is-having-a-problem "Logseq má problém. Chcete-li se pokusit uvést jej zpět do funkčního stavu, vyzkoušejte prosím následující bezpečné kroky v uvedeném pořadí:"
:page/step "Krok {1}"
:page/try "Zkusit"
:page/delete-confirmation "Jste si jisti, že chcete tuto stránku a její soubor odstranit?"
:page/open-in-finder "Otevřít v adresáři"
:page/open-with-default-app "Otevřít pomocí výchozí aplikace"
:page/make-public "Označit stránku jjako veřejnou"

View File

@@ -235,7 +235,6 @@
:page/copy-page-url "Seiten-URL kopieren"
:page/created-at "Erstellt am"
:page/delete "Seite löschen"
:page/delete-confirmation "Diese Seite und die zugehörige Datei löschen?"
:page/illegal-page-name "Seitenname nicht zulässig!"
:page/logseq-is-having-a-problem "Logseq hat ein Problem festgestellt. Versuche zurückzukehren ..."
:page/make-private "Privat machen"

View File

@@ -137,7 +137,7 @@
:page/logseq-is-having-a-problem "Logseq is having a problem. To try to get it back to a working state, please try the following safe steps in order:"
:page/step "Step {1}"
:page/try "Try"
:page/delete-confirmation "Are you sure you want to delete this page and its file?"
:page/delete-confirmation "Are you sure you want to delete this page?"
:page/db-delete-confirmation "Are you sure you want to delete this page?"
:page/open-in-finder "Open in directory"
:page/open-with-default-app "Open with default app"

View File

@@ -418,7 +418,6 @@
:page/copy-page-url "Copiar URL de la página"
:page/created-at "Creada el"
:page/delete "Eliminar página"
:page/delete-confirmation "¿Está seguro que desea eliminar esta página y su archivo?"
:page/illegal-page-name "¡Nombre de página ilegal!"
:page/logseq-is-having-a-problem "Logseq está teniendo un problema. Para intentar volver a un estado de trabajo, intenta los siguientes pasos seguros en orden:"
:page/make-private "Hacer privada"

View File

@@ -106,7 +106,6 @@
:page/logseq-is-having-a-problem "لاگ‌سیک دچار مشکلی شده است. برای بازگرداندن آن به حالت قابل استفاده لطفا گام‌های زیر را به ترتیب انجام دهید:"
:page/step "گام {1}"
:page/try "امتحان کنید"
:page/delete-confirmation "مطمئنید که می‌خواید این برگه به همراه پرونده‌اش را پاک کنید؟"
:page/open-in-finder "باز کردن در پوشه"
:page/open-with-default-app "باز کردن با برنامه پیش‌فرض"
:page/make-public "عمومی ساختن برای انتشار"

View File

@@ -29,7 +29,6 @@
:right-side-bar/all-pages "Toutes les pages"
:right-side-bar/flashcards "Cartes-mémoire"
:left-side-bar/journals "Journaux"
:page/delete-confirmation "Voulez-vous vraiment supprimer la page ?"
:page/make-public "Rendre la page publique"
:page/make-private "Rendre la page privée"
:page/delete "Supprimer la page (supprime le fichier)"

View File

@@ -125,7 +125,6 @@
:page/logseq-is-having-a-problem "Logseq mengalami masalah. Untuk mencoba mengembalikannya ke keadaan yang berfungsi, silakan coba langkah-langkah aman berikut ini secara berurutan:"
:page/step "Langkah {1}"
:page/try "Coba"
:page/delete-confirmation "Apakah Anda yakin ingin menghapus halaman ini beserta berkasnya?"
:page/open-in-finder "Buka dalam direktori"
:page/open-with-default-app "Buka dengan aplikasi default"
:page/make-public "Buat publik untuk dipublikasikan"

View File

@@ -33,7 +33,6 @@
:left-side-bar/journals "Diario"
:left-side-bar/nav-favorites "Preferiti"
:left-side-bar/nav-recent-pages "Recenti"
:page/delete-confirmation "Sei sicuro di voler eliminare questa pagina e i suoi dati?"
:page/open-in-finder "Apri nella cartella"
:page/open-with-default-app "Apri con l'app predefinita"
:page/make-public "Segna come pubblico per la pubblicazione"

View File

@@ -134,7 +134,6 @@
:page/logseq-is-having-a-problem "Logseqになにか問題があります。きちんと動く状態に戻すため、以下の項目を上から順番に実行してください"
:page/step "ステップ{1}"
:page/try "試す"
:page/delete-confirmation "このページとページのファイルを削除してもよいですか?"
:page/open-in-finder "ディレクトリで開く"
:page/open-with-default-app "既定のアプリで開く"
:page/make-public "パブリッシュのため公開する"

View File

@@ -34,7 +34,6 @@
:left-side-bar/journals "일지"
:left-side-bar/nav-favorites "즐겨찾기"
:left-side-bar/nav-recent-pages "최근 페이지"
:page/delete-confirmation "이 페이지와 페이지의 파일들을 삭제하시겠습니까?"
:page/open-in-finder "디렉토리에서 열기"
:page/open-with-default-app "기본 앱으로 열기"
:page/make-public "출판 전 공개 상태로 만들기"

View File

@@ -37,7 +37,6 @@
:left-side-bar/journals "Dagbøker"
:left-side-bar/nav-favorites "Favoritter"
:left-side-bar/nav-recent-pages "Nylig"
:page/delete-confirmation "Er du sikker på at du vil slette denne siden og filen dens?"
:page/open-in-finder "Åpne i mappe"
:page/open-with-default-app "Åpne med forhåndsvalgt app"
:page/make-public "Gjør den offentlig for publisering"

View File

@@ -97,7 +97,6 @@
:page/copy-page-url "Kopieer pagina URL"
:page/created-at "Aangemaakt op"
:page/delete "Verwijder pagina"
:page/delete-confirmation "Weet u zeker dat u deze pagina wilt verwijderen..."
:page/make-private "Maak prive"
:page/make-public "Maak publiek"
:page/open-backup-directory "Open backups map pagina"

View File

@@ -36,7 +36,6 @@
:left-side-bar/journals "Dzienniki"
:left-side-bar/nav-favorites "Ulubione"
:left-side-bar/nav-recent-pages "Ostatnio odwiedzane"
:page/delete-confirmation "Czy jesteś pewien że chcesz usunąć tę stronę i jej plik?"
:page/open-in-finder "Otwórz w przeglądarce plików"
:page/open-with-default-app "Otwórz w domyślnej aplikacji"
:page/make-public "Oznacz jako publiczną"

View File

@@ -130,7 +130,6 @@
:page/logseq-is-having-a-problem "Logseq está com um problema. Para tentar fazê-lo voltar a funcionar, siga as etapas seguras a seguir:"
:page/step "Etapa {1}"
:page/try "Tentar"
:page/delete-confirmation "Tem certeza de que deseja excluir esta página e seu arquivo?"
:page/open-in-finder "Abrir no diretório"
:page/open-with-default-app "Abrir com o aplicativo padrão"
:page/make-public "Torná-la pública para publicação"

View File

@@ -54,7 +54,6 @@
:page/logseq-is-having-a-problem "Logseq está tendo um problema. Para tentar colocá-lo de volta em um estado de funcionamento, por favor tente os seguintes passos seguros em ordem:"
:page/step "Passo {1}"
:page/try "Tentar"
:page/delete-confirmation "Tem a certeza de que quer apagar esta página e o respetivo ficheiro?"
:page/open-in-finder "Abrir em pasta"
:page/open-with-default-app "Abrir com a aplicação predefinida"
:page/make-public "Tornar pública para publicação"

View File

@@ -61,7 +61,6 @@
:page/logseq-is-having-a-problem "У Logseq возникла проблема. Чтобы попытаться вернуть его в рабочее состояние, пожалуйста, попробуйте выполнить следующие безопасные шаги по порядку:"
:page/step "Шаг {1}"
:page/try "Попробовать"
:page/delete-confirmation "Вы уверены, что хотите удалить эту страницу и её файл(ы)?"
:page/open-in-finder "Открыть в каталоге"
:page/open-with-default-app "Открыть в приложении по умолчанию"
:page/make-public "Сделать доступной для публикации"

View File

@@ -130,7 +130,6 @@
:page/logseq-is-having-a-problem "Logseq má problém. Skúste tieto bezpečné kroky, aby ste ho dostali do funkčného stavu:"
:page/step "Krok {1}"
:page/try "Vyskúšať"
:page/delete-confirmation "Naozaj chcete odstrániť túto stránku a jej súbor?"
:page/open-in-finder "Otvoriť v adresári"
:page/open-with-default-app "Otvoriť v predvolenej aplikácii"
:page/make-public "Označiť stránku ako verejnú"

View File

@@ -134,7 +134,6 @@
:page/logseq-is-having-a-problem "Logseq'te bir sorun var. Tekrar çalışır duruma getirmek için lütfen aşağıdaki güvenli adımları sırayla deneyin:"
:page/step "{1}. Adım"
:page/try "Deneyin"
:page/delete-confirmation "Bu sayfayı ve dosyasını silmek istediğinizden emin misiniz?"
:page/open-in-finder "Dizini aç"
:page/open-with-default-app "Varsayılan uygulamayla aç"
:page/make-public "Yayımlamak için herkese açık hale getir"

View File

@@ -55,7 +55,6 @@
:page/logseq-is-having-a-problem "У Logseq виникла проблема. Щоб спробувати повернути його до робочого стану, виконайте такі безпечні кроки по порядку:"
:page/step "Крок {1}"
:page/try "Спробувати"
:page/delete-confirmation "Ви впевнені, що хочете видалити цю сторінку та її файл?"
:page/open-in-finder "Відкрити у директорії"
:page/open-with-default-app "Відкрити за допомогою програми за умовчанням"
:page/make-public "Зробіти загальнодоступним для публікації"

View File

@@ -107,7 +107,6 @@
:left-side-bar/journals "日志"
:left-side-bar/nav-favorites "收藏页面"
:left-side-bar/nav-recent-pages "最近使用"
:page/delete-confirmation "您确定要删除此页面和文件吗?"
:page/open-in-finder "打开文件对应目录"
:page/open-with-default-app "用默认应用打开文件"
:page/make-public "导出 HTML 时发布本页面"

View File

@@ -55,7 +55,6 @@
:page/logseq-is-having-a-problem "Logseq 出了些問題。請按照以下安全步驟將其恢復到正常狀態:"
:page/step "步驟 {1}"
:page/try "嘗試"
:page/delete-confirmation "你確定想刪除此頁面檔案嗎?"
:page/open-in-finder "開啟資料夾"
:page/open-with-default-app "使用預設應用程式開啟"
:page/make-public "將其公開讓所有人均可檢視"

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More