Răsfoiți Sursa

Merge pull request #696 from weseek/master

release v3.2.8
Yuki Takei 7 ani în urmă
părinte
comite
6f4e2ae584

+ 9 - 1
CHANGES.md

@@ -1,13 +1,21 @@
 CHANGES
 CHANGES
 ========
 ========
 
 
-## 3.2.7-RC
+## 3.2.8-RC
+
+* 
+
+## 3.2.7
 
 
 * Feature: Import CSV/TSV/HTML table on Spreadsheet like GUI (Handsontable)
 * Feature: Import CSV/TSV/HTML table on Spreadsheet like GUI (Handsontable)
+* Fix: Pasting table data copied from Excel includes unnecessary line breaks
+* Fix: Page break Preset 1 for Presentation mode is broken
+* Fix: Login Form when LDAP login failed caused 500 Internal Server Error
 
 
 ## 3.2.6
 ## 3.2.6
 
 
 * Feature: Add select alignment buttons of Spreadsheet like GUI (Handsontable)
 * Feature: Add select alignment buttons of Spreadsheet like GUI (Handsontable)
+* Improvement: Shrink the rows that have no diff of revision history page
 * Fix: Login form rejects weak password
 * Fix: Login form rejects weak password
 * Fix: An error occured by uploading attachment file when the page is not exists
 * Fix: An error occured by uploading attachment file when the page is not exists
     * Introduced by 2.3.5
     * Introduced by 2.3.5

+ 2 - 4
README.md

@@ -175,10 +175,8 @@ Environment Variables
     * OAUTH_GOOGLE_CLIENT_SECRET: Google API client secret for OAuth login.
     * OAUTH_GOOGLE_CLIENT_SECRET: Google API client secret for OAuth login.
     * OAUTH_GITHUB_CLIENT_ID: GitHub API client id for OAuth login.
     * OAUTH_GITHUB_CLIENT_ID: GitHub API client id for OAuth login.
     * OAUTH_GITHUB_CLIENT_SECRET: GitHub API client secret for OAuth login.
     * OAUTH_GITHUB_CLIENT_SECRET: GitHub API client secret for OAuth login.
-    * OAUTH_TWITTER_CLIENT_ID: Twitter API client id for OAuth login.
-    * OAUTH_TWITTER_CLIENT_SECRET: Twitter API client secret for OAuth login.
-    * OAUTH_TWITTER_CLIENT_ID: Twitter API client id for OAuth login.
-    * OAUTH_TWITTER_CLIENT_SECRET: Twitter API client secret for OAuth login.
+    * OAUTH_TWITTER_CONSUMER_KEY: Twitter consumer key(API key) for OAuth login.
+    * OAUTH_TWITTER_CONSUMER_SECRET: Twitter consumer secret(API secret) for OAuth login.
     * SAML_ENTRY_POINT: IdP entry point
     * SAML_ENTRY_POINT: IdP entry point
     * SAML_ISSUER: Issuer string to supply to IdP
     * SAML_ISSUER: Issuer string to supply to IdP
     * SAML_CERT: PEM-encoded X.509 signing certificate string to validate the response from IdP
     * SAML_CERT: PEM-encoded X.509 signing certificate string to validate the response from IdP

+ 1 - 1
config/webpack.common.js

