Browse Source

Merge branch 'master' into support/182163-add-figma-mcp

satof3 1 day ago
parent
commit
d54505773f

+ 2 - 1
.claude/settings.json

@@ -56,6 +56,7 @@
   "enabledPlugins": {
     "context7@claude-plugins-official": true,
     "typescript-lsp@claude-plugins-official": true,
-    "figma@claude-plugins-official": true
+    "figma@claude-plugins-official": true,
+    "mcp-client-skills@growi-mcp-tools": true
   }
 }

+ 1 - 1
.devcontainer/app/devcontainer.json

@@ -18,7 +18,7 @@
 
   "initializeCommand": "/bin/bash .devcontainer/pdf-converter/initializeCommand.sh",
   // Use 'postCreateCommand' to run commands after the container is created.
-  "postCreateCommand": "/bin/bash ./.devcontainer/app/postCreateCommand.sh",
+  "postCreateCommand": "/bin/bash ./.devcontainer/app/postCreateCommand/main.sh",
 
   // Configure tool-specific properties.
   "customizations": {

+ 69 - 0
.devcontainer/app/postCreateCommand/ensure-mongo-fcv.ts

@@ -0,0 +1,69 @@
+// Ensures MongoDB Feature Compatibility Version matches the mongo image (8.2).
+// Required when the mongo image is upgraded while existing data persists in the volume.
+// https://www.mongodb.com/ja-jp/docs/upcoming/release-notes/8.2-upgrade-standalone/
+//
+// Run with Node's native TypeScript support (>= v22.6 with strip-types,
+// enabled by default in v24+). Resolution base is pinned to apps/app/ so the
+// mongodb driver installed there can be loaded without changing cwd.
+
+import { createRequire } from 'node:module';
+
+const require = createRequire('/workspace/growi/apps/app/');
+const { MongoClient } = require('mongodb') as typeof import('mongodb');
+
+const URI = 'mongodb://mongo:27017';
+const TARGET_FCV = '8.2';
+const MAX_RETRIES = 30;
+const RETRY_INTERVAL_MS = 2000;
+
+const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
+
+async function waitForMongo(): Promise<void> {
+  for (let i = 0; i < MAX_RETRIES; i++) {
+    const client = new MongoClient(URI, { serverSelectionTimeoutMS: 2000 });
+    try {
+      await client.connect();
+      await client.db('admin').command({ ping: 1 });
+      return;
+    }
+    catch {
+      await sleep(RETRY_INTERVAL_MS);
+    }
+    finally {
+      await client.close().catch(() => {});
+    }
+  }
+  throw new Error(`MongoDB at ${URI} did not become ready in time`);
+}
+
+async function ensureFcv(): Promise<void> {
+  const client = new MongoClient(URI);
+  await client.connect();
+  try {
+    const admin = client.db('admin');
+    const result = await admin.command({
+      getParameter: 1,
+      featureCompatibilityVersion: 1,
+    }) as { featureCompatibilityVersion: { version: string } };
+    const version = result.featureCompatibilityVersion.version;
+    if (version === TARGET_FCV) {
+      console.log(`FCV already ${TARGET_FCV}`);
+      return;
+    }
+    await admin.command({ setFeatureCompatibilityVersion: TARGET_FCV, confirm: true });
+    console.log(`FCV upgraded: ${version} -> ${TARGET_FCV}`);
+  }
+  finally {
+    await client.close();
+  }
+}
+
+console.log('Waiting for MongoDB to be ready...');
+await waitForMongo();
+console.log(`Ensuring MongoDB featureCompatibilityVersion is ${TARGET_FCV}...`);
+try {
+  await ensureFcv();
+}
+catch (e) {
+  console.error('FCV upgrade failed:', (e as Error).message);
+}

+ 3 - 0
.devcontainer/app/postCreateCommand.sh → .devcontainer/app/postCreateCommand/main.sh

@@ -35,3 +35,6 @@ turbo run bootstrap
 
 # Install Lefthook git hooks
 pnpm lefthook install
+
+# Ensure MongoDB Feature Compatibility Version matches the mongo image.
+node /workspace/growi/.devcontainer/app/postCreateCommand/ensure-mongo-fcv.ts

+ 3 - 0
.devcontainer/app/postCreateCommand/package.json

@@ -0,0 +1,3 @@
+{
+  "type": "module"
+}

+ 1 - 0
.devcontainer/compose.extend.template.yml

@@ -7,5 +7,6 @@ services:
     volumes:
       - ..:/workspace/growi:delegated
       - pnpm-store:/workspace/.pnpm-store
+      - ${HOME}/.claude:/home/vscode/.claude
       - page_bulk_export_tmp:/tmp/page-bulk-export
     tty: true

+ 4 - 6
.devcontainer/compose.yml

@@ -4,6 +4,7 @@ services:
     volumes:
       - ..:/workspace/growi:delegated
       - pnpm-store:/workspace/.pnpm-store
+      - ${HOME}/.claude:/home/vscode/.claude
       - ../../growi-docker-compose:/workspace/growi-docker-compose:delegated
       - ../../share:/workspace/share:delegated
       - page_bulk_export_tmp:/tmp/page-bulk-export
@@ -13,7 +14,7 @@ services:
     - opentelemetry-collector-dev-setup_default
 
   mongo:
-    image: mongo:8.0
+    image: mongo:8.2
     restart: unless-stopped
     ports:
       - 27017
@@ -23,11 +24,7 @@ services:
   # This container requires '../../growi-docker-compose' repository
   #   cloned from https://github.com/growilabs/growi-docker-compose.git
   elasticsearch:
-    build:
-      context: ../../growi-docker-compose/elasticsearch/v9
-      dockerfile: ./Dockerfile
-      args:
-        - version=9.0.3
+    image: docker.elastic.co/elasticsearch/elasticsearch:9.3.3
     restart: unless-stopped
     ports:
       - 9200
@@ -42,6 +39,7 @@ services:
     volumes:
       - /usr/share/elasticsearch/data
       - ../../growi-docker-compose/elasticsearch/v9/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
+      - ../../growi-docker-compose/elasticsearch/v9/config/elasticsearch-plugins.yml:/usr/share/elasticsearch/config/elasticsearch-plugins.yml
 
 volumes:
   pnpm-store:

+ 3 - 0
.gitignore

@@ -43,6 +43,9 @@ yarn-error.log*
 *.code-workspace
 *.timestamp-*.mjs
 
+# UTCP configuration file
+.utcp_config.json
+
 # turborepo
 .turbo
 

+ 9 - 1
.mcp.json

@@ -1,3 +1,11 @@
 {
-  "mcpServers": {}
+  "mcpServers": {
+    "code-mode": {
+      "command": "npx",
+      "args": ["-y", "@utcp/code-mode-mcp"],
+      "env": {
+        "UTCP_CONFIG_FILE": "/workspace/growi/.utcp_config.json"
+      }
+    }
+  }
 }

+ 4 - 0
apps/app/resource/Contributor.js

@@ -160,6 +160,10 @@ const contributors = [
             name: 'Yuji Tounai',
           },
           { name: 'yy0931' },
+          {
+            position: 'GMO Cybersecurity by Ierae, Inc.',
+            name: 'Sho Odagiri',
+          },
         ],
       },
     ],

