mirror of
https://github.com/usememos/memos.git
synced 2025-02-19 04:40:40 +01:00
feat: fold memo when content overflow (#1327)
* feat: fold memo when content overflow * chore: update
This commit is contained in:
parent
8c774316ae
commit
ccdcd3d154
@ -1,5 +1,5 @@
|
|||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import MemoContent, { DisplayConfig } from "./MemoContent";
|
import MemoContent from "./MemoContent";
|
||||||
import MemoResources from "./MemoResources";
|
import MemoResources from "./MemoResources";
|
||||||
import "../less/daily-memo.less";
|
import "../less/daily-memo.less";
|
||||||
|
|
||||||
@ -10,9 +10,6 @@ interface Props {
|
|||||||
const DailyMemo: React.FC<Props> = (props: Props) => {
|
const DailyMemo: React.FC<Props> = (props: Props) => {
|
||||||
const { memo } = props;
|
const { memo } = props;
|
||||||
const createdTimeStr = utils.getTimeString(memo.createdTs);
|
const createdTimeStr = utils.getTimeString(memo.createdTs);
|
||||||
const displayConfig: DisplayConfig = {
|
|
||||||
enableExpand: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="daily-memo-wrapper">
|
<div className="daily-memo-wrapper">
|
||||||
@ -20,7 +17,7 @@ const DailyMemo: React.FC<Props> = (props: Props) => {
|
|||||||
<span className="normal-text">{createdTimeStr}</span>
|
<span className="normal-text">{createdTimeStr}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="memo-container">
|
<div className="memo-container">
|
||||||
<MemoContent content={memo.content} displayConfig={displayConfig} />
|
<MemoContent content={memo.content} showFull={true} />
|
||||||
<MemoResources resourceList={memo.resourceList} />
|
<MemoResources resourceList={memo.resourceList} />
|
||||||
</div>
|
</div>
|
||||||
<div className="split-line"></div>
|
<div className="split-line"></div>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { memo, useEffect, useRef, useState } from "react";
|
import { memo, useEffect, useRef, useState } from "react";
|
||||||
@ -39,7 +38,6 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
const [createdTimeStr, setCreatedTimeStr] = useState<string>(getFormatedMemoTimeStr(memo.createdTs, i18n.language));
|
const [createdTimeStr, setCreatedTimeStr] = useState<string>(getFormatedMemoTimeStr(memo.createdTs, i18n.language));
|
||||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const isVisitorMode = userStore.isVisitorMode() || readonly;
|
const isVisitorMode = userStore.isVisitorMode() || readonly;
|
||||||
const updatedTimeStr = getFormatedMemoTimeStr(memo.updatedTs, i18n.language);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let intervalFlag: any = -1;
|
let intervalFlag: any = -1;
|
||||||
@ -111,7 +109,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGenMemoImageBtnClick = () => {
|
const handleGenerateMemoImageBtnClick = () => {
|
||||||
showShareMemo(memo);
|
showShareMemo(memo);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -203,11 +201,9 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
{memo.pinned && <div className="corner-container"></div>}
|
{memo.pinned && <div className="corner-container"></div>}
|
||||||
<div className="memo-top-wrapper">
|
<div className="memo-top-wrapper">
|
||||||
<div className="status-text-container">
|
<div className="status-text-container">
|
||||||
<Tooltip title={`Updated at ${updatedTimeStr}`} placement="top" arrow>
|
<span className="time-text" onDoubleClick={handleMemoCreatedTimeClick}>
|
||||||
<span className="time-text" onDoubleClick={handleMemoCreatedTimeClick}>
|
{createdTimeStr}
|
||||||
{createdTimeStr}
|
</span>
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
{isVisitorMode && (
|
{isVisitorMode && (
|
||||||
<a className="name-text" href={`/u/${memo.creatorId}`}>
|
<a className="name-text" href={`/u/${memo.creatorId}`}>
|
||||||
@{memo.creatorName}
|
@{memo.creatorName}
|
||||||
@ -238,7 +234,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
<Icon.Edit3 className="icon-img" />
|
<Icon.Edit3 className="icon-img" />
|
||||||
<span className="tip-text">{t("common.edit")}</span>
|
<span className="tip-text">{t("common.edit")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
<div className="btn" onClick={handleGenerateMemoImageBtnClick}>
|
||||||
<Icon.Share className="icon-img" />
|
<Icon.Share className="icon-img" />
|
||||||
<span className="tip-text">{t("common.share")}</span>
|
<span className="tip-text">{t("common.share")}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useUserStore } from "../store/module";
|
|
||||||
import { marked } from "../labs/marked";
|
import { marked } from "../labs/marked";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import "../less/memo-content.less";
|
import "../less/memo-content.less";
|
||||||
|
|
||||||
export interface DisplayConfig {
|
const MAX_EXPAND_HEIGHT = 384;
|
||||||
enableExpand: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
content: string;
|
content: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
displayConfig?: Partial<DisplayConfig>;
|
showFull?: boolean;
|
||||||
onMemoContentClick?: (e: React.MouseEvent) => void;
|
onMemoContentClick?: (e: React.MouseEvent) => void;
|
||||||
onMemoContentDoubleClick?: (e: React.MouseEvent) => void;
|
onMemoContentDoubleClick?: (e: React.MouseEvent) => void;
|
||||||
}
|
}
|
||||||
@ -23,48 +20,29 @@ interface State {
|
|||||||
expandButtonStatus: ExpandButtonStatus;
|
expandButtonStatus: ExpandButtonStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultDisplayConfig: DisplayConfig = {
|
|
||||||
enableExpand: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MemoContent: React.FC<Props> = (props: Props) => {
|
const MemoContent: React.FC<Props> = (props: Props) => {
|
||||||
const { className, content, onMemoContentClick, onMemoContentDoubleClick } = props;
|
const { className, content, showFull, onMemoContentClick, onMemoContentDoubleClick } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const userStore = useUserStore();
|
|
||||||
const user = userStore.state.user;
|
|
||||||
const foldedContent = useMemo(() => {
|
|
||||||
const firstHorizontalRuleIndex = content.search(/^---$|^\*\*\*$|^___$/m);
|
|
||||||
return firstHorizontalRuleIndex !== -1 ? content.slice(0, firstHorizontalRuleIndex) : content;
|
|
||||||
}, [content]);
|
|
||||||
|
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
expandButtonStatus: -1,
|
expandButtonStatus: -1,
|
||||||
});
|
});
|
||||||
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const displayConfig = {
|
|
||||||
...defaultDisplayConfig,
|
|
||||||
...props.displayConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!memoContentContainerRef) {
|
if (showFull) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayConfig.enableExpand && user && user.localSetting.enableFoldMemo) {
|
if (memoContentContainerRef.current) {
|
||||||
if (foldedContent.length !== content.length) {
|
const height = memoContentContainerRef.current.clientHeight;
|
||||||
|
if (height > MAX_EXPAND_HEIGHT) {
|
||||||
setState({
|
setState({
|
||||||
...state,
|
|
||||||
expandButtonStatus: 0,
|
expandButtonStatus: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
expandButtonStatus: -1,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [user?.localSetting.enableFoldMemo, content]);
|
}, []);
|
||||||
|
|
||||||
const handleMemoContentClick = async (e: React.MouseEvent) => {
|
const handleMemoContentClick = async (e: React.MouseEvent) => {
|
||||||
if (onMemoContentClick) {
|
if (onMemoContentClick) {
|
||||||
@ -89,17 +67,18 @@ const MemoContent: React.FC<Props> = (props: Props) => {
|
|||||||
<div className={`memo-content-wrapper ${className || ""}`}>
|
<div className={`memo-content-wrapper ${className || ""}`}>
|
||||||
<div
|
<div
|
||||||
ref={memoContentContainerRef}
|
ref={memoContentContainerRef}
|
||||||
className={`memo-content-text ${state.expandButtonStatus === 0 ? "expanded" : ""}`}
|
className={`memo-content-text ${state.expandButtonStatus === 0 ? "max-h-64 overflow-y-hidden" : ""}`}
|
||||||
onClick={handleMemoContentClick}
|
onClick={handleMemoContentClick}
|
||||||
onDoubleClick={handleMemoContentDoubleClick}
|
onDoubleClick={handleMemoContentDoubleClick}
|
||||||
>
|
>
|
||||||
{marked(state.expandButtonStatus === 0 ? foldedContent : content)}
|
{marked(content)}
|
||||||
</div>
|
</div>
|
||||||
{state.expandButtonStatus !== -1 && (
|
{state.expandButtonStatus !== -1 && (
|
||||||
<div className="expand-btn-container">
|
<div className={`expand-btn-container ${state.expandButtonStatus === 0 && "!-mt-7"}`}>
|
||||||
<span className={`btn ${state.expandButtonStatus === 0 ? "expand-btn" : "fold-btn"}`} onClick={handleExpandBtnClick}>
|
<div className="absolute top-0 left-0 w-full h-full blur-lg bg-white"></div>
|
||||||
|
<span className={`btn z-10 ${state.expandButtonStatus === 0 ? "expand-btn" : "fold-btn"}`} onClick={handleExpandBtnClick}>
|
||||||
{state.expandButtonStatus === 0 ? t("common.expand") : t("common.fold")}
|
{state.expandButtonStatus === 0 ? t("common.expand") : t("common.fold")}
|
||||||
<Icon.ChevronRight className="icon-img" />
|
<Icon.ChevronRight className="icon-img opacity-80" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -40,10 +40,6 @@ const PreferencesSection = () => {
|
|||||||
await userStore.upsertUserSetting("resourceVisibility", value);
|
await userStore.upsertUserSetting("resourceVisibility", value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIsFoldingEnabledChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
userStore.upsertLocalSetting({ ...localSetting, enableFoldMemo: event.target.checked });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDoubleClickEnabledChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleDoubleClickEnabledChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
userStore.upsertLocalSetting({ ...localSetting, enableDoubleClickEditing: event.target.checked });
|
userStore.upsertLocalSetting({ ...localSetting, enableDoubleClickEditing: event.target.checked });
|
||||||
};
|
};
|
||||||
@ -131,10 +127,6 @@ const PreferencesSection = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="form-label selector">
|
|
||||||
<span className="normal-text">{t("setting.preference-section.enable-folding-memo")}</span>
|
|
||||||
<Switch className="ml-2" checked={localSetting.enableFoldMemo} onChange={handleIsFoldingEnabledChanged} />
|
|
||||||
</label>
|
|
||||||
<label className="form-label selector">
|
<label className="form-label selector">
|
||||||
<span className="normal-text">{t("setting.preference-section.enable-double-click")}</span>
|
<span className="normal-text">{t("setting.preference-section.enable-double-click")}</span>
|
||||||
<Switch className="ml-2" checked={localSetting.enableDoubleClickEditing} onChange={handleDoubleClickEnabledChanged} />
|
<Switch className="ml-2" checked={localSetting.enableDoubleClickEditing} onChange={handleDoubleClickEnabledChanged} />
|
||||||
|
@ -126,7 +126,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||||||
<div className="memo-container" ref={memoElRef}>
|
<div className="memo-container" ref={memoElRef}>
|
||||||
<span className="time-text">{memo.createdAtStr}</span>
|
<span className="time-text">{memo.createdAtStr}</span>
|
||||||
<div className="memo-content-wrapper">
|
<div className="memo-content-wrapper">
|
||||||
<MemoContent content={memo.content} displayConfig={{ enableExpand: false }} />
|
<MemoContent content={memo.content} showFull={true} />
|
||||||
<MemoResources resourceList={memo.resourceList} />
|
<MemoResources resourceList={memo.resourceList} />
|
||||||
</div>
|
</div>
|
||||||
<div className="watermark-container">
|
<div className="watermark-container">
|
||||||
|
@ -110,11 +110,9 @@
|
|||||||
@apply w-full relative flex flex-row justify-start items-center;
|
@apply w-full relative flex flex-row justify-start items-center;
|
||||||
|
|
||||||
> .btn {
|
> .btn {
|
||||||
@apply flex flex-row justify-start items-center pl-2 pr-1 py-1 my-1 text-xs rounded-lg border bg-gray-100 dark:bg-zinc-600 border-gray-200 dark:border-zinc-600 opacity-80 shadow hover:opacity-60 cursor-pointer;
|
@apply flex flex-row justify-start items-center pl-2 pr-1 py-1 my-2 text-xs rounded-lg border bg-gray-100 dark:bg-zinc-600 border-gray-200 dark:border-zinc-600 shadow hover:opacity-90 cursor-pointer;
|
||||||
|
|
||||||
&.expand-btn {
|
&.expand-btn {
|
||||||
@apply mt-2;
|
|
||||||
|
|
||||||
> .icon-img {
|
> .icon-img {
|
||||||
@apply rotate-90;
|
@apply rotate-90;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ const defaultSetting: Setting = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const defaultLocalSetting: LocalSetting = {
|
const defaultLocalSetting: LocalSetting = {
|
||||||
enableFoldMemo: true,
|
|
||||||
enableDoubleClickEditing: true,
|
enableDoubleClickEditing: true,
|
||||||
dailyReviewTimeOffset: 0,
|
dailyReviewTimeOffset: 0,
|
||||||
};
|
};
|
||||||
|
1
web/src/types/modules/setting.d.ts
vendored
1
web/src/types/modules/setting.d.ts
vendored
@ -8,7 +8,6 @@ interface Setting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface LocalSetting {
|
interface LocalSetting {
|
||||||
enableFoldMemo: boolean;
|
|
||||||
enableDoubleClickEditing: boolean;
|
enableDoubleClickEditing: boolean;
|
||||||
dailyReviewTimeOffset: number;
|
dailyReviewTimeOffset: number;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user