mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(post form): add rules and FAQ prompt
This commit is contained in:
@@ -53,11 +53,30 @@ const testState = vi.hoisted(() => ({
|
||||
uploadedFileName: 'picked.png' as string | null,
|
||||
}));
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: Record<string, unknown>) => (options?.domain ? `${key}:${options.domain}` : key),
|
||||
}),
|
||||
}));
|
||||
vi.mock('react-i18next', async () => {
|
||||
const React = await vi.importActual<typeof import('react')>('react');
|
||||
|
||||
return {
|
||||
Trans: ({ i18nKey, components }: { components?: Record<string, React.ReactElement>; i18nKey: string }) => {
|
||||
if (i18nKey === 'post_form_rules_faq_prompt') {
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
{},
|
||||
'Please read the ',
|
||||
components?.rules ? React.cloneElement(components.rules, {}, 'Rules') : 'Rules',
|
||||
' and ',
|
||||
components?.faq ? React.cloneElement(components.faq, {}, 'FAQ') : 'FAQ',
|
||||
' before posting.',
|
||||
);
|
||||
}
|
||||
|
||||
return i18nKey;
|
||||
},
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: Record<string, unknown>) => (options?.domain ? `${key}:${options.domain}` : key),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||
@@ -643,6 +662,27 @@ describe('PostForm', () => {
|
||||
expect(table?.textContent).toContain('file name.jpg');
|
||||
});
|
||||
|
||||
it('shows the rules and FAQ prompt below the file row with a board-specific rules link', async () => {
|
||||
testState.resolvedCommunityAddress = 'music-posting.eth';
|
||||
|
||||
await renderPostForm('/mu');
|
||||
await clickByText(container, 'start_new_thread');
|
||||
|
||||
const table = container.querySelector('table');
|
||||
const rows = Array.from(table?.querySelectorAll('tr') || []);
|
||||
const fileRowIndex = rows.findIndex((row) => row.querySelector('td')?.textContent === 'file');
|
||||
const promptRowIndex = rows.findIndex((row) => row.textContent === 'Please read the Rules and FAQ before posting.');
|
||||
const promptRow = rows[promptRowIndex];
|
||||
const links = Array.from(promptRow?.querySelectorAll('a') || []);
|
||||
|
||||
expect(fileRowIndex).toBeGreaterThan(-1);
|
||||
expect(promptRowIndex).toBe(fileRowIndex + 1);
|
||||
expect(promptRow?.className).toBe('rules');
|
||||
expect(promptRow?.querySelector('ul')?.className).toBe('rules');
|
||||
expect(links.map((link) => link.textContent)).toEqual(['Rules', 'FAQ']);
|
||||
expect(links.map((link) => link.getAttribute('href'))).toEqual(['/rules/mu', '/faq']);
|
||||
});
|
||||
|
||||
it('shortens long pasted file-link filenames next to the upload button', async () => {
|
||||
testState.uploadedFileName = null;
|
||||
|
||||
|
||||
@@ -182,6 +182,40 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.postFormTable :global(ul.rules) {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.postFormTable :global(ul.rules > li) {
|
||||
list-style: none;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.postFormTable :global(.rules > li::before) {
|
||||
content: "\2022 \20";
|
||||
}
|
||||
|
||||
.postFormTable :global(tr.rules td) {
|
||||
border: 0px !important;
|
||||
background-color: transparent !important;
|
||||
font-weight: normal !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.postFormTable :global(tr.rules a),
|
||||
.postFormTable :global(tr.rules a:visited) {
|
||||
color: var(--post-link-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.postFormTable :global(tr.rules a:hover),
|
||||
.postFormTable :global(tr.rules a:visited:hover) {
|
||||
color: var(--post-link-text-color-hover);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Comment, setAccount, useAccount, useEditedComment } from '@bitsocial/bitsocial-react-hooks';
|
||||
import getShortAddress from '../../lib/get-short-address';
|
||||
import useCommunitiesPagesStore from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
@@ -10,6 +10,7 @@ import { getExpiringMediaLinkAlert } from '../../lib/utils/media-link-validation
|
||||
import { truncateWithEllipsisInMiddle } from '../../lib/utils/string-utils';
|
||||
import { getPublishURLFilename, isValidPublishURL, isValidURL } from '../../lib/utils/url-utils';
|
||||
import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
|
||||
import { getBoardPath } from '../../lib/utils/route-utils';
|
||||
import { isAllView, isCatalogView, isModQueueView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses';
|
||||
import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
|
||||
@@ -132,6 +133,7 @@ interface PostFormFieldsProps {
|
||||
accountCommunityAddresses: string[];
|
||||
subscriptions: string[];
|
||||
communityAddress: string | undefined;
|
||||
rulesPath: string;
|
||||
requirePostLinkIsMedia: boolean;
|
||||
showBbcodeToolbar: boolean;
|
||||
onBbcodePreviewToggle: () => void;
|
||||
@@ -171,6 +173,7 @@ const PostFormFields = ({
|
||||
accountCommunityAddresses,
|
||||
subscriptions,
|
||||
communityAddress,
|
||||
rulesPath,
|
||||
requirePostLinkIsMedia,
|
||||
showBbcodeToolbar,
|
||||
onBbcodePreviewToggle,
|
||||
@@ -313,6 +316,21 @@ const PostFormFields = ({
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr className='rules'>
|
||||
<td colSpan={2}>
|
||||
<ul className='rules'>
|
||||
<li>
|
||||
<Trans
|
||||
i18nKey='post_form_rules_faq_prompt'
|
||||
components={{
|
||||
rules: <Link to={rulesPath} />,
|
||||
faq: <Link to='/faq' />,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{((isInPostView && showSpoilerForReply) || (!isInPostView && showSpoilerForPost)) && (
|
||||
<tr className={styles.spoilerButton}>
|
||||
<td>{t('options')}</td>
|
||||
@@ -388,6 +406,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
const subscriptions = account?.subscriptions || [];
|
||||
const directories = useDirectories();
|
||||
const directoryEntry = useDirectoryByAddress(effectiveBoardAddress);
|
||||
const rulesPath = effectiveBoardAddress ? `/rules/${getBoardPath(effectiveBoardAddress, directories)}` : '/rules';
|
||||
const showSpoilerForPost = directoryEntry?.features?.noSpoilers !== true;
|
||||
const showSpoilerForReply = directoryEntry?.features?.noSpoilerReplies !== true;
|
||||
const requirePostLinkIsMediaFeature = directoryEntry?.features?.requirePostLinkIsMedia;
|
||||
@@ -621,6 +640,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
accountCommunityAddresses={accountCommunityAddresses}
|
||||
subscriptions={subscriptions}
|
||||
communityAddress={communityAddress}
|
||||
rulesPath={rulesPath}
|
||||
requirePostLinkIsMedia={requirePostLinkIsMedia}
|
||||
showBbcodeToolbar={showBbcodeToolbar}
|
||||
onBbcodePreviewToggle={handleBbcodePreviewToggle}
|
||||
|
||||
Reference in New Issue
Block a user