mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-10 15:32:55 +09:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77af0a23c4 | ||
|
|
87bfe02b5b | ||
|
|
9bac656b7d | ||
|
|
ad68c9ccb2 | ||
|
|
8d1cd4d252 | ||
|
|
64eaa2a942 | ||
|
|
489e9162fc | ||
|
|
5e62137fe3 | ||
|
|
6a081f95c0 | ||
|
|
c3c246cffc | ||
|
|
85be939c2a | ||
|
|
a680c911e4 | ||
|
|
d9c18cbba0 | ||
|
|
3daedb3877 | ||
|
|
2bf987229a | ||
|
|
f984a7e6c6 | ||
|
|
c96da610c2 | ||
|
|
e46dbec294 | ||
|
|
8f64017058 | ||
|
|
d737eaa63a | ||
|
|
058ee52333 | ||
|
|
47b1fc5149 | ||
|
|
20c2bdf86b | ||
|
|
df13fc8818 | ||
|
|
445992d929 | ||
|
|
d059156c3a |
@@ -25,7 +25,7 @@ globals:
|
|||||||
Tribute: false
|
Tribute: false
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
- files: ["web_src/**/*.worker.js", "web_src/js/serviceworker.js"]
|
- files: ["web_src/**/*worker.js"]
|
||||||
env:
|
env:
|
||||||
worker: true
|
worker: true
|
||||||
rules:
|
rules:
|
||||||
|
|||||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -4,6 +4,37 @@ This changelog goes through all the changes that have been made in each release
|
|||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||||
|
|
||||||
|
## [1.12.3](https://github.com/go-gitea/gitea/releases/tag/v1.12.3) - 2020-07-28
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Don't change creation date when updating Release (#12343) (#12351)
|
||||||
|
* Show 404 page when release not found (#12328) (#12332)
|
||||||
|
* Fix emoji detection in certain cases (#12320) (#12327)
|
||||||
|
* Reduce emoji size (#12317) (#12327)
|
||||||
|
* Fix double-indirection bug in logging IDs (#12294) (#12308)
|
||||||
|
* Link to pull list page on sidebar when view pr (#12256) (#12263)
|
||||||
|
* Extend Notifications API and return pinned notifications by default (#12164) (#12232)
|
||||||
|
|
||||||
|
## [1.12.2](https://github.com/go-gitea/gitea/releases/tag/v1.12.2) - 2020-07-11
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* When deleting repository decrese user repository count in cache (#11954) (#12188)
|
||||||
|
* Return full commit message instead of summary in commits API (#12186) (#12187)
|
||||||
|
* Properly set HEAD when a repo is created with a default branch that is not named 'master' (#12135) (#12182)
|
||||||
|
* Ensure GPG Subkeys are verified (#12155) (#12168)
|
||||||
|
* Fix failing to cache last commit with key being to long (#12151) (#12161)
|
||||||
|
* Multiple small admin dashboard fixes (#12153) (#12156)
|
||||||
|
* Remove spurious logging of " Delete all repository archives" at startup (#12139) (#12148)
|
||||||
|
* Fix repository setup instructions when default branch is not named 'master' (#12122) (#12147)
|
||||||
|
* Move EventSource to SharedWorker (#12095) (#12130)
|
||||||
|
* Fix ui bug in wiki commit page (#12089) (#12125)
|
||||||
|
* Fix gitgraph branch continues after merge (#12044) (#12105)
|
||||||
|
* Set the base url when migrating from Gitlab using access token or username without password (#11852) (#12104)
|
||||||
|
* Ensure BlameReaders close at end of request (#12102) (#12103)
|
||||||
|
* Fix panic when adding review comment (#12058)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Disable dropzone's timeout for file uploads (#12024) (#12032)
|
||||||
|
|
||||||
## [1.12.1](https://github.com/go-gitea/gitea/releases/tag/v1.12.1) - 2020-06-21
|
## [1.12.1](https://github.com/go-gitea/gitea/releases/tag/v1.12.1) - 2020-06-21
|
||||||
|
|
||||||
* BUGFIXES
|
* BUGFIXES
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -253,7 +253,7 @@ swagger-validate:
|
|||||||
.PHONY: errcheck
|
.PHONY: errcheck
|
||||||
errcheck:
|
errcheck:
|
||||||
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u github.com/kisielk/errcheck; \
|
GO111MODULE=off $(GO) get -u github.com/kisielk/errcheck; \
|
||||||
fi
|
fi
|
||||||
errcheck $(GO_PACKAGES)
|
errcheck $(GO_PACKAGES)
|
||||||
|
|
||||||
@@ -264,14 +264,14 @@ revive:
|
|||||||
.PHONY: misspell-check
|
.PHONY: misspell-check
|
||||||
misspell-check:
|
misspell-check:
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
fi
|
fi
|
||||||
misspell -error -i unknwon,destory $(GO_SOURCES_OWN)
|
misspell -error -i unknwon,destory $(GO_SOURCES_OWN)
|
||||||
|
|
||||||
.PHONY: misspell
|
.PHONY: misspell
|
||||||
misspell:
|
misspell:
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
fi
|
fi
|
||||||
misspell -w -i unknwon $(GO_SOURCES_OWN)
|
misspell -w -i unknwon $(GO_SOURCES_OWN)
|
||||||
|
|
||||||
@@ -529,7 +529,7 @@ $(DIST_DIRS):
|
|||||||
.PHONY: release-windows
|
.PHONY: release-windows
|
||||||
release-windows: | $(DIST_DIRS)
|
release-windows: | $(DIST_DIRS)
|
||||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u src.techknowlogick.com/xgo; \
|
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
|
||||||
fi
|
fi
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
||||||
ifeq ($(CI),drone)
|
ifeq ($(CI),drone)
|
||||||
@@ -539,7 +539,7 @@ endif
|
|||||||
.PHONY: release-linux
|
.PHONY: release-linux
|
||||||
release-linux: | $(DIST_DIRS)
|
release-linux: | $(DIST_DIRS)
|
||||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u src.techknowlogick.com/xgo; \
|
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
|
||||||
fi
|
fi
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out gitea-$(VERSION) .
|
||||||
ifeq ($(CI),drone)
|
ifeq ($(CI),drone)
|
||||||
@@ -549,7 +549,7 @@ endif
|
|||||||
.PHONY: release-darwin
|
.PHONY: release-darwin
|
||||||
release-darwin: | $(DIST_DIRS)
|
release-darwin: | $(DIST_DIRS)
|
||||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u src.techknowlogick.com/xgo; \
|
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
|
||||||
fi
|
fi
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
|
||||||
ifeq ($(CI),drone)
|
ifeq ($(CI),drone)
|
||||||
|
|||||||
@@ -120,6 +120,12 @@ var checklist = []check{
|
|||||||
isDefault: false,
|
isDefault: false,
|
||||||
f: runDoctorPRMergeBase,
|
f: runDoctorPRMergeBase,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Recalculate Stars number for all user",
|
||||||
|
name: "recalculate_stars_number",
|
||||||
|
isDefault: false,
|
||||||
|
f: runDoctorUserStarNum,
|
||||||
|
},
|
||||||
// more checks please append here
|
// more checks please append here
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,6 +500,10 @@ func runDoctorPRMergeBase(ctx *cli.Context) ([]string, error) {
|
|||||||
return results, err
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runDoctorUserStarNum(ctx *cli.Context) ([]string, error) {
|
||||||
|
return nil, models.DoctorUserStarNum()
|
||||||
|
}
|
||||||
|
|
||||||
func runDoctorScriptType(ctx *cli.Context) ([]string, error) {
|
func runDoctorScriptType(ctx *cli.Context) ([]string, error) {
|
||||||
path, err := exec.LookPath(setting.ScriptType)
|
path, err := exec.LookPath(setting.ScriptType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ MIN_TIMEOUT = 10s
|
|||||||
MAX_TIMEOUT = 60s
|
MAX_TIMEOUT = 60s
|
||||||
TIMEOUT_STEP = 10s
|
TIMEOUT_STEP = 10s
|
||||||
; This setting determines how often the db is queried to get the latest notification counts.
|
; This setting determines how often the db is queried to get the latest notification counts.
|
||||||
; If the browser client supports EventSource, it will be used in preference to polling notification.
|
; 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
|
||||||
EVENT_SOURCE_UPDATE_TIME = 10s
|
EVENT_SOURCE_UPDATE_TIME = 10s
|
||||||
|
|
||||||
[markdown]
|
[markdown]
|
||||||
|
|||||||
@@ -148,8 +148,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
|||||||
- `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.
|
- `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.
|
||||||
- `MAX_TIMEOUT`: **60s**.
|
- `MAX_TIMEOUT`: **60s**.
|
||||||
- `TIMEOUT_STEP`: **10s**.
|
- `TIMEOUT_STEP`: **10s**.
|
||||||
- `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.
|
- `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`.
|
||||||
|
|
||||||
|
|
||||||
## Markdown (`markdown`)
|
## Markdown (`markdown`)
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestAPINotification(t *testing.T) {
|
|||||||
assert.EqualValues(t, false, apiNL[2].Pinned)
|
assert.EqualValues(t, false, apiNL[2].Pinned)
|
||||||
|
|
||||||
// -- GET /repos/{owner}/{repo}/notifications --
|
// -- GET /repos/{owner}/{repo}/notifications --
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?token=%s", user2.Name, repo1.Name, token))
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&token=%s", user2.Name, repo1.Name, token))
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiNL)
|
DecodeJSON(t, resp, &apiNL)
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ func TestAPINotification(t *testing.T) {
|
|||||||
assert.True(t, new.New > 0)
|
assert.True(t, new.New > 0)
|
||||||
|
|
||||||
// -- mark notifications as read --
|
// -- mark notifications as read --
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiNL)
|
DecodeJSON(t, resp, &apiNL)
|
||||||
assert.Len(t, apiNL, 2)
|
assert.Len(t, apiNL, 2)
|
||||||
@@ -101,7 +101,7 @@ func TestAPINotification(t *testing.T) {
|
|||||||
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
|
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
|
||||||
resp = session.MakeRequest(t, req, http.StatusResetContent)
|
resp = session.MakeRequest(t, req, http.StatusResetContent)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiNL)
|
DecodeJSON(t, resp, &apiNL)
|
||||||
assert.Len(t, apiNL, 1)
|
assert.Len(t, apiNL, 1)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func TestEventSourceManagerRun(t *testing.T) {
|
|||||||
var apiNL []api.NotificationThread
|
var apiNL []api.NotificationThread
|
||||||
|
|
||||||
// -- mark notifications as read --
|
// -- mark notifications as read --
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
DecodeJSON(t, resp, &apiNL)
|
DecodeJSON(t, resp, &apiNL)
|
||||||
@@ -69,7 +69,7 @@ func TestEventSourceManagerRun(t *testing.T) {
|
|||||||
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
|
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
|
||||||
resp = session.MakeRequest(t, req, http.StatusResetContent)
|
resp = session.MakeRequest(t, req, http.StatusResetContent)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s&status-types=unread", token))
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiNL)
|
DecodeJSON(t, resp, &apiNL)
|
||||||
assert.Len(t, apiNL, 1)
|
assert.Len(t, apiNL, 1)
|
||||||
|
|||||||
@@ -509,6 +509,18 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *Use
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
var primaryKeys []*GPGKey
|
||||||
|
if key.PrimaryKeyID != "" {
|
||||||
|
primaryKeys, err = GetGPGKeysByKeyID(key.PrimaryKeyID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||||
|
return &CommitVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
activated := false
|
activated := false
|
||||||
if len(email) != 0 {
|
if len(email) != 0 {
|
||||||
for _, e := range key.Emails {
|
for _, e := range key.Emails {
|
||||||
@@ -518,6 +530,20 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *Use
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !activated {
|
||||||
|
for _, pkey := range primaryKeys {
|
||||||
|
for _, e := range pkey.Emails {
|
||||||
|
if e.IsActivated && strings.EqualFold(e.Email, email) {
|
||||||
|
activated = true
|
||||||
|
email = e.Email
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if activated {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, e := range key.Emails {
|
for _, e := range key.Emails {
|
||||||
if e.IsActivated {
|
if e.IsActivated {
|
||||||
@@ -526,7 +552,22 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *Use
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !activated {
|
||||||
|
for _, pkey := range primaryKeys {
|
||||||
|
for _, e := range pkey.Emails {
|
||||||
|
if e.IsActivated {
|
||||||
|
activated = true
|
||||||
|
email = e.Email
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if activated {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !activated {
|
if !activated {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -614,7 +655,6 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
|
|||||||
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||||
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultReason := NoKeyFound
|
defaultReason := NoKeyFound
|
||||||
|
|
||||||
// First check if the sig has a keyID and if so just look at that
|
// First check if the sig has a keyID and if so just look at that
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ type FindNotificationOptions struct {
|
|||||||
UserID int64
|
UserID int64
|
||||||
RepoID int64
|
RepoID int64
|
||||||
IssueID int64
|
IssueID int64
|
||||||
Status NotificationStatus
|
Status []NotificationStatus
|
||||||
UpdatedAfterUnix int64
|
UpdatedAfterUnix int64
|
||||||
UpdatedBeforeUnix int64
|
UpdatedBeforeUnix int64
|
||||||
}
|
}
|
||||||
@@ -89,8 +89,8 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond {
|
|||||||
if opts.IssueID != 0 {
|
if opts.IssueID != 0 {
|
||||||
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
|
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
|
||||||
}
|
}
|
||||||
if opts.Status != 0 {
|
if len(opts.Status) > 0 {
|
||||||
cond = cond.And(builder.Eq{"notification.status": opts.Status})
|
cond = cond.And(builder.In("notification.status", opts.Status))
|
||||||
}
|
}
|
||||||
if opts.UpdatedAfterUnix != 0 {
|
if opts.UpdatedAfterUnix != 0 {
|
||||||
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
|
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
// Needed for jpeg support
|
// Needed for jpeg support
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
@@ -1384,11 +1385,11 @@ func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) {
|
|||||||
func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) {
|
func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) {
|
||||||
repo.LowerName = strings.ToLower(repo.Name)
|
repo.LowerName = strings.ToLower(repo.Name)
|
||||||
|
|
||||||
if len(repo.Description) > 255 {
|
if utf8.RuneCountInString(repo.Description) > 255 {
|
||||||
repo.Description = repo.Description[:255]
|
repo.Description = string([]rune(repo.Description)[:255])
|
||||||
}
|
}
|
||||||
if len(repo.Website) > 255 {
|
if utf8.RuneCountInString(repo.Website) > 255 {
|
||||||
repo.Website = repo.Website[:255]
|
repo.Website = string([]rune(repo.Website)[:255])
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
|
if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
|
||||||
@@ -1566,6 +1567,10 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
|||||||
releaseAttachments = append(releaseAttachments, attachments[i].LocalPath())
|
releaseAttachments = append(releaseAttachments, attachments[i].LocalPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Exec("UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err = deleteBeans(sess,
|
if err = deleteBeans(sess,
|
||||||
&Access{RepoID: repo.ID},
|
&Access{RepoID: repo.ID},
|
||||||
&Action{RepoID: repo.ID},
|
&Action{RepoID: repo.ID},
|
||||||
@@ -2331,3 +2336,38 @@ func updateRepositoryCols(e Engine, repo *Repository, cols ...string) error {
|
|||||||
func UpdateRepositoryCols(repo *Repository, cols ...string) error {
|
func UpdateRepositoryCols(repo *Repository, cols ...string) error {
|
||||||
return updateRepositoryCols(x, repo, cols...)
|
return updateRepositoryCols(x, repo, cols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DoctorUserStarNum recalculate Stars number for all user
|
||||||
|
func DoctorUserStarNum() (err error) {
|
||||||
|
const batchSize = 100
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
for start := 0; ; start += batchSize {
|
||||||
|
users := make([]User, 0, batchSize)
|
||||||
|
if err = sess.Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
if _, err = sess.Exec("UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sess.Commit(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("recalculate Stars number for all user finished")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -187,3 +187,9 @@ func TestDeleteAvatar(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "", repo.Avatar)
|
assert.Equal(t, "", repo.Avatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDoctorUserStarNum(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
|
||||||
|
assert.NoError(t, DoctorUserStarNum())
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors)
|
|||||||
|
|
||||||
// AdminDashboardForm form for admin dashboard operations
|
// AdminDashboardForm form for admin dashboard operations
|
||||||
type AdminDashboardForm struct {
|
type AdminDashboardForm struct {
|
||||||
Op string `binding:"required"`
|
Op string `binding:"required"`
|
||||||
|
From string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates form fields
|
// Validate validates form fields
|
||||||
|
|||||||
10
modules/cache/last_commit.go
vendored
10
modules/cache/last_commit.go
vendored
@@ -5,6 +5,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
@@ -34,9 +35,14 @@ func NewLastCommitCache(repoPath string, gitRepo *git.Repository, ttl int64) *La
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c LastCommitCache) getCacheKey(repoPath, ref, entryPath string) string {
|
||||||
|
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, ref, entryPath)))
|
||||||
|
return fmt.Sprintf("last_commit:%x", hashBytes)
|
||||||
|
}
|
||||||
|
|
||||||
// Get get the last commit information by commit id and entry path
|
// Get get the last commit information by commit id and entry path
|
||||||
func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
|
func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
|
||||||
v := c.Cache.Get(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath))
|
v := c.Cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
|
||||||
if vs, ok := v.(string); ok {
|
if vs, ok := v.(string); ok {
|
||||||
log.Trace("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
|
log.Trace("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
|
||||||
if commit, ok := c.commitCache[vs]; ok {
|
if commit, ok := c.commitCache[vs]; ok {
|
||||||
@@ -60,5 +66,5 @@ func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
|
|||||||
// Put put the last commit id with commit and entry path
|
// Put put the last commit id with commit and entry path
|
||||||
func (c LastCommitCache) Put(ref, entryPath, commitID string) error {
|
func (c LastCommitCache) Put(ref, entryPath, commitID string) error {
|
||||||
log.Trace("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
|
log.Trace("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
|
||||||
return c.Cache.Put(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath), commitID, c.ttl)
|
return c.Cache.Put(c.getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ func ReplaceAliases(s string) string {
|
|||||||
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
|
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
|
||||||
func FindEmojiSubmatchIndex(s string) []int {
|
func FindEmojiSubmatchIndex(s string) []int {
|
||||||
loadMap()
|
loadMap()
|
||||||
|
found := make(map[int]int)
|
||||||
|
keys := make([]int, 0)
|
||||||
|
|
||||||
//see if there are any emoji in string before looking for position of specific ones
|
//see if there are any emoji in string before looking for position of specific ones
|
||||||
//no performance difference when there is a match but 10x faster when there are not
|
//no performance difference when there is a match but 10x faster when there are not
|
||||||
@@ -137,11 +139,26 @@ func FindEmojiSubmatchIndex(s string) []int {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get index of first emoji occurrence while also checking for longest combination
|
||||||
for j := range GemojiData {
|
for j := range GemojiData {
|
||||||
i := strings.Index(s, GemojiData[j].Emoji)
|
i := strings.Index(s, GemojiData[j].Emoji)
|
||||||
if i != -1 {
|
if i != -1 {
|
||||||
return []int{i, i + len(GemojiData[j].Emoji)}
|
if _, ok := found[i]; !ok {
|
||||||
|
if len(keys) == 0 || i < keys[0] {
|
||||||
|
found[i] = j
|
||||||
|
keys = []int{i}
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(keys) > 0 {
|
||||||
|
index := keys[0]
|
||||||
|
return []int{index, index + len(GemojiData[found[index]].Emoji)}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import (
|
|||||||
|
|
||||||
// Init starts this eventsource
|
// Init starts this eventsource
|
||||||
func (m *Manager) Init() {
|
func (m *Manager) Init() {
|
||||||
|
if setting.UI.Notification.EventSourceUpdateTime <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
go graceful.GetManager().RunWithShutdownContext(m.Run)
|
go graceful.GetManager().RunWithShutdownContext(m.Run)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
|
|||||||
// Close BlameReader - don't run NextPart after invoking that
|
// Close BlameReader - don't run NextPart after invoking that
|
||||||
func (r *BlameReader) Close() error {
|
func (r *BlameReader) Close() error {
|
||||||
defer process.GetManager().Remove(r.pid)
|
defer process.GetManager().Remove(r.pid)
|
||||||
defer r.cancel()
|
r.cancel()
|
||||||
|
|
||||||
|
_ = r.output.Close()
|
||||||
|
|
||||||
if err := r.cmd.Wait(); err != nil {
|
if err := r.cmd.Wait(); err != nil {
|
||||||
return fmt.Errorf("Wait: %v", err)
|
return fmt.Errorf("Wait: %v", err)
|
||||||
@@ -89,19 +91,19 @@ func (r *BlameReader) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateBlameReader creates reader for given repository, commit and file
|
// CreateBlameReader creates reader for given repository, commit and file
|
||||||
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
|
func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) {
|
||||||
gitRepo, err := OpenRepository(repoPath)
|
gitRepo, err := OpenRepository(repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gitRepo.Close()
|
gitRepo.Close()
|
||||||
|
|
||||||
return createBlameReader(repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
|
return createBlameReader(ctx, repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBlameReader(dir string, command ...string) (*BlameReader, error) {
|
func createBlameReader(ctx context.Context, dir string, command ...string) (*BlameReader, error) {
|
||||||
// FIXME: graceful: This should have a timeout
|
// Here we use the provided context - this should be tied to the request performing the blame so that it does not hang around.
|
||||||
ctx, cancel := context.WithCancel(DefaultContext)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
|
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
|
||||||
cmd.Dir = dir
|
cmd.Dir = dir
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -93,8 +94,10 @@ func TestReadingBlameOutput(t *testing.T) {
|
|||||||
if _, err = tempFile.WriteString(exampleBlame); err != nil {
|
if _, err = tempFile.WriteString(exampleBlame); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
blameReader, err := createBlameReader("", "cat", tempFile.Name())
|
blameReader, err := createBlameReader(ctx, "", "cat", tempFile.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ func NewColoredValueBytes(value interface{}, colorBytes *[]byte) *ColoredValue {
|
|||||||
// The Value will be colored with FgCyan
|
// The Value will be colored with FgCyan
|
||||||
// If a ColoredValue is provided it is not changed
|
// If a ColoredValue is provided it is not changed
|
||||||
func NewColoredIDValue(value interface{}) *ColoredValue {
|
func NewColoredIDValue(value interface{}) *ColoredValue {
|
||||||
return NewColoredValueBytes(&value, &fgCyanBytes)
|
return NewColoredValueBytes(value, &fgCyanBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format will format the provided value and protect against ANSI color spoofing within the value
|
// Format will format the provided value and protect against ANSI color spoofing within the value
|
||||||
|
|||||||
@@ -266,6 +266,10 @@ func TestRender_emoji(t *testing.T) {
|
|||||||
test(
|
test(
|
||||||
"Some text with 😄😄 2 emoji next to each other",
|
"Some text with 😄😄 2 emoji next to each other",
|
||||||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
|
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
|
||||||
|
test(
|
||||||
|
"😎🤪🔐🤑❓",
|
||||||
|
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="question mark">❓</span></p>`)
|
||||||
|
|
||||||
// should match nothing
|
// should match nothing
|
||||||
test(
|
test(
|
||||||
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func NewGitlabDownloader(baseURL, repoPath, username, password string) *GitlabDo
|
|||||||
var err error
|
var err error
|
||||||
if username != "" {
|
if username != "" {
|
||||||
if password == "" {
|
if password == "" {
|
||||||
gitlabClient, err = gitlab.NewClient(username)
|
gitlabClient, err = gitlab.NewClient(username, gitlab.WithBaseURL(baseURL))
|
||||||
} else {
|
} else {
|
||||||
gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL))
|
gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ func CreateQueue(name string, handle HandlerFunc, exemplar interface{}) Queue {
|
|||||||
MaxAttempts: q.MaxAttempts,
|
MaxAttempts: q.MaxAttempts,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
QueueLength: q.Length,
|
QueueLength: q.Length,
|
||||||
|
Name: name,
|
||||||
}, exemplar)
|
}, exemplar)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -214,6 +214,13 @@ func initRepository(ctx models.DBContext, repoPath string, u *models.User, repo
|
|||||||
repo.DefaultBranch = "master"
|
repo.DefaultBranch = "master"
|
||||||
if len(opts.DefaultBranch) > 0 {
|
if len(opts.DefaultBranch) > 0 {
|
||||||
repo.DefaultBranch = opts.DefaultBranch
|
repo.DefaultBranch = opts.DefaultBranch
|
||||||
|
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("openRepository: %v", err)
|
||||||
|
}
|
||||||
|
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
|
||||||
|
return fmt.Errorf("setDefaultBranch: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil {
|
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil {
|
||||||
|
|||||||
@@ -164,9 +164,16 @@ func NewFuncMap() []template.FuncMap {
|
|||||||
mimeType := mime.TypeByExtension(filepath.Ext(filename))
|
mimeType := mime.TypeByExtension(filepath.Ext(filename))
|
||||||
return strings.HasPrefix(mimeType, "image/")
|
return strings.HasPrefix(mimeType, "image/")
|
||||||
},
|
},
|
||||||
"TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string {
|
"TabSizeClass": func(ec interface{}, filename string) string {
|
||||||
|
var (
|
||||||
|
value *editorconfig.Editorconfig
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
if ec != nil {
|
if ec != nil {
|
||||||
def, err := ec.GetDefinitionForFilename(filename)
|
if value, ok = ec.(*editorconfig.Editorconfig); !ok || value == nil {
|
||||||
|
return "tab-size-8"
|
||||||
|
}
|
||||||
|
def, err := value.GetDefinitionForFilename(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("tab size class: getting definition for filename: %v", err)
|
log.Error("tab size class: getting definition for filename: %v", err)
|
||||||
return "tab-size-8"
|
return "tab-size-8"
|
||||||
@@ -282,8 +289,8 @@ func NewFuncMap() []template.FuncMap {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NotificationSettings": func() map[string]int {
|
"NotificationSettings": func() map[string]interface{} {
|
||||||
return map[string]int{
|
return map[string]interface{}{
|
||||||
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
|
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
|
||||||
"TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
|
"TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
|
||||||
"MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
|
"MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
|
||||||
|
|||||||
@@ -264,7 +264,11 @@ func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string
|
|||||||
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
||||||
return getDingtalkIssuesPayload(p.(*api.IssuePayload))
|
return getDingtalkIssuesPayload(p.(*api.IssuePayload))
|
||||||
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
||||||
return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload))
|
pl, ok := p.(*api.IssueCommentPayload)
|
||||||
|
if ok {
|
||||||
|
return getDingtalkIssueCommentPayload(pl)
|
||||||
|
}
|
||||||
|
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
|
||||||
case models.HookEventPush:
|
case models.HookEventPush:
|
||||||
return getDingtalkPushPayload(p.(*api.PushPayload))
|
return getDingtalkPushPayload(p.(*api.PushPayload))
|
||||||
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
||||||
|
|||||||
@@ -408,7 +408,11 @@ func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string)
|
|||||||
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
||||||
return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
|
return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
|
||||||
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
||||||
return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord)
|
pl, ok := p.(*api.IssueCommentPayload)
|
||||||
|
if ok {
|
||||||
|
return getDiscordIssueCommentPayload(pl, discord)
|
||||||
|
}
|
||||||
|
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
|
||||||
case models.HookEventPush:
|
case models.HookEventPush:
|
||||||
return getDiscordPushPayload(p.(*api.PushPayload), discord)
|
return getDiscordPushPayload(p.(*api.PushPayload), discord)
|
||||||
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
||||||
|
|||||||
@@ -183,13 +183,17 @@ func GetFeishuPayload(p api.Payloader, event models.HookEventType, meta string)
|
|||||||
return getFeishuForkPayload(p.(*api.ForkPayload))
|
return getFeishuForkPayload(p.(*api.ForkPayload))
|
||||||
case models.HookEventIssues:
|
case models.HookEventIssues:
|
||||||
return getFeishuIssuesPayload(p.(*api.IssuePayload))
|
return getFeishuIssuesPayload(p.(*api.IssuePayload))
|
||||||
case models.HookEventIssueComment:
|
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
||||||
return getFeishuIssueCommentPayload(p.(*api.IssueCommentPayload))
|
pl, ok := p.(*api.IssueCommentPayload)
|
||||||
|
if ok {
|
||||||
|
return getFeishuIssueCommentPayload(pl)
|
||||||
|
}
|
||||||
|
return getFeishuPullRequestPayload(p.(*api.PullRequestPayload))
|
||||||
case models.HookEventPush:
|
case models.HookEventPush:
|
||||||
return getFeishuPushPayload(p.(*api.PushPayload))
|
return getFeishuPushPayload(p.(*api.PushPayload))
|
||||||
case models.HookEventPullRequest:
|
case models.HookEventPullRequest:
|
||||||
return getFeishuPullRequestPayload(p.(*api.PullRequestPayload))
|
return getFeishuPullRequestPayload(p.(*api.PullRequestPayload))
|
||||||
case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected, models.HookEventPullRequestComment:
|
case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected:
|
||||||
return getFeishuPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
|
return getFeishuPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
|
||||||
case models.HookEventRepository:
|
case models.HookEventRepository:
|
||||||
return getFeishuRepositoryPayload(p.(*api.RepositoryPayload))
|
return getFeishuRepositoryPayload(p.(*api.RepositoryPayload))
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
|
|||||||
linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink)
|
linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink)
|
||||||
case api.HookIssueDemilestoned:
|
case api.HookIssueDemilestoned:
|
||||||
text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink)
|
text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink)
|
||||||
|
case api.HookIssueReviewed:
|
||||||
|
text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink)
|
||||||
}
|
}
|
||||||
if withSender {
|
if withSender {
|
||||||
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
|
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
|
||||||
|
|||||||
@@ -230,7 +230,11 @@ func GetMatrixPayload(p api.Payloader, event models.HookEventType, meta string)
|
|||||||
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
||||||
return getMatrixIssuesPayload(p.(*api.IssuePayload), matrix)
|
return getMatrixIssuesPayload(p.(*api.IssuePayload), matrix)
|
||||||
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
||||||
return getMatrixIssueCommentPayload(p.(*api.IssueCommentPayload), matrix)
|
pl, ok := p.(*api.IssueCommentPayload)
|
||||||
|
if ok {
|
||||||
|
return getMatrixIssueCommentPayload(pl, matrix)
|
||||||
|
}
|
||||||
|
return getMatrixPullRequestPayload(p.(*api.PullRequestPayload), matrix)
|
||||||
case models.HookEventPush:
|
case models.HookEventPush:
|
||||||
return getMatrixPushPayload(p.(*api.PushPayload), matrix)
|
return getMatrixPushPayload(p.(*api.PushPayload), matrix)
|
||||||
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
||||||
|
|||||||
@@ -558,7 +558,11 @@ func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string)
|
|||||||
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
||||||
return getMSTeamsIssuesPayload(p.(*api.IssuePayload))
|
return getMSTeamsIssuesPayload(p.(*api.IssuePayload))
|
||||||
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
||||||
return getMSTeamsIssueCommentPayload(p.(*api.IssueCommentPayload))
|
pl, ok := p.(*api.IssueCommentPayload)
|
||||||
|
if ok {
|
||||||
|
return getMSTeamsIssueCommentPayload(pl)
|
||||||
|
}
|
||||||
|
return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload))
|
||||||
case models.HookEventPush:
|
case models.HookEventPush:
|
||||||
return getMSTeamsPushPayload(p.(*api.PushPayload))
|
return getMSTeamsPushPayload(p.(*api.PushPayload))
|
||||||
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
||||||
|
|||||||
@@ -321,7 +321,11 @@ func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (
|
|||||||
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
||||||
return getSlackIssuesPayload(p.(*api.IssuePayload), slack)
|
return getSlackIssuesPayload(p.(*api.IssuePayload), slack)
|
||||||
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
||||||
return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
|
pl, ok := p.(*api.IssueCommentPayload)
|
||||||
|
if ok {
|
||||||
|
return getSlackIssueCommentPayload(pl, slack)
|
||||||
|
}
|
||||||
|
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
|
||||||
case models.HookEventPush:
|
case models.HookEventPush:
|
||||||
return getSlackPushPayload(p.(*api.PushPayload), slack)
|
return getSlackPushPayload(p.(*api.PushPayload), slack)
|
||||||
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
||||||
|
|||||||
@@ -206,7 +206,11 @@ func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string
|
|||||||
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
|
||||||
return getTelegramIssuesPayload(p.(*api.IssuePayload))
|
return getTelegramIssuesPayload(p.(*api.IssuePayload))
|
||||||
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
case models.HookEventIssueComment, models.HookEventPullRequestComment:
|
||||||
return getTelegramIssueCommentPayload(p.(*api.IssueCommentPayload))
|
pl, ok := p.(*api.IssueCommentPayload)
|
||||||
|
if ok {
|
||||||
|
return getTelegramIssueCommentPayload(pl)
|
||||||
|
}
|
||||||
|
return getTelegramPullRequestPayload(p.(*api.PullRequestPayload))
|
||||||
case models.HookEventPush:
|
case models.HookEventPush:
|
||||||
return getTelegramPushPayload(p.(*api.PushPayload))
|
return getTelegramPushPayload(p.(*api.PushPayload))
|
||||||
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
|
||||||
|
|||||||
@@ -1843,12 +1843,12 @@ dashboard.operation_switch = Switch
|
|||||||
dashboard.operation_run = Run
|
dashboard.operation_run = Run
|
||||||
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
|
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
|
||||||
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
|
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
|
||||||
dashboard.task.started=Started Task: %s
|
dashboard.task.started=Started Task: %[1]s
|
||||||
dashboard.task.process=Task: %s
|
dashboard.task.process=Task: %[1]s
|
||||||
dashboard.task.cancelled=Task: %s cancelled: %[3]s
|
dashboard.task.cancelled=Task: %[1]s cancelled: %[3]s
|
||||||
dashboard.task.error=Error in Task: %s: %[3]s
|
dashboard.task.error=Error in Task: %[1]s: %[3]s
|
||||||
dashboard.task.finished=Task: %s started by %s has finished
|
dashboard.task.finished=Task: %[1]s started by %[2]s has finished
|
||||||
dashboard.task.unknown=Unknown task: %s
|
dashboard.task.unknown=Unknown task: %[1]s
|
||||||
dashboard.cron.started=Started Cron: %[1]s
|
dashboard.cron.started=Started Cron: %[1]s
|
||||||
dashboard.cron.process=Cron: %[1]s
|
dashboard.cron.process=Cron: %[1]s
|
||||||
dashboard.cron.cancelled=Cron: %s cancelled: %[3]s
|
dashboard.cron.cancelled=Cron: %s cancelled: %[3]s
|
||||||
|
|||||||
@@ -153,8 +153,11 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) {
|
|||||||
ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
|
ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if form.From == "monitor" {
|
||||||
ctx.Redirect(setting.AppSubURL + "/admin")
|
ctx.Redirect(setting.AppSubURL + "/admin/monitor")
|
||||||
|
} else {
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/admin")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTestMail send test mail to confirm mail service is OK
|
// SendTestMail send test mail to confirm mail service is OK
|
||||||
@@ -331,7 +334,7 @@ func MonitorCancel(ctx *context.Context) {
|
|||||||
pid := ctx.ParamsInt64("pid")
|
pid := ctx.ParamsInt64("pid")
|
||||||
process.GetManager().Cancel(pid)
|
process.GetManager().Cancel(pid)
|
||||||
ctx.JSON(200, map[string]interface{}{
|
ctx.JSON(200, map[string]interface{}{
|
||||||
"redirect": ctx.Repo.RepoLink + "/admin/monitor",
|
"redirect": setting.AppSubURL + "/admin/monitor",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,37 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func statusStringToNotificationStatus(status string) models.NotificationStatus {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(status)) {
|
||||||
|
case "unread":
|
||||||
|
return models.NotificationStatusUnread
|
||||||
|
case "read":
|
||||||
|
return models.NotificationStatusRead
|
||||||
|
case "pinned":
|
||||||
|
return models.NotificationStatusPinned
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusStringsToNotificationStatuses(statuses []string, defaultStatuses []string) []models.NotificationStatus {
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
statuses = defaultStatuses
|
||||||
|
}
|
||||||
|
results := make([]models.NotificationStatus, 0, len(statuses))
|
||||||
|
for _, status := range statuses {
|
||||||
|
notificationStatus := statusStringToNotificationStatus(status)
|
||||||
|
if notificationStatus > 0 {
|
||||||
|
results = append(results, notificationStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
// ListRepoNotifications list users's notification threads on a specific repo
|
// ListRepoNotifications list users's notification threads on a specific repo
|
||||||
func ListRepoNotifications(ctx *context.APIContext) {
|
func ListRepoNotifications(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
|
// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
|
||||||
@@ -39,6 +67,14 @@ func ListRepoNotifications(ctx *context.APIContext) {
|
|||||||
// description: If true, show notifications marked as read. Default value is false
|
// description: If true, show notifications marked as read. Default value is false
|
||||||
// type: string
|
// type: string
|
||||||
// required: false
|
// required: false
|
||||||
|
// - name: status-types
|
||||||
|
// in: query
|
||||||
|
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned"
|
||||||
|
// type: array
|
||||||
|
// collectionFormat: multi
|
||||||
|
// items:
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
// - name: since
|
// - name: since
|
||||||
// in: query
|
// in: query
|
||||||
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
|
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
|
||||||
@@ -75,9 +111,10 @@ func ListRepoNotifications(ctx *context.APIContext) {
|
|||||||
UpdatedBeforeUnix: before,
|
UpdatedBeforeUnix: before,
|
||||||
UpdatedAfterUnix: since,
|
UpdatedAfterUnix: since,
|
||||||
}
|
}
|
||||||
qAll := strings.Trim(ctx.Query("all"), " ")
|
|
||||||
if qAll != "true" {
|
if !ctx.QueryBool("all") {
|
||||||
opts.Status = models.NotificationStatusUnread
|
statuses := ctx.QueryStrings("status-types")
|
||||||
|
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
|
||||||
}
|
}
|
||||||
nl, err := models.GetNotifications(opts)
|
nl, err := models.GetNotifications(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -97,7 +134,7 @@ func ListRepoNotifications(ctx *context.APIContext) {
|
|||||||
func ReadRepoNotifications(ctx *context.APIContext) {
|
func ReadRepoNotifications(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
|
// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
|
||||||
// ---
|
// ---
|
||||||
// summary: Mark notification threads as read on a specific repo
|
// summary: Mark notification threads as read, pinned or unread on a specific repo
|
||||||
// consumes:
|
// consumes:
|
||||||
// - application/json
|
// - application/json
|
||||||
// produces:
|
// produces:
|
||||||
@@ -113,6 +150,24 @@ func ReadRepoNotifications(ctx *context.APIContext) {
|
|||||||
// description: name of the repo
|
// description: name of the repo
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
|
// - name: all
|
||||||
|
// in: query
|
||||||
|
// description: If true, mark all notifications on this repo. Default value is false
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: status-types
|
||||||
|
// in: query
|
||||||
|
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
|
||||||
|
// type: array
|
||||||
|
// collectionFormat: multi
|
||||||
|
// items:
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: to-status
|
||||||
|
// in: query
|
||||||
|
// description: Status to mark notifications as. Defaults to read.
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
// - name: last_read_at
|
// - name: last_read_at
|
||||||
// in: query
|
// in: query
|
||||||
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
|
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
|
||||||
@@ -135,11 +190,17 @@ func ReadRepoNotifications(ctx *context.APIContext) {
|
|||||||
lastRead = tmpLastRead.Unix()
|
lastRead = tmpLastRead.Unix()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := models.FindNotificationOptions{
|
opts := models.FindNotificationOptions{
|
||||||
UserID: ctx.User.ID,
|
UserID: ctx.User.ID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
UpdatedBeforeUnix: lastRead,
|
UpdatedBeforeUnix: lastRead,
|
||||||
Status: models.NotificationStatusUnread,
|
}
|
||||||
|
|
||||||
|
if !ctx.QueryBool("all") {
|
||||||
|
statuses := ctx.QueryStrings("status-types")
|
||||||
|
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
|
||||||
|
log.Error("%v", opts.Status)
|
||||||
}
|
}
|
||||||
nl, err := models.GetNotifications(opts)
|
nl, err := models.GetNotifications(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -147,8 +208,13 @@ func ReadRepoNotifications(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
|
||||||
|
if targetStatus == 0 {
|
||||||
|
targetStatus = models.NotificationStatusRead
|
||||||
|
}
|
||||||
|
|
||||||
for _, n := range nl {
|
for _, n := range nl {
|
||||||
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
|
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.InternalServerError(err)
|
ctx.InternalServerError(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ func ReadThread(ctx *context.APIContext) {
|
|||||||
// description: id of notification thread
|
// description: id of notification thread
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
|
// - name: to-status
|
||||||
|
// in: query
|
||||||
|
// description: Status to mark notifications as
|
||||||
|
// type: string
|
||||||
|
// default: read
|
||||||
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
// "205":
|
// "205":
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
@@ -75,7 +81,12 @@ func ReadThread(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
|
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
|
||||||
|
if targetStatus == 0 {
|
||||||
|
targetStatus = models.NotificationStatusRead
|
||||||
|
}
|
||||||
|
|
||||||
|
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.InternalServerError(err)
|
ctx.InternalServerError(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ func ListNotifications(ctx *context.APIContext) {
|
|||||||
// description: If true, show notifications marked as read. Default value is false
|
// description: If true, show notifications marked as read. Default value is false
|
||||||
// type: string
|
// type: string
|
||||||
// required: false
|
// required: false
|
||||||
|
// - name: status-types
|
||||||
|
// in: query
|
||||||
|
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned."
|
||||||
|
// type: array
|
||||||
|
// collectionFormat: multi
|
||||||
|
// items:
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
// - name: since
|
// - name: since
|
||||||
// in: query
|
// in: query
|
||||||
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
|
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
|
||||||
@@ -64,9 +72,9 @@ func ListNotifications(ctx *context.APIContext) {
|
|||||||
UpdatedBeforeUnix: before,
|
UpdatedBeforeUnix: before,
|
||||||
UpdatedAfterUnix: since,
|
UpdatedAfterUnix: since,
|
||||||
}
|
}
|
||||||
qAll := strings.Trim(ctx.Query("all"), " ")
|
if !ctx.QueryBool("all") {
|
||||||
if qAll != "true" {
|
statuses := ctx.QueryStrings("status-types")
|
||||||
opts.Status = models.NotificationStatusUnread
|
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
|
||||||
}
|
}
|
||||||
nl, err := models.GetNotifications(opts)
|
nl, err := models.GetNotifications(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,11 +90,11 @@ func ListNotifications(ctx *context.APIContext) {
|
|||||||
ctx.JSON(http.StatusOK, nl.APIFormat())
|
ctx.JSON(http.StatusOK, nl.APIFormat())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadNotifications mark notification threads as read
|
// ReadNotifications mark notification threads as read, unread, or pinned
|
||||||
func ReadNotifications(ctx *context.APIContext) {
|
func ReadNotifications(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /notifications notification notifyReadList
|
// swagger:operation PUT /notifications notification notifyReadList
|
||||||
// ---
|
// ---
|
||||||
// summary: Mark notification threads as read
|
// summary: Mark notification threads as read, pinned or unread
|
||||||
// consumes:
|
// consumes:
|
||||||
// - application/json
|
// - application/json
|
||||||
// produces:
|
// produces:
|
||||||
@@ -98,6 +106,24 @@ func ReadNotifications(ctx *context.APIContext) {
|
|||||||
// type: string
|
// type: string
|
||||||
// format: date-time
|
// format: date-time
|
||||||
// required: false
|
// required: false
|
||||||
|
// - name: all
|
||||||
|
// in: query
|
||||||
|
// description: If true, mark all notifications on this repo. Default value is false
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: status-types
|
||||||
|
// in: query
|
||||||
|
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
|
||||||
|
// type: array
|
||||||
|
// collectionFormat: multi
|
||||||
|
// items:
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: to-status
|
||||||
|
// in: query
|
||||||
|
// description: Status to mark notifications as, Defaults to read.
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
// "205":
|
// "205":
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
@@ -117,7 +143,10 @@ func ReadNotifications(ctx *context.APIContext) {
|
|||||||
opts := models.FindNotificationOptions{
|
opts := models.FindNotificationOptions{
|
||||||
UserID: ctx.User.ID,
|
UserID: ctx.User.ID,
|
||||||
UpdatedBeforeUnix: lastRead,
|
UpdatedBeforeUnix: lastRead,
|
||||||
Status: models.NotificationStatusUnread,
|
}
|
||||||
|
if !ctx.QueryBool("all") {
|
||||||
|
statuses := ctx.QueryStrings("status-types")
|
||||||
|
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
|
||||||
}
|
}
|
||||||
nl, err := models.GetNotifications(opts)
|
nl, err := models.GetNotifications(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -125,8 +154,13 @@ func ReadNotifications(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
|
||||||
|
if targetStatus == 0 {
|
||||||
|
targetStatus = models.NotificationStatusRead
|
||||||
|
}
|
||||||
|
|
||||||
for _, n := range nl {
|
for _, n := range nl {
|
||||||
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
|
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.InternalServerError(err)
|
ctx.InternalServerError(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Comm
|
|||||||
},
|
},
|
||||||
Date: commit.Committer.When.Format(time.RFC3339),
|
Date: commit.Committer.When.Format(time.RFC3339),
|
||||||
},
|
},
|
||||||
Message: commit.Summary(),
|
Message: commit.Message(),
|
||||||
Tree: &api.CommitMeta{
|
Tree: &api.CommitMeta{
|
||||||
URL: repo.APIURL() + "/git/trees/" + commit.ID.String(),
|
URL: repo.APIURL() + "/git/trees/" + commit.ID.String(),
|
||||||
SHA: commit.ID.String(),
|
SHA: commit.ID.String(),
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import (
|
|||||||
|
|
||||||
"gitea.com/macaron/i18n"
|
"gitea.com/macaron/i18n"
|
||||||
"gitea.com/macaron/macaron"
|
"gitea.com/macaron/macaron"
|
||||||
unknwoni18n "github.com/unknwon/i18n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkRunMode() {
|
func checkRunMode() {
|
||||||
@@ -124,8 +123,6 @@ func GlobalInit(ctx context.Context) {
|
|||||||
// Setup i18n
|
// Setup i18n
|
||||||
InitLocales()
|
InitLocales()
|
||||||
|
|
||||||
log.Info("%s", unknwoni18n.Tr("en-US", "admin.dashboard.delete_repo_archives"))
|
|
||||||
|
|
||||||
NewServices()
|
NewServices()
|
||||||
|
|
||||||
if setting.InstallLock {
|
if setting.InstallLock {
|
||||||
|
|||||||
@@ -141,7 +141,13 @@ func RefBlame(ctx *context.Context) {
|
|||||||
ctx.Data["FileSize"] = blob.Size()
|
ctx.Data["FileSize"] = blob.Size()
|
||||||
ctx.Data["FileName"] = blob.Name()
|
ctx.Data["FileName"] = blob.Name()
|
||||||
|
|
||||||
blameReader, err := git.CreateBlameReader(models.RepoPath(userName, repoName), commitID, fileName)
|
ctx.Data["NumLines"], err = blob.GetBlobLineCount()
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound("GetBlobLineCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blameReader, err := git.CreateBlameReader(ctx.Req.Context(), models.RepoPath(userName, repoName), commitID, fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.NotFound("CreateBlameReader", err)
|
ctx.NotFound("CreateBlameReader", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ func SingleRelease(ctx *context.Context) {
|
|||||||
|
|
||||||
release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag"))
|
release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if models.IsErrReleaseNotExist(err) {
|
||||||
|
ctx.NotFound("GetRelease", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.ServerError("GetReleasesByRepoID", err)
|
ctx.ServerError("GetReleasesByRepoID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
|
|||||||
rel.Publisher, rel.Repo, git.TagPrefix+rel.TagName,
|
rel.Publisher, rel.Repo, git.TagPrefix+rel.TagName,
|
||||||
git.EmptySHA, commit.ID.String(), repository.NewPushCommits())
|
git.EmptySHA, commit.ID.String(), repository.NewPushCommits())
|
||||||
notification.NotifyCreateRef(rel.Publisher, rel.Repo, "tag", git.TagPrefix+rel.TagName)
|
notification.NotifyCreateRef(rel.Publisher, rel.Repo, "tag", git.TagPrefix+rel.TagName)
|
||||||
|
rel.CreatedUnix = timeutil.TimeStampNow()
|
||||||
}
|
}
|
||||||
commit, err := gitRepo.GetTagCommit(rel.TagName)
|
commit, err := gitRepo.GetTagCommit(rel.TagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,7 +54,6 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rel.Sha1 = commit.ID.String()
|
rel.Sha1 = commit.ID.String()
|
||||||
rel.CreatedUnix = timeutil.TimeStampNow()
|
|
||||||
rel.NumCommits, err = commit.CommitsCount()
|
rel.NumCommits, err = commit.CommitsCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("CommitsCount: %v", err)
|
return fmt.Errorf("CommitsCount: %v", err)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package release
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
@@ -101,3 +102,153 @@ func TestRelease_Create(t *testing.T) {
|
|||||||
IsTag: true,
|
IsTag: true,
|
||||||
}, nil))
|
}, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRelease_Update(t *testing.T) {
|
||||||
|
assert.NoError(t, models.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
|
||||||
|
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
|
||||||
|
repoPath := models.RepoPath(user.Name, repo.Name)
|
||||||
|
|
||||||
|
gitRepo, err := git.OpenRepository(repoPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
// Test a changed release
|
||||||
|
assert.NoError(t, CreateRelease(gitRepo, &models.Release{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
PublisherID: user.ID,
|
||||||
|
TagName: "v1.1.1",
|
||||||
|
Target: "master",
|
||||||
|
Title: "v1.1.1 is released",
|
||||||
|
Note: "v1.1.1 is released",
|
||||||
|
IsDraft: false,
|
||||||
|
IsPrerelease: false,
|
||||||
|
IsTag: false,
|
||||||
|
}, nil))
|
||||||
|
release, err := models.GetRelease(repo.ID, "v1.1.1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
releaseCreatedUnix := release.CreatedUnix
|
||||||
|
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
|
||||||
|
release.Note = "Changed note"
|
||||||
|
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
|
||||||
|
release, err = models.GetReleaseByID(release.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
|
||||||
|
|
||||||
|
// Test a changed draft
|
||||||
|
assert.NoError(t, CreateRelease(gitRepo, &models.Release{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
PublisherID: user.ID,
|
||||||
|
TagName: "v1.2.1",
|
||||||
|
Target: "65f1bf2",
|
||||||
|
Title: "v1.2.1 is draft",
|
||||||
|
Note: "v1.2.1 is draft",
|
||||||
|
IsDraft: true,
|
||||||
|
IsPrerelease: false,
|
||||||
|
IsTag: false,
|
||||||
|
}, nil))
|
||||||
|
release, err = models.GetRelease(repo.ID, "v1.2.1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
releaseCreatedUnix = release.CreatedUnix
|
||||||
|
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
|
||||||
|
release.Title = "Changed title"
|
||||||
|
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
|
||||||
|
release, err = models.GetReleaseByID(release.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
|
||||||
|
|
||||||
|
// Test a changed pre-release
|
||||||
|
assert.NoError(t, CreateRelease(gitRepo, &models.Release{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
PublisherID: user.ID,
|
||||||
|
TagName: "v1.3.1",
|
||||||
|
Target: "65f1bf2",
|
||||||
|
Title: "v1.3.1 is pre-released",
|
||||||
|
Note: "v1.3.1 is pre-released",
|
||||||
|
IsDraft: false,
|
||||||
|
IsPrerelease: true,
|
||||||
|
IsTag: false,
|
||||||
|
}, nil))
|
||||||
|
release, err = models.GetRelease(repo.ID, "v1.3.1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
releaseCreatedUnix = release.CreatedUnix
|
||||||
|
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
|
||||||
|
release.Title = "Changed title"
|
||||||
|
release.Note = "Changed note"
|
||||||
|
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
|
||||||
|
release, err = models.GetReleaseByID(release.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRelease_createTag(t *testing.T) {
|
||||||
|
assert.NoError(t, models.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
|
||||||
|
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
|
||||||
|
repoPath := models.RepoPath(user.Name, repo.Name)
|
||||||
|
|
||||||
|
gitRepo, err := git.OpenRepository(repoPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
// Test a changed release
|
||||||
|
release := &models.Release{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
PublisherID: user.ID,
|
||||||
|
TagName: "v2.1.1",
|
||||||
|
Target: "master",
|
||||||
|
Title: "v2.1.1 is released",
|
||||||
|
Note: "v2.1.1 is released",
|
||||||
|
IsDraft: false,
|
||||||
|
IsPrerelease: false,
|
||||||
|
IsTag: false,
|
||||||
|
}
|
||||||
|
assert.NoError(t, createTag(gitRepo, release))
|
||||||
|
assert.NotEmpty(t, release.CreatedUnix)
|
||||||
|
releaseCreatedUnix := release.CreatedUnix
|
||||||
|
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
|
||||||
|
release.Note = "Changed note"
|
||||||
|
assert.NoError(t, createTag(gitRepo, release))
|
||||||
|
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
|
||||||
|
|
||||||
|
// Test a changed draft
|
||||||
|
release = &models.Release{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
PublisherID: user.ID,
|
||||||
|
TagName: "v2.2.1",
|
||||||
|
Target: "65f1bf2",
|
||||||
|
Title: "v2.2.1 is draft",
|
||||||
|
Note: "v2.2.1 is draft",
|
||||||
|
IsDraft: true,
|
||||||
|
IsPrerelease: false,
|
||||||
|
IsTag: false,
|
||||||
|
}
|
||||||
|
assert.NoError(t, createTag(gitRepo, release))
|
||||||
|
releaseCreatedUnix = release.CreatedUnix
|
||||||
|
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
|
||||||
|
release.Title = "Changed title"
|
||||||
|
assert.NoError(t, createTag(gitRepo, release))
|
||||||
|
assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
|
||||||
|
|
||||||
|
// Test a changed pre-release
|
||||||
|
release = &models.Release{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
PublisherID: user.ID,
|
||||||
|
TagName: "v2.3.1",
|
||||||
|
Target: "65f1bf2",
|
||||||
|
Title: "v2.3.1 is pre-released",
|
||||||
|
Note: "v2.3.1 is pre-released",
|
||||||
|
IsDraft: false,
|
||||||
|
IsPrerelease: true,
|
||||||
|
IsTag: false,
|
||||||
|
}
|
||||||
|
assert.NoError(t, createTag(gitRepo, release))
|
||||||
|
releaseCreatedUnix = release.CreatedUnix
|
||||||
|
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
|
||||||
|
release.Title = "Changed title"
|
||||||
|
release.Note = "Changed note"
|
||||||
|
assert.NoError(t, createTag(gitRepo, release))
|
||||||
|
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached table segment">
|
<div class="ui attached table segment">
|
||||||
<form method="post" action="{{AppSubUrl}}/admin">
|
<form method="post" action="{{AppSubUrl}}/admin">
|
||||||
|
<input type="hidden" name="from" value="monitor"/>
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<table class="ui very basic striped table">
|
<table class="ui very basic striped table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
<div class="markdown">
|
<div class="markdown">
|
||||||
<pre><code>touch README.md
|
<pre><code>touch README.md
|
||||||
git init
|
git init
|
||||||
{{if ne .Repository.DefaultBranch "master"}}git branch -m master {{.Repository.DefaultBranch}}{{end}}
|
{{if ne .Repository.DefaultBranch "master"}}git checkout -b {{.Repository.DefaultBranch}}{{end}}
|
||||||
git add README.md
|
git add README.md
|
||||||
git commit -m "first commit"
|
git commit -m "first commit"
|
||||||
git remote add origin <span class="clone-url">{{if $.DisableSSH}}{{$.CloneLink.HTTPS}}{{else}}{{$.CloneLink.SSH}}{{end}}</span>
|
git remote add origin <span class="clone-url">{{if $.DisableSSH}}{{$.CloneLink.HTTPS}}{{else}}{{$.CloneLink.SSH}}{{end}}</span>
|
||||||
|
|||||||
@@ -128,12 +128,12 @@
|
|||||||
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span>
|
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span>
|
||||||
{{range .Labels}}
|
{{range .Labels}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
|
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range .OrgLabels}}
|
{{range .OrgLabels}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
|
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
<div class="selected">
|
<div class="selected">
|
||||||
{{range .Issue.Assignees}}
|
{{range .Issue.Assignees}}
|
||||||
<div class="item" style="margin-bottom: 10px;">
|
<div class="item" style="margin-bottom: 10px;">
|
||||||
<a href="{{$.RepoLink}}/issues?assignee={{.ID}}"><img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}}</a>
|
<a href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}"><img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}}</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -459,6 +459,16 @@
|
|||||||
"name": "all",
|
"name": "all",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned.",
|
||||||
|
"name": "status-types",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
@@ -502,7 +512,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"notification"
|
"notification"
|
||||||
],
|
],
|
||||||
"summary": "Mark notification threads as read",
|
"summary": "Mark notification threads as read, pinned or unread",
|
||||||
"operationId": "notifyReadList",
|
"operationId": "notifyReadList",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -511,6 +521,28 @@
|
|||||||
"description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
|
"description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
|
||||||
"name": "last_read_at",
|
"name": "last_read_at",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "If true, mark all notifications on this repo. Default value is false",
|
||||||
|
"name": "all",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
|
||||||
|
"name": "status-types",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Status to mark notifications as, Defaults to read.",
|
||||||
|
"name": "to-status",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -587,6 +619,13 @@
|
|||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "read",
|
||||||
|
"description": "Status to mark notifications as",
|
||||||
|
"name": "to-status",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -6290,6 +6329,16 @@
|
|||||||
"name": "all",
|
"name": "all",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned",
|
||||||
|
"name": "status-types",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
@@ -6333,7 +6382,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"notification"
|
"notification"
|
||||||
],
|
],
|
||||||
"summary": "Mark notification threads as read on a specific repo",
|
"summary": "Mark notification threads as read, pinned or unread on a specific repo",
|
||||||
"operationId": "notifyReadRepoList",
|
"operationId": "notifyReadRepoList",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -6350,6 +6399,28 @@
|
|||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "If true, mark all notifications on this repo. Default value is false",
|
||||||
|
"name": "all",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
|
||||||
|
"name": "status-types",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Status to mark notifications as. Defaults to read.",
|
||||||
|
"name": "to-status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
|
|||||||
135
web_src/js/features/eventsource.sharedworker.js
Normal file
135
web_src/js/features/eventsource.sharedworker.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
self.name = 'eventsource.sharedworker.js';
|
||||||
|
|
||||||
|
const sourcesByUrl = {};
|
||||||
|
const sourcesByPort = {};
|
||||||
|
|
||||||
|
class Source {
|
||||||
|
constructor(url) {
|
||||||
|
this.url = url;
|
||||||
|
this.eventSource = new EventSource(url);
|
||||||
|
this.listening = {};
|
||||||
|
this.clients = [];
|
||||||
|
this.listen('open');
|
||||||
|
this.listen('logout');
|
||||||
|
this.listen('notification-count');
|
||||||
|
this.listen('error');
|
||||||
|
}
|
||||||
|
|
||||||
|
register(port) {
|
||||||
|
if (this.clients.includes(port)) return;
|
||||||
|
|
||||||
|
this.clients.push(port);
|
||||||
|
|
||||||
|
port.postMessage({
|
||||||
|
type: 'status',
|
||||||
|
message: `registered to ${this.url}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deregister(port) {
|
||||||
|
const portIdx = this.clients.indexOf(port);
|
||||||
|
if (portIdx < 0) {
|
||||||
|
return this.clients.length;
|
||||||
|
}
|
||||||
|
this.clients.splice(portIdx, 1);
|
||||||
|
return this.clients.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (!this.eventSource) return;
|
||||||
|
|
||||||
|
this.eventSource.close();
|
||||||
|
this.eventSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(eventType) {
|
||||||
|
if (this.listening[eventType]) return;
|
||||||
|
this.listening[eventType] = true;
|
||||||
|
const self = this;
|
||||||
|
this.eventSource.addEventListener(eventType, (event) => {
|
||||||
|
self.notifyClients({
|
||||||
|
type: eventType,
|
||||||
|
data: event.data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyClients(event) {
|
||||||
|
for (const client of this.clients) {
|
||||||
|
client.postMessage(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status(port) {
|
||||||
|
port.postMessage({
|
||||||
|
type: 'status',
|
||||||
|
message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onconnect = (e) => {
|
||||||
|
for (const port of e.ports) {
|
||||||
|
port.addEventListener('message', (event) => {
|
||||||
|
if (event.data.type === 'start') {
|
||||||
|
const url = event.data.url;
|
||||||
|
if (sourcesByUrl[url]) {
|
||||||
|
// we have a Source registered to this url
|
||||||
|
const source = sourcesByUrl[url];
|
||||||
|
source.register(port);
|
||||||
|
sourcesByPort[port] = source;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source = sourcesByPort[port];
|
||||||
|
if (source) {
|
||||||
|
if (source.eventSource && source.url === url) return;
|
||||||
|
|
||||||
|
// How this has happened I don't understand...
|
||||||
|
// deregister from that source
|
||||||
|
const count = source.deregister(port);
|
||||||
|
// Clean-up
|
||||||
|
if (count === 0) {
|
||||||
|
source.close();
|
||||||
|
sourcesByUrl[source.url] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create a new Source
|
||||||
|
source = new Source(url);
|
||||||
|
source.register(port);
|
||||||
|
sourcesByUrl[url] = source;
|
||||||
|
sourcesByPort[port] = source;
|
||||||
|
} else if (event.data.type === 'listen') {
|
||||||
|
const source = sourcesByPort[port];
|
||||||
|
source.listen(event.data.eventType);
|
||||||
|
} else if (event.data.type === 'close') {
|
||||||
|
const source = sourcesByPort[port];
|
||||||
|
|
||||||
|
if (!source) return;
|
||||||
|
|
||||||
|
const count = source.deregister(port);
|
||||||
|
if (count === 0) {
|
||||||
|
source.close();
|
||||||
|
sourcesByUrl[source.url] = null;
|
||||||
|
sourcesByPort[port] = null;
|
||||||
|
}
|
||||||
|
} else if (event.data.type === 'status') {
|
||||||
|
const source = sourcesByPort[port];
|
||||||
|
if (!source) {
|
||||||
|
port.postMessage({
|
||||||
|
type: 'status',
|
||||||
|
message: 'not connected',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
source.status(port);
|
||||||
|
} else {
|
||||||
|
// just send it back
|
||||||
|
port.postMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: `received but don't know how to handle: ${event.data}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
port.start();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,7 +18,25 @@ export function initNotificationsTable() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initNotificationCount() {
|
async function receiveUpdateCount(event) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
const notificationCount = document.querySelector('.notification_count');
|
||||||
|
if (data.Count > 0) {
|
||||||
|
notificationCount.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
notificationCount.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationCount.textContent = `${data.Count}`;
|
||||||
|
await updateNotificationTable();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initNotificationCount() {
|
||||||
const notificationCount = $('.notification_count');
|
const notificationCount = $('.notification_count');
|
||||||
|
|
||||||
if (!notificationCount.length) {
|
if (!notificationCount.length) {
|
||||||
@@ -26,36 +44,52 @@ export function initNotificationCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource) {
|
if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource) {
|
||||||
// Try to connect to the event source first
|
// Try to connect to the event source via the shared worker first
|
||||||
const source = new EventSource(`${AppSubUrl}/user/events`);
|
if (window.SharedWorker) {
|
||||||
source.addEventListener('notification-count', async (e) => {
|
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
|
||||||
try {
|
worker.addEventListener('error', (event) => {
|
||||||
const data = JSON.parse(e.data);
|
console.error(event);
|
||||||
|
});
|
||||||
const notificationCount = $('.notification_count');
|
worker.port.onmessageerror = () => {
|
||||||
if (data.Count === 0) {
|
console.error('Unable to deserialize message');
|
||||||
notificationCount.addClass('hidden');
|
};
|
||||||
} else {
|
worker.port.postMessage({
|
||||||
notificationCount.removeClass('hidden');
|
type: 'start',
|
||||||
|
url: `${window.location.origin}${AppSubUrl}/user/events`,
|
||||||
|
});
|
||||||
|
worker.port.addEventListener('message', (event) => {
|
||||||
|
if (!event.data || !event.data.type) {
|
||||||
|
console.error(event);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (event.data.type === 'notification-count') {
|
||||||
|
receiveUpdateCount(event.data);
|
||||||
|
} else if (event.data.type === 'error') {
|
||||||
|
console.error(event.data);
|
||||||
|
} else if (event.data.type === 'logout') {
|
||||||
|
if (event.data !== 'here') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
worker.port.postMessage({
|
||||||
|
type: 'close',
|
||||||
|
});
|
||||||
|
worker.port.close();
|
||||||
|
window.location.href = AppSubUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
worker.port.addEventListener('error', (e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
worker.port.start();
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
worker.port.postMessage({
|
||||||
|
type: 'close',
|
||||||
|
});
|
||||||
|
worker.port.close();
|
||||||
|
});
|
||||||
|
|
||||||
notificationCount.text(`${data.Count}`);
|
return;
|
||||||
await updateNotificationTable();
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
source.addEventListener('logout', async (e) => {
|
|
||||||
if (e.data !== 'here') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
source.close();
|
|
||||||
window.location.href = AppSubUrl;
|
|
||||||
});
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
source.close();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NotificationSettings.MinTimeout <= 0) {
|
if (NotificationSettings.MinTimeout <= 0) {
|
||||||
|
|||||||
@@ -915,6 +915,7 @@ async function initRepository() {
|
|||||||
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
||||||
dictFileTooBig: $dropzone.data('file-too-big'),
|
dictFileTooBig: $dropzone.data('file-too-big'),
|
||||||
dictRemoveFile: $dropzone.data('remove-file'),
|
dictRemoveFile: $dropzone.data('remove-file'),
|
||||||
|
timeout: 0,
|
||||||
init() {
|
init() {
|
||||||
this.on('success', (file, data) => {
|
this.on('success', (file, data) => {
|
||||||
filenameDict[file.name] = {
|
filenameDict[file.name] = {
|
||||||
@@ -2308,6 +2309,7 @@ $(document).ready(async () => {
|
|||||||
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
||||||
dictFileTooBig: $dropzone.data('file-too-big'),
|
dictFileTooBig: $dropzone.data('file-too-big'),
|
||||||
dictRemoveFile: $dropzone.data('remove-file'),
|
dictRemoveFile: $dropzone.data('remove-file'),
|
||||||
|
timeout: 0,
|
||||||
init() {
|
init() {
|
||||||
this.on('success', (file, data) => {
|
this.on('success', (file, data) => {
|
||||||
filenameDict[file.name] = data.uuid;
|
filenameDict[file.name] = data.uuid;
|
||||||
@@ -2453,7 +2455,6 @@ $(document).ready(async () => {
|
|||||||
initTemplateSearch();
|
initTemplateSearch();
|
||||||
initContextPopups();
|
initContextPopups();
|
||||||
initNotificationsTable();
|
initNotificationsTable();
|
||||||
initNotificationCount();
|
|
||||||
initTribute();
|
initTribute();
|
||||||
|
|
||||||
// Repo clone url.
|
// Repo clone url.
|
||||||
@@ -2500,6 +2501,7 @@ $(document).ready(async () => {
|
|||||||
initClipboard(),
|
initClipboard(),
|
||||||
initUserHeatmap(),
|
initUserHeatmap(),
|
||||||
initServiceWorker(),
|
initServiceWorker(),
|
||||||
|
initNotificationCount(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
7
web_src/js/vendor/gitgraph.js
vendored
7
web_src/js/vendor/gitgraph.js
vendored
@@ -372,6 +372,10 @@ export default function gitGraph(canvas, rawGraphList, config) {
|
|||||||
inlineIntersect = false;
|
inlineIntersect = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (colomn === '|' && currentRow[colomnIndex - 1] && currentRow[colomnIndex - 1] === '\\') {
|
||||||
|
flows.splice(colomnIndex, 0, genNewFlow());
|
||||||
|
}
|
||||||
|
|
||||||
color = flows[colomnIndex].color;
|
color = flows[colomnIndex].color;
|
||||||
|
|
||||||
switch (colomn) {
|
switch (colomn) {
|
||||||
@@ -387,6 +391,9 @@ export default function gitGraph(canvas, rawGraphList, config) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case '|':
|
case '|':
|
||||||
|
if (prevColomn && prevColomn === '\\') {
|
||||||
|
x += config.unitSize;
|
||||||
|
}
|
||||||
drawLineUp(x, y, color);
|
drawLineUp(x, y, color);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -1271,33 +1271,18 @@ i.icon.centerlock {
|
|||||||
|
|
||||||
.emoji,
|
.emoji,
|
||||||
.reaction {
|
.reaction {
|
||||||
font-size: 1.5em;
|
font-size: 1.25em;
|
||||||
line-height: 1.2;
|
line-height: 1;
|
||||||
font-style: normal !important;
|
font-style: normal !important;
|
||||||
font-weight: normal !important;
|
font-weight: normal !important;
|
||||||
vertical-align: middle;
|
vertical-align: -.075em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#issue-title > .emoji {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.commit-summary > .emoji {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label > .emoji {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .emoji {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
.emoji img,
|
.emoji img,
|
||||||
.reaction img {
|
.reaction img {
|
||||||
border-width: 0 !important;
|
border-width: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
width: 1em !important;
|
width: 1em !important;
|
||||||
height: 1em !important;
|
height: 1em !important;
|
||||||
vertical-align: middle !important;
|
vertical-align: -.15em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2246,7 +2246,7 @@
|
|||||||
.select-reaction {
|
.select-reaction {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5rem;
|
padding: 0 .5rem;
|
||||||
|
|
||||||
&:not(.active) a {
|
&:not(.active) a {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -200,11 +200,6 @@ a:hover {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.view.issue .comment-list .comment .content > .bottom.segment a {
|
|
||||||
border: solid 1px #353945;
|
|
||||||
background-color: #353945;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.attached.header {
|
.ui.attached.header {
|
||||||
background: #404552;
|
background: #404552;
|
||||||
border: 1px solid #404552;
|
border: 1px solid #404552;
|
||||||
@@ -641,21 +636,6 @@ a.ui.basic.green.label:hover {
|
|||||||
border: 1px solid #404552;
|
border: 1px solid #404552;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.view.issue .comment-list .comment .content > .bottom.segment {
|
|
||||||
background: #353945;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository.view.issue .comment-list .comment .content .header {
|
|
||||||
color: #dbdbdb;
|
|
||||||
background-color: #404552;
|
|
||||||
border-bottom: 1px solid #353944;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository.view.issue .comment-list .comment .content .merge-section {
|
|
||||||
background-color: #404552;
|
|
||||||
border-top: 1px solid #353944;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository.view.issue .comment-list .event > .svg.issue-symbol {
|
.repository.view.issue .comment-list .event > .svg.issue-symbol {
|
||||||
background: #3b4954;
|
background: #3b4954;
|
||||||
}
|
}
|
||||||
@@ -677,18 +657,41 @@ a.ui.basic.green.label:hover {
|
|||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.view.issue .comment-list .comment .content .header:after {
|
.repository.view.issue .comment-list .comment .content {
|
||||||
border-right-color: #404552;
|
> .bottom.segment {
|
||||||
|
background: #353945;
|
||||||
|
a {
|
||||||
|
border: solid 1px #353945;
|
||||||
|
background-color: #353945;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
color: #dbdbdb;
|
||||||
|
background-color: #404552;
|
||||||
|
border-bottom: 1px solid #353944;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-section {
|
||||||
|
background-color: #404552;
|
||||||
|
border-top: 1px solid #353944;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header:after,
|
||||||
|
> .merge-section.no-header:after {
|
||||||
|
border-right-color: #404552;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header:before,
|
||||||
|
> .merge-section.no-header:before {
|
||||||
|
border-right-color: #404552;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.new.issue .comment.form .content:after {
|
.repository.new.issue .comment.form .content:after {
|
||||||
border-right-color: #353945;
|
border-right-color: #353945;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.view.issue .comment-list .comment .content .header:before {
|
|
||||||
border-right-color: #404552;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository.new.issue .comment.form .content:before {
|
.repository.new.issue .comment.form .content:before {
|
||||||
border-right-color: #353945;
|
border-right-color: #353945;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ module.exports = {
|
|||||||
serviceworker: [
|
serviceworker: [
|
||||||
resolve(__dirname, 'web_src/js/serviceworker.js'),
|
resolve(__dirname, 'web_src/js/serviceworker.js'),
|
||||||
],
|
],
|
||||||
|
'eventsource.sharedworker': [
|
||||||
|
resolve(__dirname, 'web_src/js/features/eventsource.sharedworker.js'),
|
||||||
|
],
|
||||||
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'),
|
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'),
|
||||||
...themes,
|
...themes,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user