Przeglądaj źródła

Merge branch 'master' into fix/114630-error-when-clicking-on-a-page-you-are-not-authorized-to-view-on-the-search-results-page

Shun Miyazawa 3 lat temu
rodzic
commit
c913a755a9

+ 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/**

+ 0 - 1
packages/app/package.json

@@ -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",

+ 4 - 23
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 { RendererOptions } from '~/services/renderer/renderer';
 import { useSWRMUTxPageRevision } from '~/stores/page';
@@ -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 {
@@ -69,25 +67,8 @@ export const RevisionLoader = (props: RevisionLoaderProps): JSX.Element => {
   }, [mutatePageRevision, onRevisionLoaded, t]);
 
   useEffect(() => {
-    if (!lazy) {
-      loadData();
-    }
-  }, [lazy, loadData]);
-
-  const onWaypointChange = (event) => {
-    if (event.currentPosition === Waypoint.above || event.currentPosition === Waypoint.inside) {
-      loadData();
-    }
-    return;
-  };
-
-  /* ----- before load ----- */
-  const isLoaded = pageRevisionData != null && !isMutating && error == null;
-  if (lazy && !isLoaded) {
-    return (
-      <Waypoint onPositionChange={onWaypointChange} bottomOffset="-100px" />
-    );
-  }
+    loadData();
+  }, [loadData]);
 
   /* ----- loading ----- */
   if (isMutating) {

+ 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>

+ 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 - 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;
+      }
     }
   }
 

+ 0 - 14
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"