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

Merge branch 'support/share-link-for-outside-for-merge' into improve/manage-share_link_for_outside

熊谷洸介(Kousuke Kumagai) 5 лет назад
Родитель
Сommit
2019253231

+ 1 - 0
resource/locales/en-US/translation.json

@@ -47,6 +47,7 @@
   "History": "History",
   "Presentation Mode": "Presentation",
   "Not available for guest": "Not available for guest",
+  "Shere this page link to public": "Shere this page link to public",
   "username": "Username",
   "Created": "Created",
   "Last updated": "Updated",

+ 1 - 0
resource/locales/ja/translation.json

@@ -47,6 +47,7 @@
   "History": "更新履歴",
   "Presentation Mode": "プレゼンテーション",
   "Not available for guest": "ゲストユーザーは利用できません",
+  "Shere this page link to public": "外部に共有するリンクを発行する",
   "username": "ユーザー名",
   "Created": "作成日",
   "Last updated": "最終更新",

+ 2 - 0
src/client/js/app.jsx

@@ -22,6 +22,7 @@ import PageComments from './components/PageComments';
 import PageTimeline from './components/PageTimeline';
 import CommentEditorLazyRenderer from './components/PageComment/CommentEditorLazyRenderer';
 import PageManagement from './components/Page/PageManagement';
+import PageShareManagement from './components/Page/PageShareManagement';
 import TrashPageAlert from './components/Page/TrashPageAlert';
 import PageAttachment from './components/PageAttachment';
 import PageStatusAlert from './components/PageStatusAlert';
