ソースを参照

Added syntax highlight feature and fix double-escaped problem

Sotaro KARASAWA 11 年 前
コミット
39961d2c23

+ 12 - 3
Gruntfile.js

@@ -37,7 +37,7 @@ module.exports = function(grunt) {
           ]
         },
         files: {
-          '<%= dirs.cssDest %>/<%= pkg.name %>.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
+          '<%= dirs.cssDest %>/<%= pkg.name %>-main.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
           '<%= dirs.cssDest %>/<%= pkg.name %>-reveal.css': '<%= dirs.css %>/<%= pkg.name %>-reveal.scss'
         }
       },
@@ -51,7 +51,7 @@ module.exports = function(grunt) {
           ]
         },
         files: {
-          '<%= dirs.cssDest %>/<%= pkg.name %>.min.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
+          '<%= dirs.cssDest %>/<%= pkg.name %>-main.min.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
           '<%= dirs.cssDest %>/<%= pkg.name %>-reveal.min.css': '<%= dirs.css %>/<%= pkg.name %>-reveal.scss'
         }
       }
@@ -59,12 +59,21 @@ module.exports = function(grunt) {
     concat: {
       dist: {
         files: {
+          '<%= dirs.cssDest %>/<%= pkg.name %>.css': [
+            'bower_components/highlightjs/styles/default.css',
+            '<%= dirs.cssDest %>/<%= pkg.name %>-main.css',
+          ],
+          '<%= dirs.cssDest %>/<%= pkg.name %>.min.css': [
+            'bower_components/highlightjs/styles/default.css', // TODO minimize
+            '<%= dirs.cssDest %>/<%= pkg.name %>-main.min.css',
+          ],
           '<%= dirs.jsDest %>/<%= pkg.name %>.js': [
             'bower_components/jquery/dist/jquery.js',
             'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js',
             'node_modules/socket.io-client/dist/socket.io.js',
             'bower_components/marked/lib/marked.js',
             'bower_components/jquery.cookie/jquery.cookie.js',
+            'bower_components/highlightjs/highlight.pack.js',
             'resource/js/crowi.js'
           ],
           '<%= dirs.jsDest %>/<%= pkg.name %>-reveal.js': [
@@ -116,6 +125,6 @@ module.exports = function(grunt) {
 
   // grunt watch dev
   grunt.registerTask('default', ['sass', 'concat', 'uglify']);
-  grunt.registerTask('dev', ['jshint', 'sass:dev', 'concat']);
+  grunt.registerTask('dev', ['sass:dev', 'concat', 'jshint']);
 
 };

+ 2 - 1
bower.json

@@ -24,6 +24,7 @@
     "jquery.cookie": "~1.4.1",
     "marked": "~0.3.3",
     "reveal.js": "~3.0.0",
-    "jquery": "~2.1.3"
+    "jquery": "~2.1.3",
+    "highlightjs": "~8.4.0"
   }
 }

+ 7 - 4
package.json

@@ -3,7 +3,10 @@
   "version": "1.1.1",
   "description": "The simple & powerful Wiki",
   "tags": [
-    "wiki", "communication", "documentation", "collaboration"
+    "wiki",
+    "communication",
+    "documentation",
+    "collaboration"
   ],
   "author": "Sotaro KARASAWA <sotaro.k@gmail.com>",
   "contributors": [
@@ -28,6 +31,7 @@
     "debug": "^1.0.3",
     "express": "=3.4.4",
     "express-form": "~0.10.1",
+    "express-session": "~1.9.3",
     "facebook-node-sdk": "=0.1.10",
     "googleapis": "=0.4.7",
     "grunt": "~0.4.1",
@@ -41,12 +45,11 @@
     "mongoose-paginate": "~3.1.0",
     "nodemailer": "~1.2.2",
     "nodemailer-ses-transport": "~1.1.0",
+    "redis": "~0.12.1",
     "socket.io": "~0.9.16",
     "socket.io-client": "~0.9.16",
     "swig": "=1.3.2",
-    "time": "=0.10.0",
-    "redis": "~0.12.1",
-    "express-session": "~1.9.3"
+    "time": "=0.10.0"
   },
   "devDependencies": {},
   "license": [

+ 18 - 0
resource/css/_layout.scss

@@ -361,6 +361,24 @@
       margin-top: 30px;
     }
   }
+
+  .content-main .timeline-body { // {{{ timeline
+     .revision-path {
+       margin-top: 1.6em;
+       margin-bottom: 0;
+       border: solid 2px #ddd;
+       border-bottom: none;
+       padding: 16px;
+       background: #ddd;
+     }
+     .revision-body {
+       font-size: 14px;
+       border: solid 2px #ddd;
+       padding: 16px;
+       background: #fdfdfd;
+     }
+  } // }}}
+
   // on-edit
   .content-main.on-edit {
     position: fixed;

+ 1 - 0
resource/css/_wiki.scss

@@ -111,6 +111,7 @@ div.body {
     margin: 5px;
     box-shadow: 0 0 12px 0px #999;
     border: solid 1px #999;
+    max-width: 100%;
   }
 
   ul, ol {

+ 2 - 0
resource/css/crowi.scss

@@ -6,6 +6,8 @@
 
 @import 'font-awesome';
 
+@import 'mixins';
+
 // crowi component
 @import 'mixins';
 @import 'layout';

+ 29 - 49
resource/js/crowi.js

@@ -95,60 +95,41 @@ Crowi.revisionToc = function(contentId, tocId) {
 
 
 Crowi.escape = function(s) {
-  s = s.replace(/&/g, '&amp;');
-  s = s.replace(/</g, '&lt;');
-  s = s.replace(/>/g, '&gt;');
-  s = s.replace(/"/g, '&quot;');
+  s = s.replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    ;
   return s;
 };
 Crowi.unescape = function(s) {
-  s = s.replace(/&nbsp;/g, ' ');
-  s = s.replace(/&amp;/g, '&');
-  s = s.replace(/&lt;(?!\?)/g, '<');
-  s = s.replace(/([^\?])&gt;/g, '$1>');
-  s = s.replace(/&quot;/g, '"');
+  s = s.replace(/&nbsp;/g, ' ')
+    .replace(/&amp;/g, '&')
+    .replace(/&lt;/g, '<')
+    .replace(/&gt;/g, '>')
+    .replace(/&quot;/g, '"')
+    ;
   return s;
 };
 
-Crowi.getRendererType = function(format) {
-  if (!Crowi.rendererType[format]) {
-    throw new Error('no such renderer');
-  }
-
-  return new Crowi.rendererType[format]();
+Crowi.getRendererType = function() {
+  return new Crowi.rendererType.markdown();
 };
 
 Crowi.rendererType = {};
-Crowi.rendererType.text = function(){};
 Crowi.rendererType.markdown = function(){};
-Crowi.rendererType.text.prototype = {
-  render: function($content) {
-    var $revisionHtml = this.$revisionBody.children('pre');
-    this.$content = $content;
-    $revisionHtml.html(this.$content.html());
-    this.expandImage();
-    this.link();
-  },
-  link: function () {
-    this.$revisionBody.html(this.$revisionBody.html().replace(/\s(https?:\/\/[\S]+)/g, ' <a href="$1">$1</a>'));
-  },
-  expandImage: function () {
-    this.$revisionBody.html(this.$revisionBody.html().replace(/\s(https?:\/\/[\S]+\.(jpg|jpeg|gif|png))/g, ' <img src="$1" class="auto-expanded-image" />'));
-  }
-};
 Crowi.rendererType.markdown.prototype = {
-  render: function($content) {
+  render: function(contentText) {
     marked.setOptions({
       gfm: true,
       highlight: function (code, lang, callback) {
-        callback(null, code);
-        // あとで
-        //highlight: function (code, lang, callback) {
-        //  pygmentize({ lang: lang, format: 'html' }, code, function (err, result) {
-        //    if (err) return callback(err);
-        //    callback(null, result.toString());
-        //  });
-        //},
+        var result;
+        if (lang) {
+          result = hljs.highlight(lang, code);
+        } else {
+          result = hljs.highlightAuto(code);
+        }
+        callback(null, result.value);
       },
       tables: true,
       breaks: true,
@@ -159,7 +140,7 @@ Crowi.rendererType.markdown.prototype = {
       langPrefix: 'lang-'
     });
 
-    var contentHtml = Crowi.unescape(Crowi.escape($content.val()) || $content.html());
+    var contentHtml = Crowi.unescape(contentText);
     contentHtml = this.expandImage(contentHtml);
     contentHtml = this.link(contentHtml);
 
@@ -170,7 +151,6 @@ Crowi.rendererType.markdown.prototype = {
         throw err;
       }
       $body.html(content);
-      //console.log(content);
     });
   },
   link: function (content) {
@@ -184,18 +164,18 @@ Crowi.rendererType.markdown.prototype = {
   }
 };
 
-Crowi.renderer = function (contentId, format, revisionBody) {
-  var $revisionBody = revisionBody || '#revision-body-content';
+Crowi.renderer = function (contentText, revisionBody) {
+  var $revisionBody = revisionBody || $('#revision-body-content');
 
-  this.$content = $(contentId);
-  this.$revisionBody = $($revisionBody);
-  this.format = format;
-  this.renderer = Crowi.getRendererType(format);
+  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.$content);
+    this.renderer.render(this.contentText);
   }
 };
 

+ 2 - 2
views/_form.html

@@ -37,12 +37,12 @@
     var watchTimer = setInterval(function() {
       var content = $('#form-body').val();
       if (prevContent != content) {
-        var renderer = new Crowi.renderer('#form-body', $('#form-format').val(), '#preview-body');
+        var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
         renderer.render();
 
         prevContent = content;
       }
-    }, 1000);
+    }, 500);
 
     // tabs handle
     $('textarea#form-body').on('keydown', function(event){

+ 3 - 7
views/page.html

@@ -42,7 +42,6 @@
       </a>
     </li>
 
-    <li><a href="#raw-text" data-toggle="tab"><i class="fa fa-font"></i> テキスト表示</a></li>
     <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> 編集</a></li>
 
     <li class="dropdown pull-right">
@@ -79,6 +78,7 @@
     </div>
   </div>
 #}
+    <script type="text/template" id="raw-text-original">{{ revision.body }}</script>
 
     {# formatted text #}
     <div class="tab-pane {% if not req.body.pageForm %}active{% endif %}" id="revision-body">
@@ -86,16 +86,12 @@
         <a data-toggle="collapse" data-parent="#revision-toc" href="#revision-toc-content" class="revision-toc-head collapsed">目次</a>
 
       </div>
-    {% if revision.format == 'text' %}
-      <div class="wiki {{ revision.format }}" id="revision-body-content"><pre class="" id=""></pre></div>
-    {% else  %}
       <div class="wiki {{ revision.format }}" id="revision-body-content"></div>
-    {% endif  %}
     </div>
 
     {# raw text #}
     <div class="tab-pane" id="raw-text">
-      <pre id="raw-text-original">{{ revision.body }}</pre>
+      <pre id="">{{ revision.body }}</pre>
     </div>
 
     {# edit form #}
@@ -105,7 +101,7 @@
   </div>
   <script type="text/javascript">
     $(function(){
-        var renderer = new Crowi.renderer('#raw-text-original', '{{ revision.format }}');
+        var renderer = new Crowi.renderer($('#raw-text-original').html());
         renderer.render();
         Crowi.correctHeaders('#revision-body-content');
         Crowi.revisionToc('#revision-body-content', '#revision-toc');

+ 5 - 6
views/page_list.html

@@ -43,9 +43,10 @@
       {% for page in pages %}
       <div class="timeline-body" id="id-{{ page.id }}">
         <h3 class="revision-path"><a href="{{ page.path }}">{{ page.path }}</a></h3>
-        <div class="revision-body" data-format="{{ page.revision.format }}"><pre></pre></div>
-        <pre class="hide raw-text-original">{{ page.revision.body }}</pre>
+        <div class="revision-body wiki"></div>
+        <script type="text/template">{{ page.revision.body }}</script>
       </div>
+      <hr>
       {% endfor %}
     </div>
   </div>
@@ -55,12 +56,10 @@
         $('#view-timeline .timeline-body').each(function()
         {
           var id = $(this).attr('id');
-          //var format = $(this).children('.body').data('format');
-          var format = 'text';
-          var contentId = '#' + id + ' .raw-text-original';
+          var contentId = '#' + id + ' > script';
           var revisionBody = '#' + id + ' .revision-body';
           var revisionPath = '#' + id + ' .revision-path';
-          var renderer = new Crowi.renderer(contentId, format, revisionBody);
+          var renderer = new Crowi.renderer($(contentId).html(), $(revisionBody));
           renderer.render();
         });
         //$('.tooltip .tabs').tabs();