mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-06-15 20:41:21 +00:00
Merge pull request #57 from Qortal/feature/large-files-and-names
Feature/large files and names
This commit is contained in:
commit
345d192bb5
20
src/App.tsx
20
src/App.tsx
@ -766,6 +766,24 @@ function App() {
|
|||||||
balanceSetInterval();
|
balanceSetInterval();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refetchUserInfo = () => {
|
||||||
|
window
|
||||||
|
.sendMessage('userInfo')
|
||||||
|
.then((response) => {
|
||||||
|
if (response && !response.error) {
|
||||||
|
setUserInfo(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to get user info:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBalanceAndUserInfoFunc = () => {
|
||||||
|
getBalanceFunc();
|
||||||
|
refetchUserInfo();
|
||||||
|
};
|
||||||
const getLtcBalanceFunc = () => {
|
const getLtcBalanceFunc = () => {
|
||||||
setLtcBalanceLoading(true);
|
setLtcBalanceLoading(true);
|
||||||
window
|
window
|
||||||
@ -1503,7 +1521,7 @@ function App() {
|
|||||||
</TextP>
|
</TextP>
|
||||||
|
|
||||||
<RefreshIcon
|
<RefreshIcon
|
||||||
onClick={getBalanceFunc}
|
onClick={getBalanceAndUserInfoFunc}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
@ -1506,6 +1506,7 @@ export async function publishOnQDNCase(request, event) {
|
|||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
name = '',
|
||||||
identifier,
|
identifier,
|
||||||
service,
|
service,
|
||||||
title,
|
title,
|
||||||
@ -1521,6 +1522,7 @@ export async function publishOnQDNCase(request, event) {
|
|||||||
|
|
||||||
const response = await publishOnQDN({
|
const response = await publishOnQDN({
|
||||||
data,
|
data,
|
||||||
|
name,
|
||||||
identifier,
|
identifier,
|
||||||
service,
|
service,
|
||||||
title,
|
title,
|
||||||
|
@ -805,21 +805,22 @@ export async function getNameInfo() {
|
|||||||
const wallet = await getSaveWallet();
|
const wallet = await getSaveWallet();
|
||||||
const address = wallet.address0;
|
const address = wallet.address0;
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + '/names/address/' + address);
|
const response = await fetch(validApi + '/names/primary/' + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0].name;
|
return nameData.name;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNameInfoForOthers(address) {
|
export async function getNameInfoForOthers(address) {
|
||||||
|
if (!address) return '';
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + '/names/address/' + address);
|
const response = await fetch(validApi + '/names/primary/' + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0].name;
|
return nameData?.name;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
AppLibrarySubTitle,
|
AppLibrarySubTitle,
|
||||||
AppPublishTagsContainer,
|
AppPublishTagsContainer,
|
||||||
@ -25,6 +25,7 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
|||||||
import { getFee } from '../../background/background.ts';
|
import { getFee } from '../../background/background.ts';
|
||||||
import { fileToBase64 } from '../../utils/fileReading';
|
import { fileToBase64 } from '../../utils/fileReading';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useSortedMyNames } from '../../hooks/useSortedMyNames';
|
||||||
|
|
||||||
const CustomSelect = styled(Select)({
|
const CustomSelect = styled(Select)({
|
||||||
border: '0.5px solid var(--50-white, #FFFFFF80)',
|
border: '0.5px solid var(--50-white, #FFFFFF80)',
|
||||||
@ -58,7 +59,8 @@ const CustomMenuItem = styled(MenuItem)({
|
|||||||
// },
|
// },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AppPublish = ({ names, categories }) => {
|
export const AppPublish = ({ categories, myAddress, myName }) => {
|
||||||
|
const [names, setNames] = useState([]);
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
@ -148,6 +150,27 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
getQapp(name, appType);
|
getQapp(name, appType);
|
||||||
}, [name, appType]);
|
}, [name, appType]);
|
||||||
|
|
||||||
|
const getNames = useCallback(async () => {
|
||||||
|
if (!myAddress) return;
|
||||||
|
try {
|
||||||
|
setIsLoading('Loading names');
|
||||||
|
const res = await fetch(
|
||||||
|
`${getBaseApiReact()}/names/address/${myAddress}?limit=0`
|
||||||
|
);
|
||||||
|
const data = await res.json();
|
||||||
|
setNames(data?.map((item) => item.name));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading('');
|
||||||
|
}
|
||||||
|
}, [myAddress]);
|
||||||
|
useEffect(() => {
|
||||||
|
getNames();
|
||||||
|
}, [getNames]);
|
||||||
|
|
||||||
|
const mySortedNames = useSortedMyNames(names, myName);
|
||||||
|
|
||||||
const publishApp = async () => {
|
const publishApp = async () => {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const data = {
|
||||||
@ -194,13 +217,13 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const fileBase64 = await fileToBase64(file);
|
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
window
|
window
|
||||||
.sendMessage('publishOnQDN', {
|
.sendMessage('publishOnQDN', {
|
||||||
data: fileBase64,
|
data: file,
|
||||||
service: appType,
|
service: appType,
|
||||||
title,
|
title,
|
||||||
|
name,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
tag1,
|
tag1,
|
||||||
@ -317,7 +340,7 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
</em>
|
</em>
|
||||||
{/* This is the placeholder item */}
|
{/* This is the placeholder item */}
|
||||||
</CustomMenuItem>
|
</CustomMenuItem>
|
||||||
{names.map((name) => {
|
{mySortedNames.map((name) => {
|
||||||
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
||||||
})}
|
})}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
|
@ -94,7 +94,7 @@ export const AppViewer = forwardRef<HTMLIFrameElement, AppViewerProps>(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const iframe = iframeRef?.current;
|
const iframe = iframeRef?.current;
|
||||||
if (!iframe) return;
|
if (!iframe || !iframe?.src) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const targetOrigin = new URL(iframe.src).origin;
|
const targetOrigin = new URL(iframe.src).origin;
|
||||||
@ -109,7 +109,7 @@ export const AppViewer = forwardRef<HTMLIFrameElement, AppViewerProps>(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const iframe = iframeRef?.current;
|
const iframe = iframeRef?.current;
|
||||||
if (!iframe) return;
|
if (!iframe || !iframe?.src) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const targetOrigin = new URL(iframe.src).origin;
|
const targetOrigin = new URL(iframe.src).origin;
|
||||||
|
@ -40,6 +40,7 @@ export const AppsDesktop = ({
|
|||||||
hasUnreadGroups,
|
hasUnreadGroups,
|
||||||
setDesktopViewMode,
|
setDesktopViewMode,
|
||||||
desktopViewMode,
|
desktopViewMode,
|
||||||
|
myAddress,
|
||||||
}) => {
|
}) => {
|
||||||
const [availableQapps, setAvailableQapps] = useState([]);
|
const [availableQapps, setAvailableQapps] = useState([]);
|
||||||
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
||||||
@ -485,6 +486,7 @@ export const AppsDesktop = ({
|
|||||||
setMode={setMode}
|
setMode={setMode}
|
||||||
myApp={myApp}
|
myApp={myApp}
|
||||||
myWebsite={myWebsite}
|
myWebsite={myWebsite}
|
||||||
|
myAddress={myAddress}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -515,7 +517,11 @@ export const AppsDesktop = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{mode === 'publish' && !selectedTab && (
|
{mode === 'publish' && !selectedTab && (
|
||||||
<AppPublish names={myName ? [myName] : []} categories={categories} />
|
<AppPublish
|
||||||
|
categories={categories}
|
||||||
|
myAddress={myAddress}
|
||||||
|
myName={myName}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tabs.map((tab) => {
|
{tabs.map((tab) => {
|
||||||
@ -552,6 +558,7 @@ export const AppsDesktop = ({
|
|||||||
myApp={myApp}
|
myApp={myApp}
|
||||||
myName={myName}
|
myName={myName}
|
||||||
myWebsite={myWebsite}
|
myWebsite={myWebsite}
|
||||||
|
myAddress={myAddress}
|
||||||
setMode={setMode}
|
setMode={setMode}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -32,17 +32,9 @@ export const AppsDevModeNavBar = () => {
|
|||||||
'tutorial',
|
'tutorial',
|
||||||
]);
|
]);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
|
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
|
||||||
const tabsRef = useRef(null);
|
const tabsRef = useRef(null);
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
|
||||||
|
|
||||||
const handleClick = (event) => {
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
|
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
|
||||||
|
@ -22,6 +22,7 @@ export const AppsHomeDesktop = ({
|
|||||||
myWebsite,
|
myWebsite,
|
||||||
availableQapps,
|
availableQapps,
|
||||||
myName,
|
myName,
|
||||||
|
myAddress,
|
||||||
}) => {
|
}) => {
|
||||||
const [qortalUrl, setQortalUrl] = useState('');
|
const [qortalUrl, setQortalUrl] = useState('');
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -157,7 +158,7 @@ export const AppsHomeDesktop = ({
|
|||||||
</AppCircleContainer>
|
</AppCircleContainer>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
|
|
||||||
<AppsPrivate myName={myName} />
|
<AppsPrivate myName={myName} myAddress={myAddress} />
|
||||||
|
|
||||||
<SortablePinnedApps
|
<SortablePinnedApps
|
||||||
isDesktop={true}
|
isDesktop={true}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import React, { useContext, useMemo, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -31,16 +37,20 @@ import {
|
|||||||
} from './Apps-styles';
|
} from './Apps-styles';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import ImageUploader from '../../common/ImageUploader';
|
import ImageUploader from '../../common/ImageUploader';
|
||||||
import { QORTAL_APP_CONTEXT } from '../../App';
|
import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App';
|
||||||
import { fileToBase64 } from '../../utils/fileReading';
|
import { fileToBase64 } from '../../utils/fileReading';
|
||||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||||
import { getFee } from '../../background/background.ts';
|
import { getFee } from '../../background/background.ts';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useSortedMyNames } from '../../hooks/useSortedMyNames';
|
||||||
|
|
||||||
const maxFileSize = 50 * 1024 * 1024; // 50MB
|
const maxFileSize = 50 * 1024 * 1024; // 50MB
|
||||||
|
|
||||||
export const AppsPrivate = ({ myName }) => {
|
export const AppsPrivate = ({ myName, myAddress }) => {
|
||||||
|
const [names, setNames] = useState([]);
|
||||||
|
const [name, setName] = useState(0);
|
||||||
|
|
||||||
const { openApp } = useHandlePrivateApps();
|
const { openApp } = useHandlePrivateApps();
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
const [logo, setLogo] = useState(null);
|
const [logo, setLogo] = useState(null);
|
||||||
@ -90,6 +100,8 @@ export const AppsPrivate = ({ myName }) => {
|
|||||||
name: '',
|
name: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mySortedNames = useSortedMyNames(names, myName);
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
'application/zip': ['.zip'], // Only accept zip files
|
'application/zip': ['.zip'], // Only accept zip files
|
||||||
@ -210,6 +222,8 @@ export const AppsPrivate = ({ myName }) => {
|
|||||||
data: decryptedData,
|
data: decryptedData,
|
||||||
identifier: newPrivateAppValues?.identifier,
|
identifier: newPrivateAppValues?.identifier,
|
||||||
service: newPrivateAppValues?.service,
|
service: newPrivateAppValues?.service,
|
||||||
|
uploadType: 'base64',
|
||||||
|
name,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
@ -232,7 +246,7 @@ export const AppsPrivate = ({ myName }) => {
|
|||||||
{
|
{
|
||||||
identifier: newPrivateAppValues?.identifier,
|
identifier: newPrivateAppValues?.identifier,
|
||||||
service: newPrivateAppValues?.service,
|
service: newPrivateAppValues?.service,
|
||||||
name: myName,
|
name,
|
||||||
groupId: selectedGroup,
|
groupId: selectedGroup,
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
@ -262,6 +276,22 @@ export const AppsPrivate = ({ myName }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNames = useCallback(async () => {
|
||||||
|
if (!myAddress) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${getBaseApiReact()}/names/address/${myAddress}?limit=0`
|
||||||
|
);
|
||||||
|
const data = await res.json();
|
||||||
|
setNames(data?.map((item) => item.name));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}, [myAddress]);
|
||||||
|
useEffect(() => {
|
||||||
|
getNames();
|
||||||
|
}, [getNames]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
@ -558,7 +588,34 @@ export const AppsPrivate = ({ myName }) => {
|
|||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})}
|
})}
|
||||||
</PublishQAppChoseFile>
|
</PublishQAppChoseFile>
|
||||||
|
<Spacer height="20px" />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Select a Qortal name</Label>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
labelId="demo-simple-select-label"
|
||||||
|
id="demo-simple-select"
|
||||||
|
value={name}
|
||||||
|
label="Groups where you are an admin"
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>No name selected</MenuItem>
|
||||||
|
{mySortedNames.map((name) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
<Spacer height="20px" />
|
<Spacer height="20px" />
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
@ -866,6 +866,7 @@ export const ChatGroup = ({
|
|||||||
data: 'RA==',
|
data: 'RA==',
|
||||||
identifier: onEditMessage?.images[0]?.identifier,
|
identifier: onEditMessage?.images[0]?.identifier,
|
||||||
service: onEditMessage?.images[0]?.service,
|
service: onEditMessage?.images[0]?.service,
|
||||||
|
uploadType: 'base64',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import { convert } from 'html-to-text';
|
|||||||
import { generateHTML } from '@tiptap/react';
|
import { generateHTML } from '@tiptap/react';
|
||||||
import ErrorBoundary from '../../common/ErrorBoundary';
|
import ErrorBoundary from '../../common/ErrorBoundary';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { isHtmlString } from '../../utils/chat';
|
||||||
|
|
||||||
const extractTextFromHTML = (htmlString = '') => {
|
const extractTextFromHTML = (htmlString = '') => {
|
||||||
return convert(htmlString, {
|
return convert(htmlString, {
|
||||||
@ -76,8 +77,11 @@ export const ChatOptions = ({
|
|||||||
return untransformedMessages?.map((item) => {
|
return untransformedMessages?.map((item) => {
|
||||||
if (item?.messageText) {
|
if (item?.messageText) {
|
||||||
let transformedMessage = item?.messageText;
|
let transformedMessage = item?.messageText;
|
||||||
|
const isHtml = isHtmlString(item?.messageText);
|
||||||
try {
|
try {
|
||||||
transformedMessage = generateHTML(item?.messageText, [
|
transformedMessage = isHtml
|
||||||
|
? item?.messageText
|
||||||
|
: generateHTML(item?.messageText, [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
Highlight,
|
Highlight,
|
||||||
|
@ -109,6 +109,7 @@ export const GroupAvatar = ({
|
|||||||
data: avatarBase64,
|
data: avatarBase64,
|
||||||
identifier: `qortal_group_avatar_${groupId}`,
|
identifier: `qortal_group_avatar_${groupId}`,
|
||||||
service: 'THUMBNAIL',
|
service: 'THUMBNAIL',
|
||||||
|
uploadType: 'base64',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
|
@ -47,7 +47,11 @@ import level8Img from '../../assets/badges/level-8.png';
|
|||||||
import level9Img from '../../assets/badges/level-9.png';
|
import level9Img from '../../assets/badges/level-9.png';
|
||||||
import level10Img from '../../assets/badges/level-10.png';
|
import level10Img from '../../assets/badges/level-10.png';
|
||||||
import { Embed } from '../Embeds/Embed';
|
import { Embed } from '../Embeds/Embed';
|
||||||
import { buildImageEmbedLink, messageHasImage } from '../../utils/chat';
|
import {
|
||||||
|
buildImageEmbedLink,
|
||||||
|
isHtmlString,
|
||||||
|
messageHasImage,
|
||||||
|
} from '../../utils/chat';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const getBadgeImg = (level) => {
|
const getBadgeImg = (level) => {
|
||||||
@ -135,6 +139,8 @@ export const MessageItem = memo(
|
|||||||
|
|
||||||
const htmlText = useMemo(() => {
|
const htmlText = useMemo(() => {
|
||||||
if (message?.messageText) {
|
if (message?.messageText) {
|
||||||
|
const isHtml = isHtmlString(message?.messageText);
|
||||||
|
if (isHtml) return message?.messageText;
|
||||||
return generateHTML(message?.messageText, [
|
return generateHTML(message?.messageText, [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
@ -147,6 +153,8 @@ export const MessageItem = memo(
|
|||||||
|
|
||||||
const htmlReply = useMemo(() => {
|
const htmlReply = useMemo(() => {
|
||||||
if (reply?.messageText) {
|
if (reply?.messageText) {
|
||||||
|
const isHtml = isHtmlString(reply?.messageText);
|
||||||
|
if (isHtml) return reply?.messageText;
|
||||||
return generateHTML(reply?.messageText, [
|
return generateHTML(reply?.messageText, [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
@ -628,6 +636,18 @@ export const ReplyPreview = ({ message, isEdit = false }) => {
|
|||||||
'tutorial',
|
'tutorial',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const replyMessageText = useMemo(() => {
|
||||||
|
const isHtml = isHtmlString(message?.messageText);
|
||||||
|
if (isHtml) return message?.messageText;
|
||||||
|
return generateHTML(message?.messageText, [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Highlight,
|
||||||
|
Mention,
|
||||||
|
TextStyle,
|
||||||
|
]);
|
||||||
|
}, [message?.messageText]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -673,15 +693,7 @@ export const ReplyPreview = ({ message, isEdit = false }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{message?.messageText && (
|
{message?.messageText && (
|
||||||
<MessageDisplay
|
<MessageDisplay htmlContent={replyMessageText} />
|
||||||
htmlContent={generateHTML(message?.messageText, [
|
|
||||||
StarterKit,
|
|
||||||
Underline,
|
|
||||||
Highlight,
|
|
||||||
Mention,
|
|
||||||
TextStyle,
|
|
||||||
])}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{message?.decryptedData?.type === 'notification' ? (
|
{message?.decryptedData?.type === 'notification' ? (
|
||||||
|
@ -265,11 +265,11 @@ export const getDataPublishesFunc = async (groupId, type) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getNameInfo(address: string) {
|
export async function getNameInfo(address: string) {
|
||||||
const response = await fetch(`${getBaseApiReact()}/names/address/` + address);
|
const response = await fetch(`${getBaseApiReact()}/names/primary/` + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
|
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0]?.name;
|
return nameData?.name;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -523,7 +523,7 @@ export const Group = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error', error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}, [setMutedGroups]);
|
}, [setMutedGroups]);
|
||||||
|
|
||||||
@ -2357,6 +2357,7 @@ export const Group = ({
|
|||||||
hasUnreadDirects={directChatHasUnread}
|
hasUnreadDirects={directChatHasUnread}
|
||||||
show={desktopViewMode === 'apps'}
|
show={desktopViewMode === 'apps'}
|
||||||
myName={userInfo?.name}
|
myName={userInfo?.name}
|
||||||
|
myAddress={userInfo?.address}
|
||||||
isGroups={isOpenSideViewGroups}
|
isGroups={isOpenSideViewGroups}
|
||||||
isDirects={isOpenSideViewDirects}
|
isDirects={isOpenSideViewDirects}
|
||||||
hasUnreadGroups={groupChatHasUnread || groupsAnnHasUnread}
|
hasUnreadGroups={groupChatHasUnread || groupsAnnHasUnread}
|
||||||
|
@ -229,6 +229,7 @@ export const ListOfGroupPromotions = () => {
|
|||||||
data: data,
|
data: data,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
service: 'DOCUMENT',
|
service: 'DOCUMENT',
|
||||||
|
uploadType: 'base64',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
|
@ -104,6 +104,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
|
|||||||
data: avatarBase64,
|
data: avatarBase64,
|
||||||
identifier: 'qortal_avatar',
|
identifier: 'qortal_avatar',
|
||||||
service: 'THUMBNAIL',
|
service: 'THUMBNAIL',
|
||||||
|
uploadType: 'base64',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
|
@ -87,14 +87,14 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
|
|||||||
const getName = async (address) => {
|
const getName = async (address) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${getBaseApiReact()}/names/address/${address}`
|
`${getBaseApiReact()}/names/primary/${address}`
|
||||||
);
|
);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
setNames((prev) => {
|
setNames((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
[address]: nameData[0].name,
|
[address]: nameData?.name,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,6 +171,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
|||||||
data: encryptData,
|
data: encryptData,
|
||||||
identifier: 'ext_saved_settings',
|
identifier: 'ext_saved_settings',
|
||||||
service: 'DOCUMENT_PRIVATE',
|
service: 'DOCUMENT_PRIVATE',
|
||||||
|
uploadType: 'base64',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
|
@ -26,15 +26,24 @@ export async function getNameInfo() {
|
|||||||
const wallet = await getSaveWallet();
|
const wallet = await getSaveWallet();
|
||||||
const address = wallet.address0;
|
const address = wallet.address0;
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + '/names/address/' + address);
|
const response = await fetch(validApi + '/names/primary/' + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0].name;
|
return nameData?.name;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAllUserNames() {
|
||||||
|
const wallet = await getSaveWallet();
|
||||||
|
const address = wallet.address0;
|
||||||
|
const validApi = await getBaseApi();
|
||||||
|
const response = await fetch(validApi + '/names/address/' + address);
|
||||||
|
const nameData = await response.json();
|
||||||
|
return nameData.map((item) => item.name);
|
||||||
|
}
|
||||||
|
|
||||||
async function getKeyPair() {
|
async function getKeyPair() {
|
||||||
const res = await getData<any>('keyPair').catch(() => null);
|
const res = await getData<any>('keyPair').catch(() => null);
|
||||||
if (res) {
|
if (res) {
|
||||||
@ -134,12 +143,12 @@ export const encryptAndPublishSymmetricKeyGroupChat = async ({
|
|||||||
if (encryptedData) {
|
if (encryptedData) {
|
||||||
const registeredName = await getNameInfo();
|
const registeredName = await getNameInfo();
|
||||||
const data = await publishData({
|
const data = await publishData({
|
||||||
registeredName,
|
data: encryptedData,
|
||||||
file: encryptedData,
|
file: encryptedData,
|
||||||
service: 'DOCUMENT_PRIVATE',
|
|
||||||
identifier: `symmetric-qchat-group-${groupId}`,
|
identifier: `symmetric-qchat-group-${groupId}`,
|
||||||
uploadType: 'file',
|
registeredName,
|
||||||
isBase64: true,
|
service: 'DOCUMENT_PRIVATE',
|
||||||
|
uploadType: 'base64',
|
||||||
withFee: true,
|
withFee: true,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -202,12 +211,12 @@ export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({
|
|||||||
if (encryptedData) {
|
if (encryptedData) {
|
||||||
const registeredName = await getNameInfo();
|
const registeredName = await getNameInfo();
|
||||||
const data = await publishData({
|
const data = await publishData({
|
||||||
registeredName,
|
data: encryptedData,
|
||||||
file: encryptedData,
|
file: encryptedData,
|
||||||
service: 'DOCUMENT_PRIVATE',
|
|
||||||
identifier: `admins-symmetric-qchat-group-${groupId}`,
|
identifier: `admins-symmetric-qchat-group-${groupId}`,
|
||||||
uploadType: 'file',
|
registeredName,
|
||||||
isBase64: true,
|
service: 'DOCUMENT_PRIVATE',
|
||||||
|
uploadType: 'base64',
|
||||||
withFee: true,
|
withFee: true,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -240,12 +249,12 @@ export const publishGroupEncryptedResource = async ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
const data = await publishData({
|
const data = await publishData({
|
||||||
registeredName,
|
data: encryptedData,
|
||||||
file: encryptedData,
|
file: encryptedData,
|
||||||
service: 'DOCUMENT',
|
|
||||||
identifier,
|
identifier,
|
||||||
uploadType: 'file',
|
registeredName,
|
||||||
isBase64: true,
|
service: 'DOCUMENT',
|
||||||
|
uploadType: 'base64',
|
||||||
withFee: true,
|
withFee: true,
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@ -262,21 +271,22 @@ export const publishGroupEncryptedResource = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const publishOnQDN = async ({
|
export const publishOnQDN = async ({
|
||||||
data,
|
|
||||||
identifier,
|
|
||||||
service,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
category,
|
category,
|
||||||
|
data,
|
||||||
|
description,
|
||||||
|
identifier,
|
||||||
|
name,
|
||||||
|
service,
|
||||||
tag1,
|
tag1,
|
||||||
tag2,
|
tag2,
|
||||||
tag3,
|
tag3,
|
||||||
tag4,
|
tag4,
|
||||||
tag5,
|
tag5,
|
||||||
uploadType = 'file',
|
title,
|
||||||
|
uploadType = 'base64',
|
||||||
}) => {
|
}) => {
|
||||||
if (data && service) {
|
if (data && service) {
|
||||||
const registeredName = await getNameInfo();
|
const registeredName = name || (await getNameInfo());
|
||||||
if (!registeredName)
|
if (!registeredName)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
i18n.t('core:message.generic.name_publish', {
|
i18n.t('core:message.generic.name_publish', {
|
||||||
@ -286,11 +296,11 @@ export const publishOnQDN = async ({
|
|||||||
|
|
||||||
const res = await publishData({
|
const res = await publishData({
|
||||||
registeredName,
|
registeredName,
|
||||||
|
data,
|
||||||
file: data,
|
file: data,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
uploadType,
|
uploadType,
|
||||||
isBase64: true,
|
|
||||||
withFee: true,
|
withFee: true,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
@ -256,6 +256,7 @@ export const listOfAllQortalRequests = [
|
|||||||
'UPDATE_GROUP',
|
'UPDATE_GROUP',
|
||||||
'UPDATE_NAME',
|
'UPDATE_NAME',
|
||||||
'VOTE_ON_POLL',
|
'VOTE_ON_POLL',
|
||||||
|
'GET_PRIMARY_NAME',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const UIQortalRequests = [
|
export const UIQortalRequests = [
|
||||||
@ -319,6 +320,7 @@ export const UIQortalRequests = [
|
|||||||
'UPDATE_GROUP',
|
'UPDATE_GROUP',
|
||||||
'UPDATE_NAME',
|
'UPDATE_NAME',
|
||||||
'VOTE_ON_POLL',
|
'VOTE_ON_POLL',
|
||||||
|
'GET_PRIMARY_NAME',
|
||||||
];
|
];
|
||||||
|
|
||||||
async function retrieveFileFromIndexedDB(fileId) {
|
async function retrieveFileFromIndexedDB(fileId) {
|
||||||
@ -615,13 +617,22 @@ export const useQortalMessageListener = (
|
|||||||
);
|
);
|
||||||
} else if (event?.data?.action === 'SAVE_FILE') {
|
} else if (event?.data?.action === 'SAVE_FILE') {
|
||||||
try {
|
try {
|
||||||
const res = await saveFile(event.data, null, true, {
|
await saveFile(event.data, null, true, {
|
||||||
openSnackGlobal,
|
openSnackGlobal,
|
||||||
setOpenSnackGlobal,
|
setOpenSnackGlobal,
|
||||||
infoSnackCustom,
|
infoSnackCustom,
|
||||||
setInfoSnackCustom,
|
setInfoSnackCustom,
|
||||||
});
|
});
|
||||||
} catch (error) {}
|
event.ports[0].postMessage({
|
||||||
|
result: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
result: null,
|
||||||
|
error: error?.message || 'Failed to save file',
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
||||||
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
|
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
|
||||||
|
11
src/hooks/useSortedMyNames.tsx
Normal file
11
src/hooks/useSortedMyNames.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export function useSortedMyNames(names, myName) {
|
||||||
|
return useMemo(() => {
|
||||||
|
return [...names].sort((a, b) => {
|
||||||
|
if (a === myName) return -1;
|
||||||
|
if (b === myName) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}, [names, myName]);
|
||||||
|
}
|
@ -34,6 +34,44 @@ async function reusablePost(endpoint, _body) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function reusablePostStream(endpoint, _body) {
|
||||||
|
const url = await createEndpoint(endpoint);
|
||||||
|
|
||||||
|
const headers = {};
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: _body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response; // return the actual response so calling code can use response.ok
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadChunkWithRetry(endpoint, formData, index, maxRetries = 3) {
|
||||||
|
let attempt = 0;
|
||||||
|
while (attempt < maxRetries) {
|
||||||
|
try {
|
||||||
|
const response = await reusablePostStream(endpoint, formData);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(errorText);
|
||||||
|
}
|
||||||
|
return; // Success
|
||||||
|
} catch (err) {
|
||||||
|
attempt++;
|
||||||
|
console.warn(
|
||||||
|
`Chunk ${index} failed (attempt ${attempt}): ${err.message}`
|
||||||
|
);
|
||||||
|
if (attempt >= maxRetries) {
|
||||||
|
throw new Error(`Chunk ${index} failed after ${maxRetries} attempts`);
|
||||||
|
}
|
||||||
|
// Wait 10 seconds before next retry
|
||||||
|
await new Promise((res) => setTimeout(res, 10_000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getKeyPair() {
|
async function getKeyPair() {
|
||||||
const res = await getData<any>('keyPair').catch(() => null);
|
const res = await getData<any>('keyPair').catch(() => null);
|
||||||
if (res) {
|
if (res) {
|
||||||
@ -44,23 +82,22 @@ async function getKeyPair() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const publishData = async ({
|
export const publishData = async ({
|
||||||
registeredName,
|
|
||||||
file,
|
|
||||||
service,
|
|
||||||
identifier,
|
|
||||||
uploadType,
|
|
||||||
isBase64,
|
|
||||||
filename,
|
|
||||||
withFee,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
category,
|
category,
|
||||||
|
data,
|
||||||
|
description,
|
||||||
|
feeAmount,
|
||||||
|
filename,
|
||||||
|
identifier,
|
||||||
|
registeredName,
|
||||||
|
service,
|
||||||
tag1,
|
tag1,
|
||||||
tag2,
|
tag2,
|
||||||
tag3,
|
tag3,
|
||||||
tag4,
|
tag4,
|
||||||
tag5,
|
tag5,
|
||||||
feeAmount,
|
title,
|
||||||
|
uploadType,
|
||||||
|
withFee,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const validateName = async (receiverName: string) => {
|
const validateName = async (receiverName: string) => {
|
||||||
return await reusableGet(`/names/${receiverName}`);
|
return await reusableGet(`/names/${receiverName}`);
|
||||||
@ -186,7 +223,8 @@ export const publishData = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let transactionBytes = await uploadData(registeredName, file, fee);
|
let transactionBytes = await uploadData(registeredName, data, fee);
|
||||||
|
|
||||||
if (!transactionBytes || transactionBytes.error) {
|
if (!transactionBytes || transactionBytes.error) {
|
||||||
throw new Error(transactionBytes?.message || 'Error when uploading');
|
throw new Error(transactionBytes?.message || 'Error when uploading');
|
||||||
} else if (transactionBytes.includes('Error 500 Internal Server Error')) {
|
} else if (transactionBytes.includes('Error 500 Internal Server Error')) {
|
||||||
@ -206,79 +244,119 @@ export const publishData = async ({
|
|||||||
return signAndProcessRes;
|
return signAndProcessRes;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadData = async (registeredName: string, file: any, fee: number) => {
|
const uploadData = async (registeredName: string, data: any, fee: number) => {
|
||||||
let postBody = '';
|
let postBody = '';
|
||||||
let urlSuffix = '';
|
let urlSuffix = '';
|
||||||
|
|
||||||
if (file != null) {
|
if (data != null) {
|
||||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
if (uploadType === 'base64') {
|
||||||
if (uploadType === 'zip') {
|
|
||||||
urlSuffix = '/zip';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
|
||||||
else if (uploadType === 'file') {
|
|
||||||
urlSuffix = '/base64';
|
urlSuffix = '/base64';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
if (uploadType === 'base64') {
|
||||||
if (isBase64) {
|
postBody = data;
|
||||||
postBody = file;
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('No data provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isBase64) {
|
let uploadDataUrl = `/arbitrary/${service}/${registeredName}`;
|
||||||
let fileBuffer = new Uint8Array(await file.arrayBuffer());
|
let paramQueries = '';
|
||||||
postBody = Buffer.from(fileBuffer).toString('base64');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`;
|
|
||||||
if (identifier?.trim().length > 0) {
|
if (identifier?.trim().length > 0) {
|
||||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`;
|
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadDataUrl = uploadDataUrl + `?fee=${fee}`;
|
paramQueries = paramQueries + `?fee=${fee}`;
|
||||||
|
|
||||||
if (filename != null && filename != 'undefined') {
|
if (filename != null && filename != 'undefined') {
|
||||||
uploadDataUrl =
|
paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename);
|
||||||
uploadDataUrl + '&filename=' + encodeURIComponent(filename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title != null && title != 'undefined') {
|
if (title != null && title != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title);
|
paramQueries = paramQueries + '&title=' + encodeURIComponent(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (description != null && description != 'undefined') {
|
if (description != null && description != 'undefined') {
|
||||||
uploadDataUrl =
|
paramQueries =
|
||||||
uploadDataUrl + '&description=' + encodeURIComponent(description);
|
paramQueries + '&description=' + encodeURIComponent(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (category != null && category != 'undefined') {
|
if (category != null && category != 'undefined') {
|
||||||
uploadDataUrl =
|
paramQueries = paramQueries + '&category=' + encodeURIComponent(category);
|
||||||
uploadDataUrl + '&category=' + encodeURIComponent(category);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag1 != null && tag1 != 'undefined') {
|
if (tag1 != null && tag1 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1);
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag2 != null && tag2 != 'undefined') {
|
if (tag2 != null && tag2 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2);
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag3 != null && tag3 != 'undefined') {
|
if (tag3 != null && tag3 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3);
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag4 != null && tag4 != 'undefined') {
|
if (tag4 != null && tag4 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4);
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag5 != null && tag5 != 'undefined') {
|
if (tag5 != null && tag5 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5);
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5);
|
||||||
|
}
|
||||||
|
if (uploadType === 'zip') {
|
||||||
|
paramQueries = paramQueries + '&isZip=' + true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uploadType === 'base64') {
|
||||||
|
if (urlSuffix) {
|
||||||
|
uploadDataUrl = uploadDataUrl + urlSuffix;
|
||||||
|
}
|
||||||
|
uploadDataUrl = uploadDataUrl + paramQueries;
|
||||||
return await reusablePost(uploadDataUrl, postBody);
|
return await reusablePost(uploadDataUrl, postBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = data;
|
||||||
|
const urlCheck = `/arbitrary/check/tmp?totalSize=${file.size}`;
|
||||||
|
|
||||||
|
const checkEndpoint = await createEndpoint(urlCheck);
|
||||||
|
const checkRes = await fetch(checkEndpoint);
|
||||||
|
if (!checkRes.ok) {
|
||||||
|
throw new Error('Not enough space on your hard drive');
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkUrl = uploadDataUrl + `/chunk`;
|
||||||
|
const chunkSize = 5 * 1024 * 1024; // 5MB
|
||||||
|
|
||||||
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
|
for (let index = 0; index < totalChunks; index++) {
|
||||||
|
const start = index * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('chunk', chunk, file.name); // Optional: include filename
|
||||||
|
formData.append('index', index);
|
||||||
|
|
||||||
|
await uploadChunkWithRetry(chunkUrl, formData, index);
|
||||||
|
}
|
||||||
|
const finalizeUrl = uploadDataUrl + `/finalize` + paramQueries;
|
||||||
|
|
||||||
|
const finalizeEndpoint = await createEndpoint(finalizeUrl);
|
||||||
|
|
||||||
|
const response = await fetch(finalizeEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Finalize failed: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.text(); // Base58-encoded unsigned transaction
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -38,7 +38,11 @@ import {
|
|||||||
getPublicKey,
|
getPublicKey,
|
||||||
transferAsset,
|
transferAsset,
|
||||||
} from '../background/background.ts';
|
} from '../background/background.ts';
|
||||||
import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts';
|
import {
|
||||||
|
getAllUserNames,
|
||||||
|
getNameInfo,
|
||||||
|
uint8ArrayToObject,
|
||||||
|
} from '../encryption/encryption.ts';
|
||||||
import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx';
|
import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx';
|
||||||
import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx';
|
import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx';
|
||||||
import { extractComponents } from '../components/Chat/MessageDisplay.tsx';
|
import { extractComponents } from '../components/Chat/MessageDisplay.tsx';
|
||||||
@ -1315,7 +1319,7 @@ export const publishQDNResource = async (
|
|||||||
if (appFee && appFee > 0 && appFeeRecipient) {
|
if (appFee && appFee > 0 && appFeeRecipient) {
|
||||||
hasAppFee = true;
|
hasAppFee = true;
|
||||||
}
|
}
|
||||||
const registeredName = await getNameInfo();
|
const registeredName = data?.name || (await getNameInfo());
|
||||||
const name = registeredName;
|
const name = registeredName;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -1331,7 +1335,7 @@ export const publishQDNResource = async (
|
|||||||
const title = data.title;
|
const title = data.title;
|
||||||
const description = data.description;
|
const description = data.description;
|
||||||
const category = data.category;
|
const category = data.category;
|
||||||
|
const file = data?.file || data?.blob;
|
||||||
const tags = data?.tags || [];
|
const tags = data?.tags || [];
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
@ -1346,9 +1350,7 @@ export const publishQDNResource = async (
|
|||||||
if (data.identifier == null) {
|
if (data.identifier == null) {
|
||||||
identifier = 'default';
|
identifier = 'default';
|
||||||
}
|
}
|
||||||
if (data?.file || data?.blob) {
|
|
||||||
data64 = await fileToBase64(data?.file || data?.blob);
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
data.encrypt &&
|
data.encrypt &&
|
||||||
(!data.publicKeys ||
|
(!data.publicKeys ||
|
||||||
@ -1367,6 +1369,9 @@ export const publishQDNResource = async (
|
|||||||
const parsedData = resKeyPair;
|
const parsedData = resKeyPair;
|
||||||
const privateKey = parsedData.privateKey;
|
const privateKey = parsedData.privateKey;
|
||||||
const userPublicKey = parsedData.publicKey;
|
const userPublicKey = parsedData.publicKey;
|
||||||
|
if (data?.file || data?.blob) {
|
||||||
|
data64 = await fileToBase64(data?.file || data?.blob);
|
||||||
|
}
|
||||||
const encryptDataResponse = encryptDataGroup({
|
const encryptDataResponse = encryptDataGroup({
|
||||||
data64,
|
data64,
|
||||||
publicKeys: data.publicKeys,
|
publicKeys: data.publicKeys,
|
||||||
@ -1410,6 +1415,7 @@ export const publishQDNResource = async (
|
|||||||
}),
|
}),
|
||||||
text2: `service: ${service}`,
|
text2: `service: ${service}`,
|
||||||
text3: `identifier: ${identifier || null}`,
|
text3: `identifier: ${identifier || null}`,
|
||||||
|
text4: `name: ${registeredName}`,
|
||||||
fee: fee.fee,
|
fee: fee.fee,
|
||||||
...handleDynamicValues,
|
...handleDynamicValues,
|
||||||
},
|
},
|
||||||
@ -1420,11 +1426,10 @@ export const publishQDNResource = async (
|
|||||||
try {
|
try {
|
||||||
const resPublish = await publishData({
|
const resPublish = await publishData({
|
||||||
registeredName: encodeURIComponent(name),
|
registeredName: encodeURIComponent(name),
|
||||||
file: data64,
|
data: data64 ? data64 : file,
|
||||||
service: service,
|
service: service,
|
||||||
identifier: encodeURIComponent(identifier),
|
identifier: encodeURIComponent(identifier),
|
||||||
uploadType: 'file',
|
uploadType: data64 ? 'base64' : 'file',
|
||||||
isBase64: true,
|
|
||||||
filename: filename,
|
filename: filename,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -1558,6 +1563,7 @@ export const publishMultipleQDNResources = async (
|
|||||||
|
|
||||||
const fee = await getFee('ARBITRARY');
|
const fee = await getFee('ARBITRARY');
|
||||||
const registeredName = await getNameInfo();
|
const registeredName = await getNameInfo();
|
||||||
|
|
||||||
const name = registeredName;
|
const name = registeredName;
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@ -1568,6 +1574,14 @@ export const publishMultipleQDNResources = async (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userNames = await getAllUserNames();
|
||||||
|
data.resources?.forEach((item) => {
|
||||||
|
if (item?.name && !userNames?.includes(item.name))
|
||||||
|
throw new Error(
|
||||||
|
`The name ${item.name}, does not belong to the publisher.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const appFee = data?.appFee ? +data.appFee : undefined;
|
const appFee = data?.appFee ? +data.appFee : undefined;
|
||||||
const appFeeRecipient = data?.appFeeRecipient;
|
const appFeeRecipient = data?.appFeeRecipient;
|
||||||
let hasAppFee = false;
|
let hasAppFee = false;
|
||||||
@ -1638,7 +1652,7 @@ export const publishMultipleQDNResources = async (
|
|||||||
<div class="resource-detail"><span>Service:</span> ${
|
<div class="resource-detail"><span>Service:</span> ${
|
||||||
resource.service
|
resource.service
|
||||||
}</div>
|
}</div>
|
||||||
<div class="resource-detail"><span>Name:</span> ${name}</div>
|
<div class="resource-detail"><span>Name:</span> ${resource?.name || name}</div>
|
||||||
<div class="resource-detail"><span>Identifier:</span> ${
|
<div class="resource-detail"><span>Identifier:</span> ${
|
||||||
resource.identifier
|
resource.identifier
|
||||||
}</div>
|
}</div>
|
||||||
@ -1695,6 +1709,7 @@ export const publishMultipleQDNResources = async (
|
|||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -1709,12 +1724,13 @@ export const publishMultipleQDNResources = async (
|
|||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const service = resource.service;
|
const service = resource.service;
|
||||||
let identifier = resource.identifier;
|
let identifier = resource.identifier;
|
||||||
let data64 = resource?.data64 || resource?.base64;
|
let rawData = resource?.data64 || resource?.base64;
|
||||||
const filename = resource.filename;
|
const filename = resource.filename;
|
||||||
const title = resource.title;
|
const title = resource.title;
|
||||||
const description = resource.description;
|
const description = resource.description;
|
||||||
@ -1741,26 +1757,31 @@ export const publishMultipleQDNResources = async (
|
|||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (resource.file) {
|
if (resource.file) {
|
||||||
data64 = await fileToBase64(resource.file);
|
rawData = resource.file;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceEncrypt) {
|
if (resourceEncrypt) {
|
||||||
try {
|
try {
|
||||||
|
if (resource?.file) {
|
||||||
|
rawData = await fileToBase64(resource.file);
|
||||||
|
}
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
const parsedData = resKeyPair;
|
const parsedData = resKeyPair;
|
||||||
const privateKey = parsedData.privateKey;
|
const privateKey = parsedData.privateKey;
|
||||||
const userPublicKey = parsedData.publicKey;
|
const userPublicKey = parsedData.publicKey;
|
||||||
const encryptDataResponse = encryptDataGroup({
|
const encryptDataResponse = encryptDataGroup({
|
||||||
data64,
|
data64: rawData,
|
||||||
publicKeys: data.publicKeys,
|
publicKeys: data.publicKeys,
|
||||||
privateKey,
|
privateKey,
|
||||||
userPublicKey,
|
userPublicKey,
|
||||||
});
|
});
|
||||||
if (encryptDataResponse) {
|
if (encryptDataResponse) {
|
||||||
data64 = encryptDataResponse;
|
rawData = encryptDataResponse;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg =
|
const errorMsg =
|
||||||
@ -1772,32 +1793,36 @@ export const publishMultipleQDNResources = async (
|
|||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const dataType =
|
||||||
|
resource?.base64 || resource?.data64 || resourceEncrypt
|
||||||
|
? 'base64'
|
||||||
|
: 'file';
|
||||||
await retryTransaction(
|
await retryTransaction(
|
||||||
publishData,
|
publishData,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
registeredName: encodeURIComponent(name),
|
apiVersion: 2,
|
||||||
file: data64,
|
|
||||||
service: service,
|
|
||||||
identifier: encodeURIComponent(identifier),
|
|
||||||
uploadType: 'file',
|
|
||||||
isBase64: true,
|
|
||||||
filename: filename,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
category,
|
category,
|
||||||
|
data: rawData,
|
||||||
|
description,
|
||||||
|
filename: filename,
|
||||||
|
identifier: encodeURIComponent(identifier),
|
||||||
|
registeredName: encodeURIComponent(resource?.name || name),
|
||||||
|
service: service,
|
||||||
tag1,
|
tag1,
|
||||||
tag2,
|
tag2,
|
||||||
tag3,
|
tag3,
|
||||||
tag4,
|
tag4,
|
||||||
tag5,
|
tag5,
|
||||||
apiVersion: 2,
|
title,
|
||||||
|
uploadType: dataType,
|
||||||
withFee: true,
|
withFee: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -1818,6 +1843,7 @@ export const publishMultipleQDNResources = async (
|
|||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1829,6 +1855,7 @@ export const publishMultipleQDNResources = async (
|
|||||||
}),
|
}),
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2324,6 +2351,44 @@ export const joinGroup = async (data, isFromExtension) => {
|
|||||||
|
|
||||||
export const saveFile = async (data, sender, isFromExtension, snackMethods) => {
|
export const saveFile = async (data, sender, isFromExtension, snackMethods) => {
|
||||||
try {
|
try {
|
||||||
|
if (!data?.filename) throw new Error('Missing filename');
|
||||||
|
if (data?.location) {
|
||||||
|
const requiredFieldsLocation = ['service', 'name'];
|
||||||
|
const missingFieldsLocation: string[] = [];
|
||||||
|
requiredFieldsLocation.forEach((field) => {
|
||||||
|
if (!data?.location[field]) {
|
||||||
|
missingFieldsLocation.push(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (missingFieldsLocation.length > 0) {
|
||||||
|
const missingFieldsString = missingFieldsLocation.join(', ');
|
||||||
|
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
const resPermission = await getUserPermission(
|
||||||
|
{
|
||||||
|
text1: 'Would you like to download:',
|
||||||
|
highlightedText: `${data?.filename}`,
|
||||||
|
},
|
||||||
|
isFromExtension
|
||||||
|
);
|
||||||
|
const { accepted } = resPermission;
|
||||||
|
if (!accepted) throw new Error('User declined to save file');
|
||||||
|
const a = document.createElement('a');
|
||||||
|
let locationUrl = `/arbitrary/${data.location.service}/${data.location.name}`;
|
||||||
|
if (data.location.identifier) {
|
||||||
|
locationUrl = locationUrl + `/${data.location.identifier}`;
|
||||||
|
}
|
||||||
|
const endpoint = await createEndpoint(
|
||||||
|
locationUrl + `?attachment=true&attachmentFilename=${data?.filename}`
|
||||||
|
);
|
||||||
|
a.href = endpoint;
|
||||||
|
a.download = data.filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const requiredFields = ['filename', 'blob'];
|
const requiredFields = ['filename', 'blob'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
@ -2341,6 +2406,8 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => {
|
|||||||
}
|
}
|
||||||
const filename = data.filename;
|
const filename = data.filename;
|
||||||
const blob = data.blob;
|
const blob = data.blob;
|
||||||
|
|
||||||
|
const mimeType = blob.type || data.mimeType;
|
||||||
const resPermission = await getUserPermission(
|
const resPermission = await getUserPermission(
|
||||||
{
|
{
|
||||||
text1: i18n.t('question:download_file', {
|
text1: i18n.t('question:download_file', {
|
||||||
@ -2351,6 +2418,17 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => {
|
|||||||
isFromExtension
|
isFromExtension
|
||||||
);
|
);
|
||||||
const { accepted } = resPermission;
|
const { accepted } = resPermission;
|
||||||
|
if (!accepted) throw new Error('User declined to save file'); // TODO translate
|
||||||
|
showSaveFilePicker(
|
||||||
|
{
|
||||||
|
filename,
|
||||||
|
mimeType,
|
||||||
|
blob,
|
||||||
|
},
|
||||||
|
snackMethods
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
const mimeType = blob.type || data.mimeType;
|
const mimeType = blob.type || data.mimeType;
|
||||||
@ -5396,11 +5474,10 @@ export const updateNameRequest = async (data, isFromExtension) => {
|
|||||||
const fee = await getFee('UPDATE_NAME');
|
const fee = await getFee('UPDATE_NAME');
|
||||||
const resPermission = await getUserPermission(
|
const resPermission = await getUserPermission(
|
||||||
{
|
{
|
||||||
text1: i18n.t('question:permission.register_name', {
|
text1: `Do you give this application permission to update this name?`, // TODO translate
|
||||||
postProcess: 'capitalizeFirstChar',
|
text2: `previous name: ${oldName}`,
|
||||||
}),
|
text3: `new name: ${newName}`,
|
||||||
highlightedText: data.newName,
|
text4: data?.description,
|
||||||
text2: data?.description,
|
|
||||||
fee: fee.fee,
|
fee: fee.fee,
|
||||||
},
|
},
|
||||||
isFromExtension
|
isFromExtension
|
||||||
@ -6012,7 +6089,7 @@ export const createGroupRequest = async (data, isFromExtension) => {
|
|||||||
];
|
];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -6076,7 +6153,7 @@ export const updateGroupRequest = async (data, isFromExtension) => {
|
|||||||
];
|
];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -6250,7 +6327,7 @@ export const sellNameRequest = async (data, isFromExtension) => {
|
|||||||
const requiredFields = ['salePrice', 'nameForSale'];
|
const requiredFields = ['salePrice', 'nameForSale'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -6320,7 +6397,7 @@ export const cancelSellNameRequest = async (data, isFromExtension) => {
|
|||||||
const requiredFields = ['nameForSale'];
|
const requiredFields = ['nameForSale'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -6377,7 +6454,7 @@ export const buyNameRequest = async (data, isFromExtension) => {
|
|||||||
const requiredFields = ['nameForSale'];
|
const requiredFields = ['nameForSale'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -6908,12 +6985,11 @@ export const multiPaymentWithPrivateData = async (data, isFromExtension) => {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
registeredName: encodeURIComponent(name),
|
registeredName: encodeURIComponent(name),
|
||||||
file: encryptDataResponse,
|
data: encryptDataResponse,
|
||||||
service: transaction.service,
|
service: transaction.service,
|
||||||
identifier: encodeURIComponent(transaction.identifier),
|
identifier: encodeURIComponent(transaction.identifier),
|
||||||
uploadType: 'file',
|
uploadType: 'base64',
|
||||||
description: transaction?.description,
|
description: transaction?.description,
|
||||||
isBase64: true,
|
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
withFee: true,
|
withFee: true,
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { gateways, getApiKeyFromStorage } from '../background/background.ts';
|
import {
|
||||||
|
gateways,
|
||||||
|
getApiKeyFromStorage,
|
||||||
|
getNameInfoForOthers,
|
||||||
|
} from '../background/background.ts';
|
||||||
import { listOfAllQortalRequests } from '../hooks/useQortalMessageListener.tsx';
|
import { listOfAllQortalRequests } from '../hooks/useQortalMessageListener.tsx';
|
||||||
import {
|
import {
|
||||||
addForeignServer,
|
addForeignServer,
|
||||||
@ -1932,6 +1936,33 @@ function setupMessageListenerQortalRequest() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'GET_PRIMARY_NAME': {
|
||||||
|
try {
|
||||||
|
const res = await getNameInfoForOthers(request.payload?.address);
|
||||||
|
const resData = res ? res : null;
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: resData,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -20,3 +20,7 @@ export const messageHasImage = (message) => {
|
|||||||
message.images[0]?.service
|
message.images[0]?.service
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isHtmlString(value) {
|
||||||
|
return typeof value === 'string' && /<[^>]+>/.test(value.trim());
|
||||||
|
}
|
||||||
|
@ -44,14 +44,14 @@ export function sortArrayByTimestampAndGroupName(array) {
|
|||||||
// Both have timestamp, sort by timestamp descending
|
// Both have timestamp, sort by timestamp descending
|
||||||
return b.timestamp - a.timestamp;
|
return b.timestamp - a.timestamp;
|
||||||
} else if (a.timestamp) {
|
} else if (a.timestamp) {
|
||||||
// Only `a` has timestamp, it comes first
|
|
||||||
return -1;
|
return -1;
|
||||||
} else if (b.timestamp) {
|
} else if (b.timestamp) {
|
||||||
// Only `b` has timestamp, it comes first
|
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
// Neither has timestamp, sort alphabetically by groupName
|
// Neither has timestamp, sort alphabetically by groupName (with fallback)
|
||||||
return a.groupName.localeCompare(b.groupName);
|
const nameA = a.groupName || '';
|
||||||
|
const nameB = b.groupName || '';
|
||||||
|
return nameA.localeCompare(nameB);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user