Răsfoiți Sursa

Merge remote-tracking branch 'origin/master' into support/use-jotai

Yuki Takei 6 luni în urmă
părinte
comite
0b97d67b42
31 a modificat fișierele cu 108 adăugiri și 91 ștergeri
  1. 4 0
      apps/app/.eslintrc.js
  2. 40 40
      apps/app/public/images/icons/favicon/manifest.json
  3. 1 1
      apps/app/public/static/locales/en_US/admin.json
  4. 1 1
      apps/app/public/static/locales/en_US/translation.json
  5. 1 1
      apps/app/public/static/locales/fr_FR/admin.json
  6. 1 1
      apps/app/public/static/locales/fr_FR/translation.json
  7. 1 1
      apps/app/public/static/locales/ja_JP/admin.json
  8. 1 1
      apps/app/public/static/locales/ja_JP/translation.json
  9. 1 1
      apps/app/public/static/locales/ko_KR/admin.json
  10. 1 1
      apps/app/public/static/locales/ko_KR/translation.json
  11. 1 1
      apps/app/public/static/locales/zh_CN/admin.json
  12. 1 1
      apps/app/public/static/locales/zh_CN/translation.json
  13. 3 0
      apps/app/src/client/components/PageEditor/PageEditor.tsx
  14. 2 0
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  15. 2 0
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  16. 5 3
      apps/app/src/models/admin/growi-archive-import-option.ts
  17. 1 1
      apps/app/src/models/admin/import-mode.ts
  18. 8 4
      apps/app/src/models/admin/import-option-for-pages.ts
  19. 5 3
      apps/app/src/models/admin/import-option-for-revisions.ts
  20. 0 2
      apps/app/src/models/cdn-resource.js
  21. 0 4
      apps/app/src/models/linked-page-path.js
  22. 8 4
      apps/app/src/models/serializers/in-app-notification-snapshot/page-bulk-export-job.ts
  23. 2 2
      apps/app/src/models/serializers/in-app-notification-snapshot/page.ts
  24. 1 1
      apps/app/src/models/serializers/in-app-notification-snapshot/user.ts
  25. 0 2
      apps/app/src/models/vo/external-account-login-error.ts
  26. 3 0
      apps/app/src/server/routes/apiv3/page/update-page.ts
  27. 7 2
      apps/app/src/server/service/page/index.ts
  28. 1 3
      apps/app/test-with-vite/.eslintrc.cjs
  29. 2 3
      apps/app/test-with-vite/setup/mongoms.ts
  30. 1 1
      apps/app/test-with-vite/tsconfig.json
  31. 3 6
      biome.json

+ 4 - 0
apps/app/.eslintrc.js

