diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go
index 9d53311d3564..8d5a5c7c8237 100644
--- a/tests/integration/api_packages_nuget_test.go
+++ b/tests/integration/api_packages_nuget_test.go
@@ -280,7 +280,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNotFound)
- req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, symbolID, symbolFilename))
+ req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFffff/%s", url, symbolFilename, symbolID, symbolFilename))
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusOK)
diff --git a/tests/integration/api_repo_lfs_locks_test.go b/tests/integration/api_repo_lfs_locks_test.go
index 0860f47533c8..2186933bd9bb 100644
--- a/tests/integration/api_repo_lfs_locks_test.go
+++ b/tests/integration/api_repo_lfs_locks_test.go
@@ -112,6 +112,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
if len(test.addTime) > 0 {
var lfsLock api.LFSLockResponse
DecodeJSON(t, resp, &lfsLock)
+ assert.Equal(t, test.user.Name, lfsLock.Lock.Owner.Name)
assert.EqualValues(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second
for _, id := range test.addTime {
resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now())
@@ -129,7 +130,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
DecodeJSON(t, resp, &lfsLocks)
assert.Len(t, lfsLocks.Locks, test.totalCount)
for i, lock := range lfsLocks.Locks {
- assert.EqualValues(t, test.locksOwners[i].DisplayName(), lock.Owner.Name)
+ assert.EqualValues(t, test.locksOwners[i].Name, lock.Owner.Name)
assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second)
assert.EqualValues(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second
}
@@ -143,7 +144,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
assert.Len(t, lfsLocksVerify.Ours, test.oursCount)
assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount)
for _, lock := range lfsLocksVerify.Ours {
- assert.EqualValues(t, test.user.DisplayName(), lock.Owner.Name)
+ assert.EqualValues(t, test.user.Name, lock.Owner.Name)
deleteTests = append(deleteTests, struct {
user *user_model.User
repo *repo_model.Repository
@@ -165,7 +166,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
var lfsLockRep api.LFSLockResponse
DecodeJSON(t, resp, &lfsLockRep)
assert.Equal(t, test.lockID, lfsLockRep.Lock.ID)
- assert.Equal(t, test.user.DisplayName(), lfsLockRep.Lock.Owner.Name)
+ assert.Equal(t, test.user.Name, lfsLockRep.Lock.Owner.Name)
}
// check that we don't have any lock
diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go
index 7fa26c81474b..9621acbbccad 100644
--- a/tests/integration/oauth_test.go
+++ b/tests/integration/oauth_test.go
@@ -12,29 +12,59 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/routers/web/auth"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
-const defaultAuthorize = "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate"
-
-func TestNoClientID(t *testing.T) {
+func TestAuthorizeNoClientID(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/login/oauth/authorize")
ctx := loginUser(t, "user2")
- ctx.MakeRequest(t, req, http.StatusBadRequest)
+ resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
+ assert.Contains(t, resp.Body.String(), "Client ID not registered")
+}
+
+func TestAuthorizeUnregisteredRedirect(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=UNREGISTERED&response_type=code&state=thestate")
+ ctx := loginUser(t, "user1")
+ resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
+ assert.Contains(t, resp.Body.String(), "Unregistered Redirect URI")
+}
+
+func TestAuthorizeUnsupportedResponseType(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate")
+ ctx := loginUser(t, "user1")
+ resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
+ u, err := resp.Result().Location()
+ assert.NoError(t, err)
+ assert.Equal(t, "unsupported_response_type", u.Query().Get("error"))
+ assert.Equal(t, "Only code response type is supported.", u.Query().Get("error_description"))
+}
+
+func TestAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=UNEXPECTED")
+ ctx := loginUser(t, "user1")
+ resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
+ u, err := resp.Result().Location()
+ assert.NoError(t, err)
+ assert.Equal(t, "invalid_request", u.Query().Get("error"))
+ assert.Equal(t, "unsupported code challenge method", u.Query().Get("error_description"))
}
-func TestLoginRedirect(t *testing.T) {
+func TestAuthorizeLoginRedirect(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/login/oauth/authorize")
assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login")
}
-func TestShowAuthorize(t *testing.T) {
+func TestAuthorizeShow(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- req := NewRequest(t, "GET", defaultAuthorize)
+ req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate")
ctx := loginUser(t, "user4")
resp := ctx.MakeRequest(t, req, http.StatusOK)
@@ -43,15 +73,17 @@ func TestShowAuthorize(t *testing.T) {
htmlDoc.GetCSRF()
}
-func TestRedirectWithExistingGrant(t *testing.T) {
+func TestAuthorizeRedirectWithExistingGrant(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- req := NewRequest(t, "GET", defaultAuthorize)
+ req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https%3A%2F%2Fexample.com%2Fxyzzy&response_type=code&state=thestate")
ctx := loginUser(t, "user1")
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
u, err := resp.Result().Location()
assert.NoError(t, err)
assert.Equal(t, "thestate", u.Query().Get("state"))
assert.Truef(t, len(u.Query().Get("code")) > 30, "authorization code '%s' should be longer then 30", u.Query().Get("code"))
+ u.RawQuery = ""
+ assert.Equal(t, "https://example.com/xyzzy", u.String())
}
func TestAccessTokenExchange(t *testing.T) {
@@ -62,7 +94,7 @@ func TestAccessTokenExchange(t *testing.T) {
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
resp := MakeRequest(t, req, http.StatusOK)
type response struct {
@@ -78,7 +110,7 @@ func TestAccessTokenExchange(t *testing.T) {
assert.True(t, len(parsed.RefreshToken) > 10)
}
-func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
+func TestAccessTokenExchangeJSON(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
"grant_type": "authorization_code",
@@ -86,7 +118,7 @@ func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
resp := MakeRequest(t, req, http.StatusOK)
type response struct {
@@ -102,16 +134,20 @@ func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
assert.True(t, len(parsed.RefreshToken) > 10)
}
-func TestAccessTokenExchangeJSON(t *testing.T) {
+func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
+ req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"grant_type": "authorization_code",
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "a",
"code": "authcode",
})
- MakeRequest(t, req, http.StatusBadRequest)
+ resp := MakeRequest(t, req, http.StatusBadRequest)
+ parsedError := new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription)
}
func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
@@ -123,9 +159,14 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
- MakeRequest(t, req, http.StatusBadRequest)
+ resp := MakeRequest(t, req, http.StatusBadRequest)
+ parsedError := new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "cannot load client with client id: '???'", parsedError.ErrorDescription)
+
// invalid client secret
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"grant_type": "authorization_code",
@@ -133,9 +174,14 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
"client_secret": "???",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
- MakeRequest(t, req, http.StatusBadRequest)
+ resp = MakeRequest(t, req, http.StatusBadRequest)
+ parsedError = new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
+
// invalid redirect uri
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"grant_type": "authorization_code",
@@ -143,9 +189,14 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "???",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
- MakeRequest(t, req, http.StatusBadRequest)
+ resp = MakeRequest(t, req, http.StatusBadRequest)
+ parsedError = new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "unexpected redirect URI", parsedError.ErrorDescription)
+
// invalid authorization code
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"grant_type": "authorization_code",
@@ -153,9 +204,14 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "a",
"code": "???",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
- MakeRequest(t, req, http.StatusBadRequest)
+ resp = MakeRequest(t, req, http.StatusBadRequest)
+ parsedError = new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "client is not authorized", parsedError.ErrorDescription)
+
// invalid grant_type
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"grant_type": "???",
@@ -163,9 +219,13 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
- MakeRequest(t, req, http.StatusBadRequest)
+ resp = MakeRequest(t, req, http.StatusBadRequest)
+ parsedError = new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "unsupported_grant_type", string(parsedError.ErrorCode))
+ assert.Equal(t, "Only refresh_token or authorization_code grant type is supported", parsedError.ErrorDescription)
}
func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
@@ -174,7 +234,7 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
"grant_type": "authorization_code",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
resp := MakeRequest(t, req, http.StatusOK)
@@ -195,19 +255,54 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
"grant_type": "authorization_code",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==")
- MakeRequest(t, req, http.StatusBadRequest)
+ resp = MakeRequest(t, req, http.StatusBadRequest)
+ parsedError := new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
// missing header
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"grant_type": "authorization_code",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
+ })
+ resp = MakeRequest(t, req, http.StatusBadRequest)
+ parsedError = new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "cannot load client with client id: ''", parsedError.ErrorDescription)
+
+ // client_id inconsistent with Authorization header
+ req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
+ "grant_type": "authorization_code",
+ "redirect_uri": "a",
+ "code": "authcode",
+ "client_id": "inconsistent",
+ })
+ req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
+ resp = MakeRequest(t, req, http.StatusBadRequest)
+ parsedError = new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
+ assert.Equal(t, "client_id in request body inconsistent with Authorization header", parsedError.ErrorDescription)
+
+ // client_secret inconsistent with Authorization header
+ req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
+ "grant_type": "authorization_code",
+ "redirect_uri": "a",
+ "code": "authcode",
+ "client_secret": "inconsistent",
})
- MakeRequest(t, req, http.StatusBadRequest)
+ req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
+ parsedError = new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
+ assert.Equal(t, "client_id in request body inconsistent with Authorization header", parsedError.ErrorDescription)
}
func TestRefreshTokenInvalidation(t *testing.T) {
@@ -218,7 +313,7 @@ func TestRefreshTokenInvalidation(t *testing.T) {
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
"redirect_uri": "a",
"code": "authcode",
- "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
+ "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
})
resp := MakeRequest(t, req, http.StatusOK)
type response struct {
@@ -256,6 +351,11 @@ func TestRefreshTokenInvalidation(t *testing.T) {
refreshReq.Body = io.NopCloser(bytes.NewReader(bs))
MakeRequest(t, refreshReq, http.StatusOK)
+ // repeat request should fail
refreshReq.Body = io.NopCloser(bytes.NewReader(bs))
- MakeRequest(t, refreshReq, http.StatusBadRequest)
+ resp = MakeRequest(t, refreshReq, http.StatusBadRequest)
+ parsedError := new(auth.AccessTokenError)
+ assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
+ assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
+ assert.Equal(t, "token was already used", parsedError.ErrorDescription)
}
diff --git a/web_src/js/features/formatting.js b/web_src/js/features/formatting.js
index a7ee7ec3cf36..5f4633bba212 100644
--- a/web_src/js/features/formatting.js
+++ b/web_src/js/features/formatting.js
@@ -1,6 +1,7 @@
import {prettyNumber} from '../utils.js';
const {lang} = document.documentElement;
+const dateFormatter = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'long', day: 'numeric'});
export function initFormattingReplacements() {
// replace english formatted numbers with locale-specific separators
@@ -11,4 +12,10 @@ export function initFormattingReplacements() {
el.textContent = formatted;
}
}
+
+ // for each
tag, if it has the data-format="date" attribute, format
+ // the text according to the user's chosen locale
+ for (const timeElement of document.querySelectorAll('time[data-format="date"]')) {
+ timeElement.textContent = dateFormatter.format(new Date(timeElement.dateTime));
+ }
}
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index fdc235164e64..c66cabd8a196 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -1,12 +1,16 @@
:root {
- /* documented customizable variables */
+ /* fonts */
--fonts-proportional: -apple-system, "Segoe UI", system-ui, "Roboto", "Helvetica Neue", "Arial";
--fonts-monospace: "SFMono-Regular", "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace, var(--fonts-emoji);
--fonts-emoji: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla";
- /* other variables */
+ /* backgrounds */
+ --checkbox-mask-checked: url('data:image/svg+xml;utf8,
');
+ --checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,
');
+ /* non-color variables */
--border-radius: .28571429rem;
--opacity-disabled: .55;
--height-loading: 12rem;
+ /* base colors */
--color-primary: #4183c4;
--color-primary-dark-1: #3876b3;
--color-primary-dark-2: #31699f;
@@ -61,7 +65,7 @@
/* console colors */
--color-console-fg: #ffffff;
--color-console-bg: #171717;
- /* colors */
+ /* named colors */
--color-red: #db2828;
--color-orange: #f2711c;
--color-yellow: #fbbd08;
@@ -113,7 +117,6 @@
--color-info-border: #a9d5de;
--color-info-bg: #f8ffff;
--color-info-text: #276f86;
- /* target-based colors */
--color-body: #ffffff;
--color-text-dark: #080808;
--color-text: #212121;
@@ -159,12 +162,9 @@
--color-tooltip-text: #ffffff;
--color-header-bar: #ffffff;
--color-label-active-bg: #d0d0d0;
- /* accent */
--color-small-accent: var(--color-primary-light-6);
--color-accent: var(--color-primary-light-4);
- /* backgrounds */
- --checkbox-mask-checked: url('data:image/svg+xml;utf8,
');
- --checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,
');
+ --color-active-line: #fffbdd;
}
:root * {
@@ -351,6 +351,10 @@ a.commit-statuses-trigger {
border-bottom: none !important;
}
+.ui.dividing.header {
+ border-bottom-color: var(--color-secondary);
+}
+
.page-content {
margin-top: 15px;
}
@@ -1374,7 +1378,7 @@ footer {
max-width: calc(100vw - 1rem) !important;
.links > * {
- border-left: 1px solid var(--color-secondary);
+ border-left: 1px solid var(--color-secondary-dark-1);
padding-left: 8px;
margin-left: 5px;
@@ -1712,7 +1716,7 @@ a.ui.label:hover {
.lines-code.active,
.lines-code .active {
- background: #fffbdd !important;
+ background: var(--color-active-line) !important;
}
.blame .lines-num {
@@ -2114,6 +2118,10 @@ table th[data-sortt-desc] {
vertical-align: -.15em;
}
+.minicolors-panel {
+ background: var(--color-secondary-dark-1) !important;
+}
+
.labelspage {
list-style: none;
padding-top: 0;
@@ -2215,6 +2223,10 @@ table th[data-sortt-desc] {
margin-top: inherit;
}
+.ui.header .sub.header {
+ color: var(--color-text-light-1);
+}
+
.flash-error details code,
.flash-warning details code {
display: block;
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index f30bafa4cc70..c5d2a5f50ac4 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -988,6 +988,12 @@
.comment-form-reply .footer {
padding-bottom: 1em;
}
+
+ @media @mediaSm {
+ .ui.segments {
+ margin-left: -2rem;
+ }
+ }
}
.ui.comments {
@@ -1165,6 +1171,10 @@
box-shadow: none;
}
}
+
+ @media @mediaSm {
+ padding: 1rem 0 !important; // Important is required here to override existing fomantic styles.
+ }
}
.ui.depending {
@@ -1934,16 +1944,16 @@
}
.dot {
- width: 9px;
- height: 9px;
- background-color: #ddd;
+ width: 10px;
+ height: 10px;
+ background-color: var(--color-secondary-dark-3);
z-index: 9;
position: absolute;
display: block;
- left: -5px;
+ left: -6px;
top: 40px;
- border-radius: 6px;
- border: 1px solid #ffffff;
+ border-radius: 100%;
+ border: 2.5px solid var(--color-body);
}
}
}
diff --git a/web_src/less/animations.less b/web_src/less/animations.less
index ea31d53bfea7..6d32625704d7 100644
--- a/web_src/less/animations.less
+++ b/web_src/less/animations.less
@@ -24,7 +24,7 @@
animation: isloadingspin 500ms infinite linear;
border-width: 4px;
border-style: solid;
- border-color: #ececec #ececec #666 #666;
+ border-color: var(--color-secondary) var(--color-secondary) var(--color-secondary-dark-8) var(--color-secondary-dark-8);
border-radius: 100%;
}
diff --git a/web_src/less/themes/theme-arc-green.less b/web_src/less/themes/theme-arc-green.less
index 3f3d4feae28d..12dba7926626 100644
--- a/web_src/less/themes/theme-arc-green.less
+++ b/web_src/less/themes/theme-arc-green.less
@@ -136,6 +136,7 @@
--color-label-active-bg: #4c525e;
--color-small-accent: var(--color-primary-light-5);
--color-accent: var(--color-primary-light-3);
+ --color-active-line: #534d1b;
}
::-webkit-calendar-picker-indicator {
@@ -231,10 +232,6 @@ a.ui.basic.green.label:hover {
background-color: #a0cc75;
}
-.repository .diff-stats li {
- border-color: var(--color-secondary);
-}
-
.ui.red.button,
.ui.red.buttons .button {
background-color: #7d3434;
@@ -245,24 +242,6 @@ a.ui.basic.green.label:hover {
background-color: #984646;
}
-.lines-code.active,
-.lines-code .active {
- background: #534d1b !important;
-}
-
-.ui.header .sub.header {
- color: var(--color-secondary-dark-6);
-}
-
-.ui.dividing.header {
- border-bottom: 1px solid var(--color-secondary);
-}
-
-.minicolors-panel {
- background: var(--color-secondary) !important;
- border-color: #6a737d !important;
-}
-
/* invert emojis that are hard to read otherwise */
.emoji[aria-label="check mark"],
.emoji[aria-label="currency exchange"],
@@ -287,36 +266,10 @@ a.ui.basic.green.label:hover {
filter: invert(100%) hue-rotate(180deg);
}
-.edit-diff > div > .ui.table {
- border-left-color: var(--color-secondary) !important;
- border-right-color: var(--color-secondary) !important;
-}
-
-footer .container .links > * {
- border-left-color: #888;
-}
-
-.repository.release #release-list > li .detail .dot {
- background-color: #505667;
- border-color: #383c4a;
-}
-
-.tribute-container {
- box-shadow: 0 .25rem .5rem rgba(0, 0, 0, .6);
-}
-
-.repository .repo-header .ui.huge.breadcrumb.repo-title .repo-header-icon .avatar {
- color: #2a2e3a;
-}
-
img[src$="/img/matrix.svg"] {
filter: invert(80%);
}
-.is-loading::after {
- border-color: #4a4c58 #4a4c58 #d7d7da #d7d7da;
-}
-
.markup-block-error {
border: 1px solid rgba(121, 71, 66, .5) !important;
border-bottom: none !important;