From 365e0ec8486330d2fe64ce536eea1f09f6799765 Mon Sep 17 00:00:00 2001 From: chaojun Date: Fri, 23 Oct 2026 18:00:24 +0800 Subject: [PATCH] Show proposal & appendant revision history --- .../appendants/common/appendantFormPopup.jsx | 88 +++++++++++++++ .../appendants/common/createPopup.jsx | 77 +++---------- .../appendants/common/editPopup.jsx | 86 +++------------ .../components/appendants/common/item.jsx | 21 ++++ .../appendants/referenda/createPopup.jsx | 29 +++-- .../appendants/referenda/editPopup.jsx | 30 +++-- .../referenda/legacyCreatePopup.jsx | 18 +++ .../appendants/referenda/legacyEditPopup.jsx | 15 +++ .../appendants/referenda/simaCreatePopup.jsx | 35 ++++++ .../appendants/referenda/simaEditPopup.jsx | 42 +++++++ .../next-common/components/articleContent.js | 5 +- .../next-common/sima/actions/appendant.js | 56 ++++++++++ .../sima/components/appendantEditedMarker.jsx | 21 ++++ .../sima/components/commentEditedMarker.jsx | 18 +-- .../editedHistoryDropdown.jsx} | 104 ++++++++++++++---- .../versionViewPopup.js} | 92 +++++++++++++--- .../sima/components/postEditedMarker.jsx | 35 ++++++ 17 files changed, 567 insertions(+), 205 deletions(-) create mode 100644 packages/next-common/components/appendants/common/appendantFormPopup.jsx create mode 100644 packages/next-common/components/appendants/referenda/legacyCreatePopup.jsx create mode 100644 packages/next-common/components/appendants/referenda/legacyEditPopup.jsx create mode 100644 packages/next-common/components/appendants/referenda/simaCreatePopup.jsx create mode 100644 packages/next-common/components/appendants/referenda/simaEditPopup.jsx create mode 100644 packages/next-common/sima/components/appendantEditedMarker.jsx rename packages/next-common/sima/components/{commentEditedHistoryDropdown.jsx => editedHistory/editedHistoryDropdown.jsx} (64%) rename packages/next-common/sima/components/{commentVersionViewPopup.js => editedHistory/versionViewPopup.js} (69%) create mode 100644 packages/next-common/sima/components/postEditedMarker.jsx diff --git a/packages/next-common/components/appendants/common/appendantFormPopup.jsx b/packages/next-common/components/appendants/common/appendantFormPopup.jsx new file mode 100644 index 0000000000..248664b59b --- /dev/null +++ b/packages/next-common/components/appendants/common/appendantFormPopup.jsx @@ -0,0 +1,88 @@ +import { useEffect, useState } from "react"; +import { useDispatch } from "react-redux"; +import AppendantEditor from "./editor"; +import AppendantPopupLayout from "./popupLayout"; +import { CreateButton } from "./submitButton"; +import { + newErrorToast, + newSuccessToast, +} from "next-common/store/reducers/toastSlice"; + +export default function AppendantFormPopup({ + title, + description = "", + onClose, + onSuccess, + submit, + successMessage, + initialContent = "", + initialContentType = "markdown", + SubmitButton = CreateButton, +}) { + const [content, setContent] = useState(initialContent || ""); + const [contentType, setContentType] = useState( + initialContentType || "markdown", + ); + const [errors, setErrors] = useState(); + const [submitting, setSubmitting] = useState(false); + const dispatch = useDispatch(); + + useEffect(() => { + setContent(initialContent || ""); + setContentType(initialContentType || "markdown"); + }, [initialContent, initialContentType]); + + const handleSubmit = async () => { + setSubmitting(true); + setErrors(null); + + try { + const { result, error } = await submit(content, contentType); + if (error) { + if (error.data) { + setErrors(error); + } else { + dispatch(newErrorToast(error.message)); + } + return; + } + + if (result) { + dispatch(newSuccessToast(successMessage)); + onSuccess?.(); + onClose(); + } + } catch (e) { + if (e.message !== "Cancelled") { + dispatch(newErrorToast(e.message)); + } + } finally { + setSubmitting(false); + } + }; + + return ( + + } + > + + + ); +} diff --git a/packages/next-common/components/appendants/common/createPopup.jsx b/packages/next-common/components/appendants/common/createPopup.jsx index 1b23d92c59..31d02140a4 100644 --- a/packages/next-common/components/appendants/common/createPopup.jsx +++ b/packages/next-common/components/appendants/common/createPopup.jsx @@ -1,14 +1,6 @@ -import { useState } from "react"; -import { useDispatch } from "react-redux"; import { useEnsureLogin } from "next-common/hooks/useEnsureLogin"; -import { - newErrorToast, - newSuccessToast, -} from "next-common/store/reducers/toastSlice"; import nextApi from "next-common/services/nextApi"; -import AppendantEditor from "./editor"; -import AppendantPopupLayout from "./popupLayout"; -import { CreateButton } from "./submitButton"; +import AppendantFormPopup from "./appendantFormPopup"; export default function CreateAppendantPopup({ onClose, @@ -16,68 +8,27 @@ export default function CreateAppendantPopup({ onSuccess, description = "", }) { - const [content, setContent] = useState(""); - const [contentType, setContentType] = useState("markdown"); - const [errors, setErrors] = useState(); - const [submitting, setSubmitting] = useState(false); - const dispatch = useDispatch(); const { ensureLogin } = useEnsureLogin(); - const handleSubmit = async () => { - setSubmitting(true); - setErrors(null); - - try { - if (!(await ensureLogin())) { - return; - } - - const { result, error } = await nextApi.post(createApi, { - content, - contentType, - }); - - if (error) { - if (error.data) { - setErrors(error); - } else { - dispatch(newErrorToast(error.message)); - } - return; - } - - if (result) { - dispatch(newSuccessToast("Appendant created successfully")); - onSuccess?.(); - onClose(); - } - } finally { - setSubmitting(false); + const submit = async (content, contentType) => { + if (!(await ensureLogin())) { + throw new Error("Cancelled"); } + + return await nextApi.post(createApi, { + content, + contentType, + }); }; return ( - - } - > - - + onSuccess={onSuccess} + submit={submit} + successMessage="Appendant created successfully" + /> ); } diff --git a/packages/next-common/components/appendants/common/editPopup.jsx b/packages/next-common/components/appendants/common/editPopup.jsx index d43577edae..0b14ed7db4 100644 --- a/packages/next-common/components/appendants/common/editPopup.jsx +++ b/packages/next-common/components/appendants/common/editPopup.jsx @@ -1,14 +1,7 @@ -import { useState, useEffect } from "react"; -import { useDispatch } from "react-redux"; import { useEnsureLogin } from "next-common/hooks/useEnsureLogin"; -import { - newErrorToast, - newSuccessToast, -} from "next-common/store/reducers/toastSlice"; import { appendantsApi } from "next-common/services/url"; import nextApi from "next-common/services/nextApi"; -import AppendantEditor from "./editor"; -import AppendantPopupLayout from "./popupLayout"; +import AppendantFormPopup from "./appendantFormPopup"; import { UpdateButton } from "./submitButton"; export default function EditAppendantPopup({ @@ -17,76 +10,31 @@ export default function EditAppendantPopup({ onSuccess, description = "You are editing an appendant.", }) { - const [content, setContent] = useState(""); - const [contentType, setContentType] = useState("markdown"); - const [errors, setErrors] = useState(); - const [submitting, setSubmitting] = useState(false); - const dispatch = useDispatch(); const { ensureLogin } = useEnsureLogin(); - useEffect(() => { - if (editData) { - setContent(editData.content || ""); - setContentType(editData.contentType || "markdown"); + const submit = async (content, contentType) => { + if (!(await ensureLogin())) { + throw new Error("Cancelled"); } - }, [editData]); - const handleSubmit = async () => { - setSubmitting(true); - setErrors(null); - - try { - if (!(await ensureLogin())) { - return; - } - - const updateApi = appendantsApi(editData?._id); - const { result, error } = await nextApi.patch(updateApi, { - content, - contentType, - }); - - if (error) { - if (error.data) { - setErrors(error); - } else { - dispatch(newErrorToast(error.message)); - } - return; - } - - if (result) { - dispatch(newSuccessToast("Appendant updated successfully")); - onSuccess?.(); - onClose(); - } - } finally { - setSubmitting(false); - } + const updateApi = appendantsApi(editData?._id); + return await nextApi.patch(updateApi, { + content, + contentType, + }); }; return ( - - } - > - - + onSuccess={onSuccess} + submit={submit} + successMessage="Appendant updated successfully" + initialContent={editData?.content || ""} + initialContentType={editData?.contentType || "markdown"} + SubmitButton={UpdateButton} + /> ); } diff --git a/packages/next-common/components/appendants/common/item.jsx b/packages/next-common/components/appendants/common/item.jsx index 850eb07ef2..00706466d4 100644 --- a/packages/next-common/components/appendants/common/item.jsx +++ b/packages/next-common/components/appendants/common/item.jsx @@ -13,6 +13,7 @@ import { formatTimeAgo } from "next-common/utils/viewfuncs/formatTimeAgo"; import Tooltip from "next-common/components/tooltip"; import useIsAppendantAuthor from "../useIsAppendantAuthor"; import correctionIpfsEndpointPlugin from "next-common/utils/previewerPlugins/correctionIpfsEndpoint"; +import AppendantEditedMarker from "next-common/sima/components/appendantEditedMarker"; function SplitDot() { return ยท; @@ -56,6 +57,21 @@ function Header({ index, data }) { ); } +function getTimeValue(value) { + if (!value) { + return null; + } + const time = + value instanceof Date ? value.getTime() : new Date(value).getTime(); + return Number.isNaN(time) ? null : time; +} + +function isAppendantEdited(data) { + const createdAt = getTimeValue(data?.createdAt); + const updatedAt = getTimeValue(data?.updatedAt); + return createdAt !== null && updatedAt !== null && updatedAt !== createdAt; +} + function Content({ data }) { return (
@@ -100,6 +116,11 @@ export default function AppendantItem({ index, data, MoreActions }) { {isAuthor && MoreActions && }
+ {isAppendantEdited(data) && + data?.dataSource === "sima" && + data?.cid && ( + + )} ); diff --git a/packages/next-common/components/appendants/referenda/createPopup.jsx b/packages/next-common/components/appendants/referenda/createPopup.jsx index 227db779da..18aeed0fc1 100644 --- a/packages/next-common/components/appendants/referenda/createPopup.jsx +++ b/packages/next-common/components/appendants/referenda/createPopup.jsx @@ -1,19 +1,18 @@ -import { gov2ReferendaAppendantApi } from "next-common/services/url"; -import { useReferendaAppendantsContext } from "next-common/context/referendaAppendants"; -import { usePageProps } from "next-common/context/page"; -import CreateAppendantPopup from "../common/createPopup"; +import { usePost } from "next-common/context/post"; +import { useArticleActions } from "next-common/sima/context/articleActions"; +import LegacyCreateAppendantPopup from "./legacyCreatePopup"; +import SimaCreateAppendantPopup from "./simaCreatePopup"; export default function ReferendaCreateAppendantPopup({ setIsAppend }) { - const { update } = useReferendaAppendantsContext(); - const { id } = usePageProps(); - const createApi = gov2ReferendaAppendantApi(id); + const post = usePost(); + const { supportSima } = useArticleActions(); - return ( - setIsAppend(false)} - createApi={createApi} - onSuccess={update} - description="You are adding an appendant as authors." - /> - ); + const isSimaPost = + supportSima && post?.refToPost?.postType === "referendaReferendum"; + + if (isSimaPost) { + return ; + } + + return ; } diff --git a/packages/next-common/components/appendants/referenda/editPopup.jsx b/packages/next-common/components/appendants/referenda/editPopup.jsx index 669fa20de4..1a44898655 100644 --- a/packages/next-common/components/appendants/referenda/editPopup.jsx +++ b/packages/next-common/components/appendants/referenda/editPopup.jsx @@ -1,15 +1,21 @@ -import { useReferendaAppendantsContext } from "next-common/context/referendaAppendants"; -import EditAppendantPopup from "../common/editPopup"; +import { usePost } from "next-common/context/post"; +import { useArticleActions } from "next-common/sima/context/articleActions"; +import LegacyEditAppendantPopup from "./legacyEditPopup"; +import SimaEditAppendantPopup from "./simaEditPopup"; export default function ReferendaEditAppendantPopup({ onClose, editData }) { - const { update } = useReferendaAppendantsContext(); - - return ( - - ); + const post = usePost(); + const { supportSima } = useArticleActions(); + + const isSimaAppendant = + supportSima && + editData?.dataSource === "sima" && + !!editData?.cid && + post?.refToPost?.postType === "referendaReferendum"; + + if (isSimaAppendant) { + return ; + } + + return ; } diff --git a/packages/next-common/components/appendants/referenda/legacyCreatePopup.jsx b/packages/next-common/components/appendants/referenda/legacyCreatePopup.jsx new file mode 100644 index 0000000000..abf89624d7 --- /dev/null +++ b/packages/next-common/components/appendants/referenda/legacyCreatePopup.jsx @@ -0,0 +1,18 @@ +import { gov2ReferendaAppendantApi } from "next-common/services/url"; +import { useReferendaAppendantsContext } from "next-common/context/referendaAppendants"; +import { usePageProps } from "next-common/context/page"; +import CreateAppendantPopup from "../common/createPopup"; + +export default function LegacyCreateAppendantPopup({ setIsAppend }) { + const { update } = useReferendaAppendantsContext(); + const { id } = usePageProps(); + + return ( + setIsAppend(false)} + createApi={gov2ReferendaAppendantApi(id)} + onSuccess={update} + description="You are adding an appendant as authors." + /> + ); +} diff --git a/packages/next-common/components/appendants/referenda/legacyEditPopup.jsx b/packages/next-common/components/appendants/referenda/legacyEditPopup.jsx new file mode 100644 index 0000000000..a07039e879 --- /dev/null +++ b/packages/next-common/components/appendants/referenda/legacyEditPopup.jsx @@ -0,0 +1,15 @@ +import { useReferendaAppendantsContext } from "next-common/context/referendaAppendants"; +import EditAppendantPopup from "../common/editPopup"; + +export default function LegacyEditAppendantPopup({ onClose, editData }) { + const { update } = useReferendaAppendantsContext(); + + return ( + + ); +} diff --git a/packages/next-common/components/appendants/referenda/simaCreatePopup.jsx b/packages/next-common/components/appendants/referenda/simaCreatePopup.jsx new file mode 100644 index 0000000000..d06f1ed92e --- /dev/null +++ b/packages/next-common/components/appendants/referenda/simaCreatePopup.jsx @@ -0,0 +1,35 @@ +import { useReferendaAppendantsContext } from "next-common/context/referendaAppendants"; +import { usePost } from "next-common/context/post"; +import { useIsPostAuthor } from "next-common/context/post/useIsPostAuthor"; +import usePostProxyAuthor from "next-common/hooks/usePostProxyAuthor"; +import { useAddReferendaAppendant } from "next-common/sima/actions/appendant"; +import { getRealField } from "next-common/sima/actions/common"; +import AppendantFormPopup from "../common/appendantFormPopup"; + +export default function SimaCreateAppendantPopup({ setIsAppend }) { + const { update } = useReferendaAppendantsContext(); + const post = usePost(); + const isAuthor = useIsPostAuthor(); + const proxyAddress = usePostProxyAuthor(); + const addReferendaAppendant = useAddReferendaAppendant(); + const realAddress = !isAuthor && proxyAddress ? proxyAddress : undefined; + + const submit = (content, contentType) => + addReferendaAppendant( + post, + content, + contentType, + getRealField(realAddress), + ); + + return ( + setIsAppend(false)} + onSuccess={update} + submit={submit} + successMessage="Appendant created successfully" + /> + ); +} diff --git a/packages/next-common/components/appendants/referenda/simaEditPopup.jsx b/packages/next-common/components/appendants/referenda/simaEditPopup.jsx new file mode 100644 index 0000000000..3457e8c066 --- /dev/null +++ b/packages/next-common/components/appendants/referenda/simaEditPopup.jsx @@ -0,0 +1,42 @@ +import { useReferendaAppendantsContext } from "next-common/context/referendaAppendants"; +import { usePost } from "next-common/context/post"; +import { useUser } from "next-common/context/user"; +import { isSameAddress } from "next-common/utils"; +import { useReplaceReferendaAppendant } from "next-common/sima/actions/appendant"; +import { getRealField } from "next-common/sima/actions/common"; +import AppendantFormPopup from "../common/appendantFormPopup"; +import { UpdateButton } from "../common/submitButton"; + +export default function SimaEditAppendantPopup({ onClose, editData }) { + const { update } = useReferendaAppendantsContext(); + const post = usePost(); + const user = useUser(); + const replaceReferendaAppendant = useReplaceReferendaAppendant(); + const realAddress = + user?.proxyAddress && isSameAddress(user.proxyAddress, editData?.proposer) + ? user.proxyAddress + : undefined; + + const submit = (content, contentType) => + replaceReferendaAppendant( + post, + editData, + content, + contentType, + getRealField(realAddress), + ); + + return ( + + ); +} diff --git a/packages/next-common/components/articleContent.js b/packages/next-common/components/articleContent.js index 96c297803e..042d2c8e4c 100644 --- a/packages/next-common/components/articleContent.js +++ b/packages/next-common/components/articleContent.js @@ -13,6 +13,7 @@ import ContentSummary from "./contentSummary"; import PostDataSource from "./postDataSource"; import PostContentTranslations from "./postContentTranslations"; import useShowTranslations from "next-common/hooks/useShowTranslations"; +import PostEditedMarker from "next-common/sima/components/postEditedMarker"; const Wrapper = styled.div` :hover { @@ -44,9 +45,7 @@ export default function ArticleContent({ <> - {isPostEdited(post) && ( -
Edited
- )} + {isPostEdited(post) && } ); diff --git a/packages/next-common/sima/actions/appendant.js b/packages/next-common/sima/actions/appendant.js index 3b2ffa0297..b341388fe6 100644 --- a/packages/next-common/sima/actions/appendant.js +++ b/packages/next-common/sima/actions/appendant.js @@ -2,6 +2,16 @@ import nextApi from "next-common/services/nextApi"; import useSignSimaMessage from "next-common/utils/sima/useSignSimaMessage"; import { getContentField } from "next-common/utils/sima/utils"; import { useCallback } from "react"; +import getProposalIndexer from "next-common/sima/utils/getProposalIndexer"; +import getDetailPageCategory from "next-common/sima/utils/getDetailPageCategoryFromPostType"; + +function getReferendumIndex(post) { + return ( + post?.referendumIndex ?? + post?.refToPost?.referendumIndex ?? + post?.onchainData?.referendumIndex + ); +} export function useAddDiscussionAppendant() { const signSimaMessage = useSignSimaMessage(); @@ -21,3 +31,49 @@ export function useAddDiscussionAppendant() { [signSimaMessage], ); } + +export function useAddReferendaAppendant() { + const signSimaMessage = useSignSimaMessage(); + + return useCallback( + async (post, content, contentType, real) => { + const entity = { + action: "append_proposal", + indexer: getProposalIndexer(post), + ...getContentField(content, contentType), + timestamp: Date.now(), + real, + }; + const data = await signSimaMessage(entity); + const type = getDetailPageCategory(post); + return await nextApi.post( + `sima/${type}/${getReferendumIndex(post)}/appendants`, + data, + ); + }, + [signSimaMessage], + ); +} + +export function useReplaceReferendaAppendant() { + const signSimaMessage = useSignSimaMessage(); + + return useCallback( + async (post, appendant, content, contentType, real) => { + const entity = { + action: "replace_appendant", + cid: appendant.cid, + ...getContentField(content, contentType), + timestamp: Date.now(), + real, + }; + const data = await signSimaMessage(entity); + const type = getDetailPageCategory(post); + return await nextApi.post( + `sima/${type}/${getReferendumIndex(post)}/appendants/${appendant.cid}`, + data, + ); + }, + [signSimaMessage], + ); +} diff --git a/packages/next-common/sima/components/appendantEditedMarker.jsx b/packages/next-common/sima/components/appendantEditedMarker.jsx new file mode 100644 index 0000000000..78a26bf8b8 --- /dev/null +++ b/packages/next-common/sima/components/appendantEditedMarker.jsx @@ -0,0 +1,21 @@ +import React from "react"; +import EditedHistoryDropdown, { + PlainEditedMarker, +} from "next-common/sima/components/editedHistory/editedHistoryDropdown"; + +export default function AppendantEditedMarker({ appendant, className }) { + const cid = appendant?.cid; + + if (!cid || appendant?.dataSource !== "sima") { + return ; + } + + return ( + + ); +} diff --git a/packages/next-common/sima/components/commentEditedMarker.jsx b/packages/next-common/sima/components/commentEditedMarker.jsx index 37d28de139..66f0e4cb6b 100644 --- a/packages/next-common/sima/components/commentEditedMarker.jsx +++ b/packages/next-common/sima/components/commentEditedMarker.jsx @@ -1,11 +1,9 @@ import React from "react"; import { useComment } from "next-common/components/comment/context"; import { useCommentActions } from "next-common/sima/context/commentActions"; -import CommentEditedHistoryDropdown from "next-common/sima/components/commentEditedHistoryDropdown"; - -function PlainCommentEditedMarker() { - return
Edited
; -} +import EditedHistoryDropdown, { + PlainEditedMarker, +} from "next-common/sima/components/editedHistory/editedHistoryDropdown"; export default function CommentEditedMarker() { const comment = useComment(); @@ -18,8 +16,14 @@ export default function CommentEditedMarker() { !!comment?.hasEditHistory; if (!hasHistory) { - return ; + return ; } - return ; + return ( + + ); } diff --git a/packages/next-common/sima/components/commentEditedHistoryDropdown.jsx b/packages/next-common/sima/components/editedHistory/editedHistoryDropdown.jsx similarity index 64% rename from packages/next-common/sima/components/commentEditedHistoryDropdown.jsx rename to packages/next-common/sima/components/editedHistory/editedHistoryDropdown.jsx index 5351a244e0..5c8ab57db9 100644 --- a/packages/next-common/sima/components/commentEditedHistoryDropdown.jsx +++ b/packages/next-common/sima/components/editedHistory/editedHistoryDropdown.jsx @@ -7,8 +7,8 @@ import Loading from "next-common/components/loading"; import nextApi from "next-common/services/nextApi"; import dynamicPopup from "next-common/lib/dynamic/popup"; -const CommentVersionViewPopup = dynamicPopup(() => - import("next-common/sima/components/commentVersionViewPopup"), +const VersionViewPopup = dynamicPopup(() => + import("next-common/sima/components/editedHistory/versionViewPopup"), ); function formatEditedAgo(timestamp) { @@ -38,44 +38,99 @@ function formatEditedAgo(timestamp) { return `${parts.join(" ")} ago`; } -export default function CommentEditedHistoryDropdown({ commentCid }) { +export function PlainEditedMarker({ label = "Edited", className }) { + return ( +
+ {label} +
+ ); +} + +export default function EditedHistoryDropdown({ + historyUrl, + popupTitle = "Edit History", + showTitle = false, + prefetch = false, + label = "Edited", + firstVersionLabel = "Created", + firstVersionTimeLabel = "Created at", + className, +}) { const [show, setShow] = useState(false); const [history, setHistory] = useState(null); const [selected, setSelected] = useState(null); + const loadingUrlRef = useRef(null); + const currentUrlRef = useRef(historyUrl); const ref = useRef(); useClickAway(ref, () => setShow(false)); const loadHistory = useCallback(async () => { - const { result, error } = await nextApi.fetch( - `sima/comments/${commentCid}/history`, - ); - if (error) { - setHistory({ cid: commentCid, error: error.message }); + if (!historyUrl || loadingUrlRef.current === historyUrl) { return; } - setHistory({ cid: commentCid, items: result?.items || [] }); - }, [commentCid]); - // Fetch history when the dropdown opens, and refetch when the comment gets a - // new version (cid changes after an edit), so the list is always up to date. + loadingUrlRef.current = historyUrl; + try { + const { result, error } = await nextApi.fetch(historyUrl); + if (currentUrlRef.current !== historyUrl) { + return; + } + if (error) { + setHistory({ url: historyUrl, error: error.message }); + return; + } + setHistory({ url: historyUrl, items: result?.items || [] }); + } finally { + if (loadingUrlRef.current === historyUrl) { + loadingUrlRef.current = null; + } + } + }, [historyUrl]); + + const isHistoryFresh = !!history && history.url === historyUrl; + const items = isHistoryFresh ? history.items : null; + const errorMessage = isHistoryFresh ? history.error : null; + useEffect(() => { - if (!show || history?.cid === commentCid) { + currentUrlRef.current = historyUrl; + setHistory(null); + setSelected(null); + setShow(false); + }, [historyUrl]); + + useEffect(() => { + if (!show || isHistoryFresh) { return; } loadHistory(); - }, [show, commentCid, history?.cid, loadHistory]); + }, [show, isHistoryFresh, loadHistory]); + + useEffect(() => { + if (!prefetch || !historyUrl || isHistoryFresh) { + return; + } + loadHistory(); + }, [prefetch, historyUrl, isHistoryFresh, loadHistory]); + + if (!historyUrl) { + return ; + } + + if (prefetch && (!items || items.length <= 1)) { + return ; + } - const isHistoryFresh = !!history && history.cid === commentCid; - const items = isHistoryFresh ? history.items : null; - const errorMessage = isHistoryFresh ? history.error : null; const editCount = items ? Math.max(0, items.length - 1) : 0; return ( <>
setShow(!show)} > - Edited + {label} { const isCreated = index === items.length - 1; const isMostRecent = index === 0; - const label = `${ - isCreated ? "Created" : "Edited" + const itemLabel = `${ + isCreated ? firstVersionLabel : "Edited" } ${formatEditedAgo(item.timestamp)}${ isMostRecent ? " (most recent)" : "" }`; @@ -135,7 +190,7 @@ export default function CommentEditedHistoryDropdown({ commentCid }) { }); }} > - {label} + {itemLabel} ); })} @@ -145,10 +200,13 @@ export default function CommentEditedHistoryDropdown({ commentCid }) { )}
{selected && ( - setSelected(null)} /> )} diff --git a/packages/next-common/sima/components/commentVersionViewPopup.js b/packages/next-common/sima/components/editedHistory/versionViewPopup.js similarity index 69% rename from packages/next-common/sima/components/commentVersionViewPopup.js rename to packages/next-common/sima/components/editedHistory/versionViewPopup.js index fe2c4cca95..a75ff1256c 100644 --- a/packages/next-common/sima/components/commentVersionViewPopup.js +++ b/packages/next-common/sima/components/editedHistory/versionViewPopup.js @@ -134,9 +134,60 @@ function VersionedContent({ item, previousItem }) { ); } -export function CommentVersionView({ item, version, previousItem }) { +function VersionedTitle({ title, previousTitle }) { + const diffParts = useMemo( + () => (previousTitle ? diffWords(previousTitle, title || "") : []), + [previousTitle, title], + ); + const hasDiff = diffParts.some((part) => part.added || part.removed); + + if (!previousTitle || !hasDiff) { + return ( +
+ {title || ""} +
+ ); + } + + return ( +
+ {diffParts.map((part, index) => { + if (part.added) { + return ( + + {part.value} + + ); + } + if (part.removed) { + return ( + + {part.value} + + ); + } + return {part.value}; + })} +
+ ); +} + +export function VersionView({ + item, + version, + previousItem, + showTitle = false, + firstVersionTimeLabel = "Created at", +}) { const time = dayjs(item.timestamp).format("YYYY-MM-DD HH:mm:ss"); - const timeLabel = version === 1 ? "Created at" : "Edited at"; + const timeLabel = version === 1 ? firstVersionTimeLabel : "Edited at"; + const hasTitle = showTitle && (item?.title || previousItem?.title); return (
@@ -144,15 +195,25 @@ export function CommentVersionView({ item, version, previousItem }) { {timeLabel} {time} - - - + {item.cid && ( + + + + )}
+ {hasTitle && ( +
+ +
+ )}
@@ -160,22 +221,27 @@ export function CommentVersionView({ item, version, previousItem }) { ); } -export default function CommentVersionViewPopup({ +export default function VersionViewPopup({ item, version, previousItem, + popupTitle = "Edit History", + showTitle = false, + firstVersionTimeLabel = "Created at", setShow = noop, }) { return ( setShow(false)} className="w-200 max-w-full" > - ); diff --git a/packages/next-common/sima/components/postEditedMarker.jsx b/packages/next-common/sima/components/postEditedMarker.jsx new file mode 100644 index 0000000000..ae0af1447e --- /dev/null +++ b/packages/next-common/sima/components/postEditedMarker.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import { useDetailType } from "next-common/context/page"; +import { detailPageCategory } from "next-common/utils/consts/business/category"; +import EditedHistoryDropdown, { + PlainEditedMarker, +} from "next-common/sima/components/editedHistory/editedHistoryDropdown"; + +export default function PostEditedMarker({ post, className }) { + const detailType = useDetailType(); + const cid = post?.cid || post?.refToPost?.cid; + const dataSource = post?.dataSource || post?.refToPost?.dataSource; + // "post" (singular) is the linked discussion collection type. + const isLinkedDiscussion = post?.refToPost?.postType === "post"; + + if ( + !cid || + dataSource !== "sima" || + detailType === detailPageCategory.POST || + isLinkedDiscussion + ) { + return ; + } + + return ( + + ); +}