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

Merge branch 'render-and-api-access-refactor' into wip-v1.5.0

Sotaro KARASAWA 9 лет назад
Родитель
Сommit
df058c0baa

+ 15 - 0
resource/js/app.js

@@ -1,14 +1,29 @@
 import React from 'react';
 import React from 'react';
 import ReactDOM from 'react-dom';
 import ReactDOM from 'react-dom';
 
 
+import Crowi from './util/Crowi';
+import CrowiRenderer from './util/CrowiRenderer';
+
 import HeaderSearchBox  from './components/HeaderSearchBox';
 import HeaderSearchBox  from './components/HeaderSearchBox';
 import SearchPage  from './components/SearchPage';
 import SearchPage  from './components/SearchPage';
 import PageListSearch  from './components/PageListSearch';
 import PageListSearch  from './components/PageListSearch';
+//import PageComment  from './components/PageComment';
+
+if (!window) {
+  window = {};
+}
+// FIXME
+const crowi = new Crowi({/* context */}, window);
+window.crowi = crowi;
+
+const crowiRenderer = new CrowiRenderer();
+window.crowiRenderer = crowiRenderer;
 
 
 const componentMappings = {
 const componentMappings = {
   'search-top': <HeaderSearchBox />,
   'search-top': <HeaderSearchBox />,
   'search-page': <SearchPage />,
   'search-page': <SearchPage />,
   'page-list-search': <PageListSearch />,
   'page-list-search': <PageListSearch />,
+  //'page-comment': <PageComment />,
 };
 };
 
 
 Object.keys(componentMappings).forEach((key) => {
 Object.keys(componentMappings).forEach((key) => {

+ 12 - 19
resource/js/components/HeaderSearchBox.js

@@ -11,6 +11,8 @@ export default class SearchBox extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
+    this.crowi = window.crowi; // FIXME
+
     this.state = {
     this.state = {
       searchingKeyword: '',
       searchingKeyword: '',
       searchedPages: [],
       searchedPages: [],
@@ -43,26 +45,17 @@ export default class SearchBox extends React.Component {
       searching: true,
       searching: true,
     });
     });
 
 
-    axios.get('/_api/search', {params: {q: keyword}})
-    .then((res) => {
-      if (res.data.ok) {
-        this.setState({
-          searchingKeyword: keyword,
-          searchedPages: res.data.data,
-          searching: false,
-          searchError: null,
-        });
-      } else {
-        this.setState({
-          searchError: res,
-          searching: false,
-        });
-      }
-      // TODO error
-    }).catch((res) => {
-      // TODO error
+    this.crowi.apiGet('/search', {q: keyword})
+    .then(res => {
+      this.setState({
+        searchingKeyword: keyword,
+        searchedPages: res.data,
+        searching: false,
+        searchError: null,
+      });
+    }).catch(err => {
       this.setState({
       this.setState({
-        searchError: res,
+        searchError: err,
         searching: false,
         searching: false,
       });
       });
     });
     });

+ 2 - 33
resource/js/components/Page/PageBody.js

@@ -1,12 +1,11 @@
 import React from 'react';
 import React from 'react';
-import marked from 'marked';
-import hljs from 'highlight.js';
 
 
 export default class PageBody extends React.Component {
 export default class PageBody extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
+    this.crowiRenderer = window.crowiRenderer; // FIXME
     this.getMarkupHTML = this.getMarkupHTML.bind(this);
     this.getMarkupHTML = this.getMarkupHTML.bind(this);
   }
   }
 
 
@@ -16,37 +15,7 @@ export default class PageBody extends React.Component {
       body = this.props.page.revision.body;
       body = this.props.page.revision.body;
     }
     }
 
 
-    let parsed = '<b>...</b>';
-    try {
-      // TODO
-      marked.setOptions({
-        gfm: true,
-        highlight: function (code, lang) {
-          let result, hl;
-          if (lang) {
-            try {
-              hl = hljs.highlight(lang, code);
-              result = hl.value;
-            } catch (e) {
-              result = code;
-            }
-          } else {
-            result = code;
-          }
-          return result;
-        },
-        tables: true,
-        breaks: true,
-        pedantic: false,
-        sanitize: false,
-        smartLists: true,
-        smartypants: false,
-        langPrefix: 'lang-'
-      });
-      parsed = marked(body);
-    } catch (e) { console.log(e, e.stack); }
-
-    return { __html: parsed };
+    return { __html: this.crowiRenderer.render(body) };
   }
   }
 
 
   render() {
   render() {

+ 13 - 19
resource/js/components/SearchPage.js

@@ -2,6 +2,7 @@
 
 
 import React from 'react';
 import React from 'react';
 import axios from 'axios'
 import axios from 'axios'
+import Crowi from '../util/Crowi';
 import SearchForm from './SearchPage/SearchForm';
 import SearchForm from './SearchPage/SearchForm';
 import SearchResult from './SearchPage/SearchResult';
 import SearchResult from './SearchPage/SearchResult';
 
 
@@ -10,6 +11,8 @@ export default class SearchPage extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
+    this.crowi = window.crowi; // FIXME
+
     this.state = {
     this.state = {
       location: location,
       location: location,
       searchingKeyword: this.props.query.q || '',
       searchingKeyword: this.props.query.q || '',
@@ -70,28 +73,19 @@ export default class SearchPage extends React.Component {
       searchingKeyword: keyword,
       searchingKeyword: keyword,
     });
     });
 
 
-    axios.get('/_api/search', {params: {q: keyword}})
-    .then((res) => {
-      if (res.data.ok) {
-        this.changeURL(keyword);
-
-        this.setState({
-          searchedKeyword: keyword,
-          searchedPages: res.data.data,
-          searchResultMeta: res.data.meta,
-        });
-      } else {
-        this.setState({
-          searchError: res.data,
-        });
-      }
+    this.crowi.apiGet('/search', {q: keyword})
+    .then(res => {
+      this.changeURL(keyword);
 
 
-      // TODO error
-    })
-    .catch((res) => {
+      this.setState({
+        searchedKeyword: keyword,
+        searchedPages: res.data,
+        searchResultMeta: res.meta,
+      });
+    }).catch(err => {
       // TODO error
       // TODO error
       this.setState({
       this.setState({
-        searchError: res.data,
+        searchError: err,
       });
       });
     });
     });
   };
   };

