Skip to content

Commit 39b758d

Browse files
committed
Merge branch 'master' into delet_tag
2 parents a3cf5ee + ae56411 commit 39b758d

File tree

11 files changed

+235
-38
lines changed

11 files changed

+235
-38
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ globals:
2828
Tribute: false
2929

3030
overrides:
31-
- files: ["web_src/**/*.worker.js", "web_src/js/serviceworker.js"]
31+
- files: ["web_src/**/*worker.js"]
3232
env:
3333
worker: true
3434

custom/conf/app.example.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ MIN_TIMEOUT = 10s
218218
MAX_TIMEOUT = 60s
219219
TIMEOUT_STEP = 10s
220220
; This setting determines how often the db is queried to get the latest notification counts.
221-
; If the browser client supports EventSource, it will be used in preference to polling notification.
221+
; If the browser client supports EventSource and SharedWorker, a SharedWorker will be used in preference to polling notification. Set to -1 to disable the EventSource
222222
EVENT_SOURCE_UPDATE_TIME = 10s
223223

224224
[markdown]

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
150150
- `MIN_TIMEOUT`: **10s**: These options control how often notification endpoint is polled to update the notification count. On page load the notification count will be checked after `MIN_TIMEOUT`. The timeout will increase to `MAX_TIMEOUT` by `TIMEOUT_STEP` if the notification count is unchanged. Set MIN_TIMEOUT to 0 to turn off.
151151
- `MAX_TIMEOUT`: **60s**.
152152
- `TIMEOUT_STEP`: **10s**.
153-
- `EVENT_SOURCE_UPDATE_TIME`: **10s**: This setting determines how often the database is queried to update notification counts. If the browser client supports `EventSource`, it will be used in preference to polling notification endpoint.
154-
153+
- `EVENT_SOURCE_UPDATE_TIME`: **10s**: This setting determines how often the database is queried to update notification counts. If the browser client supports `EventSource` and `SharedWorker`, a `SharedWorker` will be used in preference to polling notification endpoint. Set to **-1** to disable the `EventSource`.
155154

156155
## Markdown (`markdown`)
157156

