chore: Move to Typescript (#2783)
This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously. closes #1282
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import ApiKey from "models/ApiKey";
|
||||
import BaseStore from "./BaseStore";
|
||||
import ApiKey from "~/models/ApiKey";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class ApiKeysStore extends BaseStore<ApiKey> {
|
||||
actions = ["list", "create", "delete"];
|
||||
actions = [RPCAction.List, RPCAction.Create, RPCAction.Delete];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, ApiKey);
|
||||
@@ -1,54 +1,71 @@
|
||||
// @flow
|
||||
import * as Sentry from "@sentry/react";
|
||||
import invariant from "invariant";
|
||||
import { observable, action, computed, autorun, runInAction } from "mobx";
|
||||
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
|
||||
import RootStore from "stores/RootStore";
|
||||
import Policy from "models/Policy";
|
||||
import Team from "models/Team";
|
||||
import User from "models/User";
|
||||
import env from "env";
|
||||
import { client } from "utils/ApiClient";
|
||||
import { getCookieDomain } from "utils/domains";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import Policy from "~/models/Policy";
|
||||
import Team from "~/models/Team";
|
||||
import User from "~/models/User";
|
||||
import env from "~/env";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import { getCookieDomain } from "~/utils/domains";
|
||||
|
||||
const AUTH_STORE = "AUTH_STORE";
|
||||
const NO_REDIRECT_PATHS = ["/", "/create", "/home"];
|
||||
|
||||
type PersistedData = {
|
||||
user?: User,
|
||||
team?: Team,
|
||||
policies?: Policy[],
|
||||
user?: User;
|
||||
team?: Team;
|
||||
policies?: Policy[];
|
||||
};
|
||||
|
||||
type Provider = {|
|
||||
id: string,
|
||||
name: string,
|
||||
authUrl: string,
|
||||
|};
|
||||
type Provider = {
|
||||
id: string;
|
||||
name: string;
|
||||
authUrl: string;
|
||||
};
|
||||
|
||||
type Config = {|
|
||||
name?: string,
|
||||
hostname?: string,
|
||||
providers: Provider[],
|
||||
|};
|
||||
type Config = {
|
||||
name?: string;
|
||||
hostname?: string;
|
||||
providers: Provider[];
|
||||
};
|
||||
|
||||
export default class AuthStore {
|
||||
@observable user: ?User;
|
||||
@observable team: ?Team;
|
||||
@observable token: ?string;
|
||||
@observable policies: Policy[] = [];
|
||||
@observable lastSignedIn: ?string;
|
||||
@observable isSaving: boolean = false;
|
||||
@observable isSuspended: boolean = false;
|
||||
@observable suspendedContactEmail: ?string;
|
||||
@observable config: ?Config;
|
||||
@observable
|
||||
user: User | null | undefined;
|
||||
|
||||
@observable
|
||||
team: Team | null | undefined;
|
||||
|
||||
@observable
|
||||
token: string | null | undefined;
|
||||
|
||||
@observable
|
||||
policies: Policy[] = [];
|
||||
|
||||
@observable
|
||||
lastSignedIn: string | null | undefined;
|
||||
|
||||
@observable
|
||||
isSaving = false;
|
||||
|
||||
@observable
|
||||
isSuspended = false;
|
||||
|
||||
@observable
|
||||
suspendedContactEmail: string | null | undefined;
|
||||
|
||||
@observable
|
||||
config: Config | null | undefined;
|
||||
|
||||
rootStore: RootStore;
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
this.rootStore = rootStore;
|
||||
|
||||
// attempt to load the previous state of this store from localstorage
|
||||
let data: PersistedData = {};
|
||||
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(AUTH_STORE) || "{}");
|
||||
} catch (_) {
|
||||
@@ -56,7 +73,6 @@ export default class AuthStore {
|
||||
}
|
||||
|
||||
this.rehydrate(data);
|
||||
|
||||
// persists this entire store to localstorage whenever any keys are changed
|
||||
autorun(() => {
|
||||
try {
|
||||
@@ -65,13 +81,13 @@ export default class AuthStore {
|
||||
// no-op Safari private mode
|
||||
}
|
||||
});
|
||||
|
||||
// listen to the localstorage value changing in other tabs to react to
|
||||
// signin/signout events in other tabs and follow suite.
|
||||
window.addEventListener("storage", (event) => {
|
||||
if (event.key === AUTH_STORE) {
|
||||
const data: ?PersistedData = JSON.parse(event.newValue);
|
||||
|
||||
if (event.key === AUTH_STORE && event.newValue) {
|
||||
const data: PersistedData | null | undefined = JSON.parse(
|
||||
event.newValue
|
||||
);
|
||||
// data may be null if key is deleted in localStorage
|
||||
if (!data) return;
|
||||
|
||||
@@ -90,8 +106,8 @@ export default class AuthStore {
|
||||
|
||||
@action
|
||||
rehydrate(data: PersistedData) {
|
||||
this.user = new User(data.user);
|
||||
this.team = new Team(data.team);
|
||||
this.user = data.user ? new User(data.user, this) : undefined;
|
||||
this.team = data.team ? new Team(data.team, this) : undefined;
|
||||
this.token = getCookie("accessToken");
|
||||
this.lastSignedIn = getCookie("lastSignedIn");
|
||||
this.addPolicies(data.policies);
|
||||
@@ -135,16 +151,17 @@ export default class AuthStore {
|
||||
try {
|
||||
const res = await client.post("/auth.info");
|
||||
invariant(res && res.data, "Auth not available");
|
||||
|
||||
runInAction("AuthStore#fetch", () => {
|
||||
this.addPolicies(res.policies);
|
||||
const { user, team } = res.data;
|
||||
this.user = new User(user);
|
||||
this.team = new Team(team);
|
||||
this.user = new User(user, this);
|
||||
this.team = new Team(team, this);
|
||||
|
||||
if (env.SENTRY_DSN) {
|
||||
Sentry.configureScope(function (scope) {
|
||||
scope.setUser({ id: user.id });
|
||||
scope.setUser({
|
||||
id: user.id,
|
||||
});
|
||||
scope.setExtra("team", team.name);
|
||||
scope.setExtra("teamId", team.id);
|
||||
});
|
||||
@@ -152,6 +169,7 @@ export default class AuthStore {
|
||||
|
||||
// If we came from a redirect then send the user immediately there
|
||||
const postLoginRedirectPath = getCookie("postLoginRedirectPath");
|
||||
|
||||
if (postLoginRedirectPath) {
|
||||
removeCookie("postLoginRedirectPath");
|
||||
|
||||
@@ -170,8 +188,9 @@ export default class AuthStore {
|
||||
|
||||
@action
|
||||
deleteUser = async () => {
|
||||
await client.post(`/users.delete`, { confirmation: true });
|
||||
|
||||
await client.post(`/users.delete`, {
|
||||
confirmation: true,
|
||||
});
|
||||
runInAction("AuthStore#updateUser", () => {
|
||||
this.user = null;
|
||||
this.team = null;
|
||||
@@ -180,16 +199,19 @@ export default class AuthStore {
|
||||
};
|
||||
|
||||
@action
|
||||
updateUser = async (params: { name?: string, avatarUrl: ?string }) => {
|
||||
updateUser = async (params: {
|
||||
name?: string;
|
||||
avatarUrl?: string | null;
|
||||
language?: string;
|
||||
}) => {
|
||||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/users.update`, params);
|
||||
invariant(res && res.data, "User response not available");
|
||||
|
||||
runInAction("AuthStore#updateUser", () => {
|
||||
this.addPolicies(res.policies);
|
||||
this.user = new User(res.data);
|
||||
this.user = new User(res.data, this);
|
||||
});
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
@@ -198,19 +220,20 @@ export default class AuthStore {
|
||||
|
||||
@action
|
||||
updateTeam = async (params: {
|
||||
name?: string,
|
||||
avatarUrl?: ?string,
|
||||
sharing?: boolean,
|
||||
name?: string;
|
||||
avatarUrl?: string | null | undefined;
|
||||
sharing?: boolean;
|
||||
collaborativeEditing?: boolean;
|
||||
subdomain?: string | null | undefined;
|
||||
}) => {
|
||||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/team.update`, params);
|
||||
invariant(res && res.data, "Team response not available");
|
||||
|
||||
runInAction("AuthStore#updateTeam", () => {
|
||||
this.addPolicies(res.policies);
|
||||
this.team = new Team(res.data);
|
||||
this.team = new Team(res.data, this);
|
||||
});
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
@@ -218,7 +241,7 @@ export default class AuthStore {
|
||||
};
|
||||
|
||||
@action
|
||||
logout = async (savePath: boolean = false) => {
|
||||
logout = async (savePath = false) => {
|
||||
// remove user and team from localStorage
|
||||
localStorage.setItem(
|
||||
AUTH_STORE,
|
||||
@@ -228,7 +251,6 @@ export default class AuthStore {
|
||||
policies: [],
|
||||
})
|
||||
);
|
||||
|
||||
this.token = null;
|
||||
|
||||
// if this logout was forced from an authenticated route then
|
||||
@@ -242,14 +264,15 @@ export default class AuthStore {
|
||||
}
|
||||
|
||||
// remove authentication token itself
|
||||
removeCookie("accessToken", { path: "/" });
|
||||
|
||||
removeCookie("accessToken", {
|
||||
path: "/",
|
||||
});
|
||||
// remove session record on apex cookie
|
||||
const team = this.team;
|
||||
|
||||
if (team) {
|
||||
const sessions = JSON.parse(getCookie("sessions") || "{}");
|
||||
delete sessions[team.id];
|
||||
|
||||
setCookie("sessions", JSON.stringify(sessions), {
|
||||
domain: getCookieDomain(window.location.hostname),
|
||||
});
|
||||
@@ -1,15 +1,31 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { orderBy } from "lodash";
|
||||
import { observable, set, action, computed, runInAction } from "mobx";
|
||||
import RootStore from "stores/RootStore";
|
||||
import BaseModel from "../models/BaseModel";
|
||||
import type { PaginationParams } from "types";
|
||||
import { client } from "utils/ApiClient";
|
||||
import { Class } from "utility-types";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import BaseModel from "~/models/BaseModel";
|
||||
import Policy from "~/models/Policy";
|
||||
import { PaginationParams } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
|
||||
type Action = "list" | "info" | "create" | "update" | "delete" | "count";
|
||||
type PartialWithId<T> = Partial<T> & { id: string };
|
||||
|
||||
function modelNameFromClassName(string) {
|
||||
export enum RPCAction {
|
||||
Info = "info",
|
||||
List = "list",
|
||||
Create = "create",
|
||||
Update = "update",
|
||||
Delete = "delete",
|
||||
Count = "count",
|
||||
}
|
||||
|
||||
type FetchPageParams = PaginationParams & {
|
||||
documentId?: string;
|
||||
query?: string;
|
||||
filter?: string;
|
||||
};
|
||||
|
||||
function modelNameFromClassName(string: string) {
|
||||
return string.charAt(0).toLowerCase() + string.slice(1);
|
||||
}
|
||||
|
||||
@@ -17,16 +33,33 @@ export const DEFAULT_PAGINATION_LIMIT = 25;
|
||||
|
||||
export const PAGINATION_SYMBOL = Symbol.for("pagination");
|
||||
|
||||
export default class BaseStore<T: BaseModel> {
|
||||
@observable data: Map<string, T> = new Map();
|
||||
@observable isFetching: boolean = false;
|
||||
@observable isSaving: boolean = false;
|
||||
@observable isLoaded: boolean = false;
|
||||
export default class BaseStore<T extends BaseModel> {
|
||||
@observable
|
||||
data: Map<string, T> = new Map();
|
||||
|
||||
@observable
|
||||
isFetching = false;
|
||||
|
||||
@observable
|
||||
isSaving = false;
|
||||
|
||||
@observable
|
||||
isLoaded = false;
|
||||
|
||||
model: Class<T>;
|
||||
|
||||
modelName: string;
|
||||
|
||||
rootStore: RootStore;
|
||||
actions: Action[] = ["list", "info", "create", "update", "delete", "count"];
|
||||
|
||||
actions = [
|
||||
RPCAction.Info,
|
||||
RPCAction.List,
|
||||
RPCAction.Create,
|
||||
RPCAction.Update,
|
||||
RPCAction.Delete,
|
||||
RPCAction.Count,
|
||||
];
|
||||
|
||||
constructor(rootStore: RootStore, model: Class<T>) {
|
||||
this.rootStore = rootStore;
|
||||
@@ -39,24 +72,27 @@ export default class BaseStore<T: BaseModel> {
|
||||
this.data.clear();
|
||||
}
|
||||
|
||||
addPolicies = (policies) => {
|
||||
addPolicies = (policies: Policy[]) => {
|
||||
if (policies) {
|
||||
policies.forEach((policy) => this.rootStore.policies.add(policy));
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
add = (item: Object): T => {
|
||||
const Model = this.model;
|
||||
add = (item: PartialWithId<T> | T): T => {
|
||||
const ModelClass = this.model;
|
||||
|
||||
if (!(item instanceof Model)) {
|
||||
const existing: ?T = this.data.get(item.id);
|
||||
if (existing) {
|
||||
set(existing, item);
|
||||
return existing;
|
||||
} else {
|
||||
item = new Model(item, this);
|
||||
if (!(item instanceof ModelClass)) {
|
||||
const existingModel = this.data.get(item.id);
|
||||
|
||||
if (existingModel) {
|
||||
set(existingModel, item);
|
||||
return existingModel;
|
||||
}
|
||||
|
||||
const newModel = new ModelClass(item, this);
|
||||
this.data.set(newModel.id, newModel);
|
||||
return newModel;
|
||||
}
|
||||
|
||||
this.data.set(item.id, item);
|
||||
@@ -68,27 +104,33 @@ export default class BaseStore<T: BaseModel> {
|
||||
this.data.delete(id);
|
||||
}
|
||||
|
||||
save(params: Object) {
|
||||
save(params: Partial<T>): Promise<T> {
|
||||
if (params.id) return this.update(params);
|
||||
return this.create(params);
|
||||
}
|
||||
|
||||
get(id: string): ?T {
|
||||
get(id: string): T | undefined {
|
||||
return this.data.get(id);
|
||||
}
|
||||
|
||||
@action
|
||||
async create(params: Object) {
|
||||
if (!this.actions.includes("create")) {
|
||||
async create(
|
||||
params: Partial<T>,
|
||||
options?: Record<string, string | boolean | number | undefined>
|
||||
): Promise<T> {
|
||||
if (!this.actions.includes(RPCAction.Create)) {
|
||||
throw new Error(`Cannot create ${this.modelName}`);
|
||||
}
|
||||
|
||||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/${this.modelName}s.create`, params);
|
||||
const res = await client.post(`/${this.modelName}s.create`, {
|
||||
...params,
|
||||
...options,
|
||||
});
|
||||
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
return this.add(res.data);
|
||||
} finally {
|
||||
@@ -97,17 +139,23 @@ export default class BaseStore<T: BaseModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
async update(params: Object): * {
|
||||
if (!this.actions.includes("update")) {
|
||||
async update(
|
||||
params: Partial<T>,
|
||||
options?: Record<string, string | boolean | number | undefined>
|
||||
): Promise<T> {
|
||||
if (!this.actions.includes(RPCAction.Update)) {
|
||||
throw new Error(`Cannot update ${this.modelName}`);
|
||||
}
|
||||
|
||||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/${this.modelName}s.update`, params);
|
||||
const res = await client.post(`/${this.modelName}s.update`, {
|
||||
...params,
|
||||
...options,
|
||||
});
|
||||
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
return this.add(res.data);
|
||||
} finally {
|
||||
@@ -116,10 +164,11 @@ export default class BaseStore<T: BaseModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
async delete(item: T, options: Object = {}) {
|
||||
if (!this.actions.includes("delete")) {
|
||||
async delete(item: T, options: Record<string, any> = {}) {
|
||||
if (!this.actions.includes(RPCAction.Delete)) {
|
||||
throw new Error(`Cannot delete ${this.modelName}`);
|
||||
}
|
||||
|
||||
this.isSaving = true;
|
||||
|
||||
try {
|
||||
@@ -134,27 +183,27 @@ export default class BaseStore<T: BaseModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
async fetch(id: string, options: Object = {}): Promise<*> {
|
||||
if (!this.actions.includes("info")) {
|
||||
async fetch(id: string, options: Record<string, any> = {}): Promise<T> {
|
||||
if (!this.actions.includes(RPCAction.Info)) {
|
||||
throw new Error(`Cannot fetch ${this.modelName}`);
|
||||
}
|
||||
|
||||
const item = this.data.get(id);
|
||||
|
||||
if (item && !options.force) return item;
|
||||
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/${this.modelName}s.info`, { id });
|
||||
const res = await client.post(`/${this.modelName}s.info`, {
|
||||
id,
|
||||
});
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
return this.add(res.data);
|
||||
} catch (err) {
|
||||
if (err.statusCode === 403) {
|
||||
this.remove(id);
|
||||
}
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
this.isFetching = false;
|
||||
@@ -162,15 +211,15 @@ export default class BaseStore<T: BaseModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (params: ?PaginationParams): Promise<*> => {
|
||||
if (!this.actions.includes("list")) {
|
||||
fetchPage = async (params: FetchPageParams | undefined): Promise<any> => {
|
||||
if (!this.actions.includes(RPCAction.List)) {
|
||||
throw new Error(`Cannot list ${this.modelName}`);
|
||||
}
|
||||
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/${this.modelName}s.list`, params);
|
||||
|
||||
invariant(res && res.data, "Data not available");
|
||||
|
||||
runInAction(`list#${this.modelName}`, () => {
|
||||
@@ -179,7 +228,7 @@ export default class BaseStore<T: BaseModel> {
|
||||
this.isLoaded = true;
|
||||
});
|
||||
|
||||
let response = res.data;
|
||||
const response = res.data;
|
||||
response[PAGINATION_SYMBOL] = res.pagination;
|
||||
return response;
|
||||
} finally {
|
||||
@@ -1,28 +1,27 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { action, runInAction } from "mobx";
|
||||
import CollectionGroupMembership from "models/CollectionGroupMembership";
|
||||
import BaseStore from "./BaseStore";
|
||||
import CollectionGroupMembership from "~/models/CollectionGroupMembership";
|
||||
import { PaginationParams } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
import type { PaginationParams } from "types";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
export default class CollectionGroupMembershipsStore extends BaseStore<CollectionGroupMembership> {
|
||||
actions = ["create", "delete"];
|
||||
export default class CollectionGroupMembershipsStore extends BaseStore<
|
||||
CollectionGroupMembership
|
||||
> {
|
||||
actions = [RPCAction.Create, RPCAction.Delete];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, CollectionGroupMembership);
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (params: ?PaginationParams): Promise<*> => {
|
||||
fetchPage = async (params: PaginationParams | undefined): Promise<any> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/collections.group_memberships`, params);
|
||||
|
||||
invariant(res && res.data, "Data not available");
|
||||
|
||||
runInAction(`CollectionGroupMembershipsStore#fetchPage`, () => {
|
||||
res.data.groups.forEach(this.rootStore.groups.add);
|
||||
res.data.collectionGroupMemberships.forEach(this.add);
|
||||
@@ -40,9 +39,9 @@ export default class CollectionGroupMembershipsStore extends BaseStore<Collectio
|
||||
groupId,
|
||||
permission,
|
||||
}: {
|
||||
collectionId: string,
|
||||
groupId: string,
|
||||
permission: string,
|
||||
collectionId: string;
|
||||
groupId: string;
|
||||
permission: string;
|
||||
}) {
|
||||
const res = await client.post("/collections.add_group", {
|
||||
id: collectionId,
|
||||
@@ -51,7 +50,8 @@ export default class CollectionGroupMembershipsStore extends BaseStore<Collectio
|
||||
});
|
||||
invariant(res && res.data, "Membership data should be available");
|
||||
|
||||
res.data.collectionGroupMemberships.forEach(this.add);
|
||||
const cgm = res.data.collectionGroupMemberships.map(this.add);
|
||||
return cgm[0];
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -59,14 +59,13 @@ export default class CollectionGroupMembershipsStore extends BaseStore<Collectio
|
||||
collectionId,
|
||||
groupId,
|
||||
}: {
|
||||
collectionId: string,
|
||||
groupId: string,
|
||||
collectionId: string;
|
||||
groupId: string;
|
||||
}) {
|
||||
await client.post("/collections.remove_group", {
|
||||
id: collectionId,
|
||||
groupId,
|
||||
});
|
||||
|
||||
this.remove(`${groupId}-${collectionId}`);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { concat, find, last } from "lodash";
|
||||
import { computed, action } from "mobx";
|
||||
import Collection from "models/Collection";
|
||||
import Collection from "~/models/Collection";
|
||||
import { NavigationNode } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import BaseStore from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
enum DocumentPathItemType {
|
||||
Collection = "collection",
|
||||
Document = "document",
|
||||
}
|
||||
|
||||
export type DocumentPathItem = {
|
||||
id: string,
|
||||
collectionId: string,
|
||||
title: string,
|
||||
url: string,
|
||||
type: "collection" | "document",
|
||||
type: DocumentPathItemType;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
title: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type DocumentPath = DocumentPathItem & {
|
||||
path: DocumentPathItem[],
|
||||
path: DocumentPathItem[];
|
||||
};
|
||||
|
||||
export default class CollectionsStore extends BaseStore<Collection> {
|
||||
@@ -25,7 +30,7 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
}
|
||||
|
||||
@computed
|
||||
get active(): ?Collection {
|
||||
get active(): Collection | null | undefined {
|
||||
return this.rootStore.ui.activeCollectionId
|
||||
? this.data.get(this.rootStore.ui.activeCollectionId)
|
||||
: undefined;
|
||||
@@ -34,15 +39,14 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
@computed
|
||||
get orderedData(): Collection[] {
|
||||
let collections = Array.from(this.data.values());
|
||||
|
||||
collections = collections.filter((collection) =>
|
||||
collection.deletedAt ? false : true
|
||||
);
|
||||
|
||||
return collections.sort((a, b) => {
|
||||
if (a.index === b.index) {
|
||||
return a.updatedAt > b.updatedAt ? -1 : 1;
|
||||
}
|
||||
|
||||
return a.index < b.index ? -1 : 1;
|
||||
});
|
||||
}
|
||||
@@ -52,11 +56,22 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
*/
|
||||
@computed
|
||||
get pathsToDocuments(): DocumentPath[] {
|
||||
let results = [];
|
||||
const travelDocuments = (documentList, collectionId, path) =>
|
||||
documentList.forEach((document) => {
|
||||
const results: DocumentPathItem[][] = [];
|
||||
|
||||
const travelDocuments = (
|
||||
documentList: NavigationNode[],
|
||||
collectionId: string,
|
||||
path: DocumentPathItem[]
|
||||
) =>
|
||||
documentList.forEach((document: NavigationNode) => {
|
||||
const { id, title, url } = document;
|
||||
const node = { id, collectionId, title, url, type: "document" };
|
||||
const node = {
|
||||
type: DocumentPathItemType.Document,
|
||||
id,
|
||||
collectionId,
|
||||
title,
|
||||
url,
|
||||
};
|
||||
results.push(concat(path, node));
|
||||
travelDocuments(document.children, collectionId, concat(path, [node]));
|
||||
});
|
||||
@@ -65,11 +80,11 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
this.data.forEach((collection) => {
|
||||
const { id, name, url } = collection;
|
||||
const node = {
|
||||
type: DocumentPathItemType.Collection,
|
||||
id,
|
||||
collectionId: id,
|
||||
title: name,
|
||||
url,
|
||||
type: "collection",
|
||||
};
|
||||
results.push([node]);
|
||||
travelDocuments(collection.documents, id, [node]);
|
||||
@@ -77,11 +92,8 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
}
|
||||
|
||||
return results.map((result) => {
|
||||
const tail = last(result);
|
||||
return {
|
||||
...tail,
|
||||
path: result,
|
||||
};
|
||||
const tail = last(result) as DocumentPathItem;
|
||||
return { ...tail, path: result };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +112,6 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
index,
|
||||
});
|
||||
invariant(res && res.success, "Collection could not be moved");
|
||||
|
||||
const collection = this.get(collectionId);
|
||||
|
||||
if (collection) {
|
||||
@@ -108,7 +119,7 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
}
|
||||
};
|
||||
|
||||
async update(params: Object): Promise<Collection> {
|
||||
async update(params: Record<string, any>): Promise<Collection> {
|
||||
const result = await super.update(params);
|
||||
|
||||
// If we're changing sharing permissions on the collection then we need to
|
||||
@@ -116,6 +127,7 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
// are now invalid
|
||||
if (params.sharing !== undefined) {
|
||||
const collection = this.get(params.id);
|
||||
|
||||
if (collection) {
|
||||
collection.documentIds.forEach((id) => {
|
||||
this.rootStore.policies.remove(id);
|
||||
@@ -127,45 +139,48 @@ export default class CollectionsStore extends BaseStore<Collection> {
|
||||
}
|
||||
|
||||
@action
|
||||
async fetch(id: string, options: Object = {}): Promise<*> {
|
||||
async fetch(id: string, options: Record<string, any> = {}): Promise<any> {
|
||||
const item = this.get(id) || this.getByUrl(id);
|
||||
|
||||
if (item && !options.force) return item;
|
||||
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/collections.info`, { id });
|
||||
const res = await client.post(`/collections.info`, {
|
||||
id,
|
||||
});
|
||||
invariant(res && res.data, "Collection not available");
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
return this.add(res.data);
|
||||
} catch (err) {
|
||||
if (err.statusCode === 403) {
|
||||
this.remove(id);
|
||||
}
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
this.isFetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
getPathForDocument(documentId: string): ?DocumentPath {
|
||||
getPathForDocument(documentId: string): DocumentPath | undefined {
|
||||
return this.pathsToDocuments.find((path) => path.id === documentId);
|
||||
}
|
||||
|
||||
titleForDocument(documentUrl: string): ?string {
|
||||
titleForDocument(documentUrl: string): string | undefined {
|
||||
const path = this.pathsToDocuments.find((path) => path.url === documentUrl);
|
||||
if (path) return path.title;
|
||||
if (path) {
|
||||
return path.title;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
getByUrl(url: string): ?Collection {
|
||||
getByUrl(url: string): Collection | null | undefined {
|
||||
return find(this.orderedData, (col: Collection) => url.endsWith(col.urlId));
|
||||
}
|
||||
|
||||
delete = async (collection: Collection) => {
|
||||
await super.delete(collection);
|
||||
|
||||
this.rootStore.documents.fetchRecentlyUpdated();
|
||||
this.rootStore.documents.fetchRecentlyViewed();
|
||||
};
|
||||
@@ -1,27 +1,39 @@
|
||||
// @flow
|
||||
import { observable, action } from "mobx";
|
||||
import * as React from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export default class DialogsStore {
|
||||
@observable guide: {
|
||||
title: string,
|
||||
content: React.Node,
|
||||
isOpen: boolean,
|
||||
@observable
|
||||
guide: {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
};
|
||||
@observable modalStack = new Map<
|
||||
|
||||
@observable
|
||||
modalStack = new Map<
|
||||
string,
|
||||
{
|
||||
title: string,
|
||||
content: React.Node,
|
||||
isOpen: boolean,
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
openGuide = ({ title, content }: { title: string, content: React.Node }) => {
|
||||
openGuide = ({
|
||||
title,
|
||||
content,
|
||||
}: {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
}) => {
|
||||
setTimeout(
|
||||
action(() => {
|
||||
this.guide = { title, content, isOpen: true };
|
||||
this.guide = {
|
||||
title,
|
||||
content,
|
||||
isOpen: true,
|
||||
};
|
||||
}),
|
||||
0
|
||||
);
|
||||
@@ -39,9 +51,9 @@ export default class DialogsStore {
|
||||
content,
|
||||
replace,
|
||||
}: {
|
||||
title: string,
|
||||
content: React.Node,
|
||||
replace?: boolean,
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
replace?: boolean;
|
||||
}) => {
|
||||
setTimeout(
|
||||
action(() => {
|
||||
@@ -1,12 +1,19 @@
|
||||
// @flow
|
||||
import { observable, action } from "mobx";
|
||||
import { USER_PRESENCE_INTERVAL } from "shared/constants";
|
||||
import { USER_PRESENCE_INTERVAL } from "@shared/constants";
|
||||
|
||||
type DocumentPresence = Map<string, { isEditing: boolean, userId: string }>;
|
||||
type DocumentPresence = Map<
|
||||
string,
|
||||
{
|
||||
isEditing: boolean;
|
||||
userId: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export default class PresenceStore {
|
||||
@observable data: Map<string, DocumentPresence> = new Map();
|
||||
timeouts: Map<string, TimeoutID> = new Map();
|
||||
@observable
|
||||
data: Map<string, DocumentPresence> = new Map();
|
||||
|
||||
timeouts: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
||||
|
||||
// called to setup when we get the initial state from document.presence
|
||||
// websocket message. overrides any existing state
|
||||
@@ -22,6 +29,7 @@ export default class PresenceStore {
|
||||
@action
|
||||
leave(documentId: string, userId: string) {
|
||||
const existing = this.data.get(documentId);
|
||||
|
||||
if (existing) {
|
||||
existing.delete(userId);
|
||||
}
|
||||
@@ -30,7 +38,10 @@ export default class PresenceStore {
|
||||
@action
|
||||
update(documentId: string, userId: string, isEditing: boolean) {
|
||||
const existing = this.data.get(documentId) || new Map();
|
||||
existing.set(userId, { isEditing, userId });
|
||||
existing.set(userId, {
|
||||
isEditing,
|
||||
userId,
|
||||
});
|
||||
this.data.set(documentId, existing);
|
||||
}
|
||||
|
||||
@@ -43,6 +54,7 @@ export default class PresenceStore {
|
||||
touch(documentId: string, userId: string, isEditing: boolean) {
|
||||
const id = `${documentId}-${userId}`;
|
||||
let timeout = this.timeouts.get(id);
|
||||
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
this.timeouts.delete(id);
|
||||
@@ -58,7 +70,7 @@ export default class PresenceStore {
|
||||
}
|
||||
}
|
||||
|
||||
get(documentId: string): ?DocumentPresence {
|
||||
get(documentId: string): DocumentPresence | null | undefined {
|
||||
return this.data.get(documentId);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,43 @@
|
||||
// @flow
|
||||
import path from "path";
|
||||
import invariant from "invariant";
|
||||
import { find, orderBy, filter, compact, omitBy } from "lodash";
|
||||
import { observable, action, computed, runInAction } from "mobx";
|
||||
import { MAX_TITLE_LENGTH } from "shared/constants";
|
||||
import { subtractDate } from "shared/utils/date";
|
||||
import naturalSort from "shared/utils/naturalSort";
|
||||
import BaseStore from "stores/BaseStore";
|
||||
import RootStore from "stores/RootStore";
|
||||
import Document from "models/Document";
|
||||
import env from "env";
|
||||
import type {
|
||||
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||
import { DateFilter } from "@shared/types";
|
||||
import { subtractDate } from "@shared/utils/date";
|
||||
import naturalSort from "@shared/utils/naturalSort";
|
||||
import BaseStore from "~/stores/BaseStore";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import Document from "~/models/Document";
|
||||
import env from "~/env";
|
||||
import {
|
||||
NavigationNode,
|
||||
FetchOptions,
|
||||
PaginationParams,
|
||||
SearchResult,
|
||||
} from "types";
|
||||
import { client } from "utils/ApiClient";
|
||||
} from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
|
||||
type FetchParams = PaginationParams & { collectionId: string };
|
||||
|
||||
type FetchPageParams = PaginationParams & { template?: boolean };
|
||||
|
||||
type ImportOptions = {
|
||||
publish?: boolean,
|
||||
publish?: boolean;
|
||||
};
|
||||
|
||||
export default class DocumentsStore extends BaseStore<Document> {
|
||||
@observable searchCache: Map<string, SearchResult[]> = new Map();
|
||||
@observable starredIds: Map<string, boolean> = new Map();
|
||||
@observable backlinks: Map<string, string[]> = new Map();
|
||||
@observable movingDocumentId: ?string;
|
||||
@observable
|
||||
searchCache: Map<string, SearchResult[]> = new Map();
|
||||
|
||||
@observable
|
||||
starredIds: Map<string, boolean> = new Map();
|
||||
|
||||
@observable
|
||||
backlinks: Map<string, string[]> = new Map();
|
||||
|
||||
@observable
|
||||
movingDocumentId: string | null | undefined;
|
||||
|
||||
importFileTypes: string[] = [
|
||||
".md",
|
||||
@@ -54,7 +65,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
@computed
|
||||
get recentlyViewed(): Document[] {
|
||||
return orderBy(
|
||||
filter(this.all, (d) => d.lastViewedAt),
|
||||
this.all.filter((d) => d.lastViewedAt),
|
||||
"lastViewedAt",
|
||||
"desc"
|
||||
);
|
||||
@@ -123,6 +134,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
|
||||
rootInCollection(collectionId: string): Document[] {
|
||||
const collection = this.rootStore.collections.get(collectionId);
|
||||
|
||||
if (!collection) {
|
||||
return [];
|
||||
}
|
||||
@@ -156,7 +168,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
|
||||
get starred(): Document[] {
|
||||
return orderBy(
|
||||
filter(this.all, (d) => d.isStarred),
|
||||
this.all.filter((d) => d.isStarred),
|
||||
"updatedAt",
|
||||
"desc"
|
||||
);
|
||||
@@ -164,16 +176,14 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
|
||||
@computed
|
||||
get archived(): Document[] {
|
||||
return filter(
|
||||
orderBy(this.orderedData, "archivedAt", "desc"),
|
||||
return orderBy(this.orderedData, "archivedAt", "desc").filter(
|
||||
(d) => d.archivedAt && !d.deletedAt
|
||||
);
|
||||
}
|
||||
|
||||
@computed
|
||||
get deleted(): Document[] {
|
||||
return filter(
|
||||
orderBy(this.orderedData, "deletedAt", "desc"),
|
||||
return orderBy(this.orderedData, "deletedAt", "desc").filter(
|
||||
(d) => d.deletedAt
|
||||
);
|
||||
}
|
||||
@@ -194,10 +204,9 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
}
|
||||
|
||||
drafts = (
|
||||
options: {
|
||||
...PaginationParams,
|
||||
dateFilter?: "day" | "week" | "month" | "year",
|
||||
collectionId?: string,
|
||||
options: PaginationParams & {
|
||||
dateFilter?: DateFilter;
|
||||
collectionId?: string;
|
||||
} = {}
|
||||
): Document[] => {
|
||||
let drafts = filter(
|
||||
@@ -215,31 +224,36 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
}
|
||||
|
||||
if (options.collectionId) {
|
||||
drafts = filter(drafts, { collectionId: options.collectionId });
|
||||
drafts = filter(drafts, {
|
||||
collectionId: options.collectionId,
|
||||
});
|
||||
}
|
||||
|
||||
return drafts;
|
||||
};
|
||||
|
||||
@computed
|
||||
get active(): ?Document {
|
||||
get active(): Document | null | undefined {
|
||||
return this.rootStore.ui.activeDocumentId
|
||||
? this.data.get(this.rootStore.ui.activeDocumentId)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@action
|
||||
fetchBacklinks = async (documentId: string): Promise<?(Document[])> => {
|
||||
fetchBacklinks = async (documentId: string): Promise<void> => {
|
||||
const res = await client.post(`/documents.list`, {
|
||||
backlinkDocumentId: documentId,
|
||||
});
|
||||
invariant(res && res.data, "Document list not available");
|
||||
const { data } = res;
|
||||
|
||||
runInAction("DocumentsStore#fetchBacklinks", () => {
|
||||
data.forEach(this.add);
|
||||
this.addPolicies(res.policies);
|
||||
|
||||
this.backlinks.set(
|
||||
documentId,
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'doc' implicitly has an 'any' type.
|
||||
data.map((doc) => doc.id)
|
||||
);
|
||||
});
|
||||
@@ -255,12 +269,13 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
}
|
||||
|
||||
@action
|
||||
fetchChildDocuments = async (documentId: string): Promise<?(Document[])> => {
|
||||
fetchChildDocuments = async (documentId: string): Promise<void> => {
|
||||
const res = await client.post(`/documents.list`, {
|
||||
parentDocumentId: documentId,
|
||||
});
|
||||
invariant(res && res.data, "Document list not available");
|
||||
const { data } = res;
|
||||
|
||||
runInAction("DocumentsStore#fetchChildDocuments", () => {
|
||||
data.forEach(this.add);
|
||||
this.addPolicies(res.policies);
|
||||
@@ -269,9 +284,9 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
|
||||
@action
|
||||
fetchNamedPage = async (
|
||||
request: string = "list",
|
||||
options: ?Object
|
||||
): Promise<?(Document[])> => {
|
||||
request = "list",
|
||||
options: FetchPageParams | undefined
|
||||
): Promise<Document[] | undefined> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
@@ -289,27 +304,27 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
};
|
||||
|
||||
@action
|
||||
fetchArchived = async (options: ?PaginationParams): Promise<*> => {
|
||||
fetchArchived = async (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("archived", options);
|
||||
};
|
||||
|
||||
@action
|
||||
fetchDeleted = async (options: ?PaginationParams): Promise<*> => {
|
||||
fetchDeleted = async (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("deleted", options);
|
||||
};
|
||||
|
||||
@action
|
||||
fetchRecentlyUpdated = async (options: ?PaginationParams): Promise<*> => {
|
||||
fetchRecentlyUpdated = async (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("list", options);
|
||||
};
|
||||
|
||||
@action
|
||||
fetchTemplates = async (options: ?PaginationParams): Promise<*> => {
|
||||
fetchTemplates = async (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("list", { ...options, template: true });
|
||||
};
|
||||
|
||||
@action
|
||||
fetchAlphabetical = async (options: ?PaginationParams): Promise<*> => {
|
||||
fetchAlphabetical = async (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("list", {
|
||||
sort: "title",
|
||||
direction: "ASC",
|
||||
@@ -319,8 +334,8 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
|
||||
@action
|
||||
fetchLeastRecentlyUpdated = async (
|
||||
options: ?PaginationParams
|
||||
): Promise<*> => {
|
||||
options?: PaginationParams
|
||||
): Promise<any> => {
|
||||
return this.fetchNamedPage("list", {
|
||||
sort: "updatedAt",
|
||||
direction: "ASC",
|
||||
@@ -329,7 +344,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
};
|
||||
|
||||
@action
|
||||
fetchRecentlyPublished = async (options: ?PaginationParams): Promise<*> => {
|
||||
fetchRecentlyPublished = async (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("list", {
|
||||
sort: "publishedAt",
|
||||
direction: "DESC",
|
||||
@@ -338,27 +353,27 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
};
|
||||
|
||||
@action
|
||||
fetchRecentlyViewed = async (options: ?PaginationParams): Promise<*> => {
|
||||
fetchRecentlyViewed = async (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("viewed", options);
|
||||
};
|
||||
|
||||
@action
|
||||
fetchStarred = (options: ?PaginationParams): Promise<*> => {
|
||||
fetchStarred = (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("starred", options);
|
||||
};
|
||||
|
||||
@action
|
||||
fetchDrafts = (options: ?PaginationParams): Promise<*> => {
|
||||
fetchDrafts = (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("drafts", options);
|
||||
};
|
||||
|
||||
@action
|
||||
fetchPinned = (options: ?PaginationParams): Promise<*> => {
|
||||
fetchPinned = (options?: FetchParams): Promise<any> => {
|
||||
return this.fetchNamedPage("pinned", options);
|
||||
};
|
||||
|
||||
@action
|
||||
fetchOwned = (options: ?PaginationParams): Promise<*> => {
|
||||
fetchOwned = (options?: PaginationParams): Promise<any> => {
|
||||
return this.fetchNamedPage("list", options);
|
||||
};
|
||||
|
||||
@@ -368,7 +383,6 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
query,
|
||||
});
|
||||
invariant(res && res.data, "Search response should be available");
|
||||
|
||||
// add the documents and associated policies to the store
|
||||
res.data.forEach(this.add);
|
||||
this.addPolicies(res.policies);
|
||||
@@ -379,13 +393,13 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
search = async (
|
||||
query: string,
|
||||
options: {
|
||||
offset?: number,
|
||||
limit?: number,
|
||||
dateFilter?: "day" | "week" | "month" | "year",
|
||||
includeArchived?: boolean,
|
||||
includeDrafts?: boolean,
|
||||
collectionId?: string,
|
||||
userId?: string,
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
dateFilter?: DateFilter;
|
||||
includeArchived?: boolean;
|
||||
includeDrafts?: boolean;
|
||||
collectionId?: string;
|
||||
userId?: string;
|
||||
}
|
||||
): Promise<SearchResult[]> => {
|
||||
const compactedOptions = omitBy(options, (o) => !o);
|
||||
@@ -396,16 +410,17 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
invariant(res && res.data, "Search response should be available");
|
||||
|
||||
// add the documents and associated policies to the store
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'result' implicitly has an 'any' type.
|
||||
res.data.forEach((result) => this.add(result.document));
|
||||
this.addPolicies(res.policies);
|
||||
|
||||
// store a reference to the document model in the search cache instead
|
||||
// of the original result from the API.
|
||||
const results: SearchResult[] = compact(
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'result' implicitly has an 'any' type.
|
||||
res.data.map((result) => {
|
||||
const document = this.data.get(result.document.id);
|
||||
if (!document) return null;
|
||||
|
||||
return {
|
||||
ranking: result.ranking,
|
||||
context: result.context,
|
||||
@@ -413,53 +428,61 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
let existing = this.searchCache.get(query) || [];
|
||||
|
||||
const existing = this.searchCache.get(query) || [];
|
||||
// splice modifies any existing results, taking into account pagination
|
||||
existing.splice(options.offset || 0, options.limit || 0, ...results);
|
||||
|
||||
this.searchCache.set(query, existing);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@action
|
||||
prefetchDocument = (id: string) => {
|
||||
prefetchDocument = async (id: string) => {
|
||||
if (!this.data.get(id) && !this.getByUrl(id)) {
|
||||
return this.fetch(id, { prefetch: true });
|
||||
return this.fetch(id, {
|
||||
prefetch: true,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
@action
|
||||
templatize = async (id: string): Promise<?Document> => {
|
||||
const doc: ?Document = this.data.get(id);
|
||||
templatize = async (id: string): Promise<Document | null | undefined> => {
|
||||
const doc: Document | null | undefined = this.data.get(id);
|
||||
invariant(doc, "Document should exist");
|
||||
|
||||
if (doc.template) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await client.post("/documents.templatize", { id });
|
||||
const res = await client.post("/documents.templatize", {
|
||||
id,
|
||||
});
|
||||
invariant(res && res.data, "Document not available");
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
this.add(res.data);
|
||||
|
||||
return this.data.get(res.data.id);
|
||||
};
|
||||
|
||||
@action
|
||||
fetch = async (
|
||||
fetchWithSharedTree = async (
|
||||
id: string,
|
||||
options: FetchOptions = {}
|
||||
): Promise<{ document: ?Document, sharedTree?: NavigationNode }> => {
|
||||
): Promise<{
|
||||
document: Document;
|
||||
sharedTree?: NavigationNode;
|
||||
}> => {
|
||||
if (!options.prefetch) this.isFetching = true;
|
||||
|
||||
try {
|
||||
const doc: ?Document = this.data.get(id) || this.getByUrl(id);
|
||||
const doc: Document | null | undefined =
|
||||
this.data.get(id) || this.getByUrl(id);
|
||||
const policy = doc ? this.rootStore.policies.get(doc.id) : undefined;
|
||||
|
||||
if (doc && policy && !options.force) {
|
||||
return { document: doc };
|
||||
return {
|
||||
document: doc,
|
||||
};
|
||||
}
|
||||
|
||||
const res = await client.post("/documents.info", {
|
||||
@@ -467,13 +490,16 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
shareId: options.shareId,
|
||||
apiVersion: 2,
|
||||
});
|
||||
invariant(res && res.data, "Document not available");
|
||||
|
||||
invariant(res && res.data, "Document not available");
|
||||
this.addPolicies(res.policies);
|
||||
this.add(res.data.document);
|
||||
|
||||
const document = this.data.get(res.data.document.id);
|
||||
invariant(document, "Document not available");
|
||||
|
||||
return {
|
||||
document: this.data.get(res.data.document.id),
|
||||
document,
|
||||
sharedTree: res.data.sharedTree,
|
||||
};
|
||||
} finally {
|
||||
@@ -485,8 +511,8 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
move = async (
|
||||
documentId: string,
|
||||
collectionId: string,
|
||||
parentDocumentId: ?string,
|
||||
index: ?number
|
||||
parentDocumentId?: string | null,
|
||||
index?: number | null
|
||||
) => {
|
||||
this.movingDocumentId = documentId;
|
||||
|
||||
@@ -498,7 +524,6 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
index: index,
|
||||
});
|
||||
invariant(res && res.data, "Data not available");
|
||||
|
||||
res.data.documents.forEach(this.add);
|
||||
res.data.collections.forEach(this.rootStore.collections.add);
|
||||
this.addPolicies(res.policies);
|
||||
@@ -508,9 +533,8 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
};
|
||||
|
||||
@action
|
||||
duplicate = async (document: Document): * => {
|
||||
duplicate = async (document: Document): Promise<Document> => {
|
||||
const append = " (duplicate)";
|
||||
|
||||
const res = await client.post("/documents.create", {
|
||||
publish: !!document.publishedAt,
|
||||
parentDocumentId: document.parentDocumentId,
|
||||
@@ -523,10 +547,8 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
text: document.text,
|
||||
});
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
const collection = this.getCollectionForDocument(document);
|
||||
if (collection) collection.refresh();
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
return this.add(res.data);
|
||||
};
|
||||
@@ -534,8 +556,8 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
@action
|
||||
import = async (
|
||||
file: File,
|
||||
parentDocumentId: string,
|
||||
collectionId: string,
|
||||
parentDocumentId: string | null | undefined,
|
||||
collectionId: string | null | undefined,
|
||||
options: ImportOptions
|
||||
) => {
|
||||
// file.type can be an empty string sometimes
|
||||
@@ -553,28 +575,42 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
|
||||
const title = file.name.replace(/\.[^/.]+$/, "");
|
||||
const formData = new FormData();
|
||||
|
||||
[
|
||||
{ key: "parentDocumentId", value: parentDocumentId },
|
||||
{ key: "collectionId", value: collectionId },
|
||||
{ key: "title", value: title },
|
||||
{ key: "publish", value: options.publish },
|
||||
{ key: "file", value: file },
|
||||
{
|
||||
key: "parentDocumentId",
|
||||
value: parentDocumentId,
|
||||
},
|
||||
{
|
||||
key: "collectionId",
|
||||
value: collectionId,
|
||||
},
|
||||
{
|
||||
key: "title",
|
||||
value: title,
|
||||
},
|
||||
{
|
||||
key: "publish",
|
||||
value: options.publish,
|
||||
},
|
||||
{
|
||||
key: "file",
|
||||
value: file,
|
||||
},
|
||||
].forEach((info) => {
|
||||
if (typeof info.value === "string" && info.value) {
|
||||
formData.append(info.key, info.value);
|
||||
}
|
||||
|
||||
if (typeof info.value === "boolean") {
|
||||
formData.append(info.key, info.value.toString());
|
||||
}
|
||||
|
||||
if (info.value instanceof File) {
|
||||
formData.append(info.key, info.value);
|
||||
}
|
||||
});
|
||||
|
||||
const res = await client.post("/documents.import", formData);
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
return this.add(res.data);
|
||||
};
|
||||
@@ -582,7 +618,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
_add = this.add;
|
||||
|
||||
@action
|
||||
add = (item: Object) => {
|
||||
add = (item: Record<string, any>): Document => {
|
||||
const document = this._add(item);
|
||||
|
||||
if (item.starred !== undefined) {
|
||||
@@ -600,13 +636,21 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
}
|
||||
|
||||
@action
|
||||
async update(params: {
|
||||
id: string,
|
||||
title: string,
|
||||
text?: string,
|
||||
lastRevision: number,
|
||||
}) {
|
||||
const document = await super.update(params);
|
||||
async update(
|
||||
params: {
|
||||
id: string;
|
||||
title: string;
|
||||
text?: string;
|
||||
templateId?: string;
|
||||
},
|
||||
options?: {
|
||||
publish?: boolean;
|
||||
done?: boolean;
|
||||
autosave?: boolean;
|
||||
lastRevision: number;
|
||||
}
|
||||
) {
|
||||
const document = await super.update(params, options);
|
||||
|
||||
// Because the collection object contains the url and title
|
||||
// we need to ensure they are updated there as well.
|
||||
@@ -616,12 +660,17 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
}
|
||||
|
||||
@action
|
||||
async delete(document: Document, options?: {| permanent: boolean |}) {
|
||||
async delete(
|
||||
document: Document,
|
||||
options?: {
|
||||
permanent: boolean;
|
||||
}
|
||||
) {
|
||||
await super.delete(document, options);
|
||||
|
||||
// check to see if we have any shares related to this document already
|
||||
// loaded in local state. If so we can go ahead and remove those too.
|
||||
const share = this.rootStore.shares.getByDocumentId(document.id);
|
||||
|
||||
if (share) {
|
||||
this.rootStore.shares.remove(share.id);
|
||||
}
|
||||
@@ -640,7 +689,6 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
document.updateFromJson(res.data);
|
||||
this.addPolicies(res.policies);
|
||||
});
|
||||
|
||||
const collection = this.getCollectionForDocument(document);
|
||||
if (collection) collection.refresh();
|
||||
};
|
||||
@@ -648,7 +696,10 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
@action
|
||||
restore = async (
|
||||
document: Document,
|
||||
options: { revisionId?: string, collectionId?: string } = {}
|
||||
options: {
|
||||
revisionId?: string;
|
||||
collectionId?: string;
|
||||
} = {}
|
||||
) => {
|
||||
const res = await client.post("/documents.restore", {
|
||||
id: document.id,
|
||||
@@ -660,7 +711,6 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
document.updateFromJson(res.data);
|
||||
this.addPolicies(res.policies);
|
||||
});
|
||||
|
||||
const collection = this.getCollectionForDocument(document);
|
||||
if (collection) collection.refresh();
|
||||
};
|
||||
@@ -670,46 +720,52 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
const res = await client.post("/documents.unpublish", {
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
runInAction("Document#unpublish", () => {
|
||||
invariant(res && res.data, "Data should be available");
|
||||
document.updateFromJson(res.data);
|
||||
this.addPolicies(res.policies);
|
||||
});
|
||||
|
||||
const collection = this.getCollectionForDocument(document);
|
||||
if (collection) collection.refresh();
|
||||
};
|
||||
|
||||
pin = (document: Document) => {
|
||||
return client.post("/documents.pin", { id: document.id });
|
||||
return client.post("/documents.pin", {
|
||||
id: document.id,
|
||||
});
|
||||
};
|
||||
|
||||
unpin = (document: Document) => {
|
||||
return client.post("/documents.unpin", { id: document.id });
|
||||
return client.post("/documents.unpin", {
|
||||
id: document.id,
|
||||
});
|
||||
};
|
||||
|
||||
star = async (document: Document) => {
|
||||
this.starredIds.set(document.id, true);
|
||||
|
||||
try {
|
||||
return client.post("/documents.star", { id: document.id });
|
||||
return client.post("/documents.star", {
|
||||
id: document.id,
|
||||
});
|
||||
} catch (err) {
|
||||
this.starredIds.set(document.id, false);
|
||||
}
|
||||
};
|
||||
|
||||
unstar = (document: Document) => {
|
||||
unstar = async (document: Document) => {
|
||||
this.starredIds.set(document.id, false);
|
||||
|
||||
try {
|
||||
return client.post("/documents.unstar", { id: document.id });
|
||||
return client.post("/documents.unstar", {
|
||||
id: document.id,
|
||||
});
|
||||
} catch (err) {
|
||||
this.starredIds.set(document.id, false);
|
||||
}
|
||||
};
|
||||
|
||||
getByUrl = (url: string = ""): ?Document => {
|
||||
getByUrl = (url = ""): Document | null | undefined => {
|
||||
return find(this.orderedData, (doc) => url.endsWith(doc.urlId));
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import { sortBy, filter } from "lodash";
|
||||
import { computed } from "mobx";
|
||||
import Event from "models/Event";
|
||||
import BaseStore from "./BaseStore";
|
||||
import Event from "~/models/Event";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class EventsStore extends BaseStore<Event> {
|
||||
actions = ["list"];
|
||||
actions = [RPCAction.List];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, Event);
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import { orderBy } from "lodash";
|
||||
import { computed } from "mobx";
|
||||
import FileOperation from "models/FileOperation";
|
||||
import BaseStore from "./BaseStore";
|
||||
import FileOperation from "~/models/FileOperation";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class FileOperationsStore extends BaseStore<FileOperation> {
|
||||
actions = ["list", "info", "delete"];
|
||||
actions = [RPCAction.List, RPCAction.Info, RPCAction.Delete];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, FileOperation);
|
||||
@@ -1,29 +1,26 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { filter } from "lodash";
|
||||
import { action, runInAction } from "mobx";
|
||||
import GroupMembership from "models/GroupMembership";
|
||||
import BaseStore from "./BaseStore";
|
||||
import GroupMembership from "~/models/GroupMembership";
|
||||
import { PaginationParams } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
import type { PaginationParams } from "types";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
export default class GroupMembershipsStore extends BaseStore<GroupMembership> {
|
||||
actions = ["create", "delete"];
|
||||
actions = [RPCAction.Create, RPCAction.Delete];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, GroupMembership);
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (params: ?PaginationParams): Promise<*> => {
|
||||
fetchPage = async (params: PaginationParams | undefined): Promise<any> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/groups.memberships`, params);
|
||||
|
||||
invariant(res && res.data, "Data not available");
|
||||
|
||||
runInAction(`GroupMembershipsStore#fetchPage`, () => {
|
||||
res.data.users.forEach(this.rootStore.users.add);
|
||||
res.data.groupMemberships.forEach(this.add);
|
||||
@@ -36,28 +33,27 @@ export default class GroupMembershipsStore extends BaseStore<GroupMembership> {
|
||||
};
|
||||
|
||||
@action
|
||||
async create({ groupId, userId }: { groupId: string, userId: string }) {
|
||||
async create({ groupId, userId }: { groupId: string; userId: string }) {
|
||||
const res = await client.post("/groups.add_user", {
|
||||
id: groupId,
|
||||
userId,
|
||||
});
|
||||
invariant(res && res.data, "Group Membership data should be available");
|
||||
|
||||
res.data.users.forEach(this.rootStore.users.add);
|
||||
res.data.groups.forEach(this.rootStore.groups.add);
|
||||
res.data.groupMemberships.forEach(this.add);
|
||||
|
||||
const groupMemberships = res.data.groupMemberships.map(this.add);
|
||||
return groupMemberships[0];
|
||||
}
|
||||
|
||||
@action
|
||||
async delete({ groupId, userId }: { groupId: string, userId: string }) {
|
||||
async delete({ groupId, userId }: { groupId: string; userId: string }) {
|
||||
const res = await client.post("/groups.remove_user", {
|
||||
id: groupId,
|
||||
userId,
|
||||
});
|
||||
invariant(res && res.data, "Group Membership data should be available");
|
||||
|
||||
this.remove(`${userId}-${groupId}`);
|
||||
|
||||
runInAction(`GroupMembershipsStore#delete`, () => {
|
||||
res.data.groups.forEach(this.rootStore.groups.add);
|
||||
this.isLoaded = true;
|
||||
@@ -1,13 +1,14 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { filter } from "lodash";
|
||||
import { action, runInAction, computed } from "mobx";
|
||||
import naturalSort from "shared/utils/naturalSort";
|
||||
import Group from "models/Group";
|
||||
import naturalSort from "@shared/utils/naturalSort";
|
||||
import Group from "~/models/Group";
|
||||
import { PaginationParams } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import BaseStore from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
import type { PaginationParams } from "types";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
type FetchPageParams = PaginationParams & { query?: string };
|
||||
|
||||
export default class GroupsStore extends BaseStore<Group> {
|
||||
constructor(rootStore: RootStore) {
|
||||
@@ -20,14 +21,12 @@ export default class GroupsStore extends BaseStore<Group> {
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (params: ?PaginationParams): Promise<*> => {
|
||||
fetchPage = async (params: FetchPageParams | undefined): Promise<any> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/groups.list`, params);
|
||||
|
||||
invariant(res && res.data, "Data not available");
|
||||
|
||||
runInAction(`GroupsStore#fetchPage`, () => {
|
||||
this.addPolicies(res.policies);
|
||||
res.data.groups.forEach(this.add);
|
||||
@@ -40,7 +39,7 @@ export default class GroupsStore extends BaseStore<Group> {
|
||||
}
|
||||
};
|
||||
|
||||
inCollection = (collectionId: string, query: string) => {
|
||||
inCollection = (collectionId: string, query?: string) => {
|
||||
const memberships = filter(
|
||||
this.rootStore.collectionGroupMemberships.orderedData,
|
||||
(member) => member.collectionId === collectionId
|
||||
@@ -49,12 +48,11 @@ export default class GroupsStore extends BaseStore<Group> {
|
||||
const groups = filter(this.orderedData, (group) =>
|
||||
groupIds.includes(group.id)
|
||||
);
|
||||
|
||||
if (!query) return groups;
|
||||
return queriedGroups(groups, query);
|
||||
};
|
||||
|
||||
notInCollection = (collectionId: string, query: string = "") => {
|
||||
notInCollection = (collectionId: string, query = "") => {
|
||||
const memberships = filter(
|
||||
this.rootStore.collectionGroupMemberships.orderedData,
|
||||
(member) => member.collectionId === collectionId
|
||||
@@ -64,13 +62,12 @@ export default class GroupsStore extends BaseStore<Group> {
|
||||
this.orderedData,
|
||||
(group) => !groupIds.includes(group.id)
|
||||
);
|
||||
|
||||
if (!query) return groups;
|
||||
return queriedGroups(groups, query);
|
||||
};
|
||||
}
|
||||
|
||||
function queriedGroups(groups, query) {
|
||||
function queriedGroups(groups: Group[], query: string) {
|
||||
return filter(groups, (group) =>
|
||||
group.name.toLowerCase().match(query.toLowerCase())
|
||||
);
|
||||
@@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
import { filter } from "lodash";
|
||||
import { computed } from "mobx";
|
||||
|
||||
import naturalSort from "shared/utils/naturalSort";
|
||||
import BaseStore from "stores/BaseStore";
|
||||
import RootStore from "stores/RootStore";
|
||||
import Integration from "models/Integration";
|
||||
import naturalSort from "@shared/utils/naturalSort";
|
||||
import BaseStore from "~/stores/BaseStore";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import Integration from "~/models/Integration";
|
||||
|
||||
class IntegrationsStore extends BaseStore<Integration> {
|
||||
constructor(rootStore: RootStore) {
|
||||
@@ -19,7 +17,9 @@ class IntegrationsStore extends BaseStore<Integration> {
|
||||
|
||||
@computed
|
||||
get slackIntegrations(): Integration[] {
|
||||
return filter(this.orderedData, { service: "slack" });
|
||||
return filter(this.orderedData, {
|
||||
service: "slack",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { action, runInAction } from "mobx";
|
||||
import Membership from "models/Membership";
|
||||
import BaseStore from "./BaseStore";
|
||||
import Membership from "~/models/Membership";
|
||||
import { PaginationParams } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
import type { PaginationParams } from "types";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
export default class MembershipsStore extends BaseStore<Membership> {
|
||||
actions = ["create", "delete"];
|
||||
actions = [RPCAction.Create, RPCAction.Delete];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, Membership);
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (params: ?PaginationParams): Promise<*> => {
|
||||
fetchPage = async (params: PaginationParams | undefined): Promise<any> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/collections.memberships`, params);
|
||||
|
||||
invariant(res && res.data, "Data not available");
|
||||
|
||||
runInAction(`/collections.memberships`, () => {
|
||||
res.data.users.forEach(this.rootStore.users.add);
|
||||
res.data.memberships.forEach(this.add);
|
||||
@@ -40,9 +37,9 @@ export default class MembershipsStore extends BaseStore<Membership> {
|
||||
userId,
|
||||
permission,
|
||||
}: {
|
||||
collectionId: string,
|
||||
userId: string,
|
||||
permission: string,
|
||||
collectionId: string;
|
||||
userId: string;
|
||||
permission: string;
|
||||
}) {
|
||||
const res = await client.post("/collections.add_user", {
|
||||
id: collectionId,
|
||||
@@ -50,9 +47,10 @@ export default class MembershipsStore extends BaseStore<Membership> {
|
||||
permission,
|
||||
});
|
||||
invariant(res && res.data, "Membership data should be available");
|
||||
|
||||
res.data.users.forEach(this.rootStore.users.add);
|
||||
res.data.memberships.forEach(this.add);
|
||||
|
||||
const memberships = res.data.memberships.map(this.add);
|
||||
return memberships[0];
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -60,14 +58,13 @@ export default class MembershipsStore extends BaseStore<Membership> {
|
||||
collectionId,
|
||||
userId,
|
||||
}: {
|
||||
collectionId: string,
|
||||
userId: string,
|
||||
collectionId: string;
|
||||
userId: string;
|
||||
}) {
|
||||
await client.post("/collections.remove_user", {
|
||||
id: collectionId,
|
||||
userId,
|
||||
});
|
||||
|
||||
this.remove(`${userId}-${collectionId}`);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// @flow
|
||||
import { find } from "lodash";
|
||||
import NotificationSetting from "models/NotificationSetting";
|
||||
import BaseStore from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class NotificationSettingsStore extends BaseStore<NotificationSetting> {
|
||||
actions = ["list", "create", "delete"];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, NotificationSetting);
|
||||
}
|
||||
|
||||
getByEvent = (event: string) => {
|
||||
return find(this.orderedData, { event });
|
||||
};
|
||||
}
|
||||
20
app/stores/NotificationSettingsStore.ts
Normal file
20
app/stores/NotificationSettingsStore.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { find } from "lodash";
|
||||
import NotificationSetting from "~/models/NotificationSetting";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class NotificationSettingsStore extends BaseStore<
|
||||
NotificationSetting
|
||||
> {
|
||||
actions = [RPCAction.List, RPCAction.Create, RPCAction.Delete];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, NotificationSetting);
|
||||
}
|
||||
|
||||
getByEvent = (event: string) => {
|
||||
return find(this.orderedData, {
|
||||
event,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import Policy from "models/Policy";
|
||||
import Policy from "~/models/Policy";
|
||||
import BaseStore from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { filter } from "lodash";
|
||||
import { action, runInAction } from "mobx";
|
||||
import BaseStore from "stores/BaseStore";
|
||||
import RootStore from "stores/RootStore";
|
||||
import Revision from "models/Revision";
|
||||
import type { FetchOptions, PaginationParams } from "types";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
export default class RevisionsStore extends BaseStore<Revision> {
|
||||
actions = ["list"];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, Revision);
|
||||
}
|
||||
|
||||
getDocumentRevisions(documentId: string): Revision[] {
|
||||
let revisions = filter(this.orderedData, { documentId });
|
||||
const latestRevision = revisions[0];
|
||||
const document = this.rootStore.documents.get(documentId);
|
||||
|
||||
// There is no guarantee that we have a revision that represents the latest
|
||||
// state of the document. This pushes a fake revision in at the top if there
|
||||
// isn't one
|
||||
if (
|
||||
latestRevision &&
|
||||
document &&
|
||||
latestRevision.createdAt !== document.updatedAt
|
||||
) {
|
||||
revisions.unshift(
|
||||
new Revision({
|
||||
id: "latest",
|
||||
documentId: document.id,
|
||||
title: document.title,
|
||||
text: document.text,
|
||||
createdAt: document.updatedAt,
|
||||
createdBy: document.createdBy,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return revisions;
|
||||
}
|
||||
|
||||
@action
|
||||
fetch = async (id: string, options?: FetchOptions): Promise<?Revision> => {
|
||||
this.isFetching = true;
|
||||
invariant(id, "Id is required");
|
||||
|
||||
try {
|
||||
const rev = this.data.get(id);
|
||||
if (rev) return rev;
|
||||
|
||||
const res = await client.post("/revisions.info", {
|
||||
id,
|
||||
});
|
||||
invariant(res && res.data, "Revision not available");
|
||||
this.add(res.data);
|
||||
|
||||
runInAction("RevisionsStore#fetch", () => {
|
||||
this.isLoaded = true;
|
||||
});
|
||||
|
||||
return this.data.get(res.data.id);
|
||||
} finally {
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
fetchPage = async (options: ?PaginationParams): Promise<*> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post("/revisions.list", options);
|
||||
invariant(res && res.data, "Document revisions not available");
|
||||
runInAction("RevisionsStore#fetchPage", () => {
|
||||
res.data.forEach((revision) => this.add(revision));
|
||||
this.isLoaded = true;
|
||||
});
|
||||
return res.data;
|
||||
} finally {
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
66
app/stores/RevisionsStore.ts
Normal file
66
app/stores/RevisionsStore.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import invariant from "invariant";
|
||||
import { filter } from "lodash";
|
||||
import { action, runInAction } from "mobx";
|
||||
import BaseStore, { RPCAction } from "~/stores/BaseStore";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import Revision from "~/models/Revision";
|
||||
import { PaginationParams } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
|
||||
export default class RevisionsStore extends BaseStore<Revision> {
|
||||
actions = [RPCAction.List];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, Revision);
|
||||
}
|
||||
|
||||
getDocumentRevisions(documentId: string): Revision[] {
|
||||
const revisions = filter(this.orderedData, {
|
||||
documentId,
|
||||
});
|
||||
const latestRevision = revisions[0];
|
||||
const document = this.rootStore.documents.get(documentId);
|
||||
|
||||
// There is no guarantee that we have a revision that represents the latest
|
||||
// state of the document. This pushes a fake revision in at the top if there
|
||||
// isn't one
|
||||
if (
|
||||
latestRevision &&
|
||||
document &&
|
||||
latestRevision.createdAt !== document.updatedAt
|
||||
) {
|
||||
revisions.unshift(
|
||||
new Revision(
|
||||
{
|
||||
id: "latest",
|
||||
documentId: document.id,
|
||||
title: document.title,
|
||||
text: document.text,
|
||||
createdAt: document.updatedAt,
|
||||
createdBy: document.createdBy,
|
||||
},
|
||||
this
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return revisions;
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (options: PaginationParams | undefined): Promise<any> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post("/revisions.list", options);
|
||||
invariant(res && res.data, "Document revisions not available");
|
||||
runInAction("RevisionsStore#fetchPage", () => {
|
||||
res.data.forEach(this.add);
|
||||
this.isLoaded = true;
|
||||
});
|
||||
return res.data;
|
||||
} finally {
|
||||
this.isFetching = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import ApiKeysStore from "./ApiKeysStore";
|
||||
import AuthStore from "./AuthStore";
|
||||
import CollectionGroupMembershipsStore from "./CollectionGroupMembershipsStore";
|
||||
@@ -1,14 +1,18 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { sortBy, filter, find, isUndefined } from "lodash";
|
||||
import { action, computed } from "mobx";
|
||||
import Share from "models/Share";
|
||||
import BaseStore from "./BaseStore";
|
||||
import Share from "~/models/Share";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
export default class SharesStore extends BaseStore<Share> {
|
||||
actions = ["info", "list", "create", "update"];
|
||||
actions = [
|
||||
RPCAction.Info,
|
||||
RPCAction.List,
|
||||
RPCAction.Create,
|
||||
RPCAction.Update,
|
||||
];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, Share);
|
||||
@@ -26,23 +30,26 @@ export default class SharesStore extends BaseStore<Share> {
|
||||
|
||||
@action
|
||||
revoke = async (share: Share) => {
|
||||
await client.post("/shares.revoke", { id: share.id });
|
||||
await client.post("/shares.revoke", {
|
||||
id: share.id,
|
||||
});
|
||||
this.remove(share.id);
|
||||
};
|
||||
|
||||
@action
|
||||
async create(params: Object) {
|
||||
let item = this.getByDocumentId(params.documentId);
|
||||
async create(params: Record<string, any>) {
|
||||
const item = this.getByDocumentId(params.documentId);
|
||||
if (item) return item;
|
||||
|
||||
return super.create(params);
|
||||
}
|
||||
|
||||
@action
|
||||
async fetch(documentId: string, options?: Object = {}): Promise<*> {
|
||||
let item = this.getByDocumentId(documentId);
|
||||
async fetch(
|
||||
documentId: string,
|
||||
options: Record<string, any> = {}
|
||||
): Promise<any> {
|
||||
const item = this.getByDocumentId(documentId);
|
||||
if (item && !options.force) return item;
|
||||
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
@@ -50,10 +57,9 @@ export default class SharesStore extends BaseStore<Share> {
|
||||
documentId,
|
||||
apiVersion: 2,
|
||||
});
|
||||
|
||||
if (isUndefined(res)) return;
|
||||
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
this.addPolicies(res.policies);
|
||||
return res.data.shares.map(this.add);
|
||||
} finally {
|
||||
@@ -61,7 +67,7 @@ export default class SharesStore extends BaseStore<Share> {
|
||||
}
|
||||
}
|
||||
|
||||
getByDocumentParents = (documentId: string): ?Share => {
|
||||
getByDocumentParents = (documentId: string): Share | null | undefined => {
|
||||
const document = this.rootStore.documents.get(documentId);
|
||||
if (!document) return;
|
||||
|
||||
@@ -75,13 +81,16 @@ export default class SharesStore extends BaseStore<Share> {
|
||||
|
||||
for (const parentId of parentIds) {
|
||||
const share = this.getByDocumentId(parentId);
|
||||
|
||||
if (share && share.includeChildDocuments && share.published) {
|
||||
return share;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
getByDocumentId = (documentId: string): ?Share => {
|
||||
getByDocumentId = (documentId: string): Share | null | undefined => {
|
||||
return find(this.orderedData, (share) => share.documentId === documentId);
|
||||
};
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import stores from '.';
|
||||
|
||||
// Actions
|
||||
describe('ToastsStore', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = stores.toasts;
|
||||
});
|
||||
|
||||
test('#add should add messages', () => {
|
||||
expect(store.orderedData.length).toBe(0);
|
||||
store.showToast('first error');
|
||||
store.showToast('second error');
|
||||
expect(store.orderedData.length).toBe(2);
|
||||
});
|
||||
|
||||
test('#remove should remove messages', () => {
|
||||
store.toasts.clear();
|
||||
const id = store.showToast('first error');
|
||||
store.showToast('second error');
|
||||
expect(store.orderedData.length).toBe(2);
|
||||
store.hideToast(id);
|
||||
expect(store.orderedData.length).toBe(1);
|
||||
expect(store.orderedData[0].message).toBe('second error');
|
||||
});
|
||||
});
|
||||
25
app/stores/ToastsStore.test.ts
Normal file
25
app/stores/ToastsStore.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import stores from ".";
|
||||
|
||||
describe("ToastsStore", () => {
|
||||
const store = stores.toasts;
|
||||
|
||||
test("#add should add messages", () => {
|
||||
expect(store.orderedData.length).toBe(0);
|
||||
|
||||
store.showToast("first error");
|
||||
store.showToast("second error");
|
||||
expect(store.orderedData.length).toBe(2);
|
||||
});
|
||||
|
||||
test("#remove should remove messages", () => {
|
||||
store.toasts.clear();
|
||||
const id = store.showToast("first error");
|
||||
store.showToast("second error");
|
||||
|
||||
expect(store.orderedData.length).toBe(2);
|
||||
id && store.hideToast(id);
|
||||
|
||||
expect(store.orderedData.length).toBe(1);
|
||||
expect(store.orderedData[0].message).toBe("second error");
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,12 @@
|
||||
// @flow
|
||||
import { orderBy } from "lodash";
|
||||
import { observable, action, computed } from "mobx";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import type { Toast, ToastOptions } from "types";
|
||||
import { Toast, ToastOptions } from "~/types";
|
||||
|
||||
export default class ToastsStore {
|
||||
@observable toasts: Map<string, Toast> = new Map();
|
||||
@observable
|
||||
toasts: Map<string, Toast> = new Map();
|
||||
|
||||
lastToastId: string;
|
||||
|
||||
@action
|
||||
@@ -16,8 +17,8 @@ export default class ToastsStore {
|
||||
}
|
||||
) => {
|
||||
if (!message) return;
|
||||
|
||||
const lastToast = this.toasts.get(this.lastToastId);
|
||||
|
||||
if (lastToast && lastToast.message === message) {
|
||||
this.toasts.set(this.lastToastId, {
|
||||
...lastToast,
|
||||
@@ -1,36 +1,69 @@
|
||||
// @flow
|
||||
import { action, autorun, computed, observable } from "mobx";
|
||||
import { light as defaultTheme } from "shared/theme";
|
||||
import Collection from "models/Collection";
|
||||
import Document from "models/Document";
|
||||
import { light as defaultTheme } from "@shared/theme";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
import { ConnectionStatus } from "~/scenes/Document/components/MultiplayerEditor";
|
||||
|
||||
const UI_STORE = "UI_STORE";
|
||||
|
||||
type Status = "connecting" | "connected" | "disconnected" | void;
|
||||
export enum Theme {
|
||||
Light = "light",
|
||||
Dark = "dark",
|
||||
System = "system",
|
||||
}
|
||||
|
||||
export enum SystemTheme {
|
||||
Light = "light",
|
||||
Dark = "dark",
|
||||
}
|
||||
|
||||
class UiStore {
|
||||
// has the user seen the prompt to change the UI language and actioned it
|
||||
@observable languagePromptDismissed: boolean;
|
||||
@observable
|
||||
languagePromptDismissed: boolean | undefined;
|
||||
|
||||
// theme represents the users UI preference (defaults to system)
|
||||
@observable theme: "light" | "dark" | "system";
|
||||
@observable
|
||||
theme: Theme;
|
||||
|
||||
// systemTheme represents the system UI theme (Settings -> General in macOS)
|
||||
@observable systemTheme: "light" | "dark";
|
||||
@observable activeDocumentId: ?string;
|
||||
@observable activeCollectionId: ?string;
|
||||
@observable progressBarVisible: boolean = false;
|
||||
@observable isEditing: boolean = false;
|
||||
@observable tocVisible: boolean = false;
|
||||
@observable mobileSidebarVisible: boolean = false;
|
||||
@observable sidebarWidth: number;
|
||||
@observable sidebarCollapsed: boolean = false;
|
||||
@observable sidebarIsResizing: boolean = false;
|
||||
@observable multiplayerStatus: Status;
|
||||
@observable
|
||||
systemTheme: SystemTheme;
|
||||
|
||||
@observable
|
||||
activeDocumentId: string | undefined;
|
||||
|
||||
@observable
|
||||
activeCollectionId: string | undefined;
|
||||
|
||||
@observable
|
||||
progressBarVisible = false;
|
||||
|
||||
@observable
|
||||
isEditing = false;
|
||||
|
||||
@observable
|
||||
tocVisible = false;
|
||||
|
||||
@observable
|
||||
mobileSidebarVisible = false;
|
||||
|
||||
@observable
|
||||
sidebarWidth: number;
|
||||
|
||||
@observable
|
||||
sidebarCollapsed = false;
|
||||
|
||||
@observable
|
||||
sidebarIsResizing = false;
|
||||
|
||||
@observable
|
||||
multiplayerStatus: ConnectionStatus;
|
||||
|
||||
constructor() {
|
||||
// Rehydrate
|
||||
let data = {};
|
||||
let data: Partial<UiStore> = {};
|
||||
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(UI_STORE) || "{}");
|
||||
} catch (_) {
|
||||
@@ -43,10 +76,13 @@ class UiStore {
|
||||
"(prefers-color-scheme: dark)"
|
||||
);
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'event' implicitly has an 'any' type.
|
||||
const setSystemTheme = (event) => {
|
||||
this.systemTheme = event.matches ? "dark" : "light";
|
||||
this.systemTheme = event.matches ? SystemTheme.Dark : SystemTheme.Light;
|
||||
};
|
||||
|
||||
setSystemTheme(colorSchemeQueryList);
|
||||
|
||||
if (colorSchemeQueryList.addListener) {
|
||||
colorSchemeQueryList.addListener(setSystemTheme);
|
||||
}
|
||||
@@ -54,10 +90,10 @@ class UiStore {
|
||||
|
||||
// persisted keys
|
||||
this.languagePromptDismissed = data.languagePromptDismissed;
|
||||
this.sidebarCollapsed = data.sidebarCollapsed;
|
||||
this.sidebarCollapsed = !!data.sidebarCollapsed;
|
||||
this.sidebarWidth = data.sidebarWidth || defaultTheme.sidebarWidth;
|
||||
this.tocVisible = data.tocVisible;
|
||||
this.theme = data.theme || "system";
|
||||
this.tocVisible = !!data.tocVisible;
|
||||
this.theme = data.theme || Theme.System;
|
||||
|
||||
autorun(() => {
|
||||
try {
|
||||
@@ -69,7 +105,7 @@ class UiStore {
|
||||
}
|
||||
|
||||
@action
|
||||
setTheme = (theme: "light" | "dark" | "system") => {
|
||||
setTheme = (theme: Theme) => {
|
||||
this.theme = theme;
|
||||
|
||||
if (window.localStorage) {
|
||||
@@ -97,7 +133,7 @@ class UiStore {
|
||||
};
|
||||
|
||||
@action
|
||||
setMultiplayerStatus = (status: Status): void => {
|
||||
setMultiplayerStatus = (status: ConnectionStatus): void => {
|
||||
this.multiplayerStatus = status;
|
||||
};
|
||||
|
||||
@@ -177,7 +213,7 @@ class UiStore {
|
||||
};
|
||||
|
||||
@computed
|
||||
get resolvedTheme(): "dark" | "light" {
|
||||
get resolvedTheme(): Theme | SystemTheme {
|
||||
if (this.theme === "system") {
|
||||
return this.systemTheme;
|
||||
}
|
||||
@@ -1,22 +1,29 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { filter, orderBy } from "lodash";
|
||||
import { observable, computed, action, runInAction } from "mobx";
|
||||
import type { Role } from "shared/types";
|
||||
import User from "models/User";
|
||||
import { Role } from "@shared/types";
|
||||
import User from "~/models/User";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import BaseStore from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
import { client } from "utils/ApiClient";
|
||||
|
||||
export default class UsersStore extends BaseStore<User> {
|
||||
@observable counts: {
|
||||
active: number,
|
||||
admins: number,
|
||||
all: number,
|
||||
invited: number,
|
||||
suspended: number,
|
||||
viewers: number,
|
||||
} = {};
|
||||
@observable
|
||||
counts: {
|
||||
active: number;
|
||||
admins: number;
|
||||
all: number;
|
||||
invited: number;
|
||||
suspended: number;
|
||||
viewers: number;
|
||||
} = {
|
||||
active: 0,
|
||||
admins: 0,
|
||||
all: 0,
|
||||
invited: 0,
|
||||
suspended: 0,
|
||||
viewers: 0,
|
||||
};
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, User);
|
||||
@@ -24,40 +31,39 @@ export default class UsersStore extends BaseStore<User> {
|
||||
|
||||
@computed
|
||||
get active(): User[] {
|
||||
return filter(
|
||||
this.orderedData,
|
||||
return this.orderedData.filter(
|
||||
(user) => !user.isSuspended && user.lastActiveAt
|
||||
);
|
||||
}
|
||||
|
||||
@computed
|
||||
get suspended(): User[] {
|
||||
return filter(this.orderedData, (user) => user.isSuspended);
|
||||
return this.orderedData.filter((user) => user.isSuspended);
|
||||
}
|
||||
|
||||
@computed
|
||||
get activeOrInvited(): User[] {
|
||||
return filter(this.orderedData, (user) => !user.isSuspended);
|
||||
return this.orderedData.filter((user) => !user.isSuspended);
|
||||
}
|
||||
|
||||
@computed
|
||||
get invited(): User[] {
|
||||
return filter(this.orderedData, (user) => user.isInvited);
|
||||
return this.orderedData.filter((user) => user.isInvited);
|
||||
}
|
||||
|
||||
@computed
|
||||
get admins(): User[] {
|
||||
return filter(this.orderedData, (user) => user.isAdmin);
|
||||
return this.orderedData.filter((user) => user.isAdmin);
|
||||
}
|
||||
|
||||
@computed
|
||||
get viewers(): User[] {
|
||||
return filter(this.orderedData, (user) => user.isViewer);
|
||||
return this.orderedData.filter((user) => user.isViewer);
|
||||
}
|
||||
|
||||
@computed
|
||||
get all(): User[] {
|
||||
return filter(this.orderedData, (user) => user.lastActiveAt);
|
||||
return this.orderedData.filter((user) => user.lastActiveAt);
|
||||
}
|
||||
|
||||
@computed
|
||||
@@ -110,8 +116,16 @@ export default class UsersStore extends BaseStore<User> {
|
||||
};
|
||||
|
||||
@action
|
||||
invite = async (invites: { email: string, name: string, role: Role }[]) => {
|
||||
const res = await client.post(`/users.invite`, { invites });
|
||||
invite = async (
|
||||
invites: {
|
||||
email: string;
|
||||
name: string;
|
||||
role: Role;
|
||||
}[]
|
||||
) => {
|
||||
const res = await client.post(`/users.invite`, {
|
||||
invites,
|
||||
});
|
||||
invariant(res && res.data, "Data should be available");
|
||||
runInAction(`invite`, () => {
|
||||
res.data.users.forEach(this.add);
|
||||
@@ -122,32 +136,39 @@ export default class UsersStore extends BaseStore<User> {
|
||||
};
|
||||
|
||||
@action
|
||||
fetchCounts = async (teamId: string): Promise<*> => {
|
||||
const res = await client.post(`/users.count`, { teamId });
|
||||
fetchCounts = async (teamId: string): Promise<any> => {
|
||||
const res = await client.post(`/users.count`, {
|
||||
teamId,
|
||||
});
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
this.counts = res.data.counts;
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@action
|
||||
async delete(user: User, options: Object = {}) {
|
||||
async delete(user: User, options: Record<string, any> = {}) {
|
||||
super.delete(user, options);
|
||||
|
||||
if (!user.isSuspended && user.lastActiveAt) {
|
||||
this.counts.active -= 1;
|
||||
}
|
||||
|
||||
if (user.isInvited) {
|
||||
this.counts.invited -= 1;
|
||||
}
|
||||
|
||||
if (user.isAdmin) {
|
||||
this.counts.admins -= 1;
|
||||
}
|
||||
|
||||
if (user.isSuspended) {
|
||||
this.counts.suspended -= 1;
|
||||
}
|
||||
|
||||
if (user.isViewer) {
|
||||
this.counts.viewers -= 1;
|
||||
}
|
||||
|
||||
this.counts.all -= 1;
|
||||
}
|
||||
|
||||
@@ -155,27 +176,32 @@ export default class UsersStore extends BaseStore<User> {
|
||||
updateCounts = (to: Role, from: Role) => {
|
||||
if (to === "admin") {
|
||||
this.counts.admins += 1;
|
||||
|
||||
if (from === "viewer") {
|
||||
this.counts.viewers -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (to === "viewer") {
|
||||
this.counts.viewers += 1;
|
||||
|
||||
if (from === "admin") {
|
||||
this.counts.admins -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (to === "member") {
|
||||
if (from === "viewer") {
|
||||
this.counts.viewers -= 1;
|
||||
}
|
||||
|
||||
if (from === "admin") {
|
||||
this.counts.admins -= 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
notInCollection = (collectionId: string, query: string = "") => {
|
||||
notInCollection = (collectionId: string, query = "") => {
|
||||
const memberships = filter(
|
||||
this.rootStore.memberships.orderedData,
|
||||
(member) => member.collectionId === collectionId
|
||||
@@ -185,12 +211,11 @@ export default class UsersStore extends BaseStore<User> {
|
||||
this.activeOrInvited,
|
||||
(user) => !userIds.includes(user.id)
|
||||
);
|
||||
|
||||
if (!query) return users;
|
||||
return queriedUsers(users, query);
|
||||
};
|
||||
|
||||
inCollection = (collectionId: string, query: string) => {
|
||||
inCollection = (collectionId: string, query?: string) => {
|
||||
const memberships = filter(
|
||||
this.rootStore.memberships.orderedData,
|
||||
(member) => member.collectionId === collectionId
|
||||
@@ -199,12 +224,11 @@ export default class UsersStore extends BaseStore<User> {
|
||||
const users = filter(this.activeOrInvited, (user) =>
|
||||
userIds.includes(user.id)
|
||||
);
|
||||
|
||||
if (!query) return users;
|
||||
return queriedUsers(users, query);
|
||||
};
|
||||
|
||||
notInGroup = (groupId: string, query: string = "") => {
|
||||
notInGroup = (groupId: string, query = "") => {
|
||||
const memberships = filter(
|
||||
this.rootStore.groupMemberships.orderedData,
|
||||
(member) => member.groupId === groupId
|
||||
@@ -214,12 +238,11 @@ export default class UsersStore extends BaseStore<User> {
|
||||
this.activeOrInvited,
|
||||
(user) => !userIds.includes(user.id)
|
||||
);
|
||||
|
||||
if (!query) return users;
|
||||
return queriedUsers(users, query);
|
||||
};
|
||||
|
||||
inGroup = (groupId: string, query: string) => {
|
||||
inGroup = (groupId: string, query?: string) => {
|
||||
const groupMemberships = filter(
|
||||
this.rootStore.groupMemberships.orderedData,
|
||||
(member) => member.groupId === groupId
|
||||
@@ -228,7 +251,6 @@ export default class UsersStore extends BaseStore<User> {
|
||||
const users = filter(this.activeOrInvited, (user) =>
|
||||
userIds.includes(user.id)
|
||||
);
|
||||
|
||||
if (!query) return users;
|
||||
return queriedUsers(users, query);
|
||||
};
|
||||
@@ -239,7 +261,6 @@ export default class UsersStore extends BaseStore<User> {
|
||||
to,
|
||||
});
|
||||
invariant(res && res.data, "Data should be available");
|
||||
|
||||
runInAction(`UsersStore#${action}`, () => {
|
||||
this.addPolicies(res.policies);
|
||||
this.add(res.data);
|
||||
@@ -247,7 +268,7 @@ export default class UsersStore extends BaseStore<User> {
|
||||
};
|
||||
}
|
||||
|
||||
function queriedUsers(users, query) {
|
||||
function queriedUsers(users: User[], query: string) {
|
||||
return filter(users, (user) =>
|
||||
user.name.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import { reduce, filter, find, orderBy } from "lodash";
|
||||
import View from "models/View";
|
||||
import BaseStore from "./BaseStore";
|
||||
import View from "~/models/View";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class ViewsStore extends BaseStore<View> {
|
||||
actions = ["list", "create"];
|
||||
actions = [RPCAction.List, RPCAction.Create];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, View);
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import RootStore from "stores/RootStore";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
|
||||
const stores = new RootStore();
|
||||
|
||||
Reference in New Issue
Block a user