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

Merge branch 'master' into support/vrt-with-cypress

Yuki Takei 4 лет назад
Родитель
Сommit
301aec6a44
32 измененных файлов с 287 добавлено и 645 удалено
  1. 1 0
      .stylelintrc.json
  2. 28 1
      CHANGELOG.md
  3. 1 1
      lerna.json
  4. 5 1
      package.json
  5. 1 0
      packages/app/.stylelintrc.json
  6. 2 2
      packages/app/docker/README.md
  7. 10 12
      packages/app/package.json
  8. 1 0
      packages/app/resource/locales/en_US/translation.json
  9. 1 0
      packages/app/resource/locales/ja_JP/translation.json
  10. 1 0
      packages/app/resource/locales/zh_CN/translation.json
  11. 0 27
      packages/app/src/client/legacy/crowi.js
  12. 4 22
      packages/app/src/client/services/PageContainer.js
  13. 0 85
      packages/app/src/components/BookmarkButton.jsx
  14. 83 0
      packages/app/src/components/BookmarkButtons.tsx
  15. 26 12
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx
  16. 5 3
      packages/app/src/components/Navbar/SubNavButtons.jsx
  17. 10 2
      packages/app/src/components/PageEditor/CodeMirrorEditor.jsx
  18. 1 3
      packages/app/src/components/SubscribeButton.tsx
  19. 7 0
      packages/app/src/interfaces/bookmarks.ts
  20. 8 2
      packages/app/src/server/routes/apiv3/bookmarks.js
  21. 21 0
      packages/app/src/stores/bookmarks.tsx
  22. 2 2
      packages/app/src/stores/context.tsx
  23. 4 2
      packages/app/src/stores/page.tsx
  24. 1 1
      packages/codemirror-textlint/package.json
  25. 1 2
      packages/core/package.json
  26. 2 3
      packages/plugin-attachment-refs/package.json
  27. 2 2
      packages/plugin-lsx/package.json
  28. 1 3
      packages/plugin-pukiwiki-like-linker/package.json
  29. 1 1
      packages/slack/package.json
  30. 2 2
      packages/slackbot-proxy/package.json
  31. 1 2
      packages/ui/package.json
  32. 54 452
      yarn.lock

+ 1 - 0
.stylelintrc.json

@@ -2,6 +2,7 @@
   "extends": [
     "stylelint-config-recess-order"
   ],
