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

Merge branch 'master' into feat/detect-browser-language

jam411 3 лет назад
Родитель
Сommit
734079a33f
32 измененных файлов с 202 добавлено и 214 удалено
  1. 2 0
      .github/workflows/ci-app-prod.yml
  2. 1 0
      .github/workflows/ci-app.yml
  3. 10 0
      bin/data-migrations/v6/src/processor.js
  4. 1 2
      packages/app/package.json
  5. 1 0
      packages/app/public/static/locales/en_US/admin.json
  6. 1 0
      packages/app/public/static/locales/ja_JP/admin.json
  7. 1 0
      packages/app/public/static/locales/zh_CN/admin.json
  8. 0 1
      packages/app/src/client/services/AdminAppContainer.js
  9. 1 1
      packages/app/src/components/Admin/App/AwsSetting.tsx
  10. 1 1
      packages/app/src/components/Page/PageView.tsx
  11. 4 22
      packages/app/src/components/Page/RevisionLoader.tsx
  12. 1 2
      packages/app/src/components/PageTimeline.tsx
  13. 6 4
      packages/app/src/components/Sidebar/InfiniteScroll.tsx
  14. 12 11
      packages/app/src/components/Sidebar/RecentChanges.tsx
  15. 1 0
      packages/app/src/pages/[[...path]].page.tsx
  16. 8 4
      packages/app/src/server/routes/apiv3/app-settings.js
  17. 2 3
      packages/app/src/server/routes/apiv3/pages.js
  18. 12 6
      packages/app/src/services/renderer/renderer.tsx
  19. 18 12
      packages/app/src/stores/page-listing.tsx
  20. 4 14
      packages/app/src/stores/ui.tsx
  21. 4 2
      packages/app/src/styles/theme/_apply-colors-dark.scss
  22. 4 2
      packages/app/src/styles/theme/_apply-colors-light.scss
  23. 2 5
      packages/app/src/styles/theme/_apply-colors.scss
  24. 25 28
      packages/preset-themes/src/styles/antarctic.scss
  25. 10 11
      packages/preset-themes/src/styles/christmas.scss
  26. 29 29
      packages/preset-themes/src/styles/hufflepuff.scss
  27. 15 13
      packages/preset-themes/src/styles/spring.scss
  28. 10 8
      packages/preset-themes/src/styles/wood.scss
  29. 1 1
      packages/remark-lsx/package.json
  30. 2 2
      packages/remark-lsx/src/components/Lsx.tsx
  31. 9 7
      packages/remark-lsx/src/stores/lsx.tsx
  32. 4 23
      yarn.lock

+ 2 - 0
.github/workflows/ci-app-prod.yml

