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

Merge branch 'master' into support/150088-sidebar-text-size

satof3 1 год назад
Родитель
Сommit
b0c9319d80

+ 23 - 1
CHANGELOG.md

@@ -1,9 +1,31 @@
 # Changelog
 # Changelog
 
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.0.13...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.0.14...HEAD)
 
 
 *Please do not manually update this file. We've automated the process.*
 *Please do not manually update this file. We've automated the process.*
 
 
+## [v7.0.14](https://github.com/weseek/growi/compare/v7.0.13...v7.0.14) - 2024-07-19
+
+### 🐛 Bug Fixes
+
+### 💎 Features
+
+* feat: Alerts when trying to sync with latest revision when yjs data is corrupt (#8971) @miya
+
+### 🚀 Improvement
+
+* imprv: Restrict use of the editing UI from View if there is at least one user currently editing (#8966) @miya
+
+### 🐛 Bug Fixes
+
+* fix: Handle error when folding drawio blocks (#8977) @yuki-takei
+* fix: Sync the editor text with the latest revision menu (1) (#8975) @yuki-takei
+* fix: Sync the editor text with the latest revision menu (2) (#8978) @yuki-takei
+
+### 🧰 Maintenance
+
+* support: Normalize Revision.pageId (for #8954) (#8973) @miya
+
 ## [v7.0.13](https://github.com/weseek/growi/compare/v7.0.12...v7.0.13) - 2024-07-16
 ## [v7.0.13](https://github.com/weseek/growi/compare/v7.0.12...v7.0.13) - 2024-07-16
 
 
 ### 💎 Features
 ### 💎 Features

+ 1 - 1
apps/app/docker/README.md

@@ -10,7 +10,7 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 ------------------------------------------------
 
 
-* [`7.0.13`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.13/apps/app/docker/Dockerfile)
+* [`7.0.14`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.14/apps/app/docker/Dockerfile)
 * [`6.3.2`, `6.3`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.3.2/apps/app/docker/Dockerfile)
 * [`6.3.2`, `6.3`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.3.2/apps/app/docker/Dockerfile)
 * [`6.2.4`, `6.2` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.4/apps/app/docker/Dockerfile)
 * [`6.2.4`, `6.2` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.4/apps/app/docker/Dockerfile)
 * [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)
 * [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "7.0.14-RC.0",
+  "version": "7.0.15-RC.0",
   "license": "MIT",
   "license": "MIT",
   "private": "true",
   "private": "true",
   "scripts": {
   "scripts": {

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

@@ -9,7 +9,6 @@
   "delete_all": "Delete all",
   "delete_all": "Delete all",
   "Duplicate": "Duplicate",
   "Duplicate": "Duplicate",
   "PathRecovery": "Path recovery",
   "PathRecovery": "Path recovery",
-  "SyncLatestRevisionBody": "Sync editor with latest body",
   "Copy": "Copy",
   "Copy": "Copy",
   "preview": "Preview",
   "preview": "Preview",
   "desktop": "Desktop",
   "desktop": "Desktop",
@@ -851,10 +850,12 @@
   "create_page": {
   "create_page": {
     "untitled": "Untitled"
     "untitled": "Untitled"
   },
   },
-  "sync-latest-reevision-body": {
+  "sync-latest-revision-body": {
+    "menuitem": "Sync the editor text with the latest revision body",
     "confirm": "Delete the draft data being entered into the editor and synchronize the latest text. Are you sure you want to run it?",
     "confirm": "Delete the draft data being entered into the editor and synchronize the latest text. Are you sure you want to run it?",
     "alert": "The latest text may not have been synchronized. Please reload and check again.",
     "alert": "The latest text may not have been synchronized. Please reload and check again.",
     "success-toaster": "Latest text synchronized",
     "success-toaster": "Latest text synchronized",
+    "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"
   }
   }
 }
 }

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

@@ -9,7 +9,6 @@
   "delete_all": "Tout supprimer",
   "delete_all": "Tout supprimer",
   "Duplicate": "Dupliquer",
   "Duplicate": "Dupliquer",
   "PathRecovery": "Récupération de chemin",
   "PathRecovery": "Récupération de chemin",
-  "SyncLatestRevisionBody": "Synchroniser l'éditeur avec le dernier corps",
   "Copy": "Copier",
   "Copy": "Copier",
   "preview": "Prévisualiser",
   "preview": "Prévisualiser",
   "desktop": "Ordinateur",
   "desktop": "Ordinateur",
@@ -842,10 +841,12 @@
     "size_s": "Taille: P",
     "size_s": "Taille: P",
     "size_l": "Taille: G"
     "size_l": "Taille: G"
   },
   },
-  "sync-latest-reevision-body": {
+  "sync-latest-revision-body": {
+    "menuitem": "Synchroniser le texte de l'éditeur avec le corps de la dernière révision",
     "confirm": "Delete the draft data being entered into the editor and synchronize the latest text. Are you sure you want to run it?",
     "confirm": "Delete the draft data being entered into the editor and synchronize the latest text. Are you sure you want to run it?",
     "alert": "Il se peut que le texte le plus récent n'ait pas été synchronisé. Veuillez recharger et vérifier à nouveau.",
     "alert": "Il se peut que le texte le plus récent n'ait pas été synchronisé. Veuillez recharger et vérifier à nouveau.",
     "success-toaster": "Dernier texte synchronisé",
     "success-toaster": "Dernier texte synchronisé",
+    "skipped-toaster": "Synchronisation ignorée car l'éditeur n'est pas activé. Ouvrir l'éditeur et réessayer.",
     "error-toaster": "La synchronisation du dernier texte a échoué"
     "error-toaster": "La synchronisation du dernier texte a échoué"
   }
   }
 }
 }

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

@@ -9,7 +9,6 @@
   "delete_all": "全て削除",
   "delete_all": "全て削除",
   "Duplicate": "複製",
   "Duplicate": "複製",
   "PathRecovery": "パスを修復",
   "PathRecovery": "パスを修復",
-  "SyncLatestRevisionBody": "エディターを最新の本文に同期",
   "Copy": "コピー",
   "Copy": "コピー",
   "preview": "プレビュー",
   "preview": "プレビュー",
   "desktop": "パソコン",
   "desktop": "パソコン",
@@ -884,10 +883,12 @@
   "create_page": {
   "create_page": {
     "untitled": "無題のページ"
     "untitled": "無題のページ"
   },
   },
-  "sync-latest-reevision-body": {
+  "sync-latest-revision-body": {
+    "menuitem": "最新のリビジョンの本文とエディタのテキストを同期",
     "confirm": "エディターに入力中のドラフトデータを削除して最新の本文を同期します。実行しますか?",
     "confirm": "エディターに入力中のドラフトデータを削除して最新の本文を同期します。実行しますか?",
     "alert": "最新の本文が同期されていない可能性があります。リロードして再度ご確認ください。",
     "alert": "最新の本文が同期されていない可能性があります。リロードして再度ご確認ください。",
     "success-toaster": "最新の本文を同期しました",
     "success-toaster": "最新の本文を同期しました",
+    "skipped-toaster": "エディターがアクティブではないため、同期をスキップしました。エディターを開いて再度お試しください。",
     "error-toaster": "最新の本文の同期に失敗しました"
     "error-toaster": "最新の本文の同期に失敗しました"
   }
   }
 }
 }

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

@@ -9,7 +9,6 @@
   "delete_all": "删除所有",
   "delete_all": "删除所有",
   "Duplicate": "复制",
   "Duplicate": "复制",
   "PathRecovery": "路径恢复",
   "PathRecovery": "路径恢复",
-  "SyncLatestRevisionBody": "将编辑器与最新机身同步",
   "Copy": "复制",
   "Copy": "复制",
   "preview": "预览",
   "preview": "预览",
   "desktop": "电脑",
   "desktop": "电脑",
@@ -854,10 +853,12 @@
   "create_page": {
   "create_page": {
     "untitled": "Untitled"
     "untitled": "Untitled"
   },
   },
-  "sync-latest-reevision-body": {
+  "sync-latest-revision-body": {
+    "menuitem": "同步编辑器文本与最新修订正文",
     "confirm": "删除输入编辑器的草稿数据,同步最新文本。 您真的想运行它吗?",
     "confirm": "删除输入编辑器的草稿数据,同步最新文本。 您真的想运行它吗?",
     "alert": "最新文本可能尚未同步。 请重新加载并再次检查。",
     "alert": "最新文本可能尚未同步。 请重新加载并再次检查。",
     "success-toaster": "同步最新文本",
     "success-toaster": "同步最新文本",
+    "skipped-toaster": "由于编辑器未激活,因此跳过同步。 请打开编辑器并重试。",
     "error-toaster": "同步最新文本失败"
     "error-toaster": "同步最新文本失败"
   }
   }
 }
 }