@@ -27,10 +27,14 @@ module.exports = {
     'test/integration/models/**',
     'test/integration/models/**',
     'test/integration/service/**',
     'test/integration/service/**',
     'test/integration/setup.js',
     'test/integration/setup.js',
+    'test-with-vite/**',
+    'public/**',
     'bin/**',
     'bin/**',
     'config/**',
     'config/**',
+    'src/styles/**',
     'src/linter-checker/**',
     'src/linter-checker/**',
     'src/migrations/**',
     'src/migrations/**',
+    'src/models/**',
     'src/features/callout/**',
     'src/features/callout/**',
     'src/features/collaborative-editor/**',
     'src/features/collaborative-editor/**',
     'src/features/comment/**',
     'src/features/comment/**',

+ 40 - 40
apps/app/public/images/icons/favicon/manifest.json

@@ -1,41 +1,41 @@
 {
 {
- "name": "App",
- "icons": [
-  {
-   "src": "\/android-icon-36x36.png",
-   "sizes": "36x36",
-   "type": "image\/png",
-   "density": "0.75"
-  },
-  {
-   "src": "\/android-icon-48x48.png",
-   "sizes": "48x48",
-   "type": "image\/png",
-   "density": "1.0"
-  },
-  {
-   "src": "\/android-icon-72x72.png",
-   "sizes": "72x72",
-   "type": "image\/png",
-   "density": "1.5"
-  },
-  {
-   "src": "\/android-icon-96x96.png",
-   "sizes": "96x96",
-   "type": "image\/png",
-   "density": "2.0"
-  },
-  {
-   "src": "\/android-icon-144x144.png",
-   "sizes": "144x144",
-   "type": "image\/png",
-   "density": "3.0"
-  },
-  {
-   "src": "\/android-icon-192x192.png",
-   "sizes": "192x192",
-   "type": "image\/png",
-   "density": "4.0"
-  }
- ]
-}
+  "name": "App",
+  "icons": [
+    {
+      "src": "\/android-icon-36x36.png",
+      "sizes": "36x36",
+      "type": "image\/png",
+      "density": "0.75"
+    },
+    {
+      "src": "\/android-icon-48x48.png",
+      "sizes": "48x48",
+      "type": "image\/png",
+      "density": "1.0"
+    },
+    {
+      "src": "\/android-icon-72x72.png",
+      "sizes": "72x72",
+      "type": "image\/png",
+      "density": "1.5"
+    },
+    {
+      "src": "\/android-icon-96x96.png",
+      "sizes": "96x96",
+      "type": "image\/png",
+      "density": "2.0"
+    },
+    {
+      "src": "\/android-icon-144x144.png",
+      "sizes": "144x144",
+      "type": "image\/png",
+      "density": "3.0"
+    },
+    {
+      "src": "\/android-icon-192x192.png",
+      "sizes": "192x192",
+      "type": "image\/png",
+      "density": "4.0"
+    }
+  ]
+}

+ 1 - 1
apps/app/public/static/locales/en_US/admin.json

@@ -1139,4 +1139,4 @@
     "disable_mode_explanation": "Currently, AI integration is disabled. To enable it, configure the <code>AI_ENABLED</code> environment variable along with the required additional variables.<br><br>For details, please refer to the <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
     "disable_mode_explanation": "Currently, AI integration is disabled. To enable it, configure the <code>AI_ENABLED</code> environment variable along with the required additional variables.<br><br>For details, please refer to the <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
     "ai_search_management": "AI search management"
     "ai_search_management": "AI search management"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/en_US/translation.json

@@ -1062,4 +1062,4 @@
     "skipped-toaster": "Skipped synchronizing since the editor is not activated. Please open the editor and try again.",
     "skipped-toaster": "Skipped synchronizing since the editor is not activated. Please open the editor and try again.",
     "error-toaster": "Synchronization of the latest text failed"
     "error-toaster": "Synchronization of the latest text failed"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/fr_FR/admin.json

@@ -1138,4 +1138,4 @@
     "disable_mode_explanation": "Actuellement, l'intégration AI est désactivée. Pour l'activer, configurez la variable d'environnement <code>AI_ENABLED</code> ainsi que les autres variables nécessaires.<br><br>Pour plus de détails, veuillez consulter la <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
     "disable_mode_explanation": "Actuellement, l'intégration AI est désactivée. Pour l'activer, configurez la variable d'environnement <code>AI_ENABLED</code> ainsi que les autres variables nécessaires.<br><br>Pour plus de détails, veuillez consulter la <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
     "ai_search_management": "Gestion de la recherche par l'IA"
     "ai_search_management": "Gestion de la recherche par l'IA"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/fr_FR/translation.json

@@ -1053,4 +1053,4 @@
     "skipped-toaster": "L'éditeur n'est pas actif. Synchronisation annulée.",
     "skipped-toaster": "L'éditeur n'est pas actif. Synchronisation annulée.",
     "error-toaster": "Synchronisation échouée"
     "error-toaster": "Synchronisation échouée"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/ja_JP/admin.json

@@ -1148,4 +1148,4 @@
     "disable_mode_explanation": "現在、AI 連携は無効になっています。有効にする場合は環境変数 <code>AI_ENABLED</code> の他、必要な環境変数を設定してください。<br><br>詳細は<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}ja/guide/features/ai-knowledge-assistant.html>ドキュメント</a>を参照してください。",
     "disable_mode_explanation": "現在、AI 連携は無効になっています。有効にする場合は環境変数 <code>AI_ENABLED</code> の他、必要な環境変数を設定してください。<br><br>詳細は<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}ja/guide/features/ai-knowledge-assistant.html>ドキュメント</a>を参照してください。",
     "ai_search_management": "AI 検索管理"
     "ai_search_management": "AI 検索管理"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/ja_JP/translation.json

@@ -1095,4 +1095,4 @@
     "skipped-toaster": "エディターがアクティブではないため、同期をスキップしました。エディターを開いて再度お試しください。",
     "skipped-toaster": "エディターがアクティブではないため、同期をスキップしました。エディターを開いて再度お試しください。",
     "error-toaster": "最新の本文の同期に失敗しました"
     "error-toaster": "最新の本文の同期に失敗しました"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/ko_KR/admin.json

@@ -1139,4 +1139,4 @@
     "disable_mode_explanation": "현재 AI 통합이 비활성화되어 있습니다. 활성화하려면 <code>AI_ENABLED</code> 환경 변수와 필요한 추가 변수를 구성하십시오.<br><br>자세한 내용은 <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>문서</a>를 참조하십시오.",
     "disable_mode_explanation": "현재 AI 통합이 비활성화되어 있습니다. 활성화하려면 <code>AI_ENABLED</code> 환경 변수와 필요한 추가 변수를 구성하십시오.<br><br>자세한 내용은 <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>문서</a>를 참조하십시오.",
     "ai_search_management": "AI 검색 관리"
     "ai_search_management": "AI 검색 관리"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/ko_KR/translation.json

@@ -1022,4 +1022,4 @@
     "skipped-toaster": "편집기가 활성화되지 않아 동기화 건너뜀. 편집기를 열고 다시 시도하십시오.",
     "skipped-toaster": "편집기가 활성화되지 않아 동기화 건너뜀. 편집기를 열고 다시 시도하십시오.",
     "error-toaster": "최신 텍스트 동기화 실패"
     "error-toaster": "최신 텍스트 동기화 실패"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/zh_CN/admin.json

@@ -1148,4 +1148,4 @@
     "disable_mode_explanation": "目前,AI 集成已被禁用。若要启用,请配置 <code>AI_ENABLED</code> 环境变量以及其他必要的变量。<br><br>详细信息请参考<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>文档</a>。",
     "disable_mode_explanation": "目前,AI 集成已被禁用。若要启用,请配置 <code>AI_ENABLED</code> 环境变量以及其他必要的变量。<br><br>详细信息请参考<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>文档</a>。",
     "ai_search_management": "AI 搜索管理"
     "ai_search_management": "AI 搜索管理"
   }
   }
