mirror of
https://github.com/logseq/logseq.git
synced 2026-05-23 20:24:15 +00:00
fix: comment issues
This commit is contained in:
2
deps/db/src/logseq/db/frontend/class.cljs
vendored
2
deps/db/src/logseq/db/frontend/class.cljs
vendored
@@ -44,7 +44,7 @@
|
||||
:logseq.class/Comments
|
||||
{:title "Comments"
|
||||
:properties {:logseq.property.class/hide-from-node true
|
||||
:logseq.property/icon {:type :emoji, :id "speech_balloon"}}
|
||||
:logseq.property/icon {:type :tabler-icon, :id "message-circle"}}
|
||||
:schema {:properties [:logseq.property.comments/blocks]}}
|
||||
|
||||
:logseq.class/Comment
|
||||
|
||||
2
deps/db/src/logseq/db/frontend/schema.cljs
vendored
2
deps/db/src/logseq/db/frontend/schema.cljs
vendored
@@ -30,7 +30,7 @@
|
||||
(map (juxt :major :minor)
|
||||
[(parse-schema-version x) (parse-schema-version y)])))
|
||||
|
||||
(def version (parse-schema-version "65.28"))
|
||||
(def version (parse-schema-version "65.29"))
|
||||
|
||||
(defn major-version
|
||||
"Return a number.
|
||||
|
||||
37
src/main/frontend/components/avatar.cljs
Normal file
37
src/main/frontend/components/avatar.cljs
Normal file
@@ -0,0 +1,37 @@
|
||||
(ns frontend.components.avatar
|
||||
(:require [clojure.string :as string]
|
||||
[logseq.shui.ui :as shui]
|
||||
[logseq.shui.util :as shui-util]
|
||||
[rum.core :as rum]))
|
||||
|
||||
(defn initials
|
||||
([name]
|
||||
(initials name 2))
|
||||
([name n]
|
||||
(when (some? name)
|
||||
(let [name (string/trim (str name))]
|
||||
(when-not (string/blank? name)
|
||||
(-> (subs name 0 (min n (count name)))
|
||||
string/upper-case))))))
|
||||
|
||||
(rum/defc user-avatar
|
||||
[{:keys [name title uuid avatar-src class style fallback fallback-length fallback-props image-props]
|
||||
:or {fallback-length 2}}]
|
||||
(let [color (when uuid (shui-util/uuid-color uuid))
|
||||
fallback-text (or fallback (initials name fallback-length))
|
||||
fallback-style (cond-> (:style fallback-props)
|
||||
(and color (nil? (:background-color (:style fallback-props))))
|
||||
(assoc :background-color (str color "50")))
|
||||
fallback-props' (cond-> fallback-props
|
||||
(seq fallback-style)
|
||||
(assoc :style fallback-style))]
|
||||
(shui/avatar
|
||||
(cond-> {}
|
||||
class (assoc :class class)
|
||||
style (assoc :style style)
|
||||
title (assoc :title title))
|
||||
(when (seq avatar-src)
|
||||
(shui/avatar-image (merge {:src avatar-src} image-props)))
|
||||
(if (seq fallback-props')
|
||||
(shui/avatar-fallback fallback-props' fallback-text)
|
||||
(shui/avatar-fallback fallback-text)))))
|
||||
@@ -10,6 +10,7 @@
|
||||
[datascript.impl.entity :as e]
|
||||
[dommy.core :as dom]
|
||||
[electron.ipc :as ipc]
|
||||
[frontend.components.avatar :as avatar]
|
||||
[frontend.components.block.breadcrumb-model :as breadcrumb-model]
|
||||
[frontend.components.block.comments :as block-comments]
|
||||
[frontend.components.block.comments-model :as comments-model]
|
||||
@@ -92,7 +93,6 @@
|
||||
[logseq.shui.dialog.core :as shui-dialog]
|
||||
[logseq.shui.hooks :as hooks]
|
||||
[logseq.shui.ui :as shui]
|
||||
[logseq.shui.util :as shui-util]
|
||||
[medley.core :as medley]
|
||||
[missionary.core :as m]
|
||||
[promesa.core :as p]
|
||||
@@ -108,6 +108,10 @@
|
||||
(defonce *drag-to-block
|
||||
(atom nil))
|
||||
(def *move-to (atom nil))
|
||||
(def ^:private comment-thread-presence-ttl-ms 30000)
|
||||
(defonce *comment-thread-presence (atom {}))
|
||||
(defonce *comment-thread-presence-requests (atom {}))
|
||||
(defonce *comment-thread-presence-flush-scheduled? (atom false))
|
||||
|
||||
;; TODO:
|
||||
;; add `key`
|
||||
@@ -2044,12 +2048,144 @@
|
||||
[block]
|
||||
(string/blank? (:block/title block)))
|
||||
|
||||
(defn- user-initials
|
||||
[user-name]
|
||||
(when (string? user-name)
|
||||
(let [name (string/trim user-name)]
|
||||
(when-not (string/blank? name)
|
||||
(-> name (subs 0 (min 2 (count name))) string/upper-case)))))
|
||||
(defn- element-in-viewport?
|
||||
[^js el]
|
||||
(when el
|
||||
(let [rect (.getBoundingClientRect el)
|
||||
viewport-height (or (.-innerHeight js/window)
|
||||
(some-> js/document .-documentElement .-clientHeight))
|
||||
viewport-width (or (.-innerWidth js/window)
|
||||
(some-> js/document .-documentElement .-clientWidth))]
|
||||
(and (< (.-top rect) viewport-height)
|
||||
(> (.-bottom rect) 0)
|
||||
(< (.-left rect) viewport-width)
|
||||
(> (.-right rect) 0)))))
|
||||
|
||||
(defn- comments-area-in-viewport?
|
||||
[comments-area]
|
||||
(some-> (:block/uuid comments-area)
|
||||
(str)
|
||||
((fn [uuid] (gdom/getElement (str "ls-block-" uuid))))
|
||||
element-in-viewport?))
|
||||
|
||||
(defn- inline-comment-thread?
|
||||
[inline-thread target-block-uuid comments-area]
|
||||
(and comments-area
|
||||
(= (:target-block-uuid inline-thread) (str target-block-uuid))
|
||||
(= (:comments-area-uuid inline-thread) (str (:block/uuid comments-area)))))
|
||||
|
||||
(defn- open-comment-thread!
|
||||
[target-block-uuid comments-area]
|
||||
(case (comments-model/comment-thread-click-action (comments-area-in-viewport? comments-area))
|
||||
:focus-comments-area
|
||||
(do
|
||||
(state/set-state! :comments/inline-thread nil)
|
||||
(comments-handler/reveal-comments-area! comments-area {:focus-editor? true}))
|
||||
|
||||
:show-inline-comments
|
||||
(do
|
||||
(comments-handler/expand-comments-area! comments-area)
|
||||
(state/set-state! :comments/inline-thread
|
||||
(comments-model/next-inline-comment-thread
|
||||
(get @state/state :comments/inline-thread)
|
||||
target-block-uuid
|
||||
(:block/uuid comments-area))))))
|
||||
|
||||
(defn- ui-comment-thread-for-block
|
||||
[block]
|
||||
(when-let [uuid (:block/uuid block)]
|
||||
(comments-model/comment-thread-for-block
|
||||
block
|
||||
(db/entity [:block/uuid uuid]))))
|
||||
|
||||
(defn- comment-thread-presence-key
|
||||
[block]
|
||||
(when-let [uuid (:block/uuid block)]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
[repo (str uuid)])))
|
||||
|
||||
(defn- fresh-comment-thread-presence
|
||||
[cache-key]
|
||||
(when-let [{:keys [checked-at] :as entry} (get @*comment-thread-presence cache-key)]
|
||||
(when (< (- (js/Date.now) checked-at) comment-thread-presence-ttl-ms)
|
||||
entry)))
|
||||
|
||||
(defn- cache-comment-thread-presence!
|
||||
[cache-key present?]
|
||||
(swap! *comment-thread-presence
|
||||
assoc
|
||||
cache-key
|
||||
{:present? (boolean present?)
|
||||
:checked-at (js/Date.now)}))
|
||||
|
||||
(declare flush-comment-thread-presence!)
|
||||
|
||||
(defn- schedule-comment-thread-presence-check!
|
||||
[state block]
|
||||
(when-let [cache-key (comment-thread-presence-key block)]
|
||||
(let [*comment-thread-present? (get state ::comment-thread-present?)
|
||||
cached-entry (fresh-comment-thread-presence cache-key)]
|
||||
(cond
|
||||
(or (comments-model/protected-comment-block? block)
|
||||
(ui-comment-thread-for-block block))
|
||||
(reset! *comment-thread-present? nil)
|
||||
|
||||
cached-entry
|
||||
(reset! *comment-thread-present? (:present? cached-entry))
|
||||
|
||||
:else
|
||||
(do
|
||||
(swap! *comment-thread-presence-requests
|
||||
update
|
||||
cache-key
|
||||
(fnil conj #{})
|
||||
*comment-thread-present?)
|
||||
(when (compare-and-set! *comment-thread-presence-flush-scheduled? false true)
|
||||
(util/schedule flush-comment-thread-presence!)))))))
|
||||
|
||||
(defn- flush-comment-thread-presence!
|
||||
[]
|
||||
(let [requests @*comment-thread-presence-requests]
|
||||
(reset! *comment-thread-presence-requests {})
|
||||
(reset! *comment-thread-presence-flush-scheduled? false)
|
||||
(doseq [[repo repo-requests] (group-by (fn [[cache-key _listeners]] (first cache-key))
|
||||
requests)]
|
||||
(let [repo-cache-keys (mapv first repo-requests)
|
||||
block-uuids (mapv second repo-cache-keys)]
|
||||
(when (seq block-uuids)
|
||||
(p/let [commented-block-uuids (comments-handler/<get-comment-thread-block-uuids repo block-uuids)
|
||||
commented-block-uuids (into #{} commented-block-uuids)]
|
||||
(doseq [[cache-key listeners] repo-requests
|
||||
:let [present? (contains? commented-block-uuids (second cache-key))]]
|
||||
(cache-comment-thread-presence! cache-key present?)
|
||||
(doseq [*listener listeners]
|
||||
(reset! *listener present?)))))))))
|
||||
|
||||
(defn- hydrate-comment-thread!
|
||||
[state block]
|
||||
(when-let [uuid (:block/uuid block)]
|
||||
(let [*hydrated-comment-thread (get state ::hydrated-comment-thread)
|
||||
cache-key (comment-thread-presence-key block)]
|
||||
(p/let [threads (comments-handler/<get-comment-threads-for-block uuid)
|
||||
thread (or (ui-comment-thread-for-block block)
|
||||
(comments-model/comment-thread-for-block
|
||||
(assoc block :logseq.property.comments/_blocks threads)))]
|
||||
(when cache-key
|
||||
(cache-comment-thread-presence! cache-key thread))
|
||||
(reset! *hydrated-comment-thread (when thread
|
||||
{:block-uuid uuid
|
||||
:thread thread}))
|
||||
(some-> (get state ::comment-thread-present?)
|
||||
(reset! (boolean thread)))
|
||||
thread))))
|
||||
|
||||
(defn- open-comment-thread-for-block!
|
||||
[state block comment-thread]
|
||||
(if comment-thread
|
||||
(open-comment-thread! (:block/uuid block) comment-thread)
|
||||
(p/let [comment-thread (hydrate-comment-thread! state block)]
|
||||
(when comment-thread
|
||||
(open-comment-thread! (:block/uuid block) comment-thread)))))
|
||||
|
||||
(defn- editing-user-for-block
|
||||
[block-uuid online-users current-user-uuid]
|
||||
@@ -2063,18 +2199,15 @@
|
||||
|
||||
(defn- editing-user-avatar
|
||||
[{:user/keys [name uuid]}]
|
||||
(let [user-name (or name uuid)
|
||||
initials (user-initials user-name)
|
||||
color (when uuid (shui-util/uuid-color uuid))]
|
||||
(when initials
|
||||
(let [user-name (or name uuid)]
|
||||
(when (avatar/initials user-name)
|
||||
[:span.block-editing-avatar-wrap
|
||||
(shui/avatar
|
||||
(avatar/user-avatar
|
||||
{:class "block-editing-avatar w-4 h-4 flex-none"
|
||||
:title user-name}
|
||||
(shui/avatar-fallback
|
||||
{:style {:background-color (when color (str color "50"))
|
||||
:font-size 9}}
|
||||
initials))])))
|
||||
:title user-name
|
||||
:name user-name
|
||||
:uuid uuid
|
||||
:fallback-props {:style {:font-size 9}}})])))
|
||||
|
||||
(rum/defcs ^:large-vars/cleanup-todo block-control < rum/reactive
|
||||
(rum/local false ::dragging?)
|
||||
@@ -3770,11 +3903,12 @@
|
||||
config
|
||||
block
|
||||
(ldb/get-children block)
|
||||
collapsed?
|
||||
*hide-block-refs?
|
||||
*show-query?
|
||||
{:block-content-or-editor block-content-or-editor
|
||||
:block-reactions block-reactions})
|
||||
collapsed?
|
||||
*hide-block-refs?
|
||||
*show-query?
|
||||
{:block-content-or-editor block-content-or-editor
|
||||
:block-reactions block-reactions}
|
||||
{})
|
||||
(block-content-or-editor config
|
||||
parsed-block
|
||||
{:edit-input-id edit-input-id
|
||||
@@ -3823,7 +3957,17 @@
|
||||
::show-query? (atom false)
|
||||
::refs-count *refs-count
|
||||
::plugin-renderer-error? (atom false)
|
||||
::use-plugin-renderer? (atom true))))}
|
||||
::use-plugin-renderer? (atom true)
|
||||
::hydrated-comment-thread (atom nil)
|
||||
::comment-thread-present? (atom nil))))
|
||||
:did-mount (fn [state]
|
||||
(let [[_container-state _repo _config block] (:rum/args state)]
|
||||
(schedule-comment-thread-presence-check! state block))
|
||||
state)
|
||||
:did-update (fn [state]
|
||||
(let [[_container-state _repo _config block] (:rum/args state)]
|
||||
(schedule-comment-thread-presence-check! state block))
|
||||
state)}
|
||||
(mixins/event-mixin
|
||||
(fn [state]
|
||||
(let [*ref (::ref state)]
|
||||
@@ -3837,6 +3981,10 @@
|
||||
show-query? (rum/react *show-query?)
|
||||
*plugin-renderer-error? (get state ::plugin-renderer-error?)
|
||||
*use-plugin-renderer? (get state ::use-plugin-renderer?)
|
||||
*hydrated-comment-thread (get state ::hydrated-comment-thread)
|
||||
hydrated-comment-thread (rum/react *hydrated-comment-thread)
|
||||
*comment-thread-present? (get state ::comment-thread-present?)
|
||||
comment-thread-present? (rum/react *comment-thread-present?)
|
||||
plugin-renderer-error? (rum/react *plugin-renderer-error?)
|
||||
use-plugin-renderer? (rum/react *use-plugin-renderer?)
|
||||
switch-to-plugin-renderer! (fn []
|
||||
@@ -3896,7 +4044,13 @@
|
||||
children (ldb/get-children block)
|
||||
comments-area? (comments-model/comments-area? block)
|
||||
comment-thread (when-not comments-area?
|
||||
(first (comments-model/comment-threads-for-block block)))
|
||||
(or (ui-comment-thread-for-block block)
|
||||
(when (= uuid (:block-uuid hydrated-comment-thread))
|
||||
(:thread hydrated-comment-thread))))
|
||||
has-comment-thread? (or comment-thread
|
||||
(true? comment-thread-present?))
|
||||
inline-thread (state/sub :comments/inline-thread)
|
||||
show-inline-comments? (inline-comment-thread? inline-thread uuid comment-thread)
|
||||
protected-comment-block? (comments-model/protected-comment-block? block)
|
||||
page-icon (when (:page-title? config)
|
||||
(let [icon' (get block :logseq.property/icon)]
|
||||
@@ -3986,7 +4140,7 @@
|
||||
(when (ldb/recycled? block) " line-through opacity-70")
|
||||
(when order-list? " is-order-list")
|
||||
(when comments-area? " is-comments-area")
|
||||
(when comment-thread " has-comment-thread")
|
||||
(when has-comment-thread? " has-comment-thread")
|
||||
(when (string/blank? title) " is-blank")
|
||||
(when original-block " embed-block"))
|
||||
:haschild (str (boolean has-child?))
|
||||
@@ -4111,7 +4265,7 @@
|
||||
;; --- Original outline ---
|
||||
outline-view-cp)
|
||||
|
||||
(when (and comment-thread (not table?) (not property?))
|
||||
(when (and has-comment-thread? (not table?) (not property?))
|
||||
(shui/button
|
||||
{:variant :ghost
|
||||
:size :icon
|
||||
@@ -4121,9 +4275,25 @@
|
||||
:on-pointer-down util/stop
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(comments-handler/reveal-comments-area! comment-thread {:focus-editor? true}))}
|
||||
(open-comment-thread-for-block! state block comment-thread))}
|
||||
(shui/tabler-icon "message-circle" {:size 15})))])
|
||||
|
||||
(when show-inline-comments?
|
||||
[:div.ls-inline-comments
|
||||
(when-not (:page-title? config)
|
||||
{:style {:padding-left (if (util/mobile?) 12 45)}})
|
||||
(block-comments/comments-area-view
|
||||
(assoc config :container-id (comments-model/inline-comment-container-id (:container-id config)))
|
||||
comment-thread
|
||||
(ldb/get-children comment-thread)
|
||||
false
|
||||
*hide-block-refs?
|
||||
*show-query?
|
||||
{:block-content-or-editor block-content-or-editor
|
||||
:block-reactions block-reactions}
|
||||
{:focus-editor? true
|
||||
:inline? true})])
|
||||
|
||||
(when (and (not (:library? config))
|
||||
(or (:tag-dialog? config)
|
||||
(and
|
||||
|
||||
@@ -288,6 +288,21 @@
|
||||
color: var(--ls-secondary-text-color);
|
||||
}
|
||||
|
||||
.ls-comments-targets-toggle {
|
||||
margin-left: auto;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--ls-secondary-text-color);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.ls-comments-targets-toggle:hover {
|
||||
color: var(--ls-primary-text-color);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ls-comment-time {
|
||||
color: var(--ls-secondary-text-color);
|
||||
opacity: 0.72;
|
||||
@@ -299,6 +314,32 @@
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.ls-comments-targets {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 0 10px 8px;
|
||||
}
|
||||
|
||||
.ls-comments-target {
|
||||
overflow: hidden;
|
||||
padding: 4px 7px;
|
||||
border-left: 2px solid var(--ls-link-text-color);
|
||||
background: var(--ls-primary-background-color);
|
||||
color: var(--ls-secondary-text-color);
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ls-comments-target .page-reference {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.ls-inline-comments {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.ls-comment-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -308,6 +349,7 @@
|
||||
}
|
||||
|
||||
.ls-comment-avatar {
|
||||
@apply mt-1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -315,10 +357,7 @@
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: var(--lx-gray-05, var(--rx-gray-05));
|
||||
color: var(--ls-primary-text-color);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ls-comment-main {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns frontend.components.block.comments
|
||||
(:require [clojure.string :as string]
|
||||
[dommy.core :as dom]
|
||||
[frontend.components.avatar :as avatar]
|
||||
[frontend.components.block.comments-model :as comments-model]
|
||||
[frontend.components.icon :as icon-component]
|
||||
[frontend.config :as config]
|
||||
@@ -291,7 +292,7 @@
|
||||
(rum/defc comment-row-view
|
||||
[config comment-block *hide-block-refs? *show-query? {:keys [block-content-or-editor block-reactions]}]
|
||||
(let [[editing? set-editing!] (hooks/use-state false)
|
||||
{:keys [author avatar body created-at]} (comments-model/comment-row comment-block)
|
||||
{:keys [author avatar-src author-uuid body created-at]} (comments-model/comment-row comment-block)
|
||||
current-user-uuid (user-handler/user-uuid)
|
||||
show-author? (comments-model/comment-author-visible? current-user-uuid)
|
||||
comment-uuid (:block/uuid comment-block)
|
||||
@@ -303,7 +304,12 @@
|
||||
placeholder (t :block.comments/placeholder)]
|
||||
[:div.ls-comment-row
|
||||
(when show-author?
|
||||
[:div.ls-comment-avatar avatar])
|
||||
(avatar/user-avatar
|
||||
{:class "ls-comment-avatar"
|
||||
:title author
|
||||
:name author
|
||||
:uuid author-uuid
|
||||
:avatar-src avatar-src}))
|
||||
[:div.ls-comment-main
|
||||
[:div.ls-comment-meta
|
||||
(when show-author?
|
||||
@@ -371,13 +377,14 @@
|
||||
(shui/tabler-icon "trash" {:size 14})))])]))
|
||||
|
||||
(rum/defc add-comment-button
|
||||
[config comments-block]
|
||||
[config comments-block {:keys [focus-on-mount?]}]
|
||||
(let [placeholder (t :block.comments/placeholder)]
|
||||
[:div.ls-comment-add
|
||||
(comment-box
|
||||
{:config config
|
||||
:comments-block comments-block
|
||||
:placeholder placeholder
|
||||
:focus-on-mount? focus-on-mount?
|
||||
:on-submit (fn [content]
|
||||
(comments-handler/insert-comment! comments-block content))})]))
|
||||
|
||||
@@ -395,9 +402,25 @@
|
||||
(dom/add-class! el "is-comment-thread-hovered")
|
||||
(dom/remove-class! el "is-comment-thread-hovered")))))
|
||||
|
||||
(rum/defc comment-thread-targets-view
|
||||
[comments-area]
|
||||
(let [targets (comments-model/comment-thread-target-blocks comments-area)]
|
||||
(when (> (count targets) 1)
|
||||
[:div.ls-comments-targets
|
||||
(for [target targets]
|
||||
(let [uuid (:block/uuid target)
|
||||
reference (state/get-component :block/reference)]
|
||||
[:div.ls-comments-target
|
||||
{:key (str uuid)
|
||||
:data-block-id (str uuid)}
|
||||
(if reference
|
||||
(reference {} uuid)
|
||||
(string/trim (or (:block/title target) "")))]))])))
|
||||
|
||||
(rum/defc comments-area-view
|
||||
[config block children collapsed? *hide-block-refs? *show-query? renderers]
|
||||
[config block children collapsed? *hide-block-refs? *show-query? renderers {:keys [focus-editor? inline?]}]
|
||||
(let [*comments-list-ref (hooks/use-ref nil)
|
||||
[targets-open? set-targets-open!] (hooks/use-state false)
|
||||
render-token (comments-model/comments-render-token children)
|
||||
summary (comments-model/comments-summary children)
|
||||
count (count children)]
|
||||
@@ -411,11 +434,13 @@
|
||||
nil)
|
||||
[collapsed? render-token])
|
||||
[:div.ls-comments-area
|
||||
(when (comments-model/range-comments-area? block)
|
||||
{:on-mouse-enter #(set-comment-thread-targets-hover! block true)
|
||||
:on-mouse-leave #(set-comment-thread-targets-hover! block false)
|
||||
:on-focus #(set-comment-thread-targets-hover! block true)
|
||||
:on-blur #(set-comment-thread-targets-hover! block false)})
|
||||
(cond-> {}
|
||||
inline? (assoc :class "ls-comments-area-inline")
|
||||
(comments-model/range-comments-area? block)
|
||||
(assoc :on-mouse-enter #(set-comment-thread-targets-hover! block true)
|
||||
:on-mouse-leave #(set-comment-thread-targets-hover! block false)
|
||||
:on-focus #(set-comment-thread-targets-hover! block true)
|
||||
:on-blur #(set-comment-thread-targets-hover! block false)))
|
||||
(if collapsed?
|
||||
[:button.ls-comments-summary
|
||||
{:type "button"
|
||||
@@ -423,15 +448,21 @@
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(comments-handler/expand-comments-area! block))}
|
||||
(if summary
|
||||
(t :block.comments/collapsed-summary
|
||||
(:count summary)
|
||||
(:latest-author summary))
|
||||
(t :block.comments/count 0))]
|
||||
(comments-model/collapsed-comments-label summary)]
|
||||
[:<>
|
||||
[:div.ls-comments-header
|
||||
[:span.ls-comments-label (t :block.comments/label)]
|
||||
[:span.ls-comments-count count]]
|
||||
[:span.ls-comments-count count]
|
||||
(when (comments-model/comment-thread-targets-toggle-visible? block)
|
||||
[:button.ls-comments-targets-toggle
|
||||
{:type "button"
|
||||
:on-pointer-down util/stop
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(set-targets-open! (not targets-open?)))}
|
||||
(t :block.comments/on-those-blocks)])]
|
||||
(when (comments-model/show-comment-thread-targets? block targets-open?)
|
||||
(comment-thread-targets-view block))
|
||||
(when (seq children)
|
||||
[:div.ls-comments-list
|
||||
{:ref *comments-list-ref}
|
||||
@@ -439,4 +470,4 @@
|
||||
(rum/with-key
|
||||
(comment-row-view config comment-block *hide-block-refs? *show-query? renderers)
|
||||
(str (:block/uuid comment-block))))])
|
||||
(add-comment-button config block)])]))
|
||||
(add-comment-button config block {:focus-on-mount? focus-editor?})])]))
|
||||
|
||||
@@ -65,13 +65,59 @@
|
||||
(remove :logseq.property/deleted-at)
|
||||
vec))
|
||||
|
||||
(defn comment-thread-targets-toggle-visible?
|
||||
[comments-area]
|
||||
(> (count (comment-thread-target-blocks comments-area)) 1))
|
||||
|
||||
(defn show-comment-thread-targets?
|
||||
[comments-area targets-open?]
|
||||
(boolean
|
||||
(and targets-open?
|
||||
(comment-thread-targets-toggle-visible? comments-area))))
|
||||
|
||||
(defn comment-thread-click-action
|
||||
[comments-area-visible?]
|
||||
(if comments-area-visible?
|
||||
:focus-comments-area
|
||||
:show-inline-comments))
|
||||
|
||||
(defn next-inline-comment-thread
|
||||
[current-inline-thread target-block-uuid comments-area-uuid]
|
||||
(let [next-thread {:target-block-uuid (str target-block-uuid)
|
||||
:comments-area-uuid (str comments-area-uuid)}]
|
||||
(when-not (= current-inline-thread next-thread)
|
||||
next-thread)))
|
||||
|
||||
(defn inline-comment-container-id
|
||||
[container-id]
|
||||
(if (int? container-id)
|
||||
container-id
|
||||
:unknown-container))
|
||||
|
||||
(defn- child-comments-area?
|
||||
[block comments-area]
|
||||
(let [block-id (:db/id block)
|
||||
parent-id (some-> comments-area :block/parent :db/id)]
|
||||
(boolean
|
||||
(and block-id
|
||||
parent-id
|
||||
(= block-id parent-id)))))
|
||||
|
||||
(defn comment-threads-for-block
|
||||
[block]
|
||||
(->> (get block :logseq.property.comments/_blocks)
|
||||
(filter comments-area?)
|
||||
(remove #(child-comments-area? block %))
|
||||
(remove :logseq.property/deleted-at)
|
||||
vec))
|
||||
|
||||
(defn comment-thread-for-block
|
||||
([block]
|
||||
(first (comment-threads-for-block block)))
|
||||
([rendered-block fresh-block]
|
||||
(or (some-> fresh-block comment-thread-for-block)
|
||||
(comment-thread-for-block rendered-block))))
|
||||
|
||||
(defn- author-initials
|
||||
[author]
|
||||
(let [author (string/trim (str author))]
|
||||
@@ -90,6 +136,13 @@
|
||||
string/trim
|
||||
not-empty))
|
||||
|
||||
(defn- created-by-avatar-src
|
||||
[block]
|
||||
(some-> (:logseq.property/created-by-ref block)
|
||||
:logseq.property.user/avatar
|
||||
string/trim
|
||||
not-empty))
|
||||
|
||||
(defn- uuid-string
|
||||
[value]
|
||||
(cond
|
||||
@@ -106,10 +159,14 @@
|
||||
(defn comment-row
|
||||
[block]
|
||||
(let [author (created-by-title block)
|
||||
avatar-src (created-by-avatar-src block)
|
||||
author-uuid (created-by-uuid block)
|
||||
created-at (:block/created-at block)
|
||||
updated-at (:block/updated-at block)]
|
||||
{:author author
|
||||
:avatar (author-initials author)
|
||||
:avatar-src avatar-src
|
||||
:author-uuid author-uuid
|
||||
:body (string/trim (or (:block/title block) ""))
|
||||
:created-at created-at
|
||||
:updated-at updated-at
|
||||
@@ -135,6 +192,14 @@
|
||||
:latest-author (:author latest)
|
||||
:latest-created-at (:created-at latest)}))))
|
||||
|
||||
(defn collapsed-comments-label
|
||||
[summary]
|
||||
(if summary
|
||||
(t :block.comments/collapsed-summary
|
||||
(:count summary)
|
||||
(:latest-author summary))
|
||||
(t :block.comments/label)))
|
||||
|
||||
(defn- same-local-day?
|
||||
[^js a ^js b]
|
||||
(and (= (.getFullYear a) (.getFullYear b))
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
[dommy.core :as d]
|
||||
[electron.ipc :as ipc]
|
||||
[frontend.common.missionary :as c.m]
|
||||
[frontend.components.avatar :as avatar]
|
||||
[frontend.components.block :as component-block]
|
||||
[frontend.components.export :as export]
|
||||
[frontend.components.page-menu :as page-menu]
|
||||
@@ -36,7 +37,6 @@
|
||||
[logseq.db :as ldb]
|
||||
[logseq.shui.hooks :as hooks]
|
||||
[logseq.shui.ui :as shui]
|
||||
[logseq.shui.util :as shui-util]
|
||||
[missionary.core :as m]
|
||||
[promesa.core :as p]
|
||||
[reitit.frontend.easy :as rfe]
|
||||
@@ -103,17 +103,15 @@
|
||||
(when (seq online-users)
|
||||
(for [{user-email :user/email
|
||||
user-name :user/name
|
||||
user-uuid :user/uuid} online-users
|
||||
:let [color (shui-util/uuid-color user-uuid)]]
|
||||
user-uuid :user/uuid} online-users]
|
||||
(when user-name
|
||||
(shui/avatar
|
||||
(avatar/user-avatar
|
||||
{:class "w-5 h-5"
|
||||
:style {:app-region "no-drag"}
|
||||
:title user-email}
|
||||
(shui/avatar-fallback
|
||||
{:style {:background-color (str color "50")
|
||||
:font-size 11}}
|
||||
(some-> (subs user-name 0 2) (string/upper-case)))))))])))
|
||||
:title user-email
|
||||
:name user-name
|
||||
:uuid user-uuid
|
||||
:fallback-props {:style {:font-size 11}}}))))])))
|
||||
|
||||
(rum/defc left-menu-button < rum/reactive
|
||||
< {:key-fn #(identity "left-menu-toggle-button")}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns frontend.components.recycle
|
||||
"Recycle page UI"
|
||||
(:require [clojure.string :as string]
|
||||
[datascript.core :as d]
|
||||
(:require [datascript.core :as d]
|
||||
[frontend.components.avatar :as avatar]
|
||||
[frontend.components.block :as component-block]
|
||||
[frontend.context.i18n :as i18n :refer [t]]
|
||||
[frontend.db :as db]
|
||||
@@ -23,14 +23,6 @@
|
||||
(vector? value) (d/entity db value)
|
||||
:else nil))
|
||||
|
||||
(defn- user-initials
|
||||
[user]
|
||||
(let [name (or (:logseq.property.user/name user)
|
||||
(:block/title user)
|
||||
"U")
|
||||
name (string/trim name)]
|
||||
(subs name 0 (min 2 (count name)))))
|
||||
|
||||
(defn- deleted-roots
|
||||
[db]
|
||||
(->> (d/q '[:find [?e ...]
|
||||
@@ -67,12 +59,12 @@
|
||||
|
||||
(defn- deleted-by-avatar
|
||||
[user]
|
||||
(let [avatar-src (:logseq.property.user/avatar user)]
|
||||
(shui/avatar
|
||||
{:class "w-4 h-4"}
|
||||
(when (seq avatar-src)
|
||||
(shui/avatar-image {:src avatar-src}))
|
||||
(shui/avatar-fallback (user-initials user)))))
|
||||
(avatar/user-avatar
|
||||
{:class "w-4 h-4"
|
||||
:name (or (:logseq.property.user/name user)
|
||||
(:block/title user)
|
||||
"U")
|
||||
:avatar-src (:logseq.property.user/avatar user)}))
|
||||
|
||||
(defn- deleted-root-header
|
||||
[db root]
|
||||
|
||||
@@ -119,10 +119,9 @@
|
||||
dialog-config {:cancel-label (t :ui/cancel)
|
||||
:ok-label (t :ui/confirm)}]
|
||||
(-> (shui/dialog-confirm!
|
||||
[:p.font-medium.-my-4 (t :graph/delete-local-confirm-desc graph-name)
|
||||
[:span.my-2.flex.font-normal.opacity-75
|
||||
[:small (t :graph/delete-warning)]]]
|
||||
dialog-config)
|
||||
(assoc dialog-config
|
||||
:title (t :graph/delete-local-confirm-desc graph-name)
|
||||
:description (t :graph/delete-warning)))
|
||||
(p/then (fn []
|
||||
(repo-handler/remove-repo! repo))))))
|
||||
|
||||
@@ -198,10 +197,9 @@
|
||||
:on-click (fn []
|
||||
(let [prompt-str (t :graph/delete-server-confirm-desc graph-name)]
|
||||
(-> (shui/dialog-confirm!
|
||||
[:p.font-medium.-my-4 prompt-str
|
||||
[:span.my-2.flex.font-normal.opacity-75
|
||||
[:small (t :graph/delete-warning)]]]
|
||||
dialog-config)
|
||||
(assoc dialog-config
|
||||
:title prompt-str
|
||||
:description (t :graph/delete-warning)))
|
||||
(p/then
|
||||
(fn []
|
||||
(state/set-state! :rtc/loading-graphs? true)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
(:require [clojure.string :as string]
|
||||
[frontend.components.block.comments-model :as comments-model]
|
||||
[frontend.db :as db]
|
||||
[frontend.db.async :as db-async]
|
||||
[frontend.handler.block :as block-handler]
|
||||
[frontend.handler.db-based.property :as db-property-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
@@ -52,6 +53,90 @@
|
||||
[block]
|
||||
[:block/uuid (:block/uuid block)])
|
||||
|
||||
(def ^:private comment-thread-pull-selector
|
||||
'[:db/id
|
||||
:block/uuid
|
||||
:block/title
|
||||
:block/order
|
||||
:block/created-at
|
||||
:block/updated-at
|
||||
:logseq.property/deleted-at
|
||||
{:block/tags [:db/id :db/ident]}
|
||||
{:block/parent [:db/id :block/uuid]}
|
||||
{:logseq.property.comments/blocks [:db/id :block/uuid :block/title :logseq.property/deleted-at]}])
|
||||
|
||||
(defn <get-comment-threads-for-block
|
||||
[block-uuid]
|
||||
(when-let [repo (and block-uuid (state/get-current-repo))]
|
||||
(p/let [threads (db-async/<q repo
|
||||
{:transact-db? true}
|
||||
'[:find [(pull ?comments-area ?selector) ...]
|
||||
:in $ ?block-uuid ?selector
|
||||
:where
|
||||
[?block :block/uuid ?block-uuid]
|
||||
[?comments-area :logseq.property.comments/blocks ?block]
|
||||
[?comments-area :block/tags :logseq.class/Comments]
|
||||
[(missing? $ ?comments-area :logseq.property/deleted-at)]]
|
||||
block-uuid
|
||||
comment-thread-pull-selector)
|
||||
_ (p/all
|
||||
(mapv (fn [thread]
|
||||
(db-async/<get-block repo
|
||||
(:block/uuid thread)
|
||||
{:children? true
|
||||
:include-collapsed-children? true
|
||||
:skip-refresh? true}))
|
||||
threads))]
|
||||
(->> threads
|
||||
(keep (fn [thread]
|
||||
(db/entity [:block/uuid (:block/uuid thread)])))
|
||||
vec))))
|
||||
|
||||
(defn <get-comment-thread-block-uuids
|
||||
([block-uuids]
|
||||
(<get-comment-thread-block-uuids (state/get-current-repo) block-uuids))
|
||||
([repo block-uuids]
|
||||
(let [block-uuids (->> block-uuids
|
||||
(keep (fn [block-uuid]
|
||||
(cond
|
||||
(uuid? block-uuid)
|
||||
block-uuid
|
||||
|
||||
(and (string? block-uuid) (util/uuid-string? block-uuid))
|
||||
(uuid block-uuid)
|
||||
|
||||
:else
|
||||
nil)))
|
||||
vec)]
|
||||
(when (and repo (seq block-uuids))
|
||||
(p/let [result (db-async/<q repo
|
||||
{:transact-db? false}
|
||||
'[:find [?block-uuid ...]
|
||||
:in $ [?block-uuid ...]
|
||||
:where
|
||||
[?block :block/uuid ?block-uuid]
|
||||
[?comments-area :logseq.property.comments/blocks ?block]
|
||||
[?comments-area :block/tags :logseq.class/Comments]
|
||||
[(missing? $ ?comments-area :logseq.property/deleted-at)]]
|
||||
block-uuids)]
|
||||
(mapv str result))))))
|
||||
|
||||
(defn- single-comment-targets
|
||||
[block]
|
||||
#{(block-lookup-ref block)})
|
||||
|
||||
(defn- comments-area-entity
|
||||
[comments-area]
|
||||
(when-let [uuid (:block/uuid comments-area)]
|
||||
(db/entity [:block/uuid uuid])))
|
||||
|
||||
(defn- ensure-single-comment-target-property!
|
||||
[comments-area block]
|
||||
(when (and comments-area block (not (seq (get comments-area comments-model/comments-blocks-property))))
|
||||
(db-property-handler/set-block-property! (:db/id comments-area)
|
||||
comments-model/comments-blocks-property
|
||||
(single-comment-targets block))))
|
||||
|
||||
(defn- comments-area-title
|
||||
[block]
|
||||
(if (ldb/page? block)
|
||||
@@ -68,12 +153,14 @@
|
||||
[block-id]
|
||||
(when-let [block (db/entity [:block/uuid block-id])]
|
||||
(if-let [comments-area (comments-area-child block)]
|
||||
(p/resolved comments-area)
|
||||
(p/let [_ (ensure-single-comment-target-property! comments-area block)]
|
||||
comments-area)
|
||||
(editor-handler/api-insert-new-block!
|
||||
(comments-area-title block)
|
||||
(merge {:block-uuid block-id
|
||||
:edit-block? false
|
||||
:other-attrs {:block/tags #{comments-model/comments-tag-ident}}}
|
||||
:other-attrs {:block/tags #{comments-model/comments-tag-ident}
|
||||
comments-model/comments-blocks-property (single-comment-targets block)}}
|
||||
(comments-area-insert-position block))))))
|
||||
|
||||
(defn- same-comment-targets?
|
||||
@@ -131,7 +218,7 @@
|
||||
([comments-area]
|
||||
(reveal-comments-area! comments-area nil))
|
||||
([comments-area {:keys [focus-editor?]}]
|
||||
(when-let [uuid (:block/uuid comments-area)]
|
||||
(when-let [uuid (:block/uuid (comments-area-entity comments-area))]
|
||||
(p/do!
|
||||
(editor-handler/expand-block! uuid)
|
||||
(js/requestAnimationFrame
|
||||
@@ -147,7 +234,7 @@
|
||||
|
||||
(defn expand-comments-area!
|
||||
[comments-area]
|
||||
(when-let [uuid (:block/uuid comments-area)]
|
||||
(when-let [uuid (:block/uuid (comments-area-entity comments-area))]
|
||||
(editor-handler/expand-block! uuid)))
|
||||
|
||||
(defn- selected-block-entities
|
||||
|
||||
@@ -1911,7 +1911,8 @@
|
||||
(state/clear-editor-action!)
|
||||
|
||||
;; Open "Search page or New page" auto-complete
|
||||
(and (= last-input-char commands/hashtag)
|
||||
(and (not (:comment-editor? config))
|
||||
(= last-input-char commands/hashtag)
|
||||
;; Only trigger at beginning of a line, before whitespace or after a reference
|
||||
(or (re-find #"(?m)^#" (str (.-value input)))
|
||||
(start-of-new-word? input pos)
|
||||
@@ -3024,13 +3025,14 @@
|
||||
:else
|
||||
(str "Key" (string/upper-case c)))
|
||||
false]
|
||||
[key-code
|
||||
[key-code
|
||||
(gobj/get e "key")
|
||||
(if (mobile-util/native-android?)
|
||||
(gobj/get e "key")
|
||||
(if (mobile-util/native-android?)
|
||||
(gobj/get e "key")
|
||||
(gobj/getValueByKeys e "event_" "code"))
|
||||
(gobj/getValueByKeys e "event_" "code"))
|
||||
;; #3440
|
||||
(util/goog-event-is-composing? e true)])]
|
||||
(util/goog-event-is-composing? e true)])
|
||||
comment-editor? (:comment-editor? (last (state/get-editor-args)))]
|
||||
(cond
|
||||
(= value "``````") ; turn this block into a code block
|
||||
(do
|
||||
@@ -3039,7 +3041,7 @@
|
||||
:type :code
|
||||
:update-current-block? true}]))
|
||||
|
||||
(= value ">") ; turn this block into a quote block
|
||||
(and (not comment-editor?) (= value ">")) ; turn this block into a quote block
|
||||
(do
|
||||
(state/set-edit-content! (.-id input) "")
|
||||
(state/pub-event! [:editor/upsert-type-block {:block (assoc (state/get-edit-block) :block/title "")
|
||||
|
||||
@@ -80,6 +80,18 @@
|
||||
(map (fn [comment-id]
|
||||
[:db/add comment-id :block/tags :logseq.class/Comment]))))
|
||||
|
||||
(defn- add-single-block-comment-targets
|
||||
[db]
|
||||
(->> (d/q '[:find ?comments-area-id ?parent-id
|
||||
:where
|
||||
[?comments-area-id :block/tags :logseq.class/Comments]
|
||||
[?comments-area-id :block/parent ?parent-id]]
|
||||
db)
|
||||
(keep (fn [[comments-area-id parent-id]]
|
||||
(let [comments-area (d/entity db comments-area-id)]
|
||||
(when-not (seq (:logseq.property.comments/blocks comments-area))
|
||||
[:db/add comments-area-id :logseq.property.comments/blocks parent-id]))))))
|
||||
|
||||
(def schema-version->updates
|
||||
"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
|
||||
@@ -113,7 +125,8 @@
|
||||
["65.27" {:classes [:logseq.class/Comments]
|
||||
:properties [:logseq.property.comments/blocks]}]
|
||||
["65.28" {:classes [:logseq.class/Comment]
|
||||
:fix tag-comment-blocks}]])
|
||||
:fix tag-comment-blocks}]
|
||||
["65.29" {:fix add-single-block-comment-targets}]])
|
||||
|
||||
(let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
|
||||
schema-version->updates)))]
|
||||
|
||||
@@ -113,9 +113,9 @@
|
||||
:block.comments/add-comment "Add comment"
|
||||
:block.comments/add-comment-command-desc "Add a comment to this block."
|
||||
:block.comments/collapsed-summary (fn [n author] (str n (if (= 1 n) " comment" " comments") " · latest from " author))
|
||||
:block.comments/count (fn [n] (str n (if (= 1 n) " comment" " comments")))
|
||||
:block.comments/date-at-time "{1} at {2}"
|
||||
:block.comments/label "Comments"
|
||||
:block.comments/on-those-blocks "On those blocks"
|
||||
:block.comments/placeholder "Reply..."
|
||||
:block.comments/yesterday-at "Yesterday at {1}"
|
||||
|
||||
|
||||
@@ -110,9 +110,9 @@
|
||||
:block.comments/add-comment "添加评论"
|
||||
:block.comments/add-comment-command-desc "为此块添加评论。"
|
||||
:block.comments/collapsed-summary "{1} 条评论 · 最新来自 {2}"
|
||||
:block.comments/count "{1} 条评论"
|
||||
:block.comments/date-at-time "{1} {2}"
|
||||
:block.comments/label "评论"
|
||||
:block.comments/on-those-blocks "在这些块上"
|
||||
:block.comments/placeholder "回复..."
|
||||
:block.comments/yesterday-at "昨天 {1}"
|
||||
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
(ns frontend.components.block.comments-model-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[frontend.components.block.comments-model :as comments-model]
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[goog.object :as gobj]))
|
||||
|
||||
(deftest comments-area-detection
|
||||
(testing "detects blocks tagged with the built-in Comments tag"
|
||||
(is (true? (comments-model/comments-area?
|
||||
{:block/tags [{:db/ident :logseq.class/Comments}]}))))
|
||||
|
||||
(testing "does not treat ordinary blocks as comment areas"
|
||||
(is (false? (comments-model/comments-area?
|
||||
{:block/title "Comments"})))
|
||||
(is (false? (comments-model/comments-area?
|
||||
{:block/tags [{:db/ident :logseq.class/Task}]})))))
|
||||
|
||||
(deftest comment-move-guards
|
||||
(let [comments-area {:block/tags [{:db/ident :logseq.class/Comments}]}
|
||||
comment-block {:block/title "comment"
|
||||
:block/parent comments-area}
|
||||
tagged-comment-block {:block/title "tagged comment"
|
||||
:block/tags [{:db/ident :logseq.class/Comment}]}
|
||||
ordinary-block {:block/title "ordinary"}
|
||||
ordinary-target {:block/title "target"}]
|
||||
(testing "comment area and its children are protected comment blocks"
|
||||
(is (true? (comments-model/protected-comment-block? comments-area)))
|
||||
(is (true? (comments-model/protected-comment-block? comment-block)))
|
||||
(is (true? (comments-model/protected-comment-block? tagged-comment-block)))
|
||||
(is (false? (comments-model/protected-comment-block? ordinary-block))))
|
||||
(testing "comment blocks cannot be moved"
|
||||
(is (false? (comments-model/move-allowed? [comments-area] ordinary-target)))
|
||||
(is (false? (comments-model/move-allowed? [comment-block] ordinary-target)))
|
||||
(is (false? (comments-model/move-allowed? [tagged-comment-block] ordinary-target))))
|
||||
(testing "ordinary blocks cannot be moved to comment blocks"
|
||||
(is (false? (comments-model/move-allowed? [ordinary-block] comments-area)))
|
||||
(is (false? (comments-model/move-allowed? [ordinary-block] comment-block)))
|
||||
(is (false? (comments-model/move-allowed? [ordinary-block] tagged-comment-block)))
|
||||
(is (false? (comments-model/move-allowed? [ordinary-block] comments-area {:sibling? true})))
|
||||
(is (false? (comments-model/move-allowed? [ordinary-block] tagged-comment-block {:sibling? true}))))
|
||||
(testing "ordinary moves outside comments stay allowed"
|
||||
(is (true? (comments-model/move-allowed? [ordinary-block] ordinary-target))))))
|
||||
|
||||
(deftest comment-target-blocks
|
||||
(let [comments-area {:block/title "Comments"
|
||||
:block/tags [{:db/ident :logseq.class/Comments}]}
|
||||
comment-block {:block/title "comment"
|
||||
:block/parent comments-area}
|
||||
first-block {:block/title "first"}
|
||||
second-block {:block/title "second"}]
|
||||
(is (= [first-block second-block]
|
||||
(comments-model/comment-target-blocks
|
||||
[first-block comments-area comment-block first-block second-block]))
|
||||
"Selected-block comment actions should target normal blocks only once")))
|
||||
|
||||
(deftest range-comment-threads
|
||||
(let [target-a {:block/title "a"}
|
||||
target-b {:block/title "b"}
|
||||
deleted-target {:block/title "deleted"
|
||||
:logseq.property/deleted-at 1}
|
||||
comments-area {:block/title "Comments"
|
||||
:block/tags [{:db/ident :logseq.class/Comments}]
|
||||
comments-model/comments-blocks-property [target-a deleted-target target-b]}
|
||||
deleted-comments-area (assoc comments-area :logseq.property/deleted-at 1)
|
||||
ordinary-block {:block/title "ordinary"
|
||||
:logseq.property.comments/_blocks [comments-area deleted-comments-area]}]
|
||||
(testing "range comment areas are comments blocks that point at target blocks"
|
||||
(is (true? (comments-model/range-comments-area? comments-area)))
|
||||
(is (false? (comments-model/range-comments-area?
|
||||
{:block/tags [{:db/ident :logseq.class/Comments}]}))))
|
||||
(testing "deleted targets do not participate in the rendered target set"
|
||||
(is (= [target-a target-b]
|
||||
(comments-model/comment-thread-target-blocks comments-area))))
|
||||
(testing "blocks expose live comment threads through the reverse property"
|
||||
(is (= [comments-area]
|
||||
(comments-model/comment-threads-for-block ordinary-block))))))
|
||||
|
||||
(deftest comment-row-derivation
|
||||
(testing "uses the created-by ref as the comment author"
|
||||
(is (= {:author "tienson"
|
||||
:avatar "T"
|
||||
:body "tienson: push PR"
|
||||
:created-at 1710000000000
|
||||
:updated-at 1710000000000
|
||||
:edited? false}
|
||||
(comments-model/comment-row
|
||||
{:block/title "tienson: push PR"
|
||||
:logseq.property/created-by-ref {:block/title "tienson"}
|
||||
:block/created-at 1710000000000
|
||||
:block/updated-at 1710000000000}))))
|
||||
|
||||
(testing "does not invent an author when created-by ref is absent"
|
||||
(is (= {:author nil
|
||||
:avatar ""
|
||||
:body "review again"
|
||||
:created-at nil
|
||||
:updated-at nil
|
||||
:edited? false}
|
||||
(comments-model/comment-row {:block/title "review again"}))))
|
||||
|
||||
(testing "marks comments edited only when updated later than created"
|
||||
(is (true? (:edited? (comments-model/comment-row
|
||||
{:block/title "me: updated"
|
||||
:logseq.property/created-by-ref {:block/title "me"}
|
||||
:block/created-at 10
|
||||
:block/updated-at 20}))))))
|
||||
|
||||
(deftest comment-author-visibility
|
||||
(let [visible? (resolve 'frontend.components.block.comments-model/comment-author-visible?)]
|
||||
(is (fn? visible?))
|
||||
|
||||
(testing "hides comment avatar and username when there is no logged-in user"
|
||||
(when (fn? visible?)
|
||||
(is (false? (visible? nil)))
|
||||
(is (false? (visible? "")))))
|
||||
|
||||
(testing "shows comment avatar and username when a user is logged in"
|
||||
(when (fn? visible?)
|
||||
(is (true? (visible? #uuid "6a073572-fefe-44c5-8b43-267ccc715077")))
|
||||
(is (true? (visible? "6a073572-fefe-44c5-8b43-267ccc715077")))))))
|
||||
|
||||
(deftest comments-summary
|
||||
(testing "summarizes count and latest author by timestamp"
|
||||
(is (= {:count 2
|
||||
:latest-author "tienson"
|
||||
:latest-created-at 30}
|
||||
(comments-model/comments-summary
|
||||
[{:block/title "review again"
|
||||
:logseq.property/created-by-ref {:block/title "zhiyuan"}
|
||||
:block/created-at 10}
|
||||
{:block/title "push PR"
|
||||
:logseq.property/created-by-ref {:block/title "tienson"}
|
||||
:block/created-at 30}]))))
|
||||
|
||||
(testing "returns no summary for empty comment areas"
|
||||
(is (nil? (comments-model/comments-summary [])))))
|
||||
|
||||
(deftest comment-count-labels
|
||||
(testing "uses singular English labels for one comment"
|
||||
(is (= "1 comment" (t :block.comments/count 1)))
|
||||
(is (= "1 comment · latest from tienson"
|
||||
(t :block.comments/collapsed-summary 1 "tienson"))))
|
||||
|
||||
(testing "uses plural English labels for other counts"
|
||||
(is (= "0 comments" (t :block.comments/count 0)))
|
||||
(is (= "2 comments" (t :block.comments/count 2)))))
|
||||
|
||||
(deftest comment-time-label
|
||||
(let [time-label (resolve 'frontend.components.block.comments-model/comment-time-label)
|
||||
now (js/Date. 2026 4 18 9 0)
|
||||
today (.getTime (js/Date. 2026 4 18 17 5))
|
||||
yesterday (.getTime (js/Date. 2026 4 17 17 5))
|
||||
older (.getTime (js/Date. 2026 3 5 17 5))]
|
||||
(is (fn? time-label))
|
||||
(when (fn? time-label)
|
||||
(is (= "5:05 PM" (time-label today now))
|
||||
"Today's comments should display only the time")
|
||||
(is (= "Yesterday at 5:05 PM" (time-label yesterday now))
|
||||
"Yesterday's comments should include the relative day")
|
||||
(is (= "Apr 5, 2026 at 5:05 PM" (time-label older now))
|
||||
"Older comments should include the date and time")
|
||||
(is (nil? (time-label nil now))))))
|
||||
|
||||
(deftest comment-submit-content
|
||||
(let [submit-content (resolve 'frontend.components.block.comments-model/submittable-comment-content)]
|
||||
(testing "keeps comment drafts local until an explicit submit asks for content"
|
||||
(is (fn? submit-content)))
|
||||
|
||||
(testing "returns trimmed content for submitted create and edit drafts"
|
||||
(when (fn? submit-content)
|
||||
(is (= "ship comment box" (submit-content " ship comment box ")))))
|
||||
|
||||
(testing "does not submit blank drafts"
|
||||
(when (fn? submit-content)
|
||||
(is (nil? (submit-content " \n\t ")))
|
||||
(is (nil? (submit-content nil)))))))
|
||||
|
||||
(deftest comment-ownership
|
||||
(let [owned? (resolve 'frontend.components.block.comments-model/comment-owned-by?)
|
||||
user-id #uuid "6a073572-fefe-44c5-8b43-267ccc715077"
|
||||
other-id #uuid "fd94c4c7-bfb8-49d5-bbb1-46617e4f2154"]
|
||||
(testing "detects comments created by the current user"
|
||||
(is (fn? owned?))
|
||||
(when (fn? owned?)
|
||||
(is (true? (owned? {:logseq.property/created-by-ref {:block/uuid user-id}}
|
||||
(str user-id))))
|
||||
(is (true? (owned? {:logseq.property/created-by-ref {:block/uuid (str user-id)}}
|
||||
user-id)))))
|
||||
|
||||
(testing "does not allow ownership without matching created-by ref"
|
||||
(when (fn? owned?)
|
||||
(is (false? (owned? {:logseq.property/created-by-ref {:block/uuid other-id}}
|
||||
(str user-id))))
|
||||
(is (false? (owned? {} (str user-id))))
|
||||
(is (true? (owned? {} nil)))
|
||||
(is (false? (owned? {:logseq.property/created-by-ref {:block/uuid user-id}}
|
||||
nil)))))))
|
||||
|
||||
(deftest comment-actions
|
||||
(let [actions (resolve 'frontend.components.block.comments-model/comment-actions)
|
||||
user-id #uuid "6a073572-fefe-44c5-8b43-267ccc715077"
|
||||
other-id #uuid "fd94c4c7-bfb8-49d5-bbb1-46617e4f2154"]
|
||||
(testing "exposes reaction for comments created by other users"
|
||||
(is (fn? actions))
|
||||
(when (fn? actions)
|
||||
(is (= [:reaction]
|
||||
(actions {:logseq.property/created-by-ref {:block/uuid other-id}}
|
||||
(str user-id))))))
|
||||
|
||||
(testing "exposes edit and delete when logged out and created-by ref is absent"
|
||||
(when (fn? actions)
|
||||
(is (= [:reaction :edit :delete]
|
||||
(actions {} nil)))))
|
||||
|
||||
(testing "exposes edit and delete only for comments created by current user"
|
||||
(when (fn? actions)
|
||||
(is (= [:reaction :edit :delete]
|
||||
(actions {:logseq.property/created-by-ref {:block/uuid user-id}}
|
||||
(str user-id))))))))
|
||||
|
||||
(deftest comment-edit-cursor-position
|
||||
(let [cursor-position (resolve 'frontend.components.block.comments-model/comment-edit-cursor-position)]
|
||||
(testing "places the edit cursor at the end of the current comment"
|
||||
(is (fn? cursor-position))
|
||||
(when (fn? cursor-position)
|
||||
(is (= 11 (cursor-position "hello world")))
|
||||
(is (= 3 (cursor-position "a\nb")))
|
||||
(is (= 0 (cursor-position nil)))))))
|
||||
|
||||
(deftest comment-submit-shortcut
|
||||
(let [shortcut? (resolve 'frontend.components.block.comments-model/comment-submit-shortcut?)]
|
||||
(testing "accepts enter without requiring a modifier"
|
||||
(is (fn? shortcut?))
|
||||
(when (fn? shortcut?)
|
||||
(is (true? (shortcut? #js {:key "Enter"})))
|
||||
(is (true? (shortcut? #js {:key "Enter" :metaKey true})))))
|
||||
|
||||
(testing "keeps shift-enter and non-enter keys available for editing"
|
||||
(when (fn? shortcut?)
|
||||
(is (false? (shortcut? #js {:key "Enter" :metaKey true :shiftKey true})))
|
||||
(is (false? (shortcut? #js {:key "a" :metaKey true})))))
|
||||
|
||||
(testing "does not submit while editor commands or autocomplete are active"
|
||||
(when (fn? shortcut?)
|
||||
(is (false? (shortcut? #js {:key "Enter"} :commands)))
|
||||
(is (false? (shortcut? #js {:key "Enter"} :block-search)))
|
||||
(is (false? (shortcut? #js {:key "Enter"} :page-search)))))))
|
||||
|
||||
(deftest comments-render-token
|
||||
(let [render-token (resolve 'frontend.components.block.comments-model/comments-render-token)]
|
||||
(testing "tracks comment identity and content changes for scroll effects"
|
||||
(is (fn? render-token))
|
||||
(when (fn? render-token)
|
||||
(is (= [[#uuid "6a073572-fefe-44c5-8b43-267ccc715077" "first" 10]
|
||||
[#uuid "fd94c4c7-bfb8-49d5-bbb1-46617e4f2154" "second" 20]]
|
||||
(render-token
|
||||
[{:block/uuid #uuid "6a073572-fefe-44c5-8b43-267ccc715077"
|
||||
:block/title "first"
|
||||
:block/updated-at 10}
|
||||
{:block/uuid #uuid "fd94c4c7-bfb8-49d5-bbb1-46617e4f2154"
|
||||
:block/title "second"
|
||||
:block/updated-at 20}])))))))
|
||||
|
||||
(deftest comment-draft-block
|
||||
(let [draft-block (resolve 'frontend.components.block.comments-model/comment-draft-block)
|
||||
draft-id #uuid "a477a8fe-10fb-443b-9d59-45ee476931e8"
|
||||
comments-id #uuid "6a073572-fefe-44c5-8b43-267ccc715077"
|
||||
page-id #uuid "fd94c4c7-bfb8-49d5-bbb1-46617e4f2154"
|
||||
comments-block {:block/uuid comments-id
|
||||
:block/page {:block/uuid page-id}}]
|
||||
(testing "builds a temporary block shaped for the normal editor"
|
||||
(is (fn? draft-block))
|
||||
(when (fn? draft-block)
|
||||
(is (= {:block/uuid draft-id
|
||||
:block/title "draft"
|
||||
:block/format :markdown
|
||||
:block/page {:block/uuid page-id}
|
||||
:block/parent comments-block}
|
||||
(draft-block comments-block draft-id "draft")))))))
|
||||
|
||||
(deftest comment-draft-storage
|
||||
(let [draft-key (resolve 'frontend.components.block.comments-model/comment-draft-storage-key)
|
||||
load-draft (resolve 'frontend.components.block.comments-model/saved-comment-draft)
|
||||
save-draft! (resolve 'frontend.components.block.comments-model/save-comment-draft!)
|
||||
clear-draft! (resolve 'frontend.components.block.comments-model/clear-comment-draft!)
|
||||
comments-id #uuid "6a073572-fefe-44c5-8b43-267ccc715077"
|
||||
comments-block {:block/uuid comments-id}]
|
||||
(is (fn? draft-key))
|
||||
(is (fn? load-draft))
|
||||
(is (fn? save-draft!))
|
||||
(is (fn? clear-draft!))
|
||||
(when (every? fn? [draft-key load-draft save-draft! clear-draft!])
|
||||
(is (= (str "comments-" comments-id "-draft")
|
||||
(draft-key comments-block)))
|
||||
(let [items (atom {})
|
||||
old-storage (gobj/get js/globalThis "localStorage")
|
||||
storage (js-obj
|
||||
"getItem" (fn [key] (get @items key))
|
||||
"setItem" (fn [key value] (swap! items assoc key value))
|
||||
"removeItem" (fn [key] (swap! items dissoc key)))]
|
||||
(try
|
||||
(gobj/set js/globalThis "localStorage" storage)
|
||||
(save-draft! comments-block " draft body\n")
|
||||
(is (= " draft body\n" (load-draft comments-block))
|
||||
"Non-blank drafts should be restored exactly")
|
||||
(save-draft! comments-block " ")
|
||||
(is (nil? (load-draft comments-block))
|
||||
"Blank drafts should not leave stale local storage entries")
|
||||
(save-draft! comments-block "another draft")
|
||||
(clear-draft! comments-block)
|
||||
(is (nil? (load-draft comments-block))
|
||||
"Submitted comments should clear the stored draft")
|
||||
(finally
|
||||
(if old-storage
|
||||
(gobj/set js/globalThis "localStorage" old-storage)
|
||||
(gobj/remove js/globalThis "localStorage"))))))))
|
||||
|
||||
(deftest comments-block-current-page
|
||||
(let [current-page? (resolve 'frontend.components.block.comments-model/comments-block-current-page?)
|
||||
comments-id #uuid "6a073572-fefe-44c5-8b43-267ccc715077"]
|
||||
(is (fn? current-page?))
|
||||
(when (fn? current-page?)
|
||||
(is (true? (current-page? {:block/uuid comments-id}
|
||||
(str comments-id))))
|
||||
(is (false? (current-page? {:block/uuid comments-id}
|
||||
"fd94c4c7-bfb8-49d5-bbb1-46617e4f2154")))
|
||||
(is (false? (current-page? {:block/uuid comments-id} nil)))
|
||||
(is (false? (current-page? {} (str comments-id)))))))
|
||||
98
src/test/frontend/handler/comments_test.cljs
Normal file
98
src/test/frontend/handler/comments_test.cljs
Normal file
@@ -0,0 +1,98 @@
|
||||
(ns frontend.handler.comments-test
|
||||
(:require [cljs.test :refer [async deftest is testing]]
|
||||
[frontend.components.block.comments-model :as comments-model]
|
||||
[frontend.db :as db]
|
||||
[frontend.handler.comments :as comments-handler]
|
||||
[frontend.handler.db-based.property :as db-property-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(deftest ensure-comments-area-adds-target-property-for-single-block-comments
|
||||
(async done
|
||||
(let [target-uuid #uuid "11111111-1111-1111-1111-111111111111"
|
||||
comments-uuid #uuid "22222222-2222-2222-2222-222222222222"
|
||||
target {:db/id 1
|
||||
:block/uuid target-uuid
|
||||
:block/title "target"}
|
||||
comments-area {:db/id 2
|
||||
:block/uuid comments-uuid
|
||||
:block/title "Comments"
|
||||
:block/tags [{:db/ident comments-model/comments-tag-ident}]}
|
||||
inserted (atom nil)
|
||||
property-updates (atom [])]
|
||||
(-> (p/with-redefs [db/entity (fn [lookup]
|
||||
(case lookup
|
||||
[:block/uuid target-uuid] target
|
||||
nil))
|
||||
editor-handler/api-insert-new-block!
|
||||
(fn [title opts]
|
||||
(reset! inserted {:title title :opts opts})
|
||||
(p/resolved comments-area))
|
||||
db-property-handler/set-block-property!
|
||||
(fn [block-id property value]
|
||||
(swap! property-updates conj [block-id property value])
|
||||
(p/resolved nil))
|
||||
editor-handler/expand-block! (fn [_] (p/resolved nil))]
|
||||
(comments-handler/ensure-comments-area! target-uuid))
|
||||
(p/then
|
||||
(fn [result]
|
||||
(is (= comments-area result))
|
||||
(is (contains? (get-in @inserted [:opts :other-attrs])
|
||||
comments-model/comments-blocks-property))
|
||||
(is (= #{[:block/uuid target-uuid]}
|
||||
(get-in @inserted [:opts :other-attrs comments-model/comments-blocks-property])))
|
||||
(is (empty? @property-updates)
|
||||
"New single-block comment areas should be created with the target property")
|
||||
(done)))))))
|
||||
|
||||
(deftest ensure-comments-area-backfills-existing-single-block-comments
|
||||
(async done
|
||||
(let [target-uuid #uuid "11111111-1111-1111-1111-111111111111"
|
||||
comments-uuid #uuid "22222222-2222-2222-2222-222222222222"
|
||||
comments-area {:db/id 2
|
||||
:block/uuid comments-uuid
|
||||
:block/title "Comments"
|
||||
:block/tags [{:db/ident comments-model/comments-tag-ident}]}
|
||||
target {:db/id 1
|
||||
:block/uuid target-uuid
|
||||
:block/title "target"
|
||||
:block/_parent [comments-area]}
|
||||
property-updates (atom [])]
|
||||
(-> (p/with-redefs [db/entity (fn [lookup]
|
||||
(case lookup
|
||||
[:block/uuid target-uuid] target
|
||||
nil))
|
||||
db/sort-by-order identity
|
||||
db-property-handler/set-block-property!
|
||||
(fn [block-id property value]
|
||||
(swap! property-updates conj [block-id property value])
|
||||
(p/resolved nil))
|
||||
editor-handler/api-insert-new-block!
|
||||
(fn [& _]
|
||||
(throw (js/Error. "should not insert a duplicate comments area")))]
|
||||
(comments-handler/ensure-comments-area! target-uuid))
|
||||
(p/then
|
||||
(fn [result]
|
||||
(is (= comments-area result))
|
||||
(is (= [[(:db/id comments-area)
|
||||
comments-model/comments-blocks-property
|
||||
#{[:block/uuid target-uuid]}]]
|
||||
@property-updates))
|
||||
(done)))))))
|
||||
|
||||
(deftest deleted-comment-thread-actions-are-no-ops
|
||||
(testing "expand ignores stale comment thread data when the comments block is gone"
|
||||
(let [expanded (atom [])]
|
||||
(with-redefs [db/entity (constantly nil)
|
||||
editor-handler/expand-block! (fn [block-id] (swap! expanded conj block-id))]
|
||||
(comments-handler/expand-comments-area!
|
||||
{:block/uuid #uuid "22222222-2222-2222-2222-222222222222"})
|
||||
(is (empty? @expanded)))))
|
||||
|
||||
(testing "reveal ignores stale comment thread data when the comments block is gone"
|
||||
(let [expanded (atom [])]
|
||||
(with-redefs [db/entity (constantly nil)
|
||||
editor-handler/expand-block! (fn [block-id] (swap! expanded conj block-id))]
|
||||
(comments-handler/reveal-comments-area!
|
||||
{:block/uuid #uuid "22222222-2222-2222-2222-222222222222"})
|
||||
(is (empty? @expanded))))))
|
||||
@@ -421,9 +421,10 @@
|
||||
:opts {:block-uuid block-uuid
|
||||
:end? true
|
||||
:edit-block? false
|
||||
:other-attrs {:block/tags #{comments-model/comments-tag-ident}}}}]
|
||||
:other-attrs {:block/tags #{comments-model/comments-tag-ident}
|
||||
comments-model/comments-blocks-property #{[:block/uuid block-uuid]}}}}]
|
||||
@inserts)
|
||||
"Single-block comments area should be inserted as a child without range targets")
|
||||
"Single-block comments area should be inserted as a child with the target property")
|
||||
(is (= [created-comments-area-uuid] @expanded)
|
||||
"The single-block comments area should be expanded inline"))))))
|
||||
|
||||
|
||||
@@ -439,9 +439,29 @@
|
||||
:cursor-pos (dec (count "`String#gsub and String#`"))})
|
||||
(is (= nil (state/get-editor-action))
|
||||
"No page search within backticks"))
|
||||
|
||||
(testing "Comment editors do not open tag autocompletion"
|
||||
(handle-last-input-handler {:value "#"
|
||||
:cursor-pos 1
|
||||
:editor-config {:comment-editor? true}})
|
||||
(is (= nil (state/get-editor-action))
|
||||
"No tag search in comment editors"))
|
||||
;; Reset state
|
||||
(state/set-editor-action! nil))
|
||||
|
||||
(deftest comment-editor-quote-trigger-does-not-convert-draft-block
|
||||
(let [input #js {:id "edit-block-test"
|
||||
:value ">"}
|
||||
events (atom [])]
|
||||
(with-redefs [cursor/pos (constantly 1)
|
||||
state/get-editor-args (constantly [nil nil {:comment-editor? true}])
|
||||
state/set-edit-content! (fn [& _])
|
||||
state/pub-event! (fn [event] (swap! events conj event))
|
||||
editor/default-case-for-keyup-handler (fn [& _])]
|
||||
((editor/keyup-handler nil input) #js {:key ">"} nil)
|
||||
(is (empty? @events)
|
||||
"Comment editor > should stay plain text instead of converting the draft to a quote block"))))
|
||||
|
||||
(deftest comment-editor-collapse-expand-shortcuts-do-not-touch-draft-blocks
|
||||
(let [draft-uuid #uuid "6a073572-fefe-44c5-8b43-267ccc715077"
|
||||
expanded (atom [])
|
||||
|
||||
@@ -144,3 +144,45 @@
|
||||
(is (= #{:logseq.class/Comments}
|
||||
(set (map :db/ident (:block/tags (d/entity @conn [:block/uuid comments-area-uuid]))))))
|
||||
(is (empty? (:block/tags (d/entity @conn [:block/uuid ordinary-child-uuid]))))))
|
||||
|
||||
(deftest migrate-65-29-adds-single-block-comment-targets
|
||||
(let [conn (d/create-conn db-schema/schema)
|
||||
target-uuid #uuid "11111111-1111-1111-1111-111111111111"
|
||||
comments-area-uuid #uuid "22222222-2222-2222-2222-222222222222"
|
||||
range-comments-uuid #uuid "33333333-3333-3333-3333-333333333333"
|
||||
range-target-uuid #uuid "44444444-4444-4444-4444-444444444444"]
|
||||
(d/transact! conn
|
||||
[{:db/ident :logseq.kv/schema-version
|
||||
:kv/value {:major 65 :minor 28}}
|
||||
{:db/ident :logseq.class/Comments
|
||||
:block/title "Comments"}
|
||||
{:block/uuid target-uuid
|
||||
:block/title "target"}
|
||||
{:block/uuid comments-area-uuid
|
||||
:block/title "Comments"
|
||||
:block/parent [:block/uuid target-uuid]
|
||||
:block/tags #{:logseq.class/Comments}}
|
||||
{:block/uuid range-target-uuid
|
||||
:block/title "range target"}
|
||||
{:block/uuid range-comments-uuid
|
||||
:block/title "Comments"
|
||||
:block/tags #{:logseq.class/Comments}}])
|
||||
(d/transact! conn
|
||||
[[:db/add
|
||||
(:db/id (d/entity @conn [:block/uuid range-comments-uuid]))
|
||||
:logseq.property.comments/blocks
|
||||
(:db/id (d/entity @conn [:block/uuid range-target-uuid]))]])
|
||||
|
||||
(db-migrate/migrate conn :target-version {:major 65 :minor 29})
|
||||
|
||||
(is (= {:major 65 :minor 29}
|
||||
(:kv/value (d/entity @conn :logseq.kv/schema-version))))
|
||||
(is (= #{(:db/id (d/entity @conn [:block/uuid target-uuid]))}
|
||||
(set (map :db/id
|
||||
(:logseq.property.comments/blocks
|
||||
(d/entity @conn [:block/uuid comments-area-uuid]))))))
|
||||
(is (= #{(:db/id (d/entity @conn [:block/uuid range-target-uuid]))}
|
||||
(set (map :db/id
|
||||
(:logseq.property.comments/blocks
|
||||
(d/entity @conn [:block/uuid range-comments-uuid])))))
|
||||
"Existing range comment targets should be preserved")))
|
||||
|
||||
Reference in New Issue
Block a user