+ 4 - 2
apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -47,7 +47,7 @@ import {
   isUploadEnabledAtom,
 } from '~/states/server-configurations';
 import { useDeviceLargerThanMd } from '~/states/ui/device';
-import { useEditorMode } from '~/states/ui/editor';
+import { EditorMode, useEditorMode } from '~/states/ui/editor';
 import {
   PageAccessoriesModalContents,
   usePageAccessoriesModalActions,
@@ -323,7 +323,7 @@ const GrowiContextualSubNavigation = (
   const revisionId =
     revision != null && isPopulated(revision) ? revision._id : undefined;
 
-  const { editorMode } = useEditorMode();
+  const { editorMode, setEditorMode } = useEditorMode();
   const pageId = useCurrentPageId(true);
   const currentUser = useCurrentUser();
   const isGuestUser = useIsGuestUser();
@@ -390,6 +390,7 @@ const GrowiContextualSubNavigation = (
 
         if (isCompletely) {
           // redirect to NotFound Page
+          setEditorMode(EditorMode.View);
           router.push(path);
         } else if (currentPathname != null) {
           router.push(currentPathname);
@@ -408,6 +409,7 @@ const GrowiContextualSubNavigation = (
       openDeleteModal,
       router,
       mutatePageInfo,
+      setEditorMode,
     ],
   );
 

+ 10 - 2
apps/app/src/client/components/PageComment/Comment.module.scss

@@ -41,8 +41,16 @@
     // comment body
     :global(.page-comment-body) {
       word-wrap: break-word;
-      :global(.wiki p) {
-        margin: 8px 0;
+      :global(.wiki) {
+        p {
+          margin: 8px 0;
+        }
+        blockquote {
+          margin: 24px 0 8px;
+          &:first-child {
+            margin-top: 0;
+          }
+        }
       }
     }
 

+ 14 - 3
apps/app/src/client/components/PageList/PageListItemL.tsx

@@ -238,11 +238,22 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (
 
   return (
     <li key={pageData._id}>
-      <button
-        type="button"
+      {/* biome-ignore lint/a11y/useSemanticElements: cannot use <button> here because PageItemControl renders a nested <button> (DropdownToggle) */}
+      <div
+        role="button"
+        tabIndex={0}
         className={`list-group-item d-flex align-items-center px-3 px-md-1 text-start w-100 ${styleListGroupItem} ${styleActive}`}
         data-testid="page-list-item-L"
         onClick={clickHandler}
+        onKeyDown={(e) => {
+          if (
+            e.target === e.currentTarget &&
+            (e.key === 'Enter' || e.key === ' ')
+          ) {
+            e.preventDefault();
+            clickHandler();
+          }
+        }}
       >
         <div className="text-break w-100">
           <div className="d-flex">
@@ -365,7 +376,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (
           </div>
         </div>
         {/* TODO: adjust snippet position */}
-      </button>
+      </div>
     </li>
   );
 };

+ 5 - 1
apps/app/src/styles/organisms/_wiki.scss

@@ -123,11 +123,15 @@
 
   blockquote {
     padding: 0 20px;
-    margin: 0 0 30px;
+    margin: 24px 0 16px;
     font-size: 0.9em;
     /* stylelint-disable-next-line scss/no-global-function-names */
     color: lighten(bs.$gray-800, 35%);
     border-left: 0.3rem solid #ddd;
+
+    &:first-child {
+      margin-top: 0;
+    }
   }
 
   img,video {

+ 20 - 0
packages/remark-drawio/src/components/DrawioViewer.module.scss

@@ -7,3 +7,23 @@
 .drawio-viewer * {
   box-sizing: content-box;
 }
+
+// Revert host-page CSS that leaks into HTML rendered inside <foreignObject>.
+// drawio sizes each cell using UA-default HTML metrics and clips overflow via
+// an inline max-height wrapper, so non-default styles (e.g. line-height,
+// margin from a wrapping `.wiki` ruleset) cause label content to be cut off.
+//
+// `!important` is required: host selectors such as `.wiki ol:not(.nav) li`
+// outrank our scoped selector on specificity (`:not(.nav)` adds a class-level
+// weight). Defending the foreignObject content is an adversarial cross-cutting
+// concern, so we explicitly opt out of the specificity contest rather than
+// chasing host selectors.
+//
+// See: https://github.com/growilabs/growi/issues/11052
+.drawio-viewer foreignObject {
+  h1, h2, h3, h4, h5, h6,
+  p, ul, ol, li, blockquote,
+  img, video, table {
+    all: revert !important;
+  }
+}