From 96798e10b488439c9dae38815844f8e253045b8c Mon Sep 17 00:00:00 2001 From: boojack Date: Sat, 7 Jan 2023 01:56:02 +0800 Subject: [PATCH] feat: support embed memo with iframe (#912) --- web/src/components/EmbedMemoDialog.tsx | 60 ++++++++++++++++++++++++ web/src/components/Memo.tsx | 8 ++++ web/src/components/ShareMemoDialog.tsx | 2 +- web/src/css/global.css | 6 ++- web/src/less/auth.less | 2 +- web/src/less/explore.less | 4 +- web/src/less/home.less | 4 +- web/src/less/memo-detail.less | 4 +- web/src/less/memo.less | 2 +- web/src/less/siderbar.less | 4 +- web/src/pages/EmbedMemo.tsx | 65 ++++++++++++++++++++++++++ web/src/pages/Loading.tsx | 2 +- web/src/router/index.tsx | 14 ++++++ 13 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 web/src/components/EmbedMemoDialog.tsx create mode 100644 web/src/pages/EmbedMemo.tsx diff --git a/web/src/components/EmbedMemoDialog.tsx b/web/src/components/EmbedMemoDialog.tsx new file mode 100644 index 00000000..f3d3c4f5 --- /dev/null +++ b/web/src/components/EmbedMemoDialog.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import Icon from "./Icon"; +import { generateDialog } from "./Dialog"; +import copy from "copy-to-clipboard"; +import toastHelper from "./Toast"; + +interface Props extends DialogProps { + memoId: MemoId; +} + +const EmbedMemoDialog: React.FC = (props: Props) => { + const { memoId, destroy } = props; + + const memoEmbeddedCode = () => { + return ``; + }; + + const handleCopyCode = () => { + copy(memoEmbeddedCode()); + toastHelper.success("Succeed to copy code to clipboard."); + }; + + return ( + <> +
+

Embed Memo

+ +
+
+

Copy and paste the below codes into your blog or website.

+
+          {memoEmbeddedCode()}
+        
+

+ * Only the public memo supports. + + Copy + +

+
+ + ); +}; + +function showEmbedMemoDialog(memoId: MemoId) { + generateDialog( + { + className: "embed-memo-dialog", + dialogName: "embed-memo-dialog", + }, + EmbedMemoDialog, + { + memoId, + } + ); +} + +export default showEmbedMemoDialog; diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index 499785da..0168e764 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -10,6 +10,7 @@ import MemoContent from "./MemoContent"; import MemoResources from "./MemoResources"; import showShareMemo from "./ShareMemoDialog"; import showPreviewImageDialog from "./PreviewImageDialog"; +import showEmbedMemoDialog from "./EmbedMemoDialog"; import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog"; import "../less/memo.less"; @@ -54,6 +55,10 @@ const Memo: React.FC = (props: Props) => { navigate(`/m/${memo.id}`); }; + const handleShowEmbedMemoDialog = () => { + showEmbedMemoDialog(memo.id); + }; + const handleCopyContent = () => { copy(memo.content); toastHelper.success(t("message.succeed-copy-content")); @@ -214,6 +219,9 @@ const Memo: React.FC = (props: Props) => { {t("memo.view-detail")} + + Embed memo + {t("common.archive")} diff --git a/web/src/components/ShareMemoDialog.tsx b/web/src/components/ShareMemoDialog.tsx index a85f8e5b..c7032cf9 100644 --- a/web/src/components/ShareMemoDialog.tsx +++ b/web/src/components/ShareMemoDialog.tsx @@ -99,7 +99,7 @@ const ShareMemoDialog: React.FC = (props: Props) => { const handleCopyLinkBtnClick = () => { copy(`${window.location.origin}/m/${memo.id}`); - toastHelper.success(t("message.succeed-copy-content")); + toastHelper.success("Succeed to copy memo link to clipboard."); }; const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => { diff --git a/web/src/css/global.css b/web/src/css/global.css index 09052513..60f5cff3 100644 --- a/web/src/css/global.css +++ b/web/src/css/global.css @@ -1,7 +1,11 @@ html, body { - @apply text-base dark:bg-zinc-800; + @apply text-base w-full h-full dark:bg-zinc-800; font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans", "Noto Sans CJK SC", "Microsoft YaHei UI", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } + +#root { + @apply w-full h-full; +} diff --git a/web/src/less/auth.less b/web/src/less/auth.less index e6f0fa19..a88f60f5 100644 --- a/web/src/less/auth.less +++ b/web/src/less/auth.less @@ -1,5 +1,5 @@ .page-wrapper.auth { - @apply flex flex-row justify-center items-center w-full h-screen bg-zinc-100 dark:bg-zinc-800; + @apply flex flex-row justify-center items-center w-full h-full bg-zinc-100 dark:bg-zinc-800; > .page-container { @apply w-80 max-w-full h-full py-4 flex flex-col justify-start items-center ml-calc; diff --git a/web/src/less/explore.less b/web/src/less/explore.less index 536e6f6b..024f68d7 100644 --- a/web/src/less/explore.less +++ b/web/src/less/explore.less @@ -1,8 +1,8 @@ .page-wrapper.explore { - @apply relative top-0 w-full h-screen overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; + @apply relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; > .page-container { - @apply relative w-full min-h-screen mx-auto flex flex-col justify-start items-center pb-8; + @apply relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-8; > .page-header { @apply sticky top-0 z-10 max-w-2xl w-full min-h-full flex flex-row justify-between backdrop-blur-sm items-center px-4 sm:pr-6 pt-6 mb-2 ml-calc; diff --git a/web/src/less/home.less b/web/src/less/home.less index 8cdd6b9c..f92d3805 100644 --- a/web/src/less/home.less +++ b/web/src/less/home.less @@ -1,12 +1,12 @@ .page-wrapper.home { - @apply relative top-0 w-full h-screen overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; + @apply relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; > .banner-wrapper { @apply w-full flex flex-col justify-start items-center; } > .page-container { - @apply relative w-full min-h-screen mx-auto flex flex-row justify-start sm:justify-center items-start; + @apply relative w-full min-h-full mx-auto flex flex-row justify-start sm:justify-center items-start; > .sidebar-wrapper { @apply flex-shrink-0 ml-calc; diff --git a/web/src/less/memo-detail.less b/web/src/less/memo-detail.less index 0015a57f..c6038ec8 100644 --- a/web/src/less/memo-detail.less +++ b/web/src/less/memo-detail.less @@ -1,8 +1,8 @@ .page-wrapper.memo-detail { - @apply relative top-0 w-full h-screen overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; + @apply relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; > .page-container { - @apply relative w-full min-h-screen mx-auto flex flex-col justify-start items-center pb-8; + @apply relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-8; > .page-header { @apply sticky top-0 z-10 max-w-2xl w-full min-h-full flex flex-row justify-between items-center px-4 pt-6 mb-2 bg-zinc-100 dark:bg-zinc-800 ml-calc; diff --git a/web/src/less/memo.less b/web/src/less/memo.less index 4dd2826f..c5d62588 100644 --- a/web/src/less/memo.less +++ b/web/src/less/memo.less @@ -54,7 +54,7 @@ @apply hidden flex-col justify-start items-center absolute top-2 -right-4 flex-nowrap hover:flex p-3; > .more-action-btns-container { - @apply w-28 h-auto p-1 z-1 whitespace-nowrap rounded-lg bg-white dark:bg-zinc-700; + @apply w-auto h-auto p-1 z-1 whitespace-nowrap rounded-lg bg-white dark:bg-zinc-700; box-shadow: 0 0 8px 0 rgb(0 0 0 / 20%); > .btns-container { diff --git a/web/src/less/siderbar.less b/web/src/less/siderbar.less index f55f6394..d675dfcc 100644 --- a/web/src/less/siderbar.less +++ b/web/src/less/siderbar.less @@ -1,5 +1,5 @@ .sidebar-wrapper { - @apply fixed sm:sticky top-0 z-30 sm:z-0 -translate-x-64 sm:translate-x-0 sm:flex flex-col justify-start items-start w-64 h-screen py-4 pl-2 bg-white dark:bg-zinc-800 sm:bg-transparent overflow-x-hidden overflow-y-auto transition-transform duration-300 overscroll-contain hide-scrollbar; + @apply fixed sm:sticky top-0 z-30 sm:z-0 -translate-x-64 sm:translate-x-0 sm:flex flex-col justify-start items-start w-64 h-full py-4 pl-2 bg-white dark:bg-zinc-800 sm:bg-transparent overflow-x-hidden overflow-y-auto transition-transform duration-300 overscroll-contain hide-scrollbar; &.show { @apply translate-x-0 shadow-2xl sm:shadow-none; @@ -19,7 +19,7 @@ } .mask { - @apply fixed top-0 left-0 w-screen h-screen bg-black opacity-0 transition-opacity duration-300 pointer-events-none z-20 sm:hidden; + @apply fixed top-0 left-0 w-full h-full bg-black opacity-0 transition-opacity duration-300 pointer-events-none z-20 sm:hidden; &.show { @apply opacity-60 pointer-events-auto; diff --git a/web/src/pages/EmbedMemo.tsx b/web/src/pages/EmbedMemo.tsx new file mode 100644 index 00000000..a4f5b50a --- /dev/null +++ b/web/src/pages/EmbedMemo.tsx @@ -0,0 +1,65 @@ +import dayjs from "dayjs"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; +import { UNKNOWN_ID } from "../helpers/consts"; +import { useMemoStore } from "../store/module"; +import useLoading from "../hooks/useLoading"; +import toastHelper from "../components/Toast"; +import MemoContent from "../components/MemoContent"; +import MemoResources from "../components/MemoResources"; + +interface State { + memo: Memo; +} + +const EmbedMemo = () => { + const { i18n } = useTranslation(); + const params = useParams(); + const memoStore = useMemoStore(); + const [state, setState] = useState({ + memo: { + id: UNKNOWN_ID, + } as Memo, + }); + const loadingState = useLoading(); + + useEffect(() => { + const memoId = Number(params.memoId); + if (memoId && !isNaN(memoId)) { + memoStore + .fetchMemoById(memoId) + .then((memo) => { + setState({ + memo, + }); + loadingState.setFinish(); + }) + .catch((error) => { + console.error(error); + toastHelper.error(error.response.data.message); + }); + } + }, []); + + return ( +
+ {!loadingState.isLoading && ( +
+
+
+ {dayjs(state.memo.displayTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss")} + + @{state.memo.creator.nickname || state.memo.creator.username} + +
+ undefined} /> + +
+
+ )} +
+ ); +}; + +export default EmbedMemo; diff --git a/web/src/pages/Loading.tsx b/web/src/pages/Loading.tsx index d5a8fe4b..62bf326e 100644 --- a/web/src/pages/Loading.tsx +++ b/web/src/pages/Loading.tsx @@ -2,7 +2,7 @@ import Icon from "../components/Icon"; function Loading() { return ( -
+
diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index 13b084a8..c20eadfa 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -8,6 +8,7 @@ const Auth = lazy(() => import("../pages/Auth")); const Explore = lazy(() => import("../pages/Explore")); const Home = lazy(() => import("../pages/Home")); const MemoDetail = lazy(() => import("../pages/MemoDetail")); +const EmbedMemo = lazy(() => import("../pages/EmbedMemo")); const router = createBrowserRouter([ { @@ -96,6 +97,19 @@ const router = createBrowserRouter([ return null; }, }, + { + path: "/m/:memoId/embed", + element: , + loader: async () => { + try { + await initialGlobalState(); + await initialUserState(); + } catch (error) { + // do nth + } + return null; + }, + }, ]); export default router;