@@ -14,6 +14,7 @@ on:
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/preset-themes/**
       - packages/remark-*/**
       - packages/slack/**
       - packages/ui/**
@@ -31,6 +32,7 @@ on:
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/preset-themes/**
       - packages/remark-*/**
       - packages/slack/**
       - packages/ui/**

+ 1 - 0
.github/workflows/ci-app.yml

@@ -14,6 +14,7 @@ on:
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/preset-themes/**
       - packages/remark-*/**
       - packages/slack/**
       - packages/ui/**

+ 10 - 0
bin/data-migrations/v6/src/processor.js

@@ -31,6 +31,13 @@ function bracketlinkProcessor(body) {
   return body.replace(oldBracketLinkRegExp, '[[$1]]');
 }
 
+// processor for MIGRATION_TYPE=custom
+function customProcessor(body) {
+  // ADD YOUR PROCESS HERE!
+  // https://github.com/weseek/growi/discussions/7180
+  return body;
+}
+
 // ===========================================
 // define processors
 // ===========================================
@@ -56,6 +63,9 @@ function getProcessorArray(migrationType) {
     case 'v6':
       oldFormatProcessors = [drawioProcessor, plantumlProcessor, tsvProcessor, csvProcessor, bracketlinkProcessor];
       break;
+    case 'custom':
+      oldFormatProcessors = [customProcessor];
+      break;
     default:
       oldFormatProcessors = [];
   }

+ 1 - 2
packages/app/package.json

@@ -185,7 +185,7 @@
     "string-width": "=4.2.2",
     "superjson": "^1.9.1",
     "swagger-jsdoc": "^6.1.0",
-    "swr": "^2.0.2",
+    "swr": "^2.0.3",
     "throttle-debounce": "^3.0.1",
     "toastr": "^2.1.2",
     "uglifycss": "^0.0.29",
@@ -237,7 +237,6 @@
     "react-copy-to-clipboard": "^5.0.1",
     "react-dropzone": "^11.2.4",
     "react-hotkeys": "^2.0.0",
-    "react-waypoint": "^10.1.0",
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "reveal.js": "^4.3.1",

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

@@ -383,6 +383,7 @@
     "bucket_name": "Bucket name",
     "custom_endpoint": "Custom endpoint",
     "custom_endpoint_change": "Input the URL of the endpoint of an object storage service like MinIO that has a S3-compatible API.  Amazon S3 is used if empty.",
+    "s3_secret_access_key_input_description": "Setting value is hidden",
     "load_plugins": "Load plugins",
     "enable": "Enable",
     "disable": "Disable",

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

@@ -391,6 +391,7 @@
     "bucket_name": "バケット名",
     "custom_endpoint": "カスタムエンドポイント",
     "custom_endpoint_change": "MinIOなど、S3互換APIを持つ他のオブジェクトストレージサービスを使用する場合のみ、そのエンドポイントのURLを入力してください。空欄の場合は、Amazon S3を使用します。",
+    "s3_secret_access_key_input_description": "設定値は非表示です",
     "load_plugins": "プラグインを読み込む",
     "enable": "有効",
     "disable": "無効",

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

@@ -391,6 +391,7 @@
     "bucket_name": "Bucket name",
     "custom_endpoint": "Custom endpoint",
     "custom_endpoint_change": "输入对象存储服务(如MinIO)端点的URL,MinIO具有与S3兼容的API。如果为空,则使用Amazon S3。",
+    "s3_secret_access_key_input_description": "设定的值被隐藏。",
     "load_plugins": "加载插件",
     "enable": "启用",
     "disable": "停用",

+ 0 - 1
packages/app/src/client/services/AdminAppContainer.js

@@ -108,7 +108,6 @@ export default class AdminAppContainer extends Container {
       s3CustomEndpoint: appSettingsParams.s3CustomEndpoint,
       s3Bucket: appSettingsParams.s3Bucket,
       s3AccessKeyId: appSettingsParams.s3AccessKeyId,
-      s3SecretAccessKey: appSettingsParams.s3SecretAccessKey,
       s3ReferenceFileWithRelayMode: appSettingsParams.s3ReferenceFileWithRelayMode,
 
       gcsUseOnlyEnvVars: appSettingsParams.gcsUseOnlyEnvVars,

+ 1 - 1
packages/app/src/components/Admin/App/AwsSetting.tsx

@@ -140,11 +140,11 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
           <input
             className="form-control"
             type="text"
-            defaultValue={props.s3SecretAccessKey || ''}
             onChange={(e) => {
               props?.onChangeS3SecretAccessKey(e.target.value);
             }}
           />
+          <p className="form-text text-muted">{t('admin:app_setting.s3_secret_access_key_input_description')}</p>
         </div>
       </div>
 

+ 1 - 1
packages/app/src/components/Page/PageView.tsx

@@ -70,7 +70,7 @@ export const PageView = (props: Props): JSX.Element => {
   const { data: viewOptions, mutate: mutateRendererOptions } = useViewOptions();
 
   const page = pageBySWR ?? initialPage;
-  const isNotFound = isNotFoundMeta || page == null;
+  const isNotFound = isNotFoundMeta || page?.revision == null;
   const isUsersHomePagePath = isUsersHomePage(pagePath);
 
   // register to facade

+ 4 - 22
packages/app/src/components/Page/RevisionLoader.tsx

@@ -1,8 +1,7 @@
-import React, { useEffect, useState, useCallback } from 'react';
+import React, { useState, useCallback, useEffect } from 'react';
 
 import { Ref, IRevision, IRevisionHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
-import { Waypoint } from 'react-waypoint';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { RendererOptions } from '~/services/renderer/renderer';
@@ -16,7 +15,6 @@ export type RevisionLoaderProps = {
   rendererOptions: RendererOptions,
   pageId: string,
   revisionId: Ref<IRevision>,
-  lazy?: boolean,
   onRevisionLoaded?: (revision: IRevisionHasId) => void,
 }
 
@@ -34,7 +32,7 @@ export const RevisionLoader = (props: RevisionLoaderProps): JSX.Element => {
   const { t } = useTranslation();
 
   const {
-    rendererOptions, pageId, revisionId, lazy, onRevisionLoaded,
+    rendererOptions, pageId, revisionId, onRevisionLoaded,
   } = props;
 
   const [isLoading, setIsLoading] = useState<boolean>();
@@ -69,24 +67,8 @@ export const RevisionLoader = (props: RevisionLoaderProps): JSX.Element => {
   }, [isLoaded, isLoading, onRevisionLoaded, pageId, revisionId]);
 
   useEffect(() => {
-    if (!lazy) {
-      loadData();
-    }
-  }, [lazy, loadData]);
-
-  const onWaypointChange = (event) => {
-    if (event.currentPosition === Waypoint.above || event.currentPosition === Waypoint.inside) {
-      loadData();
-    }
-    return;
-  };
-
-  /* ----- before load ----- */
-  if (lazy && !isLoaded) {
-    return (
-      <Waypoint onPositionChange={onWaypointChange} bottomOffset="-100px" />
-    );
-  }
+    loadData();
+  }, [loadData]);
 
   /* ----- loading ----- */
   if (isLoading) {

+ 1 - 2
packages/app/src/components/PageTimeline.tsx

@@ -32,7 +32,6 @@ const TimelineCard = ({ page }: TimelineCardProps): JSX.Element => {
       <div className="card-body">
         { rendererOptions != null && (
           <RevisionLoader
-            lazy
             rendererOptions={rendererOptions}
             pageId={page._id}
             revisionId={page.revision}
@@ -55,7 +54,7 @@ export const PageTimeline = (): JSX.Element => {
 
   const handlePage = useCallback(async(selectedPage: number) => {
     if (currentPagePath == null) { return }
-    const res = await apiv3Get('/pages/list', { path: currentPagePath, selectedPage });
+    const res = await apiv3Get('/pages/list', { path: currentPagePath, page: selectedPage });
     setTotalPageItems(res.data.totalCount);
     setPages(res.data.pages);
     setLimit(res.data.limit);

+ 6 - 4
packages/app/src/components/Sidebar/InfiniteScroll.tsx

@@ -1,11 +1,12 @@
 import React, {
   Ref, useEffect, useState,
 } from 'react';
+
 import type { SWRInfiniteResponse } from 'swr/infinite';
 
 type Props<T> = {
   swrInifiniteResponse : SWRInfiniteResponse<T>
-  children: React.ReactChild | ((item: T) => React.ReactNode),
+  children: React.ReactNode,
   loadingIndicator?: React.ReactNode
   endingIndicator?: React.ReactNode
   isReachingEnd?: boolean,
@@ -39,7 +40,7 @@ const LoadingIndicator = (): React.ReactElement => {
 const InfiniteScroll = <E, >(props: Props<E>): React.ReactElement<Props<E>> => {
   const {
     swrInifiniteResponse: {
-      setSize, data, isValidating,
+      setSize, isValidating,
     },
     children,
     loadingIndicator,
@@ -54,11 +55,12 @@ const InfiniteScroll = <E, >(props: Props<E>): React.ReactElement<Props<E>> => {
     if (intersecting && !isValidating && !isReachingEnd) {
       setSize(size => size + 1);
     }
-  }, [setSize, intersecting]);
+  }, [setSize, intersecting, isValidating, isReachingEnd]);
 
   return (
     <>
-      {typeof children === 'function' ? data?.map(item => children(item)) : children}
+      { children }
+
       <div style={{ position: 'relative' }}>
         <div ref={ref} style={{ position: 'absolute', top: offset }}></div>
         {isReachingEnd

+ 12 - 11
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -10,7 +10,7 @@ import Link from 'next/link';
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
 import { IPageHasId } from '~/interfaces/page';
 import LinkedPagePath from '~/models/linked-page-path';
-import { useSWRInifinitexRecentlyUpdated } from '~/stores/page-listing';
+import { useSWRINFxRecentlyUpdated } from '~/stores/page-listing';
 import loggerFactory from '~/utils/logger';
 
 import FormattedDistanceDate from '../FormattedDistanceDate';
@@ -19,7 +19,6 @@ import InfiniteScroll from './InfiniteScroll';
 import { SidebarHeaderReloadButton } from './SidebarHeaderReloadButton';
 import RecentChangesContentSkeleton from './Skeleton/RecentChangesContentSkeleton';
 
-import TagLabelsStyles from '../Page/TagLabels.module.scss';
 import styles from './RecentChanges.module.scss';
 
 
@@ -104,13 +103,14 @@ const RecentChanges = (): JSX.Element => {
 
   const PER_PAGE = 20;
   const { t } = useTranslation();
-  const swrInifinitexRecentlyUpdated = useSWRInifinitexRecentlyUpdated();
-  const { data: dataRecentlyUpdated, error, mutate: mutateRecentlyUpdated } = swrInifinitexRecentlyUpdated;
+  const swrInifinitexRecentlyUpdated = useSWRINFxRecentlyUpdated(PER_PAGE);
+  const {
+    data, mutate, isLoading,
+  } = swrInifinitexRecentlyUpdated;
 
   const [isRecentChangesSidebarSmall, setIsRecentChangesSidebarSmall] = useState(false);
-  const isEmpty = dataRecentlyUpdated?.[0].length === 0;
-  const isLoading = error == null && dataRecentlyUpdated === undefined;
-  const isReachingEnd = isEmpty || (dataRecentlyUpdated && dataRecentlyUpdated[dataRecentlyUpdated.length - 1]?.length < PER_PAGE);
+  const isEmpty = data?.[0]?.pages.length === 0;
+  const isReachingEnd = isEmpty || (data != null && data[data.length - 1]?.pages.length < PER_PAGE);
 
   const retrieveSizePreferenceFromLocalStorage = useCallback(() => {
     if (window.localStorage.isRecentChangesSidebarSmall === 'true') {
@@ -132,7 +132,7 @@ const RecentChanges = (): JSX.Element => {
     <div className="px-3" data-testid="grw-recent-changes">
       <div className="grw-sidebar-content-header py-3 d-flex">
         <h3 className="mb-0 text-nowrap">{t('Recent Changes')}</h3>
-        <SidebarHeaderReloadButton onClick={() => mutateRecentlyUpdated()}/>
+        <SidebarHeaderReloadButton onClick={() => mutate()}/>
         <div className="d-flex align-items-center">
           <div className={`grw-recent-changes-resize-button ${styles['grw-recent-changes-resize-button']} custom-control custom-switch ml-1`}>
             <input
@@ -155,9 +155,10 @@ const RecentChanges = (): JSX.Element => {
                 swrInifiniteResponse={swrInifinitexRecentlyUpdated}
                 isReachingEnd={isReachingEnd}
               >
-                {pages => pages.map(
-                  page => <PageItem key={page._id} page={page} isSmall={isRecentChangesSidebarSmall} />,
-                )
+                { data != null && data.map(apiResult => apiResult.pages).flat()
+                  .map(page => (
+                    <PageItem key={page._id} page={page} isSmall={isRecentChangesSidebarSmall} />
+                  ))
                 }
               </InfiniteScroll>
             </ul>

+ 1 - 0
packages/app/src/pages/[[...path]].page.tsx

@@ -496,6 +496,7 @@ async function injectRoutingInformation(context: GetServerSidePropsContext, prop
   else {
     props.isNotFound = page.isEmpty;
     props.isNotCreatable = false;
+    props.isForbidden = false;
     // /62a88db47fed8b2d94f30000 ==> /path/to/page
     if (isPermalink && page.isEmpty) {
       props.currentPathname = page.path;

+ 8 - 4
packages/app/src/server/routes/apiv3/app-settings.js

@@ -1,4 +1,3 @@
-import { ErrorV3 } from '@growi/core';
 import { body } from 'express-validator';
 
 import { i18n } from '^/config/next-i18next.config';
@@ -9,6 +8,8 @@ import loggerFactory from '~/utils/logger';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
+import { ErrorV3 } from '@growi/core';
+
 
 const logger = loggerFactory('growi:routes:apiv3:app-settings');
 
@@ -252,7 +253,6 @@ module.exports = (crowi) => {
       s3CustomEndpoint: crowi.configManager.getConfig('crowi', 'aws:s3CustomEndpoint'),
       s3Bucket: crowi.configManager.getConfig('crowi', 'aws:s3Bucket'),
       s3AccessKeyId: crowi.configManager.getConfig('crowi', 'aws:s3AccessKeyId'),
-      s3SecretAccessKey: crowi.configManager.getConfig('crowi', 'aws:s3SecretAccessKey'),
       s3ReferenceFileWithRelayMode: crowi.configManager.getConfig('crowi', 'aws:referenceFileWithRelayMode'),
 
       gcsUseOnlyEnvVars: crowi.configManager.getConfig('crowi', 'gcs:useOnlyEnvVarsForSomeOptions'),
@@ -630,12 +630,17 @@ module.exports = (crowi) => {
       requestParams['aws:s3CustomEndpoint'] = req.body.s3CustomEndpoint;
       requestParams['aws:s3Bucket'] = req.body.s3Bucket;
       requestParams['aws:s3AccessKeyId'] = req.body.s3AccessKeyId;
-      requestParams['aws:s3SecretAccessKey'] = req.body.s3SecretAccessKey;
       requestParams['aws:referenceFileWithRelayMode'] = req.body.s3ReferenceFileWithRelayMode;
     }
 
     try {
       await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true);
+
+      const s3SecretAccessKey = req.body.s3SecretAccessKey;
+      if (fileUploadType === 'aws' && s3SecretAccessKey != null && s3SecretAccessKey.trim() !== '') {
+        await crowi.configManager.updateConfigsInTheSameNamespace('crowi', { 'aws:s3SecretAccessKey': s3SecretAccessKey }, true);
+      }
+
       await crowi.setUpFileUpload(true);
       crowi.fileUploaderSwitchService.publishUpdatedMessage();
 
@@ -655,7 +660,6 @@ module.exports = (crowi) => {
         responseParams.s3CustomEndpoint = crowi.configManager.getConfig('crowi', 'aws:s3CustomEndpoint');
         responseParams.s3Bucket = crowi.configManager.getConfig('crowi', 'aws:s3Bucket');
         responseParams.s3AccessKeyId = crowi.configManager.getConfig('crowi', 'aws:s3AccessKeyId');
-        responseParams.s3SecretAccessKey = crowi.configManager.getConfig('crowi', 'aws:s3SecretAccessKey');
         responseParams.s3ReferenceFileWithRelayMode = crowi.configManager.getConfig('crowi', 'aws:referenceFileWithRelayMode');
       }
       const parameters = { action: SupportedAction.ACTION_ADMIN_FILE_UPLOAD_CONFIG_UPDATE };

+ 2 - 3
packages/app/src/server/routes/apiv3/pages.js

@@ -379,11 +379,10 @@ module.exports = (crowi) => {
    *
    */
   router.get('/recent', accessTokenParser, loginRequired, async(req, res) => {
-    const limit = 20;
+    const limit = parseInt(req.query.limit) || 20;
     const offset = parseInt(req.query.offset) || 0;
-    const skip = offset > 0 ? (offset - 1) * limit : offset;
     const queryOptions = {
-      offset: skip,
+      offset,
       limit,
       includeTrashed: false,
       isRegExpEscapedFromPath: true,

+ 12 - 6
packages/app/src/services/renderer/renderer.tsx

@@ -12,7 +12,7 @@ import type { NormalComponents } from 'react-markdown/lib/complex-types';
 import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 import katex from 'rehype-katex';
 import raw from 'rehype-raw';
-import sanitize, { defaultSchema as sanitizeDefaultSchema } from 'rehype-sanitize';
+import sanitize, { defaultSchema as rehypeSanitizeDefaultSchema } from 'rehype-sanitize';
 import slug from 'rehype-slug';
 import type { HtmlElementNode } from 'rehype-toc';
 import breaks from 'remark-breaks';
@@ -67,13 +67,19 @@ export type RendererOptions = Omit<ReactMarkdownOptions, 'remarkPlugins' | 'rehy
     | undefined
 };
 
-const commonSanitizeAttributes = { '*': ['class', 'className', 'style'] };
+const baseSanitizeSchema = {
+  tagNames: ['iframe'],
+  attributes: {
+    iframe: ['allow', 'referrerpolicy', 'sandbox', 'src', 'srcdoc'],
+    '*': ['class', 'className', 'style'],
+  },
+};
 
 const commonSanitizeOption: SanitizeOption = deepmerge(
-  sanitizeDefaultSchema,
+  rehypeSanitizeDefaultSchema,
+  baseSanitizeSchema,
   {
     clobberPrefix: 'mdcont-',
-    attributes: commonSanitizeAttributes,
   },
 );
 
@@ -81,8 +87,8 @@ let isInjectedCustomSanitaizeOption = false;
 
 const injectCustomSanitizeOption = (config: RendererConfig) => {
   if (!isInjectedCustomSanitaizeOption && config.isEnabledXssPrevention && config.xssOption === RehypeSanitizeOption.CUSTOM) {
-    commonSanitizeOption.tagNames = config.tagWhiteList;
-    commonSanitizeOption.attributes = deepmerge(commonSanitizeAttributes, config.attrWhiteList ?? {});
+    commonSanitizeOption.tagNames = deepmerge(baseSanitizeSchema.tagNames, config.tagWhiteList ?? []);
+    commonSanitizeOption.attributes = deepmerge(baseSanitizeSchema.attributes, config.attrWhiteList ?? {});
     isInjectedCustomSanitaizeOption = true;
   }
 };

+ 18 - 12
packages/app/src/stores/page-listing.tsx

@@ -24,19 +24,25 @@ export const useSWRxPagesByPath = (path?: Nullable<string>): SWRResponse<IPageHa
   );
 };
 
-export const useSWRxRecentlyUpdated = (): SWRResponse<(IPageHasId)[], Error> => {
-  return useSWR(
-    '/pages/recent',
-    endpoint => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
-  );
-};
-export const useSWRInifinitexRecentlyUpdated = () : SWRInfiniteResponse<(IPageHasId)[], Error> => {
-  const getKey = (page: number): string => {
-    return `/pages/recent?offset=${page + 1}`;
-  };
+
+type RecentApiResult = {
+  pages: IPageHasId[],
+  totalCount: number,
+  offset: number,
+}
+export const useSWRINFxRecentlyUpdated = (limit: number) : SWRInfiniteResponse<RecentApiResult, Error> => {
   return useSWRInfinite(
-    getKey,
-    endpoint => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
+    (pageIndex, previousPageData) => {
+      if (previousPageData != null && previousPageData.pages.length === 0) return null;
+
+      if (pageIndex === 0 || previousPageData == null) {
+        return ['/pages/recent', undefined, limit];
+      }
+
+      const offset = previousPageData.offset + limit;
+      return ['/pages/recent', offset, limit];
+    },
+    ([endpoint, offset, limit]) => apiv3Get<RecentApiResult>(endpoint, { offset, limit }).then(response => response.data),
     {
       revalidateFirstPage: false,
       revalidateAll: false,

+ 4 - 14
packages/app/src/stores/ui.tsx

@@ -79,16 +79,11 @@ export const useIsMobile = (): SWRResponse<boolean, Error> => {
   return useStaticSWR<boolean, Error>(key, undefined, configuration);
 };
 
-// TODO: Enable `editing-sidebar` class
-// https://redmine.weseek.co.jp/issues/111527
-const getClassNamesByEditorMode = (editorMode: EditorMode | undefined /* , isSidebar = false */): string[] => {
+const getClassNamesByEditorMode = (editorMode: EditorMode | undefined): string[] => {
   const classNames: string[] = [];
   switch (editorMode) {
     case EditorMode.Editor:
       classNames.push('editing', 'builtin-editor');
-      // if (isSidebar) {
-      //   classNames.push('editing-sidebar');
-      // }
       break;
     case EditorMode.HackMD:
       classNames.push('editing', 'hackmd');
@@ -140,10 +135,8 @@ export const determineEditorModeByHash = (): EditorMode => {
   }
 };
 
-// TODO: Enable `editing-sidebar` class somehow
-// https://redmine.weseek.co.jp/issues/111527
 type EditorModeUtils = {
-  getClassNamesByEditorMode: (/* isEditingSidebar: boolean */) => string[],
+  getClassNamesByEditorMode: () => string[],
 }
 
 export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMode> => {
@@ -171,11 +164,8 @@ export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMod
     return mutateOriginal(editorMode, shouldRevalidate);
   }, [isEditable, mutateOriginal]);
 
-  // TODO: Enable `editing-sidebar` class
-  // https://redmine.weseek.co.jp/issues/111527
-  // construct getClassNamesByEditorMode method
-  const getClassNames = useCallback((/* isEditingSidebar: boolean */) => {
-    return getClassNamesByEditorMode(swrResponse.data /* , isEditingSidebar */);
+  const getClassNames = useCallback(() => {
+    return getClassNamesByEditorMode(swrResponse.data);
   }, [swrResponse.data]);
 
   return Object.assign(swrResponse, {

+ 4 - 2
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -519,8 +519,10 @@
     }
   }
 
-  .page-comments-row {
-    background: var(--bgcolor-subnav);
+  .page-comment-form .comment-form-main {
+    &:before {
+      border-right-color: var(--bgcolor-global);
+    }
   }
 
   /*

+ 4 - 2
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -400,8 +400,10 @@
     }
   }
 
-  .page-comments-row {
-    background: var(--bgcolor-subnav);
+  .page-comment-form .comment-form-main {
+    &:before {
+      border-right-color: var(--bgcolor-global);
+    }
   }
 
   /*

+ 2 - 5
packages/app/src/styles/theme/_apply-colors.scss

@@ -567,15 +567,12 @@ ul.pagination {
 /*
  * GROWI comment form
  */
-.page-comments {
+.page-comments-row {
+  background: var(--bgcolor-subnav);
   .page-comment .page-comment-main,
   .page-comment-form .comment-form-main {
     background-color: var(--bgcolor-global);
 
-    &:before {
-      border-right-color: var(--bgcolor-global);
-    }
-
     .nav.nav-tabs {
       > li > a.active {
         background: transparent;

+ 25 - 28
packages/preset-themes/src/styles/antarctic.scss

@@ -3,28 +3,6 @@
 @use './theme/mixins/page-editor-mode-manager';
 @use './theme/hsl-functions' as hsl;
 
-.growi:not(.login-page) {
-  // add background-image
-  .page-editor-preview-container {
-    background-image: url('/images/themes/antarctic/bg.svg');
-    background-attachment: fixed;
-    background-position: center center;
-    background-size: cover;
-  }
-}
-
-.nologin {
-  background: unset !important;
-  background-image: url('/images/themes/antarctic/topimage.svg');
-  background-attachment: fixed;
-  background-position: center center;
-  background-size: cover;
-}
-
-.grw-navbar {
-  border-bottom: #ffd700 4px solid;
-}
-
 //== Light Mode
 //
 :root[data-theme='light'] {
@@ -159,14 +137,33 @@
     }
   }
 
-  // login and register
-  .nologin {
-    .nologin-dialog a.link-switch {
-      color: rgba(black, 0.5);
+  .growi:not(.login-page) {
+    // add background-image
+    .page-editor-preview-container {
+      background-image: url('/images/themes/antarctic/bg.svg');
+      background-attachment: fixed;
+      background-position: center center;
+      background-size: cover;
     }
+  }
 
-    .grw-external-auth-form {
-      border-color: #aaa !important;
+  .nologin {
+    .page-wrapper {
+      background-image: url('../images/antarctic/topimage.svg');
+      background-attachment: fixed;
+      background-position: center center;
+      background-size: cover;
+
+      .nologin-dialog .link-switch {
+        color: rgba(black, 0.5);
+      }
+      .grw-external-auth-form {
+        border-color: #aaa;
+      }
     }
   }
+
+  .grw-navbar {
+    border-bottom: #ffd700 4px solid;
+  }
 }

+ 10 - 11
packages/preset-themes/src/styles/christmas.scss

@@ -144,8 +144,6 @@
 
   // login page
   .nologin {
-    background: unset !important;
-
     .input-group {
       .input-group-text {
         color: $gray-700;
@@ -157,16 +155,17 @@
       }
     }
 
-    .nologin-header,
-    .nologin-dialog {
-      background-color: rgba(#ccc, 0.5);
-      a.link-switch {
-        color: #bd3425;
+    .page-wrapper{
+      .nologin-header,
+      .nologin-dialog {
+        background-color: rgba(#ccc, 0.5);
+        a.link-switch {
+          color: #bd3425;
+        }
+      }
+      .grw-external-auth-form {
+        border-color: #aaa;
       }
-    }
-
-    .grw-external-auth-form {
-      border-color: #aaa !important;
     }
   }
 

+ 29 - 29
packages/preset-themes/src/styles/hufflepuff.scss

@@ -145,22 +145,22 @@
 
   // login and register
   .nologin {
-    background: unset !important;
-    background-image: url('../images/hufflepuff/badger-light.png');
-    background-attachment: fixed;
-    background-position: bottom;
-    background-size: cover;
+    .page-wrapper{
+      background-image: url('../images/hufflepuff/badger-light.png');
+      background-attachment: fixed;
+      background-position: bottom;
+      background-size: cover;
 
-    .nologin-header,
-    .nologin-dialog {
-      background-color: rgba(black, 0.1);
-      a.link-switch  {
-        color: #{hsl.darken(var(--color-global),10%)};
+      .nologin-header,
+      .nologin-dialog {
+        background-color: rgba(black, 0.1);
+        a.link-switch  {
+          color: #{hsl.darken(var(--color-global),10%)};
+        }
+      }
+      .grw-external-auth-form {
+        border-color: #993439;
       }
-    }
-
-    .grw-external-auth-form {
-      border-color: #993439 !important;
     }
   }
 
@@ -340,23 +340,23 @@
 
   // login and register
   .nologin {
-    background: unset !important;
-    background-image: url('../images/hufflepuff/badger-light.png');
-    background-attachment: fixed;
-    background-position: bottom;
-    background-size: cover;
-
-    .nologin-header,
-    .nologin-dialog {
-      background-color: rgba(black, 0.1);
-    }
+    .page-wrapper{
+      background-image: url('../images/hufflepuff/badger-light.png');
+      background-attachment: fixed;
+      background-position: bottom;
+      background-size: cover;
+      .nologin-header,
+      .nologin-dialog {
+        background-color: rgba(black, 0.1);
+      }
 
-    .link-switch {
-      color: var(--color-global)!important;
-    }
+      .link-switch {
+        color: var(--color-global);
+      }
 
-    .grw-external-auth-form {
-      border-color: var(--accentcolor) !important;
+      .grw-external-auth-form {
+        border-color: var(--accentcolor);
+      }
     }
   }
 }

+ 15 - 13
packages/preset-themes/src/styles/spring.scss

@@ -136,22 +136,24 @@
 
   // login and register
   .nologin {
-    background: unset !important;
-    background-image: url('../images/spring/spring.svg');
-    background-attachment: fixed;
-    background-position: bottom;
-    background-size: cover;
+    .page-wrapper{
+      background-color: #fff0f5;
+      background-image: url('../images/spring/spring.svg');
+      background-attachment: fixed;
+      background-position: bottom;
+      background-size: cover;
 
-    .nologin-header,
-    .nologin-dialog {
-      background-color: rgba(black, 0.1);
-      a.link-switch {
-        color: var(--color-global);
+      .nologin-header,
+      .nologin-dialog {
+        background-color: rgba(black, 0.1);
+        a.link-switch {
+          color: var(--color-global);
+        }
       }
-    }
 
-    .grw-external-auth-form {
-      border-color: var(--secondary) !important;
+      .grw-external-auth-form {
+        border-color: var(--secondary);
+      }
     }
   }
 

+ 10 - 8
packages/preset-themes/src/styles/wood.scss

@@ -172,16 +172,18 @@
   .nologin {
     background: unset !important;
 
-    .nologin-header,
-    .nologin-dialog {
-      background-color: rgba(black, 0.1);
-      a.link-switch {
-        color: rgba(black, 0.5);
+    .page-wrapper{
+      .nologin-header,
+      .nologin-dialog {
+        background-color: rgba(black, 0.1);
+        a.link-switch {
+          color: rgba(black, 0.5);
+        }
       }
-    }
 
-    .grw-external-auth-form {
-      border-color: #aaa !important;
+      .grw-external-auth-form {
+        border-color: #aaa;
+      }
     }
   }
 

+ 1 - 1
packages/remark-lsx/package.json

@@ -23,7 +23,7 @@
     "@growi/core": "^6.0.6-RC.0",
     "@growi/remark-growi-directive": "^6.0.6-RC.0",
     "@growi/ui": "^6.0.6-RC.0",
-    "swr": "^1.3.0"
+    "swr": "^2.0.3"
   },
   "devDependencies": {
     "eslint-plugin-regex": "^1.8.0",

+ 2 - 2
packages/remark-lsx/src/components/Lsx.tsx

@@ -37,9 +37,9 @@ const LsxSubstance = React.memo(({
     return new LsxContext(prefix, options);
   }, [depth, filter, num, prefix, reverse, sort, except]);
 
-  const { data, error } = useSWRxNodeTree(lsxContext, isImmutable);
+  const { data, error, isLoading: _isLoading } = useSWRxNodeTree(lsxContext, isImmutable);
 
-  const isLoading = data === undefined;
+  const isLoading = _isLoading || data === undefined;
   const hasError = error != null;
   const errorMessage = error?.message;
 

+ 9 - 7
packages/remark-lsx/src/stores/lsx.tsx

@@ -100,7 +100,7 @@ const useSWRxLsxResponse = (
 ): SWRResponse<LsxResponse, Error> => {
   return useSWR(
     ['/_api/lsx', pagePath, options, isImmutable],
-    (endpoint, pagePath, options) => {
+    ([endpoint, pagePath, options]) => {
       return axios.get(endpoint, {
         params: {
           pagePath,
@@ -109,6 +109,7 @@ const useSWRxLsxResponse = (
       }).then(result => result.data as LsxResponse);
     },
     {
+      keepPreviousData: true,
       revalidateIfStale: !isImmutable,
       revalidateOnFocus: !isImmutable,
       revalidateOnReconnect: !isImmutable,
@@ -122,22 +123,23 @@ type LsxNodeTree = {
 }
 
 export const useSWRxNodeTree = (lsxContext: LsxContext, isImmutable?: boolean): SWRResponse<LsxNodeTree, Error> => {
-  const { data, error } = useSWRxLsxResponse(lsxContext.pagePath, lsxContext.options, isImmutable);
-
-  const isLoading = data === undefined && error == null;
+  const {
+    data, error, isLoading, isValidating,
+  } = useSWRxLsxResponse(lsxContext.pagePath, lsxContext.options, isImmutable);
 
   return useSWR(
-    !isLoading ? ['lsxNodeTree', lsxContext.pagePath, lsxContext.options, isImmutable, data, error] : null,
-    (key, pagePath, options, isImmutable, data) => {
+    !isLoading && !isValidating ? ['lsxNodeTree', lsxContext.pagePath, lsxContext.options, isImmutable, data, error] : null,
+    ([, pagePath, , , data]) => {
       if (data === undefined || error != null) {
         throw error;
       }
       return {
-        nodeTree: generatePageNodeTree(pagePath, data.pages),
+        nodeTree: generatePageNodeTree(pagePath, data?.pages),
         toppageViewersCount: data.toppageViewersCount,
       };
     },
     {
+      keepPreviousData: true,
       revalidateIfStale: !isImmutable,
       revalidateOnFocus: !isImmutable,
       revalidateOnReconnect: !isImmutable,

+ 4 - 23
yarn.lock

@@ -7121,10 +7121,6 @@ consolidate@^0.16.0:
   dependencies:
     bluebird "^3.7.2"
 
-"consolidated-events@^1.1.0 || ^2.0.0":
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-2.0.2.tgz#da8d8f8c2b232831413d9e190dc11669c79f4a91"
-
 constant-case@^3.0.3, constant-case@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1"
@@ -18189,16 +18185,6 @@ react-use-ripple@^1.5.2:
   resolved "https://registry.yarnpkg.com/react-use-ripple/-/react-use-ripple-1.5.2.tgz#f42600a0c7729510c3dbba74e0c86ed6c55fd88e"
   integrity sha512-pK7PLEaEGJ4xCM5acxW+ua7ba0lqxbhNzBHzEw+MoD0yVFT3r8SkfkG6aSpiEm4iLZO9HOeSnUz+1k7YVuYX5w==
 
-react-waypoint@^10.1.0:
-  version "10.1.0"
-  resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-10.1.0.tgz#6ab522a61bd52946260e4a78b3182759a97b40ec"
-  integrity sha512-wiVF0lTslVm27xHbnvUUADUrcDjrQxAp9lEYGExvcoEBScYbXu3Kt++pLrfj6CqOeeRAL4HcX8aANVLSn6bK0Q==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    consolidated-events "^1.1.0 || ^2.0.0"
-    prop-types "^15.0.0"
-    react-is "^17.0.1"
-
 react@^18.2.0:
   version "18.2.0"
   resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -21746,15 +21732,10 @@ swagger2openapi@^5.3.1:
     yaml "^1.3.1"
     yargs "^12.0.5"
 
-swr@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8"
-  integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==
-
-swr@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/swr/-/swr-2.0.2.tgz#fd34f3aac354f6b70f9134eb4218c747cc899a8d"
-  integrity sha512-iHbQW17hsduonMEliZnr6/yaxb+yvLe2r0+AH+ZfeqKzwc2bb+QRYpZm5/b/H0Lxgy7VWow4o71JeSazSun+9A==
+swr@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/swr/-/swr-2.0.3.tgz#9fe59a17f55b0fdddccd76b7b2f723f9f8e2263e"
+  integrity sha512-sGvQDok/AHEWTPfhUWXEHBVEXmgGnuahyhmRQbjl9XBYxT/MSlAzvXEKQpyM++bMPaI52vcWS2HiKNaW7+9OFw==
   dependencies:
     use-sync-external-store "^1.2.0"