Просмотр исходного кода

Merge pull request #8472 from weseek/feat/140128-enable-saving-as-a-wip-page

feat: Enable saving as a WIP page
Shun Miyazawa 2 лет назад
Родитель
Сommit
5f6558920c

+ 1 - 1
apps/app/src/client/services/create-page/use-create-template-page.ts

@@ -25,7 +25,7 @@ export const useCreateTemplatePage: UseCreateTemplatePage = () => {
     if (isLoadingPagePath || !isCreatable) return;
 
     return createAndTransit(
-      { path: normalizePath(`${currentPagePath}/${label}`) },
+      { path: normalizePath(`${currentPagePath}/${label}`), wip: false },
       { shouldCheckPageExists: true },
     );
   }, [currentPagePath, isCreatable, isLoadingPagePath, createAndTransit]);

+ 3 - 1
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -7,6 +7,8 @@ import { toastError } from '~/client/util/toastr';
 import { useIsNotFound } from '~/stores/page';
 import { EditorMode, useEditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
 
+import { shouldCreateWipPage } from '../../utils/should-create-wip-page';
+
 
 import styles from './PageEditorModeManager.module.scss';
 
@@ -72,7 +74,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
 
     try {
       await createAndTransit(
-        { path },
+        { path, wip: shouldCreateWipPage(path) },
         { shouldCheckPageExists: true },
       );
     }

+ 13 - 4
apps/app/src/components/Sidebar/Custom/CustomSidebarNotFound.tsx

@@ -1,16 +1,25 @@
-import Link from 'next/link';
+import { useCallback } from 'react';
+
 import { useTranslation } from 'react-i18next';
 
+import { useCreatePageAndTransit } from '~/client/services/create-page';
+
 export const SidebarNotFound = (): JSX.Element => {
   const { t } = useTranslation();
 
+  const { createAndTransit } = useCreatePageAndTransit();
+
+  const clickCreateButtonHandler = useCallback(async() => {
+    createAndTransit({ path: '/Sidebar', wip: false });
+  }, [createAndTransit]);
+
   return (
-    <div className="grw-sidebar-content-header h5 text-center py-3">
-      <Link href="/Sidebar#edit">
+    <div>
+      <button type="button" className="btn btn-lg btn-link" onClick={clickCreateButtonHandler}>
         <span className="material-symbols-outlined">edit_note</span>
         {/* eslint-disable-next-line react/no-danger */}
         <span dangerouslySetInnerHTML={{ __html: t('Create Sidebar Page') }}></span>
-      </Link>
+      </button>
     </div>
   );
 };

+ 1 - 1
apps/app/src/components/Sidebar/PageCreateButton/hooks/use-create-new-page.ts

@@ -18,7 +18,7 @@ export const useCreateNewPage: UseCreateNewPage = () => {
     if (isLoadingPagePath) return;
 
     return createAndTransit(
-      { parentPath: currentPagePath, optionalParentPath: '/' },
+      { parentPath: currentPagePath, optionalParentPath: '/', wip: true },
     );
   }, [createAndTransit, currentPagePath, isLoadingPagePath]);
 

+ 1 - 1
apps/app/src/components/Sidebar/PageCreateButton/hooks/use-create-todays-memo.tsx

@@ -32,7 +32,7 @@ export const useCreateTodaysMemo: UseCreateTodaysMemo = () => {
     if (!isCreatable || todaysPath == null) return;
 
     return createAndTransit(
-      { path: todaysPath },
+      { path: todaysPath, wip: true },
       { shouldCheckPageExists: true },
     );
   }, [createAndTransit, isCreatable, todaysPath]);

+ 3 - 0
apps/app/src/components/TreeItem/NewPageInput/use-new-page-input.tsx

@@ -1,9 +1,11 @@
 import React, { useState, type FC, useCallback } from 'react';
 
+
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { usePageTreeDescCountMap } from '~/stores/ui';
 
+import { shouldCreateWipPage } from '../../../utils/should-create-wip-page';
 import type { TreeItemToolProps } from '../interfaces';
 
 import { NewPageCreateButton } from './NewPageCreateButton';
@@ -73,6 +75,7 @@ export const useNewPageInput = (): UseNewPageInput => {
         grant: page.grant,
         // grantUserGroupId: page.grantedGroup,
         grantUserGroupIds: page.grantedGroups,
+        wip: shouldCreateWipPage(newPagePath),
       });
 
       mutateChildren();