+  "customSyntax": "postcss-scss",
   "rules": {
     "indentation": 2,
     "string-quotes": "single",

+ 28 - 1
CHANGELOG.md

@@ -1,9 +1,36 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v4.5.6...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v4.5.8...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v4.5.8](https://github.com/weseek/growi/compare/v4.5.7...v4.5.8) - 2022-01-12
+
+### 💎 Features
+
+- feat: Display a list of bookmarked users (#5044) @miya
+
+### 🐛 Bug Fixes
+
+- fix: Built-in editor scroll position is reset after save (Introduced by v4.5.3) (#5074) @yuki-takei
+
+### 🧰 Maintenance
+
+- support: Bump y18n to v4.0.3 (#5071) @yuki-takei
+- support: Omit prettier-stylelint (#5070) @yuki-takei
+- support: Bump tar to 6.1.11 (#5069) @yuki-takei
+
+## [v4.5.7](https://github.com/weseek/growi/compare/v4.5.6...v4.5.7) - 2022-01-11
+
+### 🐛 Bug Fixes
+
+- fix: Subnavigation sticking initialization (#5062) @yuki-takei
+- fix: Built-in editor was broken (#5061) @yuki-takei
+
+### 🧰 Maintenance
+
+- support: Bump re2 to 1.17.2 (#5059) @yuki-takei
+
 ## [v4.5.6](https://github.com/weseek/growi/compare/v4.5.5...v4.5.6) - 2022-01-07
 
 ### 💎 Features

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
   "npmClient": "yarn",
   "useWorkspaces": true,
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "packages": [
     "packages/*"
   ]

+ 5 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -69,8 +69,12 @@
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
     "lerna": "^4.0.0",
+    "postcss": "^8.4.5",
+    "postcss-scss": "^4.0.3",
     "rewire": "^5.0.0",
     "shipjs": "^0.24.1",
+    "stylelint": "^14.2.0",
+    "stylelint-config-recess-order": "^3.0.0",
     "ts-jest": "^27.0.4",
     "ts-node": "^9.1.1",
     "tsconfig-paths": "^3.9.0",

+ 1 - 0
packages/app/.stylelintrc.json

@@ -2,6 +2,7 @@
   "extends": [
     "stylelint-config-recess-order"
   ],
+  "customSyntax": "postcss-scss",
   "ignoreFiles": [
     "src/styles/_override-bootstrap-variables.scss",
     "src/linter-checker/test.scss"

+ 2 - 2
packages/app/docker/README.md

@@ -10,8 +10,8 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`4.5.6`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.6/docker/Dockerfile)
-* [`4.5.6-nocdn`, `4.5-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.6/docker/Dockerfile)
+* [`4.5.8`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.8/docker/Dockerfile)
+* [`4.5.8-nocdn`, `4.5-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.8/docker/Dockerfile)
 * [`4.4.13`, `4.4` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.13/docker/Dockerfile)
 * [`4.4.13-nocdn`, `4.4-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.13/docker/Dockerfile)
 

+ 10 - 12
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -34,7 +34,7 @@
     "predev:ci": "run-p resources:*",
     "lint:typecheck": "npx tsc",
     "lint:eslint": "eslint --quiet \"**/*.{js,jsx,ts,tsx}\"",
-    "lint:styles": "stylelint src/**/*.scss --custom-syntax postcss-scss",
+    "lint:styles": "stylelint src/**/*.scss",
     "lint:swagger2openapi": "node node_modules/.bin/oas-validate tmp/swagger.json",
     "lint": "run-p lint:*",
     "test": "cross-env NODE_ENV=test jest --passWithNoTests -- ",
@@ -58,11 +58,11 @@
     "@browser-bunyan/console-formatted-stream": "^1.6.2",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^4.5.7-RC.0",
-    "@growi/plugin-attachment-refs": "^4.5.7-RC.0",
-    "@growi/plugin-lsx": "^4.5.7-RC.0",
-    "@growi/plugin-pukiwiki-like-linker": "^4.5.7-RC.0",
-    "@growi/slack": "^4.5.7-RC.0",
+    "@growi/codemirror-textlint": "^4.5.9-RC.0",
+    "@growi/plugin-attachment-refs": "^4.5.9-RC.0",
+    "@growi/plugin-lsx": "^4.5.9-RC.0",
+    "@growi/plugin-pukiwiki-like-linker": "^4.5.9-RC.0",
+    "@growi/slack": "^4.5.9-RC.0",
     "@promster/express": "^5.1.0",
     "@promster/server": "^6.0.3",
     "@slack/events-api": "^3.0.0",
@@ -134,7 +134,7 @@
     "passport-saml": "^3.2.0",
     "passport-twitter": "^1.0.4",
     "prom-client": "^13.0.0",
-    "re2": "^1.17.1",
+    "re2": "^1.17.2",
     "react-card-flip": "^1.0.10",
     "react-image-crop": "^8.3.0",
     "react-tagcloud": "^2.1.1",
@@ -161,7 +161,7 @@
   },
   "devDependencies": {
     "@alienfast/i18next-loader": "^1.1.4",
-    "@growi/ui": "^4.5.7-RC.0",
+    "@growi/ui": "^4.5.9-RC.0",
     "@handsontable/react": "=2.1.0",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",
@@ -235,10 +235,8 @@
     "sticky-events": "^3.4.11",
     "style-loader": "^1.0.0",
     "styled-components": "^5.0.1",
-    "stylelint": "^14.0.1",
-    "stylelint-config-recess-order": "^2.0.1",
     "swagger2openapi": "^5.3.1",
-    "swr": "^1.0.1",
+    "swr": "^1.1.2",
     "terser-webpack-plugin": "^4.1.0",
     "throttle-debounce": "^2.0.0",
     "toastr": "^2.1.2",

+ 1 - 0
packages/app/resource/locales/en_US/translation.json

@@ -58,6 +58,7 @@
   "The end": "The end",
   "Not available for guest": "Not available for guest",
   "No users have liked this yet.": "No users have liked this yet.",
+  "No users have bookmarked yet": "No users have bookmarked yet",
   "Create Archive Page": "Create Archive Page",
   "File type": "File type",
   "Target page": "Target page",

+ 1 - 0
packages/app/resource/locales/ja_JP/translation.json

@@ -58,6 +58,7 @@
   "Presentation Mode": "プレゼンテーション",
   "The end": "おしまい",
   "Not available for guest": "ゲストユーザーは利用できません",
+  "No users have bookmarked yet": "ブックマークしているユーザーはいません",
   "Create Archive Page": "アーカイブページの作成",
   "Target page": "対象ページ",
   "File type": "ファイル形式",

+ 1 - 0
packages/app/resource/locales/zh_CN/translation.json

@@ -59,6 +59,7 @@
 	"Presentation Mode": "演示文稿",
   "The end": "结束",
   "Not available for guest": "Not available for guest",
+  "No users have bookmarked yet": "还没有用户加入书签",
   "Create Archive Page": "创建归档页",
   "File type": "文件类型",
   "Target page": "目标页面",

+ 0 - 27
packages/app/src/client/legacy/crowi.js

@@ -112,33 +112,6 @@ Crowi.initClassesByOS = function() {
   });
 };
 
-// window.addEventListener('load', () => {
-//   const { appContainer } = window;
-//   const pageContainer = appContainer.getContainer('PageContainer');
-
-//   // Do nothing if the page does not exist
-//   // ex.) admin page,login page
-//   if (pageContainer == null) {
-//     return null;
-//   }
-//   const { isAbleToOpenPageEditor } = pageContainer;
-
-//   // hash on page
-//   if (window.location.hash) {
-//     const navigationContainer = appContainer.getContainer('NavigationContainer');
-
-//     if (window.location.hash === '#edit' && isAbleToOpenPageEditor) {
-//       navigationContainer.setEditorMode('edit');
-
-//       // focus
-//       Crowi.setCaretLineAndFocusToEditor();
-//     }
-//     else if (window.location.hash === '#hackmd') {
-//       navigationContainer.setEditorMode('hackmd');
-//     }
-//   }
-// });
-
 window.addEventListener('load', () => {
   const crowi = window.crowi;
   if (crowi && crowi.users && crowi.users.length !== 0) {

+ 4 - 22
packages/app/src/client/services/PageContainer.js

@@ -5,10 +5,10 @@ import * as entities from 'entities';
 import * as toastr from 'toastr';
 import { pagePathUtils } from '@growi/core';
 
-import { apiPost } from '../util/apiv1-client';
 import loggerFactory from '~/utils/logger';
-import { toastError } from '../util/apiNotification';
+import { EditorMode } from '~/stores/ui';
 
+import { toastError } from '../util/apiNotification';
 import {
   DetachCodeBlockInterceptor,
   RestoreCodeBlockInterceptor,
@@ -54,9 +54,6 @@ export default class PageContainer extends Container {
       path,
       tocHtml: '',
 
-      isBookmarked: false,
-      sumOfBookmarks: 0,
-
       seenUsers: [],
       seenUserIds: [],
       sumOfSeenUsers: [],
@@ -132,7 +129,6 @@ export default class PageContainer extends Container {
       // as it is stored in a separate collection to like and seen user
       // data so it has a separate api endpoint.
       this.initialPageLoad();
-      this.retrieveBookmarkInfo();
     }
 
     this.setTocHtml = this.setTocHtml.bind(this);
@@ -320,20 +316,6 @@ export default class PageContainer extends Container {
     this.checkAndUpdateImageUrlCached(users);
   }
 
-  async retrieveBookmarkInfo() {
-    const response = await this.appContainer.apiv3Get('/bookmarks/info', { pageId: this.state.pageId });
-    this.setState({
-      sumOfBookmarks: response.data.sumOfBookmarks,
-      isBookmarked: response.data.isBookmarked,
-    });
-  }
-
-  async toggleBookmark() {
-    const bool = !this.state.isBookmarked;
-    await this.appContainer.apiv3Put('/bookmarks', { pageId: this.state.pageId, bool });
-    return this.retrieveBookmarkInfo();
-  }
-
   async checkAndUpdateImageUrlCached(users) {
     const noImageCacheUsers = users.filter((user) => { return user.imageUrlCached == null });
     if (noImageCacheUsers.length === 0) {
@@ -402,7 +384,7 @@ export default class PageContainer extends Container {
     // PageEditor component
     const pageEditor = this.appContainer.getComponentInstance('PageEditor');
     if (pageEditor != null) {
-      if (editorMode !== 'edit') {
+      if (editorMode !== EditorMode.Editor) {
         pageEditor.updateEditorValue(newState.markdown);
       }
     }
@@ -410,7 +392,7 @@ export default class PageContainer extends Container {
     const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
     if (pageEditorByHackmd != null) {
       // reset
-      if (editorMode !== 'hackmd') {
+      if (editorMode !== EditorMode.HackMD) {
         pageEditorByHackmd.reset();
       }
     }

+ 0 - 85
packages/app/src/components/BookmarkButton.jsx

@@ -1,85 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UncontrolledTooltip } from 'reactstrap';
-import { withTranslation } from 'react-i18next';
-import { withUnstatedContainers } from './UnstatedUtils';
-
-import { toastError } from '~/client/util/apiNotification';
-import PageContainer from '~/client/services/PageContainer';
-import AppContainer from '~/client/services/AppContainer';
-
-class BookmarkButton extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  async handleClick() {
-    const { appContainer, pageContainer } = this.props;
-    const { isGuestUser } = appContainer;
-
-    if (isGuestUser) {
-      return;
-    }
-
-    try {
-      pageContainer.toggleBookmark();
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-
-  render() {
-    const { appContainer, pageContainer, t } = this.props;
-    const { isGuestUser } = appContainer;
-
-    return (
-      <div>
-        <button
-          type="button"
-          id="bookmark-button"
-          onClick={this.handleClick}
-          className={`btn btn-bookmark border-0
-          ${`btn-${this.props.size}`} ${pageContainer.state.isBookmarked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
-        >
-          <i className="icon-star mr-3"></i>
-          <span className="total-bookmarks">
-            {pageContainer.state.sumOfBookmarks}
-          </span>
-        </button>
-
-        {isGuestUser && (
-          <UncontrolledTooltip placement="top" target="bookmark-button" fade={false}>
-            {t('Not available for guest')}
-          </UncontrolledTooltip>
-        )}
-      </div>
-    );
-  }
-
-}
-
-/**
- * Wrapper component for using unstated
- */
-const BookmarkButtonWrapper = withUnstatedContainers(BookmarkButton, [AppContainer, PageContainer]);
-
-BookmarkButton.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-
-  pageId: PropTypes.string,
-  t: PropTypes.func.isRequired,
-  size: PropTypes.string,
-};
-
-BookmarkButton.defaultProps = {
-  size: 'md',
-};
-
-export default withTranslation()(BookmarkButtonWrapper);

+ 83 - 0
packages/app/src/components/BookmarkButtons.tsx

@@ -0,0 +1,83 @@
+import React, { FC, useState } from 'react';
+
+import { Types } from 'mongoose';
+import { UncontrolledTooltip, Popover, PopoverBody } from 'reactstrap';
+import { useTranslation } from 'react-i18next';
+
+import UserPictureList from './User/UserPictureList';
+import { toastError } from '~/client/util/apiNotification';
+import { useIsGuestUser } from '~/stores/context';
+import { useSWRxBookmarksInfo } from '~/stores/bookmarks';
+import { apiv3Put } from '~/client/util/apiv3-client';
+
+interface Props {
+  pageId: Types.ObjectId
+}
+
+const BookmarkButton: FC<Props> = (props: Props) => {
+  const { t } = useTranslation();
+  const { pageId } = props;
+
+  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: bookmarksInfo, mutate } = useSWRxBookmarksInfo(pageId);
+
+  const isBookmarked = bookmarksInfo?.isBookmarked != null ? bookmarksInfo.isBookmarked : false;
+  const sumOfBookmarks = bookmarksInfo?.sumOfBookmarks != null ? bookmarksInfo.sumOfBookmarks : 0;
+  const bookmarkedUsers = bookmarksInfo?.bookmarkedUsers != null ? bookmarksInfo.bookmarkedUsers : [];
+
+  const togglePopover = () => {
+    setIsPopoverOpen(!isPopoverOpen);
+  };
+
+  const handleClick = async() => {
+    if (isGuestUser) {
+      return;
+    }
+
+    try {
+      const res = await apiv3Put('/bookmarks', { pageId, bool: !isBookmarked });
+      if (res) {
+        mutate();
+      }
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
+
+  return (
+    <div className="btn-group" role="group" aria-label="Bookmark buttons">
+      <button
+        type="button"
+        id="bookmark-button"
+        onClick={handleClick}
+        className={`btn btn-bookmark border-0
+          ${isBookmarked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
+      >
+        <i className="icon-star"></i>
+      </button>
+
+      {isGuestUser && (
+        <UncontrolledTooltip placement="top" target="bookmark-button" fade={false}>
+          {t('Not available for guest')}
+        </UncontrolledTooltip>
+      )}
+
+      <button type="button" id="po-total-bookmarks" className={`btn btn-bookmark border-0 total-bookmarks ${isBookmarked ? 'active' : ''}`}>
+        {sumOfBookmarks}
+      </button>
+
+      <Popover placement="bottom" isOpen={isPopoverOpen} target="po-total-bookmarks" toggle={togglePopover} trigger="legacy">
+        <PopoverBody className="seen-user-popover">
+          <div className="px-2 text-right user-list-content text-truncate text-muted">
+            {bookmarkedUsers.length ? <UserPictureList users={bookmarkedUsers} /> : t('No users have bookmarked yet')}
+          </div>
+        </PopoverBody>
+      </Popover>
+    </div>
+  );
+};
+
+export default BookmarkButton;

+ 26 - 12
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx

@@ -32,7 +32,7 @@ const GrowiSubNavigationSwitcher = (props) => {
   const fixedContainerRef = useRef();
   const stickyEvents = useMemo(() => new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' }), []);
 
-  const resetWidth = useCallback(() => {
+  const initWidth = useCallback(() => {
     const instance = fixedContainerRef.current;
 
     if (instance == null || instance.parentNode == null) {
@@ -45,9 +45,22 @@ const GrowiSubNavigationSwitcher = (props) => {
     setWidth(clientWidth);
   }, []);
 
+  const initVisible = useCallback(() => {
+    const elements = stickyEvents.stickyElements;
+
+    for (const elem of elements) {
+      const bool = stickyEvents.isSticking(elem);
+      if (bool) {
+        setVisible(bool);
+        break;
+      }
+    }
+
+  }, [stickyEvents]);
+
   // setup effect by resizing event
   useEffect(() => {
-    const resizeHandler = debounce(100, resetWidth);
+    const resizeHandler = debounce(100, initWidth);
 
     window.addEventListener('resize', resizeHandler);
 
@@ -55,7 +68,7 @@ const GrowiSubNavigationSwitcher = (props) => {
     return () => {
       window.removeEventListener('resize', resizeHandler);
     };
-  }, [resetWidth]);
+  }, [initWidth]);
 
   const stickyChangeHandler = useCallback((event) => {
     logger.debug('StickyEvents.CHANGE detected');
@@ -79,19 +92,20 @@ const GrowiSubNavigationSwitcher = (props) => {
   // update width when sidebar collapsing changed
   useEffect(() => {
     if (isSidebarCollapsed != null) {
-      setTimeout(resetWidth, 300);
+      setTimeout(initWidth, 300);
     }
-  }, [isSidebarCollapsed, resetWidth]);
+  }, [isSidebarCollapsed, initWidth]);
 
   // initialize
   useEffect(() => {
-    resetWidth();
-    setTimeout(() => {
-      stickyEvents.state.values((sticky) => {
-        setVisible(sticky.isSticky);
-      });
-    }, 100);
-  }, [resetWidth, stickyEvents]);
+    initWidth();
+
+    // check sticky state several times
+    setTimeout(initVisible, 100);
+    setTimeout(initVisible, 300);
+    setTimeout(initVisible, 2000);
+
+  }, [initWidth, initVisible]);
 
   return (
     <div className={`grw-subnav-switcher ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>

+ 5 - 3
packages/app/src/components/Navbar/SubNavButtons.jsx

@@ -2,10 +2,11 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import AppContainer from '~/client/services/AppContainer';
 import PageContainer from '~/client/services/PageContainer';
+import { usePageId } from '~/stores/context';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import { withUnstatedContainers } from '../UnstatedUtils';
 
-import BookmarkButton from '../BookmarkButton';
+import BookmarkButtons from '../BookmarkButtons';
 import LikeButtons from '../LikeButtons';
 import SubscribeButton from '../SubscribeButton';
 import PageManagement from '../Page/PageManagement';
@@ -15,6 +16,7 @@ const SubnavButtons = React.memo((props) => {
     appContainer, pageContainer, isCompactMode,
   } = props;
 
+  const { data: pageId } = usePageId();
   const { data: editorMode } = useEditorMode();
 
   /* eslint-disable react/prop-types */
@@ -23,7 +25,7 @@ const SubnavButtons = React.memo((props) => {
     return (
       <>
         <span>
-          <SubscribeButton pageId={pageContainer.state.pageId} />
+          <SubscribeButton pageId={pageId} />
         </span>
         {pageContainer.isAbleToShowLikeButtons && (
           <span>
@@ -31,7 +33,7 @@ const SubnavButtons = React.memo((props) => {
           </span>
         )}
         <span>
-          <BookmarkButton />
+          <BookmarkButtons pageId={pageId} />
         </span>
       </>
     );

+ 10 - 2
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
 
 import urljoin from 'url-join';
 import * as codemirror from 'codemirror';
+import { UnControlled as UncontrolledCodeMirror } from 'react-codemirror2';
 
 import { Button } from 'reactstrap';
 
@@ -31,7 +32,7 @@ import LinkEditModal from './LinkEditModal';
 import HandsontableModal from './HandsontableModal';
 import EditorIcon from './EditorIcon';
 import DrawioModal from './DrawioModal';
-import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
+// import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 
 // Textlint
 window.JSHINT = JSHINT;
@@ -109,7 +110,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
     this.state = {
       value: this.props.value,
-      isGfmMode: this.props.isGfmMode,
+      isGfmMode: this.props.isGfmMode ?? true,
       isEnabledEmojiAutoComplete: false,
       isLoadingKeymap: false,
       isSimpleCheatsheetShown: this.props.isGfmMode && this.props.value.length === 0,
@@ -908,6 +909,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
   }
 
   render() {
+    const mode = this.state.isGfmMode ? 'gfm-growi' : undefined;
     const lint = this.props.isTextlintEnabled ? this.codemirrorLintConfig : false;
     const additionalClasses = Array.from(this.state.additionalClassSet).join(' ');
     const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plain Text..';
@@ -934,6 +936,12 @@ export default class CodeMirrorEditor extends AbstractEditor {
           }}
           value={this.state.value}
           options={{
+            mode,
+            theme: this.props.editorOptions.theme,
+            styleActiveLine: this.props.editorOptions.styleActiveLine,
+            lineNumbers: this.props.lineNumbers,
+            tabSize: 4,
+            indentUnit: this.props.indentSize,
             lineWrapping: true,
             scrollPastEnd: true,
             autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei

+ 1 - 3
packages/app/src/components/SubscribeButton.tsx

@@ -1,7 +1,5 @@
 import React, { FC } from 'react';
 
-
-import { Types } from 'mongoose';
 import { useTranslation } from 'react-i18next';
 import { UncontrolledTooltip } from 'reactstrap';
 import { useSWRxSubscriptionStatus } from '../stores/page';
@@ -12,7 +10,7 @@ import { apiv3Put } from '~/client/util/apiv3-client';
 import { useIsGuestUser } from '~/stores/context';
 
 type Props = {
-  pageId: Types.ObjectId,
+  pageId: string,
 };
 
 const SubscribeButton: FC<Props> = (props: Props) => {

+ 7 - 0
packages/app/src/interfaces/bookmarks.ts

@@ -0,0 +1,7 @@
+import { IUser } from '~/interfaces/user';
+
+export interface IBookmarksInfo {
+  isBookmarked: boolean
+  sumOfBookmarks: number
+  bookmarkedUsers: IUser[]
+}

+ 8 - 2
packages/app/src/server/routes/apiv3/bookmarks.js

@@ -113,10 +113,16 @@ module.exports = (crowi) => {
     const responsesParams = {};
 
     try {
-      responsesParams.sumOfBookmarks = await Bookmark.countByPageId(pageId);
+      const bookmarks = await Bookmark.find({ page: pageId }).populate('user');
+      let users = [];
+      if (bookmarks.length > 0) {
+        users = bookmarks.map(bookmark => serializeUserSecurely(bookmark.user));
+      }
+      responsesParams.sumOfBookmarks = bookmarks.length;
+      responsesParams.bookmarkedUsers = users;
     }
     catch (err) {
-      logger.error('get-bookmark-count-failed', err);
+      logger.error('get-bookmark-document-failed', err);
       return res.apiv3Err(err, 500);
     }
 

+ 21 - 0
packages/app/src/stores/bookmarks.tsx

@@ -0,0 +1,21 @@
+import useSWR, { SWRResponse } from 'swr';
+
+import { Types } from 'mongoose';
+import { apiv3Get } from '~/client/util/apiv3-client';
+
+import { IBookmarksInfo } from '~/interfaces/bookmarks';
+
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export const useSWRxBookmarksInfo = <Data, Error>(pageId: Types.ObjectId):SWRResponse<IBookmarksInfo, Error> => {
+  return useSWR(
+    ['/bookmarks/info', pageId],
+    (endpoint, pageId) => apiv3Get(endpoint, { pageId }).then((response) => {
+      return {
+        isBookmarked: response.data.isBookmarked,
+        sumOfBookmarks: response.data.sumOfBookmarks,
+        bookmarkedUsers: response.data.bookmarkedUsers,
+      };
+    }),
+  );
+};

+ 2 - 2
packages/app/src/stores/context.tsx

@@ -22,8 +22,8 @@ export const useCurrentPagePath = (initialData?: Nullable<string>): SWRResponse<
 };
 
 
-export const usePageId = (initialData?: Nullable<string>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('pageId', initialData ?? null);
+export const usePageId = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
+  return useStaticSWR<Nullable<string>, Error>('pageId', initialData ?? null);
 };
 
 export const useRevisionCreatedAt = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {

+ 4 - 2
packages/app/src/stores/page.tsx

@@ -47,14 +47,16 @@ export const useSWRxPageList = (
   );
 };
 
+type GetSubscriptionStatusResult = { subscribing: boolean };
+
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const useSWRxSubscriptionStatus = <Data, Error>(pageId: Types.ObjectId): SWRResponse<{status: boolean | null}, Error> => {
+export const useSWRxSubscriptionStatus = <Data, Error>(pageId: string): SWRResponse<{status: boolean | null}, Error> => {
   const { data: isGuestUser } = useIsGuestUser();
 
   const key = isGuestUser === false ? ['/page/subscribe', pageId] : null;
   return useSWR(
     key,
-    (endpoint, pageId) => apiv3Get(endpoint, { pageId }).then((response) => {
+    (endpoint, pageId) => apiv3Get<GetSubscriptionStatusResult>(endpoint, { pageId }).then((response) => {
       return {
         status: response.data.subscribing,
       };

+ 1 - 1
packages/codemirror-textlint/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/codemirror-textlint",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "license": "MIT",
   "main": "dist/index.js",
   "scripts": {

+ 1 - 2
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [
@@ -16,7 +16,6 @@
     "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",
     "build:esm": "tsc -p tsconfig.build.esm.json && tsc-alias -p tsconfig.build.esm.json",
     "lint:js": "eslint **/*.{js,ts}",
-    "lint:styles": "stylelint src/styles/scss/**/*.scss",
     "lint": "npm-run-all -p lint:*",
     "test": "jest --verbose"
   },

+ 2 - 3
packages/plugin-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-attachment-refs",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [
@@ -17,7 +17,7 @@
     "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",
     "build:esm": "tsc -p tsconfig.build.esm.json && tsc-alias -p tsconfig.build.esm.json",
     "lint:js": "eslint **/*.{js,jsx,ts,tsx}",
-    "lint:styles": "stylelint src/**/*.scss",
+    "lint:styles": "stylelint src/**/*.scss src/**/*.css",
     "lint": "run-p lint:*",
     "test": ""
   },
@@ -31,7 +31,6 @@
   },
   "devDependencies": {
     "npm-run-all": "^4.1.5",
-    "prettier-stylelint": "^0.4.2",
     "react": "^16.8.3",
     "react-dom": "^16.8.3"
   }

+ 2 - 2
packages/plugin-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-lsx",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [
@@ -17,7 +17,7 @@
     "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",
     "build:esm": "tsc -p tsconfig.build.esm.json && tsc-alias -p tsconfig.build.esm.json",
     "lint:js": "eslint **/*.{js,jsx,ts,tsx}",
-    "lint:styles": "stylelint src/**/*.scss",
+    "lint:styles": "stylelint src/**/*.scss src/**/*.css",
     "lint": "run-p lint:*",
     "test": ""
   },

+ 1 - 3
packages/plugin-pukiwiki-like-linker/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-pukiwiki-like-linker",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "description": "GROWI plugin to add PukiwikiLikeLinker",
   "license": "MIT",
   "keywords": [
@@ -22,8 +22,6 @@
   },
   "devDependencies": {
     "browser-bunyan": "^1.6.3",
-    "stylelint": "^14.0.1",
-    "stylelint-config-recess-order": "^2.0.1",
     "tsc-alias": "^1.2.9"
   }
 }

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "license": "MIT",
   "main": "dist/index.js",
   "typings": "dist/index.d.ts",

+ 2 - 2
packages/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "4.5.7-slackbot-proxy.0",
+  "version": "4.5.9-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
@@ -25,7 +25,7 @@
   },
   "dependencies": {
     "@godaddy/terminus": "^4.9.0",
-    "@growi/slack": "^4.5.7-RC.0",
+    "@growi/slack": "^4.5.9-RC.0",
     "@slack/oauth": "^2.0.1",
     "@slack/web-api": "^6.2.4",
     "@tsed/common": "^6.43.0",

+ 1 - 2
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "4.5.7-RC.0",
+  "version": "4.5.9-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [
@@ -12,7 +12,6 @@
   ],
   "scripts": {
     "lint:js": "eslint **/*.{js,jsx,ts,tsx}",
-    "lint:styles": "stylelint src/styles/scss/**/*.scss",
     "lint": "npm-run-all -p lint:*",
     "test": "jest --verbose"
   },

Разница между файлами не показана из-за своего большого размера
+ 54 - 452
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов