diff --git a/server/resource.go b/server/resource.go index ca067da3..cf04f3eb 100644 --- a/server/resource.go +++ b/server/resource.go @@ -1,6 +1,7 @@ package server import ( + "bytes" "encoding/json" "fmt" "io" @@ -267,18 +268,13 @@ func (s *Server) registerResourcePublicRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to fetch resource ID: %v", resourceID)).SetInternal(err) } + resourceType := resource.Type if strings.HasPrefix(resource.Type, "text") || strings.HasPrefix(resource.Type, "application") { - c.Response().Writer.Header().Set("Content-Type", echo.MIMETextPlain) - } else { - c.Response().Writer.Header().Set("Content-Type", resource.Type) + resourceType = echo.MIMETextPlain } - c.Response().Writer.WriteHeader(http.StatusOK) c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable") c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'self'") - if _, err := c.Response().Writer.Write(resource.Blob); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to write response").SetInternal(err) - } - return nil + return c.Stream(http.StatusOK, resourceType, bytes.NewReader(resource.Blob)) }) } diff --git a/web/src/components/DailyMemo.tsx b/web/src/components/DailyMemo.tsx index 38d1c7a7..00125945 100644 --- a/web/src/components/DailyMemo.tsx +++ b/web/src/components/DailyMemo.tsx @@ -21,7 +21,7 @@ const DailyMemo: React.FC = (props: Props) => {
- +
diff --git a/web/src/components/MemoResources.tsx b/web/src/components/MemoResources.tsx index 2b304cee..a614f8b0 100644 --- a/web/src/components/MemoResources.tsx +++ b/web/src/components/MemoResources.tsx @@ -1,23 +1,23 @@ -import Image from "./Image"; +import { absolutifyLink } from "../helpers/utils"; import Icon from "./Icon"; +import SquareDiv from "./common/SquareDiv"; +import showPreviewImageDialog from "./PreviewImageDialog"; import "../less/memo-resources.less"; interface Props { resourceList: Resource[]; className?: string; - style?: "row" | "col"; } const getDefaultProps = (): Props => { return { className: "", - style: "row", resourceList: [], }; }; const MemoResources: React.FC = (props: Props) => { - const { className, style, resourceList } = { + const { className, resourceList } = { ...getDefaultProps(), ...props, }; @@ -35,24 +35,39 @@ const MemoResources: React.FC = (props: Props) => { return `/o/r/${resource.id}/${resource.filename}`; }); + const handleImageClick = (imgUrl: string) => { + const index = imgUrls.findIndex((url) => url === imgUrl); + showPreviewImageDialog(imgUrls, index); + }; + return ( -
- {availableResourceList.length > 0 && ( -
- {availableResourceList.map((resource) => { - const url = `/o/r/${resource.id}/${resource.filename}`; - if (resource.type.startsWith("image")) { - return ( - item === url)} /> - ); - } else if (resource.type.startsWith("video")) { - return
- )} + <> +
+ {availableResourceList.length > 0 && ( +
+ {availableResourceList.map((resource) => { + const url = `/o/r/${resource.id}/${resource.filename}`; + if (resource.type.startsWith("image")) { + return ( + + handleImageClick(url)} decoding="async" loading="lazy" /> + + ); + } else if (resource.type.startsWith("video")) { + return ( + + + + ); + } else { + return null; + } + })} +
+ )} +
{otherResourceList.map((resource) => { return ( @@ -63,7 +78,7 @@ const MemoResources: React.FC = (props: Props) => { ); })}
-
+ ); }; diff --git a/web/src/components/ShareMemoDialog.tsx b/web/src/components/ShareMemoDialog.tsx index c7032cf9..ee5ee93b 100644 --- a/web/src/components/ShareMemoDialog.tsx +++ b/web/src/components/ShareMemoDialog.tsx @@ -135,7 +135,7 @@ const ShareMemoDialog: React.FC = (props: Props) => { {memo.createdAtStr}
- +
diff --git a/web/src/components/common/SquareDiv.tsx b/web/src/components/common/SquareDiv.tsx new file mode 100644 index 00000000..46c40c98 --- /dev/null +++ b/web/src/components/common/SquareDiv.tsx @@ -0,0 +1,45 @@ +import { useEffect, useRef } from "react"; + +interface Props { + children: React.ReactNode; + baseSide?: "width" | "height"; + className?: string; +} + +const SquareDiv: React.FC = (props: Props) => { + const { children, className } = props; + const baseSide = props.baseSide || "width"; + const squareDivRef = useRef(null); + + useEffect(() => { + const adjustSquareSize = () => { + if (!squareDivRef.current) { + return; + } + + if (baseSide === "width") { + const width = squareDivRef.current.clientWidth; + squareDivRef.current.style.height = width + "px"; + } else { + const height = squareDivRef.current.clientHeight; + squareDivRef.current.style.width = height + "px"; + } + }; + + adjustSquareSize(); + + window.addEventListener("resize", adjustSquareSize); + + return () => { + window.removeEventListener("resize", adjustSquareSize); + }; + }, []); + + return ( +
+ {children} +
+ ); +}; + +export default SquareDiv; diff --git a/web/src/less/daily-memo.less b/web/src/less/daily-memo.less index 4c59c507..67230a93 100644 --- a/web/src/less/daily-memo.less +++ b/web/src/less/daily-memo.less @@ -21,5 +21,11 @@ .memo-content-text { @apply mt-1; } + + > .resource-wrapper { + > .images-wrapper { + @apply !grid-cols-2; + } + } } } diff --git a/web/src/less/explore.less b/web/src/less/explore.less index 024f68d7..0b83fab7 100644 --- a/web/src/less/explore.less +++ b/web/src/less/explore.less @@ -1,11 +1,11 @@ .page-wrapper.explore { - @apply relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; + @apply 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-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; + @apply sticky top-0 z-10 max-w-2xl w-full h-auto flex flex-row justify-between backdrop-blur-sm items-center px-4 sm:pr-6 pt-6 mb-2 ml-calc; > .title-container { @apply flex flex-row justify-start items-center; @@ -27,7 +27,7 @@ } > .memos-wrapper { - @apply relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4 sm:pr-6 ml-calc; + @apply relative flex-grow max-w-2xl w-full h-auto flex flex-col justify-start items-start px-4 sm:pr-6 ml-calc; > .memo-container { @apply flex flex-col justify-start items-start w-full p-4 mt-2 bg-white dark:bg-zinc-700 rounded-lg border border-white dark:border-zinc-800 hover:border-gray-200 dark:hover:border-zinc-600; diff --git a/web/src/less/home.less b/web/src/less/home.less index a73a39ce..e51fbd92 100644 --- a/web/src/less/home.less +++ b/web/src/less/home.less @@ -1,5 +1,5 @@ .page-wrapper.home { - @apply relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800; + @apply 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; diff --git a/web/src/less/memo-resources.less b/web/src/less/memo-resources.less index 82e42478..5453377e 100644 --- a/web/src/less/memo-resources.less +++ b/web/src/less/memo-resources.less @@ -2,54 +2,34 @@ @apply w-full flex flex-col justify-start items-start; > .images-wrapper { - @apply w-full flex mt-2 pb-1; + @apply w-full grid grid-cols-2 sm:grid-cols-3 gap-2 mt-2; > .memo-resource { - @apply w-auto h-auto shrink-0 grow-0 cursor-pointer rounded; + @apply shadow overflow-auto hide-scrollbar; > img { - @apply rounded hover:shadow; + @apply cursor-pointer rounded min-h-full w-auto min-w-full object-cover; } - } - - &.row { - @apply flex-row justify-start items-start overflow-x-auto overflow-y-hidden; - - > .memo-resource { - @apply max-w-xs h-auto max-h-40 mr-2 last:mr-0; - - > img { - @apply w-auto max-h-40; - } - } - } - &.col { - @apply flex-col justify-start items-start; - - > .memo-resource { - @apply w-full h-auto mb-2 last:mb-0; - - > img { - @apply w-full h-auto shadow; - } + > video { + @apply cursor-pointer rounded w-full h-full object-contain bg-zinc-100 dark:bg-zinc-800; } } } +} - > .other-resource-wrapper { - @apply w-full flex flex-row justify-start flex-wrap; +.other-resource-wrapper { + @apply w-full flex flex-row justify-start flex-wrap; - > .other-resource-container { - @apply mt-1 mr-1 max-w-full flex flex-row justify-start items-center flex-nowrap bg-gray-100 px-2 py-1 rounded cursor-pointer hover:bg-gray-200; + > .other-resource-container { + @apply mt-1 mr-1 max-w-full flex flex-row justify-start items-center flex-nowrap bg-gray-100 px-2 py-1 rounded cursor-pointer hover:bg-gray-200; - > .icon-img { - @apply w-4 h-auto mr-1 text-gray-500; - } + > .icon-img { + @apply w-4 h-auto mr-1 text-gray-500; + } - > .name-text { - @apply text-gray-500 text-sm max-w-xs truncate font-mono; - } + > .name-text { + @apply text-gray-500 text-sm max-w-xs truncate font-mono; } } } diff --git a/web/src/less/share-memo-dialog.less b/web/src/less/share-memo-dialog.less index 39d71e6a..641dccc0 100644 --- a/web/src/less/share-memo-dialog.less +++ b/web/src/less/share-memo-dialog.less @@ -47,13 +47,11 @@ > .memo-content-wrapper { @apply w-full px-6 text-base pb-4; - } - - > .images-container { - @apply w-full h-auto flex flex-col justify-start items-start px-6 pb-2; - > img { - @apply w-full h-auto mb-2 rounded; + > .resource-wrapper { + > .images-wrapper { + @apply !grid-cols-2; + } } }