+ 2 - 0
apps/app/src/interfaces/page.ts

@@ -43,4 +43,6 @@ export type IOptionsForCreate = {
   grant?: PageGrant,
   grantUserGroupIds?: IGrantedGroup[],
   overwriteScopesOfDescendants?: boolean,
+
+  wip?: boolean,
 };

+ 16 - 0
apps/app/src/server/models/page.ts

@@ -139,6 +139,8 @@ const schema = new Schema<PageDocument, PageModel>({
   seenUsers: [{ type: ObjectId, ref: 'User' }],
   commentCount: { type: Number, default: 0 },
   expandContentWidth: { type: Boolean },
+  wip: { type: Boolean },
+  wipExpiredAt: { type: Date },
   updatedAt: { type: Date, default: Date.now }, // Do not use timetamps for updatedAt because it breaks 'updateMetadata: false' option
   deleteUser: { type: ObjectId, ref: 'User' },
   deletedAt: { type: Date },
@@ -1051,6 +1053,20 @@ schema.methods.calculateAndUpdateLatestRevisionBodyLength = async function(this:
   await this.save();
 };
 
+schema.methods.publish = function() {
+  this.wip = undefined;
+  this.wipExpiredAt = undefined;
+};
+
+schema.methods.unpublish = function() {
+  this.wip = true;
+};
+
+schema.methods.makeWip = function() {
+  this.wip = true;
+  this.wipExpiredAt = new Date();
+};
+
 /*
  * Merge obsolete page model methods and define new methods which depend on crowi instance
  */

+ 6 - 2
apps/app/src/server/routes/apiv3/page/create-page.ts

@@ -111,6 +111,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
     body('pageTags').optional().isArray().withMessage('pageTags must be array'),
     body('isSlackEnabled').optional().isBoolean().withMessage('isSlackEnabled must be boolean'),
     body('slackChannels').optional().isString().withMessage('slackChannels must be string'),
+    body('wip').optional().isBoolean().withMessage('wip must be boolean'),
   ];
 
 
@@ -220,8 +221,11 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
 
       let createdPage;
       try {
-        const { grant, grantUserGroupIds, overwriteScopesOfDescendants } = req.body;
-        const options: IOptionsForCreate = { overwriteScopesOfDescendants };
+        const {
+          grant, grantUserGroupIds, overwriteScopesOfDescendants, wip,
+        } = req.body;
+
+        const options: IOptionsForCreate = { overwriteScopesOfDescendants, wip };
         if (grant != null) {
           options.grant = grant;
           options.grantUserGroupIds = grantUserGroupIds;

+ 9 - 0
apps/app/src/server/service/page/index.ts

@@ -3790,6 +3790,12 @@ class PageService implements IPageService {
       const parent = await this.getParentAndFillAncestorsByUser(user, path);
       page.parent = parent._id;
     }
+
+    // Set wip
+    if (options.wip) {
+      page.makeWip();
+    }
+
     // Save
     let savedPage = await page.save();
 
@@ -4102,6 +4108,9 @@ class PageService implements IPageService {
     const clonedPageData = Page.hydrate(pageData.toObject());
     const newPageData = pageData;
 
+    // Do not consider it for automatic deletion if updated at least once
+    newPageData.wipExpiredAt = undefined;
+
     // use the previous data if absent
     const grant = options.grant ?? clonedPageData.grant;
     const grantUserGroupIds = options.userRelatedGrantUserGroupIds != null

+ 14 - 0
apps/app/src/utils/should-create-wip-page.ts

@@ -0,0 +1,14 @@
+import { checkTemplatePath } from '@growi/core/dist/utils/template-checker';
+
+/**
+ * Returns Whether to create pages with the wip flag
+ * @param {string|undefined} path
+ * @returns {boolean}
+ */
+export const shouldCreateWipPage = (path?: string): boolean => {
+  if (path == null) {
+    return true;
+  }
+
+  return !(checkTemplatePath(path) || path === '/Sidebar');
+};

+ 2 - 0
packages/core/src/interfaces/page.ts

@@ -39,6 +39,8 @@ export type IPage = {
   latestRevision?: Ref<IRevision>,
   latestRevisionBodyLength?: number,
   expandContentWidth?: boolean,
+  wip?: boolean,
+  wipExpiredAt?: Date
 }
 
 export type IPagePopulatedToList = Omit<IPageHasId, 'lastUpdateUser'> & {