]> BookStack Code Mirror - bookstack/commitdiff
Refactored Angular code to instead use VueJS, left with permissions, testing and...
authorAbijeet <redacted>
Sun, 20 Aug 2017 14:51:32 +0000 (20:21 +0530)
committerAbijeet <redacted>
Sun, 20 Aug 2017 14:51:32 +0000 (20:21 +0530)
resources/assets/js/vues/components/comments/comment-reply.js [new file with mode: 0644]
resources/assets/js/vues/components/comments/comment.js [new file with mode: 0644]
resources/assets/js/vues/page-comments.js [new file with mode: 0644]

diff --git a/resources/assets/js/vues/components/comments/comment-reply.js b/resources/assets/js/vues/components/comments/comment-reply.js
new file mode 100644 (file)
index 0000000..83cbdf4
--- /dev/null
@@ -0,0 +1,125 @@
+const MarkdownIt = require("markdown-it");
+const md = new MarkdownIt({html: true});
+
+var template = `
+<div class="comment-editor" v-cloak>
+<form novalidate>
+    <textarea name="markdown" rows="3" v-model="comment.text" :placeholder="trans('entities.comment_placeholder')"></textarea>
+    <input type="hidden" v-model="comment.pageId" name="comment.pageId" :value="pageId">
+    <button type="button" v-if="isReply || isEdit" class="button muted" v-on:click="closeBox">{{ trans('entities.comment_cancel') }}</button>
+    <button type="submit" class="button pos" v-on:click.prevent="saveComment">{{ trans('entities.comment_save') }}</button>
+</form>
+</div>
+`;
+
+const props = {
+  pageId: {},
+  commentObj: {},
+  isReply: {
+    default: false,
+    type: Boolean
+  }, isEdit: {
+    default: false,
+    type: Boolean
+  }};
+
+function data () {
+  var comment = null;
+  // initialize comment if not passed.
+  if (!this.commentObj || this.isReply) {
+    comment = {
+      text: ''
+    };
+
+    if (this.isReply) {
+      comment.page_id = this.commentObj.page_id;
+      comment.id = this.commentObj.id;
+    }
+  } else {
+    comment = this.commentObj;
+  }
+
+  return {
+    trans: trans,
+    parentId: null,
+    comment: comment
+  };
+}
+
+const methods = {
+  saveComment: function (event) {
+    let pageId = this.comment.page_id || this.pageId;
+    let commentText = this.comment.text;
+    if (!commentText) {
+        return this.$emit('evt.empty-comment');
+    }
+    let commentHTML = md.render(commentText);
+    let serviceUrl = `/ajax/page/${pageId}/comment/`;
+    let httpMethod = 'post';
+    let reqObj = {
+        text: commentText,
+        html: commentHTML
+    };
+
+    if (this.isEdit === true) {
+        // this will be set when editing the comment.
+        serviceUrl = `/ajax/page/${pageId}/comment/${this.comment.id}`;
+        httpMethod = 'put';
+    } else if (this.isReply === true) {
+        // if its reply, get the parent comment id
+        reqObj.parent_id = this.comment.id;
+    }
+
+    $http[httpMethod](window.baseUrl(serviceUrl), reqObj).then(resp => {
+        if (!isCommentOpSuccess(resp)) {
+             return;
+        }
+        // hide the comments first, and then retrigger the refresh
+        if (this.isEdit) {
+            this.$emit('comment-edited', event, resp.data.comment);
+        } else {
+            this.comment.text = '';
+            this.$emit('comment-added', event);
+            if (this.isReply === true) {
+              this.$emit('comment-replied', event, resp.data.comment);
+            } else {
+              this.$parent.$emit('new-comment', event, resp.data.comment);
+            }
+            this.$emit('evt.comment-success', null, true);
+        }
+
+    }, checkError);
+  },
+  closeBox: function (event) {
+    this.$emit('editor-removed', event);
+  }
+};
+
+const computed = {};
+
+function isCommentOpSuccess(resp) {
+  if (resp && resp.data && resp.data.status === 'success') {
+      return true;
+  }
+  return false;
+}
+
+function checkError(msgKey) {
+  return function(response) {
+    let msg = null;
+    if (isCommentOpSuccess(response)) {
+        // all good
+        return;
+    } else if (response.data) {
+        msg = response.data.message;
+    } else {
+        msg = trans(msgKey);
+    }
+    if (msg) {
+        events.emit('success', msg);
+    }
+  }
+}
+
+module.exports = {name: 'comment-reply', template, data, props, methods, computed};
+
diff --git a/resources/assets/js/vues/components/comments/comment.js b/resources/assets/js/vues/components/comments/comment.js
new file mode 100644 (file)
index 0000000..4152ba6
--- /dev/null
@@ -0,0 +1,174 @@
+const commentReply = require('./comment-reply');
+
+const template = `
+<div class="comment-box">
+  <div class='page-comment' :id="commentId">
+  <div class="user-image">
+      <img :src="comment.created_by.avatar_url" alt="user avatar">
+  </div>
+  <div class="comment-container">
+      <div class="comment-header">
+          <a :href="comment.created_by.profile_url">{{comment.created_by.name}}</a>
+      </div>
+      <div v-html="comment.html" v-if="comment.active" class="comment-body" v-bind:class="{ 'comment-inactive' : !comment.active }">
+
+      </div>
+      <div v-if="!comment.active" class="comment-body comment-inactive">
+          {{ trans('entities.comment_deleted') }}
+      </div>
+      <div class="comment-actions">
+          <ul>
+              <li v-if="(level < 3 && canComment)">
+                <a href="#" comment="comment" v-on:click.prevent="replyComment">{{ trans('entities.comment_reply') }}</a>
+              </li>
+              <li v-if="canUpdate">
+                <a href="#" comment="comment" v-on:click.prevent="editComment">{{ trans('entities.comment_edit') }}</a>
+              </li>
+              <li v-if="canDelete">
+                <a href="#" comment="comment" v-on:click.prevent="deleteComment">{{ trans('entities.comment_delete') }}</a>
+              </li>
+              <li>{{ trans('entities.comment_create') }}
+                <a :title="comment.created.day_time_str" :href="commentHref">{{comment.created.diff}}</a>
+              </li>
+              <li v-if="comment.updated">
+                <span :title="comment.updated.day_time_str">{{trans('entities.comment_updated_text', { updateDiff: comment.updated.diff }) }}
+                      <a :href="comment.updated_by.profile_url">{{comment.updated_by.name}}</a>
+                </span>
+              </li>
+          </ul>
+      </div>
+      <div v-if="showEditor && level <= 3">
+        <comment-reply :page-id="comment.page_id" :comment-obj="comment"
+          v-on:editor-removed.stop.prevent="hideComment"
+          v-on:comment-replied.stop="commentReplied(...arguments)"
+          v-on:comment-edited.stop="commentEdited(...arguments)"
+          v-on:comment-added.stop="commentAdded"
+           :is-reply="isReply" :is-edit="isEdit">
+        </comment-reply>
+      </div>
+      <comment v-for="(comment, index) in comments" :initial-comment="comment"
+        :index="index" :level="nextLevel" :key="comment.id"
+        v-on:comment-added.stop="commentAdded"></comment>
+
+  </div>
+  </div>
+</div>
+`;
+
+const props = ['initialComment', 'index', 'level'];
+
+function data () {
+  return {
+    trans: trans,
+    commentHref: null,
+    comments: [],
+    showEditor: false,
+    comment: this.initialComment,
+    nextLevel: this.level + 1
+  };
+}
+
+const methods = {
+  deleteComment: function () {
+    var resp = window.confirm(trans('entities.comment_delete_confirm'));
+    if (!resp) {
+        return;
+    }
+    this.$http.delete(window.baseUrl(`/ajax/comment/${this.comment.id}`)).then(resp => {
+      if (!isCommentOpSuccess(resp)) {
+          return;
+      }
+      updateComment(this.comment, resp.data, true);
+    }, function (resp) {
+      if (isCommentOpSuccess(resp)) {
+          this.$events.emit('success', trans('entities.comment_deleted'));
+      } else {
+          this.$events.emit('error', trans('error.comment_delete'));
+      }
+    });
+  },
+  replyComment: function () {
+    this.toggleEditor(false);
+  },
+  editComment: function () {
+    this.toggleEditor(true);
+  },
+  hideComment: function () {
+    this.showEditor = false;
+  },
+  toggleEditor: function (isEdit) {
+    this.showEditor = false;
+    this.isEdit = isEdit;
+    this.isReply = !isEdit;
+    this.showEditor = true;
+  },
+  commentReplied: function (event, comment) {
+    this.comments.push(comment);
+    this.showEditor = false;
+  },
+  commentEdited: function (event, comment) {
+    this.comment = comment;
+    this.showEditor = false;
+  },
+  commentAdded: function (event, comment) {
+    // this is to handle non-parent child relationship
+    // we want to make it go up.
+    this.$emit('comment-added', event);
+  }
+};
+
+const computed = {
+  commentId: {
+    get: function () {
+      return `comment-${this.comment.page_id}-${this.comment.id}`;
+    },
+    set: function () {
+      this.commentHref = `#?cm=${this.commentId}`
+    }
+  },
+  canUpdate: function () {
+    return true;
+  },
+  canDelete: function () {
+    return true;
+  },
+  canComment: function () {
+    return true;
+  },
+  canUpdate: function () {
+    return true;
+  }
+};
+
+function mounted () {
+  if (this.comment.sub_comments && this.comment.sub_comments.length) {
+    // set this so that we can render the next set of sub comments.
+    this.comments = this.comment.sub_comments;
+  }
+}
+
+function isCommentOpSuccess(resp) {
+  if (resp && resp.data && resp.data.status === 'success') {
+      return true;
+  }
+  return false;
+}
+
+function updateComment(comment, resp, isDelete) {
+  comment.text = resp.comment.text;
+  comment.updated = resp.comment.updated;
+  comment.updated_by = resp.comment.updated_by;
+  comment.active = resp.comment.active;
+  if (isDelete && !resp.comment.active) {
+      comment.html = trans('entities.comment_deleted');
+  } else {
+      comment.html = resp.comment.html;
+  }
+}
+
+module.exports = {
+  name: 'comment',
+  template, data, props, methods, computed, mounted, components: {
+  commentReply
+}};
+
diff --git a/resources/assets/js/vues/page-comments.js b/resources/assets/js/vues/page-comments.js
new file mode 100644 (file)
index 0000000..fd0ed68
--- /dev/null
@@ -0,0 +1,109 @@
+const comment = require('./components/comments/comment');
+const commentReply = require('./components/comments/comment-reply');
+
+// 1. Remove code from controllers
+// 2. Remove code from services.
+// 3.
+
+let data = {
+  totalCommentsStr: trans('entities.comments_loading'),
+  comments: [],
+  permissions: null,
+  current_user_id: null,
+  trans: trans,
+  commentCount: 0
+};
+
+let methods = {
+  commentAdded: function () {
+    ++this.totalComments;
+  }
+}
+
+let computed = {
+  totalComments: {
+    get: function () {
+      return this.commentCount;
+    },
+    set: function (value) {
+      this.commentCount = value;
+      if (value === 0) {
+        this.totalCommentsStr = trans('entities.no_comments');
+      } else if (value === 1) {
+        this.totalCommentsStr = trans('entities.one_comment');
+      } else {
+        this.totalCommentsStr = trans('entities.x_comments', {
+            numComments: value
+        });
+      }
+    }
+  },
+  canComment: function () {
+    return true;
+  }
+}
+
+function mounted() {
+  this.pageId = Number(this.$el.getAttribute('page-id'));
+  // let linkedCommentId = this.$route.query.cm;
+  let linkedCommentId = null;
+  this.$http.get(window.baseUrl(`/ajax/page/${this.pageId}/comments/`)).then(resp => {
+    if (!isCommentOpSuccess(resp)) {
+        // just show that no comments are available.
+        vm.totalComments = 0;
+        return;
+    }
+    this.comments = resp.data.comments;
+    this.totalComments = +resp.data.total;
+    this.permissions = resp.data.permissions;
+    this.current_user_id = resp.data.user_id;
+    if (!linkedCommentId) {
+        return;
+    }
+    $timeout(function() {
+        // wait for the UI to render.
+        focusLinkedComment(linkedCommentId);
+    });
+  }, checkError('errors.comment_list'));
+}
+
+function isCommentOpSuccess(resp) {
+  if (resp && resp.data && resp.data.status === 'success') {
+      return true;
+  }
+  return false;
+}
+
+function checkError(msgKey) {
+  return function(response) {
+    let msg = null;
+    if (isCommentOpSuccess(response)) {
+        // all good
+        return;
+    } else if (response.data) {
+        msg = response.data.message;
+    } else {
+        msg = trans(msgKey);
+    }
+    if (msg) {
+        events.emit('success', msg);
+    }
+  }
+}
+
+function created () {
+  this.$on('new-comment', function (event, comment) {
+    this.comments.push(comment);
+  })
+}
+
+function beforeDestroy() {
+  this.$off('new-comment');
+}
+
+module.exports = {
+  data, methods, mounted, computed, components : {
+    comment, commentReply
+  },
+  created, beforeDestroy
+};
\ No newline at end of file