Skip to content

Commit

Permalink
Merge pull request #1455 from ecency/bugfix/submit-page-restore-and-a…
Browse files Browse the repository at this point in the history
…uth-issue

Submit 2.1: Added body manager
  • Loading branch information
feruzm authored Aug 29, 2023
2 parents fbb231e + 37412cc commit d67578e
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 17 deletions.
9 changes: 1 addition & 8 deletions src/common/pages/submit/api/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,7 @@ import { EntriesCacheContext } from "../../../core";
export function useUpdateApi(history: History, onClear: () => void) {
const { activeUser } = useMappedStore();

const {
videoId,
is3Speak: isThreespeak,
speakPermlink,
speakAuthor,
isNsfw,
videoMetadata
} = useThreeSpeakManager();
const { videoMetadata } = useThreeSpeakManager();

const { updateCache } = useContext(EntriesCacheContext);

Expand Down
115 changes: 115 additions & 0 deletions src/common/pages/submit/hooks/body-versioning-manager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, {
createContext,
PropsWithChildren,
useContext,
useEffect,
useRef,
useState
} from "react";
import md5 from "js-md5";

const BUFFER_SIZE = 100;

interface QueueItem {
value: string;
hash: string;
metadata: Record<string, unknown>;
status: "restored" | "new";
}

export const BodyContext = createContext<{
body: string;
activeQueueItem?: QueueItem;
setBody: (value: string) => void;
updateMetadata: (value: Record<string, unknown>) => void;
}>({
body: "",
setBody: () => {},
updateMetadata: () => {}
});

export function useBodyVersioningManager(onVersionChange?: (value: QueueItem) => void) {
const context = useContext(BodyContext);

useEffect(() => {
if (context.activeQueueItem?.status === "restored") {
onVersionChange?.(context.activeQueueItem);
}
}, [context.activeQueueItem]);

return context;
}

/**
* Body versioning manager is a queue of all body changes
*
* Each queue item could contain additional metadata object
* Each body updating causes queue updating but if queue contains the same state then it will be restored
*
* Use cases:
* 1. If user added the video which contains metadata information and has removed it wrongly
* then he will do UNDO operation. In that case We may restore video metadata easily from the queue
*
* @note Active user changing causes queue clearing due to security reasons
* @note Active queue item is the last queue item
*/
export function BodyVersioningManager({ children }: PropsWithChildren<unknown>) {
const [rawBody, setRawBody] = useState("");

const [historyQueue, setHistoryQueue] = useState<QueueItem[]>([]);
const activeQueueItem = useRef<QueueItem>({
value: "",
hash: "",
metadata: {},
status: "new"
});

useEffect(() => {
const hash = md5(rawBody);
const existingState = historyQueue.find((x) => x.hash === hash);

const nextItem: QueueItem = {
value: rawBody,
hash,
metadata: activeQueueItem.current.metadata,
status: "new"
};
if (existingState) {
nextItem.status = "restored";
nextItem.metadata = JSON.parse(JSON.stringify(existingState.metadata));
}
setHistoryQueue([...historyQueue, nextItem]);
activeQueueItem.current = nextItem;
}, [rawBody]);

useEffect(() => {
if (historyQueue.length > BUFFER_SIZE) {
const temp = [...historyQueue];
temp.shift();
setHistoryQueue(temp);
}
}, [historyQueue]);

const updateMetadata = (metadata: Record<string, unknown>) => {
activeQueueItem.current = {
...activeQueueItem.current,
metadata: {
...activeQueueItem.current.metadata,
...metadata
}
};
};

return (
<BodyContext.Provider
value={{
body: rawBody,
activeQueueItem: activeQueueItem.current,
setBody: setRawBody,
updateMetadata
}}
>
{children}
</BodyContext.Provider>
);
}
1 change: 1 addition & 0 deletions src/common/pages/submit/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./community-detector";
export * from "./entry-detector";
export * from "./api-draft-detector";
export * from "./three-speak-manager";
export * from "./body-versioning-manager";
66 changes: 63 additions & 3 deletions src/common/pages/submit/hooks/three-speak-manager.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { createContext, ReactNode, useContext, useMemo, useState } from "react";
import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react";
import { ThreeSpeakVideo } from "../../../api/threespeak";
import useLocalStorage from "react-use/lib/useLocalStorage";
import { PREFIX } from "../../../util/local-storage";
import { useBodyVersioningManager } from "./body-versioning-manager";

export interface ThreeSpeakManagerContext {
clear: () => void;
Expand All @@ -25,6 +26,7 @@ export interface ThreeSpeakManagerContext {

// funcs
has3SpeakVideo: (body: string) => boolean;
findAndClearUnpublished3SpeakVideo: (body: string) => void;
}

export const ThreeSpeakVideoContext = createContext<ThreeSpeakManagerContext>({
Expand All @@ -44,15 +46,16 @@ export const ThreeSpeakVideoContext = createContext<ThreeSpeakManagerContext>({
isEditing: false,
setIsEditing: () => {},
hasUnpublishedVideo: false,
has3SpeakVideo: () => false
has3SpeakVideo: () => false,
findAndClearUnpublished3SpeakVideo: () => {}
});

export function useThreeSpeakManager() {
return useContext(ThreeSpeakVideoContext);
}

const THREE_SPEAK_VIDEO_PATTERN =
/\[!\[\]\(https:\/\/ipfs-3speak\.b-cdn\.net\/ipfs.*\)\]\(https:\/\/3speak.tv\/watch\?.*\)\[Source\]\(https:\/\/ipfs-3speak\.b-cdn\.net\/ipfs\/(.*)\)/g;
/\[!\[\]\(https:\/\/ipfs-3speak\.b-cdn\.net\/ipfs.*\)\]\(https:\/\/3speak\.tv\/watch\?.*\)\[Source\]\(https:\/\/ipfs-3speak\.b-cdn\.net\/ipfs\/(.*)\)/g;

export function ThreeSpeakManager(props: { children: ReactNode }) {
const [is3Speak, setIs3Speak, clearIs3Speak] = useLocalStorage(PREFIX + "_sa_3s", false);
Expand All @@ -73,6 +76,41 @@ export function ThreeSpeakManager(props: { children: ReactNode }) {
[videoMetadata]
);

const bodyManager = useBodyVersioningManager(({ metadata }) => {
const { videoMetadata } = metadata as {
videoMetadata: {
videoMetadata: ThreeSpeakVideo;
is3Speak: boolean;
isNsfw: boolean;
videoId: string;
speakPermlink: string;
speakAuthor: string;
};
};

if (videoMetadata) {
setVideoMetadata(videoMetadata.videoMetadata);
setIs3Speak(videoMetadata.is3Speak);
setVideoId(videoMetadata.videoId);
setSpeakPermlink(videoMetadata.speakPermlink);
setSpeakAuthor(videoMetadata.speakAuthor);
setIsNsfw(videoMetadata.isNsfw);
}
});

useEffect(() => {
bodyManager.updateMetadata({
videoMetadata: {
is3Speak,
videoId,
speakPermlink,
speakAuthor,
isNsfw,
videoMetadata
}
});
}, [speakAuthor]);

const has3SpeakVideo = (body: string) => {
const groups = body.matchAll(THREE_SPEAK_VIDEO_PATTERN);
let has = false;
Expand All @@ -85,6 +123,27 @@ export function ThreeSpeakManager(props: { children: ReactNode }) {
return has;
};

const findAndClearUnpublished3SpeakVideo = (body: string) => {
const groups = body.matchAll(THREE_SPEAK_VIDEO_PATTERN);
for (const group of groups) {
const match = group[1];

/// <center>[![](https://ipfs-3speak.b-cdn.net/ipfs/bafkreic3bnsxobtm2va6j3i6v44gc2p2wazi4iuiqqci23tdt7eba5ad5e)](https://3speak.tv/watch?v=demo.com/meohyozpiu)[Source](https://ipfs-3speak.b-cdn.net/ipfs/QmUu28DUQ6wQpH8sFt5pVb3ZDfnvib67BUdtyE8uD4p64j)</center>
// Has unpublished video
if (`ipfs://${match}` === videoMetadata?.filename && videoMetadata?.status !== "published") {
body.replace(
new RegExp(
`\[!\[\]\(https:\/\/ipfs-3speak\.b-cdn\.net\/ipfs\/${videoMetadata?.thumbnail.replace(
"ipfs://",
""
)}\)\]\(https:\/\/3speak\.tv\/watch\?.*\)\[Source\]\(https:\/\/ipfs-3speak\.b-cdn\.net\/ipfs\/${match}\)`
),
""
);
}
}
};

return (
<ThreeSpeakVideoContext.Provider
value={{
Expand All @@ -104,6 +163,7 @@ export function ThreeSpeakManager(props: { children: ReactNode }) {
setIsEditing,
hasUnpublishedVideo,
has3SpeakVideo,
findAndClearUnpublished3SpeakVideo,

clear: () => {
clearIs3Speak();
Expand Down
21 changes: 15 additions & 6 deletions src/common/pages/submit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { MatchType, PostBase, VideoProps } from "./types";
import { pageMapDispatchToProps, pageMapStateToProps, PageProps } from "../common";
import { extractMetaData } from "../../helper/posting";
import {
BodyVersioningManager,
ThreeSpeakManager,
useAdvancedManager,
useApiDraftDetector,
useBodyVersioningManager,
useCommunityDetector,
useEntryDetector,
useLocalDraftManager,
Expand Down Expand Up @@ -63,13 +65,14 @@ interface MatchProps {
export function Submit(props: PageProps & MatchProps) {
const postBodyRef = useRef<HTMLDivElement | null>(null);
const threeSpeakManager = useThreeSpeakManager();
const { body, setBody } = useBodyVersioningManager();
const previousBody = usePrevious(body);

const { activeUser } = useMappedStore();
const previousActiveUser = usePrevious(activeUser);

const [title, setTitle] = useState("");
const [tags, setTags] = useState<string[]>([]);
const [body, setBody] = useState("");
const [selectionTouched, setSelectionTouched] = useState(false);
const [thumbnails, setThumbnails] = useState<string[]>([]);
const [selectedThumbnail, setSelectedThumbnail, removeThumbnail] = useLocalStorage<string>(
Expand Down Expand Up @@ -204,6 +207,9 @@ export function Submit(props: PageProps & MatchProps) {
if (activeUser?.username !== previousActiveUser?.username && activeUser) {
// delete active user from beneficiaries list
setBeneficiaries(beneficiaries.filter((x) => x.account !== activeUser.username));

// clear not current user videos
threeSpeakManager.findAndClearUnpublished3SpeakVideo(body);
}
}, [activeUser]);

Expand All @@ -220,8 +226,9 @@ export function Submit(props: PageProps & MatchProps) {
}, [title, body, tags]);

useEffect(() => {
if (!threeSpeakManager.has3SpeakVideo(body)) {
if (!threeSpeakManager.has3SpeakVideo(body) && !!previousBody) {
threeSpeakManager.clear();
console.log("clearing 3speak");
}
}, [body]);

Expand Down Expand Up @@ -429,7 +436,7 @@ export function Submit(props: PageProps & MatchProps) {
as="textarea"
placeholder={_t("submit.body-placeholder")}
value={body && body.length > 0 ? body : preview.body}
onChange={(e: { target: { value: React.SetStateAction<string> } }) => {
onChange={(e: { target: { value: string } }) => {
setBody(e.target.value);
}}
disableRows={true}
Expand Down Expand Up @@ -824,9 +831,11 @@ export function Submit(props: PageProps & MatchProps) {

const SubmitWithProviders = (props: PageProps & MatchProps) => {
return (
<ThreeSpeakManager>
<Submit {...props} />
</ThreeSpeakManager>
<BodyVersioningManager>
<ThreeSpeakManager>
<Submit {...props} />
</ThreeSpeakManager>
</BodyVersioningManager>
);
};

Expand Down

0 comments on commit d67578e

Please sign in to comment.