From b0557c6692b6e88878bbba965d08bf2985514350 Mon Sep 17 00:00:00 2001
From: Guo Xiang Tan <tgx_world@hotmail.com>
Date: Thu, 5 Oct 2017 11:48:42 +0800
Subject: [PATCH] UX: Allow users to remove a remind me topic timer.

---
 .../components/edit-topic-timer-form.js.es6   | 50 +++++++++++
 .../controllers/edit-topic-timer.js.es6       | 79 +++++++----------
 .../javascripts/discourse/routes/topic.js.es6 |  1 +
 .../components/edit-topic-timer-form.hbs      | 36 ++++++++
 .../templates/modal/edit-topic-timer.hbs      | 85 +++++++------------
 .../javascripts/discourse/templates/topic.hbs | 21 +++--
 .../base/edit-topic-status-update-modal.scss  | 10 +++
 app/assets/stylesheets/desktop/topic.scss     |  4 +-
 app/models/topic.rb                           |  5 ++
 app/serializers/topic_view_serializer.rb      | 10 +++
 config/locales/client.en.yml                  |  4 +-
 spec/models/topic_spec.rb                     | 16 ++++
 12 files changed, 213 insertions(+), 108 deletions(-)
 create mode 100644 app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6
 create mode 100644 app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs

diff --git a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6
new file mode 100644
index 00000000000..421972b834c
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6
@@ -0,0 +1,50 @@
+import { default as computed, observes, on } from "ember-addons/ember-computed-decorators";
+
+import {
+  PUBLISH_TO_CATEGORY_STATUS_TYPE,
+  OPEN_STATUS_TYPE,
+  DELETE_STATUS_TYPE,
+  REMINDER_TYPE,
+  CLOSE_STATUS_TYPE
+} from 'discourse/controllers/edit-topic-timer';
+
+export default Ember.Component.extend({
+  selection: Ember.computed.alias('topicTimer.status_type'),
+  autoOpen: Ember.computed.equal('selection', OPEN_STATUS_TYPE),
+  autoClose: Ember.computed.equal('selection', CLOSE_STATUS_TYPE),
+  autoDelete: Ember.computed.equal('selection', DELETE_STATUS_TYPE),
+  publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE),
+  reminder: Ember.computed.equal('selection', REMINDER_TYPE),
+  showTimeOnly: Ember.computed.or('autoOpen', 'autoDelete', 'reminder'),
+
+  @computed('topicTimer.updateTime', 'loading', 'publishToCategory', 'topicTimer.category_id')
+  saveDisabled(updateTime, loading, publishToCategory, topicTimerCategoryId) {
+    return Ember.isEmpty(updateTime) ||
+      loading ||
+      (publishToCategory && !topicTimerCategoryId);
+  },
+
+  @computed("topic.visible")
+  excludeCategoryId(visible) {
+    if (visible) return this.get('topic.category_id');
+  },
+
+  @on('init')
+  @observes("topicTimer", "topicTimer.execute_at", "topicTimer.duration")
+  _setUpdateTime() {
+    let time = null;
+    const executeAt = this.get('topicTimer.execute_at');
+
+    if (executeAt && this.get("topicTimer.based_on_last_post")) {
+      time = this.get("topicTimer.duration");
+    } else if (executeAt) {
+      const closeTime = moment(executeAt);
+
+      if (closeTime > moment()) {
+        time = closeTime.format("YYYY-MM-DD HH:mm");
+      }
+    }
+
+    this.set("topicTimer.updateTime", time);
+  }
+});
diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6
index fdb66c1a65f..e1a122a37a7 100644
--- a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6
@@ -1,67 +1,51 @@
-import { default as computed, observes } from "ember-addons/ember-computed-decorators";
+import { default as computed } from "ember-addons/ember-computed-decorators";
 import ModalFunctionality from 'discourse/mixins/modal-functionality';
 import TopicTimer from 'discourse/models/topic-timer';
 import { popupAjaxError } from 'discourse/lib/ajax-error';
 
 export const CLOSE_STATUS_TYPE = 'close';
-const OPEN_STATUS_TYPE = 'open';
+export const OPEN_STATUS_TYPE = 'open';
 export const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category';
