Przeglądaj źródła

Merge branch 'master' into imprv/search-help

# Conflicts:
#	resource/js/components/HeaderSearchBox/SearchForm.js
#	resource/js/components/SearchTypeahead.js
mayu morita 7 lat temu
rodzic
commit
99d577b5df

+ 16 - 4
CHANGES.md

@@ -3,14 +3,26 @@ CHANGES
 
 ## 3.2.0-RC
 
-* Feature: Simultaneously edit by multiple people with HackMD integration
+* Feature: HackMD integration so that user can simultaneously edit with multiple people
+
+## 3.1.14-RC
+
+* 
+
+## 3.1.13
+
+* Feature: Global Notification
+* Feature: Send Global Notification with E-mail
+* Improvement: Add attribute mappings for email to LDAP settings
 * Support: Upgrade libs
+    * autoprefixer
+    * css-loader
+    * method-override
+    * optimize-css-assets-webpack-plugin
     * react
+    * react-bootstrap-typeahead
     * react-dom
 
-## 3.1.13-RC
-
-* Improvement: Add attribute mappings for email to LDAP settings
 
 ## 3.1.12
 

+ 4 - 1
lib/locales/en-US/translation.json

@@ -433,7 +433,10 @@
     "event_pageDelete": "When page is \"DELETED\"",
     "event_pageMove": "When page is \"MOVED\" (renamed)",
     "event_pageLike": "When someone \"LIKES\" page",
