feat: Collection admins (#5273

* Split permissions for reading documents from updating collection

* fix: Admins should have collection read permission, tests

* tsc

* Add admin option to permission selector

* Combine publish and create permissions, update -> createDocuments where appropriate

* Plural -> singular

* wip

* Quick version of collection structure loading, will revisit

* Remove documentIds method

* stash

* fixing tests to account for admin creation

* Add self-hosted migration

* fix: Allow groups to have admin permission

* Prefetch collection documents

* fix: Document explorer (move/publish) not working with async documents

* fix: Cannot re-parent document to collection by drag and drop

* fix: Cannot drag to import into collection item without admin permission

* Remove unused isEditor getter
This commit is contained in:
Tom Moor
2023-04-30 09:38:47 -04:00
committed by GitHub
parent 2942e9c78e
commit d8b4fef554
44 changed files with 799 additions and 535 deletions

View File

@@ -60,7 +60,6 @@ export default abstract class BaseModel {
};
updateFromJson = (data: any) => {
// const isNew = !data.id && !this.id && this.isNew;
set(this, { ...data, isNew: false });
this.persistedAttributes = this.toAPI();
};

View File

@@ -1,5 +1,5 @@
import { trim } from "lodash";
import { action, computed, observable } from "mobx";
import { action, computed, observable, runInAction } from "mobx";
import {
CollectionPermission,
FileOperationFormat,
@@ -18,6 +18,8 @@ export default class Collection extends ParanoidModel {
@observable
isSaving: boolean;
isFetching = false;
@Field
@observable
id: string;
@@ -57,32 +59,34 @@ export default class Collection extends ParanoidModel {
direction: "asc" | "desc";
};
documents: NavigationNode[];
@observable
documents?: NavigationNode[];
url: string;
urlId: string;
@computed
get isEmpty(): boolean {
get isEmpty(): boolean | undefined {
if (!this.documents) {
return undefined;
}
return (
this.documents.length === 0 &&
this.store.rootStore.documents.inCollection(this.id).length === 0
);
}
@computed
get documentIds(): string[] {
const results: string[] = [];
const travelNodes = (nodes: NavigationNode[]) =>
nodes.forEach((node) => {
results.push(node.id);
travelNodes(node.children);
});
travelNodes(this.documents);
return results;
/**
* Convenience method to return if a collection is considered private.
* This means that a membership is required to view it rather than just being
* a workspace member.
*
* @returns boolean
*/
get isPrivate(): boolean {
return !this.permission;
}
@computed
@@ -98,10 +102,35 @@ export default class Collection extends ParanoidModel {
}
@computed
get sortedDocuments() {
get sortedDocuments(): NavigationNode[] | undefined {
if (!this.documents) {
return undefined;
}
return sortNavigationNodes(this.documents, this.sort);
}
fetchDocuments = async (options?: { force: boolean }) => {
if (this.isFetching) {
return;
}
if (this.documents && options?.force !== true) {
return;
}
try {
this.isFetching = true;
const { data } = await client.post("/collections.documents", {
id: this.id,
});
runInAction("Collection#fetchDocuments", () => {
this.documents = data;
});
} finally {
this.isFetching = false;
}
};
/**
* Updates the document identified by the given id in the collection in memory.
* Does not update the document in the database.
@@ -110,6 +139,10 @@ export default class Collection extends ParanoidModel {
*/
@action
updateDocument(document: Pick<Document, "id" | "title" | "url">) {
if (!this.documents) {
throw new Error("Collection documents not loaded");
}
const travelNodes = (nodes: NavigationNode[]) =>
nodes.forEach((node) => {
if (node.id === document.id) {
@@ -131,6 +164,10 @@ export default class Collection extends ParanoidModel {
*/
@action
removeDocument(documentId: string) {
if (!this.documents) {
throw new Error("Collection documents not loaded");
}
this.documents = this.documents.filter(function f(node): boolean {
if (node.id === documentId) {
return false;
@@ -163,7 +200,7 @@ export default class Collection extends ParanoidModel {
});
};
if (this.documents) {
if (this.sortedDocuments) {
travelNodes(this.sortedDocuments);
}

View File

@@ -1,4 +1,4 @@
import { computed } from "mobx";
import { observable } from "mobx";
import { CollectionPermission } from "@shared/types";
import BaseModel from "./BaseModel";
@@ -9,12 +9,8 @@ class CollectionGroupMembership extends BaseModel {
collectionId: string;
@observable
permission: CollectionPermission;
@computed
get isEditor(): boolean {
return this.permission === CollectionPermission.ReadWrite;
}
}
export default CollectionGroupMembership;

View File

@@ -1,4 +1,4 @@
import { computed } from "mobx";
import { observable } from "mobx";
import { CollectionPermission } from "@shared/types";
import BaseModel from "./BaseModel";
@@ -9,12 +9,8 @@ class Membership extends BaseModel {
collectionId: string;
@observable
permission: CollectionPermission;
@computed
get isEditor(): boolean {
return this.permission === CollectionPermission.ReadWrite;
}
}
export default Membership;