feat: allow adding links via ip

This commit is contained in:
Sonny
2025-11-09 03:10:51 +01:00
parent 208f2c631f
commit 1c81a6a86f
9 changed files with 63 additions and 25 deletions

View File

@@ -0,0 +1,9 @@
import vine from '@vinejs/vine';
export const baseLinkValidator = vine.object({
name: vine.string().trim().minLength(1).maxLength(254),
description: vine.string().trim().maxLength(300).optional(),
url: vine.string().url({ require_tld: false }).trim(),
favorite: vine.boolean(),
collectionId: vine.number(),
});

View File

@@ -1,11 +1,8 @@
import { baseLinkValidator } from '#links/validators/base_link_validator';
import vine from '@vinejs/vine'; import vine from '@vinejs/vine';
export const createLinkValidator = vine.compile( export const createLinkValidator = vine.compile(
vine.object({ vine.object({
name: vine.string().trim().minLength(1).maxLength(254), ...baseLinkValidator.getProperties(),
description: vine.string().trim().maxLength(300).optional(),
url: vine.string().trim(),
favorite: vine.boolean(),
collectionId: vine.number(),
}) })
); );

View File

@@ -1,13 +1,10 @@
import { params } from '#core/validators/params_object'; import { params } from '#core/validators/params_object';
import { baseLinkValidator } from '#links/validators/base_link_validator';
import vine from '@vinejs/vine'; import vine from '@vinejs/vine';
export const updateLinkValidator = vine.compile( export const updateLinkValidator = vine.compile(
vine.object({ vine.object({
name: vine.string().trim().minLength(1).maxLength(254), ...baseLinkValidator.getProperties(),
description: vine.string().trim().maxLength(300).optional(),
url: vine.string().trim(),
favorite: vine.boolean(),
collectionId: vine.number(),
params, params,
}) })

View File

@@ -21,7 +21,7 @@ interface FormCollectionProps extends FormLayoutProps {
handleSubmit: () => void; handleSubmit: () => void;
} }
export default function MantineFormCollection({ export function MantineFormCollection({
data, data,
errors, errors,
disableInputs = false, disableInputs = false,

View File

@@ -24,7 +24,7 @@ interface FormLinkProps extends FormLayoutProps {
handleSubmit: () => void; handleSubmit: () => void;
} }
export default function MantineFormLink({ export function FormLink({
data, data,
errors, errors,
collections, collections,
@@ -83,7 +83,7 @@ export default function MantineFormLink({
value: id.toString(), value: id.toString(),
}))} }))}
onChange={(value) => setData('collectionId', value)} onChange={(value) => setData('collectionId', value)}
value={data.collectionId.toString()} value={data.collectionId?.toString()}
readOnly={disableInputs} readOnly={disableInputs}
mt="md" mt="md"
searchable searchable

View File

@@ -16,15 +16,50 @@ export const appendResourceId = (
) => `${url}${resourceId ? `/${resourceId}` : ''}`; ) => `${url}${resourceId ? `/${resourceId}` : ''}`;
export function isValidHttpUrl(urlParam: string) { export function isValidHttpUrl(urlParam: string) {
let url; const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}(:\d+)?(\/.*)?(\?.*)?(#[^#]*)?$/;
const domainRegex =
/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:\d+)?(\/.*)?(\?.*)?(#[^#]*)?$/;
const simpleDomainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/;
try { let urlToTest = urlParam.trim();
url = new URL(urlParam);
} catch (_) { if (urlToTest.startsWith('http://') || urlToTest.startsWith('https://')) {
return false; try {
const url = new URL(urlToTest);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (_) {
return false;
}
} }
return url.protocol === 'http:' || url.protocol === 'https:'; if (ipv4Regex.test(urlToTest)) {
try {
new URL(`http://${urlToTest}`);
return true;
} catch (_) {
return false;
}
}
if (domainRegex.test(urlToTest)) {
try {
new URL(`http://${urlToTest}`);
return true;
} catch (_) {
return false;
}
}
if (simpleDomainRegex.test(urlToTest)) {
try {
new URL(`http://${urlToTest}`);
return true;
} catch (_) {
return false;
}
}
return false;
} }
export const generateShareUrl = ( export const generateShareUrl = (

View File

@@ -2,7 +2,7 @@ import { useForm } from '@inertiajs/react';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import MantineFormLink from '~/components/form/form_link'; import { FormLink } from '~/components/form/form_link';
import useSearchParam from '~/hooks/use_search_param'; import useSearchParam from '~/hooks/use_search_param';
import { isValidHttpUrl } from '~/lib/navigation'; import { isValidHttpUrl } from '~/lib/navigation';
import { Collection } from '~/types/app'; import { Collection } from '~/types/app';
@@ -38,7 +38,7 @@ export default function CreateLinkPage({
}; };
return ( return (
<MantineFormLink <FormLink
title={t('link.create')} title={t('link.create')}
textSubmitButton={t('form.create')} textSubmitButton={t('form.create')}
canSubmit={canSubmit} canSubmit={canSubmit}

View File

@@ -1,7 +1,7 @@
import { useForm } from '@inertiajs/react'; import { useForm } from '@inertiajs/react';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import MantineFormLink from '~/components/form/form_link'; import { FormLink } from '~/components/form/form_link';
import { LinkWithCollection } from '~/types/app'; import { LinkWithCollection } from '~/types/app';
export default function DeleteLinkPage({ link }: { link: LinkWithCollection }) { export default function DeleteLinkPage({ link }: { link: LinkWithCollection }) {
@@ -22,7 +22,7 @@ export default function DeleteLinkPage({ link }: { link: LinkWithCollection }) {
}; };
return ( return (
<MantineFormLink <FormLink
title={t('link.delete')} title={t('link.delete')}
textSubmitButton={t('form.delete')} textSubmitButton={t('form.delete')}
canSubmit={!processing} canSubmit={!processing}

View File

@@ -2,7 +2,7 @@ import { useForm } from '@inertiajs/react';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import MantineFormLink from '~/components/form/form_link'; import { FormLink } from '~/components/form/form_link';
import { isValidHttpUrl } from '~/lib/navigation'; import { isValidHttpUrl } from '~/lib/navigation';
import { Collection, Link } from '~/types/app'; import { Collection, Link } from '~/types/app';
@@ -50,7 +50,7 @@ export default function EditLinkPage({
}; };
return ( return (
<MantineFormLink <FormLink
title={t('link.edit')} title={t('link.edit')}
textSubmitButton={t('form.update')} textSubmitButton={t('form.update')}
canSubmit={canSubmit} canSubmit={canSubmit}