diff --git a/.gitignore b/.gitignore index a6f4941..e1bf147 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .deps .cache +.cookies assets.go bugzini diff --git a/assets/css/main.css b/assets/css/main.css index 06b0413..87cd034 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -189,6 +189,7 @@ col.id { border: 1px solid #ddd; padding: 6px; border-radius: 6px 6px 0px 0px; + height: 24px; } #comment-author { @@ -220,6 +221,28 @@ col.id { padding-bottom: 18px; } +.comments-right-header { + float: right; + width: 90%; + width: calc(100% - 32px); +} + +.comments-author { + float: left; +} + +#comment-reply { + float:right; +} + +.bug-actions { + display: none; +} + +.bug-actions.shown { + display: block; +} + #bug-actions { display: none; } @@ -291,6 +314,10 @@ a { font-weight: bold; } +#bug-comment #comment-text .quotes { + color: #65379C; +} + #bug-comment.collapsed #comment-text { height: 0; opacity: 0; diff --git a/assets/index.html b/assets/index.html index 05d9258..ec76092 100644 --- a/assets/index.html +++ b/assets/index.html @@ -94,8 +94,17 @@
-
-
+
+
+
+
+
+
+
+ +
+
+
diff --git a/assets/js/app.js b/assets/js/app.js index 1f02968..92b0134 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -112,6 +112,7 @@ App.prototype._show_user = function(user) { this._user = user; this._show_add_comment(); + this._show_reply_buttons(); } App.prototype._hide_user = function() { @@ -126,6 +127,7 @@ App.prototype._hide_user = function() { this._user = null; this._hide_add_comment(); + this._hide_reply_buttons(); } App.prototype._init_current_user = function() { @@ -318,6 +320,30 @@ App.prototype._hide_show_add_comment = function() { } } +App.prototype._hide_reply_buttons = function() { + /* show reply button once logged */ + var replybtns = $$.queryAll('.bug-actions'); + replybtns.each(function(e){ + e.classList.remove('shown'); + }); +} + +App.prototype._show_reply_buttons = function() { + /* show reply button once logged */ + var replybtns = $$.queryAll('.bug-actions'); + replybtns.each(function(e){ + e.classList.add('shown'); + }); +} + +App.prototype._hide_show_reply_buttons = function() { + if (this._user) { + this._show_reply_buttons(); + } else { + this._hide_reply_buttons(); + } +} + App.prototype._render_bug = function(loading) { var hbug = $$.query('#bug'); @@ -330,6 +356,9 @@ App.prototype._render_bug = function(loading) { var hsum = hbug.querySelector('#bug-summary'); hsum.textContent = this._bug.summary; + var textarea = $$.query("#comment-text textarea"); + textarea.value = ''; + var templ = $$.query('template#bug-comment-template'); var hcomments = hbug.querySelector('#bug-comments') @@ -406,7 +435,45 @@ App.prototype._render_bug = function(loading) { templ.content.querySelector('#comment-date').textContent = this._date_for_display(d); templ.content.querySelector('#comment-text').textContent = c.text; + /* parsing and adding color and span quotes */ + var lines = c.text.split("\n"); + var newtext = ""; + lines.each(function(l){ + if (/Created an attachment \(id=\d+/.test(l)) { + attachmentid = l.match(/id=\d+/)[0]; + uri = "https://bugzilla.gnome.org/attachment.cgi?" + attachmentid; + l = l.replace( + /Created an attachment \(id=\d+/, + 'Created an attachment (' + attachmentid + '' + ); + } + + if (/^>.*$/.test(l)) { + newline = l.match(/^>.*$/); + quoted = '' + l + ''; + newtext = newtext + quoted + "\n"; + } else { + newtext = newtext + l + "\n"; + } + }); + templ.content.querySelector('#comment-text').innerHTML = newtext; + var clone = document.importNode(templ.content, true); + + var rbutton = clone.querySelector('#reply-comment'); + rbutton.comment_nr = i + 1; + rbutton.comment_text = c.text; + rbutton.addEventListener('click', (function() { + var lines = this.comment_text.split("\n"); + var comment = '(In reply to comment #' + this.comment_nr + ')' + "\n"; + lines.each(function(l){ + comment = comment + "> " + l + "\n"; + }); + comment = comment + "\n"; + $$.query("#comment-text textarea").value = comment; + $$.query("#comment-text textarea").focus(); + }).bind(rbutton)); + hcomments.appendChild(clone); } } @@ -433,8 +500,10 @@ App.prototype._render_bug = function(loading) { s.start(); this._hide_add_comment(); + this._hide_reply_buttons(); } else { this._hide_show_add_comment(); + this._hide_show_reply_buttons(); } } diff --git a/bug.go b/bug.go index e46e44a..20bab91 100644 --- a/bug.go +++ b/bug.go @@ -2,10 +2,11 @@ package main import ( "bugzilla" - "github.com/gorilla/mux" "net/http" "strconv" "time" + + "github.com/gorilla/mux" ) func BugGet(id int) (*bugzilla.Bug, error) { @@ -26,7 +27,9 @@ func BugGet(id int) (*bugzilla.Bug, error) { } cache.c.bugsMap[bug.Id] = &bug + cache.Save() + go SaveCookies() return &bug, nil } @@ -52,6 +55,8 @@ func BugHandler(w http.ResponseWriter, r *http.Request) { return } + go SaveCookies() + JsonResponse(w, *bug) } @@ -109,6 +114,8 @@ func BugCommentHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } + + go SaveCookies() } func BugCommentsHandler(w http.ResponseWriter, r *http.Request) { @@ -189,6 +196,8 @@ func BugCommentsHandler(w http.ResponseWriter, r *http.Request) { } cache.Save() + go SaveCookies() + JsonResponse(w, comments) } diff --git a/bugzilla/user.go b/bugzilla/user.go index 56a69aa..2ae0f5a 100644 --- a/bugzilla/user.go +++ b/bugzilla/user.go @@ -1,5 +1,10 @@ package bugzilla +import ( + "errors" + "strconv" +) + type Users struct { conn *Conn } @@ -25,6 +30,30 @@ func CurrentUser() *User { return currentUser } +func (u Users) CheckCurrentLogin() (User, error) { + cookies := u.conn.Client.Cookies() + + for _, c := range cookies { + if c.Name == "Bugzilla_login" { + id, err := strconv.ParseInt(c.Value, 10, 64) + + if err != nil { + return User{}, err + } + + us, err := u.Get(int(id)) + + if err == nil { + currentUser = &us + } + + return us, err + } + } + + return User{}, errors.New("Not logged in") +} + func (u Users) Login(user string, passwd string) (User, error) { args := struct { Login string `xmlrpc:"login"` diff --git a/bugzini.go b/bugzini.go index 272802e..aab880b 100644 --- a/bugzini.go +++ b/bugzini.go @@ -17,7 +17,7 @@ var router = mux.NewRouter() var options struct { Debug bool `short:"d" long:"debug" description:"Enable debug mode"` Launch bool `short:"l" long:"launch" description:"Launch browser at location"` - Port int `short:"p" long:"port" description:"Launch local webserver at specified port"` + Port int `short:"p" long:"port" description:"Launch local webserver at specified port" default:"8000"` Bugzilla struct { Host string `long:"bz-host" description:"Bugzilla host (i.e. bugzilla.gnome.org)" default:"bugzilla.gnome.org"` diff --git a/bz.go b/bz.go index 96c9cb3..8d61d67 100644 --- a/bz.go +++ b/bz.go @@ -2,10 +2,66 @@ package main import ( "bugzilla" + "encoding/gob" + "fmt" + "net/http" + "os" + "path" + + "github.com/jessevdk/xmlrpc" ) var bz *bugzilla.Conn +func SaveCookies() { + if bz == nil || bz.Client == nil { + return + } + + cookies := bz.Client.Cookies() + host := bz.Client.CookieHost() + + os.MkdirAll(".cookies", 0700) + + f, err := os.OpenFile(path.Join(".cookies", host), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create cookies: %s\n", err) + return + } + + defer f.Close() + + enc := gob.NewEncoder(f) + + if err := enc.Encode(cookies); err != nil { + fmt.Fprintf(os.Stderr, "Failed to encode cookies: %s\n", err) + } +} + +func loadSavedCookies(client *xmlrpc.Client) { + host := client.CookieHost() + + f, err := os.Open(path.Join(".cookies", host)) + + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to open cookie file: %s\n", err) + return + } + + defer f.Close() + dec := gob.NewDecoder(f) + + cookies := make([]*http.Cookie, 0, 10) + + if err := dec.Decode(&cookies); err != nil { + fmt.Fprintf(os.Stderr, "Failed to decode cookies: %s\n", err) + return + } + + client.SetCookies(cookies) +} + func Bz() (*bugzilla.Conn, error) { if bz != nil { return bz, nil @@ -18,5 +74,7 @@ func Bz() (*bugzilla.Conn, error) { Secure: options.Bugzilla.Secure, }) + loadSavedCookies(bz.Client) + return bz, err } diff --git a/product.go b/product.go index ba1cf5a..18f2161 100644 --- a/product.go +++ b/product.go @@ -2,10 +2,11 @@ package main import ( "bugzilla" - "github.com/gorilla/mux" "net/http" "strconv" "time" + + "github.com/gorilla/mux" ) func ProductHandler(w http.ResponseWriter, r *http.Request) { @@ -44,6 +45,8 @@ func ProductHandler(w http.ResponseWriter, r *http.Request) { cache.c.ProductMap[id] = product cache.Save() + go SaveCookies() + JsonResponse(w, product) } @@ -142,6 +145,7 @@ func ProductBugsHandler(w http.ResponseWriter, r *http.Request) { } cache.Save() + go SaveCookies() JsonResponse(w, pbugs) } @@ -188,6 +192,7 @@ func ProductAllHandler(w http.ResponseWriter, r *http.Request) { } cache.Save() + go SaveCookies() JsonResponse(w, ret) } diff --git a/user.go b/user.go index 6754739..4d0b33d 100644 --- a/user.go +++ b/user.go @@ -8,9 +8,16 @@ import ( func UserCurrentHandler(w http.ResponseWriter, r *http.Request) { noCache(w) - u := bugzilla.CurrentUser() + client, err := Bz() + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + u, err := client.Users().CheckCurrentLogin() - if u == nil { + if err != nil { http.Error(w, "Not logged in", http.StatusNoContent) return } @@ -43,6 +50,8 @@ func UserLoginHandler(w http.ResponseWriter, r *http.Request) { return } + go SaveCookies() + JsonResponse(w, u) } @@ -65,6 +74,8 @@ func UserLogoutHandler(w http.ResponseWriter, r *http.Request) { return } + go SaveCookies() + bz = nil }