Merge pull request '[gitea] week 15 cherry pick' (#3091) from algernon/forgejo:wcp/week-15 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3091 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
commit
26fc7c3461
117 changed files with 3706 additions and 4312 deletions
|
@ -22,7 +22,6 @@ package "code.gitea.io/gitea/models/actions"
|
|||
func (ScheduleList).GetRepoIDs
|
||||
func (ScheduleList).LoadTriggerUser
|
||||
func (ScheduleList).LoadRepos
|
||||
func GetVariableByID
|
||||
|
||||
package "code.gitea.io/gitea/models/asymkey"
|
||||
func (ErrGPGKeyAccessDenied).Error
|
||||
|
|
|
@ -77,7 +77,6 @@ cpu.out
|
|||
/public/assets/css
|
||||
/public/assets/fonts
|
||||
/public/assets/img/avatar
|
||||
/public/assets/img/webpack
|
||||
/vendor
|
||||
/web_src/fomantic/node_modules
|
||||
/web_src/fomantic/build/*
|
||||
|
|
|
@ -3,6 +3,7 @@ reportUnusedDisableDirectives: true
|
|||
|
||||
ignorePatterns:
|
||||
- /web_src/js/vendor
|
||||
- /web_src/fomantic
|
||||
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -83,7 +83,6 @@ cpu.out
|
|||
/public/assets/css
|
||||
/public/assets/fonts
|
||||
/public/assets/licenses.txt
|
||||
/public/assets/img/webpack
|
||||
/vendor
|
||||
/web_src/fomantic/node_modules
|
||||
/web_src/fomantic/build/*
|
||||
|
|
4
.ignore
4
.ignore
|
@ -4,6 +4,8 @@
|
|||
/modules/options/bindata.go
|
||||
/modules/public/bindata.go
|
||||
/modules/templates/bindata.go
|
||||
/vendor
|
||||
/options/gitignore
|
||||
/options/license
|
||||
/public/assets
|
||||
/vendor
|
||||
node_modules
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
plugins:
|
||||
- stylelint-declaration-strict-value
|
||||
- stylelint-declaration-block-no-ignored-properties
|
||||
- "@stylistic/stylelint-plugin"
|
||||
|
||||
ignoreFiles:
|
||||
- "**/*.go"
|
||||
|
||||
overrides:
|
||||
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console.css", "font_i18n.css"]
|
||||
rules:
|
||||
scale-unlimited/declaration-strict-value: null
|
||||
- files: ["**/chroma/*", "**/codemirror/*"]
|
||||
rules:
|
||||
block-no-empty: null
|
||||
- files: ["**/*.vue"]
|
||||
customSyntax: postcss-html
|
||||
|
||||
rules:
|
||||
"@stylistic/at-rule-name-case": null
|
||||
"@stylistic/at-rule-name-newline-after": null
|
||||
"@stylistic/at-rule-name-space-after": null
|
||||
"@stylistic/at-rule-semicolon-newline-after": null
|
||||
"@stylistic/at-rule-semicolon-space-before": null
|
||||
"@stylistic/block-closing-brace-empty-line-before": null
|
||||
"@stylistic/block-closing-brace-newline-after": null
|
||||
"@stylistic/block-closing-brace-newline-before": null
|
||||
"@stylistic/block-closing-brace-space-after": null
|
||||
"@stylistic/block-closing-brace-space-before": null
|
||||
"@stylistic/block-opening-brace-newline-after": null
|
||||
"@stylistic/block-opening-brace-newline-before": null
|
||||
"@stylistic/block-opening-brace-space-after": null
|
||||
"@stylistic/block-opening-brace-space-before": always
|
||||
"@stylistic/color-hex-case": lower
|
||||
"@stylistic/declaration-bang-space-after": never
|
||||
"@stylistic/declaration-bang-space-before": null
|
||||
"@stylistic/declaration-block-semicolon-newline-after": null
|
||||
"@stylistic/declaration-block-semicolon-newline-before": null
|
||||
"@stylistic/declaration-block-semicolon-space-after": null
|
||||
"@stylistic/declaration-block-semicolon-space-before": never
|
||||
"@stylistic/declaration-block-trailing-semicolon": null
|
||||
"@stylistic/declaration-colon-newline-after": null
|
||||
"@stylistic/declaration-colon-space-after": null
|
||||
"@stylistic/declaration-colon-space-before": never
|
||||
"@stylistic/function-comma-newline-after": null
|
||||
"@stylistic/function-comma-newline-before": null
|
||||
"@stylistic/function-comma-space-after": null
|
||||
"@stylistic/function-comma-space-before": null
|
||||
"@stylistic/function-max-empty-lines": 0
|
||||
"@stylistic/function-parentheses-newline-inside": never-multi-line
|
||||
"@stylistic/function-parentheses-space-inside": null
|
||||
"@stylistic/function-whitespace-after": null
|
||||
"@stylistic/indentation": 2
|
||||
"@stylistic/linebreaks": null
|
||||
"@stylistic/max-empty-lines": 1
|
||||
"@stylistic/max-line-length": null
|
||||
"@stylistic/media-feature-colon-space-after": null
|
||||
"@stylistic/media-feature-colon-space-before": never
|
||||
"@stylistic/media-feature-name-case": null
|
||||
"@stylistic/media-feature-parentheses-space-inside": null
|
||||
"@stylistic/media-feature-range-operator-space-after": always
|
||||
"@stylistic/media-feature-range-operator-space-before": always
|
||||
"@stylistic/media-query-list-comma-newline-after": null
|
||||
"@stylistic/media-query-list-comma-newline-before": null
|
||||
"@stylistic/media-query-list-comma-space-after": null
|
||||
"@stylistic/media-query-list-comma-space-before": null
|
||||
"@stylistic/named-grid-areas-alignment": null
|
||||
"@stylistic/no-empty-first-line": null
|
||||
"@stylistic/no-eol-whitespace": true
|
||||
"@stylistic/no-extra-semicolons": true
|
||||
"@stylistic/no-missing-end-of-source-newline": null
|
||||
"@stylistic/number-leading-zero": null
|
||||
"@stylistic/number-no-trailing-zeros": null
|
||||
"@stylistic/property-case": lower
|
||||
"@stylistic/selector-attribute-brackets-space-inside": null
|
||||
"@stylistic/selector-attribute-operator-space-after": null
|
||||
"@stylistic/selector-attribute-operator-space-before": null
|
||||
"@stylistic/selector-combinator-space-after": null
|
||||
"@stylistic/selector-combinator-space-before": null
|
||||
"@stylistic/selector-descendant-combinator-no-non-space": null
|
||||
"@stylistic/selector-list-comma-newline-after": null
|
||||
"@stylistic/selector-list-comma-newline-before": null
|
||||
"@stylistic/selector-list-comma-space-after": always-single-line
|
||||
"@stylistic/selector-list-comma-space-before": never-single-line
|
||||
"@stylistic/selector-max-empty-lines": 0
|
||||
"@stylistic/selector-pseudo-class-case": lower
|
||||
"@stylistic/selector-pseudo-class-parentheses-space-inside": never
|
||||
"@stylistic/selector-pseudo-element-case": lower
|
||||
"@stylistic/string-quotes": double
|
||||
"@stylistic/unicode-bom": null
|
||||
"@stylistic/unit-case": lower
|
||||
"@stylistic/value-list-comma-newline-after": null
|
||||
"@stylistic/value-list-comma-newline-before": null
|
||||
"@stylistic/value-list-comma-space-after": null
|
||||
"@stylistic/value-list-comma-space-before": null
|
||||
"@stylistic/value-list-max-empty-lines": 0
|
||||
alpha-value-notation: null
|
||||
annotation-no-unknown: true
|
||||
at-rule-allowed-list: null
|
||||
at-rule-disallowed-list: null
|
||||
at-rule-empty-line-before: null
|
||||
at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}]
|
||||
at-rule-no-vendor-prefix: true
|
||||
at-rule-property-required-list: null
|
||||
block-no-empty: true
|
||||
color-function-notation: null
|
||||
color-hex-alpha: null
|
||||
color-hex-length: null
|
||||
color-named: null
|
||||
color-no-hex: null
|
||||
color-no-invalid-hex: true
|
||||
comment-empty-line-before: null
|
||||
comment-no-empty: true
|
||||
comment-pattern: null
|
||||
comment-whitespace-inside: null
|
||||
comment-word-disallowed-list: null
|
||||
custom-media-pattern: null
|
||||
custom-property-empty-line-before: null
|
||||
custom-property-no-missing-var-function: true
|
||||
custom-property-pattern: null
|
||||
declaration-block-no-duplicate-custom-properties: true
|
||||
declaration-block-no-duplicate-properties: [true, {ignore: [consecutive-duplicates-with-different-values]}]
|
||||
declaration-block-no-redundant-longhand-properties: null
|
||||
declaration-block-no-shorthand-property-overrides: null
|
||||
declaration-block-single-line-max-declarations: null
|
||||
declaration-empty-line-before: null
|
||||
declaration-no-important: null
|
||||
declaration-property-max-values: null
|
||||
declaration-property-unit-allowed-list: null
|
||||
declaration-property-unit-disallowed-list: {line-height: [em]}
|
||||
declaration-property-value-allowed-list: null
|
||||
declaration-property-value-disallowed-list: null
|
||||
declaration-property-value-no-unknown: true
|
||||
font-family-name-quotes: always-where-recommended
|
||||
font-family-no-duplicate-names: true
|
||||
font-family-no-missing-generic-family-keyword: true
|
||||
font-weight-notation: null
|
||||
function-allowed-list: null
|
||||
function-calc-no-unspaced-operator: true
|
||||
function-disallowed-list: null
|
||||
function-linear-gradient-no-nonstandard-direction: true
|
||||
function-name-case: lower
|
||||
function-no-unknown: true
|
||||
function-url-no-scheme-relative: null
|
||||
function-url-quotes: always
|
||||
function-url-scheme-allowed-list: null
|
||||
function-url-scheme-disallowed-list: null
|
||||
hue-degree-notation: null
|
||||
import-notation: string
|
||||
keyframe-block-no-duplicate-selectors: true
|
||||
keyframe-declaration-no-important: true
|
||||
keyframe-selector-notation: null
|
||||
keyframes-name-pattern: null
|
||||
length-zero-no-unit: [true, ignore: [custom-properties], ignoreFunctions: [var]]
|
||||
max-nesting-depth: null
|
||||
media-feature-name-allowed-list: null
|
||||
media-feature-name-disallowed-list: null
|
||||
media-feature-name-no-unknown: true
|
||||
media-feature-name-no-vendor-prefix: true
|
||||
media-feature-name-unit-allowed-list: null
|
||||
media-feature-name-value-allowed-list: null
|
||||
media-feature-name-value-no-unknown: true
|
||||
media-feature-range-notation: null
|
||||
media-query-no-invalid: true
|
||||
named-grid-areas-no-invalid: true
|
||||
no-descending-specificity: null
|
||||
no-duplicate-at-import-rules: true
|
||||
no-duplicate-selectors: true
|
||||
no-empty-source: true
|
||||
no-invalid-double-slash-comments: true
|
||||
no-invalid-position-at-import-rule: [true, ignoreAtRules: [tailwind]]
|
||||
no-irregular-whitespace: true
|
||||
no-unknown-animations: null
|
||||
no-unknown-custom-properties: null
|
||||
number-max-precision: null
|
||||
plugin/declaration-block-no-ignored-properties: true
|
||||
property-allowed-list: null
|
||||
property-disallowed-list: null
|
||||
property-no-unknown: true
|
||||
property-no-vendor-prefix: null
|
||||
rule-empty-line-before: null
|
||||
rule-selector-property-disallowed-list: null
|
||||
scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}]
|
||||
selector-anb-no-unmatchable: true
|
||||
selector-attribute-name-disallowed-list: null
|
||||
selector-attribute-operator-allowed-list: null
|
||||
selector-attribute-operator-disallowed-list: null
|
||||
selector-attribute-quotes: always
|
||||
selector-class-pattern: null
|
||||
selector-combinator-allowed-list: null
|
||||
selector-combinator-disallowed-list: null
|
||||
selector-disallowed-list: null
|
||||
selector-id-pattern: null
|
||||
selector-max-attribute: null
|
||||
selector-max-class: null
|
||||
selector-max-combinators: null
|
||||
selector-max-compound-selectors: null
|
||||
selector-max-id: null
|
||||
selector-max-pseudo-class: null
|
||||
selector-max-specificity: null
|
||||
selector-max-type: null
|
||||
selector-max-universal: null
|
||||
selector-nested-pattern: null
|
||||
selector-no-qualifying-type: null
|
||||
selector-no-vendor-prefix: true
|
||||
selector-not-notation: null
|
||||
selector-pseudo-class-allowed-list: null
|
||||
selector-pseudo-class-disallowed-list: null
|
||||
selector-pseudo-class-no-unknown: true
|
||||
selector-pseudo-element-allowed-list: null
|
||||
selector-pseudo-element-colon-notation: double
|
||||
selector-pseudo-element-disallowed-list: null
|
||||
selector-pseudo-element-no-unknown: true
|
||||
selector-type-case: lower
|
||||
selector-type-no-unknown: [true, {ignore: [custom-elements]}]
|
||||
shorthand-property-no-redundant-values: true
|
||||
string-no-newline: true
|
||||
time-min-milliseconds: null
|
||||
unit-allowed-list: null
|
||||
unit-disallowed-list: null
|
||||
unit-no-unknown: true
|
||||
value-keyword-case: null
|
||||
value-no-vendor-prefix: [true, {ignoreValues: [box, inline-box]}]
|
2
Makefile
2
Makefile
|
@ -130,7 +130,7 @@ FOMANTIC_WORK_DIR := web_src/fomantic
|
|||
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
||||
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
||||
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
||||
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
|
||||
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
|
||||
|
||||
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
|
||||
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
|
||||
|
|
|
@ -1503,6 +1503,11 @@ LEVEL = Info
|
|||
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||
;USER_DISABLED_FEATURES =
|
||||
;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
|
||||
;; - deletion: a user cannot delete their own account
|
||||
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||
;;EXTERNAL_USER_DISABLE_FEATURES =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2326,6 +2331,8 @@ LEVEL = Info
|
|||
;SHOW_FOOTER_VERSION = true
|
||||
;; Show template execution time in the footer
|
||||
;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
|
||||
;; Show the "powered by" text in the footer
|
||||
;SHOW_FOOTER_POWERED_BY = true
|
||||
;; Generate sitemap. Defaults to `true`.
|
||||
;ENABLE_SITEMAP = true
|
||||
;; Enable/Disable RSS/Atom feed
|
||||
|
|
|
@ -6,13 +6,11 @@ package actions
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
@ -55,24 +53,24 @@ type FindVariablesOpts struct {
|
|||
db.ListOptions
|
||||
OwnerID int64
|
||||
RepoID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func (opts FindVariablesOpts) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
// Since we now support instance-level variables,
|
||||
// there is no need to check for null values for `owner_id` and `repo_id`
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
|
||||
if opts.Name != "" {
|
||||
cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) {
|
||||
var variable ActionVariable
|
||||
has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist)
|
||||
}
|
||||
return &variable, nil
|
||||
func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) {
|
||||
return db.Find[ActionVariable](ctx, opts)
|
||||
}
|
||||
|
||||
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
|
||||
|
@ -84,6 +82,13 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error)
|
|||
return count != 0, err
|
||||
}
|
||||
|
||||
func DeleteVariable(ctx context.Context, id int64) error {
|
||||
if _, err := db.DeleteByID[ActionVariable](ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
|
||||
variables := map[string]string{}
|
||||
|
||||
|
|
|
@ -134,3 +134,13 @@ func extractSignature(s string) (*packet.Signature, error) {
|
|||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
func tryGetKeyIDFromSignature(sig *packet.Signature) string {
|
||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||
return fmt.Sprintf("%016X", *sig.IssuerKeyId)
|
||||
}
|
||||
if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||
return fmt.Sprintf("%016X", sig.IssuerFingerprint[12:20])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -123,13 +123,7 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica
|
|||
}
|
||||
}
|
||||
|
||||
keyID := ""
|
||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
|
||||
}
|
||||
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
||||
}
|
||||
keyID := tryGetKeyIDFromSignature(sig)
|
||||
defaultReason := NoKeyFound
|
||||
|
||||
// First check if the sig has a keyID and if so just look at that
|
||||
|
|
|
@ -11,7 +11,9 @@ import (
|
|||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/keybase/go-crypto/openpgp/packet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -391,3 +393,13 @@ epiDVQ==
|
|||
assert.Equal(t, time.Unix(1586105389, 0), expire)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryGetKeyIDFromSignature(t *testing.T) {
|
||||
assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{}))
|
||||
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||
IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)),
|
||||
}))
|
||||
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -46,6 +46,10 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
|
|||
return "", ErrGPGKeyNotExist{}
|
||||
}
|
||||
|
||||
if err := key.LoadSubKeys(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sig, err := extractSignature(signature)
|
||||
if err != nil {
|
||||
return "", ErrGPGInvalidTokenSignature{
|
||||
|
|
|
@ -1246,3 +1246,21 @@ func GetOrderByName() string {
|
|||
}
|
||||
return "name"
|
||||
}
|
||||
|
||||
// IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the
|
||||
// user if applicable
|
||||
func IsFeatureDisabledWithLoginType(user *User, feature string) bool {
|
||||
// NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType
|
||||
return (user != nil && user.LoginType > auth.Plain && setting.Admin.ExternalUserDisableFeatures.Contains(feature)) ||
|
||||
setting.Admin.UserDisabledFeatures.Contains(feature)
|
||||
}
|
||||
|
||||
// DisabledFeaturesWithLoginType returns the set of user features disabled, taking into account the login type
|
||||
// of the user if applicable
|
||||
func DisabledFeaturesWithLoginType(user *User) *container.Set[string] {
|
||||
// NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType
|
||||
if user != nil && user.LoginType > auth.Plain {
|
||||
return &setting.Admin.ExternalUserDisableFeatures
|
||||
}
|
||||
return &setting.Admin.UserDisabledFeatures
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
@ -542,3 +543,37 @@ func Test_NormalizeUserFromEmail(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisabledUserFeatures(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testValues := container.SetOf(setting.UserFeatureDeletion,
|
||||
setting.UserFeatureManageSSHKeys,
|
||||
setting.UserFeatureManageGPGKeys)
|
||||
|
||||
oldSetting := setting.Admin.ExternalUserDisableFeatures
|
||||
defer func() {
|
||||
setting.Admin.ExternalUserDisableFeatures = oldSetting
|
||||
}()
|
||||
setting.Admin.ExternalUserDisableFeatures = testValues
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
assert.Len(t, setting.Admin.UserDisabledFeatures.Values(), 0)
|
||||
|
||||
// no features should be disabled with a plain login type
|
||||
assert.LessOrEqual(t, user.LoginType, auth.Plain)
|
||||
assert.Len(t, user_model.DisabledFeaturesWithLoginType(user).Values(), 0)
|
||||
for _, f := range testValues.Values() {
|
||||
assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||
}
|
||||
|
||||
// check disabled features with external login type
|
||||
user.LoginType = auth.OAuth2
|
||||
|
||||
// all features should be disabled
|
||||
assert.NotEmpty(t, user_model.DisabledFeaturesWithLoginType(user).Values())
|
||||
for _, f := range testValues.Values() {
|
||||
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,12 @@ func convertPGPSignature(c *object.Commit) *ObjectSignature {
|
|||
return nil
|
||||
}
|
||||
|
||||
if c.Encoding != "" && c.Encoding != "UTF-8" {
|
||||
if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@ readLoop:
|
|||
commit.Committer = &Signature{}
|
||||
commit.Committer.Decode(data)
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "encoding":
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "gpgsig":
|
||||
fallthrough
|
||||
case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
|
||||
|
|
|
@ -125,6 +125,73 @@ empty commit`, commitFromReader.Signature.Payload)
|
|||
assert.EqualValues(t, commitFromReader, commitFromReader2)
|
||||
}
|
||||
|
||||
func TestCommitWithEncodingFromReader(t *testing.T) {
|
||||
commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
|
||||
tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
|
||||
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
|
||||
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||
encoding ISO-8859-1
|
||||
gpgsig -----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
||||
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
||||
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
||||
zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr
|
||||
frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j
|
||||
FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd
|
||||
G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn
|
||||
SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
|
||||
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
|
||||
jw4YcO5u
|
||||
=r3UU
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
ISO-8859-1`
|
||||
|
||||
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, gitRepo)
|
||||
defer gitRepo.Close()
|
||||
|
||||
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
|
||||
assert.NoError(t, err)
|
||||
if !assert.NotNil(t, commitFromReader) {
|
||||
return
|
||||
}
|
||||
assert.EqualValues(t, sha, commitFromReader.ID)
|
||||
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
||||
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
||||
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
||||
zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr
|
||||
frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j
|
||||
FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd
|
||||
G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn
|
||||
SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
|
||||
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
|
||||
jw4YcO5u
|
||||
=r3UU
|
||||
-----END PGP SIGNATURE-----
|
||||
`, commitFromReader.Signature.Signature)
|
||||
assert.EqualValues(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
|
||||
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
|
||||
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||
encoding ISO-8859-1
|
||||
|
||||
ISO-8859-1`, commitFromReader.Signature.Payload)
|
||||
assert.EqualValues(t, "KN4CK3R <admin@oldschoolhack.me>", commitFromReader.Author.String())
|
||||
|
||||
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
|
||||
assert.NoError(t, err)
|
||||
commitFromReader.CommitMessage += "\n\n"
|
||||
commitFromReader.Signature.Payload += "\n\n"
|
||||
assert.EqualValues(t, commitFromReader, commitFromReader2)
|
||||
}
|
||||
|
||||
func TestHasPreviousCommit(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ func (c *FilesystemClient) Download(ctx context.Context, objects []Pointer, call
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
if err := callback(p, f, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func (c *FilesystemClient) Upload(ctx context.Context, objects []Pointer, callba
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, content)
|
||||
|
||||
return err
|
||||
|
|
|
@ -505,9 +505,17 @@ func TestMathBlock(t *testing.T) {
|
|||
`\(a\) \(b\)`,
|
||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`$a$.`,
|
||||
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
|
||||
},
|
||||
{
|
||||
`.$a$`,
|
||||
`<p>.$a$</p>` + nl,
|
||||
},
|
||||
{
|
||||
`$a a$b b$`,
|
||||
`<p><code class="language-math is-loading">a a$b b</code></p>` + nl,
|
||||
`<p>$a a$b b$</p>` + nl,
|
||||
},
|
||||
{
|
||||
`a a$b b`,
|
||||
|
@ -515,7 +523,15 @@ func TestMathBlock(t *testing.T) {
|
|||
},
|
||||
{
|
||||
`a$b $a a$b b$`,
|
||||
`<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl,
|
||||
`<p>a$b $a a$b b$</p>` + nl,
|
||||
},
|
||||
{
|
||||
"a$x$",
|
||||
`<p>a$x$</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$x$a",
|
||||
`<p>$x$a</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$$a$$",
|
||||
|
|
|
@ -41,9 +41,12 @@ func (parser *inlineParser) Trigger() []byte {
|
|||
return parser.start[0:1]
|
||||
}
|
||||
|
||||
func isPunctuation(b byte) bool {
|
||||
return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
|
||||
}
|
||||
|
||||
func isAlphanumeric(b byte) bool {
|
||||
// Github only cares about 0-9A-Za-z
|
||||
return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
|
||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
||||
}
|
||||
|
||||
// Parse parses the current line and returns a result of parsing.
|
||||
|
@ -56,7 +59,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
|||
}
|
||||
|
||||
precedingCharacter := block.PrecendingCharacter()
|
||||
if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {
|
||||
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
|
||||
// need to exclude things like `a$` from being considered a start
|
||||
return nil
|
||||
}
|
||||
|
@ -75,14 +78,19 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
|||
ender += pos
|
||||
|
||||
// Now we want to check the character at the end of our parser section
|
||||
// that is ender + len(parser.end)
|
||||
// that is ender + len(parser.end) and check if char before ender is '\'
|
||||
pos = ender + len(parser.end)
|
||||
if len(line) <= pos {
|
||||
break
|
||||
}
|
||||
if !isAlphanumeric(line[pos]) {
|
||||
suceedingCharacter := line[pos]
|
||||
if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') {
|
||||
return nil
|
||||
}
|
||||
if line[ender-1] != '\\' {
|
||||
break
|
||||
}
|
||||
|
||||
// move the pointer onwards
|
||||
ender += len(parser.end)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
package setting
|
||||
|
||||
import "code.gitea.io/gitea/modules/container"
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
)
|
||||
|
||||
// Admin settings
|
||||
var Admin struct {
|
||||
|
@ -11,6 +13,7 @@ var Admin struct {
|
|||
DefaultEmailNotification string
|
||||
SendNotificationEmailOnNewUser bool
|
||||
UserDisabledFeatures container.Set[string]
|
||||
ExternalUserDisableFeatures container.Set[string]
|
||||
}
|
||||
|
||||
func loadAdminFrom(rootCfg ConfigProvider) {
|
||||
|
@ -18,6 +21,7 @@ func loadAdminFrom(rootCfg ConfigProvider) {
|
|||
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
|
||||
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
|
||||
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
|
||||
Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -8,6 +8,7 @@ import "code.gitea.io/gitea/modules/log"
|
|||
type OtherConfig struct {
|
||||
ShowFooterVersion bool
|
||||
ShowFooterTemplateLoadTime bool
|
||||
ShowFooterPoweredBy bool
|
||||
EnableFeed bool
|
||||
EnableSitemap bool
|
||||
}
|
||||
|
@ -15,6 +16,7 @@ type OtherConfig struct {
|
|||
var Other = OtherConfig{
|
||||
ShowFooterVersion: true,
|
||||
ShowFooterTemplateLoadTime: true,
|
||||
ShowFooterPoweredBy: true,
|
||||
EnableSitemap: true,
|
||||
EnableFeed: true,
|
||||
}
|
||||
|
|
37
modules/structs/variable.go
Normal file
37
modules/structs/variable.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package structs
|
||||
|
||||
// CreateVariableOption the option when creating variable
|
||||
// swagger:model
|
||||
type CreateVariableOption struct {
|
||||
// Value of the variable to create
|
||||
//
|
||||
// required: true
|
||||
Value string `json:"value" binding:"Required"`
|
||||
}
|
||||
|
||||
// UpdateVariableOption the option when updating variable
|
||||
// swagger:model
|
||||
type UpdateVariableOption struct {
|
||||
// New name for the variable. If the field is empty, the variable name won't be updated.
|
||||
Name string `json:"name"`
|
||||
// Value of the variable to update
|
||||
//
|
||||
// required: true
|
||||
Value string `json:"value" binding:"Required"`
|
||||
}
|
||||
|
||||
// ActionVariable return value of the query API
|
||||
// swagger:model
|
||||
type ActionVariable struct {
|
||||
// the owner to which the variable belongs
|
||||
OwnerID int64 `json:"owner_id"`
|
||||
// the repository to which the variable belongs
|
||||
RepoID int64 `json:"repo_id"`
|
||||
// the name of the variable
|
||||
Name string `json:"name"`
|
||||
// the value of the variable
|
||||
Data string `json:"data"`
|
||||
}
|
|
@ -109,6 +109,9 @@ func NewFuncMap() template.FuncMap {
|
|||
"ShowFooterTemplateLoadTime": func() bool {
|
||||
return setting.Other.ShowFooterTemplateLoadTime
|
||||
},
|
||||
"ShowFooterPoweredBy": func() bool {
|
||||
return setting.Other.ShowFooterPoweredBy
|
||||
},
|
||||
"AllowedReactions": func() []string {
|
||||
return setting.UI.Reactions
|
||||
},
|
||||
|
|
|
@ -212,3 +212,12 @@ func ToFloat64(number any) (float64, error) {
|
|||
func ToPointer[T any](val T) *T {
|
||||
return &val
|
||||
}
|
||||
|
||||
func ReserveLineBreakForTextarea(input string) string {
|
||||
// Since the content is from a form which is a textarea, the line endings are \r\n.
|
||||
// It's a standard behavior of HTML.
|
||||
// But we want to store them as \n like what GitHub does.
|
||||
// And users are unlikely to really need to keep the \r.
|
||||
// Other than this, we should respect the original content, even leading or trailing spaces.
|
||||
return strings.ReplaceAll(input, "\r\n", "\n")
|
||||
}
|
||||
|
|
|
@ -235,3 +235,8 @@ func TestToPointer(t *testing.T) {
|
|||
val123 := 123
|
||||
assert.False(t, &val123 == ToPointer(val123))
|
||||
}
|
||||
|
||||
func TestReserveLineBreakForTextarea(t *testing.T) {
|
||||
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata"), "test\ndata")
|
||||
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata\r\n"), "test\ndata\n")
|
||||
}
|
||||
|
|
11
options/license/AMD-newlib
Normal file
11
options/license/AMD-newlib
Normal file
|
@ -0,0 +1,11 @@
|
|||
Copyright 1989, 1990 Advanced Micro Devices, Inc.
|
||||
|
||||
This software is the property of Advanced Micro Devices, Inc (AMD) which
|
||||
specifically grants the user the right to modify, use and distribute this
|
||||
software provided this notice is not removed or altered. All other rights
|
||||
are reserved by AMD.
|
||||
|
||||
AMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS
|
||||
SOFTWARE. IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL
|
||||
DAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR
|
||||
USE OF THIS SOFTWARE.
|
12
options/license/OAR
Normal file
12
options/license/OAR
Normal file
|
@ -0,0 +1,12 @@
|
|||
COPYRIGHT (c) 1989-2013, 2015.
|
||||
On-Line Applications Research Corporation (OAR).
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose without fee is hereby granted, provided that this entire notice
|
||||
is included in all copies of any software which is or includes a copy
|
||||
or modification of this software.
|
||||
|
||||
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION
|
||||
OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS
|
||||
SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
12
options/license/xzoom
Normal file
12
options/license/xzoom
Normal file
|
@ -0,0 +1,12 @@
|
|||
Copyright Itai Nahshon 1995, 1996.
|
||||
This program is distributed with no warranty.
|
||||
|
||||
Source files for this program may be distributed freely.
|
||||
Modifications to this file are okay as long as:
|
||||
a. This copyright notice and comment are preserved and
|
||||
left at the top of the file.
|
||||
b. The man page is fixed to reflect the change.
|
||||
c. The author of this change adds his name and change
|
||||
description to the list of changes below.
|
||||
Executable files may be distributed with sources, or with
|
||||
exact location where the source code can be obtained.
|
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -9,7 +9,6 @@
|
|||
"@citation-js/plugin-bibtex": "0.7.9",
|
||||
"@citation-js/plugin-csl": "0.7.9",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@claviska/jquery-minicolors": "2.3.6",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.0",
|
||||
"@github/text-expander-element": "2.6.1",
|
||||
|
@ -54,6 +53,7 @@
|
|||
"toastify-js": "1.12.0",
|
||||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vue": "3.4.21",
|
||||
"vue-bar-graph": "2.0.0",
|
||||
"vue-chartjs": "5.3.0",
|
||||
|
@ -92,6 +92,7 @@
|
|||
"stylelint": "16.3.1",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.4",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.2.0",
|
||||
"updates": "16.0.0",
|
||||
"vite-string-plugin": "1.1.5",
|
||||
|
@ -395,14 +396,6 @@
|
|||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@claviska/jquery-minicolors": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.6.tgz",
|
||||
"integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==",
|
||||
"peerDependencies": {
|
||||
"jquery": ">= 1.7.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
|
||||
|
@ -11121,6 +11114,22 @@
|
|||
"stylelint": ">=7 <=16"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-value-no-unknown-custom-properties": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-value-no-unknown-custom-properties/-/stylelint-value-no-unknown-custom-properties-6.0.1.tgz",
|
||||
"integrity": "sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"resolve": "^1.22.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"stylelint": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
|
@ -12090,6 +12099,11 @@
|
|||
"builtins": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/vanilla-colorful": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz",
|
||||
"integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"@citation-js/plugin-bibtex": "0.7.9",
|
||||
"@citation-js/plugin-csl": "0.7.9",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@claviska/jquery-minicolors": "2.3.6",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.0",
|
||||
"@github/text-expander-element": "2.6.1",
|
||||
|
@ -53,6 +52,7 @@
|
|||
"toastify-js": "1.12.0",
|
||||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vue": "3.4.21",
|
||||
"vue-bar-graph": "2.0.0",
|
||||
"vue-chartjs": "5.3.0",
|
||||
|
@ -91,6 +91,7 @@
|
|||
"stylelint": "16.3.1",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.4",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.2.0",
|
||||
"updates": "16.0.0",
|
||||
"vite-string-plugin": "1.1.5",
|
||||
|
|
|
@ -871,6 +871,15 @@ func Routes() *web.Route {
|
|||
Delete(user.DeleteSecret)
|
||||
})
|
||||
|
||||
m.Group("/variables", func() {
|
||||
m.Get("", user.ListVariables)
|
||||
m.Combo("/{variablename}").
|
||||
Get(user.GetVariable).
|
||||
Delete(user.DeleteVariable).
|
||||
Post(bind(api.CreateVariableOption{}), user.CreateVariable).
|
||||
Put(bind(api.UpdateVariableOption{}), user.UpdateVariable)
|
||||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
||||
})
|
||||
|
@ -990,6 +999,15 @@ func Routes() *web.Route {
|
|||
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
|
||||
})
|
||||
|
||||
m.Group("/variables", func() {
|
||||
m.Get("", reqToken(), reqOwner(), repo.ListVariables)
|
||||
m.Combo("/{variablename}").
|
||||
Get(reqToken(), reqOwner(), repo.GetVariable).
|
||||
Delete(reqToken(), reqOwner(), repo.DeleteVariable).
|
||||
Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable).
|
||||
Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable)
|
||||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
|
||||
})
|
||||
|
@ -1393,6 +1411,15 @@ func Routes() *web.Route {
|
|||
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
|
||||
})
|
||||
|
||||
m.Group("/variables", func() {
|
||||
m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables)
|
||||
m.Combo("/{variablename}").
|
||||
Get(reqToken(), reqOrgOwnership(), org.GetVariable).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable).
|
||||
Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable).
|
||||
Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable)
|
||||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
|
||||
})
|
||||
|
|
291
routers/api/v1/org/variables.go
Normal file
291
routers/api/v1/org/variables.go
Normal file
|
@ -0,0 +1,291 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// ListVariables list org-level variables
|
||||
func ListVariables(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
||||
// ---
|
||||
// summary: Get an org-level variables list
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/VariableList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "FindVariables", err)
|
||||
return
|
||||
}
|
||||
|
||||
variables := make([]*api.ActionVariable, len(vars))
|
||||
for i, v := range vars {
|
||||
variables[i] = &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, variables)
|
||||
}
|
||||
|
||||
// GetVariable get an org-level variable
|
||||
func GetVariable(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
|
||||
// ---
|
||||
// summary: Get an org-level variable
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActionVariable"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
Name: ctx.Params("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
variable := &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, variable)
|
||||
}
|
||||
|
||||
// DeleteVariable delete an org-level variable
|
||||
func DeleteVariable(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
|
||||
// ---
|
||||
// summary: Delete an org-level variable
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActionVariable"
|
||||
// "201":
|
||||
// description: response when deleting a variable
|
||||
// "204":
|
||||
// description: response when deleting a variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// CreateVariable create an org-level variable
|
||||
func CreateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
|
||||
// ---
|
||||
// summary: Create an org-level variable
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when creating an org-level variable
|
||||
// "204":
|
||||
// description: response when creating an org-level variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||
|
||||
ownerID := ctx.Org.Organization.ID
|
||||
variableName := ctx.Params("variablename")
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ownerID,
|
||||
Name: variableName,
|
||||
})
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
return
|
||||
}
|
||||
if v != nil && v.ID > 0 {
|
||||
ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "CreateVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// UpdateVariable update an org-level variable
|
||||
func UpdateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
|
||||
// ---
|
||||
// summary: Update an org-level variable
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when updating an org-level variable
|
||||
// "204":
|
||||
// description: response when updating an org-level variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.UpdateVariableOption)
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
Name: ctx.Params("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if opt.Name == "" {
|
||||
opt.Name = ctx.Params("variablename")
|
||||
}
|
||||
if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
|
@ -7,9 +7,13 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
)
|
||||
|
@ -127,3 +131,295 @@ func DeleteSecret(ctx *context.APIContext) {
|
|||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetVariable get a repo-level variable
|
||||
func GetVariable(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
|
||||
// ---
|
||||
// summary: Get a repo-level variable
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: name of the owner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActionVariable"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: ctx.Params("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
variable := &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, variable)
|
||||
}
|
||||
|
||||
// DeleteVariable delete a repo-level variable
|
||||
func DeleteVariable(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
|
||||
// ---
|
||||
// summary: Delete a repo-level variable
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: name of the owner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActionVariable"
|
||||
// "201":
|
||||
// description: response when deleting a variable
|
||||
// "204":
|
||||
// description: response when deleting a variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.Params("variablename")); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// CreateVariable create a repo-level variable
|
||||
func CreateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
|
||||
// ---
|
||||
// summary: Create a repo-level variable
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: name of the owner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when creating a repo-level variable
|
||||
// "204":
|
||||
// description: response when creating a repo-level variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
variableName := ctx.Params("variablename")
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
RepoID: repoID,
|
||||
Name: variableName,
|
||||
})
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
return
|
||||
}
|
||||
if v != nil && v.ID > 0 {
|
||||
ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "CreateVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// UpdateVariable update a repo-level variable
|
||||
func UpdateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
|
||||
// ---
|
||||
// summary: Update a repo-level variable
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: name of the owner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when updating a repo-level variable
|
||||
// "204":
|
||||
// description: response when updating a repo-level variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.UpdateVariableOption)
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: ctx.Params("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if opt.Name == "" {
|
||||
opt.Name = ctx.Params("variablename")
|
||||
}
|
||||
if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ListVariables list repo-level variables
|
||||
func ListVariables(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
|
||||
// ---
|
||||
// summary: Get repo-level variables list
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: name of the owner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/VariableList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "FindVariables", err)
|
||||
return
|
||||
}
|
||||
|
||||
variables := make([]*api.ActionVariable, len(vars))
|
||||
for i, v := range vars {
|
||||
variables[i] = &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, variables)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
|
@ -30,6 +31,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/issue"
|
||||
|
@ -1027,6 +1029,9 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
|
|||
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
|
||||
return err
|
||||
}
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||
}
|
||||
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
} else {
|
||||
if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
|
||||
|
@ -1034,6 +1039,11 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
|
|||
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
|
||||
return err
|
||||
}
|
||||
if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) {
|
||||
if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil {
|
||||
log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||
}
|
||||
}
|
||||
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,3 +18,17 @@ type swaggerResponseSecret struct {
|
|||
// in:body
|
||||
Body api.Secret `json:"body"`
|
||||
}
|
||||
|
||||
// ActionVariable
|
||||
// swagger:response ActionVariable
|
||||
type swaggerResponseActionVariable struct {
|
||||
// in:body
|
||||
Body api.ActionVariable `json:"body"`
|
||||
}
|
||||
|
||||
// VariableList
|
||||
// swagger:response VariableList
|
||||
type swaggerResponseVariableList struct {
|
||||
// in:body
|
||||
Body []api.ActionVariable `json:"body"`
|
||||
}
|
||||
|
|
|
@ -199,4 +199,10 @@ type swaggerParameterBodies struct {
|
|||
|
||||
// in:body
|
||||
CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption
|
||||
|
||||
// in:body
|
||||
CreateVariableOption api.CreateVariableOption
|
||||
|
||||
// in:body
|
||||
UpdateVariableOption api.UpdateVariableOption
|
||||
}
|
||||
|
|
|
@ -7,9 +7,13 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
)
|
||||
|
@ -101,3 +105,249 @@ func DeleteSecret(ctx *context.APIContext) {
|
|||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// CreateVariable create a user-level variable
|
||||
func CreateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/actions/variables/{variablename} user createUserVariable
|
||||
// ---
|
||||
// summary: Create a user-level variable
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when creating a variable
|
||||
// "204":
|
||||
// description: response when creating a variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||
|
||||
ownerID := ctx.Doer.ID
|
||||
variableName := ctx.Params("variablename")
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ownerID,
|
||||
Name: variableName,
|
||||
})
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
return
|
||||
}
|
||||
if v != nil && v.ID > 0 {
|
||||
ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "CreateVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// UpdateVariable update a user-level variable which is created by current doer
|
||||
func UpdateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /user/actions/variables/{variablename} user updateUserVariable
|
||||
// ---
|
||||
// summary: Update a user-level variable which is created by current doer
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when updating a variable
|
||||
// "204":
|
||||
// description: response when updating a variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.UpdateVariableOption)
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
Name: ctx.Params("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if opt.Name == "" {
|
||||
opt.Name = ctx.Params("variablename")
|
||||
}
|
||||
if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeleteVariable delete a user-level variable which is created by current doer
|
||||
func DeleteVariable(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/actions/variables/{variablename} user deleteUserVariable
|
||||
// ---
|
||||
// summary: Delete a user-level variable which is created by current doer
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when deleting a variable
|
||||
// "204":
|
||||
// description: response when deleting a variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.Params("variablename")); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetVariable get a user-level variable which is created by current doer
|
||||
func GetVariable(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/variables/{variablename} user getUserVariable
|
||||
// ---
|
||||
// summary: Get a user-level variable which is created by current doer
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActionVariable"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
Name: ctx.Params("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
variable := &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, variable)
|
||||
}
|
||||
|
||||
// ListVariables list user-level variables
|
||||
func ListVariables(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/variables user getUserVariablesList
|
||||
// ---
|
||||
// summary: Get the user-level list of variables which is created by current doer
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/VariableList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "FindVariables", err)
|
||||
return
|
||||
}
|
||||
|
||||
variables := make([]*api.ActionVariable, len(vars))
|
||||
for i, v := range vars {
|
||||
variables[i] = &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, variables)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
|
@ -133,7 +134,7 @@ func GetGPGKey(ctx *context.APIContext) {
|
|||
|
||||
// CreateUserGPGKey creates new GPG key to given user by ID.
|
||||
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
@ -274,7 +275,7 @@ func DeleteGPGKey(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -199,7 +199,7 @@ func GetPublicKey(ctx *context.APIContext) {
|
|||
|
||||
// CreateUserPublicKey creates new public key to given user by ID.
|
||||
func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
@ -269,7 +269,7 @@ func DeletePublicKey(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
|
@ -29,6 +30,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -929,6 +931,10 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
|
||||
|
||||
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
@ -947,6 +953,12 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) {
|
||||
if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil {
|
||||
log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
|
||||
|
||||
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
|
|
@ -4,17 +4,13 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
)
|
||||
|
||||
func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
|
||||
|
@ -29,41 +25,16 @@ func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
|
|||
ctx.Data["Variables"] = variables
|
||||
}
|
||||
|
||||
// some regular expression of `variables` and `secrets`
|
||||
// reference to:
|
||||
// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
|
||||
// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
|
||||
var (
|
||||
forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
|
||||
)
|
||||
|
||||
func envNameCIRegexMatch(name string) error {
|
||||
if forbiddenEnvNameCIRx.MatchString(name) {
|
||||
log.Error("Env Name cannot be ci")
|
||||
return errors.New("env name cannot be ci")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
|
||||
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
||||
|
||||
if err := secret_service.ValidateName(form.Name); err != nil {
|
||||
ctx.JSONError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(form.Name); err != nil {
|
||||
ctx.JSONError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
v, err := actions_model.InsertVariable(ctx, ownerID, repoID, form.Name, ReserveLineBreakForTextarea(form.Data))
|
||||
v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data)
|
||||
if err != nil {
|
||||
log.Error("InsertVariable error: %v", err)
|
||||
log.Error("CreateVariable: %v", err)
|
||||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
|
||||
ctx.JSONRedirect(redirectURL)
|
||||
}
|
||||
|
@ -72,23 +43,8 @@ func UpdateVariable(ctx *context.Context, redirectURL string) {
|
|||
id := ctx.ParamsInt64(":variable_id")
|
||||
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
||||
|
||||
if err := secret_service.ValidateName(form.Name); err != nil {
|
||||
ctx.JSONError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(form.Name); err != nil {
|
||||
ctx.JSONError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ok, err := actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{
|
||||
ID: id,
|
||||
Name: strings.ToUpper(form.Name),
|
||||
Data: ReserveLineBreakForTextarea(form.Data),
|
||||
})
|
||||
if err != nil || !ok {
|
||||
log.Error("UpdateVariable error: %v", err)
|
||||
if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok {
|
||||
log.Error("UpdateVariable: %v", err)
|
||||
ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
|
||||
return
|
||||
}
|
||||
|
@ -99,7 +55,7 @@ func UpdateVariable(ctx *context.Context, redirectURL string) {
|
|||
func DeleteVariable(ctx *context.Context, redirectURL string) {
|
||||
id := ctx.ParamsInt64(":variable_id")
|
||||
|
||||
if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil {
|
||||
if err := actions_service.DeleteVariableByID(ctx, id); err != nil {
|
||||
log.Error("Delete variable [%d] failed: %v", id, err)
|
||||
ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
|
||||
return
|
||||
|
@ -107,12 +63,3 @@ func DeleteVariable(ctx *context.Context, redirectURL string) {
|
|||
ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
|
||||
ctx.JSONRedirect(redirectURL)
|
||||
}
|
||||
|
||||
func ReserveLineBreakForTextarea(input string) string {
|
||||
// Since the content is from a form which is a textarea, the line endings are \r\n.
|
||||
// It's a standard behavior of HTML.
|
||||
// But we want to store them as \n like what GitHub does.
|
||||
// And users are unlikely to really need to keep the \r.
|
||||
// Other than this, we should respect the original content, even leading or trailing spaces.
|
||||
return strings.ReplaceAll(input, "\r\n", "\n")
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
secret_model "code.gitea.io/gitea/models/secret"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/shared/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
|
@ -27,7 +27,7 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
|
|||
func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
|
||||
form := web.GetForm(ctx).(*forms.AddSecretForm)
|
||||
|
||||
s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, actions.ReserveLineBreakForTextarea(form.Data))
|
||||
s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data))
|
||||
if err != nil {
|
||||
log.Error("CreateOrUpdateSecret failed: %v", err)
|
||||
ctx.JSONError(ctx.Tr("secrets.creation.failed"))
|
||||
|
|
|
@ -244,7 +244,7 @@ func DeleteEmail(ctx *context.Context) {
|
|||
|
||||
// DeleteAccount render user suicide page and response for delete user himself
|
||||
func DeleteAccount(ctx *context.Context) {
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ func loadAccountData(ctx *context.Context) {
|
|||
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
||||
ctx.Data["ActivationsPending"] = pendingActivation
|
||||
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
|
||||
ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
|
||||
ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
|
@ -78,7 +79,7 @@ func KeysPost(ctx *context.Context) {
|
|||
ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
||||
case "gpg":
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
@ -159,7 +160,7 @@ func KeysPost(ctx *context.Context) {
|
|||
ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
||||
case "ssh":
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
@ -203,7 +204,7 @@ func KeysPost(ctx *context.Context) {
|
|||
ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
||||
case "verify_ssh":
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
@ -240,7 +241,7 @@ func KeysPost(ctx *context.Context) {
|
|||
func DeleteKey(ctx *context.Context) {
|
||||
switch ctx.FormString("type") {
|
||||
case "gpg":
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
@ -250,7 +251,7 @@ func DeleteKey(ctx *context.Context) {
|
|||
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
|
||||
}
|
||||
case "ssh":
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
@ -333,5 +334,5 @@ func loadKeysData(ctx *context.Context) {
|
|||
|
||||
ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
|
||||
ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
|
||||
ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||
log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
|
||||
return nil
|
||||
}
|
||||
if input.Repo.IsEmpty {
|
||||
if input.Repo.IsEmpty || input.Repo.IsArchived {
|
||||
return nil
|
||||
}
|
||||
if unit_model.TypeActions.UnitGlobalDisabled() {
|
||||
|
@ -536,7 +536,7 @@ func handleSchedules(
|
|||
|
||||
// DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
|
||||
func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
|
||||
if repo.IsEmpty {
|
||||
if repo.IsEmpty || repo.IsArchived {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,11 @@ func startTasks(ctx context.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
if row.Repo.IsArchived {
|
||||
// Skip if the repo is archived
|
||||
continue
|
||||
}
|
||||
|
||||
cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
|
||||
if err != nil {
|
||||
if repo_model.IsErrUnitTypeNotExist(err) {
|
||||
|
|
100
services/actions/variables.go
Normal file
100
services/actions/variables.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
)
|
||||
|
||||
func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*actions_model.ActionVariable, error) {
|
||||
if err := secret_service.ValidateName(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func UpdateVariable(ctx context.Context, variableID int64, name, data string) (bool, error) {
|
||||
if err := secret_service.ValidateName(name); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(name); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{
|
||||
ID: variableID,
|
||||
Name: strings.ToUpper(name),
|
||||
Data: util.ReserveLineBreakForTextarea(data),
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteVariableByID(ctx context.Context, variableID int64) error {
|
||||
return actions_model.DeleteVariable(ctx, variableID)
|
||||
}
|
||||
|
||||
func DeleteVariableByName(ctx context.Context, ownerID, repoID int64, name string) error {
|
||||
if err := secret_service.ValidateName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return actions_model.DeleteVariable(ctx, v.ID)
|
||||
}
|
||||
|
||||
func GetVariable(ctx context.Context, opts actions_model.FindVariablesOpts) (*actions_model.ActionVariable, error) {
|
||||
vars, err := actions_model.FindVariables(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(vars) != 1 {
|
||||
return nil, util.NewNotExistErrorf("variable not found")
|
||||
}
|
||||
return vars[0], nil
|
||||
}
|
||||
|
||||
// some regular expression of `variables` and `secrets`
|
||||
// reference to:
|
||||
// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
|
||||
// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
|
||||
var (
|
||||
forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
|
||||
)
|
||||
|
||||
func envNameCIRegexMatch(name string) error {
|
||||
if forbiddenEnvNameCIRx.MatchString(name) {
|
||||
log.Error("Env Name cannot be ci")
|
||||
return util.NewInvalidArgumentErrorf("env name cannot be ci")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -967,12 +967,12 @@ func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]Co
|
|||
for _, commit := range prInfo.Commits {
|
||||
var committerOrAuthorName string
|
||||
var commitTime time.Time
|
||||
if commit.Committer != nil {
|
||||
committerOrAuthorName = commit.Committer.Name
|
||||
commitTime = commit.Committer.When
|
||||
} else {
|
||||
if commit.Author != nil {
|
||||
committerOrAuthorName = commit.Author.Name
|
||||
commitTime = commit.Author.When
|
||||
} else {
|
||||
committerOrAuthorName = commit.Committer.Name
|
||||
commitTime = commit.Committer.When
|
||||
}
|
||||
|
||||
commits = append(commits, CommitInfo{
|
||||
|
|
246
stylelint.config.js
Normal file
246
stylelint.config.js
Normal file
|
@ -0,0 +1,246 @@
|
|||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
const cssVarFiles = [
|
||||
fileURLToPath(new URL('web_src/css/base.css', import.meta.url)),
|
||||
fileURLToPath(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url)),
|
||||
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
|
||||
];
|
||||
|
||||
/** @type {import('stylelint').Config} */
|
||||
export default {
|
||||
plugins: [
|
||||
'stylelint-declaration-strict-value',
|
||||
'stylelint-declaration-block-no-ignored-properties',
|
||||
'stylelint-value-no-unknown-custom-properties',
|
||||
'@stylistic/stylelint-plugin',
|
||||
],
|
||||
ignoreFiles: [
|
||||
'**/*.go',
|
||||
'/web_src/fomantic',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/chroma/*', '**/codemirror/*', '**/standalone/*', '**/console.css', 'font_i18n.css'],
|
||||
rules: {
|
||||
'scale-unlimited/declaration-strict-value': null,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/chroma/*', '**/codemirror/*'],
|
||||
rules: {
|
||||
'block-no-empty': null,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
customSyntax: 'postcss-html',
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'@stylistic/at-rule-name-case': null,
|
||||
'@stylistic/at-rule-name-newline-after': null,
|
||||
'@stylistic/at-rule-name-space-after': null,
|
||||
'@stylistic/at-rule-semicolon-newline-after': null,
|
||||
'@stylistic/at-rule-semicolon-space-before': null,
|
||||
'@stylistic/block-closing-brace-empty-line-before': null,
|
||||
'@stylistic/block-closing-brace-newline-after': null,
|
||||
'@stylistic/block-closing-brace-newline-before': null,
|
||||
'@stylistic/block-closing-brace-space-after': null,
|
||||
'@stylistic/block-closing-brace-space-before': null,
|
||||
'@stylistic/block-opening-brace-newline-after': null,
|
||||
'@stylistic/block-opening-brace-newline-before': null,
|
||||
'@stylistic/block-opening-brace-space-after': null,
|
||||
'@stylistic/block-opening-brace-space-before': 'always',
|
||||
'@stylistic/color-hex-case': 'lower',
|
||||
'@stylistic/declaration-bang-space-after': 'never',
|
||||
'@stylistic/declaration-bang-space-before': null,
|
||||
'@stylistic/declaration-block-semicolon-newline-after': null,
|
||||
'@stylistic/declaration-block-semicolon-newline-before': null,
|
||||
'@stylistic/declaration-block-semicolon-space-after': null,
|
||||
'@stylistic/declaration-block-semicolon-space-before': 'never',
|
||||
'@stylistic/declaration-block-trailing-semicolon': null,
|
||||
'@stylistic/declaration-colon-newline-after': null,
|
||||
'@stylistic/declaration-colon-space-after': null,
|
||||
'@stylistic/declaration-colon-space-before': 'never',
|
||||
'@stylistic/function-comma-newline-after': null,
|
||||
'@stylistic/function-comma-newline-before': null,
|
||||
'@stylistic/function-comma-space-after': null,
|
||||
'@stylistic/function-comma-space-before': null,
|
||||
'@stylistic/function-max-empty-lines': 0,
|
||||
'@stylistic/function-parentheses-newline-inside': 'never-multi-line',
|
||||
'@stylistic/function-parentheses-space-inside': null,
|
||||
'@stylistic/function-whitespace-after': null,
|
||||
'@stylistic/indentation': 2,
|
||||
'@stylistic/linebreaks': null,
|
||||
'@stylistic/max-empty-lines': 1,
|
||||
'@stylistic/max-line-length': null,
|
||||
'@stylistic/media-feature-colon-space-after': null,
|
||||
'@stylistic/media-feature-colon-space-before': 'never',
|
||||
'@stylistic/media-feature-name-case': null,
|
||||
'@stylistic/media-feature-parentheses-space-inside': null,
|
||||
'@stylistic/media-feature-range-operator-space-after': 'always',
|
||||
'@stylistic/media-feature-range-operator-space-before': 'always',
|
||||
'@stylistic/media-query-list-comma-newline-after': null,
|
||||
'@stylistic/media-query-list-comma-newline-before': null,
|
||||
'@stylistic/media-query-list-comma-space-after': null,
|
||||
'@stylistic/media-query-list-comma-space-before': null,
|
||||
'@stylistic/named-grid-areas-alignment': null,
|
||||
'@stylistic/no-empty-first-line': null,
|
||||
'@stylistic/no-eol-whitespace': true,
|
||||
'@stylistic/no-extra-semicolons': true,
|
||||
'@stylistic/no-missing-end-of-source-newline': null,
|
||||
'@stylistic/number-leading-zero': null,
|
||||
'@stylistic/number-no-trailing-zeros': null,
|
||||
'@stylistic/property-case': 'lower',
|
||||
'@stylistic/selector-attribute-brackets-space-inside': null,
|
||||
'@stylistic/selector-attribute-operator-space-after': null,
|
||||
'@stylistic/selector-attribute-operator-space-before': null,
|
||||
'@stylistic/selector-combinator-space-after': null,
|
||||
'@stylistic/selector-combinator-space-before': null,
|
||||
'@stylistic/selector-descendant-combinator-no-non-space': null,
|
||||
'@stylistic/selector-list-comma-newline-after': null,
|
||||
'@stylistic/selector-list-comma-newline-before': null,
|
||||
'@stylistic/selector-list-comma-space-after': 'always-single-line',
|
||||
'@stylistic/selector-list-comma-space-before': 'never-single-line',
|
||||
'@stylistic/selector-max-empty-lines': 0,
|
||||
'@stylistic/selector-pseudo-class-case': 'lower',
|
||||
'@stylistic/selector-pseudo-class-parentheses-space-inside': 'never',
|
||||
'@stylistic/selector-pseudo-element-case': 'lower',
|
||||
'@stylistic/string-quotes': 'double',
|
||||
'@stylistic/unicode-bom': null,
|
||||
'@stylistic/unit-case': 'lower',
|
||||
'@stylistic/value-list-comma-newline-after': null,
|
||||
'@stylistic/value-list-comma-newline-before': null,
|
||||
'@stylistic/value-list-comma-space-after': null,
|
||||
'@stylistic/value-list-comma-space-before': null,
|
||||
'@stylistic/value-list-max-empty-lines': 0,
|
||||
'alpha-value-notation': null,
|
||||
'annotation-no-unknown': true,
|
||||
'at-rule-allowed-list': null,
|
||||
'at-rule-disallowed-list': null,
|
||||
'at-rule-empty-line-before': null,
|
||||
'at-rule-no-unknown': [true, {ignoreAtRules: ['tailwind']}],
|
||||
'at-rule-no-vendor-prefix': true,
|
||||
'at-rule-property-required-list': null,
|
||||
'block-no-empty': true,
|
||||
'color-function-notation': null,
|
||||
'color-hex-alpha': null,
|
||||
'color-hex-length': null,
|
||||
'color-named': null,
|
||||
'color-no-hex': null,
|
||||
'color-no-invalid-hex': true,
|
||||
'comment-empty-line-before': null,
|
||||
'comment-no-empty': true,
|
||||
'comment-pattern': null,
|
||||
'comment-whitespace-inside': null,
|
||||
'comment-word-disallowed-list': null,
|
||||
'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}],
|
||||
'custom-media-pattern': null,
|
||||
'custom-property-empty-line-before': null,
|
||||
'custom-property-no-missing-var-function': true,
|
||||
'custom-property-pattern': null,
|
||||
'declaration-block-no-duplicate-custom-properties': true,
|
||||
'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}],
|
||||
'declaration-block-no-redundant-longhand-properties': null,
|
||||
'declaration-block-no-shorthand-property-overrides': null,
|
||||
'declaration-block-single-line-max-declarations': null,
|
||||
'declaration-empty-line-before': null,
|
||||
'declaration-no-important': null,
|
||||
'declaration-property-max-values': null,
|
||||
'declaration-property-unit-allowed-list': null,
|
||||
'declaration-property-unit-disallowed-list': {'line-height': ['em']},
|
||||
'declaration-property-value-allowed-list': null,
|
||||
'declaration-property-value-disallowed-list': null,
|
||||
'declaration-property-value-no-unknown': true,
|
||||
'font-family-name-quotes': 'always-where-recommended',
|
||||
'font-family-no-duplicate-names': true,
|
||||
'font-family-no-missing-generic-family-keyword': true,
|
||||
'font-weight-notation': null,
|
||||
'function-allowed-list': null,
|
||||
'function-calc-no-unspaced-operator': true,
|
||||
'function-disallowed-list': null,
|
||||
'function-linear-gradient-no-nonstandard-direction': true,
|
||||
'function-name-case': 'lower',
|
||||
'function-no-unknown': true,
|
||||
'function-url-no-scheme-relative': null,
|
||||
'function-url-quotes': 'always',
|
||||
'function-url-scheme-allowed-list': null,
|
||||
'function-url-scheme-disallowed-list': null,
|
||||
'hue-degree-notation': null,
|
||||
'import-notation': 'string',
|
||||
'keyframe-block-no-duplicate-selectors': true,
|
||||
'keyframe-declaration-no-important': true,
|
||||
'keyframe-selector-notation': null,
|
||||
'keyframes-name-pattern': null,
|
||||
'length-zero-no-unit': [true, {ignore: ['custom-properties']}, {ignoreFunctions: ['var']}],
|
||||
'max-nesting-depth': null,
|
||||
'media-feature-name-allowed-list': null,
|
||||
'media-feature-name-disallowed-list': null,
|
||||
'media-feature-name-no-unknown': true,
|
||||
'media-feature-name-no-vendor-prefix': true,
|
||||
'media-feature-name-unit-allowed-list': null,
|
||||
'media-feature-name-value-allowed-list': null,
|
||||
'media-feature-name-value-no-unknown': true,
|
||||
'media-feature-range-notation': null,
|
||||
'media-query-no-invalid': true,
|
||||
'named-grid-areas-no-invalid': true,
|
||||
'no-descending-specificity': null,
|
||||
'no-duplicate-at-import-rules': true,
|
||||
'no-duplicate-selectors': true,
|
||||
'no-empty-source': true,
|
||||
'no-invalid-double-slash-comments': true,
|
||||
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}],
|
||||
'no-irregular-whitespace': true,
|
||||
'no-unknown-animations': null,
|
||||
'no-unknown-custom-properties': null,
|
||||
'number-max-precision': null,
|
||||
'plugin/declaration-block-no-ignored-properties': true,
|
||||
'property-allowed-list': null,
|
||||
'property-disallowed-list': null,
|
||||
'property-no-unknown': true,
|
||||
'property-no-vendor-prefix': null,
|
||||
'rule-empty-line-before': null,
|
||||
'rule-selector-property-disallowed-list': null,
|
||||
'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: false, disableFix: true, expandShorthand: true}],
|
||||
'selector-anb-no-unmatchable': true,
|
||||
'selector-attribute-name-disallowed-list': null,
|
||||
'selector-attribute-operator-allowed-list': null,
|
||||
'selector-attribute-operator-disallowed-list': null,
|
||||
'selector-attribute-quotes': 'always',
|
||||
'selector-class-pattern': null,
|
||||
'selector-combinator-allowed-list': null,
|
||||
'selector-combinator-disallowed-list': null,
|
||||
'selector-disallowed-list': null,
|
||||
'selector-id-pattern': null,
|
||||
'selector-max-attribute': null,
|
||||
'selector-max-class': null,
|
||||
'selector-max-combinators': null,
|
||||
'selector-max-compound-selectors': null,
|
||||
'selector-max-id': null,
|
||||
'selector-max-pseudo-class': null,
|
||||
'selector-max-specificity': null,
|
||||
'selector-max-type': null,
|
||||
'selector-max-universal': null,
|
||||
'selector-nested-pattern': null,
|
||||
'selector-no-qualifying-type': null,
|
||||
'selector-no-vendor-prefix': true,
|
||||
'selector-not-notation': null,
|
||||
'selector-pseudo-class-allowed-list': null,
|
||||
'selector-pseudo-class-disallowed-list': null,
|
||||
'selector-pseudo-class-no-unknown': true,
|
||||
'selector-pseudo-element-allowed-list': null,
|
||||
'selector-pseudo-element-colon-notation': 'double',
|
||||
'selector-pseudo-element-disallowed-list': null,
|
||||
'selector-pseudo-element-no-unknown': true,
|
||||
'selector-type-case': 'lower',
|
||||
'selector-type-no-unknown': [true, {ignore: ['custom-elements']}],
|
||||
'shorthand-property-no-redundant-values': true,
|
||||
'string-no-newline': true,
|
||||
'time-min-milliseconds': null,
|
||||
'unit-allowed-list': null,
|
||||
'unit-disallowed-list': null,
|
||||
'unit-no-unknown': true,
|
||||
'value-keyword-case': null,
|
||||
'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}],
|
||||
},
|
||||
};
|
|
@ -7,14 +7,14 @@
|
|||
<dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>
|
||||
<dd>
|
||||
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}">
|
||||
<input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}>
|
||||
<input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}><label></label>
|
||||
</div>
|
||||
</dd>
|
||||
<div class="divider"></div>
|
||||
<dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
|
||||
<dd>
|
||||
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">
|
||||
<input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}>
|
||||
<input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label>
|
||||
</div>
|
||||
</dd>
|
||||
</dl>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">
|
||||
<div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}">
|
||||
{{if ShowFooterPoweredBy}}
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a>
|
||||
{{end}}
|
||||
{{if (or .ShowFooterVersion .PageIsAdmin)}}
|
||||
{{ctx.Locale.Tr "version"}}:
|
||||
{{if .IsAdmin}}
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
|
||||
<div>
|
||||
<h1>Loading</h1>
|
||||
<div class="is-loading small-loading-icon tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>
|
||||
<div class="is-loading loading-icon-2px tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>
|
||||
<div class="is-loading tw-border tw-border-secondary tw-py-4">
|
||||
<p>loading ...</p>
|
||||
<p>loading ...</p>
|
||||
|
|
|
@ -42,8 +42,8 @@
|
|||
|
||||
<div class="field color-field">
|
||||
<label for="new_project_column_color_picker">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
|
||||
<div class="color picker column">
|
||||
<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
|
||||
<div class="js-color-picker-input column">
|
||||
<input maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
|
||||
{{template "repo/issue/label_precolors"}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,8 +114,8 @@
|
|||
|
||||
<div class="field color-field">
|
||||
<label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
|
||||
<div class="color picker column">
|
||||
<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">
|
||||
<div class="js-color-picker-input column">
|
||||
<input maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">
|
||||
{{template "repo/issue/label_precolors"}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -164,9 +164,9 @@
|
|||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui horizontal list tw-flex tw-items-center">
|
||||
<div class="tw-flex tw-items-center">
|
||||
{{if .Parents}}
|
||||
<div class="item">
|
||||
<div>
|
||||
<span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>
|
||||
{{range .Parents}}
|
||||
{{if $.PageIsWiki}}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<td class="author tw-flex">
|
||||
{{$userName := .Author.Name}}
|
||||
{{if .User}}
|
||||
{{if .User.FullName}}
|
||||
{{if and .User.FullName DefaultShowFullName}}
|
||||
{{$userName = .User.FullName}}
|
||||
{{end}}
|
||||
{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<span class="author tw-flex tw-items-center tw-mr-2">
|
||||
{{$userName := $commit.Commit.Author.Name}}
|
||||
{{if $commit.User}}
|
||||
{{if $commit.User.FullName}}
|
||||
{{if and $commit.User.FullName DefaultShowFullName}}
|
||||
{{$userName = $commit.User.FullName}}
|
||||
{{end}}
|
||||
<span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
|
||||
|
|
|
@ -18,23 +18,22 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-1" id="repo-topics">
|
||||
{{range .Topics}}<a class="ui repo-topic large label topic tw-m-0" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
|
||||
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" id="repo-topics">
|
||||
{{/* it should match the code in issue-home.js */}}
|
||||
{{range .Topics}}<a class="repo-topic ui large label" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
|
||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
|
||||
<div class="ui form tw-hidden tw-flex tw-flex-col tw-mt-4" id="topic_edit">
|
||||
<div class="field tw-flex-1 tw-mb-1">
|
||||
<div class="ui fluid multiple search selection dropdown tw-flex-wrap" data-text-count-prompt="{{ctx.Locale.Tr "repo.topic.count_prompt"}}" data-text-format-prompt="{{ctx.Locale.Tr "repo.topic.format_prompt"}}">
|
||||
<div class="ui form tw-hidden tw-flex tw-gap-2 tw-my-2" id="topic_edit">
|
||||
<div class="ui fluid multiple search selection dropdown tw-flex-wrap tw-flex-1">
|
||||
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
|
||||
{{range .Topics}}
|
||||
{{/* keey the same layout as Fomantic UI generated labels */}}
|
||||
{{/* keep the same layout as Fomantic UI generated labels */}}
|
||||
<a class="ui label transition visible tw-cursor-default tw-inline-block" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a>
|
||||
{{end}}
|
||||
<div class="text"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
|
||||
<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{ctx.Locale.Tr "save"}}</button>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{{template "repo/header" .}}
|
||||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
<div class="navbar">
|
||||
<div class="issue-navbar">
|
||||
{{template "repo/issue/navbar" .}}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div role="main" aria-label="{{.Title}}" class="page-content repository labels">
|
||||
{{template "repo/header" .}}
|
||||
<div class="ui container">
|
||||
<div class="navbar tw-mb-4">
|
||||
<div class="issue-navbar tw-mb-4">
|
||||
{{template "repo/issue/navbar" .}}
|
||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
||||
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
||||
|
|
|
@ -52,8 +52,8 @@
|
|||
</div>
|
||||
<div class="field color-field">
|
||||
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
||||
<div class="color picker column">
|
||||
<input class="color-picker" name="color" value="#70c24a" required maxlength="7">
|
||||
<div class="column js-color-picker-input">
|
||||
<input name="color" value="#70c24a"placeholder="#c320f6" required maxlength="7">
|
||||
{{template "repo/issue/label_precolors"}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
|
||||
</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="left menu">
|
||||
<div class="menu">
|
||||
<a class="{{if or (eq .SortType "alphabetically") (not .SortType)}}active {{end}}item" href="?sort=alphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
|
||||
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="?sort=reversealphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
|
||||
<a class="{{if eq .SortType "leastissues"}}active {{end}}item" href="?sort=leastissues&state={{$.State}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_issues"}}</a>
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
</div>
|
||||
<div class="field color-field">
|
||||
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
||||
<div class="color picker column">
|
||||
<input class="color-picker" name="color" value="#70c24a" required maxlength="7">
|
||||
<div class="js-color-picker-input column">
|
||||
<input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7">
|
||||
{{template "repo/issue/label_precolors"}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div role="main" aria-label="{{.Title}}" class="page-content repository new milestone">
|
||||
{{template "repo/header" .}}
|
||||
<div class="ui container">
|
||||
<div class="navbar">
|
||||
<div class="issue-navbar">
|
||||
{{template "repo/issue/navbar" .}}
|
||||
{{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}
|
||||
<div class="ui right floated secondary menu">
|
||||
|
|
|
@ -684,7 +684,7 @@
|
|||
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
|
||||
<div class="divider"></div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox" id="allow-edits-from-maintainers"
|
||||
<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
|
||||
data-url="{{.Issue.Link}}"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
|
||||
data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{{else}}
|
||||
{{if .LatestCommitUser}}
|
||||
{{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}
|
||||
{{if .LatestCommitUser.FullName}}
|
||||
{{if and .LatestCommitUser.FullName DefaultShowFullName}}
|
||||
<a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
|
||||
{{else}}
|
||||
<a class="muted author-wrapper" title="{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
|
||||
|
|
750
templates/swagger/v1_json.tmpl
generated
750
templates/swagger/v1_json.tmpl
generated
|
@ -1741,6 +1741,232 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/actions/variables": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Get an org-level variables list",
|
||||
"operationId": "getOrgVariablesList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/VariableList"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/actions/variables/{variablename}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Get an org-level variable",
|
||||
"operationId": "getOrgVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/ActionVariable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Update an org-level variable",
|
||||
"operationId": "updateOrgVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/UpdateVariableOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "response when updating an org-level variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when updating an org-level variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Create an org-level variable",
|
||||
"operationId": "createOrgVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/CreateVariableOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "response when creating an org-level variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when creating an org-level variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Delete an org-level variable",
|
||||
"operationId": "deleteOrgVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/ActionVariable"
|
||||
},
|
||||
"201": {
|
||||
"description": "response when deleting a variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when deleting a variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/activities/feeds": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
@ -3591,6 +3817,261 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/actions/variables": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get repo-level variables list",
|
||||
"operationId": "getRepoVariablesList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the owner",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repository",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/VariableList"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/actions/variables/{variablename}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get a repo-level variable",
|
||||
"operationId": "getRepoVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the owner",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repository",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/ActionVariable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Update a repo-level variable",
|
||||
"operationId": "updateRepoVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the owner",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repository",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/UpdateVariableOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "response when updating a repo-level variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when updating a repo-level variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Create a repo-level variable",
|
||||
"operationId": "createRepoVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the owner",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repository",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/CreateVariableOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "response when creating a repo-level variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when creating a repo-level variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Delete a repo-level variable",
|
||||
"operationId": "deleteRepoVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the owner",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repository",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/ActionVariable"
|
||||
},
|
||||
"201": {
|
||||
"description": "response when deleting a variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when deleting a variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/activities/feeds": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
@ -15375,6 +15856,194 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/actions/variables": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Get the user-level list of variables which is created by current doer",
|
||||
"operationId": "getUserVariablesList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/VariableList"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/actions/variables/{variablename}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Get a user-level variable which is created by current doer",
|
||||
"operationId": "getUserVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/ActionVariable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Update a user-level variable which is created by current doer",
|
||||
"operationId": "updateUserVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/UpdateVariableOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "response when updating a variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when updating a variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Create a user-level variable",
|
||||
"operationId": "createUserVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/CreateVariableOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "response when creating a variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when creating a variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete a user-level variable which is created by current doer",
|
||||
"operationId": "deleteUserVariable",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the variable",
|
||||
"name": "variablename",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "response when deleting a variable"
|
||||
},
|
||||
"204": {
|
||||
"description": "response when deleting a variable"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/applications/oauth2": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
@ -17493,6 +18162,35 @@
|
|||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"ActionVariable": {
|
||||
"description": "ActionVariable return value of the query API",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "the value of the variable",
|
||||
"type": "string",
|
||||
"x-go-name": "Data"
|
||||
},
|
||||
"name": {
|
||||
"description": "the name of the variable",
|
||||
"type": "string",
|
||||
"x-go-name": "Name"
|
||||
},
|
||||
"owner_id": {
|
||||
"description": "the owner to which the variable belongs",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "OwnerID"
|
||||
},
|
||||
"repo_id": {
|
||||
"description": "the repository to which the variable belongs",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "RepoID"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"Activity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -19393,6 +20091,21 @@
|
|||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"CreateVariableOption": {
|
||||
"description": "CreateVariableOption the option when creating variable",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"value": {
|
||||
"description": "Value of the variable to create",
|
||||
"type": "string",
|
||||
"x-go-name": "Value"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"CreateWikiPageOptions": {
|
||||
"description": "CreateWikiPageOptions form for creating wiki",
|
||||
"type": "object",
|
||||
|
@ -23773,6 +24486,26 @@
|
|||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"UpdateVariableOption": {
|
||||
"description": "UpdateVariableOption the option when updating variable",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "New name for the variable. If the field is empty, the variable name won't be updated.",
|
||||
"type": "string",
|
||||
"x-go-name": "Name"
|
||||
},
|
||||
"value": {
|
||||
"description": "Value of the variable to update",
|
||||
"type": "string",
|
||||
"x-go-name": "Value"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"User": {
|
||||
"description": "User represents a user",
|
||||
"type": "object",
|
||||
|
@ -24157,6 +24890,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ActionVariable": {
|
||||
"description": "ActionVariable",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ActionVariable"
|
||||
}
|
||||
},
|
||||
"ActivityFeedsList": {
|
||||
"description": "ActivityFeedsList",
|
||||
"schema": {
|
||||
|
@ -25040,6 +25779,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"VariableList": {
|
||||
"description": "VariableList",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ActionVariable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WatchInfo": {
|
||||
"description": "WatchInfo",
|
||||
"schema": {
|
||||
|
@ -25115,7 +25863,7 @@
|
|||
"parameterBodies": {
|
||||
"description": "parameterBodies",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/CreateOrUpdateSecretOption"
|
||||
"$ref": "#/definitions/UpdateVariableOption"
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="ui attached segment">
|
||||
{{if or .allowAdopt .allowDelete}}
|
||||
{{if .Dirs}}
|
||||
<div class="ui middle aligned divided list">
|
||||
<div class="ui list">
|
||||
{{range $dirI, $dir := .Dirs}}
|
||||
{{$repo := index $.ReposMap $dir}}
|
||||
<div class="item {{if not $repo}}tw-py-1{{end}}">{{/* if not repo, then there are "adapt" buttons, so the padding shouldn't be that default large*/}}
|
||||
|
|
149
tests/integration/api_repo_variables_test.go
Normal file
149
tests/integration/api_repo_variables_test.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
)
|
||||
|
||||
func TestAPIRepoVariables(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
t.Run("CreateRepoVariable", func(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
ExpectedStatus int
|
||||
}{
|
||||
{
|
||||
Name: "-",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "_",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
Name: "TEST_VAR",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
Name: "test_var",
|
||||
ExpectedStatus: http.StatusConflict,
|
||||
},
|
||||
{
|
||||
Name: "ci",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "123var",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "var@test",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "github_var",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "gitea_var",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.CreateVariableOption{
|
||||
Value: "value",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, c.ExpectedStatus)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateRepoVariable", func(t *testing.T) {
|
||||
variableName := "test_update_var"
|
||||
url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName)
|
||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||
Value: "initial_val",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
UpdateName string
|
||||
ExpectedStatus int
|
||||
}{
|
||||
{
|
||||
Name: "not_found_var",
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "1invalid",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "invalid@name",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "ci",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "updated_var_name",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
Name: "updated_var_name",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.UpdateVariableOption{
|
||||
Name: c.UpdateName,
|
||||
Value: "updated_val",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, c.ExpectedStatus)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DeleteRepoVariable", func(t *testing.T) {
|
||||
variableName := "test_delete_var"
|
||||
url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName)
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||
Value: "initial_val",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
144
tests/integration/api_user_variables_test.go
Normal file
144
tests/integration/api_user_variables_test.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
)
|
||||
|
||||
func TestAPIUserVariables(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
t.Run("CreateRepoVariable", func(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
ExpectedStatus int
|
||||
}{
|
||||
{
|
||||
Name: "-",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "_",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
Name: "TEST_VAR",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
Name: "test_var",
|
||||
ExpectedStatus: http.StatusConflict,
|
||||
},
|
||||
{
|
||||
Name: "ci",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "123var",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "var@test",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "github_var",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "gitea_var",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.CreateVariableOption{
|
||||
Value: "value",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, c.ExpectedStatus)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateRepoVariable", func(t *testing.T) {
|
||||
variableName := "test_update_var"
|
||||
url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName)
|
||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||
Value: "initial_val",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
UpdateName string
|
||||
ExpectedStatus int
|
||||
}{
|
||||
{
|
||||
Name: "not_found_var",
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "1invalid",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "invalid@name",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "ci",
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
UpdateName: "updated_var_name",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
Name: variableName,
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
Name: "updated_var_name",
|
||||
ExpectedStatus: http.StatusNoContent,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.UpdateVariableOption{
|
||||
Name: c.UpdateName,
|
||||
Value: "updated_val",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, c.ExpectedStatus)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DeleteRepoVariable", func(t *testing.T) {
|
||||
variableName := "test_delete_var"
|
||||
url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName)
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||
Value: "initial_val",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
--repo-header-issue-min-height: 41px;
|
||||
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
|
||||
--tab-size: 4;
|
||||
--checkbox-size: 16px; /* height and width of checkbox and radio inputs */
|
||||
}
|
||||
|
||||
:root * {
|
||||
|
@ -44,7 +45,7 @@ html, body {
|
|||
}
|
||||
|
||||
body {
|
||||
line-height: 1.4285rem;
|
||||
line-height: 20px;
|
||||
font-family: var(--fonts-regular);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-body);
|
||||
|
@ -316,61 +317,6 @@ a.label,
|
|||
background-color: var(--color-label-bg);
|
||||
}
|
||||
|
||||
/* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */
|
||||
.ui.input > input {
|
||||
line-height: var(--line-height-default);
|
||||
text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */
|
||||
}
|
||||
|
||||
/* fix Fomantic's line-height causing vertical scrollbars to appear */
|
||||
ul.ui.list li,
|
||||
ol.ui.list li,
|
||||
.ui.list > .item,
|
||||
.ui.list .list > .item {
|
||||
line-height: var(--line-height-default);
|
||||
}
|
||||
|
||||
.ui.input.focus > input,
|
||||
.ui.input > input:focus {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui.action.input .ui.ui.button {
|
||||
border-color: var(--color-input-border);
|
||||
padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* currently used for search bar dropdowns in repo search and explore code */
|
||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection {
|
||||
min-width: 10em;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) {
|
||||
border-right: none;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover {
|
||||
border-color: var(--color-input-border);
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > input,
|
||||
.ui.action.input:not([class*="left action"]) > input:hover {
|
||||
border-right: none;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
|
||||
border-left-color: var(--color-primary);
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > input:focus {
|
||||
border-right-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui.menu {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -514,21 +460,6 @@ ol.ui.list li,
|
|||
color: var(--color-text-light-2);
|
||||
}
|
||||
|
||||
.ui.list .list > .item .header,
|
||||
.ui.list > .item .header {
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
.ui.list .list > .item > .content,
|
||||
.ui.list > .item > .content {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.ui.list .list > .item .description,
|
||||
.ui.list > .item .description {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* replace item margin on secondary menu items with gap and remove both the
|
||||
negative margins on the menu as well as margin on the items */
|
||||
.ui.secondary.menu {
|
||||
|
@ -647,10 +578,6 @@ img.ui.avatar,
|
|||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.ui.divided.list > .item {
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.ui.error.message .header,
|
||||
.ui.warning.message .header {
|
||||
color: inherit;
|
||||
|
@ -1457,11 +1384,6 @@ table th[data-sortt-desc] .svg {
|
|||
vertical-align: -0.15em;
|
||||
}
|
||||
|
||||
/* for the jquery.minicolors plugin */
|
||||
.minicolors-panel {
|
||||
background: var(--color-secondary-dark-1) !important;
|
||||
}
|
||||
|
||||
.ui.tabular.menu {
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
@ -1625,16 +1547,6 @@ table th[data-sortt-desc] .svg {
|
|||
align-items: stretch;
|
||||
}
|
||||
|
||||
.ui.ui.icon.input .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ui.icon.input > i.icon {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.flex-items-block > .item,
|
||||
.flex-text-block {
|
||||
display: flex;
|
||||
|
|
47
web_src/css/features/colorpicker.css
Normal file
47
web_src/css/features/colorpicker.css
Normal file
|
@ -0,0 +1,47 @@
|
|||
.js-color-picker-input {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.js-color-picker-input input {
|
||||
padding-top: 8px !important;
|
||||
padding-bottom: 8px !important;
|
||||
padding-left: 32px !important;
|
||||
}
|
||||
|
||||
.js-color-picker-input .preview-square {
|
||||
position: absolute;
|
||||
aspect-ratio: 1;
|
||||
height: 16px;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-radius: 2px;
|
||||
background: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); /* stylelint-disable-line scale-unlimited/declaration-strict-value */
|
||||
background-position: 0 0, 4px 4px;
|
||||
background-size: 8px 8px;
|
||||
}
|
||||
|
||||
.js-color-picker-input .preview-square::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background-color: currentcolor;
|
||||
}
|
||||
|
||||
hex-color-picker {
|
||||
width: 180px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
hex-color-picker::part(hue-pointer),
|
||||
hex-color-picker::part(saturation-pointer) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
hex-color-picker::part(hue) {
|
||||
flex-basis: 16px;
|
||||
}
|
|
@ -102,26 +102,3 @@
|
|||
.card-ghost * {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.color-field .minicolors.minicolors-theme-default {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.color-field .minicolors.minicolors-theme-default .minicolors-input {
|
||||
height: 38px;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.color-field .minicolors.minicolors-theme-default .minicolors-swatch {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.edit-project-column-modal .color.picker.column,
|
||||
.new-project-column-modal .color.picker.column {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.edit-project-column-modal .color.picker.column .minicolors,
|
||||
.new-project-column-modal .color.picker.column .minicolors {
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
@ -32,10 +32,7 @@ textarea,
|
|||
.ui.form input[type="text"],
|
||||
.ui.form input[type="time"],
|
||||
.ui.form input[type="url"],
|
||||
.ui.selection.dropdown,
|
||||
.ui.checkbox label::before,
|
||||
.ui.checkbox input:checked ~ label::before,
|
||||
.ui.checkbox input:not([type="radio"]):indeterminate ~ label::before {
|
||||
.ui.selection.dropdown {
|
||||
background: var(--color-input-background);
|
||||
border-color: var(--color-input-border);
|
||||
color: var(--color-input-text);
|
||||
|
@ -63,12 +60,7 @@ textarea:hover,
|
|||
.ui.form input[type="text"]:hover,
|
||||
.ui.form input[type="time"]:hover,
|
||||
.ui.form input[type="url"]:hover,
|
||||
.ui.selection.dropdown:hover,
|
||||
.ui.checkbox label:hover::before,
|
||||
.ui.checkbox label:active::before,
|
||||
.ui.radio.checkbox label::after,
|
||||
.ui.radio.checkbox input:focus ~ label::before,
|
||||
.ui.radio.checkbox input:checked ~ label::before {
|
||||
.ui.selection.dropdown:hover {
|
||||
background: var(--color-input-background);
|
||||
border-color: var(--color-input-border-hover);
|
||||
color: var(--color-input-text);
|
||||
|
@ -91,11 +83,7 @@ textarea:focus,
|
|||
.ui.form input[type="text"]:focus,
|
||||
.ui.form input[type="time"]:focus,
|
||||
.ui.form input[type="url"]:focus,
|
||||
.ui.selection.dropdown:focus,
|
||||
.ui.checkbox input:focus ~ label::before,
|
||||
.ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::before,
|
||||
.ui.checkbox input:checked:focus ~ label::before,
|
||||
.ui.radio.checkbox input:focus:checked ~ label::before {
|
||||
.ui.selection.dropdown:focus {
|
||||
background: var(--color-input-background);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-input-text);
|
||||
|
@ -106,58 +94,21 @@ textarea:focus,
|
|||
.ui.form .inline.fields .field > label,
|
||||
.ui.form .inline.fields .field > p,
|
||||
.ui.form .inline.field > label,
|
||||
.ui.form .inline.field > p,
|
||||
.ui.checkbox label,
|
||||
.ui.checkbox + label,
|
||||
.ui.checkbox label:hover,
|
||||
.ui.checkbox + label:hover,
|
||||
.ui.checkbox input:focus ~ label,
|
||||
.ui.checkbox input:active ~ label {
|
||||
.ui.form .inline.field > p {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.ui.form .required.fields:not(.grouped) > .field > label::after,
|
||||
.ui.form .required.fields.grouped > label::after,
|
||||
.ui.form .required.field > label::after,
|
||||
.ui.form .required.fields:not(.grouped) > .field > .checkbox::after,
|
||||
.ui.form .required.field > .checkbox::after,
|
||||
.ui.form label.required::after {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.ui.input,
|
||||
.ui.checkbox input:focus ~ label::after,
|
||||
.ui.checkbox input:checked ~ label::after,
|
||||
.ui.checkbox label:active::after,
|
||||
.ui.checkbox input:not([type="radio"]):indeterminate ~ label::after,
|
||||
.ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::after,
|
||||
.ui.checkbox input:checked:focus ~ label::after,
|
||||
.ui.disabled.checkbox label,
|
||||
.ui.checkbox input[disabled] ~ label {
|
||||
.ui.input {
|
||||
color: var(--color-input-text);
|
||||
}
|
||||
|
||||
.ui.radio.checkbox input:focus ~ label::after,
|
||||
.ui.radio.checkbox input:checked ~ label::after,
|
||||
.ui.radio.checkbox input:focus:checked ~ label::after {
|
||||
background: var(--color-input-text);
|
||||
}
|
||||
|
||||
.ui.toggle.checkbox label::before {
|
||||
background: var(--color-input-toggle-background);
|
||||
}
|
||||
|
||||
.ui.toggle.checkbox label,
|
||||
.ui.toggle.checkbox input:checked ~ label,
|
||||
.ui.toggle.checkbox input:focus:checked ~ label {
|
||||
color: var(--color-text) !important;
|
||||
}
|
||||
|
||||
.ui.toggle.checkbox input:checked ~ label::before,
|
||||
.ui.toggle.checkbox input:focus:checked ~ label::before {
|
||||
background: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
/* match <select> padding to <input> */
|
||||
.ui.form select {
|
||||
padding: 0.67857143em 1em;
|
||||
|
|
|
@ -63,3 +63,20 @@ only use:
|
|||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-size-1 { tab-size: 1 !important; }
|
||||
.tab-size-2 { tab-size: 2 !important; }
|
||||
.tab-size-3 { tab-size: 3 !important; }
|
||||
.tab-size-4 { tab-size: 4 !important; }
|
||||
.tab-size-5 { tab-size: 5 !important; }
|
||||
.tab-size-6 { tab-size: 6 !important; }
|
||||
.tab-size-7 { tab-size: 7 !important; }
|
||||
.tab-size-8 { tab-size: 8 !important; }
|
||||
.tab-size-9 { tab-size: 9 !important; }
|
||||
.tab-size-10 { tab-size: 10 !important; }
|
||||
.tab-size-11 { tab-size: 11 !important; }
|
||||
.tab-size-12 { tab-size: 12 !important; }
|
||||
.tab-size-13 { tab-size: 13 !important; }
|
||||
.tab-size-14 { tab-size: 14 !important; }
|
||||
.tab-size-15 { tab-size: 15 !important; }
|
||||
.tab-size-16 { tab-size: 16 !important; }
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
@import "./modules/container.css";
|
||||
@import "./modules/divider.css";
|
||||
@import "./modules/header.css";
|
||||
@import "./modules/input.css";
|
||||
@import "./modules/label.css";
|
||||
@import "./modules/list.css";
|
||||
@import "./modules/segment.css";
|
||||
@import "./modules/grid.css";
|
||||
@import "./modules/message.css";
|
||||
@import "./modules/table.css";
|
||||
@import "./modules/card.css";
|
||||
@import "./modules/checkbox.css";
|
||||
@import "./modules/modal.css";
|
||||
|
||||
@import "./modules/select.css";
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
.is-loading {
|
||||
pointer-events: none !important;
|
||||
position: relative !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.is-loading > * {
|
||||
|
@ -35,10 +34,14 @@
|
|||
border-radius: var(--border-radius-circle);
|
||||
}
|
||||
|
||||
.is-loading.small-loading-icon::after {
|
||||
.is-loading.loading-icon-2px::after {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.is-loading.loading-icon-3px::after {
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
/* for single form button, the loading state should be on the button, but not go semi-transparent, just replace the text on the button with the loader. */
|
||||
form.single-button-form.is-loading > * {
|
||||
opacity: 1;
|
||||
|
@ -63,7 +66,7 @@ form.single-button-form.is-loading .button {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
/* TODO: not needed, use "is-loading small-loading-icon" instead */
|
||||
/* TODO: not needed, use "is-loading loading-icon-2px" instead */
|
||||
code.language-math.is-loading::after {
|
||||
padding: 0;
|
||||
border-width: 2px;
|
||||
|
|
120
web_src/css/modules/checkbox.css
Normal file
120
web_src/css/modules/checkbox.css
Normal file
|
@ -0,0 +1,120 @@
|
|||
/* based on Fomantic UI checkbox module, with just the parts extracted that we use. If you find any
|
||||
unused rules here after refactoring, please remove them. */
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
width: var(--checkbox-size);
|
||||
height: var(--checkbox-size);
|
||||
}
|
||||
|
||||
.ui.checkbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
min-height: var(--checkbox-size);
|
||||
line-height: var(--checkbox-size);
|
||||
min-width: var(--checkbox-size);
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.ui.checkbox input[type="checkbox"],
|
||||
.ui.checkbox input[type="radio"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--checkbox-size);
|
||||
height: var(--checkbox-size);
|
||||
}
|
||||
|
||||
.ui.checkbox input[type="checkbox"]:enabled,
|
||||
.ui.checkbox input[type="radio"]:enabled,
|
||||
.ui.checkbox label:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui.checkbox label {
|
||||
cursor: auto;
|
||||
position: relative;
|
||||
display: block;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ui.checkbox label,
|
||||
.ui.radio.checkbox label {
|
||||
margin-left: 1.85714em;
|
||||
}
|
||||
|
||||
.ui.checkbox + label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ui.disabled.checkbox label,
|
||||
.ui.checkbox input[disabled] ~ label {
|
||||
cursor: default !important;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ui.radio.checkbox {
|
||||
min-height: var(--checkbox-size);
|
||||
}
|
||||
|
||||
/* "switch" styled checkbox */
|
||||
|
||||
.ui.toggle.checkbox {
|
||||
min-height: 1.5rem;
|
||||
}
|
||||
.ui.toggle.checkbox input {
|
||||
width: 3.5rem;
|
||||
height: 1.5rem;
|
||||
opacity: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
.ui.toggle.checkbox label {
|
||||
min-height: 1.5rem;
|
||||
padding-left: 4.5rem;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
.ui.toggle.checkbox label::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: "";
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
width: 3.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 500rem;
|
||||
left: 0;
|
||||
}
|
||||
.ui.toggle.checkbox label::after {
|
||||
background: var(--color-white);
|
||||
position: absolute;
|
||||
content: "";
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 500rem;
|
||||
transition: background 0.3s ease, left 0.3s ease;
|
||||
}
|
||||
.ui.toggle.checkbox input ~ label::after {
|
||||
left: -0.05rem;
|
||||
}
|
||||
.ui.toggle.checkbox input:checked ~ label::after {
|
||||
left: 2.15rem;
|
||||
}
|
||||
.ui.toggle.checkbox input:focus ~ label::before,
|
||||
.ui.toggle.checkbox label::before {
|
||||
background: var(--color-input-toggle-background);
|
||||
}
|
||||
.ui.toggle.checkbox label,
|
||||
.ui.toggle.checkbox input:checked ~ label,
|
||||
.ui.toggle.checkbox input:focus:checked ~ label {
|
||||
color: var(--color-text) !important;
|
||||
}
|
||||
.ui.toggle.checkbox input:checked ~ label::before,
|
||||
.ui.toggle.checkbox input:focus:checked ~ label::before {
|
||||
background: var(--color-primary) !important;
|
||||
}
|
|
@ -135,6 +135,12 @@ h4.ui.header .sub.header {
|
|||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
/* open dropdown menus to the left in right-attached headers */
|
||||
.ui.attached.header > .ui.right .ui.dropdown .menu {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
/* if a .top.attached.header is followed by a .segment, add some margin */
|
||||
.ui.segments + .ui.top.attached.header,
|
||||
.ui.attached.segment + .ui.top.attached.header {
|
||||
|
|
197
web_src/css/modules/input.css
Normal file
197
web_src/css/modules/input.css
Normal file
|
@ -0,0 +1,197 @@
|
|||
/* based on Fomantic UI input module, with just the parts extracted that we use. If you find any
|
||||
unused rules here after refactoring, please remove them. */
|
||||
|
||||
.ui.input {
|
||||
position: relative;
|
||||
font-weight: var(--font-weight-normal);
|
||||
display: inline-flex;
|
||||
color: var(--color-input-text);
|
||||
}
|
||||
.ui.input > input {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
flex: 1 0 auto;
|
||||
outline: none;
|
||||
font-family: var(--fonts-regular);
|
||||
padding: 0.67857143em 1em;
|
||||
border: 1px solid var(--color-input-border);
|
||||
color: var(--color-input-text);
|
||||
border-radius: 0.28571429rem;
|
||||
line-height: var(--line-height-default);
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.ui.disabled.input,
|
||||
.ui.input:not(.disabled) input[disabled] {
|
||||
opacity: var(--opacity-disabled);
|
||||
}
|
||||
.ui.disabled.input > input,
|
||||
.ui.input:not(.disabled) input[disabled] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ui.input.focus > input,
|
||||
.ui.input > input:focus {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui.input.error > input {
|
||||
background: var(--color-error-bg);
|
||||
border-color: var(--color-error-border);
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
|
||||
.ui.icon.input > i.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 2.67142857em;
|
||||
opacity: 0.5;
|
||||
border-radius: 0 0.28571429rem 0.28571429rem 0;
|
||||
pointer-events: none;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.ui.icon.input > i.icon.is-loading {
|
||||
position: absolute !important;
|
||||
height: 28px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.ui.icon.input > i.icon.is-loading > * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ui.ui.ui.ui.icon.input > textarea,
|
||||
.ui.ui.ui.ui.icon.input > input {
|
||||
padding-right: 2.67142857em;
|
||||
}
|
||||
.ui.icon.input > i.link.icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui.icon.input > i.circular.icon {
|
||||
top: 0.35em;
|
||||
right: 0.5em;
|
||||
}
|
||||
|
||||
.ui[class*="left icon"].input > i.icon {
|
||||
right: auto;
|
||||
left: 1px;
|
||||
border-radius: 0.28571429rem 0 0 0.28571429rem;
|
||||
}
|
||||
.ui[class*="left icon"].input > i.circular.icon {
|
||||
right: auto;
|
||||
left: 0.5em;
|
||||
}
|
||||
.ui.ui.ui.ui[class*="left icon"].input > textarea,
|
||||
.ui.ui.ui.ui[class*="left icon"].input > input {
|
||||
padding-left: 2.67142857em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.ui.icon.input > textarea:focus ~ .icon,
|
||||
.ui.icon.input > input:focus ~ .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ui.icon.input > textarea ~ i.icon {
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.ui.form .field.error > .ui.action.input > .ui.button,
|
||||
.ui.action.input.error > .ui.button {
|
||||
border-top: 1px solid var(--color-error-border);
|
||||
border-bottom: 1px solid var(--color-error-border);
|
||||
}
|
||||
|
||||
.ui.action.input > .button,
|
||||
.ui.action.input > .buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.ui.action.input > .button,
|
||||
.ui.action.input > .buttons > .button {
|
||||
padding-top: 0.78571429em;
|
||||
padding-bottom: 0.78571429em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ui.action.input:not([class*="left action"]) > input {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.ui.action.input > .dropdown:first-child,
|
||||
.ui.action.input > .button:first-child,
|
||||
.ui.action.input > .buttons:first-child > .button {
|
||||
border-radius: 0.28571429rem 0 0 0.28571429rem;
|
||||
}
|
||||
.ui.action.input > .dropdown:not(:first-child),
|
||||
.ui.action.input > .button:not(:first-child),
|
||||
.ui.action.input > .buttons:not(:first-child) > .button {
|
||||
border-radius: 0;
|
||||
}
|
||||
.ui.action.input > .dropdown:last-child,
|
||||
.ui.action.input > .button:last-child,
|
||||
.ui.action.input > .buttons:last-child > .button {
|
||||
border-radius: 0 0.28571429rem 0.28571429rem 0;
|
||||
}
|
||||
|
||||
.ui.fluid.input {
|
||||
display: flex;
|
||||
}
|
||||
.ui.fluid.input > input {
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
.ui.tiny.input {
|
||||
font-size: 0.85714286em;
|
||||
}
|
||||
.ui.small.input {
|
||||
font-size: 0.92857143em;
|
||||
}
|
||||
|
||||
.ui.action.input .ui.ui.button {
|
||||
border-color: var(--color-input-border);
|
||||
padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* currently used for search bar dropdowns in repo search and explore code */
|
||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection {
|
||||
min-width: 10em;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) {
|
||||
border-right: none;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover {
|
||||
border-color: var(--color-input-border);
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > input,
|
||||
.ui.action.input:not([class*="left action"]) > input:hover {
|
||||
border-right: none;
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
|
||||
border-left-color: var(--color-primary);
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > input:focus {
|
||||
border-right-color: var(--color-primary);
|
||||
}
|
187
web_src/css/modules/list.css
Normal file
187
web_src/css/modules/list.css
Normal file
|
@ -0,0 +1,187 @@
|
|||
/* based on Fomantic UI list module, with just the parts extracted that we use. If you find any
|
||||
unused rules here after refactoring, please remove them. */
|
||||
|
||||
.ui.list {
|
||||
list-style-type: none;
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.ui.list:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ui.list:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.ui.list > .item,
|
||||
.ui.list .list > .item {
|
||||
display: list-item;
|
||||
table-layout: fixed;
|
||||
list-style-type: none;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
.ui.list > .list > .item::after,
|
||||
.ui.list > .item::after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ui.list .list:not(.icon) {
|
||||
clear: both;
|
||||
margin: 0;
|
||||
padding: 0.75em 0 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.ui.list .list > .item {
|
||||
padding: 0.14285714em 0;
|
||||
}
|
||||
|
||||
.ui.list .list > .item > i.icon,
|
||||
.ui.list > .item > i.icon {
|
||||
display: table-cell;
|
||||
min-width: 1.55em;
|
||||
padding-top: 0;
|
||||
transition: color 0.1s ease;
|
||||
padding-right: 0.28571429em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.ui.list .list > .item > i.icon:only-child,
|
||||
.ui.list > .item > i.icon:only-child {
|
||||
display: inline-block;
|
||||
min-width: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.ui.list .list > .item > .image,
|
||||
.ui.list > .item > .image {
|
||||
display: table-cell;
|
||||
background-color: transparent;
|
||||
vertical-align: top;
|
||||
}
|
||||
.ui.list .list > .item > .image:not(:only-child):not(img),
|
||||
.ui.list > .item > .image:not(:only-child):not(img) {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
.ui.list .list > .item > .image img,
|
||||
.ui.list > .item > .image img {
|
||||
vertical-align: top;
|
||||
}
|
||||
.ui.list .list > .item > img.image,
|
||||
.ui.list .list > .item > .image:only-child,
|
||||
.ui.list > .item > img.image,
|
||||
.ui.list > .item > .image:only-child {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ui.list .list > .item > .content,
|
||||
.ui.list > .item > .content {
|
||||
color: var(--color-text);
|
||||
}
|
||||
.ui.list .list > .item > .image + .content,
|
||||
.ui.list .list > .item > i.icon + .content,
|
||||
.ui.list > .item > .image + .content,
|
||||
.ui.list > .item > i.icon + .content {
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
padding: 0 0 0 0.5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.ui.list .list > .item > img.image + .content,
|
||||
.ui.list > .item > img.image + .content {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
.ui.list .list > .item > .content > .list,
|
||||
.ui.list > .item > .content > .list {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.ui.list .list > .item .header,
|
||||
.ui.list > .item .header {
|
||||
display: block;
|
||||
margin: 0;
|
||||
font-family: var(--fonts-regular);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
.ui.list .list > .item .description,
|
||||
.ui.list > .item .description {
|
||||
display: block;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.ui.list > .item a,
|
||||
.ui.list .list > .item a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui.menu .ui.list > .item,
|
||||
.ui.menu .ui.list .list > .item {
|
||||
display: list-item;
|
||||
table-layout: fixed;
|
||||
background-color: transparent;
|
||||
list-style-type: none;
|
||||
list-style-position: outside;
|
||||
padding: 0.21428571em 0;
|
||||
}
|
||||
.ui.menu .ui.list .list > .item::before,
|
||||
.ui.menu .ui.list > .item::before {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
.ui.menu .ui.list .list > .item:first-child,
|
||||
.ui.menu .ui.list > .item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
.ui.menu .ui.list .list > .item:last-child,
|
||||
.ui.menu .ui.list > .item:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.ui.list .list > .disabled.item,
|
||||
.ui.list > .disabled.item {
|
||||
pointer-events: none;
|
||||
opacity: var(--opacity-disabled);
|
||||
}
|
||||
|
||||
.ui.list .list > a.item:hover > .icons,
|
||||
.ui.list > a.item:hover > .icons,
|
||||
.ui.list .list > a.item:hover > i.icon,
|
||||
.ui.list > a.item:hover > i.icon {
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
.ui.divided.list > .item {
|
||||
border-top: 1px solid var(--color-secondary);
|
||||
}
|
||||
.ui.divided.list .list > .item {
|
||||
border-top: none;
|
||||
}
|
||||
.ui.divided.list .item .list > .item {
|
||||
border-top: none;
|
||||
}
|
||||
.ui.divided.list .list > .item:first-child,
|
||||
.ui.divided.list > .item:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
.ui.divided.list .list > .item:first-child {
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.ui.relaxed.list > .item:not(:first-child) {
|
||||
padding-top: 0.42857143em;
|
||||
}
|
||||
.ui.relaxed.list > .item:not(:last-child) {
|
||||
padding-bottom: 0.42857143em;
|
||||
}
|
|
@ -140,3 +140,8 @@
|
|||
.secondary-nav {
|
||||
background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */
|
||||
}
|
||||
|
||||
.issue-navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,17 @@
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
/* bare theme, no styling at all, except box-shadow */
|
||||
.tippy-box[data-theme="bare"] {
|
||||
border: none;
|
||||
box-shadow: 0 6px 18px var(--color-shadow);
|
||||
}
|
||||
|
||||
.tippy-box[data-theme="bare"] .tippy-content {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* tooltip theme for text tooltips */
|
||||
|
||||
.tippy-box[data-theme="tooltip"] {
|
||||
|
|
|
@ -89,10 +89,6 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.organization.options input {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.page-content.organization .org-avatar {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
|
|
@ -2299,104 +2299,6 @@
|
|||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.edit-label.modal .form .color.picker.column,
|
||||
.new-label.modal .form .color.picker.column {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.edit-label.modal .form .color.picker.column .minicolors,
|
||||
.new-label.modal .form .color.picker.column .minicolors {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.edit-label.modal .form .minicolors-swatch.minicolors-sprite,
|
||||
.new-label.modal .form .minicolors-swatch.minicolors-sprite {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.tab-size-1 {
|
||||
tab-size: 1 !important;
|
||||
-moz-tab-size: 1 !important;
|
||||
}
|
||||
|
||||
.tab-size-2 {
|
||||
tab-size: 2 !important;
|
||||
-moz-tab-size: 2 !important;
|
||||
}
|
||||
|
||||
.tab-size-3 {
|
||||
tab-size: 3 !important;
|
||||
-moz-tab-size: 3 !important;
|
||||
}
|
||||
|
||||
.tab-size-4 {
|
||||
tab-size: 4 !important;
|
||||
-moz-tab-size: 4 !important;
|
||||
}
|
||||
|
||||
.tab-size-5 {
|
||||
tab-size: 5 !important;
|
||||
-moz-tab-size: 5 !important;
|
||||
}
|
||||
|
||||
.tab-size-6 {
|
||||
tab-size: 6 !important;
|
||||
-moz-tab-size: 6 !important;
|
||||
}
|
||||
|
||||
.tab-size-7 {
|
||||
tab-size: 7 !important;
|
||||
-moz-tab-size: 7 !important;
|
||||
}
|
||||
|
||||
.tab-size-8 {
|
||||
tab-size: 8 !important;
|
||||
-moz-tab-size: 8 !important;
|
||||
}
|
||||
|
||||
.tab-size-9 {
|
||||
tab-size: 9 !important;
|
||||
-moz-tab-size: 9 !important;
|
||||
}
|
||||
|
||||
.tab-size-10 {
|
||||
tab-size: 10 !important;
|
||||
-moz-tab-size: 10 !important;
|
||||
}
|
||||
|
||||
.tab-size-11 {
|
||||
tab-size: 11 !important;
|
||||
-moz-tab-size: 11 !important;
|
||||
}
|
||||
|
||||
.tab-size-12 {
|
||||
tab-size: 12 !important;
|
||||
-moz-tab-size: 12 !important;
|
||||
}
|
||||
|
||||
.tab-size-13 {
|
||||
tab-size: 13 !important;
|
||||
-moz-tab-size: 13 !important;
|
||||
}
|
||||
|
||||
.tab-size-14 {
|
||||
tab-size: 14 !important;
|
||||
-moz-tab-size: 14 !important;
|
||||
}
|
||||
|
||||
.tab-size-15 {
|
||||
tab-size: 15 !important;
|
||||
-moz-tab-size: 15 !important;
|
||||
}
|
||||
|
||||
.tab-size-16 {
|
||||
tab-size: 16 !important;
|
||||
-moz-tab-size: 16 !important;
|
||||
}
|
||||
|
||||
.stats-table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
@ -2573,6 +2475,7 @@ tbody.commit-list {
|
|||
#repo-topics .repo-topic {
|
||||
font-weight: var(--font-weight-normal);
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#new-dependency-drop-list.ui.selection.dropdown {
|
||||
|
@ -2990,6 +2893,7 @@ tbody.commit-list {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
.issue-list-toolbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.issue-list-toolbar-right .filter.menu {
|
||||
|
|
2430
web_src/fomantic/build/semantic.css
generated
2430
web_src/fomantic/build/semantic.css
generated
File diff suppressed because it is too large
Load diff
877
web_src/fomantic/build/semantic.js
generated
877
web_src/fomantic/build/semantic.js
generated
|
@ -1184,883 +1184,6 @@ $.api.settings = {
|
|||
|
||||
|
||||
|
||||
})( jQuery, window, document );
|
||||
|
||||
/*!
|
||||
* # Fomantic-UI - Checkbox
|
||||
* http://github.com/fomantic/Fomantic-UI/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
;(function ($, window, document, undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
$.isFunction = $.isFunction || function(obj) {
|
||||
return typeof obj === "function" && typeof obj.nodeType !== "number";
|
||||
};
|
||||
|
||||
window = (typeof window != 'undefined' && window.Math == Math)
|
||||
? window
|
||||
: (typeof self != 'undefined' && self.Math == Math)
|
||||
? self
|
||||
: Function('return this')()
|
||||
;
|
||||
|
||||
$.fn.checkbox = function(parameters) {
|
||||
var
|
||||
$allModules = $(this),
|
||||
moduleSelector = $allModules.selector || '',
|
||||
|
||||
time = new Date().getTime(),
|
||||
performance = [],
|
||||
|
||||
query = arguments[0],
|
||||
methodInvoked = (typeof query == 'string'),
|
||||
queryArguments = [].slice.call(arguments, 1),
|
||||
returnedValue
|
||||
;
|
||||
|
||||
$allModules
|
||||
.each(function() {
|
||||
var
|
||||
settings = $.extend(true, {}, $.fn.checkbox.settings, parameters),
|
||||
|
||||
className = settings.className,
|
||||
namespace = settings.namespace,
|
||||
selector = settings.selector,
|
||||
error = settings.error,
|
||||
|
||||
eventNamespace = '.' + namespace,
|
||||
moduleNamespace = 'module-' + namespace,
|
||||
|
||||
$module = $(this),
|
||||
$label = $(this).children(selector.label),
|
||||
$input = $(this).children(selector.input),
|
||||
input = $input[0],
|
||||
|
||||
initialLoad = false,
|
||||
shortcutPressed = false,
|
||||
instance = $module.data(moduleNamespace),
|
||||
|
||||
observer,
|
||||
element = this,
|
||||
module
|
||||
;
|
||||
|
||||
module = {
|
||||
|
||||
initialize: function() {
|
||||
module.verbose('Initializing checkbox', settings);
|
||||
|
||||
module.create.label();
|
||||
module.bind.events();
|
||||
|
||||
module.set.tabbable();
|
||||
module.hide.input();
|
||||
|
||||
module.observeChanges();
|
||||
module.instantiate();
|
||||
module.setup();
|
||||
},
|
||||
|
||||
instantiate: function() {
|
||||
module.verbose('Storing instance of module', module);
|
||||
instance = module;
|
||||
$module
|
||||
.data(moduleNamespace, module)
|
||||
;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
module.verbose('Destroying module');
|
||||
module.unbind.events();
|
||||
module.show.input();
|
||||
$module.removeData(moduleNamespace);
|
||||
},
|
||||
|
||||
fix: {
|
||||
reference: function() {
|
||||
if( $module.is(selector.input) ) {
|
||||
module.debug('Behavior called on <input> adjusting invoked element');
|
||||
$module = $module.closest(selector.checkbox);
|
||||
module.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setup: function() {
|
||||
module.set.initialLoad();
|
||||
if( module.is.indeterminate() ) {
|
||||
module.debug('Initial value is indeterminate');
|
||||
module.indeterminate();
|
||||
}
|
||||
else if( module.is.checked() ) {
|
||||
module.debug('Initial value is checked');
|
||||
module.check();
|
||||
}
|
||||
else {
|
||||
module.debug('Initial value is unchecked');
|
||||
module.uncheck();
|
||||
}
|
||||
module.remove.initialLoad();
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
$label = $module.children(selector.label);
|
||||
$input = $module.children(selector.input);
|
||||
input = $input[0];
|
||||
},
|
||||
|
||||
hide: {
|
||||
input: function() {
|
||||
module.verbose('Modifying <input> z-index to be unselectable');
|
||||
$input.addClass(className.hidden);
|
||||
}
|
||||
},
|
||||
show: {
|
||||
input: function() {
|
||||
module.verbose('Modifying <input> z-index to be selectable');
|
||||
$input.removeClass(className.hidden);
|
||||
}
|
||||
},
|
||||
|
||||
observeChanges: function() {
|
||||
if('MutationObserver' in window) {
|
||||
observer = new MutationObserver(function(mutations) {
|
||||
module.debug('DOM tree modified, updating selector cache');
|
||||
module.refresh();
|
||||
});
|
||||
observer.observe(element, {
|
||||
childList : true,
|
||||
subtree : true
|
||||
});
|
||||
module.debug('Setting up mutation observer', observer);
|
||||
}
|
||||
},
|
||||
|
||||
attachEvents: function(selector, event) {
|
||||
var
|
||||
$element = $(selector)
|
||||
;
|
||||
event = $.isFunction(module[event])
|
||||
? module[event]
|
||||
: module.toggle
|
||||
;
|
||||
if($element.length > 0) {
|
||||
module.debug('Attaching checkbox events to element', selector, event);
|
||||
$element
|
||||
.on('click' + eventNamespace, event)
|
||||
;
|
||||
}
|
||||
else {
|
||||
module.error(error.notFound);
|
||||
}
|
||||
},
|
||||
|
||||
preventDefaultOnInputTarget: function() {
|
||||
if(typeof event !== 'undefined' && event !== null && $(event.target).is(selector.input)) {
|
||||
module.verbose('Preventing default check action after manual check action');
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
event: {
|
||||
change: function(event) {
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onChange.call(input);
|
||||
}
|
||||
},
|
||||
click: function(event) {
|
||||
var
|
||||
$target = $(event.target)
|
||||
;
|
||||
if( $target.is(selector.input) ) {
|
||||
module.verbose('Using default check action on initialized checkbox');
|
||||
return;
|
||||
}
|
||||
if( $target.is(selector.link) ) {
|
||||
module.debug('Clicking link inside checkbox, skipping toggle');
|
||||
return;
|
||||
}
|
||||
module.toggle();
|
||||
$input.focus();
|
||||
event.preventDefault();
|
||||
},
|
||||
keydown: function(event) {
|
||||
var
|
||||
key = event.which,
|
||||
keyCode = {
|
||||
enter : 13,
|
||||
space : 32,
|
||||
escape : 27,
|
||||
left : 37,
|
||||
up : 38,
|
||||
right : 39,
|
||||
down : 40
|
||||
}
|
||||
;
|
||||
|
||||
var r = module.get.radios(),
|
||||
rIndex = r.index($module),
|
||||
rLen = r.length,
|
||||
checkIndex = false;
|
||||
|
||||
if(key == keyCode.left || key == keyCode.up) {
|
||||
checkIndex = (rIndex === 0 ? rLen : rIndex) - 1;
|
||||
} else if(key == keyCode.right || key == keyCode.down) {
|
||||
checkIndex = rIndex === rLen-1 ? 0 : rIndex+1;
|
||||
}
|
||||
|
||||
if (!module.should.ignoreCallbacks() && checkIndex !== false) {
|
||||
if(settings.beforeUnchecked.apply(input)===false) {
|
||||
module.verbose('Option not allowed to be unchecked, cancelling key navigation');
|
||||
return false;
|
||||
}
|
||||
if (settings.beforeChecked.apply($(r[checkIndex]).children(selector.input)[0])===false) {
|
||||
module.verbose('Next option should not allow check, cancelling key navigation');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(key == keyCode.escape) {
|
||||
module.verbose('Escape key pressed blurring field');
|
||||
$input.blur();
|
||||
shortcutPressed = true;
|
||||
}
|
||||
else if(!event.ctrlKey && ( key == keyCode.space || (key == keyCode.enter && settings.enableEnterKey)) ) {
|
||||
module.verbose('Enter/space key pressed, toggling checkbox');
|
||||
module.toggle();
|
||||
shortcutPressed = true;
|
||||
}
|
||||
else {
|
||||
shortcutPressed = false;
|
||||
}
|
||||
},
|
||||
keyup: function(event) {
|
||||
if(shortcutPressed) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
check: function() {
|
||||
if( !module.should.allowCheck() ) {
|
||||
return;
|
||||
}
|
||||
module.debug('Checking checkbox', $input);
|
||||
module.set.checked();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onChecked.call(input);
|
||||
module.trigger.change();
|
||||
}
|
||||
module.preventDefaultOnInputTarget();
|
||||
},
|
||||
|
||||
uncheck: function() {
|
||||
if( !module.should.allowUncheck() ) {
|
||||
return;
|
||||
}
|
||||
module.debug('Unchecking checkbox');
|
||||
module.set.unchecked();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onUnchecked.call(input);
|
||||
module.trigger.change();
|
||||
}
|
||||
module.preventDefaultOnInputTarget();
|
||||
},
|
||||
|
||||
indeterminate: function() {
|
||||
if( module.should.allowIndeterminate() ) {
|
||||
module.debug('Checkbox is already indeterminate');
|
||||
return;
|
||||
}
|
||||
module.debug('Making checkbox indeterminate');
|
||||
module.set.indeterminate();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onIndeterminate.call(input);
|
||||
module.trigger.change();
|
||||
}
|
||||
},
|
||||
|
||||
determinate: function() {
|
||||
if( module.should.allowDeterminate() ) {
|
||||
module.debug('Checkbox is already determinate');
|
||||
return;
|
||||
}
|
||||
module.debug('Making checkbox determinate');
|
||||
module.set.determinate();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onDeterminate.call(input);
|
||||
module.trigger.change();
|
||||
}
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
if( module.is.enabled() ) {
|
||||
module.debug('Checkbox is already enabled');
|
||||
return;
|
||||
}
|
||||
module.debug('Enabling checkbox');
|
||||
module.set.enabled();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onEnable.call(input);
|
||||
// preserve legacy callbacks
|
||||
settings.onEnabled.call(input);
|
||||
module.trigger.change();
|
||||
}
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
if( module.is.disabled() ) {
|
||||
module.debug('Checkbox is already disabled');
|
||||
return;
|
||||
}
|
||||
module.debug('Disabling checkbox');
|
||||
module.set.disabled();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onDisable.call(input);
|
||||
// preserve legacy callbacks
|
||||
settings.onDisabled.call(input);
|
||||
module.trigger.change();
|
||||
}
|
||||
},
|
||||
|
||||
get: {
|
||||
radios: function() {
|
||||
var
|
||||
name = module.get.name()
|
||||
;
|
||||
return $('input[name="' + name + '"]').closest(selector.checkbox);
|
||||
},
|
||||
otherRadios: function() {
|
||||
return module.get.radios().not($module);
|
||||
},
|
||||
name: function() {
|
||||
return $input.attr('name');
|
||||
}
|
||||
},
|
||||
|
||||
is: {
|
||||
initialLoad: function() {
|
||||
return initialLoad;
|
||||
},
|
||||
radio: function() {
|
||||
return ($input.hasClass(className.radio) || $input.attr('type') == 'radio');
|
||||
},
|
||||
indeterminate: function() {
|
||||
return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate');
|
||||
},
|
||||
checked: function() {
|
||||
return $input.prop('checked') !== undefined && $input.prop('checked');
|
||||
},
|
||||
disabled: function() {
|
||||
return $input.prop('disabled') !== undefined && $input.prop('disabled');
|
||||
},
|
||||
enabled: function() {
|
||||
return !module.is.disabled();
|
||||
},
|
||||
determinate: function() {
|
||||
return !module.is.indeterminate();
|
||||
},
|
||||
unchecked: function() {
|
||||
return !module.is.checked();
|
||||
}
|
||||
},
|
||||
|
||||
should: {
|
||||
allowCheck: function() {
|
||||
if(module.is.determinate() && module.is.checked() && !module.is.initialLoad() ) {
|
||||
module.debug('Should not allow check, checkbox is already checked');
|
||||
return false;
|
||||
}
|
||||
if(!module.should.ignoreCallbacks() && settings.beforeChecked.apply(input) === false) {
|
||||
module.debug('Should not allow check, beforeChecked cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
allowUncheck: function() {
|
||||
if(module.is.determinate() && module.is.unchecked() && !module.is.initialLoad() ) {
|
||||
module.debug('Should not allow uncheck, checkbox is already unchecked');
|
||||
return false;
|
||||
}
|
||||
if(!module.should.ignoreCallbacks() && settings.beforeUnchecked.apply(input) === false) {
|
||||
module.debug('Should not allow uncheck, beforeUnchecked cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
allowIndeterminate: function() {
|
||||
if(module.is.indeterminate() && !module.is.initialLoad() ) {
|
||||
module.debug('Should not allow indeterminate, checkbox is already indeterminate');
|
||||
return false;
|
||||
}
|
||||
if(!module.should.ignoreCallbacks() && settings.beforeIndeterminate.apply(input) === false) {
|
||||
module.debug('Should not allow indeterminate, beforeIndeterminate cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
allowDeterminate: function() {
|
||||
if(module.is.determinate() && !module.is.initialLoad() ) {
|
||||
module.debug('Should not allow determinate, checkbox is already determinate');
|
||||
return false;
|
||||
}
|
||||
if(!module.should.ignoreCallbacks() && settings.beforeDeterminate.apply(input) === false) {
|
||||
module.debug('Should not allow determinate, beforeDeterminate cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
ignoreCallbacks: function() {
|
||||
return (initialLoad && !settings.fireOnInit);
|
||||
}
|
||||
},
|
||||
|
||||
can: {
|
||||
change: function() {
|
||||
return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') );
|
||||
},
|
||||
uncheck: function() {
|
||||
return (typeof settings.uncheckable === 'boolean')
|
||||
? settings.uncheckable
|
||||
: !module.is.radio()
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
set: {
|
||||
initialLoad: function() {
|
||||
initialLoad = true;
|
||||
},
|
||||
checked: function() {
|
||||
module.verbose('Setting class to checked');
|
||||
$module
|
||||
.removeClass(className.indeterminate)
|
||||
.addClass(className.checked)
|
||||
;
|
||||
if( module.is.radio() ) {
|
||||
module.uncheckOthers();
|
||||
}
|
||||
if(!module.is.indeterminate() && module.is.checked()) {
|
||||
module.debug('Input is already checked, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.verbose('Setting state to checked', input);
|
||||
$input
|
||||
.prop('indeterminate', false)
|
||||
.prop('checked', true)
|
||||
;
|
||||
},
|
||||
unchecked: function() {
|
||||
module.verbose('Removing checked class');
|
||||
$module
|
||||
.removeClass(className.indeterminate)
|
||||
.removeClass(className.checked)
|
||||
;
|
||||
if(!module.is.indeterminate() && module.is.unchecked() ) {
|
||||
module.debug('Input is already unchecked');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to unchecked');
|
||||
$input
|
||||
.prop('indeterminate', false)
|
||||
.prop('checked', false)
|
||||
;
|
||||
},
|
||||
indeterminate: function() {
|
||||
module.verbose('Setting class to indeterminate');
|
||||
$module
|
||||
.addClass(className.indeterminate)
|
||||
;
|
||||
if( module.is.indeterminate() ) {
|
||||
module.debug('Input is already indeterminate, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to indeterminate');
|
||||
$input
|
||||
.prop('indeterminate', true)
|
||||
;
|
||||
},
|
||||
determinate: function() {
|
||||
module.verbose('Removing indeterminate class');
|
||||
$module
|
||||
.removeClass(className.indeterminate)
|
||||
;
|
||||
if( module.is.determinate() ) {
|
||||
module.debug('Input is already determinate, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to determinate');
|
||||
$input
|
||||
.prop('indeterminate', false)
|
||||
;
|
||||
},
|
||||
disabled: function() {
|
||||
module.verbose('Setting class to disabled');
|
||||
$module
|
||||
.addClass(className.disabled)
|
||||
;
|
||||
if( module.is.disabled() ) {
|
||||
module.debug('Input is already disabled, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to disabled');
|
||||
$input
|
||||
.prop('disabled', 'disabled')
|
||||
;
|
||||
},
|
||||
enabled: function() {
|
||||
module.verbose('Removing disabled class');
|
||||
$module.removeClass(className.disabled);
|
||||
if( module.is.enabled() ) {
|
||||
module.debug('Input is already enabled, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to enabled');
|
||||
$input
|
||||
.prop('disabled', false)
|
||||
;
|
||||
},
|
||||
tabbable: function() {
|
||||
module.verbose('Adding tabindex to checkbox');
|
||||
if( $input.attr('tabindex') === undefined) {
|
||||
$input.attr('tabindex', 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
remove: {
|
||||
initialLoad: function() {
|
||||
initialLoad = false;
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
change: function() {
|
||||
var
|
||||
inputElement = $input[0]
|
||||
;
|
||||
if(inputElement) {
|
||||
var events = document.createEvent('HTMLEvents');
|
||||
module.verbose('Triggering native change event');
|
||||
events.initEvent('change', true, false);
|
||||
inputElement.dispatchEvent(events);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
create: {
|
||||
label: function() {
|
||||
if($input.prevAll(selector.label).length > 0) {
|
||||
$input.prev(selector.label).detach().insertAfter($input);
|
||||
module.debug('Moving existing label', $label);
|
||||
}
|
||||
else if( !module.has.label() ) {
|
||||
$label = $('<label>').insertAfter($input);
|
||||
module.debug('Creating label', $label);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
has: {
|
||||
label: function() {
|
||||
return ($label.length > 0);
|
||||
}
|
||||
},
|
||||
|
||||
bind: {
|
||||
events: function() {
|
||||
module.verbose('Attaching checkbox events');
|
||||
$module
|
||||
.on('click' + eventNamespace, module.event.click)
|
||||
.on('change' + eventNamespace, module.event.change)
|
||||
.on('keydown' + eventNamespace, selector.input, module.event.keydown)
|
||||
.on('keyup' + eventNamespace, selector.input, module.event.keyup)
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
unbind: {
|
||||
events: function() {
|
||||
module.debug('Removing events');
|
||||
$module
|
||||
.off(eventNamespace)
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
uncheckOthers: function() {
|
||||
var
|
||||
$radios = module.get.otherRadios()
|
||||
;
|
||||
module.debug('Unchecking other radios', $radios);
|
||||
$radios.removeClass(className.checked);
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
if( !module.can.change() ) {
|
||||
if(!module.is.radio()) {
|
||||
module.debug('Checkbox is read-only or disabled, ignoring toggle');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if( module.is.indeterminate() || module.is.unchecked() ) {
|
||||
module.debug('Currently unchecked');
|
||||
module.check();
|
||||
}
|
||||
else if( module.is.checked() && module.can.uncheck() ) {
|
||||
module.debug('Currently checked');
|
||||
module.uncheck();
|
||||
}
|
||||
},
|
||||
setting: function(name, value) {
|
||||
module.debug('Changing setting', name, value);
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, settings, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
if($.isPlainObject(settings[name])) {
|
||||
$.extend(true, settings[name], value);
|
||||
}
|
||||
else {
|
||||
settings[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return settings[name];
|
||||
}
|
||||
},
|
||||
internal: function(name, value) {
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, module, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
module[name] = value;
|
||||
}
|
||||
else {
|
||||
return module[name];
|
||||
}
|
||||
},
|
||||
debug: function() {
|
||||
if(!settings.silent && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.debug.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
verbose: function() {
|
||||
if(!settings.silent && settings.verbose && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.verbose.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
if(!settings.silent) {
|
||||
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
||||
module.error.apply(console, arguments);
|
||||
}
|
||||
},
|
||||
performance: {
|
||||
log: function(message) {
|
||||
var
|
||||
currentTime,
|
||||
executionTime,
|
||||
previousTime
|
||||
;
|
||||
if(settings.performance) {
|
||||
currentTime = new Date().getTime();
|
||||
previousTime = time || currentTime;
|
||||
executionTime = currentTime - previousTime;
|
||||
time = currentTime;
|
||||
performance.push({
|
||||
'Name' : message[0],
|
||||
'Arguments' : [].slice.call(message, 1) || '',
|
||||
'Element' : element,
|
||||
'Execution Time' : executionTime
|
||||
});
|
||||
}
|
||||
clearTimeout(module.performance.timer);
|
||||
module.performance.timer = setTimeout(module.performance.display, 500);
|
||||
},
|
||||
display: function() {
|
||||
var
|
||||
title = settings.name + ':',
|
||||
totalTime = 0
|
||||
;
|
||||
time = false;
|
||||
clearTimeout(module.performance.timer);
|
||||
$.each(performance, function(index, data) {
|
||||
totalTime += data['Execution Time'];
|
||||
});
|
||||
title += ' ' + totalTime + 'ms';
|
||||
if(moduleSelector) {
|
||||
title += ' \'' + moduleSelector + '\'';
|
||||
}
|
||||
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
||||
console.groupCollapsed(title);
|
||||
if(console.table) {
|
||||
console.table(performance);
|
||||
}
|
||||
else {
|
||||
$.each(performance, function(index, data) {
|
||||
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
||||
});
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
performance = [];
|
||||
}
|
||||
},
|
||||
invoke: function(query, passedArguments, context) {
|
||||
var
|
||||
object = instance,
|
||||
maxDepth,
|
||||
found,
|
||||
response
|
||||
;
|
||||
passedArguments = passedArguments || queryArguments;
|
||||
context = element || context;
|
||||
if(typeof query == 'string' && object !== undefined) {
|
||||
query = query.split(/[\. ]/);
|
||||
maxDepth = query.length - 1;
|
||||
$.each(query, function(depth, value) {
|
||||
var camelCaseValue = (depth != maxDepth)
|
||||
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
||||
: query
|
||||
;
|
||||
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
||||
object = object[camelCaseValue];
|
||||
}
|
||||
else if( object[camelCaseValue] !== undefined ) {
|
||||
found = object[camelCaseValue];
|
||||
return false;
|
||||
}
|
||||
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
||||
object = object[value];
|
||||
}
|
||||
else if( object[value] !== undefined ) {
|
||||
found = object[value];
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
module.error(error.method, query);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if ( $.isFunction( found ) ) {
|
||||
response = found.apply(context, passedArguments);
|
||||
}
|
||||
else if(found !== undefined) {
|
||||
response = found;
|
||||
}
|
||||
if(Array.isArray(returnedValue)) {
|
||||
returnedValue.push(response);
|
||||
}
|
||||
else if(returnedValue !== undefined) {
|
||||
returnedValue = [returnedValue, response];
|
||||
}
|
||||
else if(response !== undefined) {
|
||||
returnedValue = response;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
};
|
||||
|
||||
if(methodInvoked) {
|
||||
if(instance === undefined) {
|
||||
module.initialize();
|
||||
}
|
||||
module.invoke(query);
|
||||
}
|
||||
else {
|
||||
if(instance !== undefined) {
|
||||
instance.invoke('destroy');
|
||||
}
|
||||
module.initialize();
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
return (returnedValue !== undefined)
|
||||
? returnedValue
|
||||
: this
|
||||
;
|
||||
};
|
||||
|
||||
$.fn.checkbox.settings = {
|
||||
|
||||
name : 'Checkbox',
|
||||
namespace : 'checkbox',
|
||||
|
||||
silent : false,
|
||||
debug : false,
|
||||
verbose : true,
|
||||
performance : true,
|
||||
|
||||
// delegated event context
|
||||
uncheckable : 'auto',
|
||||
fireOnInit : false,
|
||||
enableEnterKey : true,
|
||||
|
||||
onChange : function(){},
|
||||
|
||||
beforeChecked : function(){},
|
||||
beforeUnchecked : function(){},
|
||||
beforeDeterminate : function(){},
|
||||
beforeIndeterminate : function(){},
|
||||
|
||||
onChecked : function(){},
|
||||
onUnchecked : function(){},
|
||||
|
||||
onDeterminate : function() {},
|
||||
onIndeterminate : function() {},
|
||||
|
||||
onEnable : function(){},
|
||||
onDisable : function(){},
|
||||
|
||||
// preserve misspelled callbacks (will be removed in 3.0)
|
||||
onEnabled : function(){},
|
||||
onDisabled : function(){},
|
||||
|
||||
className : {
|
||||
checked : 'checked',
|
||||
indeterminate : 'indeterminate',
|
||||
disabled : 'disabled',
|
||||
hidden : 'hidden',
|
||||
radio : 'radio',
|
||||
readOnly : 'read-only'
|
||||
},
|
||||
|
||||
error : {
|
||||
method : 'The method you called is not defined'
|
||||
},
|
||||
|
||||
selector : {
|
||||
checkbox : '.ui.checkbox',
|
||||
label : 'label, .box',
|
||||
input : 'input[type="checkbox"], input[type="radio"]',
|
||||
link : 'a[href]'
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})( jQuery, window, document );
|
||||
|
||||
/*!
|
||||
|
|
|
@ -23,12 +23,9 @@
|
|||
"components": [
|
||||
"api",
|
||||
"button",
|
||||
"checkbox",
|
||||
"dimmer",
|
||||
"dropdown",
|
||||
"form",
|
||||
"input",
|
||||
"list",
|
||||
"menu",
|
||||
"modal",
|
||||
"search",
|
||||
|
|
|
@ -350,10 +350,10 @@ export default sfc; // activate the IDE's Vue plugin
|
|||
<span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
|
||||
</div>
|
||||
</h4>
|
||||
<div class="ui top attached segment repos-search gt-rounded-top">
|
||||
<div class="ui fluid action left icon input" :class="{loading: isLoading}">
|
||||
<div class="ui attached segment repos-search">
|
||||
<div class="ui small fluid action left icon input">
|
||||
<input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos">
|
||||
<i class="icon"><svg-icon name="octicon-search" :size="16"/></i>
|
||||
<i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i>
|
||||
<div class="ui dropdown icon button" :title="textFilter">
|
||||
<svg-icon name="octicon-filter" :size="16"/>
|
||||
<div class="menu">
|
||||
|
|
|
@ -218,17 +218,24 @@ export function initAdminCommon() {
|
|||
});
|
||||
|
||||
// Select actions
|
||||
const $checkboxes = $('.select.table .ui.checkbox');
|
||||
const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input');
|
||||
|
||||
$('.select.action').on('click', function () {
|
||||
switch ($(this).data('action')) {
|
||||
case 'select-all':
|
||||
$checkboxes.checkbox('check');
|
||||
for (const checkbox of checkboxes) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
break;
|
||||
case 'deselect-all':
|
||||
$checkboxes.checkbox('uncheck');
|
||||
for (const checkbox of checkboxes) {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
break;
|
||||
case 'inverse':
|
||||
$checkboxes.checkbox('toggle');
|
||||
for (const checkbox of checkboxes) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
@ -236,11 +243,11 @@ export function initAdminCommon() {
|
|||
e.preventDefault();
|
||||
this.classList.add('is-loading', 'disabled');
|
||||
const data = new FormData();
|
||||
$checkboxes.each(function () {
|
||||
if ($(this).checkbox('is checked')) {
|
||||
data.append('ids[]', this.getAttribute('data-id'));
|
||||
for (const checkbox of checkboxes) {
|
||||
if (checkbox.checked) {
|
||||
data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id'));
|
||||
}
|
||||
}
|
||||
});
|
||||
await POST(this.getAttribute('data-link'), {data});
|
||||
window.location.href = this.getAttribute('data-redirect');
|
||||
});
|
||||
|
|
|
@ -1,12 +1,66 @@
|
|||
import $ from 'jquery';
|
||||
import {createTippy} from '../modules/tippy.js';
|
||||
|
||||
export async function createColorPicker(els) {
|
||||
export async function initColorPickers() {
|
||||
const els = document.getElementsByClassName('js-color-picker-input');
|
||||
if (!els.length) return;
|
||||
|
||||
await Promise.all([
|
||||
import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors'),
|
||||
import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors/jquery.minicolors.css'),
|
||||
import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'),
|
||||
import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'),
|
||||
]);
|
||||
|
||||
return $(els).minicolors();
|
||||
for (const el of els) {
|
||||
initPicker(el);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSquare(el, newValue) {
|
||||
el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent';
|
||||
}
|
||||
|
||||
function updatePicker(el, newValue) {
|
||||
el.setAttribute('color', newValue);
|
||||
}
|
||||
|
||||
function initPicker(el) {
|
||||
const input = el.querySelector('input');
|
||||
|
||||
const square = document.createElement('div');
|
||||
square.classList.add('preview-square');
|
||||
updateSquare(square, input.value);
|
||||
el.append(square);
|
||||
|
||||
const picker = document.createElement('hex-color-picker');
|
||||
picker.addEventListener('color-changed', (e) => {
|
||||
input.value = e.detail.value;
|
||||
input.focus();
|
||||
updateSquare(square, e.detail.value);
|
||||
});
|
||||
|
||||
input.addEventListener('input', (e) => {
|
||||
updateSquare(square, e.target.value);
|
||||
updatePicker(picker, e.target.value);
|
||||
});
|
||||
|
||||
createTippy(input, {
|
||||
trigger: 'focus click',
|
||||
theme: 'bare',
|
||||
hideOnClick: true,
|
||||
content: picker,
|
||||
placement: 'bottom-start',
|
||||
interactive: true,
|
||||
onShow() {
|
||||
updatePicker(picker, input.value);
|
||||
},
|
||||
});
|
||||
|
||||
// init precolors
|
||||
for (const colorEl of el.querySelectorAll('.precolors .color')) {
|
||||
colorEl.addEventListener('click', (e) => {
|
||||
const newValue = e.target.getAttribute('data-color-hex');
|
||||
input.value = newValue;
|
||||
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
updateSquare(square, newValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import $ from 'jquery';
|
|||
import '../vendor/jquery.are-you-sure.js';
|
||||
import {clippie} from 'clippie';
|
||||
import {createDropzone} from './dropzone.js';
|
||||
import {initCompColorPicker} from './comp/ColorPicker.js';
|
||||
import {showGlobalErrorMessage} from '../bootstrap.js';
|
||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
|
||||
import {svg} from '../svg.js';
|
||||
|
@ -110,7 +109,7 @@ async function fetchActionDoRequest(actionElem, url, opt) {
|
|||
showErrorToast(`${i18n.network_error} ${e}`);
|
||||
}
|
||||
}
|
||||
actionElem.classList.remove('is-loading', 'small-loading-icon');
|
||||
actionElem.classList.remove('is-loading', 'loading-icon-2px');
|
||||
}
|
||||
|
||||
async function formFetchAction(e) {
|
||||
|
@ -122,7 +121,7 @@ async function formFetchAction(e) {
|
|||
|
||||
formEl.classList.add('is-loading');
|
||||
if (formEl.clientHeight < 50) {
|
||||
formEl.classList.add('small-loading-icon');
|
||||
formEl.classList.add('loading-icon-2px');
|
||||
}
|
||||
|
||||
const formMethod = formEl.getAttribute('method') || 'get';
|
||||
|
@ -196,8 +195,6 @@ export function initGlobalCommon() {
|
|||
$uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');
|
||||
$uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');
|
||||
|
||||
$('.ui.checkbox').checkbox();
|
||||
|
||||
$('.tabular.menu .item').tab();
|
||||
|
||||
initSubmitEventPolyfill();
|
||||
|
@ -379,10 +376,7 @@ function initGlobalShowModal() {
|
|||
$attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
|
||||
}
|
||||
}
|
||||
const $colorPickers = $modal.find('.color-picker');
|
||||
if ($colorPickers.length > 0) {
|
||||
initCompColorPicker(); // FIXME: this might cause duplicate init
|
||||
}
|
||||
|
||||
$modal.modal('setting', {
|
||||
onApprove: () => {
|
||||
// "form-fetch-action" can handle network errors gracefully,
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import {createColorPicker} from '../colorpicker.js';
|
||||
|
||||
export function initCompColorPicker() {
|
||||
(async () => {
|
||||
await createColorPicker(document.querySelectorAll('.color-picker'));
|
||||
|
||||
for (const el of document.querySelectorAll('.precolors .color')) {
|
||||
el.addEventListener('click', (e) => {
|
||||
const color = e.target.getAttribute('data-color-hex');
|
||||
const parent = e.target.closest('.color.picker');
|
||||
$(parent.querySelector('.color-picker')).minicolors('value', color);
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import $ from 'jquery';
|
||||
import {initCompColorPicker} from './ColorPicker.js';
|
||||
|
||||
function isExclusiveScopeName(name) {
|
||||
return /.*[^/]\/[^/].*/.test(name);
|
||||
|
@ -28,13 +27,17 @@ function updateExclusiveLabelEdit(form) {
|
|||
|
||||
export function initCompLabelEdit(selector) {
|
||||
if (!$(selector).length) return;
|
||||
initCompColorPicker();
|
||||
|
||||
// Create label
|
||||
$('.new-label.button').on('click', () => {
|
||||
updateExclusiveLabelEdit('.new-label');
|
||||
$('.new-label.modal').modal({
|
||||
onApprove() {
|
||||
const form = document.querySelector('.new-label.form');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return false;
|
||||
}
|
||||
$('.new-label.form').trigger('submit');
|
||||
},
|
||||
}).modal('show');
|
||||
|
@ -60,10 +63,18 @@ export function initCompLabelEdit(selector) {
|
|||
updateExclusiveLabelEdit('.edit-label');
|
||||
|
||||
$('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
|
||||
$('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color'));
|
||||
|
||||
const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
|
||||
colorInput.value = this.getAttribute('data-color');
|
||||
colorInput.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
|
||||
$('.edit-label.modal').modal({
|
||||
onApprove() {
|
||||
const form = document.querySelector('.edit-label.form');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return false;
|
||||
}
|
||||
$('.edit-label.form').trigger('submit');
|
||||
},
|
||||
}).modal('show');
|
||||
|
|
|
@ -19,7 +19,7 @@ export function initCopyContent() {
|
|||
// the text to copy is not in the DOM or it is an image which should be
|
||||
// fetched to copy in full resolution
|
||||
if (link) {
|
||||
btn.classList.add('is-loading', 'small-loading-icon');
|
||||
btn.classList.add('is-loading', 'loading-icon-2px');
|
||||
try {
|
||||
const res = await GET(link, {credentials: 'include', redirect: 'follow'});
|
||||
const contentType = res.headers.get('content-type');
|
||||
|
@ -33,7 +33,7 @@ export function initCopyContent() {
|
|||
} catch {
|
||||
return showTemporaryTooltip(btn, i18n.copy_error);
|
||||
} finally {
|
||||
btn.classList.remove('is-loading', 'small-loading-icon');
|
||||
btn.classList.remove('is-loading', 'loading-icon-2px');
|
||||
}
|
||||
} else { // text, read from DOM
|
||||
const lineEls = document.querySelectorAll('.file-view .lines-code');
|
||||
|
|
|
@ -110,15 +110,15 @@ export function initImageDiff() {
|
|||
const $imagesAfter = imageInfos[0].$images;
|
||||
const $imagesBefore = imageInfos[1].$images;
|
||||
|
||||
initSideBySide(createContext($imagesAfter[0], $imagesBefore[0]));
|
||||
initSideBySide(this, createContext($imagesAfter[0], $imagesBefore[0]));
|
||||
if ($imagesAfter.length > 0 && $imagesBefore.length > 0) {
|
||||
initSwipe(createContext($imagesAfter[1], $imagesBefore[1]));
|
||||
initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));
|
||||
}
|
||||
|
||||
$container.find('> .image-diff-tabs').removeClass('is-loading');
|
||||
this.querySelector(':scope > .image-diff-tabs')?.classList.remove('is-loading');
|
||||
|
||||
function initSideBySide(sizes) {
|
||||
function initSideBySide(container, sizes) {
|
||||
let factor = 1;
|
||||
if (sizes.max.width > (diffContainerWidth - 24) / 2) {
|
||||
factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
|
||||
|
@ -126,13 +126,24 @@ export function initImageDiff() {
|
|||
|
||||
const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;
|
||||
const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight;
|
||||
if (sizes.$image1.length !== 0) {
|
||||
$container.find('.bounds-info-after .bounds-info-width').text(`${sizes.$image1[0].naturalWidth}px`).addClass(widthChanged ? 'green' : '');
|
||||
$container.find('.bounds-info-after .bounds-info-height').text(`${sizes.$image1[0].naturalHeight}px`).addClass(heightChanged ? 'green' : '');
|
||||
if (sizes.$image1?.length) {
|
||||
const boundsInfoAfterWidth = container.querySelector('.bounds-info-after .bounds-info-width');
|
||||
boundsInfoAfterWidth.textContent = `${sizes.$image1[0].naturalWidth}px`;
|
||||
if (widthChanged) boundsInfoAfterWidth.classList.add('green');
|
||||
|
||||
const boundsInfoAfterHeight = container.querySelector('.bounds-info-after .bounds-info-height');
|
||||
boundsInfoAfterHeight.textContent = `${sizes.$image1[0].naturalHeight}px`;
|
||||
if (heightChanged) boundsInfoAfterHeight.classList.add('green');
|
||||
}
|
||||
if (sizes.$image2.length !== 0) {
|
||||
$container.find('.bounds-info-before .bounds-info-width').text(`${sizes.$image2[0].naturalWidth}px`).addClass(widthChanged ? 'red' : '');
|
||||
$container.find('.bounds-info-before .bounds-info-height').text(`${sizes.$image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : '');
|
||||
|
||||
if (sizes.$image2?.length) {
|
||||
const boundsInfoBeforeWidth = container.querySelector('.bounds-info-before .bounds-info-width');
|
||||
boundsInfoBeforeWidth.textContent = `${sizes.$image2[0].naturalWidth}px`;
|
||||
if (widthChanged) boundsInfoBeforeWidth.classList.add('red');
|
||||
|
||||
const boundsInfoBeforeHeight = container.querySelector('.bounds-info-before .bounds-info-height');
|
||||
boundsInfoBeforeHeight.textContent = `${sizes.$image2[0].naturalHeight}px`;
|
||||
if (heightChanged) boundsInfoBeforeHeight.classList.add('red');
|
||||
}
|
||||
|
||||
const image1 = sizes.$image1[0];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import {GET} from '../modules/fetch.js';
|
||||
import {toggleElem} from '../utils/dom.js';
|
||||
|
||||
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
|
||||
let notificationSequenceNumber = 0;
|
||||
|
@ -177,14 +178,11 @@ async function updateNotificationCount() {
|
|||
|
||||
const data = await response.json();
|
||||
|
||||
const $notificationCount = $('.notification_count');
|
||||
if (data.new === 0) {
|
||||
$notificationCount.addClass('tw-hidden');
|
||||
} else {
|
||||
$notificationCount.removeClass('tw-hidden');
|
||||
}
|
||||
toggleElem('.notification_count', data.new !== 0);
|
||||
|
||||
$notificationCount.text(`${data.new}`);
|
||||
for (const el of document.getElementsByClassName('notification_count')) {
|
||||
el.textContent = `${data.new}`;
|
||||
}
|
||||
|
||||
return `${data.new}`;
|
||||
} catch (error) {
|
||||
|
|
|
@ -25,7 +25,9 @@ function getLineEls() {
|
|||
}
|
||||
|
||||
function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
||||
$linesEls.closest('tr').removeClass('active');
|
||||
for (const el of $linesEls) {
|
||||
el.closest('tr').classList.remove('active');
|
||||
}
|
||||
|
||||
// add hashchange to permalink
|
||||
const refInNewIssue = document.querySelector('a.ref-in-new-issue');
|
||||
|
@ -72,7 +74,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
|||
classes.push(`[rel=L${i}]`);
|
||||
}
|
||||
$linesEls.filter(classes.join(',')).each(function () {
|
||||
$(this).closest('tr').addClass('active');
|
||||
this.closest('tr').classList.add('active');
|
||||
});
|
||||
changeHash(`#L${a}-L${b}`);
|
||||
|
||||
|
@ -82,7 +84,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
$selectionEndEl.closest('tr').addClass('active');
|
||||
$selectionEndEl[0].closest('tr').classList.add('active');
|
||||
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
|
||||
|
||||
updateIssueHref($selectionEndEl[0].getAttribute('rel'));
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue