Integrate extension api v1.6 changes
This commit is contained in:
@@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- (**Migration**) Show dialog on manual search selection to optionally open the search result entry instead of selecting it
|
||||
- (**Migration**) Show "manual search matches" under "other matches"
|
||||
- (**Source/Extension**) Rename language "All" to "Multi"
|
||||
- (**Extension/Settings**) Renamed "extension repo" to "extension stores"
|
||||
- (**Reader**) Simplify changing settings in desktop sidebar
|
||||
- (**Reader**) Ignore tap zone clicks while window does not have focus
|
||||
- (**Reader**) Improve preloading pages from the previous/next chapter
|
||||
|
||||
12
src/App.tsx
12
src/App.tsx
@@ -100,6 +100,10 @@ const { GlobalReaderSettings } = loadable(
|
||||
const { More } = loadable(() => import('@/features/settings/screens/More.tsx'), lazyLoadFallback);
|
||||
const { Reader } = loadable(() => import('@/features/reader/screens/Reader.tsx'), lazyLoadFallback);
|
||||
const { HistorySettings } = loadable(() => import('@/features/history/screens/HistorySettings.tsx'), lazyLoadFallback);
|
||||
const { ExtensionStores } = loadable(
|
||||
() => import('@/features/extension/store/screens/ExtensionStores.tsx'),
|
||||
lazyLoadFallback,
|
||||
);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// Adds messages only in a dev environment
|
||||
@@ -323,7 +327,13 @@ const MainApp = () => {
|
||||
<Route path={AppRoutes.settings.children.backup.match} element={<Backup />} />
|
||||
<Route path={AppRoutes.settings.children.server.match} element={<ServerSettings />} />
|
||||
<Route path={AppRoutes.settings.children.webui.match} element={<WebUISettings />} />
|
||||
<Route path={AppRoutes.settings.children.browse.match} element={<BrowseSettings />} />
|
||||
<Route path={AppRoutes.settings.children.browse.match}>
|
||||
<Route index element={<BrowseSettings />} />
|
||||
<Route
|
||||
path={AppRoutes.settings.children.browse.children.extensionStores.match}
|
||||
element={<ExtensionStores />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={AppRoutes.settings.children.history.match} element={<HistorySettings />} />
|
||||
<Route path={AppRoutes.settings.children.device.match} element={<DeviceSetting />} />
|
||||
<Route path={AppRoutes.settings.children.tracking.match} element={<TrackingSettings />} />
|
||||
|
||||
22
src/assets/icons/svg/DiscordIcon.tsx
Normal file
22
src/assets/icons/svg/DiscordIcon.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import type { SvgIconProps } from '@mui/material/SvgIcon';
|
||||
import SvgIcon from '@mui/material/SvgIcon';
|
||||
|
||||
export const DiscordIcon = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props} viewBox="0 0 24 24">
|
||||
<svg width="800px" height="800px" viewBox="0 -28.5 256 256" preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
d="M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
@@ -109,6 +109,12 @@ export const AppRoutes = {
|
||||
browse: {
|
||||
match: 'browse',
|
||||
path: '/settings/browse',
|
||||
children: {
|
||||
extensionStores: {
|
||||
match: 'extension-stores',
|
||||
path: '/settings/browse/extension-stores',
|
||||
},
|
||||
},
|
||||
},
|
||||
device: {
|
||||
match: 'device',
|
||||
|
||||
@@ -28,6 +28,9 @@ export type TextSettingDialogProps = {
|
||||
isDialogOpen: boolean;
|
||||
setIsDialogOpen: (open: boolean) => void;
|
||||
validate?: (value: string) => boolean;
|
||||
onExitComplete?: () => void;
|
||||
onDismiss?: () => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const TextSettingDialog = ({
|
||||
@@ -41,6 +44,9 @@ export const TextSettingDialog = ({
|
||||
isDialogOpen,
|
||||
setIsDialogOpen,
|
||||
validate = () => true,
|
||||
onExitComplete,
|
||||
onDismiss,
|
||||
disabled,
|
||||
}: TextSettingDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
@@ -60,12 +66,21 @@ export const TextSettingDialog = ({
|
||||
}, [value]);
|
||||
|
||||
const closeDialog = (resetValue: boolean = true) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resetValue) {
|
||||
setDialogValue(value ?? '');
|
||||
setIsValidValue(true);
|
||||
}
|
||||
|
||||
setIsDialogOpen(false);
|
||||
|
||||
const rejected = resetValue;
|
||||
if (rejected) {
|
||||
onDismiss?.();
|
||||
}
|
||||
};
|
||||
|
||||
const updateSetting = () => {
|
||||
@@ -74,7 +89,7 @@ export const TextSettingDialog = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isDialogOpen} onClose={() => closeDialog()} fullWidth>
|
||||
<Dialog open={isDialogOpen} onClose={() => closeDialog()} fullWidth onTransitionExited={onExitComplete}>
|
||||
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||
<DialogContent>
|
||||
{!!dialogDescription && (
|
||||
@@ -99,10 +114,10 @@ export const TextSettingDialog = ({
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => closeDialog()} color="primary">
|
||||
<Button disabled={disabled} onClick={() => closeDialog()} color="primary">
|
||||
{t`Cancel`}
|
||||
</Button>
|
||||
<Button onClick={() => updateSetting()} disabled={!isValidValue} color="primary">
|
||||
<Button onClick={() => updateSetting()} disabled={disabled || !isValidValue} color="primary">
|
||||
{t`Ok`}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -116,14 +116,9 @@ const GroupHeader = ({
|
||||
export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
const { t } = useLingui();
|
||||
|
||||
const {
|
||||
data: serverSettingsData,
|
||||
loading: areServerSettingsLoading,
|
||||
error: serverSettingsError,
|
||||
refetch: refetchServerSettings,
|
||||
} = requestManager.useGetServerSettings();
|
||||
const [fetchExtensions, { data, loading: areExtensionsLoading, error: extensionsError }] =
|
||||
requestManager.useExtensionListFetch();
|
||||
const extensionStoresRequest = requestManager.useGetExtensionStores();
|
||||
|
||||
const {
|
||||
settings: { browseLanguages: shownLangs, showNsfw },
|
||||
@@ -137,11 +132,8 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
const [updatingExtensionIds, setUpdatingExtensionIds] = useState<string[]>([]);
|
||||
const [refetchExtensions, setRefetchExtensions] = useState({});
|
||||
|
||||
const isLoading = areServerSettingsLoading || areExtensionsLoading;
|
||||
const error = serverSettingsError ?? extensionsError;
|
||||
|
||||
const areReposDefined = !!serverSettingsData?.settings.extensionRepos.length;
|
||||
const areMultipleReposInUse = (serverSettingsData?.settings.extensionRepos.length ?? 0) > 1;
|
||||
const isLoading = extensionStoresRequest.loading || areExtensionsLoading;
|
||||
const error = extensionStoresRequest.error ?? extensionsError;
|
||||
|
||||
const allExtensions = data?.fetchExtensions?.extensions ?? STABLE_EMPTY_ARRAY;
|
||||
const allLangs = useMemo(() => getLanguagesFromExtensions(allExtensions), [allExtensions]);
|
||||
@@ -160,6 +152,17 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
[groupedExtensions],
|
||||
);
|
||||
|
||||
const areReposDefined = !!extensionStoresRequest.data?.extensionStores.totalCount;
|
||||
const areMultipleReposInUse = useMemo(() => {
|
||||
if (!allExtensions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const store = allExtensions[0].extensionStore?.indexUrl;
|
||||
|
||||
return allExtensions.slice(1).some((extension) => extension.extensionStore?.indexUrl !== store);
|
||||
}, [allExtensions]);
|
||||
|
||||
const computeItemKey = VirtuosoUtil.useCreateGroupedComputeItemKey(
|
||||
groupCounts,
|
||||
useCallback((index) => groupedExtensions[index][LANGUAGE], [groupedExtensions]),
|
||||
@@ -244,8 +247,10 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
message={t`Unable to load data`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => {
|
||||
if (serverSettingsError) {
|
||||
refetchServerSettings().catch(defaultPromiseErrorHandler('Extensions::refetchServerSettings'));
|
||||
if (extensionStoresRequest.error) {
|
||||
extensionStoresRequest
|
||||
.refetch()
|
||||
.catch(defaultPromiseErrorHandler('Extensions::refetchExtensionsStores'));
|
||||
}
|
||||
|
||||
if (extensionsError) {
|
||||
@@ -267,9 +272,13 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
paddingTop: '20px',
|
||||
}}
|
||||
>
|
||||
<Typography>{t`You have to add a extension repository to be able to install extensions`}</Typography>
|
||||
<Button component={Link} variant="contained" to={AppRoutes.settings.children.browse.path}>
|
||||
{t`Settings`}
|
||||
<Typography>{t`You have to add a extension store to be able to install extensions`}</Typography>
|
||||
<Button
|
||||
component={Link}
|
||||
variant="contained"
|
||||
to={AppRoutes.settings.children.browse.children.extensionStores.path}
|
||||
>
|
||||
{t`Add extension store`}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
@@ -306,7 +315,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
<ExtensionCard
|
||||
extension={item}
|
||||
handleUpdate={handleExtensionUpdate}
|
||||
showSourceRepo={areMultipleReposInUse}
|
||||
showSourceStore={areMultipleReposInUse}
|
||||
forcedState={
|
||||
updatingExtensionIds.includes(item.pkgName) ? ExtensionState.UPDATING : undefined
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
EXTENSION_ACTION_TO_STATE_MAP,
|
||||
INSTALLED_STATE_TO_TRANSLATION_MAP,
|
||||
} from '@/features/extension/Extensions.constants.ts';
|
||||
import { getInstalledState, updateExtension } from '@/features/extension/Extensions.utils.ts';
|
||||
import { getInstalledState, isNsfw, updateExtension } from '@/features/extension/Extensions.utils.ts';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||
import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx';
|
||||
@@ -35,7 +35,7 @@ import { languageCodeToName } from '@/base/utils/Languages.ts';
|
||||
interface IProps {
|
||||
extension: TExtension;
|
||||
handleUpdate: () => void;
|
||||
showSourceRepo: boolean;
|
||||
showSourceStore: boolean;
|
||||
forcedState?: ExtensionState;
|
||||
}
|
||||
|
||||
@@ -43,9 +43,20 @@ export function ExtensionCard(props: IProps) {
|
||||
const { t } = useLingui();
|
||||
|
||||
const {
|
||||
extension: { name, lang, versionName, isInstalled, hasUpdate, isObsolete, pkgName, iconUrl, isNsfw, repo },
|
||||
extension: {
|
||||
name,
|
||||
lang,
|
||||
versionName,
|
||||
isInstalled,
|
||||
hasUpdate,
|
||||
isObsolete,
|
||||
pkgName,
|
||||
iconUrl,
|
||||
contentWarning,
|
||||
extensionStore,
|
||||
},
|
||||
handleUpdate,
|
||||
showSourceRepo,
|
||||
showSourceStore,
|
||||
forcedState,
|
||||
} = props;
|
||||
const [localInstalledState, setInstalledState] = useState<InstalledStates>(
|
||||
@@ -117,11 +128,14 @@ export function ExtensionCard(props: IProps) {
|
||||
<Typography variant="h6" component="h3">
|
||||
{name}
|
||||
</Typography>
|
||||
{showSourceStore && !!extensionStore && (
|
||||
<Typography variant="caption">{extensionStore.name}</Typography>
|
||||
)}
|
||||
<Typography variant="caption">
|
||||
{isInstalled ? `${languageCodeToName(lang)}` : ''}
|
||||
{isInstalled ? (
|
||||
<Typography variant="caption" sx={{ px: 1 }}>
|
||||
•
|
||||
-
|
||||
</Typography>
|
||||
) : (
|
||||
''
|
||||
@@ -130,17 +144,17 @@ export function ExtensionCard(props: IProps) {
|
||||
{isObsolete && (
|
||||
<>
|
||||
<Typography variant="caption" sx={{ px: 1 }}>
|
||||
•
|
||||
-
|
||||
</Typography>
|
||||
<Typography variant="caption" color="warning" sx={{ textTransform: 'uppercase' }}>
|
||||
{t`Obsolete`}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
{isNsfw && (
|
||||
{isNsfw(contentWarning) && (
|
||||
<>
|
||||
<Typography variant="caption" sx={{ px: 1 }}>
|
||||
•
|
||||
-
|
||||
</Typography>
|
||||
<Typography variant="caption" color="error">
|
||||
18+
|
||||
@@ -148,7 +162,6 @@ export function ExtensionCard(props: IProps) {
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
{showSourceRepo && <Typography variant="caption">{repo}</Typography>}
|
||||
</Stack>
|
||||
{isInstalled && (
|
||||
<CustomTooltip title={t`Settings`}>
|
||||
|
||||
@@ -10,11 +10,10 @@ import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { plural } from '@lingui/core/macro';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
||||
import { MutableListSetting } from '@/base/components/settings/MutableListSetting.tsx';
|
||||
import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx';
|
||||
import {
|
||||
createUpdateMetadataServerSettings,
|
||||
@@ -32,8 +31,10 @@ import Typography from '@mui/material/Typography';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
|
||||
type ExtensionsSettings = Pick<GqlServerSettings, 'maxSourcesInParallel' | 'localSourcePath' | 'extensionRepos'>;
|
||||
type ExtensionsSettings = Pick<GqlServerSettings, 'maxSourcesInParallel' | 'localSourcePath'>;
|
||||
|
||||
export const BrowseSettings = () => {
|
||||
const { t } = useLingui();
|
||||
@@ -42,6 +43,9 @@ export const BrowseSettings = () => {
|
||||
|
||||
const { data, loading, error, refetch } = requestManager.useGetServerSettings();
|
||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||
const extensionStoresRequest = requestManager.useGetExtensionStores();
|
||||
|
||||
const extensionStoreCount = extensionStoresRequest.data?.extensionStores.totalCount;
|
||||
|
||||
const updateSetting = <Setting extends keyof ExtensionsSettings>(
|
||||
setting: Setting,
|
||||
@@ -108,34 +112,18 @@ export const BrowseSettings = () => {
|
||||
stepSize={1}
|
||||
handleUpdate={(parallelSources) => updateSetting('maxSourcesInParallel', parallelSources)}
|
||||
/>
|
||||
<MutableListSetting
|
||||
settingName={t`Extension repositories`}
|
||||
description={t`Add repositories from which extensions can be installed`}
|
||||
dialogDisclaimer={
|
||||
<Trans>
|
||||
<strong>
|
||||
Suwayomi does not provide any support for 3rd party repositories or extensions!
|
||||
</strong>
|
||||
<br />
|
||||
Use with caution as there could be malicious actors making those repositories.
|
||||
<br />
|
||||
You as the user need to verify the security and that you trust any repository or extension.
|
||||
</Trans>
|
||||
<ListItemLink to={AppRoutes.settings.children.browse.children.extensionStores.path}>
|
||||
<ListItemText
|
||||
primary={t`Extension stores`}
|
||||
secondary={
|
||||
extensionStoreCount &&
|
||||
plural(extensionStoreCount, {
|
||||
one: '# extension store',
|
||||
other: '# extension stores',
|
||||
})
|
||||
}
|
||||
handleChange={(repos) => {
|
||||
updateSetting('extensionRepos', repos);
|
||||
requestManager.clearExtensionCache();
|
||||
}}
|
||||
valueInfos={serverSettings.extensionRepos.map((extensionRepo) => [extensionRepo])}
|
||||
addItemButtonTitle={t`Add repository`}
|
||||
placeholder="https://github.com/MY_ACCOUNT/MY_REPO/tree/repo"
|
||||
validateItem={(repo) =>
|
||||
!!repo.match(
|
||||
/https:\/\/(www\.|raw\.)?(github|githubusercontent)\.com\/([^/]+)\/([^/]+)((\/tree|\/blob)?\/([^/\n]*))?(\/([^/\n]*\.json)?)?/g,
|
||||
)
|
||||
}
|
||||
invalidItemError={t`Invalid repository url`}
|
||||
/>
|
||||
</ListItemLink>
|
||||
<TextSetting
|
||||
settingName={t`Local source location`}
|
||||
dialogDescription={t`The path to the directory on the server where local source files are saved in`}
|
||||
|
||||
@@ -81,7 +81,7 @@ export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
[sources],
|
||||
);
|
||||
const areSourcesFromDifferentRepos = useMemo(
|
||||
() => SourceService.areFromMultipleRepos(filteredSources),
|
||||
() => SourceService.areFromMultipleStores(filteredSources),
|
||||
[filteredSources],
|
||||
);
|
||||
const visibleSources = useMemo(
|
||||
|
||||
@@ -30,6 +30,7 @@ import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { languageCodeToName } from '@/base/utils/Languages.ts';
|
||||
import { SourceContentType } from '@/features/source/Source.types.ts';
|
||||
import { isNsfw } from '@/features/extension/Extensions.utils.ts';
|
||||
|
||||
interface IProps {
|
||||
source: GetSourcesListQuery['sources']['nodes'][number];
|
||||
@@ -47,8 +48,8 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
lang,
|
||||
iconUrl,
|
||||
supportsLatest,
|
||||
isNsfw,
|
||||
extension: { repo },
|
||||
contentWarning,
|
||||
extension: { extensionStore },
|
||||
} = source;
|
||||
|
||||
const { isPinned } = useGetSourceMetadata(source);
|
||||
@@ -92,13 +93,15 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
{showLanguage && languageCodeToName(lang)}
|
||||
{isNsfw && (
|
||||
{isNsfw(contentWarning) && (
|
||||
<Typography variant="caption" color="error">
|
||||
{' 18+'}
|
||||
</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
{showSourceRepo && <Typography variant="caption">{repo}</Typography>}
|
||||
{showSourceRepo && extensionStore && (
|
||||
<Typography variant="caption">{extensionStore.name}</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
{supportsLatest && (
|
||||
<Button
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import type { ExtensionType } from '@/lib/graphql/generated/graphql-base.types.ts';
|
||||
import type { ExtensionStoreType, ExtensionType } from '@/lib/graphql/generated/graphql-base.types.ts';
|
||||
|
||||
export enum ExtensionAction {
|
||||
UPDATE = 'UPDATE',
|
||||
@@ -36,15 +36,14 @@ export type TExtension = Pick<
|
||||
| 'pkgName'
|
||||
| 'name'
|
||||
| 'lang'
|
||||
| 'versionCode'
|
||||
| 'versionCodeLong'
|
||||
| 'versionName'
|
||||
| 'iconUrl'
|
||||
| 'repo'
|
||||
| 'isNsfw'
|
||||
| 'contentWarning'
|
||||
| 'isInstalled'
|
||||
| 'isObsolete'
|
||||
| 'hasUpdate'
|
||||
>;
|
||||
> & { extensionStore?: Pick<ExtensionStoreType, 'indexUrl' | 'name'> | null };
|
||||
|
||||
export type GroupedExtensionsResult<KEY extends string = string> = [KEY, TExtension[]][];
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { i18n } from '@/i18n';
|
||||
import { ContentWarning } from '@/lib/graphql/generated/graphql-base.types.ts';
|
||||
|
||||
export const isNsfw = (contentWarning: ContentWarning): boolean => contentWarning !== ContentWarning.Safe;
|
||||
|
||||
export const getInstalledState = (
|
||||
isInstalled: boolean,
|
||||
@@ -130,7 +133,7 @@ export const filterExtensions = (
|
||||
normalizedSelectedLanguages.includes(toComparableLanguage(extension.lang)) ||
|
||||
extension.isInstalled,
|
||||
)
|
||||
.filter((extension) => showNsfw === undefined || showNsfw || !extension.isNsfw)
|
||||
.filter((extension) => showNsfw === undefined || showNsfw || !isNsfw(extension.contentWarning))
|
||||
.filter((extension) => query == null || enhancedCleanup(extension.name).includes(enhancedCleanup(query)));
|
||||
};
|
||||
|
||||
|
||||
@@ -12,19 +12,19 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { SpinnerImage } from '@/base/components/SpinnerImage.tsx';
|
||||
import type { TExtension } from '@/features/extension/Extensions.types.ts';
|
||||
|
||||
export const Header = ({ name, pkgName, iconUrl, repo }: TExtension) => (
|
||||
export const Header = ({ name, pkgName, iconUrl, extensionStore }: TExtension) => (
|
||||
<Stack sx={{ alignItems: 'center' }}>
|
||||
<SpinnerImage alt={name} src={requestManager.getValidImgUrlFor(iconUrl)} ignoreQueue />
|
||||
<Typography variant="h5" component="h2">
|
||||
{name}
|
||||
</Typography>
|
||||
{extensionStore && (
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
{extensionStore.name}
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{pkgName.replace('eu.kanade.tachiyomi.extension.', '')}
|
||||
</Typography>
|
||||
{repo && (
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{repo}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -11,15 +11,18 @@ import { useLingui } from '@lingui/react/macro';
|
||||
import type { TExtension } from '@/features/extension/Extensions.types.ts';
|
||||
import { ExtensionMetadata } from '@/features/extension/info/components/ExtensionMetadata.tsx';
|
||||
import { languageCodeToName } from '@/base/utils/Languages.ts';
|
||||
import { isNsfw } from '@/features/extension/Extensions.utils.ts';
|
||||
|
||||
export const Meta = ({ versionName, lang, isNsfw }: TExtension) => {
|
||||
export const Meta = ({ versionName, lang, contentWarning }: TExtension) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<Stack sx={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<ExtensionMetadata title={t`Version`} value={versionName} />
|
||||
<ExtensionMetadata title={t`Language`} value={languageCodeToName(lang)} />
|
||||
{isNsfw && <ExtensionMetadata title={t`Age rating`} value="18+" valueProps={{ color: 'error' }} />}
|
||||
{isNsfw(contentWarning) && (
|
||||
<ExtensionMetadata title={t`Age rating`} value="18+" valueProps={{ color: 'error' }} />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
286
src/features/extension/store/screens/ExtensionStores.tsx
Normal file
286
src/features/extension/store/screens/ExtensionStores.tsx
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||
import { getErrorMessage, noOp } from '@/lib/HelperFunctions.ts';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
|
||||
import type { ExtensionStoreFieldsFragment } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { VirtuosoPersisted } from '@/lib/virtuoso/Component/VirtuosoPersisted.tsx';
|
||||
import Card from '@mui/material/Card';
|
||||
import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx';
|
||||
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { plural } from '@lingui/core/macro';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import { IconBrowser } from '@/assets/icons/IconBrowser.tsx';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { DiscordIcon } from '@/assets/icons/svg/DiscordIcon.tsx';
|
||||
import { Confirmation } from '@/base/AppAwaitableComponent.ts';
|
||||
import Box from '@mui/material/Box';
|
||||
import { ContentWarning } from '@/lib/graphql/generated/graphql-base.types.ts';
|
||||
import { DEFAULT_FULL_FAB_HEIGHT, StyledFab } from '@/base/components/buttons/StyledFab.tsx';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { AwaitableComponent, type AwaitableComponentProps } from 'awaitable-component';
|
||||
import { TextSettingDialog } from '@/base/components/settings/text/TextSettingDialog.tsx';
|
||||
import { useMemo } from 'react';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
|
||||
const ExtensionStoreCard = ({
|
||||
indexUrl,
|
||||
name,
|
||||
contactDiscord,
|
||||
contactWebsite,
|
||||
extensions: { totalCount: extensionCount },
|
||||
}: ExtensionStoreFieldsFragment) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const [removeExtensionStore, { loading }] = requestManager.useRemoveExtensionStore();
|
||||
const [fetchExtensions] = requestManager.useExtensionListFetch();
|
||||
|
||||
const nsfwExtensionsData = requestManager.useGetExtensionList({
|
||||
variables: {
|
||||
condition: { storeIndexUrl: indexUrl },
|
||||
filter: { contentWarning: { greaterThanOrEqualTo: ContentWarning.Mixed } },
|
||||
},
|
||||
});
|
||||
const installedExtensionsData = requestManager.useGetExtensionList({
|
||||
variables: { condition: { storeIndexUrl: indexUrl, isInstalled: true } },
|
||||
});
|
||||
|
||||
const isNSfw = nsfwExtensionsData.dataState === 'complete' && nsfwExtensionsData.data?.extensions.totalCount;
|
||||
const installedExtensionCount = installedExtensionsData.data?.extensions.totalCount;
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 1, pb: 0 }}>
|
||||
<Card>
|
||||
<ListCardContent sx={{ flexWrap: 'wrap', justifyContent: 'space-between' }}>
|
||||
<Stack>
|
||||
<TypographyMaxLines variant="h6" component="h3">
|
||||
{name}
|
||||
</TypographyMaxLines>
|
||||
<TypographyMaxLines variant="caption" color="textSecondary">
|
||||
{isNSfw ? (
|
||||
<>
|
||||
<TypographyMaxLines sx={{ display: 'inline' }} variant="caption" color="error">
|
||||
{' '}
|
||||
+18
|
||||
</TypographyMaxLines>
|
||||
{extensionCount && ' - '}
|
||||
</>
|
||||
) : null}
|
||||
{!!extensionCount &&
|
||||
plural(extensionCount, {
|
||||
one: '# extension',
|
||||
other: '# extensions',
|
||||
})}
|
||||
{installedExtensionCount
|
||||
? ` - ${plural(installedExtensionCount, {
|
||||
one: '# installed',
|
||||
other: '# installed',
|
||||
})}`
|
||||
: null}
|
||||
</TypographyMaxLines>
|
||||
</Stack>
|
||||
<Stack
|
||||
sx={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'end',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{contactWebsite && (
|
||||
<CustomTooltip title={t`Open website`} disabled={!contactWebsite}>
|
||||
<IconButton
|
||||
disabled={!contactWebsite}
|
||||
href={contactWebsite ?? undefined}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
color="inherit"
|
||||
>
|
||||
<IconBrowser />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
)}
|
||||
{contactDiscord && (
|
||||
<CustomTooltip title={t`Open discord`} disabled={!contactDiscord}>
|
||||
<IconButton
|
||||
href={contactDiscord ?? undefined}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
color="inherit"
|
||||
>
|
||||
<DiscordIcon />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
)}
|
||||
<CustomTooltip title={t`Copy index url`}>
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(indexUrl);
|
||||
makeToast(t`Copied to clipboard`, 'info');
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
<ContentCopyIcon />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
<CustomTooltip disabled={loading} title={t`Delete`}>
|
||||
<IconButton
|
||||
disabled={loading}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await Confirmation.show({
|
||||
title: t`Are you sure?`,
|
||||
message: t`You are about to remove "${name}" from the extension stores`,
|
||||
});
|
||||
|
||||
try {
|
||||
await removeExtensionStore({ variables: { input: { indexUrl } } });
|
||||
|
||||
requestManager.clearExtensionCache();
|
||||
fetchExtensions().catch(
|
||||
defaultPromiseErrorHandler(
|
||||
'ExtensionStoreCard::remove::fetchExtensions',
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
makeToast(
|
||||
t`Could not remove extension store "${name}"`,
|
||||
'error',
|
||||
getErrorMessage(e),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
</Stack>
|
||||
</ListCardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const AddExtensionStoreDialog = ({
|
||||
onExitComplete,
|
||||
onDismiss,
|
||||
isVisible,
|
||||
indexUrl,
|
||||
processing = false,
|
||||
startCreation,
|
||||
}: AwaitableComponentProps<void> & {
|
||||
indexUrl?: string;
|
||||
processing?: boolean;
|
||||
startCreation: (indexUrl: string) => void;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<TextSettingDialog
|
||||
settingName={t`Add extension store`}
|
||||
handleChange={startCreation}
|
||||
isDialogOpen={isVisible}
|
||||
setIsDialogOpen={noOp}
|
||||
onDismiss={onDismiss}
|
||||
onExitComplete={onExitComplete}
|
||||
value={indexUrl}
|
||||
disabled={processing}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExtensionStores = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
useAppTitle(t`Extension stores`);
|
||||
|
||||
const [addExtensionStore] = requestManager.useAddExtensionStore();
|
||||
const { data, loading, error, refetch, dataState } = requestManager.useGetExtensionStores();
|
||||
const [fetchExtensions] = requestManager.useExtensionListFetch();
|
||||
|
||||
const extensionStores = useMemo(() => {
|
||||
if (!data?.extensionStores.nodes) {
|
||||
return STABLE_EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
return data.extensionStores.nodes.toSorted((a, b) => a.name.localeCompare(b.name));
|
||||
}, [data?.extensionStores.nodes]);
|
||||
|
||||
if (loading && dataState !== 'complete') {
|
||||
return <LoadingPlaceholder />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t`Unable to load extension stores`}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => {
|
||||
refetch().catch(defaultPromiseErrorHandler('ExtensionStores::refetchServerMetadataSettings'));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ pb: DEFAULT_FULL_FAB_HEIGHT }}>
|
||||
<VirtuosoPersisted
|
||||
persistKey="extension-store-list"
|
||||
useWindowScroll
|
||||
overscan={window.innerHeight * 0.5}
|
||||
totalCount={extensionStores.length}
|
||||
computeItemKey={(index) => extensionStores[index].indexUrl}
|
||||
itemContent={(index) => <ExtensionStoreCard {...extensionStores[index]} />}
|
||||
/>
|
||||
<StyledFab
|
||||
variant="extended"
|
||||
color="primary"
|
||||
sx={{ gap: 1 }}
|
||||
onClick={() => {
|
||||
const addStoreDialog = AwaitableComponent.showControlled(AddExtensionStoreDialog, {
|
||||
startCreation: async (indexUrl) => {
|
||||
const request = addExtensionStore({ variables: { input: { indexUrl } } });
|
||||
|
||||
addStoreDialog.update({ indexUrl, processing: true });
|
||||
|
||||
try {
|
||||
await request;
|
||||
|
||||
requestManager.clearExtensionCache();
|
||||
fetchExtensions().catch(
|
||||
defaultPromiseErrorHandler('ExtensionStores::add:fetchExtensions'),
|
||||
);
|
||||
|
||||
addStoreDialog.submit();
|
||||
} catch (e) {
|
||||
makeToast(t`Could not add extension store`, 'error', getErrorMessage(e));
|
||||
|
||||
addStoreDialog.update({ processing: false });
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
{t`Add`}
|
||||
</StyledFab>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -70,8 +70,8 @@ export type SourceIdInfo = Pick<SourceType, 'id'>;
|
||||
export type SourceLanguageInfo = Pick<SourceType, 'lang'>;
|
||||
export type SourceNameInfo = Pick<SourceType, 'name'>;
|
||||
export type SourceDisplayNameInfo = Pick<SourceType, 'displayName'>;
|
||||
export type SourceNsfwInfo = Pick<SourceType, 'isNsfw'>;
|
||||
export type SourceRepoInfo = { extension: Pick<ExtensionType, 'repo'> };
|
||||
export type SourceNsfwInfo = Pick<SourceType, 'contentWarning'>;
|
||||
export type SourceStoreInfo = { extension: Pick<ExtensionType, 'storeIndexUrl'> };
|
||||
export type SourceMetaInfo = { meta: SourceMetaFieldsFragment[] };
|
||||
export type SourceConfigurableInfo = Pick<SourceType, 'isConfigurable'>;
|
||||
export type SourceIconInfo = Pick<SourceType, 'iconUrl'>;
|
||||
|
||||
@@ -423,10 +423,10 @@ export function SourceMangas() {
|
||||
<>
|
||||
<AppbarSearch />
|
||||
<SourceGridLayout />
|
||||
<CustomTooltip title={t`Open in WebView`} disabled={!source?.baseUrl}>
|
||||
<CustomTooltip title={t`Open in WebView`} disabled={!source?.homeUrl}>
|
||||
<IconButton
|
||||
disabled={!source?.baseUrl}
|
||||
href={source?.baseUrl ? requestManager.getWebviewUrl(source?.baseUrl) : ''}
|
||||
disabled={!source?.homeUrl}
|
||||
href={source?.homeUrl ? requestManager.getWebviewUrl(source?.homeUrl) : ''}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
color="inherit"
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
SourceLanguageInfo,
|
||||
SourceNsfwInfo,
|
||||
SourceMetaInfo,
|
||||
SourceRepoInfo,
|
||||
SourceStoreInfo,
|
||||
} from '@/features/source/Source.types.ts';
|
||||
import {
|
||||
DefaultLanguage,
|
||||
@@ -34,6 +34,7 @@ import type { SourceBaseFieldsFragment } from '@/lib/graphql/generated/graphql.t
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import type { DocumentNode, Unmasked } from '@apollo/client';
|
||||
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/source/SourceFragments.ts';
|
||||
import { isNsfw as isNsfwFnc } from '@/features/extension/Extensions.utils.ts';
|
||||
|
||||
export class Sources {
|
||||
static readonly LOCAL_SOURCE_ID = '0';
|
||||
@@ -134,7 +135,7 @@ export class Sources {
|
||||
const normalizedLanguages = toComparableLanguages(toUniqueLanguageCodes(languages ?? []));
|
||||
|
||||
const filters: [Condition: any, CheckKeepLocalSource: boolean, Filter: (source: Source) => boolean][] = [
|
||||
[isNsfw, true, (source: Source) => source.isNsfw === isNsfw],
|
||||
[isNsfw, true, (source: Source) => isNsfwFnc(source.contentWarning) === isNsfw],
|
||||
[
|
||||
languages,
|
||||
true,
|
||||
@@ -157,14 +158,14 @@ export class Sources {
|
||||
}, sources);
|
||||
}
|
||||
|
||||
static areFromMultipleRepos<Source extends SourceIdInfo & SourceRepoInfo>(sources: Source[]): boolean {
|
||||
const repo = sources.find((source) => !!source.extension.repo)?.extension.repo;
|
||||
static areFromMultipleStores<Source extends SourceIdInfo & SourceStoreInfo>(sources: Source[]): boolean {
|
||||
const store = sources.find((source) => !!source.extension.storeIndexUrl)?.extension.storeIndexUrl;
|
||||
|
||||
if (!repo || !sources.length) {
|
||||
if (!store || !sources.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sources.some((source) => source.extension.repo !== repo && !Sources.isLocalSource(source));
|
||||
return sources.some((source) => source.extension.storeIndexUrl !== store && !Sources.isLocalSource(source));
|
||||
}
|
||||
|
||||
static getLastUsedSource<Source extends SourceIdInfo & SourceMetaInfo>(
|
||||
|
||||
@@ -289,6 +289,18 @@ msgstr "{currentAutoUpdateIntervalHours}h"
|
||||
msgid "{currentDownloadAheadLimit, plural, one {# Chapter} other {# Chapters}}"
|
||||
msgstr "{currentDownloadAheadLimit, plural, one {# Chapter} other {# Chapters}}"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "{extensionCount, plural, one {# extension} other {# extensions}}"
|
||||
msgstr "{extensionCount, plural, one {# extension} other {# extensions}}"
|
||||
|
||||
#: src/features/browse/screens/BrowseSettings.tsx
|
||||
msgid "{extensionStoreCount, plural, one {# extension store} other {# extension stores}}"
|
||||
msgstr "{extensionStoreCount, plural, one {# extension store} other {# extension stores}}"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "{installedExtensionCount, plural, one {# installed} other {# installed}}"
|
||||
msgstr "{installedExtensionCount, plural, one {# installed} other {# installed}}"
|
||||
|
||||
#: src/features/chapter/components/ChapterList.tsx
|
||||
#: src/features/chapter/components/MissingChaptersInfoSeparator.tsx
|
||||
msgid "{missingChapterCount, plural, one {Missing # chapter} other {Missing # chapters}}"
|
||||
@@ -315,10 +327,6 @@ msgstr "{ttl, plural, one {Delete log files that are older than # day} other {De
|
||||
msgid "*: Active setting"
|
||||
msgstr "*: Active setting"
|
||||
|
||||
#: src/features/browse/screens/BrowseSettings.tsx
|
||||
msgid "<0>Suwayomi does not provide any support for 3rd party repositories or extensions!</0><1/>Use with caution as there could be malicious actors making those repositories.<2/>You as the user need to verify the security and that you trust any repository or extension."
|
||||
msgstr "<0>Suwayomi does not provide any support for 3rd party repositories or extensions!</0><1/>Use with caution as there could be malicious actors making those repositories.<2/>You as the user need to verify the security and that you trust any repository or extension."
|
||||
|
||||
#: src/features/manga/services/Mangas.ts
|
||||
msgid "A migration is already in progress"
|
||||
msgstr "A migration is already in progress"
|
||||
@@ -363,6 +371,7 @@ msgid "Active setting ({title})"
|
||||
msgstr "Active setting ({title})"
|
||||
|
||||
#: src/base/components/settings/MutableListSetting.tsx
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||
#: src/features/reader/hotkeys/settings/components/ReaderSettingHotkey.tsx
|
||||
#: src/features/settings/components/images/KeyValueItems.tsx
|
||||
@@ -374,13 +383,10 @@ msgstr "Add"
|
||||
msgid "Add bookmark"
|
||||
msgstr "Add bookmark"
|
||||
|
||||
#: src/features/browse/screens/BrowseSettings.tsx
|
||||
msgid "Add repositories from which extensions can be installed"
|
||||
msgstr "Add repositories from which extensions can be installed"
|
||||
|
||||
#: src/features/browse/screens/BrowseSettings.tsx
|
||||
msgid "Add repository"
|
||||
msgstr "Add repository"
|
||||
#: src/features/browse/extensions/Extensions.tsx
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Add extension store"
|
||||
msgstr "Add extension store"
|
||||
|
||||
#: src/features/manga/components/details/MangaDetails.tsx
|
||||
#: src/features/manga/components/MangaBadges.tsx
|
||||
@@ -452,6 +458,7 @@ msgstr "Are you sure you want to abort the migration?"
|
||||
|
||||
#: src/features/category/components/CategorySettingsCard.tsx
|
||||
#: src/features/chapter/services/Chapters.ts
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||
#: src/features/manga/services/Mangas.ts
|
||||
#: src/features/source/browse/components/SourceOptions.tsx
|
||||
@@ -969,6 +976,7 @@ msgstr ""
|
||||
msgid "Convert images to different formats"
|
||||
msgstr "Convert images to different formats"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
#: src/features/manga/components/details/MangaDetails.tsx
|
||||
#: src/features/reader/overlay/mobile/ReaderOverlayHeaderMobile.tsx
|
||||
msgid "Copied to clipboard"
|
||||
@@ -979,6 +987,14 @@ msgstr "Copied to clipboard"
|
||||
msgid "Copy"
|
||||
msgstr "Copy"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Copy index url"
|
||||
msgstr "Copy index url"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Could not add extension store"
|
||||
msgstr "Could not add extension store"
|
||||
|
||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||
msgid "Could not add manga to library!"
|
||||
msgstr "Could not add manga to library!"
|
||||
@@ -1090,6 +1106,10 @@ msgstr "Could not migrate manga"
|
||||
msgid "Could not remove all downloads from the queue"
|
||||
msgstr "Could not remove all downloads from the queue"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Could not remove extension store \"{name}\""
|
||||
msgstr "Could not remove extension store \"{name}\""
|
||||
|
||||
#: src/features/library/screens/LibrarySettings.tsx
|
||||
msgid "Could not remove non library manga from categories"
|
||||
msgstr "Could not remove non library manga from categories"
|
||||
@@ -1289,6 +1309,7 @@ msgstr "Default category when adding new manga to the library"
|
||||
#: src/features/category/components/CategorySettingsCard.tsx
|
||||
#: src/features/chapter/Chapter.constants.ts
|
||||
#: src/features/downloads/components/DownloadQueueChapterCard.tsx
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
#: src/features/reader/hotkeys/settings/components/Hotkey.tsx
|
||||
#: src/features/settings/components/images/KeyValueItem.tsx
|
||||
#: src/features/settings/components/images/Processing.tsx
|
||||
@@ -1640,8 +1661,9 @@ msgid "Extension installed"
|
||||
msgstr "Extension installed"
|
||||
|
||||
#: src/features/browse/screens/BrowseSettings.tsx
|
||||
msgid "Extension repositories"
|
||||
msgstr "Extension repositories"
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Extension stores"
|
||||
msgstr "Extension stores"
|
||||
|
||||
#: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx
|
||||
msgid "Failed to connect to KOReader Sync server."
|
||||
@@ -2069,10 +2091,6 @@ msgstr "Invalid input"
|
||||
msgid "Invalid json"
|
||||
msgstr "Invalid json"
|
||||
|
||||
#: src/features/browse/screens/BrowseSettings.tsx
|
||||
msgid "Invalid repository url"
|
||||
msgstr "Invalid repository url"
|
||||
|
||||
#: src/features/reader/filters/settings/components/ReaderSettingInvert.tsx
|
||||
msgid "Invert"
|
||||
msgstr "Invert"
|
||||
@@ -2682,6 +2700,10 @@ msgstr "OPDS"
|
||||
msgid "Open"
|
||||
msgstr "Open"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Open discord"
|
||||
msgstr "Open discord"
|
||||
|
||||
#: src/features/chapter/components/actions/ChapterActionMenuItems.tsx
|
||||
#: src/features/manga/components/details/MangaDetails.tsx
|
||||
#: src/features/reader/overlay/mobile/ReaderOverlayHeaderMobile.tsx
|
||||
@@ -2706,6 +2728,10 @@ msgstr "Open page on exit"
|
||||
msgid "Open the WebUI when starting the server"
|
||||
msgstr "Open the WebUI when starting the server"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Open website"
|
||||
msgstr "Open website"
|
||||
|
||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||
#: src/features/manga/components/MangaOptionButton.tsx
|
||||
msgid "Options"
|
||||
@@ -3244,7 +3270,6 @@ msgstr "Setting can not be changed while the reader is opened"
|
||||
#: src/base/components/inputs/LanguageSelect.tsx
|
||||
#: src/features/backup/Backup.constants.ts
|
||||
#: src/features/browse/extensions/components/ExtensionCard.tsx
|
||||
#: src/features/browse/extensions/Extensions.tsx
|
||||
#: src/features/extension/info/components/SourceCard.tsx
|
||||
#: src/features/library/components/LibraryToolbarMenu.tsx
|
||||
#: src/features/navigation-bar/NavigationBar.constants.ts
|
||||
@@ -3830,6 +3855,10 @@ msgstr "UI Login"
|
||||
msgid "Unable to load data"
|
||||
msgstr "Unable to load data"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "Unable to load extension stores"
|
||||
msgstr "Unable to load extension stores"
|
||||
|
||||
#: src/features/migration/screens/MigrationSelectDestinationSources.tsx
|
||||
msgid "Unable to load sources"
|
||||
msgstr "Unable to load sources"
|
||||
@@ -4096,6 +4125,10 @@ msgstr "You are about to delete category \"{0}\""
|
||||
msgid "You are about to remove \"{0}\" from your library"
|
||||
msgstr "You are about to remove \"{0}\" from your library"
|
||||
|
||||
#: src/features/extension/store/screens/ExtensionStores.tsx
|
||||
msgid "You are about to remove \"{name}\" from the extension stores"
|
||||
msgstr "You are about to remove \"{name}\" from the extension stores"
|
||||
|
||||
#: src/features/backup/screens/Backup.tsx
|
||||
msgid "You can also drag and drop the backup file here to restore it"
|
||||
msgstr "You can also drag and drop the backup file here to restore it"
|
||||
@@ -4117,8 +4150,8 @@ msgid "You have not read any series yet."
|
||||
msgstr "You have not read any series yet."
|
||||
|
||||
#: src/features/browse/extensions/Extensions.tsx
|
||||
msgid "You have to add a extension repository to be able to install extensions"
|
||||
msgstr "You have to add a extension repository to be able to install extensions"
|
||||
msgid "You have to add a extension store to be able to install extensions"
|
||||
msgstr "You have to add a extension store to be able to install extensions"
|
||||
|
||||
#: src/features/settings/Settings.constants.ts
|
||||
msgid "Your browser will prompt you to enter credentials with a dialog."
|
||||
|
||||
@@ -13,13 +13,18 @@ export const EXTENSION_LIST_FIELDS = gql`
|
||||
pkgName
|
||||
name
|
||||
lang
|
||||
versionCode
|
||||
versionCodeLong
|
||||
versionName
|
||||
iconUrl
|
||||
repo
|
||||
isNsfw
|
||||
storeIndexUrl
|
||||
contentWarning
|
||||
isInstalled
|
||||
isObsolete
|
||||
hasUpdate
|
||||
extensionLib
|
||||
extensionStore {
|
||||
indexUrl
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -8,16 +8,21 @@
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
import { EXTENSION_LIST_FIELDS } from '@/lib/graphql/extension/ExtensionFragments.ts';
|
||||
import { EXTENSION_STORE_FIELDS } from '@/lib/graphql/extension/store/ExtensionStoreFragments.ts';
|
||||
|
||||
// makes the server fetch and return the latest extensions
|
||||
export const GET_EXTENSIONS_FETCH = gql`
|
||||
${EXTENSION_LIST_FIELDS}
|
||||
${EXTENSION_STORE_FIELDS}
|
||||
|
||||
mutation GET_EXTENSIONS_FETCH($input: FetchExtensionsInput = {}) {
|
||||
fetchExtensions(input: $input) {
|
||||
extensions {
|
||||
...EXTENSION_LIST_FIELDS
|
||||
}
|
||||
extensionStores {
|
||||
...EXTENSION_STORE_FIELDS
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
25
src/lib/graphql/extension/store/ExtensionStoreFragments.ts
Normal file
25
src/lib/graphql/extension/store/ExtensionStoreFragments.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const EXTENSION_STORE_FIELDS = gql`
|
||||
fragment EXTENSION_STORE_FIELDS on ExtensionStoreType {
|
||||
badgeLabel
|
||||
contactDiscord
|
||||
contactWebsite
|
||||
extensionListUrl
|
||||
indexUrl
|
||||
isLegacy
|
||||
name
|
||||
signingKey
|
||||
extensions {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
34
src/lib/graphql/extension/store/ExtensionStoreMutation.ts
Normal file
34
src/lib/graphql/extension/store/ExtensionStoreMutation.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
import { EXTENSION_STORE_FIELDS } from '@/lib/graphql/extension/store/ExtensionStoreFragments.ts';
|
||||
|
||||
export const ADD_EXTENSION_STORE = gql`
|
||||
${EXTENSION_STORE_FIELDS}
|
||||
|
||||
mutation ADD_EXTENSION_STORE($input: AddExtensionStoreInput!) {
|
||||
addExtensionStore(input: $input) {
|
||||
extensionStore {
|
||||
...EXTENSION_STORE_FIELDS
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const REMOVE_EXTENSION_STORE = gql`
|
||||
${EXTENSION_STORE_FIELDS}
|
||||
|
||||
mutation REMOVE_EXTENSION_STORE($input: RemoveExtensionStoreInput!) {
|
||||
removeExtensionStore(input: $input) {
|
||||
extensionStore {
|
||||
...EXTENSION_STORE_FIELDS
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
38
src/lib/graphql/extension/store/ExtensionStoreQuery.ts
Normal file
38
src/lib/graphql/extension/store/ExtensionStoreQuery.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_INFO } from '@/lib/graphql/common/Fragments.ts';
|
||||
import { EXTENSION_STORE_FIELDS } from '@/lib/graphql/extension/store/ExtensionStoreFragments.ts';
|
||||
|
||||
export const GET_EXTENSION_STORE = gql`
|
||||
${EXTENSION_STORE_FIELDS}
|
||||
|
||||
query GET_EXTENSION_STORE($indexUrl: String!) {
|
||||
extensionStore(indexUrl: $indexUrl) {
|
||||
...EXTENSION_STORE_FIELDS
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_EXTENSION_STORES = gql`
|
||||
${EXTENSION_STORE_FIELDS}
|
||||
${PAGE_INFO}
|
||||
|
||||
query GET_EXTENSION_STORES {
|
||||
extensionStores {
|
||||
nodes {
|
||||
...EXTENSION_STORE_FIELDS
|
||||
}
|
||||
pageInfo {
|
||||
...PAGE_INFO
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -24,6 +24,15 @@ export type AboutWebUIFieldPolicy = {
|
||||
tag?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
updateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type AddExtensionStorePayloadKeySpecifier = (
|
||||
| 'clientMutationId'
|
||||
| 'extensionStore'
|
||||
| AddExtensionStorePayloadKeySpecifier
|
||||
)[];
|
||||
export type AddExtensionStorePayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionStore?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type BackupRestoreStatusKeySpecifier = (
|
||||
| 'mangaProgress'
|
||||
| 'state'
|
||||
@@ -512,8 +521,53 @@ export type ExtensionNodeListFieldPolicy = {
|
||||
pageInfo?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
totalCount?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type ExtensionStoreEdgeKeySpecifier = ('cursor' | 'node' | ExtensionStoreEdgeKeySpecifier)[];
|
||||
export type ExtensionStoreEdgeFieldPolicy = {
|
||||
cursor?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
node?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type ExtensionStoreNodeListKeySpecifier = (
|
||||
| 'edges'
|
||||
| 'nodes'
|
||||
| 'pageInfo'
|
||||
| 'totalCount'
|
||||
| ExtensionStoreNodeListKeySpecifier
|
||||
)[];
|
||||
export type ExtensionStoreNodeListFieldPolicy = {
|
||||
edges?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
nodes?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
pageInfo?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
totalCount?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type ExtensionStoreTypeKeySpecifier = (
|
||||
| 'badgeLabel'
|
||||
| 'contactDiscord'
|
||||
| 'contactWebsite'
|
||||
| 'extensionListUrl'
|
||||
| 'extensions'
|
||||
| 'indexUrl'
|
||||
| 'isLegacy'
|
||||
| 'name'
|
||||
| 'signingKey'
|
||||
| ExtensionStoreTypeKeySpecifier
|
||||
)[];
|
||||
export type ExtensionStoreTypeFieldPolicy = {
|
||||
badgeLabel?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
contactDiscord?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
contactWebsite?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionListUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensions?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
indexUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
isLegacy?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
name?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
signingKey?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type ExtensionTypeKeySpecifier = (
|
||||
| 'apkName'
|
||||
| 'apkUrl'
|
||||
| 'contentWarning'
|
||||
| 'extensionLib'
|
||||
| 'extensionStore'
|
||||
| 'hasUpdate'
|
||||
| 'iconUrl'
|
||||
| 'isInstalled'
|
||||
@@ -524,12 +578,18 @@ export type ExtensionTypeKeySpecifier = (
|
||||
| 'pkgName'
|
||||
| 'repo'
|
||||
| 'source'
|
||||
| 'storeIndexUrl'
|
||||
| 'versionCode'
|
||||
| 'versionCodeLong'
|
||||
| 'versionName'
|
||||
| ExtensionTypeKeySpecifier
|
||||
)[];
|
||||
export type ExtensionTypeFieldPolicy = {
|
||||
apkName?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
apkUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
contentWarning?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionLib?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionStore?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
hasUpdate?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
iconUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
isInstalled?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
@@ -540,7 +600,9 @@ export type ExtensionTypeFieldPolicy = {
|
||||
pkgName?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
repo?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
source?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
storeIndexUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
versionCode?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
versionCodeLong?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
versionName?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type FetchChapterPagesPayloadKeySpecifier = (
|
||||
@@ -563,11 +625,13 @@ export type FetchChaptersPayloadFieldPolicy = {
|
||||
};
|
||||
export type FetchExtensionsPayloadKeySpecifier = (
|
||||
| 'clientMutationId'
|
||||
| 'extensionStores'
|
||||
| 'extensions'
|
||||
| FetchExtensionsPayloadKeySpecifier
|
||||
)[];
|
||||
export type FetchExtensionsPayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionStores?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensions?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type FetchMangaAndChaptersPayloadKeySpecifier = (
|
||||
@@ -888,6 +952,7 @@ export type MultiSelectListPreferenceFieldPolicy = {
|
||||
visible?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type MutationKeySpecifier = (
|
||||
| 'addExtensionStore'
|
||||
| 'bindTrack'
|
||||
| 'bindTrackRecord'
|
||||
| 'clearCachedImages'
|
||||
@@ -928,6 +993,7 @@ export type MutationKeySpecifier = (
|
||||
| 'pullKoSyncProgress'
|
||||
| 'pushKoSyncProgress'
|
||||
| 'refreshToken'
|
||||
| 'removeExtensionStore'
|
||||
| 'reorderChapterDownload'
|
||||
| 'resetSettings'
|
||||
| 'resetWebUIUpdateStatus'
|
||||
@@ -969,6 +1035,7 @@ export type MutationKeySpecifier = (
|
||||
| MutationKeySpecifier
|
||||
)[];
|
||||
export type MutationFieldPolicy = {
|
||||
addExtensionStore?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
bindTrack?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
bindTrackRecord?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
clearCachedImages?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
@@ -1009,6 +1076,7 @@ export type MutationFieldPolicy = {
|
||||
pullKoSyncProgress?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
pushKoSyncProgress?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
refreshToken?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
removeExtensionStore?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
reorderChapterDownload?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
resetSettings?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
resetWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
@@ -1298,6 +1366,8 @@ export type QueryKeySpecifier = (
|
||||
| 'checkForWebUIUpdate'
|
||||
| 'downloadStatus'
|
||||
| 'extension'
|
||||
| 'extensionStore'
|
||||
| 'extensionStores'
|
||||
| 'extensions'
|
||||
| 'getWebUIUpdateStatus'
|
||||
| 'koSyncStatus'
|
||||
@@ -1332,6 +1402,8 @@ export type QueryFieldPolicy = {
|
||||
checkForWebUIUpdate?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
downloadStatus?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extension?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionStore?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionStores?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensions?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
getWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
koSyncStatus?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
@@ -1359,6 +1431,15 @@ export type RefreshTokenPayloadFieldPolicy = {
|
||||
accessToken?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type RemoveExtensionStorePayloadKeySpecifier = (
|
||||
| 'clientMutationId'
|
||||
| 'extensionStore'
|
||||
| RemoveExtensionStorePayloadKeySpecifier
|
||||
)[];
|
||||
export type RemoveExtensionStorePayloadFieldPolicy = {
|
||||
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extensionStore?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
};
|
||||
export type ReorderChapterDownloadPayloadKeySpecifier = (
|
||||
| 'clientMutationId'
|
||||
| 'downloadStatus'
|
||||
@@ -1955,9 +2036,11 @@ export type SourceNodeListFieldPolicy = {
|
||||
};
|
||||
export type SourceTypeKeySpecifier = (
|
||||
| 'baseUrl'
|
||||
| 'contentWarning'
|
||||
| 'displayName'
|
||||
| 'extension'
|
||||
| 'filters'
|
||||
| 'homeUrl'
|
||||
| 'iconUrl'
|
||||
| 'id'
|
||||
| 'isConfigurable'
|
||||
@@ -1972,9 +2055,11 @@ export type SourceTypeKeySpecifier = (
|
||||
)[];
|
||||
export type SourceTypeFieldPolicy = {
|
||||
baseUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
contentWarning?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
displayName?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
extension?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
filters?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
homeUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
iconUrl?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
id?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
isConfigurable?: FieldPolicy<any> | FieldReadFunction<any>;
|
||||
@@ -2477,6 +2562,13 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | AboutWebUIKeySpecifier | (() => undefined | AboutWebUIKeySpecifier);
|
||||
fields?: AboutWebUIFieldPolicy;
|
||||
};
|
||||
AddExtensionStorePayload?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?:
|
||||
| false
|
||||
| AddExtensionStorePayloadKeySpecifier
|
||||
| (() => undefined | AddExtensionStorePayloadKeySpecifier);
|
||||
fields?: AddExtensionStorePayloadFieldPolicy;
|
||||
};
|
||||
BackupRestoreStatus?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?: false | BackupRestoreStatusKeySpecifier | (() => undefined | BackupRestoreStatusKeySpecifier);
|
||||
fields?: BackupRestoreStatusFieldPolicy;
|
||||
@@ -2712,6 +2804,18 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | ExtensionNodeListKeySpecifier | (() => undefined | ExtensionNodeListKeySpecifier);
|
||||
fields?: ExtensionNodeListFieldPolicy;
|
||||
};
|
||||
ExtensionStoreEdge?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?: false | ExtensionStoreEdgeKeySpecifier | (() => undefined | ExtensionStoreEdgeKeySpecifier);
|
||||
fields?: ExtensionStoreEdgeFieldPolicy;
|
||||
};
|
||||
ExtensionStoreNodeList?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?: false | ExtensionStoreNodeListKeySpecifier | (() => undefined | ExtensionStoreNodeListKeySpecifier);
|
||||
fields?: ExtensionStoreNodeListFieldPolicy;
|
||||
};
|
||||
ExtensionStoreType?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?: false | ExtensionStoreTypeKeySpecifier | (() => undefined | ExtensionStoreTypeKeySpecifier);
|
||||
fields?: ExtensionStoreTypeFieldPolicy;
|
||||
};
|
||||
ExtensionType?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?: false | ExtensionTypeKeySpecifier | (() => undefined | ExtensionTypeKeySpecifier);
|
||||
fields?: ExtensionTypeFieldPolicy;
|
||||
@@ -2901,6 +3005,13 @@ export type StrictTypedTypePolicies = {
|
||||
keyFields?: false | RefreshTokenPayloadKeySpecifier | (() => undefined | RefreshTokenPayloadKeySpecifier);
|
||||
fields?: RefreshTokenPayloadFieldPolicy;
|
||||
};
|
||||
RemoveExtensionStorePayload?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?:
|
||||
| false
|
||||
| RemoveExtensionStorePayloadKeySpecifier
|
||||
| (() => undefined | RemoveExtensionStorePayloadKeySpecifier);
|
||||
fields?: RemoveExtensionStorePayloadFieldPolicy;
|
||||
};
|
||||
ReorderChapterDownloadPayload?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
|
||||
keyFields?:
|
||||
| false
|
||||
|
||||
@@ -32,6 +32,17 @@ export type AboutWebUi = {
|
||||
updateTimestamp: Scalars['LongString']['output'];
|
||||
};
|
||||
|
||||
export type AddExtensionStoreInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
indexUrl: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type AddExtensionStorePayload = {
|
||||
__typename?: 'AddExtensionStorePayload';
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
extensionStore: ExtensionStoreType;
|
||||
};
|
||||
|
||||
export enum AuthMode {
|
||||
BasicAuth = 'BASIC_AUTH',
|
||||
None = 'NONE',
|
||||
@@ -349,6 +360,30 @@ export type ConnectKoSyncAccountInput = {
|
||||
username: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export enum ContentWarning {
|
||||
Mixed = 'MIXED',
|
||||
Nsfw = 'NSFW',
|
||||
Safe = 'SAFE',
|
||||
}
|
||||
|
||||
export type ContentWarningFilterInput = {
|
||||
distinctFrom?: InputMaybe<ContentWarning>;
|
||||
distinctFromAll?: InputMaybe<Array<ContentWarning>>;
|
||||
distinctFromAny?: InputMaybe<Array<ContentWarning>>;
|
||||
equalTo?: InputMaybe<ContentWarning>;
|
||||
greaterThan?: InputMaybe<ContentWarning>;
|
||||
greaterThanOrEqualTo?: InputMaybe<ContentWarning>;
|
||||
in?: InputMaybe<Array<ContentWarning>>;
|
||||
isNull?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
lessThan?: InputMaybe<ContentWarning>;
|
||||
lessThanOrEqualTo?: InputMaybe<ContentWarning>;
|
||||
notDistinctFrom?: InputMaybe<ContentWarning>;
|
||||
notEqualTo?: InputMaybe<ContentWarning>;
|
||||
notEqualToAll?: InputMaybe<Array<ContentWarning>>;
|
||||
notEqualToAny?: InputMaybe<Array<ContentWarning>>;
|
||||
notIn?: InputMaybe<Array<ContentWarning>>;
|
||||
};
|
||||
|
||||
export type CreateBackupInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
flags?: InputMaybe<PartialBackupFlagsInput>;
|
||||
@@ -720,16 +755,19 @@ export type EnqueueChapterDownloadsPayload = {
|
||||
|
||||
export type ExtensionConditionInput = {
|
||||
apkName?: InputMaybe<Scalars['String']['input']>;
|
||||
apkUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
contentWarning?: InputMaybe<ContentWarning>;
|
||||
extensionLib?: InputMaybe<Scalars['String']['input']>;
|
||||
hasUpdate?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
iconUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
isInstalled?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isNsfw?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isObsolete?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
lang?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
pkgName?: InputMaybe<Scalars['String']['input']>;
|
||||
repo?: InputMaybe<Scalars['String']['input']>;
|
||||
storeIndexUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
versionCode?: InputMaybe<Scalars['Int']['input']>;
|
||||
versionCodeLong?: InputMaybe<Scalars['LongString']['input']>;
|
||||
versionName?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
@@ -742,18 +780,20 @@ export type ExtensionEdge = Edge & {
|
||||
export type ExtensionFilterInput = {
|
||||
and?: InputMaybe<Array<ExtensionFilterInput>>;
|
||||
apkName?: InputMaybe<StringFilterInput>;
|
||||
apkUrl?: InputMaybe<StringFilterInput>;
|
||||
contentWarning?: InputMaybe<ContentWarningFilterInput>;
|
||||
extensionLib?: InputMaybe<StringFilterInput>;
|
||||
hasUpdate?: InputMaybe<BooleanFilterInput>;
|
||||
iconUrl?: InputMaybe<StringFilterInput>;
|
||||
isInstalled?: InputMaybe<BooleanFilterInput>;
|
||||
isNsfw?: InputMaybe<BooleanFilterInput>;
|
||||
isObsolete?: InputMaybe<BooleanFilterInput>;
|
||||
lang?: InputMaybe<StringFilterInput>;
|
||||
name?: InputMaybe<StringFilterInput>;
|
||||
not?: InputMaybe<ExtensionFilterInput>;
|
||||
or?: InputMaybe<Array<ExtensionFilterInput>>;
|
||||
pkgName?: InputMaybe<StringFilterInput>;
|
||||
repo?: InputMaybe<StringFilterInput>;
|
||||
versionCode?: InputMaybe<IntFilterInput>;
|
||||
storeIndexUrl?: InputMaybe<StringFilterInput>;
|
||||
versionCodeLong?: InputMaybe<LongFilterInput>;
|
||||
versionName?: InputMaybe<StringFilterInput>;
|
||||
};
|
||||
|
||||
@@ -766,6 +806,7 @@ export type ExtensionNodeList = NodeList & {
|
||||
};
|
||||
|
||||
export enum ExtensionOrderBy {
|
||||
/** @deprecated */
|
||||
ApkName = 'APK_NAME',
|
||||
Name = 'NAME',
|
||||
PkgName = 'PKG_NAME',
|
||||
@@ -776,20 +817,81 @@ export type ExtensionOrderInput = {
|
||||
byType?: InputMaybe<SortOrder>;
|
||||
};
|
||||
|
||||
export type ExtensionStoreConditionInput = {
|
||||
id?: InputMaybe<Scalars['Int']['input']>;
|
||||
indexUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type ExtensionStoreEdge = Edge & {
|
||||
__typename?: 'ExtensionStoreEdge';
|
||||
cursor: Scalars['Cursor']['output'];
|
||||
node: ExtensionStoreType;
|
||||
};
|
||||
|
||||
export type ExtensionStoreFilterInput = {
|
||||
and?: InputMaybe<Array<ExtensionStoreFilterInput>>;
|
||||
indexUrl?: InputMaybe<StringFilterInput>;
|
||||
name?: InputMaybe<StringFilterInput>;
|
||||
not?: InputMaybe<ExtensionStoreFilterInput>;
|
||||
or?: InputMaybe<Array<ExtensionStoreFilterInput>>;
|
||||
};
|
||||
|
||||
export type ExtensionStoreNodeList = NodeList & {
|
||||
__typename?: 'ExtensionStoreNodeList';
|
||||
edges: Array<ExtensionStoreEdge>;
|
||||
nodes: Array<ExtensionStoreType>;
|
||||
pageInfo: PageInfo;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export enum ExtensionStoreOrderBy {
|
||||
IndexUrl = 'INDEX_URL',
|
||||
Name = 'NAME',
|
||||
}
|
||||
|
||||
export type ExtensionStoreOrderInput = {
|
||||
by: ExtensionStoreOrderBy;
|
||||
byType?: InputMaybe<SortOrder>;
|
||||
};
|
||||
|
||||
export type ExtensionStoreType = {
|
||||
__typename?: 'ExtensionStoreType';
|
||||
badgeLabel: Scalars['String']['output'];
|
||||
contactDiscord?: Maybe<Scalars['String']['output']>;
|
||||
contactWebsite: Scalars['String']['output'];
|
||||
extensionListUrl?: Maybe<Scalars['String']['output']>;
|
||||
extensions: ExtensionNodeList;
|
||||
indexUrl: Scalars['String']['output'];
|
||||
isLegacy: Scalars['Boolean']['output'];
|
||||
name: Scalars['String']['output'];
|
||||
signingKey: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type ExtensionType = {
|
||||
__typename?: 'ExtensionType';
|
||||
apkName: Scalars['String']['output'];
|
||||
/** This will be nullable in the future */
|
||||
apkName?: Maybe<Scalars['String']['output']>;
|
||||
apkUrl?: Maybe<Scalars['String']['output']>;
|
||||
contentWarning: ContentWarning;
|
||||
extensionLib?: Maybe<Scalars['String']['output']>;
|
||||
extensionStore?: Maybe<ExtensionStoreType>;
|
||||
hasUpdate: Scalars['Boolean']['output'];
|
||||
iconUrl: Scalars['String']['output'];
|
||||
isInstalled: Scalars['Boolean']['output'];
|
||||
/** @deprecated Removed in extension api v1.6, replace with contentWarning */
|
||||
isNsfw: Scalars['Boolean']['output'];
|
||||
isObsolete: Scalars['Boolean']['output'];
|
||||
lang: Scalars['String']['output'];
|
||||
name: Scalars['String']['output'];
|
||||
pkgName: Scalars['String']['output'];
|
||||
/** @deprecated Removed in extension api v1.6, replace with storeIndexUrl */
|
||||
repo?: Maybe<Scalars['String']['output']>;
|
||||
source: SourceNodeList;
|
||||
storeIndexUrl?: Maybe<Scalars['String']['output']>;
|
||||
/** @deprecated Type was changed to Long, will be switched back to this variable name in the future., replace with versionCodeLong */
|
||||
versionCode: Scalars['Int']['output'];
|
||||
versionCodeLong: Scalars['LongString']['output'];
|
||||
versionName: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
@@ -825,6 +927,7 @@ export type FetchExtensionsInput = {
|
||||
export type FetchExtensionsPayload = {
|
||||
__typename?: 'FetchExtensionsPayload';
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
extensionStores: Array<ExtensionStoreType>;
|
||||
extensions: Array<ExtensionType>;
|
||||
};
|
||||
|
||||
@@ -1335,6 +1438,7 @@ export type MultiSelectListPreference = {
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
addExtensionStore?: Maybe<AddExtensionStorePayload>;
|
||||
bindTrack: BindTrackPayload;
|
||||
bindTrackRecord?: Maybe<BindTrackRecordPayload>;
|
||||
clearCachedImages: ClearCachedImagesPayload;
|
||||
@@ -1377,6 +1481,7 @@ export type Mutation = {
|
||||
pullKoSyncProgress?: Maybe<PullKoSyncProgressPayload>;
|
||||
pushKoSyncProgress?: Maybe<PushKoSyncProgressPayload>;
|
||||
refreshToken: RefreshTokenPayload;
|
||||
removeExtensionStore?: Maybe<RemoveExtensionStorePayload>;
|
||||
reorderChapterDownload?: Maybe<ReorderChapterDownloadPayload>;
|
||||
resetSettings: ResetSettingsPayload;
|
||||
resetWebUIUpdateStatus?: Maybe<WebUiUpdateStatus>;
|
||||
@@ -1417,6 +1522,10 @@ export type Mutation = {
|
||||
updateWebUI?: Maybe<WebUiUpdatePayload>;
|
||||
};
|
||||
|
||||
export type MutationAddExtensionStoreArgs = {
|
||||
input: AddExtensionStoreInput;
|
||||
};
|
||||
|
||||
export type MutationBindTrackArgs = {
|
||||
input: BindTrackInput;
|
||||
};
|
||||
@@ -1577,6 +1686,10 @@ export type MutationRefreshTokenArgs = {
|
||||
input: RefreshTokenInput;
|
||||
};
|
||||
|
||||
export type MutationRemoveExtensionStoreArgs = {
|
||||
input: RemoveExtensionStoreInput;
|
||||
};
|
||||
|
||||
export type MutationReorderChapterDownloadArgs = {
|
||||
input: ReorderChapterDownloadInput;
|
||||
};
|
||||
@@ -1732,6 +1845,7 @@ export type Node =
|
||||
| ChapterType
|
||||
| DownloadType
|
||||
| DownloadUpdate
|
||||
| ExtensionStoreType
|
||||
| ExtensionType
|
||||
| GlobalMetaType
|
||||
| MangaMetaType
|
||||
@@ -1816,6 +1930,7 @@ export type PartialSettingsType = Settings & {
|
||||
excludeEntryWithUnreadChapters?: Maybe<Scalars['Boolean']['output']>;
|
||||
excludeNotStarted?: Maybe<Scalars['Boolean']['output']>;
|
||||
excludeUnreadChapters?: Maybe<Scalars['Boolean']['output']>;
|
||||
/** @deprecated Replaced with addExtensionStore and removeExtensionStore mutations */
|
||||
extensionRepos?: Maybe<Array<Scalars['String']['output']>>;
|
||||
flareSolverrAsResponseFallback?: Maybe<Scalars['Boolean']['output']>;
|
||||
flareSolverrEnabled?: Maybe<Scalars['Boolean']['output']>;
|
||||
@@ -1917,7 +2032,6 @@ export type PartialSettingsTypeInput = {
|
||||
excludeEntryWithUnreadChapters?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
excludeNotStarted?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
excludeUnreadChapters?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
extensionRepos?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
flareSolverrAsResponseFallback?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
flareSolverrEnabled?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
flareSolverrSessionName?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -2018,6 +2132,8 @@ export type Query = {
|
||||
checkForWebUIUpdate: WebUiUpdateCheck;
|
||||
downloadStatus: DownloadStatus;
|
||||
extension: ExtensionType;
|
||||
extensionStore: ExtensionStoreType;
|
||||
extensionStores: ExtensionStoreNodeList;
|
||||
extensions: ExtensionNodeList;
|
||||
getWebUIUpdateStatus: WebUiUpdateStatus;
|
||||
koSyncStatus: KoSyncStatusPayload;
|
||||
@@ -2076,6 +2192,21 @@ export type QueryExtensionArgs = {
|
||||
pkgName: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type QueryExtensionStoreArgs = {
|
||||
indexUrl: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type QueryExtensionStoresArgs = {
|
||||
after?: InputMaybe<Scalars['Cursor']['input']>;
|
||||
before?: InputMaybe<Scalars['Cursor']['input']>;
|
||||
condition?: InputMaybe<ExtensionStoreConditionInput>;
|
||||
filter?: InputMaybe<ExtensionStoreFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
offset?: InputMaybe<Scalars['Int']['input']>;
|
||||
order?: InputMaybe<Array<ExtensionStoreOrderInput>>;
|
||||
};
|
||||
|
||||
export type QueryExtensionsArgs = {
|
||||
after?: InputMaybe<Scalars['Cursor']['input']>;
|
||||
before?: InputMaybe<Scalars['Cursor']['input']>;
|
||||
@@ -2184,6 +2315,17 @@ export type RefreshTokenPayload = {
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type RemoveExtensionStoreInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
indexUrl: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type RemoveExtensionStorePayload = {
|
||||
__typename?: 'RemoveExtensionStorePayload';
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
extensionStore?: Maybe<ExtensionStoreType>;
|
||||
};
|
||||
|
||||
export type ReorderChapterDownloadInput = {
|
||||
chapterId: Scalars['Int']['input'];
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -2425,6 +2567,7 @@ export type Settings = {
|
||||
excludeEntryWithUnreadChapters?: Maybe<Scalars['Boolean']['output']>;
|
||||
excludeNotStarted?: Maybe<Scalars['Boolean']['output']>;
|
||||
excludeUnreadChapters?: Maybe<Scalars['Boolean']['output']>;
|
||||
/** @deprecated Replaced with addExtensionStore and removeExtensionStore mutations */
|
||||
extensionRepos?: Maybe<Array<Scalars['String']['output']>>;
|
||||
flareSolverrAsResponseFallback?: Maybe<Scalars['Boolean']['output']>;
|
||||
flareSolverrEnabled?: Maybe<Scalars['Boolean']['output']>;
|
||||
@@ -2579,6 +2722,7 @@ export type SettingsType = Settings & {
|
||||
excludeEntryWithUnreadChapters: Scalars['Boolean']['output'];
|
||||
excludeNotStarted: Scalars['Boolean']['output'];
|
||||
excludeUnreadChapters: Scalars['Boolean']['output'];
|
||||
/** @deprecated Replaced with addExtensionStore and removeExtensionStore mutations */
|
||||
extensionRepos: Array<Scalars['String']['output']>;
|
||||
flareSolverrAsResponseFallback: Scalars['Boolean']['output'];
|
||||
flareSolverrEnabled: Scalars['Boolean']['output'];
|
||||
@@ -2677,8 +2821,8 @@ export type SortSelectionInput = {
|
||||
};
|
||||
|
||||
export type SourceConditionInput = {
|
||||
contentWarning?: InputMaybe<ContentWarning>;
|
||||
id?: InputMaybe<Scalars['LongString']['input']>;
|
||||
isNsfw?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
lang?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
@@ -2691,8 +2835,8 @@ export type SourceEdge = Edge & {
|
||||
|
||||
export type SourceFilterInput = {
|
||||
and?: InputMaybe<Array<SourceFilterInput>>;
|
||||
contentWarning?: InputMaybe<ContentWarningFilterInput>;
|
||||
id?: InputMaybe<LongFilterInput>;
|
||||
isNsfw?: InputMaybe<BooleanFilterInput>;
|
||||
lang?: InputMaybe<StringFilterInput>;
|
||||
name?: InputMaybe<StringFilterInput>;
|
||||
not?: InputMaybe<SourceFilterInput>;
|
||||
@@ -2743,13 +2887,17 @@ export type SourcePreferenceChangeInput = {
|
||||
|
||||
export type SourceType = {
|
||||
__typename?: 'SourceType';
|
||||
/** @deprecated , replace with homeUrl */
|
||||
baseUrl?: Maybe<Scalars['String']['output']>;
|
||||
contentWarning: ContentWarning;
|
||||
displayName: Scalars['String']['output'];
|
||||
extension: ExtensionType;
|
||||
filters: Array<Filter>;
|
||||
homeUrl?: Maybe<Scalars['String']['output']>;
|
||||
iconUrl: Scalars['String']['output'];
|
||||
id: Scalars['LongString']['output'];
|
||||
isConfigurable: Scalars['Boolean']['output'];
|
||||
/** @deprecated , replace with contentWarning */
|
||||
isNsfw: Scalars['Boolean']['output'];
|
||||
lang: Scalars['String']['output'];
|
||||
manga: MangaNodeList;
|
||||
|
||||
@@ -1261,14 +1261,16 @@ export type ExtensionListFieldsFragment = {
|
||||
pkgName: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
versionCode: number;
|
||||
versionCodeLong: string;
|
||||
versionName: string;
|
||||
iconUrl: string;
|
||||
repo: string | null;
|
||||
isNsfw: boolean;
|
||||
storeIndexUrl: string | null;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isInstalled: boolean;
|
||||
isObsolete: boolean;
|
||||
hasUpdate: boolean;
|
||||
extensionLib: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
};
|
||||
|
||||
export type GetExtensionsFetchMutationVariables = Exact<{
|
||||
@@ -1284,14 +1286,28 @@ export type GetExtensionsFetchMutation = {
|
||||
pkgName: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
versionCode: number;
|
||||
versionCodeLong: string;
|
||||
versionName: string;
|
||||
iconUrl: string;
|
||||
repo: string | null;
|
||||
isNsfw: boolean;
|
||||
storeIndexUrl: string | null;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isInstalled: boolean;
|
||||
isObsolete: boolean;
|
||||
hasUpdate: boolean;
|
||||
extensionLib: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
}>;
|
||||
extensionStores: Array<{
|
||||
__typename: 'ExtensionStoreType';
|
||||
badgeLabel: string;
|
||||
contactDiscord: string | null;
|
||||
contactWebsite: string;
|
||||
extensionListUrl: string | null;
|
||||
indexUrl: string;
|
||||
isLegacy: boolean;
|
||||
name: string;
|
||||
signingKey: string;
|
||||
extensions: { __typename: 'ExtensionNodeList'; totalCount: number };
|
||||
}>;
|
||||
} | null;
|
||||
};
|
||||
@@ -1309,14 +1325,16 @@ export type UpdateExtensionMutation = {
|
||||
pkgName: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
versionCode: number;
|
||||
versionCodeLong: string;
|
||||
versionName: string;
|
||||
iconUrl: string;
|
||||
repo: string | null;
|
||||
isNsfw: boolean;
|
||||
storeIndexUrl: string | null;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isInstalled: boolean;
|
||||
isObsolete: boolean;
|
||||
hasUpdate: boolean;
|
||||
extensionLib: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
} | null;
|
||||
} | null;
|
||||
};
|
||||
@@ -1334,14 +1352,16 @@ export type UpdateExtensionsMutation = {
|
||||
pkgName: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
versionCode: number;
|
||||
versionCodeLong: string;
|
||||
versionName: string;
|
||||
iconUrl: string;
|
||||
repo: string | null;
|
||||
isNsfw: boolean;
|
||||
storeIndexUrl: string | null;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isInstalled: boolean;
|
||||
isObsolete: boolean;
|
||||
hasUpdate: boolean;
|
||||
extensionLib: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
}>;
|
||||
} | null;
|
||||
};
|
||||
@@ -1359,14 +1379,16 @@ export type InstallExternalExtensionMutation = {
|
||||
pkgName: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
versionCode: number;
|
||||
versionCodeLong: string;
|
||||
versionName: string;
|
||||
iconUrl: string;
|
||||
repo: string | null;
|
||||
isNsfw: boolean;
|
||||
storeIndexUrl: string | null;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isInstalled: boolean;
|
||||
isObsolete: boolean;
|
||||
hasUpdate: boolean;
|
||||
extensionLib: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
@@ -1382,14 +1404,16 @@ export type GetExtensionQuery = {
|
||||
pkgName: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
versionCode: number;
|
||||
versionCodeLong: string;
|
||||
versionName: string;
|
||||
iconUrl: string;
|
||||
repo: string | null;
|
||||
isNsfw: boolean;
|
||||
storeIndexUrl: string | null;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isInstalled: boolean;
|
||||
isObsolete: boolean;
|
||||
hasUpdate: boolean;
|
||||
extensionLib: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1414,14 +1438,124 @@ export type GetExtensionsQuery = {
|
||||
pkgName: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
versionCode: number;
|
||||
versionCodeLong: string;
|
||||
versionName: string;
|
||||
iconUrl: string;
|
||||
repo: string | null;
|
||||
isNsfw: boolean;
|
||||
storeIndexUrl: string | null;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isInstalled: boolean;
|
||||
isObsolete: boolean;
|
||||
hasUpdate: boolean;
|
||||
extensionLib: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
}>;
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo';
|
||||
endCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type ExtensionStoreFieldsFragment = {
|
||||
__typename: 'ExtensionStoreType';
|
||||
badgeLabel: string;
|
||||
contactDiscord: string | null;
|
||||
contactWebsite: string;
|
||||
extensionListUrl: string | null;
|
||||
indexUrl: string;
|
||||
isLegacy: boolean;
|
||||
name: string;
|
||||
signingKey: string;
|
||||
extensions: { __typename: 'ExtensionNodeList'; totalCount: number };
|
||||
};
|
||||
|
||||
export type AddExtensionStoreMutationVariables = Exact<{
|
||||
input: Types.AddExtensionStoreInput;
|
||||
}>;
|
||||
|
||||
export type AddExtensionStoreMutation = {
|
||||
__typename: 'Mutation';
|
||||
addExtensionStore: {
|
||||
__typename: 'AddExtensionStorePayload';
|
||||
extensionStore: {
|
||||
__typename: 'ExtensionStoreType';
|
||||
badgeLabel: string;
|
||||
contactDiscord: string | null;
|
||||
contactWebsite: string;
|
||||
extensionListUrl: string | null;
|
||||
indexUrl: string;
|
||||
isLegacy: boolean;
|
||||
name: string;
|
||||
signingKey: string;
|
||||
extensions: { __typename: 'ExtensionNodeList'; totalCount: number };
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type RemoveExtensionStoreMutationVariables = Exact<{
|
||||
input: Types.RemoveExtensionStoreInput;
|
||||
}>;
|
||||
|
||||
export type RemoveExtensionStoreMutation = {
|
||||
__typename: 'Mutation';
|
||||
removeExtensionStore: {
|
||||
__typename: 'RemoveExtensionStorePayload';
|
||||
extensionStore: {
|
||||
__typename: 'ExtensionStoreType';
|
||||
badgeLabel: string;
|
||||
contactDiscord: string | null;
|
||||
contactWebsite: string;
|
||||
extensionListUrl: string | null;
|
||||
indexUrl: string;
|
||||
isLegacy: boolean;
|
||||
name: string;
|
||||
signingKey: string;
|
||||
extensions: { __typename: 'ExtensionNodeList'; totalCount: number };
|
||||
} | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type GetExtensionStoreQueryVariables = Exact<{
|
||||
indexUrl: string;
|
||||
}>;
|
||||
|
||||
export type GetExtensionStoreQuery = {
|
||||
__typename: 'Query';
|
||||
extensionStore: {
|
||||
__typename: 'ExtensionStoreType';
|
||||
badgeLabel: string;
|
||||
contactDiscord: string | null;
|
||||
contactWebsite: string;
|
||||
extensionListUrl: string | null;
|
||||
indexUrl: string;
|
||||
isLegacy: boolean;
|
||||
name: string;
|
||||
signingKey: string;
|
||||
extensions: { __typename: 'ExtensionNodeList'; totalCount: number };
|
||||
};
|
||||
};
|
||||
|
||||
export type GetExtensionStoresQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type GetExtensionStoresQuery = {
|
||||
__typename: 'Query';
|
||||
extensionStores: {
|
||||
__typename: 'ExtensionStoreNodeList';
|
||||
totalCount: number;
|
||||
nodes: Array<{
|
||||
__typename: 'ExtensionStoreType';
|
||||
badgeLabel: string;
|
||||
contactDiscord: string | null;
|
||||
contactWebsite: string;
|
||||
extensionListUrl: string | null;
|
||||
indexUrl: string;
|
||||
isLegacy: boolean;
|
||||
name: string;
|
||||
signingKey: string;
|
||||
extensions: { __typename: 'ExtensionNodeList'; totalCount: number };
|
||||
}>;
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo';
|
||||
@@ -2608,7 +2742,6 @@ export type ServerSettingsFragment = {
|
||||
excludeEntryWithUnreadChapters: boolean;
|
||||
autoDownloadNewChaptersLimit: number;
|
||||
autoDownloadIgnoreReUploads: boolean;
|
||||
extensionRepos: Array<string>;
|
||||
maxSourcesInParallel: number;
|
||||
excludeUnreadChapters: boolean;
|
||||
excludeNotStarted: boolean;
|
||||
@@ -2722,7 +2855,6 @@ export type ResetServerSettingsMutation = {
|
||||
excludeEntryWithUnreadChapters: boolean;
|
||||
autoDownloadNewChaptersLimit: number;
|
||||
autoDownloadIgnoreReUploads: boolean;
|
||||
extensionRepos: Array<string>;
|
||||
maxSourcesInParallel: number;
|
||||
excludeUnreadChapters: boolean;
|
||||
excludeNotStarted: boolean;
|
||||
@@ -2846,7 +2978,6 @@ export type UpdateServerSettingsMutation = {
|
||||
excludeEntryWithUnreadChapters: boolean;
|
||||
autoDownloadNewChaptersLimit: number;
|
||||
autoDownloadIgnoreReUploads: boolean;
|
||||
extensionRepos: Array<string>;
|
||||
maxSourcesInParallel: number;
|
||||
excludeUnreadChapters: boolean;
|
||||
excludeNotStarted: boolean;
|
||||
@@ -2966,7 +3097,6 @@ export type GetServerSettingsQuery = {
|
||||
excludeEntryWithUnreadChapters: boolean;
|
||||
autoDownloadNewChaptersLimit: number;
|
||||
autoDownloadIgnoreReUploads: boolean;
|
||||
extensionRepos: Array<string>;
|
||||
maxSourcesInParallel: number;
|
||||
excludeUnreadChapters: boolean;
|
||||
excludeNotStarted: boolean;
|
||||
@@ -3066,19 +3196,24 @@ export type SourceListFieldsFragment = {
|
||||
__typename: 'SourceType';
|
||||
lang: string;
|
||||
iconUrl: string;
|
||||
isNsfw: boolean;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isConfigurable: boolean;
|
||||
supportsLatest: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
meta: Array<{ __typename: 'SourceMetaType'; sourceId: string; key: string; value: string }>;
|
||||
extension: { __typename: 'ExtensionType'; pkgName: string; repo: string | null };
|
||||
extension: {
|
||||
__typename: 'ExtensionType';
|
||||
pkgName: string;
|
||||
storeIndexUrl: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type SourceBrowseFieldsFragment = {
|
||||
__typename: 'SourceType';
|
||||
baseUrl: string | null;
|
||||
homeUrl: string | null;
|
||||
isConfigurable: boolean;
|
||||
supportsLatest: boolean;
|
||||
id: string;
|
||||
@@ -3393,7 +3528,7 @@ export type GetSourceBrowseQuery = {
|
||||
__typename: 'Query';
|
||||
source: {
|
||||
__typename: 'SourceType';
|
||||
baseUrl: string | null;
|
||||
homeUrl: string | null;
|
||||
isConfigurable: boolean;
|
||||
supportsLatest: boolean;
|
||||
id: string;
|
||||
@@ -3563,14 +3698,19 @@ export type GetSourcesListQuery = {
|
||||
__typename: 'SourceType';
|
||||
lang: string;
|
||||
iconUrl: string;
|
||||
isNsfw: boolean;
|
||||
contentWarning: Types.ContentWarning;
|
||||
isConfigurable: boolean;
|
||||
supportsLatest: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
meta: Array<{ __typename: 'SourceMetaType'; sourceId: string; key: string; value: string }>;
|
||||
extension: { __typename: 'ExtensionType'; pkgName: string; repo: string | null };
|
||||
extension: {
|
||||
__typename: 'ExtensionType';
|
||||
pkgName: string;
|
||||
storeIndexUrl: string | null;
|
||||
extensionStore: { __typename: 'ExtensionStoreType'; indexUrl: string; name: string } | null;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -60,10 +60,7 @@ export const SERVER_SETTINGS = gql`
|
||||
connectTimeout
|
||||
}
|
||||
|
||||
# extensions
|
||||
extensionRepos
|
||||
|
||||
# requests
|
||||
# extension/source
|
||||
maxSourcesInParallel
|
||||
|
||||
# updater
|
||||
|
||||
@@ -35,7 +35,7 @@ export const SOURCE_LIST_FIELDS = gql`
|
||||
|
||||
lang
|
||||
iconUrl
|
||||
isNsfw
|
||||
contentWarning
|
||||
isConfigurable
|
||||
supportsLatest
|
||||
|
||||
@@ -45,7 +45,11 @@ export const SOURCE_LIST_FIELDS = gql`
|
||||
|
||||
extension {
|
||||
pkgName
|
||||
repo
|
||||
storeIndexUrl
|
||||
extensionStore {
|
||||
indexUrl
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -57,7 +61,7 @@ export const SOURCE_BROWSE_FIELDS = gql`
|
||||
fragment SOURCE_BROWSE_FIELDS on SourceType {
|
||||
...SOURCE_BASE_FIELDS
|
||||
|
||||
baseUrl
|
||||
homeUrl
|
||||
|
||||
isConfigurable
|
||||
supportsLatest
|
||||
|
||||
@@ -24,6 +24,8 @@ import { RestClient } from '@/lib/requests/client/RestClient.ts';
|
||||
import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts';
|
||||
import { BaseClient } from '@/lib/requests/client/BaseClient.ts';
|
||||
import type {
|
||||
AddExtensionStoreMutation,
|
||||
AddExtensionStoreMutationVariables,
|
||||
CheckForServerUpdatesQuery,
|
||||
CheckForServerUpdatesQueryVariables,
|
||||
CheckForWebuiUpdateQuery,
|
||||
@@ -74,6 +76,10 @@ import type {
|
||||
GetExtensionsFetchMutationVariables,
|
||||
GetExtensionsQuery,
|
||||
GetExtensionsQueryVariables,
|
||||
GetExtensionStoreQuery,
|
||||
GetExtensionStoreQueryVariables,
|
||||
GetExtensionStoresQuery,
|
||||
GetExtensionStoresQueryVariables,
|
||||
GetGlobalMetadatasQuery,
|
||||
GetGlobalMetadatasQueryVariables,
|
||||
GetKoSyncStatusQuery,
|
||||
@@ -108,6 +114,8 @@ import type {
|
||||
KoSyncLogoutMutationVariables,
|
||||
RefreshMangaMutation,
|
||||
RefreshMangaMutationVariables,
|
||||
RemoveExtensionStoreMutation,
|
||||
RemoveExtensionStoreMutationVariables,
|
||||
ReorderChapterDownloadMutation,
|
||||
ReorderChapterDownloadMutationVariables,
|
||||
ResetWebuiUpdateStatusMutation,
|
||||
@@ -333,6 +341,10 @@ import { GET_KO_SYNC_STATUS } from '@/lib/graphql/koreader/KoreaderSyncQuery.ts'
|
||||
import { ImageCache } from '@/lib/service-worker/ImageCache.ts';
|
||||
import { Sources } from '@/features/source/services/Sources.ts';
|
||||
import uniqBy from 'lodash/fp/uniqBy';
|
||||
import { EXTENSION_STORE_FIELDS } from '@/lib/graphql/extension/store/ExtensionStoreFragments.ts';
|
||||
import { ADD_EXTENSION_STORE, REMOVE_EXTENSION_STORE } from '@/lib/graphql/extension/store/ExtensionStoreMutation.ts';
|
||||
import { assertIsDefined } from '@/base/Asserts.ts';
|
||||
import { GET_EXTENSION_STORE, GET_EXTENSION_STORES } from '@/lib/graphql/extension/store/ExtensionStoreQuery.ts';
|
||||
|
||||
enum GQLMethod {
|
||||
QUERY = 'QUERY',
|
||||
@@ -1420,6 +1432,81 @@ export class RequestManager {
|
||||
return this.doRequest(GQLMethod.MUTATION, UPDATE_WEBUI, undefined, options);
|
||||
}
|
||||
|
||||
public useGetExtensionStore(
|
||||
indexUrl: string,
|
||||
options?: QueryHookOptions<GetExtensionStoreQuery, GetExtensionStoreQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<GetExtensionStoreQuery, GetExtensionStoreQueryVariables> {
|
||||
return this.doRequest(GQLMethod.USE_QUERY, GET_EXTENSION_STORE, { indexUrl }, options);
|
||||
}
|
||||
|
||||
public useGetExtensionStores(
|
||||
options?: QueryHookOptions<GetExtensionStoresQuery, GetExtensionStoresQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<GetExtensionStoresQuery, GetExtensionStoresQueryVariables> {
|
||||
return this.doRequest(GQLMethod.USE_QUERY, GET_EXTENSION_STORES, undefined, options);
|
||||
}
|
||||
|
||||
public useAddExtensionStore(
|
||||
options?: MutationHookOptions<AddExtensionStoreMutation, AddExtensionStoreMutationVariables>,
|
||||
): AbortableApolloUseMutationResponse<AddExtensionStoreMutation, AddExtensionStoreMutationVariables> {
|
||||
return this.doRequest(GQLMethod.USE_MUTATION, ADD_EXTENSION_STORE, undefined, {
|
||||
refetchQueries: [GET_EXTENSION_STORES],
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
public useRemoveExtensionStore(
|
||||
options?: MutationHookOptions<RemoveExtensionStoreMutation, RemoveExtensionStoreMutationVariables>,
|
||||
): AbortableApolloUseMutationResponse<RemoveExtensionStoreMutation, RemoveExtensionStoreMutationVariables> {
|
||||
const [mutate, request] = this.doRequest(GQLMethod.USE_MUTATION, REMOVE_EXTENSION_STORE, undefined, {
|
||||
refetchQueries: [GET_EXTENSION_STORES],
|
||||
...options,
|
||||
});
|
||||
|
||||
const wrappedMutate = (mutateOptions: Parameters<typeof mutate>[0]) => {
|
||||
const variables = mutateOptions?.variables;
|
||||
|
||||
assertIsDefined(variables);
|
||||
|
||||
const { indexUrl } = variables.input;
|
||||
|
||||
return mutate({
|
||||
update: (cache) => {
|
||||
cache.updateQuery<GetExtensionStoresQuery, GetExtensionStoresQueryVariables>(
|
||||
{ query: GET_EXTENSION_STORES },
|
||||
(data) => ({
|
||||
...data!,
|
||||
extensionStores: {
|
||||
...data!.extensionStores,
|
||||
nodes: data!.extensionStores.nodes.filter((store) => store.indexUrl !== indexUrl),
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
removeExtensionStore: {
|
||||
__typename: 'RemoveExtensionStorePayload',
|
||||
extensionStore: {
|
||||
__typename: 'ExtensionStoreType',
|
||||
indexUrl,
|
||||
signingKey: '',
|
||||
name: 'store',
|
||||
isLegacy: false,
|
||||
badgeLabel: '',
|
||||
contactWebsite: '',
|
||||
contactDiscord: null,
|
||||
extensionListUrl: null,
|
||||
extensions: { __typename: 'ExtensionNodeList', totalCount: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
...mutateOptions,
|
||||
});
|
||||
};
|
||||
|
||||
return [wrappedMutate, request];
|
||||
}
|
||||
|
||||
public useGetExtension(
|
||||
pkgName: string,
|
||||
options?: QueryHookOptions<GetExtensionQuery, GetExtensionQueryVariables>,
|
||||
@@ -1442,17 +1529,19 @@ export class RequestManager {
|
||||
{},
|
||||
{
|
||||
...options,
|
||||
refetchQueries: [GET_EXTENSIONS],
|
||||
update(cache, { data: mutationData }) {
|
||||
if (!mutationData?.fetchExtensions?.extensions) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.writeQuery({
|
||||
query: GET_EXTENSIONS,
|
||||
data: {
|
||||
cache.updateQuery<GetExtensionsQuery, GetExtensionsQueryVariables>(
|
||||
{ query: GET_EXTENSIONS },
|
||||
() => ({
|
||||
__typename: 'Query',
|
||||
extensions: {
|
||||
__typename: 'ExtensionNodeList',
|
||||
nodes: mutationData.fetchExtensions.extensions,
|
||||
nodes: mutationData?.fetchExtensions?.extensions ?? [],
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
hasNextPage: false,
|
||||
@@ -1460,10 +1549,10 @@ export class RequestManager {
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
},
|
||||
totalCount: mutationData.fetchExtensions.extensions.length,
|
||||
totalCount: mutationData?.fetchExtensions?.extensions.length ?? 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1482,11 +1571,14 @@ export class RequestManager {
|
||||
setUpdatedCache({});
|
||||
}, [result.loading]);
|
||||
|
||||
const cachedResult = this.cache.getResponseFor<typeof result>(
|
||||
const getCachedResult = () =>
|
||||
this.cache.getResponseFor<typeof result>(
|
||||
EXTENSION_LIST_CACHE_KEY,
|
||||
undefined,
|
||||
d(1).minutes.inWholeMilliseconds,
|
||||
);
|
||||
|
||||
const cachedResult = getCachedResult();
|
||||
const normalizedCachedResult = useMemo(
|
||||
() =>
|
||||
!cachedResult
|
||||
@@ -1510,6 +1602,17 @@ export class RequestManager {
|
||||
fragment: EXTENSION_LIST_FIELDS,
|
||||
}) ?? extension,
|
||||
),
|
||||
extensionStores: cachedResult.data.fetchExtensions.extensionStores.map(
|
||||
(store) =>
|
||||
this.graphQLClient.client.cache.readFragment<
|
||||
NonNullable<
|
||||
GetExtensionsFetchMutation['fetchExtensions']
|
||||
>['extensionStores'][0]
|
||||
>({
|
||||
id: this.graphQLClient.client.cache.identify(store),
|
||||
fragment: EXTENSION_STORE_FIELDS,
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1517,7 +1620,7 @@ export class RequestManager {
|
||||
);
|
||||
|
||||
const wrappedMutate = async (mutateOptions: Parameters<typeof mutate>[0]) => {
|
||||
if (cachedResult) {
|
||||
if (getCachedResult()) {
|
||||
return normalizedCachedResult;
|
||||
}
|
||||
|
||||
@@ -1586,7 +1689,9 @@ export class RequestManager {
|
||||
return {
|
||||
...extension,
|
||||
...installedExtension,
|
||||
hasUpdate: installedExtension?.versionCode < extension.versionCode,
|
||||
hasUpdate:
|
||||
Number(installedExtension?.versionCodeLong ?? -1) <
|
||||
Number(extension.versionCodeLong),
|
||||
};
|
||||
})
|
||||
: [
|
||||
@@ -1595,6 +1700,7 @@ export class RequestManager {
|
||||
GetExtensionsFetchMutation['fetchExtensions']
|
||||
>['extensions'][number],
|
||||
],
|
||||
extensionStores: cachedExtensions.data?.fetchExtensions!.extensionStores,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1660,6 +1766,7 @@ export class RequestManager {
|
||||
...(response.data?.updateExtension?.extension ?? []),
|
||||
};
|
||||
}) ?? [],
|
||||
extensionStores: cachedExtensions.data.fetchExtensions?.extensionStores ?? [],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1727,6 +1834,7 @@ export class RequestManager {
|
||||
) ?? []),
|
||||
};
|
||||
}) ?? [],
|
||||
extensionStores: cachedExtensions.data.fetchExtensions?.extensionStores ?? [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@ const typePolicies: TypedTypePolicies = {
|
||||
CategoryMetaType: { keyFields: ['categoryId', 'key'] },
|
||||
SourceMetaType: { keyFields: ['sourceId', 'key'] },
|
||||
ExtensionType: { keyFields: ['pkgName'] },
|
||||
ExtensionStoreType: { keyFields: ['indexUrl'] },
|
||||
AboutServerPayload: { keyFields: [] },
|
||||
AboutWebUI: { keyFields: [] },
|
||||
WebUIUpdateInfo: { keyFields: [] },
|
||||
@@ -99,6 +100,12 @@ const typePolicies: TypedTypePolicies = {
|
||||
pkgName: args?.pkgName,
|
||||
});
|
||||
},
|
||||
extensionStore(_, { args, toReference }) {
|
||||
return toReference({
|
||||
__typename: 'ExtensionStoreType',
|
||||
indexUrl: args?.indexUrl,
|
||||
});
|
||||
},
|
||||
meta(_, { args, toReference }) {
|
||||
return toReference({
|
||||
__typename: 'GlobalMetaType',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"uiVersion": "PREVIEW",
|
||||
"serverVersion": "r2217"
|
||||
"serverVersion": "r2230"
|
||||
},
|
||||
{
|
||||
"tag": "OLD-PREVIEW",
|
||||
|
||||
Reference in New Issue
Block a user