@@ -91,6 +92,7 @@ if (pageContainer.state.pageId != null) {
     'page-attachment': <PageAttachment />,
     'page-comment-write': <CommentEditorLazyRenderer />,
     'page-management': <PageManagement />,
+    'page-share-management': <PageShareManagement />,
 
     'revision-toc': <TableOfContents />,
     'seen-user-list': <UserPictureList userIds={pageContainer.state.seenUserIds} />,

+ 50 - 0
src/client/js/components/OutsideShareLinkModal.jsx

@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from './UnstatedUtils';
+
+import AppContainer from '../services/AppContainer';
+import PageContainer from '../services/PageContainer';
+
+const OutsideShareLinkModal = (props) => {
+
+  /* const { t } = props; */
+
+
+  return (
+    <Modal size="lg" isOpen={props.isOpen} toggle={props.onClose} className="grw-create-page">
+      <ModalHeader tag="h4" toggle={props.onClose} className="bg-primary text-light">Hi there!
+      </ModalHeader>
+      <ModalBody>
+        <h1>Hi there</h1>
+      </ModalBody>
+      <ModalFooter>
+      </ModalFooter>
+    </Modal>
+  );
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const ModalControlWrapper = (props) => {
+  return createSubscribedElement(OutsideShareLinkModal, props, [AppContainer, PageContainer]);
+};
+
+
+OutsideShareLinkModal.propTypes = {
+  t: PropTypes.func.isRequired, //  i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+
+  isOpen: PropTypes.bool.isRequired,
+  onClose: PropTypes.func.isRequired,
+};
+
+export default withTranslation()(ModalControlWrapper);

+ 98 - 0
src/client/js/components/Page/PageShareManagement.jsx

@@ -0,0 +1,98 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { UncontrolledTooltip } from 'reactstrap';
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from '../UnstatedUtils';
+import AppContainer from '../../services/AppContainer';
+import PageContainer from '../../services/PageContainer';
+import OutsideShareLinkModal from '../OutsideShareLinkModal';
+
+
+const PageShareManagement = (props) => {
+  const { t, appContainer/* , pageContainer */ } = props;
+
+  const { currentUser } = appContainer;
+
+  const [isOutsideShareLinkModalShown, setIsOutsideShareLinkModalShown] = useState(false);
+
+  function openOutsideShareLinkModalHandler() {
+    setIsOutsideShareLinkModalShown(true);
+  }
+
+  function closeOutsideShareLinkModalHandler() {
+    setIsOutsideShareLinkModalShown(false);
+  }
+
+  function renderModals() {
+    return (
+      <>
+        <OutsideShareLinkModal
+          isOpen={isOutsideShareLinkModalShown}
+          onClose={closeOutsideShareLinkModalHandler}
+        />
+      </>
+    );
+  }
+
+  function renderCurrentUser() {
+    return (
+      <>
+        <button
+          type="button"
+          className="nav-link bg-transparent dropdown-toggle dropdown-toggle-no-caret"
+          data-toggle="dropdown"
+        >
+          <i className="icon-share"></i>
+        </button>
+      </>
+    );
+  }
+
+  function renderGuestUser() {
+    return (
+      <>
+        <button
+          type="button"
+          className="nav-link bg-transparent"
+          id="auth-guest-tltips"
+        >
+          <i className="icon-share"></i>
+        </button>
+        <UncontrolledTooltip placement="top" target="auth-guest-tltips">
+          {t('Not available for guest')}
+        </UncontrolledTooltip>
+      </>
+    );
+  }
+
+
+  return (
+    <>
+      {currentUser == null ? renderGuestUser() : renderCurrentUser()}
+      <div className="dropdown-menu dropdown-menu-right">
+        <button className="dropdown-item" type="button" onClick={openOutsideShareLinkModalHandler}>
+          <i className="icon-link"></i> {t('Shere this page link to public')}
+        </button>
+      </div>
+      {renderModals()}
+    </>
+  );
+
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const PageShareManagementWrapper = (props) => {
+  return createSubscribedElement(PageShareManagement, props, [AppContainer, PageContainer]);
+};
+
+
+PageShareManagement.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+};
+
+export default withTranslation()(PageShareManagementWrapper);

+ 3 - 3
src/client/js/components/Sidebar/SidebarNav.jsx

@@ -53,7 +53,7 @@ class SidebarNav extends React.Component {
   }
 
   render() {
-    const { isAdmin, currentUsername } = this.props.appContainer;
+    const { isAdmin, currentUsername, isSharedUser } = this.props.appContainer;
     const isLoggedIn = currentUsername != null;
 
     const { PrimaryItem, SecondaryItem } = this;
@@ -61,8 +61,8 @@ class SidebarNav extends React.Component {
     return (
       <div className="grw-sidebar-nav d-flex flex-column justify-content-between pb-4">
         <div className="grw-sidebar-nav-primary-container">
-          <PrimaryItem id="custom" label="Custom Sidebar" iconName="code" />
-          <PrimaryItem id="recent" label="Recent Changes" iconName="update" />
+          {!isSharedUser && <PrimaryItem id="custom" label="Custom Sidebar" iconName="code" />}
+          {!isSharedUser && <PrimaryItem id="recent" label="Recent Changes" iconName="update" />}
           {/* <PrimaryItem id="tag" label="Tags" iconName="icon-tag" /> */}
           {/* <PrimaryItem id="favorite" label="Favorite" iconName="icon-star" /> */}
         </div>

+ 3 - 0
src/client/js/services/AppContainer.js

@@ -57,6 +57,9 @@ export default class AppContainer extends Container {
       this.currentUser = JSON.parse(currentUserElem.textContent);
     }
 
+    const isSharedPageElem = document.getElementById('is-shared-page');
+    this.isSharedUser = (isSharedPageElem != null);
+
     const userAgent = window.navigator.userAgent.toLowerCase();
     this.isMobile = /iphone|ipad|android/.test(userAgent);
 

+ 1 - 1
src/server/routes/index.js

@@ -180,7 +180,7 @@ module.exports = function(crowi, app) {
   app.post('/_api/hackmd.discard'        , accessTokenParser , loginRequiredStrictly , csrf, hackmd.validateForApi, hackmd.discard);
   app.post('/_api/hackmd.saveOnHackmd'   , accessTokenParser , loginRequiredStrictly , csrf, hackmd.validateForApi, hackmd.saveOnHackmd);
 
-  app.get('/share/:linkId', page.showSharePage, page.notFound);
+  app.get('/share/:linkId', page.showSharedPage, page.notFound);
 
   app.get('/*/$'                   , loginRequired , page.showPageWithEndOfSlash, page.notFound);
   app.get('/*'                     , loginRequired , page.showPage, page.notFound);

+ 4 - 4
src/server/routes/page.js

@@ -440,19 +440,19 @@ module.exports = function(crowi, app) {
     return showPageForGrowiBehavior(req, res, next);
   };
 
-  actions.showSharePage = async function(req, res, next) {
+  actions.showSharedPage = async function(req, res, next) {
     const { linkId } = req.params;
 
     const layoutName = configManager.getConfig('crowi', 'customize:layout');
-    // TODO Consider the layout for share
-    const view = `layout-${layoutName}/page`;
+    const view = `layout-${layoutName}/shared_page`;
 
     const shareLink = await ShareLink.find({ _id: linkId }).populate('Page');
     const page = shareLink.relatedPage;
 
     if (page == null) {
       // page is not found
-      return next();
+      // TODO GW-2735 create not found page
+      // return res.render(`layout-${layoutName}/not_found_shared_page`);
     }
 
     const renderVars = {};

+ 44 - 0
src/server/views/layout-growi/shared_page.html

@@ -0,0 +1,44 @@
+{% extends 'base/layout.html' %}
+
+
+{% block content_header %}
+  <h1 class="p-3">{{ encodeURI(page.path) }}</h1>
+{% endblock %}
+
+
+{% block content_main_before %}
+{% endblock %}
+{% block search %}
+{% endblock %}
+{% block head_warn_alert_siteurl_undefined %}
+{% endblock %}
+
+{% block content_main %}
+  <div class="row" id="is-shared-page">
+    <div class="col grw-page-content-container">
+
+      {% include '../widget/page_content.html' %}
+      {# force remove #revision-toc from #content_main of parent #}
+      <script>
+        $('#revision-toc').remove();
+      </script>
+
+    </div>
+
+    {# relocate #revision-toc #}
+    <div class="col-xl-2 col-lg-3 d-none d-lg-block revision-toc-container">
+      <div id="revision-toc" class="revision-toc mt-3 sps sps--abv" data-sps-offset="123">
+        <div id="revision-toc-content" class="revision-toc-content"></div>
+      </div>
+    </div>
+
+  </div>
+
+{% endblock %}
+
+
+{% block body_end %}
+  <div id="presentation-layer" class="fullscreen-layer">
+    <div id="presentation-container"></div>
+  </div>
+{% endblock %}

+ 43 - 0
src/server/views/layout-kibela/shared_page.html

@@ -0,0 +1,43 @@
+{% extends 'base/layout.html' %}
+
+
+{% block content_header %}
+  <h1 class="p-3">{{ encodeURI(page.path) }}</h1>
+{% endblock %}
+
+
+{% block content_main_before %}
+{% endblock %}
+{% block search %}
+{% endblock %}
+{% block head_warn_alert_siteurl_undefined %}
+{% endblock %}
+
+{% block content_main %}
+  <div class="row" id="is-shared-page">
+    <div class="col-12 col-xl-9 col-lg-8 bg-white round-corner">
+
+      {% include '../widget/page_content.html' %}
+      {# force remove #revision-toc from #content_main of parent #}
+      <script>
+        $('#revision-toc').remove();
+      </script>
+
+    </div>
+
+    {# relocate #revision-toc #}
+    <div class="col-xl-3 col-lg-4 d-none d-lg-block revision-toc-container">
+      <div id="revision-toc" class="revision-toc mt-3 sps sps--abv" data-sps-offset="123">
+        <div id="revision-toc-content" class="revision-toc-content"></div>
+      </div>
+    </div>
+
+  </div>
+{% endblock %}
+
+
+{% block body_end %}
+  <div id="presentation-layer" class="fullscreen-layer">
+    <div id="presentation-container"></div>
+  </div>
+{% endblock %}

+ 5 - 4
src/server/views/layout/layout.html

@@ -121,10 +121,11 @@
     <div class="flex-fill mw-0">
       {% block head_warn_alert_siteurl_undefined %}{% include '../widget/alert_siteurl_undefined.html' %}{% endblock %}
 
-      {# Search #}
-      {% if isSearchServiceConfigured() %}
-        <div id="grw-search-top" class="search-top" role="search"></div>
-      {% endif %}
+      {% block search %}
+        {% if isSearchServiceConfigured() %}
+          <div id="grw-search-top" class="search-top" role="search"></div>
+        {% endif %}
+      {% endblock %}
 
       {% block layout_main %}{% endblock %}
     </div>

+ 5 - 0
src/server/views/widget/page_tabs.html

@@ -63,6 +63,11 @@
     </a>
   </li>
 
+  <!-- Outside-share-link -->
+  {% if !isTrashPage() %}
+    <li id="page-share-management" class="nav-item dropdown"></li>
+  {% endif %}
+  
   <!-- icon-options-vertical -->
   {% if !isTrashPage() %}
     <li id="page-management" class="nav-item dropdown"></li>