Quellcode durchsuchen

Merge pull request #141 from weseek/master

release v2.0.1
Yuki Takei vor 8 Jahren
Ursprung
Commit
6457595022

+ 7 - 0
CHANGES.md

@@ -1,6 +1,13 @@
 CHANGES
 ========
 
+## 2.0.1
+
+* Feature: Custom Script
+* Improvement: Adjust layout and styles for admin pages
+* Improvement: Record and show last updated date in user list page
+* Fix: Ignore Ctrl+(Shift+)Tab when editing (cherry-pick from the official)
+
 ## 2.0.0
 
 * Feature: Enabled to integrate with Slack using Incoming Webhooks

+ 9 - 0
lib/form/admin/customscript.js

@@ -0,0 +1,9 @@
+'use strict';
+
+var form = require('express-form')
+  , field = form.field
+  ;
+
+module.exports = form(
+  field('settingForm[customize:script]')
+);

+ 1 - 0
lib/form/index.js

@@ -19,6 +19,7 @@ module.exports = {
     plugin: require('./admin/plugin'),
     markdown: require('./admin/markdown'),
     customcss: require('./admin/customcss'),
+    customscript: require('./admin/customscript'),
     custombehavior: require('./admin/custombehavior'),
     customlayout: require('./admin/customlayout'),
     customfeatures: require('./admin/customfeatures'),

+ 25 - 8
lib/models/config.js

@@ -68,6 +68,7 @@ module.exports = function(crowi) {
       'plugin:isEnabledPlugins' : true,
 
       'customize:css' : '',
+      'customize:script' : '',
       'customize:behavior' : 'crowi',
       'customize:layout' : 'crowi',
       'customize:isEnabledTimeline' : true,
@@ -123,8 +124,9 @@ module.exports = function(crowi) {
     originalConfig[ns] = newNSConfig;
     crowi.setConfig(originalConfig);
 
-    // uglify and store
-    Config.generateUglifiedCustomCss(originalConfig);
+    // initialize custom css/script
+    Config.initCustomCss(originalConfig);
+    Config.initCustomScript(originalConfig);
   };
 
   // Execute only once for installing application
@@ -225,8 +227,9 @@ module.exports = function(crowi) {
 
         debug('Config loaded', config);
 
-        // uglify and store
-        Config.generateUglifiedCustomCss(config);
+        // initialize custom css/script
+        Config.initCustomCss(config);
+        Config.initCustomScript(config);
 
         return callback(null, config);
       });
@@ -288,18 +291,32 @@ module.exports = function(crowi) {
   };
 
   /**
-   * uglify store custom css strings
+   * initialize custom css strings
    */
-  configSchema.statics.generateUglifiedCustomCss = function(config)
+  configSchema.statics.initCustomCss = function(config)
   {
     const key = 'customize:css';
     const rawCss = getValueForCrowiNS(config, key);
-    this.uglifiedCustomCss = uglifycss.processString(rawCss);
+    // uglify and store
+    this._customCss = uglifycss.processString(rawCss);
   }
 
   configSchema.statics.customCss = function(config)
   {
-    return this.uglifiedCustomCss;
+    return this._customCss;
+  }
+
+  configSchema.statics.initCustomScript = function(config)
+  {
+    const key = 'customize:script';
+    const rawScript = getValueForCrowiNS(config, key);
+    // store as is
+    this._customScript = rawScript;
+  }
+
+  configSchema.statics.customScript = function(config)
+  {
+    return this._customScript;
   }
 
   configSchema.statics.behaviorType = function(config)

+ 8 - 0
lib/models/user.js

@@ -42,6 +42,7 @@ module.exports = function(crowi) {
     },
     status: { type: Number, required: true, default: STATUS_ACTIVE, index: true  },
     createdAt: { type: Date, default: Date.now },
+    lastLoginAt: { type: Date },
     admin: { type: Boolean, default: 0, index: true  }
   });
   userSchema.plugin(mongoosePaginate);
@@ -138,6 +139,13 @@ module.exports = function(crowi) {
     });
   };
 
