From 93b21f2929d08320ba99737de02eeb51ce8e89ce Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Mon, 8 Jan 2024 15:20:19 +0200 Subject: [PATCH] feat: allow copying link to questions and answers closes #104 --- .../search/collators/QetaCollatorFactory.ts | 2 +- .../components/QuestionPage/AnswerCard.tsx | 10 ++++-- .../components/QuestionPage/LinkButton.tsx | 33 +++++++++++++++++++ .../components/QuestionPage/QuestionCard.tsx | 14 +++++++- .../components/QuestionPage/QuestionPage.tsx | 4 +-- plugins/qeta/src/utils/hooks.ts | 11 +++++++ 6 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 plugins/qeta/src/components/QuestionPage/LinkButton.tsx diff --git a/plugins/qeta-backend/src/search/collators/QetaCollatorFactory.ts b/plugins/qeta-backend/src/search/collators/QetaCollatorFactory.ts index 03264ae7..ccfba54a 100644 --- a/plugins/qeta-backend/src/search/collators/QetaCollatorFactory.ts +++ b/plugins/qeta-backend/src/search/collators/QetaCollatorFactory.ts @@ -56,7 +56,7 @@ export class QetaCollatorFactory implements DocumentCollatorFactory { yield { title: `Answer for ${question.title}`, text: answer.content, - location: `/qeta/questions/${question.id}`, + location: `/qeta/questions/${question.id}#answer_${answer.id}`, docType: 'qeta', author: answer.author, score: answer.score, diff --git a/plugins/qeta/src/components/QuestionPage/AnswerCard.tsx b/plugins/qeta/src/components/QuestionPage/AnswerCard.tsx index 0f185066..30235f81 100644 --- a/plugins/qeta/src/components/QuestionPage/AnswerCard.tsx +++ b/plugins/qeta/src/components/QuestionPage/AnswerCard.tsx @@ -8,6 +8,7 @@ import { DeleteModal } from '../DeleteModal/DeleteModal'; import { AnswerForm } from './AnswerForm'; import { AuthorBox } from './AuthorBox'; import { CommentSection } from '../CommentSection/CommentSection'; +import { LinkButton } from './LinkButton'; export const AnswerCard = (props: { answer: AnswerResponse; @@ -22,6 +23,8 @@ export const AnswerCard = (props: { const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); const handleDeleteModalOpen = () => setDeleteModalOpen(true); const handleDeleteModalClose = () => setDeleteModalOpen(false); + const highlightedAnswer = + window.location.hash.slice(1) === `answer_${answer.id}`; const onAnswerEdit = (a: AnswerResponse) => { setEditMode(false); @@ -37,12 +40,15 @@ export const AnswerCard = (props: { return ( <>
+
{editMode ? ( diff --git a/plugins/qeta/src/components/QuestionPage/LinkButton.tsx b/plugins/qeta/src/components/QuestionPage/LinkButton.tsx new file mode 100644 index 00000000..36604d0e --- /dev/null +++ b/plugins/qeta/src/components/QuestionPage/LinkButton.tsx @@ -0,0 +1,33 @@ +import { IconButton, Tooltip } from '@material-ui/core'; +import Link from '@material-ui/icons/Link'; +import React from 'react'; +import { AnswerResponse, QuestionResponse } from '../../api'; + +export const LinkButton = (props: { + entity: QuestionResponse | AnswerResponse; +}) => { + const isQuestion = 'title' in props.entity; + const copyToClipboard = () => { + const url = new URL(window.location.href); + if (!isQuestion) { + url.hash = `#answer_${props.entity.id}`; + } + window.navigator.clipboard.writeText(url.toString()); + }; + + return ( + + + + + + ); +}; diff --git a/plugins/qeta/src/components/QuestionPage/QuestionCard.tsx b/plugins/qeta/src/components/QuestionPage/QuestionCard.tsx index 7c94e6bf..ab8eef1e 100644 --- a/plugins/qeta/src/components/QuestionPage/QuestionCard.tsx +++ b/plugins/qeta/src/components/QuestionPage/QuestionCard.tsx @@ -1,7 +1,7 @@ import { AnswerResponse, QuestionResponse } from '../../api'; import { Box, Card, CardContent, Grid, Typography } from '@material-ui/core'; import { Link, MarkdownContent } from '@backstage/core-components'; -import React from 'react'; +import React, { useEffect } from 'react'; import { VoteButtons } from './VoteButtons'; import { useStyles } from '../../utils/hooks'; import { DeleteModal } from '../DeleteModal/DeleteModal'; @@ -11,6 +11,7 @@ import { TagsAndEntities } from './TagsAndEntities'; import { CommentSection } from '../CommentSection/CommentSection'; import { useRouteRef } from '@backstage/core-plugin-api'; import { editQuestionRouteRef } from '../../routes'; +import { LinkButton } from './LinkButton'; export const QuestionCard = (props: { question: QuestionResponse }) => { const { question } = props; @@ -24,6 +25,16 @@ export const QuestionCard = (props: { question: QuestionResponse }) => { setQuestionEntity(q); }; + const highlightedAnswer = window.location.hash.slice(1) ?? undefined; + useEffect(() => { + if (highlightedAnswer) { + const element = document.querySelector(`#${highlightedAnswer}`); + if (element) { + element.scrollIntoView(); + } + } + }, [highlightedAnswer]); + return ( <> {
+
diff --git a/plugins/qeta/src/components/QuestionPage/QuestionPage.tsx b/plugins/qeta/src/components/QuestionPage/QuestionPage.tsx index e003f646..0ddb9690 100644 --- a/plugins/qeta/src/components/QuestionPage/QuestionPage.tsx +++ b/plugins/qeta/src/components/QuestionPage/QuestionPage.tsx @@ -166,12 +166,12 @@ export const QuestionPage = () => { {allQuestions.sort(sortAnswers).map(a => { return ( - <> + - + ); })} diff --git a/plugins/qeta/src/utils/hooks.ts b/plugins/qeta/src/utils/hooks.ts index f4234d47..319517bc 100644 --- a/plugins/qeta/src/utils/hooks.ts +++ b/plugins/qeta/src/utils/hooks.ts @@ -194,6 +194,17 @@ export const useStyles = makeStyles(theme => { overflow: 'hidden', whiteSpace: 'nowrap', }, + highlight: { + animation: '$highlight 2s', + }, + '@keyframes highlight': { + '0%': { + boxShadow: `0px 0px 0px 3px ${theme.palette.secondary.light}`, + }, + '100%': { + boxShadow: 'none', + }, + }, }; });