modules/eventsource/manager_run.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import (
1717

1818
// Init starts this eventsource
1919
func (m *Manager) Init() {
20+
if setting.UI.Notification.EventSourceUpdateTime <= 0 {
21+
return
22+
}
2023
go graceful.GetManager().RunWithShutdownContext(m.Run)
2124
}
2225

modules/templates/helper.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,16 @@ func NewFuncMap() []template.FuncMap {
164164
mimeType := mime.TypeByExtension(filepath.Ext(filename))
165165
return strings.HasPrefix(mimeType, "image/")
166166
},
167-
"TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string {
167+
"TabSizeClass": func(ec interface{}, filename string) string {
168+
var (
169+
value *editorconfig.Editorconfig
170+
ok bool
171+
)
168172
if ec != nil {
169-
def, err := ec.GetDefinitionForFilename(filename)
173+
if value, ok = ec.(*editorconfig.Editorconfig); !ok || value == nil {
174+
return "tab-size-8"
175+
}
176+
def, err := value.GetDefinitionForFilename(filename)
170177
if err != nil {
171178
log.Error("tab size class: getting definition for filename: %v", err)
172179
return "tab-size-8"
@@ -282,8 +289,8 @@ func NewFuncMap() []template.FuncMap {
282289
return ""
283290
}
284291
},
285-
"NotificationSettings": func() map[string]int {
286-
return map[string]int{
292+
"NotificationSettings": func() map[string]interface{} {
293+
return map[string]interface{}{
287294
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
288295
"TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
289296
"MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),

options/locale/locale_tr-TR.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,6 +1381,11 @@ settings.convert_desc=Bu yansıyı normal bir depoya dönüştürebilirsiniz. Bu
13811381
settings.convert_notices_1=Bu işlem yansıyı normal bir depoya dönüştürür ve geri alınamaz.
13821382
settings.convert_confirm=Depoyu Dönüştür
13831383
settings.convert_succeed=Yansı normal bir depoya dönüştürüldü.
1384+
settings.convert_fork=Düzenli Depoya Dönüştür
1385+
settings.convert_fork_desc=Bu çatalı normal bir depoya dönüştürebilirsiniz. Bu işlem geri alınamaz.
1386+
settings.convert_fork_notices_1=Bu işlem çatalı normal bir depoya dönüştürür ve geri alınamaz.
1387+
settings.convert_fork_confirm=Depoyu Dönüştür
1388+
settings.convert_fork_succeed=Çatal normal bir depoya dönüştürüldü.
13841389
settings.transfer=Sahipliği Aktar
13851390
settings.transfer_desc=Bu depoyu bir kullanıcıya veya yönetici haklarına sahip olduğunuz bir organizasyona aktarın.
13861391
settings.transfer_notices_1=- Bireysel bir kullanıcıya aktarırsanız depoya erişiminizi kaybedersiniz.

routers/api/v1/user/repo.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func listUserRepos(ctx *context.APIContext, u *models.User, private bool) {
2222
Actor: u,
2323
Private: private,
2424
ListOptions: opts,
25+
OrderBy: "id ASC",
2526
})
2627
if err != nil {
2728
ctx.Error(http.StatusInternalServerError, "GetUserRepositories", err)
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
self.name = 'eventsource.sharedworker.js';
2+
3+
const sourcesByUrl = {};
4+
const sourcesByPort = {};
5+
6+
class Source {
7+
constructor(url) {
8+
this.url = url;
9+
this.eventSource = new EventSource(url);
10+
this.listening = {};
11+
this.clients = [];
12+
this.listen('open');
13+
this.listen('logout');
14+
this.listen('notification-count');
15+
this.listen('error');
16+
}
17+
18+
register(port) {
19+
if (!this.clients.includes(port)) return;
20+
21+
this.clients.push(port);
22+
23+
port.postMessage({
24+
type: 'status',
25+
message: `registered to ${this.url}`,
26+
});
27+
}
28+
29+
deregister(port) {
30+
const portIdx = this.clients.indexOf(port);
31+
if (portIdx < 0) {
32+
return this.clients.length;
33+
}
34+
this.clients.splice(portIdx, 1);
35+
return this.clients.length;
36+
}
37+
38+
close() {
39+
if (!this.eventSource) return;
40+
41+
this.eventSource.close();
42+
this.eventSource = null;
43+
}
44+
45+
listen(eventType) {
46+
if (this.listening[eventType]) return;
47+
this.listening[eventType] = true;
48+
const self = this;
49+
this.eventSource.addEventListener(eventType, (event) => {
50+
self.notifyClients({
51+
type: eventType,
52+
data: event.data
53+
});
54+
});
55+
}
56+
57+
notifyClients(event) {
58+
for (const client of this.clients) {
59+
client.postMessage(event);
60+
}
61+
}
62+
63+
status(port) {
64+
port.postMessage({
65+
type: 'status',
66+
message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,
67+
});
68+
}
69+
}
70+
71+
self.onconnect = (e) => {
72+
for (const port of e.ports) {
73+
port.addEventListener('message', (event) => {
74+
if (event.data.type === 'start') {
75+
const url = event.data.url;
76+
if (sourcesByUrl[url]) {
77+
// we have a Source registered to this url
78+
const source = sourcesByUrl[url];
79+
source.register(port);
80+
sourcesByPort[port] = source;
81+
return;
82+
}
83+
let source = sourcesByPort[port];
84+
if (source) {
85+
if (source.eventSource && source.url === url) return;
86+
87+
// How this has happened I don't understand...
88+
// deregister from that source
89+
const count = source.deregister(port);
90+
// Clean-up
91+
if (count === 0) {
92+
source.close();
93+
sourcesByUrl[source.url] = null;
94+
}
95+
}
96+
// Create a new Source
97+
source = new Source(url);
98+
source.register(port);
99+
sourcesByUrl[url] = source;
100+
sourcesByPort[port] = source;
101+
return;
102+
} else if (event.data.type === 'listen') {
103+
const source = sourcesByPort[port];
104+
source.listen(event.data.eventType);
105+
return;
106+
} else if (event.data.type === 'close') {
107+
const source = sourcesByPort[port];
108+
109+
if (!source) return;
110+
111+
const count = source.deregister(port);
112+
if (count === 0) {
113+
source.close();
114+
sourcesByUrl[source.url] = null;
115+
sourcesByPort[port] = null;
116+
}
117+
return;
118+
} else if (event.data.type === 'status') {
119+
const source = sourcesByPort[port];
120+
if (!source) {
121+
port.postMessage({
122+
type: 'status',
123+
message: 'not connected',
124+
});
125+
return;
126+
}
127+
source.status(port);
128+
return;
129+
} else {
130+
// just send it back
131+
port.postMessage({
132+
type: 'error',
133+
message: `received but don't know how to handle: ${event.data}`,
134+
});
135+
return;
136+
}
137+
});
138+
port.start();
139+
}
140+
};

web_src/js/features/notification.js

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,44 +18,83 @@ export function initNotificationsTable() {
1818
});
1919
}
2020

21-
export function initNotificationCount() {
21+
async function receiveUpdateCount(event) {
22+
try {
23+
const data = JSON.parse(event.data);
24+
25+
const notificationCount = document.querySelector('.notification_count');
26+
if (data.Count > 0) {
27+
notificationCount.classList.remove('hidden');
28+
} else {
29+
notificationCount.classList.add('hidden');
30+
}
31+
32+
notificationCount.text(`${data.Count}`);
33+
await updateNotificationTable();
34+
} catch (error) {
35+
console.error(error, event);
36+
}
37+
}
38+
39+
export async function initNotificationCount() {
2240
const notificationCount = $('.notification_count');
2341

2442
if (!notificationCount.length) {
2543
return;
2644
}
2745

2846
if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource) {
29-
// Try to connect to the event source first
30-
const source = new EventSource(`${AppSubUrl}/user/events`);
31-
source.addEventListener('notification-count', async (e) => {
32-
try {
33-
const data = JSON.parse(e.data);
34-
35-
const notificationCount = $('.notification_count');
36-
if (data.Count === 0) {
37-
notificationCount.addClass('hidden');
47+
// Try to connect to the event source via the shared worker first
48+
if (window.SharedWorker) {
49+
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
50+
worker.addEventListener('error', (event) => {
51+
console.error(event);
52+
});
53+
worker.port.onmessageerror = () => {
54+
console.error('Unable to deserialize message');
55+
};
56+
worker.port.postMessage({
57+
type: 'start',
58+
url: `${window.location.origin}${AppSubUrl}/user/events`,
59+
});
60+
worker.port.addEventListener('message', (e) => {
61+
if (!e.data || !e.data.type) {
62+
console.error(e);
63+
return;
64+
}
65+
if (event.data.type === 'notification-count') {
66+
receiveUpdateCount(e.data);
67+
return;
68+
} else if (event.data.type === 'error') {
69+
console.error(e.data);
70+
return;
71+
} else if (event.data.type === 'logout') {
72+
if (e.data !== 'here') {
73+
return;
74+
}
75+
worker.port.postMessage({
76+
type: 'close',
77+
});
78+
worker.port.close();
79+
window.location.href = AppSubUrl;
80+
return;
3881
} else {
39-
notificationCount.removeClass('hidden');
82+
return;
4083
}
41-
42-
notificationCount.text(`${data.Count}`);
43-
await updateNotificationTable();
44-
} catch (error) {
45-
console.error(error);
46-
}
47-
});
48-
source.addEventListener('logout', async (e) => {
49-
if (e.data !== 'here') {
50-
return;
51-
}
52-
source.close();
53-
window.location.href = AppSubUrl;
54-
});
55-
window.addEventListener('beforeunload', () => {
56-
source.close();
57-
});
58-
return;
84+
});
85+
worker.port.addEventListener('error', (e) => {
86+
console.error(e);
87+
});
88+
worker.port.start();
89+
window.addEventListener('beforeunload', () => {
90+
worker.port.postMessage({
91+
type: 'close',
92+
});
93+
worker.port.close();
94+
});
95+
96+
return;
97+
}
5998
}
6099

61100
if (NotificationSettings.MinTimeout <= 0) {

web_src/js/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2432,7 +2432,6 @@ $(document).ready(async () => {
24322432
initContextPopups();
24332433
initTableSort();
24342434
initNotificationsTable();
2435-
initNotificationCount();
24362435

24372436
// Repo clone url.
24382437
if ($('#repo-clone-url').length > 0) {
@@ -2477,6 +2476,7 @@ $(document).ready(async () => {
24772476
initClipboard(),
24782477
initUserHeatmap(),
24792478
initServiceWorker(),
2479+
initNotificationCount(),
24802480
]);
24812481
});
24822482

webpack.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ module.exports = {
5252
serviceworker: [
5353
resolve(__dirname, 'web_src/js/serviceworker.js'),
5454
],
55+
'eventsource.sharedworker': [
56+
resolve(__dirname, 'web_src/js/features/eventsource.sharedworker.js'),
57+
],
5558
icons: [
5659
...glob('node_modules/@primer/octicons/build/svg/**/*.svg'),
5760
...glob('assets/svg/*.svg'),

0 commit comments

Comments
 (0)