-    "event_comment": "When someone \"COMMENTS\" on page"
+    "event_comment": "When someone \"COMMENTS\" on page",
+    "email": {
+      "ifttt_link": "Create a new IFTTT applet with Email trigger"
+    }
   },
 
   "customize_page": {

+ 4 - 1
lib/locales/ja/translation.json

@@ -449,7 +449,10 @@
     "event_pageDelete": "ページが削除されたとき",
     "event_pageMove": "ページが移動(名前が変更)されたとき",
     "event_pageLike": "ページに「いいね」がついたとき",
-    "event_comment": "コメントが投稿されたとき"
+    "event_comment": "コメントが投稿されたとき",
+    "email": {
+      "ifttt_link": "IFTTT でメールトリガの新しいアプレットを作る"
+    }
   },
 
   "customize_page": {

+ 93 - 87
lib/views/admin/global-notification-detail.html

@@ -42,105 +42,111 @@
       {% else %}
         {% set actionPath = '/admin/global-notification/new' %}
       {% endif %}
-      <div class="m-t-20 form-box col-md-11">
-        <form action="{{ actionPath }}" method="post" class="form-horizontal" role="form">
-          <legend>{{ t('notification_setting.notification_detail') }}</legend>
 
-          <fieldset class="col-sm-offset-1 col-sm-4">
-            <div class="form-group">
-              <label for="triggerPath" class="control-label">{{ t('notification_setting.trigger_path') }}</label> {{ t('notification_setting.trigger_path_help', '<code>*</code>') }}<br />
-              <input class="form-control" type="text" name="notificationGlobal[triggerPath]" value="{{ setting.triggerPath || '' }}" required>
-            </div>
+      <div class="row">
+        <div class="m-t-20 form-box col-md-12">
+          <legend>{{ t('notification_setting.notification_detail') }}</legend>
 
-            <div class="form-group">
-              <label for="notificationGlobal[notifyToType]"class="control-label">{{ t('notification_setting.notify_to') }}</label><br />
-              <div class="radio radio-primary">
-                <input type="radio" id="mail" name="notificationGlobal[notifyToType]" value="mail" {% if setting.__t == 'mail' %}checked{% endif %} checked>
-                <label for="mail">
-                  <p class="font-weight-bold">Email</p>
-                </label>
+          <form action="{{ actionPath }}" method="post" class="form-horizontal" role="form">
+            <fieldset class="col-sm-4">
+              <div class="form-group">
+                <h3 for="triggerPath">{{ t('notification_setting.trigger_path') }} <small>{{ t('notification_setting.trigger_path_help', '<code>*</code>') }}</small></h3>
+                <input class="form-control" type="text" name="notificationGlobal[triggerPath]" value="{{ setting.triggerPath || '' }}" required>
               </div>
-              <div class="radio radio-primary">
-                <input type="radio" id="slack" name="notificationGlobal[notifyToType]" value="slack" {% if setting.__t == 'slack' %}checked{% endif %} disabled>
-                <label for="slack">
-                  <p class="font-weight-bold">Slack</p>
-                </label>
-              </div>
-            </div>
-
-            <!-- <div class="form-group notify-to-option {% if setting.__t != 'mail' %}d-none{% endif %}" id="mail-input"> -->
-            <div class="form-group notify-to-option" id="mail-input">
-              <label for="notificationGlobal[toEmail]"class="control-label">Email</label><br />
-              <input class="form-control" type="text" name="notificationGlobal[toEmail]" value="{{ setting.toEmail || '' }}">
-            </div>
 
-            <div class="form-group notify-to-option {% if setting.__t != 'slack' %}d-none{% endif %}" id="slack-input">
-              <label for="notificationGlobal[slackChannels]"class="control-label">Slack Channels</label><br />
-              <input class="form-control" type="text" name="notificationGlobal[slackChannels]" value="{{ setting.slackChannels || '' }}" disabled>
-            </div>
-          </fieldset>
-
-          <fieldset class="col-sm-offset-1 col-sm-5">
-            <div class="form-group">
-              <label for="triggerEvent"class="control-label">{{ t('notification_setting.trigger_events') }}</label><br />
-              <div class="checkbox checkbox-info">
-                <input type="checkbox" id="trigger-event-pageCreate" name="notificationGlobal[triggerEvent:pageCreate]" value="pageCreate"
-                  {% if setting && (setting.triggerEvents.indexOf('pageCreate') != -1) %}checked{% endif %} />
-                <label for="trigger-event-pageCreate">
-                  <span class="label label-info"><i class="icon-doc"></i> CREATE</span> - {{ t('notification_setting.event_pageCreate') }}
-                </label>
-              </div>
-              <div class="checkbox checkbox-info">
-                <input type="checkbox" id="trigger-event-pageEdit" name="notificationGlobal[triggerEvent:pageEdit]" value="pageEdit"
-                  {% if setting && (setting.triggerEvents.indexOf('pageEdit') != -1) %}checked{% endif %} />
-                <label for="trigger-event-pageEdit">
-                  <span class="label label-info"><i class="icon-doc"></i> EDIT</span> - {{ t('notification_setting.event_pageEdit') }}
-                </label>
+              <div class="form-group form-inline">
+                <h3>{{ t('notification_setting.notify_to') }}</h3>
+                <div class="radio radio-primary">
+                  <input type="radio" id="mail" name="notificationGlobal[notifyToType]" value="mail" {% if setting.__t == 'mail' %}checked{% endif %} checked>
+                  <label for="mail">
+                    <p class="font-weight-bold">Email</p>
+                  </label>
+                </div>
+                <div class="radio radio-primary">
+                  <input type="radio" id="slack" name="notificationGlobal[notifyToType]" value="slack" {% if setting.__t == 'slack' %}checked{% endif %} disabled>
+                  <label for="slack">
+                    <p class="font-weight-bold">Slack</p>
+                  </label>
+                </div>
               </div>
-              <div class="checkbox checkbox-info">
-                <input type="checkbox" id="trigger-event-pageDelete" name="notificationGlobal[triggerEvent:pageDelete]" value="pageDelete"
-                  {% if setting && (setting.triggerEvents.indexOf('pageDelete') != -1) %}checked{% endif %} />
-                <label for="trigger-event-pageDelete">
-                  <span class="label label-info"><i class="icon-doc"></i> DELETE</span> - {{ t('notification_setting.event_pageDelete') }}
-                </label>
+
+              <!-- <div class="form-group notify-to-option {% if setting.__t != 'mail' %}d-none{% endif %}" id="mail-input"> -->
+              <div class="form-group notify-to-option" id="mail-input">
+                <input class="form-control" type="text" name="notificationGlobal[toEmail]" value="{{ setting.toEmail || '' }}">
+                <p class="help">
+                  <b>Hint: </b>
+                  <a href="https://ifttt.com/create" target="_blank">{{ t('notification_setting.email.ifttt_link') }} <i class="icon-share-alt"></i></a>
+                </p>
               </div>
-              <div class="checkbox checkbox-info">
-                <input type="checkbox" id="trigger-event-pageMove" name="notificationGlobal[triggerEvent:pageMove]" value="pageMove"
-                  {% if setting && (setting.triggerEvents.indexOf('pageMove') != -1) %}checked{% endif %} />
-                <label for="trigger-event-pageMove">
-                  <span class="label label-info"><i class="icon-doc"></i> MOVE</span> - {{ t('notification_setting.event_pageMove') }}
-                </label>
+
+              <div class="form-group notify-to-option {% if setting.__t != 'slack' %}d-none{% endif %}" id="slack-input">
+                <input class="form-control" type="text" name="notificationGlobal[slackChannels]" value="{{ setting.slackChannels || '' }}" disabled>
               </div>
-              <div class="checkbox checkbox-info">
-                  <input type="checkbox" id="trigger-event-pageLike" name="notificationGlobal[triggerEvent:pageLike]" value="pageLike"
-                    {% if setting && (setting.triggerEvents.indexOf('pageLike') != -1) %}checked{% endif %} />
-                  <label for="trigger-event-pageLike">
-                    <span class="label label-info"><i class="icon-doc"></i> LIKE</span> - {{ t('notification_setting.event_pageLike') }}
+            </fieldset>
+
+            <fieldset class="col-sm-offset-1 col-sm-5">
+              <div class="form-group">
+                <h3>{{ t('notification_setting.trigger_events') }}</h3>
+                <div class="checkbox checkbox-info">
+                  <input type="checkbox" id="trigger-event-pageCreate" name="notificationGlobal[triggerEvent:pageCreate]" value="pageCreate"
+                    {% if setting && (setting.triggerEvents.indexOf('pageCreate') != -1) %}checked{% endif %} />
+                  <label for="trigger-event-pageCreate">
+                    <span class="label label-info"><i class="icon-doc"></i> CREATE</span> - {{ t('notification_setting.event_pageCreate') }}
+                  </label>
+                </div>
+                <div class="checkbox checkbox-info">
+                  <input type="checkbox" id="trigger-event-pageEdit" name="notificationGlobal[triggerEvent:pageEdit]" value="pageEdit"
+                    {% if setting && (setting.triggerEvents.indexOf('pageEdit') != -1) %}checked{% endif %} />
+                  <label for="trigger-event-pageEdit">
+                    <span class="label label-info"><i class="icon-doc"></i> EDIT</span> - {{ t('notification_setting.event_pageEdit') }}
+                  </label>
+                </div>
+                <div class="checkbox checkbox-info">
+                  <input type="checkbox" id="trigger-event-pageDelete" name="notificationGlobal[triggerEvent:pageDelete]" value="pageDelete"
+                    {% if setting && (setting.triggerEvents.indexOf('pageDelete') != -1) %}checked{% endif %} />
+                  <label for="trigger-event-pageDelete">
+                    <span class="label label-info"><i class="icon-doc"></i> DELETE</span> - {{ t('notification_setting.event_pageDelete') }}
+                  </label>
+                </div>
+                <div class="checkbox checkbox-info">
+                  <input type="checkbox" id="trigger-event-pageMove" name="notificationGlobal[triggerEvent:pageMove]" value="pageMove"
+                    {% if setting && (setting.triggerEvents.indexOf('pageMove') != -1) %}checked{% endif %} />
+                  <label for="trigger-event-pageMove">
+                    <span class="label label-info"><i class="icon-doc"></i> MOVE</span> - {{ t('notification_setting.event_pageMove') }}
+                  </label>
+                </div>
+                <div class="checkbox checkbox-info">
+                    <input type="checkbox" id="trigger-event-pageLike" name="notificationGlobal[triggerEvent:pageLike]" value="pageLike"
+                      {% if setting && (setting.triggerEvents.indexOf('pageLike') != -1) %}checked{% endif %} />
+                    <label for="trigger-event-pageLike">
+                      <span class="label label-info"><i class="icon-doc"></i> LIKE</span> - {{ t('notification_setting.event_pageLike') }}
+                    </label>
+                  </div>
+                <div class="checkbox checkbox-info">
+                  <input type="checkbox" id="trigger-event-comment" name="notificationGlobal[triggerEvent:comment]" value="comment"
+                    {% if setting && (setting.triggerEvents.indexOf('comment') != -1) %}checked{% endif %} />
+                  <label for="trigger-event-comment">
+                    <span class="label label-info"><i class="icon-fw icon-bubbles"></i> POST</span> - {{ t('notification_setting.event_comment') }}
                   </label>
                 </div>
-              <div class="checkbox checkbox-info">
-                <input type="checkbox" id="trigger-event-comment" name="notificationGlobal[triggerEvent:comment]" value="comment"
-                  {% if setting && (setting.triggerEvents.indexOf('comment') != -1) %}checked{% endif %} />
-                <label for="trigger-event-comment">
-                  <span class="label label-info"><i class="icon-fw icon-bubbles"></i> POST</span> - {{ t('notification_setting.event_comment') }}
-                </label>
               </div>
+            </fieldset>
+
+            <div class="col-sm-offset-5 col-sm-12 m-t-20">
+              <input type="hidden" name="notificationGlobal[id]" value="{{ setting.id }}">
+              <input type="hidden" name="_csrf" value="{{ csrf() }}">
+              <button type="submit" class="btn btn-primary">
+                {% if setting %}
+                  {{ t('Update') }}
+                {% else %}
+                  {{ t('Create') }}
+                {% endif %}
+              </button>
             </div>
-          </fieldset>
-
-          <div class="col-sm-offset-5 col-sm-12 m-t-20">
-            <input type="hidden" name="notificationGlobal[id]" value="{{ setting.id }}">
-            <input type="hidden" name="_csrf" value="{{ csrf() }}">
-            <button type="submit" class="btn btn-primary">
-              {% if setting %}
-                {{ t('Update') }}
-              {% else %}
-                {{ t('Create') }}
-              {% endif %}
-            </button>
-          </div>
-        </form>
+          </form>
+        </div>
       </div>
+
     </div>
   </div>
 </div>

+ 1 - 1
lib/views/modal/create_page.html

@@ -32,7 +32,7 @@
             <div class="d-flex create-page-input-container">
               <div class="create-page-input-row d-flex align-items-center">
                 {% if searchConfigured() %}
-                <div id="page-name-inputter"></div>
+                <div id="page-name-input"></div>
                 {% else %}
                 <input type="text" value="{{ parentPath(path) }}" class="page-name-input form-control " placeholder="{{ t('Input page name') }}" required />
                 {% endif %}

+ 6 - 6
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "3.1.13-RC",
+  "version": "3.1.14-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -87,7 +87,7 @@
     "i18next-sprintf-postprocessor": "^0.2.2",
     "markdown-it-blockdiag": "^1.0.2",
     "md5": "^2.2.1",
-    "method-override": "^2.3.10",
+    "method-override": "^3.0.0",
     "mkdirp": "~0.5.1",
     "module-alias": "^2.0.6",
     "mongoose": "^5.0.0",
@@ -112,7 +112,7 @@
   },
   "devDependencies": {
     "@alienfast/i18next-loader": "^1.0.16",
-    "autoprefixer": "^8.2.0",
+    "autoprefixer": "^9.0.0",
     "babel-core": "^6.25.0",
     "babel-loader": "^7.1.1",
     "babel-plugin-lodash": "^3.3.2",
@@ -131,7 +131,7 @@
     "colors": "^1.2.5",
     "commander": "^2.11.0",
     "connect-browser-sync": "^2.1.0",
-    "css-loader": "^0.28.0",
+    "css-loader": "^1.0.0",
     "csv-to-markdown-table": "^0.4.0",
     "date-fns": "^1.29.0",
     "diff2html": "^2.3.3",
@@ -166,12 +166,12 @@
     "normalize-path": "^3.0.0",
     "null-loader": "^0.1.1",
     "on-headers": "^1.0.1",
-    "optimize-css-assets-webpack-plugin": "^4.0.2",
+    "optimize-css-assets-webpack-plugin": "^5.0.0",
     "plantuml-encoder": "^1.2.5",
     "postcss-loader": "^2.1.3",
     "react": "^16.4.1",
     "react-bootstrap": "^0.32.1",
-    "react-bootstrap-typeahead": "=3.0.4",
+    "react-bootstrap-typeahead": "^3.1.5",
     "react-clipboard.js": "^2.0.0",
     "react-codemirror2": "^5.0.4",
     "react-dom": "^16.4.1",

+ 3 - 4
resource/js/app.js

@@ -56,8 +56,7 @@ let pageIdOnHackmd = null;
 let pagePath;
 let pageContent = '';
 let markdown = '';
-let pageGrant = null;
-let slackChannels = '';
+let slackChannels;
 if (mainContent !== null) {
   pageId = mainContent.getAttribute('data-page-id');
   pageRevisionId = mainContent.getAttribute('data-page-revision-id');
@@ -65,7 +64,7 @@ if (mainContent !== null) {
   pageRevisionIdHackmdSynced = mainContent.getAttribute('data-page-revision-id-hackmd-synced') || null;
   pageIdOnHackmd = mainContent.getAttribute('data-page-id-on-hackmd') || null;
   pagePath = mainContent.attributes['data-path'].value;
-  slackChannels = mainContent.getAttribute('data-slack-channels');
+  slackChannels = mainContent.getAttribute('data-slack-channels') || '';
   const rawText = document.getElementById('raw-text-original');
   if (rawText) {
     pageContent = rawText.innerHTML;
@@ -125,7 +124,7 @@ const componentMappings = {
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
 
-  'page-name-inputter': <NewPageNameInput crowi={crowi} parentPageName={pagePath} />,
+  'page-name-input': <NewPageNameInput crowi={crowi} parentPageName={pagePath} />,
 
 };
 // additional definitions if data exists

+ 9 - 2
resource/js/components/HeaderSearchBox/SearchForm.js

@@ -21,6 +21,7 @@ export default class SearchForm extends React.Component {
 
     this.onSearchError = this.onSearchError.bind(this);
     this.onChange = this.onChange.bind(this);
+    this.onSubmit = this.onSubmit.bind(this);
   }
 
   componentDidMount() {
@@ -68,6 +69,10 @@ export default class SearchForm extends React.Component {
           </table>;
   }
 
+  onSubmit(query) {
+    this.refs.form.submit(query);
+  }
+
   render() {
     const emptyLabel = (this.state.searchError !== null)
       ? 'Error on searching.'
@@ -75,14 +80,16 @@ export default class SearchForm extends React.Component {
 
     return (
       <form
-        action="/_search"
-        className="search-form form-group input-group search-input-group"
+        ref='form'
+        action='/_search'
+        className='search-form form-group input-group search-input-group'
       >
         <FormGroup>
           <InputGroup>
             <SearchTypeahead
               crowi={this.crowi}
               onChange={this.onChange}
+              onSubmit={this.onSubmit}
               emptyLabel={emptyLabel}
               placeholder="Search ..."
               promptText={this.getHelpElement()}

+ 20 - 7
resource/js/components/NewPageNameInput.js

@@ -15,6 +15,7 @@ export default class NewPageNameInput extends React.Component {
     this.crowi = this.props.crowi;
 
     this.onSearchError = this.onSearchError.bind(this);
+    this.onSubmit = this.onSubmit.bind(this);
     this.getParentPageName = this.getParentPageName.bind(this);
   }
 
@@ -30,6 +31,14 @@ export default class NewPageNameInput extends React.Component {
     });
   }
 
+  onSubmit(query) {
+    // get the closest form element
+    const elem = this.refs.rootDom;
+    const form = elem.closest('form');
+    // submit with jQuery
+    $(form).submit();
+  }
+
   getParentPageName(path) {
     if (path == '/') {
       return path;
@@ -48,13 +57,17 @@ export default class NewPageNameInput extends React.Component {
       : 'No matches found on title...';
 
     return (
-      <SearchTypeahead
-        crowi={this.crowi}
-        onSearchError={this.onSearchError}
-        emptyLabel={emptyLabel}
-        placeholder="Input page name"
-        keywordOnInit={this.getParentPageName(this.props.parentPageName)}
-      />
+      <div ref='rootDom'>
+        <SearchTypeahead
+          ref={this.searchTypeaheadDom}
+          crowi={this.crowi}
+          onSearchError={this.onSearchError}
+          onSubmit={this.onSubmit}
+          emptyLabel={emptyLabel}
+          placeholder="Input page name"
+          keywordOnInit={this.getParentPageName(this.props.parentPageName)}
+        />
+      </div>
     );
   }
 }

+ 16 - 0
resource/js/components/SearchTypeahead.js

@@ -27,7 +27,9 @@ export default class SearchTypeahead extends React.Component {
 
     this.search = this.search.bind(this);
     this.onInputChange = this.onInputChange.bind(this);
+    this.onKeyDown = this.onKeyDown.bind(this);
     this.onChange = this.onChange.bind(this);
+    this.dispatchSubmit = this.dispatchSubmit.bind(this);
     this.getRestoreFormButton = this.getRestoreFormButton.bind(this);
     this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
     this.restoreInitialData = this.restoreInitialData.bind(this);
@@ -96,6 +98,12 @@ export default class SearchTypeahead extends React.Component {
     }
   }
 
+  onKeyDown(event) {
+    if (event.keyCode === 13) {
+      this.dispatchSubmit();
+    }
+  }
+
   onChange(selected) {
     const page = selected[0];  // should be single page selected
 
@@ -105,6 +113,12 @@ export default class SearchTypeahead extends React.Component {
     }
   }
 
+  dispatchSubmit() {
+    if (this.props.onSubmit != null) {
+      this.props.onSubmit(this.state.keyword);
+    }
+  }
+
   renderMenuItemChildren(option, props, index) {
     const page = option;
     return (
@@ -161,6 +175,7 @@ export default class SearchTypeahead extends React.Component {
           submitFormOnEnter={true}
           onSearch={this.search}
           onInputChange={this.onInputChange}
+          onKeyDown={this.onKeyDown}
           renderMenuItemChildren={this.renderMenuItemChildren}
           caseSensitive={false}
           defaultSelected={defaultSelected}
@@ -180,6 +195,7 @@ SearchTypeahead.propTypes = {
   onSearchSuccess: PropTypes.func,
   onSearchError:   PropTypes.func,
   onChange:        PropTypes.func,
+  onSubmit:        PropTypes.func,
   emptyLabel:      PropTypes.string,
   placeholder:     PropTypes.string,
   keywordOnInit:   PropTypes.string,

+ 4 - 0
resource/js/components/SlackNotification.js

@@ -76,3 +76,7 @@ SlackNotification.propTypes = {
   slackChannels: PropTypes.string,
   formName: PropTypes.string,
 };
+
+SlackNotification.defaultProps = {
+  slackChannels: '',
+};

+ 1 - 1
resource/styles/scss/_create-page.scss

@@ -51,7 +51,7 @@
         margin-left: 5px;
       }
 
-      #page-name-inputter {
+      #page-name-input {
         flex: 1;
         input {
           min-width: 300px; // Workaround to display placeholder.

Plik diff jest za duży
+ 381 - 273
yarn.lock


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików