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

Merge branch 'master' into feat/app-siteUrl-from-env

# Conflicts:
#	src/server/util/slack.js
#	src/server/views/admin/app.html
utsushiiro 7 лет назад
Родитель
Сommit
7ea0142229

+ 14 - 1
CHANGES.md

@@ -3,8 +3,21 @@ CHANGES
 
 
 ## 3.3.5-RC
 ## 3.3.5-RC
 
 
-* Fix: Prevent XSS by DetachCodeBlockInterceptor
+* Fix: "Anyone with the link" ACL doesn't work correctly
+    * Introduced 3.3.0
+* Fix: Diff of revision contents doesn't appeared when notifing with slack
 * Fix: NPE occured on /admin/security when Crowi Classic Auth Mechanism is set
 * Fix: NPE occured on /admin/security when Crowi Classic Auth Mechanism is set
+* Fix: Coudn't render Timing Diagram with PlantUML
+* I18n: Cheatsheet for editor
+* I18n: Some admin pages
+* Support: Upgrade libs
+    * diff
+    * markdown-it-plantuml
+    * mongoose
+    * nodemailer
+    * mongoose-gridfs
+    * sinon
+    * sinon-chai
 
 
 ## 3.3.4
 ## 3.3.4
 
 

+ 7 - 7
package.json

@@ -71,7 +71,7 @@
     "cookie-parser": "^1.4.3",
     "cookie-parser": "^1.4.3",
     "cross-env": "^5.0.5",
     "cross-env": "^5.0.5",
     "csrf": "~3.0.3",
     "csrf": "~3.0.3",
-    "diff": "^3.5.0",
+    "diff": "^4.0.1",
     "elasticsearch": "^15.0.0",
     "elasticsearch": "^15.0.0",
     "entities": "^1.1.1",
     "entities": "^1.1.1",
     "env-cmd": "^8.0.1",
     "env-cmd": "^8.0.1",
@@ -96,12 +96,12 @@
     "migrate-mongo": "^5.0.1",
     "migrate-mongo": "^5.0.1",
     "mkdirp": "~0.5.1",
     "mkdirp": "~0.5.1",
     "module-alias": "^2.0.6",
     "module-alias": "^2.0.6",
-    "mongoose": "^5.3.1",
-    "mongoose-gridfs": "^0.5.0",
+    "mongoose": "^5.4.4",
+    "mongoose-gridfs": "^0.6.2",
     "mongoose-paginate": "^5.0.3",
     "mongoose-paginate": "^5.0.3",
     "mongoose-unique-validator": "^2.0.2",
     "mongoose-unique-validator": "^2.0.2",
     "multer": "~1.4.0",
     "multer": "~1.4.0",
-    "nodemailer": "^4.0.1",
+    "nodemailer": "^5.1.1",
     "nodemailer-ses-transport": "~1.5.0",
     "nodemailer-ses-transport": "~1.5.0",
     "npm-run-all": "^4.1.2",
     "npm-run-all": "^4.1.2",
     "passport": "^0.4.0",
     "passport": "^0.4.0",
@@ -165,7 +165,7 @@
     "markdown-it-footnote": "^3.0.1",
     "markdown-it-footnote": "^3.0.1",
     "markdown-it-mathjax": "^2.0.0",
     "markdown-it-mathjax": "^2.0.0",
     "markdown-it-named-headers": "^0.0.4",
     "markdown-it-named-headers": "^0.0.4",
-    "markdown-it-plantuml": "^1.0.0",
+    "markdown-it-plantuml": "^1.3.0",
     "markdown-it-task-checkbox": "^1.0.6",
     "markdown-it-task-checkbox": "^1.0.6",
     "markdown-it-toc-and-anchor-with-slugid": "^1.1.4",
     "markdown-it-toc-and-anchor-with-slugid": "^1.1.4",
     "markdown-table": "^1.1.1",
     "markdown-table": "^1.1.1",
@@ -196,8 +196,8 @@
     "reveal.js": "^3.5.0",
     "reveal.js": "^3.5.0",
     "sass-loader": "^7.1.0",
     "sass-loader": "^7.1.0",
     "simple-load-script": "^1.0.2",
     "simple-load-script": "^1.0.2",
-    "sinon": "^7.0.0",
-    "sinon-chai": "^3.2.0",
+    "sinon": "^7.2.2",
+    "sinon-chai": "^3.3.0",
     "socket.io-client": "^2.0.3",
     "socket.io-client": "^2.0.3",
     "stream-to-promise": "^2.2.0",
     "stream-to-promise": "^2.2.0",
     "style-loader": "^0.23.0",
     "style-loader": "^0.23.0",

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

@@ -13,6 +13,8 @@
   "Admin": "Admin",
   "Admin": "Admin",
   "New": "New",
   "New": "New",
   "Shortcuts": "Shortcuts",
   "Shortcuts": "Shortcuts",
+  "eg": "e.g.",
+  "Undo": "Undo",
 
 
   "Update": "Update",
   "Update": "Update",
   "Update Page": "Update Page",
   "Update Page": "Update Page",
@@ -272,6 +274,38 @@
     }
     }
   },
   },
 
 
+  "sandbox": {
+    "header": "Header",
+    "header_x": "Header {{index}}",
+    "block": "Paragraph",
+    "block_detail": "makes a paragraph",
+    "empty_line": "Empty Line",
+    "line_break": "Line Break",
+    "line_break_detail": "(2 spaces) make a line break",
+    "typography": "Typography",
+    "italics": "Italics",
+    "bold": "Bold",
+    "italic_bold": "Italic Bold",
+    "strikethrough": "strikethrough",
+    "link": "Link",
+    "code_highlight": "Code Highlight",
+    "list": "List",
+    "unordered_list_x": "Unordered List {{index}}",
+    "ordered_list_x": "Ordered List {{index}}",
+    "task": "Task",
+    "task_checked": "Checked",
+    "task_unchecked": "Unchecked",
+    "quote": "Quote",
+    "quote1": "You can write",
+    "quote2": "multi-line quotations",
+    "quote_nested": "Nested Quote",
+    "table": "Table",
+    "image": "Image",
+    "alt_text": "Alt Text",
+    "insert_image": "inserts an image",
+    "open_sandbox": "Open Sandbox"
+  },
+
   "admin_top": {
   "admin_top": {
     "Management Wiki": "Management Wiki",
     "Management Wiki": "Management Wiki",
     "System Information": "System Information",
     "System Information": "System Information",
@@ -538,6 +572,10 @@
     "save_edit": "Save edit tab and history tab switching in the browser and make it object for forward/back command of the browser.",
     "save_edit": "Save edit tab and history tab switching in the browser and make it object for forward/back command of the browser.",
     "by_invalidating": "By invalidating, you can make page transition as the only object for forward/back command of the browser.",
     "by_invalidating": "By invalidating, you can make page transition as the only object for forward/back command of the browser.",
     "nocdn_desc": "This function is disabled when the environment variable <code>NO_CDN=true</code>.<br>Github style has been forcibly applied.",
     "nocdn_desc": "This function is disabled when the environment variable <code>NO_CDN=true</code>.<br>Github style has been forcibly applied.",
+    "custom_title": "Custom Title",
+    "custom_title_detail": "You can customize <code>%s</code> tag.<br><code>%s</code> will be automatically replaced with the app name, and <code>%s</code> will be replaced with the page name/path.",
+    "custom_header": "Custom HTML Header",
+    "custom_header_detail": "You can customize HTML header that applies all pages. Your custom script will be inserted in <code>%s</code> but above other <code>%s</code> tags.<br>Relaod page to see changes.",
     "Custom CSS": "Custom CSS",
     "Custom CSS": "Custom CSS",
     "write_CSS": "You can write CSS that is applied to whole system.",
     "write_CSS": "You can write CSS that is applied to whole system.",
     "reflect_change": "You need to reload the page to reflect the change.",
     "reflect_change": "You need to reload the page to reflect the change.",
@@ -553,14 +591,28 @@
   "user_management": {
   "user_management": {
     "User management": "User management",
     "User management": "User management",
     "invite_users": "Invite new users",
     "invite_users": "Invite new users",
+    "emails": "Emails",
+    "invite_thru_email": "Send Invitation Email",
+    "invite": "Invite",
+    "give_admin_access": "Give admin access",
+    "remove_admin_access": "Remove admin access",
     "external_account": "External account management",
     "external_account": "External account management",
     "user_list": "List of users",
     "user_list": "List of users",
+    "external_account_list": "External Account List",
+    "back_to_user_management": "Back to User Management",
+    "authentication_provider": "Authentication Provider",
     "Date created": "Date created",
     "Date created": "Date created",
     "Last login": "Last login",
     "Last login": "Last login",
     "Manage": "Manage",
     "Manage": "Manage",
     "Edit menu": "Edit menu",
     "Edit menu": "Edit menu",
+    "password_setting": "Password Setting",
+    "set": "Yes",
+    "unset": "No",
+    "password_setting_help": "Show whether the related user has a password set",
     "Reissue password": "Reissue password",
     "Reissue password": "Reissue password",
+    "related_username": "Related user's <code>%s</code>",
     "Status":"Status",
     "Status":"Status",
+    "accept": "Accept",
     "Deactivate account":"Deactivate account",
     "Deactivate account":"Deactivate account",
     "your_own":"You cannot deactivate your own account",
     "your_own":"You cannot deactivate your own account",
     "Administrator menu":"Administrator menu",
     "Administrator menu":"Administrator menu",

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

@@ -13,6 +13,8 @@
   "Admin": "管理",
   "Admin": "管理",
   "New": "作成",
   "New": "作成",
   "Shortcuts": "ショートカット",
   "Shortcuts": "ショートカット",
+  "eg": "例:",
+  "Undo": "元に戻す",
 
 
   "Update": "更新",
   "Update": "更新",
   "Update Page": "ページを更新",
   "Update Page": "ページを更新",
@@ -286,6 +288,38 @@
     }
     }
   },
   },
 
 