-const DELETE_STATUS_TYPE = 'delete';
-const REMINDER_TYPE = 'reminder';
+export const DELETE_STATUS_TYPE = 'delete';
+export const REMINDER_TYPE = 'reminder';
 
 export default Ember.Controller.extend(ModalFunctionality, {
   loading: false,
-  updateTime: null,
-  topicTimer: Ember.computed.alias("model.topic_timer"),
-  selection: Ember.computed.alias('model.topic_timer.status_type'),
-  autoOpen: Ember.computed.equal('selection', OPEN_STATUS_TYPE),
-  autoClose: Ember.computed.equal('selection', CLOSE_STATUS_TYPE),
-  autoDelete: Ember.computed.equal('selection', DELETE_STATUS_TYPE),
-  publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE),
-  reminder: Ember.computed.equal('selection', REMINDER_TYPE),
-
-  showTimeOnly: Ember.computed.or('autoOpen', 'autoDelete', 'reminder'),
+  isPublic: "true",
 
   @computed("model.closed")
-  timerTypes(closed) {
+  publicTimerTypes(closed) {
     return [
       { id: CLOSE_STATUS_TYPE, name: I18n.t(closed ? 'topic.temp_open.title' : 'topic.auto_close.title'), },
       { id: OPEN_STATUS_TYPE, name: I18n.t(closed ? 'topic.auto_reopen.title' : 'topic.temp_close.title') },
       { id: PUBLISH_TO_CATEGORY_STATUS_TYPE, name: I18n.t('topic.publish_to_category.title') },
-      { id: DELETE_STATUS_TYPE, name: I18n.t('topic.auto_delete.title') },
+      { id: DELETE_STATUS_TYPE, name: I18n.t('topic.auto_delete.title') }
+    ];
+  },
+
+  @computed()
+  privateTimerTypes() {
+    return [
       { id: REMINDER_TYPE, name: I18n.t('topic.reminder.title') }
     ];
   },
 
-  @computed('updateTime', 'loading', 'publishToCategory', 'topicTimer.category_id')
-  saveDisabled(updateTime, loading, publishToCategory, topicTimerCategoryId) {
-    return Ember.isEmpty(updateTime) ||
-      loading ||
-      (publishToCategory && !topicTimerCategoryId);
-  },
-
-  @computed("model.visible")
-  excludeCategoryId(visible) {
-    if (visible) return this.get('model.category_id');
-  },
-
-  @observes("topicTimer.execute_at", "topicTimer.duration")
-  _setUpdateTime() {
-    if (!this.get('topicTimer.execute_at')) return;
-
-    let time = null;
-
-    if (this.get("topicTimer.based_on_last_post")) {
-      time = this.get("topicTimer.duration");
-    } else if (this.get("topicTimer.execute_at")) {
-      const closeTime = moment(this.get('topicTimer.execute_at'));
-
-      if (closeTime > moment()) {
-        time = closeTime.format("YYYY-MM-DD HH:mm");
-      }
+  @computed("isPublic", 'publicTimerTypes', 'privateTimerTypes')
+  selections(isPublic, publicTimerTypes, privateTimerTypes) {
+    if (isPublic === 'true') {
+      return publicTimerTypes;
+    } else {
+      return privateTimerTypes;
     }
+  },
 
-    this.set("updateTime", time);
+  @computed('isPublic', 'model.topic_timer', 'model.private_topic_timer')
+  topicTimer(isPublic, publicTopicTimer, privateTopicTimer) {
+    if (isPublic === 'true') {
+      return publicTopicTimer;
+    } else {
+      return privateTopicTimer;
+    }
   },
 
   _setTimer(time, statusType) {
@@ -85,10 +69,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
 
         this.set('model.closed', result.closed);
       } else {
+        const topicTimer = this.get('isPublic') === 'true' ? 'topic_timer' : 'private_topic_timer';
+        this.set(`model.${topicTimer}`, Ember.Object.create({}));
+
         this.setProperties({
-          topicTimer: Ember.Object.create({}),
           selection: null,
-          updateTime: null
         });
       }
     }).catch(error => {
@@ -98,11 +83,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
 
   actions: {
     saveTimer() {
-      this._setTimer(this.get("updateTime"), this.get('selection'));
+      this._setTimer(this.get("topicTimer.updateTime"), this.get('topicTimer.status_type'));
     },
 
     removeTimer() {
-      this._setTimer(null, this.get('selection'));
+      this._setTimer(null, this.get('topicTimer.status_type'));
     }
   }
 });
diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6
index 4e17be6c6b3..50867225346 100644
--- a/app/assets/javascripts/discourse/routes/topic.js.es6
+++ b/app/assets/javascripts/discourse/routes/topic.js.es6
@@ -54,6 +54,7 @@ const TopicRoute = Discourse.Route.extend({
     showTopicStatusUpdate() {
       const model = this.modelFor('topic');
       model.set('topic_timer', Ember.Object.create(model.get('topic_timer')));
+      model.set('private_topic_timer', Ember.Object.create(model.get('private_topic_timer')));
       showModal('edit-topic-timer', { model });
       this.controllerFor('modal').set('modalClass', 'edit-topic-timer-modal');
     },
diff --git a/app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs b/app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs
new file mode 100644
index 00000000000..e2310800812
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs
@@ -0,0 +1,36 @@
+<form>
+  <div class="control-group">
+    {{combo-box content=timerTypes value=selection width="50%"}}
+  </div>
+
+  <div>
+    {{#if showTimeOnly}}
+      {{future-date-input
+          input=topicTimer.updateTime
+          statusType=selection
+          includeWeekend=true
+          basedOnLastPost=false}}
+    {{else if publishToCategory}}
+      <div class="control-group">
+        <label>{{i18n 'topic.topic_status_update.publish_to'}}</label>
+        {{category-select-box
+          value=topicTimer.category_id
+          excludeCategoryId=excludeCategoryId}}
+      </div>
+
+      {{future-date-input
+          input=topicTimer.updateTime
+          statusType=selection
+          includeWeekend=true
+          categoryId=topicTimer.category_id
+          basedOnLastPost=false}}
+    {{else if autoClose}}
+      {{future-date-input
+          input=topicTimer.updateTime
+          statusType=selection
+          includeWeekend=true
+          basedOnLastPost=topicTimer.based_on_last_post
+          lastPostedAt=model.last_posted_at}}
+    {{/if}}
+  </div>
+</form>
diff --git a/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs b/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs
index dfe6826bdba..4de56698a39 100644
--- a/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs
@@ -1,54 +1,35 @@
-<form>
-  {{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}}
-    <div class="control-group">
-      {{combo-box content=timerTypes value=selection width="50%"}}
-    </div>
+{{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}}
+  <div class="radios">
+    <label for="public-topic-timer">
+      {{radio-button id='public-topic-timer' name="topic-timer" value="true" selection=isPublic}}
+      <b>{{i18n 'topic.topic_status_update.public_timer_types'}}</b>
+    </label>
 
-    <div>
-      {{#if showTimeOnly}}
-        {{future-date-input
-            input=updateTime
-            statusType=selection
-            includeWeekend=true
-            basedOnLastPost=false}}
-      {{else if publishToCategory}}
-        <div class="control-group">
-          <label>{{i18n 'topic.topic_status_update.publish_to'}}</label>
-          {{category-select-box
-            value=topicTimer.category_id
-            excludeCategoryId=excludeCategoryId}}
-        </div>
-
-        {{future-date-input
-            input=updateTime
-            statusType=selection
-            includeWeekend=true
-            categoryId=topicTimer.category_id
-            basedOnLastPost=false}}
-      {{else if autoClose}}
-        {{future-date-input
-            input=updateTime
-            statusType=selection
-            includeWeekend=true
-            basedOnLastPost=topicTimer.based_on_last_post
-            lastPostedAt=model.last_posted_at}}
-      {{/if}}
-    </div>
-  {{/d-modal-body}}
-
-  <div class="modal-footer">
-    {{d-button class="btn-primary"
-        disabled=saveDisabled
-        label="topic.topic_status_update.save"
-        action="saveTimer"}}
-
-    {{d-modal-cancel close=(action "closeModal")}}
-    {{conditional-loading-spinner size="small" condition=loading}}
-
-    {{#if topicTimer.execute_at}}
-      {{d-button class="pull-right btn-danger"
-          action="removeTimer"
-          label='topic.topic_status_update.remove'}}
-    {{/if}}
+    <label for="private-topic-timer">
+      {{radio-button id='private-topic-timer' name="topic-timer" value="false" selection=isPublic}}
+      <b>{{i18n 'topic.topic_status_update.private_timer_types'}}</b>
+    </label>
   </div>
-</form>
+
+  {{edit-topic-timer-form
+      topic=model
+      topicTimer=topicTimer
+      timerTypes=selections
+      updateTime=updateTime
+      closeModal="closeModal"}}
+{{/d-modal-body}}
+
+<div class="modal-footer">
+  {{d-button class="btn-primary"
+      disabled=saveDisabled
+      label="topic.topic_status_update.save"
+      action="saveTimer"}}
+
+  {{conditional-loading-spinner size="small" condition=loading}}
+
+  {{#if topicTimer.execute_at}}
+    {{d-button class="pull-right btn-danger"
+        action="removeTimer"
+        label='topic.topic_status_update.remove'}}
+  {{/if}}
+</div>
diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs
index 4f0fcddacdc..8c9eed6e505 100644
--- a/app/assets/javascripts/discourse/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/templates/topic.hbs
@@ -199,12 +199,21 @@
                 </div>
               {{/if}}
 
-              {{topic-timer-info
-                  statusType=model.topic_timer.status_type
-                  executeAt=model.topic_timer.execute_at
-                  basedOnLastPost=model.topic_timer.based_on_last_post
-                  duration=model.topic_timer.duration
-                  categoryId=model.topic_timer.category_id}}
+              {{#if model.private_topic_timer.execute_at}}
+                {{topic-timer-info
+                    statusType=model.private_topic_timer.status_type
+                    executeAt=model.private_topic_timer.execute_at
+                    duration=model.private_topic_timer.duration}}
+              {{/if}}
+
+              {{#if model.topic_timer.execute_at}}
+                {{topic-timer-info
+                    statusType=model.topic_timer.status_type
+                    executeAt=model.topic_timer.execute_at
+                    basedOnLastPost=model.topic_timer.based_on_last_post
+                    duration=model.topic_timer.duration
+                    categoryId=model.topic_timer.category_id}}
+              {{/if}}
 
               {{#if session.showSignupCta}}
                 {{! replace "Log In to Reply" with the infobox }}
diff --git a/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss b/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss
index 1c17981d7be..3a9424ca478 100644
--- a/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss
+++ b/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss
@@ -8,8 +8,18 @@
     text-align: left;
   }
 
+  .radios {
+    margin-bottom: 10px;
+  }
+
   label {
+    vertical-align: middle;
     display: inline-block;
+    padding-right: 5px;
+
+    input {
+      vertical-align: middle;
+    }
   }
 
   .btn.pull-right {
diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss
index 8448400be78..98f7ec65fd3 100644
--- a/app/assets/stylesheets/desktop/topic.scss
+++ b/app/assets/stylesheets/desktop/topic.scss
@@ -69,8 +69,8 @@
 }
 
 .topic-status-info {
-  border-top: 1px solid $primary-low; 
-  padding-top: 10px;
+  border-top: 1px solid $primary-low;
+  padding: 10px 0px;
   height: 20px;
   max-width: 757px;
 }
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 161d5ba3615..59822cd2562 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -411,6 +411,7 @@ class Topic < ActiveRecord::Base
   def reload(options = nil)
     @post_numbers = nil
     @public_topic_timer = nil
+    @private_topic_timer = nil
     super(options)
   end
 
@@ -1002,6 +1003,10 @@ SQL
     @public_topic_timer ||= topic_timers.find_by(deleted_at: nil, public_type: true)
   end
 
+  def private_topic_timer(user)
+    @private_topic_Timer ||= topic_timers.find_by(deleted_at: nil, public_type: false, user_id: user.id)
+  end
+
   def delete_topic_timer(status_type, by_user: Discourse.system_user)
     options = { status_type: status_type }
     options.merge!(user: by_user) unless TopicTimer.public_types[status_type]
diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb
index c5ccc917894..df70f3592bd 100644
--- a/app/serializers/topic_view_serializer.rb
+++ b/app/serializers/topic_view_serializer.rb
@@ -61,6 +61,7 @@ class TopicViewSerializer < ApplicationSerializer
              :message_archived,
              :tags,
              :topic_timer,
+             :private_topic_timer,
              :unicode_title,
              :message_bus_last_id,
              :participant_count
@@ -242,6 +243,15 @@ class TopicViewSerializer < ApplicationSerializer
     TopicTimerSerializer.new(object.topic.public_topic_timer, root: false)
   end
 
+  def include_private_topic_timer?
+    scope.user
+  end
+
+  def private_topic_timer
+    timer = object.topic.private_topic_timer(scope.user)
+    TopicTimerSerializer.new(timer, root: false)
+  end
+
   def tags
     object.topic.tags.map(&:name)
   end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index ecc0e9f5e4f..28f3c24433f 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1555,12 +1555,14 @@ en:
       deleted: "The topic has been deleted"
 
       topic_status_update:
-        title: "Set Topic Timer"
+        title: "Topic Timer"
         save: "Set Timer"
         num_of_hours: "Number of hours:"
         remove: "Remove Timer"
         publish_to: "Publish To:"
         when: "When:"
+        public_timer_types: Topic Timers
+        private_timer_types: User Topic Timers
       auto_update_input:
         none: ""
         later_today: "Later today"
diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb
index ce3ece7a4f4..706b2ee4970 100644
--- a/spec/models/topic_spec.rb
+++ b/spec/models/topic_spec.rb
@@ -1167,6 +1167,22 @@ describe Topic do
     end
   end
 
+  describe '#private_topic_timer' do
+    let(:user) { Fabricate(:user) }
+
+    let(:topic_timer) do
+      Fabricate(:topic_timer,
+        public_type: false,
+        user: user,
+        status_type: TopicTimer.private_types[:reminder]
+      )
+    end
+
+    it 'should return the right record' do
+      expect(topic_timer.topic.private_topic_timer(user)).to eq(topic_timer)
+    end
+  end
+
   describe '#set_or_create_timer' do
     let(:topic) { Fabricate.build(:topic) }