Procházet zdrojové kódy

Merge branch 'feat/6982-textlint' into feat/7075-add-textlint-rules

Steven Fukase před 4 roky
rodič
revize
92ca9f1470

+ 1 - 0
CHANGES.md

@@ -2,6 +2,7 @@
 
 ## v4.3.3-RC
 
+* Improvement: Add attachment button in editor navbar
 * Fix: Encode spaces in page path in LinkEditModal
 * Fix: Layout is broken when editing users page ([#4128](https://github.com/weseek/growi/issues/4128))
 * Support: Create @growi/core package

+ 1 - 1
package.json

@@ -31,7 +31,7 @@
   "scripts": {
     "start": "yarn app:server",
     "prestart": "yarn app:build",
-    "app:build": "yarn lerna run build --scope @growi/app --scope @growi/slack --scope @growi/plugin-pukiwiki-like-linker",
+    "app:build": "yarn lerna run build --scope @growi/app --scope @growi/slack --scope @growi/plugin-*",
     "app:server": "yarn lerna run server --scope @growi/app",
     "slackbot-proxy:build": "yarn lerna run build --scope @growi/slackbot-proxy --scope @growi/slack",
     "slackbot-proxy:server": "yarn lerna run start:prod --scope @growi/slackbot-proxy",

+ 1 - 0
packages/app/.env.development

@@ -10,6 +10,7 @@ MONGO_URI="mongodb://mongo:27017/growi"
 # REDIS_URI="http://redis:6379"
 # NCHAN_URI="http://nchan"
 ELASTICSEARCH_URI="http://elasticsearch:9200/growi"
+ELASTICSEARCH_REQUEST_TIMEOUT=15000
 HACKMD_URI="http://localhost:3010"
 HACKMD_URI_FOR_SERVER="http://hackmd:3000"
 # DRAWIO_URI="http://localhost:8080/?offline=1&https=0"

+ 22 - 22
packages/app/resource/locales/en_US/sandbox.md

@@ -37,7 +37,7 @@ Add one `#` per level at the start of the line
 
 ## Block paragraph
 
-Pararaphs are created by inserting a newline character
+Paragraphs are created by inserting a newline character
 A paragraph can be created by pressing Enter at the end of the previous paragraph.
 
 ```
@@ -53,17 +53,17 @@ paragraph2
 ## Br new line
 
 Add two spaces before break.
-***This behaviour can be modified in the options menu.***
+***This behavior can be modified in the options menu.***
 
 ```
-hoge
-fuga(two spaces)
-piyo
+foo
+bar(two spaces)
+baz
 ```
 
-hoge
-fuga
-piyo
+foo
+bar
+baz
 
 ## Blockquotes
 
@@ -84,7 +84,7 @@ Add one `>` per level at the start of the line
 Wrap code with three back quotes or tildes.
 
 ```
-print 'hoge'
+print 'foo'
 ```
 
 ### Syntax highlight and file name
@@ -131,16 +131,16 @@ This is  `Inline Code`.
 Code blocks should be preceded by four spaces or one tab.
 
 ```
-    class Hoge
-        def hoge
-            print 'hoge'
+    class Foo
+        def foo
+            print 'foo'
         end
     end
 ```
 
-    class Hoge
-        def hoge
-            print 'hoge'
+    class Foo
+        def foo
+            print 'foo'
         end
     end
 
@@ -166,7 +166,7 @@ ___
 
 ### Italic
 
-To italicize text, add One asterisk or underscores before and after a word or phrase.
+To italicize text, add one asterisk or underscores before and after a word or phrase.
 
 ```
 This is *Italic* .
@@ -178,7 +178,7 @@ This is _Italic_ .
 
 ### Bold
 
-To bold text, add two asterisks or underscores before and after a word or phrase.
+To make text bold, add two asterisks or underscores before and after a word or phrase.
 
 ```
 This is **bold**.
@@ -263,7 +263,7 @@ Example of Bootstrap4 is[[here>./Bootstrap4]]
 
 ## Ul Bulleted list
 
-To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items. 
+To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items.
 Items can be nested using indentation.
 
 ```
@@ -286,7 +286,7 @@ Items can be nested using indentation.
 
 ## Ol Numbered List
 
-To create an ordered list, add line items with numbers followed by periods. 
+To create an ordered list, add line items with numbers followed by periods.
 The numbers don’t have to be in numerical order, but the list should start with the number one.
 
 ```
@@ -449,9 +449,9 @@ See [emojione](https://www.emojione.com/)
 
 # :heavy_plus_sign: More..
 
-- Try to attach Bootstrap4 Tags?
+- Want to attach Bootstrap4 Tags?
     - :arrow_right: [/Sandbox/Bootstrap4]
-- Try to draw Diagrams?
+- Want to draw Diagrams?
     - :arrow_right: [/Sandbox/Diagrams]
-- Try to write Math Formulas?
+- Want to write Math Formulas?
     - :arrow_right: [/Sandbox/Math]

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

@@ -837,6 +837,15 @@ export default class CodeMirrorEditor extends AbstractEditor {
       >
         <EditorIcon icon="CheckList" />
       </Button>,
+      <Button
+        key="nav-item-attachment"
+        color={null}
+        size="sm"
+        title="Attachment"
+        onClick={this.props.onAddAttachmentButtonClicked}
+      >
+        <EditorIcon icon="Attachment" />
+      </Button>,
       <Button
         key="nav-item-link"
         color={null}
@@ -992,6 +1001,7 @@ CodeMirrorEditor.propTypes = Object.assign({
   emojiStrategy: PropTypes.object,
   lineNumbers: PropTypes.bool,
   onMarkdownHelpButtonClicked: PropTypes.func,
+  onAddAttachmentButtonClicked: PropTypes.func,
 }, AbstractEditor.propTypes);
 CodeMirrorEditor.defaultProps = {
   lineNumbers: true,

+ 7 - 1
packages/app/src/components/PageEditor/Editor.jsx

@@ -39,6 +39,7 @@ export default class Editor extends AbstractEditor {
     this.dropHandler = this.dropHandler.bind(this);
 
     this.showMarkdownHelp = this.showMarkdownHelp.bind(this);
+    this.addAttachmentHandler = this.addAttachmentHandler.bind(this);
 
     this.getAcceptableType = this.getAcceptableType.bind(this);
     this.getDropzoneClassName = this.getDropzoneClassName.bind(this);
@@ -187,6 +188,10 @@ export default class Editor extends AbstractEditor {
     this.setState({ isCheatsheetModalShown: true });
   }
 
+  addAttachmentHandler() {
+    this.dropzone.open();
+  }
+
   getDropzoneClassName(isDragAccept, isDragReject) {
     let className = 'dropzone';
     if (!this.props.isUploadable) {
@@ -314,6 +319,7 @@ export default class Editor extends AbstractEditor {
                         onPasteFiles={this.pasteFilesHandler}
                         onDragEnter={this.dragEnterHandler}
                         onMarkdownHelpButtonClicked={this.showMarkdownHelp}
+                        onAddAttachmentButtonClicked={this.addAttachmentHandler}
                         {...this.props}
                       />
                     )}
@@ -341,7 +347,7 @@ export default class Editor extends AbstractEditor {
             <button
               type="button"
               className="btn btn-outline-secondary btn-block btn-open-dropzone"
-              onClick={() => { this.dropzone.open() }}
+              onClick={this.addAttachmentHandler}
             >
               <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
               Attach files

+ 7 - 9
packages/app/src/components/PageEditor/EditorIcon.jsx

@@ -118,15 +118,13 @@ const EditorIcon = (props) => {
           <path d="M22.12,17H19.75l-3.12-4H18a1,1,0,0,0,1-1V8a1,1,0,0,0-1-1H12a1,1,0,0,0-1,1v4a1,1,0,0,0,1,1h1.38l-2.92,4H7.88A.94.94,0,0,0,7,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,14,22V18a.94.94,0,0,0-.88-1H11.63l3.13-4h.47l3.13,4H16.88A.94.94,0,0,0,16,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,23,22V18A.94.94,0,0,0,22.12,17ZM13,22H8V18h5ZM12,8h6v4H12ZM22,22H17V18h5Z" />
         </svg>
       );
-    // Unused icon
-    // case 'attachment':
-    //   return (
-    //     <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-    //       <rect fillOpacity="0" width="30" height="30" />
-    //       <path d="M9.71,22.5a2.57,2.57,0,0,1-1.85-.79,2.79,2.79,0,0,1,0-4l9-9.23a3.21,3.21,0,0,1,1.59-.87,3.39,3.39,0,0,1,1.81.1,4.38,4.38,0,0,1,1.7,1.05,4.15,4.15,0,0,1,.46.56,3.73,3.73,0,0,1,.35.65,4.25,4.25,0,0,1,.2.72,3.91,3.91,0,0,1,.07.76,3.71,3.71,0,0,1-1.12,2.67l-6.79,7a.48.48,0,0,1-.34.16.51.51,0,0,1-.35-.13.48.48,0,0,1,0-.7l6.78-7a2.8,2.8,0,0,0,.84-2,2.58,2.58,0,0,0-.79-2,3.63,3.63,0,0,0-1.11-.75,2.41,2.41,0,0,0-1.31-.17,2.19,2.19,0,0,0-1.25.62l-9,9.22A1.8,1.8,0,0,0,8,19.69,1.78,1.78,0,0,0,8.58,21a1.81,1.81,0,0,0,.57.39,1.48,1.48,0,0,0,.66.1,2,2,0,0,0,1.28-.62l7.12-7.35.15-.16a1.15,1.15,0,0,0,.15-.2.9.9,0,0,0,.12-.24,1.17,1.17,0,0,0,.07-.25.52.52,0,0,0-.05-.27.75.75,0,0,0-.19-.26.73.73,0,0,0-.58-.27,1.29,1.29,0,0,0-.67.38l-5.36,5.53a.5.5,0,0,1-.22.13.46.46,0,0,1-.26,0,.48.48,0,0,1-.22-.12A.41.41,0,0,1,11,17.5a.5.5,0,0,1,.14-.35L16.5,11.6a2.19,2.19,0,0,1,1.29-.67,1.69,1.69,0,0,1,1.37.55,1.54,1.54,0,0,1,.53,1.31,2.26,2.26,0,0,1-.76,1.42L11.8,21.58a3.06,3.06,0,0,1-2,.91H9.71Z" />
-    //     </svg>
-    // );
-
+    case 'Attachment':
+      return (
+        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
+          <rect fillOpacity="0" width="30" height="30" />
+          <path d="M9.71,22.5a2.57,2.57,0,0,1-1.85-.79,2.79,2.79,0,0,1,0-4l9-9.23a3.21,3.21,0,0,1,1.59-.87,3.39,3.39,0,0,1,1.81.1,4.38,4.38,0,0,1,1.7,1.05,4.15,4.15,0,0,1,.46.56,3.73,3.73,0,0,1,.35.65,4.25,4.25,0,0,1,.2.72,3.91,3.91,0,0,1,.07.76,3.71,3.71,0,0,1-1.12,2.67l-6.79,7a.48.48,0,0,1-.34.16.51.51,0,0,1-.35-.13.48.48,0,0,1,0-.7l6.78-7a2.8,2.8,0,0,0,.84-2,2.58,2.58,0,0,0-.79-2,3.63,3.63,0,0,0-1.11-.75,2.41,2.41,0,0,0-1.31-.17,2.19,2.19,0,0,0-1.25.62l-9,9.22A1.8,1.8,0,0,0,8,19.69,1.78,1.78,0,0,0,8.58,21a1.81,1.81,0,0,0,.57.39,1.48,1.48,0,0,0,.66.1,2,2,0,0,0,1.28-.62l7.12-7.35.15-.16a1.15,1.15,0,0,0,.15-.2.9.9,0,0,0,.12-.24,1.17,1.17,0,0,0,.07-.25.52.52,0,0,0-.05-.27.75.75,0,0,0-.19-.26.73.73,0,0,0-.58-.27,1.29,1.29,0,0,0-.67.38l-5.36,5.53a.5.5,0,0,1-.22.13.46.46,0,0,1-.26,0,.48.48,0,0,1-.22-.12A.41.41,0,0,1,11,17.5a.5.5,0,0,1,.14-.35L16.5,11.6a2.19,2.19,0,0,1,1.29-.67,1.69,1.69,0,0,1,1.37.55,1.54,1.54,0,0,1,.53,1.31,2.26,2.26,0,0,1-.76,1.42L11.8,21.58a3.06,3.06,0,0,1-2,.91H9.71Z" />
+        </svg>
+      );
   }
 
 

+ 2 - 0
packages/slack/src/index.ts

@@ -26,7 +26,9 @@ export * from './utils/block-kit-builder';
 export * from './utils/check-communicable';
 export * from './utils/get-supported-growi-actions-regexps';
 export * from './utils/post-ephemeral-errors';
+export * from './utils/publish-initial-home-view';
 export * from './utils/reshape-contents-body';
 export * from './utils/slash-command-parser';
 export * from './utils/webclient-factory';
+export * from './utils/welcome-message';
 export * from './utils/required-scopes';

+ 28 - 0
packages/slack/src/utils/publish-initial-home-view.ts

@@ -0,0 +1,28 @@
+import { ViewsPublishResponse, WebClient } from '@slack/web-api';
+
+export const publishInitialHomeView = (client: WebClient, userId: string): Promise<ViewsPublishResponse> => {
+  return client.views.publish({
+    user_id: userId,
+    view: {
+      type: 'home',
+      blocks: [
+        {
+          type: 'section',
+          text: {
+            type: 'mrkdwn',
+            text: 'Welcome GROWI Official Bot Home',
+          },
+        },
+        {
+          type: 'section',
+          text: {
+            type: 'mrkdwn',
+            text: 'Learn how to use GROWI Official bot.'
+            // eslint-disable-next-line max-len
+              + 'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.',
+          },
+        },
+      ],
+    },
+  });
+};

+ 21 - 0
packages/slack/src/utils/welcome-message.ts

@@ -0,0 +1,21 @@
+import { ChatPostMessageResponse, WebClient } from '@slack/web-api';
+
+export const postWelcomeMessage = (client: WebClient, userId: string): Promise<ChatPostMessageResponse> => {
+  return client.chat.postMessage({
+    channel: userId,
+    user: userId,
+    blocks: [
+      {
+        type: 'section',
+        text: {
+          type: 'mrkdwn',
+          text: ':tada: You have successfully installed GROWI Official bot on this Slack workspace.\n'
+            + 'At first you do `/growi register` in the channel that you want to use.\n'
+            + 'Looking for additional help?'
+            // eslint-disable-next-line max-len
+            + 'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.',
+        },
+      },
+    ],
+  });
+};

+ 10 - 1
packages/slackbot-proxy/CHANGES.md

@@ -1,6 +1,15 @@
 # CHANGES
 
-## v1.0.1-RC
+## v1.0.2
 
+* Fix: Label for the install succeeded screen
+
+## v1.0.1
+
+* Improvement: Automatically add GROWI bot user to the channel where the command was executed
+* Improvement: Post a welcome message when the installation is successful
+* Improvement: Post a warning message when none of GROWI permit the command
 * Improvement: Permission scopes
+* Improvement: Top page information
 * Improvement: Add a link to docs
+

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

@@ -37,7 +37,7 @@ RUN tar cf node_modules.tar node_modules \
 FROM deps-resolver-base AS deps-resolver-prod
 RUN npx lerna bootstrap -- --production
 # make artifacts
-RUN tar cf dependencies.tar
+RUN tar cf dependencies.tar \
   node_modules \
   packages/slack/node_modules \
   packages/slackbot-proxy/node_modules

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

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "1.0.1-RC",
+  "version": "1.0.2",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",

+ 62 - 49
packages/slackbot-proxy/src/controllers/slack.ts

@@ -1,14 +1,16 @@
 import {
-  BodyParams, Controller, Get, Inject, Post, Req, Res, UseBefore,
+  BodyParams, Controller, Get, Inject, PlatformResponse, Post, Req, Res, UseBefore,
 } from '@tsed/common';
 
 import axios from 'axios';
 
 import { WebAPICallResult } from '@slack/web-api';
+import { Installation } from '@slack/oauth';
+
 
 import {
   markdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest, generateWebClient,
-  InvalidGrowiCommandError,
+  InvalidGrowiCommandError, requiredScopes, postWelcomeMessage, publishInitialHomeView,
 } from '@growi/slack';
 
 import { Relation } from '~/entities/relation';
@@ -213,7 +215,7 @@ export class SlackCtrl {
 
     // forward to GROWI server
     if (relationsForBroadcastUse.length > 0) {
-      this.sendCommand(growiCommand, relationsForBroadcastUse, body);
+      return this.sendCommand(growiCommand, relationsForBroadcastUse, body);
     }
 
     // when all of GROWI disallowed
@@ -334,57 +336,68 @@ export class SlackCtrl {
   }
 
   @Get('/oauth_redirect')
-  async handleOauthRedirect(@Req() req: Req, @Res() res: Res): Promise<void> {
-
-    if (req.query.state === '') {
-      res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
-      res.end('<html>'
-      + '<head><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
-      + '<body style="text-align:center; padding-top:20%;">'
-      + '<h1>Illegal state, try it again.</h1>'
-      + '<a href="/">'
-      + 'Go to install page'
-      + '</a>'
-      + '</body></html>');
+  async handleOauthRedirect(@Req() req: Req, @Res() serverRes: Res, @Res() platformRes: PlatformResponse): Promise<void|string> {
+
+    // create 'Add to Slack' url
+    const addToSlackUrl = await this.installerService.installer.generateInstallUrl({
+      scopes: requiredScopes,
+    });
+
+    const state = req.query.state;
+    if (state == null || state === '') {
+      return platformRes.status(400).render('install-failed.ejs', { url: addToSlackUrl });
     }
 
-    await this.installerService.installer.handleCallback(req, res, {
-      success: (installation, metadata, req, res) => {
-        logger.info('Success to install', { installation, metadata });
+    // promisify
+    const installPromise = new Promise<Installation>((resolve, reject) => {
+      this.installerService.installer.handleCallback(req, serverRes, {
+        success: async(installation, metadata) => {
+          logger.info('Success to install', { installation, metadata });
+          resolve(installation);
+        },
+        failure: async(error) => {
+          reject(error); // go to catch block
+        },
+      });
+    });
+
+    let httpStatus = 200;
+    let httpBody;
+    try {
+      const installation = await installPromise;
 
+      // check whether bot is not null
+      if (installation.bot == null) {
+        logger.warn('Success to install but something wrong. `installation.bot` is null.');
+        httpStatus = 500;
+        httpBody = await platformRes.render('install-succeeded-but-has-problem.ejs', { reason: '`installation.bot` is null' });
+      }
+      // MAIN PATH: everything is fine
+      else {
         const appPageUrl = `https://slack.com/apps/${installation.appId}`;
+        httpBody = await platformRes.render('install-succeeded.ejs', { appPageUrl });
 
-        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
-        res.end('<html>'
-        + '<head><meta name="viewport" content="width=device-width,initial-scale=1">'
-        + '<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">'
-        + '</head>'
-        + '<body style="text-align:center; padding-top:20%;">'
-        + '<h1>Congratulations!</h1>'
-        + '<p>GROWI Bot installation has succeeded.</p>'
-        + '<div class="d-inline-flex flex-column">'
-        + `<a class="mb-3" href="${appPageUrl}">`
-        + 'Access to Slack App detail page.'
-        + '</a>'
-        + '<a class="btn btn-outline-success" href="https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html">'
-        + 'Getting started'
-        + '</a>'
-        + '</div>'
-        + '</body></html>');
-      },
-      failure: (error, installOptions, req, res) => {
-        res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
-        res.end('<html>'
-        + '<head><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
-        + '<body style="text-align:center; padding-top:20%;">'
-        + '<h1>GROWI Bot installation failed</h1>'
-        + '<p>Please contact administrators of your workspace</p>'
-        + 'Reference: <a href="https://slack.com/help/articles/222386767-Manage-app-installation-settings-for-your-workspace">'
-        + 'Manage app installation settings for your workspace'
-        + '</a>'
-        + '</body></html>');
-      },
-    });
+        // generate client
+        const client = generateWebClient(installation.bot.token);
+
+        const userId = installation.user.id;
+
+        await Promise.all([
+          // post message
+          postWelcomeMessage(client, userId),
+          // publish home
+          publishInitialHomeView(client, userId),
+        ]);
+      }
+    }
+    catch (error) {
+      logger.error(error);
+      httpStatus = 500;
+      httpBody = await platformRes.status(400).render('install-failed.ejs', { url: addToSlackUrl });
+    }
+
+    platformRes.status(httpStatus);
+    return httpBody;
   }
 
 }

