]> source.dussan.org Git - gitea.git/commitdiff
Add 'Alt + click' feature to exclude labels (#8199)
authorjaqra <48099350+jaqra@users.noreply.github.com>
Wed, 23 Oct 2019 16:29:14 +0000 (19:29 +0300)
committerzeripath <art27@cantab.net>
Wed, 23 Oct 2019 16:29:14 +0000 (17:29 +0100)
Add 'Alt + click' and 'Alt +enter' feature to exclude particular labels on searching for issues.

models/issue.go
models/issue_label.go
options/locale/locale_en-US.ini
public/css/index.css
public/js/index.js
public/less/_repository.less
templates/repo/issue/list.tmpl

index 688a412d8c370865cabc7fa9708d835c31340bc7..f0069178911d0001bde20db293826f5a7afcb4d7 100644 (file)
@@ -1248,8 +1248,12 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
 
        if opts.LabelIDs != nil {
                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)
+                       }
                }
        }
 }
index 9efc7fd51f4d46097a9f520804543ec557dcd104..1fc873cfd44aedce4f07c1f068068034da092631 100644 (file)
@@ -72,6 +72,7 @@ type Label struct {
        IsChecked       bool   `xorm:"-"`
        QueryString     string `xorm:"-"`
        IsSelected      bool   `xorm:"-"`
+       IsExcluded      bool   `xorm:"-"`
 }
 
 // APIFormat converts a Label to the api.Label format
@@ -97,7 +98,10 @@ func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64)
        for _, s := range currentSelectedLabels {
                if s == label.ID {
                        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))
                }
        }
index eb38a777c82f0b02ec4e33fd8a185450af58fc92..60acab01783c882b88ab85283f8a3b1e7498edd3 100644 (file)
@@ -802,6 +802,7 @@ issues.delete_branch_at = `deleted branch <b>%s</b> %s`
 issues.open_tab = %d Open
 issues.close_tab = %d Closed
 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_milestone = Milestone
 issues.filter_milestone_no_select = All milestones
index 9292604422dd1758e33408bada932e053e0df958..e404c1fec636ff5484c4fd9ad48a78c578d50a64 100644 (file)
@@ -458,6 +458,8 @@ i.icon.centerlock{top:1.5em}
 .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.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 .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important}
 .repository .filter.menu .dropdown.item{margin:1px;padding-right:0}
index 90819677a5703c9307618eed285aaa0e09a2ff30..cf19bf71a03647e4d6cfed72b5de18eee0be93af 100644 (file)
@@ -3278,8 +3278,39 @@ function initIssueList() {
             },
 
             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) {
     const form = $(btn).closest("form");
index 33ee5761c40dc0348db0e74a4a981e879b94811b..48a1214c076be8c34df3d99afd45209c1a86bd2e 100644 (file)
             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 {
             margin-left: 0.9em;
         }
index e64cef2724ce879b364950e8925201e43bda7da5..9b354a6800bad1856cb9ab379ab983b87cd6c91f 100644 (file)
                        <div class="ten wide right aligned column">
                                <div class="ui secondary filter stackable menu labels">
                                        <!-- 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">
                                                        {{.i18n.Tr "repo.issues.filter_label"}}
                                                        <i class="dropdown icon"></i>
                                                </span>
                                                <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>
                                                        {{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}}
                                                </div>
                                        </div>