+ 11 - 6
apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -17,7 +17,7 @@ import Sticky from 'react-stickynode';
 import { DropdownItem } from 'reactstrap';
 import { DropdownItem } from 'reactstrap';
 
 
 import { exportAsMarkdown, updateContentWidth, syncLatestRevisionBody } from '~/client/services/page-operation';
 import { exportAsMarkdown, updateContentWidth, syncLatestRevisionBody } from '~/client/services/page-operation';
-import { toastSuccess, toastError } from '~/client/util/toastr';
+import { toastSuccess, toastError, toastWarning } from '~/client/util/toastr';
 import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
@@ -83,22 +83,27 @@ const PageOperationMenuItems = (props: PageOperationMenuItemsProps): JSX.Element
 
 
   const syncLatestRevisionBodyHandler = useCallback(async() => {
   const syncLatestRevisionBodyHandler = useCallback(async() => {
     // eslint-disable-next-line no-alert
     // eslint-disable-next-line no-alert
-    const answer = window.confirm(t('sync-latest-reevision-body.confirm'));
+    const answer = window.confirm(t('sync-latest-revision-body.confirm'));
     if (answer) {
     if (answer) {
       try {
       try {
         const editingMarkdownLength = codeMirrorEditor?.getDoc().length;
         const editingMarkdownLength = codeMirrorEditor?.getDoc().length;
         const res = await syncLatestRevisionBody(pageId, editingMarkdownLength);
         const res = await syncLatestRevisionBody(pageId, editingMarkdownLength);
 
 
+        if (!res.synced) {
+          toastWarning(t('sync-latest-revision-body.skipped-toaster'));
+          return;
+        }
+
         if (res?.isYjsDataBroken) {
         if (res?.isYjsDataBroken) {
           // eslint-disable-next-line no-alert
           // eslint-disable-next-line no-alert
-          window.alert(t('sync-latest-reevision-body.alert'));
+          window.alert(t('sync-latest-revision-body.alert'));
           return;
           return;
         }
         }
 
 
-        toastSuccess(t('sync-latest-reevision-body.success-toaster'));
+        toastSuccess(t('sync-latest-revision-body.success-toaster'));
       }
       }
       catch {
       catch {
-        toastError(t('sync-latest-reevision-body.error-toaster'));
+        toastError(t('sync-latest-revision-body.error-toaster'));
       }
       }
     }
     }
   }, [codeMirrorEditor, pageId, t]);
   }, [codeMirrorEditor, pageId, t]);
