2
0
Эх сурвалжийг харах

Merge remote-tracking branch 'origin/improve-textarea' into v1.3.0-wip

Sotaro KARASAWA 10 жил өмнө
parent
commit
14c61721c8
4 өөрчлөгдсөн 309 нэмэгдсэн , 121 устгасан
  1. 2 1
      bower.json
  2. 14 1
      gulpfile.js
  3. 1 119
      lib/views/_form.html
  4. 292 0
      resource/js/crowi-form.js

+ 2 - 1
bower.json

@@ -26,6 +26,7 @@
     "reveal.js": "~3.0.0",
     "jquery": "~2.1.3",
     "highlightjs": "~8.4.0",
-    "inline-attachment": "~2.0.1"
+    "inline-attachment": "~2.0.1",
+    "jquery-selection": "~1.0.1"
   }
 }

+ 14 - 1
gulpfile.js

@@ -41,6 +41,7 @@ var js = {
     'node_modules/socket.io-client/socket.io.js',
     'bower_components/marked/lib/marked.js',
     'bower_components/jquery.cookie/jquery.cookie.js',
+    'bower_components/jquery-selection/src/jquery.selection.js',
     'bower_components/highlightjs/highlight.pack.js',
     'resource/js/crowi.js'
   ],
@@ -51,6 +52,10 @@ var js = {
     'bower_components/reveal.js/js/reveal.js'
   ],
   revealDist: dirs.jsDist + '/crowi-reveal.js',
+  formSrc: [
+    'resource/js/crowi-form.js'
+  ],
+  formDist: dirs.jsDist + '/crowi-form.js',
   clientWatch: ['resource/js/**/*.js'],
   watch: ['test/**/*.test.js', 'app.js', 'lib/**/*.js'],
   lint: ['app.js', 'lib/**/*.js'],
