From 9257cac617eebb7e954035d9b90fa367e3cfc39f Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Thu, 21 Mar 2024 17:01:03 +0000
Subject: [PATCH 001/180] move locale-utils
---
apps/app/src/client/util/locale-utils.ts | 60 ------------------------
apps/app/src/utils/locale-utils.ts | 59 +++++++++++++++++++++++
2 files changed, 59 insertions(+), 60 deletions(-)
create mode 100644 apps/app/src/utils/locale-utils.ts
diff --git a/apps/app/src/client/util/locale-utils.ts b/apps/app/src/client/util/locale-utils.ts
index 318450f8f5e..0c6fffae5d9 100644
--- a/apps/app/src/client/util/locale-utils.ts
+++ b/apps/app/src/client/util/locale-utils.ts
@@ -1,69 +1,9 @@
-import type { IncomingHttpHeaders } from 'http';
-
-import { Lang } from '@growi/core';
-
-import * as nextI18NextConfig from '^/config/next-i18next.config';
-
// https://docs.google.com/spreadsheets/d/1FoYdyEraEQuWofzbYCDPKN7EdKgS_2ZrsDrOA8scgwQ
const DIAGRAMS_NET_LANG_MAP = {
ja_JP: 'ja',
zh_CN: 'zh',
};
-const ACCEPT_LANG_MAP = {
- en: Lang.en_US,
- ja: Lang.ja_JP,
- zh: Lang.zh_CN,
-};
-
export const getDiagramsNetLangCode = (lang) => {
return DIAGRAMS_NET_LANG_MAP[lang];
};
-
-/**
- * It return the first language that matches ACCEPT_LANG_MAP keys from sorted accept languages array
- * @param sortedAcceptLanguagesArray
- */
-const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
- for (const lang of sortedAcceptLanguagesArray) {
- const matchingLang = Object.keys(ACCEPT_LANG_MAP).find(key => lang.includes(key));
- if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
- }
- return nextI18NextConfig.defaultLang;
-};
-
-/**
- * Detect locale from browser accept language
- * @param headers
- */
-export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeaders): Lang => {
- // 1. get the header accept-language
- // ex. "ja,ar-SA;q=0.8,en;q=0.6,en-CA;q=0.4,en-US;q=0.2"
- const acceptLanguages = headers['accept-language'];
-
- if (acceptLanguages == null) {
- return nextI18NextConfig.defaultLang;
- }
-
- // 1. trim blank spaces.
- // 2. separate by ,.
- // 3. if "lang;q=x", then { 'x', 'lang' } to add to the associative array.
- // if "lang" has no weight x (";q=x"), add it with key = 1.
- // ex. {'1': 'ja','0.8': 'ar-SA','0.6': 'en','0.4': 'en-CA','0.2': 'en-US'}
- const acceptLanguagesDict = acceptLanguages
- .replace(/\s+/g, '')
- .split(',')
- .map(item => item.split(/\s*;\s*q\s*=\s*/))
- .reduce((acc, [key, value = '1']) => {
- acc[value] = key;
- return acc;
- }, {});
-
- // 1. create an array of sorted languages in descending order.
- // ex. [ 'ja', 'ar-SA', 'en', 'en-CA', 'en-US' ]
- const sortedAcceptLanguagesArray = Object.keys(acceptLanguagesDict)
- .sort((x, y) => y.localeCompare(x))
- .map(item => acceptLanguagesDict[item]);
-
- return getPreferredLanguage(sortedAcceptLanguagesArray);
-};
diff --git a/apps/app/src/utils/locale-utils.ts b/apps/app/src/utils/locale-utils.ts
new file mode 100644
index 00000000000..4e0c4587b48
--- /dev/null
+++ b/apps/app/src/utils/locale-utils.ts
@@ -0,0 +1,59 @@
+import type { IncomingHttpHeaders } from 'http';
+
+import { Lang } from '@growi/core';
+
+import * as nextI18NextConfig from '^/config/next-i18next.config';
+
+const ACCEPT_LANG_MAP = {
+ en: Lang.en_US,
+ ja: Lang.ja_JP,
+ zh: Lang.zh_CN,
+};
+
+/**
+ * It return the first language that matches ACCEPT_LANG_MAP keys from sorted accept languages array
+ * @param sortedAcceptLanguagesArray
+ */
+const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
+ for (const lang of sortedAcceptLanguagesArray) {
+ const matchingLang = Object.keys(ACCEPT_LANG_MAP).find(key => lang.includes(key));
+ if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
+ }
+ return nextI18NextConfig.defaultLang;
+};
+
+/**
+ * Detect locale from browser accept language
+ * @param headers
+ */
+export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeaders): Lang => {
+ // 1. get the header accept-language
+ // ex. "ja,ar-SA;q=0.8,en;q=0.6,en-CA;q=0.4,en-US;q=0.2"
+ const acceptLanguages = headers['accept-language'];
+
+ if (acceptLanguages == null) {
+ return nextI18NextConfig.defaultLang;
+ }
+
+ // 1. trim blank spaces.
+ // 2. separate by ,.
+ // 3. if "lang;q=x", then { 'x', 'lang' } to add to the associative array.
+ // if "lang" has no weight x (";q=x"), add it with key = 1.
+ // ex. {'1': 'ja','0.8': 'ar-SA','0.6': 'en','0.4': 'en-CA','0.2': 'en-US'}
+ const acceptLanguagesDict = acceptLanguages
+ .replace(/\s+/g, '')
+ .split(',')
+ .map(item => item.split(/\s*;\s*q\s*=\s*/))
+ .reduce((acc, [key, value = '1']) => {
+ acc[value] = key;
+ return acc;
+ }, {});
+
+ // 1. create an array of sorted languages in descending order.
+ // ex. [ 'ja', 'ar-SA', 'en', 'en-CA', 'en-US' ]
+ const sortedAcceptLanguagesArray = Object.keys(acceptLanguagesDict)
+ .sort((x, y) => y.localeCompare(x))
+ .map(item => acceptLanguagesDict[item]);
+
+ return getPreferredLanguage(sortedAcceptLanguagesArray);
+};
From 002041b9439696f1d8dd6e4e1f9594da8a3531ea Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Thu, 21 Mar 2024 17:03:04 +0000
Subject: [PATCH 002/180] add determine lang logic
---
apps/app/src/pages/utils/commons.ts | 2 +-
.../server/routes/apiv3/page/create-page.ts | 25 ++++++++++++-------
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/apps/app/src/pages/utils/commons.ts b/apps/app/src/pages/utils/commons.ts
index c4bca78c36c..8f35708d6d6 100644
--- a/apps/app/src/pages/utils/commons.ts
+++ b/apps/app/src/pages/utils/commons.ts
@@ -8,7 +8,6 @@ import type { SSRConfig, UserConfig } from 'next-i18next';
import * as nextI18NextConfig from '^/config/next-i18next.config';
-import { detectLocaleFromBrowserAcceptLanguage } from '~/client/util/locale-utils';
import { type SupportedActionType } from '~/interfaces/activity';
import type { CrowiRequest } from '~/interfaces/crowi-request';
import type { ISidebarConfig } from '~/interfaces/sidebar-config';
@@ -18,6 +17,7 @@ import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
import {
useCurrentProductNavWidth, useCurrentSidebarContents, usePreferCollapsedMode,
} from '~/stores/ui';
+import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
export type CommonProps = {
namespacesRequired: string[], // i18next
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index 6521b1447b6..1715cd9d2b5 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -1,7 +1,5 @@
-import { allOrigin } from '@growi/core';
-import type {
- IPage, IUser, IUserHasId,
-} from '@growi/core';
+import type { IPage, IUser, IUserHasId } from '@growi/core';
+import { allOrigin, Lang } from '@growi/core';
import { ErrorV3 } from '@growi/core/dist/models';
import { isCreatablePage, isUserPage, isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
import { attachTitleHeader, normalizePath } from '@growi/core/dist/utils/path-utils';
@@ -22,13 +20,13 @@ import {
import type { PageDocument, PageModel } from '~/server/models/page';
import PageTagRelation from '~/server/models/page-tag-relation';
import { configManager } from '~/server/service/config-manager';
+import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
import loggerFactory from '~/utils/logger';
import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
import { excludeReadOnlyUser } from '../../../middlewares/exclude-read-only-user';
import type { ApiV3Response } from '../interfaces/apiv3-response';
-
const logger = loggerFactory('growi:routes:apiv3:page:create-page');
@@ -42,9 +40,13 @@ async function generateUntitledPath(parentPath: string, basePathname: string, in
return path;
}
-async function determinePath(_parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
- // TODO: i18n
- const basePathname = 'Untitled';
+async function determinePath(locale: Lang, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
+ const basePathnames = {
+ [Lang.en_US]: 'Untitled',
+ [Lang.ja_JP]: '無題のページ',
+ [Lang.zh_CN]: 'Untitled',
+ };
+ const basePathname = basePathnames[locale] || 'Untitled';
if (_path != null) {
const path = normalizePath(_path);
@@ -211,7 +213,12 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
let pathToCreate: string;
try {
const { path, parentPath, optionalParentPath } = req.body;
- pathToCreate = await determinePath(parentPath, path, optionalParentPath);
+
+ // determine language
+ const locale = req.user == null ? detectLocaleFromBrowserAcceptLanguage(req.headers)
+ : (req.user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
+
+ pathToCreate = await determinePath(locale, parentPath, path, optionalParentPath);
}
catch (err) {
return res.apiv3Err(new ErrorV3(err.toString(), 'could_not_create_page'));
From 7435e3634309e26f2d8c6d2bf0b7c82a692356e3 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Thu, 4 Apr 2024 09:36:05 +0000
Subject: [PATCH 003/180] impl
---
apps/app/src/components/Navbar/PageEditorModeManager.tsx | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index 52508365398..88229336e19 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -68,6 +68,9 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
const { isCreating, createAndTransit } = useCreatePageAndTransit();
+ // TODO: https://redmine.weseek.co.jp/issues/132775
+ const hasDraft = false;
+
const editButtonClickedHandler = useCallback(async() => {
if (isNotFound == null || isNotFound === false) {
mutateEditorMode(EditorMode.Editor);
@@ -113,6 +116,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
onClick={editButtonClickedHandler}
>
edit_square{t('Edit')}
+ { hasDraft && }
)}
From 0b61a2c0b8e40dc1cd0c67709555c550d40a1ef8 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Thu, 4 Apr 2024 09:39:36 +0000
Subject: [PATCH 004/180] hasDraft -> hasYjsDraft
---
apps/app/src/components/Navbar/PageEditorModeManager.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index 88229336e19..e027d156ac7 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -69,7 +69,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
const { isCreating, createAndTransit } = useCreatePageAndTransit();
// TODO: https://redmine.weseek.co.jp/issues/132775
- const hasDraft = false;
+ const hasYjsDraft = false;
const editButtonClickedHandler = useCallback(async() => {
if (isNotFound == null || isNotFound === false) {
@@ -116,7 +116,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
onClick={editButtonClickedHandler}
>
edit_square{t('Edit')}
- { hasDraft && }
+ { hasYjsDraft && }
)}
From 922c6352d4f99bc97bceaa83feba0cdd3d80b112 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Thu, 4 Apr 2024 11:03:20 +0000
Subject: [PATCH 005/180] rm border
---
apps/app/src/components/Navbar/PageEditorModeManager.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index e027d156ac7..9b95330601a 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -69,7 +69,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
const { isCreating, createAndTransit } = useCreatePageAndTransit();
// TODO: https://redmine.weseek.co.jp/issues/132775
- const hasYjsDraft = false;
+ const hasYjsDraft = true;
const editButtonClickedHandler = useCallback(async() => {
if (isNotFound == null || isNotFound === false) {
@@ -116,7 +116,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
onClick={editButtonClickedHandler}
>
edit_square{t('Edit')}
- { hasYjsDraft && }
+ { hasYjsDraft && }
)}
From 5ab17827c48ee14cf6fe5562d30d773a44688f6b Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Wed, 10 Apr 2024 10:17:40 +0000
Subject: [PATCH 006/180] temp commit for check error
---
.../static/locales/en_US/translation.json | 3 +++
.../static/locales/ja_JP/translation.json | 3 +++
.../static/locales/zh_CN/translation.json | 3 +++
apps/app/src/pages/utils/commons.ts | 11 ++++------
.../server/routes/apiv3/page/create-page.ts | 22 +++++++++----------
apps/app/src/utils/locale-utils.ts | 11 +++++++++-
6 files changed, 33 insertions(+), 20 deletions(-)
diff --git a/apps/app/public/static/locales/en_US/translation.json b/apps/app/public/static/locales/en_US/translation.json
index b206a69a7b3..ef237bc943b 100644
--- a/apps/app/public/static/locales/en_US/translation.json
+++ b/apps/app/public/static/locales/en_US/translation.json
@@ -850,5 +850,8 @@
"show_wip_page": "Show WIP",
"size_s": "Size: S",
"size_l": "Size: L"
+ },
+ "create_page": {
+ "untitled": "Untitled"
}
}
diff --git a/apps/app/public/static/locales/ja_JP/translation.json b/apps/app/public/static/locales/ja_JP/translation.json
index 1a5e5628e0b..1176267a6ec 100644
--- a/apps/app/public/static/locales/ja_JP/translation.json
+++ b/apps/app/public/static/locales/ja_JP/translation.json
@@ -883,5 +883,8 @@
"show_wip_page": "WIP を表示",
"size_s": "サイズ: S",
"size_l": "サイズ: L"
+ },
+ "create_page": {
+ "untitled": "無題のページ"
}
}
diff --git a/apps/app/public/static/locales/zh_CN/translation.json b/apps/app/public/static/locales/zh_CN/translation.json
index d6cd3764541..c30e52f355f 100644
--- a/apps/app/public/static/locales/zh_CN/translation.json
+++ b/apps/app/public/static/locales/zh_CN/translation.json
@@ -853,5 +853,8 @@
"show_wip_page": "显示 WIP",
"size_s": "尺寸: S",
"size_l": "尺寸: L"
+ },
+ "create_page": {
+ "untitled": "Untitled"
}
}
diff --git a/apps/app/src/pages/utils/commons.ts b/apps/app/src/pages/utils/commons.ts
index 8f35708d6d6..20ef73f081c 100644
--- a/apps/app/src/pages/utils/commons.ts
+++ b/apps/app/src/pages/utils/commons.ts
@@ -1,5 +1,5 @@
import type { ColorScheme, IUserHasId } from '@growi/core';
-import { Lang, AllLang } from '@growi/core';
+import { AllLang } from '@growi/core';
import { DevidedPagePath } from '@growi/core/dist/models';
import { isServer } from '@growi/core/dist/utils';
import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
@@ -17,7 +17,7 @@ import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
import {
useCurrentProductNavWidth, useCurrentSidebarContents, usePreferCollapsedMode,
} from '~/stores/ui';
-import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
+import { determineLocale } from '~/utils/locale-utils';
export type CommonProps = {
namespacesRequired: string[], // i18next
@@ -120,12 +120,9 @@ export const getNextI18NextConfig = async(
): Promise => {
const req: CrowiRequest = context.req as CrowiRequest;
- const { crowi, user, headers } = req;
- const { configManager } = crowi;
+ const { user, headers } = req;
- // determine language
- const locale = user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
- : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
+ const locale = determineLocale(headers, user);
const namespaces = ['commons'];
if (namespacesRequired != null) {
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index 1715cd9d2b5..7dc0c8738cb 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -1,5 +1,7 @@
-import type { IPage, IUser, IUserHasId } from '@growi/core';
-import { allOrigin, Lang } from '@growi/core';
+import { allOrigin } from '@growi/core';
+import type {
+ IPage, IUser, IUserHasId, Lang,
+} from '@growi/core';
import { ErrorV3 } from '@growi/core/dist/models';
import { isCreatablePage, isUserPage, isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
import { attachTitleHeader, normalizePath } from '@growi/core/dist/utils/path-utils';
@@ -7,6 +9,7 @@ import type { Request, RequestHandler } from 'express';
import type { ValidationChain } from 'express-validator';
import { body } from 'express-validator';
import mongoose from 'mongoose';
+// import { i18n } from 'next-i18next';
import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
@@ -20,7 +23,7 @@ import {
import type { PageDocument, PageModel } from '~/server/models/page';
import PageTagRelation from '~/server/models/page-tag-relation';
import { configManager } from '~/server/service/config-manager';
-import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
+import { determineLocale } from '~/utils/locale-utils';
import loggerFactory from '~/utils/logger';
import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
@@ -41,12 +44,9 @@ async function generateUntitledPath(parentPath: string, basePathname: string, in
}
async function determinePath(locale: Lang, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
- const basePathnames = {
- [Lang.en_US]: 'Untitled',
- [Lang.ja_JP]: '無題のページ',
- [Lang.zh_CN]: 'Untitled',
- };
- const basePathname = basePathnames[locale] || 'Untitled';
+ // const t = i18n?.getFixedT(locale);
+ // const basePathname = t?.('create_page.untitled') || 'Undefined';
+ const basePathname = 'Undefined';
if (_path != null) {
const path = normalizePath(_path);
@@ -214,9 +214,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
try {
const { path, parentPath, optionalParentPath } = req.body;
- // determine language
- const locale = req.user == null ? detectLocaleFromBrowserAcceptLanguage(req.headers)
- : (req.user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
+ const locale = determineLocale(req.headers, req.user);
pathToCreate = await determinePath(locale, parentPath, path, optionalParentPath);
}
diff --git a/apps/app/src/utils/locale-utils.ts b/apps/app/src/utils/locale-utils.ts
index 4e0c4587b48..1388e0683ab 100644
--- a/apps/app/src/utils/locale-utils.ts
+++ b/apps/app/src/utils/locale-utils.ts
@@ -1,9 +1,13 @@
import type { IncomingHttpHeaders } from 'http';
+import type { IUser, IUserHasId } from '@growi/core';
import { Lang } from '@growi/core';
+import type { Document } from 'mongoose';
import * as nextI18NextConfig from '^/config/next-i18next.config';
+import { configManager } from '~/server/service/config-manager';
+
const ACCEPT_LANG_MAP = {
en: Lang.en_US,
ja: Lang.ja_JP,
@@ -26,7 +30,7 @@ const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
* Detect locale from browser accept language
* @param headers
*/
-export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeaders): Lang => {
+const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeaders): Lang => {
// 1. get the header accept-language
// ex. "ja,ar-SA;q=0.8,en;q=0.6,en-CA;q=0.4,en-US;q=0.2"
const acceptLanguages = headers['accept-language'];
@@ -57,3 +61,8 @@ export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeade
return getPreferredLanguage(sortedAcceptLanguagesArray);
};
+
+export const determineLocale = (headers: IncomingHttpHeaders, user?: IUserHasId | (IUser & Document)): Lang => {
+ return user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
+ : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
+};
From 730584ddbea91b5289a7ccad740175b137bfdeca Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Thu, 11 Apr 2024 05:36:13 +0000
Subject: [PATCH 007/180] impl
---
apps/app/src/pages/[[...path]].page.tsx | 4 ++++
apps/app/src/server/service/page/index.ts | 7 +++++++
.../src/server/service/yjs-connection-manager.ts | 16 ++++++++++------
3 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/apps/app/src/pages/[[...path]].page.tsx b/apps/app/src/pages/[[...path]].page.tsx
index 6fdd43f269c..a222501e2f0 100644
--- a/apps/app/src/pages/[[...path]].page.tsx
+++ b/apps/app/src/pages/[[...path]].page.tsx
@@ -172,6 +172,8 @@ type Props = CommonProps & {
skipSSR: boolean,
ssrMaxRevisionBodyLength: number,
+ hasYjsDraft: boolean,
+
grantData?: IPageGrantData,
rendererConfig: RendererConfig,
@@ -525,6 +527,8 @@ async function injectRoutingInformation(context: GetServerSidePropsContext, prop
props.currentPathname = `/${page._id}`;
}
}
+
+ props.hasYjsDraft = crowi.pageService.hasYjsDraft(page._id);
}
}
diff --git a/apps/app/src/server/service/page/index.ts b/apps/app/src/server/service/page/index.ts
index bc57a8a796b..08be08c8d5a 100644
--- a/apps/app/src/server/service/page/index.ts
+++ b/apps/app/src/server/service/page/index.ts
@@ -40,6 +40,7 @@ import {
import type { PageTagRelationDocument } from '~/server/models/page-tag-relation';
import PageTagRelation from '~/server/models/page-tag-relation';
import type { UserGroupDocument } from '~/server/models/user-group';
+import { getYjsConnectionManager } from '~/server/service/yjs-connection-manager';
import { createBatchStream } from '~/server/util/batch-stream';
import { collectAncestorPaths } from '~/server/util/collect-ancestor-paths';
import loggerFactory from '~/utils/logger';
@@ -4437,6 +4438,12 @@ class PageService implements IPageService {
});
}
+ hasYjsDraft(pageId: string): boolean {
+ const yjsConnectionManager = getYjsConnectionManager();
+ const currentYdoc = yjsConnectionManager.getCurrentYdoc(pageId);
+ return currentYdoc != null;
+ }
+
async createTtlIndex(): Promise {
const wipPageExpirationSeconds = configManager.getConfig('crowi', 'app:wipPageExpirationSeconds') ?? 172800;
const collection = mongoose.connection.collection('pages');
diff --git a/apps/app/src/server/service/yjs-connection-manager.ts b/apps/app/src/server/service/yjs-connection-manager.ts
index 22bdccd5a47..7a15d55d7ed 100644
--- a/apps/app/src/server/service/yjs-connection-manager.ts
+++ b/apps/app/src/server/service/yjs-connection-manager.ts
@@ -40,13 +40,16 @@ class YjsConnectionManager {
}
public async handleYDocSync(pageId: string, initialValue: string): Promise {
+ const currentYdoc = this.getCurrentYdoc(pageId);
+ if (currentYdoc == null) {
+ return;
+ }
+
const persistedYdoc = await this.mdb.getYDoc(pageId);
const persistedStateVector = Y.encodeStateVector(persistedYdoc);
await this.mdb.flushDocument(pageId);
- const currentYdoc = this.getCurrentYdoc(pageId);
-
const persistedCodeMirrorText = persistedYdoc.getText('codemirror').toString();
const currentCodeMirrorText = currentYdoc.getText('codemirror').toString();
@@ -77,17 +80,18 @@ class YjsConnectionManager {
// TODO: https://redmine.weseek.co.jp/issues/132775
// It's necessary to confirm that the user is not editing the target page in the Editor
const currentYdoc = this.getCurrentYdoc(pageId);
+ if (currentYdoc == null) {
+ return;
+ }
+
const currentMarkdownLength = currentYdoc.getText('codemirror').length;
currentYdoc.getText('codemirror').delete(0, currentMarkdownLength);
currentYdoc.getText('codemirror').insert(0, newValue);
Y.encodeStateAsUpdate(currentYdoc);
}
- private getCurrentYdoc(pageId: string): Y.Doc {
+ public getCurrentYdoc(pageId: string): Y.Doc | undefined {
const currentYdoc = this.ysocketio.documents.get(`yjs/${pageId}`);
- if (currentYdoc == null) {
- throw new Error(`currentYdoc for pageId ${pageId} is undefined.`);
- }
return currentYdoc;
}
From 4ee2ac541a3911c1f243f949802910efdb8366f7 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Thu, 11 Apr 2024 05:50:39 +0000
Subject: [PATCH 008/180] impl useHasYjsDraft()
---
apps/app/src/pages/[[...path]].page.tsx | 4 +++-
apps/app/src/stores/page.tsx | 4 ++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/apps/app/src/pages/[[...path]].page.tsx b/apps/app/src/pages/[[...path]].page.tsx
index a222501e2f0..865bbb73695 100644
--- a/apps/app/src/pages/[[...path]].page.tsx
+++ b/apps/app/src/pages/[[...path]].page.tsx
@@ -45,7 +45,7 @@ import {
import { useEditingMarkdown } from '~/stores/editor';
import {
useSWRxCurrentPage, useSWRMUTxCurrentPage, useSWRxIsGrantNormalized, useCurrentPageId,
- useIsNotFound, useIsLatestRevision, useTemplateTagData, useTemplateBodyData,
+ useIsNotFound, useIsLatestRevision, useTemplateTagData, useTemplateBodyData, useHasYjsDraft,
} from '~/stores/page';
import { useRedirectFrom } from '~/stores/page-redirect';
import { useRemoteRevisionId } from '~/stores/remote-latest-page';
@@ -226,6 +226,8 @@ const Page: NextPageWithLayout = (props: Props) => {
useIsUploadAllFileAllowed(props.isUploadAllFileAllowed);
useIsUploadEnabled(props.isUploadEnabled);
+ useHasYjsDraft(props.hasYjsDraft);
+
const { pageWithMeta } = props;
const pageId = pageWithMeta?.data._id;
diff --git a/apps/app/src/stores/page.tsx b/apps/app/src/stores/page.tsx
index 74b8f332b68..646caef38f7 100644
--- a/apps/app/src/stores/page.tsx
+++ b/apps/app/src/stores/page.tsx
@@ -52,6 +52,10 @@ export const useTemplateBodyData = (initialData?: string): SWRResponse('templateBodyData', initialData);
};
+export const useHasYjsDraft = (initialData?: boolean): SWRResponse => {
+ return useSWRStatic('hasYjsDraft', initialData);
+};
+
/** "useSWRxCurrentPage" is intended for initial data retrieval only. Use "useSWRMUTxCurrentPage" for revalidation */
export const useSWRxCurrentPage = (initialData?: IPagePopulatedToShowRevision|null): SWRResponse => {
const key = 'currentPage';
From 47fb469de5fc9bd0ad9918e5df4348ce8c6d671f Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Thu, 11 Apr 2024 06:09:06 +0000
Subject: [PATCH 009/180] Use useHasYjsDraft()
---
apps/app/src/components/Navbar/PageEditorModeManager.tsx | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index 9b95330601a..6e1ab07a073 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -6,7 +6,7 @@ import { useTranslation } from 'next-i18next';
import { useCreatePageAndTransit } from '~/client/services/create-page';
import { toastError } from '~/client/util/toastr';
-import { useIsNotFound } from '~/stores/page';
+import { useIsNotFound, useHasYjsDraft } from '~/stores/page';
import { EditorMode, useEditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
import { shouldCreateWipPage } from '../../utils/should-create-wip-page';
@@ -65,12 +65,10 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
const { data: isNotFound } = useIsNotFound();
const { mutate: mutateEditorMode } = useEditorMode();
const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
+ const { data: hasYjsDraft } = useHasYjsDraft();
const { isCreating, createAndTransit } = useCreatePageAndTransit();
- // TODO: https://redmine.weseek.co.jp/issues/132775
- const hasYjsDraft = true;
-
const editButtonClickedHandler = useCallback(async() => {
if (isNotFound == null || isNotFound === false) {
mutateEditorMode(EditorMode.Editor);
From d9eb941b018e7f5196267f887b15e4d5d9a7135d Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 06:45:09 +0000
Subject: [PATCH 010/180] dont use slideviewer and renderers
---
.../src/client/services/renderer/renderer.tsx | 3 -
.../renderer/slide-viewer-renderer.tsx | 90 -------------------
.../ReactMarkdownComponents/SlideViewer.tsx | 12 ++-
apps/app/src/stores/slide-viewer-renderer.tsx | 27 ------
packages/presentation/src/index.ts | 1 -
.../src/services/renderer/slides.ts | 89 ------------------
6 files changed, 5 insertions(+), 217 deletions(-)
delete mode 100644 apps/app/src/client/services/renderer/slide-viewer-renderer.tsx
delete mode 100644 apps/app/src/stores/slide-viewer-renderer.tsx
delete mode 100644 packages/presentation/src/services/renderer/slides.ts
diff --git a/apps/app/src/client/services/renderer/renderer.tsx b/apps/app/src/client/services/renderer/renderer.tsx
index 4b14867830e..677b3c7e702 100644
--- a/apps/app/src/client/services/renderer/renderer.tsx
+++ b/apps/app/src/client/services/renderer/renderer.tsx
@@ -19,7 +19,6 @@ import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents
import { Header } from '~/components/ReactMarkdownComponents/Header';
import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
-import { SlideViewer } from '~/components/ReactMarkdownComponents/SlideViewer';
import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
import * as mermaid from '~/features/mermaid';
import { RehypeSanitizeOption } from '~/interfaces/rehype';
@@ -119,7 +118,6 @@ export const generateViewOptions = (
components.mermaid = mermaid.MermaidViewer;
components.attachment = RichAttachment;
components.img = LightBox;
- components.slide = SlideViewer;
}
if (config.isEnabledXssPrevention) {
@@ -306,7 +304,6 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
components.mermaid = mermaid.MermaidViewer;
components.attachment = RichAttachment;
components.img = LightBox;
- components.slide = SlideViewer;
}
if (config.isEnabledXssPrevention) {
diff --git a/apps/app/src/client/services/renderer/slide-viewer-renderer.tsx b/apps/app/src/client/services/renderer/slide-viewer-renderer.tsx
deleted file mode 100644
index f0792ea7b7b..00000000000
--- a/apps/app/src/client/services/renderer/slide-viewer-renderer.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
-import * as drawio from '@growi/remark-drawio';
-// eslint-disable-next-line import/extensions
-import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client';
-import katex from 'rehype-katex';
-import sanitize from 'rehype-sanitize';
-import math from 'remark-math';
-import deepmerge from 'ts-deepmerge';
-import type { Pluggable } from 'unified';
-
-import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
-import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
-import * as mermaid from '~/features/mermaid';
-import { RehypeSanitizeOption } from '~/interfaces/rehype';
-import type { RendererOptions } from '~/interfaces/renderer-options';
-import type { RendererConfig } from '~/interfaces/services/renderer';
-import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-line-number-attribute';
-import * as attachment from '~/services/renderer/remark-plugins/attachment';
-import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
-import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
-import {
- commonSanitizeOption, generateCommonOptions, injectCustomSanitizeOption, verifySanitizePlugin,
-} from '~/services/renderer/renderer';
-
-
-export const generatePresentationViewOptions = (
- config: RendererConfig,
- pagePath: string,
-): RendererOptions => {
- const options = generateCommonOptions(pagePath);
-
- const { remarkPlugins, rehypePlugins, components } = options;
-
- // add remark plugins
- remarkPlugins.push(
- math,
- [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
- drawio.remarkPlugin,
- mermaid.remarkPlugin,
- xsvToTable.remarkPlugin,
- attachment.remarkPlugin,
- lsxGrowiDirective.remarkPlugin,
- refsGrowiDirective.remarkPlugin,
- );
-
- if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
- injectCustomSanitizeOption(config);
- }
-
-
- const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
- ? [sanitize, deepmerge(
- commonSanitizeOption,
- drawio.sanitizeOption,
- mermaid.sanitizeOption,
- attachment.sanitizeOption,
- lsxGrowiDirective.sanitizeOption,
- refsGrowiDirective.sanitizeOption,
- addLineNumberAttribute.sanitizeOption,
- )]
- : () => {};
-
- // add rehype plugins
- rehypePlugins.push(
- [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
- [refsGrowiDirective.rehypePlugin, { pagePath }],
- rehypeSanitizePlugin,
- addLineNumberAttribute.rehypePlugin,
- katex,
- );
-
- // add components
- if (components != null) {
- components.lsx = lsxGrowiDirective.LsxImmutable;
- components.ref = refsGrowiDirective.RefImmutable;
- components.refs = refsGrowiDirective.RefsImmutable;
- components.refimg = refsGrowiDirective.RefImgImmutable;
- components.refsimg = refsGrowiDirective.RefsImgImmutable;
- components.gallery = refsGrowiDirective.GalleryImmutable;
- components.drawio = drawio.DrawioViewer;
- components.mermaid = mermaid.MermaidViewer;
- components.attachment = RichAttachment;
- components.img = LightBox;
- }
-
- if (config.isEnabledXssPrevention) {
- verifySanitizePlugin(options, false);
- }
- return options;
-};
diff --git a/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx b/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
index 93ca9c05977..71ca98332b9 100644
--- a/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
+++ b/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
@@ -1,25 +1,23 @@
import React from 'react';
import dynamic from 'next/dynamic';
-import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
-
-import { usePresentationViewOptions } from '~/stores/slide-viewer-renderer';
+import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
+import type { RendererOptions } from '~/interfaces/renderer-options';
const Slides = dynamic(() => import('../Presentation/Slides').then(mod => mod.Slides), { ssr: false });
type SlideViewerProps = {
marp: string | undefined,
children: string,
+ rendererOptions: RendererOptions,
}
-export const SlideViewer: React.FC = React.memo((props: SlideViewerProps) => {
+export const SlideViewer = React.memo((props: SlideViewerProps) => {
const {
- marp, children,
+ marp, children, rendererOptions,
} = props;
- const { data: rendererOptions } = usePresentationViewOptions();
-
return (
=> {
- const { data: currentPagePath } = useCurrentPagePath();
- const { data: rendererConfig } = useRendererConfig();
-
- const isAllDataValid = currentPagePath != null && rendererConfig != null;
-
- return useSWR(
- isAllDataValid
- ? ['presentationViewOptions', currentPagePath, rendererConfig]
- : null,
- async([, currentPagePath, rendererConfig]) => {
- const { generatePresentationViewOptions } = await import('~/client/services/renderer/slide-viewer-renderer');
- return generatePresentationViewOptions(rendererConfig, currentPagePath);
- },
- {
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- },
- );
-};
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts
index 39eb44c3132..4dd96f2470c 100644
--- a/packages/presentation/src/index.ts
+++ b/packages/presentation/src/index.ts
@@ -1,3 +1,2 @@
export * from './components/Presentation';
export * from './components/Slides';
-export * from './services/renderer/slides';
diff --git a/packages/presentation/src/services/renderer/slides.ts b/packages/presentation/src/services/renderer/slides.ts
deleted file mode 100644
index 3f215bfbb09..00000000000
--- a/packages/presentation/src/services/renderer/slides.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import type { Schema as SanitizeOption } from 'hast-util-sanitize';
-import type { Root } from 'mdast';
-import { frontmatterToMarkdown } from 'mdast-util-frontmatter';
-import { gfmToMarkdown } from 'mdast-util-gfm';
-import { toMarkdown } from 'mdast-util-to-markdown';
-import type { Plugin } from 'unified';
-import type { Node } from 'unist';
-import { visit } from 'unist-util-visit';
-
-import { parseSlideFrontmatter } from '../parse-slide-frontmatter';
-
-const SUPPORTED_ATTRIBUTES = ['children', 'marp'];
-
-const nodeToMakrdown = (node: Node) => {
- return toMarkdown(node as Root, {
- extensions: [
- frontmatterToMarkdown(['yaml']),
- gfmToMarkdown(),
- ],
- });
-};
-
-// Allow node tree to be converted to markdown
-const removeCustomType = (tree: Node) => {
- // Try toMarkdown() on all Node.
- visit(tree, (node) => {
- const tmp = node?.children;
- node.children = [];
- try {
- nodeToMakrdown(node);
- }
- catch (err) {
- // if some Node cannot convert to markdown, change to a convertible type
- node.type = 'text';
- node.value = '';
- }
- finally {
- node.children = tmp;
- }
- });
-};
-
-const rewriteNode = (tree: Node, node: Node, isEnabledMarp: boolean) => {
-
- const [marp, slide] = parseSlideFrontmatter(node.value as string);
-
- if ((marp && isEnabledMarp) || slide) {
-
- removeCustomType(tree);
-
- const markdown = nodeToMakrdown(tree);
-
- const newNode: Node = {
- type: 'root',
- data: {},
- position: tree.position,
- children: tree.children,
- };
-
- const data = newNode.data ?? (newNode.data = {});
- tree.children = [newNode];
- data.hName = 'slide';
- data.hProperties = {
- marp: (marp && isEnabledMarp) ? '' : undefined,
- children: markdown,
- };
- }
-};
-
-type SlidePluginParams = {
- isEnabledMarp: boolean,
-}
-
-export const remarkPlugin: Plugin<[SlidePluginParams]> = (options) => {
- return (tree) => {
- visit(tree, (node) => {
- if (node.type === 'yaml' && node.value != null) {
- rewriteNode(tree, node, options.isEnabledMarp);
- }
- });
- };
-};
-
-export const sanitizeOption: SanitizeOption = {
- tagNames: ['slide'],
- attributes: {
- slide: SUPPORTED_ATTRIBUTES,
- },
-};
From eaa7374e8ca21953d34c37ef34c9f8e324f3f5cd Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 07:10:01 +0000
Subject: [PATCH 011/180] remove slide in renderer
---
apps/app/src/client/services/renderer/renderer.tsx | 5 -----
1 file changed, 5 deletions(-)
diff --git a/apps/app/src/client/services/renderer/renderer.tsx b/apps/app/src/client/services/renderer/renderer.tsx
index 677b3c7e702..9072ffdf0dd 100644
--- a/apps/app/src/client/services/renderer/renderer.tsx
+++ b/apps/app/src/client/services/renderer/renderer.tsx
@@ -1,7 +1,6 @@
import assert from 'assert';
import { isClient } from '@growi/core/dist/utils/browser-utils';
-import * as slides from '@growi/presentation';
import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
import * as drawio from '@growi/remark-drawio';
// eslint-disable-next-line import/extensions
@@ -67,7 +66,6 @@ export const generateViewOptions = (
attachment.remarkPlugin,
lsxGrowiDirective.remarkPlugin,
refsGrowiDirective.remarkPlugin,
- [slides.remarkPlugin, { isEnabledMarp: config.isEnabledMarp }],
);
if (config.isEnabledLinebreaks) {
remarkPlugins.push(breaks);
@@ -83,7 +81,6 @@ export const generateViewOptions = (
drawio.sanitizeOption,
mermaid.sanitizeOption,
attachment.sanitizeOption,
- slides.sanitizeOption,
lsxGrowiDirective.sanitizeOption,
refsGrowiDirective.sanitizeOption,
)]
@@ -260,7 +257,6 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
attachment.remarkPlugin,
lsxGrowiDirective.remarkPlugin,
refsGrowiDirective.remarkPlugin,
- [slides.remarkPlugin, { isEnabledMarp: config.isEnabledMarp }],
);
if (config.isEnabledLinebreaks) {
remarkPlugins.push(breaks);
@@ -279,7 +275,6 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
lsxGrowiDirective.sanitizeOption,
refsGrowiDirective.sanitizeOption,
addLineNumberAttribute.sanitizeOption,
- slides.sanitizeOption,
)]
: () => {};
From 15f3bdc7481c3b6927220a4109f465040f88732b Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 08:36:10 +0000
Subject: [PATCH 012/180] slide presentation
---
.../ReactMarkdownComponents/SlideViewer.tsx | 4 +-
.../src/components/Presentation.tsx | 4 +-
.../src/services/has-enabled-slide-types.ts | 25 +++++++++++
.../src/services/parse-slide-frontmatter.ts | 43 -------------------
4 files changed, 29 insertions(+), 47 deletions(-)
create mode 100644 packages/presentation/src/services/has-enabled-slide-types.ts
delete mode 100644 packages/presentation/src/services/parse-slide-frontmatter.ts
diff --git a/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx b/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
index 71ca98332b9..210da8276ab 100644
--- a/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
+++ b/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
@@ -8,7 +8,7 @@ import type { RendererOptions } from '~/interfaces/renderer-options';
const Slides = dynamic(() => import('../Presentation/Slides').then(mod => mod.Slides), { ssr: false });
type SlideViewerProps = {
- marp: string | undefined,
+ marp: boolean,
children: string,
rendererOptions: RendererOptions,
}
@@ -20,7 +20,7 @@ export const SlideViewer = React.memo((props: SlideViewerProps) => {
return (
{children}
diff --git a/packages/presentation/src/components/Presentation.tsx b/packages/presentation/src/components/Presentation.tsx
index e4b0e6beba6..c47b0392e92 100644
--- a/packages/presentation/src/components/Presentation.tsx
+++ b/packages/presentation/src/components/Presentation.tsx
@@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
import Reveal from 'reveal.js';
import type { PresentationOptions } from '../consts';
-import { parseSlideFrontmatterInMarkdown } from '../services/parse-slide-frontmatter';
+import { hasEnabledSlideTypes } from '../services/has-enabled-slide-types';
import { Slides } from './Slides';
@@ -42,7 +42,7 @@ export const Presentation = (props: PresentationProps): JSX.Element => {
const { options, isEnabledMarp, children } = props;
const { revealOptions } = options;
- const [marp] = parseSlideFrontmatterInMarkdown(children);
+ const [, marp] = hasEnabledSlideTypes(children);
const hasMarpFlag = isEnabledMarp && marp;
useEffect(() => {
diff --git a/packages/presentation/src/services/has-enabled-slide-types.ts b/packages/presentation/src/services/has-enabled-slide-types.ts
new file mode 100644
index 00000000000..e079cf8465f
--- /dev/null
+++ b/packages/presentation/src/services/has-enabled-slide-types.ts
@@ -0,0 +1,25 @@
+export const hasEnabledSlideTypes = (markdown?: string): [boolean, boolean, boolean] => {
+
+ if (markdown == null) {
+ return [false, false, false];
+ }
+
+ const text = markdown.slice(0, 1000);
+
+ const reStartFrontmatter = /^---\n/;
+ const reEndFrontmatter = /\n---\n/;
+
+ if (!reStartFrontmatter.test(text) && !reEndFrontmatter.test(text)) {
+ return [false, false, false];
+ }
+
+ const reEnableMarp = /\nmarp\s*:\s+true\n/;
+ const reEnableSlide = /\nslide\s*:\s+true\n/;
+
+ const marp = reEnableMarp.test(text) && reEnableMarp.lastIndex < reEndFrontmatter.lastIndex;
+ const slide = reEnableSlide.test(text) && reEnableSlide.lastIndex < reEndFrontmatter.lastIndex;
+
+ const enable = marp || slide;
+
+ return [enable, marp, slide];
+};
diff --git a/packages/presentation/src/services/parse-slide-frontmatter.ts b/packages/presentation/src/services/parse-slide-frontmatter.ts
deleted file mode 100644
index 5b30d21e0f4..00000000000
--- a/packages/presentation/src/services/parse-slide-frontmatter.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import remarkFrontmatter from 'remark-frontmatter';
-import remarkParse from 'remark-parse';
-import remarkStringify from 'remark-stringify';
-import { unified } from 'unified';
-
-
-export const parseSlideFrontmatter = (frontmatter: string): [boolean, boolean] => {
-
- let marp = false;
- let slide = false;
-
- const lines = frontmatter.split('\n');
- lines.forEach((line) => {
- const [key, value] = line.split(':').map(part => part.trim());
- if (key === 'marp' && value === 'true') {
- marp = true;
- }
- if (key === 'slide' && value === 'true') {
- slide = true;
- }
- });
-
- return [marp, slide];
-};
-
-export const parseSlideFrontmatterInMarkdown = (markdown?: string): [boolean, boolean] => {
-
- let marp = false;
- let slide = false;
-
- unified()
- .use(remarkParse)
- .use(remarkStringify)
- .use(remarkFrontmatter, ['yaml'])
- .use(() => ((obj) => {
- if (obj.children[0]?.type === 'yaml') {
- [marp, slide] = parseSlideFrontmatter(obj.children[0]?.value as string);
- }
- }))
- .process(markdown as string);
-
- return [marp, slide];
-};
From 01d574a66189ea375770a3fea3f183725792f400 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 08:45:19 +0000
Subject: [PATCH 013/180] add to switch slide type
---
.../src/components/Page/RevisionRenderer.tsx | 35 +++++++++++++++----
packages/presentation/src/index.ts | 1 +
2 files changed, 29 insertions(+), 7 deletions(-)
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index 3f1c13457ca..ad2dc4a7bd4 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -1,12 +1,16 @@
import React from 'react';
-import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
+import { hasEnabledSlideTypes } from '@growi/presentation';
+import type { FallbackProps } from 'react-error-boundary';
+import { ErrorBoundary } from 'react-error-boundary';
import ReactMarkdown from 'react-markdown';
import type { RendererOptions } from '~/interfaces/renderer-options';
import loggerFactory from '~/utils/logger';
+
import 'katex/dist/katex.min.css';
+import { SlideViewer } from '../ReactMarkdownComponents/SlideViewer';
const logger = loggerFactory('components:Page:RevisionRenderer');
@@ -33,14 +37,31 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
rendererOptions, markdown, additionalClassName,
} = props;
+ const [enableSlide, marp] = hasEnabledSlideTypes(markdown);
+
return (
-
- {markdown}
-
+ {
+ enableSlide
+ ? (
+
+ {markdown}
+
+ )
+ : (
+
+ {markdown}
+
+ )
+
+
+ }
);
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts
index 4dd96f2470c..08920a0a4fc 100644
--- a/packages/presentation/src/index.ts
+++ b/packages/presentation/src/index.ts
@@ -1,2 +1,3 @@
export * from './components/Presentation';
export * from './components/Slides';
+export * from './services/has-enabled-slide-types';
From 955f61b64f8cfccd68f62dd085f29ddacb64f3ee Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 09:15:39 +0000
Subject: [PATCH 014/180] global regexp
---
.../src/services/has-enabled-slide-types.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/packages/presentation/src/services/has-enabled-slide-types.ts b/packages/presentation/src/services/has-enabled-slide-types.ts
index e079cf8465f..814af89fadc 100644
--- a/packages/presentation/src/services/has-enabled-slide-types.ts
+++ b/packages/presentation/src/services/has-enabled-slide-types.ts
@@ -4,17 +4,17 @@ export const hasEnabledSlideTypes = (markdown?: string): [boolean, boolean, bool
return [false, false, false];
}
- const text = markdown.slice(0, 1000);
+ const text = markdown.slice(0, 300);
- const reStartFrontmatter = /^---\n/;
- const reEndFrontmatter = /\n---\n/;
+ const reStartFrontmatter = /^---\s*\n/g;
+ const reEndFrontmatter = /\n---\s*\n/g;
- if (!reStartFrontmatter.test(text) && !reEndFrontmatter.test(text)) {
+ if (!reStartFrontmatter.test(text) || !reEndFrontmatter.test(text)) {
return [false, false, false];
}
- const reEnableMarp = /\nmarp\s*:\s+true\n/;
- const reEnableSlide = /\nslide\s*:\s+true\n/;
+ const reEnableMarp = /\nmarp\s*:\s+true\n/g;
+ const reEnableSlide = /\nslide\s*:\s+true\n/g;
const marp = reEnableMarp.test(text) && reEnableMarp.lastIndex < reEndFrontmatter.lastIndex;
const slide = reEnableSlide.test(text) && reEnableSlide.lastIndex < reEndFrontmatter.lastIndex;
From cfda00ddb1cc67a4eead0bae9163f18e0b2547da Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 09:55:44 +0000
Subject: [PATCH 015/180] use hook
---
apps/app/src/components/Page/RevisionRenderer.tsx | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index ad2dc4a7bd4..d16964b579b 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -5,12 +5,16 @@ import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import ReactMarkdown from 'react-markdown';
+
import type { RendererOptions } from '~/interfaces/renderer-options';
+import { useIsEnabledMarp } from '~/stores/context';
import loggerFactory from '~/utils/logger';
+import { SlideViewer } from '../ReactMarkdownComponents/SlideViewer';
+
import 'katex/dist/katex.min.css';
-import { SlideViewer } from '../ReactMarkdownComponents/SlideViewer';
+
const logger = loggerFactory('components:Page:RevisionRenderer');
@@ -37,6 +41,7 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
rendererOptions, markdown, additionalClassName,
} = props;
+ const { data: isEnabledMarp } = useIsEnabledMarp();
const [enableSlide, marp] = hasEnabledSlideTypes(markdown);
return (
@@ -46,7 +51,7 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
? (
{markdown}
@@ -59,8 +64,6 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
{markdown}
)
-
-
}
);
From 2129a1ebfd77551e8046e567e0aa1974720375e0 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 10:12:02 +0000
Subject: [PATCH 016/180] fix export problem
---
apps/app/src/components/Page/RevisionRenderer.tsx | 2 +-
packages/presentation/src/index.ts | 1 -
packages/presentation/src/services/index.ts | 1 +
3 files changed, 2 insertions(+), 2 deletions(-)
create mode 100644 packages/presentation/src/services/index.ts
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index d16964b579b..ec97376cc90 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { hasEnabledSlideTypes } from '@growi/presentation';
+import { hasEnabledSlideTypes } from '@growi/presentation/src/services';
import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import ReactMarkdown from 'react-markdown';
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts
index 08920a0a4fc..4dd96f2470c 100644
--- a/packages/presentation/src/index.ts
+++ b/packages/presentation/src/index.ts
@@ -1,3 +1,2 @@
export * from './components/Presentation';
export * from './components/Slides';
-export * from './services/has-enabled-slide-types';
diff --git a/packages/presentation/src/services/index.ts b/packages/presentation/src/services/index.ts
new file mode 100644
index 00000000000..393ad416d81
--- /dev/null
+++ b/packages/presentation/src/services/index.ts
@@ -0,0 +1 @@
+export * from './has-enabled-slide-types';
From 1a20fd0e24a6ceab3f067e2daaa02346f5a6926a Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 12 Apr 2024 10:13:43 +0000
Subject: [PATCH 017/180] clean code export
---
packages/presentation/src/components/index.ts | 2 ++
packages/presentation/src/index.ts | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
create mode 100644 packages/presentation/src/components/index.ts
diff --git a/packages/presentation/src/components/index.ts b/packages/presentation/src/components/index.ts
new file mode 100644
index 00000000000..8d3c4172753
--- /dev/null
+++ b/packages/presentation/src/components/index.ts
@@ -0,0 +1,2 @@
+export * from './Slides';
+export * from './Presentation';
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts
index 4dd96f2470c..80e3bea6755 100644
--- a/packages/presentation/src/index.ts
+++ b/packages/presentation/src/index.ts
@@ -1,2 +1,2 @@
-export * from './components/Presentation';
-export * from './components/Slides';
+export * from './components';
+export * from './services';
From 323d4258a6e900c73efba8d047647ee5b340ed19 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 16 Apr 2024 05:10:15 +0000
Subject: [PATCH 018/180] use presentation view options directly
---
.../app/src/client/services/renderer/renderer.tsx | 15 +++++++++++++++
.../ReactMarkdownComponents/SlideViewer.tsx | 7 ++++---
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/apps/app/src/client/services/renderer/renderer.tsx b/apps/app/src/client/services/renderer/renderer.tsx
index 9072ffdf0dd..847cf663fef 100644
--- a/apps/app/src/client/services/renderer/renderer.tsx
+++ b/apps/app/src/client/services/renderer/renderer.tsx
@@ -236,6 +236,21 @@ export const generatePresentationViewOptions = (
// based on simple view options
const options = generateSimpleViewOptions(config, pagePath);
+ const { rehypePlugins } = options;
+
+
+ const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
+ ? [sanitize, deepmerge(
+ addLineNumberAttribute.sanitizeOption,
+ )]
+ : () => {};
+
+ // add rehype plugins
+ rehypePlugins.push(
+ addLineNumberAttribute.rehypePlugin,
+ rehypeSanitizePlugin,
+ );
+
if (config.isEnabledXssPrevention) {
verifySanitizePlugin(options, false);
}
diff --git a/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx b/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
index 210da8276ab..0c2f66ffb07 100644
--- a/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
+++ b/apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
@@ -3,21 +3,22 @@ import React from 'react';
import dynamic from 'next/dynamic';
import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
-import type { RendererOptions } from '~/interfaces/renderer-options';
+import { usePresentationViewOptions } from '~/stores/renderer';
const Slides = dynamic(() => import('../Presentation/Slides').then(mod => mod.Slides), { ssr: false });
type SlideViewerProps = {
marp: boolean,
children: string,
- rendererOptions: RendererOptions,
}
export const SlideViewer = React.memo((props: SlideViewerProps) => {
const {
- marp, children, rendererOptions,
+ marp, children,
} = props;
+ const { data: rendererOptions } = usePresentationViewOptions();
+
return (
Date: Tue, 16 Apr 2024 05:10:21 +0000
Subject: [PATCH 019/180] fix typo
---
.../components/Admin/Customize/CustomizePresentationSetting.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx b/apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
index bbe02c5769c..7f9937e8acc 100644
--- a/apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
+++ b/apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
@@ -53,7 +53,7 @@ const CustomizePresentationSetting = (props: Props): JSX.Element => {
href={`${t('admin:customize_settings.presentation_options.marp_in_gorwi_link')}`}
target="_blank"
rel="noopener noreferrer"
- >{`${t('admin:customize_settings.presenattion_options.marp_in_growi')}`}
+ >{`${t('admin:customize_settings.presentation_options.marp_in_growi')}`}
From cee266bc728792a178a02140a03c0b1a5d0001ab Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 16 Apr 2024 05:15:05 +0000
Subject: [PATCH 020/180] enable slide
---
apps/app/src/components/Page/RevisionRenderer.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index ec97376cc90..ed1acdb4926 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -42,16 +42,16 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
} = props;
const { data: isEnabledMarp } = useIsEnabledMarp();
- const [enableSlide, marp] = hasEnabledSlideTypes(markdown);
+ const [, marp, useSlide] = hasEnabledSlideTypes(markdown);
+ const useMarp = !!isEnabledMarp && marp;
return (
{
- enableSlide
+ (useMarp || useSlide)
? (
{markdown}
From b6067363d7fdf686bf637c5ba3f3604863a6d74e Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 16 Apr 2024 06:01:19 +0000
Subject: [PATCH 021/180] clean code
---
.../src/components/Presentation.tsx | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/packages/presentation/src/components/Presentation.tsx b/packages/presentation/src/components/Presentation.tsx
index c47b0392e92..cd8d20835b8 100644
--- a/packages/presentation/src/components/Presentation.tsx
+++ b/packages/presentation/src/components/Presentation.tsx
@@ -46,19 +46,19 @@ export const Presentation = (props: PresentationProps): JSX.Element => {
const hasMarpFlag = isEnabledMarp && marp;
useEffect(() => {
- let deck: Reveal.Api;
if (children != null) {
- deck = new Reveal({ ...baseRevealOptions, ...revealOptions });
- deck.initialize()
- .then(() => deck.slide(0)); // navigate to the first slide
-
- deck.on('ready', removeAllHiddenElements);
- deck.on('slidechanged', removeAllHiddenElements);
+ return;
}
+ const deck = new Reveal({ ...baseRevealOptions, ...revealOptions });
+ deck.initialize()
+ .then(() => deck.slide(0)); // navigate to the first slide
+
+ deck.on('ready', removeAllHiddenElements);
+ deck.on('slidechanged', removeAllHiddenElements);
return function cleanup() {
- deck?.off('ready', removeAllHiddenElements);
- deck?.off('slidechanged', removeAllHiddenElements);
+ deck.off('ready', removeAllHiddenElements);
+ deck.off('slidechanged', removeAllHiddenElements);
};
}, [children, revealOptions]);
From ea5425885302706eef44a572fece8236b52556d9 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Fri, 19 Apr 2024 07:51:02 +0000
Subject: [PATCH 022/180] Trigger soket.emit when a draft is created
---
.../Navbar/PageEditorModeManager.tsx | 22 +++++++++++++++++--
apps/app/src/server/service/socket-io.js | 2 +-
.../server/service/yjs-connection-manager.ts | 11 ++++++++--
3 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index 6e1ab07a073..4863de39222 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -1,6 +1,7 @@
-import React, { type ReactNode, useCallback } from 'react';
+import React, { type ReactNode, useCallback, useEffect } from 'react';
import { Origin } from '@growi/core';
+import { useGlobalSocket } from '@growi/core/dist/swr';
import { useTranslation } from 'next-i18next';
@@ -65,7 +66,8 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
const { data: isNotFound } = useIsNotFound();
const { mutate: mutateEditorMode } = useEditorMode();
const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
- const { data: hasYjsDraft } = useHasYjsDraft();
+ const { data: hasYjsDraft, mutate: mutateHasYjsDraft } = useHasYjsDraft();
+ const { data: socket } = useGlobalSocket();
const { isCreating, createAndTransit } = useCreatePageAndTransit();
@@ -86,6 +88,22 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
}
}, [createAndTransit, isNotFound, mutateEditorMode, path, t]);
+ useEffect(() => {
+
+ if (socket == null) { return }
+
+ const yjsDraftUpdateHandler = (hasYjsDraft: boolean) => {
+ mutateHasYjsDraft(hasYjsDraft);
+ };
+
+ socket.on('yjsDraft:update', yjsDraftUpdateHandler);
+
+ return () => {
+ socket.off('yjsDraft:update', yjsDraftUpdateHandler);
+ };
+
+ }, [mutateHasYjsDraft, socket]);
+
const _isBtnDisabled = isCreating || isBtnDisabled;
return (
diff --git a/apps/app/src/server/service/socket-io.js b/apps/app/src/server/service/socket-io.js
index 5f8cb8e02e3..c5d92ea2888 100644
--- a/apps/app/src/server/service/socket-io.js
+++ b/apps/app/src/server/service/socket-io.js
@@ -160,7 +160,7 @@ class SocketIoService {
this.io.on('connection', (socket) => {
socket.on(GlobalSocketEventName.YDocSync, async({ pageId, initialValue }) => {
try {
- await yjsConnectionManager.handleYDocSync(pageId, initialValue);
+ await yjsConnectionManager.handleYDocSync(pageId, initialValue, socket);
}
catch (error) {
logger.warn(error.message);
diff --git a/apps/app/src/server/service/yjs-connection-manager.ts b/apps/app/src/server/service/yjs-connection-manager.ts
index 7a15d55d7ed..3e93e446741 100644
--- a/apps/app/src/server/service/yjs-connection-manager.ts
+++ b/apps/app/src/server/service/yjs-connection-manager.ts
@@ -1,9 +1,10 @@
-import type { Server } from 'socket.io';
+import type { Server, Socket } from 'socket.io';
import { MongodbPersistence } from 'y-mongodb-provider';
import { YSocketIO } from 'y-socket.io/dist/server';
import * as Y from 'yjs';
import { getMongoUri } from '../util/mongoose-utils';
+import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
const MONGODB_PERSISTENCE_COLLECTION_NAME = 'yjs-writings';
const MONGODB_PERSISTENCE_FLUSH_SIZE = 100;
@@ -39,7 +40,7 @@ class YjsConnectionManager {
return this.instance;
}
- public async handleYDocSync(pageId: string, initialValue: string): Promise {
+ public async handleYDocSync(pageId: string, initialValue: string, socket: Socket): Promise {
const currentYdoc = this.getCurrentYdoc(pageId);
if (currentYdoc == null) {
return;
@@ -73,6 +74,12 @@ class YjsConnectionManager {
await this.mdb.flushDocument(pageId);
});
+ currentYdoc.once('update', async() => {
+ socket
+ .in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
+ .emit('yjsDraft:update', true);
+ });
+
persistedYdoc.destroy();
}
From 990d8746b80f511b2238f695c2bfb6125acabfb9 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Fri, 19 Apr 2024 07:58:06 +0000
Subject: [PATCH 023/180] use SocketEventName
---
.../src/components/Navbar/PageEditorModeManager.tsx | 5 +++--
apps/app/src/interfaces/websocket.ts | 3 +++
apps/app/src/server/service/yjs-connection-manager.ts | 11 ++++++-----
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index 4863de39222..836c3944db8 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -7,6 +7,7 @@ import { useTranslation } from 'next-i18next';
import { useCreatePageAndTransit } from '~/client/services/create-page';
import { toastError } from '~/client/util/toastr';
+import { SocketEventName } from '~/interfaces/websocket';
import { useIsNotFound, useHasYjsDraft } from '~/stores/page';
import { EditorMode, useEditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
@@ -96,10 +97,10 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
mutateHasYjsDraft(hasYjsDraft);
};
- socket.on('yjsDraft:update', yjsDraftUpdateHandler);
+ socket.on(SocketEventName.YjsUpdated, yjsDraftUpdateHandler);
return () => {
- socket.off('yjsDraft:update', yjsDraftUpdateHandler);
+ socket.off(SocketEventName.YjsUpdated, yjsDraftUpdateHandler);
};
}, [mutateHasYjsDraft, socket]);
diff --git a/apps/app/src/interfaces/websocket.ts b/apps/app/src/interfaces/websocket.ts
index c5ffa38746e..b0c1ffcb973 100644
--- a/apps/app/src/interfaces/websocket.ts
+++ b/apps/app/src/interfaces/websocket.ts
@@ -44,6 +44,9 @@ export const SocketEventName = {
PageCreated: 'page:create',
PageUpdated: 'page:update',
PageDeleted: 'page:delete',
+
+ // Yjs
+ YjsUpdated: 'yjsDraft:update',
} as const;
export type SocketEventName = typeof SocketEventName[keyof typeof SocketEventName];
diff --git a/apps/app/src/server/service/yjs-connection-manager.ts b/apps/app/src/server/service/yjs-connection-manager.ts
index 3e93e446741..952b924f528 100644
--- a/apps/app/src/server/service/yjs-connection-manager.ts
+++ b/apps/app/src/server/service/yjs-connection-manager.ts
@@ -3,6 +3,8 @@ import { MongodbPersistence } from 'y-mongodb-provider';
import { YSocketIO } from 'y-socket.io/dist/server';
import * as Y from 'yjs';
+import { SocketEventName } from '~/interfaces/websocket';
+
import { getMongoUri } from '../util/mongoose-utils';
import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
@@ -74,11 +76,10 @@ class YjsConnectionManager {
await this.mdb.flushDocument(pageId);
});
- currentYdoc.once('update', async() => {
- socket
- .in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
- .emit('yjsDraft:update', true);
- });
+ // Tell client that a draft has been created
+ socket
+ .in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
+ .emit(SocketEventName.YjsUpdated, true);
persistedYdoc.destroy();
}
From 9c8f6991c4dacea248d88e92bb1e375d37035937 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Sun, 21 Apr 2024 16:44:54 +0000
Subject: [PATCH 024/180] Emit on soket-io.js side
---
apps/app/src/server/service/socket-io.js | 6 ++++++
apps/app/src/server/service/yjs-connection-manager.ts | 8 --------
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/apps/app/src/server/service/socket-io.js b/apps/app/src/server/service/socket-io.js
index c5d92ea2888..7f35211eb89 100644
--- a/apps/app/src/server/service/socket-io.js
+++ b/apps/app/src/server/service/socket-io.js
@@ -1,6 +1,7 @@
import { GlobalSocketEventName } from '@growi/core/dist/interfaces';
import { Server } from 'socket.io';
+import { SocketEventName } from '~/interfaces/websocket';
import loggerFactory from '~/utils/logger';
import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
@@ -159,6 +160,11 @@ class SocketIoService {
const yjsConnectionManager = getYjsConnectionManager();
this.io.on('connection', (socket) => {
socket.on(GlobalSocketEventName.YDocSync, async({ pageId, initialValue }) => {
+ // Tell client that a draft has been created
+ socket
+ .in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
+ .emit(SocketEventName.YjsUpdated, true);
+
try {
await yjsConnectionManager.handleYDocSync(pageId, initialValue, socket);
}
diff --git a/apps/app/src/server/service/yjs-connection-manager.ts b/apps/app/src/server/service/yjs-connection-manager.ts
index 952b924f528..b677e4fbb29 100644
--- a/apps/app/src/server/service/yjs-connection-manager.ts
+++ b/apps/app/src/server/service/yjs-connection-manager.ts
@@ -3,10 +3,7 @@ import { MongodbPersistence } from 'y-mongodb-provider';
import { YSocketIO } from 'y-socket.io/dist/server';
import * as Y from 'yjs';
-import { SocketEventName } from '~/interfaces/websocket';
-
import { getMongoUri } from '../util/mongoose-utils';
-import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
const MONGODB_PERSISTENCE_COLLECTION_NAME = 'yjs-writings';
const MONGODB_PERSISTENCE_FLUSH_SIZE = 100;
@@ -76,11 +73,6 @@ class YjsConnectionManager {
await this.mdb.flushDocument(pageId);
});
- // Tell client that a draft has been created
- socket
- .in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
- .emit(SocketEventName.YjsUpdated, true);
-
persistedYdoc.destroy();
}
From 0528787a2aa2161a72cc25c983744e489e3391bd Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Sun, 21 Apr 2024 17:25:16 +0000
Subject: [PATCH 025/180] impl useYjsDraftEffect
---
.../client/services/side-effects/yjs-draft.ts | 28 +++++++++++++++++++
.../Navbar/PageEditorModeManager.tsx | 23 ++-------------
.../src/components/Page/DisplaySwitcher.tsx | 2 ++
3 files changed, 32 insertions(+), 21 deletions(-)
create mode 100644 apps/app/src/client/services/side-effects/yjs-draft.ts
diff --git a/apps/app/src/client/services/side-effects/yjs-draft.ts b/apps/app/src/client/services/side-effects/yjs-draft.ts
new file mode 100644
index 00000000000..43cefe1e086
--- /dev/null
+++ b/apps/app/src/client/services/side-effects/yjs-draft.ts
@@ -0,0 +1,28 @@
+import { useCallback, useEffect } from 'react';
+
+import { useGlobalSocket } from '@growi/core/dist/swr';
+
+import { SocketEventName } from '~/interfaces/websocket';
+import { useHasYjsDraft } from '~/stores/page';
+
+export const useYjsDraftEffect = (): void => {
+
+ const { mutate: mutateHasYjsDraft } = useHasYjsDraft();
+ const { data: socket } = useGlobalSocket();
+
+ const yjsDraftUpdateHandler = useCallback(((hasYjsDraft: boolean) => {
+ mutateHasYjsDraft(hasYjsDraft);
+ }), [mutateHasYjsDraft]);
+
+ useEffect(() => {
+
+ if (socket == null) { return }
+
+ socket.on(SocketEventName.YjsUpdated, yjsDraftUpdateHandler);
+
+ return () => {
+ socket.off(SocketEventName.YjsUpdated, yjsDraftUpdateHandler);
+ };
+
+ }, [mutateHasYjsDraft, socket, yjsDraftUpdateHandler]);
+};
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index 836c3944db8..c03a94a8ee6 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -1,13 +1,11 @@
-import React, { type ReactNode, useCallback, useEffect } from 'react';
+import React, { type ReactNode, useCallback } from 'react';
import { Origin } from '@growi/core';
import { useGlobalSocket } from '@growi/core/dist/swr';
import { useTranslation } from 'next-i18next';
-
import { useCreatePageAndTransit } from '~/client/services/create-page';
import { toastError } from '~/client/util/toastr';
-import { SocketEventName } from '~/interfaces/websocket';
import { useIsNotFound, useHasYjsDraft } from '~/stores/page';
import { EditorMode, useEditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
@@ -67,8 +65,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
const { data: isNotFound } = useIsNotFound();
const { mutate: mutateEditorMode } = useEditorMode();
const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
- const { data: hasYjsDraft, mutate: mutateHasYjsDraft } = useHasYjsDraft();
- const { data: socket } = useGlobalSocket();
+ const { data: hasYjsDraft } = useHasYjsDraft();
const { isCreating, createAndTransit } = useCreatePageAndTransit();
@@ -89,22 +86,6 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
}
}, [createAndTransit, isNotFound, mutateEditorMode, path, t]);
- useEffect(() => {
-
- if (socket == null) { return }
-
- const yjsDraftUpdateHandler = (hasYjsDraft: boolean) => {
- mutateHasYjsDraft(hasYjsDraft);
- };
-
- socket.on(SocketEventName.YjsUpdated, yjsDraftUpdateHandler);
-
- return () => {
- socket.off(SocketEventName.YjsUpdated, yjsDraftUpdateHandler);
- };
-
- }, [mutateHasYjsDraft, socket]);
-
const _isBtnDisabled = isCreating || isBtnDisabled;
return (
diff --git a/apps/app/src/components/Page/DisplaySwitcher.tsx b/apps/app/src/components/Page/DisplaySwitcher.tsx
index fd3f04ffc63..44882f29a52 100644
--- a/apps/app/src/components/Page/DisplaySwitcher.tsx
+++ b/apps/app/src/components/Page/DisplaySwitcher.tsx
@@ -4,6 +4,7 @@ import dynamic from 'next/dynamic';
import { useHashChangedEffect } from '~/client/services/side-effects/hash-changed';
import { usePageUpdatedEffect } from '~/client/services/side-effects/page-updated';
+import { useYjsDraftEffect } from '~/client/services/side-effects/yjs-draft';
import { useIsEditable } from '~/stores/context';
import { useIsLatestRevision } from '~/stores/page';
import { EditorMode, useEditorMode } from '~/stores/ui';
@@ -26,6 +27,7 @@ export const DisplaySwitcher = (props: Props): JSX.Element => {
usePageUpdatedEffect();
useHashChangedEffect();
+ useYjsDraftEffect();
return (
<>
From f401be9108ba1ab8b05626d75ac6891d7130afca Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Mon, 22 Apr 2024 03:02:42 +0000
Subject: [PATCH 026/180] rm unnecessary var
---
apps/app/src/server/service/yjs-connection-manager.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/app/src/server/service/yjs-connection-manager.ts b/apps/app/src/server/service/yjs-connection-manager.ts
index b677e4fbb29..7a15d55d7ed 100644
--- a/apps/app/src/server/service/yjs-connection-manager.ts
+++ b/apps/app/src/server/service/yjs-connection-manager.ts
@@ -1,4 +1,4 @@
-import type { Server, Socket } from 'socket.io';
+import type { Server } from 'socket.io';
import { MongodbPersistence } from 'y-mongodb-provider';
import { YSocketIO } from 'y-socket.io/dist/server';
import * as Y from 'yjs';
@@ -39,7 +39,7 @@ class YjsConnectionManager {
return this.instance;
}
- public async handleYDocSync(pageId: string, initialValue: string, socket: Socket): Promise {
+ public async handleYDocSync(pageId: string, initialValue: string): Promise {
const currentYdoc = this.getCurrentYdoc(pageId);
if (currentYdoc == null) {
return;
From 6130ab6cb623c0728f853a5b2c6bf5d2180a6ed6 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Mon, 22 Apr 2024 09:07:56 +0000
Subject: [PATCH 027/180] Modified to emit to itself as well
---
apps/app/src/server/service/socket-io.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/apps/app/src/server/service/socket-io.js b/apps/app/src/server/service/socket-io.js
index 7f35211eb89..56a9addac25 100644
--- a/apps/app/src/server/service/socket-io.js
+++ b/apps/app/src/server/service/socket-io.js
@@ -160,13 +160,14 @@ class SocketIoService {
const yjsConnectionManager = getYjsConnectionManager();
this.io.on('connection', (socket) => {
socket.on(GlobalSocketEventName.YDocSync, async({ pageId, initialValue }) => {
- // Tell client that a draft has been created
- socket
+
+ // Emit to the client in the room of the target pageId.
+ this.io
.in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
.emit(SocketEventName.YjsUpdated, true);
try {
- await yjsConnectionManager.handleYDocSync(pageId, initialValue, socket);
+ await yjsConnectionManager.handleYDocSync(pageId, initialValue);
}
catch (error) {
logger.warn(error.message);
From 04c0bdca3e024fb6105a1acd8d9f036a2a64bb70 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Mon, 22 Apr 2024 09:08:20 +0000
Subject: [PATCH 028/180] rm unnecessary import
---
apps/app/src/components/Navbar/PageEditorModeManager.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index c03a94a8ee6..52ac35ce9f8 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -1,7 +1,6 @@
import React, { type ReactNode, useCallback } from 'react';
import { Origin } from '@growi/core';
-import { useGlobalSocket } from '@growi/core/dist/swr';
import { useTranslation } from 'next-i18next';
import { useCreatePageAndTransit } from '~/client/services/create-page';
From d22c91d0767a6ec4ff12331b852e53c093da65ff Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Mon, 22 Apr 2024 09:59:30 +0000
Subject: [PATCH 029/180] Leave the room during cleanup
---
apps/app/src/interfaces/websocket.ts | 4 ++++
apps/app/src/server/service/socket-io.js | 13 ++++++++++++-
apps/app/src/stores/websocket.tsx | 10 ++++++++--
3 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/apps/app/src/interfaces/websocket.ts b/apps/app/src/interfaces/websocket.ts
index b0c1ffcb973..10d30800c51 100644
--- a/apps/app/src/interfaces/websocket.ts
+++ b/apps/app/src/interfaces/websocket.ts
@@ -40,6 +40,10 @@ export const SocketEventName = {
// External user group sync
externalUserGroup: generateGroupSyncEvents(),
+ // room per pageId
+ JoinPage: 'join:page',
+ LeavePage: 'leave:page',
+
// Page Operation
PageCreated: 'page:create',
PageUpdated: 'page:update',
diff --git a/apps/app/src/server/service/socket-io.js b/apps/app/src/server/service/socket-io.js
index 56a9addac25..3419666e384 100644
--- a/apps/app/src/server/service/socket-io.js
+++ b/apps/app/src/server/service/socket-io.js
@@ -52,6 +52,7 @@ class SocketIoService {
await this.setupLoginedUserRoomsJoinOnConnection();
await this.setupDefaultSocketJoinRoomsEventHandler();
+ await this.setupDefaultSocketLeaveRoomsEventHandler();
}
getDefaultSocket() {
@@ -150,12 +151,22 @@ class SocketIoService {
setupDefaultSocketJoinRoomsEventHandler() {
this.io.on('connection', (socket) => {
// set event handlers for joining rooms
- socket.on('join:page', ({ pageId }) => {
+ socket.on(SocketEventName.JoinPage, ({ pageId }) => {
+ console.log('こんにちは', pageId);
socket.join(getRoomNameWithId(RoomPrefix.PAGE, pageId));
});
});
}
+ setupDefaultSocketLeaveRoomsEventHandler() {
+ this.io.on('connection', (socket) => {
+ socket.on(SocketEventName.LeavePage, ({ pageId }) => {
+ console.log('さようなら', pageId);
+ socket.leave(getRoomNameWithId(RoomPrefix.PAGE, pageId));
+ });
+ });
+ }
+
setupYjsConnection() {
const yjsConnectionManager = getYjsConnectionManager();
this.io.on('connection', (socket) => {
diff --git a/apps/app/src/stores/websocket.tsx b/apps/app/src/stores/websocket.tsx
index d839d120f07..7ce332c6628 100644
--- a/apps/app/src/stores/websocket.tsx
+++ b/apps/app/src/stores/websocket.tsx
@@ -2,8 +2,9 @@ import { useEffect } from 'react';
import { useGlobalSocket, GLOBAL_SOCKET_KEY, GLOBAL_SOCKET_NS } from '@growi/core/dist/swr';
import type { Socket } from 'socket.io-client';
-import { SWRResponse } from 'swr';
+import type { SWRResponse } from 'swr';
+import { SocketEventName } from '~/interfaces/websocket';
import loggerFactory from '~/utils/logger';
import { useStaticSWR } from './use-static-swr';
@@ -67,6 +68,11 @@ export const useSetupGlobalSocketForPage = (pageId: string | undefined): void =>
useEffect(() => {
if (socket == null || pageId == null) { return }
- socket.emit('join:page', { socketId: socket.id, pageId });
+ socket.emit(SocketEventName.JoinPage, { socketId: socket.id, pageId });
+
+ return () => {
+ console.log('あああ');
+ socket.emit(SocketEventName.LeavePage, { socketId: socket.id, pageId });
+ };
}, [pageId, socket]);
};
From 3ffe5f3805f75d512a5c0072fdb89668fbd93faf Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Mon, 22 Apr 2024 10:00:31 +0000
Subject: [PATCH 030/180] rm debug logger
---
apps/app/src/server/service/socket-io.js | 2 --
apps/app/src/stores/websocket.tsx | 1 -
2 files changed, 3 deletions(-)
diff --git a/apps/app/src/server/service/socket-io.js b/apps/app/src/server/service/socket-io.js
index 3419666e384..55c588173e8 100644
--- a/apps/app/src/server/service/socket-io.js
+++ b/apps/app/src/server/service/socket-io.js
@@ -152,7 +152,6 @@ class SocketIoService {
this.io.on('connection', (socket) => {
// set event handlers for joining rooms
socket.on(SocketEventName.JoinPage, ({ pageId }) => {
- console.log('こんにちは', pageId);
socket.join(getRoomNameWithId(RoomPrefix.PAGE, pageId));
});
});
@@ -161,7 +160,6 @@ class SocketIoService {
setupDefaultSocketLeaveRoomsEventHandler() {
this.io.on('connection', (socket) => {
socket.on(SocketEventName.LeavePage, ({ pageId }) => {
- console.log('さようなら', pageId);
socket.leave(getRoomNameWithId(RoomPrefix.PAGE, pageId));
});
});
diff --git a/apps/app/src/stores/websocket.tsx b/apps/app/src/stores/websocket.tsx
index 7ce332c6628..f0bd637440d 100644
--- a/apps/app/src/stores/websocket.tsx
+++ b/apps/app/src/stores/websocket.tsx
@@ -71,7 +71,6 @@ export const useSetupGlobalSocketForPage = (pageId: string | undefined): void =>
socket.emit(SocketEventName.JoinPage, { socketId: socket.id, pageId });
return () => {
- console.log('あああ');
socket.emit(SocketEventName.LeavePage, { socketId: socket.id, pageId });
};
}, [pageId, socket]);
From 3bd4993fdf01cc8e386b0b2e47eec52138fcbdaf Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Mon, 22 Apr 2024 10:03:40 +0000
Subject: [PATCH 031/180] rm unnecessary args
---
apps/app/src/stores/websocket.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/app/src/stores/websocket.tsx b/apps/app/src/stores/websocket.tsx
index f0bd637440d..79e521215c9 100644
--- a/apps/app/src/stores/websocket.tsx
+++ b/apps/app/src/stores/websocket.tsx
@@ -68,10 +68,10 @@ export const useSetupGlobalSocketForPage = (pageId: string | undefined): void =>
useEffect(() => {
if (socket == null || pageId == null) { return }
- socket.emit(SocketEventName.JoinPage, { socketId: socket.id, pageId });
+ socket.emit(SocketEventName.JoinPage, { pageId });
return () => {
- socket.emit(SocketEventName.LeavePage, { socketId: socket.id, pageId });
+ socket.emit(SocketEventName.LeavePage, { pageId });
};
}, [pageId, socket]);
};
From 74d4bbc398f029120d2b90b0d62cf2c45e79e5f0 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Mon, 22 Apr 2024 11:55:43 +0000
Subject: [PATCH 032/180] apply fb
---
.../src/client/services/side-effects/yjs-draft.ts | 14 +++++++-------
.../components/Navbar/PageEditorModeManager.tsx | 6 +++---
apps/app/src/interfaces/page.ts | 8 ++++++++
apps/app/src/pages/[[...path]].page.tsx | 4 ++--
apps/app/src/server/service/socket-io.js | 4 +++-
apps/app/src/stores/page.tsx | 6 ++++--
6 files changed, 27 insertions(+), 15 deletions(-)
diff --git a/apps/app/src/client/services/side-effects/yjs-draft.ts b/apps/app/src/client/services/side-effects/yjs-draft.ts
index 43cefe1e086..7ef0dd3a687 100644
--- a/apps/app/src/client/services/side-effects/yjs-draft.ts
+++ b/apps/app/src/client/services/side-effects/yjs-draft.ts
@@ -2,17 +2,17 @@ import { useCallback, useEffect } from 'react';
import { useGlobalSocket } from '@growi/core/dist/swr';
+import type { CurrentPageYjsDraft } from '~/interfaces/page';
import { SocketEventName } from '~/interfaces/websocket';
-import { useHasYjsDraft } from '~/stores/page';
+import { useCurrentPageYjsDraft } from '~/stores/page';
export const useYjsDraftEffect = (): void => {
-
- const { mutate: mutateHasYjsDraft } = useHasYjsDraft();
+ const { mutate: mutateeCurrentPageYjsDraft } = useCurrentPageYjsDraft();
const { data: socket } = useGlobalSocket();
- const yjsDraftUpdateHandler = useCallback(((hasYjsDraft: boolean) => {
- mutateHasYjsDraft(hasYjsDraft);
- }), [mutateHasYjsDraft]);
+ const yjsDraftUpdateHandler = useCallback(((currentPageYjsDraft: CurrentPageYjsDraft) => {
+ mutateeCurrentPageYjsDraft(currentPageYjsDraft);
+ }), [mutateeCurrentPageYjsDraft]);
useEffect(() => {
@@ -24,5 +24,5 @@ export const useYjsDraftEffect = (): void => {
socket.off(SocketEventName.YjsUpdated, yjsDraftUpdateHandler);
};
- }, [mutateHasYjsDraft, socket, yjsDraftUpdateHandler]);
+ }, [mutateeCurrentPageYjsDraft, socket, yjsDraftUpdateHandler]);
};
diff --git a/apps/app/src/components/Navbar/PageEditorModeManager.tsx b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
index 52ac35ce9f8..850ce942d16 100644
--- a/apps/app/src/components/Navbar/PageEditorModeManager.tsx
+++ b/apps/app/src/components/Navbar/PageEditorModeManager.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
import { useCreatePageAndTransit } from '~/client/services/create-page';
import { toastError } from '~/client/util/toastr';
-import { useIsNotFound, useHasYjsDraft } from '~/stores/page';
+import { useIsNotFound, useCurrentPageYjsDraft } from '~/stores/page';
import { EditorMode, useEditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
import { shouldCreateWipPage } from '../../utils/should-create-wip-page';
@@ -64,7 +64,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
const { data: isNotFound } = useIsNotFound();
const { mutate: mutateEditorMode } = useEditorMode();
const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
- const { data: hasYjsDraft } = useHasYjsDraft();
+ const { data: currentPageYjsDraft } = useCurrentPageYjsDraft();
const { isCreating, createAndTransit } = useCreatePageAndTransit();
@@ -113,7 +113,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
onClick={editButtonClickedHandler}
>
edit_square{t('Edit')}
- { hasYjsDraft && }
+ { currentPageYjsDraft?.hasYjsDraft && }
)}
diff --git a/apps/app/src/interfaces/page.ts b/apps/app/src/interfaces/page.ts
index 4404c7d7b39..cfa83f79f3d 100644
--- a/apps/app/src/interfaces/page.ts
+++ b/apps/app/src/interfaces/page.ts
@@ -75,3 +75,11 @@ export type IOptionsForCreate = {
origin?: Origin
wip?: boolean,
};
+
+export type CurrentPageYjsDraft = {
+ hasYjsDraft: boolean,
+}
+
+export const CurrentPageYjsDraftData = {
+ hasYjsDraft: { hasYjsDraft: true },
+};
diff --git a/apps/app/src/pages/[[...path]].page.tsx b/apps/app/src/pages/[[...path]].page.tsx
index 1305f7e7c42..fd45f6bbb0e 100644
--- a/apps/app/src/pages/[[...path]].page.tsx
+++ b/apps/app/src/pages/[[...path]].page.tsx
@@ -42,7 +42,7 @@ import {
} from '~/stores/context';
import { useEditingMarkdown } from '~/stores/editor';
import {
- useSWRxCurrentPage, useSWRMUTxCurrentPage, useCurrentPageId, useHasYjsDraft,
+ useSWRxCurrentPage, useSWRMUTxCurrentPage, useCurrentPageId, useCurrentPageYjsDraft,
useIsNotFound, useIsLatestRevision, useTemplateTagData, useTemplateBodyData,
} from '~/stores/page';
import { useRedirectFrom } from '~/stores/page-redirect';
@@ -221,7 +221,7 @@ const Page: NextPageWithLayout = (props: Props) => {
useIsUploadAllFileAllowed(props.isUploadAllFileAllowed);
useIsUploadEnabled(props.isUploadEnabled);
- useHasYjsDraft(props.hasYjsDraft);
+ useCurrentPageYjsDraft({ hasYjsDraft: props.hasYjsDraft });
const { pageWithMeta } = props;
diff --git a/apps/app/src/server/service/socket-io.js b/apps/app/src/server/service/socket-io.js
index 55c588173e8..6ead9b2302c 100644
--- a/apps/app/src/server/service/socket-io.js
+++ b/apps/app/src/server/service/socket-io.js
@@ -1,6 +1,7 @@
import { GlobalSocketEventName } from '@growi/core/dist/interfaces';
import { Server } from 'socket.io';
+import { CurrentPageYjsDraftData } from '~/interfaces/page';
import { SocketEventName } from '~/interfaces/websocket';
import loggerFactory from '~/utils/logger';
@@ -8,6 +9,7 @@ import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
import { getYjsConnectionManager } from './yjs-connection-manager';
+
const expressSession = require('express-session');
const passport = require('passport');
@@ -173,7 +175,7 @@ class SocketIoService {
// Emit to the client in the room of the target pageId.
this.io
.in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
- .emit(SocketEventName.YjsUpdated, true);
+ .emit(SocketEventName.YjsUpdated, CurrentPageYjsDraftData.hasYjsDraft);
try {
await yjsConnectionManager.handleYDocSync(pageId, initialValue);
diff --git a/apps/app/src/stores/page.tsx b/apps/app/src/stores/page.tsx
index 79defa0d510..0043a392d02 100644
--- a/apps/app/src/stores/page.tsx
+++ b/apps/app/src/stores/page.tsx
@@ -18,11 +18,13 @@ import useSWRMutation, { type SWRMutationResponse } from 'swr/mutation';
import { apiGet } from '~/client/util/apiv1-client';
import { apiv3Get } from '~/client/util/apiv3-client';
+import type { CurrentPageYjsDraft } from '~/interfaces/page';
import type { IRecordApplicableGrant, IResIsGrantNormalized } from '~/interfaces/page-grant';
import type { AxiosResponse } from '~/utils/axios';
import type { IPageTagsInfo } from '../interfaces/tag';
+
import {
useCurrentPathname, useShareLinkId, useIsGuestUser, useIsReadOnlyUser,
} from './context';
@@ -52,8 +54,8 @@ export const useTemplateBodyData = (initialData?: string): SWRResponse('templateBodyData', initialData);
};
-export const useHasYjsDraft = (initialData?: boolean): SWRResponse => {
- return useSWRStatic('hasYjsDraft', initialData);
+export const useCurrentPageYjsDraft = (initialData?: CurrentPageYjsDraft): SWRResponse => {
+ return useSWRStatic('currentPageYjsDraft', initialData);
};
/** "useSWRxCurrentPage" is intended for initial data retrieval only. Use "useSWRMUTxCurrentPage" for revalidation */
From ba9326f146c56687e373417feb746be896debf67 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 06:18:57 +0000
Subject: [PATCH 033/180] use until now
---
.../src/components/Page/RevisionRenderer.tsx | 4 +-
packages/presentation/src/components/index.ts | 2 -
packages/presentation/src/index.ts | 5 ++-
.../src/services/has-enabled-slide-types.ts | 25 -----------
packages/presentation/src/services/index.ts | 1 -
.../src/services/parse-slide-frontmatter.ts | 43 +++++++++++++++++++
6 files changed, 48 insertions(+), 32 deletions(-)
delete mode 100644 packages/presentation/src/components/index.ts
delete mode 100644 packages/presentation/src/services/has-enabled-slide-types.ts
delete mode 100644 packages/presentation/src/services/index.ts
create mode 100644 packages/presentation/src/services/parse-slide-frontmatter.ts
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index ed1acdb4926..ce5e212bdc7 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { hasEnabledSlideTypes } from '@growi/presentation/src/services';
+import { parseSlideFrontmatterInMarkdown } from '@growi/presentation';
import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import ReactMarkdown from 'react-markdown';
@@ -42,7 +42,7 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
} = props;
const { data: isEnabledMarp } = useIsEnabledMarp();
- const [, marp, useSlide] = hasEnabledSlideTypes(markdown);
+ const [, marp, useSlide] = parseSlideFrontmatterInMarkdown(markdown);
const useMarp = !!isEnabledMarp && marp;
return (
diff --git a/packages/presentation/src/components/index.ts b/packages/presentation/src/components/index.ts
deleted file mode 100644
index 8d3c4172753..00000000000
--- a/packages/presentation/src/components/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './Slides';
-export * from './Presentation';
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts
index 80e3bea6755..79d97b6de1c 100644
--- a/packages/presentation/src/index.ts
+++ b/packages/presentation/src/index.ts
@@ -1,2 +1,3 @@
-export * from './components';
-export * from './services';
+export * from './components/Presentation';
+export * from './components/Slides';
+export * from './services/parse-slide-frontmatter';
diff --git a/packages/presentation/src/services/has-enabled-slide-types.ts b/packages/presentation/src/services/has-enabled-slide-types.ts
deleted file mode 100644
index 814af89fadc..00000000000
--- a/packages/presentation/src/services/has-enabled-slide-types.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-export const hasEnabledSlideTypes = (markdown?: string): [boolean, boolean, boolean] => {
-
- if (markdown == null) {
- return [false, false, false];
- }
-
- const text = markdown.slice(0, 300);
-
- const reStartFrontmatter = /^---\s*\n/g;
- const reEndFrontmatter = /\n---\s*\n/g;
-
- if (!reStartFrontmatter.test(text) || !reEndFrontmatter.test(text)) {
- return [false, false, false];
- }
-
- const reEnableMarp = /\nmarp\s*:\s+true\n/g;
- const reEnableSlide = /\nslide\s*:\s+true\n/g;
-
- const marp = reEnableMarp.test(text) && reEnableMarp.lastIndex < reEndFrontmatter.lastIndex;
- const slide = reEnableSlide.test(text) && reEnableSlide.lastIndex < reEndFrontmatter.lastIndex;
-
- const enable = marp || slide;
-
- return [enable, marp, slide];
-};
diff --git a/packages/presentation/src/services/index.ts b/packages/presentation/src/services/index.ts
deleted file mode 100644
index 393ad416d81..00000000000
--- a/packages/presentation/src/services/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './has-enabled-slide-types';
diff --git a/packages/presentation/src/services/parse-slide-frontmatter.ts b/packages/presentation/src/services/parse-slide-frontmatter.ts
new file mode 100644
index 00000000000..5b30d21e0f4
--- /dev/null
+++ b/packages/presentation/src/services/parse-slide-frontmatter.ts
@@ -0,0 +1,43 @@
+import remarkFrontmatter from 'remark-frontmatter';
+import remarkParse from 'remark-parse';
+import remarkStringify from 'remark-stringify';
+import { unified } from 'unified';
+
+
+export const parseSlideFrontmatter = (frontmatter: string): [boolean, boolean] => {
+
+ let marp = false;
+ let slide = false;
+
+ const lines = frontmatter.split('\n');
+ lines.forEach((line) => {
+ const [key, value] = line.split(':').map(part => part.trim());
+ if (key === 'marp' && value === 'true') {
+ marp = true;
+ }
+ if (key === 'slide' && value === 'true') {
+ slide = true;
+ }
+ });
+
+ return [marp, slide];
+};
+
+export const parseSlideFrontmatterInMarkdown = (markdown?: string): [boolean, boolean] => {
+
+ let marp = false;
+ let slide = false;
+
+ unified()
+ .use(remarkParse)
+ .use(remarkStringify)
+ .use(remarkFrontmatter, ['yaml'])
+ .use(() => ((obj) => {
+ if (obj.children[0]?.type === 'yaml') {
+ [marp, slide] = parseSlideFrontmatter(obj.children[0]?.value as string);
+ }
+ }))
+ .process(markdown as string);
+
+ return [marp, slide];
+};
From bfde8a9276fdf2fa87f6257b8aea9dad74ff76b6 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 06:21:11 +0000
Subject: [PATCH 034/180] fix import
---
packages/presentation/src/components/Presentation.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/presentation/src/components/Presentation.tsx b/packages/presentation/src/components/Presentation.tsx
index cd8d20835b8..f1f7a05b170 100644
--- a/packages/presentation/src/components/Presentation.tsx
+++ b/packages/presentation/src/components/Presentation.tsx
@@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
import Reveal from 'reveal.js';
import type { PresentationOptions } from '../consts';
-import { hasEnabledSlideTypes } from '../services/has-enabled-slide-types';
+import { parseSlideFrontmatterInMarkdown } from '../services/parse-slide-frontmatter';
import { Slides } from './Slides';
@@ -42,11 +42,11 @@ export const Presentation = (props: PresentationProps): JSX.Element => {
const { options, isEnabledMarp, children } = props;
const { revealOptions } = options;
- const [, marp] = hasEnabledSlideTypes(children);
+ const [marp] = parseSlideFrontmatterInMarkdown(children);
const hasMarpFlag = isEnabledMarp && marp;
useEffect(() => {
- if (children != null) {
+ if (children == null) {
return;
}
const deck = new Reveal({ ...baseRevealOptions, ...revealOptions });
From 3906352ec52b875ae1e52362c283bae7d548ba38 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 06:22:48 +0000
Subject: [PATCH 035/180] fix code
---
apps/app/src/components/Page/RevisionRenderer.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index ce5e212bdc7..04e1854477e 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -42,7 +42,7 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
} = props;
const { data: isEnabledMarp } = useIsEnabledMarp();
- const [, marp, useSlide] = parseSlideFrontmatterInMarkdown(markdown);
+ const [marp, useSlide] = parseSlideFrontmatterInMarkdown(markdown);
const useMarp = !!isEnabledMarp && marp;
return (
From b3e66b33cc8f6202d235329754455c3b21fd2084 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 06:35:18 +0000
Subject: [PATCH 036/180] dont use parseFrontmatter
---
packages/presentation/src/components/Presentation.tsx | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/packages/presentation/src/components/Presentation.tsx b/packages/presentation/src/components/Presentation.tsx
index f1f7a05b170..f60bb2a70f7 100644
--- a/packages/presentation/src/components/Presentation.tsx
+++ b/packages/presentation/src/components/Presentation.tsx
@@ -3,7 +3,6 @@ import React, { useEffect } from 'react';
import Reveal from 'reveal.js';
import type { PresentationOptions } from '../consts';
-import { parseSlideFrontmatterInMarkdown } from '../services/parse-slide-frontmatter';
import { Slides } from './Slides';
@@ -34,17 +33,14 @@ const removeAllHiddenElements = () => {
export type PresentationProps = {
options: PresentationOptions,
- isEnabledMarp: boolean,
+ hasMarpFlag: boolean,
children?: string,
}
export const Presentation = (props: PresentationProps): JSX.Element => {
- const { options, isEnabledMarp, children } = props;
+ const { options, hasMarpFlag, children } = props;
const { revealOptions } = options;
- const [marp] = parseSlideFrontmatterInMarkdown(children);
- const hasMarpFlag = isEnabledMarp && marp;
-
useEffect(() => {
if (children == null) {
return;
From 83a0f2cfc155bc5c45dbeaa1d8628aa08985e8ee Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 06:53:22 +0000
Subject: [PATCH 037/180] use from apps/app
---
apps/app/src/components/Page/RevisionRenderer.tsx | 3 ++-
.../Page/markdown-slide-util-for-view.ts | 0
.../src/components/Presentation/Presentation.tsx | 15 +++++++++++++--
3 files changed, 15 insertions(+), 3 deletions(-)
rename packages/presentation/src/services/parse-slide-frontmatter.ts => apps/app/src/components/Page/markdown-slide-util-for-view.ts (100%)
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index 04e1854477e..b94438cf008 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -1,6 +1,5 @@
import React from 'react';
-import { parseSlideFrontmatterInMarkdown } from '@growi/presentation';
import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import ReactMarkdown from 'react-markdown';
@@ -12,6 +11,8 @@ import loggerFactory from '~/utils/logger';
import { SlideViewer } from '../ReactMarkdownComponents/SlideViewer';
+import { parseSlideFrontmatterInMarkdown } from './markdown-slide-util-for-view';
+
import 'katex/dist/katex.min.css';
diff --git a/packages/presentation/src/services/parse-slide-frontmatter.ts b/apps/app/src/components/Page/markdown-slide-util-for-view.ts
similarity index 100%
rename from packages/presentation/src/services/parse-slide-frontmatter.ts
rename to apps/app/src/components/Page/markdown-slide-util-for-view.ts
diff --git a/apps/app/src/components/Presentation/Presentation.tsx b/apps/app/src/components/Presentation/Presentation.tsx
index 4b0cad7e5c0..5b11a70d67a 100644
--- a/apps/app/src/components/Presentation/Presentation.tsx
+++ b/apps/app/src/components/Presentation/Presentation.tsx
@@ -1,7 +1,18 @@
import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation';
+import { parseSlideFrontmatter } from '../Page/markdown-slide-util-for-view';
+
import '@growi/presentation/dist/style.css';
-export const Presentation = (props: PresentationProps): JSX.Element => {
- return ;
+type Props = {
+ isEnabledMarp: boolean
+} & PresentationProps;
+
+export const Presentation = (props: Props): JSX.Element => {
+ const { options, isEnabledMarp, children } = props;
+
+ const [marp] = parseSlideFrontmatter(children ?? '');
+ const hasMarpFlag = isEnabledMarp && marp;
+
+ return {children};
};
From 9b7abcebfd337d2ad7d1fbb9031140afe5cc70b7 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 06:57:29 +0000
Subject: [PATCH 038/180] clean code
---
apps/app/src/components/Page/markdown-slide-util-for-view.ts | 2 +-
apps/app/src/components/Presentation/Presentation.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/apps/app/src/components/Page/markdown-slide-util-for-view.ts b/apps/app/src/components/Page/markdown-slide-util-for-view.ts
index 5b30d21e0f4..f463799a699 100644
--- a/apps/app/src/components/Page/markdown-slide-util-for-view.ts
+++ b/apps/app/src/components/Page/markdown-slide-util-for-view.ts
@@ -4,7 +4,7 @@ import remarkStringify from 'remark-stringify';
import { unified } from 'unified';
-export const parseSlideFrontmatter = (frontmatter: string): [boolean, boolean] => {
+const parseSlideFrontmatter = (frontmatter: string): [boolean, boolean] => {
let marp = false;
let slide = false;
diff --git a/apps/app/src/components/Presentation/Presentation.tsx b/apps/app/src/components/Presentation/Presentation.tsx
index 5b11a70d67a..617e64a38c3 100644
--- a/apps/app/src/components/Presentation/Presentation.tsx
+++ b/apps/app/src/components/Presentation/Presentation.tsx
@@ -1,6 +1,6 @@
import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation';
-import { parseSlideFrontmatter } from '../Page/markdown-slide-util-for-view';
+import { parseSlideFrontmatterInMarkdown } from '../Page/markdown-slide-util-for-view';
import '@growi/presentation/dist/style.css';
@@ -11,7 +11,7 @@ type Props = {
export const Presentation = (props: Props): JSX.Element => {
const { options, isEnabledMarp, children } = props;
- const [marp] = parseSlideFrontmatter(children ?? '');
+ const [marp] = parseSlideFrontmatterInMarkdown(children ?? '');
const hasMarpFlag = isEnabledMarp && marp;
return {children};
From 092f4c0d52896ee460bd378dd2d9a121b2034273 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 07:11:04 +0000
Subject: [PATCH 039/180] fix code to use marp presentation
---
apps/app/src/components/PagePresentationModal.tsx | 4 +---
apps/app/src/components/Presentation/Presentation.tsx | 6 +++---
packages/presentation/src/index.ts | 1 -
3 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/apps/app/src/components/PagePresentationModal.tsx b/apps/app/src/components/PagePresentationModal.tsx
index 899a8fcb763..2e60414fe70 100644
--- a/apps/app/src/components/PagePresentationModal.tsx
+++ b/apps/app/src/components/PagePresentationModal.tsx
@@ -1,6 +1,5 @@
import React, { useCallback } from 'react';
-import type { PresentationProps } from '@growi/presentation';
import { LoadingSpinner } from '@growi/ui/dist/components';
import { useFullScreen } from '@growi/ui/dist/utils';
import dynamic from 'next/dynamic';
@@ -15,11 +14,10 @@ import { useSWRxCurrentPage } from '~/stores/page';
import { usePresentationViewOptions } from '~/stores/renderer';
import { useNextThemes } from '~/stores/use-next-themes';
-
import styles from './PagePresentationModal.module.scss';
-const Presentation = dynamic(() => import('./Presentation/Presentation').then(mod => mod.Presentation), {
+const Presentation = dynamic(() => import('./Presentation/Presentation').then(mod => mod.Presentation), {
ssr: false,
loading: () => (
diff --git a/apps/app/src/components/Presentation/Presentation.tsx b/apps/app/src/components/Presentation/Presentation.tsx
index 617e64a38c3..2d3067ccc08 100644
--- a/apps/app/src/components/Presentation/Presentation.tsx
+++ b/apps/app/src/components/Presentation/Presentation.tsx
@@ -1,12 +1,12 @@
-import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation';
+import { Presentation as PresentationSubstance, type PresentationProps as PresentationPropsSubstance } from '@growi/presentation';
import { parseSlideFrontmatterInMarkdown } from '../Page/markdown-slide-util-for-view';
import '@growi/presentation/dist/style.css';
-type Props = {
+type Props = Omit & {
isEnabledMarp: boolean
-} & PresentationProps;
+};
export const Presentation = (props: Props): JSX.Element => {
const { options, isEnabledMarp, children } = props;
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts
index 79d97b6de1c..4dd96f2470c 100644
--- a/packages/presentation/src/index.ts
+++ b/packages/presentation/src/index.ts
@@ -1,3 +1,2 @@
export * from './components/Presentation';
export * from './components/Slides';
-export * from './services/parse-slide-frontmatter';
From 849e3f632d9fae23d00cad7a72631689d79cc121 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 09:16:17 +0000
Subject: [PATCH 040/180] clean code
---
apps/app/src/components/TreeItem/ItemNode.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/app/src/components/TreeItem/ItemNode.ts b/apps/app/src/components/TreeItem/ItemNode.ts
index a9125143324..b8a3fefd4b3 100644
--- a/apps/app/src/components/TreeItem/ItemNode.ts
+++ b/apps/app/src/components/TreeItem/ItemNode.ts
@@ -1,4 +1,4 @@
-import { IPageForItem } from '../../interfaces/page';
+import type { IPageForItem } from '../../interfaces/page';
export class ItemNode {
From e429095c592ec7df74dd85161f6eb70388b94da8 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 23 Apr 2024 09:16:31 +0000
Subject: [PATCH 041/180] add ref active item
---
apps/app/src/components/TreeItem/SimpleItem.tsx | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/apps/app/src/components/TreeItem/SimpleItem.tsx b/apps/app/src/components/TreeItem/SimpleItem.tsx
index a93dc7f33a2..8284647c29b 100644
--- a/apps/app/src/components/TreeItem/SimpleItem.tsx
+++ b/apps/app/src/components/TreeItem/SimpleItem.tsx
@@ -1,6 +1,7 @@
import React, {
useCallback, useState, useEffect,
type FC, type RefObject, type RefCallback, type MouseEvent,
+ useRef,
} from 'react';
import nodePath from 'path';
@@ -112,6 +113,7 @@ export const SimpleItem: FC = (props) => {
const { data } = useSWRxPageChildren(isOpen ? page._id : null);
+ const activeTargetRef = useRef(null);
const itemClickHandler = useCallback((e: MouseEvent) => {
// DO NOT handle the event when e.currentTarget and e.target is different
@@ -166,6 +168,18 @@ export const SimpleItem: FC = (props) => {
}
}, [data, isOpen, targetPathOrId]);
+
+ // When open Sidebar, scroll into view active item.
+ useEffect(() => {
+ if (!isOpen || data == null) {
+ return;
+ }
+ const timeoutId = setTimeout(() => {
+ activeTargetRef.current?.scrollIntoView(true);
+ }, 4000);
+ return () => clearTimeout(timeoutId);
+ }, [data, isOpen]);
+
const ItemClassFixed = itemClass ?? SimpleItem;
const EndComponents = props.customEndComponents ?? [SimpleItemTool];
@@ -194,6 +208,7 @@ export const SimpleItem: FC = (props) => {
return (
Date: Thu, 25 Apr 2024 02:22:01 +0000
Subject: [PATCH 042/180] Revert "move locale-utils"
This reverts commit 9257cac617eebb7e954035d9b90fa367e3cfc39f.
---
apps/app/src/client/util/locale-utils.ts | 60 +++++++++++++++++++++
apps/app/src/utils/locale-utils.ts | 68 ------------------------
2 files changed, 60 insertions(+), 68 deletions(-)
delete mode 100644 apps/app/src/utils/locale-utils.ts
diff --git a/apps/app/src/client/util/locale-utils.ts b/apps/app/src/client/util/locale-utils.ts
index 0c6fffae5d9..318450f8f5e 100644
--- a/apps/app/src/client/util/locale-utils.ts
+++ b/apps/app/src/client/util/locale-utils.ts
@@ -1,9 +1,69 @@
+import type { IncomingHttpHeaders } from 'http';
+
+import { Lang } from '@growi/core';
+
+import * as nextI18NextConfig from '^/config/next-i18next.config';
+
// https://docs.google.com/spreadsheets/d/1FoYdyEraEQuWofzbYCDPKN7EdKgS_2ZrsDrOA8scgwQ
const DIAGRAMS_NET_LANG_MAP = {
ja_JP: 'ja',
zh_CN: 'zh',
};
+const ACCEPT_LANG_MAP = {
+ en: Lang.en_US,
+ ja: Lang.ja_JP,
+ zh: Lang.zh_CN,
+};
+
export const getDiagramsNetLangCode = (lang) => {
return DIAGRAMS_NET_LANG_MAP[lang];
};
+
+/**
+ * It return the first language that matches ACCEPT_LANG_MAP keys from sorted accept languages array
+ * @param sortedAcceptLanguagesArray
+ */
+const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
+ for (const lang of sortedAcceptLanguagesArray) {
+ const matchingLang = Object.keys(ACCEPT_LANG_MAP).find(key => lang.includes(key));
+ if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
+ }
+ return nextI18NextConfig.defaultLang;
+};
+
+/**
+ * Detect locale from browser accept language
+ * @param headers
+ */
+export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeaders): Lang => {
+ // 1. get the header accept-language
+ // ex. "ja,ar-SA;q=0.8,en;q=0.6,en-CA;q=0.4,en-US;q=0.2"
+ const acceptLanguages = headers['accept-language'];
+
+ if (acceptLanguages == null) {
+ return nextI18NextConfig.defaultLang;
+ }
+
+ // 1. trim blank spaces.
+ // 2. separate by ,.
+ // 3. if "lang;q=x", then { 'x', 'lang' } to add to the associative array.
+ // if "lang" has no weight x (";q=x"), add it with key = 1.
+ // ex. {'1': 'ja','0.8': 'ar-SA','0.6': 'en','0.4': 'en-CA','0.2': 'en-US'}
+ const acceptLanguagesDict = acceptLanguages
+ .replace(/\s+/g, '')
+ .split(',')
+ .map(item => item.split(/\s*;\s*q\s*=\s*/))
+ .reduce((acc, [key, value = '1']) => {
+ acc[value] = key;
+ return acc;
+ }, {});
+
+ // 1. create an array of sorted languages in descending order.
+ // ex. [ 'ja', 'ar-SA', 'en', 'en-CA', 'en-US' ]
+ const sortedAcceptLanguagesArray = Object.keys(acceptLanguagesDict)
+ .sort((x, y) => y.localeCompare(x))
+ .map(item => acceptLanguagesDict[item]);
+
+ return getPreferredLanguage(sortedAcceptLanguagesArray);
+};
diff --git a/apps/app/src/utils/locale-utils.ts b/apps/app/src/utils/locale-utils.ts
deleted file mode 100644
index 1388e0683ab..00000000000
--- a/apps/app/src/utils/locale-utils.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { IncomingHttpHeaders } from 'http';
-
-import type { IUser, IUserHasId } from '@growi/core';
-import { Lang } from '@growi/core';
-import type { Document } from 'mongoose';
-
-import * as nextI18NextConfig from '^/config/next-i18next.config';
-
-import { configManager } from '~/server/service/config-manager';
-
-const ACCEPT_LANG_MAP = {
- en: Lang.en_US,
- ja: Lang.ja_JP,
- zh: Lang.zh_CN,
-};
-
-/**
- * It return the first language that matches ACCEPT_LANG_MAP keys from sorted accept languages array
- * @param sortedAcceptLanguagesArray
- */
-const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
- for (const lang of sortedAcceptLanguagesArray) {
- const matchingLang = Object.keys(ACCEPT_LANG_MAP).find(key => lang.includes(key));
- if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
- }
- return nextI18NextConfig.defaultLang;
-};
-
-/**
- * Detect locale from browser accept language
- * @param headers
- */
-const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeaders): Lang => {
- // 1. get the header accept-language
- // ex. "ja,ar-SA;q=0.8,en;q=0.6,en-CA;q=0.4,en-US;q=0.2"
- const acceptLanguages = headers['accept-language'];
-
- if (acceptLanguages == null) {
- return nextI18NextConfig.defaultLang;
- }
-
- // 1. trim blank spaces.
- // 2. separate by ,.
- // 3. if "lang;q=x", then { 'x', 'lang' } to add to the associative array.
- // if "lang" has no weight x (";q=x"), add it with key = 1.
- // ex. {'1': 'ja','0.8': 'ar-SA','0.6': 'en','0.4': 'en-CA','0.2': 'en-US'}
- const acceptLanguagesDict = acceptLanguages
- .replace(/\s+/g, '')
- .split(',')
- .map(item => item.split(/\s*;\s*q\s*=\s*/))
- .reduce((acc, [key, value = '1']) => {
- acc[value] = key;
- return acc;
- }, {});
-
- // 1. create an array of sorted languages in descending order.
- // ex. [ 'ja', 'ar-SA', 'en', 'en-CA', 'en-US' ]
- const sortedAcceptLanguagesArray = Object.keys(acceptLanguagesDict)
- .sort((x, y) => y.localeCompare(x))
- .map(item => acceptLanguagesDict[item]);
-
- return getPreferredLanguage(sortedAcceptLanguagesArray);
-};
-
-export const determineLocale = (headers: IncomingHttpHeaders, user?: IUserHasId | (IUser & Document
)): Lang => {
- return user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
- : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
-};
From b35c40ed232d05445ef0faaed6a686502d8603ae Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Thu, 25 Apr 2024 02:31:13 +0000
Subject: [PATCH 043/180] use req t
---
apps/app/src/pages/utils/commons.ts | 11 +++++++----
.../src/server/routes/apiv3/page/create-page.ts | 17 ++++++++---------
2 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/apps/app/src/pages/utils/commons.ts b/apps/app/src/pages/utils/commons.ts
index 20ef73f081c..4d94a6fb1be 100644
--- a/apps/app/src/pages/utils/commons.ts
+++ b/apps/app/src/pages/utils/commons.ts
@@ -1,5 +1,5 @@
import type { ColorScheme, IUserHasId } from '@growi/core';
-import { AllLang } from '@growi/core';
+import { AllLang, Lang } from '@growi/core';
import { DevidedPagePath } from '@growi/core/dist/models';
import { isServer } from '@growi/core/dist/utils';
import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
@@ -8,6 +8,7 @@ import type { SSRConfig, UserConfig } from 'next-i18next';
import * as nextI18NextConfig from '^/config/next-i18next.config';
+import { detectLocaleFromBrowserAcceptLanguage } from '~/client/util/locale-utils';
import { type SupportedActionType } from '~/interfaces/activity';
import type { CrowiRequest } from '~/interfaces/crowi-request';
import type { ISidebarConfig } from '~/interfaces/sidebar-config';
@@ -17,7 +18,6 @@ import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
import {
useCurrentProductNavWidth, useCurrentSidebarContents, usePreferCollapsedMode,
} from '~/stores/ui';
-import { determineLocale } from '~/utils/locale-utils';
export type CommonProps = {
namespacesRequired: string[], // i18next
@@ -120,9 +120,12 @@ export const getNextI18NextConfig = async(
): Promise => {
const req: CrowiRequest = context.req as CrowiRequest;
- const { user, headers } = req;
+ const { crowi, user, headers } = req;
+ const { configManager } = crowi;
- const locale = determineLocale(headers, user);
+ // determine language
+ const locale = user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
+ : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
const namespaces = ['commons'];
if (namespacesRequired != null) {
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index 7dc0c8738cb..a31080f0524 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -1,6 +1,6 @@
import { allOrigin } from '@growi/core';
import type {
- IPage, IUser, IUserHasId, Lang,
+ IPage, IUser, IUserHasId,
} from '@growi/core';
import { ErrorV3 } from '@growi/core/dist/models';
import { isCreatablePage, isUserPage, isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
@@ -23,7 +23,6 @@ import {
import type { PageDocument, PageModel } from '~/server/models/page';
import PageTagRelation from '~/server/models/page-tag-relation';
import { configManager } from '~/server/service/config-manager';
-import { determineLocale } from '~/utils/locale-utils';
import loggerFactory from '~/utils/logger';
import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
@@ -43,10 +42,8 @@ async function generateUntitledPath(parentPath: string, basePathname: string, in
return path;
}
-async function determinePath(locale: Lang, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
- // const t = i18n?.getFixedT(locale);
- // const basePathname = t?.('create_page.untitled') || 'Undefined';
- const basePathname = 'Undefined';
+async function determinePath(t: any, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
+ const basePathname = t('create_page.untitled');
if (_path != null) {
const path = normalizePath(_path);
@@ -92,6 +89,10 @@ type ReqBody = IApiv3PageCreateParams
interface CreatePageRequest extends Request {
user: IUserHasId,
+
+ // TODO: remove req.t
+ // https://redmine.weseek.co.jp/issues/125884
+ t: any
}
type CreatePageHandlersFactory = (crowi: Crowi) => RequestHandler[];
@@ -214,9 +215,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
try {
const { path, parentPath, optionalParentPath } = req.body;
- const locale = determineLocale(req.headers, req.user);
-
- pathToCreate = await determinePath(locale, parentPath, path, optionalParentPath);
+ pathToCreate = await determinePath(req.t, parentPath, path, optionalParentPath);
}
catch (err) {
return res.apiv3Err(new ErrorV3(err.toString(), 'could_not_create_page'));
From d3db778d06a193beb200c65f5a429c5b7540f718 Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Thu, 25 Apr 2024 02:49:36 +0000
Subject: [PATCH 044/180] clean code
---
apps/app/src/pages/utils/commons.ts | 2 +-
apps/app/src/server/routes/apiv3/page/create-page.ts | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/apps/app/src/pages/utils/commons.ts b/apps/app/src/pages/utils/commons.ts
index 4d94a6fb1be..c4bca78c36c 100644
--- a/apps/app/src/pages/utils/commons.ts
+++ b/apps/app/src/pages/utils/commons.ts
@@ -1,5 +1,5 @@
import type { ColorScheme, IUserHasId } from '@growi/core';
-import { AllLang, Lang } from '@growi/core';
+import { Lang, AllLang } from '@growi/core';
import { DevidedPagePath } from '@growi/core/dist/models';
import { isServer } from '@growi/core/dist/utils';
import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index a31080f0524..805845f8b25 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -9,7 +9,6 @@ import type { Request, RequestHandler } from 'express';
import type { ValidationChain } from 'express-validator';
import { body } from 'express-validator';
import mongoose from 'mongoose';
-// import { i18n } from 'next-i18next';
import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
@@ -29,6 +28,7 @@ import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
import { excludeReadOnlyUser } from '../../../middlewares/exclude-read-only-user';
import type { ApiV3Response } from '../interfaces/apiv3-response';
+
const logger = loggerFactory('growi:routes:apiv3:page:create-page');
@@ -214,7 +214,6 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
let pathToCreate: string;
try {
const { path, parentPath, optionalParentPath } = req.body;
-
pathToCreate = await determinePath(req.t, parentPath, path, optionalParentPath);
}
catch (err) {
From 2b7a32dfe23f9943983b99f6b883c463df1f40b0 Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Fri, 26 Apr 2024 01:46:13 +0000
Subject: [PATCH 045/180] move locale utils
---
apps/app/src/components/PageEditor/DrawioModal.tsx | 2 +-
apps/app/src/pages/utils/commons.ts | 2 +-
apps/app/src/{client/util => utils}/locale-utils.ts | 0
3 files changed, 2 insertions(+), 2 deletions(-)
rename apps/app/src/{client/util => utils}/locale-utils.ts (100%)
diff --git a/apps/app/src/components/PageEditor/DrawioModal.tsx b/apps/app/src/components/PageEditor/DrawioModal.tsx
index 826c3396bf6..698c6952169 100644
--- a/apps/app/src/components/PageEditor/DrawioModal.tsx
+++ b/apps/app/src/components/PageEditor/DrawioModal.tsx
@@ -12,11 +12,11 @@ import {
ModalBody,
} from 'reactstrap';
-import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
import { replaceFocusedDrawioWithEditor, getMarkdownDrawioMxfile } from '~/components/PageEditor/markdown-drawio-util-for-editor';
import { useRendererConfig } from '~/stores/context';
import { useDrawioModal } from '~/stores/modal';
import { usePersonalSettings } from '~/stores/personal-settings';
+import { getDiagramsNetLangCode } from '~/utils/locale-utils';
import loggerFactory from '~/utils/logger';
diff --git a/apps/app/src/pages/utils/commons.ts b/apps/app/src/pages/utils/commons.ts
index c4bca78c36c..8f35708d6d6 100644
--- a/apps/app/src/pages/utils/commons.ts
+++ b/apps/app/src/pages/utils/commons.ts
@@ -8,7 +8,6 @@ import type { SSRConfig, UserConfig } from 'next-i18next';
import * as nextI18NextConfig from '^/config/next-i18next.config';
-import { detectLocaleFromBrowserAcceptLanguage } from '~/client/util/locale-utils';
import { type SupportedActionType } from '~/interfaces/activity';
import type { CrowiRequest } from '~/interfaces/crowi-request';
import type { ISidebarConfig } from '~/interfaces/sidebar-config';
@@ -18,6 +17,7 @@ import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
import {
useCurrentProductNavWidth, useCurrentSidebarContents, usePreferCollapsedMode,
} from '~/stores/ui';
+import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
export type CommonProps = {
namespacesRequired: string[], // i18next
diff --git a/apps/app/src/client/util/locale-utils.ts b/apps/app/src/utils/locale-utils.ts
similarity index 100%
rename from apps/app/src/client/util/locale-utils.ts
rename to apps/app/src/utils/locale-utils.ts
From add74f207e74e88131d40f9bb6c445d12a56acc9 Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Fri, 26 Apr 2024 01:46:29 +0000
Subject: [PATCH 046/180] use next i18 next
---
.../server/routes/apiv3/page/create-page.ts | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index 805845f8b25..bfbd2b2ad9d 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -1,4 +1,4 @@
-import { allOrigin } from '@growi/core';
+import { allOrigin, Lang } from '@growi/core';
import type {
IPage, IUser, IUserHasId,
} from '@growi/core';
@@ -9,6 +9,7 @@ import type { Request, RequestHandler } from 'express';
import type { ValidationChain } from 'express-validator';
import { body } from 'express-validator';
import mongoose from 'mongoose';
+import { i18n } from 'next-i18next';
import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
@@ -22,6 +23,7 @@ import {
import type { PageDocument, PageModel } from '~/server/models/page';
import PageTagRelation from '~/server/models/page-tag-relation';
import { configManager } from '~/server/service/config-manager';
+import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
import loggerFactory from '~/utils/logger';
import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
@@ -42,8 +44,16 @@ async function generateUntitledPath(parentPath: string, basePathname: string, in
return path;
}
-async function determinePath(t: any, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
- const basePathname = t('create_page.untitled');
+async function determinePath(req: any, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
+ const user = req.user;
+ const headers = req.headers;
+
+ // determine language
+ const locale = user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
+ : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
+
+ const t = i18n?.getFixedT(locale);
+ const basePathname = t?.('create_page.untitled') || 'Untitled';
if (_path != null) {
const path = normalizePath(_path);
@@ -214,7 +224,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
let pathToCreate: string;
try {
const { path, parentPath, optionalParentPath } = req.body;
- pathToCreate = await determinePath(req.t, parentPath, path, optionalParentPath);
+ pathToCreate = await determinePath(req, parentPath, path, optionalParentPath);
}
catch (err) {
return res.apiv3Err(new ErrorV3(err.toString(), 'could_not_create_page'));
From 36f01f87f0df79fcadb7de10658007182defbff5 Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Fri, 26 Apr 2024 01:47:28 +0000
Subject: [PATCH 047/180] remove todo comment
---
apps/app/src/server/routes/apiv3/page/create-page.ts | 4 ----
1 file changed, 4 deletions(-)
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index bfbd2b2ad9d..8f442d3bfb7 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -99,10 +99,6 @@ type ReqBody = IApiv3PageCreateParams
interface CreatePageRequest extends Request {
user: IUserHasId,
-
- // TODO: remove req.t
- // https://redmine.weseek.co.jp/issues/125884
- t: any
}
type CreatePageHandlersFactory = (crowi: Crowi) => RequestHandler[];
From 341a514b82c16f28fa2a9fcc4da10ca9cb9204cd Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Fri, 26 Apr 2024 01:50:51 +0000
Subject: [PATCH 048/180] add type
---
apps/app/src/server/routes/apiv3/page/create-page.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index 8f442d3bfb7..791bcbc2b1e 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -44,7 +44,7 @@ async function generateUntitledPath(parentPath: string, basePathname: string, in
return path;
}
-async function determinePath(req: any, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
+async function determinePath(req: CreatePageRequest, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
const user = req.user;
const headers = req.headers;
From 101ae0d4b279b0c1d18cfb1d0f57683638425e92 Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Fri, 26 Apr 2024 02:29:18 +0000
Subject: [PATCH 049/180] Revert "move locale utils"
This reverts commit 2b7a32dfe23f9943983b99f6b883c463df1f40b0.
---
apps/app/src/{utils => client/util}/locale-utils.ts | 0
apps/app/src/components/PageEditor/DrawioModal.tsx | 2 +-
apps/app/src/pages/utils/commons.ts | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename apps/app/src/{utils => client/util}/locale-utils.ts (100%)
diff --git a/apps/app/src/utils/locale-utils.ts b/apps/app/src/client/util/locale-utils.ts
similarity index 100%
rename from apps/app/src/utils/locale-utils.ts
rename to apps/app/src/client/util/locale-utils.ts
diff --git a/apps/app/src/components/PageEditor/DrawioModal.tsx b/apps/app/src/components/PageEditor/DrawioModal.tsx
index 698c6952169..826c3396bf6 100644
--- a/apps/app/src/components/PageEditor/DrawioModal.tsx
+++ b/apps/app/src/components/PageEditor/DrawioModal.tsx
@@ -12,11 +12,11 @@ import {
ModalBody,
} from 'reactstrap';
+import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
import { replaceFocusedDrawioWithEditor, getMarkdownDrawioMxfile } from '~/components/PageEditor/markdown-drawio-util-for-editor';
import { useRendererConfig } from '~/stores/context';
import { useDrawioModal } from '~/stores/modal';
import { usePersonalSettings } from '~/stores/personal-settings';
-import { getDiagramsNetLangCode } from '~/utils/locale-utils';
import loggerFactory from '~/utils/logger';
diff --git a/apps/app/src/pages/utils/commons.ts b/apps/app/src/pages/utils/commons.ts
index 8f35708d6d6..c4bca78c36c 100644
--- a/apps/app/src/pages/utils/commons.ts
+++ b/apps/app/src/pages/utils/commons.ts
@@ -8,6 +8,7 @@ import type { SSRConfig, UserConfig } from 'next-i18next';
import * as nextI18NextConfig from '^/config/next-i18next.config';
+import { detectLocaleFromBrowserAcceptLanguage } from '~/client/util/locale-utils';
import { type SupportedActionType } from '~/interfaces/activity';
import type { CrowiRequest } from '~/interfaces/crowi-request';
import type { ISidebarConfig } from '~/interfaces/sidebar-config';
@@ -17,7 +18,6 @@ import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
import {
useCurrentProductNavWidth, useCurrentSidebarContents, usePreferCollapsedMode,
} from '~/stores/ui';
-import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
export type CommonProps = {
namespacesRequired: string[], // i18next
From 868266da24f8dd9ab157db8f1b418ee2994a309d Mon Sep 17 00:00:00 2001
From: ryoji-s
Date: Fri, 26 Apr 2024 02:31:08 +0000
Subject: [PATCH 050/180] only see global lang
---
.../src/server/routes/apiv3/page/create-page.ts | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts
index 791bcbc2b1e..2618b6ebfb1 100644
--- a/apps/app/src/server/routes/apiv3/page/create-page.ts
+++ b/apps/app/src/server/routes/apiv3/page/create-page.ts
@@ -1,6 +1,6 @@
-import { allOrigin, Lang } from '@growi/core';
+import { allOrigin } from '@growi/core';
import type {
- IPage, IUser, IUserHasId,
+ IPage, IUser, IUserHasId, Lang,
} from '@growi/core';
import { ErrorV3 } from '@growi/core/dist/models';
import { isCreatablePage, isUserPage, isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
@@ -23,7 +23,6 @@ import {
import type { PageDocument, PageModel } from '~/server/models/page';
import PageTagRelation from '~/server/models/page-tag-relation';
import { configManager } from '~/server/service/config-manager';
-import { detectLocaleFromBrowserAcceptLanguage } from '~/utils/locale-utils';
import loggerFactory from '~/utils/logger';
import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
@@ -44,13 +43,8 @@ async function generateUntitledPath(parentPath: string, basePathname: string, in
return path;
}
-async function determinePath(req: CreatePageRequest, _parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
- const user = req.user;
- const headers = req.headers;
-
- // determine language
- const locale = user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
- : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
+async function determinePath(_parentPath?: string, _path?: string, optionalParentPath?: string): Promise {
+ const locale = configManager.getConfig('crowi', 'app:globalLang') as Lang;
const t = i18n?.getFixedT(locale);
const basePathname = t?.('create_page.untitled') || 'Untitled';
@@ -220,7 +214,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
let pathToCreate: string;
try {
const { path, parentPath, optionalParentPath } = req.body;
- pathToCreate = await determinePath(req, parentPath, path, optionalParentPath);
+ pathToCreate = await determinePath(parentPath, path, optionalParentPath);
}
catch (err) {
return res.apiv3Err(new ErrorV3(err.toString(), 'could_not_create_page'));
From 72452bcd6b0ac40c1c1c7b6f472b41f04a2d501b Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 26 Apr 2024 10:54:22 +0000
Subject: [PATCH 051/180] remove slideviewer switch in revisionrenderer
---
.../src/components/Page/RevisionRenderer.tsx | 33 ++++---------------
1 file changed, 6 insertions(+), 27 deletions(-)
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index b94438cf008..6eac006a6d8 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -6,13 +6,8 @@ import ReactMarkdown from 'react-markdown';
import type { RendererOptions } from '~/interfaces/renderer-options';
-import { useIsEnabledMarp } from '~/stores/context';
import loggerFactory from '~/utils/logger';
-import { SlideViewer } from '../ReactMarkdownComponents/SlideViewer';
-
-import { parseSlideFrontmatterInMarkdown } from './markdown-slide-util-for-view';
-
import 'katex/dist/katex.min.css';
@@ -42,30 +37,14 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
rendererOptions, markdown, additionalClassName,
} = props;
- const { data: isEnabledMarp } = useIsEnabledMarp();
- const [marp, useSlide] = parseSlideFrontmatterInMarkdown(markdown);
- const useMarp = !!isEnabledMarp && marp;
-
return (
- {
- (useMarp || useSlide)
- ? (
-
- {markdown}
-
- )
- : (
-
- {markdown}
-
- )
- }
+
+ {markdown}
+
);
From d48b394e313aa50302c34931a815d2cde53b04b5 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 26 Apr 2024 11:01:17 +0000
Subject: [PATCH 052/180] change to parse.ts path
---
.../presentation/src/services/parse-slide-frontmatter.ts | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename apps/app/src/components/Page/markdown-slide-util-for-view.ts => packages/presentation/src/services/parse-slide-frontmatter.ts (100%)
diff --git a/apps/app/src/components/Page/markdown-slide-util-for-view.ts b/packages/presentation/src/services/parse-slide-frontmatter.ts
similarity index 100%
rename from apps/app/src/components/Page/markdown-slide-util-for-view.ts
rename to packages/presentation/src/services/parse-slide-frontmatter.ts
From 63236067916e0f0b008d5a776b816a5d5a9ed2da Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Fri, 26 Apr 2024 11:08:28 +0000
Subject: [PATCH 053/180] clean code
---
.../src/components/Page/RevisionRenderer.tsx | 2 --
apps/app/src/components/PageEditor/Preview.tsx | 2 ++
.../components/Presentation/Presentation.tsx | 17 +++--------------
packages/presentation/src/index.ts | 1 +
4 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/apps/app/src/components/Page/RevisionRenderer.tsx b/apps/app/src/components/Page/RevisionRenderer.tsx
index 6eac006a6d8..89e30dd0154 100644
--- a/apps/app/src/components/Page/RevisionRenderer.tsx
+++ b/apps/app/src/components/Page/RevisionRenderer.tsx
@@ -4,11 +4,9 @@ import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import ReactMarkdown from 'react-markdown';
-
import type { RendererOptions } from '~/interfaces/renderer-options';
import loggerFactory from '~/utils/logger';
-
import 'katex/dist/katex.min.css';
diff --git a/apps/app/src/components/PageEditor/Preview.tsx b/apps/app/src/components/PageEditor/Preview.tsx
index 17eeb30c9d2..c9e4629f984 100644
--- a/apps/app/src/components/PageEditor/Preview.tsx
+++ b/apps/app/src/components/PageEditor/Preview.tsx
@@ -1,6 +1,8 @@
import type { CSSProperties } from 'react';
import React from 'react';
+import { parseSlideFrontmatter } from '@growi/presentation';
+
import type { RendererOptions } from '~/interfaces/renderer-options';
import RevisionRenderer from '../Page/RevisionRenderer';
diff --git a/apps/app/src/components/Presentation/Presentation.tsx b/apps/app/src/components/Presentation/Presentation.tsx
index 2d3067ccc08..4b0cad7e5c0 100644
--- a/apps/app/src/components/Presentation/Presentation.tsx
+++ b/apps/app/src/components/Presentation/Presentation.tsx
@@ -1,18 +1,7 @@
-import { Presentation as PresentationSubstance, type PresentationProps as PresentationPropsSubstance } from '@growi/presentation';
-
-import { parseSlideFrontmatterInMarkdown } from '../Page/markdown-slide-util-for-view';
+import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation';
import '@growi/presentation/dist/style.css';
-type Props = Omit & {
- isEnabledMarp: boolean
-};
-
-export const Presentation = (props: Props): JSX.Element => {
- const { options, isEnabledMarp, children } = props;
-
- const [marp] = parseSlideFrontmatterInMarkdown(children ?? '');
- const hasMarpFlag = isEnabledMarp && marp;
-
- return {children};
+export const Presentation = (props: PresentationProps): JSX.Element => {
+ return ;
};
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts
index 4dd96f2470c..79d97b6de1c 100644
--- a/packages/presentation/src/index.ts
+++ b/packages/presentation/src/index.ts
@@ -1,2 +1,3 @@
export * from './components/Presentation';
export * from './components/Slides';
+export * from './services/parse-slide-frontmatter';
From aa561d0806c1be03b6af5f2c5b44f0c1df9df9ef Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 30 Apr 2024 04:20:59 +0000
Subject: [PATCH 054/180] undo
---
apps/app/src/components/TreeItem/SimpleItem.tsx | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/apps/app/src/components/TreeItem/SimpleItem.tsx b/apps/app/src/components/TreeItem/SimpleItem.tsx
index 8284647c29b..92e4d9b68d8 100644
--- a/apps/app/src/components/TreeItem/SimpleItem.tsx
+++ b/apps/app/src/components/TreeItem/SimpleItem.tsx
@@ -1,7 +1,6 @@
import React, {
useCallback, useState, useEffect,
type FC, type RefObject, type RefCallback, type MouseEvent,
- useRef,
} from 'react';
import nodePath from 'path';
@@ -113,7 +112,6 @@ export const SimpleItem: FC = (props) => {
const { data } = useSWRxPageChildren(isOpen ? page._id : null);
- const activeTargetRef = useRef(null);
const itemClickHandler = useCallback((e: MouseEvent) => {
// DO NOT handle the event when e.currentTarget and e.target is different
@@ -169,17 +167,6 @@ export const SimpleItem: FC = (props) => {
}, [data, isOpen, targetPathOrId]);
- // When open Sidebar, scroll into view active item.
- useEffect(() => {
- if (!isOpen || data == null) {
- return;
- }
- const timeoutId = setTimeout(() => {
- activeTargetRef.current?.scrollIntoView(true);
- }, 4000);
- return () => clearTimeout(timeoutId);
- }, [data, isOpen]);
-
const ItemClassFixed = itemClass ?? SimpleItem;
const EndComponents = props.customEndComponents ?? [SimpleItemTool];
@@ -208,7 +195,6 @@ export const SimpleItem: FC = (props) => {
return (
Date: Tue, 30 Apr 2024 05:26:44 +0000
Subject: [PATCH 055/180] cache element ref
---
apps/app/src/stores/ui.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/app/src/stores/ui.tsx b/apps/app/src/stores/ui.tsx
index a4fcda1f7cb..6db4b69dc3a 100644
--- a/apps/app/src/stores/ui.tsx
+++ b/apps/app/src/stores/ui.tsx
@@ -52,10 +52,6 @@ export type EditorMode = typeof EditorMode[keyof typeof EditorMode];
* Storing objects to ref
*********************************************************** */
-export const useSidebarScrollerRef = (initialData?: RefObject
): SWRResponse, Error> => {
- return useStaticSWR, Error>('sidebarScrollerRef', initialData);
-};
-
export const useCurrentPageTocNode = (): SWRResponse => {
const { data: currentPagePath } = useCurrentPagePath();
@@ -67,6 +63,10 @@ export const useCurrentPageTocNode = (): SWRResponse => {
* for switching UI
*********************************************************** */
+export const useSidebarScrollerRef = (initialData?: RefObject): SWRResponse, Error> => {
+ return useSWRStatic, Error>('sidebarScrollerRef', initialData);
+};
+
export const useIsMobile = (): SWRResponse => {
const key = isClient() ? 'isMobile' : null;
From 111fbc7881f18796c7d767c2cfd37617d658a4d6 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 30 Apr 2024 05:26:56 +0000
Subject: [PATCH 056/180] use element ref
---
apps/app/src/components/ItemsTree/ItemsTree.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/apps/app/src/components/ItemsTree/ItemsTree.tsx b/apps/app/src/components/ItemsTree/ItemsTree.tsx
index 85fcfd5d4be..17ae48abaee 100644
--- a/apps/app/src/components/ItemsTree/ItemsTree.tsx
+++ b/apps/app/src/components/ItemsTree/ItemsTree.tsx
@@ -32,6 +32,7 @@ import ItemsTreeContentSkeleton from './ItemsTreeContentSkeleton';
import styles from './ItemsTree.module.scss';
+const moduleClass = styles['grw-pagetree'] ?? '';
const logger = loggerFactory('growi:cli:ItemsTree');
@@ -210,7 +211,7 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
logger.debug('scrollOnInit has invoked');
- const scrollElement = sidebarScrollerRef.current.getScrollElement();
+ const scrollElement = sidebarScrollerRef.current;
// NOTE: could not use scrollIntoView
// https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
@@ -275,7 +276,7 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
if (initialItemNode != null) {
return (
-
+
Date: Tue, 30 Apr 2024 05:27:12 +0000
Subject: [PATCH 057/180] mutate scroller
---
apps/app/src/components/Sidebar/Sidebar.tsx | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/apps/app/src/components/Sidebar/Sidebar.tsx b/apps/app/src/components/Sidebar/Sidebar.tsx
index 0dcdd71d391..849ae3e439d 100644
--- a/apps/app/src/components/Sidebar/Sidebar.tsx
+++ b/apps/app/src/components/Sidebar/Sidebar.tsx
@@ -1,6 +1,7 @@
import React, {
type FC,
memo, useCallback, useEffect, useState,
+ useRef,
} from 'react';
import dynamic from 'next/dynamic';
@@ -13,6 +14,7 @@ import {
useCurrentProductNavWidth,
usePreferCollapsedMode,
useSidebarMode,
+ useSidebarScrollerRef,
} from '~/stores/ui';
import { DrawerToggler } from '../Common/DrawerToggler';
@@ -109,6 +111,10 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
const { data: currentProductNavWidth } = useCurrentProductNavWidth();
const { data: isCollapsedContentsOpened, mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
+ const sidebarScrollerRef = useRef(null);
+ const { mutate: mutateSidebarScroller } = useSidebarScrollerRef();
+ mutateSidebarScroller(sidebarScrollerRef);
+
// open menu when collapsed mode
const primaryItemHoverHandler = useCallback(() => {
@@ -138,6 +144,7 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
From 320f3082490a8d473a16cfd32243021ea2200d72 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 30 Apr 2024 05:30:35 +0000
Subject: [PATCH 058/180] clean code
---
apps/app/src/components/TreeItem/SimpleItem.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/apps/app/src/components/TreeItem/SimpleItem.tsx b/apps/app/src/components/TreeItem/SimpleItem.tsx
index 92e4d9b68d8..a93dc7f33a2 100644
--- a/apps/app/src/components/TreeItem/SimpleItem.tsx
+++ b/apps/app/src/components/TreeItem/SimpleItem.tsx
@@ -166,7 +166,6 @@ export const SimpleItem: FC = (props) => {
}
}, [data, isOpen, targetPathOrId]);
-
const ItemClassFixed = itemClass ?? SimpleItem;
const EndComponents = props.customEndComponents ?? [SimpleItemTool];
From 6c0d1283be153f58b27cb77ffc79154e52ad99c5 Mon Sep 17 00:00:00 2001
From: reiji-h
Date: Tue, 30 Apr 2024 06:43:12 +0000
Subject: [PATCH 059/180] switch slide or reactmarkdown
---
apps/app/src/components/Page/PageView.tsx | 14 +++++++++++++-
.../app/src/components/PageEditor/Preview.tsx | 19 ++++++++++++++-----
apps/app/src/components/ShareLinkPageView.tsx | 13 ++++++++++++-
3 files changed, 39 insertions(+), 7 deletions(-)
diff --git a/apps/app/src/components/Page/PageView.tsx b/apps/app/src/components/Page/PageView.tsx
index 9e4ca91eb48..c3985d24f73 100644
--- a/apps/app/src/components/Page/PageView.tsx
+++ b/apps/app/src/components/Page/PageView.tsx
@@ -4,12 +4,14 @@ import React, {
import type { IPagePopulatedToShowRevision } from '@growi/core';
import { isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
+import { parseSlideFrontmatterInMarkdown } from '@growi/presentation';
import dynamic from 'next/dynamic';
import { useShouldExpandContent } from '~/client/services/layout';
import type { RendererConfig } from '~/interfaces/services/renderer';
import { generateSSRViewOptions } from '~/services/renderer/renderer';
import {
+ useIsEnabledMarp,
useIsForbidden, useIsIdenticalPath, useIsNotCreatable,
} from '~/stores/context';
import { useSWRxCurrentPage, useIsNotFound } from '~/stores/page';
@@ -23,6 +25,7 @@ import { PageViewLayout } from '../Common/PageViewLayout';
import { PageAlerts } from '../PageAlert/PageAlerts';
import { PageContentFooter } from '../PageContentFooter';
import type { PageSideContentsProps } from '../PageSideContents';
+import { SlideViewer } from '../ReactMarkdownComponents/SlideViewer';
import { UserInfo } from '../User/UserInfo';
import type { UsersHomepageFooterProps } from '../UsersHomepageFooter';
@@ -73,6 +76,8 @@ export const PageView = (props: Props): JSX.Element => {
const shouldExpandContent = useShouldExpandContent(page);
+ const { data: enabledMarp } = useIsEnabledMarp();
+
// *************************** Auto Scroll ***************************
useEffect(() => {
@@ -131,12 +136,19 @@ export const PageView = (props: Props): JSX.Element => {
const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
const markdown = page.revision.body;
+ const [marp, useSlide] = parseSlideFrontmatterInMarkdown(markdown);
+ const useMarp = enabledMarp && marp;
+
return (
<>
-
+ {
+ (viewOptions && (useMarp || useSlide))
+ ? ({markdown})
+ : ()
+ }
{ !isIdenticalPathPage && !isNotFound && (
+ void
@@ -91,6 +100,65 @@ export const PageTreeContent = memo(({ isWipPageShown }: PageTreeContentProps) =
const { data: migrationStatus } = useSWRxV5MigrationStatus({ suspense: true });
const targetPathOrId = targetId || currentPath;
+ const path = currentPath || '/';
+
+ const { data: ancestorsChildrenResult } = useSWRxPageAncestorsChildren(path, { suspense: true });
+ const { data: rootPageResult } = useSWRxRootPage({ suspense: true });
+ const { data: sidebarScrollerRef } = useSidebarScrollerRef();
+ const [isInitialScrollCompleted, setIsInitialScrollCompleted] = useState(false);
+
+ const rootElemRef = useRef(null);
+
+ // *************************** Scroll on init ***************************
+ const scrollOnInit = useCallback(() => {
+ const scrollTargetElement = document.getElementById('grw-pagetree-current-page-item');
+
+ if (sidebarScrollerRef?.current == null || scrollTargetElement == null) {
+ return;
+ }
+
+ logger.debug('scrollOnInit has invoked');
+
+ const scrollElement = sidebarScrollerRef.current;
+
+ // NOTE: could not use scrollIntoView
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
+
+ // calculate the center point
+ const scrollTop = scrollTargetElement.offsetTop - scrollElement.getBoundingClientRect().height / 2;
+ scrollElement.scrollTo({ top: scrollTop });
+
+ setIsInitialScrollCompleted(true);
+ }, [sidebarScrollerRef]);
+
+ const scrollOnInitDebounced = useMemo(() => debounce(500, scrollOnInit), [scrollOnInit]);
+
+ useEffect(() => {
+ if (isInitialScrollCompleted || ancestorsChildrenResult == null || rootPageResult == null) {
+ return;
+ }
+
+ const rootElement = rootElemRef.current as HTMLElement | null;
+ if (rootElement == null) {
+ return;
+ }
+
+ const observerCallback = (mutationRecords: MutationRecord[]) => {
+ mutationRecords.forEach(() => scrollOnInitDebounced());
+ };
+
+ const observer = new MutationObserver(observerCallback);
+ observer.observe(rootElement, { childList: true, subtree: true });
+
+ // first call for the situation that all rendering is complete at this point
+ scrollOnInitDebounced();
+
+ return () => {
+ observer.disconnect();
+ };
+ }, [isInitialScrollCompleted, scrollOnInitDebounced, ancestorsChildrenResult, rootPageResult]);
+ // ******************************* end *******************************
+
if (!migrationStatus?.isV5Compatible) {
return ;
@@ -103,10 +171,9 @@ export const PageTreeContent = memo(({ isWipPageShown }: PageTreeContentProps) =
return null;
}
- const path = currentPath || '/';
return (
- <>
+
)}
- >
+
+ = (props) => {
diff --git a/apps/app/src/components/TreeItem/SimpleItem.module.scss b/apps/app/src/components/TreeItem/SimpleItem.module.scss
new file mode 100644
index 00000000000..2ee2e131b4f
--- /dev/null
+++ b/apps/app/src/components/TreeItem/SimpleItem.module.scss
@@ -0,0 +1,15 @@
+// show / hide on hover
+.simple-item {
+ :global {
+ .list-group-item {
+ &:hover {
+ .d-hover-none {
+ display: none !important;
+ }
+ .d-hover-block {
+ display: block !important;
+ }
+ }
+ }
+ }
+}
diff --git a/apps/app/src/components/TreeItem/SimpleItem.tsx b/apps/app/src/components/TreeItem/SimpleItem.tsx
index a93dc7f33a2..71fd856c067 100644
--- a/apps/app/src/components/TreeItem/SimpleItem.tsx
+++ b/apps/app/src/components/TreeItem/SimpleItem.tsx
@@ -22,6 +22,11 @@ import { useNewPageInput } from './NewPageInput';
import type { TreeItemProps, TreeItemToolProps } from './interfaces';
+import styles from './SimpleItem.module.scss';
+
+const moduleClass = styles['simple-item'] ?? '';
+
+
// Utility to mark target
const markTarget = (children: ItemNode[], targetPathOrId?: Nullable): void => {
if (targetPathOrId == null) {
@@ -84,7 +89,7 @@ export const SimpleItemTool: FC = (props) => {
return (
<>
{descendantCount > 0 && (
-
+
)}
@@ -196,7 +201,7 @@ export const SimpleItem: FC = (props) => {
-
Date: Tue, 7 May 2024 12:51:41 +0000
Subject: [PATCH 109/180] WIP: relocate CountBadge
---
.../CountBadgeForPageTreeItem.tsx | 21 ++++
.../Sidebar/PageTreeItem/Ellipsis.module.scss | 9 ++
.../Sidebar/PageTreeItem/Ellipsis.tsx | 98 +++++++++++--------
.../Sidebar/PageTreeItem/PageTreeItem.tsx | 3 +-
.../src/components/TreeItem/SimpleItem.tsx | 23 +----
5 files changed, 90 insertions(+), 64 deletions(-)
create mode 100644 apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx
create mode 100644 apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.module.scss
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx b/apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx
new file mode 100644
index 00000000000..7b6984c7467
--- /dev/null
+++ b/apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx
@@ -0,0 +1,21 @@
+import CountBadge from '~/components/Common/CountBadge';
+import type { TreeItemToolProps } from '~/components/TreeItem';
+import { usePageTreeDescCountMap } from '~/stores/ui';
+
+export const CountBadgeForPageTreeItem = (props: TreeItemToolProps): JSX.Element => {
+ const { getDescCount } = usePageTreeDescCountMap();
+
+ const { page } = props.itemNode;
+
+ const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
+
+ return (
+ <>
+ {descendantCount > 0 && (
+
+
+
+ )}
+ >
+ );
+};
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.module.scss b/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.module.scss
new file mode 100644
index 00000000000..f7fc43c4097
--- /dev/null
+++ b/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.module.scss
@@ -0,0 +1,9 @@
+.rename-input-container {
+ &:global {
+ // right: 0;
+ // left: 0;
+ // display: inline-flex;
+
+ // width: calc(100% - 80px);
+ }
+}
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx b/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx
index 7f6bc0db667..0742894cefa 100644
--- a/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx
+++ b/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx
@@ -1,6 +1,6 @@
import type { FC } from 'react';
import React, {
- useCallback, useState,
+ useCallback, useRef, useState,
} from 'react';
import nodePath from 'path';
@@ -8,6 +8,7 @@ import nodePath from 'path';
import type { IPageInfoAll, IPageToDeleteWithMeta } from '@growi/core';
import { pathUtils } from '@growi/core/dist/utils';
+import { useRect } from '@growi/ui/dist/utils';
import { useTranslation } from 'next-i18next';
import { DropdownToggle } from 'reactstrap';
@@ -15,16 +16,22 @@ import { bookmark, unbookmark, resumeRenameOperation } from '~/client/services/p
import { apiv3Put } from '~/client/util/apiv3-client';
import { ValidationTarget } from '~/client/util/input-validator';
import { toastError, toastSuccess } from '~/client/util/toastr';
+import { AutosizeSubmittableInput } from '~/components/Common/SubmittableInput';
import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
import { useSWRMUTxCurrentUserBookmarks } from '~/stores/bookmark';
import { useSWRMUTxPageInfo } from '~/stores/page';
-import ClosableTextInput from '../../Common/ClosableTextInput';
import { PageItemControl } from '../../Common/Dropdown/PageItemControl';
import {
- type TreeItemToolProps, NotDraggableForClosableTextInput, SimpleItemTool,
+ type TreeItemToolProps, NotDraggableForClosableTextInput,
} from '../../TreeItem';
+
+import styles from './Ellipsis.module.scss';
+
+const renameInputContainerClass = styles['rename-input-container'] ?? '';
+
+
export const Ellipsis: FC = (props) => {
const [isRenameInputShown, setRenameInputShown] = useState(false);
const { t } = useTranslation();
@@ -34,6 +41,9 @@ export const Ellipsis: FC = (props) => {
onClickDeleteMenuItem, isEnableActions, isReadOnlyUser,
} = props;
+ const parentRef = useRef(null);
+ const parentRect = useRect(parentRef);
+
const { page } = itemNode;
const { trigger: mutateCurrentUserBookmarks } = useSWRMUTxCurrentUserBookmarks();
@@ -132,48 +142,52 @@ export const Ellipsis: FC = (props) => {
}
};
- const hasChildren = page.descendantCount ? page.descendantCount > 0 : false;
+ const maxWidth = parentRect[0]?.width;
+ console.log({ parentRef });
+ console.log('maxWidth:', maxWidth);
return (
<>
- {isRenameInputShown ? (
-
-
-
-
-
- ) : (
-
- )}
-
-
-
- {/* pass the color property to reactstrap dropdownToggle props. https://6-4-0--reactstrap.netlify.app/components/dropdowns/ */}
-
- more_vert
-
-
-
-
+ {/* {isRenameInputShown || page._id === '6630d957b26dc26e85ee21a8' ? ( */}
+ {/* */}
+
+
+
+ {/* */}
+
+ { !isRenameInputShown && (
+
+
+
+ {/* pass the color property to reactstrap dropdownToggle props. https://6-4-0--reactstrap.netlify.app/components/dropdowns/ */}
+
+ more_vert
+
+
+
+
+ ) }
>
);
};
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx b/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx
index c9dbfe4c2ec..94b316e81f6 100644
--- a/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx
+++ b/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx
@@ -22,6 +22,7 @@ import {
SimpleItem, useNewPageInput, type TreeItemProps,
} from '../../TreeItem';
+import { CountBadgeForPageTreeItem } from './CountBadgeForPageTreeItem';
import { Ellipsis } from './Ellipsis';
import styles from './PageTreeItem.module.scss';
@@ -186,7 +187,7 @@ export const PageTreeItem: FC = (props) => {
itemRef={itemRef}
itemClass={PageTreeItem}
mainClassName={mainClassName}
- customEndComponents={[Ellipsis, NewPageCreateButton]}
+ customEndComponents={[CountBadgeForPageTreeItem, Ellipsis, NewPageCreateButton]}
customNextComponents={[NewPageInput]}
/>
);
diff --git a/apps/app/src/components/TreeItem/SimpleItem.tsx b/apps/app/src/components/TreeItem/SimpleItem.tsx
index 71fd856c067..3874282971c 100644
--- a/apps/app/src/components/TreeItem/SimpleItem.tsx
+++ b/apps/app/src/components/TreeItem/SimpleItem.tsx
@@ -15,8 +15,6 @@ import { useSWRxPageChildren } from '~/stores/page-listing';
import { usePageTreeDescCountMap } from '~/stores/ui';
import { shouldRecoverPagePaths } from '~/utils/page-operation';
-import CountBadge from '../Common/CountBadge';
-
import { ItemNode } from './ItemNode';
import { useNewPageInput } from './NewPageInput';
import type { TreeItemProps, TreeItemToolProps } from './interfaces';
@@ -79,23 +77,6 @@ const SimpleItemContent = ({ page }: { page: IPageForItem }) => {
);
};
-export const SimpleItemTool: FC = (props) => {
- const { getDescCount } = usePageTreeDescCountMap();
-
- const { page } = props.itemNode;
-
- const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
-
- return (
- <>
- {descendantCount > 0 && (
-
-
-
- )}
- >
- );
-};
type SimpleItemProps = TreeItemProps & {
itemRef?: RefObject | RefCallback,
@@ -173,7 +154,7 @@ export const SimpleItem: FC = (props) => {
const ItemClassFixed = itemClass ?? SimpleItem;
- const EndComponents = props.customEndComponents ?? [SimpleItemTool];
+ const EndComponents = props.customEndComponents;
const baseProps: Omit = {
isEnableActions,
@@ -227,7 +208,7 @@ export const SimpleItem: FC = (props) => {
- {EndComponents.map((EndComponent, index) => (
+ {EndComponents?.map((EndComponent, index) => (
// eslint-disable-next-line react/no-array-index-key
))}
From af39515fd4b2893edfc17df6d65ea697893277ee Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Tue, 7 May 2024 13:04:46 +0000
Subject: [PATCH 110/180] Implement a socket to emit when the last user leaves
the Editor
---
apps/app/src/server/service/socket-io.js | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/apps/app/src/server/service/socket-io.js b/apps/app/src/server/service/socket-io.js
index 57633f33ee9..75c2db494a5 100644
--- a/apps/app/src/server/service/socket-io.js
+++ b/apps/app/src/server/service/socket-io.js
@@ -1,4 +1,5 @@
import { GlobalSocketEventName } from '@growi/core/dist/interfaces';
+import mongoose from 'mongoose';
import { Server } from 'socket.io';
import { SocketEventName } from '~/interfaces/websocket';
@@ -169,14 +170,31 @@ class SocketIoService {
setupYjsConnection() {
const yjsConnectionManager = getYjsConnectionManager();
+ const Page = mongoose.model('Page');
this.io.on('connection', (socket) => {
yjsConnectionManager.ysocketioInstance.on('awareness-update', async(update) => {
const pageId = extractPageIdFromYdocId(update.name);
const awarenessStateSize = update.awareness.states.size;
+
this.io
.in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
.emit(SocketEventName.YjsAwarenessStateUpdated, awarenessStateSize);
+
+ // Executed when the last user leaves the Editor
+ if (awarenessStateSize === 0) {
+ const page = await Page.findOne({ _id: pageId });
+ if (page != null) {
+ const populatedPage = await page.populateDataToShowRevision();
+ const revisionBody = populatedPage.revision.body;
+ const currentYdoc = yjsConnectionManager.getCurrentYdoc(pageId);
+ const yjsDraft = currentYdoc?.getText('codemirror').toString();
+ const hasRevisionBodyDiff = revisionBody != null && yjsDraft != null && yjsDraft !== revisionBody;
+ this.io
+ .in(getRoomNameWithId(RoomPrefix.PAGE, pageId))
+ .emit(SocketEventName.YjsHasRevisionBodyDiffUpdated, hasRevisionBodyDiff);
+ }
+ }
});
socket.on(GlobalSocketEventName.YDocSync, async({ pageId, initialValue }) => {
From 3974677cf73a4d6f1eb500b1ead859674dc16946 Mon Sep 17 00:00:00 2001
From: Shun Miyazawa
Date: Tue, 7 May 2024 13:06:34 +0000
Subject: [PATCH 111/180] Listen to YjsHasRevisionBodyDiffUpdated
---
.../src/client/services/side-effects/yjs.ts | 30 +++++++------------
.../src/components/Page/DisplaySwitcher.tsx | 5 ++--
apps/app/src/interfaces/websocket.ts | 1 +
apps/app/src/stores/yjs.ts | 9 +++++-
4 files changed, 21 insertions(+), 24 deletions(-)
diff --git a/apps/app/src/client/services/side-effects/yjs.ts b/apps/app/src/client/services/side-effects/yjs.ts
index ecae53565a8..b03a31b655a 100644
--- a/apps/app/src/client/services/side-effects/yjs.ts
+++ b/apps/app/src/client/services/side-effects/yjs.ts
@@ -6,30 +6,17 @@ import { SocketEventName } from '~/interfaces/websocket';
import { type CurrentPageYjsDraft } from '~/interfaces/yjs';
import { useCurrentPageYjsData } from '~/stores/yjs';
-export const useYjsDraftEffect = (): void => {
+export const useCurrentPageYjsDataEffect = (): void => {
const { data: socket } = useGlobalSocket();
- const { updateHasDraft } = useCurrentPageYjsData();
+ const { updateHasDraft, updateHasRevisionBodyDiff, updateAwarenessStateSize } = useCurrentPageYjsData();
const yjsDraftUpdateHandler = useCallback(((currentPageYjsDraft: CurrentPageYjsDraft) => {
updateHasDraft(currentPageYjsDraft.hasYjsDraft);
}), [updateHasDraft]);
- useEffect(() => {
-
- if (socket == null) { return }
-
- socket.on(SocketEventName.YjsDraftUpdated, yjsDraftUpdateHandler);
-
- return () => {
- socket.off(SocketEventName.YjsDraftUpdated, yjsDraftUpdateHandler);
- };
-
- }, [socket, yjsDraftUpdateHandler]);
-};
-
-export const useYjsAwarenessStateEffect = (): void => {
- const { data: socket } = useGlobalSocket();
- const { updateAwarenessStateSize } = useCurrentPageYjsData();
+ const yjsHasRevisionBodyDiffUpdateHandler = useCallback((hasRevisionBodyDiff: boolean) => {
+ updateHasRevisionBodyDiff(hasRevisionBodyDiff);
+ }, [updateHasRevisionBodyDiff]);
const yjsAwarenessStateUpdateHandler = useCallback(((awarenessStateSize: number) => {
updateAwarenessStateSize(awarenessStateSize);
@@ -39,12 +26,15 @@ export const useYjsAwarenessStateEffect = (): void => {
if (socket == null) { return }
+ socket.on(SocketEventName.YjsDraftUpdated, yjsDraftUpdateHandler);
+ socket.on(SocketEventName.YjsHasRevisionBodyDiffUpdated, yjsHasRevisionBodyDiffUpdateHandler);
socket.on(SocketEventName.YjsAwarenessStateUpdated, yjsAwarenessStateUpdateHandler);
return () => {
+ socket.off(SocketEventName.YjsDraftUpdated, yjsDraftUpdateHandler);
+ socket.off(SocketEventName.YjsHasRevisionBodyDiffUpdated, yjsHasRevisionBodyDiffUpdateHandler);
socket.off(SocketEventName.YjsAwarenessStateUpdated, yjsAwarenessStateUpdateHandler);
};
- }, [socket, yjsAwarenessStateUpdateHandler]);
-
+ }, [socket, yjsAwarenessStateUpdateHandler, yjsDraftUpdateHandler, yjsHasRevisionBodyDiffUpdateHandler]);
};
diff --git a/apps/app/src/components/Page/DisplaySwitcher.tsx b/apps/app/src/components/Page/DisplaySwitcher.tsx
index b6afdc5e851..313d4571923 100644
--- a/apps/app/src/components/Page/DisplaySwitcher.tsx
+++ b/apps/app/src/components/Page/DisplaySwitcher.tsx
@@ -4,7 +4,7 @@ import dynamic from 'next/dynamic';
import { useHashChangedEffect } from '~/client/services/side-effects/hash-changed';
import { usePageUpdatedEffect } from '~/client/services/side-effects/page-updated';
-import { useYjsDraftEffect, useYjsAwarenessStateEffect } from '~/client/services/side-effects/yjs';
+import { useCurrentPageYjsDataEffect } from '~/client/services/side-effects/yjs';
import { useIsEditable } from '~/stores/context';
import { useIsLatestRevision } from '~/stores/page';
import { EditorMode, useEditorMode } from '~/stores/ui';
@@ -27,8 +27,7 @@ export const DisplaySwitcher = (props: Props): JSX.Element => {
usePageUpdatedEffect();
useHashChangedEffect();
- useYjsDraftEffect();
- useYjsAwarenessStateEffect();
+ useCurrentPageYjsDataEffect();
return (
<>
diff --git a/apps/app/src/interfaces/websocket.ts b/apps/app/src/interfaces/websocket.ts
index f47fa7fd998..709545c4813 100644
--- a/apps/app/src/interfaces/websocket.ts
+++ b/apps/app/src/interfaces/websocket.ts
@@ -52,6 +52,7 @@ export const SocketEventName = {
// Yjs
YjsDraftUpdated: 'yjs:draft-update',
YjsAwarenessStateUpdated: 'yjs:awareness-state-update',
+ YjsHasRevisionBodyDiffUpdated: 'yjs:has-revision-body-diff-update',
} as const;
export type SocketEventName = typeof SocketEventName[keyof typeof SocketEventName];
diff --git a/apps/app/src/stores/yjs.ts b/apps/app/src/stores/yjs.ts
index e8f26441c6d..35a315db896 100644
--- a/apps/app/src/stores/yjs.ts
+++ b/apps/app/src/stores/yjs.ts
@@ -7,6 +7,7 @@ import type { CurrentPageYjsData } from '~/interfaces/yjs';
type CurrentPageYjsDataUtils = {
updateHasDraft(hasYjsDraft: boolean): void
+ updateHasRevisionBodyDiff(hasRevisionBodyDiff: boolean): void
updateAwarenessStateSize(awarenessStateSize: number): void
}
@@ -17,9 +18,15 @@ export const useCurrentPageYjsData = (): SWRResponse
swrResponse.mutate({ ...swrResponse.data, hasDraft });
}, [swrResponse]);
+ const updateHasRevisionBodyDiff = useCallback((hasRevisionBodyDiff: boolean) => {
+ swrResponse.mutate({ ...swrResponse.data, hasRevisionBodyDiff });
+ }, [swrResponse]);
+
const updateAwarenessStateSize = useCallback((awarenessStateSize: number) => {
swrResponse.mutate({ ...swrResponse.data, awarenessStateSize });
}, [swrResponse]);
- return { ...swrResponse, updateHasDraft, updateAwarenessStateSize };
+ return {
+ ...swrResponse, updateHasDraft, updateHasRevisionBodyDiff, updateAwarenessStateSize,
+ };
};
From 1c2cb4566175aadc66290c52a6eaff5edcab8f08 Mon Sep 17 00:00:00 2001
From: Yuki Takei
Date: Tue, 7 May 2024 13:14:00 +0000
Subject: [PATCH 112/180] refactor layout
---
.../CountBadgeForPageTreeItem.tsx | 4 +-
.../PageTreeItem/CreatingNewPageSpinner.tsx | 13 ++++++
.../Sidebar/PageTreeItem/Ellipsis.tsx | 2 +-
.../Sidebar/PageTreeItem/PageTreeItem.tsx | 7 ++-
.../NewPageInput/NewPageCreateButton.tsx | 2 +-
.../TreeItem/SimpleItem.module.scss | 4 +-
.../src/components/TreeItem/SimpleItem.tsx | 44 +++++++++++--------
.../components/TreeItem/interfaces/index.ts | 3 ++
8 files changed, 52 insertions(+), 27 deletions(-)
create mode 100644 apps/app/src/components/Sidebar/PageTreeItem/CreatingNewPageSpinner.tsx
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx b/apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx
index 7b6984c7467..096d3409d11 100644
--- a/apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx
+++ b/apps/app/src/components/Sidebar/PageTreeItem/CountBadgeForPageTreeItem.tsx
@@ -12,9 +12,7 @@ export const CountBadgeForPageTreeItem = (props: TreeItemToolProps): JSX.Element
return (
<>
{descendantCount > 0 && (
-
-
-
+
)}
>
);
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/CreatingNewPageSpinner.tsx b/apps/app/src/components/Sidebar/PageTreeItem/CreatingNewPageSpinner.tsx
new file mode 100644
index 00000000000..14878d2abb7
--- /dev/null
+++ b/apps/app/src/components/Sidebar/PageTreeItem/CreatingNewPageSpinner.tsx
@@ -0,0 +1,13 @@
+import { LoadingSpinner } from '@growi/ui/dist/components';
+
+export const CreatingNewPageSpinner = ({ show }: { show?: boolean }): JSX.Element => {
+ if (!show) {
+ return <>>;
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx b/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx
index 0742894cefa..ef42869dfc8 100644
--- a/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx
+++ b/apps/app/src/components/Sidebar/PageTreeItem/Ellipsis.tsx
@@ -181,7 +181,7 @@ export const Ellipsis: FC = (props) => {
operationProcessData={page.processData}
>
{/* pass the color property to reactstrap dropdownToggle props. https://6-4-0--reactstrap.netlify.app/components/dropdowns/ */}
-
+
more_vert
diff --git a/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx b/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx
index 94b316e81f6..692f6e44e46 100644
--- a/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx
+++ b/apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx
@@ -23,6 +23,7 @@ import {
} from '../../TreeItem';
import { CountBadgeForPageTreeItem } from './CountBadgeForPageTreeItem';
+import { CreatingNewPageSpinner } from './CreatingNewPageSpinner';
import { Ellipsis } from './Ellipsis';
import styles from './PageTreeItem.module.scss';
@@ -170,7 +171,7 @@ export const PageTreeItem: FC = (props) => {
const mainClassName = `${isOver ? 'grw-pagetree-is-over' : ''} ${shouldHide ? 'd-none' : ''}`;
- const { Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
+ const { isProcessingSubmission, Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
return (
= (props) => {
itemRef={itemRef}
itemClass={PageTreeItem}
mainClassName={mainClassName}
- customEndComponents={[CountBadgeForPageTreeItem, Ellipsis, NewPageCreateButton]}
+ customEndComponents={[CountBadgeForPageTreeItem]}
+ customHoveredEndComponents={[Ellipsis, NewPageCreateButton]}
customNextComponents={[NewPageInput]}
+ customNextToChildrenComponents={[() => ]}
/>
);
};
diff --git a/apps/app/src/components/TreeItem/NewPageInput/NewPageCreateButton.tsx b/apps/app/src/components/TreeItem/NewPageInput/NewPageCreateButton.tsx
index dd004f62684..0c1ab30eda6 100644
--- a/apps/app/src/components/TreeItem/NewPageInput/NewPageCreateButton.tsx
+++ b/apps/app/src/components/TreeItem/NewPageInput/NewPageCreateButton.tsx
@@ -24,7 +24,7 @@ export const NewPageCreateButton: FC = (props) => {
- {CustomNextComponents?.map((UnderItemContent, index) => (
+ {NextComponents?.map((NextContent, index) => (
// eslint-disable-next-line react/no-array-index-key
-
+
))}
{
@@ -233,11 +240,12 @@ export const SimpleItem: FC = (props) => {
return (
- {isProcessingSubmission && (currentChildren.length - 1 === index) && (
-
-
-
- )}
+
+ {NextToChildrenComponents?.map((NextToChildrenContent, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
);
})
diff --git a/apps/app/src/components/TreeItem/interfaces/index.ts b/apps/app/src/components/TreeItem/interfaces/index.ts
index d51f6658d39..327713bf151 100644
--- a/apps/app/src/components/TreeItem/interfaces/index.ts
+++ b/apps/app/src/components/TreeItem/interfaces/index.ts
@@ -28,6 +28,9 @@ export type TreeItemProps = TreeItemBaseProps & {
itemClass?: React.FunctionComponent,
mainClassName?: string,
customEndComponents?: Array>,
+ customHoveredEndComponents?: Array>,
customNextComponents?: Array>,
+ customNextToChildrenComponents?: Array>,
+ customAlternativeComponents?: Array>,
onClick?(page: IPageForItem): void,
};
From f7c3eb047f3886af8837324cfa24ff74bb52cf4e Mon Sep 17 00:00:00 2001
From: Yuki Takei
Date: Tue, 7 May 2024 13:34:39 +0000
Subject: [PATCH 113/180] refactor SimpleItem as TreeItemLayout
---
.../PageSelectModal/TreeItemForModal.tsx | 4 +-
.../Sidebar/PageTreeItem/PageTreeItem.tsx | 4 +-
.../components/TreeItem/SimpleItemContent.tsx | 42 ++++++++++++
...module.scss => TreeItemLayout.module.scss} | 2 +-
.../{SimpleItem.tsx => TreeItemLayout.tsx} | 68 ++++++-------------
apps/app/src/components/TreeItem/index.ts | 2 +-
.../components/TreeItem/interfaces/index.ts | 4 +-
7 files changed, 72 insertions(+), 54 deletions(-)
create mode 100644 apps/app/src/components/TreeItem/SimpleItemContent.tsx
rename apps/app/src/components/TreeItem/{SimpleItem.module.scss => TreeItemLayout.module.scss} (92%)
rename apps/app/src/components/TreeItem/{SimpleItem.tsx => TreeItemLayout.tsx} (77%)
diff --git a/apps/app/src/components/PageSelectModal/TreeItemForModal.tsx b/apps/app/src/components/PageSelectModal/TreeItemForModal.tsx
index 7402f39233c..ef07f4b98aa 100644
--- a/apps/app/src/components/PageSelectModal/TreeItemForModal.tsx
+++ b/apps/app/src/components/PageSelectModal/TreeItemForModal.tsx
@@ -1,7 +1,7 @@
import type { FC } from 'react';
import {
- SimpleItem, useNewPageInput, type TreeItemProps,
+ TreeItemLayout, useNewPageInput, type TreeItemProps,
} from '../TreeItem';
@@ -16,7 +16,7 @@ export const TreeItemForModal: FC = (props) => {
const { Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
return (
- = (props) => {
const { isProcessingSubmission, Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
return (
- {
+ const { t } = useTranslation();
+
+ const pageName = nodePath.basename(page.path ?? '') || '/';
+
+ const shouldShowAttentionIcon = page.processData != null ? shouldRecoverPagePaths(page.processData) : false;
+
+ return (
+
+ {shouldShowAttentionIcon && (
+ <>
+ warning
+
+ {t('tooltip.operation.attention.rename')}
+
+ >
+ )}
+ {page != null && page.path != null && page._id != null && (
+
+
+ {pageName}
+ { page.wip && (
+ WIP
+ )}
+
+
+ )}
+
+ );
+};
diff --git a/apps/app/src/components/TreeItem/SimpleItem.module.scss b/apps/app/src/components/TreeItem/TreeItemLayout.module.scss
similarity index 92%
rename from apps/app/src/components/TreeItem/SimpleItem.module.scss
rename to apps/app/src/components/TreeItem/TreeItemLayout.module.scss
index f18f4ffd0c5..8f94af7accb 100644
--- a/apps/app/src/components/TreeItem/SimpleItem.module.scss
+++ b/apps/app/src/components/TreeItem/TreeItemLayout.module.scss
@@ -1,5 +1,5 @@
// show / hide on hover
-.simple-item {
+.tree-item-layout {
:global {
.list-group-item {
&:hover {
diff --git a/apps/app/src/components/TreeItem/SimpleItem.tsx b/apps/app/src/components/TreeItem/TreeItemLayout.tsx
similarity index 77%
rename from apps/app/src/components/TreeItem/SimpleItem.tsx
rename to apps/app/src/components/TreeItem/TreeItemLayout.tsx
index 076b0c0d7df..d0d59a3a798 100644
--- a/apps/app/src/components/TreeItem/SimpleItem.tsx
+++ b/apps/app/src/components/TreeItem/TreeItemLayout.tsx
@@ -3,24 +3,19 @@ import React, {
type FC, type RefObject, type RefCallback, type MouseEvent,
} from 'react';
-import nodePath from 'path';
-
import type { Nullable } from '@growi/core';
-import { useTranslation } from 'next-i18next';
-import { UncontrolledTooltip } from 'reactstrap';
-import type { IPageForItem } from '~/interfaces/page';
import { useSWRxPageChildren } from '~/stores/page-listing';
import { usePageTreeDescCountMap } from '~/stores/ui';
-import { shouldRecoverPagePaths } from '~/utils/page-operation';
import { ItemNode } from './ItemNode';
+import { SimpleItemContent } from './SimpleItemContent';
import type { TreeItemProps, TreeItemToolProps } from './interfaces';
-import styles from './SimpleItem.module.scss';
+import styles from './TreeItemLayout.module.scss';
-const moduleClass = styles['simple-item'] ?? '';
+const moduleClass = styles['tree-item-layout'] ?? '';
// Utility to mark target
@@ -41,46 +36,11 @@ const markTarget = (children: ItemNode[], targetPathOrId?: Nullable): vo
};
-const SimpleItemContent = ({ page }: { page: IPageForItem }) => {
- const { t } = useTranslation();
-
- const pageName = nodePath.basename(page.path ?? '') || '/';
-
- const shouldShowAttentionIcon = page.processData != null ? shouldRecoverPagePaths(page.processData) : false;
-
- return (
-
- {shouldShowAttentionIcon && (
- <>
- warning
-
- {t('tooltip.operation.attention.rename')}
-
- >
- )}
- {page != null && page.path != null && page._id != null && (
-
-
- {pageName}
- { page.wip && (
- WIP
- )}
-
-
- )}
-
- );
-};
-
-
-type SimpleItemProps = TreeItemProps & {
+type TreeItemLayoutProps = TreeItemProps & {
itemRef?: RefObject | RefCallback,
}
-export const SimpleItem: FC = (props) => {
+export const TreeItemLayout: FC = (props) => {
const {
itemNode, targetPathOrId, isOpen: _isOpen = false,
onRenamed, onClick, onClickDuplicateMenuItem, onClickDeleteMenuItem, isEnableActions, isReadOnlyUser, isWipPageShown = true,
@@ -91,6 +51,7 @@ export const SimpleItem: FC = (props) => {
const [currentChildren, setCurrentChildren] = useState(children);
const [isOpen, setIsOpen] = useState(_isOpen);
+ const [showAlternativeContent, setShowAlternativeContent] = useState(false);
const { data } = useSWRxPageChildren(isOpen ? page._id : null);
@@ -122,6 +83,10 @@ export const SimpleItem: FC = (props) => {
setIsOpen(!isOpen);
}, [isOpen]);
+ const onSwitchToAlternativeContent = useCallback(() => {
+ setShowAlternativeContent(!showAlternativeContent);
+ }, [showAlternativeContent]);
+
// didMount
useEffect(() => {
if (hasChildren()) setIsOpen(true);
@@ -148,7 +113,7 @@ export const SimpleItem: FC = (props) => {
}
}, [data, isOpen, targetPathOrId]);
- const ItemClassFixed = itemClass ?? SimpleItem;
+ const ItemClassFixed = itemClass ?? TreeItemLayout;
const baseProps: Omit = {
isEnableActions,
@@ -164,6 +129,7 @@ export const SimpleItem: FC = (props) => {
const toolProps: TreeItemToolProps = {
...baseProps,
itemNode,
+ onSwitchToAlternativeContent,
};
const EndComponents = props.customEndComponents;
@@ -204,7 +170,15 @@ export const SimpleItem: FC = (props) => {
)}
-
+ { showAlternativeContent && AlternativeComponents != null
+ ? (
+ AlternativeComponents.map((AlternativeContent, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))
+ )
+ :
+ }
{EndComponents?.map((EndComponent, index) => (
diff --git a/apps/app/src/components/TreeItem/index.ts b/apps/app/src/components/TreeItem/index.ts
index de874fd82c8..829049127ef 100644
--- a/apps/app/src/components/TreeItem/index.ts
+++ b/apps/app/src/components/TreeItem/index.ts
@@ -2,5 +2,5 @@ export * from './interfaces';
export * from './NewPageInput';
export * from './ItemNode';
-export * from './SimpleItem';
+export * from './TreeItemLayout';
export * from './NotDraggableForClosableTextInput';
diff --git a/apps/app/src/components/TreeItem/interfaces/index.ts b/apps/app/src/components/TreeItem/interfaces/index.ts
index 327713bf151..b42d4ca336a 100644
--- a/apps/app/src/components/TreeItem/interfaces/index.ts
+++ b/apps/app/src/components/TreeItem/interfaces/index.ts
@@ -19,7 +19,9 @@ type TreeItemBaseProps = {
},
}
-export type TreeItemToolProps = TreeItemBaseProps;
+export type TreeItemToolProps = TreeItemBaseProps & {
+ onSwitchToAlternativeContent?(): void,
+};
export type TreeItemProps = TreeItemBaseProps & {
targetPathOrId?: Nullable,
From 4e700045ea6a2561940ad61590a756d5d6f9c7a3 Mon Sep 17 00:00:00 2001
From: Yuki Takei
Date: Tue, 7 May 2024 14:37:43 +0000
Subject: [PATCH 114/180] fix skeleton color
---
.../ItemsTree/ItemsTreeContentSkeleton.module.scss | 14 ++++++++++++++
.../ItemsTree/ItemsTreeContentSkeleton.tsx | 12 +++++-------
apps/app/src/components/Skeleton.module.scss | 4 ++++
apps/app/src/components/Skeleton.tsx | 7 +++++--
4 files changed, 28 insertions(+), 9 deletions(-)
create mode 100644 apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.module.scss
create mode 100644 apps/app/src/components/Skeleton.module.scss
diff --git a/apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.module.scss b/apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.module.scss
new file mode 100644
index 00000000000..46719ef173b
--- /dev/null
+++ b/apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.module.scss
@@ -0,0 +1,14 @@
+@use '~/styles/mixins';
+
+@use './items-tree-variables';
+
+
+.text-skeleton-level1 {
+ @include mixins.grw-skeleton-text($font-size:16px, $line-height: items-tree-variables.$list-item-height);
+ padding-left: 12px;
+}
+
+.text-skeleton-level2 {
+ @extend .text-skeleton-level1;
+ padding-left: 12px + items-tree-variables.$list-item-padding-left * 2;
+}
diff --git a/apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx b/apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx
index 8de5150520e..5c8239121d5 100644
--- a/apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx
+++ b/apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx
@@ -1,16 +1,14 @@
-import React from 'react';
-
import { Skeleton } from '~/components/Skeleton';
-import styles from './ItemsTree.module.scss';
+import styles from './ItemsTreeContentSkeleton.module.scss';
const ItemsTreeContentSkeleton = (): JSX.Element => {
return (
-
-
-
-
+
-
+
);
};
From 3e12f8eeb10e92d50102400d0b0eb34ebd5aef71 Mon Sep 17 00:00:00 2001
From: Yuki Takei
Date: Tue, 7 May 2024 14:38:06 +0000
Subject: [PATCH 115/180] tidy up styles
---
.../ItemsTree/ItemsTree.module.scss | 118 ++++++------------
.../src/components/ItemsTree/ItemsTree.tsx | 2 +-
.../ItemsTree/_items-tree-variables.scss | 2 +
.../TreeItem/SimpleItemContent.module.scss | 7 ++
.../components/TreeItem/SimpleItemContent.tsx | 10 +-
.../TreeItem/TreeItemLayout.module.scss | 21 ++++
.../components/TreeItem/TreeItemLayout.tsx | 12 +-
7 files changed, 83 insertions(+), 89 deletions(-)
create mode 100644 apps/app/src/components/ItemsTree/_items-tree-variables.scss
create mode 100644 apps/app/src/components/TreeItem/SimpleItemContent.module.scss
diff --git a/apps/app/src/components/ItemsTree/ItemsTree.module.scss b/apps/app/src/components/ItemsTree/ItemsTree.module.scss
index eedb6a1dcc1..429d09d176a 100644
--- a/apps/app/src/components/ItemsTree/ItemsTree.module.scss
+++ b/apps/app/src/components/ItemsTree/ItemsTree.module.scss
@@ -1,104 +1,71 @@
-@use '~/styles/mixins' as *;
-$grw-sidebar-content-header-height: 58px;
-$grw-sidebar-content-footer-height: 50px;
-$grw-pagetree-item-padding-left: 10px;
-$grw-pagetree-item-container-height: 40px;
+@use './items-tree-variables';
-.grw-items-tree {
-
- .grw-pagetree-item-skeleton-text {
- @include grw-skeleton-text($font-size:16px, $line-height:$grw-pagetree-item-container-height);
- padding-left: 12px;
- }
-
- .grw-pagetree-item-skeleton-text-child {
- @extend .grw-pagetree-item-skeleton-text;
- padding-left: 12px + $grw-pagetree-item-padding-left;
+// fix height
+.items-tree :global {
+ li {
+ height: items-tree-variables.$list-item-height;
}
+}
- :global {
-
- .list-group-item {
- .grw-pagetree-triangle-btn {
- border: 0;
- transition: all 0.2s ease-out;
- transform: rotate(0deg);
-
- &.grw-pagetree-open {
- transform: rotate(90deg);
- }
- }
-
- .grw-pagetree-title-anchor {
- width: 100%;
- overflow: hidden;
- text-decoration: none;
- }
- }
+// fix padding-left
+.items-tree {
- .grw-pagetree-item-container {
- .grw-triangle-container {
- min-width: 35px;
- height: $grw-pagetree-item-container-height;
- }
- }
- }
&:global{
// To realize a hierarchical structure, set multiplied padding-left to each pagetree-item
- > .grw-pagetree-item-container {
+ > .tree-item-layout {
> .list-group-item {
padding-left: 0;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left;
+ padding-left: items-tree-variables.$list-item-padding-left;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 2;
+ padding-left: items-tree-variables.$list-item-padding-left * 2;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 3;
+ padding-left: items-tree-variables.$list-item-padding-left * 3;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 4;
+ padding-left: items-tree-variables.$list-item-padding-left * 4;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 5;
+ padding-left: items-tree-variables.$list-item-padding-left * 5;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 6;
+ padding-left: items-tree-variables.$list-item-padding-left * 6;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 7;
+ padding-left: items-tree-variables.$list-item-padding-left * 7;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 8;
+ padding-left: items-tree-variables.$list-item-padding-left * 8;
}
- > .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ > .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 9;
+ padding-left: items-tree-variables.$list-item-padding-left * 9;
}
- .grw-pagetree-item-children {
- > .grw-pagetree-item-container {
+ .tree-item-layout-children {
+ > .tree-item-layout {
> .list-group-item {
- padding-left: $grw-pagetree-item-padding-left * 10;
+ padding-left: items-tree-variables.$list-item-padding-left * 10;
}
}
}
@@ -123,10 +90,3 @@ $grw-pagetree-item-container-height: 40px;
}
}
}
-
-
-.grw-pagetree :global {
- .grw-pagetree-triangle-btn {
- --btn-color: var(--bs-tertiary-color);
- }
-}
diff --git a/apps/app/src/components/ItemsTree/ItemsTree.tsx b/apps/app/src/components/ItemsTree/ItemsTree.tsx
index 5b1c2164dd7..a00171d3959 100644
--- a/apps/app/src/components/ItemsTree/ItemsTree.tsx
+++ b/apps/app/src/components/ItemsTree/ItemsTree.tsx
@@ -32,7 +32,7 @@ import ItemsTreeContentSkeleton from './ItemsTreeContentSkeleton';
import styles from './ItemsTree.module.scss';
-const moduleClass = styles['grw-items-tree'] ?? '';
+const moduleClass = styles['items-tree'] ?? '';
const logger = loggerFactory('growi:cli:ItemsTree');
diff --git a/apps/app/src/components/ItemsTree/_items-tree-variables.scss b/apps/app/src/components/ItemsTree/_items-tree-variables.scss
new file mode 100644
index 00000000000..c5b5a7ff5c4
--- /dev/null
+++ b/apps/app/src/components/ItemsTree/_items-tree-variables.scss
@@ -0,0 +1,2 @@
+$list-item-height: 40px;
+$list-item-padding-left: 10px;
diff --git a/apps/app/src/components/TreeItem/SimpleItemContent.module.scss b/apps/app/src/components/TreeItem/SimpleItemContent.module.scss
new file mode 100644
index 00000000000..5b409550c42
--- /dev/null
+++ b/apps/app/src/components/TreeItem/SimpleItemContent.module.scss
@@ -0,0 +1,7 @@
+.simple-item-content :global {
+ .grw-page-title-anchor {
+ width: 100%;
+ overflow: hidden;
+ text-decoration: none;
+ }
+}
diff --git a/apps/app/src/components/TreeItem/SimpleItemContent.tsx b/apps/app/src/components/TreeItem/SimpleItemContent.tsx
index 8e8ebf464a7..0a391822833 100644
--- a/apps/app/src/components/TreeItem/SimpleItemContent.tsx
+++ b/apps/app/src/components/TreeItem/SimpleItemContent.tsx
@@ -6,6 +6,10 @@ import { UncontrolledTooltip } from 'reactstrap';
import type { IPageForItem } from '~/interfaces/page';
import { shouldRecoverPagePaths } from '~/utils/page-operation';
+import styles from './SimpleItemContent.module.scss';
+
+const moduleClass = styles['simple-item-content'] ?? '';
+
export const SimpleItemContent = ({ page }: { page: IPageForItem }): JSX.Element => {
const { t } = useTranslation();
@@ -16,7 +20,7 @@ export const SimpleItemContent = ({ page }: { page: IPageForItem }): JSX.Element
return (
{shouldShowAttentionIcon && (
@@ -28,9 +32,9 @@ export const SimpleItemContent = ({ page }: { page: IPageForItem }): JSX.Element
>
)}
{page != null && page.path != null && page._id != null && (
-
+
- {pageName}
+ {pageName}
{ page.wip && (
WIP
)}
diff --git a/apps/app/src/components/TreeItem/TreeItemLayout.module.scss b/apps/app/src/components/TreeItem/TreeItemLayout.module.scss
index 8f94af7accb..6d76ab4ac10 100644
--- a/apps/app/src/components/TreeItem/TreeItemLayout.module.scss
+++ b/apps/app/src/components/TreeItem/TreeItemLayout.module.scss
@@ -1,3 +1,5 @@
+@use '../ItemsTree/items-tree-variables';
+
// show / hide on hover
.tree-item-layout {
:global {
@@ -13,3 +15,22 @@
}
}
}
+
+// btn-triangle
+.tree-item-layout :global {
+ .btn-triangle-container {
+ min-width: 35px;
+ }
+
+ .btn-triangle {
+ --bs-btn-color: var(--bs-tertiary-color);
+
+ border: 0;
+ transition: all 0.2s ease-out;
+ transform: rotate(0deg);
+
+ &.open {
+ transform: rotate(90deg);
+ }
+ }
+}
diff --git a/apps/app/src/components/TreeItem/TreeItemLayout.tsx b/apps/app/src/components/TreeItem/TreeItemLayout.tsx
index d0d59a3a798..1795a9a0716 100644
--- a/apps/app/src/components/TreeItem/TreeItemLayout.tsx
+++ b/apps/app/src/components/TreeItem/TreeItemLayout.tsx
@@ -144,23 +144,23 @@ export const TreeItemLayout: FC = (props) => {
return (
-
-
+
{hasDescendants && (
+
+
+
); }; diff --git a/apps/app/src/components/Skeleton.module.scss b/apps/app/src/components/Skeleton.module.scss new file mode 100644 index 00000000000..81a8a73773b --- /dev/null +++ b/apps/app/src/components/Skeleton.module.scss @@ -0,0 +1,4 @@ +.grw-skeleton { + --bs-list-group-color: rgba(var(--bs-tertiary-color-rgb), 0.2); + background-color: var(--bs-list-group-color); +} diff --git a/apps/app/src/components/Skeleton.tsx b/apps/app/src/components/Skeleton.tsx index 1a8b4fed779..f2661adacc6 100644 --- a/apps/app/src/components/Skeleton.tsx +++ b/apps/app/src/components/Skeleton.tsx @@ -1,4 +1,7 @@ -import React from 'react'; +import styles from './Skeleton.module.scss'; + +const moduleClass = styles['grw-skeleton'] ?? ''; + type SkeletonProps = { additionalClass?: string, @@ -12,7 +15,7 @@ export const Skeleton = (props: SkeletonProps): JSX.Element => { return (