-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Live removal of issue comments using htmx websocket #28958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 58 commits
2b247dd
5005245
7cf50ef
4947abc
3b35637
e85f85b
d77073b
17f4faf
948f1bd
1d6a34d
337456a
e4beba8
21bcb1b
c2ca7d8
fb49e6b
4c03093
7173bea
bc2a352
26ab35c
b2f4aae
afcdd78
21485f8
7cda3aa
d9c4554
ea06a25
9ace44c
78b94ff
c595916
b805631
e9ded26
b5f61df
7de741c
bdde78a
3d2ec73
4f97ad2
72bb32f
c1512d3
201fd83
39421d6
634834b
671a8df
94bcdba
77f89b7
1602afd
f2f97a2
4d75e28
7f3435f
cf23cd6
bb4443d
372faaa
d86c1dc
70f2fee
154f61c
8919106
1328b2b
0aa3d5e
40fc078
c6abd32
e9502e0
b8dab69
088454f
43ad9f5
7cb8e2f
602a42a
6af641f
f83d6fe
0262846
aed4810
a2e1c14
85cf27e
abcc609
597fac4
63f4e6c
7daafae
edb0f9c
f2001a2
cbfb0bd
56d11fc
877eb69
d34e36b
e6da1b1
5ceae12
7efef85
97544d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright 2023 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package websocket | ||
|
||
import ( | ||
"code.gitea.io/gitea/modules/web" | ||
"code.gitea.io/gitea/services/context" | ||
"code.gitea.io/gitea/services/websocket" | ||
) | ||
|
||
func Init(r *web.Route) { | ||
m := websocket.Init() | ||
|
||
r.Any("/-/ws", func(ctx *context.Context) { | ||
err := m.HandleRequest(ctx.Resp, ctx.Req) | ||
if err != nil { | ||
ctx.ServerError("HandleRequest", err) | ||
return | ||
} | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package pubsub | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
) | ||
|
||
type Memory struct { | ||
sync.Mutex | ||
|
||
topics map[string]map[*Subscriber]struct{} | ||
} | ||
|
||
// New creates an in-memory publisher. | ||
func NewMemory() Broker { | ||
return &Memory{ | ||
topics: make(map[string]map[*Subscriber]struct{}), | ||
} | ||
} | ||
|
||
func (p *Memory) Publish(_ context.Context, message Message) { | ||
p.Lock() | ||
|
||
topic, ok := p.topics[message.Topic] | ||
if !ok { | ||
p.Unlock() | ||
return | ||
} | ||
|
||
for s := range topic { | ||
go (*s)(message) | ||
} | ||
p.Unlock() | ||
} | ||
|
||
func (p *Memory) Subscribe(c context.Context, topic string, subscriber Subscriber) { | ||
// Subscribe | ||
p.Lock() | ||
_, ok := p.topics[topic] | ||
if !ok { | ||
p.topics[topic] = make(map[*Subscriber]struct{}) | ||
} | ||
p.topics[topic][&subscriber] = struct{}{} | ||
p.Unlock() | ||
|
||
// Wait for context to be done | ||
<-c.Done() | ||
|
||
// Unsubscribe | ||
p.Lock() | ||
delete(p.topics[topic], &subscriber) | ||
if len(p.topics[topic]) == 0 { | ||
delete(p.topics, topic) | ||
} | ||
p.Unlock() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package pubsub | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestPubsub(t *testing.T) { | ||
var ( | ||
wg sync.WaitGroup | ||
|
||
testMessage = Message{ | ||
Data: []byte("test"), | ||
Topic: "hello-world", | ||
} | ||
) | ||
|
||
ctx, cancel := context.WithCancelCause( | ||
context.Background(), | ||
) | ||
|
||
broker := NewMemory() | ||
go func() { | ||
broker.Subscribe(ctx, "hello-world", func(message Message) { assert.Equal(t, testMessage, message); wg.Done() }) | ||
}() | ||
go func() { | ||
broker.Subscribe(ctx, "hello-world", func(_ Message) { wg.Done() }) | ||
}() | ||
|
||
// Wait a bit for the subscriptions to be registered | ||
<-time.After(100 * time.Millisecond) | ||
|
||
wg.Add(2) | ||
go func() { | ||
broker.Publish(ctx, testMessage) | ||
}() | ||
|
||
wg.Wait() | ||
cancel(nil) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package pubsub | ||
|
||
import "context" | ||
|
||
// Message defines a published message. | ||
type Message struct { | ||
// Data is the actual data in the entry. | ||
Data []byte `json:"data"` | ||
|
||
// Topic is the topic of the message. | ||
Topic string `json:"topic"` | ||
} | ||
|
||
// Subscriber receives published messages. | ||
type Subscriber func(Message) | ||
|
||
type Broker interface { | ||
Publish(c context.Context, message Message) | ||
Subscribe(c context.Context, topic string, subscriber Subscriber) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package websocket | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
|
||
issues_model "code.gitea.io/gitea/models/issues" | ||
user_model "code.gitea.io/gitea/models/user" | ||
"code.gitea.io/gitea/services/pubsub" | ||
) | ||
|
||
func (n *websocketNotifier) DeleteComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment) { | ||
d, err := json.Marshal(c) | ||
if err != nil { | ||
return | ||
} | ||
|
||
n.pubsub.Publish(ctx, pubsub.Message{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the topic should still contain the issue and index. And it also includes the actions like delete/create/update. Because I think different pages of the same clients should not share the same web socket client. |
||
Data: d, | ||
Topic: fmt.Sprintf("repo:%s/%s", c.RefRepo.OwnerName, c.RefRepo.Name), | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package websocket | ||
|
||
import ( | ||
"code.gitea.io/gitea/modules/templates" | ||
notify_service "code.gitea.io/gitea/services/notify" | ||
"code.gitea.io/gitea/services/pubsub" | ||
|
||
"github.com/olahol/melody" | ||
) | ||
|
||
type websocketNotifier struct { | ||
notify_service.NullNotifier | ||
m *melody.Melody | ||
rnd *templates.HTMLRender | ||
pubsub pubsub.Broker | ||
} | ||
|
||
// NewNotifier create a new webhooksNotifier notifier | ||
func newNotifier(m *melody.Melody) notify_service.Notifier { | ||
return &websocketNotifier{ | ||
m: m, | ||
rnd: templates.HTMLRenderer(), | ||
} | ||
} | ||
|
||
// htmxAddElementEnd = "<div hx-swap-oob=\"beforebegin:%s\">%s</div>" | ||
// htmxUpdateElement = "<div hx-swap-oob=\"outerHTML:%s\">%s</div>" | ||
|
||
var htmxRemoveElement = "<div hx-swap-oob=\"delete:%s\"></div>" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package websocket | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
|
||
user_model "code.gitea.io/gitea/models/user" | ||
|
||
"github.com/olahol/melody" | ||
) | ||
|
||
type sessionData struct { | ||
user *user_model.User | ||
onURL string | ||
} | ||
|
||
func (d *sessionData) isOnURL(_u1 string) bool { | ||
if d.onURL == "" { | ||
return true | ||
} | ||
|
||
u1, _ := url.Parse(d.onURL) | ||
u2, _ := url.Parse(_u1) | ||
return u1.Path == u2.Path | ||
} | ||
|
||
func getSessionData(s *melody.Session) (*sessionData, error) { | ||
_data, ok := s.Get("data") | ||
if !ok { | ||
return nil, fmt.Errorf("no session data") | ||
} | ||
|
||
data, ok := _data.(*sessionData) | ||
if !ok { | ||
return nil, fmt.Errorf("invalid session data") | ||
} | ||
|
||
return data, nil | ||
} |
Uh oh!
There was an error while loading. Please reload this page.