@@ -68,6 +73,10 @@ gulp.task('js:concat', function() {
     .pipe(concat('crowi-reveal.js'))
     .pipe(gulp.dest(dirs.jsDist));
 
+  gulp.src(js.formSrc)
+    .pipe(concat('crowi-form.js'))
+    .pipe(gulp.dest(dirs.jsDist));
+
   return gulp.src(js.src)
     .pipe(concat('crowi.js'))
     .pipe(gulp.dest(dirs.jsDist));
@@ -79,6 +88,11 @@ gulp.task('js:min', ['js:concat'], function() {
     .pipe(rename({suffix: '.min'}))
     .pipe(gulp.dest(dirs.jsDist));
 
+  gulp.src(js.formDist)
+    .pipe(uglify())
+    .pipe(rename({suffix: '.min'}))
+    .pipe(gulp.dest(dirs.jsDist));
+
   return gulp.src(js.dist)
     .pipe(uglify())
     .pipe(rename({suffix: '.min'}))
@@ -140,4 +154,3 @@ gulp.task('watch', function() {
 gulp.task('css', ['css:sass', 'css:concat',]);
 gulp.task('default', ['css:min', 'js:min',]);
 gulp.task('dev', ['css:concat', 'js:concat','jshint', 'test']);
-

+ 1 - 119
lib/views/_form.html

@@ -36,123 +36,5 @@
   </div>
   <div class="file-module hidden">
   </div>
-  <script type="text/javascript">
-  $(function() {
-    // preview watch
-    var originalContent = $('#form-body').val();
-    var prevContent = "";
-    var watchTimer = setInterval(function() {
-      var content = $('#form-body').val();
-      if (prevContent != content) {
-        var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
-        renderer.render();
-
-        prevContent = content;
-      }
-    }, 500);
-
-    // tabs handle
-    $('textarea#form-body').on('keydown', function(event){
-      var self  = $(this)
-          start = this.selectionStart,
-          end   = this.selectionEnd
-          val   = self.val();
-
-      if (event.keyCode === 9) {
-        // tab
-        event.preventDefault();
-        self.val(
-          val.substring(0, start)
-          + '    '
-          + val.substring(end, val.length)
-        );
-        this.selectionStart = start + 4;
-        this.selectionEnd   = start + 4;
-      } else if (event.keyCode === 27) {
-        // escape
-        self.blur();
-      }
-    });
-
-    var unbindInlineAttachment = function($form) {
-      $form.unbind('.inlineattach');
-    };
-    var bindInlineAttachment = function($form, attachmentOption) {
-      var $this = $form;
-      var editor = createEditorInstance($form);
-      var inlineattach = new inlineAttachment(attachmentOption, editor);
-      $form.bind({
-        'paste.inlineattach': function(e) {
-          inlineattach.onPaste(e.originalEvent);
-        },
-        'drop.inlineattach': function(e) {
-          e.stopPropagation();
-          e.preventDefault();
-          inlineattach.onDrop(e.originalEvent);
-        },
-        'dragenter.inlineattach dragover.inlineattach': function(e) {
-          e.stopPropagation();
-          e.preventDefault();
-        }
-      });
-    };
-    var createEditorInstance = function($form) {
-      var $this = $form;
-
-      return {
-        getValue: function() {
-          return $this.val();
-        },
-        insertValue: function(val) {
-          inlineAttachment.util.insertTextAtCursor($this[0], val);
-        },
-        setValue: function(val) {
-          $this.val(val);
-        }
-      };
-    };
-
-    var $inputForm = $('textarea#form-body');
-    if ($inputForm.length > 0) {
-      var pageId = $('#content-main').data('page-id') || 0;
-      var attachmentOption = {
-        uploadUrl: '/_api/attachment/page/' + pageId,
-        extraParams: {
-          path: location.pathname
-        },
-        progressText: '(Uploading file...)',
-        urlText: "\n![file]({filename})\n"
-      };
-
-      attachmentOption.onFileUploadResponse = function(res) {
-        var result = JSON.parse(res.response);
-
-        if (result.status && result.pageCreated) {
-          var page = result.page,
-            pageId = page._id;
-
-          $('#content-main').data('page-id', page._id);
-          $('#page-form [name="pageForm[currentRevision]"]').val(page.revision)
-
-          unbindInlineAttachment($inputForm);
-
-          attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
-          bindInlineAttachment($inputForm, attachmentOption);
-        }
-        return true;
-      };
-
-      bindInlineAttachment($inputForm, attachmentOption);
-    }
-
-    $('textarea#form-body').on('dragenter dragover', function() {
-        $(this).addClass('dragover');
-      });
-    $('textarea#form-body').on('drop dragleave dragend', function() {
-        $(this).removeClass('dragover');
-      });
-  });
-
-
-  </script>
+  <script src="/js/crowi-form{% if env  == 'production' %}.min{% endif %}.js"></script>
 </div>

+ 292 - 0
resource/js/crowi-form.js

@@ -0,0 +1,292 @@
+$(function() {
+  // preview watch
+  var originalContent = $('#form-body').val();
+  var prevContent = "";
+  var watchTimer = setInterval(function() {
+    var content = $('#form-body').val();
+    if (prevContent != content) {
+      var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
+      renderer.render();
+
+      prevContent = content;
+    }
+  }, 500);
+
+  var getCurrentLine = function(event) {
+    var $target = $(event.target);
+
+    var text = $target.val();
+    var pos = $target.selection('getPos');
+    if (text === null || pos.start !== pos.end) {
+      return null;
+    }
+
+    var startPos = text.lastIndexOf("\n", pos.start - 1) + 1;
+    var endPos = text.indexOf("\n", pos.start);
+    if (endPos === -1) {
+      endPos = text.length;
+    }
+
+    return {
+      text: text.slice(startPos, endPos),
+      start: startPos,
+      end: endPos,
+      caret: pos.start,
+      endOfLine: !$.trim(text.slice(pos.start, endPos))
+    };
+  };
+
+  var getPrevLine = function(event) {
+    var $target = $(event.target);
+    var currentLine = getCurrentLine(event);
+    var text = $target.val().slice(0, currentLine.start);
+    var startPos = text.lastIndexOf("\n", currentLine.start - 2) + 1;
+    var endPos = currentLine.start;
+
+    return {
+      text: text.slice(startPos, endPos),
+      start: startPos,
+      end: endPos
+    };
+  };
+
+  var handleTabKey = function(event) {
+    event.preventDefault();
+
+    var $target = $(event.target);
+    var currentLine = getCurrentLine(event);
+    var text = $target.val();
+    var pos = $target.selection('getPos');
+
+    if (currentLine) {
+      $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
+    }
+
+    if (event.shiftKey === true) {
+      if (currentLine && currentLine.text.charAt(0) === '|') {
+        // prev cell in table
+        var newPos = text.lastIndexOf('|', pos.start - 1);
+        if (newPos > 0) {
+          $target.selection('setPos', {start: newPos - 1, end: newPos - 1});
+        }
+      } else {
+        // re indent
+        var reindentedText = $target.selection().replace(/^ {1,4}/gm, '');
+        var reindentedCount = $target.selection().length - reindentedText.length;
+        $target.selection('replace', {text: reindentedText, mode: 'before'});
+        if (currentLine) {
+          $target.selection('setPos', {start: pos.start - reindentedCount, end: pos.start - reindentedCount});
+        }
+      }
+    } else {
+      if (currentLine && currentLine.text.charAt(0) === '|') {
+        // next cell in table
+        var newPos = text.indexOf('|', pos.start + 1);
+        if (newPos < 0 || newPos === text.lastIndexOf('|', currentLine.end - 1)) {
+          $target.selection('setPos', {start: currentLine.end, end: currentLine.end});
+        } else {
+          $target.selection('setPos', {start: newPos + 2, end: newPos + 2});
+        }
+      } else {
+        // indent
+        $target.selection('replace', {
+          text: '    ' + $target.selection().split("\n").join("\n    "),
+          mode: 'before'
+        });
+        if (currentLine) {
+          $target.selection('setPos', {start: pos.start + 4, end: pos.start + 4});
+        }
+      }
+    }
+
+    $target.trigger('input');
+  };
+
+  var handleEnterKey = function(event) {
+    if (event.metaKey || event.ctrlKey || event.shiftKey) {
+      return;
+    }
+
+    var currentLine = getCurrentLine(event);
+    if (!currentLine || currentLine.start === currentLine.caret) {
+      return;
+    }
+
+    var $target = $(event.target);
+    var match = currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)\s*\S/);
+    if (match) {
+      // smart indent with list
+      if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] ))\s*$/)) {
+        // empty task list
+        $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
+        return;
+      }
+      event.preventDefault();
+      var listMark = match[1].replace(/\[x\]/, '[ ]');
+      var listMarkMatch = listMark.match(/^(\s*)(\d+)\./);
+      if (listMarkMatch) {
+        var indent = listMarkMatch[1];
+        var num = parseInt(listMarkMatch[2]);
+        if (num !== 1) {
+          listMark = listMark.return(/\s*\d+/, indent + (num +1));
+        }
+      }
+      $target.selection('insert', {text: "\n" + listMark, mode: 'before'});
+    } else if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) )/)) {
+      // remove list
+      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
+    } else if (currentLine.text.match(/^.*\|\s*$/)) {
+      // new row for table
+      if (currentLine.text.match(/^[\|\s]+$/)) {
+        $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
+        return;
+      }
+      if (!currentLine.endOfLine) {
+        return;
+      }
+      event.preventDefault();
+      var row = [];
+      var cellbarMatch = currentLine.text.match(/\|/g);
+      for (var i = 0; i < cellbarMatch.length; i++) {
+        row.push('|');
+      }
+      var prevLine = getPrevLine(event);
+      if (!prevLine || (!currentLine.text.match(/---/) && !prevLine.text.match(/\|/g))) {
+        $target.selection('insert', {text: "\n" + row.join(' --- ') + "\n" + row.join('  '), mode: 'before'});
+        $target.selection('setPos', {start: currentLine.caret + 6 * row.length - 1, end: currentLine.caret + 6 * row.length - 1});
+      } else {
+        $target.selection('insert', {text: "\n" + row.join('  '), mode: 'before'});
+        $target.selection('setPos', {start: currentLine.caret + 3, end: currentLine.caret + 3});
+      }
+    }
+
+    $target.trigger('input');
+  };
+
+  var handleEscapeKey = function(event) {
+    event.preventDefault();
+    var $target = $(event.target);
+    $target.blur();
+  };
+
+  var handleSpaceKey = function(event) {
+    // keybind: alt + shift + space
+    if (!(event.shiftKey && event.altKey)) {
+      return;
+    }
+    var currentLine = getCurrentLine(event);
+    if (!currentLine) {
+      return;
+    }
+
+    var $target = $(event.target);
+    var match = currentLine.text.match(/^(\s*)(-|\+|\*|\d+\.) (?:\[(x| )\] )(.*)/);
+    if (match) {
+      event.preventDefault();
+      var checkMark = (match[3] == ' ') ? 'x' : ' ';
+      var replaceTo = match[1] + match[2] + ' [' + checkMark + '] ' + match[4];
+      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
+      $target.selection('replace', {text: replaceTo, mode: 'keep'});
+      $target.selection('setPos', {start: currentLine.caret, end: currentLine.caret});
+      $target.trigger('input');
+    }
+  };
+
+  // markdown helper inspired by 'esarea'.
+  // see: https://github.com/fukayatsu/esarea
+  $('textarea#form-body').on('keydown', function(event) {
+    switch (event.which || event.keyCode) {
+      case 9:
+        handleTabKey(event);
+        break;
+      case 13:
+        handleEnterKey(event);
+        break;
+      case 27:
+        handleEscapeKey(event);
+        break;
+      case 32:
+        handleSpaceKey(event);
+        break;
+      default:
+    }
+  });
+
+  var unbindInlineAttachment = function($form) {
+    $form.unbind('.inlineattach');
+  };
+  var bindInlineAttachment = function($form, attachmentOption) {
+    var $this = $form;
+    var editor = createEditorInstance($form);
+    var inlineattach = new inlineAttachment(attachmentOption, editor);
+    $form.bind({
+      'paste.inlineattach': function(e) {
+        inlineattach.onPaste(e.originalEvent);
+      },
+      'drop.inlineattach': function(e) {
+        e.stopPropagation();
+        e.preventDefault();
+        inlineattach.onDrop(e.originalEvent);
+      },
+      'dragenter.inlineattach dragover.inlineattach': function(e) {
+        e.stopPropagation();
+        e.preventDefault();
+      }
+    });
+  };
+  var createEditorInstance = function($form) {
+    var $this = $form;
+
+    return {
+      getValue: function() {
+        return $this.val();
+      },
+      insertValue: function(val) {
+        inlineAttachment.util.insertTextAtCursor($this[0], val);
+      },
+      setValue: function(val) {
+        $this.val(val);
+      }
+    };
+  };
+
+  var $inputForm = $('textarea#form-body');
+  if ($inputForm.length > 0) {
+    var pageId = $('#content-main').data('page-id') || 0;
+    var attachmentOption = {
+      uploadUrl: '/_api/attachment/page/' + pageId,
+      extraParams: {
+        path: location.pathname
+      },
+      progressText: '(Uploading file...)',
+      urlText: "\n![file]({filename})\n"
+    };
+
+    attachmentOption.onFileUploadResponse = function(res) {
+      var result = JSON.parse(res.response);
+
+      if (result.status && result.pageCreated) {
+        var page = result.page,
+            pageId = page._id;
+
+        $('#content-main').data('page-id', page._id);
+        $('#page-form [name="pageForm[currentRevision]"]').val(page.revision)
+
+        unbindInlineAttachment($inputForm);
+
+        attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
+        bindInlineAttachment($inputForm, attachmentOption);
+      }
+      return true;
+    };
+
+    bindInlineAttachment($inputForm, attachmentOption);
+  }
+
+  $('textarea#form-body').on('dragenter dragover', function() {
+    $(this).addClass('dragover');
+  });
+  $('textarea#form-body').on('drop dragleave dragend', function() {
+    $(this).removeClass('dragover');
+  });
+});