mirror of
https://github.com/diced/zipline.git
synced 2025-12-05 20:40:12 -08:00
fix: random visual bugs + enhancements
This commit is contained in:
10
SECURITY.md
10
SECURITY.md
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------------------------- |
|
| ------- | ------------------ |
|
||||||
| 4.x.x | :white_check_mark: |
|
| 4.2.x | :white_check_mark: |
|
||||||
| < 3 | :white_check_mark: (EOL at June 2025) |
|
| < 3 | :x: |
|
||||||
| < 2 | :x: |
|
| < 2 | :x: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
Pill,
|
Pill,
|
||||||
PillsInput,
|
PillsInput,
|
||||||
ScrollArea,
|
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
@@ -61,8 +60,8 @@ import {
|
|||||||
removeFromFolder,
|
removeFromFolder,
|
||||||
viewFile,
|
viewFile,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import FileStat from './FileStat';
|
|
||||||
import EditFileDetailsModal from './EditFileDetailsModal';
|
import EditFileDetailsModal from './EditFileDetailsModal';
|
||||||
|
import FileStat from './FileStat';
|
||||||
|
|
||||||
function ActionButton({
|
function ActionButton({
|
||||||
Icon,
|
Icon,
|
||||||
@@ -189,9 +188,9 @@ export default function FileModal({
|
|||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
size='auto'
|
size='auto'
|
||||||
|
maw='90vw'
|
||||||
centered
|
centered
|
||||||
zIndex={200}
|
zIndex={200}
|
||||||
scrollAreaComponent={ScrollArea.Autosize}
|
|
||||||
>
|
>
|
||||||
{file ? (
|
{file ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Icon, IconFileUnknown, IconPlayerPlay, IconShieldLockFilled } from '@tabler/icons-react';
|
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 { renderMode } from '../pages/upload/renderMode';
|
||||||
import Render from '../render/Render';
|
import Render from '../render/Render';
|
||||||
import fileIcon from './fileIcon';
|
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 }) {
|
function Placeholder({ text, Icon, ...props }: { text: string; Icon: Icon; onClick?: () => void }) {
|
||||||
return (
|
return (
|
||||||
<Center py='xs' style={{ height: '100%', width: '100%', cursor: 'pointed' }} {...props}>
|
<Center py='xs' style={{ height: '100%', width: '100%', cursor: 'pointer' }} {...props}>
|
||||||
<PlaceholderContent text={text} Icon={Icon} />
|
<PlaceholderContent text={text} Icon={Icon} />
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
@@ -83,57 +83,60 @@ export default function DashboardFileType({
|
|||||||
const disableMediaPreview = useSettingsStore((state) => state.settings.disableMediaPreview);
|
const disableMediaPreview = useSettingsStore((state) => state.settings.disableMediaPreview);
|
||||||
|
|
||||||
const dbFile = 'id' in file;
|
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 [fileContent, setFileContent] = useState('');
|
||||||
const [type, setType] = useState<string>(file.type.split('/')[0]);
|
const [type, setType] = useState<string>(file.type.split('/')[0]);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const gettext = async () => {
|
const getText = useCallback(async () => {
|
||||||
if (!dbFile) {
|
try {
|
||||||
const reader = new FileReader();
|
if (!dbFile) {
|
||||||
reader.onload = () => {
|
const reader = new FileReader();
|
||||||
if ((reader.result! as string).length > 1 * 1024 * 1024) {
|
reader.onload = () => {
|
||||||
setFileContent(
|
if ((reader.result! as string).length > 1 * 1024 * 1024) {
|
||||||
reader.result!.slice(0, 1 * 1024 * 1024) +
|
setFileContent(
|
||||||
'\n...\nThe file is too big to display click the download icon to view/download it.',
|
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);
|
} else {
|
||||||
}
|
setFileContent(reader.result as string);
|
||||||
};
|
}
|
||||||
reader.readAsText(file);
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
if (file.size > 1 * 1024 * 1024) {
|
||||||
}
|
const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`, {
|
||||||
|
headers: {
|
||||||
if (file.size > 1 * 1024 * 1024) {
|
Range: 'bytes=0-' + 1 * 1024 * 1024, // 0 mb to 1 mb
|
||||||
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();
|
const text = await res.text();
|
||||||
setFileContent(
|
setFileContent(text);
|
||||||
text + '\n...\nThe file is too big to display click the download icon to view/download it.',
|
} catch {
|
||||||
);
|
setFileContent('Error loading file.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}, [dbFile, file, password]);
|
||||||
const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`);
|
|
||||||
const text = await res.text();
|
|
||||||
|
|
||||||
setFileContent(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (code) {
|
if (code) {
|
||||||
setType('text');
|
setType('text');
|
||||||
gettext();
|
getText();
|
||||||
} else if (overrideType === 'text' || type === 'text') {
|
} else if (overrideType === 'text' || type === 'text') {
|
||||||
gettext();
|
getText();
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -177,7 +180,10 @@ export default function DashboardFileType({
|
|||||||
/>
|
/>
|
||||||
) : (file as DbFile).thumbnail && dbFile ? (
|
) : (file as DbFile).thumbnail && dbFile ? (
|
||||||
<Box pos='relative'>
|
<Box pos='relative'>
|
||||||
<MantineImage src={`/raw/${(file as DbFile).thumbnail!.path}`} alt={file.name} />
|
<MantineImage
|
||||||
|
src={`/raw/${(file as DbFile).thumbnail!.path}`}
|
||||||
|
alt={file.name || 'Video thumbnail'}
|
||||||
|
/>
|
||||||
|
|
||||||
<Center
|
<Center
|
||||||
pos='absolute'
|
pos='absolute'
|
||||||
@@ -203,7 +209,7 @@ export default function DashboardFileType({
|
|||||||
<Center>
|
<Center>
|
||||||
<MantineImage
|
<MantineImage
|
||||||
src={dbFile ? `/raw/${file.name}${password ? `?pw=${password}` : ''}` : URL.createObjectURL(file)}
|
src={dbFile ? `/raw/${file.name}${password ? `?pw=${password}` : ''}` : URL.createObjectURL(file)}
|
||||||
alt={file.name}
|
alt={file.name || 'Image'}
|
||||||
style={{
|
style={{
|
||||||
cursor: allowZoom ? 'zoom-in' : 'default',
|
cursor: allowZoom ? 'zoom-in' : 'default',
|
||||||
maxWidth: '70vw',
|
maxWidth: '70vw',
|
||||||
@@ -217,7 +223,7 @@ export default function DashboardFileType({
|
|||||||
src={
|
src={
|
||||||
dbFile ? `/raw/${file.name}${password ? `?pw=${password}` : ''}` : URL.createObjectURL(file)
|
dbFile ? `/raw/${file.name}${password ? `?pw=${password}` : ''}` : URL.createObjectURL(file)
|
||||||
}
|
}
|
||||||
alt={file.name}
|
alt={file.name || 'Image'}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '95vw',
|
maxWidth: '95vw',
|
||||||
maxHeight: '95vh',
|
maxHeight: '95vh',
|
||||||
@@ -234,7 +240,7 @@ export default function DashboardFileType({
|
|||||||
fit='contain'
|
fit='contain'
|
||||||
mah={400}
|
mah={400}
|
||||||
src={dbFile ? `/raw/${file.name}${password ? `?pw=${password}` : ''}` : URL.createObjectURL(file)}
|
src={dbFile ? `/raw/${file.name}${password ? `?pw=${password}` : ''}` : URL.createObjectURL(file)}
|
||||||
alt={file.name}
|
alt={file.name || 'Image'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'audio':
|
case 'audio':
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import FilesUrlsCountGraph from './parts/FilesUrlsCountGraph';
|
|||||||
import { useApiStats } from './useStats';
|
import { useApiStats } from './useStats';
|
||||||
import { StatsCardsSkeleton } from './parts/StatsCards';
|
import { StatsCardsSkeleton } from './parts/StatsCards';
|
||||||
import { StatsTablesSkeleton } from './parts/StatsTables';
|
import { StatsTablesSkeleton } from './parts/StatsTables';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const StatsCards = dynamic(() => import('./parts/StatsCards'));
|
const StatsCards = dynamic(() => import('./parts/StatsCards'));
|
||||||
const StatsTables = dynamic(() => import('./parts/StatsTables'));
|
const StatsTables = dynamic(() => import('./parts/StatsTables'));
|
||||||
@@ -14,9 +15,11 @@ const StorageGraph = dynamic(() => import('./parts/StorageGraph'));
|
|||||||
const ViewsGraph = dynamic(() => import('./parts/ViewsGraph'));
|
const ViewsGraph = dynamic(() => import('./parts/ViewsGraph'));
|
||||||
|
|
||||||
export default function DashboardMetrics() {
|
export default function DashboardMetrics() {
|
||||||
|
const today = dayjs();
|
||||||
|
|
||||||
const [dateRange, setDateRange] = useState<[string | null, string | null]>([
|
const [dateRange, setDateRange] = useState<[string | null, string | null]>([
|
||||||
new Date(Date.now() - 86400000 * 7).toISOString(),
|
today.subtract(7, 'day').toISOString(),
|
||||||
new Date().toISOString(),
|
today.toISOString(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -40,17 +43,49 @@ export default function DashboardMetrics() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal title='Change range' opened={open} onClose={() => setOpen(false)} size='auto'>
|
<Modal title='Change range' opened={open} onClose={() => setOpen(false)} size='auto'>
|
||||||
<Paper withBorder>
|
<Paper withBorder style={{ minHeight: 300 }}>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
type='range'
|
type='range'
|
||||||
value={dateRange}
|
value={dateRange}
|
||||||
onChange={handleDateChange}
|
onChange={handleDateChange}
|
||||||
allowSingleDateInRange={false}
|
allowSingleDateInRange={false}
|
||||||
maxDate={new Date()}
|
maxDate={new Date()}
|
||||||
|
presets={[
|
||||||
|
{
|
||||||
|
value: [today.subtract(2, 'day').format('YYYY-MM-DD'), today.format('YYYY-MM-DD')],
|
||||||
|
label: 'Last two days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [today.subtract(7, 'day').format('YYYY-MM-DD'), today.format('YYYY-MM-DD')],
|
||||||
|
label: 'Last 7 days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [today.startOf('month').format('YYYY-MM-DD'), today.format('YYYY-MM-DD')],
|
||||||
|
label: 'This month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [
|
||||||
|
today.subtract(1, 'month').startOf('month').format('YYYY-MM-DD'),
|
||||||
|
today.subtract(1, 'month').endOf('month').format('YYYY-MM-DD'),
|
||||||
|
],
|
||||||
|
label: 'Last month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [today.startOf('year').format('YYYY-MM-DD'), today.format('YYYY-MM-DD')],
|
||||||
|
label: 'This year',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [
|
||||||
|
today.subtract(1, 'year').startOf('year').format('YYYY-MM-DD'),
|
||||||
|
today.subtract(1, 'year').endOf('year').format('YYYY-MM-DD'),
|
||||||
|
],
|
||||||
|
label: 'Last year',
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Group mt='md'>
|
<Group mt='lg'>
|
||||||
<Button fullWidth onClick={() => setOpen(false)}>
|
<Button fullWidth onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -63,12 +63,35 @@ export default function Domains({
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<SimpleGrid mt='md' cols={{ base: 1, sm: 2, md: 3 }} spacing='xs'>
|
<SimpleGrid mt='md' cols={{ base: 1, sm: 2, md: 3 }} spacing='md' verticalSpacing='md'>
|
||||||
{domains.map((domain, index) => (
|
{domains.map((domain, index) => (
|
||||||
<Paper key={index} withBorder p='xs'>
|
<Paper
|
||||||
<Group justify='space-between'>
|
key={index}
|
||||||
<div>
|
withBorder
|
||||||
<strong>{domain}</strong>
|
p='md'
|
||||||
|
radius='md'
|
||||||
|
shadow='xs'
|
||||||
|
style={{
|
||||||
|
background: 'rgba(0,0,0,0.03)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: 64,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group justify='space-between' align='center' wrap='nowrap'>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
minWidth: 0,
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{domain}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant='subtle'
|
variant='subtle'
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useClipboard, useColorScheme } from '@mantine/hooks';
|
|||||||
import { notifications, showNotification } from '@mantine/notifications';
|
import { notifications, showNotification } from '@mantine/notifications';
|
||||||
import { IconDeviceSdCard, IconFiles, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconDeviceSdCard, IconFiles, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import UploadOptionsButton from '../UploadOptionsButton';
|
import UploadOptionsButton from '../UploadOptionsButton';
|
||||||
import { uploadFiles } from '../uploadFiles';
|
import { uploadFiles } from '../uploadFiles';
|
||||||
import ToUploadFile from './ToUploadFile';
|
import ToUploadFile from './ToUploadFile';
|
||||||
@@ -49,34 +49,23 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
|
|||||||
});
|
});
|
||||||
const [dropLoading, setLoading] = useState(false);
|
const [dropLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handlePaste = (e: ClipboardEvent) => {
|
const aggSize = useCallback(() => files.reduce((acc, file) => acc + file.size, 0), [files]);
|
||||||
if (!e.clipboardData) return;
|
|
||||||
|
|
||||||
|
const handlePaste = useCallback((e: ClipboardEvent) => {
|
||||||
|
if (!e.clipboardData) return;
|
||||||
for (let i = 0; i !== e.clipboardData.items.length; ++i) {
|
for (let i = 0; i !== e.clipboardData.items.length; ++i) {
|
||||||
if (!e.clipboardData.items[i].type.startsWith('image')) return;
|
if (!e.clipboardData.items[i].type.startsWith('image')) return;
|
||||||
|
|
||||||
const blob = e.clipboardData.items[i].getAsFile();
|
const blob = e.clipboardData.items[i].getAsFile();
|
||||||
if (!blob) return;
|
if (!blob) return;
|
||||||
|
setFiles((prev) => [...prev, blob]);
|
||||||
setFiles([...files, blob]);
|
showNotification({ message: `Image ${blob.name} pasted from clipboard`, color: 'blue' });
|
||||||
showNotification({
|
|
||||||
message: `Image ${blob.name} pasted from clipboard`,
|
|
||||||
color: 'blue',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const aggSize = () => files.reduce((acc, file) => acc + file.size, 0);
|
|
||||||
|
|
||||||
const upload = () => {
|
const upload = () => {
|
||||||
const toPartialFiles: File[] = [];
|
const toPartialFiles: File[] = files.filter(
|
||||||
for (let i = 0; i !== files.length; ++i) {
|
(file) => config.chunks.enabled && file.size >= bytes(config.chunks.max),
|
||||||
const file = files[i];
|
);
|
||||||
if (config.chunks.enabled && file.size >= bytes(config.chunks.max)) {
|
|
||||||
toPartialFiles.push(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toPartialFiles.length > 0) {
|
if (toPartialFiles.length > 0) {
|
||||||
uploadPartialFiles(toPartialFiles, {
|
uploadPartialFiles(toPartialFiles, {
|
||||||
setFiles,
|
setFiles,
|
||||||
@@ -91,7 +80,7 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const size = aggSize();
|
const size = aggSize();
|
||||||
if (size > bytes(config.files.maxFileSize) && !toPartialFiles.length) {
|
if (size > bytes(config.files.maxFileSize)) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: 'Upload may fail',
|
title: 'Upload may fail',
|
||||||
color: 'yellow',
|
color: 'yellow',
|
||||||
@@ -105,7 +94,6 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadFiles(files, {
|
uploadFiles(files, {
|
||||||
setFiles,
|
setFiles,
|
||||||
setLoading,
|
setLoading,
|
||||||
@@ -121,11 +109,22 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('paste', handlePaste);
|
document.addEventListener('paste', handlePaste);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('paste', handlePaste);
|
document.removeEventListener('paste', handlePaste);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [handlePaste]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
|
if (files.length > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
};
|
||||||
|
}, [files.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -142,7 +141,7 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Dropzone
|
<Dropzone
|
||||||
onDrop={(f) => setFiles([...f, ...files])}
|
onDrop={(f) => setFiles((prev) => [...f, ...prev])}
|
||||||
my='sm'
|
my='sm'
|
||||||
loading={dropLoading}
|
loading={dropLoading}
|
||||||
disabled={dropLoading}
|
disabled={dropLoading}
|
||||||
@@ -220,7 +219,6 @@ export default function UploadFile({ title, folder }: { title?: string; folder?:
|
|||||||
|
|
||||||
<Group justify='right' gap='sm' my='md'>
|
<Group justify='right' gap='sm' my='md'>
|
||||||
<UploadOptionsButton folder={folder} numFiles={files.length} />
|
<UploadOptionsButton folder={folder} numFiles={files.length} />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
leftSection={<IconUpload size={18} />}
|
leftSection={<IconUpload size={18} />}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import { useClipboard } from '@mantine/hooks';
|
import { useClipboard } from '@mantine/hooks';
|
||||||
import { IconCursorText, IconEyeFilled, IconFiles, IconUpload } from '@tabler/icons-react';
|
import { IconCursorText, IconEyeFilled, IconFiles, IconUpload } from '@tabler/icons-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import UploadOptionsButton from '../UploadOptionsButton';
|
import UploadOptionsButton from '../UploadOptionsButton';
|
||||||
import { renderMode } from '../renderMode';
|
import { renderMode } from '../renderMode';
|
||||||
import { uploadFiles } from '../uploadFiles';
|
import { uploadFiles } from '../uploadFiles';
|
||||||
@@ -30,15 +30,26 @@ export default function UploadText({
|
|||||||
codeMeta: Parameters<typeof DashboardUploadText>[0]['codeMeta'];
|
codeMeta: Parameters<typeof DashboardUploadText>[0]['codeMeta'];
|
||||||
}) {
|
}) {
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
const [options, ephemeral, clearEphemeral] = useUploadOptionsStore(
|
const [options, ephemeral, clearEphemeral] = useUploadOptionsStore(
|
||||||
useShallow((state) => [state.options, state.ephemeral, state.clearEphemeral]),
|
useShallow((state) => [state.options, state.ephemeral, state.clearEphemeral]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selectedLanguage, setSelectedLanguage] = useState('txt');
|
const [selectedLanguage, setSelectedLanguage] = useState('txt');
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
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 renderIn = renderMode(selectedLanguage);
|
||||||
|
|
||||||
const handleTab = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleTab = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
@@ -52,12 +63,10 @@ export default function UploadText({
|
|||||||
|
|
||||||
const upload = () => {
|
const upload = () => {
|
||||||
const blob = new Blob([text]);
|
const blob = new Blob([text]);
|
||||||
|
|
||||||
const file = new File([blob], `text.${selectedLanguage}`, {
|
const file = new File([blob], `text.${selectedLanguage}`, {
|
||||||
type: codeMeta.find((meta) => meta.ext === selectedLanguage)?.mime,
|
type: codeMeta.find((meta) => meta.ext === selectedLanguage)?.mime,
|
||||||
lastModified: Date.now(),
|
lastModified: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadFiles([file], {
|
uploadFiles([file], {
|
||||||
clipboard,
|
clipboard,
|
||||||
setFiles: () => {},
|
setFiles: () => {},
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function HighlightCode({ language, code }: { language: string; co
|
|||||||
</CopyButton>
|
</CopyButton>
|
||||||
|
|
||||||
<ScrollArea type='auto' dir='ltr' offsetScrollbars={false}>
|
<ScrollArea type='auto' dir='ltr' offsetScrollbars={false}>
|
||||||
<pre style={{ margin: 0 }} className='theme'>
|
<pre style={{ margin: 0, whiteSpace: 'pre', overflowX: 'auto' }} className='theme'>
|
||||||
<code className='theme'>
|
<code className='theme'>
|
||||||
{displayLines.map((line, i) => (
|
{displayLines.map((line, i) => (
|
||||||
<div key={i}>
|
<div key={i}>
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ export async function read() {
|
|||||||
const database = (await readDatabaseSettings()) as Record<string, any>;
|
const database = (await readDatabaseSettings()) as Record<string, any>;
|
||||||
const { dbEnv, env } = readEnv();
|
const { dbEnv, env } = readEnv();
|
||||||
|
|
||||||
|
if (global.__tamperedConfig__) {
|
||||||
|
global.__tamperedConfig__ = [];
|
||||||
|
}
|
||||||
|
|
||||||
// this overwrites database settings with provided env vars if they exist
|
// this overwrites database settings with provided env vars if they exist
|
||||||
for (const [propPath, val] of Object.entries(dbEnv)) {
|
for (const [propPath, val] of Object.entries(dbEnv)) {
|
||||||
const col = Object.entries(DATABASE_TO_PROP).find(([_colName, path]) => path === propPath)?.[0];
|
const col = Object.entries(DATABASE_TO_PROP).find(([_colName, path]) => path === propPath)?.[0];
|
||||||
|
|||||||
Reference in New Issue
Block a user