From 4877d1cd4bc3c73046247b88c3a55bf927104722 Mon Sep 17 00:00:00 2001 From: indyteo Date: Wed, 21 Jul 2021 11:11:38 +0200 Subject: [PATCH] feature: add ability to copy comment link --- locale/en/client.json | 4 + public/pages/ShowPost/ShowPost.page.tsx | 41 +++++- .../ShowPost/components/DiscussionPanel.tsx | 3 +- .../pages/ShowPost/components/ShowComment.tsx | 135 +++++++++++------- public/services/utils.ts | 29 ++++ 5 files changed, 161 insertions(+), 51 deletions(-) diff --git a/locale/en/client.json b/locale/en/client.json index 7648d4fa8..b6b8e6f4e 100644 --- a/locale/en/client.json +++ b/locale/en/client.json @@ -3,6 +3,7 @@ "action.change": "change", "action.close": "Close", "action.confirm": "Confirm", + "action.copylink": "Copy link", "action.delete": "Delete", "action.edit": "Edit", "action.markallasread": "Mark All as Read", @@ -127,6 +128,9 @@ "page.pendingactivation.text": "We sent you a confirmation email with a link to activate your site.", "page.pendingactivation.text2": "Please check your inbox to activate it.", "page.pendingactivation.title": "Your account is pending activation", + "showpost.comment.copylink.error": "Could not copy comment link, please copy page URL", + "showpost.comment.copylink.success": "Successfully copied comment link to clipboard", + "showpost.comment.unknownhighlighted": "Unknown comment ID #{id}", "showpost.commentinput.placeholder": "Leave a comment", "showpost.discussionpanel.emptymessage": "No one has commented yet.", "showpost.label.author": "Posted by <0/> · <1/>", diff --git a/public/pages/ShowPost/ShowPost.page.tsx b/public/pages/ShowPost/ShowPost.page.tsx index 3c200926b..8690b378d 100644 --- a/public/pages/ShowPost/ShowPost.page.tsx +++ b/public/pages/ShowPost/ShowPost.page.tsx @@ -3,7 +3,7 @@ import "./ShowPost.page.scss" import React from "react" import { Comment, Post, Tag, Vote, ImageUpload, CurrentUser } from "@fider/models" -import { actions, Failure, Fider, timeAgo } from "@fider/services" +import { actions, clearUrlHash, Failure, Fider, notify, timeAgo } from "@fider/services" import { VoteCounter, @@ -48,6 +48,7 @@ interface ShowPostPageState { newTitle: string attachments: ImageUpload[] newDescription: string + highlightedComment?: number error?: Failure } @@ -72,6 +73,15 @@ export default class ShowPostPage extends React.Component { const result = await actions.updatePost(this.props.post.number, this.state.newTitle, this.state.newDescription, this.state.attachments) if (result.ok) { @@ -103,6 +113,33 @@ export default class ShowPostPage extends React.Component { + const hash = window.location.hash + const result = /#comment-([0-9]+)/.exec(hash) + + let highlightedComment + if (result === null) { + // No match + highlightedComment = undefined + } else { + // Match, extract numeric ID + const id = parseInt(result[1]) + if (this.props.comments.map((comment) => comment.id).includes(id)) { + highlightedComment = id + } else { + // Unknown comment + if (e?.cancelable) { + e.preventDefault() + } else { + clearUrlHash(true) + } + notify.error(Unknown comment ID #{id}) + highlightedComment = undefined + } + } + this.setState({ highlightedComment }) + } + public render() { return ( <> @@ -201,7 +238,7 @@ export default class ShowPostPage extends React.Component
- +
diff --git a/public/pages/ShowPost/components/DiscussionPanel.tsx b/public/pages/ShowPost/components/DiscussionPanel.tsx index 814a2ea7e..a7190a8e4 100644 --- a/public/pages/ShowPost/components/DiscussionPanel.tsx +++ b/public/pages/ShowPost/components/DiscussionPanel.tsx @@ -11,6 +11,7 @@ interface DiscussionPanelProps { user?: CurrentUser post: Post comments: Comment[] + highlightedComment?: number } export const DiscussionPanel = (props: DiscussionPanelProps) => { @@ -22,7 +23,7 @@ export const DiscussionPanel = (props: DiscussionPanelProps) => { {props.comments.map((c) => ( - + ))} diff --git a/public/pages/ShowPost/components/ShowComment.tsx b/public/pages/ShowPost/components/ShowComment.tsx index 3d6f0f018..429ee42e2 100644 --- a/public/pages/ShowPost/components/ShowComment.tsx +++ b/public/pages/ShowPost/components/ShowComment.tsx @@ -1,25 +1,42 @@ -import React, { useState } from "react" +import React, { useEffect, useRef, useState } from "react" import { Comment, Post, ImageUpload } from "@fider/models" import { Avatar, UserName, Moment, Form, TextArea, Button, Markdown, Modal, ImageViewer, MultiImageUploader, Dropdown, Icon } from "@fider/components" import { HStack } from "@fider/components/layout" -import { formatDate, Failure, actions } from "@fider/services" +import { formatDate, Failure, actions, notify, copyToClipboard, classSet, clearUrlHash } from "@fider/services" import { useFider } from "@fider/hooks" import IconDotsHorizontal from "@fider/assets/images/heroicons-dots-horizontal.svg" -import { Trans } from "@lingui/macro" +import { t, Trans } from "@lingui/macro" interface ShowCommentProps { post: Post comment: Comment + highlighted?: boolean } export const ShowComment = (props: ShowCommentProps) => { const fider = useFider() + const node = useRef(null) const [isEditing, setIsEditing] = useState(false) const [newContent, setNewContent] = useState("") const [isDeleteConfirmationModalOpen, setIsDeleteConfirmationModalOpen] = useState(false) const [attachments, setAttachments] = useState([]) const [error, setError] = useState() + const handleClick = (e: MouseEvent) => { + if (node.current == null || !node.current.contains(e.target as Node)) { + clearUrlHash() + } + } + + useEffect(() => { + if (props.highlighted) { + document.addEventListener("mousedown", handleClick) + return () => { + document.removeEventListener("mousedown", handleClick) + } + } + }, [props.highlighted]) + const canEditComment = (): boolean => { if (fider.session.isAuthenticated) { return fider.session.user.isCollaborator || props.comment.user.id === fider.session.user.id @@ -56,7 +73,13 @@ export const ShowComment = (props: ShowCommentProps) => { } const onActionSelected = (action: string) => () => { - if (action === "edit") { + if (action === "copylink") { + window.location.hash = `#comment-${props.comment.id}` + copyToClipboard(window.location.href).then( + () => notify.success(t({ id: "showpost.comment.copylink.success", message: "Successfully copied comment link to clipboard" })), + () => notify.error(t({ id: "showpost.comment.copylink.error", message: "Could not copy comment link, please copy page URL" })) + ) + } else if (action === "edit") { setIsEditing(true) setNewContent(props.comment.content) clearError() @@ -97,53 +120,69 @@ export const ShowComment = (props: ShowCommentProps) => { · edited ) + const classList = classSet({ + "flex-grow rounded-md p-2": true, + "bg-gray-50": !props.highlighted, + "bg-gray-200": props.highlighted, + }) + return ( - - {modal()} -
- -
-
-
- - - {" "} -
- · {editedMetadata} -
+
+ + {modal()} +
+ +
+
+
+ + + {" "} +
+ · {editedMetadata} +
+
+ {!isEditing && ( + }> + + Copy link + + {canEditComment() && ( + <> + + + Edit + + + Delete + + + )} + + )}
- {!isEditing && canEditComment() && ( - }> - - Edit - - - Delete - - +
+
+ {isEditing ? ( +
+