+ 2 - 2
resource/js/components/SearchPage/SearchResult.js

@@ -24,8 +24,8 @@ export default class SearchResult extends React.Component {
   render() {
   render() {
     const excludePathString = this.props.tree;
     const excludePathString = this.props.tree;
 
 
-    console.log(this.props.searchError);
-    console.log(this.isError());
+    //console.log(this.props.searchError);
+    //console.log(this.isError());
     if (this.isError()) {
     if (this.isError()) {
       return (
       return (
         <div className="content-main">
         <div className="content-main">

+ 2 - 2
resource/js/crowi-form.js

@@ -49,8 +49,8 @@ $(function() {
   var watchTimer = setInterval(function() {
   var watchTimer = setInterval(function() {
     var content = $('#form-body').val();
     var content = $('#form-body').val();
     if (prevContent != content) {
     if (prevContent != content) {
-      var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
-      renderer.render();
+      var parsedHTML = crowiRenderer.render(content);
+      $('#preview-body').html(parsedHTML);
 
 
       prevContent = content;
       prevContent = content;
     }
     }

+ 8 - 106
resource/js/crowi.js

@@ -2,9 +2,7 @@
 /* Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
 /* Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
 */
 */
 
 
-var hljs = require('highlight.js');
 var jsdiff = require('diff');
 var jsdiff = require('diff');
-var marked = require('marked');
 var io = require('socket.io-client');
 var io = require('socket.io-client');
 
 
 //require('bootstrap-sass');
 //require('bootstrap-sass');
@@ -133,89 +131,6 @@ Crowi.unescape = function(s) {
   return s;
   return s;
 };
 };
 
 
-Crowi.getRendererType = function() {
-  return new Crowi.rendererType.markdown();
-};
-
-Crowi.rendererType = {};
-Crowi.rendererType.markdown = function(){};
-Crowi.rendererType.markdown.prototype = {
-  render: function(contentText) {
-
-    marked.setOptions({
-      gfm: true,
-      highlight: function (code, lang, callback) {
-        var result, hl;
-        if (lang) {
-          try {
-            hl = hljs.highlight(lang, code);
-            result = hl.value;
-          } catch (e) {
-            result = code;
-          }
-        } else {
-          //result = hljs.highlightAuto(code);
-          //callback(null, result.value);
-          result = code;
-        }
-        return callback(null, result);
-      },
-      tables: true,
-      breaks: true,
-      pedantic: false,
-      sanitize: false,
-      smartLists: true,
-      smartypants: false,
-      langPrefix: 'lang-'
-    });
-
-    var contentHtml = Crowi.unescape(contentText);
-    // TODO 前処理系のプラグイン化
-    contentHtml = this.preFormatMarkdown(contentHtml);
-    contentHtml = this.expandImage(contentHtml);
-    contentHtml = this.link(contentHtml);
-
-    var $body = this.$revisionBody;
-    // Using async version of marked
-    marked(contentHtml, {}, function (err, content) {
-      if (err) {
-        throw err;
-      }
-      $body.html(content);
-    });
-  },
-  preFormatMarkdown: function(content){
-    var x = content
-      .replace(/^(#{1,})([^\s]+)?(.*)$/gm, '$1 $2$3') // spacer for section
-      .replace(/>[\s]*\n>[\s]*\n/g, '> <br>\n> \n');
-    return x;
-  },
-  link: function (content) {
-    return content
-      //.replace(/\s(https?:\/\/[\S]+)/g, ' <a href="$1">$1</a>') // リンク
-      .replace(/\s<((\/[^>]+?){2,})>/g, ' <a href="$1">$1</a>') // ページ間リンク: <> でかこまれてて / から始まり、 / が2個以上
-      ;
-  },
-  expandImage: function (content) {
-    return content.replace(/\s(https?:\/\/[\S]+\.(jpg|jpeg|gif|png))/g, ' <a href="$1"><img src="$1" class="auto-expanded-image"></a>');
-  }
-};
-
-Crowi.renderer = function (contentText, revisionBody) {
-  var $revisionBody = revisionBody || $('#revision-body-content');
-
-  this.contentText = contentText;
-  this.$revisionBody = $revisionBody;
-  this.format = 'markdown'; // とりあえず
-  this.renderer = Crowi.getRendererType();
-  this.renderer.$revisionBody = this.$revisionBody;
-};
-Crowi.renderer.prototype = {
-  render: function() {
-    this.renderer.render(this.contentText);
-  }
-};
-
 // original: middleware.swigFilter
 // original: middleware.swigFilter
 Crowi.userPicture = function (user) {
 Crowi.userPicture = function (user) {
   if (!user) {
   if (!user) {
@@ -256,23 +171,6 @@ Crowi.modifyScrollTop = function() {
 }
 }
 
 
 
 
-//CrowiSearcher = function(path, $el) {
-//  this.$el = $el;
-//  this.path = path;
-//  this.searchResult = {};
-//};
-//CrowiSearcher.prototype.querySearch = function(keyword, option) {
-//};
-//CrowiSearcher.prototype.search = function(keyword) {
-//  var option = {};
-//  this.querySearch(keyword, option);
-//  this.$el.html(this.render());
-//};
-//CrowiSearcher.prototype.render = function() {
-//  return $('<div>');
-//};
-
-
 $(function() {
 $(function() {
   var pageId = $('#content-main').data('page-id');
   var pageId = $('#content-main').data('page-id');
   var revisionId = $('#content-main').data('page-revision-id');
   var revisionId = $('#content-main').data('page-revision-id');
@@ -433,8 +331,10 @@ $(function() {
     var contentId = '#' + id + ' > script';
     var contentId = '#' + id + ' > script';
     var revisionBody = '#' + id + ' .revision-body';
     var revisionBody = '#' + id + ' .revision-body';
     var revisionPath = '#' + id + ' .revision-path';
     var revisionPath = '#' + id + ' .revision-path';
-    var renderer = new Crowi.renderer($(contentId).html(), $(revisionBody));
-    renderer.render();
+
+    var markdown = Crowi.unescape($(contentId).html());
+    var parsedHTML = crowiRenderer.render(markdown);
+    $(revisionBody).html(parsedHTML);
   });
   });
 
 
   // login
   // login
@@ -514,8 +414,10 @@ $(function() {
     // if page exists
     // if page exists
     var $rawTextOriginal = $('#raw-text-original');
     var $rawTextOriginal = $('#raw-text-original');
     if ($rawTextOriginal.length > 0) {
     if ($rawTextOriginal.length > 0) {
-      var renderer = new Crowi.renderer($('#raw-text-original').html());
-      renderer.render();
+      var markdown = Crowi.unescape($('#raw-text-original').html());
+      var parsedHTML = crowiRenderer.render(markdown);
+      $('#revision-body-content').html(parsedHTML);
+
       Crowi.correctHeaders('#revision-body-content');
       Crowi.correctHeaders('#revision-body-content');
       Crowi.revisionToc('#revision-body-content', '#revision-toc');
       Crowi.revisionToc('#revision-body-content', '#revision-toc');
     }
     }

+ 45 - 0
resource/js/util/Crowi.js

@@ -0,0 +1,45 @@
+/**
+ * Crowi context class for client
+ */
+
+import axios from 'axios'
+
+export default class Crowi {
+  constructor(context, window) {
+    this.context = context;
+
+    this.location = window.location || {};
+    this.document = window.document || {};
+
+    this.apiGet = this.apiGet.bind(this);
+    this.apiPost = this.apiPost.bind(this);
+    this.apiRequest = this.apiRequest.bind(this);
+  }
+
+  apiGet(path, params) {
+    return this.apiRequest('get', path, params);
+  }
+
+  apiPost(path, params) {
+    return this.apiRequest('post', path, params);
+  }
+
+  apiRequest(method, path, params) {
+    return new Promise((resolve, reject) => {
+      axios[method](`/_api${path}`, {params})
+      .then(res => {
+        if (res.data.ok) {
+          resolve(res.data);
+        } else {
+          // FIXME?
+          throw new Error(res.data);
+        }
+      }).catch(res => {
+          // FIXME?
+        throw new Error(res);
+      });
+    });
+  }
+
+}
+

+ 86 - 0
resource/js/util/CrowiRenderer.js

@@ -0,0 +1,86 @@
+import marked from 'marked';
+import hljs from 'highlight.js';
+
+import MarkdownFixer from './PreProcessor/MarkdownFixer';
+import Linker from './PreProcessor/Linker';
+import ImageExpander from './PreProcessor/ImageExpander';
+
+export default class CrowiRenderer {
+
+  constructor(plugins) {
+    this.preProcessors = [
+      new MarkdownFixer(),
+      new Linker(),
+      new ImageExpander(),
+    ];
+
+    this.postProcessors = [
+    ];
+  }
+
+  preProcess(markdown) {
+    for (let i = 0; i < this.preProcessors.length; i++) {
+      if (!this.preProcessors[i].process) {
+        continue;
+      }
+      markdown = this.preProcessors[i].process(markdown);
+    }
+    return markdown;
+  }
+
+  postProcess(html) {
+    for (let i = 0; i < this.postProcessors.length; i++) {
+      if (!this.postProcessors[i].process) {
+        continue;
+      }
+      html = this.postProcessors[i].process(html);
+    }
+    return html;
+  }
+
+  parseMarkdown(markdown) {
+    let parsed = '';
+
+    try {
+      // TODO
+      marked.setOptions({
+        gfm: true,
+        highlight: function (code, lang) {
+          let result, hl;
+          if (lang) {
+            try {
+              hl = hljs.highlight(lang, code);
+              result = hl.value;
+            } catch (e) {
+              result = code;
+            }
+          } else {
+            result = code;
+          }
+          return result;
+        },
+        tables: true,
+        breaks: true,
+        pedantic: false,
+        sanitize: false,
+        smartLists: true,
+        smartypants: false,
+        langPrefix: 'lang-'
+      });
+
+      parsed = marked(markdown);
+    } catch (e) { console.log(e, e.stack); }
+
+    return parsed;
+  }
+
+  render(markdown) {
+    let html = '';
+
+    markdown = this.preProcess(markdown);
+    html = this.parseMarkdown(markdown);
+    html = this.postProcess(html);
+
+    return html;
+  }
+}

+ 9 - 0
resource/js/util/PreProcessor/ImageExpander.js

@@ -0,0 +1,9 @@
+
+export default class ImageExpander {
+
+  process(markdown) {
+
+    return markdown
+      .replace(/\s(https?:\/\/[\S]+\.(jpg|jpeg|gif|png))/g, ' <a href="$1"><img src="$1" class="auto-expanded-image"></a>');
+  }
+}

+ 10 - 0
resource/js/util/PreProcessor/Linker.js

@@ -0,0 +1,10 @@
+
+export default class Linker {
+  process(markdown) {
+
+    return markdown
+      //.replace(/\s(https?:\/\/[\S]+)/g, ' <a href="$1">$1</a>') // リンク
+      .replace(/\s<((\/[^>]+?){2,})>/g, ' <a href="$1">$1</a>') // ページ間リンク: <> でかこまれてて / から始まり、 / が2個以上
+      ;
+  }
+}

+ 10 - 0
resource/js/util/PreProcessor/MarkdownFixer.js

@@ -0,0 +1,10 @@
+
+export default class MarkdownFixer {
+  process(markdown) {
+    var x = markdown
+      .replace(/^(#{1,})([^\s]+)?(.*)$/gm, '$1 $2$3') // spacer for section
+      .replace(/>[\s]*\n>[\s]*\n/g, '> <br>\n> \n');
+
+    return x;
+  }
+}