+  userSchema.methods.updateLastLoginAt = function(lastLoginAt, callback) {
+    this.lastLoginAt = lastLoginAt;
+    this.save(function(err, userData) {
+      return callback(err, userData);
+    });
+  };
+
   userSchema.methods.updateIsGravatarEnabled = function(isGravatarEnabled, callback) {
     this.isGravatarEnabled = isGravatarEnabled;
     this.save(function(err, userData) {

+ 1 - 0
lib/routes/index.js

@@ -54,6 +54,7 @@ module.exports = function(crowi, app) {
   // markdown admin
   app.get('/admin/customize'                , loginRequired(crowi, app) , middleware.adminRequired() , admin.customize.index);
   app.post('/_api/admin/customize/css'      , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customcss, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/script'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customscript, admin.api.customizeSetting);
   app.post('/_api/admin/customize/behavior' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.custombehavior, admin.api.customizeSetting);
   app.post('/_api/admin/customize/layout'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customlayout, admin.api.customizeSetting);
   app.post('/_api/admin/customize/features' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customfeatures, admin.api.customizeSetting);

+ 5 - 0
lib/routes/login.js

@@ -81,6 +81,11 @@ module.exports = function(crowi, app) {
       User.findUserByEmailAndPassword(email, password, function(err, userData) {
         debug('on login findUserByEmailAndPassword', err, userData);
         if (userData) {
+          userData.updateLastLoginAt(Date.now(), function(err, userData) {
+            if (err) {
+              debug(err);
+            }
+          });
           loginSuccess(req, res, userData);
         } else {
           loginFailure(req, res);

+ 4 - 0
lib/util/swigFunctions.js

@@ -55,6 +55,10 @@ module.exports = function(crowi, app, req, locals) {
     return Config.customCss();
   }
 
+  locals.customScript = function() {
+    return Config.customScript();
+  }
+
   locals.behaviorType = function() {
     var config = crowi.getConfig()
     return Config.behaviorType(config);

+ 75 - 7
lib/views/admin/customize.html

@@ -209,7 +209,7 @@
       <fieldset>
         <legend>カスタムCSS</legend>
 
-        <p class="help-block">
+        <p class="well">
           システム全体に適用されるCSSを記述できます。<br>
           変更の反映はページの更新が必要です。
         </p>
@@ -236,11 +236,57 @@
       </fieldset>
       </form>
 
+
+      <form action="/_api/admin/customize/script" method="post" class="form-horizontal" id="cutomscriptSettingForm" role="form">
+      <fieldset>
+        <legend>カスタムスクリプト</legend>
+
+        <p class="well">
+          システム全体に適用されるJavaScriptを記述できます。<br>
+          変更の反映はページの更新が必要です。
+        </p>
+
+        <p class="help-block">
+          Available Placeholders:
+          <dl class="dl-horizontal">
+            <dt><code>crowi</code></dt>
+            <dd>Crowi context instance</dd>
+            <dt><code>Crowi</code></dt>
+            <dd>Crowi legacy instance (jQuery based)</dd>
+            <dt><code>crowiRenderer</code></dt>
+            <dd>Crowi Renderer instance</dd>
+            <dt><code>crowiPlugin</code></dt>
+            <dd>crowi-plus plugin manager instance</dd>
+          </dl>
+        </p>
+
+        <div class="form-group">
+          <div class="col-xs-12">
+            <textarea id="taCustomScript" class="form-control" type="textarea" name="settingForm[customize:script]" rows="20">{{ settingForm['customize:script'] }}</textarea>
+          </div>
+          <div class="col-xs-12">
+            <p class="help-block text-right">
+              <i class="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
+              Ctrl+Space でコード補完
+            </p>
+          </div>
+        </div>
+
+        <div class="form-group">
+          <div class="col-xs-offset-5 col-xs-6">
+            <input type="hidden" name="_csrf" value="{{ csrf() }}">
+            <button type="submit" class="btn btn-primary">更新</button>
+          </div>
+        </div>
+
+      </fieldset>
+      </form>
+
     </div>
   </div>
 
   <script>
-    $('#cutomcssSettingForm, #cutomlayoutSettingForm, #cutombehaviorSettingForm, #customfeaturesSettingForm').each(function() {
+    $('#cutomcssSettingForm, #cutomscriptSettingForm, #cutomlayoutSettingForm, #cutombehaviorSettingForm, #customfeaturesSettingForm').each(function() {
       $(this).submit(function()
       {
         function showMessage(formId, msg, status) {
@@ -291,9 +337,10 @@
   </script>
 
   <!-- CodeMirror -->
-  <script src="https://cdn.jsdelivr.net/g/codemirror@4.5.0(codemirror.min.js+addon/lint/css-lint.js+mode/css/css.js+addon/hint/css-hint.js+addon/hint/show-hint.js+addon/edit/matchbrackets.js+addon/edit/closebrackets.js),jquery.ui@1.11.4"></script>
+  <script src="https://cdn.jsdelivr.net/g/codemirror@4.5.0(codemirror.min.js+addon/lint/css-lint.js+addon/lint/javascript-lint.js+mode/css/css.js+mode/javascript/javascript.js+addon/hint/css-hint.js+addon/hint/javascript-hint.js+addon/hint/show-hint.js+addon/edit/matchbrackets.js+addon/edit/closebrackets.js),jquery.ui@1.11.4"></script>
   <script>
-    var editor = CodeMirror.fromTextArea(document.getElementById('taCustomCss'), {
+    // Configure for CSS editor
+    var editorCss = CodeMirror.fromTextArea(document.getElementById('taCustomCss'), {
       mode: "css",
       lineNumbers: true,
       tabSize: 2,
@@ -303,13 +350,34 @@
       autoCloseBrackets: true,
       extraKeys: {"Ctrl-Space": "autocomplete"},
     });
-    editor.on('change', function(cm, change) {
+    editorCss.on('change', function(cm, change) {
+      cm.save();
+    });
+    // resizable with jquery.ui
+    $(editorCss.getWrapperElement()).resizable({
+      resize: function() {
+        editorCss.setSize($(this).width(), $(this).height());
+      }
+    });
+
+    // Configure for JavaScript editor
+    var editorScript = CodeMirror.fromTextArea(document.getElementById('taCustomScript'), {
+      mode: "javascript",
+      lineNumbers: true,
+      tabSize: 2,
+      indentUnit: 2,
+      theme: 'eclipse',
+      matchBrackets: true,
+      autoCloseBrackets: true,
+      extraKeys: {"Ctrl-Space": "autocomplete"},
+    });
+    editorScript.on('change', function(cm, change) {
       cm.save();
     });
     // resizable with jquery.ui
-    $(editor.getWrapperElement()).resizable({
+    $(editorScript.getWrapperElement()).resizable({
       resize: function() {
-        editor.setSize($(this).width(), $(this).height());
+        editorScript.setSize($(this).width(), $(this).height());
       }
     });
 

+ 20 - 13
lib/views/admin/users.html

@@ -134,16 +134,17 @@
 
       <h2>ユーザー一覧</h2>
 
-      <table class="table table-hover table-striped table-bordered">
+      <table class="table table-hover table-striped table-bordered table-user-list">
         <thead>
           <tr>
-            <th>#</th>
-            <th>ユーザーID</th>
+            <th width="100px">#</th>
+            <th>Status</th>
+            <th><code>username</code></th>
             <th>名前</th>
             <th>メールアドレス</th>
-            <th>作成日</th>
-            <th>最終ログイン</th>
-            <th>操作</th>
+            <th width="100px">作成日</th>
+            <th width="150px">最終ログイン</th>
+            <th width="90px">操作</th>
           </tr>
         </thead>
         <tbody>
@@ -151,25 +152,31 @@
           <tr>
             <td>
               <img src="{{ sUser|picture }}" class="picture picture-rounded" />
-              <span class="label {{ css.userStatus(sUser) }}">
-                {{ consts.userStatus[sUser.status] }}
-              </span><br>
               {% if sUser.admin %}
-              <span class="label label-primary">
+              <span class="label label-primary label-admin">
                 Admin
               </span>
               {% endif %}
             </td>
+            <td>
+              <span class="label {{ css.userStatus(sUser) }}">
+                {{ consts.userStatus[sUser.status] }}
+              </span>
+            </td>
             <td>
               <strong>{{ sUser.username }}</strong>
             </td>
             <td>{{ sUser.name }}</td>
             <td>{{ sUser.email }}</td>
-            <td>{{ sUser.createdAt|date('Y-m-d') }}</td>
-            <td>{{ sUser.lastLoginAt }}</td>
+            <td>{{ sUser.createdAt|date('Y-m-d', sUser.createdAt.getTimezoneOffset()) }}</td>
+            <td>
+              {% if sUser.lastLoginAt %}
+                {{ sUser.lastLoginAt|date('Y-m-d H:i', sUser.createdAt.getTimezoneOffset()) }}
+              {% endif %}
+            </td>
             <td>
               <div class="btn-group admin-user-menu">
-                <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+                <button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
                   編集
                   <span class="caret"></span>
                 </button>

+ 1 - 1
lib/views/crowi-plus/widget/header.html

@@ -1,7 +1,7 @@
 <div class="header-wrap">
   <header id="page-header">
     <div class="flex-title-line">
-      <div class="title-logo-container visible-lg visible-md">
+      <div class="title-logo-container hidden-xs hidden-sm">
         <a href="/">
           <img alt="Crowi" src="/logo/32x32_g.png" />
         </a>

+ 4 - 1
lib/views/layout/admin.html

@@ -1,7 +1,10 @@
-{% extends '2column.html' %}
+{% extends 'single.html' %}
 
 {% block html_additional_headers %}
   {% parent %}
   <script src="{{ webpack_asset('legacy-admin').js }}" defer></script>
 {% endblock %}
 
+{# disable custom script in admin page #}
+{% block custom_script %}
+{% endblock %}

+ 6 - 0
lib/views/layout/layout.html

@@ -182,4 +182,10 @@
 {{ local_config|json|safe }}
 </script>
 
+{% block custom_script %}
+<script>
+  {{ customScript() }}
+</script>
+{% endblock %}
+
 </html>

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "crowi-plus",
-  "version": "2.0.0-RC",
+  "version": "2.0.1-RC",
   "description": "Enhanced Crowi",
   "tags": [
     "wiki",

+ 5 - 0
resource/css/_admin.scss

@@ -31,4 +31,9 @@
     }
   }
 
+  .table-user-list {
+    .label-admin {
+      margin-left: 1em;
+    }
+  }
 } // }}}

+ 6 - 0
resource/js/legacy/crowi-form.js

@@ -262,6 +262,12 @@ $(function() {
     var text = $target.val();
     var pos = $target.selection('getPos');
 
+    // When the user presses CTRL + TAB, it is a case to control the tab of the browser
+    // (for Firefox 54 on Windows)
+    if (event.ctrlKey === true) {
+      return;
+    }
+
     if (currentLine) {
       $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
     }