Skip to content

Commit

Permalink
Resolve "Add edit mode for post and comment"
Browse files Browse the repository at this point in the history
  • Loading branch information
func0x committed Apr 4, 2024
1 parent a491798 commit e3afbf3
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 94 deletions.
42 changes: 35 additions & 7 deletions apps/blog/components/comment-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { UserHoverCard } from './user-hover-card';
import { useTranslation } from 'next-i18next';
import VotesComponent from './votes';
import { useLocalStorage } from '@smart-signer/lib/use-local-storage';
import { useUser } from '@smart-signer/lib/auth/use-user';

const CommentListItem = ({
comment,
Expand All @@ -31,12 +32,14 @@ const CommentListItem = ({
const { t } = useTranslation('common_blog');
const username = comment.author;
const router = useRouter();
const { user } = useUser();
const ref = useRef<HTMLTableRowElement>(null);
const [hiddenComment, setHiddenComment] = useState(comment.stats?.gray);
const [openState, setOpenState] = useState<boolean>(comment.stats?.gray && hiddenComment ? false : true);
const comment_html = renderer.render(comment.body);
const commentId = `@${username}/${comment.permlink}`;
const storageId = `replybox-/${username}/${comment.permlink}`;
const [edit, setEdit] = useState(false);
const [storedBox, storeBox] = useLocalStorage<Boolean>(storageId, false);
const [reply, setReply] = useState<Boolean>(storedBox !== undefined ? storedBox : false);
useEffect(() => {
Expand Down Expand Up @@ -188,13 +191,24 @@ const CommentListItem = ({
<Separator orientation="horizontal" />
<AccordionContent className="p-0">
<CardContent className="pb-2 ">
<CardDescription
className="prose break-words"
data-testid="comment-card-description"
dangerouslySetInnerHTML={{
__html: comment_html
}}
/>
{edit && comment.parent_permlink ? (
<ReplyTextbox
onSetReply={setEdit}
username={username}
permlink={comment.permlink}
parentPermlink={comment.parent_permlink}
storageId={storageId}
comment={comment.body}
/>
) : (
<CardDescription
className="prose break-words"
data-testid="comment-card-description"
dangerouslySetInnerHTML={{
__html: comment_html
}}
/>
)}
</CardContent>
<Separator orientation="horizontal" />{' '}
<CardFooter>
Expand Down Expand Up @@ -241,6 +255,20 @@ const CommentListItem = ({
>
{t('cards.comment_card.reply')}
</button>
{user && user.isLoggedIn && comment.author === user.username ? (
<>
<Separator orientation="vertical" className="h-5" />
<button
onClick={() => {
setEdit(!edit);
}}
className="flex items-center hover:cursor-pointer hover:text-red-600"
data-testid="comment-card-footer-edit"
>
{t('cards.comment_card.edit')}
</button>
</>
) : null}
</div>
</CardFooter>
</AccordionContent>
Expand Down
161 changes: 98 additions & 63 deletions apps/blog/components/post-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ import {
FormItem,
FormMessage
} from '@hive/ui/components/form';
import { useForm } from 'react-hook-form';
import { useForm, useWatch } from 'react-hook-form';
import useManabars from './hooks/useManabars';
import { AdvancedSettingsPostForm } from './advanced_settings_post_form';
import MdEditor from './md-editor';
import { useContext, useEffect, useState } from 'react';
import { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react';
import clsx from 'clsx';
import { useLocalStorage } from '@smart-signer/lib/use-local-storage';
import { useTranslation } from 'next-i18next';
import { HiveRendererContext } from './hive-renderer-context';
import { transactionService } from '@transaction/index';
import { createPermlink } from '@transaction/lib/utils';
import { useQuery } from '@tanstack/react-query';
import { getCommunity, getSubscriptions } from '@transaction/lib/bridge';
import { Entry, getCommunity, getSubscriptions } from '@transaction/lib/bridge';
import { useRouter } from 'next/router';
import { hiveChainService } from '@transaction/lib/hive-chain-service';
import { TFunction } from 'i18next';
Expand Down Expand Up @@ -99,14 +99,29 @@ function validateAltUsernameInput(value: string, t: TFunction<'common_wallet', u
: null;
}

export default function PostForm({ username }: { username: string }) {
export default function PostForm({
username,
editMode = false,
sideBySidePreview = true,
post_s,
setEditMode
}: {
username: string;
editMode: boolean;
sideBySidePreview: boolean;
post_s?: Entry;
setEditMode?: Dispatch<SetStateAction<boolean>>;
}) {
const { hiveRenderer } = useContext(HiveRendererContext);
const router = useRouter();
const [preview, setPreview] = useState(true);
const [previewContent, setPreviewContent] = useState<string>('');
const [sideBySide, setSideBySide] = useState(true);
const [sideBySide, setSideBySide] = useState(sideBySidePreview);
const { manabarsData } = useManabars(username);
const [storedPost, storePost] = useLocalStorage<AccountFormValues>('postData', defaultValues);
const [storedPost, storePost] = useLocalStorage<AccountFormValues>(
editMode ? `postData-edit-${post_s?.permlink}` : 'postData-new',
defaultValues
);
const [previewContent, setPreviewContent] = useState<string | undefined>(storedPost.postArea);
const { t } = useTranslation('common_blog');

const {
Expand Down Expand Up @@ -148,61 +163,75 @@ export default function PostForm({ username }: { username: string }) {

type AccountFormValues = z.infer<typeof accountFormSchema>;
const getValues = (storedPost?: AccountFormValues) => ({
title: storedPost?.title ?? '',
postArea: storedPost?.postArea ?? '',
postSummary: storedPost?.postSummary ?? '',
tags: storedPost?.tags ?? '',
author: storedPost?.author ?? '',
category: storedPost?.category ?? '',
title: post_s ? post_s.title : storedPost?.title ?? '',
postArea: post_s ? post_s.body : storedPost?.postArea ?? '',
postSummary: post_s?.json_metadata.summary ? post_s.json_metadata.summary : storedPost?.postSummary ?? '',
tags: post_s?.json_metadata.tags ? post_s.json_metadata.tags.join(' ') : storedPost?.tags ?? '',
author: post_s ? post_s.author : storedPost?.author ?? '',
category: post_s ? post_s.category : storedPost?.category ?? '',
// beneficiaries: post_s ? post_s.beneficiaries : storedPost?.beneficiaries ?? [],
beneficiaries: storedPost?.beneficiaries ?? [],
maxAcceptedPayout: storedPost?.maxAcceptedPayout ?? null,
payoutType: storedPost?.payoutType ?? '50%'
maxAcceptedPayout: post_s
? Number(post_s.max_accepted_payout.split(' ')[0])
: storedPost?.maxAcceptedPayout ?? null,
payoutType: post_s ? `${post_s.percent_hbd}%` : storedPost?.payoutType ?? '50%'
});
const form = useForm<AccountFormValues>({
resolver: zodResolver(accountFormSchema),
values: getValues(storedPost)
});

const { postArea, ...restFields } = useWatch({
control: form.control
});

const watchedValues = form.watch();
const tagsCheck = validateTagInput(watchedValues.tags, watchedValues.category === 'blog', t);
const summaryCheck = validateSummoryInput(watchedValues.postSummary, t);
const altUsernameCheck = validateAltUsernameInput(watchedValues.author, t);

useEffect(() => {
debounce(() => {
storePost(form.getValues());
}, 50)();
}, [form, postArea, restFields, storePost]);

// update debounced post preview content
useEffect(() => {
if (watchedValues.postArea !== previewContent) {
if (typeof previewContent !== 'undefined' && postArea !== previewContent) {
debounce(() => {
setPreviewContent(watchedValues.postArea);
}, 300)();
setPreviewContent(postArea);
}, 50)();
}
}, [watchedValues.postArea, previewContent]);
}, [postArea, previewContent]);

async function onSubmit(data: AccountFormValues) {
const chain = await hiveChainService.getHiveChain();
const tags = storedPost?.tags.replace(/#/g, '').split(' ') ?? [];
const tags = storedPost.tags.replace(/#/g, '').split(' ') ?? [];
const maxAcceptedPayout = await chain.hbd(Number(storedPost.maxAcceptedPayout));
const postPermlink = await createPermlink(storedPost?.title ?? '', username);
const permlinInEditMode = post_s?.permlink;
try {
await transactionService.post(
postPermlink,
storedPost?.title ?? '',
watchedValues.postArea,
editMode && permlinInEditMode ? permlinInEditMode : postPermlink,
storedPost.title,
storedPost.postArea,
storedPost.beneficiaries,
Number(storedPost.payoutType.slice(0, 2)),
maxAcceptedPayout,
tags,
storedPost.category
storedPost.category,
storedPost.postSummary
);
form.reset(defaultValues);
setPreviewContent(undefined);
storePost(defaultValues);
router.push(`/created/${tags[0]}`);
await router.push(`/created/${tags[0]}`, undefined, { shallow: true });
} catch (error) {
console.error(error);
}
}

useEffect(() => {
storePost(watchedValues);
}, [JSON.stringify(watchedValues), storePost]);

return (
<div className={clsx({ container: !sideBySide || !preview })}>
<div
Expand Down Expand Up @@ -253,7 +282,7 @@ export default function PostForm({ username }: { username: string }) {
onChange={(value) => {
form.setValue('postArea', value);
}}
persistedValue={storedPost.postArea}
persistedValue={field.value}
/>
</FormControl>
<FormDescription className="border-x-2 border-b-2 border-border px-3 pb-1 text-xs text-destructive">
Expand Down Expand Up @@ -310,42 +339,45 @@ export default function PostForm({ username }: { username: string }) {
</FormItem>
)}
/>
<div className="flex flex-col gap-2">
<span>{t('submit_page.post_options')}</span>
{storedPost?.maxAcceptedPayout !== null && storedPost.maxAcceptedPayout > 0 ? (
<span className="text-xs">
{t('submit_page.advanced_settings_dialog.maximum_accepted_payout') +
': ' +
storedPost.maxAcceptedPayout +
' HBD'}
</span>
) : null}
{storedPost.beneficiaries.length > 0 ? (
{!editMode ? (
<div className="flex flex-col gap-2">
<span>{t('submit_page.post_options')}</span>
{storedPost?.maxAcceptedPayout !== null && storedPost.maxAcceptedPayout > 0 ? (
<span className="text-xs">
{t('submit_page.advanced_settings_dialog.maximum_accepted_payout') +
': ' +
storedPost.maxAcceptedPayout +
' HBD'}
</span>
) : null}
{storedPost.beneficiaries.length > 0 ? (
<span className="text-xs">
{t('submit_page.advanced_settings_dialog.beneficiaries', {
num: storedPost.beneficiaries.length
})}
</span>
) : null}

<span className="text-xs">
{t('submit_page.advanced_settings_dialog.beneficiaries', {
num: storedPost.beneficiaries.length
})}
{t('submit_page.author_rewards')}
{storedPost.maxAcceptedPayout === 0
? ' ' + t('submit_page.advanced_settings_dialog.decline_payout')
: storedPost?.payoutType === '100%'
? t('submit_page.power_up')
: ' 50% HBD / 50% HP'}
</span>
) : null}

<span className="text-xs">
{t('submit_page.author_rewards')}
{storedPost.maxAcceptedPayout === 0
? ' ' + t('submit_page.advanced_settings_dialog.decline_payout')
: storedPost?.payoutType === '100%'
? t('submit_page.power_up')
: ' 50% HBD / 50% HP'}
</span>
<AdvancedSettingsPostForm username={username} onChangeStore={storePost} data={storedPost}>
<span
className="w-fit cursor-pointer text-xs text-destructive"
title={t('submit_page.advanced_tooltip')}
>
{t('submit_page.advanced_settings')}
</span>
</AdvancedSettingsPostForm>
</div>
) : null}

<AdvancedSettingsPostForm username={username} onChangeStore={storePost} data={storedPost}>
<span
className="w-fit cursor-pointer text-xs text-destructive"
title={t('submit_page.advanced_tooltip')}
>
{t('submit_page.advanced_settings')}
</span>
</AdvancedSettingsPostForm>
</div>
<div className="flex flex-col gap-2">
<span>{t('submit_page.account_stats')}</span>
<span className="text-xs">
Expand Down Expand Up @@ -403,11 +435,14 @@ export default function PostForm({ username }: { username: string }) {
<Button
onClick={() => {
form.reset(defaultValues);
if (editMode && setEditMode) {
setEditMode(false);
}
}}
variant="ghost"
className="font-thiny text-foreground/60 hover:text-destructive"
>
{t('submit_page.clean')}
{editMode ? t('submit_page.cancel') : t('submit_page.clean')}
</Button>
</form>
</Form>
Expand Down
14 changes: 11 additions & 3 deletions apps/blog/components/reply-textbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ export function ReplyTextbox({
onSetReply,
username,
permlink,
storageId
parentPermlink,
storageId,
comment
}: {
onSetReply: (e: boolean) => void;
username: string;
permlink: string;
parentPermlink?: string;
storageId: string;
comment?: string;
}) {
const [storedPost, storePost] = useLocalStorage<string>(`replyTo-/${username}/${permlink}`, '');
const { user } = useUser();
const { t } = useTranslation('common_blog');
const [text, setText] = useState(storedPost ? storedPost : '');
const [text, setText] = useState(comment ? comment : storedPost ? storedPost : '');
const [cleanedText, setCleanedText] = useState('');
const { hiveRenderer } = useContext(HiveRendererContext);

Expand Down Expand Up @@ -80,7 +84,11 @@ export function ReplyTextbox({
<Button
disabled={text === ''}
onClick={() => {
transactionService.comment(username, permlink, cleanedText);
if (parentPermlink) {
transactionService.updateComment(username, parentPermlink, permlink, cleanedText);
} else {
transactionService.comment(username, permlink, cleanedText);
}
setText('');
localStorage.removeItem(`replyTo-/${username}/${permlink}`);
localStorage.removeItem(storageId);
Expand Down
3 changes: 3 additions & 0 deletions apps/blog/locales/en/common_blog.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
"one_reply": "reply",
"replies": "replies",
"reply": "Reply",
"edit": "Edit",
"will_be_hidden": "Will be hidden due to low rating ",
"load_more": "Load more"
}
Expand Down Expand Up @@ -359,6 +360,7 @@
"votes": "{{votes}} votes",
"reblog": "Reblog",
"reply": "Reply",
"edit": "Edit",
"no_responses": "No responses. ",
"response": "1 response. ",
"responses": "{{responses}} responses. ",
Expand Down Expand Up @@ -410,6 +412,7 @@
"preview": "Preview",
"submit": "Submit",
"clean": "Clean",
"cancel": "Cancel",
"markdown_styling_guide": "Markdown Styling Guide",
"power_up": " Power up 100%",
"maximum_characters": "Maximum {{num}} characters",
Expand Down
Loading

0 comments on commit e3afbf3

Please sign in to comment.