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

Merge branch 'feat/growi-ai-next' into feat/unified-merge-view

Yuki Takei 1 год назад
Родитель
Сommit
48a39b8d73

+ 1 - 1
.devcontainer/devcontainer.json

@@ -8,7 +8,7 @@
 
   "features": {
     "ghcr.io/devcontainers/features/node:1": {
-      "version": "20.18.0"
+      "version": "20.18.3"
     }
   },
 

+ 2 - 0
.npmrc

@@ -0,0 +1,2 @@
+# see: https://pnpm.io/next/npmrc#force-legacy-deploy
+force-legacy-deploy=true

+ 1 - 1
apps/app/.env.production

@@ -7,4 +7,4 @@ MIGRATIONS_DIR=dist/migrations/
 
 # OpenTelemetry Configuration
 OTEL_TRACES_SAMPLER_ARG=0.1
-
+OTEL_EXPORTER_OTLP_ENDPOINT="https://telemetry.growi.org"

+ 1 - 1
apps/app/docker/Dockerfile

@@ -14,7 +14,7 @@ WORKDIR ${optDir}
 RUN apt-get update && apt-get install -y ca-certificates wget curl --no-install-recommends
 
 # install pnpm
-RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -
+RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" PNPM_VERSION="10.4.1" sh -
 ENV PNPM_HOME="/root/.local/share/pnpm"
 ENV PATH="$PNPM_HOME:$PATH"
 

+ 9 - 5
apps/app/src/client/components/PageEditor/EditorNavbar/EditorNavbar.tsx

@@ -8,16 +8,20 @@ import styles from './EditorNavbar.module.scss';
 
 const moduleClass = styles['editor-navbar'] ?? '';
 