@@ -110,7 +115,7 @@ const PageOperationMenuItems = (props: PageOperationMenuItemsProps): JSX.Element
         className="grw-page-control-dropdown-item"
         className="grw-page-control-dropdown-item"
       >
       >
         <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">sync</span>
         <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">sync</span>
-        {t('SyncLatestRevisionBody')}
+        {t('sync-latest-revision-body.menuitem')}
       </DropdownItem>
       </DropdownItem>
 
 
       {/* Presentation */}
       {/* Presentation */}

+ 2 - 1
apps/app/src/client/services/page-operation.ts

@@ -4,6 +4,7 @@ import type { IPageHasId } from '@growi/core';
 import { SubscriptionStatusType } from '@growi/core';
 import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
+import type { SyncLatestRevisionBody } from '~/interfaces/yjs';
 import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
 import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
 import {
 import {
   useCurrentPageId, useSWRMUTxCurrentPage, useSWRxApplicableGrant, useSWRxTagsInfo,
   useCurrentPageId, useSWRMUTxCurrentPage, useSWRxApplicableGrant, useSWRxTagsInfo,
@@ -175,7 +176,7 @@ export const unpublish = async(pageId: string): Promise<IPageHasId> => {
   return res.data;
   return res.data;
 };
 };
 
 
-export const syncLatestRevisionBody = async(pageId: string, editingMarkdownLength?: number): Promise<{ isYjsDataBroken?: boolean } | void> => {
+export const syncLatestRevisionBody = async(pageId: string, editingMarkdownLength?: number): Promise<SyncLatestRevisionBody> => {
   const res = await apiv3Put(`/page/${pageId}/sync-latest-revision-body-to-yjs-draft`, { editingMarkdownLength });
   const res = await apiv3Put(`/page/${pageId}/sync-latest-revision-body-to-yjs-draft`, { editingMarkdownLength });
   return res.data;
   return res.data;
 };
 };

+ 5 - 0
apps/app/src/interfaces/yjs.ts

@@ -2,3 +2,8 @@ export type CurrentPageYjsData = {
   hasYdocsNewerThanLatestRevision?: boolean,
   hasYdocsNewerThanLatestRevision?: boolean,
   awarenessStateSize?: number,
   awarenessStateSize?: number,
 }
 }