-}
+}

+ 1 - 1
apps/app/public/static/locales/zh_CN/translation.json

@@ -1067,4 +1067,4 @@
     "skipped-toaster": "由于编辑器未激活,因此跳过同步。 请打开编辑器并重试。",
     "skipped-toaster": "由于编辑器未激活,因此跳过同步。 请打开编辑器并重试。",
     "error-toaster": "同步最新文本失败"
     "error-toaster": "同步最新文本失败"
   }
   }
-}
+}

+ 3 - 0
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -138,6 +138,9 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
   }, [resolvedTheme, setResolvedTheme]);
   }, [resolvedTheme, setResolvedTheme]);
 
 
   const currentRevisionId = currentPage?.revision?._id;
   const currentRevisionId = currentPage?.revision?._id;
+
+  // There are cases where "revisionId" is not required for revision updates
+  // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
   const isRevisionIdRequiredForPageUpdate = currentPage?.revision?.origin === undefined;
   const isRevisionIdRequiredForPageUpdate = currentPage?.revision?.origin === undefined;
 
 
   const initialValueRef = useRef('');
   const initialValueRef = useRef('');

+ 2 - 0
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -47,6 +47,8 @@ export const useDrawioModalLauncherForView = (opts?: {
       return;
       return;
     }
     }
 
 
