diff --git a/SECURITY.md b/SECURITY.md
index 8bd7c633..10387a04 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,11 +2,11 @@
## Supported Versions
-| Version | Supported |
-| ------- | ------------------------------------- |
-| 4.x.x | :white_check_mark: |
-| < 3 | :white_check_mark: (EOL at June 2025) |
-| < 2 | :x: |
+| Version | Supported |
+| ------- | ------------------ |
+| 4.2.x | :white_check_mark: |
+| < 3 | :x: |
+| < 2 | :x: |
## Reporting a Vulnerability
diff --git a/src/components/file/DashboardFile/FileModal.tsx b/src/components/file/DashboardFile/FileModal.tsx
index cd5dd8b1..25b41509 100755
--- a/src/components/file/DashboardFile/FileModal.tsx
+++ b/src/components/file/DashboardFile/FileModal.tsx
@@ -18,7 +18,6 @@ import {
Modal,
Pill,
PillsInput,
- ScrollArea,
SimpleGrid,
Text,
Title,
@@ -61,8 +60,8 @@ import {
removeFromFolder,
viewFile,
} from '../actions';
-import FileStat from './FileStat';
import EditFileDetailsModal from './EditFileDetailsModal';
+import FileStat from './FileStat';
function ActionButton({
Icon,
@@ -189,9 +188,9 @@ export default function FileModal({
}
size='auto'
+ maw='90vw'
centered
zIndex={200}
- scrollAreaComponent={ScrollArea.Autosize}
>
{file ? (
<>
diff --git a/src/components/file/DashboardFileType.tsx b/src/components/file/DashboardFileType.tsx
index 548388b9..38369557 100755
--- a/src/components/file/DashboardFileType.tsx
+++ b/src/components/file/DashboardFileType.tsx
@@ -11,7 +11,7 @@ import {
Text,
} from '@mantine/core';
import { Icon, IconFileUnknown, IconPlayerPlay, IconShieldLockFilled } from '@tabler/icons-react';
-import { useEffect, useState } from 'react';
+import { useEffect, useState, useCallback, useMemo } from 'react';
import { renderMode } from '../pages/upload/renderMode';
import Render from '../render/Render';
import fileIcon from './fileIcon';
@@ -30,7 +30,7 @@ function PlaceholderContent({ text, Icon }: { text: string; Icon: Icon }) {
function Placeholder({ text, Icon, ...props }: { text: string; Icon: Icon; onClick?: () => void }) {
return (
-
+
);
@@ -83,57 +83,60 @@ export default function DashboardFileType({
const disableMediaPreview = useSettingsStore((state) => state.settings.disableMediaPreview);
const dbFile = 'id' in file;
- const renderIn = renderMode(file.name.split('.').pop() || '');
+ const renderIn = useMemo(() => renderMode(file.name.split('.').pop() || ''), [file.name]);
const [fileContent, setFileContent] = useState('');
const [type, setType] = useState(file.type.split('/')[0]);
const [open, setOpen] = useState(false);
- const gettext = async () => {
- if (!dbFile) {
- const reader = new FileReader();
- reader.onload = () => {
- if ((reader.result! as string).length > 1 * 1024 * 1024) {
- setFileContent(
- reader.result!.slice(0, 1 * 1024 * 1024) +
- '\n...\nThe file is too big to display click the download icon to view/download it.',
- );
- } else {
- setFileContent(reader.result as string);
- }
- };
- reader.readAsText(file);
+ const getText = useCallback(async () => {
+ try {
+ if (!dbFile) {
+ const reader = new FileReader();
+ reader.onload = () => {
+ if ((reader.result! as string).length > 1 * 1024 * 1024) {
+ setFileContent(
+ reader.result!.slice(0, 1 * 1024 * 1024) +
+ '\n...\nThe file is too big to display click the download icon to view/download it.',
+ );
+ } else {
+ setFileContent(reader.result as string);
+ }
+ };
+ reader.readAsText(file);
+ return;
+ }
- return;
- }
-
- if (file.size > 1 * 1024 * 1024) {
- const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`, {
- headers: {
- Range: 'bytes=0-' + 1 * 1024 * 1024, // 0 mb to 1 mb
- },
- });
+ if (file.size > 1 * 1024 * 1024) {
+ const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`, {
+ headers: {
+ Range: 'bytes=0-' + 1 * 1024 * 1024, // 0 mb to 1 mb
+ },
+ });
+ if (!res.ok) throw new Error('Failed to fetch file');
+ const text = await res.text();
+ setFileContent(
+ text + '\n...\nThe file is too big to display click the download icon to view/download it.',
+ );
+ return;
+ }
+ const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`);
+ if (!res.ok) throw new Error('Failed to fetch file');
const text = await res.text();
- setFileContent(
- text + '\n...\nThe file is too big to display click the download icon to view/download it.',
- );
- return;
+ setFileContent(text);
+ } catch {
+ setFileContent('Error loading file.');
}
-
- const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`);
- const text = await res.text();
-
- setFileContent(text);
- };
+ }, [dbFile, file, password]);
useEffect(() => {
if (code) {
setType('text');
- gettext();
+ getText();
} else if (overrideType === 'text' || type === 'text') {
- gettext();
+ getText();
} else {
return;
}
@@ -177,7 +180,10 @@ export default function DashboardFileType({
/>
) : (file as DbFile).thumbnail && dbFile ? (
-
+
);
case 'audio':
diff --git a/src/components/pages/metrics/index.tsx b/src/components/pages/metrics/index.tsx
index 7e8b8f87..e4450deb 100755
--- a/src/components/pages/metrics/index.tsx
+++ b/src/components/pages/metrics/index.tsx
@@ -7,6 +7,7 @@ import FilesUrlsCountGraph from './parts/FilesUrlsCountGraph';
import { useApiStats } from './useStats';
import { StatsCardsSkeleton } from './parts/StatsCards';
import { StatsTablesSkeleton } from './parts/StatsTables';
+import dayjs from 'dayjs';
const StatsCards = dynamic(() => import('./parts/StatsCards'));
const StatsTables = dynamic(() => import('./parts/StatsTables'));
@@ -14,9 +15,11 @@ const StorageGraph = dynamic(() => import('./parts/StorageGraph'));
const ViewsGraph = dynamic(() => import('./parts/ViewsGraph'));
export default function DashboardMetrics() {
+ const today = dayjs();
+
const [dateRange, setDateRange] = useState<[string | null, string | null]>([
- new Date(Date.now() - 86400000 * 7).toISOString(),
- new Date().toISOString(),
+ today.subtract(7, 'day').toISOString(),
+ today.toISOString(),
]);
const [open, setOpen] = useState(false);
@@ -40,17 +43,49 @@ export default function DashboardMetrics() {
return (
<>
setOpen(false)} size='auto'>
-
+
-
+
diff --git a/src/components/pages/serverSettings/parts/Domains.tsx b/src/components/pages/serverSettings/parts/Domains.tsx
index e554dc1e..edaac41d 100644
--- a/src/components/pages/serverSettings/parts/Domains.tsx
+++ b/src/components/pages/serverSettings/parts/Domains.tsx
@@ -63,12 +63,35 @@ export default function Domains({
-
+
{domains.map((domain, index) => (
-
-
-
-
{domain}
+
+
+
+ {domain}
setFiles([...f, ...files])}
+ onDrop={(f) => setFiles((prev) => [...f, ...prev])}
my='sm'
loading={dropLoading}
disabled={dropLoading}
@@ -220,7 +219,6 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
-
}
diff --git a/src/components/pages/upload/Text/index.tsx b/src/components/pages/upload/Text/index.tsx
index 0a84f269..a728447d 100755
--- a/src/components/pages/upload/Text/index.tsx
+++ b/src/components/pages/upload/Text/index.tsx
@@ -16,7 +16,7 @@ import {
import { useClipboard } from '@mantine/hooks';
import { IconCursorText, IconEyeFilled, IconFiles, IconUpload } from '@tabler/icons-react';
import Link from 'next/link';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import UploadOptionsButton from '../UploadOptionsButton';
import { renderMode } from '../renderMode';
import { uploadFiles } from '../uploadFiles';
@@ -30,15 +30,26 @@ export default function UploadText({
codeMeta: Parameters[0]['codeMeta'];
}) {
const clipboard = useClipboard();
-
const [options, ephemeral, clearEphemeral] = useUploadOptionsStore(
useShallow((state) => [state.options, state.ephemeral, state.clearEphemeral]),
);
-
const [selectedLanguage, setSelectedLanguage] = useState('txt');
const [text, setText] = useState('');
const [loading, setLoading] = useState(false);
+ useEffect(() => {
+ const handleBeforeUnload = (e: BeforeUnloadEvent) => {
+ if (text.length > 0) {
+ e.preventDefault();
+ }
+ };
+
+ window.addEventListener('beforeunload', handleBeforeUnload);
+ return () => {
+ window.removeEventListener('beforeunload', handleBeforeUnload);
+ };
+ }, [text]);
+
const renderIn = renderMode(selectedLanguage);
const handleTab = (e: React.KeyboardEvent) => {
@@ -52,12 +63,10 @@ export default function UploadText({
const upload = () => {
const blob = new Blob([text]);
-
const file = new File([blob], `text.${selectedLanguage}`, {
type: codeMeta.find((meta) => meta.ext === selectedLanguage)?.mime,
lastModified: Date.now(),
});
-
uploadFiles([file], {
clipboard,
setFiles: () => {},
diff --git a/src/components/render/code/HighlightCode.tsx b/src/components/render/code/HighlightCode.tsx
index 5a4c812f..1c68dbf7 100755
--- a/src/components/render/code/HighlightCode.tsx
+++ b/src/components/render/code/HighlightCode.tsx
@@ -37,7 +37,7 @@ export default function HighlightCode({ language, code }: { language: string; co
-
+
{displayLines.map((line, i) => (
diff --git a/src/lib/config/read/index.ts b/src/lib/config/read/index.ts
index 76c583e3..403204e3 100755
--- a/src/lib/config/read/index.ts
+++ b/src/lib/config/read/index.ts
@@ -143,6 +143,10 @@ export async function read() {
const database = (await readDatabaseSettings()) as Record;
const { dbEnv, env } = readEnv();
+ if (global.__tamperedConfig__) {
+ global.__tamperedConfig__ = [];
+ }
+
// this overwrites database settings with provided env vars if they exist
for (const [propPath, val] of Object.entries(dbEnv)) {
const col = Object.entries(DATABASE_TO_PROP).find(([_colName, path]) => path === propPath)?.[0];