mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: add memo resources component
This commit is contained in:
@ -1,16 +1,16 @@
|
|||||||
import { memo, useEffect, useRef, useState } from "react";
|
|
||||||
import { indexOf } from "lodash-es";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import { indexOf } from "lodash-es";
|
||||||
|
import { memo, useEffect, useRef, useState } from "react";
|
||||||
import "dayjs/locale/zh";
|
import "dayjs/locale/zh";
|
||||||
import useI18n from "../hooks/useI18n";
|
import useI18n from "../hooks/useI18n";
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
import { UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { DONE_BLOCK_REG, formatMemoContent, IMAGE_URL_REG, TODO_BLOCK_REG } from "../helpers/marked";
|
import { DONE_BLOCK_REG, formatMemoContent, TODO_BLOCK_REG } from "../helpers/marked";
|
||||||
import { editorStateService, locationService, memoService, userService } from "../services";
|
import { editorStateService, locationService, memoService, userService } from "../services";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import Image from "./Image";
|
import MemoResources from "./MemoResources";
|
||||||
import showMemoCardDialog from "./MemoCardDialog";
|
import showMemoCardDialog from "./MemoCardDialog";
|
||||||
import showShareMemoImageDialog from "./ShareMemoImageDialog";
|
import showShareMemoImageDialog from "./ShareMemoImageDialog";
|
||||||
import "../less/memo.less";
|
import "../less/memo.less";
|
||||||
@ -46,7 +46,6 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
||||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
|
||||||
const isVisitorMode = userService.isVisitorMode();
|
const isVisitorMode = userService.isVisitorMode();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -239,13 +238,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Only when={imageUrls.length > 0}>
|
<MemoResources memo={memo} />
|
||||||
<div className="images-wrapper">
|
|
||||||
{imageUrls.map((imgUrl, idx) => (
|
|
||||||
<Image className="memo-img" key={idx} imgUrl={imgUrl} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Only>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,13 +3,13 @@ import { editorStateService, memoService, userService } from "../services";
|
|||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
import { UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { formatMemoContent, IMAGE_URL_REG, MEMO_LINK_REG, parseHtmlToRawText } from "../helpers/marked";
|
import { formatMemoContent, MEMO_LINK_REG, parseHtmlToRawText } from "../helpers/marked";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import Image from "./Image";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Selector from "./common/Selector";
|
import Selector from "./common/Selector";
|
||||||
|
import MemoResources from "./MemoResources";
|
||||||
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
||||||
import "../less/memo-card-dialog.less";
|
import "../less/memo-card-dialog.less";
|
||||||
|
|
||||||
@ -29,7 +29,6 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
|||||||
});
|
});
|
||||||
const [linkMemos, setLinkMemos] = useState<LinkedMemo[]>([]);
|
const [linkMemos, setLinkMemos] = useState<LinkedMemo[]>([]);
|
||||||
const [linkedMemos, setLinkedMemos] = useState<LinkedMemo[]>([]);
|
const [linkedMemos, setLinkedMemos] = useState<LinkedMemo[]>([]);
|
||||||
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLinkedMemos = async () => {
|
const fetchLinkedMemos = async () => {
|
||||||
@ -167,13 +166,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
|||||||
onClick={handleMemoContentClick}
|
onClick={handleMemoContentClick}
|
||||||
dangerouslySetInnerHTML={{ __html: formatMemoContent(memo.content) }}
|
dangerouslySetInnerHTML={{ __html: formatMemoContent(memo.content) }}
|
||||||
></div>
|
></div>
|
||||||
<Only when={imageUrls.length > 0}>
|
<MemoResources memo={memo} />
|
||||||
<div className="images-wrapper">
|
|
||||||
{imageUrls.map((imgUrl, idx) => (
|
|
||||||
<Image className="memo-img" key={idx} imgUrl={imgUrl} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Only>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="layer-container"></div>
|
<div className="layer-container"></div>
|
||||||
{linkMemos.map((_, idx) => {
|
{linkMemos.map((_, idx) => {
|
||||||
|
28
web/src/components/MemoResources.tsx
Normal file
28
web/src/components/MemoResources.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { IMAGE_URL_REG } from "../helpers/marked";
|
||||||
|
import Only from "./common/OnlyWhen";
|
||||||
|
import Image from "./Image";
|
||||||
|
import "../less/memo-resources.less";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
memo: Memo;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemoResources: React.FC<Props> = (props: Props) => {
|
||||||
|
const { className, memo } = props;
|
||||||
|
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resource-wrapper">
|
||||||
|
<Only when={imageUrls.length > 0}>
|
||||||
|
<div className={`images-wrapper ${className ?? ""}`}>
|
||||||
|
{imageUrls.map((imgUrl, idx) => (
|
||||||
|
<Image className="memo-img" key={idx} imgUrl={imgUrl} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Only>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MemoResources;
|
@ -35,12 +35,12 @@ const Sidebar = () => {
|
|||||||
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
||||||
</button>
|
</button>
|
||||||
<Only when={!userService.isVisitorMode()}>
|
<Only when={!userService.isVisitorMode()}>
|
||||||
<button className="btn action-btn" onClick={handleSettingBtnClick}>
|
|
||||||
<span className="icon">⚙️</span> {t("sidebar.setting")}
|
|
||||||
</button>
|
|
||||||
<button className="btn action-btn" onClick={() => handleExploreBtnClick()}>
|
<button className="btn action-btn" onClick={() => handleExploreBtnClick()}>
|
||||||
<span className="icon">🏂</span> {t("common.explore")}
|
<span className="icon">🏂</span> {t("common.explore")}
|
||||||
</button>
|
</button>
|
||||||
|
<button className="btn action-btn" onClick={handleSettingBtnClick}>
|
||||||
|
<span className="icon">⚙️</span> {t("sidebar.setting")}
|
||||||
|
</button>
|
||||||
</Only>
|
</Only>
|
||||||
</div>
|
</div>
|
||||||
<Only when={!userService.isVisitorMode()}>
|
<Only when={!userService.isVisitorMode()}>
|
||||||
|
@ -61,23 +61,6 @@
|
|||||||
> .memo-content-text {
|
> .memo-content-text {
|
||||||
@apply w-full text-base;
|
@apply w-full text-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .images-wrapper {
|
|
||||||
.flex(row, flex-start, flex-start);
|
|
||||||
@apply w-full mt-2 overflow-x-auto overflow-y-hidden;
|
|
||||||
padding-bottom: 2px;
|
|
||||||
.pretty-scroll-bar(0, 2px);
|
|
||||||
|
|
||||||
> .memo-img {
|
|
||||||
@apply mr-2 w-auto h-32 shrink-0 grow-0 overflow-y-hidden hover:border-gray-400 last:mr-0;
|
|
||||||
.hide-scroll-bar();
|
|
||||||
|
|
||||||
> img {
|
|
||||||
@apply w-auto rounded-lg;
|
|
||||||
max-height: 128px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .normal-text {
|
> .normal-text {
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .tag-list {
|
> .tag-list {
|
||||||
@apply hidden flex-col justify-start items-start absolute top-6 left-0 mt-1 p-1 z-1 rounded w-32 max-h-52 overflow-auto bg-black;
|
@apply hidden flex-col justify-start items-start absolute top-6 left-0 mt-1 p-1 z-1 rounded w-32 max-h-52 overflow-auto font-mono bg-black;
|
||||||
|
|
||||||
> .item-container {
|
> .item-container {
|
||||||
@apply w-full text-white cursor-pointer rounded text-sm leading-6 px-2 hover:bg-gray-700;
|
@apply w-full text-white cursor-pointer rounded text-sm leading-6 px-2 hover:bg-gray-700;
|
||||||
|
19
web/src/less/memo-resources.less
Normal file
19
web/src/less/memo-resources.less
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
@import "./mixin.less";
|
||||||
|
|
||||||
|
.resource-wrapper {
|
||||||
|
@apply w-full flex flex-col justify-start items-start;
|
||||||
|
|
||||||
|
> .images-wrapper {
|
||||||
|
@apply flex flex-row justify-start items-start mt-2 w-full overflow-x-auto overflow-y-hidden pb-1;
|
||||||
|
.pretty-scroll-bar(0, 2px);
|
||||||
|
|
||||||
|
> .memo-img {
|
||||||
|
@apply mr-2 last:mr-0 w-auto h-auto shrink-0 grow-0 overflow-y-hidden;
|
||||||
|
.hide-scroll-bar();
|
||||||
|
|
||||||
|
> img {
|
||||||
|
@apply w-auto max-h-40 rounded-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -140,18 +140,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .images-wrapper {
|
|
||||||
@apply flex flex-row justify-start items-start mt-2 w-full overflow-x-auto overflow-y-hidden pb-1;
|
|
||||||
.pretty-scroll-bar(0, 2px);
|
|
||||||
|
|
||||||
> .memo-img {
|
|
||||||
@apply mr-2 last:mr-0 w-auto h-auto shrink-0 grow-0 overflow-y-hidden;
|
|
||||||
.hide-scroll-bar();
|
|
||||||
|
|
||||||
> img {
|
|
||||||
@apply w-auto max-h-40 rounded-lg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { useAppSelector } from "../store";
|
|||||||
import useI18n from "../hooks/useI18n";
|
import useI18n from "../hooks/useI18n";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import MemoContent from "../components/MemoContent";
|
import MemoContent from "../components/MemoContent";
|
||||||
|
import MemoResources from "../components/MemoResources";
|
||||||
import "../less/explore.less";
|
import "../less/explore.less";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -72,6 +73,7 @@ const Explore = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<MemoContent className="memo-content" content={memo.content} onMemoContentClick={() => undefined} />
|
<MemoContent className="memo-content" content={memo.content} onMemoContentClick={() => undefined} />
|
||||||
|
<MemoResources memo={memo} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
Reference in New Issue
Block a user