Add 'Alt + click' and 'Alt +enter' feature to exclude particular labels on searching for issues.tags/v1.11.0-rc1
if opts.LabelIDs != nil { | if opts.LabelIDs != nil { | ||||
for i, labelID := range opts.LabelIDs { | for i, labelID := range opts.LabelIDs { | ||||
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i), | |||||
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID)) | |||||
if labelID > 0 { | |||||
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i), | |||||
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID)) | |||||
} else { | |||||
sess.Where("issue.id not in (select issue_id from issue_label where label_id = ?)", -labelID) | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
IsChecked bool `xorm:"-"` | IsChecked bool `xorm:"-"` | ||||
QueryString string `xorm:"-"` | QueryString string `xorm:"-"` | ||||
IsSelected bool `xorm:"-"` | IsSelected bool `xorm:"-"` | ||||
IsExcluded bool `xorm:"-"` | |||||
} | } | ||||
// APIFormat converts a Label to the api.Label format | // APIFormat converts a Label to the api.Label format | ||||
for _, s := range currentSelectedLabels { | for _, s := range currentSelectedLabels { | ||||
if s == label.ID { | if s == label.ID { | ||||
labelSelected = true | labelSelected = true | ||||
} else if s > 0 { | |||||
} else if -s == label.ID { | |||||
labelSelected = true | |||||
label.IsExcluded = true | |||||
} else if s != 0 { | |||||
labelQuerySlice = append(labelQuerySlice, strconv.FormatInt(s, 10)) | labelQuerySlice = append(labelQuerySlice, strconv.FormatInt(s, 10)) | ||||
} | } | ||||
} | } |
issues.open_tab = %d Open | issues.open_tab = %d Open | ||||
issues.close_tab = %d Closed | issues.close_tab = %d Closed | ||||
issues.filter_label = Label | issues.filter_label = Label | ||||
issues.filter_label_exclude = `Use <code>alt</code> + <code>click/enter</code> to exclude labels` | |||||
issues.filter_label_no_select = All labels | issues.filter_label_no_select = All labels | ||||
issues.filter_milestone = Milestone | issues.filter_milestone = Milestone | ||||
issues.filter_milestone_no_select = All milestones | issues.filter_milestone_no_select = All milestones |
.repository .filter.menu .label.color{border-radius:3px;margin-left:15px;padding:0 8px} | .repository .filter.menu .label.color{border-radius:3px;margin-left:15px;padding:0 8px} | ||||
.repository .filter.menu .octicon{float:left;margin:5px -7px 0 -5px;width:16px} | .repository .filter.menu .octicon{float:left;margin:5px -7px 0 -5px;width:16px} | ||||
.repository .filter.menu.labels .octicon{margin:-2px -7px 0 -5px} | .repository .filter.menu.labels .octicon{margin:-2px -7px 0 -5px} | ||||
.repository .filter.menu.labels .label-filter .menu .info{display:inline-block;padding:9px 7px 7px 7px;text-align:center;border-bottom:1px solid #ccc;font-size:12px} | |||||
.repository .filter.menu.labels .label-filter .menu .info code{border:1px solid #ccc;border-radius:3px;padding:3px 2px 1px 2px;font-size:11px} | |||||
.repository .filter.menu .text{margin-left:.9em} | .repository .filter.menu .text{margin-left:.9em} | ||||
.repository .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important} | .repository .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important} | ||||
.repository .filter.menu .dropdown.item{margin:1px;padding-right:0} | .repository .filter.menu .dropdown.item{margin:1px;padding-right:0} |
}, | }, | ||||
fullTextSearch: true | fullTextSearch: true | ||||
}) | |||||
; | |||||
}); | |||||
$(".menu a.label-filter-item").each(function() { | |||||
$(this).click(function(e) { | |||||
if (e.altKey) { | |||||
const href = $(this).attr("href"); | |||||
const id = $(this).data("label-id"); | |||||
const regStr = "labels=(-?[0-9]+%2c)*(" + id + ")(%2c-?[0-9]+)*&"; | |||||
const newStr = "labels=$1-$2$3&"; | |||||
window.location = href.replace(new RegExp(regStr), newStr); | |||||
} | |||||
}); | |||||
}); | |||||
$(".menu .ui.dropdown.label-filter").keydown(function(e) { | |||||
if (e.altKey && e.keyCode == 13) { | |||||
const selectedItems = $(".menu .ui.dropdown.label-filter .menu .item.selected"); | |||||
if (selectedItems.length > 0) { | |||||
const item = $(selectedItems[0]); | |||||
const href = item.attr("href"); | |||||
const id = item.data("label-id"); | |||||
const regStr = "labels=(-?[0-9]+%2c)*(" + id + ")(%2c-?[0-9]+)*&"; | |||||
const newStr = "labels=$1-$2$3&"; | |||||
window.location = href.replace(new RegExp(regStr), newStr); | |||||
} | |||||
} | |||||
}); | |||||
} | } | ||||
function cancelCodeComment(btn) { | function cancelCodeComment(btn) { | ||||
const form = $(btn).closest("form"); | const form = $(btn).closest("form"); |
margin: -2px -7px 0 -5px; | margin: -2px -7px 0 -5px; | ||||
} | } | ||||
&.labels { | |||||
.label-filter .menu .info { | |||||
display: inline-block; | |||||
padding: 9px 7px 7px 7px; | |||||
text-align: center; | |||||
border-bottom: 1px solid #cccccc; | |||||
font-size: 12px; | |||||
code { | |||||
border: 1px solid #cccccc; | |||||
border-radius: 3px; | |||||
padding: 3px 2px 1px 2px; | |||||
font-size: 11px; | |||||
} | |||||
} | |||||
} | |||||
.text { | .text { | ||||
margin-left: 0.9em; | margin-left: 0.9em; | ||||
} | } |
<div class="ten wide right aligned column"> | <div class="ten wide right aligned column"> | ||||
<div class="ui secondary filter stackable menu labels"> | <div class="ui secondary filter stackable menu labels"> | ||||
<!-- Label --> | <!-- Label --> | ||||
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item" style="margin-left: auto"> | |||||
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item label-filter" style="margin-left: auto"> | |||||
<span class="text"> | <span class="text"> | ||||
{{.i18n.Tr "repo.issues.filter_label"}} | {{.i18n.Tr "repo.issues.filter_label"}} | ||||
<i class="dropdown icon"></i> | <i class="dropdown icon"></i> | ||||
</span> | </span> | ||||
<div class="menu"> | <div class="menu"> | ||||
<span class="info">{{.i18n.Tr "repo.issues.filter_label_exclude" | Safe}}</span> | |||||
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a> | <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a> | ||||
{{range .Labels}} | {{range .Labels}} | ||||
<a class="item has-emoji" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}"><span class="octicon {{if .IsSelected}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a> | |||||
<a class="item has-emoji label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" data-label-id="{{.ID}}"><span class="octicon {{if .IsExcluded}}octicon-circle-slash{{else if .IsSelected}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> |