Explorar el Código

Merge pull request #1487 from shield-9/stale

Feat/Notify user if page content is stale
Yuki Takei hace 6 años
padre
commit
427757ad9b

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

@@ -223,7 +223,9 @@
       "redirected": "You are redirected from <code>%s</code>",
       "duplicated": "This page was duplicated from <code>%s</code>",
       "unlinked": "Redirect pages to this page have been deleted.",
-      "restricted": "Access to this page is restricted"
+      "restricted": "Access to this page is restricted",
+      "stale": "More than {{count}} year has passed since last update.",
+      "stale_plural": "More than {{count}} years has passed since last update."
     }
   },
   "page_edit": {
@@ -636,6 +638,8 @@
     "attach_title_header_desc": "Add page path to the first line as h1 section when create new page",
     "recent_created__n_draft_num_desc": "Number of Recently Created Pages & Drafts Displayed",
     "recently_created_n_draft_num_desc": "Number of recently created pages and drafts displayed on user page",
+    "stale_notification": "Display Notification on Stale Pages",
+    "stale_notification_desc": "Displays the notification to pages more than 1 year since the last update.",
     "update_layout_success": "Succeeded to update layout",
     "update_behavior_success": "Succeeded to update behavior",
     "update_function_success": "Succeeded to update function",

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

@@ -222,7 +222,8 @@
       "redirected": "リダイレクト元 >> <code>%s</code>",
       "duplicated": "このページは <code>%s</code> から複製されました。",
       "unlinked": "このページへのリダイレクトは削除されました。",
-      "restricted": "このページの閲覧は制限されています"
+      "restricted": "このページの閲覧は制限されています",
+      "stale": "このページは最終更新日から{{count}}年以上が経過しています。"
     }
   },
   "page_edit": {
@@ -620,6 +621,8 @@
     "attach_title_header_desc": "新規作成したページの1行目に、ページのパスを h1 セクションとして挿入します。",
     "recent_created__n_draft_num_desc": "最近作成したページと下書きの表示数",
     "recently_created_n_draft_num_desc": "ホーム画面の Recently Created での、1ページの表示数を設定します。",
+    "stale_notification": "更新されていないページに通知を表示",
+    "stale_notification_desc": "最終更新から1年以上が経過しているページに通知を表示します。",
     "update_layout_success": "レイアウトを更新しました",
     "update_behavior_success": "動作を更新しました",
     "update_function_success": "機能を更新しました",

+ 15 - 0
src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx

@@ -123,6 +123,21 @@ class CustomizeBehaviorSetting extends React.Component {
           </div>
         </div>
 
+        <div className="form-group row">
+          <div className="col-xs-offset-3 col-xs-6 text-left">
+            <CustomizeFunctionOption
+              optionId="isEnabledStaleNotification"
+              label={t('customize_page.stale_notification')}
+              isChecked={adminCustomizeContainer.state.isEnabledStaleNotification}
+              onChecked={() => { adminCustomizeContainer.switchEnableStaleNotification() }}
+            >
+              <p className="help-block">
+                { t('customize_page.stale_notification_desc') }
+              </p>
+            </CustomizeFunctionOption>
+          </div>
+        </div>
+
         <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );

+ 10 - 0
src/client/js/services/AdminCustomizeContainer.js

@@ -27,6 +27,7 @@ export default class AdminCustomizeContainer extends Container {
       isSavedStatesOfTabChanges: false,
       isEnabledAttachTitleHeader: false,
       currentRecentCreatedLimit: 10,
+      isEnabledStaleNotification: false,
       currentHighlightJsStyleId: '',
       isHighlightJsStyleBorderEnabled: false,
       currentCustomizeTitle: '',
@@ -74,6 +75,7 @@ export default class AdminCustomizeContainer extends Container {
         isSavedStatesOfTabChanges: customizeParams.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: customizeParams.isEnabledAttachTitleHeader,
         currentRecentCreatedLimit: customizeParams.recentCreatedLimit,
+        isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         currentHighlightJsStyleId: customizeParams.styleName,
         isHighlightJsStyleBorderEnabled: customizeParams.styleBorder,
         currentCustomizeTitle: customizeParams.customizeTitle,
@@ -144,6 +146,13 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ currentRecentCreatedLimit: value });
   }
 
+  /**
+   * Switch enabledStaleNotification
+   */
+  switchEnableStaleNotification() {
+    this.setState({ isEnabledStaleNotification:  !this.state.isEnabledStaleNotification });
+  }
+
   /**
    * Switch highlightJsStyle
    */
@@ -228,6 +237,7 @@ export default class AdminCustomizeContainer extends Container {
       isSavedStatesOfTabChanges: this.state.isSavedStatesOfTabChanges,
       isEnabledAttachTitleHeader: this.state.isEnabledAttachTitleHeader,
       recentCreatedLimit: this.state.currentRecentCreatedLimit,
+      isEnabledStaleNotification: this.state.isEnabledStaleNotification,
     });
     const { customizedParams } = response.data;
     return customizedParams;

