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

Merge branch 'imprv/welcome-page' into support/gw-7102-en-welcome.md

Yuki Takei 4 лет назад
Родитель
Сommit
05d1f0e950

+ 1 - 0
CHANGES.md

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

+ 1 - 1
package.json

@@ -31,7 +31,7 @@
   "scripts": {
   "scripts": {
     "start": "yarn app:server",
     "start": "yarn app:server",
     "prestart": "yarn app:build",
     "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",
     "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:build": "yarn lerna run build --scope @growi/slackbot-proxy --scope @growi/slack",
     "slackbot-proxy:server": "yarn lerna run start:prod --scope @growi/slackbot-proxy",
     "slackbot-proxy:server": "yarn lerna run start:prod --scope @growi/slackbot-proxy",

+ 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
 ## 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.
 A paragraph can be created by pressing Enter at the end of the previous paragraph.
 
 
 ```
 ```
@@ -53,17 +53,17 @@ paragraph2
 ## Br new line
 ## Br new line
 
 
 Add two spaces before break.
 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
 ## Blockquotes
 
 
@@ -84,7 +84,7 @@ Add one `>` per level at the start of the line
 Wrap code with three back quotes or tildes.
 Wrap code with three back quotes or tildes.
 
 
 ```
 ```
-print 'hoge'
+print 'foo'
 ```
 ```
 
 
 ### Syntax highlight and file name
 ### Syntax highlight and file name
@@ -131,16 +131,16 @@ This is  `Inline Code`.
 Code blocks should be preceded by four spaces or one tab.
 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
     end
     end
 ```
 ```
 
 
-    class Hoge
-        def hoge
-            print 'hoge'
+    class Foo
+        def foo
+            print 'foo'
         end
         end
     end
     end
 
 
@@ -166,7 +166,7 @@ ___
 
 
 ### Italic
 ### 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* .
 This is *Italic* .
@@ -178,7 +178,7 @@ This is _Italic_ .
 
 
 ### Bold
 ### 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**.
 This is **bold**.
@@ -263,7 +263,7 @@ Example of Bootstrap4 is[[here>./Bootstrap4]]
 
 
 ## Ul Bulleted list
 ## 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.
 Items can be nested using indentation.
 
 
 ```
 ```
@@ -286,7 +286,7 @@ Items can be nested using indentation.
 
 
 ## Ol Numbered List
 ## 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.
 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..
 # :heavy_plus_sign: More..
 
 
-- Try to attach Bootstrap4 Tags?
+- Want to attach Bootstrap4 Tags?
     - :arrow_right: [/Sandbox/Bootstrap4]
     - :arrow_right: [/Sandbox/Bootstrap4]
-- Try to draw Diagrams?
+- Want to draw Diagrams?
     - :arrow_right: [/Sandbox/Diagrams]
     - :arrow_right: [/Sandbox/Diagrams]
-- Try to write Math Formulas?
+- Want to write Math Formulas?
     - :arrow_right: [/Sandbox/Math]
     - :arrow_right: [/Sandbox/Math]

+ 43 - 10
packages/app/resource/locales/ja_JP/welcome.md

@@ -1,9 +1,27 @@
-# Welcome to GROWI :anchor:
-
+# :tada: GROWI へようこそ
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
 [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
 
 
-<div class="card border-primary">
+GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
+会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。
+
+知っている情報をカジュアルに書き出しみんなで編集することで、**チーム内での暗黙知を減らす**ことができます。  
+当たり前に共有される情報を日々増やしていきましょう。
+
+### :beginner: 簡単なページの作り方
+
+- 右上の "**作成**"ボタンまたは右下の**鉛筆アイコン**のボタンからページを書き始めることができます
+    - ページタイトルは後から変更できますので、適当に入力しても大丈夫です
+        - タイトル入力欄では、半角の `/` (スラッシュ) でページ階層を作れます
+        - (例)`/カテゴリ1/カテゴリ2/作りたいページタイトル` のように入力してみてください
+- `- ` を行頭につけると、この文章のような箇条書きを書くことができます
+- 画像やPDF、Word/Excel/PowerPointなどの添付ファイルも、コピー&ペースト、ドラッグ&ドロップで貼ることができます
+- 書けたら "**更新**" ボタンを押してページを公開しましょう
+    - `Ctrl(⌘) +S` でも保存できます
+
+さらに詳しくはこちら: [チュートリアル#新規ページ作成](https://docs.growi.org/ja/guide/tutorial/create_page.html#新規ページ作成)
+
+<div class="mt-4 card border-primary">
   <div class="card-header bg-primary text-light">Tips</div>
   <div class="card-header bg-primary text-light">Tips</div>
   <div class="card-body"><ul>
   <div class="card-body"><ul>
     <li>Ctrl(⌘) + "/" でショートカットヘルプを表示します</li>
     <li>Ctrl(⌘) + "/" でショートカットヘルプを表示します</li>
@@ -11,15 +29,30 @@
   </ul></div>
   </ul></div>
 </div>
 </div>
 
 
-Contents
-=========
 
 
-|All Pages|[/Sandbox]|
-| --- | --- |
-| $lsx(/) | <div class="alert alert-success"><span style="font-size: x-large;"><i class="icon-check"></i> [Sandboxをチェック](/Sandbox)</span></div> $lsx(/Sandbox)|
+# :anchor: 管理者の方へ <small>〜Wikiを作ったら〜</small>
+
+### :arrow_right: 複数人でWikiを使いますか?
+- :heavy_check_mark: メンバーを招待しましょう
+    - [Wikiに新しいメンバーを追加・招待する](https://docs.growi.org/ja/admin-guide/management-cookbook/user-management.html#%E6%96%B0%E8%A6%8F%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BB%AE%E7%99%BA%E8%A1%8C)
+### :arrow_right: Slackと連携してページやコメントの通知を受け取りましょう
+- :heavy_check_mark:  [Slack連携](https://docs.growi.org/ja/admin-guide/management-cookbook/slack-integration/#%E6%A6%82%E8%A6%81)
+### :arrow_right: 他のシステムからの乗り換えですか?
+- :heavy_check_mark: 他の GROWI、esa. io、Qiita:Team のデータをインポートすることが出来ます
+    -  [データのインポート](https://docs.growi.org/ja/admin-guide/management-cookbook/import.html)
+
+さらに詳しくはこちら: [管理者ガイド](https://docs.growi.org/ja/admin-guide/)
+
+
+# コンテンツリストアップ例
+
+テーブルと `$lsx` を使ってコンテンツリストを表示できます。
+
+| 全てのページリスト (First 15 pages) | [/Sandbox] 配下ページ一覧 |
+| ----------------------------------- | ------------------------- |
+| $lsx(/,num=15)                      | $lsx(/Sandbox)            |
 
 
-Slack
-=====
+# Slack
 
 
 <a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
 <a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
 
 

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

@@ -801,6 +801,15 @@ export default class CodeMirrorEditor extends AbstractEditor {
       >
       >
         <EditorIcon icon="CheckList" />
         <EditorIcon icon="CheckList" />
       </Button>,
       </Button>,
+      <Button
+        key="nav-item-attachment"
+        color={null}
+        size="sm"
+        title="Attachment"
+        onClick={this.props.onAddAttachmentButtonClicked}
+      >
+        <EditorIcon icon="Attachment" />
+      </Button>,
       <Button
       <Button
         key="nav-item-link"
         key="nav-item-link"
         color={null}
         color={null}
@@ -947,6 +956,7 @@ CodeMirrorEditor.propTypes = Object.assign({
   emojiStrategy: PropTypes.object,
   emojiStrategy: PropTypes.object,
   lineNumbers: PropTypes.bool,
   lineNumbers: PropTypes.bool,
   onMarkdownHelpButtonClicked: PropTypes.func,
   onMarkdownHelpButtonClicked: PropTypes.func,
+  onAddAttachmentButtonClicked: PropTypes.func,
 }, AbstractEditor.propTypes);
 }, AbstractEditor.propTypes);
 CodeMirrorEditor.defaultProps = {
 CodeMirrorEditor.defaultProps = {
   lineNumbers: true,
   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.dropHandler = this.dropHandler.bind(this);
 
 
     this.showMarkdownHelp = this.showMarkdownHelp.bind(this);
     this.showMarkdownHelp = this.showMarkdownHelp.bind(this);
+    this.addAttachmentHandler = this.addAttachmentHandler.bind(this);
 
 
     this.getAcceptableType = this.getAcceptableType.bind(this);
     this.getAcceptableType = this.getAcceptableType.bind(this);
     this.getDropzoneClassName = this.getDropzoneClassName.bind(this);
     this.getDropzoneClassName = this.getDropzoneClassName.bind(this);
@@ -187,6 +188,10 @@ export default class Editor extends AbstractEditor {
     this.setState({ isCheatsheetModalShown: true });
     this.setState({ isCheatsheetModalShown: true });
   }
   }
 
 
+  addAttachmentHandler() {
+    this.dropzone.open();
+  }
+
   getDropzoneClassName(isDragAccept, isDragReject) {
   getDropzoneClassName(isDragAccept, isDragReject) {
     let className = 'dropzone';
     let className = 'dropzone';
     if (!this.props.isUploadable) {
     if (!this.props.isUploadable) {
@@ -314,6 +319,7 @@ export default class Editor extends AbstractEditor {
                         onPasteFiles={this.pasteFilesHandler}
                         onPasteFiles={this.pasteFilesHandler}
                         onDragEnter={this.dragEnterHandler}
                         onDragEnter={this.dragEnterHandler}
                         onMarkdownHelpButtonClicked={this.showMarkdownHelp}
                         onMarkdownHelpButtonClicked={this.showMarkdownHelp}
+                        onAddAttachmentButtonClicked={this.addAttachmentHandler}
                         {...this.props}
                         {...this.props}
                       />
                       />
                     )}
                     )}
@@ -341,7 +347,7 @@ export default class Editor extends AbstractEditor {
             <button
             <button
               type="button"
               type="button"
               className="btn btn-outline-secondary btn-block btn-open-dropzone"
               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;
               <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
               Attach files
               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" />
           <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>
         </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/check-communicable';
 export * from './utils/get-supported-growi-actions-regexps';
 export * from './utils/get-supported-growi-actions-regexps';
 export * from './utils/post-ephemeral-errors';
 export * from './utils/post-ephemeral-errors';
+export * from './utils/publish-initial-home-view';
 export * from './utils/reshape-contents-body';
 export * from './utils/reshape-contents-body';
 export * from './utils/slash-command-parser';
 export * from './utils/slash-command-parser';
 export * from './utils/webclient-factory';
 export * from './utils/webclient-factory';
+export * from './utils/welcome-message';
 export * from './utils/required-scopes';
 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>.',
+        },
+      },
+    ],
+  });
+};

+ 5 - 0
packages/slackbot-proxy/CHANGES.md

@@ -2,5 +2,10 @@
 
 
 ## v1.0.1-RC
 ## v1.0.1-RC
 
 
+* 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: Permission scopes
+* Improvement: Top page information
 * Improvement: Add a link to docs
 * Improvement: Add a link to docs
+

+ 61 - 48
packages/slackbot-proxy/src/controllers/slack.ts

@@ -1,14 +1,16 @@
 import {
 import {
-  BodyParams, Controller, Get, Inject, Post, Req, Res, UseBefore,
+  BodyParams, Controller, Get, Inject, PlatformResponse, Post, Req, Res, UseBefore,
 } from '@tsed/common';
 } from '@tsed/common';
 
 
 import axios from 'axios';
 import axios from 'axios';
 
 
 import { WebAPICallResult } from '@slack/web-api';
 import { WebAPICallResult } from '@slack/web-api';
+import { Installation } from '@slack/oauth';
+
 
 
 import {
 import {
   markdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest, generateWebClient,
   markdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest, generateWebClient,
-  InvalidGrowiCommandError,
+  InvalidGrowiCommandError, requiredScopes, postWelcomeMessage, publishInitialHomeView,
 } from '@growi/slack';
 } from '@growi/slack';
 
 
 import { Relation } from '~/entities/relation';
 import { Relation } from '~/entities/relation';
@@ -334,57 +336,68 @@ export class SlackCtrl {
   }
   }
 
 
   @Get('/oauth_redirect')
   @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}`;
         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>
+          Retry from
+          <a href=<%- appPageUrl %>>
+            Access to 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>