Added tag autocompletion and tag management in admin interface.
This commit is contained in:
parent
5977b9e348
commit
7c0893ab36
18 changed files with 1044 additions and 159 deletions
|
@ -2,8 +2,26 @@
|
|||
class App.UiElement.tag
|
||||
@render: (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
|
149
app/assets/javascripts/app/controllers/tag.coffee
Normal file
149
app/assets/javascripts/app/controllers/tag.coffee
Normal file
|
@ -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')
|
|
@ -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
|
||||
|
|
5
app/assets/javascripts/app/views/tag/edit.jst.eco
Normal file
5
app/assets/javascripts/app/views/tag/edit.jst.eco
Normal file
|
@ -0,0 +1,5 @@
|
|||
<form>
|
||||
<div class="form-item">
|
||||
<input type="text" name="name" class="" value="<%= @name %>" autocomplete="off">
|
||||
</div>
|
||||
</form>
|
29
app/assets/javascripts/app/views/tag/index.jst.eco
Normal file
29
app/assets/javascripts/app/views/tag/index.jst.eco
Normal file
|
@ -0,0 +1,29 @@
|
|||
<div class="page-header">
|
||||
<div class="page-header-title">
|
||||
<h1><%- @T('Tags') %><small></small></h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
|
||||
<div class="settings-entry">
|
||||
<div class="page-header-title">
|
||||
<div class="zammad-switch zammad-switch--small js-newTagSetting">
|
||||
<input name="chat" type="checkbox" id="tag-new" <% if @C('tag_new'): %>checked<% end %>>
|
||||
<label for="tag-new"></label>
|
||||
</div>
|
||||
<h2><%- @T('New Tags') %></h2>
|
||||
</div>
|
||||
<p>⚠ <%- @T('Allow users to add new tags.') %></p>
|
||||
</div>
|
||||
|
||||
<div class="settings-entry">
|
||||
<h2><%- @T('Manage Tags') %></h2>
|
||||
<form class="horizontal form-group formGroup--halfSize js-create">
|
||||
<div class="form-item">
|
||||
<input type="text" name="name" class="" autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" class="btn btn--primary js-submit"><%- @T('Add') %></button>
|
||||
</form>
|
||||
<div class="js-Table"></div>
|
||||
</div>
|
||||
</div>
|
18
app/assets/javascripts/app/views/tag/table.jst.eco
Normal file
18
app/assets/javascripts/app/views/tag/table.jst.eco
Normal file
|
@ -0,0 +1,18 @@
|
|||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%- @T('Name') %></th>
|
||||
<th><%- @T('Count') %></th>
|
||||
<th><%- @T('Action') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for item in @list: %>
|
||||
<tr data-id="<%= item.id %>" class="js-edit u-clickable">
|
||||
<td class="js-name"><%= item.name %></td>
|
||||
<td><%= item.count %></td>
|
||||
<td><a href="#" class="js-delete" title="<%- @Ti('Delete') %>"><%- @Icon('trash') %></a></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
|
@ -2,7 +2,7 @@
|
|||
<ul class="list list--sidebar">
|
||||
<% for tag in @tags: %>
|
||||
<li class="list-item">
|
||||
<div class="list-item-name js-tag"><%= tag %></div>
|
||||
<a href="#" class="list-item-name js-tag"><%= tag %></a>
|
||||
<div class="list-item-delete js-delete">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* the top of the compiled file, but it's generally better to create a new file per style scope.
|
||||
*= require_self
|
||||
*= require ./bootstrap.css
|
||||
*= require ./jquery-ui.css
|
||||
*= require ./cropper.css
|
||||
*= require ./fineuploader.css
|
||||
*= require ./font.css
|
||||
|
|
332
app/assets/stylesheets/jquery-ui.css
vendored
Normal file
332
app/assets/stylesheets/jquery-ui.css
vendored
Normal file
|
@ -0,0 +1,332 @@
|
|||
/*! jQuery UI - v1.11.4 - 2016-06-01
|
||||
* http://jqueryui.com
|
||||
* Includes: core.css, autocomplete.css, menu.css, theme.css
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&fwDefault=normal&cornerRadius=3px&bgColorHeader=e9e9e9&bgTextureHeader=flat&borderColorHeader=dddddd&fcHeader=333333&iconColorHeader=444444&bgColorContent=ffffff&bgTextureContent=flat&borderColorContent=dddddd&fcContent=333333&iconColorContent=444444&bgColorDefault=f6f6f6&bgTextureDefault=flat&borderColorDefault=c5c5c5&fcDefault=454545&iconColorDefault=777777&bgColorHover=ededed&bgTextureHover=flat&borderColorHover=cccccc&fcHover=2b2b2b&iconColorHover=555555&bgColorActive=007fff&bgTextureActive=flat&borderColorActive=003eff&fcActive=ffffff&iconColorActive=ffffff&bgColorHighlight=fffa90&bgTextureHighlight=flat&borderColorHighlight=dad55e&fcHighlight=777620&iconColorHighlight=777620&bgColorError=fddfdf&bgTextureError=flat&borderColorError=f1a899&fcError=5f3f3f&iconColorError=cc0000&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=666666&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=5px&offsetTopShadow=0px&offsetLeftShadow=0px&cornerRadiusShadow=8px
|
||||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
display: none;
|
||||
}
|
||||
.ui-helper-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.ui-helper-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
.ui-helper-clearfix {
|
||||
min-height: 0; /* support: IE7 */
|
||||
}
|
||||
.ui-helper-zfix {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
filter:Alpha(Opacity=0); /* support: IE8 */
|
||||
}
|
||||
|
||||
.ui-front {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
display: block;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-autocomplete {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: default;
|
||||
}
|
||||
.ui-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: block;
|
||||
outline: none;
|
||||
}
|
||||
.ui-menu .ui-menu {
|
||||
position: absolute;
|
||||
}
|
||||
.ui-menu .ui-menu-item {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 3px 1em 3px .4em;
|
||||
cursor: pointer;
|
||||
min-height: 0; /* support: IE7 */
|
||||
/* support: IE10, see #8844 */
|
||||
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
|
||||
}
|
||||
.ui-menu .ui-menu-divider {
|
||||
margin: 5px 0;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
.ui-menu .ui-state-focus,
|
||||
.ui-menu .ui-state-active {
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
/* icon support */
|
||||
.ui-menu-icons {
|
||||
position: relative;
|
||||
}
|
||||
.ui-menu-icons .ui-menu-item {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
/* left-aligned */
|
||||
.ui-menu .ui-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: .2em;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
/* right-aligned */
|
||||
.ui-menu .ui-menu-icon {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget .ui-widget {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #dddddd;
|
||||
background: #ffffff;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border: 1px solid #dddddd;
|
||||
background: #e9e9e9;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
----------------------------------*/
|
||||
.ui-state-default,
|
||||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default {
|
||||
border: 1px solid #c5c5c5;
|
||||
background: #f6f6f6;
|
||||
font-weight: normal;
|
||||
color: #454545;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
.ui-state-default a:visited {
|
||||
color: #454545;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-hover,
|
||||
.ui-widget-content .ui-state-hover,
|
||||
.ui-widget-header .ui-state-hover,
|
||||
.ui-state-focus,
|
||||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus {
|
||||
border: 1px solid #cccccc;
|
||||
background: #ededed;
|
||||
font-weight: normal;
|
||||
color: #2b2b2b;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited,
|
||||
.ui-state-focus a,
|
||||
.ui-state-focus a:hover,
|
||||
.ui-state-focus a:link,
|
||||
.ui-state-focus a:visited {
|
||||
color: #2b2b2b;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-active,
|
||||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active {
|
||||
border: 1px solid #003eff;
|
||||
background: #007fff;
|
||||
font-weight: normal;
|
||||
color: #ffffff;
|
||||
}
|
||||
.ui-state-active a,
|
||||
.ui-state-active a:link,
|
||||
.ui-state-active a:visited {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #f1a899;
|
||||
background: #fddfdf;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
filter:Alpha(Opacity=70); /* support: IE8 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
filter:Alpha(Opacity=35); /* support: IE8 */
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Corner radius */
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-left,
|
||||
.ui-corner-tl {
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-right,
|
||||
.ui-corner-tr {
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-left,
|
||||
.ui-corner-bl {
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-right,
|
||||
.ui-corner-br {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
margin: 0px 0 0 0px;
|
||||
padding: 5px;
|
||||
background: #666666;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
border-radius: 8px;
|
||||
}
|
|
@ -3,14 +3,18 @@
|
|||
class TagsController < ApplicationController
|
||||
before_action :authentication_check
|
||||
|
||||
# GET /api/v1/tags
|
||||
def index
|
||||
list = Tag.list()
|
||||
|
||||
# return result
|
||||
render json: {
|
||||
tags: list,
|
||||
# GET /api/v1/tag_search?term=abc
|
||||
def search
|
||||
list = Tag::Item.where('name_downcase LIKE ?', "#{params[:term].strip.downcase}%").order('name ASC').limit(params[:limit] || 10)
|
||||
results = []
|
||||
list.each {|item|
|
||||
result = {
|
||||
id: item.id,
|
||||
value: item.name,
|
||||
}
|
||||
results.push result
|
||||
}
|
||||
render json: results
|
||||
end
|
||||
|
||||
# GET /api/v1/tags
|
||||
|
@ -31,7 +35,7 @@ class TagsController < ApplicationController
|
|||
success = Tag.tag_add(
|
||||
object: params[:object],
|
||||
o_id: params[:o_id],
|
||||
item: params[:item],
|
||||
item: params[:item].strip,
|
||||
)
|
||||
if success
|
||||
render json: success, status: :created
|
||||
|
@ -45,7 +49,7 @@ class TagsController < ApplicationController
|
|||
success = Tag.tag_remove(
|
||||
object: params[:object],
|
||||
o_id: params[:o_id],
|
||||
item: params[:item],
|
||||
item: params[:item].strip,
|
||||
)
|
||||
if success
|
||||
render json: success, status: :created
|
||||
|
@ -54,4 +58,60 @@ class TagsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# GET /api/v1/tag_list
|
||||
def admin_list
|
||||
list = Tag::Item.order('name ASC').limit(params[:limit] || 1000)
|
||||
results = []
|
||||
list.each {|item|
|
||||
result = {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
count: Tag.where(tag_item_id: item.id).count
|
||||
}
|
||||
results.push result
|
||||
}
|
||||
render json: results
|
||||
end
|
||||
|
||||
# POST /api/v1/tag_list
|
||||
def admin_create
|
||||
return if deny_if_not_role('Admin')
|
||||
name = params[:name].strip
|
||||
if !Tag.tag_item_lookup(name)
|
||||
Tag::Item.create(name: name)
|
||||
end
|
||||
render json: {}
|
||||
end
|
||||
|
||||
# PUT /api/v1/tag_list/:id
|
||||
def admin_rename
|
||||
return if deny_if_not_role('Admin')
|
||||
name = params[:name].strip
|
||||
tag_item = Tag::Item.find(params[:id])
|
||||
existing_tag_id = Tag.tag_item_lookup(name)
|
||||
if existing_tag_id
|
||||
|
||||
# assign to already existing tag
|
||||
Tag.where(tag_item_id: tag_item.id).update_all(tag_item_id: existing_tag_id)
|
||||
|
||||
# delete not longer used tag
|
||||
tag_item.destroy
|
||||
|
||||
# update new tag name
|
||||
else
|
||||
tag_item.name = name
|
||||
tag_item.save
|
||||
end
|
||||
render json: {}
|
||||
end
|
||||
|
||||
# DELETE /api/v1/tag_list/:id
|
||||
def admin_delete
|
||||
return if deny_if_not_role('Admin')
|
||||
tag_item = Tag::Item.find(params[:id])
|
||||
Tag.where(tag_item_id: tag_item.id).destroy_all
|
||||
tag_item.destroy
|
||||
render json: {}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ class Tag < ApplicationModel
|
|||
|
||||
# return in duplicate
|
||||
current_tags = tag_list(data)
|
||||
return true if current_tags.include?(data[:item].downcase.strip)
|
||||
return true if current_tags.include?(data[:item].strip)
|
||||
|
||||
# create history
|
||||
Tag.create(
|
||||
|
@ -79,17 +79,16 @@ class Tag < ApplicationModel
|
|||
|
||||
def self.tag_item_lookup(name)
|
||||
|
||||
name = name.downcase
|
||||
|
||||
# use cache
|
||||
return @@cache_item[name] if @@cache_item[name]
|
||||
|
||||
# lookup
|
||||
tag_item = Tag::Item.find_by(name: name)
|
||||
if tag_item
|
||||
tag_items = Tag::Item.where(name: name)
|
||||
tag_items.each {|tag_item|
|
||||
next if tag_item.name != name
|
||||
@@cache_item[name] = tag_item.id
|
||||
return tag_item.id
|
||||
end
|
||||
}
|
||||
|
||||
# create
|
||||
tag_item = Tag::Item.create(name: name)
|
||||
|
@ -130,6 +129,12 @@ class Tag < ApplicationModel
|
|||
end
|
||||
|
||||
class Item < ActiveRecord::Base
|
||||
before_save :fill_namedowncase
|
||||
|
||||
def fill_namedowncase
|
||||
self.name_downcase = name.downcase
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
Zammad::Application.routes.draw do
|
||||
api_path = Rails.configuration.api_path
|
||||
|
||||
# links
|
||||
match api_path + '/tags', to: 'tags#list', via: :get
|
||||
match api_path + '/tags/add', to: 'tags#add', via: :get
|
||||
match api_path + '/tags/remove', to: 'tags#remove', via: :get
|
||||
match api_path + '/tag_search', to: 'tags#search', via: :get
|
||||
|
||||
match api_path + '/tag_list', to: 'tags#admin_list', via: :get
|
||||
match api_path + '/tag_list', to: 'tags#admin_create', via: :post
|
||||
match api_path + '/tag_list/:id', to: 'tags#admin_rename', via: :put
|
||||
match api_path + '/tag_list/:id', to: 'tags#admin_delete', via: :delete
|
||||
|
||||
end
|
||||
|
|
|
@ -244,9 +244,10 @@ class CreateBase < ActiveRecord::Migration
|
|||
|
||||
create_table :tag_items do |t|
|
||||
t.string :name, limit: 250, null: false
|
||||
t.string :name_downcase, limit: 250, null: false
|
||||
t.timestamps null: false
|
||||
end
|
||||
add_index :tag_items, [:name], unique: true
|
||||
add_index :tag_items, [:name_downcase]
|
||||
|
||||
create_table :recent_views do |t|
|
||||
t.references :recent_view_object, null: false
|
||||
|
|
35
db/migrate/20160602000001_update_tag.rb
Normal file
35
db/migrate/20160602000001_update_tag.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
class UpdateTag < ActiveRecord::Migration
|
||||
def up
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
remove_index :tag_items, column: [:name]
|
||||
add_column :tag_items, :name_downcase, :string, limit: 250
|
||||
add_index :tag_items, [:name_downcase]
|
||||
Tag.reset_column_information
|
||||
Tag::Item.all.each(&:save)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'New Tags',
|
||||
name: 'tag_new',
|
||||
area: 'Web::Base',
|
||||
description: 'Allow users to crate new tags.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'tag_new',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
state: true,
|
||||
frontend: true,
|
||||
)
|
||||
end
|
||||
end
|
23
db/seeds.rb
23
db/seeds.rb
|
@ -1513,6 +1513,29 @@ Setting.create_if_not_exists(
|
|||
frontend: false
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'New Tags',
|
||||
name: 'tag_new',
|
||||
area: 'Web::Base',
|
||||
description: 'Allow users to crate new tags.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'tag_new',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
state: true,
|
||||
frontend: true
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Default calendar Tickets subscriptions',
|
||||
name: 'defaults_calendar_subscriptions_tickets',
|
||||
|
|
|
@ -27,9 +27,7 @@ class AgentTicketActionLevel8Test < TestCase
|
|||
css: '.active .ticket-form-bottom .token-input',
|
||||
value: 'tag1, tag2',
|
||||
)
|
||||
sendkey(
|
||||
value: :tab,
|
||||
)
|
||||
sendkey(value: :tab)
|
||||
|
||||
# reload browser
|
||||
sleep 6
|
||||
|
@ -45,25 +43,14 @@ class AgentTicketActionLevel8Test < TestCase
|
|||
end
|
||||
|
||||
# verify tags
|
||||
tags = @browser.find_elements({ css: '.content.active .js-tag' })
|
||||
assert(tags)
|
||||
assert(tags[0])
|
||||
tag1_found = false
|
||||
tag2_found = false
|
||||
tags.each {|element|
|
||||
text = element.text
|
||||
if text == 'tag1'
|
||||
tag1_found = true
|
||||
assert(true, 'tag1 exists')
|
||||
elsif text == 'tag2'
|
||||
tag2_found = true
|
||||
assert(true, 'tag2 exists')
|
||||
else
|
||||
assert(false, "invalid tag '#{text}'")
|
||||
end
|
||||
tags_verify(
|
||||
tags: {
|
||||
'tag1' => true,
|
||||
'tag2' => true,
|
||||
'tag3' => false,
|
||||
'tag4' => false,
|
||||
}
|
||||
assert(tag1_found, 'tag1 exists')
|
||||
assert(tag2_found, 'tag2 exists')
|
||||
)
|
||||
|
||||
# set tag (by blur)
|
||||
ticket2 = ticket_create(
|
||||
|
@ -80,38 +67,22 @@ class AgentTicketActionLevel8Test < TestCase
|
|||
css: '.active .ticket-form-bottom .token-input',
|
||||
value: 'tag3, tag4',
|
||||
)
|
||||
click(
|
||||
css: '#global-search',
|
||||
)
|
||||
|
||||
click(
|
||||
css: '.active .newTicket button.js-submit',
|
||||
)
|
||||
click(css: '#global-search')
|
||||
click(css: '.active .newTicket button.js-submit')
|
||||
sleep 5
|
||||
if @browser.current_url !~ /#{Regexp.quote('#ticket/zoom/')}/
|
||||
raise 'Unable to create ticket!'
|
||||
end
|
||||
|
||||
# verify tags
|
||||
tags = @browser.find_elements({ css: '.content.active .js-tag' })
|
||||
assert(tags)
|
||||
assert(tags[0])
|
||||
tag3_found = false
|
||||
tag4_found = false
|
||||
tags.each {|element|
|
||||
text = element.text
|
||||
if text == 'tag3'
|
||||
tag3_found = true
|
||||
assert(true, 'tag 3 exists')
|
||||
elsif text == 'tag4'
|
||||
tag4_found = true
|
||||
assert(true, 'tag 4 exists')
|
||||
else
|
||||
assert(false, "invalid tag '#{text}'")
|
||||
end
|
||||
tags_verify(
|
||||
tags: {
|
||||
'tag1' => false,
|
||||
'tag2' => false,
|
||||
'tag3' => true,
|
||||
'tag4' => true,
|
||||
}
|
||||
assert(tag3_found, 'tag3 exists')
|
||||
assert(tag4_found, 'tag4 exists')
|
||||
)
|
||||
|
||||
ticket3 = ticket_create(
|
||||
data: {
|
||||
|
@ -175,82 +146,202 @@ class AgentTicketActionLevel8Test < TestCase
|
|||
sleep 0.5
|
||||
|
||||
# verify tags
|
||||
tags = @browser.find_elements({ css: '.content.active .js-tag' })
|
||||
assert(tags)
|
||||
assert(tags[0])
|
||||
tag1_found = false
|
||||
tag2_found = false
|
||||
tag3_found = false
|
||||
tag4_found = false
|
||||
tag5_found = false
|
||||
tags.each {|element|
|
||||
text = element.text
|
||||
if text == 'tag1'
|
||||
tag1_found = true
|
||||
assert(true, 'tag1 exists')
|
||||
elsif text == 'tag 2'
|
||||
tag2_found = true
|
||||
assert(true, 'tag 2 exists')
|
||||
elsif text == 'tag3'
|
||||
tag3_found = true
|
||||
assert(true, 'tag3 exists')
|
||||
elsif text == 'tag4'
|
||||
tag4_found = true
|
||||
assert(true, 'tag4 exists')
|
||||
elsif text == 'tag5'
|
||||
tag5_found = true
|
||||
assert(true, 'tag5 exists')
|
||||
else
|
||||
assert(false, "invalid tag '#{text}'")
|
||||
end
|
||||
tags_verify(
|
||||
tags: {
|
||||
'tag1' => true,
|
||||
'tag 2' => true,
|
||||
'tag2' => false,
|
||||
'tag3' => true,
|
||||
'tag4' => true,
|
||||
'tag5' => true,
|
||||
}
|
||||
assert(tag1_found, 'tag1 exists')
|
||||
assert(tag2_found, 'tag2 exists')
|
||||
assert(tag3_found, 'tag3 exists')
|
||||
assert(tag4_found, 'tag4 exists')
|
||||
assert(tag5_found, 'tag5 exists')
|
||||
)
|
||||
|
||||
# reload browser
|
||||
reload()
|
||||
sleep 2
|
||||
|
||||
# verify tags
|
||||
tags = @browser.find_elements({ css: '.content.active .js-tag' })
|
||||
assert(tags)
|
||||
assert(tags[0])
|
||||
tag1_found = false
|
||||
tag2_found = false
|
||||
tag3_found = false
|
||||
tag4_found = false
|
||||
tag5_found = false
|
||||
tags.each {|element|
|
||||
text = element.text
|
||||
if text == 'tag1'
|
||||
tag1_found = true
|
||||
assert(true, 'tag1 exists')
|
||||
elsif text == 'tag 2'
|
||||
tag2_found = true
|
||||
assert(true, 'tag 2 exists')
|
||||
elsif text == 'tag3'
|
||||
tag3_found = true
|
||||
assert(true, 'tag3 exists')
|
||||
elsif text == 'tag4'
|
||||
tag4_found = true
|
||||
assert(true, 'tag4 exists')
|
||||
elsif text == 'tag5'
|
||||
tag5_found = true
|
||||
assert(true, 'tag5 exists')
|
||||
else
|
||||
assert(false, "invalid tag '#{text}'")
|
||||
end
|
||||
tags_verify(
|
||||
tags: {
|
||||
'tag1' => true,
|
||||
'tag 2' => true,
|
||||
'tag2' => false,
|
||||
'tag3' => true,
|
||||
'tag4' => true,
|
||||
'tag5' => true,
|
||||
}
|
||||
assert(tag1_found, 'tag1 exists')
|
||||
assert(tag2_found, 'tag2 exists')
|
||||
assert(tag3_found, 'tag3 exists')
|
||||
assert(tag4_found, 'tag4 exists')
|
||||
assert(tag5_found, 'tag5 exists')
|
||||
)
|
||||
end
|
||||
|
||||
def test_b_link
|
||||
def test_b_tags
|
||||
tag_prefix = "tag#{rand(999_999_999)}"
|
||||
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
click(css: 'a[href="#manage"]')
|
||||
click(css: 'a[href="#manage/tags"]')
|
||||
switch(
|
||||
css: '#content .js-newTagSetting',
|
||||
type: 'off',
|
||||
)
|
||||
|
||||
set(
|
||||
css: '#content .js-create input[name="name"]',
|
||||
value: tag_prefix + ' A',
|
||||
)
|
||||
click(css: '#content .js-create .js-submit')
|
||||
set(
|
||||
css: '#content .js-create input[name="name"]',
|
||||
value: tag_prefix + ' a',
|
||||
)
|
||||
click(css: '#content .js-create .js-submit')
|
||||
set(
|
||||
css: '#content .js-create input[name="name"]',
|
||||
value: tag_prefix + ' B',
|
||||
)
|
||||
click(css: '#content .js-create .js-submit')
|
||||
set(
|
||||
css: '#content .js-create input[name="name"]',
|
||||
value: tag_prefix + ' C',
|
||||
)
|
||||
click(css: '#content .js-create .js-submit')
|
||||
|
||||
# set tag (by tab)
|
||||
ticket1 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: 'some subject 123äöü - tags no new 1',
|
||||
body: 'some body 123äöü - tags no new 1',
|
||||
},
|
||||
do_not_submit: true,
|
||||
)
|
||||
sleep 1
|
||||
set(
|
||||
css: '.active .ticket-form-bottom .token-input',
|
||||
value: "#{tag_prefix} A",
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
sleep 1
|
||||
set(
|
||||
css: '.active .ticket-form-bottom .token-input',
|
||||
value: "#{tag_prefix} a",
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
sleep 1
|
||||
set(
|
||||
css: '.active .ticket-form-bottom .token-input',
|
||||
value: "#{tag_prefix} B",
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
sleep 1
|
||||
set(
|
||||
css: '.active .ticket-form-bottom .token-input',
|
||||
value: 'NOT EXISTING',
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
sleep 1
|
||||
|
||||
click(
|
||||
css: '.active .newTicket button.js-submit',
|
||||
)
|
||||
sleep 5
|
||||
if @browser.current_url !~ /#{Regexp.quote('#ticket/zoom/')}/
|
||||
raise 'Unable to create ticket!'
|
||||
end
|
||||
|
||||
# verify tags
|
||||
tags_verify(
|
||||
tags: {
|
||||
"#{tag_prefix} A" => true,
|
||||
"#{tag_prefix} a" => true,
|
||||
"#{tag_prefix} B" => true,
|
||||
'NOT EXISTING' => false,
|
||||
}
|
||||
)
|
||||
|
||||
# new ticket with tags in zoom
|
||||
ticket1 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: 'some subject 123äöü - tags no new 2',
|
||||
body: 'some body 223äöü - tags no new 1',
|
||||
},
|
||||
)
|
||||
|
||||
click(css: '.active .sidebar .js-newTagLabel')
|
||||
set(
|
||||
css: '.active .sidebar .js-newTagInput',
|
||||
value: "#{tag_prefix} A",
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
click(css: '.active .sidebar .js-newTagLabel')
|
||||
set(
|
||||
css: '.active .sidebar .js-newTagInput',
|
||||
value: "#{tag_prefix} a",
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
click(css: '.active .sidebar .js-newTagLabel')
|
||||
set(
|
||||
css: '.active .sidebar .js-newTagInput',
|
||||
value: "#{tag_prefix} B",
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
click(css: '.active .sidebar .js-newTagLabel')
|
||||
set(
|
||||
css: '.active .sidebar .js-newTagInput',
|
||||
value: 'NOT EXISTING',
|
||||
)
|
||||
sleep 2
|
||||
sendkey(value: :tab)
|
||||
|
||||
# verify tags
|
||||
tags_verify(
|
||||
tags: {
|
||||
"#{tag_prefix} A" => true,
|
||||
"#{tag_prefix} a" => true,
|
||||
"#{tag_prefix} B" => true,
|
||||
'NOT EXISTING' => false,
|
||||
}
|
||||
)
|
||||
reload()
|
||||
sleep 2
|
||||
|
||||
# verify tags
|
||||
tags_verify(
|
||||
tags: {
|
||||
"#{tag_prefix} A" => true,
|
||||
"#{tag_prefix} a" => true,
|
||||
"#{tag_prefix} B" => true,
|
||||
'NOT EXISTING' => false,
|
||||
}
|
||||
)
|
||||
|
||||
click(css: 'a[href="#manage"]')
|
||||
click(css: 'a[href="#manage/tags"]')
|
||||
switch(
|
||||
css: '#content .js-newTagSetting',
|
||||
type: 'on',
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
def test_c_link
|
||||
|
||||
@browser = browser_instance
|
||||
login(
|
||||
|
|
|
@ -3019,6 +3019,47 @@ wait untill text in selector disabppears
|
|||
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
tags_verify(
|
||||
browser: browser2,
|
||||
tags: {
|
||||
'tag 1' => true,
|
||||
'tag 2' => true,
|
||||
'tag 3' => false,
|
||||
},
|
||||
)
|
||||
|
||||
=end
|
||||
|
||||
def tags_verify(params = {})
|
||||
switch_window_focus(params)
|
||||
log('tags_verify', params)
|
||||
|
||||
instance = params[:browser] || @browser
|
||||
|
||||
tags = instance.find_elements({ css: '.content.active .js-tag' })
|
||||
assert(tags)
|
||||
assert(tags[0])
|
||||
|
||||
tags_found = {}
|
||||
params[:tags].each {|key, _value|
|
||||
tags_found[key] = false
|
||||
}
|
||||
|
||||
tags.each {|element|
|
||||
text = element.text
|
||||
if tags_found.key?(text)
|
||||
tags_found[text] = true
|
||||
else
|
||||
assert(false, "tag exists but is not in check to verify '#{text}'")
|
||||
end
|
||||
}
|
||||
params[:tags].each {|key, value|
|
||||
assert_equal(value, tags_found[key], "tag '#{key}'")
|
||||
}
|
||||
end
|
||||
|
||||
def quote(string)
|
||||
string_quoted = string
|
||||
string_quoted.gsub!(/&/, '&')
|
||||
|
|
|
@ -39,6 +39,23 @@ class TagTest < ActiveSupport::TestCase
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
tag_add: {
|
||||
item: 'TAG2',
|
||||
object: 'Object1',
|
||||
o_id: 123,
|
||||
created_by_id: 1
|
||||
},
|
||||
verify: {
|
||||
object: 'Object1',
|
||||
items: {
|
||||
'tag1' => true,
|
||||
'tag2' => true,
|
||||
'TAG2' => true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
# test 2
|
||||
{
|
||||
tag_add: {
|
||||
|
@ -68,7 +85,7 @@ class TagTest < ActiveSupport::TestCase
|
|||
object: 'Object2',
|
||||
items: {
|
||||
'tagöäüß1' => true,
|
||||
'tagöäüß2' => true,
|
||||
'Tagöäüß2' => true,
|
||||
'tagöäüß3' => false,
|
||||
},
|
||||
},
|
||||
|
@ -87,6 +104,25 @@ class TagTest < ActiveSupport::TestCase
|
|||
items: {
|
||||
'tag1' => false,
|
||||
'tag2' => true,
|
||||
'TAG2' => true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
# test 5
|
||||
{
|
||||
tag_remove: {
|
||||
item: 'TAG2',
|
||||
object: 'Object1',
|
||||
o_id: 123,
|
||||
created_by_id: 1
|
||||
},
|
||||
verify: {
|
||||
object: 'Object1',
|
||||
items: {
|
||||
'tag1' => false,
|
||||
'tag2' => true,
|
||||
'TAG2' => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue