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

Merge branch 'master' into imprv/119788-mail-setting-and-id-password-login-specification

ryoji-s 3 лет назад
Родитель
Сommit
898725bb4e

+ 3 - 2
apps/app/docker/Dockerfile

@@ -90,13 +90,14 @@ RUN tar -cf packages.tar \
   package.json \
   apps/app/.next \
   apps/app/config \
+  apps/app/dist \
   apps/app/public \
   apps/app/resource \
   apps/app/tmp \
   apps/app/.env.production* \
   apps/app/next.config.js \
-  **/package.json \
-  **/dist
+  packages/*/package.json \
+  packages/*/dist
 
 
 

+ 2 - 0
apps/app/docker/Dockerfile.dockerignore

@@ -5,3 +5,5 @@
 **/*.dockerignore
 **/.next
 **/.turbo
+out
+apps/slackbot-proxy

+ 5 - 1
apps/app/src/components/Fab.tsx

@@ -68,7 +68,11 @@ export const Fab = (): JSX.Element => {
 
   const PageCreateButton = useCallback(() => {
     return (
-      <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: '2.3rem', right: '4rem' }}>
+      <div
+        className={`rounded-circle position-absolute ${animateClasses}`}
+        style={{ bottom: '2.3rem', right: '4rem' }}
+        data-testid="grw-fab-page-create-button"
+      >
         <button
           type="button"
           className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 ${buttonClasses}`}

+ 1 - 1
apps/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx

@@ -83,7 +83,7 @@ export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProp
   }
 
   return (
-    <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`}>
+    <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`} data-testid="grw-subnav-switcher" >
       <div
         id="grw-subnav-fixed-container"
         className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}

+ 2 - 2
apps/app/src/server/routes/attachment.js

@@ -245,7 +245,7 @@ module.exports = function(crowi, app) {
       'Last-Modified': attachment.createdAt.toUTCString(),
     });
 
-    if (!attachment.fileSize) {
+    if (attachment.fileSize) {
       res.set({
         'Content-Length': attachment.fileSize,
       });
@@ -261,7 +261,7 @@ module.exports = function(crowi, app) {
     else {
       res.set({
         'Content-Type': attachment.fileFormat,
-        'Content-Security-Policy': "script-src 'unsafe-hashes'; object-src 'none'; require-trusted-types-for 'script'; default-src 'none';",
+        'Content-Security-Policy': "script-src 'unsafe-hashes'; object-src 'none'; require-trusted-types-for 'script'; media-src 'self'; default-src 'none';",
       });
     }
   }

+ 2 - 1
apps/app/src/services/renderer/renderer.tsx

@@ -35,9 +35,10 @@ const logger = loggerFactory('growi:services:renderer');
 type SanitizePlugin = PluginTuple<[SanitizeOption]>;
 
 const baseSanitizeSchema = {
-  tagNames: ['iframe', 'section'],
+  tagNames: ['iframe', 'section', 'video'],
   attributes: {
     iframe: ['allow', 'referrerpolicy', 'sandbox', 'src', 'srcdoc'],
+    video: ['controls', 'src', 'muted', 'preload', 'width', 'height', 'autoplay'],
     // The special value 'data*' as a property name can be used to allow all data properties.
     // see: https://github.com/syntax-tree/hast-util-sanitize/
     '*': ['key', 'class', 'className', 'style', 'data*'],

+ 137 - 0
apps/app/test/cypress/integration/20-basic-features/20-basic-features--sticky-features.spec.ts

@@ -0,0 +1,137 @@
+context('Access to any page', () => {
+  const ssPrefix = 'subnav-and-fab-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+
+    cy.visit('/');
+
+    cy.waitUntilSkeletonDisappear();
+    cy.collapseSidebar(true, true);
+  });
+
+  it('Subnavigation and fab displays changes on scroll down and up', () => {
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window 250px down is enough to trigger sticky effect
+       cy.scrollTo(0, 250);
+      // wait until
+      return cy.getByTestid('grw-subnav-switcher').then($elem => !$elem.hasClass('grw-subnav-switcher-hidden'));
+    });
+    // wait until fab visible
+    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('visible')));
+
+    cy.waitUntilSkeletonDisappear();
+    cy.screenshot(`${ssPrefix}visible-on-scroll-down`);
+
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window back to top
+      cy.scrollTo(0, 0);
+      // wait until
+      return cy.waitUntil(() => cy.getByTestid('grw-subnav-switcher').then($elem => $elem.hasClass('grw-subnav-switcher-hidden')));
+    });
+    // wait until fab invisible
+    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('invisible')));
+
+    cy.screenshot(`${ssPrefix}invisible-on-scroll-top`);
+  });
+
+  it('Subnavigation and fab are not displayed when move to other pages', () => {
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window 250px down is enough to trigger sticky effect
+      cy.scrollTo(0, 250);
+      // wait until
+      return () => cy.getByTestid('grw-subnav-switcher').then($elem => !$elem.hasClass('grw-subnav-switcher-hidden'));
+    });
+    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('visible')));
+
+    // Move to /Sandbox page
+    cy.visit('/Sandbox');
+
+    cy.waitUntilSkeletonDisappear();
+    cy.collapseSidebar(true);
+
+    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('invisible')));
+    cy.waitUntil(() => cy.getByTestid('grw-subnav-switcher').then($elem => $elem.hasClass('grw-subnav-switcher-hidden')));
+    cy.screenshot(`${ssPrefix}not-visible-on-move-to-other-pages`);
+  });
+
+  it('Able to open create page modal from fab', () => {
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window back to top
+      cy.scrollTo(0, 250);
+      // wait until
+      return cy.getByTestid('grw-fab-page-create-button')
+      .should('have.class', 'visible')
+      .within(() => {
+        cy.get('.btn-create-page').click();
+        return true;
+      });
+    });
+
+    cy.getByTestid('page-create-modal').should('be.visible').within(() => {
+      cy.screenshot(`${ssPrefix}new-page-modal-opened-from-fab`);
+      cy.get('button.close').click();
+    });
+  });
+
+  it('Able to scroll page to top from fab', () => {
+    // Initial scroll down
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window 250px down is enough to trigger sticky effect
+      cy.scrollTo(0, 250);
+      // wait until
+      return cy.getByTestid('grw-fab-return-to-top')
+        .should('have.class', 'visible')
+        .then(() => {
+          cy.waitUntil(() => {
+            cy.get('.btn-scroll-to-top').click();
+            return cy.getByTestid('grw-fab-return-to-top').should('have.class', 'invisible');
+          });
+        });
+    });
+    cy.waitUntilSkeletonDisappear();
+    cy.screenshot(`${ssPrefix}scroll-page-to-top`);
+  });
+
+  it('Able to click buttons on subnavigation switcher when sticky', () => {
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window 250px down is enough to trigger sticky effect
+      cy.scrollTo(0, 250);
+      // wait until
+      return cy.getByTestid('grw-subnav-switcher').then($elem => !$elem.hasClass('grw-subnav-switcher-hidden'));
+    });
+    cy.waitUntil(() => {
+      cy.getByTestid('grw-subnav-switcher').within(() => {
+        cy.getByTestid('editor-button').should('be.visible').click();
+      });
+      return cy.get('.layout-root').then($elem => $elem.hasClass('editing'));
+    });
+    cy.get('.grw-editor-navbar-bottom').should('be.visible');
+    cy.screenshot(`${ssPrefix}open-editor-when-sticky`);
+  });
+
+  it('Subnavigation is sticky when on small window', () => {
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window 500px down
+      cy.scrollTo(0, 500);
+      // wait until
+      return cy.getByTestid('grw-subnav-switcher').then($elem => !$elem.hasClass('grw-subnav-switcher-hidden'));
+    });
+    cy.waitUntilSkeletonDisappear();
+    cy.viewport(600, 1024);
+    cy.getByTestid('grw-subnav-switcher').within(() => {
+      cy.get('#grw-page-editor-mode-manager').should('be.visible');
+    })
+    cy.screenshot(`${ssPrefix}sticky-on-small-window`);
+  });
+});

+ 58 - 0
apps/app/test/cypress/integration/21-basic-features-for-guest/21-basic-features-for-guest--sticky-for-guest.spec.ts

@@ -0,0 +1,58 @@
+context('Access sticky sub navigation switcher and Fab for guest', () => {
+  const ssPrefix = 'access-sticky-by-guest-';
+  it('Sub navigation sticky changes when scrolling down and up', () => {
+    cy.visit('/Sandbox');
+    cy.waitUntilSkeletonDisappear();
+    cy.collapseSidebar(true, true);
+
+    // Sticky
+    cy.waitUntil(() => {
+      // do
+      // Scroll page down 250px
+      cy.scrollTo(0, 250);
+      // wait until
+      return cy.getByTestid('grw-subnav-switcher').then($elem => !$elem.hasClass('grw-subnav-switcher-hidden'));
+    });
+    cy.screenshot(`${ssPrefix}subnav-switcher-is-sticky-on-scroll-down`);
+
+    // Not sticky
+    cy.waitUntil(() => {
+      // do
+      // Scroll page to top
+      cy.scrollTo(0, 0);
+      // wait until
+      return cy.getByTestid('grw-subnav-switcher').then($elem => $elem.hasClass('grw-subnav-switcher-hidden'));
+    });
+    cy.screenshot(`${ssPrefix}subnav-switcher-is-not-sticky-on-scroll-top`);
+  });
+
+  it('Fab display changes when scrolling down and up', () => {
+    cy.visit('/Sandbox');
+    cy.waitUntilSkeletonDisappear();
+    cy.collapseSidebar(true, true);
+
+    // Visible
+    cy.waitUntil(() => {
+      // do
+      // Scroll the window 250px down is enough to trigger sticky effect
+       cy.scrollTo(0, 250);
+
+      // wait until
+      return cy.getByTestid('grw-fab-return-to-top').then($elem => $elem.hasClass('visible'));
+
+    });
+    cy.screenshot(`${ssPrefix}fab-is-visible-on-scroll-down`);
+
+    // Invisible
+    cy.waitUntil(() => {
+      // do
+      // Scroll page to top
+       cy.scrollTo(0, 0);
+
+       // wait until
+      return cy.getByTestid('grw-fab-return-to-top').then($elem => $elem.hasClass('invisible'));
+    });
+    cy.screenshot(`${ssPrefix}fab-is-invisible-on-scroll-top`);
+
+  });
+});

+ 42 - 44
apps/slackbot-proxy/docker/Dockerfile

@@ -1,38 +1,40 @@
 # syntax = docker/dockerfile:1.4
 
 ##
-## packages-json-picker
+## base
 ##
-FROM node:16-slim AS packages-json-picker
+FROM node:18-slim AS base
 
 ENV optDir /opt
 
 WORKDIR ${optDir}
-COPY ["package.json", "yarn.lock", "lerna.json", "./"]
-COPY packages packages
-# Find and remove non-package.json files
-RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf
+
+RUN yarn global add turbo
+COPY . .
+RUN turbo prune --scope=@growi/slackbot-proxy --docker
 
 
 ##
-## deps-resolver-dev
+## deps-resolver
 ##
-FROM node:16-slim AS deps-resolver-dev
+FROM node:18-slim AS deps-resolver
 
 ENV optDir /opt
 
 WORKDIR ${optDir}
 
 # copy files
-COPY --from=packages-json-picker ${optDir} .
+COPY --from=base ${optDir}/out/json/ .
+COPY --from=base ${optDir}/out/yarn.lock ./yarn.lock
 
-# setup
-RUN yarn config set network-timeout 300000
-RUN npx -y lerna bootstrap -- --frozen-lockfile
+# setup (with network-timeout = 1 hour)
+RUN yarn config set network-timeout 3600000
+RUN yarn --frozen-lockfile
 
 # make artifacts
 RUN tar -cf node_modules.tar \
   node_modules \
+  apps/*/node_modules \
   packages/*/node_modules
 
 
@@ -40,19 +42,13 @@ RUN tar -cf node_modules.tar \
 ##
 ## deps-resolver-prod
 ##
-FROM node:16-slim AS deps-resolver-prod
-
-ENV optDir /opt
-
-WORKDIR ${optDir}
-COPY ["package.json", "yarn.lock", "lerna.json", "./"]
-COPY ./packages/slack/package.json ./packages/slack/package.json
-COPY ./apps/slackbot-proxy/package.json ./apps/slackbot-proxy/package.json
+FROM deps-resolver AS deps-resolver-prod
 
-RUN npx -y lerna bootstrap -- --production
+RUN yarn --production
 # make artifacts
-RUN tar -cf dependencies.tar \
+RUN tar -cf node_modules.tar \
   node_modules \
+  apps/*/node_modules \
   packages/*/node_modules
 
 
@@ -60,62 +56,64 @@ RUN tar -cf dependencies.tar \
 ##
 ## builder
 ##
-FROM node:16-slim AS builder
+FROM node:18-slim AS builder
 
 ENV optDir /opt
 
 WORKDIR ${optDir}
 
+RUN yarn global add turbo
+
+# copy files
+COPY --from=base ${optDir}/out/full/ .
+COPY --from=base ${optDir}/out/yarn.lock ./yarn.lock
+COPY ["tsconfig.base.json", "./"]
+
 # copy dependent packages
-COPY --from=deps-resolver-dev \
+COPY --from=deps-resolver \
   ${optDir}/node_modules.tar ${optDir}/
 
 # extract node_modules.tar
 RUN tar -xf node_modules.tar
 RUN rm node_modules.tar
 
-COPY ["package.json", "lerna.json", "tsconfig.base.json", "./"]
-# copy all related packages
-COPY packages/slack packages/slack
-COPY apps/slackbot-proxy apps/slackbot-proxy
-
 # build
-RUN yarn lerna run build
+RUN turbo run build
 
 # make artifacts
 RUN tar -cf packages.tar \
-  packages/slack/package.json \
-  packages/slack/dist \
-  apps/slackbot-proxy/package.json \
-  apps/slackbot-proxy/.env \
-  apps/slackbot-proxy/dist
+  package.json \
+  apps/*/package.json \
+  apps/*/dist \
+  apps/*/.env \
+  packages/*/package.json \
+  packages/*/dist
 
 
 
 ##
 ## release
 ##
-FROM node:16-slim
+FROM node:18-slim
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 
 ENV NODE_ENV production
 
 ENV optDir /opt
-ENV appDir ${optDir}
-
+ENV appDir ${optDir}/slackbot-proxy
 USER node
-
-WORKDIR ${appDir}
 # copy artifacts
 COPY --from=deps-resolver-prod --chown=node:node \
-  ${optDir}/dependencies.tar ./
+  ${optDir}/node_modules.tar ${appDir}/
 COPY --from=builder --chown=node:node \
-  ${optDir}/packages.tar ./
+  ${optDir}/packages.tar ${appDir}/
+
+WORKDIR ${appDir}
 
 # extract artifacts
-RUN tar -xf dependencies.tar
+RUN tar -xf node_modules.tar
 RUN tar -xf packages.tar
-RUN rm dependencies.tar packages.tar
+RUN rm node_modules.tar packages.tar
 
 WORKDIR ${appDir}/apps/slackbot-proxy
 

+ 3 - 0
apps/slackbot-proxy/docker/Dockerfile.dockerignore

@@ -3,3 +3,6 @@
 **/coverage
 **/Dockerfile
 **/*.dockerignore
+**/.turbo
+out
+apps/app

+ 1 - 1
packages/core/src/models/devided-page-path.js

@@ -32,7 +32,7 @@ export class DevidedPagePath {
       }
     }
 
-    let PATTERN_DEFAULT = /^((.*)\/)?(.+)$/; // this will not ignore html end tags https://regex101.com/r/jpZwIe/1
+    let PATTERN_DEFAULT = /^((.*)\/(?!em>))?(.+)$/; // this will ignore em's end tags
     try { // for non-chrome browsers
       // eslint-disable-next-line regex/invalid
       PATTERN_DEFAULT = new RegExp('^((.*)(?<!<)\\/)?(.+)$'); // https://regex101.com/r/HJNvMW/1

+ 2 - 1
turbo.json

@@ -76,7 +76,8 @@
     "@growi/app#dev:styles-prebuilt": {
       "outputs": ["src/styles/prebuilt/**"],
       "inputs": [
-        "src/styles/**/*.scss"
+        "src/styles/**/*.scss",
+        "!src/styles/prebuilt/**"
       ],
       "outputMode": "new-only"
     },