+ 1 - 0
src/server/form/admin/customfeatures.js

@@ -8,4 +8,5 @@ module.exports = form(
   field('settingForm[customize:isSavedStatesOfTabChanges]').trim().toBooleanStrict(),
   field('settingForm[customize:isEnabledAttachTitleHeader]').trim().toBooleanStrict(),
   field('settingForm[customize:showRecentCreatedNumber]').trim().toInt(),
+  field('settingForm[customize:isEnabledStaleNotification]').trim().toBooleanStrict(),
 );

+ 2 - 0
src/server/models/config.js

@@ -102,6 +102,7 @@ module.exports = function(crowi) {
       'customize:isSavedStatesOfTabChanges' : true,
       'customize:isEnabledAttachTitleHeader' : false,
       'customize:showRecentCreatedNumber' : 10,
+      'customize:isEnabledStaleNotification': false,
 
       'importer:esa:team_name': undefined,
       'importer:esa:access_token': undefined,
@@ -201,6 +202,7 @@ module.exports = function(crowi) {
         NO_CDN: env.NO_CDN || null,
       },
       recentCreatedLimit: crowi.configManager.getConfig('crowi', 'customize:showRecentCreatedNumber'),
+      isEnabledStaleNotification: crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       isAclEnabled: crowi.aclService.isAclEnabled(),
       globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
     };

+ 5 - 0
src/server/models/page.js

@@ -9,6 +9,7 @@ const urljoin = require('url-join');
 const mongoose = require('mongoose');
 const mongoosePaginate = require('mongoose-paginate-v2');
 const uniqueValidator = require('mongoose-unique-validator');
+const differenceInYears = require('date-fns/differenceInYears')
 
 const { pathUtils } = require('growi-commons');
 const templateChecker = require('@commons/util/template-checker');
@@ -488,6 +489,10 @@ module.exports = function(crowi) {
     }
   };
 
+  pageSchema.methods.getContentAge = function() {
+    return differenceInYears(new Date(), this.updatedAt)
+  }
+
 
   pageSchema.statics.updateCommentCount = function(pageId) {
     validateCrowi();

+ 6 - 0
src/server/routes/apiv3/customize-setting.js

@@ -44,6 +44,8 @@ const ErrorV3 = require('../../models/vo/error-apiv3');
  *            type: boolean
  *          recentCreatedLimit:
  *            type: number
+ *          isEnabledStaleNotification:
+ *            type: boolean
  *      CustomizeHighlight:
  *        type: object
  *        properties:
@@ -96,6 +98,7 @@ module.exports = (crowi) => {
       body('isSavedStatesOfTabChanges').isBoolean(),
       body('isEnabledAttachTitleHeader').isBoolean(),
       body('recentCreatedLimit').isInt().isInt({ min: 1, max: 1000 }),
+      body('isEnabledStaleNotification').isBoolean(),
     ],
     customizeTitle: [
       body('customizeTitle').isString(),
@@ -145,6 +148,7 @@ module.exports = (crowi) => {
       isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
       isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
       recentCreatedLimit: await crowi.configManager.getConfig('crowi', 'customize:showRecentCreatedNumber'),
+      isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       styleName: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
       styleBorder: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
       customizeTitle: await crowi.configManager.getConfig('crowi', 'customize:title'),
@@ -265,6 +269,7 @@ module.exports = (crowi) => {
       'customize:isSavedStatesOfTabChanges': req.body.isSavedStatesOfTabChanges,
       'customize:isEnabledAttachTitleHeader': req.body.isEnabledAttachTitleHeader,
       'customize:showRecentCreatedNumber': req.body.recentCreatedLimit,
+      'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification,
     };
 
     try {
@@ -274,6 +279,7 @@ module.exports = (crowi) => {
         isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
         isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
         recentCreatedLimit: await crowi.configManager.getConfig('crowi', 'customize:showRecentCreatedNumber'),
+        isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       };
       return res.apiv3({ customizedParams });
     }

+ 15 - 0
src/server/views/widget/page_alerts.html

@@ -13,6 +13,21 @@
       </p>
     {% endif %}
 
+    {% if getConfig('crowi', 'customize:isEnabledStaleNotification') %}
+      {% if page && page.updatedAt && page.getContentAge() > 0 %}
+        {% if page.getContentAge() == 1 %}
+        <div class="alert alert-info">
+        {% elseif page.getContentAge() == 2 %}
+        <div class="alert alert-warning">
+        {% else %}
+        <div class="alert alert-danger">
+        {% endif %}
+          <i class="icon-fw icon-hourglass"></i>
+          <strong>{{ t('page_page.notice.stale', { count: page.getContentAge() }) }}</strong>
+        </div>
+      {% endif %}
+    {% endif %}
+
     {% if redirectFrom or req.query.renamed or req.query.redirectFrom %}
     <div class="alert alert-info alert-moved d-flex align-items-center justify-content-between">
       <span>