plane/web/store/page.store.ts
Henit Chobisa 4416419c9b feat: New Pages with Enhanced Document Editor Packages made over Editor Core 📝 (#2784)
* fix: page transaction model

* fix: page transaction model

* feat: updated ui for page route

* chore: initailized `document-editor` package for plane

* fix: format persistence while pasting markdown in editor

* feat: Inititalized Document-Editor and Editor with Ref

* feat: added tooltip component and slash command for editor

* feat: added `document-editor` extensions

* feat: added custom search component for embedding labels

* feat: added top bar menu component

* feat: created document-editor exposed components

* feat: integrated `document-editor` in `pages` route

* chore: updated dependencies

* feat: merge conflict resolution

* chore: modified configuration for document editor

* feat: added content browser menu for document editor summary

* feat: added fixed menu and editor instances

* feat: added document edittor instances and summary table

* feat: implemented document-editor in PageDetail

* chore: css and export fixes

* fix: migration and optimisation

* fix: added `on_create` hook in the core editor

* feat: added conditional menu bar action in document-editor

* feat: added menu actions from single page view

* feat: added services for archiving, unarchiving and retriving archived pages

* feat: added services for page archives

* feat: implemented page archives in page list view

* feat: implemented page archives in document-editor

* feat: added editor marking hook

* chore: seperated editor header from the main content

* chore: seperated editor summary utilities from the main editor

* chore: refactored necessary components from the document editor

* chore: removed summary sidebar component from the main content editor

* chore: removed scrollSummaryDependency from Header and Sidebar

* feat: seperated page renderer as a seperate component

* chore: seperated page_renderer and sidebar as component from index

* feat: added locked property to IPage type

* feat: added lock/unlock services in page service

* chore: seperated DocumentDetails as exported interface from index

* feat: seperated document editor configs as seperate interfaces

* chore: seperated menu options from the editor header component

* fix: fixed page_lock performing lock/unlock operation on queryset instead of single instance

* fix: css positioning changes

* feat: added archive/lock alert labels

* feat: added boolean props in menu-actions/options

* feat: added lock/unlock & archive/unarchive services

* feat: added on update mutations for archived pages in page-view

* feat: added archive/lock on_update mutations in single page vieq

* feat: exported readonly editor for locked pages

* chore: seperated kanban menu props and saved over passing redundant data

* fix: readonly editor not generating markings on first render

* fix: cheveron overflowing from editor-header

* chore: removed unused utility actions

* fix: enabled sidebar view by default

* feat: removed locking on pages in archived state

* feat: added indentation in heading component

* fix: button classnames in vertical dropdowns

* feat: added `last_archived_at` and `last_edited_at` details in editor-header

* feat: changed types for archived updates and document last updates

* feat: updated editor and header props

* feat: updated queryset according to new page query format

* feat: added parameters in page view for shared / private pages

* feat: updated other-page-view to shared page view && same with private pages

* feat: added page-view as shared / private

* fix: replaced deleting to archiving for pages

* feat: handle restoring of page from archived section from list view

* feat: made previledge based option render for pages

* feat: removed layout view for page list view

* feat: linting changes

* fix: adding mobx changes to pages

* fix: removed uneccessary migrations

* fix: mobx store changes

* fix: adding date-fns pacakge

* fix: updating yarn lock

* fix: removing unneccessary method params

* chore: added access specifier to the create/update page modal

* fix: tab view layout changes

* chore: delete endpoint for page

* fix: page actions, including- archive, favorite, access control, delete

* chore: remove archive page modal

* fix: build errors

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-12-07 19:59:35 +05:30

338 lines
10 KiB
TypeScript

import { observable, action, computed, makeObservable, runInAction } from "mobx";
import isYesterday from "date-fns/isYesterday";
import isToday from "date-fns/isToday";
import isThisWeek from "date-fns/isThisWeek";
// services
import { ProjectService } from "services/project";
import { PageService } from "services/page.service";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
// types
import { RootStore } from "./root";
import { IPage, IRecentPages } from "types";
export interface IPageStore {
loader: boolean;
error: any | null;
pages: {
[project_id: string]: IPage[];
};
archivedPages: {
[project_id: string]: IPage[];
};
//computed
projectPages: IPage[] | undefined;
recentProjectPages: IRecentPages | undefined;
favoriteProjectPages: IPage[] | undefined;
privateProjectPages: IPage[] | undefined;
sharedProjectPages: IPage[] | undefined;
archivedProjectPages: IPage[] | undefined;
// actions
fetchPages: (workspaceSlug: string, projectId: string) => Promise<IPage[]>;
createPage: (workspaceSlug: string, projectId: string, data: Partial<IPage>) => Promise<IPage>;
updatePage: (workspaceSlug: string, projectId: string, pageId: string, data: Partial<IPage>) => Promise<IPage>;
deletePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
addToFavorites: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
removeFromFavorites: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
makePublic: (workspaceSlug: string, projectId: string, pageId: string) => Promise<IPage>;
makePrivate: (workspaceSlug: string, projectId: string, pageId: string) => Promise<IPage>;
fetchArchivedPages: (workspaceSlug: string, projectId: string) => Promise<IPage[]>;
archivePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
restorePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
}
export class PageStore implements IPageStore {
loader: boolean = false;
error: any | null = null;
pages: { [project_id: string]: IPage[] } = {};
archivedPages: { [project_id: string]: IPage[] } = {};
// root store
rootStore;
// service
projectService;
pageService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// observable
loader: observable,
error: observable,
pages: observable.ref,
archivedPages: observable.ref,
// computed
projectPages: computed,
recentProjectPages: computed,
favoriteProjectPages: computed,
privateProjectPages: computed,
sharedProjectPages: computed,
archivedProjectPages: computed,
// action
fetchPages: action,
createPage: action,
updatePage: action,
deletePage: action,
addToFavorites: action,
removeFromFavorites: action,
makePublic: action,
makePrivate: action,
archivePage: action,
restorePage: action,
fetchArchivedPages: action,
});
this.rootStore = _rootStore;
this.projectService = new ProjectService();
this.pageService = new PageService();
}
get projectPages() {
if (!this.rootStore.project.projectId) return;
return this.pages?.[this.rootStore.project.projectId] || [];
}
get recentProjectPages() {
if (!this.rootStore.project.projectId) return;
const data: IRecentPages = { today: [], yesterday: [], this_week: [] };
data["today"] = this.pages[this.rootStore.project.projectId]?.filter((p) => isToday(new Date(p.created_at))) || [];
data["yesterday"] =
this.pages[this.rootStore.project.projectId]?.filter((p) => isYesterday(new Date(p.created_at))) || [];
data["this_week"] =
this.pages[this.rootStore.project.projectId]?.filter((p) => isThisWeek(new Date(p.created_at))) || [];
return data;
}
get favoriteProjectPages() {
if (!this.rootStore.project.projectId) return;
return this.pages[this.rootStore.project.projectId]?.filter((p) => p.is_favorite);
}
get privateProjectPages() {
if (!this.rootStore.project.projectId) return;
return this.pages[this.rootStore.project.projectId]?.filter((p) => p.access === 1);
}
get sharedProjectPages() {
if (!this.rootStore.project.projectId) return;
return this.pages[this.rootStore.project.projectId]?.filter((p) => p.access === 0);
}
get archivedProjectPages() {
if (!this.rootStore.project.projectId) return;
return this.archivedPages[this.rootStore.project.projectId];
}
addToFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => {
try {
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: this.pages[projectId].map((page) => {
if (page.id === pageId) {
return { ...page, is_favorite: true };
}
return page;
}),
};
});
await this.pageService.addPageToFavorites(workspaceSlug, projectId, pageId);
} catch (error) {
throw error;
}
};
removeFromFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => {
try {
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: this.pages[projectId].map((page) => {
if (page.id === pageId) {
return { ...page, is_favorite: false };
}
return page;
}),
};
});
await this.pageService.removePageFromFavorites(workspaceSlug, projectId, pageId);
} catch (error) {
throw error;
}
};
fetchPages = async (workspaceSlug: string, projectId: string) => {
try {
this.loader = true;
this.error = null;
const response = await this.pageService.getPagesWithParams(workspaceSlug, projectId, "all");
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: response,
};
this.loader = false;
this.error = null;
});
return response;
} catch (error) {
console.error("Failed to fetch project pages in project store", error);
this.loader = false;
this.error = error;
throw error;
}
};
createPage = async (workspaceSlug: string, projectId: string, data: Partial<IPage>) => {
try {
const response = await this.pageService.createPage(workspaceSlug, projectId, data);
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: [...this.pages[projectId], response],
};
});
return response;
} catch (error) {
throw error;
}
};
updatePage = async (workspaceSlug: string, projectId: string, pageId: string, data: Partial<IPage>) => {
try {
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: this.pages[projectId]?.map((page) => {
if (page.id === pageId) {
return { ...page, ...data };
}
return page;
}),
};
});
const response = await this.pageService.patchPage(workspaceSlug, projectId, pageId, data);
return response;
} catch (error) {
throw error;
}
};
deletePage = async (workspaceSlug: string, projectId: string, pageId: string) => {
try {
runInAction(() => {
this.archivedPages = {
...this.archivedPages,
[projectId]: this.archivedPages[projectId]?.filter((page) => page.id !== pageId),
};
});
const response = await this.pageService.deletePage(workspaceSlug, projectId, pageId);
return response;
} catch (error) {
throw error;
}
};
makePublic = async (workspaceSlug: string, projectId: string, pageId: string) => {
try {
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: this.pages[projectId]?.map((page) => ({
...page,
access: page.id === pageId ? 0 : page.access,
})),
};
});
const response = await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 0 });
return response;
} catch (error) {
throw error;
}
};
makePrivate = async (workspaceSlug: string, projectId: string, pageId: string) => {
try {
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: this.pages[projectId]?.map((page) => ({
...page,
access: page.id === pageId ? 1 : page.access,
})),
};
});
const response = await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 1 });
return response;
} catch (error) {
throw error;
}
};
fetchArchivedPages = async (workspaceSlug: string, projectId: string) => {
try {
const response = await this.pageService.getArchivedPages(workspaceSlug, projectId);
runInAction(() => {
this.archivedPages = {
...this.archivedPages,
[projectId]: response,
};
});
return response;
} catch (error) {
throw error;
}
};
archivePage = async (workspaceSlug: string, projectId: string, pageId: string) => {
try {
const archivedPage = this.pages[projectId]?.find((page) => page.id === pageId);
if (archivedPage) {
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: [...this.pages[projectId]?.filter((page) => page.id != pageId)],
};
this.archivedPages = {
...this.archivedPages,
[projectId]: [
...this.archivedPages[projectId],
{ ...archivedPage, archived_at: renderDateFormat(new Date()) },
],
};
});
}
await this.pageService.archivePage(workspaceSlug, projectId, pageId);
} catch (error) {
throw error;
}
};
restorePage = async (workspaceSlug: string, projectId: string, pageId: string) => {
try {
const restoredPage = this.archivedPages[projectId]?.find((page) => page.id === pageId);
if (restoredPage) {
runInAction(() => {
this.pages = {
...this.pages,
[projectId]: [...this.pages[projectId], { ...restoredPage, archived_at: null }],
};
this.archivedPages = {
...this.archivedPages,
[projectId]: [...this.archivedPages[projectId]?.filter((page) => page.id != pageId)],
};
});
}
await this.pageService.restorePage(workspaceSlug, projectId, pageId);
} catch (error) {
throw error;
}
};
}