fix: Do not rely on class names in production bundle (#6212)
This commit is contained in:
@@ -3,8 +3,7 @@ import { shallow } from "enzyme";
|
|||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { getI18n } from "react-i18next";
|
import { getI18n } from "react-i18next";
|
||||||
import RootStore from "~/stores/RootStore";
|
import { Pagination } from "@shared/constants";
|
||||||
import { DEFAULT_PAGINATION_LIMIT } from "~/stores/base/Store";
|
|
||||||
import { runAllPromises } from "~/test/support";
|
import { runAllPromises } from "~/test/support";
|
||||||
import { Component as PaginatedList } from "./PaginatedList";
|
import { Component as PaginatedList } from "./PaginatedList";
|
||||||
|
|
||||||
@@ -12,17 +11,12 @@ describe("PaginatedList", () => {
|
|||||||
const render = () => null;
|
const render = () => null;
|
||||||
|
|
||||||
const i18n = getI18n();
|
const i18n = getI18n();
|
||||||
const { logout, ...store } = new RootStore();
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
i18n,
|
i18n,
|
||||||
tReady: true,
|
tReady: true,
|
||||||
t: ((key: string) => key) as TFunction,
|
t: ((key: string) => key) as TFunction,
|
||||||
logout: () => {
|
} as any;
|
||||||
//
|
|
||||||
},
|
|
||||||
...store,
|
|
||||||
};
|
|
||||||
|
|
||||||
it("with no items renders nothing", () => {
|
it("with no items renders nothing", () => {
|
||||||
const list = shallow(
|
const list = shallow(
|
||||||
@@ -59,13 +53,13 @@ describe("PaginatedList", () => {
|
|||||||
);
|
);
|
||||||
expect(fetch).toHaveBeenCalledWith({
|
expect(fetch).toHaveBeenCalledWith({
|
||||||
...options,
|
...options,
|
||||||
limit: DEFAULT_PAGINATION_LIMIT,
|
limit: Pagination.defaultLimit,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls fetch when options prop changes", async () => {
|
it("calls fetch when options prop changes", async () => {
|
||||||
const fetchedItems = Array(DEFAULT_PAGINATION_LIMIT).fill(undefined);
|
const fetchedItems = Array(Pagination.defaultLimit).fill(undefined);
|
||||||
const fetch = jest.fn().mockReturnValue(Promise.resolve(fetchedItems));
|
const fetch = jest.fn().mockReturnValue(Promise.resolve(fetchedItems));
|
||||||
const list = shallow(
|
const list = shallow(
|
||||||
<PaginatedList
|
<PaginatedList
|
||||||
@@ -81,7 +75,7 @@ describe("PaginatedList", () => {
|
|||||||
await runAllPromises();
|
await runAllPromises();
|
||||||
expect(fetch).toHaveBeenCalledWith({
|
expect(fetch).toHaveBeenCalledWith({
|
||||||
id: "one",
|
id: "one",
|
||||||
limit: DEFAULT_PAGINATION_LIMIT,
|
limit: Pagination.defaultLimit,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
});
|
});
|
||||||
fetch.mockReset();
|
fetch.mockReset();
|
||||||
@@ -95,7 +89,7 @@ describe("PaginatedList", () => {
|
|||||||
await runAllPromises();
|
await runAllPromises();
|
||||||
expect(fetch).toHaveBeenCalledWith({
|
expect(fetch).toHaveBeenCalledWith({
|
||||||
id: "two",
|
id: "two",
|
||||||
limit: DEFAULT_PAGINATION_LIMIT,
|
limit: Pagination.defaultLimit,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import * as React from "react";
|
|||||||
import { withTranslation, WithTranslation } from "react-i18next";
|
import { withTranslation, WithTranslation } from "react-i18next";
|
||||||
import { Waypoint } from "react-waypoint";
|
import { Waypoint } from "react-waypoint";
|
||||||
import { CompositeStateReturn } from "reakit/Composite";
|
import { CompositeStateReturn } from "reakit/Composite";
|
||||||
|
import { Pagination } from "@shared/constants";
|
||||||
import RootStore from "~/stores/RootStore";
|
import RootStore from "~/stores/RootStore";
|
||||||
import { DEFAULT_PAGINATION_LIMIT } from "~/stores/base/Store";
|
|
||||||
import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
|
import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
|
||||||
import DelayedMount from "~/components/DelayedMount";
|
import DelayedMount from "~/components/DelayedMount";
|
||||||
import PlaceholderList from "~/components/List/Placeholder";
|
import PlaceholderList from "~/components/List/Placeholder";
|
||||||
@@ -86,7 +86,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
|
|||||||
reset = () => {
|
reset = () => {
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
this.allowLoadMore = true;
|
this.allowLoadMore = true;
|
||||||
this.renderCount = DEFAULT_PAGINATION_LIMIT;
|
this.renderCount = Pagination.defaultLimit;
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
this.isFetchingInitial = false;
|
this.isFetchingInitial = false;
|
||||||
this.isFetchingMore = false;
|
this.isFetchingMore = false;
|
||||||
@@ -99,7 +99,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
|
|||||||
}
|
}
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
const counter = ++this.fetchCounter;
|
const counter = ++this.fetchCounter;
|
||||||
const limit = this.props.options?.limit ?? DEFAULT_PAGINATION_LIMIT;
|
const limit = this.props.options?.limit ?? Pagination.defaultLimit;
|
||||||
this.error = undefined;
|
this.error = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -139,12 +139,12 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
|
|||||||
const leftToRender = (this.props.items?.length ?? 0) - this.renderCount;
|
const leftToRender = (this.props.items?.length ?? 0) - this.renderCount;
|
||||||
|
|
||||||
if (leftToRender > 0) {
|
if (leftToRender > 0) {
|
||||||
this.renderCount += DEFAULT_PAGINATION_LIMIT;
|
this.renderCount += Pagination.defaultLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are less than a pages results in the cache go ahead and fetch
|
// If there are less than a pages results in the cache go ahead and fetch
|
||||||
// another page from the server
|
// another page from the server
|
||||||
if (leftToRender <= DEFAULT_PAGINATION_LIMIT) {
|
if (leftToRender <= Pagination.defaultLimit) {
|
||||||
this.isFetchingMore = true;
|
this.isFetchingMore = true;
|
||||||
await this.fetchResults();
|
await this.fetchResults();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import Model from "./base/Model";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
class ApiKey extends Model {
|
class ApiKey extends Model {
|
||||||
|
static modelName = "ApiKey";
|
||||||
|
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import Model from "./base/Model";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
class AuthenticationProvider extends Model {
|
class AuthenticationProvider extends Model {
|
||||||
|
static modelName = "AuthenticationProvider";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { client } from "~/utils/ApiClient";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
export default class Collection extends ParanoidModel {
|
export default class Collection extends ParanoidModel {
|
||||||
|
static modelName = "Collection";
|
||||||
|
|
||||||
store: CollectionsStore;
|
store: CollectionsStore;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { CollectionPermission } from "@shared/types";
|
import { CollectionPermission } from "@shared/types";
|
||||||
|
import Collection from "./Collection";
|
||||||
|
import Group from "./Group";
|
||||||
import Model from "./base/Model";
|
import Model from "./base/Model";
|
||||||
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class CollectionGroupMembership extends Model {
|
class CollectionGroupMembership extends Model {
|
||||||
|
static modelName = "CollectionGroupMembership";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
|
||||||
|
@Relation(() => Group, { onDelete: "cascade" })
|
||||||
|
group: Group;
|
||||||
|
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
|
|
||||||
|
@Relation(() => Collection, { onDelete: "cascade" })
|
||||||
|
collection: Collection;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
permission: CollectionPermission;
|
permission: CollectionPermission;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import Field from "./decorators/Field";
|
|||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class Comment extends Model {
|
class Comment extends Model {
|
||||||
|
static modelName = "Comment";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map to keep track of which users are currently typing a reply in this
|
* Map to keep track of which users are currently typing a reply in this
|
||||||
* comments thread.
|
* comments thread.
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type SaveOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class Document extends ParanoidModel {
|
export default class Document extends ParanoidModel {
|
||||||
|
static modelName = "Document";
|
||||||
|
|
||||||
constructor(fields: Record<string, any>, store: DocumentsStore) {
|
constructor(fields: Record<string, any>, store: DocumentsStore) {
|
||||||
super(fields, store);
|
super(fields, store);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import Model from "./base/Model";
|
|||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class Event extends Model {
|
class Event extends Model {
|
||||||
|
static modelName = "Event";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import User from "./User";
|
|||||||
import Model from "./base/Model";
|
import Model from "./base/Model";
|
||||||
|
|
||||||
class FileOperation extends Model {
|
class FileOperation extends Model {
|
||||||
|
static modelName = "FileOperation";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import Model from "./base/Model";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
class Group extends Model {
|
class Group extends Model {
|
||||||
|
static modelName = "Group";
|
||||||
|
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
|
import Group from "./Group";
|
||||||
import User from "./User";
|
import User from "./User";
|
||||||
import Model from "./base/Model";
|
import Model from "./base/Model";
|
||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class GroupMembership extends Model {
|
class GroupMembership extends Model {
|
||||||
id: string;
|
static modelName = "GroupMembership";
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
groupId: string;
|
|
||||||
|
|
||||||
@Relation(() => User, { onDelete: "cascade" })
|
@Relation(() => User, { onDelete: "cascade" })
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
|
groupId: string;
|
||||||
|
|
||||||
|
@Relation(() => Group, { onDelete: "cascade" })
|
||||||
|
group: Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GroupMembership;
|
export default GroupMembership;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import Model from "~/models/base/Model";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
class Integration<T = unknown> extends Model {
|
class Integration<T = unknown> extends Model {
|
||||||
|
static modelName = "Integration";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
type: IntegrationType;
|
type: IntegrationType;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { CollectionPermission } from "@shared/types";
|
|||||||
import Model from "./base/Model";
|
import Model from "./base/Model";
|
||||||
|
|
||||||
class Membership extends Model {
|
class Membership extends Model {
|
||||||
|
static modelName = "Membership";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import Field from "./decorators/Field";
|
|||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class Notification extends Model {
|
class Notification extends Model {
|
||||||
|
static modelName = "Notification";
|
||||||
|
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import Field from "./decorators/Field";
|
|||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class Pin extends Model {
|
class Pin extends Model {
|
||||||
|
static modelName = "Pin";
|
||||||
|
|
||||||
/** The collection ID that the document is pinned to. If empty the document is pinned to home. */
|
/** The collection ID that the document is pinned to. If empty the document is pinned to home. */
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { observable } from "mobx";
|
|||||||
import Model from "./base/Model";
|
import Model from "./base/Model";
|
||||||
|
|
||||||
class Policy extends Model {
|
class Policy extends Model {
|
||||||
|
static modelName = "Policy";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import Model from "./base/Model";
|
|||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class Revision extends Model {
|
class Revision extends Model {
|
||||||
|
static modelName = "Revision";
|
||||||
|
|
||||||
/** The document ID that the revision is related to */
|
/** The document ID that the revision is related to */
|
||||||
documentId: string;
|
documentId: string;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { client } from "~/utils/ApiClient";
|
|||||||
import Model from "./base/Model";
|
import Model from "./base/Model";
|
||||||
|
|
||||||
class SearchQuery extends Model {
|
class SearchQuery extends Model {
|
||||||
|
static modelName = "Search";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
query: string;
|
query: string;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import Field from "./decorators/Field";
|
|||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class Share extends Model {
|
class Share extends Model {
|
||||||
|
static modelName = "Share";
|
||||||
|
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
published: boolean;
|
published: boolean;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import Field from "./decorators/Field";
|
|||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class Star extends Model {
|
class Star extends Model {
|
||||||
|
static modelName = "Star";
|
||||||
|
|
||||||
/** The sort order of the star */
|
/** The sort order of the star */
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import Relation from "./decorators/Relation";
|
|||||||
* A subscription represents a request for a user to receive notifications for a document.
|
* A subscription represents a request for a user to receive notifications for a document.
|
||||||
*/
|
*/
|
||||||
class Subscription extends Model {
|
class Subscription extends Model {
|
||||||
|
static modelName = "Subscription";
|
||||||
|
|
||||||
/** The user ID subscribing */
|
/** The user ID subscribing */
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import Model from "./base/Model";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
class Team extends Model {
|
class Team extends Model {
|
||||||
|
static modelName = "Team";
|
||||||
|
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import ParanoidModel from "./base/ParanoidModel";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
class User extends ParanoidModel {
|
class User extends ParanoidModel {
|
||||||
|
static modelName = "User";
|
||||||
|
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { action, observable } from "mobx";
|
import { action, observable } from "mobx";
|
||||||
|
import Document from "./Document";
|
||||||
import User from "./User";
|
import User from "./User";
|
||||||
import Model from "./base/Model";
|
import Model from "./base/Model";
|
||||||
import Relation from "./decorators/Relation";
|
import Relation from "./decorators/Relation";
|
||||||
|
|
||||||
class View extends Model {
|
class View extends Model {
|
||||||
|
static modelName = "View";
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
documentId: string;
|
documentId: string;
|
||||||
|
|
||||||
|
@Relation(() => Document)
|
||||||
|
document?: Document;
|
||||||
|
|
||||||
firstViewedAt: string;
|
firstViewedAt: string;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import Model from "./base/Model";
|
|||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
class WebhookSubscription extends Model {
|
class WebhookSubscription extends Model {
|
||||||
|
static modelName = "WebhookSubscription";
|
||||||
|
|
||||||
@Field
|
@Field
|
||||||
@observable
|
@observable
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import Logger from "~/utils/Logger";
|
|||||||
import { getFieldsForModel } from "../decorators/Field";
|
import { getFieldsForModel } from "../decorators/Field";
|
||||||
|
|
||||||
export default abstract class Model {
|
export default abstract class Model {
|
||||||
|
static modelName: string;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export const getInverseRelationsForModelClass = (targetClass: typeof Model) => {
|
|||||||
|
|
||||||
relations.forEach((relation, modelName) => {
|
relations.forEach((relation, modelName) => {
|
||||||
relation.forEach((properties, propertyName) => {
|
relation.forEach((properties, propertyName) => {
|
||||||
if (properties.relationClassResolver().name === targetClass.name) {
|
if (
|
||||||
|
properties.relationClassResolver().modelName === targetClass.modelName
|
||||||
|
) {
|
||||||
inverseRelations.set(propertyName, {
|
inverseRelations.set(propertyName, {
|
||||||
...properties,
|
...properties,
|
||||||
modelName,
|
modelName,
|
||||||
@@ -66,13 +68,13 @@ export default function Relation<T extends typeof Model>(
|
|||||||
// this to determine how to update relations when a model is deleted.
|
// this to determine how to update relations when a model is deleted.
|
||||||
if (options) {
|
if (options) {
|
||||||
const configForClass =
|
const configForClass =
|
||||||
relations.get(target.constructor.name) || new Map();
|
relations.get(target.constructor.modelName) || new Map();
|
||||||
configForClass.set(propertyKey, {
|
configForClass.set(propertyKey, {
|
||||||
options,
|
options,
|
||||||
relationClassResolver: classResolver,
|
relationClassResolver: classResolver,
|
||||||
idKey,
|
idKey,
|
||||||
});
|
});
|
||||||
relations.set(target.constructor.name, configForClass);
|
relations.set(target.constructor.modelName, configForClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(target, propertyKey, {
|
Object.defineProperty(target, propertyKey, {
|
||||||
@@ -83,9 +85,9 @@ export default function Relation<T extends typeof Model>(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationClassName = classResolver().name;
|
const relationClassName = classResolver().modelName;
|
||||||
const store =
|
const store =
|
||||||
this.store.rootStore[`${relationClassName.toLowerCase()}s`];
|
this.store.rootStore.getStoreForModelName(relationClassName);
|
||||||
invariant(store, `Store for ${relationClassName} not found`);
|
invariant(store, `Store for ${relationClassName} not found`);
|
||||||
|
|
||||||
return store.get(id);
|
return store.get(id);
|
||||||
@@ -94,9 +96,9 @@ export default function Relation<T extends typeof Model>(
|
|||||||
this[idKey] = newValue ? newValue.id : undefined;
|
this[idKey] = newValue ? newValue.id : undefined;
|
||||||
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
const relationClassName = classResolver().name;
|
const relationClassName = classResolver().modelName;
|
||||||
const store =
|
const store =
|
||||||
this.store.rootStore[`${relationClassName.toLowerCase()}s`];
|
this.store.rootStore.getStoreForModelName(relationClassName);
|
||||||
invariant(store, `Store for ${relationClassName} not found`);
|
invariant(store, `Store for ${relationClassName} not found`);
|
||||||
|
|
||||||
store.add(newValue);
|
store.add(newValue);
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import { Waypoint } from "react-waypoint";
|
|||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import breakpoint from "styled-components-breakpoint";
|
import breakpoint from "styled-components-breakpoint";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { Pagination } from "@shared/constants";
|
||||||
import { hideScrollbars } from "@shared/styles";
|
import { hideScrollbars } from "@shared/styles";
|
||||||
import { DateFilter as TDateFilter } from "@shared/types";
|
import { DateFilter as TDateFilter } from "@shared/types";
|
||||||
import { SearchParams } from "~/stores/DocumentsStore";
|
import { SearchParams } from "~/stores/DocumentsStore";
|
||||||
import RootStore from "~/stores/RootStore";
|
import RootStore from "~/stores/RootStore";
|
||||||
import { DEFAULT_PAGINATION_LIMIT } from "~/stores/base/Store";
|
|
||||||
import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
|
import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
|
||||||
import DocumentListItem from "~/components/DocumentListItem";
|
import DocumentListItem from "~/components/DocumentListItem";
|
||||||
import Empty from "~/components/Empty";
|
import Empty from "~/components/Empty";
|
||||||
@@ -248,7 +248,7 @@ class Search extends React.Component<Props> {
|
|||||||
if (this.query.trim()) {
|
if (this.query.trim()) {
|
||||||
const params = {
|
const params = {
|
||||||
offset: this.offset,
|
offset: this.offset,
|
||||||
limit: DEFAULT_PAGINATION_LIMIT,
|
limit: Pagination.defaultLimit,
|
||||||
dateFilter: this.dateFilter,
|
dateFilter: this.dateFilter,
|
||||||
includeArchived: this.includeArchived,
|
includeArchived: this.includeArchived,
|
||||||
includeDrafts: true,
|
includeDrafts: true,
|
||||||
@@ -280,10 +280,10 @@ class Search extends React.Component<Props> {
|
|||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (results.length === 0 || results.length < DEFAULT_PAGINATION_LIMIT) {
|
if (results.length === 0 || results.length < Pagination.defaultLimit) {
|
||||||
this.allowLoadMore = false;
|
this.allowLoadMore = false;
|
||||||
} else {
|
} else {
|
||||||
this.offset += DEFAULT_PAGINATION_LIMIT;
|
this.offset += Pagination.defaultLimit;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error("Search query failed", error);
|
Logger.error("Search query failed", error);
|
||||||
|
|||||||
@@ -351,6 +351,6 @@ export default class AuthStore extends Store<Team> {
|
|||||||
|
|
||||||
// Tell the host application we logged out, if any – allows window cleanup.
|
// Tell the host application we logged out, if any – allows window cleanup.
|
||||||
void Desktop.bridge?.onLogout?.();
|
void Desktop.bridge?.onLogout?.();
|
||||||
this.rootStore.logout();
|
this.rootStore.clear();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import RootStore from "./RootStore";
|
|||||||
import Store from "./base/Store";
|
import Store from "./base/Store";
|
||||||
|
|
||||||
export default class CommentsStore extends Store<Comment> {
|
export default class CommentsStore extends Store<Comment> {
|
||||||
apiEndpoint = "comments";
|
|
||||||
|
|
||||||
constructor(rootStore: RootStore) {
|
constructor(rootStore: RootStore) {
|
||||||
super(rootStore, Comment);
|
super(rootStore, Comment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import invariant from "invariant";
|
||||||
|
import pluralize from "pluralize";
|
||||||
import ApiKeysStore from "./ApiKeysStore";
|
import ApiKeysStore from "./ApiKeysStore";
|
||||||
import AuthStore from "./AuthStore";
|
import AuthStore from "./AuthStore";
|
||||||
import AuthenticationProvidersStore from "./AuthenticationProvidersStore";
|
import AuthenticationProvidersStore from "./AuthenticationProvidersStore";
|
||||||
@@ -25,6 +27,7 @@ import UiStore from "./UiStore";
|
|||||||
import UsersStore from "./UsersStore";
|
import UsersStore from "./UsersStore";
|
||||||
import ViewsStore from "./ViewsStore";
|
import ViewsStore from "./ViewsStore";
|
||||||
import WebhookSubscriptionsStore from "./WebhookSubscriptionStore";
|
import WebhookSubscriptionsStore from "./WebhookSubscriptionStore";
|
||||||
|
import Store from "./base/Store";
|
||||||
|
|
||||||
export default class RootStore {
|
export default class RootStore {
|
||||||
apiKeys: ApiKeysStore;
|
apiKeys: ApiKeysStore;
|
||||||
@@ -56,42 +59,79 @@ export default class RootStore {
|
|||||||
webhookSubscriptions: WebhookSubscriptionsStore;
|
webhookSubscriptions: WebhookSubscriptionsStore;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.apiKeys = new ApiKeysStore(this);
|
// Models
|
||||||
this.authenticationProviders = new AuthenticationProvidersStore(this);
|
this.registerStore(ApiKeysStore);
|
||||||
this.collections = new CollectionsStore(this);
|
this.registerStore(AuthenticationProvidersStore);
|
||||||
this.collectionGroupMemberships = new CollectionGroupMembershipsStore(this);
|
this.registerStore(CollectionsStore);
|
||||||
this.comments = new CommentsStore(this);
|
this.registerStore(CollectionGroupMembershipsStore);
|
||||||
this.dialogs = new DialogsStore();
|
this.registerStore(CommentsStore);
|
||||||
this.documents = new DocumentsStore(this);
|
this.registerStore(DocumentsStore);
|
||||||
this.events = new EventsStore(this);
|
this.registerStore(EventsStore);
|
||||||
this.groups = new GroupsStore(this);
|
this.registerStore(GroupsStore);
|
||||||
this.groupMemberships = new GroupMembershipsStore(this);
|
this.registerStore(GroupMembershipsStore);
|
||||||
this.integrations = new IntegrationsStore(this);
|
this.registerStore(IntegrationsStore);
|
||||||
this.memberships = new MembershipsStore(this);
|
this.registerStore(MembershipsStore);
|
||||||
this.notifications = new NotificationsStore(this);
|
this.registerStore(NotificationsStore);
|
||||||
this.pins = new PinsStore(this);
|
this.registerStore(PinsStore);
|
||||||
this.policies = new PoliciesStore(this);
|
this.registerStore(PoliciesStore);
|
||||||
this.presence = new DocumentPresenceStore();
|
this.registerStore(RevisionsStore);
|
||||||
this.revisions = new RevisionsStore(this);
|
this.registerStore(SearchesStore);
|
||||||
this.searches = new SearchesStore(this);
|
this.registerStore(SharesStore);
|
||||||
this.shares = new SharesStore(this);
|
this.registerStore(StarsStore);
|
||||||
this.stars = new StarsStore(this);
|
this.registerStore(SubscriptionsStore);
|
||||||
this.subscriptions = new SubscriptionsStore(this);
|
this.registerStore(UsersStore);
|
||||||
this.ui = new UiStore();
|
this.registerStore(ViewsStore);
|
||||||
this.users = new UsersStore(this);
|
this.registerStore(FileOperationsStore);
|
||||||
this.views = new ViewsStore(this);
|
this.registerStore(WebhookSubscriptionsStore);
|
||||||
this.fileOperations = new FileOperationsStore(this);
|
|
||||||
this.webhookSubscriptions = new WebhookSubscriptionsStore(this);
|
// Non-models
|
||||||
|
this.registerStore(DocumentPresenceStore, "presence");
|
||||||
|
this.registerStore(DialogsStore, "dialogs");
|
||||||
|
this.registerStore(UiStore, "ui");
|
||||||
|
|
||||||
// AuthStore must be initialized last as it makes use of the other stores.
|
// AuthStore must be initialized last as it makes use of the other stores.
|
||||||
this.auth = new AuthStore(this);
|
this.registerStore(AuthStore, "auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
/**
|
||||||
|
* Get a store by model name.
|
||||||
|
*
|
||||||
|
* @param modelName
|
||||||
|
*/
|
||||||
|
public getStoreForModelName<K extends keyof RootStore>(
|
||||||
|
modelName: string
|
||||||
|
): RootStore[K] {
|
||||||
|
const storeName = this.getStoreNameForModelName(modelName);
|
||||||
|
const store = this[storeName];
|
||||||
|
invariant(store, `No store found for model name "${modelName}"`);
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all data from the stores except for auth and ui.
|
||||||
|
*/
|
||||||
|
public clear() {
|
||||||
Object.getOwnPropertyNames(this)
|
Object.getOwnPropertyNames(this)
|
||||||
.filter((key) => ["auth", "ui"].includes(key) === false)
|
.filter((key) => ["auth", "ui"].includes(key) === false)
|
||||||
.forEach((key) => {
|
.forEach((key) => {
|
||||||
this[key]?.clear?.();
|
this[key]?.clear?.();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a store with the root store.
|
||||||
|
*
|
||||||
|
* @param StoreClass
|
||||||
|
*/
|
||||||
|
private registerStore<T = typeof Store>(StoreClass: T, name?: string) {
|
||||||
|
// @ts-expect-error TS thinks we are instantiating an abstract class.
|
||||||
|
const store = new StoreClass(this);
|
||||||
|
const storeName = name ?? this.getStoreNameForModelName(store.modelName);
|
||||||
|
this[storeName] = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStoreNameForModelName(modelName: string) {
|
||||||
|
return pluralize(modelName.toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import Store, { RPCAction } from "./base/Store";
|
|||||||
export default class SearchesStore extends Store<SearchQuery> {
|
export default class SearchesStore extends Store<SearchQuery> {
|
||||||
actions = [RPCAction.List, RPCAction.Delete];
|
actions = [RPCAction.List, RPCAction.Delete];
|
||||||
|
|
||||||
apiEndpoint = "searches";
|
|
||||||
|
|
||||||
constructor(rootStore: RootStore) {
|
constructor(rootStore: RootStore) {
|
||||||
super(rootStore, SearchQuery);
|
super(rootStore, SearchQuery);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import invariant from "invariant";
|
|||||||
import lowerFirst from "lodash/lowerFirst";
|
import lowerFirst from "lodash/lowerFirst";
|
||||||
import orderBy from "lodash/orderBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import { observable, action, computed, runInAction } from "mobx";
|
import { observable, action, computed, runInAction } from "mobx";
|
||||||
import { Class } from "utility-types";
|
import pluralize from "pluralize";
|
||||||
import RootStore from "~/stores/RootStore";
|
import RootStore from "~/stores/RootStore";
|
||||||
import Policy from "~/models/Policy";
|
import Policy from "~/models/Policy";
|
||||||
import Model from "~/models/base/Model";
|
import Model from "~/models/base/Model";
|
||||||
@@ -22,8 +22,6 @@ export enum RPCAction {
|
|||||||
|
|
||||||
type FetchPageParams = PaginationParams & Record<string, any>;
|
type FetchPageParams = PaginationParams & Record<string, any>;
|
||||||
|
|
||||||
export const DEFAULT_PAGINATION_LIMIT = 25;
|
|
||||||
|
|
||||||
export const PAGINATION_SYMBOL = Symbol.for("pagination");
|
export const PAGINATION_SYMBOL = Symbol.for("pagination");
|
||||||
|
|
||||||
export default abstract class Store<T extends Model> {
|
export default abstract class Store<T extends Model> {
|
||||||
@@ -39,7 +37,7 @@ export default abstract class Store<T extends Model> {
|
|||||||
@observable
|
@observable
|
||||||
isLoaded = false;
|
isLoaded = false;
|
||||||
|
|
||||||
model: Class<T>;
|
model: typeof Model;
|
||||||
|
|
||||||
modelName: string;
|
modelName: string;
|
||||||
|
|
||||||
@@ -56,13 +54,13 @@ export default abstract class Store<T extends Model> {
|
|||||||
RPCAction.Count,
|
RPCAction.Count,
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(rootStore: RootStore, model: Class<T>) {
|
constructor(rootStore: RootStore, model: typeof Model) {
|
||||||
this.rootStore = rootStore;
|
this.rootStore = rootStore;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.modelName = lowerFirst(model.name).replace(/\d$/, "");
|
this.modelName = model.modelName;
|
||||||
|
|
||||||
if (!this.apiEndpoint) {
|
if (!this.apiEndpoint) {
|
||||||
this.apiEndpoint = `${this.modelName}s`;
|
this.apiEndpoint = pluralize(lowerFirst(model.modelName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +87,7 @@ export default abstract class Store<T extends Model> {
|
|||||||
return existingModel;
|
return existingModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error TS thinks that we're instantiating an abstract class here
|
||||||
const newModel = new ModelClass(item, this);
|
const newModel = new ModelClass(item, this);
|
||||||
this.data.set(newModel.id, newModel);
|
this.data.set(newModel.id, newModel);
|
||||||
return newModel;
|
return newModel;
|
||||||
@@ -103,20 +102,21 @@ export default abstract class Store<T extends Model> {
|
|||||||
const inverseRelations = getInverseRelationsForModelClass(this.model);
|
const inverseRelations = getInverseRelationsForModelClass(this.model);
|
||||||
|
|
||||||
inverseRelations.forEach((relation) => {
|
inverseRelations.forEach((relation) => {
|
||||||
// TODO: Need a better way to get the store for a given model name.
|
const store = this.rootStore.getStoreForModelName(relation.modelName);
|
||||||
const store = this.rootStore[`${relation.modelName.toLowerCase()}s`];
|
if ("orderedData" in store) {
|
||||||
const items = store.orderedData.filter(
|
const items = (store.orderedData as Model[]).filter(
|
||||||
(item: Model) => item[relation.idKey] === id
|
(item) => item[relation.idKey] === id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (relation.options.onDelete === "cascade") {
|
if (relation.options.onDelete === "cascade") {
|
||||||
items.forEach((item: Model) => store.remove(item.id));
|
items.forEach((item) => store.remove(item.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relation.options.onDelete === "null") {
|
if (relation.options.onDelete === "null") {
|
||||||
items.forEach((item: Model) => {
|
items.forEach((item) => {
|
||||||
item[relation.idKey] = null;
|
item[relation.idKey] = null;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
"patch-package": "^7.0.2",
|
"patch-package": "^7.0.2",
|
||||||
"pg": "^8.11.1",
|
"pg": "^8.11.1",
|
||||||
"pg-tsquery": "^8.4.1",
|
"pg-tsquery": "^8.4.1",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
"polished": "^4.2.2",
|
"polished": "^4.2.2",
|
||||||
"prosemirror-codemark": "^0.4.2",
|
"prosemirror-codemark": "^0.4.2",
|
||||||
"prosemirror-commands": "^1.5.2",
|
"prosemirror-commands": "^1.5.2",
|
||||||
@@ -278,6 +279,7 @@
|
|||||||
"@types/node-fetch": "^2.6.9",
|
"@types/node-fetch": "^2.6.9",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/passport-oauth2": "^1.4.15",
|
"@types/passport-oauth2": "^1.4.15",
|
||||||
|
"@types/pluralize": "^0.0.33",
|
||||||
"@types/quoted-printable": "^1.0.0",
|
"@types/quoted-printable": "^1.0.0",
|
||||||
"@types/randomstring": "^1.1.11",
|
"@types/randomstring": "^1.1.11",
|
||||||
"@types/react": "^17.0.34",
|
"@types/react": "^17.0.34",
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import querystring from "querystring";
|
import querystring from "querystring";
|
||||||
import { Next } from "koa";
|
import { Next } from "koa";
|
||||||
|
import { Pagination } from "@shared/constants";
|
||||||
import { InvalidRequestError } from "@server/errors";
|
import { InvalidRequestError } from "@server/errors";
|
||||||
import { AppContext } from "@server/types";
|
import { AppContext } from "@server/types";
|
||||||
|
|
||||||
export default function pagination() {
|
export default function pagination() {
|
||||||
return async function paginationMiddleware(ctx: AppContext, next: Next) {
|
return async function paginationMiddleware(ctx: AppContext, next: Next) {
|
||||||
const opts = {
|
const opts = {
|
||||||
defaultLimit: 15,
|
defaultLimit: Pagination.defaultLimit,
|
||||||
defaultOffset: 0,
|
defaultOffset: Pagination.defaultOffset,
|
||||||
maxLimit: 100,
|
maxLimit: Pagination.maxLimit,
|
||||||
};
|
};
|
||||||
const query = ctx.request.query;
|
const query = ctx.request.query;
|
||||||
const body = ctx.request.body;
|
const body = ctx.request.body;
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import {
|
|||||||
|
|
||||||
export const MAX_AVATAR_DISPLAY = 6;
|
export const MAX_AVATAR_DISPLAY = 6;
|
||||||
|
|
||||||
|
export const Pagination = {
|
||||||
|
defaultLimit: 25,
|
||||||
|
defaultOffset: 0,
|
||||||
|
maxLimit: 100,
|
||||||
|
};
|
||||||
|
|
||||||
export const TeamPreferenceDefaults: TeamPreferences = {
|
export const TeamPreferenceDefaults: TeamPreferences = {
|
||||||
[TeamPreference.SeamlessEdit]: true,
|
[TeamPreference.SeamlessEdit]: true,
|
||||||
[TeamPreference.ViewersCanExport]: true,
|
[TeamPreference.ViewersCanExport]: true,
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@@ -3314,6 +3314,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/express" "*"
|
"@types/express" "*"
|
||||||
|
|
||||||
|
"@types/pluralize@^0.0.33":
|
||||||
|
version "0.0.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.33.tgz#8ad9018368c584d268667dd9acd5b3b806e8c82a"
|
||||||
|
integrity sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==
|
||||||
|
|
||||||
"@types/prismjs@*":
|
"@types/prismjs@*":
|
||||||
version "1.26.0"
|
version "1.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654"
|
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654"
|
||||||
@@ -10609,6 +10614,11 @@ pkg-up@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^3.0.0"
|
find-up "^3.0.0"
|
||||||
|
|
||||||
|
pluralize@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||||
|
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
|
||||||
|
|
||||||
polished@^4.2.2:
|
polished@^4.2.2:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1"
|
resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1"
|
||||||
|
|||||||
Reference in New Issue
Block a user