+  "sandbox": {
+    "header": "見出し",
+    "header_x": "見出し {{index}}",
+    "block": "ブロック",
+    "block_detail": "を挟むことで段落になります",
+    "empty_line": "空白行",
+    "line_break": "改行",
+    "line_break_detail": "(スペース2つ) で改行されます",
+    "typography": "タイポグラフィー",
+    "italics": "斜体",
+    "bold": "強調",
+    "italic_bold": "イタリックボールド",
+    "strikethrough": "取り消し線",
+    "link": "リンク",
+    "code_highlight": "コードハイライト",
+    "list": "リスト",
+    "unordered_list_x": "リスト {{index}}",
+    "ordered_list_x": "番号付きリスト {{index}}",
+    "task": "タスク",
+    "task_checked": "チェック付き",
+    "task_unchecked": "チェックなし",
+    "quote": "引用",
+    "quote1": "複数行の引用文を",
+    "quote2": "書くことができます",
+    "table": "テーブル",
+    "quote_nested": "多重引用",
+    "image": "画像",
+    "alt_text": "Alt文字列",
+    "insert_image": "で画像を挿入できます",
+    "open_sandbox": "Sandbox を開く"
+  },
+
   "admin_top": {
   "admin_top": {
     "Management Wiki": "Wiki管理",
     "Management Wiki": "Wiki管理",
     "System Information": "システム情報",
     "System Information": "システム情報",
@@ -551,6 +585,10 @@
     "save_edit": "編集タブやヒストリータブ等の切り替えをブラウザ履歴に保存し、ブラウザの戻る/進む操作の対象にします。",
     "save_edit": "編集タブやヒストリータブ等の切り替えをブラウザ履歴に保存し、ブラウザの戻る/進む操作の対象にします。",
     "by_invalidating": "無効化することで、ページ遷移のみを戻る/進む操作の対象にすることができます。",
     "by_invalidating": "無効化することで、ページ遷移のみを戻る/進む操作の対象にすることができます。",
     "nocdn_desc": "この機能は、環境変数 <code>NO_CDN=true</code> の時は無効化されます。<br>GitHub スタイルが適用されています。",
     "nocdn_desc": "この機能は、環境変数 <code>NO_CDN=true</code> の時は無効化されます。<br>GitHub スタイルが適用されています。",
+    "custom_title": "カスタム Title",
+    "custom_title_detail": "<code>%s</code>タグのコンテンツをカスタマイズできます。<br><code>%s</code>がサイト名、<code>%s</code>がページ名またはページパスに置換されます。",
+    "custom_header": "カスタム HTML Header",
+    "custom_header_detail": "システム全体に適用される HTML を記述できます。<code>%s</code> タグ内の他の <code>%s</code> タグ読み込み前に展開されます。<br>変更の反映はページの更新が必要です。",
     "Custom CSS": "カスタム CSS",
     "Custom CSS": "カスタム CSS",
     "write_CSS": " システム全体に適用されるCSSを記述できます。",
     "write_CSS": " システム全体に適用されるCSSを記述できます。",
     "reflect_change": "変更の反映はページの更新が必要です。",
     "reflect_change": "変更の反映はページの更新が必要です。",
@@ -566,14 +604,28 @@
   "user_management": {
   "user_management": {
     "User management": "ユーザー管理",
     "User management": "ユーザー管理",
     "invite_users": "新規ユーザーの招待",
     "invite_users": "新規ユーザーの招待",
+    "emails": "メールアドレス (複数行入力で複数人招待可能)",
+    "invite_thru_email": "招待をメールで送信",
+    "invite": "招待する",
+    "give_admin_access": "管理者にする",
+    "remove_admin_access": "管理者から外す",
     "external_account": "外部アカウントの管理",
     "external_account": "外部アカウントの管理",
     "user_list": "ユーザー一覧",
     "user_list": "ユーザー一覧",
+    "external_account_list": "外部アカウント一覧",
+    "back_to_user_management": "ユーザー管理に戻る",
+    "authentication_provider": "認証情報プロバイダ",
     "Date created": "作成日",
     "Date created": "作成日",
     "Last login": "最終ログイン",
     "Last login": "最終ログイン",
     "Manage": "操作",
     "Manage": "操作",
     "Edit menu": "編集メニュー",
     "Edit menu": "編集メニュー",
+    "password_setting": "パスワード設定",
+    "password_setting_help": "関連付けられているユーザーがパスワードを設定しているかどうかを表示します",
+    "set": "設定済み",
+    "unset": "未設定",
     "Reissue password": "パスワードの再発行",
     "Reissue password": "パスワードの再発行",
+    "related_username": "関連付けられているユーザーの <code>%s</code>",
     "Status": "ステータス",
     "Status": "ステータス",
+    "accept": "承認する",
     "Deactivate account": "アカウント停止",
     "Deactivate account": "アカウント停止",
     "your_own": "自分自身のアカウントを停止することはできません",
     "your_own": "自分自身のアカウントを停止することはできません",
     "Administrator menu": "管理者メニュー",
     "Administrator menu": "管理者メニュー",

+ 102 - 0
src/client/js/components/PageEditor/Cheatsheet.js

@@ -0,0 +1,102 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { translate } from 'react-i18next';
+
+class Cheatsheet extends React.Component {
+  render() {
+    const { t } = this.props;
+
+    return (
+      <div className="row small">
+        <div className="col-sm-6">
+          <h4>{t('sandbox.header')}</h4>
+          <ul className="hljs">
+            <li><code># </code>{t('sandbox.header_x', {index: '1'})}</li>
+            <li><code>## </code>{t('sandbox.header_x', {index: '2'})}</li>
+            <li><code>### </code>{t('sandbox.header_x', {index: '3'})}</li>
+          </ul>
+          <h4>{t('sandbox.block')}</h4>
+          <p className="mb-1"><code>[{t('sandbox.empty_line')}]</code>{t('sandbox.block_detail')}</p>
+          <ul className="hljs">
+            <li>text</li>
+            <li></li>
+            <li>text</li>
+          </ul>
+          <h4>{t('sandbox.line_break')}</h4>
+          <p className="mb-1"><code>[ ][ ]</code> {t('sandbox.line_break_detail')}</p>
+          <ul className="hljs">
+            <li>text</li>
+            <li>text</li>
+          </ul>
+          <h4>{t('sandbox.typography')}</h4>
+          <ul className="hljs">
+            <li><i>*{t('sandbox.italics')}*</i></li>
+            <li><b>**{t('sandbox.bold')}**</b></li>
+            <li><i><b>***{t('sandbox.italic_bold')}***</b></i></li>
+            <li>~~{t('sandbox.strikethrough')}~~ => <s>{t('sandbox.strikethrough')}</s></li>
+          </ul>
+          <h4>{t('sandbox.link')}</h4>
+          <ul className="hljs">
+            <li>[Google](https://www.google.co.jp/)</li>
+            <li>[/Page1/ChildPage1]</li>
+          </ul>
+          <h4>{t('sandbox.code_highlight')}</h4>
+          <ul className="hljs">
+            <li>```javascript:index.js</li>
+            <li>writeCode();</li>
+            <li>```</li>
+          </ul>
+        </div>
+        <div className="col-sm-6">
+          <h4>{t('sandbox.list')}</h4>
+          <ul className="hljs">
+            <li>- {t('sandbox.unordered_list_x', {index: '1'})}</li>
+            <li>&nbsp;&nbsp;- {t('sandbox.unordered_list_x', {index: '1.1'})}</li>
+            <li>- {t('sandbox.unordered_list_x', {index: '2'})}</li>
+          </ul>
+          <ul className="hljs">
+            <li>1. {t('sandbox.ordered_list_x', {index: '1'})}</li>
+            <li>1. {t('sandbox.ordered_list_x', {index: '2'})}</li>
+          </ul>
+          <ul className="hljs">
+            <li>- [ ] {t('sandbox.task')}({t('sandbox.task_unchecked')})</li>
+            <li>- [x] {t('sandbox.task')}({t('sandbox.task_checked')})</li>
+          </ul>
+          <h4>{t('sandbox.quote')}</h4>
+          <ul className="hljs">
+            <li>> {t('sandbox.quote1')}</li>
+            <li>> {t('sandbox.quote2')}</li>
+          </ul>
+          <ul className="hljs">
+            <li>>> {t('sandbox.quote_nested')}</li>
+            <li>>>> {t('sandbox.quote_nested')}</li>
+            <li>>>>> {t('sandbox.quote_nested')}</li>
+          </ul>
+          <h4>{t('sandbox.table')}</h4>
+          <ul className="hljs text-center">
+            <li>|Left&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;Mid&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Right|</li>
+            <li>|:----------|:---------:|----------:|</li>
+            <li>|col 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;col 2&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col 3|</li>
+            <li>|col 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;col 2&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col 3|</li>
+          </ul>
+          <h4>{t('sandbox.image')}</h4>
+          <p className="mb-1"><code> ![{t('sandbox.alt_text')}](URL)</code> {t('sandbox.insert_image')}</p>
+          <ul className="hljs">
+            <li>![ex](https://example.com/image.png)</li>
+          </ul>
+
+          <hr />
+          <a href="/Sandbox" className="btn btn-info btn-block" target="_blank">
+            <i className="icon-share-alt"/> {t('sandbox.open_sandbox')}
+          </a>
+        </div>
+      </div>
+    );
+  }
+}
+
+Cheatsheet.propTypes = {
+  t: PropTypes.func.isRequired,               // i18next
+};
+
+export default translate()(Cheatsheet);

+ 5 - 121
src/client/js/components/PageEditor/CodeMirrorEditor.js

@@ -44,6 +44,9 @@ require('../../util/codemirror/autorefresh.ext');
 
 
 import AbstractEditor from './AbstractEditor';
 import AbstractEditor from './AbstractEditor';
 
 
+import SimpleCheatsheet from './SimpleCheatsheet';
+import Cheatsheet from './Cheatsheet';
+
 import pasteHelper from './PasteHelper';
 import pasteHelper from './PasteHelper';
 import EmojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
 import EmojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
 
 
@@ -510,130 +513,11 @@ export default class CodeMirrorEditor extends AbstractEditor {
   }
   }
 
 
   renderSimpleCheatsheet() {
   renderSimpleCheatsheet() {
-    return (
-      <div className="panel panel-default gfm-cheatsheet mb-0">
-        <div className="panel-body small p-b-0">
-          <div className="row">
-            <div className="col-xs-6">
-              <p>
-                # 見出し1<br />
-                ## 見出し2
-              </p>
-              <p><i>*斜体*</i>&nbsp;&nbsp;<b>**強調**</b></p>
-              <p>
-                [リンク](http://..)<br />
-                [/ページ名/子ページ名]
-              </p>
-              <p>
-                ```javascript:index.js<br />
-                writeCode();<br />
-                ```
-              </p>
-            </div>
-            <div className="col-xs-6">
-              <p>
-                - リスト 1<br />
-                &nbsp;&nbsp;&nbsp;&nbsp;- リスト 1_1<br />
-                - リスト 2<br />
-                1. 番号付きリスト 1<br />
-                1. 番号付きリスト 2
-              </p>
-              <hr />
-              <p>行末にスペース2つ[ ][ ]<br />で改行</p>
-            </div>
-          </div>
-        </div>
-      </div>
-    );
+    return <SimpleCheatsheet />;
   }
   }
 
 
   renderCheatsheetModalBody() {
   renderCheatsheetModalBody() {
-    return (
-      <div className="row small">
-        <div className="col-sm-6">
-          <h4>Header</h4>
-          <ul className="hljs">
-            <li><code># </code>見出し1</li>
-            <li><code>## </code>見出し2</li>
-            <li><code>### </code>見出し3</li>
-          </ul>
-          <h4>Block</h4>
-          <p className="mb-1"><code>[空白行]</code>を挟むことで段落になります</p>
-          <ul className="hljs">
-            <li>text</li>
-            <li></li>
-            <li>text</li>
-          </ul>
-          <h4>Line breaks</h4>
-          <p className="mb-1">段落中、<code>[space][space]</code>(スペース2つ) で改行されます</p>
-          <ul className="hljs">
-            <li>text<code> </code><code> </code></li>
-            <li>text</li>
-          </ul>
-          <h4>Typography</h4>
-          <ul className="hljs">
-            <li><i>*イタリック*</i></li>
-            <li><b>**ボールド**</b></li>
-            <li><i><b>***イタリックボールド***</b></i></li>
-            <li>~~取り消し線~~ => <s>striked text</s></li>
-          </ul>
-          <h4>Link</h4>
-          <ul className="hljs">
-            <li>[Google](https://www.google.co.jp/)</li>
-            <li>[/Page1/ChildPage1]</li>
-          </ul>
-          <h4>コードハイライト</h4>
-          <ul className="hljs">
-            <li>```javascript:index.js</li>
-            <li>writeCode();</li>
-            <li>```</li>
-          </ul>
-        </div>
-        <div className="col-sm-6">
-          <h4>リスト</h4>
-          <ul className="hljs">
-            <li>- リスト 1</li>
-            <li>&nbsp;&nbsp;- リスト 1_1</li>
-            <li>- リスト 2</li>
-          </ul>
-          <ul className="hljs">
-            <li>1. 番号付きリスト 1</li>
-            <li>1. 番号付きリスト 2</li>
-          </ul>
-          <ul className="hljs">
-            <li>- [ ] タスク(チェックなし)</li>
-            <li>- [x] タスク(チェック付き)</li>
-          </ul>
-          <h4>引用</h4>
-          <ul className="hljs">
-            <li>> 複数行の引用文を</li>
-            <li>> 書くことができます</li>
-          </ul>
-          <ul className="hljs">
-            <li>>> 多重引用</li>
-            <li>>>> 多重引用</li>
-            <li>>>>> 多重引用</li>
-          </ul>
-          <h4>Table</h4>
-          <ul className="hljs text-center">
-            <li>|&nbsp;&nbsp;&nbsp;左寄せ&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;中央寄せ&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;右寄せ&nbsp;&nbsp;&nbsp;|</li>
-            <li>|:-----------|:----------:|-----------:|</li>
-            <li>|column 1&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;column 2&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;column 3|</li>
-            <li>|column 1&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;column 2&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;column 3|</li>
-          </ul>
-          <h4>Images</h4>
-          <p className="mb-1"><code> ![Alt文字列](URL)</code> で<span className="text-info">&lt;img&gt;</span>タグを挿入できます</p>
-          <ul className="hljs">
-            <li>![ex](https://example.com/images/a.png)</li>
-          </ul>
-
-          <hr />
-          <a href="/Sandbox" className="btn btn-info btn-block" target="_blank">
-            <i className="icon-share-alt"/> Sandbox を開く
-          </a>
-        </div>
-      </div>
-    );
+    return <Cheatsheet />;
   }
   }
 
 
   renderCheatsheetModalButton() {
   renderCheatsheetModalButton() {

+ 51 - 0
src/client/js/components/PageEditor/SimpleCheatsheet.js

@@ -0,0 +1,51 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { translate } from 'react-i18next';
+
+class SimpleCheatsheet extends React.Component {
+  render() {
+    const { t } = this.props;
+
+    return (
+      <div className="panel panel-default gfm-cheatsheet mb-0">
+        <div className="panel-body small p-b-0">
+          <div className="row">
+            <div className="col-xs-6">
+              <p>
+                # {t('sandbox.header_x', {index: '1'})}<br />
+                ## {t('sandbox.header_x', {index: '2'})}
+              </p>
+              <p><i>*{t('sandbox.italics')}*</i>&nbsp;&nbsp;<b>**{t('sandbox.bold')}**</b></p>
+              <p>
+                [{t('sandbox.link')}](http://..)<br />
+                [/Page1/ChildPage1]
+              </p>
+              <p>
+                ```javascript:index.js<br />
+                writeCode();<br />
+                ```
+              </p>
+            </div>
+            <div className="col-xs-6">
+              <p>
+                - {t('sandbox.unordered_list_x', {index: '1'})}<br />
+                &nbsp;&nbsp;&nbsp;- {t('sandbox.unordered_list_x', {index: '1.1'})}<br />
+                - {t('sandbox.unordered_list_x', {index: '2'})}<br />
+                1. {t('sandbox.ordered_list_x', {index: '1'})}<br />
+                1. {t('sandbox.ordered_list_x', {index: '2'})}
+              </p>
+              <hr />
+              <p>[ ][ ] {t('sandbox.block_detail')}</p>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+SimpleCheatsheet.propTypes = {
+  t: PropTypes.func.isRequired,               // i18next
+};
+
+export default translate()(SimpleCheatsheet);

+ 1 - 1
src/server/events/user.js

@@ -14,7 +14,7 @@ UserEvent.prototype.onActivated = async function(user) {
 
 
   const userPagePath = Page.getUserPagePath(user);
   const userPagePath = Page.getUserPagePath(user);
 
 
-  const page = await Page.findByPathAndViewer(userPagePath, user);
+  const page = await Page.findByPath(userPagePath, user);
 
 
   if (page == null) {
   if (page == null) {
     const body = `# ${user.username}\nThis is ${user.username}'s page`;
     const body = `# ${user.username}\nThis is ${user.username}'s page`;

+ 49 - 18
src/server/models/page.js

@@ -62,6 +62,26 @@ const pageSchema = new mongoose.Schema({
 pageSchema.plugin(uniqueValidator);
 pageSchema.plugin(uniqueValidator);
 
 
 
 
+/**
+ * return an array of ancestors paths that is extracted from specified pagePath
+ * e.g.
+ *  when `pagePath` is `/foo/bar/baz`,
+ *  this method returns [`/foo/bar/baz`, `/foo/bar`, `/foo`, `/`]
+ *
+ * @param {string} pagePath
+ * @return {string[]} ancestors paths
+ */
+const extractToAncestorsPaths = (pagePath) => {
+  const ancestorsPaths = [];
+
+  let parentPath;
+  while (parentPath !== '/') {
+    parentPath = nodePath.dirname(parentPath || pagePath);
+    ancestorsPaths.push(parentPath);
+  }
+
+  return ancestorsPaths;
+};
 
 
 const addSlashOfEnd = (path) => {
 const addSlashOfEnd = (path) => {
   let returnPath = path;
   let returnPath = path;
@@ -168,22 +188,24 @@ class PageQueryBuilder {
     return this;
     return this;
   }
   }
 
 
-  addConditionToFilteringByViewer(user, userGroups, showPagesRestrictedByOwner, showPagesRestrictedByGroup) {
+  addConditionToFilteringByViewer(user, userGroups, showAnyoneKnowsLink, showPagesRestrictedByOwner, showPagesRestrictedByGroup) {
     const grantConditions = [
     const grantConditions = [
       {grant: null},
       {grant: null},
       {grant: GRANT_PUBLIC},
       {grant: GRANT_PUBLIC},
     ];
     ];
 
 
+    if (showAnyoneKnowsLink) {
+      grantConditions.push({grant: GRANT_RESTRICTED});
+    }
+
     if (showPagesRestrictedByOwner) {
     if (showPagesRestrictedByOwner) {
       grantConditions.push(
       grantConditions.push(
-        {grant: GRANT_RESTRICTED},
         {grant: GRANT_SPECIFIED},
         {grant: GRANT_SPECIFIED},
         {grant: GRANT_OWNER},
         {grant: GRANT_OWNER},
       );
       );
     }
     }
     else if (user != null) {
     else if (user != null) {
       grantConditions.push(
       grantConditions.push(
-        {grant: GRANT_RESTRICTED, grantedUsers: user._id},
         {grant: GRANT_SPECIFIED, grantedUsers: user._id},
         {grant: GRANT_SPECIFIED, grantedUsers: user._id},
         {grant: GRANT_OWNER, grantedUsers: user._id},
         {grant: GRANT_OWNER, grantedUsers: user._id},
       );
       );
@@ -549,7 +571,7 @@ module.exports = function(crowi) {
     }
     }
 
 
     const queryBuilder = new PageQueryBuilder(baseQuery);
     const queryBuilder = new PageQueryBuilder(baseQuery);
-    queryBuilder.addConditionToFilteringByViewer(user, userGroups);
+    queryBuilder.addConditionToFilteringByViewer(user, userGroups, true);
 
 
     const count = await queryBuilder.query.exec();
     const count = await queryBuilder.query.exec();
     return count > 0;
     return count > 0;
@@ -571,7 +593,7 @@ module.exports = function(crowi) {
     }
     }
 
 
     const queryBuilder = new PageQueryBuilder(baseQuery);
     const queryBuilder = new PageQueryBuilder(baseQuery);
-    queryBuilder.addConditionToFilteringByViewer(user, relatedUserGroups);
+    queryBuilder.addConditionToFilteringByViewer(user, relatedUserGroups, true);
 
 
     return await queryBuilder.query.exec();
     return await queryBuilder.query.exec();
   };
   };
@@ -604,7 +626,7 @@ module.exports = function(crowi) {
     }
     }
 
 
     const queryBuilder = new PageQueryBuilder(baseQuery);
     const queryBuilder = new PageQueryBuilder(baseQuery);
-    queryBuilder.addConditionToFilteringByViewer(user, relatedUserGroups);
+    queryBuilder.addConditionToFilteringByViewer(user, relatedUserGroups, true);
 
 
     return await queryBuilder.query.exec();
     return await queryBuilder.query.exec();
   };
   };
@@ -623,7 +645,10 @@ module.exports = function(crowi) {
       return null;
       return null;
     }
     }
 
 
-    const parentPath = nodePath.dirname(path);
+    const ancestorsPaths = extractToAncestorsPaths(path);
+
+    // pick the longest one
+    const baseQuery = this.findOne({path: { $in: ancestorsPaths }}).sort({path: -1});
 
 
     let relatedUserGroups = userGroups;
     let relatedUserGroups = userGroups;
     if (user != null && relatedUserGroups == null) {
     if (user != null && relatedUserGroups == null) {
@@ -632,11 +657,10 @@ module.exports = function(crowi) {
       relatedUserGroups = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
       relatedUserGroups = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
     }
     }
 
 
-    const page = await this.findByPathAndViewer(parentPath, user, relatedUserGroups);
+    const queryBuilder = new PageQueryBuilder(baseQuery);
+    queryBuilder.addConditionToFilteringByViewer(user, relatedUserGroups);
 
 
-    return (page != null)
-      ? page
-      : this.findAncestorByPathAndViewer(parentPath, user, relatedUserGroups);
+    return await queryBuilder.query.exec();
   };
   };
 
 
   pageSchema.statics.findByRedirectTo = function(path) {
   pageSchema.statics.findByRedirectTo = function(path) {
@@ -650,7 +674,7 @@ module.exports = function(crowi) {
     const builder = new PageQueryBuilder(this.find());
     const builder = new PageQueryBuilder(this.find());
     builder.addConditionToListWithDescendants(path, option);
     builder.addConditionToListWithDescendants(path, option);
 
 
-    return await findListFromBuilderAndViewer(builder, user, option);
+    return await findListFromBuilderAndViewer(builder, user, false, option);
   };
   };
 
 
   /**
   /**
@@ -660,7 +684,7 @@ module.exports = function(crowi) {
     const builder = new PageQueryBuilder(this.find());
     const builder = new PageQueryBuilder(this.find());
     builder.addConditionToListByStartWith(path, option);
     builder.addConditionToListByStartWith(path, option);
 
 
-    return await findListFromBuilderAndViewer(builder, user, option);
+    return await findListFromBuilderAndViewer(builder, user, false, option);
   };
   };
 
 
   /**
   /**
@@ -674,7 +698,12 @@ module.exports = function(crowi) {
     const opt = Object.assign({sort: 'createdAt', desc: -1}, option);
     const opt = Object.assign({sort: 'createdAt', desc: -1}, option);
     const builder = new PageQueryBuilder(this.find({ creator: targetUser._id }));
     const builder = new PageQueryBuilder(this.find({ creator: targetUser._id }));
 
 
-    return await findListFromBuilderAndViewer(builder, currentUser, opt);
+    let showAnyoneKnowsLink = null;
+    if (targetUser != null && currentUser != null) {
+      showAnyoneKnowsLink = targetUser._id.equals(currentUser._id);
+    }
+
+    return await findListFromBuilderAndViewer(builder, currentUser, showAnyoneKnowsLink, opt);
   };
   };
 
 
   pageSchema.statics.findListByPageIds = async function(ids, option) {
   pageSchema.statics.findListByPageIds = async function(ids, option) {
@@ -700,9 +729,10 @@ module.exports = function(crowi) {
    * find pages by PageQueryBuilder
    * find pages by PageQueryBuilder
    * @param {PageQueryBuilder} builder
    * @param {PageQueryBuilder} builder
    * @param {User} user
    * @param {User} user
+   * @param {boolean} showAnyoneKnowsLink
    * @param {any} option
    * @param {any} option
    */
    */
-  async function findListFromBuilderAndViewer(builder, user, option) {
+  async function findListFromBuilderAndViewer(builder, user, showAnyoneKnowsLink, option) {
     validateCrowi();
     validateCrowi();
 
 
     const User = crowi.model('User');
     const User = crowi.model('User');
@@ -721,7 +751,7 @@ module.exports = function(crowi) {
     }
     }
 
 
     // add grant conditions
     // add grant conditions
-    await addConditionToFilteringByViewerForList(builder, user);
+    await addConditionToFilteringByViewerForList(builder, user, showAnyoneKnowsLink);
 
 
     builder.addConditionToPagenate(opt.offset, opt.limit, sortOpt);
     builder.addConditionToPagenate(opt.offset, opt.limit, sortOpt);
 
 
@@ -740,8 +770,9 @@ module.exports = function(crowi) {
    *
    *
    * @param {PageQueryBuilder} builder
    * @param {PageQueryBuilder} builder
    * @param {User} user
    * @param {User} user
+   * @param {boolean} showAnyoneKnowsLink
    */
    */
-  async function addConditionToFilteringByViewerForList(builder, user) {
+  async function addConditionToFilteringByViewerForList(builder, user, showAnyoneKnowsLink) {
     validateCrowi();
     validateCrowi();
 
 
     const Config = crowi.model('Config');
     const Config = crowi.model('Config');
@@ -758,7 +789,7 @@ module.exports = function(crowi) {
       userGroups = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
       userGroups = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
     }
     }
 
 
-    return builder.addConditionToFilteringByViewer(user, userGroups, !hidePagesRestrictedByOwner, !hidePagesRestrictedByGroup);
+    return builder.addConditionToFilteringByViewer(user, userGroups, showAnyoneKnowsLink, !hidePagesRestrictedByOwner, !hidePagesRestrictedByGroup);
   }
   }
 
 
   /**
   /**

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

@@ -120,7 +120,6 @@ module.exports = function(crowi, app) {
     if (userData != null) {
     if (userData != null) {
       renderVars.pageUser = userData;
       renderVars.pageUser = userData;
       renderVars.bookmarkList = await Bookmark.findByUser(userData, {limit: 10, populatePage: true, requestUser: requestUser});
       renderVars.bookmarkList = await Bookmark.findByUser(userData, {limit: 10, populatePage: true, requestUser: requestUser});
-      renderVars.createdList = await Page.findListByCreator(userData, {limit: 10}, requestUser);
     }
     }
   }
   }
 
 
@@ -622,9 +621,9 @@ module.exports = function(crowi, app) {
       options.grantUserGroupId = grantUserGroupId;
       options.grantUserGroupId = grantUserGroupId;
     }
     }
 
 
-    try {
       const Revision = crowi.model('Revision');
       const Revision = crowi.model('Revision');
       const previousRevision = await Revision.findById(revisionId);
       const previousRevision = await Revision.findById(revisionId);
+    try {
       page = await Page.updatePage(page, pageBody, previousRevision.body, req.user, options);
       page = await Page.updatePage(page, pageBody, previousRevision.body, req.user, options);
     }
     }
     catch (err) {
     catch (err) {
@@ -651,8 +650,6 @@ module.exports = function(crowi, app) {
 
 
     // user notification
     // user notification
     if (isSlackEnabled && slackChannels != null) {
     if (isSlackEnabled && slackChannels != null) {
-      const Revision = crowi.model('Revision');
-      const previousRevision = await Revision.findById(page.revision);
       await notifyToSlackByUser(page, req.user, slackChannels, 'update', previousRevision);
       await notifyToSlackByUser(page, req.user, slackChannels, 'update', previousRevision);
     }
     }
   };
   };

+ 1 - 1
src/server/service/file-uploader/gridfs.js

@@ -97,7 +97,7 @@ module.exports = function(crowi) {
 
 
   lib.getFileData = async function(filePath) {
   lib.getFileData = async function(filePath) {
     const file = await getFile(filePath);
     const file = await getFile(filePath);
-    const id = file.id;
+    const id = file._id;
     const contentType = file.contentType;
     const contentType = file.contentType;
     const data = await readFileData(id);
     const data = await readFileData(id);
     return {
     return {

+ 6 - 7
src/server/util/search.js

@@ -541,23 +541,22 @@ SearchClient.prototype.filterPagesByViewer = async function(query, user, userGro
 
 
   const grantConditions = [
   const grantConditions = [
     { term: { grant: GRANT_PUBLIC } },
     { term: { grant: GRANT_PUBLIC } },
+    { bool: {
+      must: [
+        { term: { grant: GRANT_RESTRICTED } },
+        { term: { granted_users: user._id.toString() } }
+      ]
+    } },
   ];
   ];
 
 
   if (showPagesRestrictedByOwner) {
   if (showPagesRestrictedByOwner) {
     grantConditions.push(
     grantConditions.push(
-      { term: { grant: GRANT_RESTRICTED } },
       { term: { grant: GRANT_SPECIFIED } },
       { term: { grant: GRANT_SPECIFIED } },
       { term: { grant: GRANT_OWNER } },
       { term: { grant: GRANT_OWNER } },
     );
     );
   }
   }
   else if (user != null) {
   else if (user != null) {
     grantConditions.push(
     grantConditions.push(
-      { bool: {
-        must: [
-          { term: { grant: GRANT_RESTRICTED } },
-          { term: { granted_users: user._id.toString() } }
-        ]
-      } },
       { bool: {
       { bool: {
         must: [
         must: [
           { term: { grant: GRANT_SPECIFIED } },
           { term: { grant: GRANT_SPECIFIED } },

+ 4 - 4
src/server/util/slack.js

@@ -58,7 +58,7 @@ module.exports = function(crowi) {
   };
   };
 
 
   const prepareAttachmentTextForCreate = function(page, user) {
   const prepareAttachmentTextForCreate = function(page, user) {
-    var body = page.revision.body;
+    let body = page.revision.body;
     if (body.length > 2000) {
     if (body.length > 2000) {
       body = body.substr(0, 2000) + '...';
       body = body.substr(0, 2000) + '...';
     }
     }
@@ -67,13 +67,13 @@ module.exports = function(crowi) {
   };
   };
 
 
   const prepareAttachmentTextForUpdate = function(page, user, previousRevision) {
   const prepareAttachmentTextForUpdate = function(page, user, previousRevision) {
-    var diff = require('diff');
-    var diffText = '';
+    const diff = require('diff');
+    let diffText = '';
 
 
     diff.diffLines(previousRevision.body, page.revision.body).forEach(function(line) {
     diff.diffLines(previousRevision.body, page.revision.body).forEach(function(line) {
       debug('diff line', line);
       debug('diff line', line);
       /* eslint-disable no-unused-vars */
       /* eslint-disable no-unused-vars */
-      var value = line.value.replace(/\r\n|\r/g, '\n');
+      const value = line.value.replace(/\r\n|\r/g, '\n');
       /* eslint-enable */
       /* eslint-enable */
       if (line.added) {
       if (line.added) {
         diffText += `${line.value} ... :lower_left_fountain_pen:`;
         diffText += `${line.value} ... :lower_left_fountain_pen:`;

+ 1 - 1
src/server/views/admin/app.html

@@ -174,7 +174,7 @@
                    id="settingForm[mail.from]"
                    id="settingForm[mail.from]"
                    type="text"
                    type="text"
                    name="settingForm[mail:from]"
                    name="settingForm[mail:from]"
-                   placeholder="例: mail@growi.org"
+                   placeholder="{{ t('eg') }} mail@growi.org"
                    value="{{ settingForm['mail:from'] }}">
                    value="{{ settingForm['mail:from'] }}">
           </div>
           </div>
         </div>
         </div>

+ 6 - 8
src/server/views/admin/customize.html

@@ -377,11 +377,10 @@ export  $initHighlight;</code></pre>
 
 
       <form action="/_api/admin/customize/title" method="post" class="form-horizontal" id="customtitleSettingForm" role="form">
       <form action="/_api/admin/customize/title" method="post" class="form-horizontal" id="customtitleSettingForm" role="form">
         <fieldset>
         <fieldset>
-          <legend>カスタム Title</legend>
+          <legend>{{ t('customize_page.custom_title') }}</legend>
 
 
           <p class="well">
           <p class="well">
-            <code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。<br>
-            <code>&#123;&#123;sitename&#125;&#125;</code>がサイト名、<code>&#123;&#123;page&#125;&#125;</code>がページ名またはページパスに置換されます
+            {{ t('customize_page.custom_title_detail', '&lt;title&gt;', '&#123;&#123;sitename&#125;&#125;', '&#123;&#123;page&#125;&#125;') }}
           </p>
           </p>
 
 
           <p class="help-block">
           <p class="help-block">
@@ -408,15 +407,14 @@ export  $initHighlight;</code></pre>
 
 
       <form action="/_api/admin/customize/header" method="post" class="form-horizontal" id="customheaderSettingForm" role="form">
       <form action="/_api/admin/customize/header" method="post" class="form-horizontal" id="customheaderSettingForm" role="form">
       <fieldset>
       <fieldset>
-        <legend>カスタムヘッダーHTML</legend>
+        <legend>{{ t('customize_page.custom_header') }}</legend>
 
 
         <p class="well">
         <p class="well">
-          システム全体に適用される HTML を記述できます。<code>&lt;header&gt;</code> タグ内の他の <code>&lt;script&gt;</code> タグ読み込み前に展開されます。<br>
-          変更の反映はページの更新が必要です。
+          {{ t('customize_page.custom_header_detail', '&lt;header&gt;', '&lt;script&gt;') }}
         </p>
         </p>
 
 
         <p class="help-block">
         <p class="help-block">
-          Examples:
+          {{ t('Example') }}:
           <pre class="hljs"><code>&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js" defer&gt;&lt;/script&gt;</code></pre>
           <pre class="hljs"><code>&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js" defer&gt;&lt;/script&gt;</code></pre>
         </p>
         </p>
 
 
@@ -428,7 +426,7 @@ export  $initHighlight;</code></pre>
           <div class="col-xs-12">
           <div class="col-xs-12">
             <p class="help-block text-right">
             <p class="help-block text-right">
               <i class="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
               <i class="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
-              Ctrl+Space でコード補完
+              {{ t("customize_page.ctrl_space") }}
             </p>
             </p>
           </div>
           </div>
         </div>
         </div>

+ 10 - 9
src/server/views/admin/external-accounts.html

@@ -42,25 +42,26 @@
       <p>
       <p>
         <a class="btn btn-default" href="/admin/users">
         <a class="btn btn-default" href="/admin/users">
           <i class="icon-fw ti-arrow-left" aria-hidden="true"></i>
           <i class="icon-fw ti-arrow-left" aria-hidden="true"></i>
-          ユーザー管理に戻る
+          {{ t('user_management.back_to_user_management') }}
         </a>
         </a>
       </p>
       </p>
 
 
-      <h2>外部アカウント一覧</h2>
+      <h2>{{ t('user_management.external_account_list') }}</h2>
 
 
       <table class="table table-bordered table-user-list">
       <table class="table table-bordered table-user-list">
         <thead>
         <thead>
           <tr>
           <tr>
-            <th width="120px">Authentication Provider</th>
+            <th width="120px">{{ t('user_management.authentication_provider') }}</th>
             <th><code>accountId</code></th>
             <th><code>accountId</code></th>
-            <th>関連付けられているユーザーの <code>username</code></th>
+            <th>{{ t('user_management.related_username', 'username') }}</th>
+
             <th>
             <th>
-              パスワード設定
+              {{ t('user_management.password_setting') }}
               <a class="text-muted"
               <a class="text-muted"
                   data-toggle="popover" data-placement="top"
                   data-toggle="popover" data-placement="top"
                   data-trigger="hover focus" tabindex="0" role="button" {# dismiss settings #}
                   data-trigger="hover focus" tabindex="0" role="button" {# dismiss settings #}
                   data-animation="false" data-html="true"
                   data-animation="false" data-html="true"
-                  data-content="<small>関連付けられているユーザーがパスワードを設定しているかどうかを表示します</small>">
+                  data-content="<small>{{ t('user_management.password_setting_help') }}</small>">
                 <small>
                 <small>
                   <i class="icon-question" aria-hidden="true"></i>
                   <i class="icon-question" aria-hidden="true"></i>
                 </small>
                 </small>
@@ -83,11 +84,11 @@
             <td>
             <td>
               {% if account.user.password != null %}
               {% if account.user.password != null %}
               <span class="label label-info">
               <span class="label label-info">
-                設定済み
+                {{ t('user_management.set') }}
               </span>
               </span>
               {% else %}
               {% else %}
               <span class="label label-warning">
               <span class="label label-warning">
-                未設定
+                {{ t('user_management.unset') }}
               </span>
               </span>
               {% endif %}
               {% endif %}
             </td>
             </td>
@@ -106,7 +107,7 @@
                   <li>
                   <li>
                     <a href="javascript:form_remove_{{ loop.index }}.submit()">
                     <a href="javascript:form_remove_{{ loop.index }}.submit()">
                       <i class="icon-fw icon-fire text-danger"></i>
                       <i class="icon-fw icon-fire text-danger"></i>
-                      削除する
+                      {{ t('Delete') }}
                     </a>
                     </a>
                   </li>
                   </li>
                 </ul>{# end of .dropdown-menu #}
                 </ul>{# end of .dropdown-menu #}

+ 12 - 12
src/server/views/admin/users.html

@@ -44,14 +44,14 @@
       <form role="form" action="/admin/user/invite" method="post">
       <form role="form" action="/admin/user/invite" method="post">
         <div id="inviteUserForm" class="collapse">
         <div id="inviteUserForm" class="collapse">
           <div class="form-group">
           <div class="form-group">
-            <label for="inviteForm[emailList]">メールアドレス (複数行入力で複数人招待可能)</label>
-            <textarea class="form-control" name="inviteForm[emailList]" placeholder="例: user@growi.org"></textarea>
+            <label for="inviteForm[emailList]">{{ t('user_management.emails') }}</label>
+            <textarea class="form-control" name="inviteForm[emailList]" placeholder="{{ t('eg') }} user@growi.org"></textarea>
           </div>
           </div>
           <div class="checkbox checkbox-info">
           <div class="checkbox checkbox-info">
             <input type="checkbox" id="inviteWithEmail" name="inviteForm[sendEmail]" checked>
             <input type="checkbox" id="inviteWithEmail" name="inviteForm[sendEmail]" checked>
-            <label for="inviteWithEmail">招待をメールで送信</label>
+            <label for="inviteWithEmail">{{ t('user_management.invite_thru_email') }}</label>
           </div>
           </div>
-          <button type="submit" class="btn btn-primary">招待する</button>
+          <button type="submit" class="btn btn-primary">{{ t('user_management.invite') }}</button>
         </div>
         </div>
         <input type="hidden" name="_csrf" value="{{ csrf() }}">
         <input type="hidden" name="_csrf" value="{{ csrf() }}">
       </form>
       </form>
@@ -150,7 +150,7 @@
         <thead>
         <thead>
           <tr>
           <tr>
             <th width="100px">#</th>
             <th width="100px">#</th>
-            <th>Status</th>
+            <th>{{ t('user_management.Status') }}</th>
             <th><code>username</code></th>
             <th><code>username</code></th>
             <th>{{ t('Name') }}</th>
             <th>{{ t('Name') }}</th>
             <th>{{ t('Email') }}</th>
             <th>{{ t('Email') }}</th>
@@ -213,7 +213,7 @@
                   </form>
                   </form>
                   <li>
                   <li>
                     <a href="javascript:form_activate_{{ sUserId }}.submit()">
                     <a href="javascript:form_activate_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-user-following"></i> 承認する
+                      <i class="icon-fw icon-user-following"></i> {{ t('user_management.accept') }}
                     </a>
                     </a>
                   </li>
                   </li>
                   {% endif  %}
                   {% endif  %}
@@ -247,13 +247,13 @@
                   </form>
                   </form>
                   <li>
                   <li>
                     <a href="javascript:form_activate_{{ sUserId }}.submit()">
                     <a href="javascript:form_activate_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-action-redo"></i> 元に戻す
+                      <i class="icon-fw icon-action-redo"></i> {{ t('Undo') }}
                     </a>
                     </a>
                   </li>
                   </li>
                   <li>
                   <li>
                     {# label は同じだけど、こっちは論理削除 #}
                     {# label は同じだけど、こっちは論理削除 #}
                     <a href="javascript:form_remove_{{ sUserId }}.submit()">
                     <a href="javascript:form_remove_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-fire text-danger"></i> 削除する
+                      <i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}
                     </a>
                     </a>
                   </li>
                   </li>
                   {% endif  %}
                   {% endif  %}
@@ -265,7 +265,7 @@
                   <li class="dropdown-button">
                   <li class="dropdown-button">
                     {# label は同じだけど、こっちは物理削除 #}
                     {# label は同じだけど、こっちは物理削除 #}
                     <a href="javascript:form_removeCompletely_{{ sUserId }}.submit()">
                     <a href="javascript:form_removeCompletely_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-fire text-danger"></i> 削除する
+                      <i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}
                     </a>
                     </a>
                   </li>
                   </li>
                   {% endif  %}
                   {% endif  %}
@@ -281,11 +281,11 @@
                   <li>
                   <li>
                     {% if sUser.username != user.username %}
                     {% if sUser.username != user.username %}
                       <a href="javascript:form_removeFromAdmin_{{ sUserId }}.submit()">
                       <a href="javascript:form_removeFromAdmin_{{ sUserId }}.submit()">
-                        <i class="icon-fw icon-user-unfollow"></i> 管理者からはずす
+                        <i class="icon-fw icon-user-unfollow"></i> {{ t("user_management.remove_admin_access") }}
                       </a>
                       </a>
                     {% else %}
                     {% else %}
                       <a disabled>
                       <a disabled>
-                        <i class="icon-fw icon-user-unfollow"></i> 管理者からはずす
+                        <i class="icon-fw icon-user-unfollow"></i> {{ t("user_management.remove_admin_access") }}
                       </a>
                       </a>
                       <p class="alert alert-danger m-l-10 m-r-10 p-10">{{ t("user_management.cannot_remove") }}</p>
                       <p class="alert alert-danger m-l-10 m-r-10 p-10">{{ t("user_management.cannot_remove") }}</p>
                     {% endif %}
                     {% endif %}
@@ -296,7 +296,7 @@
                   </form>
                   </form>
                   <li>
                   <li>
                     <a href="javascript:form_makeAdmin_{{ sUserId }}.submit()">
                     <a href="javascript:form_makeAdmin_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-magic-wand"></i> 管理者にする
+                      <i class="icon-fw icon-magic-wand"></i> {{ t("user_management.give_admin_access") }}
                     </a>
                     </a>
                   </li>
                   </li>
                   {% endif %}
                   {% endif %}

+ 25 - 7
src/test/models/page.test.js

@@ -76,7 +76,7 @@ describe('Page', () => {
         path: '/grant/specified',
         path: '/grant/specified',
         grant: Page.GRANT_SPECIFIED,
         grant: Page.GRANT_SPECIFIED,
         grantedUsers: [testUser0],
         grantedUsers: [testUser0],
-        creator: testUser0
+        creator: testUser0,
       },
       },
       {
       {
         path: '/grant/owner',
         path: '/grant/owner',
@@ -92,7 +92,7 @@ describe('Page', () => {
       },
       },
       {
       {
         path: '/grant/groupacl',
         path: '/grant/groupacl',
-        grant: 5,
+        grant: Page.GRANT_USER_GROUP,
         grantedUsers: [],
         grantedUsers: [],
         grantedGroup: testGroup0,
         grantedGroup: testGroup0,
         creator: testUser1,
         creator: testUser1,
@@ -233,7 +233,7 @@ describe('Page', () => {
     context('with a restricted page and an user who has no grant', () => {
     context('with a restricted page and an user who has no grant', () => {
       it('should return false', async() => {
       it('should return false', async() => {
         const user = await User.findOne({email: 'anonymous1@example.com'});
         const user = await User.findOne({email: 'anonymous1@example.com'});
-        const page = await Page.findOne({path: '/grant/restricted'});
+        const page = await Page.findOne({path: '/grant/owner'});
 
 
         const bool = await Page.isAccessiblePageByViewer(page.id, user);
         const bool = await Page.isAccessiblePageByViewer(page.id, user);
         expect(bool).to.be.equal(false);
         expect(bool).to.be.equal(false);
@@ -283,8 +283,8 @@ describe('Page', () => {
 
 
   describe('.findPage', () => {
   describe('.findPage', () => {
     context('findByIdAndViewer', () => {
     context('findByIdAndViewer', () => {
-      it('should find page', async() => {
-        const pageToFind = createdPages[0];
+      it('should find page (public)', async() => {
+        const pageToFind = createdPages[1];
         const grantedUser = createdUsers[0];
         const grantedUser = createdUsers[0];
 
 
         const page = await Page.findByIdAndViewer(pageToFind._id, grantedUser);
         const page = await Page.findByIdAndViewer(pageToFind._id, grantedUser);
@@ -292,8 +292,26 @@ describe('Page', () => {
         expect(page.path).to.equal(pageToFind.path);
         expect(page.path).to.equal(pageToFind.path);
       });
       });
 
 
-      it('should not be found by grant', async() => {
-        const pageToFind = createdPages[0];
+      it('should find page (anyone knows link)', async() => {
+        const pageToFind = createdPages[2];
+        const grantedUser = createdUsers[1];
+
+        const page = await Page.findByIdAndViewer(pageToFind._id, grantedUser);
+        expect(page).to.be.not.null;
+        expect(page.path).to.equal(pageToFind.path);
+      });
+
+      it('should find page (just me)', async() => {
+        const pageToFind = createdPages[4];
+        const grantedUser = createdUsers[0];
+
+        const page = await Page.findByIdAndViewer(pageToFind._id, grantedUser);
+        expect(page).to.be.not.null;
+        expect(page.path).to.equal(pageToFind.path);
+      });
+
+      it('should not be found by grant (just me)', async() => {
+        const pageToFind = createdPages[4];
         const grantedUser = createdUsers[1];
         const grantedUser = createdUsers[1];
 
 
         const page = await Page.findByIdAndViewer(pageToFind._id, grantedUser);
         const page = await Page.findByIdAndViewer(pageToFind._id, grantedUser);

+ 72 - 86
yarn.lock

@@ -31,33 +31,34 @@
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@handsontable/react/-/react-2.0.0.tgz#30d9c2bd05421588a6ed1b3050b1f7dc476b35d3"
   resolved "https://registry.yarnpkg.com/@handsontable/react/-/react-2.0.0.tgz#30d9c2bd05421588a6ed1b3050b1f7dc476b35d3"
 
 
-"@lykmapipo/gridfs-stream@^1.2.0":
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/@lykmapipo/gridfs-stream/-/gridfs-stream-1.2.0.tgz#0f74826816b4f7414ae36862d67ce4849a224d91"
-  dependencies:
-    flushwritable "^1.0.0"
-
 "@sinonjs/commons@^1.0.2":
 "@sinonjs/commons@^1.0.2":
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.0.2.tgz#3e0ac737781627b8844257fadc3d803997d0526e"
   resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.0.2.tgz#3e0ac737781627b8844257fadc3d803997d0526e"
   dependencies:
   dependencies:
     type-detect "4.0.8"
     type-detect "4.0.8"
 
 
-"@sinonjs/formatio@3.0.0", "@sinonjs/formatio@^3.0.0":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.0.0.tgz#9d282d81030a03a03fa0c5ce31fd8786a4da311a"
+"@sinonjs/commons@^1.2.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.3.0.tgz#50a2754016b6f30a994ceda6d9a0a8c36adda849"
+  integrity sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==
   dependencies:
   dependencies:
-    "@sinonjs/samsam" "2.1.0"
+    type-detect "4.0.8"
 
 
-"@sinonjs/samsam@2.1.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.0.tgz#b8b8f5b819605bd63601a6ede459156880f38ea3"
+"@sinonjs/formatio@^3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.1.0.tgz#6ac9d1eb1821984d84c4996726e45d1646d8cce5"
+  integrity sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==
   dependencies:
   dependencies:
-    array-from "^2.1.1"
+    "@sinonjs/samsam" "^2 || ^3"
 
 
-"@sinonjs/samsam@^2.1.2":
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.2.tgz#16947fce5f57258d01f1688fdc32723093c55d3f"
+"@sinonjs/samsam@^2 || ^3", "@sinonjs/samsam@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.0.2.tgz#304fb33bd5585a0b2df8a4c801fcb47fa84d8e43"
+  integrity sha512-m08g4CS3J6lwRQk1pj1EO+KEVWbrbXsmi9Pw0ySmrIbcVxVaedoFgLvFsV8wHLwh01EpROVz3KvVcD1Jmks9FQ==
+  dependencies:
+    "@sinonjs/commons" "^1.0.2"
+    array-from "^2.1.1"
+    lodash.get "^4.4.2"
 
 
 "@types/body-parser@*":
 "@types/body-parser@*":
   version "1.16.8"
   version "1.16.8"
@@ -1570,7 +1571,7 @@ bs-recipes@1.3.4:
   version "1.3.4"
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/bs-recipes/-/bs-recipes-1.3.4.tgz#0d2d4d48a718c8c044769fdc4f89592dc8b69585"
   resolved "https://registry.yarnpkg.com/bs-recipes/-/bs-recipes-1.3.4.tgz#0d2d4d48a718c8c044769fdc4f89592dc8b69585"
 
 
-bson@^1.1.0:
+bson@^1.1.0, bson@~1.1.0:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.0.tgz#bee57d1fb6a87713471af4e32bcae36de814b5b0"
   resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.0.tgz#bee57d1fb6a87713471af4e32bcae36de814b5b0"
 
 
@@ -1578,10 +1579,6 @@ bson@~1.0.4:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.4.tgz#93c10d39eaa5b58415cbc4052f3e53e562b0b72c"
   resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.4.tgz#93c10d39eaa5b58415cbc4052f3e53e562b0b72c"
 
 
-bson@~1.0.5:
-  version "1.0.9"
-  resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.9.tgz#12319f8323b1254739b7c6bef8d3e89ae05a2f57"
-
 buffer-equal-constant-time@1.0.1:
 buffer-equal-constant-time@1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
   resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@@ -2762,6 +2759,11 @@ diff@^3.3.1:
   version "3.4.0"
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
 
 
+diff@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
+  integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
+
 diffie-hellman@^5.0.0:
 diffie-hellman@^5.0.0:
   version "5.0.2"
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
@@ -3661,10 +3663,6 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.1"
     inherits "^2.0.1"
     readable-stream "^2.0.4"
     readable-stream "^2.0.4"
 
 
-flushwritable@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/flushwritable/-/flushwritable-1.0.0.tgz#3e328d8fde412ad47e738e3be750b4d290043498"
-
 fn-args@3.0.0:
 fn-args@3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/fn-args/-/fn-args-3.0.0.tgz#df5c3805ed41ec3b38a72aabe390cf9493ec084c"
   resolved "https://registry.yarnpkg.com/fn-args/-/fn-args-3.0.0.tgz#df5c3805ed41ec3b38a72aabe390cf9493ec084c"
@@ -5068,9 +5066,10 @@ jsx-ast-utils@^2.0.1:
   dependencies:
   dependencies:
     array-includes "^3.0.3"
     array-includes "^3.0.3"
 
 
-just-extend@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-3.0.0.tgz#cee004031eaabf6406da03a7b84e4fe9d78ef288"
+just-extend@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc"
+  integrity sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==
 
 
 jwa@^1.1.4:
 jwa@^1.1.4:
   version "1.1.5"
   version "1.1.5"
@@ -5335,7 +5334,7 @@ lodash.foreach@^4.1.0:
   version "4.5.0"
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
   resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
 
 
-lodash.get@4.4.2, lodash.get@^4.0, lodash.get@^4.0.2, lodash.get@^4.4.2:
+lodash.get@^4.0, lodash.get@^4.0.2, lodash.get@^4.4.2:
   version "4.4.2"
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
 
 
@@ -5395,7 +5394,7 @@ lodash.uniq@^4.5.0:
   version "4.5.0"
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
 
-lodash@4.17.11, lodash@^4.17.11:
+lodash@4.17.11, lodash@>=4.17.11, lodash@^4.17.11:
   version "4.17.11"
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
 
 
@@ -5522,9 +5521,10 @@ markdown-it-named-headers@^0.0.4:
   dependencies:
   dependencies:
     string "^3.0.1"
     string "^3.0.1"
 
 
-markdown-it-plantuml@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/markdown-it-plantuml/-/markdown-it-plantuml-1.0.0.tgz#7b6a351a1d9275705c09626b02d873301e5899c2"
+markdown-it-plantuml@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/markdown-it-plantuml/-/markdown-it-plantuml-1.3.0.tgz#a4eb7bcdc718f6b3b889840d974e5f9184a90102"
+  integrity sha512-BW4/dzaIFX3D3dMnifmYko2o2+gajkkt1VQpEuicC6E7sCtKgjOAI4qpYmJKH70guGH0qoK0sNMHXJfi3noTbQ==
 
 
 markdown-it-task-checkbox@^1.0.6:
 markdown-it-task-checkbox@^1.0.6:
   version "1.0.6"
   version "1.0.6"
@@ -5877,16 +5877,6 @@ mongodb-core@2.1.19:
     bson "~1.0.4"
     bson "~1.0.4"
     require_optional "~1.0.0"
     require_optional "~1.0.0"
 
 
-mongodb-core@3.1.5:
-  version "3.1.5"
-  resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.1.5.tgz#59ca67d7f6cea570d5437624a7afec8d752d477a"
-  dependencies:
-    bson "^1.1.0"
-    require_optional "^1.0.1"
-    safe-buffer "^5.1.2"
-  optionalDependencies:
-    saslprep "^1.0.0"
-
 mongodb-core@3.1.9:
 mongodb-core@3.1.9:
   version "3.1.9"
   version "3.1.9"
   resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.1.9.tgz#c31ee407bf932b0149eaed775c17ee09974e4ca3"
   resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.1.9.tgz#c31ee407bf932b0149eaed775c17ee09974e4ca3"
@@ -5904,13 +5894,6 @@ mongodb@3.1.10:
     mongodb-core "3.1.9"
     mongodb-core "3.1.9"
     safe-buffer "^5.1.2"
     safe-buffer "^5.1.2"
 
 
-mongodb@3.1.6:
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.1.6.tgz#6054641973b5bf5b5ae1c67dcbcf8fa88280273d"
-  dependencies:
-    mongodb-core "3.1.5"
-    safe-buffer "^5.1.2"
-
 mongodb@^2.0.36:
 mongodb@^2.0.36:
   version "2.2.35"
   version "2.2.35"
   resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.35.tgz#cd1b5af8a9463e3f9a787fa5b3d05565579730f9"
   resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.35.tgz#cd1b5af8a9463e3f9a787fa5b3d05565579730f9"
@@ -5919,13 +5902,13 @@ mongodb@^2.0.36:
     mongodb-core "2.1.19"
     mongodb-core "2.1.19"
     readable-stream "2.2.7"
     readable-stream "2.2.7"
 
 
-mongoose-gridfs@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/mongoose-gridfs/-/mongoose-gridfs-0.5.0.tgz#626e12ab605c2ed2a205a5953cd5aa8615f44feb"
+mongoose-gridfs@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/mongoose-gridfs/-/mongoose-gridfs-0.6.2.tgz#b144219af01c51b14c58e5cdf7f58293d2ce1d67"
+  integrity sha512-fW6+D1Pn+qtpuYK3lYl1OMd3wenNIJ0/KhpOAoiRZWdzHjABiUQrtZSCyLEjmVgsxuTqIsUqvS1KeMUvUmtO2A==
   dependencies:
   dependencies:
-    "@lykmapipo/gridfs-stream" "^1.2.0"
-    lodash "^4.17.10"
-    stream-read "^1.1.2"
+    lodash ">=4.17.11"
+    stream-read ">=1.1.2"
 
 
 mongoose-legacy-pluralize@1.0.2:
 mongoose-legacy-pluralize@1.0.2:
   version "1.0.2"
   version "1.0.2"
@@ -5944,16 +5927,16 @@ mongoose-unique-validator@^2.0.2:
     lodash.foreach "^4.1.0"
     lodash.foreach "^4.1.0"
     lodash.get "^4.0.2"
     lodash.get "^4.0.2"
 
 
-mongoose@^5.3.1:
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.3.1.tgz#52d5bfb67788a2194e5f7a2a5c0d597e4b86fd7a"
+mongoose@^5.4.4:
+  version "5.4.4"
+  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.4.4.tgz#923923cd39a03b970c269f828322990ea163f9a8"
+  integrity sha512-KoYUFtgrXQ9Sxuf5IEE0Y2LswWGhHiN27bxtObANxMgXbuNLTtLB3xaep8jITUDitG4kkI+FKElUJH4uY8G2Bw==
   dependencies:
   dependencies:
     async "2.6.1"
     async "2.6.1"
-    bson "~1.0.5"
+    bson "~1.1.0"
     kareem "2.3.0"
     kareem "2.3.0"
-    lodash.get "4.4.2"
-    mongodb "3.1.6"
-    mongodb-core "3.1.5"
+    mongodb "3.1.10"
+    mongodb-core "3.1.9"
     mongoose-legacy-pluralize "1.0.2"
     mongoose-legacy-pluralize "1.0.2"
     mpath "0.5.1"
     mpath "0.5.1"
     mquery "3.2.0"
     mquery "3.2.0"
@@ -6095,12 +6078,13 @@ nice-try@^1.0.4:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
 
 
-nise@^1.4.5:
-  version "1.4.6"
-  resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.6.tgz#76cc3915925056ae6c405dd8ad5d12bde570c19f"
+nise@^1.4.7:
+  version "1.4.8"
+  resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.8.tgz#ce91c31e86cf9b2c4cac49d7fcd7f56779bfd6b0"
+  integrity sha512-kGASVhuL4tlAV0tvA34yJYZIVihrUt/5bDwpp4tTluigxUr2bBlJeDXmivb6NuEdFkqvdv/Ybb9dm16PSKUhtw==
   dependencies:
   dependencies:
-    "@sinonjs/formatio" "3.0.0"
-    just-extend "^3.0.0"
+    "@sinonjs/formatio" "^3.1.0"
+    just-extend "^4.0.2"
     lolex "^2.3.2"
     lolex "^2.3.2"
     path-to-regexp "^1.7.0"
     path-to-regexp "^1.7.0"
     text-encoding "^0.6.4"
     text-encoding "^0.6.4"
@@ -6270,9 +6254,10 @@ nodemailer-ses-transport@~1.5.0:
   dependencies:
   dependencies:
     aws-sdk "^2.2.36"
     aws-sdk "^2.2.36"
 
 
-nodemailer@^4.0.1:
-  version "4.4.1"
-  resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.4.1.tgz#ce480eb3db7b949b3366e301b8f0af1c1248025e"
+nodemailer@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-5.1.1.tgz#0c48d1ecab02e86d9ff6c620ee75ed944b763505"
+  integrity sha512-hKGCoeNdFL2W7S76J/Oucbw0/qRlfG815tENdhzcqTpSjKgAN91mFOqU2lQUflRRxFM7iZvCyaFcAR9noc/CqQ==
 
 
 nopt@1.0.10:
 nopt@1.0.10:
   version "1.0.10"
   version "1.0.10"
@@ -8419,23 +8404,23 @@ simple-swizzle@^0.2.2:
   dependencies:
   dependencies:
     is-arrayish "^0.3.1"
     is-arrayish "^0.3.1"
 
 
-sinon-chai@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.2.0.tgz#ed995e13a8a3cfccec18f218d9b767edc47e0715"
+sinon-chai@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.3.0.tgz#8084ff99451064910fbe2c2cb8ab540c00b740ea"
+  integrity sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==
 
 
-sinon@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.0.0.tgz#99f2e5198d90a01ccbcebd4dc181a24827cb90dd"
+sinon@^7.2.2:
+  version "7.2.2"
+  resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.2.2.tgz#388ecabd42fa93c592bfc71d35a70894d5a0ca07"
+  integrity sha512-WLagdMHiEsrRmee3jr6IIDntOF4kbI6N2pfbi8wkv50qaUQcBglkzkjtoOEbeJ2vf1EsrHhLI+5Ny8//WHdMoA==
   dependencies:
   dependencies:
-    "@sinonjs/commons" "^1.0.2"
-    "@sinonjs/formatio" "^3.0.0"
-    "@sinonjs/samsam" "^2.1.2"
+    "@sinonjs/commons" "^1.2.0"
+    "@sinonjs/formatio" "^3.1.0"
+    "@sinonjs/samsam" "^3.0.2"
     diff "^3.5.0"
     diff "^3.5.0"
-    lodash.get "^4.4.2"
     lolex "^3.0.0"
     lolex "^3.0.0"
-    nise "^1.4.5"
+    nise "^1.4.7"
     supports-color "^5.5.0"
     supports-color "^5.5.0"
-    type-detect "^4.0.8"
 
 
 slack-node@^0.1.8:
 slack-node@^0.1.8:
   version "0.1.8"
   version "0.1.8"
@@ -8696,9 +8681,10 @@ stream-http@^2.7.2:
     to-arraybuffer "^1.0.0"
     to-arraybuffer "^1.0.0"
     xtend "^4.0.0"
     xtend "^4.0.0"
 
 
-stream-read@^1.1.2:
+stream-read@>=1.1.2:
   version "1.1.2"
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/stream-read/-/stream-read-1.1.2.tgz#3137110d7aa80ba54e4b829c4cd33ca106b9564d"
   resolved "https://registry.yarnpkg.com/stream-read/-/stream-read-1.1.2.tgz#3137110d7aa80ba54e4b829c4cd33ca106b9564d"
+  integrity sha1-MTcRDXqoC6VOS4KcTNM8oQa5Vk0=
   dependencies:
   dependencies:
     dezalgo "^1.0.1"
     dezalgo "^1.0.1"
 
 
@@ -9135,7 +9121,7 @@ type-check@~0.3.2:
   dependencies:
   dependencies:
     prelude-ls "~1.1.2"
     prelude-ls "~1.1.2"
 
 
-type-detect@4.0.8, type-detect@^4.0.8:
+type-detect@4.0.8:
   version "4.0.8"
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"