feat: add copy button to memo (#267)

* feat: copy-content

* Update web/src/less/memo-detail.less

Co-authored-by: boojack <stevenlgtm@gmail.com>
pull/275/head
f97 3 years ago committed by GitHub
parent ca2557eb7e
commit 2ea612e2fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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: 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: Props) => {
<span className="btn" onClick={handleMarkMemoClick}>
{t("common.mark")}
</span>
<span className="btn" onClick={handleCopyContent}>
{t("memo.copy")}
</span>
<span className="btn" onClick={handleViewMemoDetailPage}>
{t("memo.view-detail")}
</span>

@ -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: 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: Props) => {
<button className="btn edit-btn" onClick={handleGotoMemoLinkBtnClick}>
<Icon.ExternalLink className="icon-img" />
</button>
<button className="btn copy-btn" onClick={handleCopyContent}>
<Icon.Clipboard className="icon-img" />
</button>
<button className="btn edit-btn" onClick={handleEditMemoBtnClick}>
<Icon.Edit3 className="icon-img" />
</button>

@ -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;
}
}
}

@ -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."
}
}

@ -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."
}
}

@ -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."
}
}

@ -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 (
<section className="page-wrapper memo-detail">
<div className="page-container">
@ -92,38 +98,45 @@ const MemoDetail = () => {
<main className="memos-wrapper">
<div className="memo-container">
<div className="memo-header">
<span className="time-text">{dayjs(state.memo.createdTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss")}</span>
{user?.id === state.memo.creatorId ? (
<Dropdown
className="visibility-selector"
trigger={
<span className={`status-text ${state.memo.visibility.toLowerCase()}`}>
{state.memo.visibility} <Icon.ChevronDown className="w-4 h-auto ml-px" />
</span>
}
actions={
<>
<span className="action-button" onClick={() => handleVisibilitySelectorChange("PRIVATE")}>
Private
</span>
<span className="action-button" onClick={() => handleVisibilitySelectorChange("PROTECTED")}>
Protected
<div className="status-container">
<span className="time-text">{dayjs(state.memo.createdTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss")}</span>
{user?.id === state.memo.creatorId ? (
<Dropdown
className="visibility-selector"
trigger={
<span className={`status-text ${state.memo.visibility.toLowerCase()}`}>
{state.memo.visibility} <Icon.ChevronDown className="w-4 h-auto ml-px" />
</span>
<span className="action-button" onClick={() => handleVisibilitySelectorChange("PUBLIC")}>
Public
</span>
</>
}
actionsClassName="!w-28 !left-0 !p-1"
/>
) : (
<>
<span className="split-text">by</span>
<a className="name-text" href={`/u/${state.memo.creator.id}`}>
{state.memo.creator.name}
</a>
</>
)}
}
actions={
<>
<span className="action-button" onClick={() => handleVisibilitySelectorChange("PRIVATE")}>
Private
</span>
<span className="action-button" onClick={() => handleVisibilitySelectorChange("PROTECTED")}>
Protected
</span>
<span className="action-button" onClick={() => handleVisibilitySelectorChange("PUBLIC")}>
Public
</span>
</>
}
actionsClassName="!w-28 !left-0 !p-1"
/>
) : (
<>
<span className="split-text">by</span>
<a className="name-text" href={`/u/${state.memo.creator.id}`}>
{state.memo.creator.name}
</a>
</>
)}
</div>
<div className="btns-container">
<button className="btn copy-btn" onClick={handleCopyContent}>
<Icon.Clipboard className="icon-img" />
</button>
</div>
</div>
<MemoContent className="memo-content" content={state.memo.content} onMemoContentClick={() => undefined} />
<MemoResources memo={state.memo} />

Loading…
Cancel
Save