+    // There are cases where "revisionId" is not required for revision updates
+    // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
     try {
     try {
       await _updatePage({
       await _updatePage({
         pageId: currentPage._id,
         pageId: currentPage._id,

+ 2 - 0
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -48,6 +48,8 @@ export const useHandsontableModalLauncherForView = (opts?: {
     }
     }
 
 
     try {
     try {
+      // There are cases where "revisionId" is not required for revision updates
+      // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
       await _updatePage({
       await _updatePage({
         pageId: currentPage._id,
         pageId: currentPage._id,
         revisionId,
         revisionId,

+ 5 - 3
apps/app/src/models/admin/growi-archive-import-option.ts

@@ -1,12 +1,15 @@
 import { ImportMode } from './import-mode';
 import { ImportMode } from './import-mode';
 
 
 export class GrowiArchiveImportOption {
 export class GrowiArchiveImportOption {
-
   collectionName: string;
   collectionName: string;
 
 
   mode: ImportMode;
   mode: ImportMode;
 
 
-  constructor(collectionName: string, mode: ImportMode = ImportMode.insert, initProps = {}) {
+  constructor(
+    collectionName: string,
+    mode: ImportMode = ImportMode.insert,
+    initProps = {},
+  ) {
     this.collectionName = collectionName;
     this.collectionName = collectionName;
     this.mode = mode;
     this.mode = mode;
 
 
@@ -14,5 +17,4 @@ export class GrowiArchiveImportOption {
       this[key] = value;
       this[key] = value;
     });
     });
   }
   }
-
 }
 }

+ 1 - 1
apps/app/src/models/admin/import-mode.ts

@@ -3,4 +3,4 @@ export const ImportMode = {
   upsert: 'upsert',
   upsert: 'upsert',
   flushAndInsert: 'flushAndInsert',
   flushAndInsert: 'flushAndInsert',
 } as const;
 } as const;
-export type ImportMode = typeof ImportMode[keyof typeof ImportMode];
+export type ImportMode = (typeof ImportMode)[keyof typeof ImportMode];

+ 8 - 4
apps/app/src/models/admin/import-option-for-pages.ts

@@ -11,7 +11,6 @@ const DEFAULT_PROPS = {
 };
 };
 
 
 export class ImportOptionForPages extends GrowiArchiveImportOption {
 export class ImportOptionForPages extends GrowiArchiveImportOption {
-
   isOverwriteAuthorWithCurrentUser;
   isOverwriteAuthorWithCurrentUser;
 
 
   makePublicForGrant2;
   makePublicForGrant2;
@@ -22,12 +21,17 @@ export class ImportOptionForPages extends GrowiArchiveImportOption {
 
 
   initPageMetadatas;
   initPageMetadatas;
 
 
-  constructor(collectionName: string, mode: ImportMode = ImportMode.insert, initProps = DEFAULT_PROPS) {
+  constructor(
+    collectionName: string,
+    mode: ImportMode = ImportMode.insert,
+    initProps = DEFAULT_PROPS,
+  ) {
     super(collectionName, mode, initProps);
     super(collectionName, mode, initProps);
   }
   }
-
 }
 }
 
 
-export const isImportOptionForPages = (opt: GrowiArchiveImportOption): opt is ImportOptionForPages => {
+export const isImportOptionForPages = (
+  opt: GrowiArchiveImportOption,
+): opt is ImportOptionForPages => {
   return 'isOverwriteAuthorWithCurrentUser' in opt;
   return 'isOverwriteAuthorWithCurrentUser' in opt;
 };
 };

+ 5 - 3
apps/app/src/models/admin/import-option-for-revisions.ts

@@ -7,9 +7,11 @@ const DEFAULT_PROPS = {
 };
 };
 
 
 export class ImportOptionForRevisions extends GrowiArchiveImportOption {
 export class ImportOptionForRevisions extends GrowiArchiveImportOption {
-
-  constructor(collectionName: string, mode: ImportMode = ImportMode.insert, initProps = DEFAULT_PROPS) {
+  constructor(
+    collectionName: string,
+    mode: ImportMode = ImportMode.insert,
+    initProps = DEFAULT_PROPS,
+  ) {
     super(collectionName, mode, initProps);
     super(collectionName, mode, initProps);
   }
   }
-
 }
 }

+ 0 - 2
apps/app/src/models/cdn-resource.js

@@ -2,13 +2,11 @@
  * Value Object
  * Value Object
  */
  */
 class CdnResource {
 class CdnResource {
-
   constructor(name, url, outDir) {
   constructor(name, url, outDir) {
     this.name = name;
     this.name = name;
     this.url = url;
     this.url = url;
     this.outDir = outDir;
     this.outDir = outDir;
   }
   }
-
 }
 }
 
 
 module.exports = CdnResource;
 module.exports = CdnResource;

+ 0 - 4
apps/app/src/models/linked-page-path.js

@@ -7,9 +7,7 @@ const { isTrashPage } = pagePathUtils;
  * Linked Array Structured PagePath Model
  * Linked Array Structured PagePath Model
  */
  */
 export default class LinkedPagePath {
 export default class LinkedPagePath {
-
   constructor(path) {
   constructor(path) {
-
     const pagePath = new DevidedPagePath(path);
     const pagePath = new DevidedPagePath(path);
 
 
     this.path = path;
     this.path = path;
@@ -18,7 +16,6 @@ export default class LinkedPagePath {
     this.parent = pagePath.isRoot
     this.parent = pagePath.isRoot
       ? null
       ? null
       : new LinkedPagePath(pagePath.former, true);
       : new LinkedPagePath(pagePath.former, true);
-
   }
   }
 
 
   get href() {
   get href() {
@@ -32,5 +29,4 @@ export default class LinkedPagePath {
   get isInTrash() {
   get isInTrash() {
     return isTrashPage(this.path);
     return isTrashPage(this.path);
   }
   }
-
 }
 }

+ 8 - 4
apps/app/src/models/serializers/in-app-notification-snapshot/page-bulk-export-job.ts

@@ -1,17 +1,21 @@
-import { isPopulated } from '@growi/core';
 import type { IPage } from '@growi/core';
 import type { IPage } from '@growi/core';
+import { isPopulated } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 import type { IPageBulkExportJob } from '~/features/page-bulk-export/interfaces/page-bulk-export';
 import type { IPageBulkExportJob } from '~/features/page-bulk-export/interfaces/page-bulk-export';
 import type { PageModel } from '~/server/models/page';
 import type { PageModel } from '~/server/models/page';
 
 
 export interface IPageBulkExportJobSnapshot {
 export interface IPageBulkExportJobSnapshot {
-  path: string
+  path: string;
 }
 }
 
 
-export const stringifySnapshot = async(exportJob: IPageBulkExportJob): Promise<string | undefined> => {
+export const stringifySnapshot = async (
+  exportJob: IPageBulkExportJob,
+): Promise<string | undefined> => {
   const Page = mongoose.model<IPage, PageModel>('Page');
   const Page = mongoose.model<IPage, PageModel>('Page');
-  const page = isPopulated(exportJob.page) ? exportJob.page : (await Page.findById(exportJob.page));
+  const page = isPopulated(exportJob.page)
+    ? exportJob.page
+    : await Page.findById(exportJob.page);
 
 
   if (page != null) {
   if (page != null) {
     return JSON.stringify({
     return JSON.stringify({

+ 2 - 2
apps/app/src/models/serializers/in-app-notification-snapshot/page.ts

@@ -1,8 +1,8 @@
 import type { IPage, IUser } from '@growi/core';
 import type { IPage, IUser } from '@growi/core';
 
 
 export interface IPageSnapshot {
 export interface IPageSnapshot {
-  path: string
-  creator: IUser
+  path: string;
+  creator: IUser;
 }
 }
 
 
 export const stringifySnapshot = (page: IPage): string => {
 export const stringifySnapshot = (page: IPage): string => {

+ 1 - 1
apps/app/src/models/serializers/in-app-notification-snapshot/user.ts

@@ -1,7 +1,7 @@
 import type { IUser } from '@growi/core';
 import type { IUser } from '@growi/core';
 
 
 export interface IUserSnapshot {
 export interface IUserSnapshot {
-  username: string
+  username: string;
 }
 }
 
 
 export const stringifySnapshot = (user: IUser): string => {
 export const stringifySnapshot = (user: IUser): string => {

+ 0 - 2
apps/app/src/models/vo/external-account-login-error.ts

@@ -1,5 +1,4 @@
 export class ExternalAccountLoginError extends Error {
 export class ExternalAccountLoginError extends Error {
-
   args?: any;
   args?: any;
 
 
   constructor(message = '', args = undefined) {
   constructor(message = '', args = undefined) {
@@ -7,5 +6,4 @@ export class ExternalAccountLoginError extends Error {
     this.message = message;
     this.message = message;
     this.args = args;
     this.args = args;
   }
   }
-
 }
 }

+ 3 - 0
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -196,6 +196,9 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
           options.userRelatedGrantUserGroupIds = userRelatedGrantUserGroupIds;
           options.userRelatedGrantUserGroupIds = userRelatedGrantUserGroupIds;
         }
         }
         previousRevision = await Revision.findById(sanitizeRevisionId);
         previousRevision = await Revision.findById(sanitizeRevisionId);
+
+        // There are cases where "revisionId" is not required for revision updates
+        // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
         updatedPage = await crowi.pageService.updatePage(currentPage, body, previousRevision?.body ?? null, req.user, options);
         updatedPage = await crowi.pageService.updatePage(currentPage, body, previousRevision?.body ?? null, req.user, options);
       }
       }
       catch (err) {
       catch (err) {

+ 7 - 2
apps/app/src/server/service/page/index.ts

@@ -4154,6 +4154,9 @@ class PageService implements IPageService {
     return [...userUnrelatedPreviousGrantedGroups, ...userRelatedGrantedGroups];
     return [...userUnrelatedPreviousGrantedGroups, ...userRelatedGrantedGroups];
   }
   }
 
 
+
+  // There are cases where "revisionId" is not required for revision updates
+  // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
   async updatePage(
   async updatePage(
       pageData: HydratedDocument<PageDocument>,
       pageData: HydratedDocument<PageDocument>,
       body: string | null,
       body: string | null,
@@ -4303,6 +4306,8 @@ class PageService implements IPageService {
   }
   }
 
 
 
 
+  // There are cases where "revisionId" is not required for revision updates
+  // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
   async updatePageV4(
   async updatePageV4(
       pageData: HydratedDocument<PageDocument>, body, previousBody, user, options: IOptionsForUpdate = {},
       pageData: HydratedDocument<PageDocument>, body, previousBody, user, options: IOptionsForUpdate = {},
   ): Promise<HydratedDocument<PageDocument>> {
   ): Promise<HydratedDocument<PageDocument>> {
@@ -4323,10 +4328,10 @@ class PageService implements IPageService {
     let savedPage = await pageData.save();
     let savedPage = await pageData.save();
 
 
     // Update revision
     // Update revision
-    const isBodyPresent = body != null && previousBody != null;
+    const isBodyPresent = body != null;
     const shouldUpdateBody = isBodyPresent;
     const shouldUpdateBody = isBodyPresent;
     if (shouldUpdateBody) {
     if (shouldUpdateBody) {
-      const newRevision = await Revision.prepareRevision(pageData, body, previousBody, user);
+      const newRevision = await Revision.prepareRevision(pageData, body, previousBody, user, options.origin);
       savedPage = await pushRevision(savedPage, newRevision, user);
       savedPage = await pushRevision(savedPage, newRevision, user);
       await savedPage.populateDataToShowRevision();
       await savedPage.populateDataToShowRevision();
     }
     }

+ 1 - 3
apps/app/test-with-vite/.eslintrc.cjs

@@ -1,5 +1,3 @@
 module.exports = {
 module.exports = {
-  extends: [
-    'plugin:vitest/recommended',
-  ],
+  extends: ['plugin:vitest/recommended'],
 };
 };

+ 2 - 3
apps/app/test-with-vite/setup/mongoms.ts

@@ -3,8 +3,7 @@ import mongoose from 'mongoose';
 
 
 import { mongoOptions } from '~/server/util/mongoose-utils';
 import { mongoOptions } from '~/server/util/mongoose-utils';
 
 
-
-beforeAll(async() => {
+beforeAll(async () => {
   // set debug flag
   // set debug flag
   process.env.MONGOMS_DEBUG = process.env.VITE_MONGOMS_DEBUG;
   process.env.MONGOMS_DEBUG = process.env.VITE_MONGOMS_DEBUG;
 
 
@@ -25,6 +24,6 @@ beforeAll(async() => {
   await mongoose.connect(mongoServer.getUri(), mongoOptions);
   await mongoose.connect(mongoServer.getUri(), mongoOptions);
 });
 });
 
 
-afterAll(async() => {
+afterAll(async () => {
   await mongoose.disconnect();
   await mongoose.disconnect();
 });
 });

+ 1 - 1
apps/app/test-with-vite/tsconfig.json

@@ -5,7 +5,7 @@
     "baseUrl": "../",
     "baseUrl": "../",
     "paths": {
     "paths": {
       "~/*": ["./src/*"],
       "~/*": ["./src/*"],
-      "^/*": ["./*"],
+      "^/*": ["./*"]
     }
     }
   }
   }
 }
 }

+ 3 - 6
biome.json

@@ -22,23 +22,20 @@
       "!**/.eslintrc.js",
       "!**/.eslintrc.js",
       "!**/.stylelintrc.json",
       "!**/.stylelintrc.json",
       "!**/package.json",
       "!**/package.json",
+      "!apps/app/src/styles/prebuilt/**",
+      "!apps/app/tmp/**",
       "!apps/slackbot-proxy/src/public/bootstrap/**",
       "!apps/slackbot-proxy/src/public/bootstrap/**",
       "!packages/editor/**",
       "!packages/editor/**",
       "!packages/pdf-converter-client/src/index.ts",
       "!packages/pdf-converter-client/src/index.ts",
       "!packages/pdf-converter-client/specs/**",
       "!packages/pdf-converter-client/specs/**",
       "!apps/app/playwright/**",
       "!apps/app/playwright/**",
-      "!apps/app/public/**",
       "!apps/app/src/client/**",
       "!apps/app/src/client/**",
       "!apps/app/src/components/**",
       "!apps/app/src/components/**",
       "!apps/app/src/features/openai/**",
       "!apps/app/src/features/openai/**",
-      "!apps/app/src/models/**",
       "!apps/app/src/pages/**",
       "!apps/app/src/pages/**",
       "!apps/app/src/server/**",
       "!apps/app/src/server/**",
       "!apps/app/src/services/**",
       "!apps/app/src/services/**",
-      "!apps/app/src/stores/**",
-      "!apps/app/src/styles/**",
-      "!apps/app/test-with-vite/**",
-      "!apps/app/tmp/**"
+      "!apps/app/src/stores/**"
     ]
     ]
   },
   },
   "formatter": {
   "formatter": {