+
+export type SyncLatestRevisionBody = {
+  synced: boolean,
+  isYjsDataBroken?: boolean,
+}

+ 10 - 6
apps/app/src/server/service/yjs/yjs.ts

@@ -8,6 +8,7 @@ import type { Document } from 'y-socket.io/dist/server';
 import { YSocketIO, type Document as Ydoc } from 'y-socket.io/dist/server';
 import { YSocketIO, type Document as Ydoc } from 'y-socket.io/dist/server';
 
 
 import { SocketEventName } from '~/interfaces/websocket';
 import { SocketEventName } from '~/interfaces/websocket';
+import type { SyncLatestRevisionBody } from '~/interfaces/yjs';
 import { RoomPrefix, getRoomNameWithId } from '~/server/util/socket-io-helpers';
 import { RoomPrefix, getRoomNameWithId } from '~/server/util/socket-io-helpers';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -32,7 +33,7 @@ type RequestWithUser = IncomingMessage & { user: IUserHasId };
 
 
 export interface IYjsService {
 export interface IYjsService {
   getYDocStatus(pageId: string): Promise<YDocStatus>;
   getYDocStatus(pageId: string): Promise<YDocStatus>;
-  syncWithTheLatestRevisionForce(pageId: string, editingMarkdownLength?: number): Promise<{ isYjsDataBroken?: boolean } | void>
+  syncWithTheLatestRevisionForce(pageId: string, editingMarkdownLength?: number): Promise<SyncLatestRevisionBody>
   getCurrentYdoc(pageId: string): Ydoc | undefined;
   getCurrentYdoc(pageId: string): Ydoc | undefined;
 }
 }
 
 
@@ -181,19 +182,22 @@ class YjsService implements IYjsService {
     return YDocStatus.OUTDATED;
     return YDocStatus.OUTDATED;
   }
   }
 
 
