Fixes #2612 - Tag KB Answers
This commit is contained in:
parent
467bc03224
commit
dde8046470
43 changed files with 522 additions and 58 deletions
|
@ -244,6 +244,11 @@
|
||||||
"url": "",
|
"url": "",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"hashtag.svg": {
|
||||||
|
"author": "Felix Niklas",
|
||||||
|
"url": "",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"help.svg": {
|
"help.svg": {
|
||||||
"author": "Felix Niklas",
|
"author": "Felix Niklas",
|
||||||
"url": "",
|
"url": "",
|
||||||
|
|
|
@ -2,11 +2,15 @@ class App.KnowledgeBaseReaderController extends App.Controller
|
||||||
@extend App.PopoverProvidable
|
@extend App.PopoverProvidable
|
||||||
@registerPopovers 'Ticket'
|
@registerPopovers 'Ticket'
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click .js-tag': 'searchTag'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-answer-title': 'answerTitle'
|
'.js-answer-title': 'answerTitle'
|
||||||
'.js-answer-body': 'answerBody'
|
'.js-answer-body': 'answerBody'
|
||||||
'.js-answer-pagination': 'answerPagination'
|
'.js-answer-pagination': 'answerPagination'
|
||||||
'.js-answer-attachments': 'answerAttachments'
|
'.js-answer-attachments': 'answerAttachments'
|
||||||
|
'.js-answer-tags': 'answerTags'
|
||||||
'.js-answer-linked-tickets': 'answerLinkedTickets'
|
'.js-answer-linked-tickets': 'answerLinkedTickets'
|
||||||
'.js-answer-meta': 'answerMeta'
|
'.js-answer-meta': 'answerMeta'
|
||||||
|
|
||||||
|
@ -60,6 +64,7 @@ class App.KnowledgeBaseReaderController extends App.Controller
|
||||||
return
|
return
|
||||||
|
|
||||||
@renderAttachments(answer.attachments)
|
@renderAttachments(answer.attachments)
|
||||||
|
@renderTags(answer.tags)
|
||||||
@renderLinkedTickets(answer.translation(kb_locale.id)?.linked_tickets())
|
@renderLinkedTickets(answer.translation(kb_locale.id)?.linked_tickets())
|
||||||
|
|
||||||
paginator = new App.KnowledgeBaseReaderPagination(object: @object, kb_locale: kb_locale)
|
paginator = new App.KnowledgeBaseReaderPagination(object: @object, kb_locale: kb_locale)
|
||||||
|
@ -129,6 +134,11 @@ class App.KnowledgeBaseReaderController extends App.Controller
|
||||||
attachments: attachments
|
attachments: attachments
|
||||||
)
|
)
|
||||||
|
|
||||||
|
renderTags: (tags) ->
|
||||||
|
@answerTags.html App.view('knowledge_base/_reader_tags')(
|
||||||
|
tags: tags
|
||||||
|
)
|
||||||
|
|
||||||
renderLinkedTickets: (linked_tickets) ->
|
renderLinkedTickets: (linked_tickets) ->
|
||||||
@answerLinkedTickets.html App.view('knowledge_base/_reader_linked_tickets')(
|
@answerLinkedTickets.html App.view('knowledge_base/_reader_linked_tickets')(
|
||||||
tickets: linked_tickets
|
tickets: linked_tickets
|
||||||
|
@ -154,3 +164,8 @@ class App.KnowledgeBaseReaderController extends App.Controller
|
||||||
return
|
return
|
||||||
|
|
||||||
decodeURIComponent @parentController.lastParams.arguments
|
decodeURIComponent @parentController.lastParams.arguments
|
||||||
|
|
||||||
|
searchTag: (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
item = $(e.currentTarget).text()
|
||||||
|
App.GlobalSearchWidget.search(item, 'tags')
|
||||||
|
|
|
@ -15,7 +15,9 @@ class App.KnowledgeBaseSidebar extends App.Controller
|
||||||
true
|
true
|
||||||
|
|
||||||
rerender: ->
|
rerender: ->
|
||||||
|
@delay( =>
|
||||||
@show(@savedParams, @savedAction)
|
@show(@savedParams, @savedAction)
|
||||||
|
, 300, 'rerender')
|
||||||
|
|
||||||
contentActionClicked: (e) ->
|
contentActionClicked: (e) ->
|
||||||
# coffeelint: disable=indentation
|
# coffeelint: disable=indentation
|
||||||
|
@ -59,5 +61,6 @@ class App.KnowledgeBaseSidebar extends App.Controller
|
||||||
if object instanceof App.KnowledgeBaseAnswer
|
if object instanceof App.KnowledgeBaseAnswer
|
||||||
output.push App.KnowledgeBaseSidebarLinkedTickets
|
output.push App.KnowledgeBaseSidebarLinkedTickets
|
||||||
output.push App.KnowledgeBaseSidebarAttachments
|
output.push App.KnowledgeBaseSidebarAttachments
|
||||||
|
output.push App.KnowledgeBaseSidebarTags
|
||||||
|
|
||||||
output
|
output
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
class App.KnowledgeBaseSidebarTags extends App.Controller
|
||||||
|
className: 'sidebar-block'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
@widget = new App.WidgetTag(
|
||||||
|
el: @el
|
||||||
|
templateName: 'knowledge_base/sidebar/tags'
|
||||||
|
object_type: 'KnowledgeBaseAnswer'
|
||||||
|
object: @object
|
||||||
|
tags: @object.tags
|
||||||
|
)
|
|
@ -2,6 +2,7 @@ class App.WidgetTag extends App.Controller
|
||||||
editMode: false
|
editMode: false
|
||||||
pendingRefresh: false
|
pendingRefresh: false
|
||||||
possibleTags: {}
|
possibleTags: {}
|
||||||
|
templateName:'widget/tag'
|
||||||
elements:
|
elements:
|
||||||
'.js-newTagLabel': 'newTagLabel'
|
'.js-newTagLabel': 'newTagLabel'
|
||||||
'.js-newTagInput': 'newTagInput'
|
'.js-newTagInput': 'newTagInput'
|
||||||
|
@ -50,7 +51,7 @@ class App.WidgetTag extends App.Controller
|
||||||
render: =>
|
render: =>
|
||||||
return if @lastLocalTags && _.isEqual(@lastLocalTags, @localTags)
|
return if @lastLocalTags && _.isEqual(@lastLocalTags, @localTags)
|
||||||
@lastLocalTags = _.clone(@localTags)
|
@lastLocalTags = _.clone(@localTags)
|
||||||
@html App.view('widget/tag')(
|
@html App.view(@templateName)(
|
||||||
tags: @localTags || [],
|
tags: @localTags || [],
|
||||||
)
|
)
|
||||||
source = "#{App.Config.get('api_path')}/tag_search"
|
source = "#{App.Config.get('api_path')}/tag_search"
|
||||||
|
|
|
@ -45,6 +45,7 @@ InstanceMethods =
|
||||||
if @ instanceof App.KnowledgeBaseAnswer
|
if @ instanceof App.KnowledgeBaseAnswer
|
||||||
attrs.icon = 'knowledge-base-answer'
|
attrs.icon = 'knowledge-base-answer'
|
||||||
attrs.state = @can_be_published_state()
|
attrs.state = @can_be_published_state()
|
||||||
|
attrs.tags = @tags
|
||||||
|
|
||||||
if options.isEditor
|
if options.isEditor
|
||||||
attrs.editorOnly = !@is_internally_published(kb_locale)
|
attrs.editorOnly = !@is_internally_published(kb_locale)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<% if @tags?.length: %>
|
||||||
|
<div class="attachments attachments--list">
|
||||||
|
<%- @Icon('hashtag') %>
|
||||||
|
<div class="attachments-title">
|
||||||
|
<%- @T('Tags') %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="knowledge-base-article-tags--container">
|
||||||
|
<% for tag, i in @tags: %>
|
||||||
|
<a href="#" class="tag js-tag"><%= tag %></a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -16,6 +16,7 @@
|
||||||
<div class="knowledge-base-article-content richtext-content js-answer-body"></div>
|
<div class="knowledge-base-article-content richtext-content js-answer-body"></div>
|
||||||
<div class="knowledge-base-article-attachments js-answer-attachments"></div>
|
<div class="knowledge-base-article-attachments js-answer-attachments"></div>
|
||||||
<div class="knowledge-base-article-linked-tickets js-answer-linked-tickets"></div>
|
<div class="knowledge-base-article-linked-tickets js-answer-linked-tickets"></div>
|
||||||
|
<div class="knowledge-base-article-tags js-answer-tags"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="js-answer-pagination"></div>
|
<div class="js-answer-pagination"></div>
|
||||||
|
|
|
@ -4,15 +4,12 @@
|
||||||
|
|
||||||
<div class="dropContainer">
|
<div class="dropContainer">
|
||||||
<% if @attachments.length > 0: %>
|
<% if @attachments.length > 0: %>
|
||||||
<ol class="tasks tasks--standalone">
|
<ol class="list list--sidebar">
|
||||||
<% for attachment in @attachments: %>
|
<% for attachment in @attachments: %>
|
||||||
<li class="task">
|
<li class="list-item">
|
||||||
<div class="task-text">
|
<a class="list-item-name" target=_blank href="<%= attachment.url %>">
|
||||||
<a class="name" target=_blank href="<%= attachment.url %>">
|
|
||||||
<%= attachment.filename %>
|
<%= attachment.filename %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="list-item-delete js-delete" data-object-id="<%= attachment.id %>" href="#">
|
<a class="list-item-delete js-delete" data-object-id="<%= attachment.id %>" href="#">
|
||||||
<%- @Icon('diagonal-cross') %>
|
<%- @Icon('diagonal-cross') %>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<div class="sidebar-block-header">
|
||||||
|
<h2><%- @T('Tags') %></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if @tags.length > 0: %>
|
||||||
|
<ol class="list list--sidebar">
|
||||||
|
<% for elem in @tags: %>
|
||||||
|
<li class="list-item" data-id="<%= elem %>">
|
||||||
|
<a class="list-item-name js-tag"><%= elem %></a>
|
||||||
|
<div class="list-item-delete js-delete" data-type="remove">
|
||||||
|
<%- @Icon('diagonal-cross') %>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ol>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<a class="sidebar-block-button text-muted js-newTagLabel" href="#">
|
||||||
|
+ <%- @T('Add Tag') %>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<form class="ui-front">
|
||||||
|
<input type="text" name="new_tag" class="hide js-newTagInput" autocomplete="off">
|
||||||
|
</form>
|
|
@ -35,6 +35,13 @@
|
||||||
<label><%- @T('Language') %></label>
|
<label><%- @T('Language') %></label>
|
||||||
<%= App.KnowledgeBaseLocale.localeFor(@object).systemLocale().name %>
|
<%= App.KnowledgeBaseLocale.localeFor(@object).systemLocale().name %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% if !_.isEmpty(@object.parent().tags): %>
|
||||||
|
<div class="column">
|
||||||
|
<label><%- @T('Tags') %></label>
|
||||||
|
<%= @object.parent().tags.map((elem) -> "##{elem}").join(' ') %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -74,15 +74,17 @@
|
||||||
this.el.classList.add('result')
|
this.el.classList.add('result')
|
||||||
this.el.innerHTML = this.constructor.template
|
this.el.innerHTML = this.constructor.template
|
||||||
|
|
||||||
this.setTitle(data.title)
|
this.setTitle(data.title, data.tags)
|
||||||
this.setSubtitle(data.subtitle)
|
this.setSubtitle(data.subtitle)
|
||||||
this.setPreview(data.body)
|
this.setPreview(data.body)
|
||||||
this.setURL(data.url)
|
this.setURL(data.url)
|
||||||
this.setIcon(data.icon, data.type)
|
this.setIcon(data.icon, data.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setTitle = function(text) {
|
this.setTitle = function(text, tags) {
|
||||||
this.el.querySelector('.result-title').innerHTML = text || ''
|
var title = text || ''
|
||||||
|
|
||||||
|
this.el.querySelector('.result-title').innerHTML = title
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setSubtitle = function(text) {
|
this.setSubtitle = function(text) {
|
||||||
|
|
|
@ -455,6 +455,12 @@ b {
|
||||||
.main--categories {
|
.main--categories {
|
||||||
h1 {
|
h1 {
|
||||||
color: $dark-color;
|
color: $dark-color;
|
||||||
|
|
||||||
|
.icon-hashtag {
|
||||||
|
fill: hsl(208,13%,81%);
|
||||||
|
width: .7em;
|
||||||
|
height: .7em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,6 +572,11 @@ b {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|
||||||
|
&-hashtag {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
&-knowledge-base {
|
&-knowledge-base {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
@ -627,6 +638,8 @@ b {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sections--list {
|
.sections--list {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
@ -948,9 +961,13 @@ b {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachments {
|
.article-tags {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-accessories {
|
||||||
padding: 2rem 0 2rem 4rem;
|
padding: 2rem 0 2rem 4rem;
|
||||||
margin: 2rem 0 !important;
|
margin: 2rem 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
border-top: 1px solid $border;
|
border-top: 1px solid $border;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -959,7 +976,7 @@ b {
|
||||||
padding-left: 2.8rem;
|
padding-left: 2.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-paperclip {
|
.icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 1.2rem;
|
left: 1.2rem;
|
||||||
top: 2rem;
|
top: 2rem;
|
||||||
|
@ -979,6 +996,30 @@ b {
|
||||||
color: $light-color;
|
color: $light-color;
|
||||||
padding: 0 .8rem .2rem;
|
padding: 0 .8rem .2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: .2rem .6rem 0;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
margin: .2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-size: .8em;
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 2px 12px 1px;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment {
|
.attachment {
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
.icon-gitlab-logo { width: 24px; height: 24px; }
|
.icon-gitlab-logo { width: 24px; height: 24px; }
|
||||||
.icon-google-button { width: 29px; height: 24px; }
|
.icon-google-button { width: 29px; height: 24px; }
|
||||||
.icon-group { width: 24px; height: 24px; }
|
.icon-group { width: 24px; height: 24px; }
|
||||||
|
.icon-hashtag { width: 28px; height: 28px; }
|
||||||
.icon-help { width: 16px; height: 16px; }
|
.icon-help { width: 16px; height: 16px; }
|
||||||
.icon-horizontal-rule { width: 12px; height: 12px; }
|
.icon-horizontal-rule { width: 12px; height: 12px; }
|
||||||
.icon-important { width: 16px; height: 16px; }
|
.icon-important { width: 16px; height: 16px; }
|
||||||
|
|
|
@ -5168,6 +5168,12 @@ footer {
|
||||||
.btn-list {
|
.btn-list {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-button {
|
||||||
|
.sidebar-block-header + &.text-muted {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-git-issue-delete {
|
.sidebar-git-issue-delete {
|
||||||
|
@ -6608,8 +6614,7 @@ footer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachments .icon-paperclip,
|
.attachments > .icon:first-child {
|
||||||
.attachments .icon-overviews {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 33px;
|
left: 33px;
|
||||||
top: 27px;
|
top: 27px;
|
||||||
|
@ -7610,6 +7615,18 @@ footer {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: #0F94D6;
|
||||||
|
color: white;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 2px 12px 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: hsl(200deg, 87%, 34%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.userNotifications label + .btn {
|
.userNotifications label + .btn {
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
@ -12929,6 +12946,7 @@ span.is-disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
&-attachments,
|
&-attachments,
|
||||||
|
&-tags,
|
||||||
&-linked-tickets {
|
&-linked-tickets {
|
||||||
margin: 0 -30px;
|
margin: 0 -30px;
|
||||||
|
|
||||||
|
@ -12941,6 +12959,16 @@ span.is-disabled {
|
||||||
@include bidi-style(margin-left, 7px, margin-right, 0);
|
@include bidi-style(margin-left, 7px, margin-right, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-tags--container {
|
||||||
|
padding: 6px 5px 11px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-nav {
|
&-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
13
app/controllers/knowledge_base/public/tags_controller.rb
Normal file
13
app/controllers/knowledge_base/public/tags_controller.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
class KnowledgeBase::Public::TagsController < KnowledgeBase::Public::BaseController
|
||||||
|
def show
|
||||||
|
@object = [:tag, params[:tag]]
|
||||||
|
|
||||||
|
all_tagged = KnowledgeBase::Answer.tag_objects(params[:tag])
|
||||||
|
|
||||||
|
@answers = policy_scope(all_tagged)
|
||||||
|
.localed(system_locale_via_uri)
|
||||||
|
.sorted
|
||||||
|
end
|
||||||
|
end
|
|
@ -100,7 +100,8 @@ class KnowledgeBase::SearchController < ApplicationController
|
||||||
url: url,
|
url: url,
|
||||||
title: meta.dig(:highlight, 'title')&.first || object.title,
|
title: meta.dig(:highlight, 'title')&.first || object.title,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
body: meta.dig(:highlight, 'content.body')&.first || strip_tags(object.content.body).truncate(100)
|
body: meta.dig(:highlight, 'content.body')&.first || strip_tags(object.content.body).truncate(100),
|
||||||
|
tags: object.answer.tag_list
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
module KnowledgeBaseBreadcrumbHelper
|
module KnowledgeBaseBreadcrumbHelper
|
||||||
def render_breadcrumb_if_needed(knowledge_base, object, alternative)
|
def render_breadcrumb_if_needed(knowledge_base, object, alternative)
|
||||||
objects = calculate_breadcrumb_path(object, alternative)
|
objects = if object.is_a? Array
|
||||||
|
calculate_breadcrumb_nonpath(object)
|
||||||
|
else
|
||||||
|
calculate_breadcrumb_path(object, alternative)
|
||||||
|
end
|
||||||
|
|
||||||
return if objects.empty?
|
return if objects.empty?
|
||||||
|
|
||||||
|
@ -25,6 +29,10 @@ module KnowledgeBaseBreadcrumbHelper
|
||||||
objects + [last].compact
|
objects + [last].compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def calculate_breadcrumb_nonpath(object)
|
||||||
|
[object]
|
||||||
|
end
|
||||||
|
|
||||||
def calculate_breadcrumb_to_category(category)
|
def calculate_breadcrumb_to_category(category)
|
||||||
return [] if category.blank?
|
return [] if category.blank?
|
||||||
|
|
||||||
|
@ -53,6 +61,8 @@ module KnowledgeBaseBreadcrumbHelper
|
||||||
case object
|
case object
|
||||||
when HasTranslations
|
when HasTranslations
|
||||||
object.translation.title
|
object.translation.title
|
||||||
|
when Array
|
||||||
|
object[1]
|
||||||
else
|
else
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
|
|
|
@ -73,7 +73,7 @@ module KnowledgeBaseHelper
|
||||||
def kb_public_system_path(*objects)
|
def kb_public_system_path(*objects)
|
||||||
objects
|
objects
|
||||||
.compact
|
.compact
|
||||||
.map { |elem| elem.translation.to_param }
|
.map { |elem| elem.is_a?(HasTranslations) ? elem.translation.to_param : elem }
|
||||||
.unshift(help_root_path)
|
.unshift(help_root_path)
|
||||||
.join('/')
|
.join('/')
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,8 @@ module KnowledgeBaseIconHelper
|
||||||
icon 'knowledge-base-answer'
|
icon 'knowledge-base-answer'
|
||||||
when KnowledgeBase
|
when KnowledgeBase
|
||||||
icon 'knowledge-base'
|
icon 'knowledge-base'
|
||||||
|
when Array
|
||||||
|
icon 'hashtag' # object[0] override while tag icon is available
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,22 @@ module KnowledgeBasePublicPageTitleHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def kb_public_page_title_suffix(item, exception)
|
def kb_public_page_title_suffix(item, exception)
|
||||||
|
case item
|
||||||
|
when HasTranslations
|
||||||
return item&.translation&.title if exception.blank?
|
return item&.translation&.title if exception.blank?
|
||||||
|
|
||||||
suffix = case exception
|
zt kb_public_page_title_suffix_exception(exception)
|
||||||
|
when String
|
||||||
|
item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def kb_public_page_title_suffix_exception(exception)
|
||||||
|
case exception
|
||||||
when :not_found
|
when :not_found
|
||||||
'Not Found'
|
'Not Found'
|
||||||
when :alternatives
|
when :alternatives
|
||||||
'Alternative Translations'
|
'Alternative Translations'
|
||||||
end
|
end
|
||||||
|
|
||||||
zt(suffix)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,4 +34,14 @@ module KnowledgeBaseTopBarHelper
|
||||||
'Published'
|
'Published'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_top_bar_if_needed(object, knowledge_base)
|
||||||
|
return if !policy(:knowledge_base).edit?
|
||||||
|
|
||||||
|
editable = object || knowledge_base
|
||||||
|
|
||||||
|
return if !editable.is_a? HasTranslations
|
||||||
|
|
||||||
|
render 'knowledge_base/public/top_banner', object: editable
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class KnowledgeBase::Answer < ApplicationModel
|
class KnowledgeBase::Answer < ApplicationModel
|
||||||
include HasTranslations
|
include HasTranslations
|
||||||
include HasAgentAllowedParams
|
include HasAgentAllowedParams
|
||||||
|
include HasTags
|
||||||
include CanBePublished
|
include CanBePublished
|
||||||
include ChecksKbClientNotification
|
include ChecksKbClientNotification
|
||||||
include CanCloneAttachments
|
include CanCloneAttachments
|
||||||
|
@ -33,6 +34,7 @@ class KnowledgeBase::Answer < ApplicationModel
|
||||||
attrs = super
|
attrs = super
|
||||||
|
|
||||||
attrs[:attachments] = attachments_sorted.map { |elem| self.class.attachment_to_hash(elem) }
|
attrs[:attachments] = attachments_sorted.map { |elem| self.class.attachment_to_hash(elem) }
|
||||||
|
attrs[:tags] = tag_list
|
||||||
|
|
||||||
Cache.write(key, attrs)
|
Cache.write(key, attrs)
|
||||||
|
|
||||||
|
@ -112,6 +114,11 @@ class KnowledgeBase::Answer < ApplicationModel
|
||||||
end
|
end
|
||||||
before_save :reordering_callback
|
before_save :reordering_callback
|
||||||
|
|
||||||
|
def touch_translations
|
||||||
|
translations.each(&:touch) # move to #touch_all when migrationg to Rails 6
|
||||||
|
end
|
||||||
|
after_touch :touch_translations
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def attachment_to_hash(attachment)
|
def attachment_to_hash(attachment)
|
||||||
url = Rails.application.routes.url_helpers.attachment_path(attachment.id)
|
url = Rails.application.routes.url_helpers.attachment_path(attachment.id)
|
||||||
|
|
|
@ -56,12 +56,13 @@ class KnowledgeBase::Answer::Translation < ApplicationModel
|
||||||
def search_index_attribute_lookup(include_references: true)
|
def search_index_attribute_lookup(include_references: true)
|
||||||
attrs = super
|
attrs = super
|
||||||
|
|
||||||
attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title']
|
attrs.merge({
|
||||||
attrs['content'] = content.search_index_attribute_lookup if content
|
title: ActionController::Base.helpers.strip_tags(attrs['title']),
|
||||||
attrs['scope_id'] = answer.category_id
|
content: content&.search_index_attribute_lookup,
|
||||||
attrs['attachment'] = answer.attachments_for_search_index_attribute_lookup
|
scope_id: answer.category_id,
|
||||||
|
attachment: answer.attachments_for_search_index_attribute_lookup,
|
||||||
attrs
|
tags: answer.tag_list
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def linked_references
|
def linked_references
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
color: <%= knowledge_base.color_highlight %>;
|
color: <%= knowledge_base.color_highlight %>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
background: <%= knowledge_base.color_highlight %>;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background-color: <%= knowledge_base.color_header %>;
|
background-color: <%= knowledge_base.color_header %>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@ data-available-locales='<%= @object_locales.map(&:locale).join(',') %>'>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if (attachments = @object.attachments_sorted) && attachments.present? %>
|
<% if (attachments = @object.attachments_sorted) && attachments.present? %>
|
||||||
<div class="attachments">
|
<div class="attachments article-accessories">
|
||||||
<%= icon 'paperclip' %>
|
<%= icon 'paperclip' %>
|
||||||
<div class="attachments-title"><%= zt('Attached Files') %></div>
|
<div class="article-accessories-title"><%= zt('Attached Files') %></div>
|
||||||
<% attachments.each do |attachment| %>
|
<% attachments.each do |attachment| %>
|
||||||
<%= link_to custom_path_if_needed(attachment_path(attachment), @knowledge_base), class: 'attachment', download: true do %>
|
<%= link_to custom_path_if_needed(attachment_path(attachment), @knowledge_base), class: 'attachment', download: true do %>
|
||||||
<span class="attachment-name u-highlight"><%= attachment.filename %></span>
|
<span class="attachment-name u-highlight"><%= attachment.filename %></span>
|
||||||
|
@ -23,6 +23,18 @@ data-available-locales='<%= @object_locales.map(&:locale).join(',') %>'>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if (tags = @object.tag_list) && tags.present? %>
|
||||||
|
<div class="tags article-accessories">
|
||||||
|
<%= icon 'hashtag' %>
|
||||||
|
<div class="article-accessories-title"><%= zt('Tags') %></div>
|
||||||
|
<div class="tags-content">
|
||||||
|
<% tags.each do |tag| %>
|
||||||
|
<%= link_to tag, custom_path_if_needed(help_tag_path(tag, locale: params[:locale]), @knowledge_base), class: 'tag' %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<%= link_to custom_path_if_needed help_answer_path(answer.category.translation, answer.translation, locale: params[:locale]), @knowledge_base do %>
|
<li class="section <%= visibility_class_name(answer) %>">
|
||||||
|
<%= link_to custom_path_if_needed help_answer_path(answer.category.translation, answer.translation, locale: params[:locale]), @knowledge_base do %>
|
||||||
<span class="section-inner">
|
<span class="section-inner">
|
||||||
<%= icon('knowledge-base-answer') %>
|
<%= icon('knowledge-base-answer') %>
|
||||||
<%= answer.translation.title %> <%= visibility_note(answer) %>
|
<%= answer.translation.title %>
|
||||||
|
<%= visibility_note(answer) %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</li>
|
||||||
|
|
|
@ -33,9 +33,7 @@
|
||||||
<% if @answers&.present? %>
|
<% if @answers&.present? %>
|
||||||
<ul class="sections sections--list" data-less-than-four="<%= @answers.length < 4 %>">
|
<ul class="sections sections--list" data-less-than-four="<%= @answers.length < 4 %>">
|
||||||
<% @answers.each do |translation| %>
|
<% @answers.each do |translation| %>
|
||||||
<li class="section <%= visibility_class_name(translation) %>">
|
|
||||||
<%= render 'answer', { answer: translation } %>
|
<%= render 'answer', { answer: translation } %>
|
||||||
</li>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
19
app/views/knowledge_base/public/tags/show.html.erb
Normal file
19
app/views/knowledge_base/public/tags/show.html.erb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<main class="main main--categories">
|
||||||
|
<div class="container">
|
||||||
|
<h1>
|
||||||
|
<%= icon 'hashtag' %> <%= @object[1] %>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<% if @answers&.present? %>
|
||||||
|
<ul class="sections sections--list" data-less-than-four="<%= @answers.length < 4 %>">
|
||||||
|
<% @answers.each do |translation| %>
|
||||||
|
<%= render 'knowledge_base/public/categories/answer', { answer: translation } %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% else %>
|
||||||
|
<div class="sections-empty">
|
||||||
|
<%= zt('No content to show') %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</main>
|
|
@ -16,7 +16,7 @@
|
||||||
<%= canonical_link_tag @knowledge_base, @category, @object %>
|
<%= canonical_link_tag @knowledge_base, @category, @object %>
|
||||||
|
|
||||||
<div class="wrapper js-wrapper">
|
<div class="wrapper js-wrapper">
|
||||||
<%= render 'knowledge_base/public/top_banner', object: @object || @knowledge_base if policy(:knowledge_base).edit? %>
|
<%= render_top_bar_if_needed @object, @knowledge_base %>
|
||||||
|
|
||||||
<header class="header js-header">
|
<header class="header js-header">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
|
@ -68,6 +68,8 @@ Zammad::Application.routes.draw do
|
||||||
get '', to: 'knowledge_base/public/categories#forward_root', as: :help_no_locale
|
get '', to: 'knowledge_base/public/categories#forward_root', as: :help_no_locale
|
||||||
get ':locale', to: 'knowledge_base/public/categories#index', as: :help_root
|
get ':locale', to: 'knowledge_base/public/categories#index', as: :help_root
|
||||||
|
|
||||||
|
get ':locale/tag/:tag', to: 'knowledge_base/public/tags#show', as: :help_tag
|
||||||
|
|
||||||
get ':locale/:category', to: 'knowledge_base/public/categories#show', as: :help_category
|
get ':locale/:category', to: 'knowledge_base/public/categories#show', as: :help_category
|
||||||
get ':locale/:category/:answer', to: 'knowledge_base/public/answers#show', as: :help_answer
|
get ':locale/:category/:answer', to: 'knowledge_base/public/answers#show', as: :help_answer
|
||||||
end
|
end
|
||||||
|
|
|
@ -173,7 +173,7 @@ class SearchKnowledgeBaseBackend
|
||||||
|
|
||||||
if @params.fetch(:highlight_enabled, true)
|
if @params.fetch(:highlight_enabled, true)
|
||||||
output[:highlight_fields_by_indexes] = {
|
output[:highlight_fields_by_indexes] = {
|
||||||
'KnowledgeBase::Answer::Translation': %w[title content.body attachment.content],
|
'KnowledgeBase::Answer::Translation': %w[title content.body attachment.content tags],
|
||||||
'KnowledgeBase::Category::Translation': %w[title],
|
'KnowledgeBase::Category::Translation': %w[title],
|
||||||
'KnowledgeBase::Translation': %w[title]
|
'KnowledgeBase::Translation': %w[title]
|
||||||
}
|
}
|
||||||
|
|
|
@ -350,6 +350,11 @@
|
||||||
group
|
group
|
||||||
</title>
|
</title>
|
||||||
<path d="M5.221 5.43c1.453 0 2.63-1.215 2.63-2.715C7.852 1.215 6.675 0 5.222 0c-1.453 0-2.63 1.216-2.63 2.715 0 1.5 1.177 2.715 2.63 2.715zm6.98 0c1.453 0 2.63-1.215 2.63-2.715 0-1.5-1.177-2.715-2.63-2.715C10.748 0 9.57 1.216 9.57 2.715c0 1.5 1.178 2.716 2.63 2.716zm5.982 0c1.453 0 2.631-1.215 2.631-2.715 0-1.5-1.178-2.715-2.63-2.715-1.454 0-2.632 1.216-2.632 2.715 0 1.5 1.178 2.716 2.631 2.716zm-2.408.863H8.273c-.641 0-1.161.536-1.161 1.197v6.846c0 .662.52 1.197 1.16 1.197h.358V24h6.65v-8.467h.495c.64 0 1.16-.535 1.16-1.197V7.49c0-.66-.52-1.197-1.16-1.197zm-10.3 8.043V7.49c0-.428.097-.83.26-1.197H1.86C1.22 6.293.7 6.829.7 7.49v6.846c0 .662.52 1.198 1.16 1.198h.358V24h4.775v-7.097a2.902 2.902 0 0 1-1.518-2.567zM22.29 6.293h-3.977c.163.366.26.77.26 1.197v6.846c0 1.172-.68 2.183-1.655 2.635V24h4.878v-8.467h.493c.641 0 1.161-.535 1.161-1.197V7.49c0-.66-.52-1.197-1.16-1.197z" fill-rule="evenodd"/>
|
<path d="M5.221 5.43c1.453 0 2.63-1.215 2.63-2.715C7.852 1.215 6.675 0 5.222 0c-1.453 0-2.63 1.216-2.63 2.715 0 1.5 1.177 2.715 2.63 2.715zm6.98 0c1.453 0 2.63-1.215 2.63-2.715 0-1.5-1.177-2.715-2.63-2.715C10.748 0 9.57 1.216 9.57 2.715c0 1.5 1.178 2.716 2.63 2.716zm5.982 0c1.453 0 2.631-1.215 2.631-2.715 0-1.5-1.178-2.715-2.63-2.715-1.454 0-2.632 1.216-2.632 2.715 0 1.5 1.178 2.716 2.631 2.716zm-2.408.863H8.273c-.641 0-1.161.536-1.161 1.197v6.846c0 .662.52 1.197 1.16 1.197h.358V24h6.65v-8.467h.495c.64 0 1.16-.535 1.16-1.197V7.49c0-.66-.52-1.197-1.16-1.197zm-10.3 8.043V7.49c0-.428.097-.83.26-1.197H1.86C1.22 6.293.7 6.829.7 7.49v6.846c0 .662.52 1.198 1.16 1.198h.358V24h4.775v-7.097a2.902 2.902 0 0 1-1.518-2.567zM22.29 6.293h-3.977c.163.366.26.77.26 1.197v6.846c0 1.172-.68 2.183-1.655 2.635V24h4.878v-8.467h.493c.641 0 1.161-.535 1.161-1.197V7.49c0-.66-.52-1.197-1.16-1.197z" fill-rule="evenodd"/>
|
||||||
|
</symbol><symbol id="icon-hashtag" viewBox="0 0 28 28">
|
||||||
|
<title>
|
||||||
|
hashtag
|
||||||
|
</title>
|
||||||
|
<path d="M12.5 1l-.78 6.5h5.5L18 1h3.5l-.78 6.5H26V11h-5.7l-.66 5.5H25V20h-5.78l-.72 6H15l.72-6h-5.5l-.72 6H6l.72-6H2v-3.5h5.14L7.8 11H3V7.5h5.22L9 1h3.5zm4.3 10h-5.5l-.66 5.5h5.5l.66-5.5z" fill-rule="evenodd"/>
|
||||||
</symbol><symbol id="icon-help" viewBox="0 0 16 16">
|
</symbol><symbol id="icon-help" viewBox="0 0 16 16">
|
||||||
<title>
|
<title>
|
||||||
help
|
help
|
||||||
|
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
9
public/assets/images/icons/hashtag.svg
Normal file
9
public/assets/images/icons/hashtag.svg
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
|
||||||
|
<title>hashtag</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="hashtag" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M12.5,1 L11.72,7.5 L17.22,7.5 L18,1 L21.5,1 L20.72,7.5 L26,7.5 L26,11 L20.3,11 L19.64,16.5 L25,16.5 L25,20 L19.22,20 L18.5,26 L15,26 L15.72,20 L10.22,20 L9.5,26 L6,26 L6.72,20 L2,20 L2,16.5 L7.14,16.5 L7.8,11 L3,11 L3,7.5 L8.22,7.5 L9,1 L12.5,1 Z M16.8,11 L11.3,11 L10.64,16.5 L16.14,16.5 L16.8,11 Z" id="Combined-Shape" fill="#50E3C2"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 774 B |
|
@ -43,6 +43,16 @@ FactoryBot.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :with_tag do
|
||||||
|
transient do
|
||||||
|
tag_names { %w[example_kb_tag] }
|
||||||
|
end
|
||||||
|
|
||||||
|
after(:create) do |answer, context|
|
||||||
|
context.tag_names.each { |tag| answer.tag_add tag }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
trait :with_attachment do
|
trait :with_attachment do
|
||||||
transient do
|
transient do
|
||||||
attachment { File.open('spec/fixtures/upload/hello_world.txt') }
|
attachment { File.open('spec/fixtures/upload/hello_world.txt') }
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
require 'models/concerns/checks_kb_client_notification_examples'
|
require 'models/concerns/checks_kb_client_notification_examples'
|
||||||
|
require 'models/concerns/has_tags_examples'
|
||||||
require 'models/contexts/factory_context'
|
require 'models/contexts/factory_context'
|
||||||
|
|
||||||
RSpec.describe KnowledgeBase::Answer, type: :model, current_user_id: 1 do
|
RSpec.describe KnowledgeBase::Answer, type: :model, current_user_id: 1 do
|
||||||
subject!(:kb_answer) { create(:knowledge_base_answer) }
|
subject!(:kb_answer) { create(:knowledge_base_answer) }
|
||||||
|
|
||||||
|
it_behaves_like 'HasTags'
|
||||||
|
|
||||||
include_context 'factory'
|
include_context 'factory'
|
||||||
|
|
||||||
it_behaves_like 'ChecksKbClientNotification'
|
it_behaves_like 'ChecksKbClientNotification'
|
||||||
|
|
|
@ -9,6 +9,10 @@ RSpec.shared_context 'basic Knowledge Base', current_user_id: 1 do # rubocop:dis
|
||||||
knowledge_base.translation_primary.kb_locale
|
knowledge_base.translation_primary.kb_locale
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let :locale_name do
|
||||||
|
primary_locale.system_locale.locale
|
||||||
|
end
|
||||||
|
|
||||||
let :alternative_locale do
|
let :alternative_locale do
|
||||||
create(:knowledge_base_locale, knowledge_base: knowledge_base, system_locale: Locale.find_by(locale: 'lt'))
|
create(:knowledge_base_locale, knowledge_base: knowledge_base, system_locale: Locale.find_by(locale: 'lt'))
|
||||||
end
|
end
|
||||||
|
@ -29,6 +33,14 @@ RSpec.shared_context 'basic Knowledge Base', current_user_id: 1 do # rubocop:dis
|
||||||
create(:knowledge_base_answer, :published, :with_video, category: category)
|
create(:knowledge_base_answer, :published, :with_video, category: category)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let :published_answer_with_tag do
|
||||||
|
create(:knowledge_base_answer, :published, :with_tag, tag_names: [published_answer_tag_name], category: category)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:published_answer_tag_name) do
|
||||||
|
'example_kb_tag'
|
||||||
|
end
|
||||||
|
|
||||||
let :internal_answer do
|
let :internal_answer do
|
||||||
create(:knowledge_base_answer, :internal, category: category)
|
create(:knowledge_base_answer, :internal, category: category)
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,4 +87,64 @@ RSpec.describe 'Knowledge Base Locale Answer Edit', type: :system do
|
||||||
expect(iframe['src']).to start_with('https://www.youtube.com/embed/')
|
expect(iframe['src']).to start_with('https://www.youtube.com/embed/')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'tags' do
|
||||||
|
before do
|
||||||
|
visit "#knowledge_base/#{knowledge_base.id}/locale/#{locale_name}/answer/#{published_answer_with_tag.id}/edit"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:new_tag_name) { 'capybara_kb_tag' }
|
||||||
|
|
||||||
|
it 'adds a new tag' do
|
||||||
|
within :active_content do
|
||||||
|
click '.js-newTagLabel'
|
||||||
|
|
||||||
|
elem = find('.js-newTagInput')
|
||||||
|
elem.fill_in with: new_tag_name
|
||||||
|
elem.send_keys :return
|
||||||
|
|
||||||
|
expect(page).to have_css('a.js-tag', text: new_tag_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'saves new tag to the database' do
|
||||||
|
within :active_content do
|
||||||
|
click '.js-newTagLabel'
|
||||||
|
|
||||||
|
elem = find('.js-newTagInput')
|
||||||
|
elem.fill_in with: new_tag_name
|
||||||
|
elem.send_keys :return
|
||||||
|
|
||||||
|
wait.until_exists { published_answer_with_tag.reload.tag_list.include? new_tag_name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows an existing tag' do
|
||||||
|
within :active_content do
|
||||||
|
expect(page).to have_css('a.js-tag', text: published_answer_tag_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes a tag' do
|
||||||
|
within :active_content do
|
||||||
|
click '.js-newTagLabel'
|
||||||
|
|
||||||
|
find('.list-item', text: published_answer_tag_name)
|
||||||
|
.find('.js-delete').click
|
||||||
|
|
||||||
|
expect(page).to have_no_css('a.js-tag', text: published_answer_tag_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes the tag from the database' do
|
||||||
|
within :active_content do
|
||||||
|
click '.js-newTagLabel'
|
||||||
|
|
||||||
|
find('.list-item', text: published_answer_tag_name)
|
||||||
|
.find('.js-delete').click
|
||||||
|
|
||||||
|
wait.until_exists { published_answer_with_tag.reload.tag_list.exclude? published_answer_tag_name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
51
spec/system/knowledge_base/locale/answer/read_spec.rb
Normal file
51
spec/system/knowledge_base/locale/answer/read_spec.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Knowledge Base Locale Answer Read', type: :system, authenticated_as: true do
|
||||||
|
include_context 'basic Knowledge Base'
|
||||||
|
|
||||||
|
describe 'tags' do
|
||||||
|
context 'when answer has tags' do
|
||||||
|
before do
|
||||||
|
visit "#knowledge_base/#{knowledge_base.id}/locale/#{locale_name}/answer/#{published_answer_with_tag.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has tags container' do
|
||||||
|
within :active_content do
|
||||||
|
expect(page).to have_css('.knowledge-base-article-tags--container')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows tag' do
|
||||||
|
within :active_content do
|
||||||
|
within '.knowledge-base-article-tags--container' do
|
||||||
|
expect(page).to have_css('a', text: published_answer_tag_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'opens search on clicking' do
|
||||||
|
within :active_content do
|
||||||
|
find('.knowledge-base-article-tags--container a', text: published_answer_tag_name).click
|
||||||
|
end
|
||||||
|
|
||||||
|
search_bar = find '#global-search'
|
||||||
|
|
||||||
|
expect(search_bar.value).to eq "tags:#{published_answer_tag_name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when answer has no tags' do
|
||||||
|
before do
|
||||||
|
visit "#knowledge_base/#{knowledge_base.id}/locale/#{locale_name}/answer/#{published_answer.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has no tags container' do
|
||||||
|
within :active_content do
|
||||||
|
expect(page).to have_no_css('.knowledge-base-article-tags--container')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,4 +18,20 @@ RSpec.describe 'Public Knowledge Base answer', type: :system, authenticated_as:
|
||||||
expect(iframe['src']).to start_with('https://www.youtube.com/embed/')
|
expect(iframe['src']).to start_with('https://www.youtube.com/embed/')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'tags' do
|
||||||
|
before do
|
||||||
|
visit help_answer_path(locale_name, category, published_answer_with_tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows an associated tag' do
|
||||||
|
expect(page).to have_css('.tags a', text: published_answer_tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'links to tag page' do
|
||||||
|
click '.tags a'
|
||||||
|
|
||||||
|
expect(current_url).to end_with help_tag_path(locale_name, published_answer_tag_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,6 +31,11 @@ RSpec.describe 'Public Knowledge Base canonical link', type: :system, current_us
|
||||||
visit help_answer_path(locale, published_answer.category, published_answer)
|
visit help_answer_path(locale, published_answer.category, published_answer)
|
||||||
expect(page).to have_canonical_url("#{prefix}/#{locale}/#{category_slug}/#{answer_slug}")
|
expect(page).to have_canonical_url("#{prefix}/#{locale}/#{category_slug}/#{answer_slug}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes canonical link on tag page' do
|
||||||
|
visit help_tag_path(locale, published_answer_tag_name)
|
||||||
|
expect(page).to have_canonical_url("#{prefix}/#{locale}/tag/#{published_answer_tag_name}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'core locations' do
|
shared_examples 'core locations' do
|
||||||
|
|
51
spec/system/knowledge_base_public/tag_spec.rb
Normal file
51
spec/system/knowledge_base_public/tag_spec.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Public Knowledge Base tag', type: :system, authenticated_as: false do
|
||||||
|
include_context 'basic Knowledge Base'
|
||||||
|
|
||||||
|
context 'when answer with the tag exists' do
|
||||||
|
before do
|
||||||
|
published_answer && published_answer_with_tag
|
||||||
|
|
||||||
|
visit help_tag_path(locale_name, published_answer_tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays tag name' do
|
||||||
|
expect(page).to have_css('h1', text: published_answer_tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'lists an answer with the tag' do
|
||||||
|
expect(page).to have_css('a', text: published_answer_with_tag.translations.first.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not list another answer' do
|
||||||
|
expect(page).to have_no_css('a', text: published_answer.translations.first.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not show empty placeholder' do
|
||||||
|
expect(page).to have_no_css('.sections-empty')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no answers with the tag exists' do
|
||||||
|
before do
|
||||||
|
published_answer
|
||||||
|
|
||||||
|
visit help_tag_path(locale_name, published_answer_tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows empty placeholder' do
|
||||||
|
expect(page).to have_css('.sections-empty')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows no links' do
|
||||||
|
expect(page).to have_no_css('.main a')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays tag name' do
|
||||||
|
expect(page).to have_css('h1', text: published_answer_tag_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue