feat: display memo with updated ts (#1760)

pull/1761/head
boojack 2 years ago committed by GitHub
parent 826541a714
commit e0e59c5831
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -34,6 +34,7 @@ type MemoResponse struct {
UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields
DisplayTs int64 `json:"displayTs"`
Content string `json:"content"`
Visibility Visibility `json:"visibility"`
Pinned bool `json:"pinned"`

@ -24,6 +24,8 @@ type SystemStatus struct {
CustomizedProfile CustomizedProfile `json:"customizedProfile"`
// Storage service ID.
StorageServiceID int `json:"storageServiceId"`
// Local storage path
// Local storage path.
LocalStoragePath string `json:"localStoragePath"`
// Memo display with updated timestamp.
MemoDisplayWithUpdatedTs bool `json:"memoDisplayWithUpdatedTs"`
}

@ -36,7 +36,8 @@ const (
// SystemSettingOpenAIConfigName is the name of OpenAI config.
SystemSettingOpenAIConfigName SystemSettingName = "openai-config"
// SystemSettingTelegramRobotToken is the name of Telegram Robot Token.
SystemSettingTelegramRobotTokenName SystemSettingName = "telegram-robot-token"
SystemSettingTelegramRobotTokenName SystemSettingName = "telegram-robot-token"
SystemSettingMemoDisplayWithUpdatedTsName SystemSettingName = "memo-display-with-updated-ts"
)
// CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
@ -88,6 +89,8 @@ func (key SystemSettingName) String() string {
return "openai-config"
case SystemSettingTelegramRobotTokenName:
return "telegram-robot-token"
case SystemSettingMemoDisplayWithUpdatedTsName:
return "memo-display-with-updated-ts"
}
return ""
}
@ -111,43 +114,36 @@ func (upsert SystemSettingUpsert) Validate() error {
switch settingName := upsert.Name; settingName {
case SystemSettingServerIDName:
return fmt.Errorf("updating %v is not allowed", settingName)
case SystemSettingAllowSignUpName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingIgnoreUpgradeName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingDisablePublicMemosName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingMaxUploadSizeMiBName:
var value int
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingAdditionalStyleName:
var value string
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingAdditionalScriptName:
var value string
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingCustomizedProfileName:
customizedProfile := CustomizedProfile{
Name: "memos",
@ -157,7 +153,6 @@ func (upsert SystemSettingUpsert) Validate() error {
Appearance: "system",
ExternalURL: "",
}
if err := json.Unmarshal([]byte(upsert.Value), &customizedProfile); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
@ -167,26 +162,22 @@ func (upsert SystemSettingUpsert) Validate() error {
if !slices.Contains(UserSettingAppearanceValue, customizedProfile.Appearance) {
return fmt.Errorf(`invalid appearance value for system setting "%v"`, settingName)
}
case SystemSettingStorageServiceIDName:
value := DatabaseStorage
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
return nil
case SystemSettingLocalStoragePathName:
value := ""
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingOpenAIConfigName:
value := OpenAIConfig{}
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingTelegramRobotTokenName:
if upsert.Value == "" {
return nil
@ -195,11 +186,14 @@ func (upsert SystemSettingUpsert) Validate() error {
if len(fragments) != 2 {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingMemoDisplayWithUpdatedTsName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
default:
return fmt.Errorf("invalid system setting name")
}
return nil
}

@ -609,19 +609,39 @@ func (s *Server) composeMemoMessageToMemoResponse(ctx context.Context, memoMessa
Pinned: memoMessage.Pinned,
}
// Compose creator name.
user, err := s.Store.FindUser(ctx, &api.UserFind{
ID: &memoResponse.CreatorID,
})
if err != nil {
return nil, err
}
if user.Nickname != "" {
memoResponse.CreatorName = user.Nickname
} else {
memoResponse.CreatorName = user.Username
}
// Compose display ts.
memoResponse.DisplayTs = memoResponse.CreatedTs
// Find memo display with updated ts setting.
memoDisplayWithUpdatedTsSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{
Name: api.SystemSettingMemoDisplayWithUpdatedTsName,
})
if err != nil && common.ErrorCode(err) != common.NotFound {
return nil, errors.Wrap(err, "failed to find system setting")
}
if memoDisplayWithUpdatedTsSetting != nil {
memoDisplayWithUpdatedTs := false
err = json.Unmarshal([]byte(memoDisplayWithUpdatedTsSetting.Value), &memoDisplayWithUpdatedTs)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal system setting value")
}
if memoDisplayWithUpdatedTs {
memoResponse.DisplayTs = memoResponse.UpdatedTs
}
}
relationList := []*api.MemoRelation{}
for _, relation := range memoMessage.RelationList {
relationList = append(relationList, convertMemoRelationMessageToMemoRelation(relation))

@ -55,8 +55,9 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
Appearance: "system",
ExternalURL: "",
},
StorageServiceID: api.DatabaseStorage,
LocalStoragePath: "assets/{timestamp}_{filename}",
StorageServiceID: api.DatabaseStorage,
LocalStoragePath: "assets/{timestamp}_{filename}",
MemoDisplayWithUpdatedTs: false,
}
systemSettingList, err := s.Store.FindSystemSettingList(ctx, &api.SystemSettingFind{})
@ -78,35 +79,28 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
switch systemSetting.Name {
case api.SystemSettingAllowSignUpName:
systemStatus.AllowSignUp = baseValue.(bool)
case api.SystemSettingIgnoreUpgradeName:
systemStatus.IgnoreUpgrade = baseValue.(bool)
case api.SystemSettingDisablePublicMemosName:
systemStatus.DisablePublicMemos = baseValue.(bool)
case api.SystemSettingMaxUploadSizeMiBName:
systemStatus.MaxUploadSizeMiB = int(baseValue.(float64))
case api.SystemSettingAdditionalStyleName:
systemStatus.AdditionalStyle = baseValue.(string)
case api.SystemSettingAdditionalScriptName:
systemStatus.AdditionalScript = baseValue.(string)
case api.SystemSettingCustomizedProfileName:
customizedProfile := api.CustomizedProfile{}
if err := json.Unmarshal([]byte(systemSetting.Value), &customizedProfile); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting customized profile value").SetInternal(err)
}
systemStatus.CustomizedProfile = customizedProfile
case api.SystemSettingStorageServiceIDName:
systemStatus.StorageServiceID = int(baseValue.(float64))
case api.SystemSettingLocalStoragePathName:
systemStatus.LocalStoragePath = baseValue.(string)
case api.SystemSettingMemoDisplayWithUpdatedTsName:
systemStatus.MemoDisplayWithUpdatedTs = baseValue.(bool)
default:
log.Warn("Unknown system setting name", zap.String("setting name", systemSetting.Name.String()))
}

@ -9,12 +9,12 @@ interface Props {
const DailyMemo: React.FC<Props> = (props: Props) => {
const { memo } = props;
const createdTimeStr = getTimeString(memo.createdTs);
const displayTimeStr = getTimeString(memo.displayTs);
return (
<div className="daily-memo-wrapper">
<div className="time-wrapper">
<span className="normal-text">{createdTimeStr}</span>
<span className="normal-text">{displayTimeStr}</span>
</div>
<div className="memo-container">
<MemoContent content={memo.content} showFull={true} />

@ -33,7 +33,7 @@ const Memo: React.FC<Props> = (props: Props) => {
const userStore = useUserStore();
const memoStore = useMemoStore();
const memoCacheStore = useMemoCacheStore();
const [createdTimeStr, setCreatedTimeStr] = useState<string>(getRelativeTimeString(memo.createdTs));
const [createdTimeStr, setCreatedTimeStr] = useState<string>(getRelativeTimeString(memo.displayTs));
const [relatedMemoList, setRelatedMemoList] = useState<Memo[]>([]);
const memoContainerRef = useRef<HTMLDivElement>(null);
const isVisitorMode = userStore.isVisitorMode() || readonly;
@ -54,9 +54,9 @@ const Memo: React.FC<Props> = (props: Props) => {
useEffect(() => {
let intervalFlag: any = -1;
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
if (Date.now() - memo.displayTs < 1000 * 60 * 60 * 24) {
intervalFlag = setInterval(() => {
setCreatedTimeStr(getRelativeTimeString(memo.createdTs));
setCreatedTimeStr(getRelativeTimeString(memo.displayTs));
}, 1000 * 1);
}

@ -56,7 +56,7 @@ const MemoList = () => {
if (
duration &&
duration.from < duration.to &&
(getTimeStampByDate(memo.createdTs) < duration.from || getTimeStampByDate(memo.createdTs) > duration.to)
(getTimeStampByDate(memo.displayTs) < duration.from || getTimeStampByDate(memo.displayTs) > duration.to)
) {
shouldShow = false;
}
@ -82,7 +82,7 @@ const MemoList = () => {
const pinnedMemos = shownMemos.filter((m) => m.pinned);
const unpinnedMemos = shownMemos.filter((m) => !m.pinned);
const memoSort = (mi: Memo, mj: Memo) => {
return mj.createdTs - mi.createdTs;
return mj.displayTs - mi.displayTs;
};
pinnedMemos.sort(memoSort);
unpinnedMemos.sort(memoSort);
@ -168,7 +168,7 @@ const MemoList = () => {
return (
<div className="memo-list-container">
{sortedMemos.map((memo) => (
<Memo key={`${memo.id}-${memo.createdTs}`} memo={memo} />
<Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} />
))}
{isFetching ? (
<div className="status-text-container fetching-tip">

@ -17,6 +17,7 @@ interface State {
additionalStyle: string;
additionalScript: string;
maxUploadSizeMiB: number;
memoDisplayWithUpdatedTs: boolean;
}
const SystemSection = () => {
@ -31,6 +32,7 @@ const SystemSection = () => {
additionalScript: systemStatus.additionalScript,
disablePublicMemos: systemStatus.disablePublicMemos,
maxUploadSizeMiB: systemStatus.maxUploadSizeMiB,
memoDisplayWithUpdatedTs: systemStatus.memoDisplayWithUpdatedTs,
});
const [telegramRobotToken, setTelegramRobotToken] = useState<string>("");
const [openAIConfig, setOpenAIConfig] = useState<OpenAIConfig>({
@ -65,6 +67,7 @@ const SystemSection = () => {
additionalScript: systemStatus.additionalScript,
disablePublicMemos: systemStatus.disablePublicMemos,
maxUploadSizeMiB: systemStatus.maxUploadSizeMiB,
memoDisplayWithUpdatedTs: systemStatus.memoDisplayWithUpdatedTs,
});
}, [systemStatus]);
@ -202,6 +205,18 @@ const SystemSection = () => {
});
};
const handleMemoDisplayWithUpdatedTs = async (value: boolean) => {
setState({
...state,
memoDisplayWithUpdatedTs: value,
});
globalStore.setSystemStatus({ disablePublicMemos: value });
await api.upsertSystemSetting({
name: "memo-display-with-updated-ts",
value: JSON.stringify(value),
});
};
const handleMaxUploadSizeChanged = async (event: React.FocusEvent<HTMLInputElement>) => {
// fixes cursor skipping position on mobile
event.target.selectionEnd = event.target.value.length;
@ -254,6 +269,10 @@ const SystemSection = () => {
<span className="normal-text">{t("setting.system-section.disable-public-memos")}</span>
<Switch checked={state.disablePublicMemos} onChange={(event) => handleDisablePublicMemosChanged(event.target.checked)} />
</div>
<div className="form-label">
<span className="normal-text">Display with updated time</span>
<Switch checked={state.memoDisplayWithUpdatedTs} onChange={(event) => handleMemoDisplayWithUpdatedTs(event.target.checked)} />
</div>
<div className="form-label">
<div className="flex flex-row items-center">
<span className="text-sm mr-1">{t("setting.system-section.max-upload-size")}</span>

@ -46,7 +46,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
const memoElRef = useRef<HTMLDivElement>(null);
const memo = {
...propsMemo,
createdAtStr: getDateTimeString(propsMemo.createdTs),
displayTsStr: getDateTimeString(propsMemo.displayTs),
};
const createdDays = Math.ceil((Date.now() - getTimeStampByDate(user.createdTs)) / 1000 / 3600 / 24);
@ -174,7 +174,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
className="w-full h-auto select-none relative flex flex-col justify-start items-start bg-white dark:bg-zinc-800"
ref={memoElRef}
>
<span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{memo.createdAtStr}</span>
<span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{memo.displayTsStr}</span>
<div className="w-full px-6 text-base pb-4">
<MemoContent content={memo.content} showFull={true} />
<MemoResourceListView className="!grid-cols-2" resourceList={memo.resourceList} />

@ -203,9 +203,9 @@ export const checkShouldShowMemo = (memo: Memo, filter: Filter) => {
}
} else if (type === "DISPLAY_TIME") {
if (operator === "BEFORE") {
return memo.createdTs < getUnixTimeMillis(value);
return memo.displayTs < getUnixTimeMillis(value);
} else {
return memo.createdTs >= getUnixTimeMillis(value);
return memo.displayTs >= getUnixTimeMillis(value);
}
} else if (type === "VISIBILITY") {
let matched = memo.visibility === value;

@ -28,15 +28,15 @@ const DailyReview = () => {
const currentDate = new Date(currentDateStamp);
const dailyMemos = memos
.filter((m) => {
const createdTimestamp = getTimeStampByDate(m.createdTs);
const displayTimestamp = getTimeStampByDate(m.displayTs);
const currentDateStampWithOffset = currentDateStamp + convertToMillis(localSetting);
return (
m.rowStatus === "NORMAL" &&
createdTimestamp >= currentDateStampWithOffset &&
createdTimestamp < currentDateStampWithOffset + DAILY_TIMESTAMP
displayTimestamp >= currentDateStampWithOffset &&
displayTimestamp < currentDateStampWithOffset + DAILY_TIMESTAMP
);
})
.sort((a, b) => getTimeStampByDate(a.createdTs) - getTimeStampByDate(b.createdTs));
.sort((a, b) => getTimeStampByDate(a.displayTs) - getTimeStampByDate(b.displayTs));
useEffect(() => {
let offset = 0;
@ -46,7 +46,7 @@ const DailyReview = () => {
offset += fetchedMemos.length;
if (fetchedMemos.length === DEFAULT_MEMO_LIMIT) {
const lastMemo = last(fetchedMemos);
if (lastMemo && lastMemo.createdTs > currentDateStamp) {
if (lastMemo && lastMemo.displayTs > currentDateStamp) {
await fetchMoreMemos();
}
}

@ -45,7 +45,7 @@ const EmbedMemo = () => {
<main className="w-full max-w-lg mx-auto my-auto shadow px-4 py-4 rounded-lg">
<div className="w-full flex flex-col justify-start items-start">
<div className="w-full mb-2 flex flex-row justify-start items-center text-sm text-gray-400 dark:text-gray-300">
<span>{getDateTimeString(state.memo.createdTs)}</span>
<span>{getDateTimeString(state.memo.displayTs)}</span>
<a className="ml-2 hover:underline hover:text-green-600" href={`/u/${state.memo.creatorId}`}>
@{state.memo.creatorName}
</a>

@ -90,7 +90,7 @@ const Explore = () => {
<main className="relative w-full h-auto flex flex-col justify-start items-start -mt-2">
<MemoFilter />
{sortedMemos.map((memo) => {
return <Memo key={`${memo.id}-${memo.createdTs}`} memo={memo} readonly={true} />;
return <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} readonly={true} />;
})}
{isComplete ? (
state.memos.length === 0 ? (

@ -16,6 +16,7 @@ export const initialGlobalState = async () => {
maxUploadSizeMiB: 0,
additionalStyle: "",
additionalScript: "",
memoDisplayWithUpdatedTs: false,
customizedProfile: {
name: "memos",
logoUrl: "/logo.webp",

@ -11,6 +11,7 @@ export const convertResponseModelMemo = (memo: Memo): Memo => {
...memo,
createdTs: memo.createdTs * 1000,
updatedTs: memo.updatedTs * 1000,
displayTs: memo.displayTs * 1000,
};
};

@ -23,6 +23,7 @@ const globalSlice = createSlice({
disablePublicMemos: false,
additionalStyle: "",
additionalScript: "",
memoDisplayWithUpdatedTs: false,
customizedProfile: {
name: "memos",
logoUrl: "/logo.webp",

@ -10,6 +10,7 @@ interface Memo {
updatedTs: TimeStamp;
rowStatus: RowStatus;
displayTs: TimeStamp;
content: string;
visibility: Visibility;
pinned: boolean;

@ -31,6 +31,7 @@ interface SystemStatus {
customizedProfile: CustomizedProfile;
storageServiceId: number;
localStoragePath: string;
memoDisplayWithUpdatedTs: boolean;
}
interface SystemSetting {

Loading…
Cancel
Save