-  public async syncWithTheLatestRevisionForce(pageId: string, editingMarkdownLength?: number): Promise<{ isYjsDataBroken?: boolean } | void> {
+  public async syncWithTheLatestRevisionForce(pageId: string, editingMarkdownLength?: number): Promise<SyncLatestRevisionBody> {
     const doc = this.ysocketio.documents.get(pageId);
     const doc = this.ysocketio.documents.get(pageId);
 
 
     if (doc == null) {
     if (doc == null) {
-      return;
+      return { synced: false };
     }
     }
 
 
     const ytextLength = doc?.getText('codemirror').length;
     const ytextLength = doc?.getText('codemirror').length;
     syncYDoc(this.mdb, doc, true);
     syncYDoc(this.mdb, doc, true);
 
 
-    if (editingMarkdownLength != null) {
-      return { isYjsDataBroken: editingMarkdownLength !== ytextLength };
-    }
+    return {
+      synced: true,
+      isYjsDataBroken: editingMarkdownLength != null
+        ? editingMarkdownLength !== ytextLength
+        : undefined,
+    };
   }
   }
 
 
   public getCurrentYdoc(pageId: string): Ydoc | undefined {
   public getCurrentYdoc(pageId: string): Ydoc | undefined {

+ 1 - 1
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/slackbot-proxy",
   "name": "@growi/slackbot-proxy",
-  "version": "7.0.14-slackbot-proxy.0",
+  "version": "7.0.15-slackbot-proxy.0",
   "license": "MIT",
   "license": "MIT",
   "private": "true",
   "private": "true",
   "scripts": {
   "scripts": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "7.0.14-RC.0",
+  "version": "7.0.15-RC.0",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "license": "MIT",
   "license": "MIT",
   "private": "true",
   "private": "true",

+ 40 - 21
packages/editor/src/client/services/use-codemirror-editor/utils/fold-drawio.ts

@@ -3,42 +3,61 @@ import { useEffect } from 'react';
 import { foldEffect } from '@codemirror/language';
 import { foldEffect } from '@codemirror/language';
 import type { EditorView } from '@codemirror/view';
 import type { EditorView } from '@codemirror/view';
 
 
+
 export type FoldDrawio = void;
 export type FoldDrawio = void;
 
 
 const findAllDrawioSection = (view?: EditorView) => {
 const findAllDrawioSection = (view?: EditorView) => {
   if (view == null) {
   if (view == null) {
     return;
     return;
   }
   }
-  const lineBeginPartOfDrawioRE = /^```(\s.*)drawio$/;
-  const lineNumbers: number[] = [];
-  // repeat the process in each line from the top to the bottom in the editor
-  for (let i = 1, e = view.state.doc.lines; i <= e; i++) {
-    // get each line text
-    const lineTxt = view.state.doc.line(i).text;
-    const match = lineBeginPartOfDrawioRE.exec(lineTxt);
-    if (match) {
-      lineNumbers.push(i);
+
+  try {
+    const lineBeginPartOfDrawioRE = /^```(\s.*)drawio$/;
+    const lineNumbers: number[] = [];
+    // repeat the process in each line from the top to the bottom in the editor
+    for (let i = 1, e = view.state.doc.lines; i <= e; i++) {
+      // get each line text
+      const lineTxt = view.state.doc.line(i).text;
+      const match = lineBeginPartOfDrawioRE.exec(lineTxt);
+      if (match) {
+        lineNumbers.push(i);
+      }
+    }
+    return lineNumbers;
+  }
+  catch (err) {
+    if (err instanceof Error) {
+      // eslint-disable-next-line no-console
+      console.warn(err.toString());
     }
     }
   }
   }
-  return lineNumbers;
 };
 };
 
 
 const foldDrawioSection = (lineNumbers?: number[], view?: EditorView) => {
 const foldDrawioSection = (lineNumbers?: number[], view?: EditorView) => {
   if (view == null || lineNumbers == null) {
   if (view == null || lineNumbers == null) {
     return;
     return;
   }
   }
-  lineNumbers.forEach((lineNumber) => {
-    // get the end of the lines containing '''drawio
-    const from = view.state.doc.line(lineNumber).to;
-    // get the end of the lines containing '''
-    const to = view.state.doc.line(lineNumber + 2).to;
-    view?.dispatch({
-      effects: foldEffect.of({
-        from,
-        to,
-      }),
+
+  try {
+    lineNumbers.forEach((lineNumber) => {
+      // get the end of the lines containing '''drawio
+      const from = view.state.doc.line(lineNumber).to;
+      // get the end of the lines containing '''
+      const to = view.state.doc.line(lineNumber + 2).to;
+      view?.dispatch({
+        effects: foldEffect.of({
+          from,
+          to,
+        }),
+      });
     });
     });
-  });
+  }
+  catch (err) {
+    if (err instanceof Error) {
+    // eslint-disable-next-line no-console
+      console.warn(err.toString());
+    }
+  }
 };
 };
 
 
 export const useFoldDrawio = (view?: EditorView): FoldDrawio => {
 export const useFoldDrawio = (view?: EditorView): FoldDrawio => {