+ 21 - 0
packages/slackbot-proxy/src/views/install-failed.ejs

@@ -0,0 +1,21 @@
+<%- include('commons/head'); %>
+
+<body>
+  <div class="container">
+    <div class="row">
+      <div class="col text-center">
+        <h1 class="my-5">GROWI Bot installation failed..</h1>
+        <p>
+          Retry from
+          <a href=<%- url %>>
+            <img alt="Add to Slack" height="40" width="139" src="/images/add-to-slack.png"/>
+          </a>
+        </p>
+        <p>
+          Or, please contact administrators of your workspace<br>
+          Reference: <a href="https://slack.com/help/articles/222386767-Manage-app-installation-settings-for-your-workspace">Manage app installation settings for your workspace</a>
+        </p>
+      </div>
+    </div>
+  </div>
+</body>

+ 18 - 0
packages/slackbot-proxy/src/views/install-succeeded-but-has-problem.ejs

@@ -0,0 +1,18 @@
+<%- include('commons/head'); %>
+
+<body>
+  <div class="container">
+    <div class="row">
+      <div class="col text-center">
+        <h1 class="my-5">GROWI Bot installation has succeeded, but something went wrong..</h1>
+        <p>
+          Reason: <%- reason%>
+        </p>
+        <p>
+          Please contact administrators of your workspace<br>
+          Reference: <a href="https://slack.com/help/articles/222386767-Manage-app-installation-settings-for-your-workspace">Manage app installation settings for your workspace</a>
+        </p>
+      </div>
+    </div>
+  </div>
+</body>

+ 21 - 0
packages/slackbot-proxy/src/views/install-succeeded.ejs

@@ -0,0 +1,21 @@
+<%- include('commons/head'); %>
+
+<body>
+  <div class="container">
+    <div class="row">
+      <div class="col text-center">
+        <h1 class="my-5">Congratulations!</h1>
+        <h2 class="my-5">GROWI Bot installation has succeeded!</h2>
+        <p>
+          Access to
+          <a href=<%- appPageUrl %>>
+            Slack App detail page.
+          </a>
+        </p>
+        <p>
+          <a class="btn btn-outline-success" href="https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html">Getting started</a>
+        </p>
+      </div>
+    </div>
+  </div>
+</body>