diff --git a/src/cljs/athens/db.cljs b/src/cljs/athens/db.cljs index 7a758521fa..477748f04b 100644 --- a/src/cljs/athens/db.cljs +++ b/src/cljs/athens/db.cljs @@ -164,7 +164,7 @@ :fs/watcher nil :presence {} :connection-status :disconnected - :comment/show-inline-comments true}) + :comment/show-comments? true}) (defn init-app-db diff --git a/src/cljs/athens/self_hosted/presence/subs.cljs b/src/cljs/athens/self_hosted/presence/subs.cljs index e48ed5e014..851a115453 100644 --- a/src/cljs/athens/self_hosted/presence/subs.cljs +++ b/src/cljs/athens/self_hosted/presence/subs.cljs @@ -40,6 +40,13 @@ second))) +(rf/reg-sub + :presence/current-username + :<- [:presence/current-user] + (fn [current-user _] + (:username current-user))) + + (defn on-page-uid? [page-uid [_username user]] (= page-uid (:page/uid user))) diff --git a/src/cljs/athens/views/app_toolbar.cljs b/src/cljs/athens/views/app_toolbar.cljs index 1b9f94ded8..66a1cb0c34 100644 --- a/src/cljs/athens/views/app_toolbar.cljs +++ b/src/cljs/athens/views/app_toolbar.cljs @@ -21,7 +21,7 @@ right-open? (rf/subscribe [:right-sidebar/open]) help-open? (rf/subscribe [:help/open?]) athena-open? (rf/subscribe [:athena/open]) - inline-comments (rf/subscribe [:comment/show-inline-comments?]) + show-comments? (rf/subscribe [:comment/show-comments?]) route-name (rf/subscribe [:current-route/name]) theme-dark (rf/subscribe [:theme/dark]) selected-db (rf/subscribe [:db-picker/selected-db]) @@ -109,5 +109,5 @@ {:notificationPopover (r/as-element [notifications-popover]) :isNotificationsPopoverOpen @notificationsPopoverOpen?}) (when (comments/enabled?) - {:isShowInlineComments @inline-comments - :onClickInlineComments #(rf/dispatch [:comment/toggle-inline-comments])}))])) + {:isShowComments @show-comments? + :onClickComments #(rf/dispatch [:comment/toggle-comments])}))])) diff --git a/src/cljs/athens/views/blocks/context_menu.cljs b/src/cljs/athens/views/blocks/context_menu.cljs index 373450d5d4..06ecb6b2ac 100644 --- a/src/cljs/athens/views/blocks/context_menu.cljs +++ b/src/cljs/athens/views/blocks/context_menu.cljs @@ -41,7 +41,7 @@ (defn handle-click-comment [e uid] - (rf/dispatch [:comment/show-comment-textarea uid]) + (rf/dispatch [:comment/show-editor uid]) (.. e preventDefault)) diff --git a/src/cljs/athens/views/blocks/editor.cljs b/src/cljs/athens/views/blocks/editor.cljs index 3696e4408c..494fe88cb6 100644 --- a/src/cljs/athens/views/blocks/editor.cljs +++ b/src/cljs/athens/views/blocks/editor.cljs @@ -160,9 +160,9 @@ linked-ref-open? (rf/subscribe [::linked-ref.subs/open? uid]) inline-refs-open? (rf/subscribe [::inline-refs.subs/open? uid]) feature-flags (rf/subscribe [:feature-flags]) - show-inline-comments (rf/subscribe [:comment/show-inline-comments?]) - show-textarea (rf/subscribe [:comment/show-comment-textarea? uid]) - enable-properties? (rf/subscribe [:feature-flags/enabled? :properties])] + enable-properties? (rf/subscribe [:feature-flags/enabled? :properties]) + show-comments? (rf/subscribe [:comment/show-comments?]) + show-textarea (rf/subscribe [:comment/show-editor? uid])] (fn editor-component-render [_block-el _block-o _children? _block _linked-ref-data _uid-sanitized-block _state-hooks _opts] (let [{:block/keys [;; uid @@ -244,7 +244,7 @@ :onToggleReaction (partial toggle-reaction [:block/uid uid])}]) ;; Show comments when the toggle is on - (when (and @show-inline-comments + (when (and @show-comments? open (or @show-textarea (comments/get-comment-thread-uid @db/dsdb uid))) diff --git a/src/cljs/athens/views/comments/core.cljs b/src/cljs/athens/views/comments/core.cljs index ce1bb385bb..49ee168a21 100644 --- a/src/cljs/athens/views/comments/core.cljs +++ b/src/cljs/athens/views/comments/core.cljs @@ -17,44 +17,43 @@ (rf/reg-sub - :comment/show-comment-textarea? + :comment/show-editor? (fn [db [_ uid]] - (= uid (:comment/show-comment-textarea db)))) + (= uid (:comment/show-editor db)))) (rf/reg-event-fx - :comment/show-comment-textarea + :comment/show-editor (fn [{:keys [db]} [_ uid]] - {:db (assoc db :comment/show-comment-textarea uid)})) + {:db (assoc db :comment/show-editor uid)})) (rf/reg-event-fx - :comment/hide-comment-textarea + :comment/hide-editor (fn [{:keys [db]} [_]] - {:db (assoc db :comment/show-comment-textarea nil)})) + {:db (assoc db :comment/show-editor nil)})) (rf/reg-sub - :comment/show-inline-comments? + :comment/show-comments? (fn [db [_]] - (= true (:comment/show-inline-comments db)))) + (= true (:comment/show-comments? db)))) -(rf/reg-event-fx - :comment/toggle-inline-comments - (fn [{:keys [db]} [_]] - (let [current-state (:comment/show-inline-comments db)] - (println "toggle inline comments" current-state) - {:db (assoc db :comment/show-inline-comments (not current-state))}))) +(rf/reg-event-db + :comment/toggle-comments + (fn [db [_]] + (update db :comment/show-comments? not))) (defn thread-child->comment [comment-block] - (let [comment-uid (:block/uid comment-block)] - {:block/uid comment-uid - :string (:block/string comment-block) - :author (-> comment-block :block/create :event/auth :presence/id) - :time (-> comment-block :block/create :event/time :time/ts)})) + (let [{:block/keys [uid string create properties]} comment-block] + {:block/uid uid + :string string + :author (-> create :event/auth :presence/id) + :time (-> create :event/time :time/ts) + :edited? (boolean (get properties "athens/comment/edited"))})) (defn add-is-follow-up? diff --git a/src/cljs/athens/views/comments/inline.cljs b/src/cljs/athens/views/comments/inline.cljs index 7e05de586b..48faa8477e 100644 --- a/src/cljs/athens/views/comments/inline.cljs +++ b/src/cljs/athens/views/comments/inline.cljs @@ -2,10 +2,14 @@ (:require ["/components/Block/Anchor" :refer [Anchor]] ["/components/Comments/Comments" :refer [CommentCounter CommentContainer]] - ["/components/Icons/Icons" :refer [ChevronDownIcon ChevronRightIcon BlockEmbedIcon]] + ["/components/Icons/Icons" :refer [ChevronDownIcon ChevronRightIcon BlockEmbedIcon PencilIcon TrashIcon]] ["/timeAgo.js" :refer [timeAgo]] ["@chakra-ui/react" :refer [Button Box Text VStack Avatar HStack Badge]] + [athens.common-events :as common-events] + [athens.common-events.graph.ops :as graph-ops] + [athens.common.logging :as log] [athens.common.utils :as common.utils] + [athens.db :as db] [athens.parse-renderer :as parse-renderer] [athens.reactive :as reactive] [athens.util :as util] @@ -17,64 +21,172 @@ (defn copy-comment-uid - [comment-data] - (let [uid (:block/uid comment-data) - ref (str "((" uid "))")] + [uid] + (let [ref (str "((" uid "))")] (.. js/navigator -clipboard (writeText ref)) (util/toast (clj->js {:status "info" :position "top-right" :title "Copied uid to clipboard"})))) +(rf/reg-event-fx + :comment/remove-comment + (fn [_ [_ uid]] + (log/debug ":comment/remove-comment:" uid) + {:fx [[:dispatch [:resolve-transact-forward (-> (graph-ops/build-block-remove-op @db/dsdb uid) + (common-events/build-atomic-event))]]]})) + + +(rf/reg-event-fx + :comment/edit-comment + (fn [_ [_ uid]] + {:fx [[:dispatch [:editing/uid uid :end]]]})) + + +(rf/reg-event-fx + :comment/update-comment + (fn [_ [_ uid string]] + (log/debug ":comment/update-comment:" uid string) + {:fx [[:dispatch-n [[:resolve-transact-forward (-> (graph-ops/build-block-save-op @db/dsdb uid string) + (common-events/build-atomic-event))] + [:properties/update-in [:block/uid uid] ["athens/comment/edited"] (fn [db prop-uid] + [(graph-ops/build-block-save-op db prop-uid "")])] + [:editing/uid nil]]]]})) + + +(defn create-menu + [{:keys [block/uid]} current-user-is-author?] + (->> [{:children "Copy comment ref" + :icon (r/as-element [:> BlockEmbedIcon]) + :onClick #(copy-comment-uid uid)} + (when current-user-is-author? + {:children "Edit" + :icon (r/as-element [:> PencilIcon]) + :onClick #(rf/dispatch [:comment/edit-comment uid])}) + (when current-user-is-author? + {:children "Delete" + :icon (r/as-element [:> TrashIcon]) + :onClick #(rf/dispatch [:comment/remove-comment uid])})] + (filterv seq))) + + (defn comment-el [item] - (let [{:keys [string time author block/uid is-followup?]} item - linked-refs (reactive/get-reactive-linked-references [:block/uid uid]) + (let [{:keys [string time author block/uid is-followup? edited?]} item + linked-refs (reactive/get-reactive-linked-references [:block/uid uid]) linked-refs-count (count linked-refs) - on-copy-comment-ref #(copy-comment-uid item) - menu (clj->js [{:children "Copy comment ref" - :icon (r/as-element [:> BlockEmbedIcon]) - :onClick on-copy-comment-ref}]) - human-timestamp (timeAgo time)] + current-username (rf/subscribe [:presence/current-username]) + human-timestamp (timeAgo time) + is-editing (rf/subscribe [:editing/is-editing uid]) + value-atom (r/atom string) + show-edit-atom? (r/atom true)] (fn [] - [:> CommentContainer {:menu menu :isFollowUp is-followup?} - [:> HStack {:gridArea "byline" - :alignItems "center" - :lineHeight 1.25} - ;; if is-followup?, hide byline and avatar - ;; else show avatar and byline - - (when-not is-followup? - [:<> - [:> Avatar {:name author :color "#fff" :size "xs"}] - [:> Text {:fontWeight "bold" - :fontSize "sm" - :noOfLines 0} - author] - - [:> Text {:fontSize "xs" - :_hover {:color "foreground.secondary"} - :color "foreground.tertiary"} - human-timestamp]])] - - [:> Anchor {:ml "0.25em" - :height "2em"}] - [:> Box {:flex "1 1 100%" - :gridArea "comment" - :overflow "hidden" - :fontSize "sm" - :ml 1 - :sx {"> *" {:lineHeight 1.5}}} - ;; In future this should be rendered differently for reply type and ref-type - [athens.parse-renderer/parse-and-render string uid]] - (when (pos? linked-refs-count) - [:> Badge {:size "xs" - :m 1.5 - :mr 0 - :alignSelf "baseline" - :lineHeight "1.5" - :gridArea "refs"} linked-refs-count])]))) + (let [current-user-is-author? (= author @current-username) + menu (create-menu item current-user-is-author?)] + [:> CommentContainer {:menu menu :isFollowUp is-followup? :isEdited edited?} + + ;; if is-followup?, hide byline and avatar + ;; else show avatar and byline + (when-not is-followup? + [:> HStack {:gridArea "byline" + :alignItems "center" + :pt 1 + :lineHeight 1.25} + [:<> + [:> Avatar {:name author :color "#fff" :size "xs"}] + [:> Text {:fontWeight "bold" + :fontSize "sm" + :noOfLines 0} + author] + [:> Text {:fontSize "xs" + :color "foreground.secondary"} + human-timestamp]]]) + + [:> Anchor {:ml "0.25em" + :height "2em"}] + [:> Box {:flex "1 1 100%" + :gridArea "comment" + :alignItems "center" + :display "flex" + :overflow "hidden" + :fontSize "sm" + :py 1 + :ml 1 + :sx {"> *" {:lineHeight 1.5}}} + ;; In future this should be rendered differently for reply type and ref-type + (if-not @is-editing + [:<> + [athens.parse-renderer/parse-and-render string uid] + (when edited? + [:> Text {:fontSize "xs" + :as "span" + :marginLeft "0.5em" + :color "foreground.tertiary"} + "(edited)"])] + (let [block-o {:block/uid uid + :block/string string + :block/children []} + blur-fn #(prn "blur-fn") + save-fn #(reset! value-atom %) + enter-handler (fn jetsam-enter-handler + [_uid _d-key-down] + (rf/dispatch [:comment/update-comment uid @value-atom])) + tab-handler (fn jetsam-tab-handler + [_uid _embed-id _d-key-down]) + backspace-handler (fn jetsam-backspace-handler + [_uid _value]) + delete-handler (fn jetsam-delete-handler + [_uid _d-key-down]) + state-hooks {:save-fn blur-fn + :update-fn #(save-fn %) + :idle-fn #(println "idle-fn" (pr-str %)) + :read-value value-atom + :show-edit? show-edit-atom? + :enter-handler enter-handler + :tab-handler tab-handler + :backspace-handler backspace-handler + :delete-handler delete-handler + :default-verbatim-paste? true + :keyboard-navigation? false + :style {:opacity 1 + :background-color "var(--chakra-colors-background-attic)" + :minHeight "100%" + :border-radius "5px"}}] + [b-content/block-content-el block-o state-hooks]))] + + (when (pos? linked-refs-count) + [:> Badge {:size "xs" + :m 1.5 + :mr 0 + :alignSelf "baseline" + :lineHeight "1.5" + :gridArea "refs"} linked-refs-count])])))) + + +(defn comments-disclosure + [hide? num-comments] + [:> Button (merge + (when-not @hide? + {:bg "background.upper" + :borderColor "transparent" + :borderBottomRadius 0}) + {:justifyContent "flex-start" + :color "foreground.secondary" + :variant "outline" + :size "sm" + :gap 2 + :flex "1 0 auto" + :onClick #(reset! hide? (not @hide?))}) + (if @hide? + [:<> + [:> ChevronRightIcon] + [:> CommentCounter {:count num-comments}] + [:> Text {:pl 1.5} "Comments"]] + [:<> + [:> ChevronDownIcon] + [:> CommentCounter {:count num-comments}] + [:> Text {:pl 1.5} "Comments"]])]) (defn inline-comments @@ -102,27 +214,8 @@ :spacing 0 :borderRadius "md" :align "stretch"}) - [:> Button (merge - (when-not @hide? - {:bg "background.upper" - :borderColor "transparent" - :borderBottomRadius 0}) - {:justifyContent "flex-start" - :color "foreground.secondary" - :variant "outline" - :size "sm" - :gap 2 - :flex "1 0 auto" - :onClick #(swap! @hide? not)}) - (if @hide? - [:<> - [:> ChevronRightIcon] - [:> CommentCounter {:count num-comments}] - [:> Text {:pl 1.5} "Comments"]] - [:<> - [:> ChevronDownIcon] - [:> CommentCounter {:count num-comments}] - [:> Text {:pl 1.5} "Comments"]])] + + [comments-disclosure hide? num-comments] (when-not @hide? [:> Box {:pl 8 @@ -136,7 +229,7 @@ ;; :block/string @value-atom :block/children []} blur-fn #(when (not (seq @value-atom)) - (rf/dispatch [:comment/hide-comment-textarea])) + (rf/dispatch [:comment/hide-editor])) save-fn #(reset! value-atom %) enter-handler (fn jetsam-enter-handler [_uid _d-key-down] diff --git a/src/cljs/athens/views/pages/block_page.cljs b/src/cljs/athens/views/pages/block_page.cljs index 618db282cf..ab6be2af00 100644 --- a/src/cljs/athens/views/pages/block_page.cljs +++ b/src/cljs/athens/views/pages/block_page.cljs @@ -141,8 +141,8 @@ ;; Show comments when the toggle is on [:> Box {:ml "4%" :w "100%"} - (when (or @(rf/subscribe [:comment/show-comment-textarea? uid]) - (and @(rf/subscribe [:comment/show-inline-comments?]) + (when (or @(rf/subscribe [:comment/show-editor? uid]) + (and @(rf/subscribe [:comment/show-comments?]) (comments/get-comment-thread-uid @db/dsdb uid))) [inline-comments/inline-comments (comments/get-comments-in-thread @db/dsdb (comments/get-comment-thread-uid @db/dsdb uid)) uid false])] diff --git a/src/js/components/AppToolbar/AppToolbar.tsx b/src/js/components/AppToolbar/AppToolbar.tsx index 40b17d5723..85caf9addf 100644 --- a/src/js/components/AppToolbar/AppToolbar.tsx +++ b/src/js/components/AppToolbar/AppToolbar.tsx @@ -202,7 +202,7 @@ export interface AppToolbarProps extends React.HTMLAttributes { /** * Whether comments should be shown */ - isShowInlineComments: boolean; + isShowComments: boolean; // Electron only onPressMinimize?(): void; onPressClose?(): void; @@ -210,7 +210,7 @@ export interface AppToolbarProps extends React.HTMLAttributes { onPressFullscreen?(): void; onPressHistoryBack(): void; onPressHistoryForward(): void; - onClickInlineComments(): void; + onClickComments(): void; // Main toolbar onPressCommandBar(): void; onPressDailyNotes(): void; @@ -274,9 +274,9 @@ export const AppToolbar = (props: AppToolbarProps): React.ReactElement => { isLeftSidebarOpen, isRightSidebarOpen, isCommandBarOpen, - isShowInlineComments, + isShowComments, isNotificationsPopoverOpen, - onClickInlineComments: handleClickInlineComments, + onClickComments: handleClickComments, onPressCommandBar: handlePressCommandBar, onPressDailyNotes: handlePressDailyNotes, onPressAllPages: handlePressAllPages, @@ -312,10 +312,10 @@ export const AppToolbar = (props: AppToolbarProps): React.ReactElement => { }, [isThemeDark, toggleColorMode]); const secondaryTools = [ - handleClickInlineComments && { - label: isShowInlineComments ? "Hide comments" : "Show comments", - isActive: isShowInlineComments, - onClick: handleClickInlineComments, + handleClickComments && { + label: isShowComments ? "Hide comments" : "Show comments", + isActive: isShowComments, + onClick: handleClickComments, icon: }, { diff --git a/src/js/components/Comments/Comments.tsx b/src/js/components/Comments/Comments.tsx index 6bb3f36fd2..b5af2c0b39 100644 --- a/src/js/components/Comments/Comments.tsx +++ b/src/js/components/Comments/Comments.tsx @@ -84,7 +84,6 @@ export const CommentContainer = withErrorBoundary(({ children, menu, isFollowUp mb="-1px" borderTop={isFollowUp ? null : "1px solid"} borderTopColor="separator.divider" - py={1} alignItems="stretch" justifyContent="stretch" rowGap={0} diff --git a/src/js/theme/theme.js b/src/js/theme/theme.js index 8a1be9a40b..000d483d3e 100644 --- a/src/js/theme/theme.js +++ b/src/js/theme/theme.js @@ -100,11 +100,11 @@ const semanticTokens = { // foreground colors "foreground.primary": { default: 'hsla(0, 0%, 0%, 0.87)', - _dark: 'hsla(0, 0%, 100%, 0.8)' + _dark: 'hsla(0, 0%, 100%, 0.83)' }, "foreground.secondary": { default: 'hsla(0, 0%, 0%, 0.57)', - _dark: 'hsla(0, 0%, 100%, 0.57)' + _dark: 'hsla(0, 0%, 100%, 0.5)' }, "foreground.tertiary": { default: 'hsla(0, 0%, 0%, 0.2)', @@ -491,6 +491,9 @@ const components = { margin: '0.35rem 0.5rem', } } + }, + defaultProps: { + size: 'sm', } }, Modal: {