From 7c0893ab3612af182e42fba37454a5de2b76ba86 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 2 Jun 2016 08:58:10 +0200 Subject: [PATCH] Added tag autocompletion and tag management in admin interface. --- .../app/controllers/_ui_element/tag.coffee | 24 +- .../javascripts/app/controllers/tag.coffee | 149 ++++++++ .../app/controllers/widget/tag.coffee | 46 ++- .../javascripts/app/views/tag/edit.jst.eco | 5 + .../javascripts/app/views/tag/index.jst.eco | 29 ++ .../javascripts/app/views/tag/table.jst.eco | 18 + .../javascripts/app/views/widget/tag.jst.eco | 4 +- app/assets/stylesheets/application.css | 1 + app/assets/stylesheets/jquery-ui.css | 332 ++++++++++++++++++ app/controllers/tags_controller.rb | 78 +++- app/models/tag.rb | 17 +- config/routes/tag.rb | 13 +- db/migrate/20120101000001_create_base.rb | 3 +- db/migrate/20160602000001_update_tag.rb | 35 ++ db/seeds.rb | 23 ++ .../agent_ticket_actions_level8_test.rb | 325 +++++++++++------ test/browser_test_helper.rb | 41 +++ test/unit/tag_test.rb | 60 +++- 18 files changed, 1044 insertions(+), 159 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/tag.coffee create mode 100644 app/assets/javascripts/app/views/tag/edit.jst.eco create mode 100644 app/assets/javascripts/app/views/tag/index.jst.eco create mode 100644 app/assets/javascripts/app/views/tag/table.jst.eco create mode 100644 app/assets/stylesheets/jquery-ui.css create mode 100644 db/migrate/20160602000001_update_tag.rb diff --git a/app/assets/javascripts/app/controllers/_ui_element/tag.coffee b/app/assets/javascripts/app/controllers/_ui_element/tag.coffee index fac0cfac1..47a375043 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/tag.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/tag.coffee @@ -1,9 +1,27 @@ # coffeelint: disable=camel_case_classes class App.UiElement.tag @render: (attribute) -> - item = $( App.view('generic/input')( attribute: attribute ) ) + item = $( App.view('generic/input')(attribute: attribute) ) + source = "#{App.Config.get('api_path')}/tag_search" + possibleTags = {} a = -> - $('#' + attribute.id ).tokenfield(createTokensOnBlur: true) + $('#' + attribute.id ).tokenfield( + createTokensOnBlur: true + autocomplete: { + source: source + minLength: 2 + response: (e, ui) -> + return if !ui + return if !ui.content + for item in ui.content + possibleTags[item.value] = true + }, + ).on('tokenfield:createtoken', (e) -> + if App.Config.get('tag_new') is false && !possibleTags[e.attrs.value] + e.preventDefault() + return false + true + ) $('#' + attribute.id ).parent().css('height', 'auto') App.Delay.set(a, 120, undefined, 'tags') - item \ No newline at end of file + item diff --git a/app/assets/javascripts/app/controllers/tag.coffee b/app/assets/javascripts/app/controllers/tag.coffee new file mode 100644 index 000000000..33a0ab0d7 --- /dev/null +++ b/app/assets/javascripts/app/controllers/tag.coffee @@ -0,0 +1,149 @@ +class Index extends App.ControllerContent + events: + 'change .js-newTagSetting input': 'setTagNew' + 'submit .js-create': 'create' + + elements: + '.js-newTagSetting input': 'tagNewSetting' + + constructor: -> + super + + # check authentication + return if !@authenticate(false, 'Admin') + + @title 'Tags', true + + @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false) + + release: => + App.Setting.unsubscribe(@subscribeId) + + render: => + currentNewTagSetting = @Config.get('tag_new') || true + return if currentNewTagSetting is @lastNewTagSetting + @lastNewTagSetting = currentNewTagSetting + + @html App.view('tag/index')() + new Table( + el: @$('.js-Table') + ) + + setTagNew: (e) => + value = @tagNewSetting.prop('checked') + console.log('aa', value) + App.Setting.set('tag_new', value) + + create: (e) => + e.preventDefault() + field = $(e.currentTarget).find('input[name]') + name = field.val().trim() + return if !name + @ajax( + type: 'POST' + url: "#{@apiPath}/tag_list" + data: JSON.stringify(name: name) + success: (data, status, xhr) => + field.val('') + new Table( + el: @$('.js-Table') + ) + ) + +class Table extends App.Controller + events: + 'click .js-delete': 'destroy' + 'click .js-edit': 'edit' + + constructor: -> + super + @load() + + load: => + @ajax( + id: 'tag_admin_list' + type: 'GET' + url: "#{@apiPath}/tag_list" + processData: true + success: (data, status, xhr) => + @render(data) + ) + + render: (list) => + @html App.view('tag/table')( + list: list + ) + + edit: (e) => + e.preventDefault() + row = $(e.currentTarget).closest('tr') + name = row.find('.js-name').text() + id = row.data('id') + new Edit( + id: id + name: name + callback: @load + ) + + destroy: (e) -> + e.preventDefault() + row = $(e.currentTarget).closest('tr') + id = row.data('id') + new DestroyConfirm( + id: id + row: row + ) + +class Edit extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: 'Submit' + head: 'Edit' + small: true + + content: -> + App.view('tag/edit')( + id: @id + name: @name + ) + + onSubmit: (e) => + e.preventDefault() + params = @formParam(e.target) + + @ajax( + id: 'tag_admin_list' + type: 'PUT' + url: "#{@apiPath}/tag_list/#{@id}" + data: JSON.stringify( + id: @id + name: params.name + ) + success: (data, status, xhr) => + @callback() + @close() + ) + +class DestroyConfirm extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: 'yes' + buttonClass: 'btn--danger' + head: 'Confirm' + small: true + + content: -> + App.i18n.translateContent('Sure to delete this object?') + + onSubmit: => + @ajax( + id: 'tag_admin_list' + type: 'DELETE' + url: "#{@apiPath}/tag_list/#{@id}" + processData: true + success: (data, status, xhr) => + @row.remove() + @close() + ) + +App.Config.set('Tags', { prio: 2320, name: 'Tags', parent: '#manage', target: '#manage/tags', controller: Index, role: ['Admin'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/widget/tag.coffee b/app/assets/javascripts/app/controllers/widget/tag.coffee index 9d70c7633..53894a279 100644 --- a/app/assets/javascripts/app/controllers/widget/tag.coffee +++ b/app/assets/javascripts/app/controllers/widget/tag.coffee @@ -1,4 +1,6 @@ class App.WidgetTag extends App.Controller + possibleTags: {} + shiftHeld: false elements: '.js-newTagLabel': 'newTagLabel' '.js-newTagInput': 'newTagInput' @@ -9,6 +11,8 @@ class App.WidgetTag extends App.Controller 'click .js-newTagInput': 'onAddTag' 'submit form': 'onAddTag' 'click .js-delete': 'onRemoveTag' + 'mousedown .js-tag': 'shiftHeldToogle' + 'click .js-tag': 'searchTag' constructor: -> super @@ -40,6 +44,17 @@ class App.WidgetTag extends App.Controller tags: @tags || [], ) + source = "#{App.Config.get('api_path')}/tag_search" + @el.find('.js-newTagInput').autocomplete( + source: source + minLength: 2 + response: (e, ui) => + return if !ui + return if !ui.content + for item in ui.content + @possibleTags[item.value] = true + ) + showInput: (e) -> e.preventDefault() @newTagLabel.addClass('hide') @@ -66,16 +81,16 @@ class App.WidgetTag extends App.Controller if _.contains(@tags, item) @render() return - + return if App.Config.get('tag_new') is false && !@possibleTags[item] @tags.push item @render() @ajax( - type: 'GET', - url: @apiPath + '/tags/add', + type: 'GET' + url: @apiPath + '/tags/add' data: - object: @object_type, - o_id: @object.id, + object: @object_type + o_id: @object.id item: item processData: true, success: (data, status, xhr) => @@ -104,3 +119,24 @@ class App.WidgetTag extends App.Controller success: (data, status, xhr) => @fetch() ) + + searchTag: (e) => + e.preventDefault() + item = $(e.target).text() + item = item.replace('"', '') + if item.match(/\W/) + item = "\"#{item}\"" + searchAttribute = "tag:#{item}" + currentValue = $('#global-search').val() + if @shiftHeld && currentValue + currentValue += ' AND ' + currentValue += searchAttribute + else + currentValue = searchAttribute + $('#global-search').val(currentValue) + delay = -> + $('#global-search').focus() + @delay(delay, 20) + + shiftHeldToogle: (e) => + @shiftHeld = e.shiftKey diff --git a/app/assets/javascripts/app/views/tag/edit.jst.eco b/app/assets/javascripts/app/views/tag/edit.jst.eco new file mode 100644 index 000000000..7f65a3b82 --- /dev/null +++ b/app/assets/javascripts/app/views/tag/edit.jst.eco @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/tag/index.jst.eco b/app/assets/javascripts/app/views/tag/index.jst.eco new file mode 100644 index 000000000..f54612d62 --- /dev/null +++ b/app/assets/javascripts/app/views/tag/index.jst.eco @@ -0,0 +1,29 @@ + +
+ +
+
+
+ checked<% end %>> + +
+

<%- @T('New Tags') %>

+
+

⚠ <%- @T('Allow users to add new tags.') %>

+
+ +
+

<%- @T('Manage Tags') %>

+
+
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/tag/table.jst.eco b/app/assets/javascripts/app/views/tag/table.jst.eco new file mode 100644 index 000000000..7b57c8078 --- /dev/null +++ b/app/assets/javascripts/app/views/tag/table.jst.eco @@ -0,0 +1,18 @@ + + + + + + + + + + <% for item in @list: %> + + + + + + <% end %> + +
<%- @T('Name') %><%- @T('Count') %><%- @T('Action') %>
<%= item.name %><%= item.count %><%- @Icon('trash') %>
diff --git a/app/assets/javascripts/app/views/widget/tag.jst.eco b/app/assets/javascripts/app/views/widget/tag.jst.eco index 85e61c34b..a7052e3d5 100644 --- a/app/assets/javascripts/app/views/widget/tag.jst.eco +++ b/app/assets/javascripts/app/views/widget/tag.jst.eco @@ -1,8 +1,8 @@ - +