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

Merge branch 'master' into feat/editor-component-comment-write

yusuketk 7 лет назад
Родитель
Сommit
81be6fb38e

+ 22 - 5
CHANGES.md

@@ -1,21 +1,38 @@
 CHANGES
 ========
 
-## 3.1.4-RC
+## 3.1.6-RC
 
+* (WIP) Feature: Support [blockdiag](http://blockdiag.com)
+* Feature: Add `BLOCKDIAG_URI` environment variable
 * Support: Upgrade libs
-    * markdown-it-toc-and-anchor-with-slugid
-
+    * googleapis
+    * throttle-debounce
 
-## 3.1.3
+## 3.1.5
 
 * Feature: Write comment with Markdown
 * Improvement: Support some placeholders for template page
 * Improvement: Omit unnecessary response header
 * Improvement: Support LDAP attribute mappings for user's full name
-* Fix: HTML escaped characters in markdown are unescaped unexpectedly after page is saved
+* Improvement: Enable to scroll revision-toc
 * Fix: Posting to Slack doesn't work
     * Introduced by 3.1.0
+* Fix: page.rename api doesn't work
+* Fix: HTML escaped characters in markdown are unescaped unexpectedly after page is saved
+* Fix: sanitize `#raw-text-original` content with 'entities'
+* Fix: Double newline character posted
+    * Introduced by 3.1.4
+* Fix: List and Comment components do not displayed
+    * Introduced by 3.1.4
+* Support: Upgrade libs
+    * markdown-it-toc-and-anchor-with-slugid
+
+
+## 3.1.4 (Missing number)
+
+## 3.1.3 (Missing number)
+
 
 ## 3.1.2
 

+ 3 - 1
README.md

@@ -159,10 +159,12 @@ Environment Variables
     * NODE_ENV: `production` OR `development`.
     * PORT: Server port. default: `3000`
     * REDIS_URL: URI to connect to Redis (to session store).
-    * SESSION_NAME: The name of the session ID cookie to set in the response by Express. default: `connect.sid`
     * ELASTICSEARCH_URI: URI to connect to Elasticearch.
+    * PLANTUML_URI: URI to connect to [PlantUML](http://plantuml.com/) server.
+    * BLOCKDIAG_URI: URI to connect to [blockdiag](http://http://blockdiag.com/) server.
     * PASSWORD_SEED: A password seed used by password hash generator.
     * SECRET_TOKEN: A secret key for verifying the integrity of signed cookies.
+    * SESSION_NAME: The name of the session ID cookie to set in the response by Express. default: `connect.sid`
     * FILE_UPLOAD: `aws` (default), `local`, `none`
 
 

+ 4 - 1
lib/form/revision.js

@@ -5,7 +5,10 @@ var form = require('express-form')
 
 module.exports = form(
   field('pageForm.path').required(),
-  field('pageForm.body').required().custom(function(value) { return value.replace(/\r/g, '\n') }),
+  field('pageForm.body').required().custom(function(value) {
+    // see https://github.com/weseek/growi/issues/463
+    return value.replace(/\r\n?/g, '\n');
+  }),
   field('pageForm.currentRevision'),
   field('pageForm.grant').toInt().required(),
   field('pageForm.grantUserGroupId'),

+ 1 - 0
lib/models/config.js

@@ -465,6 +465,7 @@ module.exports = function(crowi) {
       isSavedStatesOfTabChanges: Config.isSavedStatesOfTabChanges(config),
       env: {
         PLANTUML_URI: env.PLANTUML_URI || null,
+        BLOCKDIAG_URI: env.BLOCKDIAG_URI || null,
         MATHJAX: env.MATHJAX || null,
       },
     };

+ 5 - 1
lib/models/revision.js

@@ -10,7 +10,11 @@ module.exports = function(crowi) {
 
   revisionSchema = new mongoose.Schema({
     path: { type: String, required: true },
-    body: { type: String, required: true },
+    body: { type: String, required: true, get: (data) => {
+      // replace CR/CRLF to LF above v3.1.5
+      // see https://github.com/weseek/growi/issues/463
+      return data.replace(/\r\n?/g, '\n');
+    }},
     format: { type: String, default: 'markdown' },
     author: { type: ObjectId, ref: 'User' },
     createdAt: { type: Date, default: Date.now }

+ 15 - 7
lib/routes/page.js

@@ -607,11 +607,16 @@ module.exports = function(crowi, app) {
 
   actions.pageEdit = function(req, res) {
 
-    var pageForm = req.body.pageForm;
+    if (!req.form.isValid) {
+      req.flash('dangerMessage', 'Request is invalid.');
+      return res.redirect(req.headers.referer);
+    }
+
+    var pageForm = req.form.pageForm;
+    var path = pageForm.path;
     var body = pageForm.body;
     var currentRevision = pageForm.currentRevision;
     var grant = pageForm.grant;
-    var path = pageForm.path;
     var grantUserGroupId = pageForm.grantUserGroupId;
 
     // TODO: make it pluggable
@@ -1106,9 +1111,10 @@ module.exports = function(crowi, app) {
 
     Page.findPageByPath(newPagePath)
     .then(function(page) {
-      // if page found, cannot cannot rename to that path
-      return res.json(ApiResponse.error(`このページ名は作成できません (${newPagePath})。ページが存在します。`));
-    }).catch(function(err) {
+      if (page != null) {
+        // if page found, cannot cannot rename to that path
+        return res.json(ApiResponse.error(`このページ名は作成できません (${newPagePath})。ページが存在します。`));
+      }
 
       Page.findPageById(pageId)
       .then(function(pageData) {
@@ -1124,12 +1130,14 @@ module.exports = function(crowi, app) {
           return Page.rename(pageData, newPagePath, req.user, options);
         }
 
-      }).then(function() {
+      })
+      .then(function() {
         var result = {};
         result.page = page;
 
         return res.json(ApiResponse.success(result));
-      }).catch(function(err) {
+      })
+      .catch(function(err) {
         return res.json(ApiResponse.error('Failed to update page.'));
       });
     });

+ 7 - 2
lib/util/middlewares.js

@@ -1,5 +1,6 @@
-var debug = require('debug')('growi:lib:middlewares');
-var md5 = require('md5');
+const debug = require('debug')('growi:lib:middlewares');
+const md5 = require('md5');
+const entities = require('entities');
 
 exports.csrfKeyGenerator = function(crowi, app) {
   return function(req, res, next) {
@@ -178,6 +179,10 @@ exports.swigFilters = function(app, swig) {
       }
     });
 
+    swig.setFilter('sanitize', function(string) {
+      return entities.encodeHTML(string);
+    });
+
     next();
   };
 };

+ 0 - 36
lib/views/layout-growi/base/layout.html

@@ -26,42 +26,6 @@
 
 </div><!-- /.container-fluid -->
 
-
-<!-- Side Scroll Bar-->
-<script>
-  /*
-   * Disabled temporally -- 2018.06.06 Yuki Takei
-   * see https://weseek.myjetbrains.com/youtrack/issue/GC-278
-   *
-  function DrawScrollbar() {
-    var h = window.innerHeight - document.getElementById('page-header').clientHeight ;
-    $('#revision-toc-content').slimScroll({
-      railVisible: true,
-      position: 'right',
-      height: h,
-    });
-  }
-
-  $(function(){
-    DrawScrollbar();
-  });
-
-  (function () {
-    var timer = 0;
-
-    window.onresize = function () {
-      if (timer > 0) {
-        clearTimeout(timer);
-      }
-
-      timer = setTimeout(function () {
-        DrawScrollbar();
-      }, 200);
-    };
-  }());
-  */
-  </script>
-
 <footer class="footer">
   {% include '../../widget/system-version.html' %}
 </footer>

+ 7 - 0
lib/views/widget/page_alerts.html

@@ -79,5 +79,12 @@
       <a href="{{ page.path }}"><i class="icon-fw icon-arrow-right-circle"></i>{{ t('Show latest') }}</a>
     </div>
     {% endif %}
+
+    {% set dmessage = req.flash('dangerMessage') %}
+    {% if dmessage.length %}
+    <div class="alert alert-danger m-b-15">
+      {{ dmessage }}
+    </div>
+    {% endif %}
   </div>
 </div>

+ 1 - 1
lib/views/widget/page_content.html

@@ -15,7 +15,7 @@
   <div class="tab-content">
 
     {% if page %}
-      <script type="text/template" id="raw-text-original">{{ revision.body.toString() }}</script>
+      <script type="text/template" id="raw-text-original">{{ revision.body.toString() | sanitize }}</script>
 
       {# formatted text #}
       <div class="tab-pane {% if not req.body.pageForm %}active{% endif %}" id="revision-body">

+ 1 - 1
lib/views/widget/page_list_and_timeline.html

@@ -33,7 +33,7 @@
             <div class="revision-body wiki"></div>
           </div>
         </div>
-        <script type="text/template">{{ page.revision.body.toString() }}</script>
+        <script type="text/template">{{ page.revision.body.toString() | sanitize }}</script>
       </div>
       <hr>
       {% endfor %}

+ 4 - 3
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "3.1.4-RC",
+  "version": "3.1.6-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -76,7 +76,7 @@
     "express-sanitizer": "^1.0.4",
     "express-session": "~1.15.0",
     "express-webpack-assets": "^0.1.0",
-    "googleapis": "^31.0.2",
+    "googleapis": "^32.0.0",
     "graceful-fs": "^4.1.11",
     "growi-pluginkit": "^1.1.0",
     "i18next": "^11.1.1",
@@ -143,6 +143,7 @@
     "jquery.cookie": "~1.4.1",
     "load-css-file": "^1.0.0",
     "markdown-it": "^8.4.0",
+    "markdown-it-blockdiag": "^1.0.0",
     "markdown-it-emoji": "^1.4.0",
     "markdown-it-footnote": "^3.0.1",
     "markdown-it-mathjax": "^2.0.0",
@@ -173,7 +174,7 @@
     "sinon-chai": "^3.0.0",
     "socket.io-client": "^2.0.3",
     "style-loader": "^0.21.0",
-    "throttle-debounce": "^1.0.1",
+    "throttle-debounce": "^2.0.0",
     "toastr": "^2.1.2",
     "url-join": "^4.0.0",
     "webpack": "3.11.0",

+ 5 - 1
resource/js/app.js

@@ -30,6 +30,8 @@ import CustomCssEditor  from './components/Admin/CustomCssEditor';
 import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 
+import * as entities from 'entities';
+
 if (!window) {
   window = {};
 }
@@ -44,6 +46,7 @@ let pageRevisionCreatedAt = null;
 let pagePath;
 let pageContent = '';
 let markdown = '';
+let pageGrant = null;
 if (mainContent !== null) {
   pageId = mainContent.getAttribute('data-page-id');
   pageRevisionId = mainContent.getAttribute('data-page-revision-id');
@@ -51,8 +54,9 @@ if (mainContent !== null) {
   pagePath = mainContent.attributes['data-path'].value;
   const rawText = document.getElementById('raw-text-original');
   if (rawText) {
-    markdown = rawText.innerHTML;
+    pageContent = rawText.innerHTML;
   }
+  markdown = entities.decodeHTML(pageContent);
 }
 const isLoggedin = document.querySelector('.main-container.nologin') == null;
 

+ 72 - 1
resource/js/legacy/crowi.js

@@ -8,6 +8,8 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
 
+import { debounce } from 'throttle-debounce';
+
 import GrowiRenderer from '../util/GrowiRenderer';
 import Page from '../components/Page';
 
@@ -165,6 +167,60 @@ Crowi.handleKeyCtrlSlashHandler = (event) => {
   event.preventDefault();
 };
 
+Crowi.initSlimScrollForRevisionToc = () => {
+  const revisionTocElem = document.querySelector('.growi .revision-toc');
+  const tocContentElem = document.querySelector('.growi .revision-toc .markdownIt-TOC');
+
+  // growi layout only
+  if (revisionTocElem == null || tocContentElem == null) {
+    return;
+  }
+
+  function getCurrentRevisionTocTop() {
+    // calculate absolute top of '#revision-toc' element
+    return revisionTocElem.getBoundingClientRect().top;
+  }
+
+  function resetScrollbar(revisionTocTop) {
+    // window height - revisionTocTop - .system-version height
+    let h = window.innerHeight - revisionTocTop - 20;
+
+    const tocContentHeight = tocContentElem.getBoundingClientRect().height + 15;  // add margin
+
+    h = Math.min(h, tocContentHeight);
+
+    $('#revision-toc-content').slimScroll({
+      railVisible: true,
+      position: 'right',
+      height: h,
+    });
+  }
+
+  const resetScrollbarDebounced = debounce(100, resetScrollbar);
+
+  // initialize
+  const revisionTocTop = getCurrentRevisionTocTop();
+  resetScrollbar(revisionTocTop);
+
+  /*
+   * set event listener
+   */
+  // resize
+  window.addEventListener('resize', (event) => {
+    resetScrollbarDebounced(getCurrentRevisionTocTop());
+  });
+  // affix on
+  $('#revision-toc').on('affixed.bs.affix', function() {
+    resetScrollbar(getCurrentRevisionTocTop());
+  });
+  // affix off
+  $('#revision-toc').on('affixed-top.bs.affix', function() {
+    // calculate sum of height (.navbar-header + .bg-title) + margin-top of .main
+    const sum = 138;
+    resetScrollbar(sum);
+  });
+};
+
 $(function() {
   var config = JSON.parse(document.getElementById('crowi-context-hydrate').textContent || '{}');
 
@@ -430,7 +486,7 @@ $(function() {
         var revisionPath = '#' + id + ' .revision-path';
         /* eslint-enable */
         var pagePath = document.getElementById(id).getAttribute('data-page-path');
-        var markdown = $(contentId).html();
+        var markdown = entities.decodeHTML($(contentId).html());
 
         ReactDOM.render(<Page crowi={crowi} crowiRenderer={growiRendererForTimeline} markdown={markdown} pagePath={pagePath} />, revisionBodyElem);
       });
@@ -529,6 +585,20 @@ $(function() {
       });
     }
 
+    // (function () {
+    //   var timer = 0;
+
+    //   window.onresize = function () {
+    //     if (timer > 0) {
+    //       clearTimeout(timer);
+    //     }
+
+    //     timer = setTimeout(function () {
+    //       DrawScrollbar();
+    //     }, 200);
+    //   };
+    // }());
+
     /*
      * transplanted to React components -- 2017.06.02 Yuki Takei
      *
@@ -890,6 +960,7 @@ window.addEventListener('load', function(e) {
   Crowi.highlightSelectedSection(location.hash);
   Crowi.modifyScrollTop();
   Crowi.setCaretLineAndFocusToEditor();
+  Crowi.initSlimScrollForRevisionToc();
 });
 
 window.addEventListener('hashchange', function(e) {

+ 2 - 0
resource/js/util/GrowiRenderer.js

@@ -15,6 +15,7 @@ import PlantUMLConfigurer from './markdown-it/plantuml';
 import TableConfigurer from './markdown-it/table';
 import TaskListsConfigurer from './markdown-it/task-lists';
 import TocAndAnchorConfigurer from './markdown-it/toc-and-anchor';
+import BlockdiagConfigurer from './markdown-it/blockdiag';
 
 export default class GrowiRenderer {
 
@@ -75,6 +76,7 @@ export default class GrowiRenderer {
       new EmojiConfigurer(crowi),
       new MathJaxConfigurer(crowi),
       new PlantUMLConfigurer(crowi),
+      new BlockdiagConfigurer(crowi),
     ];
 
     // add configurers according to mode

+ 17 - 0
resource/js/util/markdown-it/blockdiag.js

@@ -0,0 +1,17 @@
+export default class BlockdiagConfigurer {
+
+  constructor(crowi) {
+    this.crowi = crowi;
+    const config = crowi.getConfig();
+
+    this.generateSourceUrl = config.env.BLOCKDIAG_URL || 'https://blockdiag-api.com/';
+  }
+
+  configure(md) {
+    //// disable temporary because this breaks /Sandbox -- 2018.06.08 Yuki Takei
+    // md.use(require('markdown-it-blockdiag'), {
+    //   generateSourceUrl: this.generateSourceUrl,
+    //   marker: ':::',
+    // });
+  }
+}

+ 1 - 0
resource/styles/scss/_layout_growi.scss

@@ -7,6 +7,7 @@
     &.affix {
       margin-top: 5px;
       top: calc(46px + 5px);
+      min-width: calc(#{100/12*2%} - #{$grid-gutter-width});  // width of 2column - padding
     }
 
     .revision-toc-content {

+ 25 - 7
yarn.lock

@@ -3240,9 +3240,9 @@ google-p12-pem@^1.0.0:
     node-forge "^0.7.1"
     pify "^3.0.0"
 
-googleapis@^31.0.2:
-  version "31.0.2"
-  resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-31.0.2.tgz#03266287c8b52681e4311a28d9ff2d800e8f1afb"
+googleapis@^32.0.0:
+  version "32.0.0"
+  resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-32.0.0.tgz#04795d1956568546bb3e3cca3cae29a759326493"
   dependencies:
     google-auth-library "^1.4.0"
     pify "^3.0.0"
@@ -4370,10 +4370,24 @@ map-values@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/map-values/-/map-values-1.0.1.tgz#768b8e79c009bf2b64fee806e22a7b1c4190c990"
 
+markdown-it-blockdiag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/markdown-it-blockdiag/-/markdown-it-blockdiag-1.0.0.tgz#d949ab426e59f948713eb9702ab186a92a572736"
+  dependencies:
+    markdown-it-fence "0.0.2"
+    pako "^1.0.6"
+    paths "^0.1.1"
+    url-join "^4.0.0"
+    utf8-bytes "0.0.1"
+
 markdown-it-emoji@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz#9bee0e9a990a963ba96df6980c4fddb05dfb4dcc"
 
+markdown-it-fence@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/markdown-it-fence/-/markdown-it-fence-0.0.2.tgz#ce1fe95900891603300d9da519aada144d5de9fc"
+
 markdown-it-footnote@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/markdown-it-footnote/-/markdown-it-footnote-3.0.1.tgz#7f3730747cacc86e2fe0bf8a17a710f34791517a"
@@ -5218,7 +5232,7 @@ pako@1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.3.tgz#5f515b0c6722e1982920ae8005eacb0b7ca73ccf"
 
-pako@~1.0.5:
+pako@^1.0.6, pako@~1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
 
@@ -5356,6 +5370,10 @@ path-type@^3.0.0:
   dependencies:
     pify "^3.0.0"
 
+paths@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/paths/-/paths-0.1.1.tgz#9ad909d7f769dd8acb3a1c033c5eef43123d3d17"
+
 pathval@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
@@ -7082,9 +7100,9 @@ tfunk@^3.0.1:
     chalk "^1.1.1"
     object-path "^0.9.0"
 
-throttle-debounce@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-1.0.1.tgz#dad0fe130f9daf3719fdea33dc36a8e6ba7f30b5"
+throttle-debounce@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.0.0.tgz#2d8d24bd8cf3cb0cc7bd1a2dbeb624b4081a1ed4"
 
 through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
   version "2.3.8"