-export const EditorNavbar = (): JSX.Element => {
+const EditingUsers = (): JSX.Element => {
   const { data: editingUsers } = useEditingUsers();
+  return (
+    <EditingUserList
+      userList={editingUsers?.userList ?? []}
+    />
+  );
+};
 
+export const EditorNavbar = (): JSX.Element => {
   return (
     <div className={`${moduleClass} d-flex flex-column flex-sm-row justify-content-between ps-3 ps-md-5 ps-xl-4 pe-4 py-1 align-items-sm-end`}>
       <div className="order-2 order-sm-1"><PageHeader /></div>
-      <div className="order-1 order-sm-2"><EditingUserList
-        userList={editingUsers?.userList ?? []}
-      />
-      </div>
+      <div className="order-1 order-sm-2"><EditingUsers /></div>
     </div>
   );
 };

+ 38 - 32
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -81,7 +81,7 @@ type Props = {
   visibility?: boolean,
 }
 
-export const PageEditor = React.memo((props: Props): JSX.Element => {
+export const PageEditorSubstance = (props: Props): JSX.Element => {
 
   const { t } = useTranslation();
 
@@ -361,42 +361,48 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     return <></>;
   }
 
+  return (
+    <div className={`flex-expand-horiz ${props.visibility ? '' : 'd-none'}`}>
+      <div className="page-editor-editor-container flex-expand-vert border-end">
+        <CodeMirrorEditorMain
+          enableCollaboration={editorMode === EditorMode.Editor}
+          onSave={saveWithShortcut}
+          onUpload={uploadHandler}
+          acceptedUploadFileType={acceptedUploadFileType}
+          onScroll={scrollEditorHandlerThrottle}
+          indentSize={currentIndentSize ?? defaultIndentSize}
+          user={user ?? undefined}
+          pageId={pageId ?? undefined}
+          initialValue={initialValue}
+          editorSettings={editorSettings}
+          onEditorsUpdated={onEditorsUpdated}
+          cmProps={cmProps}
+        />
+      </div>
+      <div
+        ref={previewRef}
+        onScroll={scrollPreviewHandlerThrottle}
+        className="page-editor-preview-container flex-expand-vert overflow-y-auto d-none d-lg-flex"
+      >
+        <Preview
+          rendererOptions={rendererOptions}
+          markdown={markdownToPreview}
+          pagePath={currentPagePath}
+          expandContentWidth={shouldExpandContent}
+          style={pastEndStyle}
+        />
+      </div>
+    </div>
+  );
+};
+
+export const PageEditor = React.memo((props: Props): JSX.Element => {
   return (
     <div data-testid="page-editor" id="page-editor" className={`flex-expand-vert ${props.visibility ? '' : 'd-none'}`}>
 
       <EditorNavbar />
 
-      <div className={`flex-expand-horiz ${props.visibility ? '' : 'd-none'}`}>
-        <div className="page-editor-editor-container flex-expand-vert border-end">
-          <CodeMirrorEditorMain
-            enableCollaboration={editorMode === EditorMode.Editor}
-            onSave={saveWithShortcut}
-            onUpload={uploadHandler}
-            acceptedUploadFileType={acceptedUploadFileType}
-            onScroll={scrollEditorHandlerThrottle}
-            indentSize={currentIndentSize ?? defaultIndentSize}
-            user={user ?? undefined}
-            pageId={pageId ?? undefined}
-            initialValue={initialValue}
-            editorSettings={editorSettings}
-            onEditorsUpdated={onEditorsUpdated}
-            cmProps={cmProps}
-          />
-        </div>
-        <div
-          ref={previewRef}
-          onScroll={scrollPreviewHandlerThrottle}
-          className="page-editor-preview-container flex-expand-vert overflow-y-auto d-none d-lg-flex"
-        >
-          <Preview
-            rendererOptions={rendererOptions}
-            markdown={markdownToPreview}
-            pagePath={currentPagePath}
-            expandContentWidth={shouldExpandContent}
-            style={pastEndStyle}
-          />
-        </div>
-      </div>
+      <PageEditorSubstance visibility={props.visibility} />
 
       <EditorNavbarBottom />
 

+ 41 - 0
apps/app/src/client/components/Sidebar/AppTitle/AppTitle.module.scss

@@ -2,6 +2,7 @@
 @use '@growi/core-styles/scss/variables/growi-official-colors';
 @use '~/styles/variables' as var;
 @use '../button-styles';
+@use '~/styles/mixins';
 
 // GROWI Logo
 .grw-app-title :global {
@@ -25,6 +26,22 @@
   }
 }
 
+// == GROWI Logo when Editor mode
+@include mixins.at-editing() {
+  @include bs.media-breakpoint-up(xl) {
+    .grw-app-title :global {
+      .grw-logo {
+          opacity: 0.5;
+          transition: opacity 0.8s ease;
+
+          &:hover {
+            opacity: 1;
+          }
+      }
+    }
+  }
+}
+
 
 // == Location
 .on-subnavigation {
@@ -64,6 +81,30 @@
   width: calc(100% - $toggle-collapse-button-width);
 }
 
+// ==Sidebar Head when Editor mode
+@include bs.color-mode(light) {
+  .on-editor-sidebar-head {
+    background-color: var(
+      --on-editor-sidebar-head-bg,
+      var(
+        --grw-sidebar-nav-bg,
+        var(--grw-highlight-100)
+      )
+    );
+  }
+}
+
+@include bs.color-mode(dark) {
+  .on-editor-sidebar-head {
+    background-color: var(
+      --on-editor-sidebar-head-bg,
+      var(
+        --grw-sidebar-nav-bg,
+        var(--grw-highlight-800)
+      )
+    );
+  }
+}
 
 // == Interaction
 @keyframes bounce-to-right {

+ 27 - 11
apps/app/src/client/components/Sidebar/AppTitle/AppTitle.tsx

@@ -12,28 +12,29 @@ import styles from './AppTitle.module.scss';
 
 type Props = {
   className?: string,
+  hideAppTitle?: boolean;
 }
 
-const AppTitleSubstance = memo((props: Props): JSX.Element => {
-
-  const { className } = props;
+const AppTitleSubstance = memo(({ className = '', hideAppTitle = false }: Props): JSX.Element => {
 
   const { data: isDefaultLogo } = useIsDefaultLogo();
   const { data: appTitle } = useAppTitle();
   const { data: confidential } = useConfidential();
 
   return (
-    <div className={`${styles['grw-app-title']} ${className} d-flex d-edit-none`}>
+    <div className={`${styles['grw-app-title']} ${className} d-flex`}>
       {/* Brand Logo  */}
       <Link href="/" className="grw-logo d-block">
         <SidebarBrandLogo isDefaultLogo={isDefaultLogo} />
       </Link>
       <div className="flex-grow-1 d-flex align-items-center justify-content-between gap-3 overflow-hidden">
-        <div id="grw-site-name" className="grw-site-name text-truncate">
-          <Link href="/" className="fs-4">
-            {appTitle}
-          </Link>
-        </div>
+        {!hideAppTitle && (
+          <div id="grw-site-name" className="grw-site-name text-truncate">
+            <Link href="/" className="fs-4">
+              {appTitle}
+            </Link>
+          </div>
+        )}
       </div>
       {!(confidential == null || confidential === '')
       && (
@@ -56,6 +57,21 @@ export const AppTitleOnSubnavigation = memo((): JSX.Element => {
   return <AppTitleSubstance className={`position-absolute ${styles['on-subnavigation']}`} />;
 });
 
-export const AppTitleOnSidebarHead = memo((): JSX.Element => {
-  return <AppTitleSubstance className={`position-absolute z-1 ${styles['on-sidebar-head']}`} />;
+export const AppTitleOnSidebarHead = memo(({ hideAppTitle }: Props): JSX.Element => {
+  return (
+    <AppTitleSubstance
+      className={`position-absolute z-1 ${styles['on-sidebar-head']}`}
+      hideAppTitle={hideAppTitle}
+    />
+  );
+});
+
+export const AppTitleOnEditorSidebarHead = memo((): JSX.Element => {
+  return (
+    <div className={`${styles['on-editor-sidebar-head']}`}>
+      <AppTitleSubstance
+        className={`${styles['on-sidebar-head']}`}
+      />
+    </div>
+  );
 });

+ 20 - 5
apps/app/src/client/components/Sidebar/Sidebar.tsx

@@ -11,6 +11,7 @@ import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 import { SidebarMode } from '~/interfaces/ui';
 import { useIsSearchPage } from '~/stores-universal/context';
+import { EditorMode, useEditorMode } from '~/stores-universal/ui';
 import {
   useDrawerOpened,
   useCollapsedContentsOpened,
@@ -18,11 +19,13 @@ import {
   usePreferCollapsedMode,
   useSidebarMode,
   useSidebarScrollerRef,
+  useIsDeviceLargerThanMd,
+  useIsDeviceLargerThanXl,
 } from '~/stores/ui';
 
 import { DrawerToggler } from '../Common/DrawerToggler';
 
-import { AppTitleOnSidebarHead, AppTitleOnSubnavigation } from './AppTitle/AppTitle';
+import { AppTitleOnSidebarHead, AppTitleOnEditorSidebarHead, AppTitleOnSubnavigation } from './AppTitle/AppTitle';
 import { ResizableAreaFallback } from './ResizableArea/ResizableAreaFallback';
 import type { ResizableAreaProps } from './ResizableArea/props';
 import { SidebarHead } from './SidebarHead';
@@ -230,6 +233,14 @@ export const Sidebar = (): JSX.Element => {
   } = useSidebarMode();
 
   const { data: isSearchPage } = useIsSearchPage();
+  const { data: editorMode } = useEditorMode();
+  const { data: isMdSize } = useIsDeviceLargerThanMd();
+  const { data: isXlSize } = useIsDeviceLargerThanXl();
+
+  const isEditorMode = editorMode === EditorMode.Editor;
+  const shouldHideSiteName = isEditorMode && isXlSize;
+  const shouldHideSubnavAppTitle = isEditorMode && isMdSize && (isDrawerMode() || isCollapsedMode());
+  const shouldShowEditorSidebarHead = isEditorMode && isXlSize;
 
   // css styles
   const grwSidebarClass = styles['grw-sidebar'];
@@ -253,12 +264,16 @@ export const Sidebar = (): JSX.Element => {
         <DrawerToggler className="position-fixed d-none d-md-block">
           <span className="material-symbols-outlined">reorder</span>
         </DrawerToggler>
-      ) }
-      { sidebarMode != null && !isDockMode() && !isSearchPage && <AppTitleOnSubnavigation /> }
+      )}
+      { sidebarMode != null && !isDockMode() && !isSearchPage && !shouldHideSubnavAppTitle && (
+        <AppTitleOnSubnavigation />
+      )}
       <DrawableContainer className={`${grwSidebarClass} ${modeClass} border-end flex-expand-vh-100`} divProps={{ 'data-testid': 'grw-sidebar' }}>
         <ResizableContainer>
-          { sidebarMode != null && !isCollapsedMode() && <AppTitleOnSidebarHead /> }
-          <SidebarHead />
+          { sidebarMode != null && !isCollapsedMode() && (
+            <AppTitleOnSidebarHead hideAppTitle={shouldHideSiteName} />
+          )}
+          {shouldShowEditorSidebarHead ? <AppTitleOnEditorSidebarHead /> : <SidebarHead />}
           <CollapsibleContainer Nav={SidebarNav} className="border-top">
             <SidebarContents />
           </CollapsibleContainer>

+ 1 - 1
apps/app/src/server/routes/apiv3/import.js

@@ -319,7 +319,7 @@ export default function route(crowi) {
    *                      type: object
    *                      description: the property of each extracted file
    */
-  router.post('/upload', uploads.single('file'), accessTokenParser, loginRequired, adminRequired, addActivity, async(req, res) => {
+  router.post('/upload', accessTokenParser, loginRequired, adminRequired, uploads.single('file'), addActivity, async(req, res) => {
     const { file } = req;
     const zipFile = importService.getFile(file.filename);
     let data = null;

+ 1 - 1
apps/slackbot-proxy/docker/Dockerfile

@@ -11,7 +11,7 @@ WORKDIR ${optDir}
 
 # install pnpm
 RUN apt-get update && apt-get install -y ca-certificates wget --no-install-recommends \
-  && wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -
+  && wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" PNPM_VERSION="10.4.1" sh -
 ENV PNPM_HOME="/root/.local/share/pnpm"
 ENV PATH="$PNPM_HOME:$PATH"
 

+ 1 - 1
bin/data-migrations/src/migrations/v60x/csv.js

@@ -7,7 +7,7 @@ module.exports = [
    * @type {MigrationModule}
    */
   (body) => {
-    const oldCsvTableRegExp = /::: csv(-h)?\n([\s\S]*?)\n:::/g; // CSV old format
+    const oldCsvTableRegExp = /:::\s?csv(-h)?\n([\s\S]*?)\n:::/g; // CSV old format
     return body.replace(oldCsvTableRegExp, '``` csv$1\n$2\n```');
   },
 ];

+ 1 - 1
bin/data-migrations/src/migrations/v60x/tsv.js

@@ -7,7 +7,7 @@ module.exports = [
    * @type {MigrationModule}
    */
   (body) => {
-    const oldTsvTableRegExp = /::: tsv(-h)?\n([\s\S]*?)\n:::/g; // TSV old format
+    const oldTsvTableRegExp = /:::\s?tsv(-h)?\n([\s\S]*?)\n:::/g; // TSV old format
     return body.replace(oldTsvTableRegExp, '``` tsv$1\n$2\n```');
   },
 ];

+ 2 - 3
package.json

@@ -20,7 +20,7 @@
   "bugs": {
     "url": "https://github.com/weseek/growi/issues"
   },
-  "packageManager": "pnpm@9.4.0",
+  "packageManager": "pnpm@10.4.1",
   "scripts": {
     "bootstrap": "pnpm install",
     "start": "pnpm run app:server",
@@ -114,7 +114,6 @@
     }
   },
   "engines": {
-    "node": "^18 || ^20",
-    "pnpm": ">=9.4 <10"
+    "node": "^18 || ^20"
   }
 }

+ 3 - 2
packages/remark-attachment-refs/src/server/routes/refs.ts

@@ -86,6 +86,7 @@ export const routesFactory = (crowi): any => {
   router.get('/ref', accessTokenParser, loginRequired, async(req: RequestWithUser, res) => {
     const user = req.user;
     const { pagePath, fileNameOrId } = req.query;
+    const filterXSS = new FilterXSS();
 
     if (pagePath == null) {
       res.status(400).send('the param \'pagePath\' must be set.');
@@ -96,7 +97,7 @@ export const routesFactory = (crowi): any => {
 
     // not found
     if (page == null) {
-      res.status(404).send(`pagePath: '${pagePath}' is not found or forbidden.`);
+      res.status(404).send(filterXSS.process(`pagePath: '${pagePath}' is not found or forbidden.`));
       return;
     }
 
@@ -117,7 +118,7 @@ export const routesFactory = (crowi): any => {
 
     // not found
     if (attachment == null) {
-      res.status(404).send(`attachment '${fileNameOrId}' is not found.`);
+      res.status(404).send(filterXSS.process(`attachment '${fileNameOrId}' is not found.`));
       return;
     }