From 2ea612e2fe8b769f48a6f024c3f5e804d9eafdee Mon Sep 17 00:00:00 2001 From: f97 Date: Wed, 5 Oct 2022 15:39:04 +0700 Subject: [PATCH] feat: add copy button to memo (#267) * feat: copy-content * Update web/src/less/memo-detail.less Co-authored-by: boojack --- web/src/components/Memo.tsx | 9 ++++ web/src/components/MemoCardDialog.tsx | 9 ++++ web/src/less/memo-detail.less | 52 ++++++++++++------- web/src/locales/en.json | 4 +- web/src/locales/vi.json | 4 +- web/src/locales/zh.json | 4 +- web/src/pages/MemoDetail.tsx | 75 ++++++++++++++++----------- 7 files changed, 105 insertions(+), 52 deletions(-) diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index 976b262d..14711468 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -1,3 +1,4 @@ +import copy from "copy-to-clipboard"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import { memo, useEffect, useRef, useState } from "react"; @@ -61,6 +62,11 @@ const Memo: React.FC = (props: Props) => { navigate(`/m/${memo.id}`); }; + const handleCopyContent = () => { + copy(memo.content); + toastHelper.success(t("message.succeed-copy-content")); + }; + const handleTogglePinMemoBtnClick = async () => { try { if (memo.pinned) { @@ -205,6 +211,9 @@ const Memo: React.FC = (props: Props) => { {t("common.mark")} + + {t("memo.copy")} + {t("memo.view-detail")} diff --git a/web/src/components/MemoCardDialog.tsx b/web/src/components/MemoCardDialog.tsx index 74f2d5d8..44fc4d17 100644 --- a/web/src/components/MemoCardDialog.tsx +++ b/web/src/components/MemoCardDialog.tsx @@ -1,3 +1,4 @@ +import copy from "copy-to-clipboard"; import { useState, useEffect, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { editorStateService, memoService, userService } from "../services"; @@ -130,6 +131,11 @@ const MemoCardDialog: React.FC = (props: Props) => { editorStateService.setEditMemoWithId(memo.id); }; + const handleCopyContent = () => { + copy(memo.content); + toastHelper.success(t("message.succeed-copy-content")); + }; + const handleVisibilitySelectorChange = async (visibility: Visibility) => { if (memo.visibility === visibility) { return; @@ -171,6 +177,9 @@ const MemoCardDialog: React.FC = (props: Props) => { + diff --git a/web/src/less/memo-detail.less b/web/src/less/memo-detail.less index 07ce6f4a..513041a8 100644 --- a/web/src/less/memo-detail.less +++ b/web/src/less/memo-detail.less @@ -41,31 +41,47 @@ @apply flex flex-col justify-start items-start w-full p-4 mt-2 bg-white rounded-lg border border-white hover:border-gray-200; > .memo-header { - @apply mb-2 w-full flex flex-row justify-start items-center text-sm font-mono text-gray-400; + @apply mb-2 w-full flex flex-row justify-between items-center text-sm font-mono text-gray-400; - > .split-text { - @apply mx-2; - } + > .status-container { + @apply flex flex-row justify-start items-center; - > .name-text { - @apply hover:text-green-600 hover:underline; + > .split-text { + @apply mx-2; + } + + > .name-text { + @apply hover:text-green-600 hover:underline; + } + + > .visibility-selector { + > .status-text { + @apply flex flex-row justify-start items-center leading-5 text-xs cursor-pointer ml-2 rounded border px-1; + + &.public { + @apply border-green-600 text-green-600; + } + + &.protected { + @apply border-gray-400 text-gray-400; + } + } + + .action-button { + @apply px-2 leading-7 w-full rounded text-gray-600 hover:bg-gray-100; + } + } } - > .visibility-selector { - > .status-text { - @apply flex flex-row justify-start items-center leading-5 text-xs cursor-pointer ml-2 rounded border px-1; - - &.public { - @apply border-green-600 text-green-600; - } + .btns-container { + .flex(row, flex-start, center); - &.protected { - @apply border-gray-400 text-gray-400; - } + > .btn { + @apply rounded; } - .action-button { - @apply px-2 leading-7 w-full rounded text-gray-600 hover:bg-gray-100; + > .copy-btn { + @apply hover:bg-gray-100; } } } diff --git a/web/src/locales/en.json b/web/src/locales/en.json index e95fda81..ce416022 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -74,6 +74,7 @@ }, "memo": { "view-detail": "View Detail", + "copy": "Copy", "visibility": { "private": "Private", "protected": "Protected", @@ -138,6 +139,7 @@ "user-not-found": "User not found", "password-changed": "Password Changed", "private-only": "This memo is private only.", - "copied": "Copied" + "copied": "Copied", + "succeed-copy-content": "Succeed to copy content to clipboard." } } diff --git a/web/src/locales/vi.json b/web/src/locales/vi.json index e5e5b7dc..230abdc5 100644 --- a/web/src/locales/vi.json +++ b/web/src/locales/vi.json @@ -74,6 +74,7 @@ }, "memo": { "view-detail": "Xem chi tiết", + "copy": "Sao chép", "visibility": { "private": "Private", "protected": "Protected", @@ -138,6 +139,7 @@ "user-not-found": "Không tìm thấy người dùng này", "password-changed": "Mật khẩu đã được thay đổi", "private-only": "Memo này có trạng thái riêng tư.", - "copied": "Đã sao chép" + "copied": "Đã sao chép", + "succeed-copy-content": "Đã sao chép nội dung memo thành công." } } diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 02e6caf1..f9bb38db 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -74,6 +74,7 @@ }, "memo": { "view-detail": "查看详情", + "copy": "Copy", "visibility": { "private": "仅自己可见", "protected": "对所有用户公开", @@ -138,6 +139,7 @@ "user-not-found": "未找到用户", "password-changed": "密码已修改", "private-only": "This memo is private only.", - "copied": "Copied" + "copied": "Copied", + "succeed-copy-content": "Succeed to copy content to clipboard." } } diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index 76061b6b..3daa036a 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -1,3 +1,4 @@ +import copy from "copy-to-clipboard"; import dayjs from "dayjs"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -65,6 +66,11 @@ const MemoDetail = () => { }); }; + const handleCopyContent = () => { + copy(state.memo.content); + toastHelper.success(t("message.succeed-copy-content")); + }; + return (
@@ -92,38 +98,45 @@ const MemoDetail = () => {
- {dayjs(state.memo.createdTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss")} - {user?.id === state.memo.creatorId ? ( - - {state.memo.visibility} - - } - actions={ - <> - handleVisibilitySelectorChange("PRIVATE")}> - Private - - handleVisibilitySelectorChange("PROTECTED")}> - Protected +
+ {dayjs(state.memo.createdTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss")} + {user?.id === state.memo.creatorId ? ( + + {state.memo.visibility} - handleVisibilitySelectorChange("PUBLIC")}> - Public - - - } - actionsClassName="!w-28 !left-0 !p-1" - /> - ) : ( - <> - by - - {state.memo.creator.name} - - - )} + } + actions={ + <> + handleVisibilitySelectorChange("PRIVATE")}> + Private + + handleVisibilitySelectorChange("PROTECTED")}> + Protected + + handleVisibilitySelectorChange("PUBLIC")}> + Public + + + } + actionsClassName="!w-28 !left-0 !p-1" + /> + ) : ( + <> + by + + {state.memo.creator.name} + + + )} +
+
+ +
undefined} />