From dc90c396f2bb997c50b0049f271370495d820ed7 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 15 Jul 2015 18:24:28 +0530 Subject: [PATCH] FEATURE: manage Permalinks --- .../admin/components/permalink-form.js.es6 | 56 +++++++++++++++++++ .../admin/controllers/admin-permalinks.js.es6 | 36 ++++++++++++ .../javascripts/admin/models/permalink.js.es6 | 22 ++++++++ .../admin/routes/admin-permalinks.js.es6 | 9 +++ .../admin/routes/admin-route-map.js.es6 | 1 + .../templates/components/permalink-form.hbs | 5 ++ .../javascripts/admin/templates/customize.hbs | 1 + .../admin/templates/permalinks.hbs | 25 +++++++++ .../admin/templates/permalinks_list_item.hbs | 7 +++ .../admin/views/permalinks-list.js.es6 | 8 +++ .../stylesheets/common/admin/admin_base.scss | 18 +++++- .../admin/permalinks_controller.rb | 39 +++++++++++++ app/serializers/permalink_serializer.rb | 3 + config/locales/client.en.yml | 13 +++++ config/routes.rb | 3 + .../admin/permalinks_controller_spec.rb | 51 +++++++++++++++++ 16 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/admin/components/permalink-form.js.es6 create mode 100644 app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 create mode 100644 app/assets/javascripts/admin/models/permalink.js.es6 create mode 100644 app/assets/javascripts/admin/routes/admin-permalinks.js.es6 create mode 100644 app/assets/javascripts/admin/templates/components/permalink-form.hbs create mode 100644 app/assets/javascripts/admin/templates/permalinks.hbs create mode 100644 app/assets/javascripts/admin/templates/permalinks_list_item.hbs create mode 100644 app/assets/javascripts/admin/views/permalinks-list.js.es6 create mode 100644 app/controllers/admin/permalinks_controller.rb create mode 100644 app/serializers/permalink_serializer.rb create mode 100644 spec/controllers/admin/permalinks_controller_spec.rb diff --git a/app/assets/javascripts/admin/components/permalink-form.js.es6 b/app/assets/javascripts/admin/components/permalink-form.js.es6 new file mode 100644 index 00000000000..1bb29e52eb6 --- /dev/null +++ b/app/assets/javascripts/admin/components/permalink-form.js.es6 @@ -0,0 +1,56 @@ +export default Ember.Component.extend({ + classNames: ['permalink-form'], + formSubmitted: false, + permalinkType: 'topic_id', + + permalinkTypes: function() { + return [ + {id: 'topic_id', name: I18n.t('admin.permalink.topic_id')}, + {id: 'post_id', name: I18n.t('admin.permalink.post_id')}, + {id: 'category_id', name: I18n.t('admin.permalink.category_id')}, + {id: 'external_url', name: I18n.t('admin.permalink.external_url')} + ]; + }.property(), + + permalinkTypePlaceholder: function() { + return 'admin.permalink.' + this.get('permalinkType'); + }.property('permalinkType'), + + actions: { + submit: function() { + if (!this.get('formSubmitted')) { + const self = this; + self.set('formSubmitted', true); + const permalink = Discourse.Permalink.create({url: self.get('url'), permalink_type: self.get('permalinkType'), permalink_type_value: self.get('permalink_type_value')}); + permalink.save().then(function(result) { + self.set('url', ''); + self.set('permalink_type_value', ''); + self.set('formSubmitted', false); + self.sendAction('action', Discourse.Permalink.create(result.permalink)); + Em.run.schedule('afterRender', function() { self.$('.permalink-url').focus(); }); + }, function(e) { + self.set('formSubmitted', false); + let error; + if (e.responseJSON && e.responseJSON.errors) { + error = I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')}); + } else { + error = I18n.t("generic_error"); + } + bootbox.alert(error, function() { self.$('.permalink-url').focus(); }); + }); + } + } + }, + + didInsertElement: function() { + var self = this; + self._super(); + Em.run.schedule('afterRender', function() { + self.$('.external-url').keydown(function(e) { + if (e.keyCode === 13) { // enter key + self.send('submit'); + } + }); + }); + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 new file mode 100644 index 00000000000..e03e5ebda45 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 @@ -0,0 +1,36 @@ +export default Ember.ArrayController.extend({ + loading: false, + filter: null, + + show: Discourse.debounce(function() { + var self = this; + self.set('loading', true); + Discourse.Permalink.findAll(self.get("filter")).then(function(result) { + self.set('model', result); + self.set('loading', false); + }); + }, 250).observes("filter"), + + actions: { + recordAdded(arg) { + this.get("model").unshiftObject(arg); + }, + + destroy: function(record) { + const self = this; + return bootbox.confirm(I18n.t("admin.permalink.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { + if (result) { + record.destroy().then(function(deleted) { + if (deleted) { + self.removeObject(record); + } else { + bootbox.alert(I18n.t("generic_error")); + } + }, function(){ + bootbox.alert(I18n.t("generic_error")); + }); + } + }); + } + } +}); diff --git a/app/assets/javascripts/admin/models/permalink.js.es6 b/app/assets/javascripts/admin/models/permalink.js.es6 new file mode 100644 index 00000000000..761cd4ab51f --- /dev/null +++ b/app/assets/javascripts/admin/models/permalink.js.es6 @@ -0,0 +1,22 @@ +const Permalink = Discourse.Model.extend({ + save: function() { + return Discourse.ajax("/admin/permalinks.json", { + type: 'POST', + data: {url: this.get('url'), permalink_type: this.get('permalink_type'), permalink_type_value: this.get('permalink_type_value')} + }); + }, + + destroy: function() { + return Discourse.ajax("/admin/permalinks/" + this.get('id') + ".json", {type: 'DELETE'}); + } +}); + +Permalink.reopenClass({ + findAll: function(filter) { + return Discourse.ajax("/admin/permalinks.json", { data: { filter: filter } }).then(function(permalinks) { + return permalinks.map(p => Discourse.Permalink.create(p)); + }); + } +}); + +export default Permalink; diff --git a/app/assets/javascripts/admin/routes/admin-permalinks.js.es6 b/app/assets/javascripts/admin/routes/admin-permalinks.js.es6 new file mode 100644 index 00000000000..72b7f444d2f --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-permalinks.js.es6 @@ -0,0 +1,9 @@ +export default Discourse.Route.extend({ + model() { + return Discourse.Permalink.findAll(); + }, + + setupController(controller, model) { + controller.set('model', model); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index ab5f2e16c93..daf3b1ced6f 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -22,6 +22,7 @@ export default { }); this.resource('adminUserFields', { path: '/user_fields' }); this.resource('adminEmojis', { path: '/emojis' }); + this.resource('adminPermalinks', { path: '/permalinks' }); }); this.route('api'); diff --git a/app/assets/javascripts/admin/templates/components/permalink-form.hbs b/app/assets/javascripts/admin/templates/components/permalink-form.hbs new file mode 100644 index 00000000000..f5155c27c2e --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/permalink-form.hbs @@ -0,0 +1,5 @@ +{{i18n 'admin.permalink.form.label'}} +{{text-field value=url disabled=formSubmitted class="permalink-url" placeholderKey="admin.permalink.url" autocorrect="off" autocapitalize="off"}} +{{combo-box content=permalinkTypes value=permalinkType}} +{{text-field value=permalink_type_value disabled=formSubmitted class="external-url" placeholderKey=permalinkTypePlaceholder autocorrect="off" autocapitalize="off"}} + diff --git a/app/assets/javascripts/admin/templates/customize.hbs b/app/assets/javascripts/admin/templates/customize.hbs index 1622539bbb3..c009909e1a7 100644 --- a/app/assets/javascripts/admin/templates/customize.hbs +++ b/app/assets/javascripts/admin/templates/customize.hbs @@ -4,6 +4,7 @@ {{nav-item route='adminSiteText' label='admin.site_text.title'}} {{nav-item route='adminUserFields' label='admin.user_fields.title'}} {{nav-item route='adminEmojis' label='admin.emoji.title'}} + {{nav-item route='adminPermalinks' label='admin.permalink.title'}} {{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/permalinks.hbs b/app/assets/javascripts/admin/templates/permalinks.hbs new file mode 100644 index 00000000000..47717627b6a --- /dev/null +++ b/app/assets/javascripts/admin/templates/permalinks.hbs @@ -0,0 +1,25 @@ + +
+ {{text-field value=filter class="url-input" placeholderKey="admin.permalink.form.filter" autocorrect="off" autocapitalize="off"}} +
+{{permalink-form action="recordAdded"}} +
+ +{{#conditional-loading-spinner condition=loading}} + {{#if model.length}} + + {{else}} + {{i18n 'search.no_results'}} + {{/if}} +{{/conditional-loading-spinner}} diff --git a/app/assets/javascripts/admin/templates/permalinks_list_item.hbs b/app/assets/javascripts/admin/templates/permalinks_list_item.hbs new file mode 100644 index 00000000000..4fbf2db55e2 --- /dev/null +++ b/app/assets/javascripts/admin/templates/permalinks_list_item.hbs @@ -0,0 +1,7 @@ +
{{url}}
+
{{topic_id}}
+
{{post_id}}
+
{{category_id}}
+
{{external_url}}
+
+
diff --git a/app/assets/javascripts/admin/views/permalinks-list.js.es6 b/app/assets/javascripts/admin/views/permalinks-list.js.es6 new file mode 100644 index 00000000000..c4254376862 --- /dev/null +++ b/app/assets/javascripts/admin/views/permalinks-list.js.es6 @@ -0,0 +1,8 @@ +import ListView from 'ember-addons/list-view'; +import ListItemView from 'ember-addons/list-item-view'; + +export default ListView.extend({ + height: 700, + rowHeight: 32, + itemViewClass: ListItemView.extend({templateName: "admin/templates/permalinks_list_item"}) +}); diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 1aeabb40106..05891ad3446 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -1199,7 +1199,7 @@ table.api-keys { position: absolute; } -.staff-actions, .screened-emails, .screened-urls, .screened-ip-addresses { +.staff-actions, .screened-emails, .screened-urls, .screened-ip-addresses, .permalinks { border-bottom: dotted 1px scale-color($primary, $lightness: 75%); @@ -1469,3 +1469,19 @@ table#user-badges { width: 90%; } } + +// Permalinks + +.permalinks { + .url, .external_url { + width: 300px; + } + .action, .topic_id, .post_id, .category_id { + text-align: center; + width: 9.9099%; + } +} + +.permalink-title { + margin-bottom: 10px; +} diff --git a/app/controllers/admin/permalinks_controller.rb b/app/controllers/admin/permalinks_controller.rb new file mode 100644 index 00000000000..9c2eb47fb2e --- /dev/null +++ b/app/controllers/admin/permalinks_controller.rb @@ -0,0 +1,39 @@ +class Admin::PermalinksController < Admin::AdminController + + before_filter :fetch_permalink, only: [:destroy] + + def index + filter = params[:filter] + + permalinks = Permalink + permalinks = permalinks.where('url ILIKE :filter OR external_url ILIKE :filter', filter: "%#{params[:filter]}%") if filter.present? + permalinks = permalinks.limit(100).order('created_at desc').to_a + + render_serialized(permalinks, PermalinkSerializer) + end + + def create + params.require(:url) + params.require(:permalink_type) + params.require(:permalink_type_value) + + permalink = Permalink.new(:url => params[:url], params[:permalink_type] => params[:permalink_type_value]) + if permalink.save + render_serialized(permalink, PermalinkSerializer) + else + render_json_error(permalink) + end + end + + def destroy + @permalink.destroy + render json: success_json + end + + private + + def fetch_permalink + @permalink = Permalink.find(params[:id]) + end + +end diff --git a/app/serializers/permalink_serializer.rb b/app/serializers/permalink_serializer.rb new file mode 100644 index 00000000000..2d621ed72de --- /dev/null +++ b/app/serializers/permalink_serializer.rb @@ -0,0 +1,3 @@ +class PermalinkSerializer < ApplicationSerializer + attributes :id, :url, :topic_id, :post_id, :category_id, :external_url +end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 52e0ca0250b..4b6020abf9a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2443,6 +2443,19 @@ en: image: "Image" delete_confirm: "Are you sure you want to delete the :%{name}: emoji?" + permalink: + title: "Permalinks" + url: "URL" + topic_id: "Topic ID" + post_id: "Post ID" + category_id: "Category ID" + external_url: "External URL" + delete_confirm: Are you sure you want to delete this permalink? + form: + label: "New:" + add: "Add" + filter: "Search (URL or External URL)" + lightbox: download: "download" diff --git a/config/routes.rb b/config/routes.rb index 0553fd322bb..f691a8c2ea0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -133,6 +133,7 @@ Discourse::Application.routes.draw do get "customize" => "color_schemes#index", constraints: AdminConstraint.new get "customize/css_html" => "site_customizations#index", constraints: AdminConstraint.new get "customize/colors" => "color_schemes#index", constraints: AdminConstraint.new + get "customize/permalinks" => "permalinks#index", constraints: AdminConstraint.new get "flags" => "flags#index" get "flags/:filter" => "flags#index" post "flags/agree/:id" => "flags#agree" @@ -148,6 +149,8 @@ Discourse::Application.routes.draw do resources :color_schemes, constraints: AdminConstraint.new + resources :permalinks, constraints: AdminConstraint.new + get "version_check" => "versions#show" resources :dashboard, only: [:index] do diff --git a/spec/controllers/admin/permalinks_controller_spec.rb b/spec/controllers/admin/permalinks_controller_spec.rb new file mode 100644 index 00000000000..c885363bfd0 --- /dev/null +++ b/spec/controllers/admin/permalinks_controller_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Admin::PermalinksController do + + it "is a subclass of AdminController" do + expect(Admin::PermalinksController < Admin::AdminController).to eq(true) + end + + let!(:user) { log_in(:admin) } + + describe 'index' do + it 'filters url' do + Fabricate(:permalink, url: "/forum/23") + Fabricate(:permalink, url: "/forum/98") + Fabricate(:permalink, url: "/discuss/topic/45") + Fabricate(:permalink, url: "/discuss/topic/76") + + xhr :get, :index, filter: "topic" + + expect(response).to be_success + result = JSON.parse(response.body) + expect(result.length).to eq(2) + end + + it 'filters external url' do + Fabricate(:permalink, external_url: "http://google.com") + Fabricate(:permalink, external_url: "http://wikipedia.org") + Fabricate(:permalink, external_url: "http://www.discourse.org") + Fabricate(:permalink, external_url: "http://try.discourse.org") + + xhr :get, :index, filter: "discourse" + + expect(response).to be_success + result = JSON.parse(response.body) + expect(result.length).to eq(2) + end + + it 'filters url and external url both' do + Fabricate(:permalink, url: "/forum/23", external_url: "http://google.com") + Fabricate(:permalink, url: "/discourse/98", external_url: "http://wikipedia.org") + Fabricate(:permalink, url: "/discuss/topic/45", external_url: "http://discourse.org") + Fabricate(:permalink, url: "/discuss/topic/76", external_url: "http://try.discourse.org") + + xhr :get, :index, filter: "discourse" + + expect(response).to be_success + result = JSON.parse(response.body) + expect(result.length).to eq(3) + end + end +end