mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'codex/fix/link-file-filename-validation'
This commit is contained in:
@@ -309,7 +309,7 @@ vi.mock('../../../hooks/use-file-upload', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../loading-ellipsis', () => ({
|
||||
vi.mock('../../loading-ellipsis/loading-ellipsis', () => ({
|
||||
default: ({ string }: { string: string }) => createElement('span', { 'data-testid': 'loading-ellipsis' }, string),
|
||||
}));
|
||||
|
||||
@@ -331,19 +331,26 @@ vi.mock('../../../lib/utils/media-utils', () => {
|
||||
return {
|
||||
getDisplayMediaInfoType: (type: string, t: (key: string) => string) => t(type),
|
||||
getLinkMediaInfo: (link: string) => {
|
||||
const path = (() => {
|
||||
try {
|
||||
return new URL(link).pathname;
|
||||
} catch {
|
||||
return link;
|
||||
}
|
||||
})();
|
||||
if (getTwimgMediaFilePublishUrl(link)) {
|
||||
return { type: 'image', url: link };
|
||||
}
|
||||
if (link.endsWith('.gif')) {
|
||||
if (path.endsWith('.gif')) {
|
||||
return { type: 'gif', url: link };
|
||||
}
|
||||
if (link.endsWith('.jpg') || link.endsWith('.jpeg') || link.endsWith('.png')) {
|
||||
if (path.endsWith('.jpg') || path.endsWith('.jpeg') || path.endsWith('.png')) {
|
||||
return { type: 'image', url: link };
|
||||
}
|
||||
if (link.endsWith('.mp4')) {
|
||||
if (path.endsWith('.mp4')) {
|
||||
return { type: 'video', url: link };
|
||||
}
|
||||
if (link.endsWith('.mp3')) {
|
||||
if (path.endsWith('.mp3')) {
|
||||
return { type: 'audio', url: link };
|
||||
}
|
||||
if (link.includes('youtube.com')) {
|
||||
@@ -1306,6 +1313,23 @@ describe('PostForm', () => {
|
||||
expect(table?.textContent).toContain('file name.jpg');
|
||||
});
|
||||
|
||||
it('does not show a pasted non-file path segment next to the upload button', async () => {
|
||||
testState.uploadedFileName = null;
|
||||
|
||||
await renderPostForm('/all');
|
||||
await clickByText(container, 'start_new_thread');
|
||||
|
||||
const table = container.querySelector('table');
|
||||
const textInputs = table?.querySelectorAll<HTMLInputElement>('input[type="text"]') || [];
|
||||
const linkInput = textInputs[3];
|
||||
|
||||
await dispatchInput(linkInput as HTMLInputElement, 'https://imgur.com/8EJ2T76');
|
||||
|
||||
expect(table?.textContent).toContain('not_a_file');
|
||||
expect(table?.textContent).toContain('no_file_chosen');
|
||||
expect(table?.textContent).not.toContain('8EJ2T76');
|
||||
});
|
||||
|
||||
it('shows the rules and FAQ prompt at the bottom of the form with a board-specific rules link', async () => {
|
||||
testState.resolvedCommunityAddress = 'music-posting.eth';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Comment, setAccount, useAccount, useEditedComment } from '@bitsocial/bi
|
||||
import getShortAddress from '../../lib/get-short-address';
|
||||
import useCommunitiesPagesStore from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
import { getDisplayMediaInfoType, getLinkMediaInfo, getTwimgMediaFilePublishUrl } from '../../lib/utils/media-utils';
|
||||
import { getExpiringMediaLinkAlert } from '../../lib/utils/media-link-validation-utils';
|
||||
import { getExpiringMediaLinkAlert, getPublishFileDisplayName, isPublishFileMediaLink, isPublishFileMediaType } from '../../lib/utils/media-link-validation-utils';
|
||||
import {
|
||||
type DiceRoll,
|
||||
type FortuneEntry,
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
isPostOptionsValidationError,
|
||||
} from '../../lib/utils/post-options-utils';
|
||||
import { truncateWithEllipsisInMiddle } from '../../lib/utils/string-utils';
|
||||
import { getPublishURLFilename, isValidPublishURL, isValidURL } from '../../lib/utils/url-utils';
|
||||
import { isValidPublishURL, isValidURL } from '../../lib/utils/url-utils';
|
||||
import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils';
|
||||
import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
|
||||
import { getBoardPath } from '../../lib/utils/route-utils';
|
||||
@@ -46,8 +46,8 @@ import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||
import useMediaHostingStore from '../../stores/use-media-hosting-store';
|
||||
import BoardOfflineAlert from '../board-offline-alert/board-offline-alert';
|
||||
import BbcodeEditorToolbar, { BbcodePreview } from '../bbcode-editor-toolbar/bbcode-editor-toolbar';
|
||||
import LoadingEllipsis from '../loading-ellipsis';
|
||||
import OekakiDrawingControls from '../oekaki-drawing-controls';
|
||||
import LoadingEllipsis from '../loading-ellipsis/loading-ellipsis';
|
||||
import OekakiDrawingControls from '../oekaki-drawing-controls/oekaki-drawing-controls';
|
||||
import PostOptionsErrorMessage from '../post-options-error-message/post-options-error-message';
|
||||
import styles from './post-form.module.css';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
@@ -55,23 +55,18 @@ import debounce from 'lodash/debounce';
|
||||
|
||||
const FILE_LINK_PLACEHOLDER = 'https://website.com/image.jpg';
|
||||
const POST_FORM_FILE_DISPLAY_MAX_LENGTH = 28;
|
||||
const POST_FORM_FILE_MEDIA_TYPES = new Set(['gif', 'image', 'video']);
|
||||
|
||||
const mergeFlairs = (...flairGroups: Array<Comment['flairs'] | undefined>): Comment['flairs'] | undefined => {
|
||||
const flairs = flairGroups.flatMap((group) => (Array.isArray(group) ? group : []));
|
||||
return flairs.length > 0 ? flairs : undefined;
|
||||
};
|
||||
|
||||
const getPostFormFileDisplayLabel = (url: string, uploadedFileName: string | null | undefined, noFileLabel: string): string => {
|
||||
const raw = getPublishURLFilename(url) || uploadedFileName;
|
||||
const getPostFormFileDisplayLabel = (url: string, uploadedFileName: string | null | undefined, noFileLabel: string, requireFile: boolean): string => {
|
||||
const raw = getPublishFileDisplayName(url, uploadedFileName, requireFile);
|
||||
if (!raw) return noFileLabel;
|
||||
return truncateWithEllipsisInMiddle(raw, POST_FORM_FILE_DISPLAY_MAX_LENGTH);
|
||||
};
|
||||
|
||||
const isPostFormFileMediaType = (type: string | undefined): boolean => Boolean(type && POST_FORM_FILE_MEDIA_TYPES.has(type));
|
||||
|
||||
const isPostFormFileMediaLink = (link: string): boolean => isPostFormFileMediaType(getLinkMediaInfo(link)?.type);
|
||||
|
||||
const getPublishLinkOptions = (link: string, includeCurrentLink: boolean): Partial<Pick<Comment, 'link'>> => {
|
||||
const twimgPublishUrl = getTwimgMediaFilePublishUrl(link);
|
||||
if (twimgPublishUrl) {
|
||||
@@ -87,7 +82,7 @@ export const LinkTypePreviewer = ({ link, requireFile = false }: { link: string;
|
||||
let type = mediaInfo?.type;
|
||||
const { status: gifFrameStatus } = useFetchGifFirstFrame(type === 'gif' ? mediaInfo?.url : undefined);
|
||||
|
||||
if (requireFile && isValidURL(link) && !isPostFormFileMediaType(type)) {
|
||||
if (requireFile && isValidURL(link) && !isPublishFileMediaType(type)) {
|
||||
return <span className={styles.linkTypeError}>{t('not_a_file')}</span>;
|
||||
}
|
||||
|
||||
@@ -400,8 +395,8 @@ const PostFormFields = ({
|
||||
isUploading={isUploading}
|
||||
showUploadControls={showUploadControls}
|
||||
/>
|
||||
<span title={getPublishURLFilename(url) || uploadedFileName || undefined}>
|
||||
{isUploading ? <LoadingEllipsis string={t('uploading')} /> : getPostFormFileDisplayLabel(url, uploadedFileName, t('no_file_chosen'))}
|
||||
<span title={getPublishFileDisplayName(url, uploadedFileName, requirePostLinkIsMedia) || undefined}>
|
||||
{isUploading ? <LoadingEllipsis string={t('uploading')} /> : getPostFormFileDisplayLabel(url, uploadedFileName, t('no_file_chosen'), requirePostLinkIsMedia)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -653,7 +648,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
setFormError(`${t('error')}: ${t('invalid_url_alert')}`);
|
||||
return;
|
||||
}
|
||||
if (currentUrl && requirePostLinkIsMedia && !isPostFormFileMediaLink(currentUrl)) {
|
||||
if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) {
|
||||
setFormError(`${t('error')}: ${t('link_not_image_or_video_alert')}`);
|
||||
return;
|
||||
}
|
||||
@@ -781,7 +776,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
||||
setFormError(`${t('error')}: ${t('invalid_url_alert')}`);
|
||||
return;
|
||||
}
|
||||
if (currentUrl && requirePostLinkIsMedia && !isPostFormFileMediaLink(currentUrl)) {
|
||||
if (currentUrl && requirePostLinkIsMedia && !isPublishFileMediaLink(currentUrl)) {
|
||||
setFormError(`${t('error')}: ${t('link_not_image_or_video_alert')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ vi.mock('../../../hooks/use-file-upload', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../loading-ellipsis', () => ({
|
||||
vi.mock('../../loading-ellipsis/loading-ellipsis', () => ({
|
||||
default: ({ string }: { string: string }) => createElement('span', { 'data-testid': 'loading-ellipsis' }, string),
|
||||
}));
|
||||
|
||||
@@ -1021,6 +1021,21 @@ describe('ReplyModal', () => {
|
||||
expect(container.textContent).not.toContain('Spoiler?');
|
||||
});
|
||||
|
||||
it('only shows pasted link filenames after media-only links are confirmed as files', async () => {
|
||||
await renderReplyModal('/all/thread/post-1');
|
||||
|
||||
const linkInput = container.querySelectorAll<HTMLInputElement>('input[type="text"]')[2];
|
||||
|
||||
await dispatchInput(linkInput, 'https://imgur.com/8EJ2T76');
|
||||
|
||||
expect(container.textContent).toContain('no_file_chosen');
|
||||
expect(container.textContent).not.toContain('8EJ2T76');
|
||||
|
||||
await dispatchInput(linkInput, 'https://imgur.com/8EJ2T76.jpg');
|
||||
|
||||
expect(container.textContent).toContain('8EJ2T76.jpg');
|
||||
});
|
||||
|
||||
it('positions the draggable modal with left/top styles instead of a transform layer', async () => {
|
||||
await renderReplyModal('/mu/thread/post-1');
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { setAccount, useAccount } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { getExpiringMediaLinkAlert } from '../../lib/utils/media-link-validation-utils';
|
||||
import { getExpiringMediaLinkAlert, getPublishFileDisplayName } from '../../lib/utils/media-link-validation-utils';
|
||||
import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsForDirectory } from '../../lib/comment-flag-selection';
|
||||
import {
|
||||
type DiceRoll,
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
hasNonokoOption,
|
||||
isPostOptionsValidationError,
|
||||
} from '../../lib/utils/post-options-utils';
|
||||
import { getPublishURLFilename, isValidPublishURL } from '../../lib/utils/url-utils';
|
||||
import { isValidPublishURL } from '../../lib/utils/url-utils';
|
||||
import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
|
||||
import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils';
|
||||
import { isAllView, isModView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
@@ -35,8 +35,8 @@ import { useCommunityField } from '../../hooks/use-stable-community';
|
||||
import { OEKAKI_WEB_WARNING_TEXT } from '../../lib/oekaki/oekaki-copy';
|
||||
import BbcodeEditorToolbar, { BbcodePreview } from '../bbcode-editor-toolbar/bbcode-editor-toolbar';
|
||||
import BoardOfflineAlert from '../board-offline-alert/board-offline-alert';
|
||||
import LoadingEllipsis from '../loading-ellipsis';
|
||||
import OekakiDrawingControls from '../oekaki-drawing-controls';
|
||||
import LoadingEllipsis from '../loading-ellipsis/loading-ellipsis';
|
||||
import OekakiDrawingControls from '../oekaki-drawing-controls/oekaki-drawing-controls';
|
||||
import PostOptionsErrorMessage from '../post-options-error-message/post-options-error-message';
|
||||
import styles from './reply-modal.module.css';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
@@ -498,7 +498,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
};
|
||||
const uploadMode = useMediaHostingStore((state) => state.uploadMode);
|
||||
const showUploadControls = getShowUploadControls(uploadMode, isWebRuntime());
|
||||
const displayedFileName = getPublishURLFilename(url) || uploadedFileName;
|
||||
const displayedFileName = getPublishFileDisplayName(url, uploadedFileName, requirePostLinkIsMedia);
|
||||
const youtubeThumbnailConversionNotice =
|
||||
youtubeThumbnailConversionCountdown !== null ? t('youtube_thumbnail_link_conversion_notice', { count: youtubeThumbnailConversionCountdown }) : null;
|
||||
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
import { getExpiringMediaLinkHostname } from './url-utils';
|
||||
import { getExpiringMediaLinkHostname, getPublishURLFilename } from './url-utils';
|
||||
import { getLinkMediaInfo } from './media-utils';
|
||||
|
||||
type TranslateFn = (key: string, options?: Record<string, unknown>) => string;
|
||||
|
||||
const PUBLISH_FILE_MEDIA_TYPES = new Set(['gif', 'image', 'video']);
|
||||
|
||||
export const getExpiringMediaLinkAlert = (url: string, t: TranslateFn): string | null => {
|
||||
const expiringMediaLinkHostname = getExpiringMediaLinkHostname(url);
|
||||
return expiringMediaLinkHostname ? `${t('error')}: ${t('expiring_media_link_alert', { domain: expiringMediaLinkHostname })}` : null;
|
||||
};
|
||||
|
||||
export const isPublishFileMediaType = (type: string | undefined): boolean => Boolean(type && PUBLISH_FILE_MEDIA_TYPES.has(type));
|
||||
|
||||
export const isPublishFileMediaLink = (link: string): boolean => isPublishFileMediaType(getLinkMediaInfo(link)?.type);
|
||||
|
||||
export const getPublishFileDisplayName = (url: string, uploadedFileName: string | null | undefined, requireFile: boolean): string | null => {
|
||||
if (!url) {
|
||||
return uploadedFileName || null;
|
||||
}
|
||||
if (requireFile && !isPublishFileMediaLink(url)) {
|
||||
return null;
|
||||
}
|
||||
return getPublishURLFilename(url) || uploadedFileName || null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user