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