diff --git a/js/package.json b/js/package.json index 0e7d04864..7d74e8b21 100644 --- a/js/package.json +++ b/js/package.json @@ -14,6 +14,7 @@ "dependencies": { "@absinthe/socket": "^0.2.1", "@absinthe/socket-apollo-link": "^0.2.1", + "@apollo/client": "^3.3.16", "@mdi/font": "^5.0.45", "@tiptap/core": "^2.0.0-beta.41", "@tiptap/extension-blockquote": "^2.0.0-beta.6", @@ -29,14 +30,6 @@ "@tiptap/starter-kit": "^2.0.0-beta.37", "@tiptap/vue-2": "^2.0.0-beta.21", "apollo-absinthe-upload-link": "^1.5.0", - "apollo-cache": "^1.3.5", - "apollo-cache-inmemory": "^1.6.6", - "apollo-client": "^2.6.10", - "apollo-link": "^1.2.14", - "apollo-link-error": "^1.1.13", - "apollo-link-http": "^1.5.17", - "apollo-link-ws": "^1.0.19", - "apollo-utilities": "^1.3.2", "buefy": "^0.9.0", "bulma-divider": "^0.2.0", "core-js": "^3.6.4", @@ -96,7 +89,7 @@ "eslint-plugin-vue": "^7.6.0", "flush-promises": "^1.0.2", "jest-junit": "^12.0.0", - "mock-apollo-client": "^0.6", + "mock-apollo-client": "^1.1.0", "prettier": "^2.2.1", "prettier-eslint": "^12.0.0", "sass": "^1.29.0", diff --git a/js/src/apollo/user.ts b/js/src/apollo/user.ts index f063ad2ea..2eb20ed6e 100644 --- a/js/src/apollo/user.ts +++ b/js/src/apollo/user.ts @@ -1,12 +1,14 @@ +import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; +import { CURRENT_USER_CLIENT } from "@/graphql/user"; import { ICurrentUserRole } from "@/types/enums"; -import { ApolloCache } from "apollo-cache"; -import { NormalizedCacheObject } from "apollo-cache-inmemory"; -import { Resolvers } from "apollo-client/core/types"; +import { ApolloCache, NormalizedCacheObject } from "@apollo/client/cache"; +import { Resolvers } from "@apollo/client/core/types"; export default function buildCurrentUserResolver( cache: ApolloCache ): Resolvers { - cache.writeData({ + cache.writeQuery({ + query: CURRENT_USER_CLIENT, data: { currentUser: { __typename: "CurrentUser", @@ -15,6 +17,12 @@ export default function buildCurrentUserResolver( isLoggedIn: false, role: ICurrentUserRole.USER, }, + }, + }); + + cache.writeQuery({ + query: CURRENT_ACTOR_CLIENT, + data: { currentActor: { __typename: "CurrentActor", id: null, @@ -47,7 +55,7 @@ export default function buildCurrentUserResolver( }, }; - localCache.writeData({ data }); + localCache.writeQuery({ data, query: CURRENT_USER_CLIENT }); }, updateCurrentActor: ( _: any, @@ -74,7 +82,7 @@ export default function buildCurrentUserResolver( }, }; - localCache.writeData({ data }); + localCache.writeQuery({ data, query: CURRENT_ACTOR_CLIENT }); }, }, }; diff --git a/js/src/apollo/utils.ts b/js/src/apollo/utils.ts index 92cdcd585..0e3ed7f51 100644 --- a/js/src/apollo/utils.ts +++ b/js/src/apollo/utils.ts @@ -1,16 +1,76 @@ -import { - IntrospectionFragmentMatcher, - NormalizedCacheObject, -} from "apollo-cache-inmemory"; import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from "@/constants"; import { REFRESH_TOKEN } from "@/graphql/auth"; +import { IFollower } from "@/types/actor/follower.model"; +import { Paginate } from "@/types/paginate"; import { saveTokenData } from "@/utils/auth"; -import { ApolloClient } from "apollo-client"; +import { + ApolloClient, + FieldPolicy, + NormalizedCacheObject, + Reference, + TypePolicies, +} from "@apollo/client/core"; import introspectionQueryResultData from "../../fragmentTypes.json"; -export const fragmentMatcher = new IntrospectionFragmentMatcher({ - introspectionQueryResultData, -}); +type possibleTypes = { name: string }; +type schemaType = { + kind: string; + name: string; + possibleTypes: possibleTypes[]; +}; + +// eslint-disable-next-line no-underscore-dangle +const types = introspectionQueryResultData.__schema.types as schemaType[]; +export const possibleTypes = types.reduce((acc, type) => { + if (type.kind === "INTERFACE") { + acc[type.name] = type.possibleTypes.map(({ name }) => name); + } + return acc; +}, {} as Record); + +export const typePolicies: TypePolicies = { + Discussion: { + fields: { + comments: pageLimitPagination(), + }, + }, + Group: { + fields: { + organizedEvents: pageLimitPagination(["afterDatetime", "beforeDatetime"]), + }, + }, + Person: { + fields: { + organizedEvents: pageLimitPagination(), + participations: pageLimitPagination(["eventId"]), + memberships: pageLimitPagination(["group"]), + }, + }, + RootQueryType: { + fields: { + relayFollowers: paginatedLimitPagination(), + relayFollowings: paginatedLimitPagination([ + "orderBy", + "direction", + ]), + events: pageLimitPagination(), + groups: pageLimitPagination([ + "preferredUsername", + "name", + "domain", + "local", + "suspended", + ]), + persons: pageLimitPagination([ + "preferredUsername", + "name", + "domain", + "local", + "suspended", + ]), + }, + }, +}; export async function refreshAccessToken( apolloClient: ApolloClient @@ -37,3 +97,71 @@ export async function refreshAccessToken( return false; } } + +type KeyArgs = FieldPolicy["keyArgs"]; + +export function pageLimitPagination( + keyArgs: KeyArgs = false +): FieldPolicy { + console.log("pageLimitPagination"); + return { + keyArgs, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + merge(existing, incoming, { args }) { + console.log("existing", existing); + console.log("incoming", incoming); + // console.log("args", args); + if (!incoming) return existing; + if (!existing) return incoming; // existing will be empty the first time + + return doMerge(existing as Array, incoming as Array, args); + }, + }; +} + +export function paginatedLimitPagination>( + keyArgs: KeyArgs = false +): FieldPolicy> { + console.log("paginatedLimitPagination"); + return { + keyArgs, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + merge(existing, incoming, { args }) { + if (!incoming) return existing; + if (!existing) return incoming; // existing will be empty the first time + + return { + total: incoming.total, + elements: doMerge(existing.elements, incoming.elements, args), + }; + }, + }; +} + +function doMerge( + existing: Array, + incoming: Array, + args: Record | null +): Array { + const merged = existing ? existing.slice(0) : []; + let res; + if (args) { + // Assume an page of 1 if args.page omitted. + const { page = 1, limit = 10 } = args; + console.log("args, selected", { page, limit }); + for (let i = 0; i < incoming.length; ++i) { + merged[(page - 1) * limit + i] = incoming[i]; + } + res = merged; + } else { + // It's unusual (probably a mistake) for a paginated field not + // to receive any arguments, so you might prefer to throw an + // exception here, instead of recovering by appending incoming + // onto the existing array. + res = [...merged, ...incoming]; + } + console.log("doMerge returns", res); + return res; +} diff --git a/js/src/components/Admin/Followers.vue b/js/src/components/Admin/Followers.vue index f8472c042..80c7b43df 100644 --- a/js/src/components/Admin/Followers.vue +++ b/js/src/components/Admin/Followers.vue @@ -10,8 +10,13 @@ :show-detail-icon="false" paginated backend-pagination + :current-page.sync="page" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="relayFollowers.total" - :per-page="perPage" + :per-page="FOLLOWERS_PER_PAGE" @page-change="onFollowersPageChange" checkable checkbox-position="left" @@ -123,14 +128,33 @@ diff --git a/js/src/components/Admin/Followings.vue b/js/src/components/Admin/Followings.vue index 18a9fbb9f..e257c8fa2 100644 --- a/js/src/components/Admin/Followings.vue +++ b/js/src/components/Admin/Followings.vue @@ -32,8 +32,13 @@ :show-detail-icon="false" paginated backend-pagination + :current-page.sync="page" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="relayFollowings.total" - :per-page="perPage" + :per-page="FOLLOWINGS_PER_PAGE" @page-change="onFollowingsPageChange" checkable checkbox-position="left" @@ -127,7 +132,7 @@ - {{ + {{ $t("You don't follow any instances yet.") }} @@ -139,8 +144,31 @@ import { formatDistanceToNow } from "date-fns"; import { ADD_RELAY, REMOVE_RELAY } from "../../graphql/admin"; import { IFollower } from "../../types/actor/follower.model"; import RelayMixin from "../../mixins/relay"; +import { RELAY_FOLLOWINGS } from "@/graphql/admin"; +import { Paginate } from "@/types/paginate"; +import RouteName from "@/router/name"; +import { + ApolloCache, + FetchResult, + InMemoryCache, + Reference, +} from "@apollo/client/core"; +import gql from "graphql-tag"; + +const FOLLOWINGS_PER_PAGE = 10; @Component({ + apollo: { + relayFollowings: { + query: RELAY_FOLLOWINGS, + variables() { + return { + page: this.page, + limit: FOLLOWINGS_PER_PAGE, + }; + }, + }, + }, metaInfo() { return { title: this.$t("Followings") as string, @@ -155,16 +183,78 @@ export default class Followings extends Mixins(RelayMixin) { formatDistanceToNow = formatDistanceToNow; + relayFollowings: Paginate = { elements: [], total: 0 }; + + FOLLOWINGS_PER_PAGE = FOLLOWINGS_PER_PAGE; + + checkedRows: IFollower[] = []; + + get page(): number { + return parseInt((this.$route.query.page as string) || "1", 10); + } + + set page(page: number) { + this.pushRouter(RouteName.RELAY_FOLLOWINGS, { + page: page.toString(), + }); + } + + async onFollowingsPageChange(page: number): Promise { + this.page = page; + try { + await this.$apollo.queries.relayFollowings.fetchMore({ + variables: { + page: this.page, + limit: FOLLOWINGS_PER_PAGE, + }, + }); + } catch (err) { + console.error(err); + } + } + async followRelay(e: Event): Promise { e.preventDefault(); try { - await this.$apollo.mutate({ + await this.$apollo.mutate<{ relayFollowings: Paginate }>({ mutation: ADD_RELAY, variables: { address: this.newRelayAddress.trim(), // trim to fix copy and paste domain name spaces and tabs }, + update(cache: ApolloCache, { data }: FetchResult) { + cache.modify({ + fields: { + relayFollowings( + existingFollowings = { elements: [], total: 0 }, + { readField } + ) { + const newFollowingRef = cache.writeFragment({ + id: `${data?.addRelay.__typename}:${data?.addRelay.id}`, + data: data?.addRelay, + fragment: gql` + fragment NewFollowing on Follower { + id + } + `, + }); + if ( + existingFollowings.elements.some( + (ref: Reference) => + readField("id", ref) === data?.addRelay.id + ) + ) { + return existingFollowings; + } + return { + total: existingFollowings.total + 1, + elements: [newFollowingRef, ...existingFollowings.elements], + }; + }, + }, + broadcast: false, + }); + }, }); - await this.$apollo.queries.relayFollowings.refetch(); this.newRelayAddress = ""; } catch (err) { Snackbar.open({ @@ -175,21 +265,35 @@ export default class Followings extends Mixins(RelayMixin) { } } - async removeRelays(): Promise { - await this.checkedRows.forEach((row: IFollower) => { - this.removeRelay( - `${row.targetActor.preferredUsername}@${row.targetActor.domain}` - ); + removeRelays(): void { + this.checkedRows.forEach((row: IFollower) => { + this.removeRelay(row); }); } - async removeRelay(address: string): Promise { + async removeRelay(follower: IFollower): Promise { + const address = `${follower.targetActor.preferredUsername}@${follower.targetActor.domain}`; try { await this.$apollo.mutate({ mutation: REMOVE_RELAY, variables: { address, }, + update(cache: ApolloCache) { + cache.modify({ + fields: { + relayFollowings(existingFollowingRefs, { readField }) { + return { + total: existingFollowingRefs.total - 1, + elements: existingFollowingRefs.elements.filter( + (followingRef: Reference) => + follower.id !== readField("id", followingRef) + ), + }; + }, + }, + }); + }, }); await this.$apollo.queries.relayFollowings.refetch(); this.checkedRows = []; diff --git a/js/src/components/Comment/Comment.vue b/js/src/components/Comment/Comment.vue index 8d5810b8f..090d398d1 100644 --- a/js/src/components/Comment/Comment.vue +++ b/js/src/components/Comment/Comment.vue @@ -279,7 +279,7 @@ export default class Comment extends Vue { const { event } = eventData; const { comments } = event; const parentCommentIndex = comments.findIndex( - (oldComment) => oldComment.id === parentId + (oldComment: IComment) => oldComment.id === parentId ); const parentComment = comments[parentCommentIndex]; if (!parentComment) return; diff --git a/js/src/components/Comment/CommentTree.vue b/js/src/components/Comment/CommentTree.vue index 3f3c64e54..3d2f23abd 100644 --- a/js/src/components/Comment/CommentTree.vue +++ b/js/src/components/Comment/CommentTree.vue @@ -88,6 +88,7 @@ import { import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor"; import { IPerson } from "../../types/actor"; import { IEvent } from "../../types/event.model"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component({ apollo: { @@ -157,7 +158,7 @@ export default class CommentTree extends Vue { ? comment.inReplyToComment.id : null, }, - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (data == null) return; const newComment = data.createComment; @@ -249,7 +250,7 @@ export default class CommentTree extends Vue { variables: { commentId: comment.id, }, - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (data == null) return; const deletedCommentId = data.deleteComment.id; diff --git a/js/src/components/Editor/Image.ts b/js/src/components/Editor/Image.ts index 653fd8c9c..a67b82756 100644 --- a/js/src/components/Editor/Image.ts +++ b/js/src/components/Editor/Image.ts @@ -1,10 +1,10 @@ import { UPLOAD_MEDIA } from "@/graphql/upload"; import apolloProvider from "@/vue-apollo"; -import ApolloClient from "apollo-client"; -import { NormalizedCacheObject } from "apollo-cache-inmemory"; +import { ApolloClient } from "@apollo/client/core/ApolloClient"; import { Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import Image from "@tiptap/extension-image"; +import { NormalizedCacheObject } from "@apollo/client/cache"; /* eslint-disable class-methods-use-this */ diff --git a/js/src/components/Editor/Mention.ts b/js/src/components/Editor/Mention.ts index dbb6f809e..0842b1764 100644 --- a/js/src/components/Editor/Mention.ts +++ b/js/src/components/Editor/Mention.ts @@ -2,11 +2,11 @@ import { SEARCH_PERSONS } from "@/graphql/search"; import { VueRenderer } from "@tiptap/vue-2"; import tippy from "tippy.js"; import MentionList from "./MentionList.vue"; -import ApolloClient from "apollo-client"; -import { NormalizedCacheObject } from "apollo-cache-inmemory"; +import { ApolloClient } from "@apollo/client/core/ApolloClient"; import apolloProvider from "@/vue-apollo"; import { IPerson } from "@/types/actor"; import pDebounce from "p-debounce"; +import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; const client = apolloProvider.defaultClient as ApolloClient; diff --git a/js/src/components/NavBar.vue b/js/src/components/NavBar.vue index 1256b22c1..c2be785cf 100644 --- a/js/src/components/NavBar.vue +++ b/js/src/components/NavBar.vue @@ -234,7 +234,9 @@ export default class NavBar extends Vue { query: IDENTITIES, }); if (data) { - this.identities = data.identities.map((identity) => new Person(identity)); + this.identities = data.identities.map( + (identity: IPerson) => new Person(identity) + ); // If we don't have any identities, the user has validated their account, // is logging for the first time but didn't create an identity somehow diff --git a/js/src/components/Participation/ParticipationWithoutAccount.vue b/js/src/components/Participation/ParticipationWithoutAccount.vue index 802b11e7a..1b86c7a4e 100644 --- a/js/src/components/Participation/ParticipationWithoutAccount.vue +++ b/js/src/components/Participation/ParticipationWithoutAccount.vue @@ -134,6 +134,7 @@ import { addLocalUnconfirmedAnonymousParticipation } from "@/services/AnonymousP import { EventJoinOptions, ParticipantRole } from "@/types/enums"; import RouteName from "@/router/name"; import { IParticipant } from "../../types/participant.model"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component({ apollo: { @@ -195,7 +196,10 @@ export default class ParticipationWithoutAccount extends Vue { message: this.anonymousParticipation.message, locale: this.$i18n.locale, }, - update: (store, { data: updateData }) => { + update: ( + store: ApolloCache, + { data: updateData }: FetchResult + ) => { if (updateData == null) { console.error( "Cannot update event participant cache, because of data null value." diff --git a/js/src/graphql/actor.ts b/js/src/graphql/actor.ts index 68f08d5f2..e23c5d073 100644 --- a/js/src/graphql/actor.ts +++ b/js/src/graphql/actor.ts @@ -37,12 +37,14 @@ export const FETCH_PERSON = gql` `; export const GET_PERSON = gql` - query ( + query Person( $actorId: ID! $organizedEventsPage: Int $organizedEventsLimit: Int $participationPage: Int $participationLimit: Int + $membershipsPage: Int + $membershipsLimit: Int ) { person(id: $actorId) { id @@ -89,6 +91,24 @@ export const GET_PERSON = gql` } } } + memberships(page: $membershipsPage, limit: $membershipsLimit) { + total + elements { + id + role + insertedAt + parent { + id + preferredUsername + name + domain + avatar { + id + url + } + } + } + } user { id email diff --git a/js/src/graphql/admin.ts b/js/src/graphql/admin.ts index f6aa1978a..5dac4ea00 100644 --- a/js/src/graphql/admin.ts +++ b/js/src/graphql/admin.ts @@ -37,6 +37,7 @@ export const DASHBOARD = gql` export const RELAY_FRAGMENT = gql` fragment relayFragment on Follower { + id actor { id preferredUsername diff --git a/js/src/graphql/discussion.ts b/js/src/graphql/discussion.ts index 1d96a0d01..ef66d22c6 100644 --- a/js/src/graphql/discussion.ts +++ b/js/src/graphql/discussion.ts @@ -67,6 +67,17 @@ export const DISCUSSION_FIELDS_FRAGMENT = gql` text insertedAt updatedAt + deletedAt + publishedAt + actor { + id + domain + name + preferredUsername + avatar { + url + } + } } actor { id @@ -104,8 +115,7 @@ export const REPLY_TO_DISCUSSION = gql` export const GET_DISCUSSION = gql` query getDiscussion($slug: String!, $page: Int, $limit: Int) { discussion(slug: $slug) { - comments(page: $page, limit: $limit) - @connection(key: "discussion-comments", filter: ["slug"]) { + comments(page: $page, limit: $limit) { total elements { id @@ -158,6 +168,8 @@ export const DISCUSSION_COMMENT_CHANGED = gql` text updatedAt insertedAt + deletedAt + publishedAt actor { id preferredUsername diff --git a/js/src/graphql/group.ts b/js/src/graphql/group.ts index bb5f23dca..08e679300 100644 --- a/js/src/graphql/group.ts +++ b/js/src/graphql/group.ts @@ -125,13 +125,13 @@ export const GROUP_FIELDS_FRAGMENTS = gql` ...DiscussionBasicFields } } - posts { + posts(page: $postsPage, limit: $postsLimit) { total elements { ...PostBasicFields } } - members { + members(page: $membersPage, limit: $membersLimit) { elements { id role @@ -212,6 +212,10 @@ export const GET_GROUP = gql` $beforeDateTime: DateTime $organisedEventsPage: Int $organisedEventslimit: Int + $postsPage: Int + $postsLimit: Int + $membersPage: Int + $membersLimit: Int ) { getGroup(id: $id) { mediaSize diff --git a/js/src/graphql/report.ts b/js/src/graphql/report.ts index 8b0e8ff0d..bc842070c 100644 --- a/js/src/graphql/report.ts +++ b/js/src/graphql/report.ts @@ -1,41 +1,44 @@ import gql from "graphql-tag"; export const REPORTS = gql` - query Reports($status: ReportStatus) { - reports(status: $status) { - id - reported { + query Reports($status: ReportStatus, $page: Int, $limit: Int) { + reports(status: $status, page: $page, limit: $limit) { + total + elements { id - preferredUsername - domain - name - avatar { + reported { id - url + preferredUsername + domain + name + avatar { + id + url + } } - } - reporter { - id - preferredUsername - name - avatar { + reporter { id - url + preferredUsername + name + avatar { + id + url + } + domain + type } - domain - type - } - event { - id - uuid - title - picture { + event { id - url + uuid + title + picture { + id + url + } } + status + content } - status - content } } `; diff --git a/js/src/mixins/event.ts b/js/src/mixins/event.ts index df3f65bf1..0608ae0d6 100644 --- a/js/src/mixins/event.ts +++ b/js/src/mixins/event.ts @@ -11,6 +11,7 @@ import { LEAVE_EVENT, } from "../graphql/event"; import { IPerson } from "../types/actor"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component export default class EventMixin extends mixins(Vue) { @@ -30,7 +31,7 @@ export default class EventMixin extends mixins(Vue) { actorId, token, }, - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (data == null) return; let participation; diff --git a/js/src/mixins/relay.ts b/js/src/mixins/relay.ts index 0db4033a6..0289c1392 100644 --- a/js/src/mixins/relay.ts +++ b/js/src/mixins/relay.ts @@ -1,106 +1,30 @@ -import { Component, Vue, Ref } from "vue-property-decorator"; import { IActor } from "@/types/actor"; -import { IFollower } from "@/types/actor/follower.model"; -import { RELAY_FOLLOWERS, RELAY_FOLLOWINGS } from "@/graphql/admin"; -import { Paginate } from "@/types/paginate"; import { ActorType } from "@/types/enums"; +import { Component, Vue, Ref } from "vue-property-decorator"; +import VueRouter from "vue-router"; +const { isNavigationFailure, NavigationFailureType } = VueRouter; -@Component({ - apollo: { - relayFollowings: { - query: RELAY_FOLLOWINGS, - fetchPolicy: "cache-and-network", - variables() { - return { - page: this.followingsPage, - limit: this.perPage, - }; - }, - }, - relayFollowers: { - query: RELAY_FOLLOWERS, - fetchPolicy: "cache-and-network", - variables() { - return { - page: this.followersPage, - limit: this.perPage, - }; - }, - }, - }, -}) +@Component export default class RelayMixin extends Vue { @Ref("table") readonly table!: any; - relayFollowers: Paginate = { elements: [], total: 0 }; - - relayFollowings: Paginate = { elements: [], total: 0 }; - - checkedRows: IFollower[] = []; - - followingsPage = 1; - - followersPage = 1; - - perPage = 10; - toggle(row: Record): void { this.table.toggleDetails(row); } - async onFollowingsPageChange(page: number): Promise { - this.followingsPage = page; + protected async pushRouter( + routeName: string, + args: Record + ): Promise { try { - await this.$apollo.queries.relayFollowings.fetchMore({ - variables: { - page: this.followingsPage, - limit: this.perPage, - }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newFollowings = fetchMoreResult.relayFollowings.elements; - return { - relayFollowings: { - __typename: previousResult.relayFollowings.__typename, - total: previousResult.relayFollowings.total, - elements: [ - ...previousResult.relayFollowings.elements, - ...newFollowings, - ], - }, - }; - }, + await this.$router.push({ + name: routeName, + query: { ...this.$route.query, ...args }, }); - } catch (err) { - console.error(err); - } - } - - async onFollowersPageChange(page: number): Promise { - this.followersPage = page; - try { - await this.$apollo.queries.relayFollowers.fetchMore({ - variables: { - page: this.followersPage, - limit: this.perPage, - }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newFollowers = fetchMoreResult.relayFollowers.elements; - return { - relayFollowers: { - __typename: previousResult.relayFollowers.__typename, - total: previousResult.relayFollowers.total, - elements: [ - ...previousResult.relayFollowers.elements, - ...newFollowers, - ], - }, - }; - }, - }); - } catch (err) { - console.error(err); + } catch (e) { + if (isNavigationFailure(e, NavigationFailureType.redirected)) { + throw Error(e.toString()); + } } } diff --git a/js/src/services/push-subscription.ts b/js/src/services/push-subscription.ts index 841ad5df6..820a4d25c 100644 --- a/js/src/services/push-subscription.ts +++ b/js/src/services/push-subscription.ts @@ -1,6 +1,6 @@ import apolloProvider from "@/vue-apollo"; -import ApolloClient from "apollo-client"; -import { NormalizedCacheObject } from "apollo-cache-inmemory"; +import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; +import { ApolloClient } from "@apollo/client/core/ApolloClient"; import { WEB_PUSH } from "../graphql/config"; import { IConfig } from "../types/config.model"; diff --git a/js/src/types/apollo.ts b/js/src/types/apollo.ts index 76db37489..da8b84b34 100644 --- a/js/src/types/apollo.ts +++ b/js/src/types/apollo.ts @@ -1,4 +1,5 @@ -import { ServerError, ServerParseError } from "apollo-link-http-common"; +import { ServerParseError } from "@apollo/client/link/http"; +import { ServerError } from "@apollo/client/link/utils"; function isServerError( err: Error | ServerError | ServerParseError | undefined diff --git a/js/src/utils/auth.ts b/js/src/utils/auth.ts index 52354432a..b59efefcd 100644 --- a/js/src/utils/auth.ts +++ b/js/src/utils/auth.ts @@ -9,11 +9,11 @@ import { } from "@/constants"; import { ILogin, IToken } from "@/types/login.model"; import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user"; -import ApolloClient from "apollo-client"; +import { ApolloClient } from "@apollo/client/core/ApolloClient"; import { IPerson } from "@/types/actor"; import { IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; -import { NormalizedCacheObject } from "apollo-cache-inmemory"; import { ICurrentUserRole } from "@/types/enums"; +import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; export function saveTokenData(obj: IToken): void { localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken); diff --git a/js/src/views/Account/Register.vue b/js/src/views/Account/Register.vue index 381b0c894..0bb1ce8e4 100644 --- a/js/src/views/Account/Register.vue +++ b/js/src/views/Account/Register.vue @@ -128,6 +128,7 @@ import { MOBILIZON_INSTANCE_HOST } from "../../api/_entrypoint"; import RouteName from "../../router/name"; import { changeIdentity } from "../../utils/auth"; import identityEditionMixin from "../../mixins/identityEdition"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component({ apollo: { @@ -164,7 +165,10 @@ export default class Register extends mixins(identityEditionMixin) { const { data } = await this.$apollo.mutate<{ registerPerson: IPerson }>({ mutation: REGISTER_PERSON, variables: { email: this.email, ...this.identity }, - update: (store, { data: localData }) => { + update: ( + store: ApolloCache, + { data: localData }: FetchResult + ) => { if (this.userAlreadyActivated) { const identitiesData = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES, diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue index 4f1b08c20..367b0226e 100644 --- a/js/src/views/Account/children/EditIdentity.vue +++ b/js/src/views/Account/children/EditIdentity.vue @@ -232,9 +232,10 @@ import { DELETE_FEED_TOKEN, } from "@/graphql/feed_tokens"; import { IFeedToken } from "@/types/feedtoken.model"; -import { ServerParseError } from "apollo-link-http-common"; import { IConfig } from "@/types/config.model"; import { CONFIG } from "@/graphql/config"; +import { ServerParseError } from "@apollo/client/link/http"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component({ components: { @@ -324,7 +325,7 @@ export default class EditIdentity extends mixins(identityEditionMixin) { variables: { id: this.identity.id, }, - update: (store) => { + update: (store: ApolloCache) => { const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES, }); @@ -368,18 +369,21 @@ export default class EditIdentity extends mixins(identityEditionMixin) { await this.$apollo.mutate({ mutation: UPDATE_PERSON, variables, - update: (store, { data: { updatePerson } }) => { + update: ( + store: ApolloCache, + { data: updateData }: FetchResult + ) => { const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES, }); - if (data) { + if (data && updateData?.updatePerson) { const index = data.identities.findIndex( (i) => i.id === this.identity.id ); - this.$set(data.identities, index, updatePerson); - this.maybeUpdateCurrentActorCache(updatePerson); + this.$set(data.identities, index, updateData?.updatePerson); + this.maybeUpdateCurrentActorCache(updateData?.updatePerson); store.writeQuery({ query: IDENTITIES, data }); } @@ -403,13 +407,16 @@ export default class EditIdentity extends mixins(identityEditionMixin) { await this.$apollo.mutate({ mutation: CREATE_PERSON, variables, - update: (store, { data: { createPerson } }) => { + update: ( + store: ApolloCache, + { data: updateData }: FetchResult + ) => { const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES, }); - if (data) { - data.identities.push(createPerson); + if (data && updateData?.createPerson) { + data.identities.push(updateData?.createPerson); store.writeQuery({ query: IDENTITIES, data }); } diff --git a/js/src/views/Admin/AdminGroupProfile.vue b/js/src/views/Admin/AdminGroupProfile.vue index 33d22c198..5237382c8 100644 --- a/js/src/views/Admin/AdminGroupProfile.vue +++ b/js/src/views/Admin/AdminGroupProfile.vue @@ -88,6 +88,11 @@ :loading="$apollo.queries.group.loading" paginated backend-pagination + :current-page.sync="membersPage" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="group.members.total" :per-page="EVENTS_PER_PAGE" @page-change="onMembersPageChange" @@ -119,8 +124,11 @@ {{ props.row.actor.name }}@{{ usernameWithDomain(props.row.actor) }}
- @{{ usernameWithDomain(props.row.actor) }} @@ -170,11 +178,9 @@ @@ -191,6 +197,11 @@ :loading="$apollo.queries.group.loading" paginated backend-pagination + :current-page.sync="organizedEventsPage" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="group.organizedEvents.total" :per-page="EVENTS_PER_PAGE" @page-change="onOrganizedEventsPageChange" @@ -213,11 +224,9 @@ {{ props.row.beginsOn | formatDateTimeString }} @@ -234,8 +243,13 @@ :loading="$apollo.queries.group.loading" paginated backend-pagination + :current-page.sync="postsPage" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="group.posts.total" - :per-page="EVENTS_PER_PAGE" + :per-page="POSTS_PER_PAGE" @page-change="onPostsPageChange" > @@ -256,11 +270,9 @@ {{ props.row.publishAt | formatDateTimeString }} @@ -276,8 +288,14 @@ import { IGroup } from "../../types/actor"; import { usernameWithDomain, IActor } from "../../types/actor/actor.model"; import RouteName from "../../router/name"; import ActorCard from "../../components/Account/ActorCard.vue"; +import EmptyContent from "../../components/Utils/EmptyContent.vue"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; +import VueRouter from "vue-router"; +const { isNavigationFailure, NavigationFailureType } = VueRouter; -const EVENTS_PER_PAGE = 10; +const EVENTS_PER_PAGE = 3; +const POSTS_PER_PAGE = 1; +const MEMBERS_PER_PAGE = 3; @Component({ apollo: { @@ -287,8 +305,10 @@ const EVENTS_PER_PAGE = 10; variables() { return { id: this.id, - organizedEventsPage: 1, + organizedEventsPage: this.organizedEventsPage, organizedEventsLimit: EVENTS_PER_PAGE, + postsPage: this.postsPage, + postsLimit: POSTS_PER_PAGE, }; }, skip() { @@ -299,6 +319,7 @@ const EVENTS_PER_PAGE = 10; }, components: { ActorCard, + EmptyContent, }, }) export default class AdminGroupProfile extends Vue { @@ -312,14 +333,39 @@ export default class AdminGroupProfile extends Vue { EVENTS_PER_PAGE = EVENTS_PER_PAGE; - organizedEventsPage = 1; + POSTS_PER_PAGE = POSTS_PER_PAGE; - membersPage = 1; - - postsPage = 1; + MEMBERS_PER_PAGE = MEMBERS_PER_PAGE; MemberRole = MemberRole; + get organizedEventsPage(): number { + return parseInt( + (this.$route.query.organizedEventsPage as string) || "1", + 10 + ); + } + + set organizedEventsPage(page: number) { + this.pushRouter({ organizedEventsPage: page.toString() }); + } + + get membersPage(): number { + return parseInt((this.$route.query.membersPage as string) || "1", 10); + } + + set membersPage(page: number) { + this.pushRouter({ membersPage: page.toString() }); + } + + get postsPage(): number { + return parseInt((this.$route.query.postsPage as string) || "1", 10); + } + + set postsPage(page: number) { + this.pushRouter({ postsPage: page.toString() }); + } + get metadata(): Array> { if (!this.group) return []; const res: Record[] = [ @@ -367,64 +413,85 @@ export default class AdminGroupProfile extends Vue { } async suspendProfile(): Promise { - this.$apollo.mutate<{ suspendProfile: { id: string } }>({ - mutation: SUSPEND_PROFILE, - variables: { - id: this.id, - }, - update: (store, { data }) => { - if (data == null) return; - const profileId = this.id; + try { + await this.$apollo.mutate<{ suspendProfile: { id: string } }>({ + mutation: SUSPEND_PROFILE, + variables: { + id: this.id, + }, + update: (store: ApolloCache, { data }: FetchResult) => { + if (data == null) return; + const profileId = this.id; - const profileData = store.readQuery<{ getGroup: IGroup }>({ - query: GET_GROUP, - variables: { - id: profileId, - }, - }); + const profileData = store.readQuery<{ getGroup: IGroup }>({ + query: GET_GROUP, + variables: { + id: profileId, + }, + }); - if (!profileData) return; - const { getGroup: group } = profileData; - group.suspended = true; - group.avatar = null; - group.name = ""; - group.summary = ""; - store.writeQuery({ - query: GET_GROUP, - variables: { - id: profileId, - }, - data: { getGroup: group }, - }); - }, - }); + if (!profileData) return; + store.writeQuery({ + query: GET_GROUP, + variables: { + id: profileId, + }, + data: { + getGroup: { + ...profileData.getGroup, + suspended: true, + avatar: null, + name: "", + summary: "", + }, + }, + }); + }, + }); + } catch (e) { + console.error(e); + this.$notifier.error(this.$t("Error while suspending group") as string); + } } async unsuspendProfile(): Promise { - const profileID = this.id; - await this.$apollo.mutate<{ unsuspendProfile: { id: string } }>({ - mutation: UNSUSPEND_PROFILE, - variables: { - id: this.id, - }, - refetchQueries: [ - { - query: GET_GROUP, - variables: { - id: profileID, - }, + try { + const profileID = this.id; + await this.$apollo.mutate<{ unsuspendProfile: { id: string } }>({ + mutation: UNSUSPEND_PROFILE, + variables: { + id: this.id, }, - ], - }); + refetchQueries: [ + { + query: GET_GROUP, + variables: { + id: profileID, + }, + }, + ], + }); + } catch (e) { + console.error(e); + this.$notifier.error(this.$t("Error while suspending group") as string); + } } async refreshProfile(): Promise { - this.$apollo.mutate<{ refreshProfile: IActor }>({ - mutation: REFRESH_PROFILE, - variables: { - actorId: this.id, - }, - }); + try { + this.$apollo.mutate<{ refreshProfile: IActor }>({ + mutation: REFRESH_PROFILE, + variables: { + actorId: this.id, + }, + }); + this.$notifier.success( + this.$t("Triggered profile refreshment") as string + ); + } catch (e) { + console.error(e); + this.$notifier.error(this.$t("Error while suspending group") as string); + } } async onOrganizedEventsPageChange(page: number): Promise { @@ -435,24 +502,6 @@ export default class AdminGroupProfile extends Vue { organizedEventsPage: this.organizedEventsPage, organizedEventsLimit: EVENTS_PER_PAGE, }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newOrganizedEvents = - fetchMoreResult.group.organizedEvents.elements; - return { - group: { - ...previousResult.group, - organizedEvents: { - __typename: previousResult.group.organizedEvents.__typename, - total: previousResult.group.organizedEvents.total, - elements: [ - ...previousResult.group.organizedEvents.elements, - ...newOrganizedEvents, - ], - }, - }, - }; - }, }); } @@ -464,23 +513,6 @@ export default class AdminGroupProfile extends Vue { memberPage: this.membersPage, memberLimit: EVENTS_PER_PAGE, }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newMembers = fetchMoreResult.group.members.elements; - return { - group: { - ...previousResult.group, - members: { - __typename: previousResult.group.members.__typename, - total: previousResult.group.members.total, - elements: [ - ...previousResult.group.members.elements, - ...newMembers, - ], - }, - }, - }; - }, }); } @@ -490,24 +522,23 @@ export default class AdminGroupProfile extends Vue { variables: { actorId: this.id, postsPage: this.postsPage, - postLimit: EVENTS_PER_PAGE, - }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newPosts = fetchMoreResult.group.posts.elements; - return { - group: { - ...previousResult.group, - posts: { - __typename: previousResult.group.posts.__typename, - total: previousResult.group.posts.total, - elements: [...previousResult.group.posts.elements, ...newPosts], - }, - }, - }; + postLimit: POSTS_PER_PAGE, }, }); } + + private async pushRouter(args: Record): Promise { + try { + await this.$router.push({ + name: RouteName.ADMIN_GROUP_PROFILE, + query: { ...this.$route.query, ...args }, + }); + } catch (e) { + if (isNavigationFailure(e, NavigationFailureType.redirected)) { + throw Error(e.toString()); + } + } + } } diff --git a/js/src/views/Admin/AdminProfile.vue b/js/src/views/Admin/AdminProfile.vue index 68b0221b7..84e909ffb 100644 --- a/js/src/views/Admin/AdminProfile.vue +++ b/js/src/views/Admin/AdminProfile.vue @@ -74,6 +74,11 @@ :loading="$apollo.queries.person.loading" paginated backend-pagination + :current-page.sync="organizedEventsPage" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="person.organizedEvents.total" :per-page="EVENTS_PER_PAGE" @page-change="onOrganizedEventsPageChange" @@ -93,11 +98,9 @@ @@ -115,9 +118,14 @@ (participation) => participation.event ) " - :loading="$apollo.queries.person.loading" + :loading="$apollo.loading" paginated backend-pagination + :current-page.sync="participationsPage" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="person.participations.total" :per-page="EVENTS_PER_PAGE" @page-change="onParticipationsPageChange" @@ -137,11 +145,115 @@ + + +
+

