diff --git a/src/cljs/athens/components.cljs b/src/cljs/athens/components.cljs index 12ed519c33..e9c8d22027 100644 --- a/src/cljs/athens/components.cljs +++ b/src/cljs/athens/components.cljs @@ -87,7 +87,7 @@ renderer-k (block-type-dispatcher/block-type->protocol-k block-type ff) renderer (block-type-dispatcher/block-type->protocol renderer-k {})] ^{:key renderer-k} - [types/transclusion-view renderer blocks/block-el block-uid {} :embed]) + [:f> types/transclusion-view renderer blocks/block-el block-uid {} :embed]) ;; roam actually hides the brackets around [[embed]] [:span "{{" content "}}"]))) diff --git a/src/cljs/athens/types/default/view.cljs b/src/cljs/athens/types/default/view.cljs index fb53edb98f..3159255da9 100644 --- a/src/cljs/athens/types/default/view.cljs +++ b/src/cljs/athens/types/default/view.cljs @@ -176,7 +176,7 @@ :pr "1.3rem" "textarea" {:background "transparent"}}}} [:<> - [block-el + [:f> block-el (util/recursively-modify-block-for-embed block embed-id) {:linked-ref false} {:block-embed? true}] diff --git a/src/cljs/athens/views/blocks/core.cljs b/src/cljs/athens/views/blocks/core.cljs index 7c99fe96ca..3a9ebf4be1 100644 --- a/src/cljs/athens/views/blocks/core.cljs +++ b/src/cljs/athens/views/blocks/core.cljs @@ -7,7 +7,9 @@ ["/components/Block/Toggle" :refer [Toggle]] ["/components/Icons/Icons" :refer [BlockEmbedIcon TextIcon ChatBubbleIcon ArchiveIcon]] ["/components/References/InlineReferences" :refer [ReferenceGroup ReferenceBlock]] - ["@chakra-ui/react" :refer [Box Breadcrumb BreadcrumbItem MenuGroup BreadcrumbLink Button Divider HStack MenuDivider MenuItem VStack]] + ["@chakra-ui/react" :refer [MenuDivider Breadcrumb BreadcrumbItem BreadcrumbLink MenuGroup Button Divider HStack MenuItem VStack]] + ["react" :as react] + ["react-intersection-observer" :refer [useInView]] [athens.common-db :as common-db] [athens.common.logging :as log] [athens.db :as db] @@ -259,7 +261,7 @@ ;; Display the single child block only when focusing. ;; This is the default behaviour for a ref without children, for brevity. [:div.block-embed {:fontSize "0.7em"} - [block-el + [:f> block-el (util/recursively-modify-block-for-embed block @inline-ref-embed-id) linked-ref-data {:block-embed? true}]] @@ -268,7 +270,7 @@ ;; Otherwise display children of the parent directly. (for [child (:block/children block)] [:<> {:key (:db/id child)} - [block-el + [:f> block-el (util/recursively-modify-block-for-embed child @inline-ref-embed-id) linked-ref-data {:block-embed? true}]])))])))) @@ -301,9 +303,9 @@ (defn block-el "Two checks dec to make sure block is open or not: children exist and :block/open bool" ([block] - [block-el block {:linked-ref false} {}]) + [:f> block-el block {:linked-ref false} {}]) ([block linked-ref-data] - [block-el block linked-ref-data {}]) + [:f> block-el block linked-ref-data {}]) ([block linked-ref-data _opts] (let [block-uid (:block/uid block) {:keys [initial-open @@ -325,229 +327,236 @@ show-comments? (rf/subscribe [:comment/show-comments?]) show-textarea? (rf/subscribe [:comment/show-editor? block-uid]) inline-refs-open? (rf/subscribe [::inline-refs.subs/open? block-uid]) - enable-properties? (rf/subscribe [:feature-flags/enabled? :properties])] - (rf/dispatch [::linked-ref.events/set-open! block-uid (or (false? linked-ref) initial-open)]) - (rf/dispatch [::inline-refs.events/set-open! block-uid false]) - (r/create-class - {:component-will-unmount - (fn will-unmount-block - [_] - (rf/dispatch [::linked-ref.events/cleanup! block-uid]) - (rf/dispatch [::inline-refs.events/cleanup! block-uid])) - :reagent-render - (fn block-core-render - [block linked-ref-data opts] - (let [block-o (reactive/get-reactive-block-document ident) - {:block/keys [uid - open - children - key - properties - _refs]} (merge block block-o) - block-type (reactive/reactive-get-entity-type [:block/uid block-uid]) - children-uids (set (map :block/uid children)) - children? (seq children-uids) - presence? (seq @present-user) - comments-enabled? (:comments @feature-flags) - reactions-enabled? (:reactions @feature-flags) - notifications-enabled? (:notifications @feature-flags) - uid-sanitized-block (s/transform - (util/specter-recursive-path #(contains? % :block/uid)) - (fn [{:block/keys [original-uid uid] :as block}] - (assoc block :block/uid (or original-uid uid))) - block) - user-id (or (:username @current-user) - ;; We use empty string for when there is no user information, like in PKM. - "") - reactions (and reactions-enabled? - (block-reaction/props->reactions properties)) - menu (r/as-element - [:> MenuGroup - (when (< (count @selected-items) 2) - [:> MenuItem {:children "Open block" - :icon (r/as-element [:> TextIcon]) - :onClick (fn [e] - (let [shift? (.-shiftKey e)] - (rf/dispatch [:reporting/navigation {:source :block-bullet - :target :block - :pane (if shift? - :right-pane - :main-pane)}]) - (router/navigate-uid uid e)))}]) - [:> MenuItem {:children (if (> (count @selected-items) 1) - "Copy selected block refs" - "Copy block ref") - :icon (r/as-element [:> BlockEmbedIcon]) - :onClick #(ctx-menu/handle-copy-refs nil uid)}] - [:> MenuItem {:children "Copy unformatted text" - :icon (r/as-element [:> TextIcon]) - :onClick #(ctx-menu/handle-copy-unformatted uid)}] - - - (when comments-enabled? - [:> MenuItem {:children "Add comment" - :onClick #(ctx-menu/handle-click-comment % uid) - :icon (r/as-element [:> ChatBubbleIcon])}]) - (when reactions-enabled? - [:<> - [:> MenuDivider] - [block-reaction/reactions-menu-list uid user-id]]) - - (when (and notifications-enabled? (actions/is-block-inbox? properties)) - [:<> - [:> Divider] - [:> MenuItem {:children "Archive all notifications" - :icon (r/as-element [:> ArchiveIcon]) - :onClick #(actions/archive-all-notifications uid)}] - [:> MenuItem {:children "Unarchive all notifications" - :icon (r/as-element [:> ArchiveIcon]) - :onClick #(actions/unarchive-all-notifications uid)}]]) - - (when (and notifications-enabled? (actions/is-block-notification? properties)) - [:> MenuItem {:children "Archive" - :icon (r/as-element [:> ArchiveIcon]) - :onClick #(rf/dispatch (actions/update-state-prop uid "athens/notification/is-archived" "true"))}])]) - ff @(rf/subscribe [:feature-flags]) - renderer-k (block-type-dispatcher/block-type->protocol-k block-type ff) - renderer (block-type-dispatcher/block-type->protocol renderer-k {:linked-ref-data linked-ref-data})] - (log/debug "block open render: block-o:" (pr-str (:block/open block-o)) - "block:" (pr-str (:block/open block)) - "merge:" (pr-str (:block/open (merge block-o block)))) - - [:> Container {:isHidden (actions/archived-notification? properties) - :isDragging (and @dragging? (not @selected?)) - :isSelected @selected? - :hasChildren (seq children) - :isOpen open - :isLinkedRef (and (false? initial-open) (= uid linked-ref-uid)) - :hasPresence presence? - :uid uid - ;; need to know children for selection resolution - :childrenUids children-uids - ;; show-edit? allows us to render the editing elements (like the textarea) - ;; even when not editing this block. When true, clicking the block content will pass - ;; the clicks down to the underlying textarea. The textarea is expensive to render, - ;; so we avoid rendering it when it's not needed. - :onMouseEnter show-edit-fn - :onMouseLeave hide-edit-fn - :onDragOver (fn [e] - (block-drag-over e block)) - :onDragLeave (fn [e] - (block-drag-leave e block)) - :onDrop (fn [e] - (block-drop e block)) - :menu menu - :style (merge {} (time-controls/block-styles block-o))} - - (when (= @drag-target :before) [drop-area-indicator/drop-area-indicator {:placement "above"}]) - - [:<> - [:div.block-body - (when (and children? - (or (seq children) - (seq properties))) - [:> Toggle {:isOpen (if (or (and (true? linked-ref) @linked-ref-open?) - (and (false? linked-ref) open)) - true - false) - :onClick (fn [e] - (.. e stopPropagation) - (if (true? linked-ref) - (rf/dispatch [::linked-ref.events/toggle-open! uid]) - (block-open-toggle! uid (not open))))}]) - - (when key - [:> PropertyName {:name (:node/title key) - :onClick (fn [e] - (let [shift? (.-shiftKey e)] - (rf/dispatch [:reporting/navigation {:source :block-property - :target :page - :pane (if shift? - :right-pane - :main-pane)}]) - (router/navigate-page (:node/title key) e))) - :on-drag-start (fn [e] - (block-bullet/bullet-drag-start e uid)) - :on-drag-stop (fn [e] - (block-bullet/bullet-drag-end e uid))}]) - - - [:> Anchor {:isClosedWithChildren (when (and (seq children) - (or (and (true? linked-ref) (not @linked-ref-open?)) - (and (false? linked-ref) (not open)))) - "closed-with-children") - :uidSanitizedBlock uid-sanitized-block - :shouldShowDebugDetails (util/re-frame-10x-open?) - :menu menu - :onDoubleClick (fn [e] - (let [shift? (.-shiftKey e)] - (rf/dispatch [:reporting/navigation {:source :block-bullet - :target :block - :pane (if shift? - :right-pane - :main-pane)}]) - (router/navigate-uid uid e))) - :on-drag-start (fn [e] - (block-bullet/bullet-drag-start e uid)) - :on-drag-end (fn [e] - (block-bullet/bullet-drag-end e uid)) - :unreadNotification (actions/unread-notification? properties)}] - - ;; `BlockTypeProtocol` dispatch placement - [:> Box {:gridArea "content"} - ^{:key renderer-k} - [types/outline-view renderer block {:show-edit? show-edit?}]] - - (when reactions - [:> Reactions {:reactions (clj->js reactions) - :currentUser user-id - :onToggleReaction (partial block-reaction/toggle-reaction [:block/uid uid])}]) - - ;; Show comments when the toggle is on - (when (and @show-comments? - (or @show-textarea? - (comments/get-comment-thread-uid @db/dsdb uid))) - (cond - @show-textarea? [inline-comments/inline-comments (comments/get-comments-in-thread @db/dsdb (comments/get-comment-thread-uid @db/dsdb uid)) uid false] - :else [inline-comments/inline-comments (comments/get-comments-in-thread @db/dsdb (comments/get-comment-thread-uid @db/dsdb uid)) uid true])) - - [presence/inline-presence-el uid] - - (when (and (> (count _refs) 0) (not= :block-embed? opts)) - [block-refs-count-el - (count _refs) - (fn [e] - (if (.. e -shiftKey) - (rf/dispatch [:right-sidebar/open-item [:block/uid uid]]) - (rf/dispatch [::inline-refs.events/toggle-open! uid]))) - @inline-refs-open?])] - - ;; Inline refs - (when (and (> (count _refs) 0) - (not= :block-embed? opts) - @inline-refs-open?) - [inline-linked-refs-el block-el uid]) - - ;; Properties - (when (and @enable-properties? - (or (and (true? linked-ref) @linked-ref-open?) - (and (false? linked-ref) open))) - (for [prop (common-db/sort-block-properties properties)] - ^{:key (:db/id prop)} - [block-el prop - (assoc linked-ref-data :initial-open (contains? parent-uids (:block/uid prop))) - opts])) - - ;; Children - (when (and (seq children) - (or (and (true? linked-ref) @linked-ref-open?) - (and (false? linked-ref) open))) - (for [child children - :let [child-uid (:block/uid child)]] - ^{:key (:db/id child)} - [block-el child - (assoc linked-ref-data :initial-open (contains? parent-uids child-uid)) - opts]))] - - (when (= @drag-target :first) [drop-area-indicator/drop-area-indicator {:placement "below" :child? true}]) - (when (= @drag-target :after) [drop-area-indicator/drop-area-indicator {:placement "below"}])]))})))) + enable-properties? (rf/subscribe [:feature-flags/enabled? :properties]) + on-block-mount (fn [] + (rf/dispatch [::linked-ref.events/set-open! block-uid (or (false? linked-ref) initial-open)]) + (rf/dispatch [::inline-refs.events/set-open! block-uid false])) + on-unmount-block (fn [] + (rf/dispatch [::linked-ref.events/cleanup! block-uid]) + (rf/dispatch [::inline-refs.events/cleanup! block-uid]))] + + (fn [opts] + [block linked-ref-data opts] + (let [block-o (reactive/get-reactive-block-document ident) + {:block/keys [uid + open + children + key + properties + _refs]} (merge block block-o) + block-type (reactive/reactive-get-entity-type [:block/uid block-uid]) + children-uids (set (map :block/uid children)) + children? (seq children-uids) + presence? (seq @present-user) + comments-enabled? (:comments @feature-flags) + reactions-enabled? (:reactions @feature-flags) + notifications-enabled? (:notifications @feature-flags) + uid-sanitized-block (s/transform + (util/specter-recursive-path #(contains? % :block/uid)) + (fn [{:block/keys [original-uid uid] :as block}] + (assoc block :block/uid (or original-uid uid))) + block) + user-id (or (:username @current-user) + ;; We use empty string for when there is no user information, like in PKM. + "") + reactions (and reactions-enabled? + (block-reaction/props->reactions properties)) + menu (r/as-element + [:> MenuGroup + (when (< (count @selected-items) 2) + [:> MenuItem {:children "Open block" + :icon (r/as-element [:> TextIcon]) + :onClick (fn [e] + (let [shift? (.-shiftKey e)] + (rf/dispatch [:reporting/navigation {:source :block-bullet + :target :block + :pane (if shift? + :right-pane + :main-pane)}]) + (router/navigate-uid uid e)))}]) + [:> MenuItem {:children (if (> (count @selected-items) 1) + "Copy selected block refs" + "Copy block ref") + :icon (r/as-element [:> BlockEmbedIcon]) + :onClick #(ctx-menu/handle-copy-refs nil uid)}] + [:> MenuItem {:children "Copy unformatted text" + :icon (r/as-element [:> TextIcon]) + :onClick #(ctx-menu/handle-copy-unformatted uid)}] + (when comments-enabled? + [:> MenuItem {:children "Add comment" + :onClick #(ctx-menu/handle-click-comment % uid) + :icon (r/as-element [:> ChatBubbleIcon])}]) + (when reactions-enabled? + [:<> + [:> MenuDivider] + [block-reaction/reactions-menu-list uid user-id]]) + + (when (and notifications-enabled? (actions/is-block-inbox? properties)) + [:<> + [:> Divider] + [:> MenuItem {:children "Archive all notifications" + :icon (r/as-element [:> ArchiveIcon]) + :onClick #(actions/archive-all-notifications uid)}] + [:> MenuItem {:children "Unarchive all notifications" + :icon (r/as-element [:> ArchiveIcon]) + :onClick #(actions/unarchive-all-notifications uid)}]]) + + (when (and notifications-enabled? (actions/is-block-notification? properties)) + [:> MenuItem {:children "Archive" + :icon (r/as-element [:> ArchiveIcon]) + :onClick #(rf/dispatch (actions/update-state-prop uid "athens/notification/is-archived" "true"))}])]) + ff @(rf/subscribe [:feature-flags]) + renderer-k (block-type-dispatcher/block-type->protocol-k block-type ff) + renderer (block-type-dispatcher/block-type->protocol renderer-k {:linked-ref-data linked-ref-data}) + [ref in-view?] (useInView {:delay 50}) + _ (react/useEffect (fn [] + #(on-block-mount) + on-unmount-block) + #js [])] + (log/debug "block open render: block-o:" (pr-str (:block/open block-o)) + "block:" (pr-str (:block/open block)) + "merge:" (pr-str (:block/open (merge block-o block)))) + + [:> Container {:isHidden (actions/archived-notification? properties) + :isDragging (and @dragging? (not @selected?)) + :isSelected @selected? + :hasChildren (seq children) + :isOpen open + :isLinkedRef (and (false? initial-open) (= uid linked-ref-uid)) + :hasPresence presence? + :uid uid + ;; need to know children for selection resolution + :childrenUids children-uids + ;; show-edit? allows us to render the editing elements (like the textarea) + ;; even when not editing this block. When true, clicking the block content will pass + ;; the clicks down to the underlying textarea. The textarea is expensive to render, + ;; so we avoid rendering it when it's not needed. + :onMouseEnter show-edit-fn + :onMouseLeave hide-edit-fn + :onDragOver (fn [e] + (block-drag-over e block)) + :onDragLeave (fn [e] + (block-drag-leave e block)) + :onDrop (fn [e] + (block-drop e block)) + :menu menu + :style (merge {} (time-controls/block-styles block-o))} + + (when (= @drag-target :before) [drop-area-indicator/drop-area-indicator {:placement "above"}]) + + [:<> + [:div.block-body {:ref ref} + (when (and children? + (or (seq children) + (seq properties))) + (when in-view? + [:> Toggle {:isOpen (if (or (and (true? linked-ref) @linked-ref-open?) + (and (false? linked-ref) open)) + true + false) + :onClick (fn [e] + (.. e stopPropagation) + (if (true? linked-ref) + (rf/dispatch [::linked-ref.events/toggle-open! uid]) + (block-open-toggle! uid (not open))))}])) + + (when key + [:> PropertyName {:name (:node/title key) + :onClick (fn [e] + (let [shift? (.-shiftKey e)] + (rf/dispatch [:reporting/navigation {:source :block-property + :target :page + :pane (if shift? + :right-pane + :main-pane)}]) + (router/navigate-page (:node/title key) e))) + :on-drag-start (fn [e] + (block-bullet/bullet-drag-start e uid)) + :on-drag-stop (fn [e] + (block-bullet/bullet-drag-end e uid))}]) + + + [:> Anchor {:isClosedWithChildren (when (and (seq children) + (or (and (true? linked-ref) (not @linked-ref-open?)) + (and (false? linked-ref) (not open)))) + "closed-with-children") + :uidSanitizedBlock uid-sanitized-block + :shouldShowDebugDetails (util/re-frame-10x-open?) + :menu menu + :onDoubleClick (fn [e] + (let [shift? (.-shiftKey e)] + (rf/dispatch [:reporting/navigation {:source :block-bullet + :target :block + :pane (if shift? + :right-pane + :main-pane)}]) + (router/navigate-uid uid e))) + :on-drag-start (fn [e] + (block-bullet/bullet-drag-start e uid)) + :on-drag-end (fn [e] + (block-bullet/bullet-drag-end e uid)) + :unreadNotification (actions/unread-notification? properties)}] + + ;; `BlockTypeProtocol` dispatch placement + ^{:key renderer-k} + [types/outline-view renderer block {:show-edit? show-edit?}] + + (when (and in-view? reactions-enabled? reactions) + [:> Reactions {:reactions (clj->js reactions) + :currentUser user-id + :onToggleReaction (partial block-reaction/toggle-reaction [:block/uid uid])}]) + + ;; Show comments when the toggle is on + (when (and @show-comments? + open + (or @show-textarea? + (comments/get-comment-thread-uid @db/dsdb uid))) + (cond + @show-textarea? [inline-comments/inline-comments (comments/get-comments-in-thread @db/dsdb (comments/get-comment-thread-uid @db/dsdb uid)) uid false] + :else [inline-comments/inline-comments (comments/get-comments-in-thread @db/dsdb (comments/get-comment-thread-uid @db/dsdb uid)) uid true])) + + (when in-view? + [presence/inline-presence-el uid]) + + (when (and in-view? + (> (count _refs) 0) + (not= :block-embed? opts)) + [block-refs-count-el + (count _refs) + (fn [e] + (if (.. e -shiftKey) + (rf/dispatch [:right-sidebar/open-item uid]) + (rf/dispatch [::inline-refs.events/toggle-open! uid]))) + @inline-refs-open?])] + + ;; Inline refs + (when (and in-view? + (> (count _refs) 0) + (not= :block-embed? opts) + @inline-refs-open?) + [inline-linked-refs-el block-el uid]) + + ;; Properties + (when (and in-view? + @enable-properties? + (or (and (true? linked-ref) @linked-ref-open?) + (and (false? linked-ref) open))) + (for [prop (common-db/sort-block-properties properties)] + ^{:key (:db/id prop)} + [:f> block-el prop + (assoc linked-ref-data :initial-open (contains? parent-uids (:block/uid prop))) + opts])) + + ;; Children + (when (and (seq children) + (or (and (true? linked-ref) @linked-ref-open?) + (and (false? linked-ref) open))) + (for [child children + :let [child-uid (:block/uid child)]] + ^{:key (:db/id child)} + [:f> block-el child + (assoc linked-ref-data :initial-open (contains? parent-uids child-uid)) + opts]))] + + (when (= @drag-target :first) [drop-area-indicator/drop-area-indicator {:placement "below" :child? true}]) + (when (= @drag-target :after) [drop-area-indicator/drop-area-indicator {:placement "below"}])]))))) diff --git a/src/cljs/athens/views/pages/block_page.cljs b/src/cljs/athens/views/pages/block_page.cljs index ceb86cd204..fe988e6b58 100644 --- a/src/cljs/athens/views/pages/block_page.cljs +++ b/src/cljs/athens/views/pages/block_page.cljs @@ -154,13 +154,13 @@ [:> PageBody (for [prop (common-db/sort-block-properties properties)] ^{:key (:db/id prop)} - [blocks/block-el prop])]) + [:f> blocks/block-el prop])]) ;; Children [:> PageBody (for [child children] (let [{:keys [db/id]} child] - ^{:key id} [blocks/block-el child]))] + ^{:key id} [:f> blocks/block-el child]))] ;; Refs [:> PageFooter diff --git a/src/cljs/athens/views/pages/node_page.cljs b/src/cljs/athens/views/pages/node_page.cljs index 6f21d6d75c..293166ca90 100644 --- a/src/cljs/athens/views/pages/node_page.cljs +++ b/src/cljs/athens/views/pages/node_page.cljs @@ -309,7 +309,7 @@ (swap! state assoc :block new-B :parents new-P))} [parse-and-render (common-db/breadcrumb-string @db/dsdb uid) uid]]]))] [:> Box {:class "block-embed"} - [blocks/block-el + [:f> blocks/block-el (recursively-modify-block-for-embed block embed-id) linked-ref-data {:block-embed? true}]]])))) @@ -517,14 +517,14 @@ (seq properties)) (for [prop (common-db/sort-block-properties properties)] ^{:key (:db/id prop)} - [blocks/block-el prop]))] + [:f> blocks/block-el prop]))] ;; Children [:div (for [{:block/keys [uid] :as child} children] ^{:key uid} [perf-mon/hoc-perfmon {:span-name "block-el"} - [blocks/block-el child]])]] + [:f> blocks/block-el child]])]] ;; References [:> PageFooter diff --git a/src/js/components/Block/Autocomplete.tsx b/src/js/components/Block/Autocomplete.tsx index 8d70470a03..08f2eb62fa 100644 --- a/src/js/components/Block/Autocomplete.tsx +++ b/src/js/components/Block/Autocomplete.tsx @@ -98,10 +98,6 @@ export const Autocomplete = ({ isOpen, onClose, event, children }) => { } }, [isOpen]); - if (!isOpen) { - return false; - } - return ( An error occurred while rendering this block.; // Don't open the context menu on these elements const CONTAINER_CONTEXT_MENU_FILTERED_TAGS = ["A", "BUTTON", "INPUT", "TEXTAREA", "LABEL", "VIDEO", "EMBED", "IFRAME", "IMG"]; @@ -102,6 +102,9 @@ const _Container = React.forwardRef(({ children, isDragging, isHidden, isSelecte minHeight: '2em', position: "relative", }, + "> .block-body > .block-content": { + gridArea: "content", + }, "&:hover > .block-toggle, &:focus-within > .block-toggle": { opacity: "1" }, "button.block-edit-toggle": { position: "absolute", diff --git a/src/js/theme/theme.js b/src/js/theme/theme.js index 9afbbf10e7..b05be530a9 100644 --- a/src/js/theme/theme.js +++ b/src/js/theme/theme.js @@ -262,6 +262,14 @@ const components = { color: "var(--toast-color)", } }) + }, + subtle: { + container: { + borderRadius: "md" + }, + title: { + fontWeight: "normal" + }, } } },