@@ -58,7 +58,7 @@ module.exports = (options) => {
     },
     },
     resolve: {
     resolve: {
       extensions: ['.js', '.jsx', '.json'],
       extensions: ['.js', '.jsx', '.json'],
-      modules: [helpers.root('node_modules')],
+      modules: ((options.resolve && options.resolve.modules) || []).concat([helpers.root('node_modules')]),
       alias: {
       alias: {
         '@root': helpers.root('/'),
         '@root': helpers.root('/'),
         '@commons': helpers.root('src/lib'),
         '@commons': helpers.root('src/lib'),

+ 1 - 2
config/webpack.dev.js

@@ -24,8 +24,7 @@ module.exports = require('./webpack.common')({
     'js/dev': './src/client/js/dev',
     'js/dev': './src/client/js/dev',
   },
   },
   resolve: {
   resolve: {
-    // TODO merge in webpack.common.js
-    modules: [path.join(process.env.HOME, '.node_modules')],
+    modules: ['../node_modules'],
   },
   },
   module: {
   module: {
     rules: [
     rules: [

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "3.2.7-RC",
+  "version": "3.2.8-RC",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",

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

@@ -255,7 +255,6 @@
     "Package name": "Package name",
     "Package name": "Package name",
     "Specified version": "Specified version",
     "Specified version": "Specified version",
     "Installed version": "Installed version"
     "Installed version": "Installed version"
-
   },
   },
 
 
   "app_setting": {
   "app_setting": {
@@ -265,7 +264,9 @@
     "Site URL": "Site URL",
     "Site URL": "Site URL",
     "siteurl_help": "Site full URL beginning from <code>http://</code> or <code>https://</code>.",
     "siteurl_help": "Site full URL beginning from <code>http://</code> or <code>https://</code>.",
     "Confidential name": "Confidential name",
     "Confidential name": "Confidential name",
+    "Default Language for new users": "Default Language for new users",
     "ex): internal use only":"ex): internal use only",
     "ex): internal use only":"ex): internal use only",
+    "File Uploading": "File Uploading",
     "enable_files_except_image": "Enable file upload other than image files.",
     "enable_files_except_image": "Enable file upload other than image files.",
     "attach_enable": "You can attach files other than image files if you enable this option.",
     "attach_enable": "You can attach files other than image files if you enable this option.",
     "Update": "Update",
     "Update": "Update",
@@ -351,6 +352,8 @@
     "optional": "Optional",
     "optional": "Optional",
     "Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
     "Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
     "Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
     "Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
+    "Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
+    "Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
     "Use env var if empty": "Use env var <code>%s</code> if empty",
     "Use env var if empty": "Use env var <code>%s</code> if empty",
     "ldap": {
     "ldap": {
       "server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",
       "server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",

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

@@ -271,9 +271,6 @@
     "Package name": "パッケージ名",
     "Package name": "パッケージ名",
     "Specified version": "指定バージョン",
     "Specified version": "指定バージョン",
     "Installed version": "インストールされているバージョン"
     "Installed version": "インストールされているバージョン"
-
-
-
   },
   },
 
 
   "app_setting": {
   "app_setting": {
@@ -283,7 +280,9 @@
     "Site URL": "サイトURL",
     "Site URL": "サイトURL",
     "siteurl_help": "<code>http://</code> または <code>https://</code> から始まるサイトのURL。",
     "siteurl_help": "<code>http://</code> または <code>https://</code> から始まるサイトのURL。",
     "Confidential name": "コンフィデンシャル表示",
     "Confidential name": "コンフィデンシャル表示",
+    "Default Language for new users": "新規ユーザーのデフォルト設定言語",
     "ex): internal use only": "例: 社外秘",
     "ex): internal use only": "例: 社外秘",
+    "File Uploading": "ファイルアップロード",
     "enable_files_except_image": "画像以外のファイルアップロードを許可",
     "enable_files_except_image": "画像以外のファイルアップロードを許可",
     "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。",
     "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。",
     "Update": "更新",
     "Update": "更新",
@@ -369,6 +368,8 @@
     "optional": "オプション",
     "optional": "オプション",
     "Treat username matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
     "Treat username matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
     "Treat username matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
     "Treat username matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
+    "Treat email matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
+    "Treat email matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
     "Use env var if empty": "空の場合、環境変数 <code>%s</code> を利用します",
     "Use env var if empty": "空の場合、環境変数 <code>%s</code> を利用します",
     "ldap": {
     "ldap": {
       "server_url_detail": "LDAP URLを <code>ldap://host:port/DN</code> または <code>ldaps://host:port/DN</code> の形式で入力してください。",
       "server_url_detail": "LDAP URLを <code>ldap://host:port/DN</code> または <code>ldaps://host:port/DN</code> の形式で入力してください。",

+ 1 - 1
src/client/js/components/PageEditor/HandsontableModal.jsx

@@ -130,7 +130,7 @@ export default class HandsontableModal extends React.PureComponent {
 
 
   save() {
   save() {
     if (this.props.onSave != null) {
     if (this.props.onSave != null) {
-      this.props.onSave(this.state.markdownTable);
+      this.props.onSave(this.state.markdownTable.clone().normalizeCells());
     }
     }
 
 
     this.hide();
     this.hide();

+ 1 - 1
src/client/js/components/PageEditor/MarkdownTableDataImportForm.jsx

@@ -45,7 +45,7 @@ export default class MarkdownTableDataImportForm extends React.Component {
         result = MarkdownTable.fromHTMLTableTag(this.state.data);
         result = MarkdownTable.fromHTMLTableTag(this.state.data);
         break;
         break;
     }
     }
-    return result;
+    return result.normalizeCells();
   }
   }
 
 
   render() {
   render() {

+ 5 - 0
src/client/js/legacy/crowi.js

@@ -726,11 +726,16 @@ window.addEventListener('load', function(e) {
   if (location.hash) {
   if (location.hash) {
     if (location.hash === '#edit' || location.hash === '#edit-form') {
     if (location.hash === '#edit' || location.hash === '#edit-form') {
       $('a[data-toggle="tab"][href="#edit"]').tab('show');
       $('a[data-toggle="tab"][href="#edit"]').tab('show');
+      $('body').addClass('on-edit');
+      $('body').addClass('builtin-editor');
+
       // focus
       // focus
       Crowi.setCaretLineAndFocusToEditor();
       Crowi.setCaretLineAndFocusToEditor();
     }
     }
     else if (location.hash == '#hackmd') {
     else if (location.hash == '#hackmd') {
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
+      $('body').addClass('on-edit');
+      $('body').addClass('hackmd');
     }
     }
     else if (location.hash == '#revision-history') {
     else if (location.hash == '#revision-history') {
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');

+ 13 - 0
src/client/js/models/MarkdownTable.js

@@ -40,6 +40,19 @@ export default class MarkdownTable {
     return new MarkdownTable(newTable, this.options);
     return new MarkdownTable(newTable, this.options);
   }
   }
 
 
+  /**
+   * normalize all cell data(trim & convert the newline character to space)
+   */
+  normalizeCells() {
+    for (let i = 0; i < this.table.length; i++) {
+      for (let j = 0; j < this.table[i].length; j++) {
+        this.table[i][j] = this.table[i][j].trim().replace(/\r?\n/g, ' ');
+      }
+    }
+
+    return this;
+  }
+
   /**
   /**
    * return a MarkdownTable instance made from a string of HTML table tag
    * return a MarkdownTable instance made from a string of HTML table tag
    *
    *

+ 5 - 2
src/client/styles/agile-admin/inverse/colors/_apply-colors.scss

@@ -34,11 +34,14 @@ body{
 }
 }
 
 
 .navbar-header{
 .navbar-header{
-     background:$topbar;
+  background:$topbar;
 }
 }
-.navbar-top-links > li > a{
+.navbar-top-links > li > a {
     color:$white;
     color:$white;
 }
 }
+.navbar-top-links .confidential {
+  background-color: darken($topbar, 10%);
+}
 /*
 /*
 .notify .heartbit{
 .notify .heartbit{
     border-color:$white;
     border-color:$white;

+ 2 - 8
src/client/styles/scss/_layout.scss

@@ -6,14 +6,8 @@
   .navbar-top-links { // {{{
   .navbar-top-links { // {{{
     .confidential {
     .confidential {
       a {
       a {
-        // border: solid 2px #f00;
-        // background: #fff;
-        // color: #f00;
-        // font-weight: bold;
-        // height: 42px;
-        // margin-top: 5px;
-        // padding: 10px;
-        // margin-right: 5px;
+        cursor: default;
+        font-weight: bold;
       }
       }
     }
     }
 
 

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

@@ -7,6 +7,7 @@ module.exports = form(
   field('settingForm[app:title]').trim(),
   field('settingForm[app:title]').trim(),
   field('settingForm[app:siteUrl]').trim().required().isUrl(),
   field('settingForm[app:siteUrl]').trim().required().isUrl(),
   field('settingForm[app:confidential]'),
   field('settingForm[app:confidential]'),
+  field('settingForm[app:globalLang]'),
   field('settingForm[app:fileUpload]').trim().toBooleanStrict()
   field('settingForm[app:fileUpload]').trim().toBooleanStrict()
 );
 );
 
 

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

@@ -14,4 +14,5 @@ module.exports = form(
   field('settingForm[security:passport-saml:attrMapLastName]').trim(),
   field('settingForm[security:passport-saml:attrMapLastName]').trim(),
   field('settingForm[security:passport-saml:cert]').trim(),
   field('settingForm[security:passport-saml:cert]').trim(),
   field('settingForm[security:passport-saml:isSameUsernameTreatedAsIdenticalUser]').trim().toBooleanStrict(),
   field('settingForm[security:passport-saml:isSameUsernameTreatedAsIdenticalUser]').trim().toBooleanStrict(),
+  field('settingForm[security:passport-saml:isSameEmailTreatedAsIdenticalUser]').trim().toBooleanStrict(),
 );
 );

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

@@ -53,6 +53,7 @@ module.exports = function(crowi) {
       'app:confidential'  : '',
       'app:confidential'  : '',
 
 
       'app:fileUpload'    : false,
       'app:fileUpload'    : false,
+      'app:globalLang'    : 'en',
 
 
       'security:restrictGuestMode'      : 'Deny',
       'security:restrictGuestMode'      : 'Deny',
 
 
@@ -74,6 +75,7 @@ module.exports = function(crowi) {
       'security:passport-ldap:groupDnProperty' : undefined,
       'security:passport-ldap:groupDnProperty' : undefined,
       'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser': false,
       'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser': false,
       'security:passport-saml:isEnabled' : false,
       'security:passport-saml:isEnabled' : false,
+      'security:passport-saml:isSameEmailTreatedAsIdenticalUser': false,
       'security:passport-google:isEnabled' : false,
       'security:passport-google:isEnabled' : false,
       'security:passport-github:isEnabled' : false,
       'security:passport-github:isEnabled' : false,
       'security:passport-twitter:isEnabled' : false,
       'security:passport-twitter:isEnabled' : false,
@@ -283,6 +285,11 @@ module.exports = function(crowi) {
     return getValueForCrowiNS(config, key) || 'GROWI';
     return getValueForCrowiNS(config, key) || 'GROWI';
   };
   };
 
 
+  configSchema.statics.globalLang = function(config) {
+    const key = 'app:globalLang';
+    return getValueForCrowiNS(config, key);
+  };
+
   configSchema.statics.isEnabledPassport = function(config) {
   configSchema.statics.isEnabledPassport = function(config) {
     // always true if growi installed cleanly
     // always true if growi installed cleanly
     if (Object.keys(config.crowi).length == 0) {
     if (Object.keys(config.crowi).length == 0) {
@@ -323,6 +330,11 @@ module.exports = function(crowi) {
     return getValueForCrowiNS(config, key);
     return getValueForCrowiNS(config, key);
   };
   };
 
 
+  configSchema.statics.isSameEmailTreatedAsIdenticalUser = function(config, providerType) {
+    const key = `security:passport-${providerType}:isSameEmailTreatedAsIdenticalUser`;
+    return getValueForCrowiNS(config, key);
+  };
+
   configSchema.statics.isUploadable = function(config) {
   configSchema.statics.isUploadable = function(config) {
     const method = process.env.FILE_UPLOAD || 'aws';
     const method = process.env.FILE_UPLOAD || 'aws';
 
 
@@ -605,6 +617,7 @@ module.exports = function(crowi) {
       },
       },
       recentCreatedLimit: Config.showRecentCreatedNumber(config),
       recentCreatedLimit: Config.showRecentCreatedNumber(config),
       isAclEnabled: !Config.isPublicWikiOnly(config),
       isAclEnabled: !Config.isPublicWikiOnly(config),
+      globalLang: Config.globalLang(config),
     };
     };
 
 
     return local_config;
     return local_config;

+ 16 - 2
src/server/models/external-account.js

@@ -67,10 +67,12 @@ class ExternalAccount {
    * @param {object} usernameToBeRegistered the username of User entity that will be created when accountId is not found
    * @param {object} usernameToBeRegistered the username of User entity that will be created when accountId is not found
    * @param {object} nameToBeRegistered the name of User entity that will be created when accountId is not found
    * @param {object} nameToBeRegistered the name of User entity that will be created when accountId is not found
    * @param {object} mailToBeRegistered the mail of User entity that will be created when accountId is not found
    * @param {object} mailToBeRegistered the mail of User entity that will be created when accountId is not found
+   * @param {boolean} isSameUsernameTreatedAsIdenticalUser
+   * @param {boolean} isSameEmailTreatedAsIdenticalUser
    * @returns {Promise<ExternalAccount>}
    * @returns {Promise<ExternalAccount>}
    * @memberof ExternalAccount
    * @memberof ExternalAccount
    */
    */
-  static findOrRegister(providerType, accountId, usernameToBeRegistered, nameToBeRegistered, mailToBeRegistered) {
+  static findOrRegister(providerType, accountId, usernameToBeRegistered, nameToBeRegistered, mailToBeRegistered, isSameUsernameTreatedAsIdenticalUser, isSameEmailTreatedAsIdenticalUser) {
 
 
     return this.findOne({ providerType, accountId })
     return this.findOne({ providerType, accountId })
       .then(account => {
       .then(account => {
@@ -82,7 +84,19 @@ class ExternalAccount {
 
 
         const User = ExternalAccount.crowi.model('User');
         const User = ExternalAccount.crowi.model('User');
 
 
-        return User.findOne({username: usernameToBeRegistered})
+        let promise = User.findOne({username: usernameToBeRegistered});
+        if (isSameUsernameTreatedAsIdenticalUser && isSameEmailTreatedAsIdenticalUser) {
+          promise = promise
+            .then(user => {
+              if (user == null) { return User.findOne({email: mailToBeRegistered}) }
+              return user;
+            });
+        }
+        else if (isSameEmailTreatedAsIdenticalUser) {
+          promise = User.findOne({email: mailToBeRegistered});
+        }
+
+        return promise
           .then(user => {
           .then(user => {
             // when the User that have the same `username` exists
             // when the User that have the same `username` exists
             if (user != null) {
             if (user != null) {

+ 6 - 3
src/server/routes/login-passport.js

@@ -427,6 +427,9 @@ module.exports = function(crowi, app) {
   };
   };
 
 
   const getOrCreateUser = async(req, res, userInfo, providerId) => {
   const getOrCreateUser = async(req, res, userInfo, providerId) => {
+    // get option
+    const isSameUsernameTreatedAsIdenticalUser = Config.isSameUsernameTreatedAsIdenticalUser(config, providerId);
+    const isSameEmailTreatedAsIdenticalUser = Config.isSameEmailTreatedAsIdenticalUser(config, providerId);
     try {
     try {
       // find or register(create) user
       // find or register(create) user
       const externalAccount = await ExternalAccount.findOrRegister(
       const externalAccount = await ExternalAccount.findOrRegister(
@@ -435,14 +438,14 @@ module.exports = function(crowi, app) {
         userInfo.username,
         userInfo.username,
         userInfo.name,
         userInfo.name,
         userInfo.email,
         userInfo.email,
+        isSameUsernameTreatedAsIdenticalUser,
+        isSameEmailTreatedAsIdenticalUser,
       );
       );
       return externalAccount;
       return externalAccount;
     }
     }
     catch (err) {
     catch (err) {
       if (err.name === 'DuplicatedUsernameException') {
       if (err.name === 'DuplicatedUsernameException') {
-        // get option
-        const isSameUsernameTreatedAsIdenticalUser = Config.isSameUsernameTreatedAsIdenticalUser(config, providerId);
-        if (isSameUsernameTreatedAsIdenticalUser) {
+        if (isSameEmailTreatedAsIdenticalUser || isSameUsernameTreatedAsIdenticalUser) {
           // associate to existing user
           // associate to existing user
           debug(`ExternalAccount '${userInfo.username}' will be created and bound to the exisiting User account`);
           debug(`ExternalAccount '${userInfo.username}' will be created and bound to the exisiting User account`);
           return ExternalAccount.associate(providerId, userInfo.id, err.user);
           return ExternalAccount.associate(providerId, userInfo.id, err.user);

+ 1 - 0
src/server/routes/me.js

@@ -117,6 +117,7 @@ module.exports = function(crowi, app) {
             Object.keys(err.errors).forEach((e) => {
             Object.keys(err.errors).forEach((e) => {
               req.form.errors.push(err.errors[e].message);
               req.form.errors.push(err.errors[e].message);
             });
             });
+
             return res.render('me/index', {});
             return res.render('me/index', {});
           }
           }
           req.i18n.changeLanguage(lang);
           req.i18n.changeLanguage(lang);

+ 8 - 0
src/server/util/swigFunctions.js

@@ -49,6 +49,14 @@ module.exports = function(crowi, app, req, locals) {
     return crowi.xss.process(Config.appTitle(config));
     return crowi.xss.process(Config.appTitle(config));
   };
   };
 
 
+  /**
+   * return app-global language
+   */
+  locals.appGlobalLang = function() {
+    const config = crowi.getConfig();
+    return Config.globalLang(config);
+  };
+
   /**
   /**
    * return true if enabled
    * return true if enabled
    */
    */

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

@@ -60,7 +60,22 @@
         </div>
         </div>
 
 
         <div class="form-group">
         <div class="form-group">
-          <div class="col-xs-offset-3 col-xs-6">
+          <label class="col-xs-3 control-label tbd">(TBD) {{ t('app_setting.Default Language for new users') }}</label>
+          <div class="col-xs-6">
+            <div class="radio radio-primary radio-inline">
+                <input type="radio" id="radioLangEn" name="settingForm[app:globalLang]" value="{{ consts.language.LANG_EN }}" {% if appGlobalLang() == consts.language.LANG_EN %}checked="checked"{% endif %}>
+                <label for="radioLangEn">{{ t('English') }}</label>
+            </div>
+            <div class="radio radio-primary radio-inline">
+                <input type="radio" id="radioLangJa" name="settingForm[app:globalLang]" value="{{ consts.language.LANG_JA }}" {% if appGlobalLang() == consts.language.LANG_JA %}checked="checked"{% endif %}>
+                <label for="radioLangJa">{{ t('Japanese') }}</label>
+            </div>
+          </div>
+        </div>
+
+        <div class="form-group">
+          <label class="col-xs-3 control-label">{{ t('app_setting.File Uploading') }}</label>
+          <div class="col-xs-6">
             <div class="checkbox checkbox-info">
             <div class="checkbox checkbox-info">
               <input type="checkbox" id="cbFileUpload" name="settingForm[app:fileUpload]" value="1"
               <input type="checkbox" id="cbFileUpload" name="settingForm[app:fileUpload]" value="1"
                 {% if settingForm['app:fileUpload'] %}
                 {% if settingForm['app:fileUpload'] %}

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

@@ -65,12 +65,6 @@
       <form action="/_api/admin/customize/layout" method="post" class="form-horizontal" id="customlayoutSettingForm" role="form">
       <form action="/_api/admin/customize/layout" method="post" class="form-horizontal" id="customlayoutSettingForm" role="form">
       <fieldset>
       <fieldset>
         <legend>{{ t('customize_page.Layout') }}</legend>
         <legend>{{ t('customize_page.Layout') }}</legend>
-        {% if env === 'development' %}
-          <br>
-          <div class="alert alert-warning">
-            <strong>DEBUG MESSAGE:</strong> development build では、リアルタイムプレビューが無効になります
-          </div>
-        {% endif %}
         <div class="form-group">
         <div class="form-group">
           <div class="col-sm-4">
           <div class="col-sm-4">
             <h4>
             <h4>
@@ -134,6 +128,11 @@
           </div>
           </div>
         </div>
         </div>
         <h2>{{ t('customize_page.Theme') }}</h2>
         <h2>{{ t('customize_page.Theme') }}</h2>
+        {% if env === 'development' %}
+          <div class="alert alert-warning">
+            <strong>DEBUG MESSAGE:</strong> development build では、リアルタイムプレビューが無効になります
+          </div>
+        {% endif %}
         <div id="themeOptions" {% if 'kibela' == settingForm['customize:layout'] %}class="disabled"{% endif %}>
         <div id="themeOptions" {% if 'kibela' == settingForm['customize:layout'] %}class="disabled"{% endif %}>
           {# Light Themes #}
           {# Light Themes #}
           <div class="d-flex">
           <div class="d-flex">

+ 17 - 0
src/server/views/admin/widget/passport/saml.html

@@ -117,6 +117,23 @@
       </div>
       </div>
     </div>
     </div>
 
 
+    <div class="form-group">
+      <div class="col-xs-6 col-xs-offset-3">
+        <div class="checkbox checkbox-info">
+          <input type="checkbox" id="bindByEmail-SAML" name="settingForm[security:passport-saml:isSameEmailTreatedAsIdenticalUser]" value="1"
+              {% if settingForm['security:passport-saml:isSameEmailTreatedAsIdenticalUser'] %}checked{% endif %} />
+          <label for="bindByEmail-SAML">
+            {{ t("security_setting.Treat email matching as identical", "email") }}
+          </label>
+          <p class="help-block">
+            <small>
+              {{ t("security_setting.Treat email matching as identical_warn", "email") }}
+            </small>
+          </p>
+        </div>
+      </div>
+    </div>
+
     <div class="form-group">
     <div class="form-group">
       <label for="settingForm[security:passport-saml:attrMapFirstName]" class="col-xs-3 control-label">{{ t("security_setting.SAML.First Name") }}</label>
       <label for="settingForm[security:passport-saml:attrMapFirstName]" class="col-xs-3 control-label">{{ t("security_setting.SAML.First Name") }}</label>
       <div class="col-xs-6">
       <div class="col-xs-6">

+ 4 - 0
src/server/views/installer.html

@@ -49,6 +49,10 @@
         <small>初めに作成するアカウントは、自動的に管理者権限が付与されます</small>
         <small>初めに作成するアカウントは、自動的に管理者権限が付与されます</small>
       </p>
       </p>
 
 
+      <p class="alert alert-warning p-b-10 p-t-10">
+        <small>現在の言語設定: {{ appGlobalLang() }}</small>
+      </p>
+
       <form role="form" action="/installer/createAdmin" method="post" id="register-form">
       <form role="form" action="/installer/createAdmin" method="post" id="register-form">
 
 
         <div class="input-group" id="input-group-username">
         <div class="input-group" id="input-group-username">