+ {{ + $tc("{number} memberships", person.memberships.total, { + number: person.memberships.total, + }) + }} +

+ + +
+
+ +
+ +
+
+ {{ + props.row.parent.name + }}
+ @{{ usernameWithDomain(props.row.parent) }} +
-
+ + + + + {{ $t("Administrator") }} + + + {{ $t("Moderator") }} + + + {{ $t("Member") }} + + + {{ $t("Not approved") }} + + + {{ $t("Rejected") }} + + + {{ $t("Invited") }} + + + + + {{ props.row.insertedAt | formatDateString }}
{{ + props.row.insertedAt | formatTimeString + }} +
+
+ @@ -159,8 +271,15 @@ import { IPerson } from "../../types/actor"; import { usernameWithDomain } from "../../types/actor/actor.model"; import RouteName from "../../router/name"; import ActorCard from "../../components/Account/ActorCard.vue"; +import EmptyContent from "../../components/Utils/EmptyContent.vue"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; +import VueRouter from "vue-router"; +import { MemberRole } from "@/types/enums"; +const { isNavigationFailure, NavigationFailureType } = VueRouter; const EVENTS_PER_PAGE = 10; +const PARTICIPATIONS_PER_PAGE = 10; +const MEMBERSHIPS_PER_PAGE = 10; @Component({ apollo: { @@ -170,8 +289,12 @@ const EVENTS_PER_PAGE = 10; variables() { return { actorId: this.id, - organizedEventsPage: 1, + organizedEventsPage: this.organizedEventsPage, organizedEventsLimit: EVENTS_PER_PAGE, + participationsPage: this.participationsPage, + participationLimit: PARTICIPATIONS_PER_PAGE, + membershipsPage: this.membershipsPage, + membershipsLimit: MEMBERSHIPS_PER_PAGE, }; }, skip() { @@ -181,6 +304,7 @@ const EVENTS_PER_PAGE = 10; }, components: { ActorCard, + EmptyContent, }, }) export default class AdminProfile extends Vue { @@ -194,9 +318,41 @@ export default class AdminProfile extends Vue { EVENTS_PER_PAGE = EVENTS_PER_PAGE; - organizedEventsPage = 1; + PARTICIPATIONS_PER_PAGE = PARTICIPATIONS_PER_PAGE; - participationsPage = 1; + MEMBERSHIPS_PER_PAGE = MEMBERSHIPS_PER_PAGE; + + MemberRole = MemberRole; + + get organizedEventsPage(): number { + return parseInt( + (this.$route.query.organizedEventsPage as string) || "1", + 10 + ); + } + + set organizedEventsPage(page: number) { + this.pushRouter({ organizedEventsPage: page.toString() }); + } + + get participationsPage(): number { + return parseInt( + (this.$route.query.participationsPage as string) || "1", + 10 + ); + } + + set participationsPage(page: number) { + this.pushRouter({ participationsPage: page.toString() }); + } + + get membershipsPage(): number { + return parseInt((this.$route.query.membershipsPage as string) || "1", 10); + } + + set membershipsPage(page: number) { + this.pushRouter({ membershipsPage: page.toString() }); + } get metadata(): Array> { if (!this.person) return []; @@ -233,7 +389,7 @@ export default class AdminProfile extends Vue { variables: { id: this.id, }, - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (data == null) return; const profileId = this.id; @@ -248,16 +404,20 @@ export default class AdminProfile extends Vue { if (!profileData) return; const { person } = profileData; - person.suspended = true; - person.avatar = null; - person.name = ""; - person.summary = ""; store.writeQuery({ query: GET_PERSON, variables: { actorId: profileId, }, - data: { person }, + data: { + person: { + ...person, + suspended: true, + avatar: null, + name: "", + summary: "", + }, + }, }); }, }); @@ -283,63 +443,48 @@ export default class AdminProfile extends Vue { }); } - async onOrganizedEventsPageChange(page: number): Promise { - this.organizedEventsPage = page; + async onOrganizedEventsPageChange(): Promise { await this.$apollo.queries.person.fetchMore({ variables: { actorId: this.id, organizedEventsPage: this.organizedEventsPage, organizedEventsLimit: EVENTS_PER_PAGE, }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newOrganizedEvents = - fetchMoreResult.person.organizedEvents.elements; - return { - person: { - ...previousResult.person, - organizedEvents: { - __typename: previousResult.person.organizedEvents.__typename, - total: previousResult.person.organizedEvents.total, - elements: [ - ...previousResult.person.organizedEvents.elements, - ...newOrganizedEvents, - ], - }, - }, - }; - }, }); } - async onParticipationsPageChange(page: number): Promise { - this.participationsPage = page; + async onParticipationsPageChange(): Promise { await this.$apollo.queries.person.fetchMore({ variables: { actorId: this.id, participationPage: this.participationsPage, - participationLimit: EVENTS_PER_PAGE, - }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newParticipations = - fetchMoreResult.person.participations.elements; - return { - person: { - ...previousResult.person, - participations: { - __typename: previousResult.person.participations.__typename, - total: previousResult.person.participations.total, - elements: [ - ...previousResult.person.participations.elements, - ...newParticipations, - ], - }, - }, - }; + participationLimit: PARTICIPATIONS_PER_PAGE, }, }); } + + async onMembershipsPageChange(): Promise { + await this.$apollo.queries.person.fetchMore({ + variables: { + actorId: this.id, + membershipsPage: this.participationsPage, + membershipsLimit: MEMBERSHIPS_PER_PAGE, + }, + }); + } + + private async pushRouter(args: Record): Promise { + try { + await this.$router.push({ + name: RouteName.ADMIN_PROFILE, + query: { ...this.$route.query, ...args }, + }); + } catch (e) { + if (isNavigationFailure(e, NavigationFailureType.redirected)) { + throw Error(e.toString()); + } + } + } } diff --git a/js/src/views/Admin/Follows.vue b/js/src/views/Admin/Follows.vue index 4a7d007f3..2d9b6e599 100644 --- a/js/src/views/Admin/Follows.vue +++ b/js/src/views/Admin/Follows.vue @@ -32,7 +32,6 @@ tag="li" active-class="is-active" :to="{ name: RouteName.RELAY_FOLLOWINGS }" - exact > @@ -46,7 +45,6 @@ tag="li" active-class="is-active" :to="{ name: RouteName.RELAY_FOLLOWERS }" - exact > diff --git a/js/src/views/Admin/GroupProfiles.vue b/js/src/views/Admin/GroupProfiles.vue index 9d2553317..261bfcb07 100644 --- a/js/src/views/Admin/GroupProfiles.vue +++ b/js/src/views/Admin/GroupProfiles.vue @@ -23,6 +23,12 @@ paginated backend-pagination backend-filtering + :debounce-search="200" + :current-page.sync="page" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="groups.total" :per-page="PROFILES_PER_PAGE" @page-change="onPageChange" @@ -81,20 +87,21 @@ diff --git a/js/src/views/Admin/Profiles.vue b/js/src/views/Admin/Profiles.vue index 71384cf5a..682f8b5eb 100644 --- a/js/src/views/Admin/Profiles.vue +++ b/js/src/views/Admin/Profiles.vue @@ -23,6 +23,12 @@ paginated backend-pagination backend-filtering + :debounce-search="200" + :current-page.sync="page" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :total="persons.total" :per-page="PROFILES_PER_PAGE" @page-change="onPageChange" @@ -81,20 +87,21 @@ diff --git a/js/src/views/Admin/Users.vue b/js/src/views/Admin/Users.vue index 60ffb817b..942304174 100644 --- a/js/src/views/Admin/Users.vue +++ b/js/src/views/Admin/Users.vue @@ -22,6 +22,11 @@ backend-pagination backend-filtering detailed + :current-page.sync="page" + :aria-next-label="$t('Next page')" + :aria-previous-label="$t('Previous page')" + :aria-page-label="$t('Page')" + :aria-current-label="$t('Current page')" :show-detail-icon="true" :total="users.total" :per-page="USERS_PER_PAGE" @@ -108,6 +113,8 @@ import { Component, Vue } from "vue-property-decorator"; import { LIST_USERS } from "../../graphql/user"; import RouteName from "../../router/name"; +import VueRouter from "vue-router"; +const { isNavigationFailure, NavigationFailureType } = VueRouter; const USERS_PER_PAGE = 10; @@ -119,7 +126,7 @@ const USERS_PER_PAGE = 10; variables() { return { email: this.email, - page: 1, + page: this.page, limit: USERS_PER_PAGE, }; }, @@ -127,14 +134,26 @@ const USERS_PER_PAGE = 10; }, }) export default class Users extends Vue { - page = 1; - - email = ""; - USERS_PER_PAGE = USERS_PER_PAGE; RouteName = RouteName; + get page(): number { + return parseInt((this.$route.query.page as string) || "1", 10); + } + + set page(page: number) { + this.pushRouter({ page: page.toString() }); + } + + get email(): string { + return (this.$route.query.email as string) || ""; + } + + set email(email: string) { + this.pushRouter({ email }); + } + async onPageChange(page: number): Promise { this.page = page; await this.$apollo.queries.users.fetchMore({ @@ -143,23 +162,25 @@ export default class Users extends Vue { page: this.page, limit: USERS_PER_PAGE, }, - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newFollowings = fetchMoreResult.users.elements; - return { - users: { - __typename: previousResult.users.__typename, - total: previousResult.users.total, - elements: [...previousResult.users.elements, ...newFollowings], - }, - }; - }, }); } onFiltersChange({ email }: { email: string }): void { this.email = email; } + + private async pushRouter(args: Record): Promise { + try { + await this.$router.push({ + name: RouteName.USERS, + query: { ...this.$route.query, ...args }, + }); + } catch (e) { + if (isNavigationFailure(e, NavigationFailureType.redirected)) { + throw Error(e.toString()); + } + } + } } diff --git a/js/src/views/Discussions/Discussion.vue b/js/src/views/Discussions/Discussion.vue index da3b1e9f3..522c18642 100644 --- a/js/src/views/Discussions/Discussion.vue +++ b/js/src/views/Discussions/Discussion.vue @@ -143,6 +143,7 @@ import { DELETE_COMMENT, UPDATE_COMMENT } from "@/graphql/comment"; import GroupMixin from "@/mixins/group"; import RouteName from "../../router/name"; import { IComment } from "../../types/comment.model"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component({ apollo: { @@ -168,24 +169,36 @@ import { IComment } from "../../types/comment.model"; variables() { return { slug: this.slug, + page: this.page, + limit: this.COMMENTS_PER_PAGE, }; }, - updateQuery: (previousResult, { subscriptionData }) => { + updateQuery: function ( + previousResult: any, + { subscriptionData }: { subscriptionData: any } + ) { const previousDiscussion = previousResult.discussion; - console.log("updating subscription with ", subscriptionData); - if ( - !previousDiscussion.comments.elements.find( - (comment: IComment) => - comment.id === - subscriptionData.data.discussionCommentChanged.lastComment.id - ) - ) { - previousDiscussion.lastComment = - subscriptionData.data.discussionCommentChanged.lastComment; - previousDiscussion.comments.elements.push( - subscriptionData.data.discussionCommentChanged.lastComment - ); - previousDiscussion.comments.total += 1; + const lastComment = + subscriptionData.data.discussionCommentChanged.lastComment; + const canLoadMore = !previousDiscussion.comments.elements.find( + (comment: IComment) => comment.id === lastComment.id + ); + if (canLoadMore) { + return { + discussion: { + ...previousDiscussion, + lastComment: lastComment, + comments: { + elements: [ + ...previousDiscussion.comments.elements.filter( + ({ id }: { id: string }) => id !== lastComment.id + ), + lastComment, + ], + total: previousDiscussion.comments.total + 1, + }, + }, + }; } return previousDiscussion; @@ -239,29 +252,6 @@ export default class discussion extends mixins(GroupMixin) { discussionId: this.discussion.id, text: this.newComment, }, - update: (store, { data: { replyToDiscussion } }) => { - const discussionData = store.readQuery<{ - discussion: IDiscussion; - }>({ - query: GET_DISCUSSION, - variables: { - slug: this.slug, - page: this.page, - }, - }); - if (!discussionData) return; - const { discussion: discussionCached } = discussionData; - discussionCached.lastComment = replyToDiscussion.lastComment; - discussionCached.comments.elements.push(replyToDiscussion.lastComment); - discussionCached.comments.total += 1; - store.writeQuery({ - query: GET_DISCUSSION, - variables: { slug: this.slug, page: this.page }, - data: { discussion: discussionCached }, - }); - }, - // We don't need to handle cache update since - // there's the subscription that handles this for us }); this.newComment = ""; } @@ -273,7 +263,7 @@ export default class discussion extends mixins(GroupMixin) { commentId: comment.id, text: comment.text, }, - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (!data || !data.deleteComment) return; const discussionData = store.readQuery<{ discussion: IDiscussion; @@ -308,7 +298,7 @@ export default class discussion extends mixins(GroupMixin) { variables: { commentId: comment.id, }, - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (!data || !data.deleteComment) return; const discussionData = store.readQuery<{ discussion: IDiscussion; @@ -324,17 +314,30 @@ export default class discussion extends mixins(GroupMixin) { const index = discussionCached.comments.elements.findIndex( ({ id }) => id === data.deleteComment.id ); + let discussionUpdated = discussionCached; if (index > -1) { - const updatedComment = discussionCached.comments.elements[index]; - updatedComment.deletedAt = new Date(); - updatedComment.actor = null; - updatedComment.text = ""; - discussionCached.comments.elements.splice(index, 1, updatedComment); + const updatedComment = { + ...discussionCached.comments.elements[index], + deletedAt: new Date(), + actor: null, + updatedComment: { + text: "", + }, + }; + const elements = [...discussionCached.comments.elements]; + elements.splice(index, 1, updatedComment); + discussionUpdated = { + ...discussionCached, + comments: { + total: discussionCached.comments.total, + elements, + }, + }; } store.writeQuery({ query: GET_DISCUSSION, variables: { slug: this.slug, page: this.page }, - data: { discussion: discussionCached }, + data: { discussion: discussionUpdated }, }); }, }); @@ -351,19 +354,6 @@ export default class discussion extends mixins(GroupMixin) { page: this.page, limit: this.COMMENTS_PER_PAGE, }, - // Transform the previous result with new data - updateQuery: (previousResult, { fetchMoreResult }) => { - if (!fetchMoreResult) return previousResult; - const newComments = fetchMoreResult.discussion.comments.elements; - this.hasMoreComments = newComments.length === 1; - const { discussion: discussionCached } = previousResult; - discussionCached.comments.elements = [ - ...previousResult.discussion.comments.elements, - ...newComments, - ]; - - return { discussion: discussionCached }; - }, }); } catch (e) { console.error(e); @@ -377,7 +367,10 @@ export default class discussion extends mixins(GroupMixin) { discussionId: this.discussion.id, title: this.newTitle, }, - update: (store, { data: { updateDiscussion } }) => { + update: ( + store: ApolloCache, + { data }: FetchResult<{ updateDiscussion: IDiscussion }> + ) => { const discussionData = store.readQuery<{ discussion: IDiscussion; }>({ @@ -387,14 +380,18 @@ export default class discussion extends mixins(GroupMixin) { page: this.page, }, }); - if (!discussionData) return; - const { discussion: discussionCached } = discussionData; - discussionCached.title = updateDiscussion.title; - store.writeQuery({ - query: GET_DISCUSSION, - variables: { slug: this.slug, page: this.page }, - data: { discussion: discussionCached }, - }); + if (discussionData && data?.updateDiscussion) { + store.writeQuery({ + query: GET_DISCUSSION, + variables: { slug: this.slug, page: this.page }, + data: { + discussion: { + ...discussionData.discussion, + title: data?.updateDiscussion.title, + }, + }, + }); + } }, }); this.editTitleMode = false; diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue index 98f012837..95460777e 100644 --- a/js/src/views/Event/Edit.vue +++ b/js/src/views/Event/Edit.vue @@ -442,7 +442,7 @@ section { diff --git a/js/src/views/Resources/ResourceFolder.vue b/js/src/views/Resources/ResourceFolder.vue index 1ae5a4e98..7e3435a4f 100644 --- a/js/src/views/Resources/ResourceFolder.vue +++ b/js/src/views/Resources/ResourceFolder.vue @@ -229,7 +229,7 @@ import { Component, Mixins, Prop, Watch } from "vue-property-decorator"; import ResourceItem from "@/components/Resource/ResourceItem.vue"; import FolderItem from "@/components/Resource/FolderItem.vue"; import Draggable from "vuedraggable"; -import { RefetchQueryDescription } from "apollo-client/core/watchQueryOptions"; +import { RefetchQueryDescription } from "@apollo/client/core/watchQueryOptions"; import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor"; import { IActor, usernameWithDomain } from "../../types/actor"; import RouteName from "../../router/name"; @@ -249,6 +249,7 @@ import { CONFIG } from "../../graphql/config"; import { IConfig } from "../../types/config.model"; import ResourceMixin from "../../mixins/resource"; import ResourceSelector from "../../components/Resource/ResourceSelector.vue"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component({ components: { FolderItem, ResourceItem, Draggable, ResourceSelector }, @@ -538,7 +539,7 @@ export default class Resources extends Mixins(ResourceMixin) { path: resource.path, }, refetchQueries: () => this.postRefreshQueries(), - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (!data || data.updateResource == null || parentPath == null) return; if (!this.resource.actor) return; diff --git a/js/src/views/Todos/TodoList.vue b/js/src/views/Todos/TodoList.vue index 415d019e0..c980c46af 100644 --- a/js/src/views/Todos/TodoList.vue +++ b/js/src/views/Todos/TodoList.vue @@ -51,6 +51,7 @@ import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; import { IActor } from "@/types/actor"; import { ITodoList } from "@/types/todolist"; import RouteName from "../../router/name"; +import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @Component({ components: { @@ -88,7 +89,7 @@ export default class TodoList extends Vue { status: this.newTodo.status, todoListId: this.id, }, - update: (store, { data }) => { + update: (store: ApolloCache, { data }: FetchResult) => { if (data == null) return; const cachedData = store.readQuery<{ todoList: ITodoList }>({ query: FETCH_TODO_LIST, diff --git a/js/src/vue-apollo.ts b/js/src/vue-apollo.ts index 3191afd56..65a23ac43 100644 --- a/js/src/vue-apollo.ts +++ b/js/src/vue-apollo.ts @@ -1,14 +1,16 @@ import Vue from "vue"; import VueApollo from "vue-apollo"; -import { ApolloLink, Observable, split } from "apollo-link"; +import { onError } from "@apollo/client/link/error"; +import { createLink } from "apollo-absinthe-upload-link"; import { + ApolloClient, + ApolloLink, defaultDataIdFromObject, InMemoryCache, NormalizedCacheObject, -} from "apollo-cache-inmemory"; -import { onError } from "apollo-link-error"; -import { createLink } from "apollo-absinthe-upload-link"; -import { ApolloClient } from "apollo-client"; + Observable, + split, +} from "@apollo/client/core"; import buildCurrentUserResolver from "@/apollo/user"; import { isServerError } from "@/types/apollo"; import { AUTH_ACCESS_TOKEN } from "@/constants"; @@ -16,10 +18,14 @@ import { logout } from "@/utils/auth"; import { Socket as PhoenixSocket } from "phoenix"; import * as AbsintheSocket from "@absinthe/socket"; import { createAbsintheSocketLink } from "@absinthe/socket-apollo-link"; -import { getMainDefinition } from "apollo-utilities"; +import { getMainDefinition } from "@apollo/client/utilities"; import fetch from "unfetch"; import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from "./api/_entrypoint"; -import { fragmentMatcher, refreshAccessToken } from "./apollo/utils"; +import { + possibleTypes, + typePolicies, + refreshAccessToken, +} from "./apollo/utils"; // Install the vue plugin Vue.use(VueApollo); @@ -89,7 +95,7 @@ const errorLink = onError( ({ graphQLErrors, networkError, forward, operation }) => { if ( isServerError(networkError) && - networkError.statusCode === 401 && + networkError?.statusCode === 401 && !alreadyRefreshedToken ) { if (!refreshingTokenPromise) @@ -130,7 +136,8 @@ const errorLink = onError( const fullLink = authMiddleware.concat(errorLink).concat(link); const cache = new InMemoryCache({ - fragmentMatcher, + typePolicies, + possibleTypes, dataIdFromObject: (object: any) => { if (object.__typename === "Address") { return object.origin_id; diff --git a/js/tests/unit/specs/components/Comment/CommentTree.spec.ts b/js/tests/unit/specs/components/Comment/CommentTree.spec.ts index 44d1b635b..004158bcd 100644 --- a/js/tests/unit/specs/components/Comment/CommentTree.spec.ts +++ b/js/tests/unit/specs/components/Comment/CommentTree.spec.ts @@ -1,7 +1,6 @@ import { config, createLocalVue, shallowMount, Wrapper } from "@vue/test-utils"; import CommentTree from "@/components/Comment/CommentTree.vue"; import Buefy from "buefy"; -import { InMemoryCache } from "apollo-cache-inmemory"; import { createMockClient, MockApolloClient, @@ -17,6 +16,7 @@ import { newCommentForEventMock, newCommentForEventResponse, } from "../../mocks/event"; +import { InMemoryCache } from "@apollo/client/cache"; const localVue = createLocalVue(); localVue.use(Buefy); diff --git a/js/tests/unit/specs/components/Participation/ParticipationSection.spec.ts b/js/tests/unit/specs/components/Participation/ParticipationSection.spec.ts index 2b20839bb..7bf400179 100644 --- a/js/tests/unit/specs/components/Participation/ParticipationSection.spec.ts +++ b/js/tests/unit/specs/components/Participation/ParticipationSection.spec.ts @@ -10,10 +10,10 @@ import { RequestHandler, } from "mock-apollo-client"; import buildCurrentUserResolver from "@/apollo/user"; -import { InMemoryCache } from "apollo-cache-inmemory"; import { CONFIG } from "@/graphql/config"; import VueApollo from "vue-apollo"; import { configMock } from "../../mocks/config"; +import { InMemoryCache } from "@apollo/client/cache"; const localVue = createLocalVue(); localVue.use(Buefy); diff --git a/js/tests/unit/specs/components/Participation/ParticipationWithoutAccount.spec.ts b/js/tests/unit/specs/components/Participation/ParticipationWithoutAccount.spec.ts index 5c7923183..047cb595b 100644 --- a/js/tests/unit/specs/components/Participation/ParticipationWithoutAccount.spec.ts +++ b/js/tests/unit/specs/components/Participation/ParticipationWithoutAccount.spec.ts @@ -14,7 +14,6 @@ import { RequestHandler, } from "mock-apollo-client"; import buildCurrentUserResolver from "@/apollo/user"; -import { InMemoryCache } from "apollo-cache-inmemory"; import { CONFIG } from "@/graphql/config"; import VueApollo from "vue-apollo"; import { FETCH_EVENT_BASIC, JOIN_EVENT } from "@/graphql/event"; @@ -26,6 +25,7 @@ import { joinEventMock, joinEventResponseMock, } from "../../mocks/event"; +import { InMemoryCache } from "@apollo/client/cache"; const localVue = createLocalVue(); localVue.use(Buefy); diff --git a/js/tests/unit/specs/components/User/login.spec.ts b/js/tests/unit/specs/components/User/login.spec.ts index 67698e7fc..600d14841 100644 --- a/js/tests/unit/specs/components/User/login.spec.ts +++ b/js/tests/unit/specs/components/User/login.spec.ts @@ -8,7 +8,6 @@ import { } from "mock-apollo-client"; import VueApollo from "vue-apollo"; import buildCurrentUserResolver from "@/apollo/user"; -import { InMemoryCache } from "apollo-cache-inmemory"; import { configMock } from "../../mocks/config"; import { i18n } from "@/utils/i18n"; import { CONFIG } from "@/graphql/config"; @@ -18,6 +17,7 @@ import { CURRENT_USER_CLIENT } from "@/graphql/user"; import { ICurrentUser } from "@/types/current-user.model"; import flushPromises from "flush-promises"; import RouteName from "@/router/name"; +import { InMemoryCache } from "@apollo/client/cache"; const localVue = createLocalVue(); localVue.use(Buefy); diff --git a/js/tests/unit/specs/components/navbar.spec.ts b/js/tests/unit/specs/components/navbar.spec.ts index f2123eed1..cd7297495 100644 --- a/js/tests/unit/specs/components/navbar.spec.ts +++ b/js/tests/unit/specs/components/navbar.spec.ts @@ -8,10 +8,10 @@ import { import VueApollo from "vue-apollo"; import { CONFIG } from "@/graphql/config"; import { USER_SETTINGS } from "@/graphql/user"; -import { InMemoryCache } from "apollo-cache-inmemory"; import buildCurrentUserResolver from "@/apollo/user"; import Buefy from "buefy"; import { configMock } from "../mocks/config"; +import { InMemoryCache } from "@apollo/client/cache"; const localVue = createLocalVue(); localVue.use(VueApollo); diff --git a/js/yarn.lock b/js/yarn.lock index 315dc1dde..deaf540cd 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -27,7 +27,7 @@ core-js "2.6.0" zen-observable "0.8.11" -"@apollo/client@^3.0.0": +"@apollo/client@^3.0.0", "@apollo/client@^3.3.16": version "3.3.19" resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.3.19.tgz#f1172dc9b9d7eae04c8940b047fd3b452ef92d2c" integrity sha512-vzljWLPP0GwocfBhUopzDCUwsiaNTtii1eu8qDybAXqwj4/ZhnIM46c6dNQmnVcJpAIFRIsNCOxM4OlMDySJug== @@ -1906,7 +1906,7 @@ resolved "https://registry.yarnpkg.com/@types/ngeohash/-/ngeohash-0.6.2.tgz#356bb5e79294bc9f746ad89eb848eca2741a6e43" integrity sha512-6nlq2eEh75JegDGUXis9wGTYIJpUvbori4qx++PRKQsV3YRkaqUNPNykzphniqPSZADXCouBuAnyptjUkMkhvw== -"@types/node@*", "@types/node@>=6": +"@types/node@*": version "15.3.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26" integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ== @@ -2746,14 +2746,6 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.4.0.tgz#f84fd07bcacefe56ce762925798871092f0f228e" integrity sha512-xgT/HqJ+uLWGX+Mzufusl3cgjAcnqYYskaB7o0vRcwOEfuu6hMzSILQpnIzFMGsTaeaX4Nnekl+6fadLbl1/Vg== -"@wry/context@^0.4.0": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.4.4.tgz#e50f5fa1d6cfaabf2977d1fda5ae91717f8815f8" - integrity sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag== - dependencies: - "@types/node" ">=6" - tslib "^1.9.3" - "@wry/context@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.0.tgz#f903eceb89d238ef7e8168ed30f4511f92d83e06" @@ -2984,74 +2976,6 @@ apollo-absinthe-upload-link@^1.5.0: graphql "^15.0.0" rxjs "~6.2.2" -apollo-cache-inmemory@^1.6.6: - version "1.6.6" - resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz#56d1f2a463a6b9db32e9fa990af16d2a008206fd" - integrity sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A== - dependencies: - apollo-cache "^1.3.5" - apollo-utilities "^1.3.4" - optimism "^0.10.0" - ts-invariant "^0.4.0" - tslib "^1.10.0" - -apollo-cache@1.3.5, apollo-cache@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.5.tgz#9dbebfc8dbe8fe7f97ba568a224bca2c5d81f461" - integrity sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA== - dependencies: - apollo-utilities "^1.3.4" - tslib "^1.10.0" - -apollo-client@^2.6.10: - version "2.6.10" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.10.tgz#86637047b51d940c8eaa771a4ce1b02df16bea6a" - integrity sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA== - dependencies: - "@types/zen-observable" "^0.8.0" - apollo-cache "1.3.5" - apollo-link "^1.0.0" - apollo-utilities "1.3.4" - symbol-observable "^1.0.2" - ts-invariant "^0.4.0" - tslib "^1.10.0" - zen-observable "^0.8.0" - -apollo-link-error@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.13.tgz#c1a1bb876ffe380802c8df0506a32c33aad284cd" - integrity sha512-jAZOOahJU6bwSqb2ZyskEK1XdgUY9nkmeclCrW7Gddh1uasHVqmoYc4CKdb0/H0Y1J9lvaXKle2Wsw/Zx1AyUg== - dependencies: - apollo-link "^1.2.14" - apollo-link-http-common "^0.2.16" - tslib "^1.9.3" - -apollo-link-http-common@^0.2.16: - version "0.2.16" - resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz#756749dafc732792c8ca0923f9a40564b7c59ecc" - integrity sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg== - dependencies: - apollo-link "^1.2.14" - ts-invariant "^0.4.0" - tslib "^1.9.3" - -apollo-link-http@^1.5.17: - version "1.5.17" - resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.17.tgz#499e9f1711bf694497f02c51af12d82de5d8d8ba" - integrity sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg== - dependencies: - apollo-link "^1.2.14" - apollo-link-http-common "^0.2.16" - tslib "^1.9.3" - -apollo-link-ws@^1.0.19: - version "1.0.20" - resolved "https://registry.yarnpkg.com/apollo-link-ws/-/apollo-link-ws-1.0.20.tgz#dfad44121f8445c6d7b7f8101a1b24813ba008ed" - integrity sha512-mjSFPlQxmoLArpHBeUb2Xj+2HDYeTaJqFGOqQ+I8NVJxgL9lJe84PDWcPah/yMLv3rB7QgBDSuZ0xoRFBPlySw== - dependencies: - apollo-link "^1.2.14" - tslib "^1.9.3" - apollo-link@1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.5.tgz#f54932d6b8f1412a35e088bc199a116bce3f1f16" @@ -3060,17 +2984,7 @@ apollo-link@1.2.5: apollo-utilities "^1.0.0" zen-observable-ts "^0.8.12" -apollo-link@^1.0.0, apollo-link@^1.2.14: - version "1.2.14" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9" - integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg== - dependencies: - apollo-utilities "^1.3.0" - ts-invariant "^0.4.0" - tslib "^1.9.3" - zen-observable-ts "^0.8.21" - -apollo-utilities@1.3.4, apollo-utilities@^1.0.0, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2, apollo-utilities@^1.3.4: +apollo-utilities@^1.0.0: version "1.3.4" resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.4.tgz#6129e438e8be201b6c55b0f13ce49d2c7175c9cf" integrity sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig== @@ -8182,10 +8096,10 @@ mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mock-apollo-client@^0.6: - version "0.6.0" - resolved "https://registry.yarnpkg.com/mock-apollo-client/-/mock-apollo-client-0.6.0.tgz#ff1760f18798789931e421c6668bfc0e7f1f0649" - integrity sha512-HPo6yVkAE+uxIpB8oPO9ISM86hFDwefrHKz5uOowecT/hR79e/NbXAl372LOQ1lWYVNVgCaP3RIMgWEhVFWtjw== +mock-apollo-client@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mock-apollo-client/-/mock-apollo-client-1.1.0.tgz#0589ae458a2539518e870bb0c08961f30a9eaec1" + integrity sha512-OXCvwAwwHbieMMipcE3wGdPONPHC+f65EEiyC1XpYaS5Jk6/c7oBe9t8knwzAqyCQ9nZziHdR8UDqORPTfkcFw== module-alias@^2.2.2: version "2.2.2" @@ -8578,13 +8492,6 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimism@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.10.3.tgz#163268fdc741dea2fb50f300bedda80356445fd7" - integrity sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw== - dependencies: - "@wry/context" "^0.4.0" - optimism@^0.16.0: version "0.16.1" resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.1.tgz#7c8efc1f3179f18307b887e18c15c5b7133f6e7d" @@ -10907,7 +10814,7 @@ svgo@^2.3.0: csso "^4.2.0" stable "^0.1.8" -symbol-observable@^1.0.2, symbol-observable@^1.1.0: +symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -12449,7 +12356,7 @@ yorkie@^2.0.0: normalize-path "^1.0.0" strip-indent "^2.0.0" -zen-observable-ts@^0.8.12, zen-observable-ts@^0.8.21: +zen-observable-ts@^0.8.12: version "0.8.21" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d" integrity sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==