loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

+3706 -4312
-1
.deadcode-out
··· 22 22 func (ScheduleList).GetRepoIDs 23 23 func (ScheduleList).LoadTriggerUser 24 24 func (ScheduleList).LoadRepos 25 - func GetVariableByID 26 25 27 26 package "code.gitea.io/gitea/models/asymkey" 28 27 func (ErrGPGKeyAccessDenied).Error
-1
.dockerignore
··· 77 77 /public/assets/css 78 78 /public/assets/fonts 79 79 /public/assets/img/avatar 80 - /public/assets/img/webpack 81 80 /vendor 82 81 /web_src/fomantic/node_modules 83 82 /web_src/fomantic/build/*
+1
.eslintrc.yaml
··· 3 3 4 4 ignorePatterns: 5 5 - /web_src/js/vendor 6 + - /web_src/fomantic 6 7 7 8 parserOptions: 8 9 sourceType: module
-1
.gitignore
··· 83 83 /public/assets/css 84 84 /public/assets/fonts 85 85 /public/assets/licenses.txt 86 - /public/assets/img/webpack 87 86 /vendor 88 87 /web_src/fomantic/node_modules 89 88 /web_src/fomantic/build/*
+3 -1
.ignore
··· 4 4 /modules/options/bindata.go 5 5 /modules/public/bindata.go 6 6 /modules/templates/bindata.go 7 - /vendor 7 + /options/gitignore 8 + /options/license 8 9 /public/assets 10 + /vendor 9 11 node_modules
-223
.stylelintrc.yaml
··· 1 - plugins: 2 - - stylelint-declaration-strict-value 3 - - stylelint-declaration-block-no-ignored-properties 4 - - "@stylistic/stylelint-plugin" 5 - 6 - ignoreFiles: 7 - - "**/*.go" 8 - 9 - overrides: 10 - - files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console.css", "font_i18n.css"] 11 - rules: 12 - scale-unlimited/declaration-strict-value: null 13 - - files: ["**/chroma/*", "**/codemirror/*"] 14 - rules: 15 - block-no-empty: null 16 - - files: ["**/*.vue"] 17 - customSyntax: postcss-html 18 - 19 - rules: 20 - "@stylistic/at-rule-name-case": null 21 - "@stylistic/at-rule-name-newline-after": null 22 - "@stylistic/at-rule-name-space-after": null 23 - "@stylistic/at-rule-semicolon-newline-after": null 24 - "@stylistic/at-rule-semicolon-space-before": null 25 - "@stylistic/block-closing-brace-empty-line-before": null 26 - "@stylistic/block-closing-brace-newline-after": null 27 - "@stylistic/block-closing-brace-newline-before": null 28 - "@stylistic/block-closing-brace-space-after": null 29 - "@stylistic/block-closing-brace-space-before": null 30 - "@stylistic/block-opening-brace-newline-after": null 31 - "@stylistic/block-opening-brace-newline-before": null 32 - "@stylistic/block-opening-brace-space-after": null 33 - "@stylistic/block-opening-brace-space-before": always 34 - "@stylistic/color-hex-case": lower 35 - "@stylistic/declaration-bang-space-after": never 36 - "@stylistic/declaration-bang-space-before": null 37 - "@stylistic/declaration-block-semicolon-newline-after": null 38 - "@stylistic/declaration-block-semicolon-newline-before": null 39 - "@stylistic/declaration-block-semicolon-space-after": null 40 - "@stylistic/declaration-block-semicolon-space-before": never 41 - "@stylistic/declaration-block-trailing-semicolon": null 42 - "@stylistic/declaration-colon-newline-after": null 43 - "@stylistic/declaration-colon-space-after": null 44 - "@stylistic/declaration-colon-space-before": never 45 - "@stylistic/function-comma-newline-after": null 46 - "@stylistic/function-comma-newline-before": null 47 - "@stylistic/function-comma-space-after": null 48 - "@stylistic/function-comma-space-before": null 49 - "@stylistic/function-max-empty-lines": 0 50 - "@stylistic/function-parentheses-newline-inside": never-multi-line 51 - "@stylistic/function-parentheses-space-inside": null 52 - "@stylistic/function-whitespace-after": null 53 - "@stylistic/indentation": 2 54 - "@stylistic/linebreaks": null 55 - "@stylistic/max-empty-lines": 1 56 - "@stylistic/max-line-length": null 57 - "@stylistic/media-feature-colon-space-after": null 58 - "@stylistic/media-feature-colon-space-before": never 59 - "@stylistic/media-feature-name-case": null 60 - "@stylistic/media-feature-parentheses-space-inside": null 61 - "@stylistic/media-feature-range-operator-space-after": always 62 - "@stylistic/media-feature-range-operator-space-before": always 63 - "@stylistic/media-query-list-comma-newline-after": null 64 - "@stylistic/media-query-list-comma-newline-before": null 65 - "@stylistic/media-query-list-comma-space-after": null 66 - "@stylistic/media-query-list-comma-space-before": null 67 - "@stylistic/named-grid-areas-alignment": null 68 - "@stylistic/no-empty-first-line": null 69 - "@stylistic/no-eol-whitespace": true 70 - "@stylistic/no-extra-semicolons": true 71 - "@stylistic/no-missing-end-of-source-newline": null 72 - "@stylistic/number-leading-zero": null 73 - "@stylistic/number-no-trailing-zeros": null 74 - "@stylistic/property-case": lower 75 - "@stylistic/selector-attribute-brackets-space-inside": null 76 - "@stylistic/selector-attribute-operator-space-after": null 77 - "@stylistic/selector-attribute-operator-space-before": null 78 - "@stylistic/selector-combinator-space-after": null 79 - "@stylistic/selector-combinator-space-before": null 80 - "@stylistic/selector-descendant-combinator-no-non-space": null 81 - "@stylistic/selector-list-comma-newline-after": null 82 - "@stylistic/selector-list-comma-newline-before": null 83 - "@stylistic/selector-list-comma-space-after": always-single-line 84 - "@stylistic/selector-list-comma-space-before": never-single-line 85 - "@stylistic/selector-max-empty-lines": 0 86 - "@stylistic/selector-pseudo-class-case": lower 87 - "@stylistic/selector-pseudo-class-parentheses-space-inside": never 88 - "@stylistic/selector-pseudo-element-case": lower 89 - "@stylistic/string-quotes": double 90 - "@stylistic/unicode-bom": null 91 - "@stylistic/unit-case": lower 92 - "@stylistic/value-list-comma-newline-after": null 93 - "@stylistic/value-list-comma-newline-before": null 94 - "@stylistic/value-list-comma-space-after": null 95 - "@stylistic/value-list-comma-space-before": null 96 - "@stylistic/value-list-max-empty-lines": 0 97 - alpha-value-notation: null 98 - annotation-no-unknown: true 99 - at-rule-allowed-list: null 100 - at-rule-disallowed-list: null 101 - at-rule-empty-line-before: null 102 - at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}] 103 - at-rule-no-vendor-prefix: true 104 - at-rule-property-required-list: null 105 - block-no-empty: true 106 - color-function-notation: null 107 - color-hex-alpha: null 108 - color-hex-length: null 109 - color-named: null 110 - color-no-hex: null 111 - color-no-invalid-hex: true 112 - comment-empty-line-before: null 113 - comment-no-empty: true 114 - comment-pattern: null 115 - comment-whitespace-inside: null 116 - comment-word-disallowed-list: null 117 - custom-media-pattern: null 118 - custom-property-empty-line-before: null 119 - custom-property-no-missing-var-function: true 120 - custom-property-pattern: null 121 - declaration-block-no-duplicate-custom-properties: true 122 - declaration-block-no-duplicate-properties: [true, {ignore: [consecutive-duplicates-with-different-values]}] 123 - declaration-block-no-redundant-longhand-properties: null 124 - declaration-block-no-shorthand-property-overrides: null 125 - declaration-block-single-line-max-declarations: null 126 - declaration-empty-line-before: null 127 - declaration-no-important: null 128 - declaration-property-max-values: null 129 - declaration-property-unit-allowed-list: null 130 - declaration-property-unit-disallowed-list: {line-height: [em]} 131 - declaration-property-value-allowed-list: null 132 - declaration-property-value-disallowed-list: null 133 - declaration-property-value-no-unknown: true 134 - font-family-name-quotes: always-where-recommended 135 - font-family-no-duplicate-names: true 136 - font-family-no-missing-generic-family-keyword: true 137 - font-weight-notation: null 138 - function-allowed-list: null 139 - function-calc-no-unspaced-operator: true 140 - function-disallowed-list: null 141 - function-linear-gradient-no-nonstandard-direction: true 142 - function-name-case: lower 143 - function-no-unknown: true 144 - function-url-no-scheme-relative: null 145 - function-url-quotes: always 146 - function-url-scheme-allowed-list: null 147 - function-url-scheme-disallowed-list: null 148 - hue-degree-notation: null 149 - import-notation: string 150 - keyframe-block-no-duplicate-selectors: true 151 - keyframe-declaration-no-important: true 152 - keyframe-selector-notation: null 153 - keyframes-name-pattern: null 154 - length-zero-no-unit: [true, ignore: [custom-properties], ignoreFunctions: [var]] 155 - max-nesting-depth: null 156 - media-feature-name-allowed-list: null 157 - media-feature-name-disallowed-list: null 158 - media-feature-name-no-unknown: true 159 - media-feature-name-no-vendor-prefix: true 160 - media-feature-name-unit-allowed-list: null 161 - media-feature-name-value-allowed-list: null 162 - media-feature-name-value-no-unknown: true 163 - media-feature-range-notation: null 164 - media-query-no-invalid: true 165 - named-grid-areas-no-invalid: true 166 - no-descending-specificity: null 167 - no-duplicate-at-import-rules: true 168 - no-duplicate-selectors: true 169 - no-empty-source: true 170 - no-invalid-double-slash-comments: true 171 - no-invalid-position-at-import-rule: [true, ignoreAtRules: [tailwind]] 172 - no-irregular-whitespace: true 173 - no-unknown-animations: null 174 - no-unknown-custom-properties: null 175 - number-max-precision: null 176 - plugin/declaration-block-no-ignored-properties: true 177 - property-allowed-list: null 178 - property-disallowed-list: null 179 - property-no-unknown: true 180 - property-no-vendor-prefix: null 181 - rule-empty-line-before: null 182 - rule-selector-property-disallowed-list: null 183 - scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}] 184 - selector-anb-no-unmatchable: true 185 - selector-attribute-name-disallowed-list: null 186 - selector-attribute-operator-allowed-list: null 187 - selector-attribute-operator-disallowed-list: null 188 - selector-attribute-quotes: always 189 - selector-class-pattern: null 190 - selector-combinator-allowed-list: null 191 - selector-combinator-disallowed-list: null 192 - selector-disallowed-list: null 193 - selector-id-pattern: null 194 - selector-max-attribute: null 195 - selector-max-class: null 196 - selector-max-combinators: null 197 - selector-max-compound-selectors: null 198 - selector-max-id: null 199 - selector-max-pseudo-class: null 200 - selector-max-specificity: null 201 - selector-max-type: null 202 - selector-max-universal: null 203 - selector-nested-pattern: null 204 - selector-no-qualifying-type: null 205 - selector-no-vendor-prefix: true 206 - selector-not-notation: null 207 - selector-pseudo-class-allowed-list: null 208 - selector-pseudo-class-disallowed-list: null 209 - selector-pseudo-class-no-unknown: true 210 - selector-pseudo-element-allowed-list: null 211 - selector-pseudo-element-colon-notation: double 212 - selector-pseudo-element-disallowed-list: null 213 - selector-pseudo-element-no-unknown: true 214 - selector-type-case: lower 215 - selector-type-no-unknown: [true, {ignore: [custom-elements]}] 216 - shorthand-property-no-redundant-values: true 217 - string-no-newline: true 218 - time-min-milliseconds: null 219 - unit-allowed-list: null 220 - unit-disallowed-list: null 221 - unit-no-unknown: true 222 - value-keyword-case: null 223 - value-no-vendor-prefix: [true, {ignoreValues: [box, inline-box]}]
+1 -1
Makefile
··· 130 130 WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) 131 131 WEBPACK_CONFIGS := webpack.config.js tailwind.config.js 132 132 WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css 133 - WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack 133 + WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts 134 134 135 135 BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go 136 136 BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
+7
custom/conf/app.example.ini
··· 1503 1503 ;; - manage_ssh_keys: a user cannot configure ssh keys 1504 1504 ;; - manage_gpg_keys: a user cannot configure gpg keys 1505 1505 ;USER_DISABLED_FEATURES = 1506 + ;; 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. 1507 + ;; - deletion: a user cannot delete their own account 1508 + ;; - manage_ssh_keys: a user cannot configure ssh keys 1509 + ;; - manage_gpg_keys: a user cannot configure gpg keys 1510 + ;;EXTERNAL_USER_DISABLE_FEATURES = 1506 1511 1507 1512 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1508 1513 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ··· 2326 2331 ;SHOW_FOOTER_VERSION = true 2327 2332 ;; Show template execution time in the footer 2328 2333 ;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true 2334 + ;; Show the "powered by" text in the footer 2335 + ;SHOW_FOOTER_POWERED_BY = true 2329 2336 ;; Generate sitemap. Defaults to `true`. 2330 2337 ;ENABLE_SITEMAP = true 2331 2338 ;; Enable/Disable RSS/Atom feed
+16 -11
models/actions/variable.go
··· 6 6 import ( 7 7 "context" 8 8 "errors" 9 - "fmt" 10 9 "strings" 11 10 12 11 "code.gitea.io/gitea/models/db" 13 12 "code.gitea.io/gitea/modules/log" 14 13 "code.gitea.io/gitea/modules/timeutil" 15 - "code.gitea.io/gitea/modules/util" 16 14 17 15 "xorm.io/builder" 18 16 ) ··· 55 53 db.ListOptions 56 54 OwnerID int64 57 55 RepoID int64 56 + Name string 58 57 } 59 58 60 59 func (opts FindVariablesOpts) ToConds() builder.Cond { 61 60 cond := builder.NewCond() 61 + // Since we now support instance-level variables, 62 + // there is no need to check for null values for `owner_id` and `repo_id` 62 63 cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) 63 64 cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) 65 + 66 + if opts.Name != "" { 67 + cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)}) 68 + } 64 69 return cond 65 70 } 66 71 67 - func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) { 68 - var variable ActionVariable 69 - has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable) 70 - if err != nil { 71 - return nil, err 72 - } else if !has { 73 - return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist) 74 - } 75 - return &variable, nil 72 + func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) { 73 + return db.Find[ActionVariable](ctx, opts) 76 74 } 77 75 78 76 func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) { ··· 82 80 Data: variable.Data, 83 81 }) 84 82 return count != 0, err 83 + } 84 + 85 + func DeleteVariable(ctx context.Context, id int64) error { 86 + if _, err := db.DeleteByID[ActionVariable](ctx, id); err != nil { 87 + return err 88 + } 89 + return nil 85 90 } 86 91 87 92 func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
+10
models/asymkey/gpg_key_common.go
··· 134 134 } 135 135 return sig, nil 136 136 } 137 + 138 + func tryGetKeyIDFromSignature(sig *packet.Signature) string { 139 + if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 { 140 + return fmt.Sprintf("%016X", *sig.IssuerKeyId) 141 + } 142 + if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 { 143 + return fmt.Sprintf("%016X", sig.IssuerFingerprint[12:20]) 144 + } 145 + return "" 146 + }
+1 -7
models/asymkey/gpg_key_object_verification.go
··· 123 123 } 124 124 } 125 125 126 - keyID := "" 127 - if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 { 128 - keyID = fmt.Sprintf("%X", *sig.IssuerKeyId) 129 - } 130 - if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 { 131 - keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20]) 132 - } 126 + keyID := tryGetKeyIDFromSignature(sig) 133 127 defaultReason := NoKeyFound 134 128 135 129 // First check if the sig has a keyID and if so just look at that
+12
models/asymkey/gpg_key_test.go
··· 11 11 "code.gitea.io/gitea/models/unittest" 12 12 user_model "code.gitea.io/gitea/models/user" 13 13 "code.gitea.io/gitea/modules/timeutil" 14 + "code.gitea.io/gitea/modules/util" 14 15 16 + "github.com/keybase/go-crypto/openpgp/packet" 15 17 "github.com/stretchr/testify/assert" 16 18 ) 17 19 ··· 391 393 assert.Equal(t, time.Unix(1586105389, 0), expire) 392 394 } 393 395 } 396 + 397 + func TestTryGetKeyIDFromSignature(t *testing.T) { 398 + assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{})) 399 + assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{ 400 + IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)), 401 + })) 402 + assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{ 403 + IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c}, 404 + })) 405 + }
+4
models/asymkey/gpg_key_verify.go
··· 46 46 return "", ErrGPGKeyNotExist{} 47 47 } 48 48 49 + if err := key.LoadSubKeys(ctx); err != nil { 50 + return "", err 51 + } 52 + 49 53 sig, err := extractSignature(signature) 50 54 if err != nil { 51 55 return "", ErrGPGInvalidTokenSignature{
+18
models/user/user.go
··· 1246 1246 } 1247 1247 return "name" 1248 1248 } 1249 + 1250 + // IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the 1251 + // user if applicable 1252 + func IsFeatureDisabledWithLoginType(user *User, feature string) bool { 1253 + // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType 1254 + return (user != nil && user.LoginType > auth.Plain && setting.Admin.ExternalUserDisableFeatures.Contains(feature)) || 1255 + setting.Admin.UserDisabledFeatures.Contains(feature) 1256 + } 1257 + 1258 + // DisabledFeaturesWithLoginType returns the set of user features disabled, taking into account the login type 1259 + // of the user if applicable 1260 + func DisabledFeaturesWithLoginType(user *User) *container.Set[string] { 1261 + // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType 1262 + if user != nil && user.LoginType > auth.Plain { 1263 + return &setting.Admin.ExternalUserDisableFeatures 1264 + } 1265 + return &setting.Admin.UserDisabledFeatures 1266 + }
+35
models/user/user_test.go
··· 16 16 "code.gitea.io/gitea/models/unittest" 17 17 user_model "code.gitea.io/gitea/models/user" 18 18 "code.gitea.io/gitea/modules/auth/password/hash" 19 + "code.gitea.io/gitea/modules/container" 19 20 "code.gitea.io/gitea/modules/optional" 20 21 "code.gitea.io/gitea/modules/setting" 21 22 "code.gitea.io/gitea/modules/structs" ··· 542 543 } 543 544 } 544 545 } 546 + 547 + func TestDisabledUserFeatures(t *testing.T) { 548 + assert.NoError(t, unittest.PrepareTestDatabase()) 549 + 550 + testValues := container.SetOf(setting.UserFeatureDeletion, 551 + setting.UserFeatureManageSSHKeys, 552 + setting.UserFeatureManageGPGKeys) 553 + 554 + oldSetting := setting.Admin.ExternalUserDisableFeatures 555 + defer func() { 556 + setting.Admin.ExternalUserDisableFeatures = oldSetting 557 + }() 558 + setting.Admin.ExternalUserDisableFeatures = testValues 559 + 560 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) 561 + 562 + assert.Len(t, setting.Admin.UserDisabledFeatures.Values(), 0) 563 + 564 + // no features should be disabled with a plain login type 565 + assert.LessOrEqual(t, user.LoginType, auth.Plain) 566 + assert.Len(t, user_model.DisabledFeaturesWithLoginType(user).Values(), 0) 567 + for _, f := range testValues.Values() { 568 + assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f)) 569 + } 570 + 571 + // check disabled features with external login type 572 + user.LoginType = auth.OAuth2 573 + 574 + // all features should be disabled 575 + assert.NotEmpty(t, user_model.DisabledFeaturesWithLoginType(user).Values()) 576 + for _, f := range testValues.Values() { 577 + assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f)) 578 + } 579 + }
+6
modules/git/commit_convert_gogit.go
··· 47 47 return nil 48 48 } 49 49 50 + if c.Encoding != "" && c.Encoding != "UTF-8" { 51 + if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil { 52 + return nil 53 + } 54 + } 55 + 50 56 if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { 51 57 return nil 52 58 }
+2
modules/git/commit_reader.go
··· 84 84 commit.Committer = &Signature{} 85 85 commit.Committer.Decode(data) 86 86 _, _ = payloadSB.Write(line) 87 + case "encoding": 88 + _, _ = payloadSB.Write(line) 87 89 case "gpgsig": 88 90 fallthrough 89 91 case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
+67
modules/git/commit_test.go
··· 125 125 assert.EqualValues(t, commitFromReader, commitFromReader2) 126 126 } 127 127 128 + func TestCommitWithEncodingFromReader(t *testing.T) { 129 + commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074 130 + tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 131 + parent 47b24e7ab977ed31c5a39989d570847d6d0052af 132 + author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100 133 + committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100 134 + encoding ISO-8859-1 135 + gpgsig -----BEGIN PGP SIGNATURE----- 136 + 137 + iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow 138 + Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR 139 + gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq 140 + zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr 141 + frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j 142 + FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd 143 + G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn 144 + SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F 145 + yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz 146 + jw4YcO5u 147 + =r3UU 148 + -----END PGP SIGNATURE----- 149 + 150 + ISO-8859-1` 151 + 152 + sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2} 153 + gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) 154 + assert.NoError(t, err) 155 + assert.NotNil(t, gitRepo) 156 + defer gitRepo.Close() 157 + 158 + commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString)) 159 + assert.NoError(t, err) 160 + if !assert.NotNil(t, commitFromReader) { 161 + return 162 + } 163 + assert.EqualValues(t, sha, commitFromReader.ID) 164 + assert.EqualValues(t, `-----BEGIN PGP SIGNATURE----- 165 + 166 + iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow 167 + Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR 168 + gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq 169 + zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr 170 + frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j 171 + FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd 172 + G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn 173 + SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F 174 + yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz 175 + jw4YcO5u 176 + =r3UU 177 + -----END PGP SIGNATURE----- 178 + `, commitFromReader.Signature.Signature) 179 + assert.EqualValues(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 180 + parent 47b24e7ab977ed31c5a39989d570847d6d0052af 181 + author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100 182 + committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100 183 + encoding ISO-8859-1 184 + 185 + ISO-8859-1`, commitFromReader.Signature.Payload) 186 + assert.EqualValues(t, "KN4CK3R <admin@oldschoolhack.me>", commitFromReader.Author.String()) 187 + 188 + commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) 189 + assert.NoError(t, err) 190 + commitFromReader.CommitMessage += "\n\n" 191 + commitFromReader.Signature.Payload += "\n\n" 192 + assert.EqualValues(t, commitFromReader, commitFromReader2) 193 + } 194 + 128 195 func TestHasPreviousCommit(t *testing.T) { 129 196 bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") 130 197
+2 -2
modules/lfs/filesystem_client.go
··· 44 44 if err != nil { 45 45 return err 46 46 } 47 - 47 + defer f.Close() 48 48 if err := callback(p, f, nil); err != nil { 49 49 return err 50 50 } ··· 75 75 if err != nil { 76 76 return err 77 77 } 78 - 78 + defer f.Close() 79 79 _, err = io.Copy(f, content) 80 80 81 81 return err
+18 -2
modules/markup/markdown/markdown_test.go
··· 506 506 `<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl, 507 507 }, 508 508 { 509 + `$a$.`, 510 + `<p><code class="language-math is-loading">a</code>.</p>` + nl, 511 + }, 512 + { 513 + `.$a$`, 514 + `<p>.$a$</p>` + nl, 515 + }, 516 + { 509 517 `$a a$b b$`, 510 - `<p><code class="language-math is-loading">a a$b b</code></p>` + nl, 518 + `<p>$a a$b b$</p>` + nl, 511 519 }, 512 520 { 513 521 `a a$b b`, ··· 515 523 }, 516 524 { 517 525 `a$b $a a$b b$`, 518 - `<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl, 526 + `<p>a$b $a a$b b$</p>` + nl, 527 + }, 528 + { 529 + "a$x$", 530 + `<p>a$x$</p>` + nl, 531 + }, 532 + { 533 + "$x$a", 534 + `<p>$x$a</p>` + nl, 519 535 }, 520 536 { 521 537 "$$a$$",
+13 -5
modules/markup/markdown/math/inline_parser.go
··· 41 41 return parser.start[0:1] 42 42 } 43 43 44 + func isPunctuation(b byte) bool { 45 + return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':' 46 + } 47 + 44 48 func isAlphanumeric(b byte) bool { 45 - // Github only cares about 0-9A-Za-z 46 - return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') 49 + return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') 47 50 } 48 51 49 52 // Parse parses the current line and returns a result of parsing. ··· 56 59 } 57 60 58 61 precedingCharacter := block.PrecendingCharacter() 59 - if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) { 62 + if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) { 60 63 // need to exclude things like `a$` from being considered a start 61 64 return nil 62 65 } ··· 75 78 ender += pos 76 79 77 80 // Now we want to check the character at the end of our parser section 78 - // that is ender + len(parser.end) 81 + // that is ender + len(parser.end) and check if char before ender is '\' 79 82 pos = ender + len(parser.end) 80 83 if len(line) <= pos { 81 84 break 82 85 } 83 - if !isAlphanumeric(line[pos]) { 86 + suceedingCharacter := line[pos] 87 + if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') { 88 + return nil 89 + } 90 + if line[ender-1] != '\\' { 84 91 break 85 92 } 93 + 86 94 // move the pointer onwards 87 95 ender += len(parser.end) 88 96 }
+5 -1
modules/setting/admin.go
··· 3 3 4 4 package setting 5 5 6 - import "code.gitea.io/gitea/modules/container" 6 + import ( 7 + "code.gitea.io/gitea/modules/container" 8 + ) 7 9 8 10 // Admin settings 9 11 var Admin struct { ··· 11 13 DefaultEmailNotification string 12 14 SendNotificationEmailOnNewUser bool 13 15 UserDisabledFeatures container.Set[string] 16 + ExternalUserDisableFeatures container.Set[string] 14 17 } 15 18 16 19 func loadAdminFrom(rootCfg ConfigProvider) { ··· 18 21 Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false) 19 22 Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled") 20 23 Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...) 24 + Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...) 21 25 } 22 26 23 27 const (
+2
modules/setting/other.go
··· 8 8 type OtherConfig struct { 9 9 ShowFooterVersion bool 10 10 ShowFooterTemplateLoadTime bool 11 + ShowFooterPoweredBy bool 11 12 EnableFeed bool 12 13 EnableSitemap bool 13 14 } ··· 15 16 var Other = OtherConfig{ 16 17 ShowFooterVersion: true, 17 18 ShowFooterTemplateLoadTime: true, 19 + ShowFooterPoweredBy: true, 18 20 EnableSitemap: true, 19 21 EnableFeed: true, 20 22 }
+37
modules/structs/variable.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package structs 5 + 6 + // CreateVariableOption the option when creating variable 7 + // swagger:model 8 + type CreateVariableOption struct { 9 + // Value of the variable to create 10 + // 11 + // required: true 12 + Value string `json:"value" binding:"Required"` 13 + } 14 + 15 + // UpdateVariableOption the option when updating variable 16 + // swagger:model 17 + type UpdateVariableOption struct { 18 + // New name for the variable. If the field is empty, the variable name won't be updated. 19 + Name string `json:"name"` 20 + // Value of the variable to update 21 + // 22 + // required: true 23 + Value string `json:"value" binding:"Required"` 24 + } 25 + 26 + // ActionVariable return value of the query API 27 + // swagger:model 28 + type ActionVariable struct { 29 + // the owner to which the variable belongs 30 + OwnerID int64 `json:"owner_id"` 31 + // the repository to which the variable belongs 32 + RepoID int64 `json:"repo_id"` 33 + // the name of the variable 34 + Name string `json:"name"` 35 + // the value of the variable 36 + Data string `json:"data"` 37 + }
+3
modules/templates/helper.go
··· 109 109 "ShowFooterTemplateLoadTime": func() bool { 110 110 return setting.Other.ShowFooterTemplateLoadTime 111 111 }, 112 + "ShowFooterPoweredBy": func() bool { 113 + return setting.Other.ShowFooterPoweredBy 114 + }, 112 115 "AllowedReactions": func() []string { 113 116 return setting.UI.Reactions 114 117 },
+9
modules/util/util.go
··· 212 212 func ToPointer[T any](val T) *T { 213 213 return &val 214 214 } 215 + 216 + func ReserveLineBreakForTextarea(input string) string { 217 + // Since the content is from a form which is a textarea, the line endings are \r\n. 218 + // It's a standard behavior of HTML. 219 + // But we want to store them as \n like what GitHub does. 220 + // And users are unlikely to really need to keep the \r. 221 + // Other than this, we should respect the original content, even leading or trailing spaces. 222 + return strings.ReplaceAll(input, "\r\n", "\n") 223 + }
+5
modules/util/util_test.go
··· 235 235 val123 := 123 236 236 assert.False(t, &val123 == ToPointer(val123)) 237 237 } 238 + 239 + func TestReserveLineBreakForTextarea(t *testing.T) { 240 + assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata"), "test\ndata") 241 + assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata\r\n"), "test\ndata\n") 242 + }
+11
options/license/AMD-newlib
··· 1 + Copyright 1989, 1990 Advanced Micro Devices, Inc. 2 + 3 + This software is the property of Advanced Micro Devices, Inc (AMD) which 4 + specifically grants the user the right to modify, use and distribute this 5 + software provided this notice is not removed or altered. All other rights 6 + are reserved by AMD. 7 + 8 + AMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS 9 + SOFTWARE. IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL 10 + DAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR 11 + USE OF THIS SOFTWARE.
+12
options/license/OAR
··· 1 + COPYRIGHT (c) 1989-2013, 2015. 2 + On-Line Applications Research Corporation (OAR). 3 + 4 + Permission to use, copy, modify, and distribute this software for any 5 + purpose without fee is hereby granted, provided that this entire notice 6 + is included in all copies of any software which is or includes a copy 7 + or modification of this software. 8 + 9 + THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED 10 + WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION 11 + OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS 12 + SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+12
options/license/xzoom
··· 1 + Copyright Itai Nahshon 1995, 1996. 2 + This program is distributed with no warranty. 3 + 4 + Source files for this program may be distributed freely. 5 + Modifications to this file are okay as long as: 6 + a. This copyright notice and comment are preserved and 7 + left at the top of the file. 8 + b. The man page is fixed to reflect the change. 9 + c. The author of this change adds his name and change 10 + description to the list of changes below. 11 + Executable files may be distributed with sources, or with 12 + exact location where the source code can be obtained.
+23 -9
package-lock.json
··· 9 9 "@citation-js/plugin-bibtex": "0.7.9", 10 10 "@citation-js/plugin-csl": "0.7.9", 11 11 "@citation-js/plugin-software-formats": "0.6.1", 12 - "@claviska/jquery-minicolors": "2.3.6", 13 12 "@github/markdown-toolbar-element": "2.2.3", 14 13 "@github/relative-time-element": "4.4.0", 15 14 "@github/text-expander-element": "2.6.1", ··· 54 53 "toastify-js": "1.12.0", 55 54 "tributejs": "5.1.3", 56 55 "uint8-to-base64": "0.2.0", 56 + "vanilla-colorful": "0.7.2", 57 57 "vue": "3.4.21", 58 58 "vue-bar-graph": "2.0.0", 59 59 "vue-chartjs": "5.3.0", ··· 92 92 "stylelint": "16.3.1", 93 93 "stylelint-declaration-block-no-ignored-properties": "2.8.0", 94 94 "stylelint-declaration-strict-value": "1.10.4", 95 + "stylelint-value-no-unknown-custom-properties": "6.0.1", 95 96 "svgo": "3.2.0", 96 97 "updates": "16.0.0", 97 98 "vite-string-plugin": "1.1.5", ··· 393 394 }, 394 395 "engines": { 395 396 "node": ">=14.0.0" 396 - } 397 - }, 398 - "node_modules/@claviska/jquery-minicolors": { 399 - "version": "2.3.6", 400 - "resolved": "https://registry.npmjs.org/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.6.tgz", 401 - "integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==", 402 - "peerDependencies": { 403 - "jquery": ">= 1.7.x" 404 397 } 405 398 }, 406 399 "node_modules/@csstools/css-parser-algorithms": { ··· 11121 11114 "stylelint": ">=7 <=16" 11122 11115 } 11123 11116 }, 11117 + "node_modules/stylelint-value-no-unknown-custom-properties": { 11118 + "version": "6.0.1", 11119 + "resolved": "https://registry.npmjs.org/stylelint-value-no-unknown-custom-properties/-/stylelint-value-no-unknown-custom-properties-6.0.1.tgz", 11120 + "integrity": "sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==", 11121 + "dev": true, 11122 + "dependencies": { 11123 + "postcss-value-parser": "^4.2.0", 11124 + "resolve": "^1.22.8" 11125 + }, 11126 + "engines": { 11127 + "node": ">=18.12.0" 11128 + }, 11129 + "peerDependencies": { 11130 + "stylelint": ">=16" 11131 + } 11132 + }, 11124 11133 "node_modules/stylelint/node_modules/ansi-regex": { 11125 11134 "version": "6.0.1", 11126 11135 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", ··· 12089 12098 "dependencies": { 12090 12099 "builtins": "^1.0.3" 12091 12100 } 12101 + }, 12102 + "node_modules/vanilla-colorful": { 12103 + "version": "0.7.2", 12104 + "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", 12105 + "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==" 12092 12106 }, 12093 12107 "node_modules/vite": { 12094 12108 "version": "5.2.8",
+2 -1
package.json
··· 8 8 "@citation-js/plugin-bibtex": "0.7.9", 9 9 "@citation-js/plugin-csl": "0.7.9", 10 10 "@citation-js/plugin-software-formats": "0.6.1", 11 - "@claviska/jquery-minicolors": "2.3.6", 12 11 "@github/markdown-toolbar-element": "2.2.3", 13 12 "@github/relative-time-element": "4.4.0", 14 13 "@github/text-expander-element": "2.6.1", ··· 53 52 "toastify-js": "1.12.0", 54 53 "tributejs": "5.1.3", 55 54 "uint8-to-base64": "0.2.0", 55 + "vanilla-colorful": "0.7.2", 56 56 "vue": "3.4.21", 57 57 "vue-bar-graph": "2.0.0", 58 58 "vue-chartjs": "5.3.0", ··· 91 91 "stylelint": "16.3.1", 92 92 "stylelint-declaration-block-no-ignored-properties": "2.8.0", 93 93 "stylelint-declaration-strict-value": "1.10.4", 94 + "stylelint-value-no-unknown-custom-properties": "6.0.1", 94 95 "svgo": "3.2.0", 95 96 "updates": "16.0.0", 96 97 "vite-string-plugin": "1.1.5",
+27
routers/api/v1/api.go
··· 871 871 Delete(user.DeleteSecret) 872 872 }) 873 873 874 + m.Group("/variables", func() { 875 + m.Get("", user.ListVariables) 876 + m.Combo("/{variablename}"). 877 + Get(user.GetVariable). 878 + Delete(user.DeleteVariable). 879 + Post(bind(api.CreateVariableOption{}), user.CreateVariable). 880 + Put(bind(api.UpdateVariableOption{}), user.UpdateVariable) 881 + }) 882 + 874 883 m.Group("/runners", func() { 875 884 m.Get("/registration-token", reqToken(), user.GetRegistrationToken) 876 885 }) ··· 988 997 m.Combo("/{secretname}"). 989 998 Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret). 990 999 Delete(reqToken(), reqOwner(), repo.DeleteSecret) 1000 + }) 1001 + 1002 + m.Group("/variables", func() { 1003 + m.Get("", reqToken(), reqOwner(), repo.ListVariables) 1004 + m.Combo("/{variablename}"). 1005 + Get(reqToken(), reqOwner(), repo.GetVariable). 1006 + Delete(reqToken(), reqOwner(), repo.DeleteVariable). 1007 + Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable). 1008 + Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable) 991 1009 }) 992 1010 993 1011 m.Group("/runners", func() { ··· 1391 1409 m.Combo("/{secretname}"). 1392 1410 Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret). 1393 1411 Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret) 1412 + }) 1413 + 1414 + m.Group("/variables", func() { 1415 + m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables) 1416 + m.Combo("/{variablename}"). 1417 + Get(reqToken(), reqOrgOwnership(), org.GetVariable). 1418 + Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable). 1419 + Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable). 1420 + Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable) 1394 1421 }) 1395 1422 1396 1423 m.Group("/runners", func() {
+291
routers/api/v1/org/variables.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package org 5 + 6 + import ( 7 + "errors" 8 + "net/http" 9 + 10 + actions_model "code.gitea.io/gitea/models/actions" 11 + "code.gitea.io/gitea/models/db" 12 + api "code.gitea.io/gitea/modules/structs" 13 + "code.gitea.io/gitea/modules/util" 14 + "code.gitea.io/gitea/modules/web" 15 + "code.gitea.io/gitea/routers/api/v1/utils" 16 + actions_service "code.gitea.io/gitea/services/actions" 17 + "code.gitea.io/gitea/services/context" 18 + ) 19 + 20 + // ListVariables list org-level variables 21 + func ListVariables(ctx *context.APIContext) { 22 + // swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList 23 + // --- 24 + // summary: Get an org-level variables list 25 + // produces: 26 + // - application/json 27 + // parameters: 28 + // - name: org 29 + // in: path 30 + // description: name of the organization 31 + // type: string 32 + // required: true 33 + // - name: page 34 + // in: query 35 + // description: page number of results to return (1-based) 36 + // type: integer 37 + // - name: limit 38 + // in: query 39 + // description: page size of results 40 + // type: integer 41 + // responses: 42 + // "200": 43 + // "$ref": "#/responses/VariableList" 44 + // "400": 45 + // "$ref": "#/responses/error" 46 + // "404": 47 + // "$ref": "#/responses/notFound" 48 + 49 + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ 50 + OwnerID: ctx.Org.Organization.ID, 51 + ListOptions: utils.GetListOptions(ctx), 52 + }) 53 + if err != nil { 54 + ctx.Error(http.StatusInternalServerError, "FindVariables", err) 55 + return 56 + } 57 + 58 + variables := make([]*api.ActionVariable, len(vars)) 59 + for i, v := range vars { 60 + variables[i] = &api.ActionVariable{ 61 + OwnerID: v.OwnerID, 62 + RepoID: v.RepoID, 63 + Name: v.Name, 64 + Data: v.Data, 65 + } 66 + } 67 + 68 + ctx.SetTotalCountHeader(count) 69 + ctx.JSON(http.StatusOK, variables) 70 + } 71 + 72 + // GetVariable get an org-level variable 73 + func GetVariable(ctx *context.APIContext) { 74 + // swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable 75 + // --- 76 + // summary: Get an org-level variable 77 + // produces: 78 + // - application/json 79 + // parameters: 80 + // - name: org 81 + // in: path 82 + // description: name of the organization 83 + // type: string 84 + // required: true 85 + // - name: variablename 86 + // in: path 87 + // description: name of the variable 88 + // type: string 89 + // required: true 90 + // responses: 91 + // "200": 92 + // "$ref": "#/responses/ActionVariable" 93 + // "400": 94 + // "$ref": "#/responses/error" 95 + // "404": 96 + // "$ref": "#/responses/notFound" 97 + 98 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 99 + OwnerID: ctx.Org.Organization.ID, 100 + Name: ctx.Params("variablename"), 101 + }) 102 + if err != nil { 103 + if errors.Is(err, util.ErrNotExist) { 104 + ctx.Error(http.StatusNotFound, "GetVariable", err) 105 + } else { 106 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 107 + } 108 + return 109 + } 110 + 111 + variable := &api.ActionVariable{ 112 + OwnerID: v.OwnerID, 113 + RepoID: v.RepoID, 114 + Name: v.Name, 115 + Data: v.Data, 116 + } 117 + 118 + ctx.JSON(http.StatusOK, variable) 119 + } 120 + 121 + // DeleteVariable delete an org-level variable 122 + func DeleteVariable(ctx *context.APIContext) { 123 + // swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable 124 + // --- 125 + // summary: Delete an org-level variable 126 + // produces: 127 + // - application/json 128 + // parameters: 129 + // - name: org 130 + // in: path 131 + // description: name of the organization 132 + // type: string 133 + // required: true 134 + // - name: variablename 135 + // in: path 136 + // description: name of the variable 137 + // type: string 138 + // required: true 139 + // responses: 140 + // "200": 141 + // "$ref": "#/responses/ActionVariable" 142 + // "201": 143 + // description: response when deleting a variable 144 + // "204": 145 + // description: response when deleting a variable 146 + // "400": 147 + // "$ref": "#/responses/error" 148 + // "404": 149 + // "$ref": "#/responses/notFound" 150 + 151 + if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil { 152 + if errors.Is(err, util.ErrInvalidArgument) { 153 + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) 154 + } else if errors.Is(err, util.ErrNotExist) { 155 + ctx.Error(http.StatusNotFound, "DeleteVariableByName", err) 156 + } else { 157 + ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err) 158 + } 159 + return 160 + } 161 + 162 + ctx.Status(http.StatusNoContent) 163 + } 164 + 165 + // CreateVariable create an org-level variable 166 + func CreateVariable(ctx *context.APIContext) { 167 + // swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable 168 + // --- 169 + // summary: Create an org-level variable 170 + // consumes: 171 + // - application/json 172 + // produces: 173 + // - application/json 174 + // parameters: 175 + // - name: org 176 + // in: path 177 + // description: name of the organization 178 + // type: string 179 + // required: true 180 + // - name: variablename 181 + // in: path 182 + // description: name of the variable 183 + // type: string 184 + // required: true 185 + // - name: body 186 + // in: body 187 + // schema: 188 + // "$ref": "#/definitions/CreateVariableOption" 189 + // responses: 190 + // "201": 191 + // description: response when creating an org-level variable 192 + // "204": 193 + // description: response when creating an org-level variable 194 + // "400": 195 + // "$ref": "#/responses/error" 196 + // "404": 197 + // "$ref": "#/responses/notFound" 198 + 199 + opt := web.GetForm(ctx).(*api.CreateVariableOption) 200 + 201 + ownerID := ctx.Org.Organization.ID 202 + variableName := ctx.Params("variablename") 203 + 204 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 205 + OwnerID: ownerID, 206 + Name: variableName, 207 + }) 208 + if err != nil && !errors.Is(err, util.ErrNotExist) { 209 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 210 + return 211 + } 212 + if v != nil && v.ID > 0 { 213 + ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) 214 + return 215 + } 216 + 217 + if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil { 218 + if errors.Is(err, util.ErrInvalidArgument) { 219 + ctx.Error(http.StatusBadRequest, "CreateVariable", err) 220 + } else { 221 + ctx.Error(http.StatusInternalServerError, "CreateVariable", err) 222 + } 223 + return 224 + } 225 + 226 + ctx.Status(http.StatusNoContent) 227 + } 228 + 229 + // UpdateVariable update an org-level variable 230 + func UpdateVariable(ctx *context.APIContext) { 231 + // swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable 232 + // --- 233 + // summary: Update an org-level variable 234 + // consumes: 235 + // - application/json 236 + // produces: 237 + // - application/json 238 + // parameters: 239 + // - name: org 240 + // in: path 241 + // description: name of the organization 242 + // type: string 243 + // required: true 244 + // - name: variablename 245 + // in: path 246 + // description: name of the variable 247 + // type: string 248 + // required: true 249 + // - name: body 250 + // in: body 251 + // schema: 252 + // "$ref": "#/definitions/UpdateVariableOption" 253 + // responses: 254 + // "201": 255 + // description: response when updating an org-level variable 256 + // "204": 257 + // description: response when updating an org-level variable 258 + // "400": 259 + // "$ref": "#/responses/error" 260 + // "404": 261 + // "$ref": "#/responses/notFound" 262 + 263 + opt := web.GetForm(ctx).(*api.UpdateVariableOption) 264 + 265 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 266 + OwnerID: ctx.Org.Organization.ID, 267 + Name: ctx.Params("variablename"), 268 + }) 269 + if err != nil { 270 + if errors.Is(err, util.ErrNotExist) { 271 + ctx.Error(http.StatusNotFound, "GetVariable", err) 272 + } else { 273 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 274 + } 275 + return 276 + } 277 + 278 + if opt.Name == "" { 279 + opt.Name = ctx.Params("variablename") 280 + } 281 + if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil { 282 + if errors.Is(err, util.ErrInvalidArgument) { 283 + ctx.Error(http.StatusBadRequest, "UpdateVariable", err) 284 + } else { 285 + ctx.Error(http.StatusInternalServerError, "UpdateVariable", err) 286 + } 287 + return 288 + } 289 + 290 + ctx.Status(http.StatusNoContent) 291 + }
+296
routers/api/v1/repo/action.go
··· 7 7 "errors" 8 8 "net/http" 9 9 10 + actions_model "code.gitea.io/gitea/models/actions" 11 + "code.gitea.io/gitea/models/db" 10 12 api "code.gitea.io/gitea/modules/structs" 11 13 "code.gitea.io/gitea/modules/util" 12 14 "code.gitea.io/gitea/modules/web" 15 + "code.gitea.io/gitea/routers/api/v1/utils" 16 + actions_service "code.gitea.io/gitea/services/actions" 13 17 "code.gitea.io/gitea/services/context" 14 18 secret_service "code.gitea.io/gitea/services/secrets" 15 19 ) ··· 127 131 128 132 ctx.Status(http.StatusNoContent) 129 133 } 134 + 135 + // GetVariable get a repo-level variable 136 + func GetVariable(ctx *context.APIContext) { 137 + // swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable 138 + // --- 139 + // summary: Get a repo-level variable 140 + // produces: 141 + // - application/json 142 + // parameters: 143 + // - name: owner 144 + // in: path 145 + // description: name of the owner 146 + // type: string 147 + // required: true 148 + // - name: repo 149 + // in: path 150 + // description: name of the repository 151 + // type: string 152 + // required: true 153 + // - name: variablename 154 + // in: path 155 + // description: name of the variable 156 + // type: string 157 + // required: true 158 + // responses: 159 + // "200": 160 + // "$ref": "#/responses/ActionVariable" 161 + // "400": 162 + // "$ref": "#/responses/error" 163 + // "404": 164 + // "$ref": "#/responses/notFound" 165 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 166 + RepoID: ctx.Repo.Repository.ID, 167 + Name: ctx.Params("variablename"), 168 + }) 169 + if err != nil { 170 + if errors.Is(err, util.ErrNotExist) { 171 + ctx.Error(http.StatusNotFound, "GetVariable", err) 172 + } else { 173 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 174 + } 175 + return 176 + } 177 + 178 + variable := &api.ActionVariable{ 179 + OwnerID: v.OwnerID, 180 + RepoID: v.RepoID, 181 + Name: v.Name, 182 + Data: v.Data, 183 + } 184 + 185 + ctx.JSON(http.StatusOK, variable) 186 + } 187 + 188 + // DeleteVariable delete a repo-level variable 189 + func DeleteVariable(ctx *context.APIContext) { 190 + // swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable 191 + // --- 192 + // summary: Delete a repo-level variable 193 + // produces: 194 + // - application/json 195 + // parameters: 196 + // - name: owner 197 + // in: path 198 + // description: name of the owner 199 + // type: string 200 + // required: true 201 + // - name: repo 202 + // in: path 203 + // description: name of the repository 204 + // type: string 205 + // required: true 206 + // - name: variablename 207 + // in: path 208 + // description: name of the variable 209 + // type: string 210 + // required: true 211 + // responses: 212 + // "200": 213 + // "$ref": "#/responses/ActionVariable" 214 + // "201": 215 + // description: response when deleting a variable 216 + // "204": 217 + // description: response when deleting a variable 218 + // "400": 219 + // "$ref": "#/responses/error" 220 + // "404": 221 + // "$ref": "#/responses/notFound" 222 + 223 + if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.Params("variablename")); err != nil { 224 + if errors.Is(err, util.ErrInvalidArgument) { 225 + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) 226 + } else if errors.Is(err, util.ErrNotExist) { 227 + ctx.Error(http.StatusNotFound, "DeleteVariableByName", err) 228 + } else { 229 + ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err) 230 + } 231 + return 232 + } 233 + 234 + ctx.Status(http.StatusNoContent) 235 + } 236 + 237 + // CreateVariable create a repo-level variable 238 + func CreateVariable(ctx *context.APIContext) { 239 + // swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable 240 + // --- 241 + // summary: Create a repo-level variable 242 + // produces: 243 + // - application/json 244 + // parameters: 245 + // - name: owner 246 + // in: path 247 + // description: name of the owner 248 + // type: string 249 + // required: true 250 + // - name: repo 251 + // in: path 252 + // description: name of the repository 253 + // type: string 254 + // required: true 255 + // - name: variablename 256 + // in: path 257 + // description: name of the variable 258 + // type: string 259 + // required: true 260 + // - name: body 261 + // in: body 262 + // schema: 263 + // "$ref": "#/definitions/CreateVariableOption" 264 + // responses: 265 + // "201": 266 + // description: response when creating a repo-level variable 267 + // "204": 268 + // description: response when creating a repo-level variable 269 + // "400": 270 + // "$ref": "#/responses/error" 271 + // "404": 272 + // "$ref": "#/responses/notFound" 273 + 274 + opt := web.GetForm(ctx).(*api.CreateVariableOption) 275 + 276 + repoID := ctx.Repo.Repository.ID 277 + variableName := ctx.Params("variablename") 278 + 279 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 280 + RepoID: repoID, 281 + Name: variableName, 282 + }) 283 + if err != nil && !errors.Is(err, util.ErrNotExist) { 284 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 285 + return 286 + } 287 + if v != nil && v.ID > 0 { 288 + ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) 289 + return 290 + } 291 + 292 + if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value); err != nil { 293 + if errors.Is(err, util.ErrInvalidArgument) { 294 + ctx.Error(http.StatusBadRequest, "CreateVariable", err) 295 + } else { 296 + ctx.Error(http.StatusInternalServerError, "CreateVariable", err) 297 + } 298 + return 299 + } 300 + 301 + ctx.Status(http.StatusNoContent) 302 + } 303 + 304 + // UpdateVariable update a repo-level variable 305 + func UpdateVariable(ctx *context.APIContext) { 306 + // swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable 307 + // --- 308 + // summary: Update a repo-level variable 309 + // produces: 310 + // - application/json 311 + // parameters: 312 + // - name: owner 313 + // in: path 314 + // description: name of the owner 315 + // type: string 316 + // required: true 317 + // - name: repo 318 + // in: path 319 + // description: name of the repository 320 + // type: string 321 + // required: true 322 + // - name: variablename 323 + // in: path 324 + // description: name of the variable 325 + // type: string 326 + // required: true 327 + // - name: body 328 + // in: body 329 + // schema: 330 + // "$ref": "#/definitions/UpdateVariableOption" 331 + // responses: 332 + // "201": 333 + // description: response when updating a repo-level variable 334 + // "204": 335 + // description: response when updating a repo-level variable 336 + // "400": 337 + // "$ref": "#/responses/error" 338 + // "404": 339 + // "$ref": "#/responses/notFound" 340 + 341 + opt := web.GetForm(ctx).(*api.UpdateVariableOption) 342 + 343 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 344 + RepoID: ctx.Repo.Repository.ID, 345 + Name: ctx.Params("variablename"), 346 + }) 347 + if err != nil { 348 + if errors.Is(err, util.ErrNotExist) { 349 + ctx.Error(http.StatusNotFound, "GetVariable", err) 350 + } else { 351 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 352 + } 353 + return 354 + } 355 + 356 + if opt.Name == "" { 357 + opt.Name = ctx.Params("variablename") 358 + } 359 + if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil { 360 + if errors.Is(err, util.ErrInvalidArgument) { 361 + ctx.Error(http.StatusBadRequest, "UpdateVariable", err) 362 + } else { 363 + ctx.Error(http.StatusInternalServerError, "UpdateVariable", err) 364 + } 365 + return 366 + } 367 + 368 + ctx.Status(http.StatusNoContent) 369 + } 370 + 371 + // ListVariables list repo-level variables 372 + func ListVariables(ctx *context.APIContext) { 373 + // swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList 374 + // --- 375 + // summary: Get repo-level variables list 376 + // produces: 377 + // - application/json 378 + // parameters: 379 + // - name: owner 380 + // in: path 381 + // description: name of the owner 382 + // type: string 383 + // required: true 384 + // - name: repo 385 + // in: path 386 + // description: name of the repository 387 + // type: string 388 + // required: true 389 + // - name: page 390 + // in: query 391 + // description: page number of results to return (1-based) 392 + // type: integer 393 + // - name: limit 394 + // in: query 395 + // description: page size of results 396 + // type: integer 397 + // responses: 398 + // "200": 399 + // "$ref": "#/responses/VariableList" 400 + // "400": 401 + // "$ref": "#/responses/error" 402 + // "404": 403 + // "$ref": "#/responses/notFound" 404 + 405 + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ 406 + RepoID: ctx.Repo.Repository.ID, 407 + ListOptions: utils.GetListOptions(ctx), 408 + }) 409 + if err != nil { 410 + ctx.Error(http.StatusInternalServerError, "FindVariables", err) 411 + return 412 + } 413 + 414 + variables := make([]*api.ActionVariable, len(vars)) 415 + for i, v := range vars { 416 + variables[i] = &api.ActionVariable{ 417 + OwnerID: v.OwnerID, 418 + RepoID: v.RepoID, 419 + Name: v.Name, 420 + } 421 + } 422 + 423 + ctx.SetTotalCountHeader(count) 424 + ctx.JSON(http.StatusOK, variables) 425 + }
+10
routers/api/v1/repo/repo.go
··· 11 11 "strings" 12 12 "time" 13 13 14 + actions_model "code.gitea.io/gitea/models/actions" 14 15 activities_model "code.gitea.io/gitea/models/activities" 15 16 "code.gitea.io/gitea/models/db" 16 17 "code.gitea.io/gitea/models/organization" ··· 30 31 "code.gitea.io/gitea/modules/validation" 31 32 "code.gitea.io/gitea/modules/web" 32 33 "code.gitea.io/gitea/routers/api/v1/utils" 34 + actions_service "code.gitea.io/gitea/services/actions" 33 35 "code.gitea.io/gitea/services/context" 34 36 "code.gitea.io/gitea/services/convert" 35 37 "code.gitea.io/gitea/services/issue" ··· 1027 1029 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err) 1028 1030 return err 1029 1031 } 1032 + if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil { 1033 + log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) 1034 + } 1030 1035 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) 1031 1036 } else { 1032 1037 if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil { 1033 1038 log.Error("Tried to un-archive a repo: %s", err) 1034 1039 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err) 1035 1040 return err 1041 + } 1042 + if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) { 1043 + if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil { 1044 + log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) 1045 + } 1036 1046 } 1037 1047 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) 1038 1048 }
+14
routers/api/v1/swagger/action.go
··· 18 18 // in:body 19 19 Body api.Secret `json:"body"` 20 20 } 21 + 22 + // ActionVariable 23 + // swagger:response ActionVariable 24 + type swaggerResponseActionVariable struct { 25 + // in:body 26 + Body api.ActionVariable `json:"body"` 27 + } 28 + 29 + // VariableList 30 + // swagger:response VariableList 31 + type swaggerResponseVariableList struct { 32 + // in:body 33 + Body []api.ActionVariable `json:"body"` 34 + }
+6
routers/api/v1/swagger/options.go
··· 199 199 200 200 // in:body 201 201 CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption 202 + 203 + // in:body 204 + CreateVariableOption api.CreateVariableOption 205 + 206 + // in:body 207 + UpdateVariableOption api.UpdateVariableOption 202 208 }
+250
routers/api/v1/user/action.go
··· 7 7 "errors" 8 8 "net/http" 9 9 10 + actions_model "code.gitea.io/gitea/models/actions" 11 + "code.gitea.io/gitea/models/db" 10 12 api "code.gitea.io/gitea/modules/structs" 11 13 "code.gitea.io/gitea/modules/util" 12 14 "code.gitea.io/gitea/modules/web" 15 + "code.gitea.io/gitea/routers/api/v1/utils" 16 + actions_service "code.gitea.io/gitea/services/actions" 13 17 "code.gitea.io/gitea/services/context" 14 18 secret_service "code.gitea.io/gitea/services/secrets" 15 19 ) ··· 101 105 102 106 ctx.Status(http.StatusNoContent) 103 107 } 108 + 109 + // CreateVariable create a user-level variable 110 + func CreateVariable(ctx *context.APIContext) { 111 + // swagger:operation POST /user/actions/variables/{variablename} user createUserVariable 112 + // --- 113 + // summary: Create a user-level variable 114 + // consumes: 115 + // - application/json 116 + // produces: 117 + // - application/json 118 + // parameters: 119 + // - name: variablename 120 + // in: path 121 + // description: name of the variable 122 + // type: string 123 + // required: true 124 + // - name: body 125 + // in: body 126 + // schema: 127 + // "$ref": "#/definitions/CreateVariableOption" 128 + // responses: 129 + // "201": 130 + // description: response when creating a variable 131 + // "204": 132 + // description: response when creating a variable 133 + // "400": 134 + // "$ref": "#/responses/error" 135 + // "404": 136 + // "$ref": "#/responses/notFound" 137 + 138 + opt := web.GetForm(ctx).(*api.CreateVariableOption) 139 + 140 + ownerID := ctx.Doer.ID 141 + variableName := ctx.Params("variablename") 142 + 143 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 144 + OwnerID: ownerID, 145 + Name: variableName, 146 + }) 147 + if err != nil && !errors.Is(err, util.ErrNotExist) { 148 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 149 + return 150 + } 151 + if v != nil && v.ID > 0 { 152 + ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) 153 + return 154 + } 155 + 156 + if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil { 157 + if errors.Is(err, util.ErrInvalidArgument) { 158 + ctx.Error(http.StatusBadRequest, "CreateVariable", err) 159 + } else { 160 + ctx.Error(http.StatusInternalServerError, "CreateVariable", err) 161 + } 162 + return 163 + } 164 + 165 + ctx.Status(http.StatusNoContent) 166 + } 167 + 168 + // UpdateVariable update a user-level variable which is created by current doer 169 + func UpdateVariable(ctx *context.APIContext) { 170 + // swagger:operation PUT /user/actions/variables/{variablename} user updateUserVariable 171 + // --- 172 + // summary: Update a user-level variable which is created by current doer 173 + // consumes: 174 + // - application/json 175 + // produces: 176 + // - application/json 177 + // parameters: 178 + // - name: variablename 179 + // in: path 180 + // description: name of the variable 181 + // type: string 182 + // required: true 183 + // - name: body 184 + // in: body 185 + // schema: 186 + // "$ref": "#/definitions/UpdateVariableOption" 187 + // responses: 188 + // "201": 189 + // description: response when updating a variable 190 + // "204": 191 + // description: response when updating a variable 192 + // "400": 193 + // "$ref": "#/responses/error" 194 + // "404": 195 + // "$ref": "#/responses/notFound" 196 + 197 + opt := web.GetForm(ctx).(*api.UpdateVariableOption) 198 + 199 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 200 + OwnerID: ctx.Doer.ID, 201 + Name: ctx.Params("variablename"), 202 + }) 203 + if err != nil { 204 + if errors.Is(err, util.ErrNotExist) { 205 + ctx.Error(http.StatusNotFound, "GetVariable", err) 206 + } else { 207 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 208 + } 209 + return 210 + } 211 + 212 + if opt.Name == "" { 213 + opt.Name = ctx.Params("variablename") 214 + } 215 + if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil { 216 + if errors.Is(err, util.ErrInvalidArgument) { 217 + ctx.Error(http.StatusBadRequest, "UpdateVariable", err) 218 + } else { 219 + ctx.Error(http.StatusInternalServerError, "UpdateVariable", err) 220 + } 221 + return 222 + } 223 + 224 + ctx.Status(http.StatusNoContent) 225 + } 226 + 227 + // DeleteVariable delete a user-level variable which is created by current doer 228 + func DeleteVariable(ctx *context.APIContext) { 229 + // swagger:operation DELETE /user/actions/variables/{variablename} user deleteUserVariable 230 + // --- 231 + // summary: Delete a user-level variable which is created by current doer 232 + // produces: 233 + // - application/json 234 + // parameters: 235 + // - name: variablename 236 + // in: path 237 + // description: name of the variable 238 + // type: string 239 + // required: true 240 + // responses: 241 + // "201": 242 + // description: response when deleting a variable 243 + // "204": 244 + // description: response when deleting a variable 245 + // "400": 246 + // "$ref": "#/responses/error" 247 + // "404": 248 + // "$ref": "#/responses/notFound" 249 + 250 + if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.Params("variablename")); err != nil { 251 + if errors.Is(err, util.ErrInvalidArgument) { 252 + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) 253 + } else if errors.Is(err, util.ErrNotExist) { 254 + ctx.Error(http.StatusNotFound, "DeleteVariableByName", err) 255 + } else { 256 + ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err) 257 + } 258 + return 259 + } 260 + 261 + ctx.Status(http.StatusNoContent) 262 + } 263 + 264 + // GetVariable get a user-level variable which is created by current doer 265 + func GetVariable(ctx *context.APIContext) { 266 + // swagger:operation GET /user/actions/variables/{variablename} user getUserVariable 267 + // --- 268 + // summary: Get a user-level variable which is created by current doer 269 + // produces: 270 + // - application/json 271 + // parameters: 272 + // - name: variablename 273 + // in: path 274 + // description: name of the variable 275 + // type: string 276 + // required: true 277 + // responses: 278 + // "200": 279 + // "$ref": "#/responses/ActionVariable" 280 + // "400": 281 + // "$ref": "#/responses/error" 282 + // "404": 283 + // "$ref": "#/responses/notFound" 284 + 285 + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ 286 + OwnerID: ctx.Doer.ID, 287 + Name: ctx.Params("variablename"), 288 + }) 289 + if err != nil { 290 + if errors.Is(err, util.ErrNotExist) { 291 + ctx.Error(http.StatusNotFound, "GetVariable", err) 292 + } else { 293 + ctx.Error(http.StatusInternalServerError, "GetVariable", err) 294 + } 295 + return 296 + } 297 + 298 + variable := &api.ActionVariable{ 299 + OwnerID: v.OwnerID, 300 + RepoID: v.RepoID, 301 + Name: v.Name, 302 + Data: v.Data, 303 + } 304 + 305 + ctx.JSON(http.StatusOK, variable) 306 + } 307 + 308 + // ListVariables list user-level variables 309 + func ListVariables(ctx *context.APIContext) { 310 + // swagger:operation GET /user/actions/variables user getUserVariablesList 311 + // --- 312 + // summary: Get the user-level list of variables which is created by current doer 313 + // produces: 314 + // - application/json 315 + // parameters: 316 + // - name: page 317 + // in: query 318 + // description: page number of results to return (1-based) 319 + // type: integer 320 + // - name: limit 321 + // in: query 322 + // description: page size of results 323 + // type: integer 324 + // responses: 325 + // "200": 326 + // "$ref": "#/responses/VariableList" 327 + // "400": 328 + // "$ref": "#/responses/error" 329 + // "404": 330 + // "$ref": "#/responses/notFound" 331 + 332 + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ 333 + OwnerID: ctx.Doer.ID, 334 + ListOptions: utils.GetListOptions(ctx), 335 + }) 336 + if err != nil { 337 + ctx.Error(http.StatusInternalServerError, "FindVariables", err) 338 + return 339 + } 340 + 341 + variables := make([]*api.ActionVariable, len(vars)) 342 + for i, v := range vars { 343 + variables[i] = &api.ActionVariable{ 344 + OwnerID: v.OwnerID, 345 + RepoID: v.RepoID, 346 + Name: v.Name, 347 + Data: v.Data, 348 + } 349 + } 350 + 351 + ctx.SetTotalCountHeader(count) 352 + ctx.JSON(http.StatusOK, variables) 353 + }
+3 -2
routers/api/v1/user/gpg_key.go
··· 10 10 11 11 asymkey_model "code.gitea.io/gitea/models/asymkey" 12 12 "code.gitea.io/gitea/models/db" 13 + user_model "code.gitea.io/gitea/models/user" 13 14 "code.gitea.io/gitea/modules/setting" 14 15 api "code.gitea.io/gitea/modules/structs" 15 16 "code.gitea.io/gitea/modules/web" ··· 133 134 134 135 // CreateUserGPGKey creates new GPG key to given user by ID. 135 136 func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) { 136 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { 137 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { 137 138 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) 138 139 return 139 140 } ··· 274 275 // "404": 275 276 // "$ref": "#/responses/notFound" 276 277 277 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { 278 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { 278 279 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) 279 280 return 280 281 }
+2 -2
routers/api/v1/user/key.go
··· 199 199 200 200 // CreateUserPublicKey creates new public key to given user by ID. 201 201 func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) { 202 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { 202 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { 203 203 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) 204 204 return 205 205 } ··· 269 269 // "404": 270 270 // "$ref": "#/responses/notFound" 271 271 272 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { 272 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { 273 273 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) 274 274 return 275 275 }
+12
routers/web/repo/setting/setting.go
··· 13 13 "time" 14 14 15 15 "code.gitea.io/gitea/models" 16 + actions_model "code.gitea.io/gitea/models/actions" 16 17 "code.gitea.io/gitea/models/db" 17 18 "code.gitea.io/gitea/models/organization" 18 19 repo_model "code.gitea.io/gitea/models/repo" ··· 29 30 "code.gitea.io/gitea/modules/util" 30 31 "code.gitea.io/gitea/modules/validation" 31 32 "code.gitea.io/gitea/modules/web" 33 + actions_service "code.gitea.io/gitea/services/actions" 32 34 asymkey_service "code.gitea.io/gitea/services/asymkey" 33 35 "code.gitea.io/gitea/services/context" 34 36 "code.gitea.io/gitea/services/forms" ··· 929 931 return 930 932 } 931 933 934 + if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil { 935 + log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) 936 + } 937 + 932 938 ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) 933 939 934 940 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) ··· 945 951 ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error")) 946 952 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 947 953 return 954 + } 955 + 956 + if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) { 957 + if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil { 958 + log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) 959 + } 948 960 } 949 961 950 962 ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
+7 -60
routers/web/shared/actions/variables.go
··· 4 4 package actions 5 5 6 6 import ( 7 - "errors" 8 - "regexp" 9 - "strings" 10 - 11 7 actions_model "code.gitea.io/gitea/models/actions" 12 8 "code.gitea.io/gitea/models/db" 13 9 "code.gitea.io/gitea/modules/log" 14 10 "code.gitea.io/gitea/modules/web" 11 + actions_service "code.gitea.io/gitea/services/actions" 15 12 "code.gitea.io/gitea/services/context" 16 13 "code.gitea.io/gitea/services/forms" 17 - secret_service "code.gitea.io/gitea/services/secrets" 18 14 ) 19 15 20 16 func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { ··· 29 25 ctx.Data["Variables"] = variables 30 26 } 31 27 32 - // some regular expression of `variables` and `secrets` 33 - // reference to: 34 - // https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables 35 - // https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets 36 - var ( 37 - forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI") 38 - ) 39 - 40 - func envNameCIRegexMatch(name string) error { 41 - if forbiddenEnvNameCIRx.MatchString(name) { 42 - log.Error("Env Name cannot be ci") 43 - return errors.New("env name cannot be ci") 44 - } 45 - return nil 46 - } 47 - 48 28 func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) { 49 29 form := web.GetForm(ctx).(*forms.EditVariableForm) 50 30 51 - if err := secret_service.ValidateName(form.Name); err != nil { 52 - ctx.JSONError(err.Error()) 53 - return 54 - } 55 - 56 - if err := envNameCIRegexMatch(form.Name); err != nil { 57 - ctx.JSONError(err.Error()) 58 - return 59 - } 60 - 61 - v, err := actions_model.InsertVariable(ctx, ownerID, repoID, form.Name, ReserveLineBreakForTextarea(form.Data)) 31 + v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data) 62 32 if err != nil { 63 - log.Error("InsertVariable error: %v", err) 33 + log.Error("CreateVariable: %v", err) 64 34 ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) 65 35 return 66 36 } 37 + 67 38 ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name)) 68 39 ctx.JSONRedirect(redirectURL) 69 40 } ··· 72 43 id := ctx.ParamsInt64(":variable_id") 73 44 form := web.GetForm(ctx).(*forms.EditVariableForm) 74 45 75 - if err := secret_service.ValidateName(form.Name); err != nil { 76 - ctx.JSONError(err.Error()) 77 - return 78 - } 79 - 80 - if err := envNameCIRegexMatch(form.Name); err != nil { 81 - ctx.JSONError(err.Error()) 82 - return 83 - } 84 - 85 - ok, err := actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{ 86 - ID: id, 87 - Name: strings.ToUpper(form.Name), 88 - Data: ReserveLineBreakForTextarea(form.Data), 89 - }) 90 - if err != nil || !ok { 91 - log.Error("UpdateVariable error: %v", err) 46 + if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok { 47 + log.Error("UpdateVariable: %v", err) 92 48 ctx.JSONError(ctx.Tr("actions.variables.update.failed")) 93 49 return 94 50 } ··· 99 55 func DeleteVariable(ctx *context.Context, redirectURL string) { 100 56 id := ctx.ParamsInt64(":variable_id") 101 57 102 - if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil { 58 + if err := actions_service.DeleteVariableByID(ctx, id); err != nil { 103 59 log.Error("Delete variable [%d] failed: %v", id, err) 104 60 ctx.JSONError(ctx.Tr("actions.variables.deletion.failed")) 105 61 return ··· 107 63 ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success")) 108 64 ctx.JSONRedirect(redirectURL) 109 65 } 110 - 111 - func ReserveLineBreakForTextarea(input string) string { 112 - // Since the content is from a form which is a textarea, the line endings are \r\n. 113 - // It's a standard behavior of HTML. 114 - // But we want to store them as \n like what GitHub does. 115 - // And users are unlikely to really need to keep the \r. 116 - // Other than this, we should respect the original content, even leading or trailing spaces. 117 - return strings.ReplaceAll(input, "\r\n", "\n") 118 - }
+2 -2
routers/web/shared/secrets/secrets.go
··· 7 7 "code.gitea.io/gitea/models/db" 8 8 secret_model "code.gitea.io/gitea/models/secret" 9 9 "code.gitea.io/gitea/modules/log" 10 + "code.gitea.io/gitea/modules/util" 10 11 "code.gitea.io/gitea/modules/web" 11 - "code.gitea.io/gitea/routers/web/shared/actions" 12 12 "code.gitea.io/gitea/services/context" 13 13 "code.gitea.io/gitea/services/forms" 14 14 secret_service "code.gitea.io/gitea/services/secrets" ··· 27 27 func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) { 28 28 form := web.GetForm(ctx).(*forms.AddSecretForm) 29 29 30 - s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, actions.ReserveLineBreakForTextarea(form.Data)) 30 + s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data)) 31 31 if err != nil { 32 32 log.Error("CreateOrUpdateSecret failed: %v", err) 33 33 ctx.JSONError(ctx.Tr("secrets.creation.failed"))
+2 -2
routers/web/user/setting/account.go
··· 244 244 245 245 // DeleteAccount render user suicide page and response for delete user himself 246 246 func DeleteAccount(ctx *context.Context) { 247 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) { 247 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) { 248 248 ctx.Error(http.StatusNotFound) 249 249 return 250 250 } ··· 328 328 ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference 329 329 ctx.Data["ActivationsPending"] = pendingActivation 330 330 ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm 331 - ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures 331 + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) 332 332 333 333 if setting.Service.UserDeleteWithCommentsMaxTime != 0 { 334 334 ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
+7 -6
routers/web/user/setting/keys.go
··· 10 10 11 11 asymkey_model "code.gitea.io/gitea/models/asymkey" 12 12 "code.gitea.io/gitea/models/db" 13 + user_model "code.gitea.io/gitea/models/user" 13 14 "code.gitea.io/gitea/modules/base" 14 15 "code.gitea.io/gitea/modules/setting" 15 16 "code.gitea.io/gitea/modules/web" ··· 78 79 ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) 79 80 ctx.Redirect(setting.AppSubURL + "/user/settings/keys") 80 81 case "gpg": 81 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { 82 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { 82 83 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) 83 84 return 84 85 } ··· 159 160 ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID)) 160 161 ctx.Redirect(setting.AppSubURL + "/user/settings/keys") 161 162 case "ssh": 162 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { 163 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { 163 164 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) 164 165 return 165 166 } ··· 203 204 ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) 204 205 ctx.Redirect(setting.AppSubURL + "/user/settings/keys") 205 206 case "verify_ssh": 206 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { 207 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { 207 208 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) 208 209 return 209 210 } ··· 240 241 func DeleteKey(ctx *context.Context) { 241 242 switch ctx.FormString("type") { 242 243 case "gpg": 243 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { 244 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { 244 245 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) 245 246 return 246 247 } ··· 250 251 ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) 251 252 } 252 253 case "ssh": 253 - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { 254 + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { 254 255 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) 255 256 return 256 257 } ··· 333 334 334 335 ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg") 335 336 ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh") 336 - ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures 337 + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) 337 338 }
+2 -2
services/actions/notifier_helper.go
··· 119 119 log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name) 120 120 return nil 121 121 } 122 - if input.Repo.IsEmpty { 122 + if input.Repo.IsEmpty || input.Repo.IsArchived { 123 123 return nil 124 124 } 125 125 if unit_model.TypeActions.UnitGlobalDisabled() { ··· 536 536 537 537 // DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks 538 538 func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error { 539 - if repo.IsEmpty { 539 + if repo.IsEmpty || repo.IsArchived { 540 540 return nil 541 541 } 542 542
+5
services/actions/schedule_tasks.go
··· 66 66 } 67 67 } 68 68 69 + if row.Repo.IsArchived { 70 + // Skip if the repo is archived 71 + continue 72 + } 73 + 69 74 cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions) 70 75 if err != nil { 71 76 if repo_model.IsErrUnitTypeNotExist(err) {
+100
services/actions/variables.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package actions 5 + 6 + import ( 7 + "context" 8 + "regexp" 9 + "strings" 10 + 11 + actions_model "code.gitea.io/gitea/models/actions" 12 + "code.gitea.io/gitea/modules/log" 13 + "code.gitea.io/gitea/modules/util" 14 + secret_service "code.gitea.io/gitea/services/secrets" 15 + ) 16 + 17 + func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*actions_model.ActionVariable, error) { 18 + if err := secret_service.ValidateName(name); err != nil { 19 + return nil, err 20 + } 21 + 22 + if err := envNameCIRegexMatch(name); err != nil { 23 + return nil, err 24 + } 25 + 26 + v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data)) 27 + if err != nil { 28 + return nil, err 29 + } 30 + 31 + return v, nil 32 + } 33 + 34 + func UpdateVariable(ctx context.Context, variableID int64, name, data string) (bool, error) { 35 + if err := secret_service.ValidateName(name); err != nil { 36 + return false, err 37 + } 38 + 39 + if err := envNameCIRegexMatch(name); err != nil { 40 + return false, err 41 + } 42 + 43 + return actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{ 44 + ID: variableID, 45 + Name: strings.ToUpper(name), 46 + Data: util.ReserveLineBreakForTextarea(data), 47 + }) 48 + } 49 + 50 + func DeleteVariableByID(ctx context.Context, variableID int64) error { 51 + return actions_model.DeleteVariable(ctx, variableID) 52 + } 53 + 54 + func DeleteVariableByName(ctx context.Context, ownerID, repoID int64, name string) error { 55 + if err := secret_service.ValidateName(name); err != nil { 56 + return err 57 + } 58 + 59 + if err := envNameCIRegexMatch(name); err != nil { 60 + return err 61 + } 62 + 63 + v, err := GetVariable(ctx, actions_model.FindVariablesOpts{ 64 + OwnerID: ownerID, 65 + RepoID: repoID, 66 + Name: name, 67 + }) 68 + if err != nil { 69 + return err 70 + } 71 + 72 + return actions_model.DeleteVariable(ctx, v.ID) 73 + } 74 + 75 + func GetVariable(ctx context.Context, opts actions_model.FindVariablesOpts) (*actions_model.ActionVariable, error) { 76 + vars, err := actions_model.FindVariables(ctx, opts) 77 + if err != nil { 78 + return nil, err 79 + } 80 + if len(vars) != 1 { 81 + return nil, util.NewNotExistErrorf("variable not found") 82 + } 83 + return vars[0], nil 84 + } 85 + 86 + // some regular expression of `variables` and `secrets` 87 + // reference to: 88 + // https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables 89 + // https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets 90 + var ( 91 + forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI") 92 + ) 93 + 94 + func envNameCIRegexMatch(name string) error { 95 + if forbiddenEnvNameCIRx.MatchString(name) { 96 + log.Error("Env Name cannot be ci") 97 + return util.NewInvalidArgumentErrorf("env name cannot be ci") 98 + } 99 + return nil 100 + }
+4 -4
services/pull/pull.go
··· 967 967 for _, commit := range prInfo.Commits { 968 968 var committerOrAuthorName string 969 969 var commitTime time.Time 970 - if commit.Committer != nil { 971 - committerOrAuthorName = commit.Committer.Name 972 - commitTime = commit.Committer.When 973 - } else { 970 + if commit.Author != nil { 974 971 committerOrAuthorName = commit.Author.Name 975 972 commitTime = commit.Author.When 973 + } else { 974 + committerOrAuthorName = commit.Committer.Name 975 + commitTime = commit.Committer.When 976 976 } 977 977 978 978 commits = append(commits, CommitInfo{
+246
stylelint.config.js
··· 1 + import {fileURLToPath} from 'node:url'; 2 + 3 + const cssVarFiles = [ 4 + fileURLToPath(new URL('web_src/css/base.css', import.meta.url)), 5 + fileURLToPath(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url)), 6 + fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)), 7 + ]; 8 + 9 + /** @type {import('stylelint').Config} */ 10 + export default { 11 + plugins: [ 12 + 'stylelint-declaration-strict-value', 13 + 'stylelint-declaration-block-no-ignored-properties', 14 + 'stylelint-value-no-unknown-custom-properties', 15 + '@stylistic/stylelint-plugin', 16 + ], 17 + ignoreFiles: [ 18 + '**/*.go', 19 + '/web_src/fomantic', 20 + ], 21 + overrides: [ 22 + { 23 + files: ['**/chroma/*', '**/codemirror/*', '**/standalone/*', '**/console.css', 'font_i18n.css'], 24 + rules: { 25 + 'scale-unlimited/declaration-strict-value': null, 26 + }, 27 + }, 28 + { 29 + files: ['**/chroma/*', '**/codemirror/*'], 30 + rules: { 31 + 'block-no-empty': null, 32 + }, 33 + }, 34 + { 35 + files: ['**/*.vue'], 36 + customSyntax: 'postcss-html', 37 + }, 38 + ], 39 + rules: { 40 + '@stylistic/at-rule-name-case': null, 41 + '@stylistic/at-rule-name-newline-after': null, 42 + '@stylistic/at-rule-name-space-after': null, 43 + '@stylistic/at-rule-semicolon-newline-after': null, 44 + '@stylistic/at-rule-semicolon-space-before': null, 45 + '@stylistic/block-closing-brace-empty-line-before': null, 46 + '@stylistic/block-closing-brace-newline-after': null, 47 + '@stylistic/block-closing-brace-newline-before': null, 48 + '@stylistic/block-closing-brace-space-after': null, 49 + '@stylistic/block-closing-brace-space-before': null, 50 + '@stylistic/block-opening-brace-newline-after': null, 51 + '@stylistic/block-opening-brace-newline-before': null, 52 + '@stylistic/block-opening-brace-space-after': null, 53 + '@stylistic/block-opening-brace-space-before': 'always', 54 + '@stylistic/color-hex-case': 'lower', 55 + '@stylistic/declaration-bang-space-after': 'never', 56 + '@stylistic/declaration-bang-space-before': null, 57 + '@stylistic/declaration-block-semicolon-newline-after': null, 58 + '@stylistic/declaration-block-semicolon-newline-before': null, 59 + '@stylistic/declaration-block-semicolon-space-after': null, 60 + '@stylistic/declaration-block-semicolon-space-before': 'never', 61 + '@stylistic/declaration-block-trailing-semicolon': null, 62 + '@stylistic/declaration-colon-newline-after': null, 63 + '@stylistic/declaration-colon-space-after': null, 64 + '@stylistic/declaration-colon-space-before': 'never', 65 + '@stylistic/function-comma-newline-after': null, 66 + '@stylistic/function-comma-newline-before': null, 67 + '@stylistic/function-comma-space-after': null, 68 + '@stylistic/function-comma-space-before': null, 69 + '@stylistic/function-max-empty-lines': 0, 70 + '@stylistic/function-parentheses-newline-inside': 'never-multi-line', 71 + '@stylistic/function-parentheses-space-inside': null, 72 + '@stylistic/function-whitespace-after': null, 73 + '@stylistic/indentation': 2, 74 + '@stylistic/linebreaks': null, 75 + '@stylistic/max-empty-lines': 1, 76 + '@stylistic/max-line-length': null, 77 + '@stylistic/media-feature-colon-space-after': null, 78 + '@stylistic/media-feature-colon-space-before': 'never', 79 + '@stylistic/media-feature-name-case': null, 80 + '@stylistic/media-feature-parentheses-space-inside': null, 81 + '@stylistic/media-feature-range-operator-space-after': 'always', 82 + '@stylistic/media-feature-range-operator-space-before': 'always', 83 + '@stylistic/media-query-list-comma-newline-after': null, 84 + '@stylistic/media-query-list-comma-newline-before': null, 85 + '@stylistic/media-query-list-comma-space-after': null, 86 + '@stylistic/media-query-list-comma-space-before': null, 87 + '@stylistic/named-grid-areas-alignment': null, 88 + '@stylistic/no-empty-first-line': null, 89 + '@stylistic/no-eol-whitespace': true, 90 + '@stylistic/no-extra-semicolons': true, 91 + '@stylistic/no-missing-end-of-source-newline': null, 92 + '@stylistic/number-leading-zero': null, 93 + '@stylistic/number-no-trailing-zeros': null, 94 + '@stylistic/property-case': 'lower', 95 + '@stylistic/selector-attribute-brackets-space-inside': null, 96 + '@stylistic/selector-attribute-operator-space-after': null, 97 + '@stylistic/selector-attribute-operator-space-before': null, 98 + '@stylistic/selector-combinator-space-after': null, 99 + '@stylistic/selector-combinator-space-before': null, 100 + '@stylistic/selector-descendant-combinator-no-non-space': null, 101 + '@stylistic/selector-list-comma-newline-after': null, 102 + '@stylistic/selector-list-comma-newline-before': null, 103 + '@stylistic/selector-list-comma-space-after': 'always-single-line', 104 + '@stylistic/selector-list-comma-space-before': 'never-single-line', 105 + '@stylistic/selector-max-empty-lines': 0, 106 + '@stylistic/selector-pseudo-class-case': 'lower', 107 + '@stylistic/selector-pseudo-class-parentheses-space-inside': 'never', 108 + '@stylistic/selector-pseudo-element-case': 'lower', 109 + '@stylistic/string-quotes': 'double', 110 + '@stylistic/unicode-bom': null, 111 + '@stylistic/unit-case': 'lower', 112 + '@stylistic/value-list-comma-newline-after': null, 113 + '@stylistic/value-list-comma-newline-before': null, 114 + '@stylistic/value-list-comma-space-after': null, 115 + '@stylistic/value-list-comma-space-before': null, 116 + '@stylistic/value-list-max-empty-lines': 0, 117 + 'alpha-value-notation': null, 118 + 'annotation-no-unknown': true, 119 + 'at-rule-allowed-list': null, 120 + 'at-rule-disallowed-list': null, 121 + 'at-rule-empty-line-before': null, 122 + 'at-rule-no-unknown': [true, {ignoreAtRules: ['tailwind']}], 123 + 'at-rule-no-vendor-prefix': true, 124 + 'at-rule-property-required-list': null, 125 + 'block-no-empty': true, 126 + 'color-function-notation': null, 127 + 'color-hex-alpha': null, 128 + 'color-hex-length': null, 129 + 'color-named': null, 130 + 'color-no-hex': null, 131 + 'color-no-invalid-hex': true, 132 + 'comment-empty-line-before': null, 133 + 'comment-no-empty': true, 134 + 'comment-pattern': null, 135 + 'comment-whitespace-inside': null, 136 + 'comment-word-disallowed-list': null, 137 + 'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}], 138 + 'custom-media-pattern': null, 139 + 'custom-property-empty-line-before': null, 140 + 'custom-property-no-missing-var-function': true, 141 + 'custom-property-pattern': null, 142 + 'declaration-block-no-duplicate-custom-properties': true, 143 + 'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}], 144 + 'declaration-block-no-redundant-longhand-properties': null, 145 + 'declaration-block-no-shorthand-property-overrides': null, 146 + 'declaration-block-single-line-max-declarations': null, 147 + 'declaration-empty-line-before': null, 148 + 'declaration-no-important': null, 149 + 'declaration-property-max-values': null, 150 + 'declaration-property-unit-allowed-list': null, 151 + 'declaration-property-unit-disallowed-list': {'line-height': ['em']}, 152 + 'declaration-property-value-allowed-list': null, 153 + 'declaration-property-value-disallowed-list': null, 154 + 'declaration-property-value-no-unknown': true, 155 + 'font-family-name-quotes': 'always-where-recommended', 156 + 'font-family-no-duplicate-names': true, 157 + 'font-family-no-missing-generic-family-keyword': true, 158 + 'font-weight-notation': null, 159 + 'function-allowed-list': null, 160 + 'function-calc-no-unspaced-operator': true, 161 + 'function-disallowed-list': null, 162 + 'function-linear-gradient-no-nonstandard-direction': true, 163 + 'function-name-case': 'lower', 164 + 'function-no-unknown': true, 165 + 'function-url-no-scheme-relative': null, 166 + 'function-url-quotes': 'always', 167 + 'function-url-scheme-allowed-list': null, 168 + 'function-url-scheme-disallowed-list': null, 169 + 'hue-degree-notation': null, 170 + 'import-notation': 'string', 171 + 'keyframe-block-no-duplicate-selectors': true, 172 + 'keyframe-declaration-no-important': true, 173 + 'keyframe-selector-notation': null, 174 + 'keyframes-name-pattern': null, 175 + 'length-zero-no-unit': [true, {ignore: ['custom-properties']}, {ignoreFunctions: ['var']}], 176 + 'max-nesting-depth': null, 177 + 'media-feature-name-allowed-list': null, 178 + 'media-feature-name-disallowed-list': null, 179 + 'media-feature-name-no-unknown': true, 180 + 'media-feature-name-no-vendor-prefix': true, 181 + 'media-feature-name-unit-allowed-list': null, 182 + 'media-feature-name-value-allowed-list': null, 183 + 'media-feature-name-value-no-unknown': true, 184 + 'media-feature-range-notation': null, 185 + 'media-query-no-invalid': true, 186 + 'named-grid-areas-no-invalid': true, 187 + 'no-descending-specificity': null, 188 + 'no-duplicate-at-import-rules': true, 189 + 'no-duplicate-selectors': true, 190 + 'no-empty-source': true, 191 + 'no-invalid-double-slash-comments': true, 192 + 'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}], 193 + 'no-irregular-whitespace': true, 194 + 'no-unknown-animations': null, 195 + 'no-unknown-custom-properties': null, 196 + 'number-max-precision': null, 197 + 'plugin/declaration-block-no-ignored-properties': true, 198 + 'property-allowed-list': null, 199 + 'property-disallowed-list': null, 200 + 'property-no-unknown': true, 201 + 'property-no-vendor-prefix': null, 202 + 'rule-empty-line-before': null, 203 + 'rule-selector-property-disallowed-list': null, 204 + 'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: false, disableFix: true, expandShorthand: true}], 205 + 'selector-anb-no-unmatchable': true, 206 + 'selector-attribute-name-disallowed-list': null, 207 + 'selector-attribute-operator-allowed-list': null, 208 + 'selector-attribute-operator-disallowed-list': null, 209 + 'selector-attribute-quotes': 'always', 210 + 'selector-class-pattern': null, 211 + 'selector-combinator-allowed-list': null, 212 + 'selector-combinator-disallowed-list': null, 213 + 'selector-disallowed-list': null, 214 + 'selector-id-pattern': null, 215 + 'selector-max-attribute': null, 216 + 'selector-max-class': null, 217 + 'selector-max-combinators': null, 218 + 'selector-max-compound-selectors': null, 219 + 'selector-max-id': null, 220 + 'selector-max-pseudo-class': null, 221 + 'selector-max-specificity': null, 222 + 'selector-max-type': null, 223 + 'selector-max-universal': null, 224 + 'selector-nested-pattern': null, 225 + 'selector-no-qualifying-type': null, 226 + 'selector-no-vendor-prefix': true, 227 + 'selector-not-notation': null, 228 + 'selector-pseudo-class-allowed-list': null, 229 + 'selector-pseudo-class-disallowed-list': null, 230 + 'selector-pseudo-class-no-unknown': true, 231 + 'selector-pseudo-element-allowed-list': null, 232 + 'selector-pseudo-element-colon-notation': 'double', 233 + 'selector-pseudo-element-disallowed-list': null, 234 + 'selector-pseudo-element-no-unknown': true, 235 + 'selector-type-case': 'lower', 236 + 'selector-type-no-unknown': [true, {ignore: ['custom-elements']}], 237 + 'shorthand-property-no-redundant-values': true, 238 + 'string-no-newline': true, 239 + 'time-min-milliseconds': null, 240 + 'unit-allowed-list': null, 241 + 'unit-disallowed-list': null, 242 + 'unit-no-unknown': true, 243 + 'value-keyword-case': null, 244 + 'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}], 245 + }, 246 + };
+2 -2
templates/admin/config_settings.tmpl
··· 7 7 <dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt> 8 8 <dd> 9 9 <div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}"> 10 - <input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}> 10 + <input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}><label></label> 11 11 </div> 12 12 </dd> 13 13 <div class="divider"></div> 14 14 <dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt> 15 15 <dd> 16 16 <div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}"> 17 - <input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}> 17 + <input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label> 18 18 </div> 19 19 </dd> 20 20 </dl>
+3 -1
templates/base/footer_content.tmpl
··· 1 1 <footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}"> 2 2 <div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}"> 3 - <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a> 3 + {{if ShowFooterPoweredBy}} 4 + <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a> 5 + {{end}} 4 6 {{if (or .ShowFooterVersion .PageIsAdmin)}} 5 7 {{ctx.Locale.Tr "version"}}: 6 8 {{if .IsAdmin}}
+1 -1
templates/devtest/gitea-ui.tmpl
··· 102 102 103 103 <div> 104 104 <h1>Loading</h1> 105 - <div class="is-loading small-loading-icon tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div> 105 + <div class="is-loading loading-icon-2px tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div> 106 106 <div class="is-loading tw-border tw-border-secondary tw-py-4"> 107 107 <p>loading ...</p> 108 108 <p>loading ...</p>
+4 -4
templates/projects/view.tmpl
··· 42 42 43 43 <div class="field color-field"> 44 44 <label for="new_project_column_color_picker">{{ctx.Locale.Tr "repo.projects.column.color"}}</label> 45 - <div class="color picker column"> 46 - <input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color"> 45 + <div class="js-color-picker-input column"> 46 + <input maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color"> 47 47 {{template "repo/issue/label_precolors"}} 48 48 </div> 49 49 </div> ··· 114 114 115 115 <div class="field color-field"> 116 116 <label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label> 117 - <div class="color picker column"> 118 - <input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}"> 117 + <div class="js-color-picker-input column"> 118 + <input maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}"> 119 119 {{template "repo/issue/label_precolors"}} 120 120 </div> 121 121 </div>
+2 -2
templates/repo/commit_page.tmpl
··· 164 164 {{end}} 165 165 {{end}} 166 166 </div> 167 - <div class="ui horizontal list tw-flex tw-items-center"> 167 + <div class="tw-flex tw-items-center"> 168 168 {{if .Parents}} 169 - <div class="item"> 169 + <div> 170 170 <span>{{ctx.Locale.Tr "repo.diff.parent"}}</span> 171 171 {{range .Parents}} 172 172 {{if $.PageIsWiki}}
+1 -1
templates/repo/commits_list.tmpl
··· 16 16 <td class="author tw-flex"> 17 17 {{$userName := .Author.Name}} 18 18 {{if .User}} 19 - {{if .User.FullName}} 19 + {{if and .User.FullName DefaultShowFullName}} 20 20 {{$userName = .User.FullName}} 21 21 {{end}} 22 22 {{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>
+1 -1
templates/repo/graph/commits.tmpl
··· 61 61 <span class="author tw-flex tw-items-center tw-mr-2"> 62 62 {{$userName := $commit.Commit.Author.Name}} 63 63 {{if $commit.User}} 64 - {{if $commit.User.FullName}} 64 + {{if and $commit.User.FullName DefaultShowFullName}} 65 65 {{$userName = $commit.User.FullName}} 66 66 {{end}} 67 67 <span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
+11 -12
templates/repo/home.tmpl
··· 18 18 </div> 19 19 </form> 20 20 </div> 21 - <div class="tw-flex tw-items-center tw-flex-wrap tw-gap-1" id="repo-topics"> 22 - {{range .Topics}}<a class="ui repo-topic large label topic tw-m-0" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}} 21 + <div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" id="repo-topics"> 22 + {{/* it should match the code in issue-home.js */}} 23 + {{range .Topics}}<a class="repo-topic ui large label" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}} 23 24 {{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}} 24 25 </div> 25 26 {{end}} 26 27 {{if and .Permission.IsAdmin (not .Repository.IsArchived)}} 27 - <div class="ui form tw-hidden tw-flex tw-flex-col tw-mt-4" id="topic_edit"> 28 - <div class="field tw-flex-1 tw-mb-1"> 29 - <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"}}"> 30 - <input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}"> 31 - {{range .Topics}} 32 - {{/* keey the same layout as Fomantic UI generated labels */}} 33 - <a class="ui label transition visible tw-cursor-default tw-inline-block" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a> 34 - {{end}} 35 - <div class="text"></div> 36 - </div> 28 + <div class="ui form tw-hidden tw-flex tw-gap-2 tw-my-2" id="topic_edit"> 29 + <div class="ui fluid multiple search selection dropdown tw-flex-wrap tw-flex-1"> 30 + <input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}"> 31 + {{range .Topics}} 32 + {{/* keep the same layout as Fomantic UI generated labels */}} 33 + <a class="ui label transition visible tw-cursor-default tw-inline-block" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a> 34 + {{end}} 35 + <div class="text"></div> 37 36 </div> 38 37 <div> 39 38 <button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
+1 -1
templates/repo/issue/choose.tmpl
··· 3 3 {{template "repo/header" .}} 4 4 <div class="ui container"> 5 5 {{template "base/alert" .}} 6 - <div class="navbar"> 6 + <div class="issue-navbar"> 7 7 {{template "repo/issue/navbar" .}} 8 8 </div> 9 9 <div class="divider"></div>
+1 -1
templates/repo/issue/labels.tmpl
··· 2 2 <div role="main" aria-label="{{.Title}}" class="page-content repository labels"> 3 3 {{template "repo/header" .}} 4 4 <div class="ui container"> 5 - <div class="navbar tw-mb-4"> 5 + <div class="issue-navbar tw-mb-4"> 6 6 {{template "repo/issue/navbar" .}} 7 7 {{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}} 8 8 <button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
+2 -2
templates/repo/issue/labels/edit_delete_label.tmpl
··· 52 52 </div> 53 53 <div class="field color-field"> 54 54 <label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label> 55 - <div class="color picker column"> 56 - <input class="color-picker" name="color" value="#70c24a" required maxlength="7"> 55 + <div class="column js-color-picker-input"> 56 + <input name="color" value="#70c24a"placeholder="#c320f6" required maxlength="7"> 57 57 {{template "repo/issue/label_precolors"}} 58 58 </div> 59 59 </div>
+1 -1
templates/repo/issue/labels/label_list.tmpl
··· 8 8 {{ctx.Locale.Tr "repo.issues.filter_sort"}} 9 9 </span> 10 10 {{svg "octicon-triangle-down" 14 "dropdown icon"}} 11 - <div class="left menu"> 11 + <div class="menu"> 12 12 <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> 13 13 <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> 14 14 <a class="{{if eq .SortType "leastissues"}}active {{end}}item" href="?sort=leastissues&state={{$.State}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_issues"}}</a>
+2 -2
templates/repo/issue/labels/label_new.tmpl
··· 27 27 </div> 28 28 <div class="field color-field"> 29 29 <label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label> 30 - <div class="color picker column"> 31 - <input class="color-picker" name="color" value="#70c24a" required maxlength="7"> 30 + <div class="js-color-picker-input column"> 31 + <input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7"> 32 32 {{template "repo/issue/label_precolors"}} 33 33 </div> 34 34 </div>
+1 -1
templates/repo/issue/milestone_new.tmpl
··· 2 2 <div role="main" aria-label="{{.Title}}" class="page-content repository new milestone"> 3 3 {{template "repo/header" .}} 4 4 <div class="ui container"> 5 - <div class="navbar"> 5 + <div class="issue-navbar"> 6 6 {{template "repo/issue/navbar" .}} 7 7 {{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}} 8 8 <div class="ui right floated secondary menu">
+1 -1
templates/repo/issue/view_content/sidebar.tmpl
··· 684 684 {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}} 685 685 <div class="divider"></div> 686 686 <div class="inline field"> 687 - <div class="ui checkbox" id="allow-edits-from-maintainers" 687 + <div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers" 688 688 data-url="{{.Issue.Link}}" 689 689 data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}" 690 690 data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
+1 -1
templates/repo/latest_commit.tmpl
··· 3 3 {{else}} 4 4 {{if .LatestCommitUser}} 5 5 {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}} 6 - {{if .LatestCommitUser.FullName}} 6 + {{if and .LatestCommitUser.FullName DefaultShowFullName}} 7 7 <a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a> 8 8 {{else}} 9 9 <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>
+749 -1
templates/swagger/v1_json.tmpl
··· 1741 1741 } 1742 1742 } 1743 1743 }, 1744 + "/orgs/{org}/actions/variables": { 1745 + "get": { 1746 + "produces": [ 1747 + "application/json" 1748 + ], 1749 + "tags": [ 1750 + "organization" 1751 + ], 1752 + "summary": "Get an org-level variables list", 1753 + "operationId": "getOrgVariablesList", 1754 + "parameters": [ 1755 + { 1756 + "type": "string", 1757 + "description": "name of the organization", 1758 + "name": "org", 1759 + "in": "path", 1760 + "required": true 1761 + }, 1762 + { 1763 + "type": "integer", 1764 + "description": "page number of results to return (1-based)", 1765 + "name": "page", 1766 + "in": "query" 1767 + }, 1768 + { 1769 + "type": "integer", 1770 + "description": "page size of results", 1771 + "name": "limit", 1772 + "in": "query" 1773 + } 1774 + ], 1775 + "responses": { 1776 + "200": { 1777 + "$ref": "#/responses/VariableList" 1778 + }, 1779 + "400": { 1780 + "$ref": "#/responses/error" 1781 + }, 1782 + "404": { 1783 + "$ref": "#/responses/notFound" 1784 + } 1785 + } 1786 + } 1787 + }, 1788 + "/orgs/{org}/actions/variables/{variablename}": { 1789 + "get": { 1790 + "produces": [ 1791 + "application/json" 1792 + ], 1793 + "tags": [ 1794 + "organization" 1795 + ], 1796 + "summary": "Get an org-level variable", 1797 + "operationId": "getOrgVariable", 1798 + "parameters": [ 1799 + { 1800 + "type": "string", 1801 + "description": "name of the organization", 1802 + "name": "org", 1803 + "in": "path", 1804 + "required": true 1805 + }, 1806 + { 1807 + "type": "string", 1808 + "description": "name of the variable", 1809 + "name": "variablename", 1810 + "in": "path", 1811 + "required": true 1812 + } 1813 + ], 1814 + "responses": { 1815 + "200": { 1816 + "$ref": "#/responses/ActionVariable" 1817 + }, 1818 + "400": { 1819 + "$ref": "#/responses/error" 1820 + }, 1821 + "404": { 1822 + "$ref": "#/responses/notFound" 1823 + } 1824 + } 1825 + }, 1826 + "put": { 1827 + "consumes": [ 1828 + "application/json" 1829 + ], 1830 + "produces": [ 1831 + "application/json" 1832 + ], 1833 + "tags": [ 1834 + "organization" 1835 + ], 1836 + "summary": "Update an org-level variable", 1837 + "operationId": "updateOrgVariable", 1838 + "parameters": [ 1839 + { 1840 + "type": "string", 1841 + "description": "name of the organization", 1842 + "name": "org", 1843 + "in": "path", 1844 + "required": true 1845 + }, 1846 + { 1847 + "type": "string", 1848 + "description": "name of the variable", 1849 + "name": "variablename", 1850 + "in": "path", 1851 + "required": true 1852 + }, 1853 + { 1854 + "name": "body", 1855 + "in": "body", 1856 + "schema": { 1857 + "$ref": "#/definitions/UpdateVariableOption" 1858 + } 1859 + } 1860 + ], 1861 + "responses": { 1862 + "201": { 1863 + "description": "response when updating an org-level variable" 1864 + }, 1865 + "204": { 1866 + "description": "response when updating an org-level variable" 1867 + }, 1868 + "400": { 1869 + "$ref": "#/responses/error" 1870 + }, 1871 + "404": { 1872 + "$ref": "#/responses/notFound" 1873 + } 1874 + } 1875 + }, 1876 + "post": { 1877 + "consumes": [ 1878 + "application/json" 1879 + ], 1880 + "produces": [ 1881 + "application/json" 1882 + ], 1883 + "tags": [ 1884 + "organization" 1885 + ], 1886 + "summary": "Create an org-level variable", 1887 + "operationId": "createOrgVariable", 1888 + "parameters": [ 1889 + { 1890 + "type": "string", 1891 + "description": "name of the organization", 1892 + "name": "org", 1893 + "in": "path", 1894 + "required": true 1895 + }, 1896 + { 1897 + "type": "string", 1898 + "description": "name of the variable", 1899 + "name": "variablename", 1900 + "in": "path", 1901 + "required": true 1902 + }, 1903 + { 1904 + "name": "body", 1905 + "in": "body", 1906 + "schema": { 1907 + "$ref": "#/definitions/CreateVariableOption" 1908 + } 1909 + } 1910 + ], 1911 + "responses": { 1912 + "201": { 1913 + "description": "response when creating an org-level variable" 1914 + }, 1915 + "204": { 1916 + "description": "response when creating an org-level variable" 1917 + }, 1918 + "400": { 1919 + "$ref": "#/responses/error" 1920 + }, 1921 + "404": { 1922 + "$ref": "#/responses/notFound" 1923 + } 1924 + } 1925 + }, 1926 + "delete": { 1927 + "produces": [ 1928 + "application/json" 1929 + ], 1930 + "tags": [ 1931 + "organization" 1932 + ], 1933 + "summary": "Delete an org-level variable", 1934 + "operationId": "deleteOrgVariable", 1935 + "parameters": [ 1936 + { 1937 + "type": "string", 1938 + "description": "name of the organization", 1939 + "name": "org", 1940 + "in": "path", 1941 + "required": true 1942 + }, 1943 + { 1944 + "type": "string", 1945 + "description": "name of the variable", 1946 + "name": "variablename", 1947 + "in": "path", 1948 + "required": true 1949 + } 1950 + ], 1951 + "responses": { 1952 + "200": { 1953 + "$ref": "#/responses/ActionVariable" 1954 + }, 1955 + "201": { 1956 + "description": "response when deleting a variable" 1957 + }, 1958 + "204": { 1959 + "description": "response when deleting a variable" 1960 + }, 1961 + "400": { 1962 + "$ref": "#/responses/error" 1963 + }, 1964 + "404": { 1965 + "$ref": "#/responses/notFound" 1966 + } 1967 + } 1968 + } 1969 + }, 1744 1970 "/orgs/{org}/activities/feeds": { 1745 1971 "get": { 1746 1972 "produces": [ ··· 3591 3817 } 3592 3818 } 3593 3819 }, 3820 + "/repos/{owner}/{repo}/actions/variables": { 3821 + "get": { 3822 + "produces": [ 3823 + "application/json" 3824 + ], 3825 + "tags": [ 3826 + "repository" 3827 + ], 3828 + "summary": "Get repo-level variables list", 3829 + "operationId": "getRepoVariablesList", 3830 + "parameters": [ 3831 + { 3832 + "type": "string", 3833 + "description": "name of the owner", 3834 + "name": "owner", 3835 + "in": "path", 3836 + "required": true 3837 + }, 3838 + { 3839 + "type": "string", 3840 + "description": "name of the repository", 3841 + "name": "repo", 3842 + "in": "path", 3843 + "required": true 3844 + }, 3845 + { 3846 + "type": "integer", 3847 + "description": "page number of results to return (1-based)", 3848 + "name": "page", 3849 + "in": "query" 3850 + }, 3851 + { 3852 + "type": "integer", 3853 + "description": "page size of results", 3854 + "name": "limit", 3855 + "in": "query" 3856 + } 3857 + ], 3858 + "responses": { 3859 + "200": { 3860 + "$ref": "#/responses/VariableList" 3861 + }, 3862 + "400": { 3863 + "$ref": "#/responses/error" 3864 + }, 3865 + "404": { 3866 + "$ref": "#/responses/notFound" 3867 + } 3868 + } 3869 + } 3870 + }, 3871 + "/repos/{owner}/{repo}/actions/variables/{variablename}": { 3872 + "get": { 3873 + "produces": [ 3874 + "application/json" 3875 + ], 3876 + "tags": [ 3877 + "repository" 3878 + ], 3879 + "summary": "Get a repo-level variable", 3880 + "operationId": "getRepoVariable", 3881 + "parameters": [ 3882 + { 3883 + "type": "string", 3884 + "description": "name of the owner", 3885 + "name": "owner", 3886 + "in": "path", 3887 + "required": true 3888 + }, 3889 + { 3890 + "type": "string", 3891 + "description": "name of the repository", 3892 + "name": "repo", 3893 + "in": "path", 3894 + "required": true 3895 + }, 3896 + { 3897 + "type": "string", 3898 + "description": "name of the variable", 3899 + "name": "variablename", 3900 + "in": "path", 3901 + "required": true 3902 + } 3903 + ], 3904 + "responses": { 3905 + "200": { 3906 + "$ref": "#/responses/ActionVariable" 3907 + }, 3908 + "400": { 3909 + "$ref": "#/responses/error" 3910 + }, 3911 + "404": { 3912 + "$ref": "#/responses/notFound" 3913 + } 3914 + } 3915 + }, 3916 + "put": { 3917 + "produces": [ 3918 + "application/json" 3919 + ], 3920 + "tags": [ 3921 + "repository" 3922 + ], 3923 + "summary": "Update a repo-level variable", 3924 + "operationId": "updateRepoVariable", 3925 + "parameters": [ 3926 + { 3927 + "type": "string", 3928 + "description": "name of the owner", 3929 + "name": "owner", 3930 + "in": "path", 3931 + "required": true 3932 + }, 3933 + { 3934 + "type": "string", 3935 + "description": "name of the repository", 3936 + "name": "repo", 3937 + "in": "path", 3938 + "required": true 3939 + }, 3940 + { 3941 + "type": "string", 3942 + "description": "name of the variable", 3943 + "name": "variablename", 3944 + "in": "path", 3945 + "required": true 3946 + }, 3947 + { 3948 + "name": "body", 3949 + "in": "body", 3950 + "schema": { 3951 + "$ref": "#/definitions/UpdateVariableOption" 3952 + } 3953 + } 3954 + ], 3955 + "responses": { 3956 + "201": { 3957 + "description": "response when updating a repo-level variable" 3958 + }, 3959 + "204": { 3960 + "description": "response when updating a repo-level variable" 3961 + }, 3962 + "400": { 3963 + "$ref": "#/responses/error" 3964 + }, 3965 + "404": { 3966 + "$ref": "#/responses/notFound" 3967 + } 3968 + } 3969 + }, 3970 + "post": { 3971 + "produces": [ 3972 + "application/json" 3973 + ], 3974 + "tags": [ 3975 + "repository" 3976 + ], 3977 + "summary": "Create a repo-level variable", 3978 + "operationId": "createRepoVariable", 3979 + "parameters": [ 3980 + { 3981 + "type": "string", 3982 + "description": "name of the owner", 3983 + "name": "owner", 3984 + "in": "path", 3985 + "required": true 3986 + }, 3987 + { 3988 + "type": "string", 3989 + "description": "name of the repository", 3990 + "name": "repo", 3991 + "in": "path", 3992 + "required": true 3993 + }, 3994 + { 3995 + "type": "string", 3996 + "description": "name of the variable", 3997 + "name": "variablename", 3998 + "in": "path", 3999 + "required": true 4000 + }, 4001 + { 4002 + "name": "body", 4003 + "in": "body", 4004 + "schema": { 4005 + "$ref": "#/definitions/CreateVariableOption" 4006 + } 4007 + } 4008 + ], 4009 + "responses": { 4010 + "201": { 4011 + "description": "response when creating a repo-level variable" 4012 + }, 4013 + "204": { 4014 + "description": "response when creating a repo-level variable" 4015 + }, 4016 + "400": { 4017 + "$ref": "#/responses/error" 4018 + }, 4019 + "404": { 4020 + "$ref": "#/responses/notFound" 4021 + } 4022 + } 4023 + }, 4024 + "delete": { 4025 + "produces": [ 4026 + "application/json" 4027 + ], 4028 + "tags": [ 4029 + "repository" 4030 + ], 4031 + "summary": "Delete a repo-level variable", 4032 + "operationId": "deleteRepoVariable", 4033 + "parameters": [ 4034 + { 4035 + "type": "string", 4036 + "description": "name of the owner", 4037 + "name": "owner", 4038 + "in": "path", 4039 + "required": true 4040 + }, 4041 + { 4042 + "type": "string", 4043 + "description": "name of the repository", 4044 + "name": "repo", 4045 + "in": "path", 4046 + "required": true 4047 + }, 4048 + { 4049 + "type": "string", 4050 + "description": "name of the variable", 4051 + "name": "variablename", 4052 + "in": "path", 4053 + "required": true 4054 + } 4055 + ], 4056 + "responses": { 4057 + "200": { 4058 + "$ref": "#/responses/ActionVariable" 4059 + }, 4060 + "201": { 4061 + "description": "response when deleting a variable" 4062 + }, 4063 + "204": { 4064 + "description": "response when deleting a variable" 4065 + }, 4066 + "400": { 4067 + "$ref": "#/responses/error" 4068 + }, 4069 + "404": { 4070 + "$ref": "#/responses/notFound" 4071 + } 4072 + } 4073 + } 4074 + }, 3594 4075 "/repos/{owner}/{repo}/activities/feeds": { 3595 4076 "get": { 3596 4077 "produces": [ ··· 15375 15856 } 15376 15857 } 15377 15858 }, 15859 + "/user/actions/variables": { 15860 + "get": { 15861 + "produces": [ 15862 + "application/json" 15863 + ], 15864 + "tags": [ 15865 + "user" 15866 + ], 15867 + "summary": "Get the user-level list of variables which is created by current doer", 15868 + "operationId": "getUserVariablesList", 15869 + "parameters": [ 15870 + { 15871 + "type": "integer", 15872 + "description": "page number of results to return (1-based)", 15873 + "name": "page", 15874 + "in": "query" 15875 + }, 15876 + { 15877 + "type": "integer", 15878 + "description": "page size of results", 15879 + "name": "limit", 15880 + "in": "query" 15881 + } 15882 + ], 15883 + "responses": { 15884 + "200": { 15885 + "$ref": "#/responses/VariableList" 15886 + }, 15887 + "400": { 15888 + "$ref": "#/responses/error" 15889 + }, 15890 + "404": { 15891 + "$ref": "#/responses/notFound" 15892 + } 15893 + } 15894 + } 15895 + }, 15896 + "/user/actions/variables/{variablename}": { 15897 + "get": { 15898 + "produces": [ 15899 + "application/json" 15900 + ], 15901 + "tags": [ 15902 + "user" 15903 + ], 15904 + "summary": "Get a user-level variable which is created by current doer", 15905 + "operationId": "getUserVariable", 15906 + "parameters": [ 15907 + { 15908 + "type": "string", 15909 + "description": "name of the variable", 15910 + "name": "variablename", 15911 + "in": "path", 15912 + "required": true 15913 + } 15914 + ], 15915 + "responses": { 15916 + "200": { 15917 + "$ref": "#/responses/ActionVariable" 15918 + }, 15919 + "400": { 15920 + "$ref": "#/responses/error" 15921 + }, 15922 + "404": { 15923 + "$ref": "#/responses/notFound" 15924 + } 15925 + } 15926 + }, 15927 + "put": { 15928 + "consumes": [ 15929 + "application/json" 15930 + ], 15931 + "produces": [ 15932 + "application/json" 15933 + ], 15934 + "tags": [ 15935 + "user" 15936 + ], 15937 + "summary": "Update a user-level variable which is created by current doer", 15938 + "operationId": "updateUserVariable", 15939 + "parameters": [ 15940 + { 15941 + "type": "string", 15942 + "description": "name of the variable", 15943 + "name": "variablename", 15944 + "in": "path", 15945 + "required": true 15946 + }, 15947 + { 15948 + "name": "body", 15949 + "in": "body", 15950 + "schema": { 15951 + "$ref": "#/definitions/UpdateVariableOption" 15952 + } 15953 + } 15954 + ], 15955 + "responses": { 15956 + "201": { 15957 + "description": "response when updating a variable" 15958 + }, 15959 + "204": { 15960 + "description": "response when updating a variable" 15961 + }, 15962 + "400": { 15963 + "$ref": "#/responses/error" 15964 + }, 15965 + "404": { 15966 + "$ref": "#/responses/notFound" 15967 + } 15968 + } 15969 + }, 15970 + "post": { 15971 + "consumes": [ 15972 + "application/json" 15973 + ], 15974 + "produces": [ 15975 + "application/json" 15976 + ], 15977 + "tags": [ 15978 + "user" 15979 + ], 15980 + "summary": "Create a user-level variable", 15981 + "operationId": "createUserVariable", 15982 + "parameters": [ 15983 + { 15984 + "type": "string", 15985 + "description": "name of the variable", 15986 + "name": "variablename", 15987 + "in": "path", 15988 + "required": true 15989 + }, 15990 + { 15991 + "name": "body", 15992 + "in": "body", 15993 + "schema": { 15994 + "$ref": "#/definitions/CreateVariableOption" 15995 + } 15996 + } 15997 + ], 15998 + "responses": { 15999 + "201": { 16000 + "description": "response when creating a variable" 16001 + }, 16002 + "204": { 16003 + "description": "response when creating a variable" 16004 + }, 16005 + "400": { 16006 + "$ref": "#/responses/error" 16007 + }, 16008 + "404": { 16009 + "$ref": "#/responses/notFound" 16010 + } 16011 + } 16012 + }, 16013 + "delete": { 16014 + "produces": [ 16015 + "application/json" 16016 + ], 16017 + "tags": [ 16018 + "user" 16019 + ], 16020 + "summary": "Delete a user-level variable which is created by current doer", 16021 + "operationId": "deleteUserVariable", 16022 + "parameters": [ 16023 + { 16024 + "type": "string", 16025 + "description": "name of the variable", 16026 + "name": "variablename", 16027 + "in": "path", 16028 + "required": true 16029 + } 16030 + ], 16031 + "responses": { 16032 + "201": { 16033 + "description": "response when deleting a variable" 16034 + }, 16035 + "204": { 16036 + "description": "response when deleting a variable" 16037 + }, 16038 + "400": { 16039 + "$ref": "#/responses/error" 16040 + }, 16041 + "404": { 16042 + "$ref": "#/responses/notFound" 16043 + } 16044 + } 16045 + } 16046 + }, 15378 16047 "/user/applications/oauth2": { 15379 16048 "get": { 15380 16049 "produces": [ ··· 17493 18162 }, 17494 18163 "x-go-package": "code.gitea.io/gitea/modules/structs" 17495 18164 }, 18165 + "ActionVariable": { 18166 + "description": "ActionVariable return value of the query API", 18167 + "type": "object", 18168 + "properties": { 18169 + "data": { 18170 + "description": "the value of the variable", 18171 + "type": "string", 18172 + "x-go-name": "Data" 18173 + }, 18174 + "name": { 18175 + "description": "the name of the variable", 18176 + "type": "string", 18177 + "x-go-name": "Name" 18178 + }, 18179 + "owner_id": { 18180 + "description": "the owner to which the variable belongs", 18181 + "type": "integer", 18182 + "format": "int64", 18183 + "x-go-name": "OwnerID" 18184 + }, 18185 + "repo_id": { 18186 + "description": "the repository to which the variable belongs", 18187 + "type": "integer", 18188 + "format": "int64", 18189 + "x-go-name": "RepoID" 18190 + } 18191 + }, 18192 + "x-go-package": "code.gitea.io/gitea/modules/structs" 18193 + }, 17496 18194 "Activity": { 17497 18195 "type": "object", 17498 18196 "properties": { ··· 19389 20087 "visibility": { 19390 20088 "type": "string", 19391 20089 "x-go-name": "Visibility" 20090 + } 20091 + }, 20092 + "x-go-package": "code.gitea.io/gitea/modules/structs" 20093 + }, 20094 + "CreateVariableOption": { 20095 + "description": "CreateVariableOption the option when creating variable", 20096 + "type": "object", 20097 + "required": [ 20098 + "value" 20099 + ], 20100 + "properties": { 20101 + "value": { 20102 + "description": "Value of the variable to create", 20103 + "type": "string", 20104 + "x-go-name": "Value" 19392 20105 } 19393 20106 }, 19394 20107 "x-go-package": "code.gitea.io/gitea/modules/structs" ··· 23773 24486 }, 23774 24487 "x-go-package": "code.gitea.io/gitea/modules/structs" 23775 24488 }, 24489 + "UpdateVariableOption": { 24490 + "description": "UpdateVariableOption the option when updating variable", 24491 + "type": "object", 24492 + "required": [ 24493 + "value" 24494 + ], 24495 + "properties": { 24496 + "name": { 24497 + "description": "New name for the variable. If the field is empty, the variable name won't be updated.", 24498 + "type": "string", 24499 + "x-go-name": "Name" 24500 + }, 24501 + "value": { 24502 + "description": "Value of the variable to update", 24503 + "type": "string", 24504 + "x-go-name": "Value" 24505 + } 24506 + }, 24507 + "x-go-package": "code.gitea.io/gitea/modules/structs" 24508 + }, 23776 24509 "User": { 23777 24510 "description": "User represents a user", 23778 24511 "type": "object", ··· 24155 24888 "items": { 24156 24889 "$ref": "#/definitions/AccessToken" 24157 24890 } 24891 + } 24892 + }, 24893 + "ActionVariable": { 24894 + "description": "ActionVariable", 24895 + "schema": { 24896 + "$ref": "#/definitions/ActionVariable" 24158 24897 } 24159 24898 }, 24160 24899 "ActivityFeedsList": { ··· 25040 25779 } 25041 25780 } 25042 25781 }, 25782 + "VariableList": { 25783 + "description": "VariableList", 25784 + "schema": { 25785 + "type": "array", 25786 + "items": { 25787 + "$ref": "#/definitions/ActionVariable" 25788 + } 25789 + } 25790 + }, 25043 25791 "WatchInfo": { 25044 25792 "description": "WatchInfo", 25045 25793 "schema": { ··· 25115 25863 "parameterBodies": { 25116 25864 "description": "parameterBodies", 25117 25865 "schema": { 25118 - "$ref": "#/definitions/CreateOrUpdateSecretOption" 25866 + "$ref": "#/definitions/UpdateVariableOption" 25119 25867 } 25120 25868 }, 25121 25869 "redirect": {
+1 -1
templates/user/settings/repos.tmpl
··· 6 6 <div class="ui attached segment"> 7 7 {{if or .allowAdopt .allowDelete}} 8 8 {{if .Dirs}} 9 - <div class="ui middle aligned divided list"> 9 + <div class="ui list"> 10 10 {{range $dirI, $dir := .Dirs}} 11 11 {{$repo := index $.ReposMap $dir}} 12 12 <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
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "fmt" 8 + "net/http" 9 + "testing" 10 + 11 + auth_model "code.gitea.io/gitea/models/auth" 12 + repo_model "code.gitea.io/gitea/models/repo" 13 + "code.gitea.io/gitea/models/unittest" 14 + user_model "code.gitea.io/gitea/models/user" 15 + api "code.gitea.io/gitea/modules/structs" 16 + "code.gitea.io/gitea/tests" 17 + ) 18 + 19 + func TestAPIRepoVariables(t *testing.T) { 20 + defer tests.PrepareTestEnv(t)() 21 + 22 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 23 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 24 + session := loginUser(t, user.Name) 25 + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 26 + 27 + t.Run("CreateRepoVariable", func(t *testing.T) { 28 + cases := []struct { 29 + Name string 30 + ExpectedStatus int 31 + }{ 32 + { 33 + Name: "-", 34 + ExpectedStatus: http.StatusBadRequest, 35 + }, 36 + { 37 + Name: "_", 38 + ExpectedStatus: http.StatusNoContent, 39 + }, 40 + { 41 + Name: "TEST_VAR", 42 + ExpectedStatus: http.StatusNoContent, 43 + }, 44 + { 45 + Name: "test_var", 46 + ExpectedStatus: http.StatusConflict, 47 + }, 48 + { 49 + Name: "ci", 50 + ExpectedStatus: http.StatusBadRequest, 51 + }, 52 + { 53 + Name: "123var", 54 + ExpectedStatus: http.StatusBadRequest, 55 + }, 56 + { 57 + Name: "var@test", 58 + ExpectedStatus: http.StatusBadRequest, 59 + }, 60 + { 61 + Name: "github_var", 62 + ExpectedStatus: http.StatusBadRequest, 63 + }, 64 + { 65 + Name: "gitea_var", 66 + ExpectedStatus: http.StatusBadRequest, 67 + }, 68 + } 69 + 70 + for _, c := range cases { 71 + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.CreateVariableOption{ 72 + Value: "value", 73 + }).AddTokenAuth(token) 74 + MakeRequest(t, req, c.ExpectedStatus) 75 + } 76 + }) 77 + 78 + t.Run("UpdateRepoVariable", func(t *testing.T) { 79 + variableName := "test_update_var" 80 + url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName) 81 + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ 82 + Value: "initial_val", 83 + }).AddTokenAuth(token) 84 + MakeRequest(t, req, http.StatusNoContent) 85 + 86 + cases := []struct { 87 + Name string 88 + UpdateName string 89 + ExpectedStatus int 90 + }{ 91 + { 92 + Name: "not_found_var", 93 + ExpectedStatus: http.StatusNotFound, 94 + }, 95 + { 96 + Name: variableName, 97 + UpdateName: "1invalid", 98 + ExpectedStatus: http.StatusBadRequest, 99 + }, 100 + { 101 + Name: variableName, 102 + UpdateName: "invalid@name", 103 + ExpectedStatus: http.StatusBadRequest, 104 + }, 105 + { 106 + Name: variableName, 107 + UpdateName: "ci", 108 + ExpectedStatus: http.StatusBadRequest, 109 + }, 110 + { 111 + Name: variableName, 112 + UpdateName: "updated_var_name", 113 + ExpectedStatus: http.StatusNoContent, 114 + }, 115 + { 116 + Name: variableName, 117 + ExpectedStatus: http.StatusNotFound, 118 + }, 119 + { 120 + Name: "updated_var_name", 121 + ExpectedStatus: http.StatusNoContent, 122 + }, 123 + } 124 + 125 + for _, c := range cases { 126 + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.UpdateVariableOption{ 127 + Name: c.UpdateName, 128 + Value: "updated_val", 129 + }).AddTokenAuth(token) 130 + MakeRequest(t, req, c.ExpectedStatus) 131 + } 132 + }) 133 + 134 + t.Run("DeleteRepoVariable", func(t *testing.T) { 135 + variableName := "test_delete_var" 136 + url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName) 137 + 138 + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ 139 + Value: "initial_val", 140 + }).AddTokenAuth(token) 141 + MakeRequest(t, req, http.StatusNoContent) 142 + 143 + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) 144 + MakeRequest(t, req, http.StatusNoContent) 145 + 146 + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) 147 + MakeRequest(t, req, http.StatusNotFound) 148 + }) 149 + }
+144
tests/integration/api_user_variables_test.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "fmt" 8 + "net/http" 9 + "testing" 10 + 11 + auth_model "code.gitea.io/gitea/models/auth" 12 + api "code.gitea.io/gitea/modules/structs" 13 + "code.gitea.io/gitea/tests" 14 + ) 15 + 16 + func TestAPIUserVariables(t *testing.T) { 17 + defer tests.PrepareTestEnv(t)() 18 + 19 + session := loginUser(t, "user1") 20 + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) 21 + 22 + t.Run("CreateRepoVariable", func(t *testing.T) { 23 + cases := []struct { 24 + Name string 25 + ExpectedStatus int 26 + }{ 27 + { 28 + Name: "-", 29 + ExpectedStatus: http.StatusBadRequest, 30 + }, 31 + { 32 + Name: "_", 33 + ExpectedStatus: http.StatusNoContent, 34 + }, 35 + { 36 + Name: "TEST_VAR", 37 + ExpectedStatus: http.StatusNoContent, 38 + }, 39 + { 40 + Name: "test_var", 41 + ExpectedStatus: http.StatusConflict, 42 + }, 43 + { 44 + Name: "ci", 45 + ExpectedStatus: http.StatusBadRequest, 46 + }, 47 + { 48 + Name: "123var", 49 + ExpectedStatus: http.StatusBadRequest, 50 + }, 51 + { 52 + Name: "var@test", 53 + ExpectedStatus: http.StatusBadRequest, 54 + }, 55 + { 56 + Name: "github_var", 57 + ExpectedStatus: http.StatusBadRequest, 58 + }, 59 + { 60 + Name: "gitea_var", 61 + ExpectedStatus: http.StatusBadRequest, 62 + }, 63 + } 64 + 65 + for _, c := range cases { 66 + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.CreateVariableOption{ 67 + Value: "value", 68 + }).AddTokenAuth(token) 69 + MakeRequest(t, req, c.ExpectedStatus) 70 + } 71 + }) 72 + 73 + t.Run("UpdateRepoVariable", func(t *testing.T) { 74 + variableName := "test_update_var" 75 + url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) 76 + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ 77 + Value: "initial_val", 78 + }).AddTokenAuth(token) 79 + MakeRequest(t, req, http.StatusNoContent) 80 + 81 + cases := []struct { 82 + Name string 83 + UpdateName string 84 + ExpectedStatus int 85 + }{ 86 + { 87 + Name: "not_found_var", 88 + ExpectedStatus: http.StatusNotFound, 89 + }, 90 + { 91 + Name: variableName, 92 + UpdateName: "1invalid", 93 + ExpectedStatus: http.StatusBadRequest, 94 + }, 95 + { 96 + Name: variableName, 97 + UpdateName: "invalid@name", 98 + ExpectedStatus: http.StatusBadRequest, 99 + }, 100 + { 101 + Name: variableName, 102 + UpdateName: "ci", 103 + ExpectedStatus: http.StatusBadRequest, 104 + }, 105 + { 106 + Name: variableName, 107 + UpdateName: "updated_var_name", 108 + ExpectedStatus: http.StatusNoContent, 109 + }, 110 + { 111 + Name: variableName, 112 + ExpectedStatus: http.StatusNotFound, 113 + }, 114 + { 115 + Name: "updated_var_name", 116 + ExpectedStatus: http.StatusNoContent, 117 + }, 118 + } 119 + 120 + for _, c := range cases { 121 + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.UpdateVariableOption{ 122 + Name: c.UpdateName, 123 + Value: "updated_val", 124 + }).AddTokenAuth(token) 125 + MakeRequest(t, req, c.ExpectedStatus) 126 + } 127 + }) 128 + 129 + t.Run("DeleteRepoVariable", func(t *testing.T) { 130 + variableName := "test_delete_var" 131 + url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) 132 + 133 + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ 134 + Value: "initial_val", 135 + }).AddTokenAuth(token) 136 + MakeRequest(t, req, http.StatusNoContent) 137 + 138 + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) 139 + MakeRequest(t, req, http.StatusNoContent) 140 + 141 + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) 142 + MakeRequest(t, req, http.StatusNotFound) 143 + }) 144 + }
+2 -90
web_src/css/base.css
··· 24 24 --repo-header-issue-min-height: 41px; 25 25 --min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */ 26 26 --tab-size: 4; 27 + --checkbox-size: 16px; /* height and width of checkbox and radio inputs */ 27 28 } 28 29 29 30 :root * { ··· 44 45 } 45 46 46 47 body { 47 - line-height: 1.4285rem; 48 + line-height: 20px; 48 49 font-family: var(--fonts-regular); 49 50 color: var(--color-text); 50 51 background-color: var(--color-body); ··· 316 317 background-color: var(--color-label-bg); 317 318 } 318 319 319 - /* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */ 320 - .ui.input > input { 321 - line-height: var(--line-height-default); 322 - text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */ 323 - } 324 - 325 - /* fix Fomantic's line-height causing vertical scrollbars to appear */ 326 - ul.ui.list li, 327 - ol.ui.list li, 328 - .ui.list > .item, 329 - .ui.list .list > .item { 330 - line-height: var(--line-height-default); 331 - } 332 - 333 - .ui.input.focus > input, 334 - .ui.input > input:focus { 335 - border-color: var(--color-primary); 336 - } 337 - 338 - .ui.action.input .ui.ui.button { 339 - border-color: var(--color-input-border); 340 - padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */ 341 - padding-bottom: 0; 342 - } 343 - 344 - /* currently used for search bar dropdowns in repo search and explore code */ 345 - .ui.action.input:not([class*="left action"]) > .ui.dropdown.selection { 346 - min-width: 10em; 347 - } 348 - .ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) { 349 - border-right: none; 350 - } 351 - .ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover { 352 - border-color: var(--color-input-border); 353 - } 354 - .ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible { 355 - border-bottom-left-radius: 0 !important; 356 - border-bottom-right-radius: 0 !important; 357 - } 358 - .ui.action.input:not([class*="left action"]) > input, 359 - .ui.action.input:not([class*="left action"]) > input:hover { 360 - border-right: none; 361 - } 362 - .ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection, 363 - .ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover, 364 - .ui.action.input:not([class*="left action"]) > input:focus + .button, 365 - .ui.action.input:not([class*="left action"]) > input:focus + .button:hover, 366 - .ui.action.input:not([class*="left action"]) > input:focus + .icon + .button, 367 - .ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover { 368 - border-left-color: var(--color-primary); 369 - } 370 - .ui.action.input:not([class*="left action"]) > input:focus { 371 - border-right-color: var(--color-primary); 372 - } 373 - 374 320 .ui.menu { 375 321 display: flex; 376 322 } ··· 514 460 color: var(--color-text-light-2); 515 461 } 516 462 517 - .ui.list .list > .item .header, 518 - .ui.list > .item .header { 519 - color: var(--color-text-dark); 520 - } 521 - 522 - .ui.list .list > .item > .content, 523 - .ui.list > .item > .content { 524 - color: var(--color-text); 525 - } 526 - 527 - .ui.list .list > .item .description, 528 - .ui.list > .item .description { 529 - color: var(--color-text); 530 - } 531 - 532 463 /* replace item margin on secondary menu items with gap and remove both the 533 464 negative margins on the menu as well as margin on the items */ 534 465 .ui.secondary.menu { ··· 645 576 border-radius: var(--border-radius); 646 577 object-fit: contain; 647 578 aspect-ratio: 1; 648 - } 649 - 650 - .ui.divided.list > .item { 651 - border-color: var(--color-secondary); 652 579 } 653 580 654 581 .ui.error.message .header, ··· 1457 1384 vertical-align: -0.15em; 1458 1385 } 1459 1386 1460 - /* for the jquery.minicolors plugin */ 1461 - .minicolors-panel { 1462 - background: var(--color-secondary-dark-1) !important; 1463 - } 1464 - 1465 1387 .ui.tabular.menu { 1466 1388 border-color: var(--color-secondary); 1467 1389 } ··· 1623 1545 .ui.ui.labeled.button { 1624 1546 gap: 0; 1625 1547 align-items: stretch; 1626 - } 1627 - 1628 - .ui.ui.icon.input .icon { 1629 - display: flex; 1630 - align-items: center; 1631 - justify-content: center; 1632 - } 1633 - 1634 - .ui.icon.input > i.icon { 1635 - transition: none; 1636 1548 } 1637 1549 1638 1550 .flex-items-block > .item,
+47
web_src/css/features/colorpicker.css
··· 1 + .js-color-picker-input { 2 + display: flex; 3 + position: relative; 4 + } 5 + 6 + .js-color-picker-input input { 7 + padding-top: 8px !important; 8 + padding-bottom: 8px !important; 9 + padding-left: 32px !important; 10 + } 11 + 12 + .js-color-picker-input .preview-square { 13 + position: absolute; 14 + aspect-ratio: 1; 15 + height: 16px; 16 + left: 10px; 17 + top: 50%; 18 + transform: translateY(-50%); 19 + border-radius: 2px; 20 + 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 */ 21 + background-position: 0 0, 4px 4px; 22 + background-size: 8px 8px; 23 + } 24 + 25 + .js-color-picker-input .preview-square::after { 26 + content: ""; 27 + position: absolute; 28 + width: 100%; 29 + height: 100%; 30 + border-radius: inherit; 31 + background-color: currentcolor; 32 + } 33 + 34 + hex-color-picker { 35 + width: 180px; 36 + height: 120px; 37 + } 38 + 39 + hex-color-picker::part(hue-pointer), 40 + hex-color-picker::part(saturation-pointer) { 41 + width: 22px; 42 + height: 22px; 43 + } 44 + 45 + hex-color-picker::part(hue) { 46 + flex-basis: 16px; 47 + }
-23
web_src/css/features/projects.css
··· 102 102 .card-ghost * { 103 103 opacity: 0; 104 104 } 105 - 106 - .color-field .minicolors.minicolors-theme-default { 107 - display: block; 108 - } 109 - 110 - .color-field .minicolors.minicolors-theme-default .minicolors-input { 111 - height: 38px; 112 - padding-left: 2rem; 113 - } 114 - 115 - .color-field .minicolors.minicolors-theme-default .minicolors-swatch { 116 - top: 10px; 117 - } 118 - 119 - .edit-project-column-modal .color.picker.column, 120 - .new-project-column-modal .color.picker.column { 121 - display: flex; 122 - } 123 - 124 - .edit-project-column-modal .color.picker.column .minicolors, 125 - .new-project-column-modal .color.picker.column .minicolors { 126 - flex: 1; 127 - }
+5 -54
web_src/css/form.css
··· 32 32 .ui.form input[type="text"], 33 33 .ui.form input[type="time"], 34 34 .ui.form input[type="url"], 35 - .ui.selection.dropdown, 36 - .ui.checkbox label::before, 37 - .ui.checkbox input:checked ~ label::before, 38 - .ui.checkbox input:not([type="radio"]):indeterminate ~ label::before { 35 + .ui.selection.dropdown { 39 36 background: var(--color-input-background); 40 37 border-color: var(--color-input-border); 41 38 color: var(--color-input-text); ··· 63 60 .ui.form input[type="text"]:hover, 64 61 .ui.form input[type="time"]:hover, 65 62 .ui.form input[type="url"]:hover, 66 - .ui.selection.dropdown:hover, 67 - .ui.checkbox label:hover::before, 68 - .ui.checkbox label:active::before, 69 - .ui.radio.checkbox label::after, 70 - .ui.radio.checkbox input:focus ~ label::before, 71 - .ui.radio.checkbox input:checked ~ label::before { 63 + .ui.selection.dropdown:hover { 72 64 background: var(--color-input-background); 73 65 border-color: var(--color-input-border-hover); 74 66 color: var(--color-input-text); ··· 91 83 .ui.form input[type="text"]:focus, 92 84 .ui.form input[type="time"]:focus, 93 85 .ui.form input[type="url"]:focus, 94 - .ui.selection.dropdown:focus, 95 - .ui.checkbox input:focus ~ label::before, 96 - .ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::before, 97 - .ui.checkbox input:checked:focus ~ label::before, 98 - .ui.radio.checkbox input:focus:checked ~ label::before { 86 + .ui.selection.dropdown:focus { 99 87 background: var(--color-input-background); 100 88 border-color: var(--color-primary); 101 89 color: var(--color-input-text); ··· 106 94 .ui.form .inline.fields .field > label, 107 95 .ui.form .inline.fields .field > p, 108 96 .ui.form .inline.field > label, 109 - .ui.form .inline.field > p, 110 - .ui.checkbox label, 111 - .ui.checkbox + label, 112 - .ui.checkbox label:hover, 113 - .ui.checkbox + label:hover, 114 - .ui.checkbox input:focus ~ label, 115 - .ui.checkbox input:active ~ label { 97 + .ui.form .inline.field > p { 116 98 color: var(--color-text); 117 99 } 118 100 119 101 .ui.form .required.fields:not(.grouped) > .field > label::after, 120 102 .ui.form .required.fields.grouped > label::after, 121 103 .ui.form .required.field > label::after, 122 - .ui.form .required.fields:not(.grouped) > .field > .checkbox::after, 123 - .ui.form .required.field > .checkbox::after, 124 104 .ui.form label.required::after { 125 105 color: var(--color-red); 126 106 } 127 107 128 - .ui.input, 129 - .ui.checkbox input:focus ~ label::after, 130 - .ui.checkbox input:checked ~ label::after, 131 - .ui.checkbox label:active::after, 132 - .ui.checkbox input:not([type="radio"]):indeterminate ~ label::after, 133 - .ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::after, 134 - .ui.checkbox input:checked:focus ~ label::after, 135 - .ui.disabled.checkbox label, 136 - .ui.checkbox input[disabled] ~ label { 108 + .ui.input { 137 109 color: var(--color-input-text); 138 - } 139 - 140 - .ui.radio.checkbox input:focus ~ label::after, 141 - .ui.radio.checkbox input:checked ~ label::after, 142 - .ui.radio.checkbox input:focus:checked ~ label::after { 143 - background: var(--color-input-text); 144 - } 145 - 146 - .ui.toggle.checkbox label::before { 147 - background: var(--color-input-toggle-background); 148 - } 149 - 150 - .ui.toggle.checkbox label, 151 - .ui.toggle.checkbox input:checked ~ label, 152 - .ui.toggle.checkbox input:focus:checked ~ label { 153 - color: var(--color-text) !important; 154 - } 155 - 156 - .ui.toggle.checkbox input:checked ~ label::before, 157 - .ui.toggle.checkbox input:focus:checked ~ label::before { 158 - background: var(--color-primary) !important; 159 110 } 160 111 161 112 /* match <select> padding to <input> */
+17
web_src/css/helpers.css
··· 63 63 display: none !important; 64 64 } 65 65 } 66 + 67 + .tab-size-1 { tab-size: 1 !important; } 68 + .tab-size-2 { tab-size: 2 !important; } 69 + .tab-size-3 { tab-size: 3 !important; } 70 + .tab-size-4 { tab-size: 4 !important; } 71 + .tab-size-5 { tab-size: 5 !important; } 72 + .tab-size-6 { tab-size: 6 !important; } 73 + .tab-size-7 { tab-size: 7 !important; } 74 + .tab-size-8 { tab-size: 8 !important; } 75 + .tab-size-9 { tab-size: 9 !important; } 76 + .tab-size-10 { tab-size: 10 !important; } 77 + .tab-size-11 { tab-size: 11 !important; } 78 + .tab-size-12 { tab-size: 12 !important; } 79 + .tab-size-13 { tab-size: 13 !important; } 80 + .tab-size-14 { tab-size: 14 !important; } 81 + .tab-size-15 { tab-size: 15 !important; } 82 + .tab-size-16 { tab-size: 16 !important; }
+3
web_src/css/index.css
··· 6 6 @import "./modules/container.css"; 7 7 @import "./modules/divider.css"; 8 8 @import "./modules/header.css"; 9 + @import "./modules/input.css"; 9 10 @import "./modules/label.css"; 11 + @import "./modules/list.css"; 10 12 @import "./modules/segment.css"; 11 13 @import "./modules/grid.css"; 12 14 @import "./modules/message.css"; 13 15 @import "./modules/table.css"; 14 16 @import "./modules/card.css"; 17 + @import "./modules/checkbox.css"; 15 18 @import "./modules/modal.css"; 16 19 17 20 @import "./modules/select.css";
+6 -3
web_src/css/modules/animations.css
··· 6 6 .is-loading { 7 7 pointer-events: none !important; 8 8 position: relative !important; 9 - overflow: hidden !important; 10 9 } 11 10 12 11 .is-loading > * { ··· 35 34 border-radius: var(--border-radius-circle); 36 35 } 37 36 38 - .is-loading.small-loading-icon::after { 37 + .is-loading.loading-icon-2px::after { 39 38 border-width: 2px; 40 39 } 41 40 41 + .is-loading.loading-icon-3px::after { 42 + border-width: 3px; 43 + } 44 + 42 45 /* 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. */ 43 46 form.single-button-form.is-loading > * { 44 47 opacity: 1; ··· 63 66 background: transparent; 64 67 } 65 68 66 - /* TODO: not needed, use "is-loading small-loading-icon" instead */ 69 + /* TODO: not needed, use "is-loading loading-icon-2px" instead */ 67 70 code.language-math.is-loading::after { 68 71 padding: 0; 69 72 border-width: 2px;
+120
web_src/css/modules/checkbox.css
··· 1 + /* based on Fomantic UI checkbox module, with just the parts extracted that we use. If you find any 2 + unused rules here after refactoring, please remove them. */ 3 + 4 + input[type="checkbox"], 5 + input[type="radio"] { 6 + width: var(--checkbox-size); 7 + height: var(--checkbox-size); 8 + } 9 + 10 + .ui.checkbox { 11 + position: relative; 12 + display: inline-block; 13 + vertical-align: baseline; 14 + min-height: var(--checkbox-size); 15 + line-height: var(--checkbox-size); 16 + min-width: var(--checkbox-size); 17 + padding: 1px; 18 + } 19 + 20 + .ui.checkbox input[type="checkbox"], 21 + .ui.checkbox input[type="radio"] { 22 + position: absolute; 23 + top: 0; 24 + left: 0; 25 + width: var(--checkbox-size); 26 + height: var(--checkbox-size); 27 + } 28 + 29 + .ui.checkbox input[type="checkbox"]:enabled, 30 + .ui.checkbox input[type="radio"]:enabled, 31 + .ui.checkbox label:enabled { 32 + cursor: pointer; 33 + } 34 + 35 + .ui.checkbox label { 36 + cursor: auto; 37 + position: relative; 38 + display: block; 39 + user-select: none; 40 + } 41 + 42 + .ui.checkbox label, 43 + .ui.radio.checkbox label { 44 + margin-left: 1.85714em; 45 + } 46 + 47 + .ui.checkbox + label { 48 + vertical-align: middle; 49 + } 50 + 51 + .ui.disabled.checkbox label, 52 + .ui.checkbox input[disabled] ~ label { 53 + cursor: default !important; 54 + opacity: 0.5; 55 + pointer-events: none; 56 + } 57 + 58 + .ui.radio.checkbox { 59 + min-height: var(--checkbox-size); 60 + } 61 + 62 + /* "switch" styled checkbox */ 63 + 64 + .ui.toggle.checkbox { 65 + min-height: 1.5rem; 66 + } 67 + .ui.toggle.checkbox input { 68 + width: 3.5rem; 69 + height: 1.5rem; 70 + opacity: 0; 71 + z-index: 3; 72 + } 73 + .ui.toggle.checkbox label { 74 + min-height: 1.5rem; 75 + padding-left: 4.5rem; 76 + padding-top: 0.15em; 77 + } 78 + .ui.toggle.checkbox label::before { 79 + display: block; 80 + position: absolute; 81 + content: ""; 82 + z-index: 1; 83 + top: 0; 84 + width: 3.5rem; 85 + height: 1.5rem; 86 + border-radius: 500rem; 87 + left: 0; 88 + } 89 + .ui.toggle.checkbox label::after { 90 + background: var(--color-white); 91 + position: absolute; 92 + content: ""; 93 + opacity: 1; 94 + z-index: 2; 95 + width: 1.5rem; 96 + height: 1.5rem; 97 + top: 0; 98 + left: 0; 99 + border-radius: 500rem; 100 + transition: background 0.3s ease, left 0.3s ease; 101 + } 102 + .ui.toggle.checkbox input ~ label::after { 103 + left: -0.05rem; 104 + } 105 + .ui.toggle.checkbox input:checked ~ label::after { 106 + left: 2.15rem; 107 + } 108 + .ui.toggle.checkbox input:focus ~ label::before, 109 + .ui.toggle.checkbox label::before { 110 + background: var(--color-input-toggle-background); 111 + } 112 + .ui.toggle.checkbox label, 113 + .ui.toggle.checkbox input:checked ~ label, 114 + .ui.toggle.checkbox input:focus:checked ~ label { 115 + color: var(--color-text) !important; 116 + } 117 + .ui.toggle.checkbox input:checked ~ label::before, 118 + .ui.toggle.checkbox input:focus:checked ~ label::before { 119 + background: var(--color-primary) !important; 120 + }
+6
web_src/css/modules/header.css
··· 135 135 font-weight: var(--font-weight-normal); 136 136 } 137 137 138 + /* open dropdown menus to the left in right-attached headers */ 139 + .ui.attached.header > .ui.right .ui.dropdown .menu { 140 + right: 0; 141 + left: auto; 142 + } 143 + 138 144 /* if a .top.attached.header is followed by a .segment, add some margin */ 139 145 .ui.segments + .ui.top.attached.header, 140 146 .ui.attached.segment + .ui.top.attached.header {
+197
web_src/css/modules/input.css
··· 1 + /* based on Fomantic UI input module, with just the parts extracted that we use. If you find any 2 + unused rules here after refactoring, please remove them. */ 3 + 4 + .ui.input { 5 + position: relative; 6 + font-weight: var(--font-weight-normal); 7 + display: inline-flex; 8 + color: var(--color-input-text); 9 + } 10 + .ui.input > input { 11 + margin: 0; 12 + max-width: 100%; 13 + flex: 1 0 auto; 14 + outline: none; 15 + font-family: var(--fonts-regular); 16 + padding: 0.67857143em 1em; 17 + border: 1px solid var(--color-input-border); 18 + color: var(--color-input-text); 19 + border-radius: 0.28571429rem; 20 + line-height: var(--line-height-default); 21 + text-align: start; 22 + } 23 + 24 + .ui.disabled.input, 25 + .ui.input:not(.disabled) input[disabled] { 26 + opacity: var(--opacity-disabled); 27 + } 28 + .ui.disabled.input > input, 29 + .ui.input:not(.disabled) input[disabled] { 30 + pointer-events: none; 31 + } 32 + 33 + .ui.input.focus > input, 34 + .ui.input > input:focus { 35 + border-color: var(--color-primary); 36 + } 37 + 38 + .ui.input.error > input { 39 + background: var(--color-error-bg); 40 + border-color: var(--color-error-border); 41 + color: var(--color-error-text); 42 + } 43 + 44 + .ui.icon.input > i.icon { 45 + display: flex; 46 + align-items: center; 47 + justify-content: center; 48 + cursor: default; 49 + position: absolute; 50 + text-align: center; 51 + top: 0; 52 + right: 0; 53 + margin: 0; 54 + height: 100%; 55 + width: 2.67142857em; 56 + opacity: 0.5; 57 + border-radius: 0 0.28571429rem 0.28571429rem 0; 58 + pointer-events: none; 59 + padding: 4px; 60 + } 61 + 62 + .ui.icon.input > i.icon.is-loading { 63 + position: absolute !important; 64 + height: 28px; 65 + top: 4px; 66 + } 67 + 68 + .ui.icon.input > i.icon.is-loading > * { 69 + visibility: hidden; 70 + } 71 + 72 + .ui.ui.ui.ui.icon.input > textarea, 73 + .ui.ui.ui.ui.icon.input > input { 74 + padding-right: 2.67142857em; 75 + } 76 + .ui.icon.input > i.link.icon { 77 + cursor: pointer; 78 + } 79 + .ui.icon.input > i.circular.icon { 80 + top: 0.35em; 81 + right: 0.5em; 82 + } 83 + 84 + .ui[class*="left icon"].input > i.icon { 85 + right: auto; 86 + left: 1px; 87 + border-radius: 0.28571429rem 0 0 0.28571429rem; 88 + } 89 + .ui[class*="left icon"].input > i.circular.icon { 90 + right: auto; 91 + left: 0.5em; 92 + } 93 + .ui.ui.ui.ui[class*="left icon"].input > textarea, 94 + .ui.ui.ui.ui[class*="left icon"].input > input { 95 + padding-left: 2.67142857em; 96 + padding-right: 1em; 97 + } 98 + 99 + .ui.icon.input > textarea:focus ~ .icon, 100 + .ui.icon.input > input:focus ~ .icon { 101 + opacity: 1; 102 + } 103 + 104 + .ui.icon.input > textarea ~ i.icon { 105 + height: 3em; 106 + } 107 + 108 + .ui.form .field.error > .ui.action.input > .ui.button, 109 + .ui.action.input.error > .ui.button { 110 + border-top: 1px solid var(--color-error-border); 111 + border-bottom: 1px solid var(--color-error-border); 112 + } 113 + 114 + .ui.action.input > .button, 115 + .ui.action.input > .buttons { 116 + display: flex; 117 + align-items: center; 118 + flex: 0 0 auto; 119 + } 120 + .ui.action.input > .button, 121 + .ui.action.input > .buttons > .button { 122 + padding-top: 0.78571429em; 123 + padding-bottom: 0.78571429em; 124 + margin: 0; 125 + } 126 + 127 + .ui.action.input:not([class*="left action"]) > input { 128 + border-top-right-radius: 0; 129 + border-bottom-right-radius: 0; 130 + border-right-color: transparent; 131 + } 132 + 133 + .ui.action.input > .dropdown:first-child, 134 + .ui.action.input > .button:first-child, 135 + .ui.action.input > .buttons:first-child > .button { 136 + border-radius: 0.28571429rem 0 0 0.28571429rem; 137 + } 138 + .ui.action.input > .dropdown:not(:first-child), 139 + .ui.action.input > .button:not(:first-child), 140 + .ui.action.input > .buttons:not(:first-child) > .button { 141 + border-radius: 0; 142 + } 143 + .ui.action.input > .dropdown:last-child, 144 + .ui.action.input > .button:last-child, 145 + .ui.action.input > .buttons:last-child > .button { 146 + border-radius: 0 0.28571429rem 0.28571429rem 0; 147 + } 148 + 149 + .ui.fluid.input { 150 + display: flex; 151 + } 152 + .ui.fluid.input > input { 153 + width: 0 !important; 154 + } 155 + 156 + .ui.tiny.input { 157 + font-size: 0.85714286em; 158 + } 159 + .ui.small.input { 160 + font-size: 0.92857143em; 161 + } 162 + 163 + .ui.action.input .ui.ui.button { 164 + border-color: var(--color-input-border); 165 + padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */ 166 + padding-bottom: 0; 167 + } 168 + 169 + /* currently used for search bar dropdowns in repo search and explore code */ 170 + .ui.action.input:not([class*="left action"]) > .ui.dropdown.selection { 171 + min-width: 10em; 172 + } 173 + .ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) { 174 + border-right: none; 175 + } 176 + .ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover { 177 + border-color: var(--color-input-border); 178 + } 179 + .ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible { 180 + border-bottom-left-radius: 0 !important; 181 + border-bottom-right-radius: 0 !important; 182 + } 183 + .ui.action.input:not([class*="left action"]) > input, 184 + .ui.action.input:not([class*="left action"]) > input:hover { 185 + border-right: none; 186 + } 187 + .ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection, 188 + .ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover, 189 + .ui.action.input:not([class*="left action"]) > input:focus + .button, 190 + .ui.action.input:not([class*="left action"]) > input:focus + .button:hover, 191 + .ui.action.input:not([class*="left action"]) > input:focus + .icon + .button, 192 + .ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover { 193 + border-left-color: var(--color-primary); 194 + } 195 + .ui.action.input:not([class*="left action"]) > input:focus { 196 + border-right-color: var(--color-primary); 197 + }
+187
web_src/css/modules/list.css
··· 1 + /* based on Fomantic UI list module, with just the parts extracted that we use. If you find any 2 + unused rules here after refactoring, please remove them. */ 3 + 4 + .ui.list { 5 + list-style-type: none; 6 + margin: 1em 0; 7 + padding: 0; 8 + font-size: 1em; 9 + } 10 + 11 + .ui.list:first-child { 12 + margin-top: 0; 13 + padding-top: 0; 14 + } 15 + 16 + .ui.list:last-child { 17 + margin-bottom: 0; 18 + padding-bottom: 0; 19 + } 20 + 21 + .ui.list > .item, 22 + .ui.list .list > .item { 23 + display: list-item; 24 + table-layout: fixed; 25 + list-style-type: none; 26 + list-style-position: outside; 27 + } 28 + 29 + .ui.list > .list > .item::after, 30 + .ui.list > .item::after { 31 + content: ""; 32 + display: block; 33 + height: 0; 34 + clear: both; 35 + visibility: hidden; 36 + } 37 + 38 + .ui.list .list:not(.icon) { 39 + clear: both; 40 + margin: 0; 41 + padding: 0.75em 0 0.25em 0.5em; 42 + } 43 + 44 + .ui.list .list > .item { 45 + padding: 0.14285714em 0; 46 + } 47 + 48 + .ui.list .list > .item > i.icon, 49 + .ui.list > .item > i.icon { 50 + display: table-cell; 51 + min-width: 1.55em; 52 + padding-top: 0; 53 + transition: color 0.1s ease; 54 + padding-right: 0.28571429em; 55 + vertical-align: top; 56 + } 57 + .ui.list .list > .item > i.icon:only-child, 58 + .ui.list > .item > i.icon:only-child { 59 + display: inline-block; 60 + min-width: auto; 61 + vertical-align: top; 62 + } 63 + 64 + .ui.list .list > .item > .image, 65 + .ui.list > .item > .image { 66 + display: table-cell; 67 + background-color: transparent; 68 + vertical-align: top; 69 + } 70 + .ui.list .list > .item > .image:not(:only-child):not(img), 71 + .ui.list > .item > .image:not(:only-child):not(img) { 72 + padding-right: 0.5em; 73 + } 74 + .ui.list .list > .item > .image img, 75 + .ui.list > .item > .image img { 76 + vertical-align: top; 77 + } 78 + .ui.list .list > .item > img.image, 79 + .ui.list .list > .item > .image:only-child, 80 + .ui.list > .item > img.image, 81 + .ui.list > .item > .image:only-child { 82 + display: inline-block; 83 + } 84 + 85 + .ui.list .list > .item > .content, 86 + .ui.list > .item > .content { 87 + color: var(--color-text); 88 + } 89 + .ui.list .list > .item > .image + .content, 90 + .ui.list .list > .item > i.icon + .content, 91 + .ui.list > .item > .image + .content, 92 + .ui.list > .item > i.icon + .content { 93 + display: table-cell; 94 + width: 100%; 95 + padding: 0 0 0 0.5em; 96 + vertical-align: top; 97 + } 98 + .ui.list .list > .item > img.image + .content, 99 + .ui.list > .item > img.image + .content { 100 + display: inline-block; 101 + width: auto; 102 + } 103 + .ui.list .list > .item > .content > .list, 104 + .ui.list > .item > .content > .list { 105 + margin-left: 0; 106 + padding-left: 0; 107 + } 108 + 109 + .ui.list .list > .item .header, 110 + .ui.list > .item .header { 111 + display: block; 112 + margin: 0; 113 + font-family: var(--fonts-regular); 114 + font-weight: var(--font-weight-medium); 115 + color: var(--color-text-dark); 116 + } 117 + 118 + .ui.list .list > .item .description, 119 + .ui.list > .item .description { 120 + display: block; 121 + color: var(--color-text); 122 + } 123 + 124 + .ui.list > .item a, 125 + .ui.list .list > .item a { 126 + cursor: pointer; 127 + } 128 + 129 + .ui.menu .ui.list > .item, 130 + .ui.menu .ui.list .list > .item { 131 + display: list-item; 132 + table-layout: fixed; 133 + background-color: transparent; 134 + list-style-type: none; 135 + list-style-position: outside; 136 + padding: 0.21428571em 0; 137 + } 138 + .ui.menu .ui.list .list > .item::before, 139 + .ui.menu .ui.list > .item::before { 140 + border: none; 141 + background: none; 142 + } 143 + .ui.menu .ui.list .list > .item:first-child, 144 + .ui.menu .ui.list > .item:first-child { 145 + padding-top: 0; 146 + } 147 + .ui.menu .ui.list .list > .item:last-child, 148 + .ui.menu .ui.list > .item:last-child { 149 + padding-bottom: 0; 150 + } 151 + 152 + .ui.list .list > .disabled.item, 153 + .ui.list > .disabled.item { 154 + pointer-events: none; 155 + opacity: var(--opacity-disabled); 156 + } 157 + 158 + .ui.list .list > a.item:hover > .icons, 159 + .ui.list > a.item:hover > .icons, 160 + .ui.list .list > a.item:hover > i.icon, 161 + .ui.list > a.item:hover > i.icon { 162 + color: var(--color-text-dark); 163 + } 164 + 165 + .ui.divided.list > .item { 166 + border-top: 1px solid var(--color-secondary); 167 + } 168 + .ui.divided.list .list > .item { 169 + border-top: none; 170 + } 171 + .ui.divided.list .item .list > .item { 172 + border-top: none; 173 + } 174 + .ui.divided.list .list > .item:first-child, 175 + .ui.divided.list > .item:first-child { 176 + border-top: none; 177 + } 178 + .ui.divided.list .list > .item:first-child { 179 + border-top-width: 1px; 180 + } 181 + 182 + .ui.relaxed.list > .item:not(:first-child) { 183 + padding-top: 0.42857143em; 184 + } 185 + .ui.relaxed.list > .item:not(:last-child) { 186 + padding-bottom: 0.42857143em; 187 + }
+5
web_src/css/modules/navbar.css
··· 140 140 .secondary-nav { 141 141 background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */ 142 142 } 143 + 144 + .issue-navbar { 145 + display: flex; 146 + justify-content: space-between; 147 + }
+11
web_src/css/modules/tippy.css
··· 29 29 z-index: 1; 30 30 } 31 31 32 + /* bare theme, no styling at all, except box-shadow */ 33 + .tippy-box[data-theme="bare"] { 34 + border: none; 35 + box-shadow: 0 6px 18px var(--color-shadow); 36 + } 37 + 38 + .tippy-box[data-theme="bare"] .tippy-content { 39 + padding: 0; 40 + background: transparent; 41 + } 42 + 32 43 /* tooltip theme for text tooltips */ 33 44 34 45 .tippy-box[data-theme="tooltip"] {
-4
web_src/css/org.css
··· 89 89 text-align: center; 90 90 } 91 91 92 - .organization.options input { 93 - min-width: 300px; 94 - } 95 - 96 92 .page-content.organization .org-avatar { 97 93 margin-right: 15px; 98 94 }
+2 -98
web_src/css/repo.css
··· 2299 2299 padding-top: 15px; 2300 2300 } 2301 2301 2302 - .edit-label.modal .form .color.picker.column, 2303 - .new-label.modal .form .color.picker.column { 2304 - display: flex; 2305 - } 2306 - 2307 - .edit-label.modal .form .color.picker.column .minicolors, 2308 - .new-label.modal .form .color.picker.column .minicolors { 2309 - flex: 1; 2310 - } 2311 - 2312 - .edit-label.modal .form .minicolors-swatch.minicolors-sprite, 2313 - .new-label.modal .form .minicolors-swatch.minicolors-sprite { 2314 - top: 10px; 2315 - left: 10px; 2316 - width: 15px; 2317 - height: 15px; 2318 - } 2319 - 2320 - .tab-size-1 { 2321 - tab-size: 1 !important; 2322 - -moz-tab-size: 1 !important; 2323 - } 2324 - 2325 - .tab-size-2 { 2326 - tab-size: 2 !important; 2327 - -moz-tab-size: 2 !important; 2328 - } 2329 - 2330 - .tab-size-3 { 2331 - tab-size: 3 !important; 2332 - -moz-tab-size: 3 !important; 2333 - } 2334 - 2335 - .tab-size-4 { 2336 - tab-size: 4 !important; 2337 - -moz-tab-size: 4 !important; 2338 - } 2339 - 2340 - .tab-size-5 { 2341 - tab-size: 5 !important; 2342 - -moz-tab-size: 5 !important; 2343 - } 2344 - 2345 - .tab-size-6 { 2346 - tab-size: 6 !important; 2347 - -moz-tab-size: 6 !important; 2348 - } 2349 - 2350 - .tab-size-7 { 2351 - tab-size: 7 !important; 2352 - -moz-tab-size: 7 !important; 2353 - } 2354 - 2355 - .tab-size-8 { 2356 - tab-size: 8 !important; 2357 - -moz-tab-size: 8 !important; 2358 - } 2359 - 2360 - .tab-size-9 { 2361 - tab-size: 9 !important; 2362 - -moz-tab-size: 9 !important; 2363 - } 2364 - 2365 - .tab-size-10 { 2366 - tab-size: 10 !important; 2367 - -moz-tab-size: 10 !important; 2368 - } 2369 - 2370 - .tab-size-11 { 2371 - tab-size: 11 !important; 2372 - -moz-tab-size: 11 !important; 2373 - } 2374 - 2375 - .tab-size-12 { 2376 - tab-size: 12 !important; 2377 - -moz-tab-size: 12 !important; 2378 - } 2379 - 2380 - .tab-size-13 { 2381 - tab-size: 13 !important; 2382 - -moz-tab-size: 13 !important; 2383 - } 2384 - 2385 - .tab-size-14 { 2386 - tab-size: 14 !important; 2387 - -moz-tab-size: 14 !important; 2388 - } 2389 - 2390 - .tab-size-15 { 2391 - tab-size: 15 !important; 2392 - -moz-tab-size: 15 !important; 2393 - } 2394 - 2395 - .tab-size-16 { 2396 - tab-size: 16 !important; 2397 - -moz-tab-size: 16 !important; 2398 - } 2399 - 2400 2302 .stats-table { 2401 2303 display: table; 2402 2304 width: 100%; ··· 2573 2475 #repo-topics .repo-topic { 2574 2476 font-weight: var(--font-weight-normal); 2575 2477 cursor: pointer; 2478 + margin: 0; 2576 2479 } 2577 2480 2578 2481 #new-dependency-drop-list.ui.selection.dropdown { ··· 2990 2893 display: flex; 2991 2894 align-items: center; 2992 2895 justify-content: flex-end; 2896 + gap: 8px; 2993 2897 } 2994 2898 2995 2899 @media (max-width: 767.98px) {
+1
web_src/css/repo/issue-list.css
··· 9 9 10 10 .issue-list-toolbar-left { 11 11 display: flex; 12 + align-items: center; 12 13 } 13 14 14 15 .issue-list-toolbar-right .filter.menu {
-2430
web_src/fomantic/build/semantic.css
··· 2327 2327 Site Overrides 2328 2328 *******************************/ 2329 2329 /*! 2330 - * # Fomantic-UI - Checkbox 2331 - * http://github.com/fomantic/Fomantic-UI/ 2332 - * 2333 - * 2334 - * Released under the MIT license 2335 - * http://opensource.org/licenses/MIT 2336 - * 2337 - */ 2338 - 2339 - /******************************* 2340 - Checkbox 2341 - *******************************/ 2342 - 2343 - /*-------------- 2344 - Content 2345 - ---------------*/ 2346 - 2347 - .ui.checkbox { 2348 - position: relative; 2349 - display: inline-block; 2350 - backface-visibility: hidden; 2351 - outline: none; 2352 - vertical-align: baseline; 2353 - font-style: normal; 2354 - min-height: 17px; 2355 - font-size: 1em; 2356 - line-height: 17px; 2357 - min-width: 17px; 2358 - } 2359 - 2360 - /* HTML Checkbox */ 2361 - 2362 - .ui.checkbox input[type="checkbox"], 2363 - .ui.checkbox input[type="radio"] { 2364 - cursor: pointer; 2365 - position: absolute; 2366 - top: 0; 2367 - left: 0; 2368 - opacity: 0 !important; 2369 - outline: none; 2370 - z-index: 3; 2371 - width: 17px; 2372 - height: 17px; 2373 - } 2374 - 2375 - .ui.checkbox label { 2376 - cursor: auto; 2377 - position: relative; 2378 - display: block; 2379 - padding-left: 1.85714em; 2380 - outline: none; 2381 - font-size: 1em; 2382 - } 2383 - 2384 - .ui.checkbox label:before { 2385 - position: absolute; 2386 - top: 0; 2387 - left: 0; 2388 - width: 17px; 2389 - height: 17px; 2390 - content: ''; 2391 - background: #FFFFFF; 2392 - border-radius: 0.21428571rem; 2393 - transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease; 2394 - border: 1px solid #D4D4D5; 2395 - } 2396 - 2397 - /*-------------- 2398 - Checkmark 2399 - ---------------*/ 2400 - 2401 - .ui.checkbox label:after { 2402 - position: absolute; 2403 - font-size: 14px; 2404 - top: 0; 2405 - left: 0; 2406 - width: 17px; 2407 - height: 17px; 2408 - text-align: center; 2409 - opacity: 0; 2410 - color: rgba(0, 0, 0, 0.87); 2411 - transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease; 2412 - } 2413 - 2414 - /*-------------- 2415 - Label 2416 - ---------------*/ 2417 - 2418 - /* Inside */ 2419 - 2420 - .ui.checkbox label, 2421 - .ui.checkbox + label { 2422 - color: rgba(0, 0, 0, 0.87); 2423 - transition: color 0.1s ease; 2424 - } 2425 - 2426 - /* Outside */ 2427 - 2428 - .ui.checkbox + label { 2429 - vertical-align: middle; 2430 - } 2431 - 2432 - /******************************* 2433 - States 2434 - *******************************/ 2435 - 2436 - /*-------------- 2437 - Hover 2438 - ---------------*/ 2439 - 2440 - .ui.checkbox label:hover::before { 2441 - background: #FFFFFF; 2442 - border-color: rgba(34, 36, 38, 0.35); 2443 - } 2444 - 2445 - .ui.checkbox label:hover, 2446 - .ui.checkbox + label:hover { 2447 - color: rgba(0, 0, 0, 0.8); 2448 - } 2449 - 2450 - /*-------------- 2451 - Down 2452 - ---------------*/ 2453 - 2454 - .ui.checkbox label:active::before { 2455 - background: #F9FAFB; 2456 - border-color: rgba(34, 36, 38, 0.35); 2457 - } 2458 - 2459 - .ui.checkbox label:active::after { 2460 - color: rgba(0, 0, 0, 0.95); 2461 - } 2462 - 2463 - .ui.checkbox input:active ~ label { 2464 - color: rgba(0, 0, 0, 0.95); 2465 - } 2466 - 2467 - /*-------------- 2468 - Focus 2469 - ---------------*/ 2470 - 2471 - .ui.checkbox input:focus ~ label:before { 2472 - background: #FFFFFF; 2473 - border-color: #96C8DA; 2474 - } 2475 - 2476 - .ui.checkbox input:focus ~ label:after { 2477 - color: rgba(0, 0, 0, 0.95); 2478 - } 2479 - 2480 - .ui.checkbox input:focus ~ label { 2481 - color: rgba(0, 0, 0, 0.95); 2482 - } 2483 - 2484 - /*-------------- 2485 - Active 2486 - ---------------*/ 2487 - 2488 - .ui.checkbox input:checked ~ label:before { 2489 - background: #FFFFFF; 2490 - border-color: rgba(34, 36, 38, 0.35); 2491 - } 2492 - 2493 - .ui.checkbox input:checked ~ label:after { 2494 - opacity: 1; 2495 - color: rgba(0, 0, 0, 0.95); 2496 - } 2497 - 2498 - /*-------------- 2499 - Indeterminate 2500 - ---------------*/ 2501 - 2502 - .ui.checkbox input:not([type=radio]):indeterminate ~ label:before { 2503 - background: #FFFFFF; 2504 - border-color: rgba(34, 36, 38, 0.35); 2505 - } 2506 - 2507 - .ui.checkbox input:not([type=radio]):indeterminate ~ label:after { 2508 - opacity: 1; 2509 - color: rgba(0, 0, 0, 0.95); 2510 - } 2511 - 2512 - .ui.indeterminate.toggle.checkbox input:not([type=radio]):indeterminate ~ label:before { 2513 - background: rgba(0, 0, 0, 0.15); 2514 - } 2515 - 2516 - .ui.indeterminate.toggle.checkbox input:not([type=radio]) ~ label:after { 2517 - left: 1.075rem; 2518 - } 2519 - 2520 - /*-------------- 2521 - Active Focus 2522 - ---------------*/ 2523 - 2524 - .ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:before, 2525 - .ui.checkbox input:checked:focus ~ label:before { 2526 - background: #FFFFFF; 2527 - border-color: #96C8DA; 2528 - } 2529 - 2530 - .ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:after, 2531 - .ui.checkbox input:checked:focus ~ label:after { 2532 - color: rgba(0, 0, 0, 0.95); 2533 - } 2534 - 2535 - /*-------------- 2536 - Read-Only 2537 - ---------------*/ 2538 - 2539 - .ui.read-only.checkbox, 2540 - .ui.read-only.checkbox label { 2541 - cursor: default; 2542 - } 2543 - 2544 - /*-------------- 2545 - Disabled 2546 - ---------------*/ 2547 - 2548 - .ui.disabled.checkbox label, 2549 - .ui.checkbox input[disabled] ~ label { 2550 - cursor: default !important; 2551 - opacity: 0.5; 2552 - color: #000000; 2553 - pointer-events: none; 2554 - } 2555 - 2556 - /*-------------- 2557 - Hidden 2558 - ---------------*/ 2559 - 2560 - /* Initialized checkbox moves input below element 2561 - to prevent manually triggering */ 2562 - 2563 - .ui.checkbox input.hidden { 2564 - z-index: -1; 2565 - } 2566 - 2567 - /* Selectable Label */ 2568 - 2569 - .ui.checkbox input.hidden + label { 2570 - cursor: pointer; 2571 - -webkit-user-select: none; 2572 - -moz-user-select: none; 2573 - user-select: none; 2574 - } 2575 - 2576 - /******************************* 2577 - Types 2578 - *******************************/ 2579 - 2580 - /*-------------- 2581 - Radio 2582 - ---------------*/ 2583 - 2584 - .ui.radio.checkbox { 2585 - min-height: 15px; 2586 - } 2587 - 2588 - .ui.radio.checkbox label { 2589 - padding-left: 1.85714em; 2590 - } 2591 - 2592 - /* Box */ 2593 - 2594 - .ui.radio.checkbox label:before { 2595 - content: ''; 2596 - transform: none; 2597 - width: 15px; 2598 - height: 15px; 2599 - border-radius: 500rem; 2600 - top: 1px; 2601 - left: 0; 2602 - } 2603 - 2604 - /* Bullet */ 2605 - 2606 - .ui.radio.checkbox label:after { 2607 - border: none; 2608 - content: '' !important; 2609 - line-height: 15px; 2610 - top: 1px; 2611 - left: 0; 2612 - width: 15px; 2613 - height: 15px; 2614 - border-radius: 500rem; 2615 - transform: scale(0.46666667); 2616 - background-color: rgba(0, 0, 0, 0.87); 2617 - } 2618 - 2619 - /* Focus */ 2620 - 2621 - .ui.radio.checkbox input:focus ~ label:before { 2622 - background-color: #FFFFFF; 2623 - } 2624 - 2625 - .ui.radio.checkbox input:focus ~ label:after { 2626 - background-color: rgba(0, 0, 0, 0.95); 2627 - } 2628 - 2629 - /* Indeterminate */ 2630 - 2631 - .ui.radio.checkbox input:indeterminate ~ label:after { 2632 - opacity: 0; 2633 - } 2634 - 2635 - /* Active */ 2636 - 2637 - .ui.radio.checkbox input:checked ~ label:before { 2638 - background-color: #FFFFFF; 2639 - } 2640 - 2641 - .ui.radio.checkbox input:checked ~ label:after { 2642 - background-color: rgba(0, 0, 0, 0.95); 2643 - } 2644 - 2645 - /* Active Focus */ 2646 - 2647 - .ui.radio.checkbox input:focus:checked ~ label:before { 2648 - background-color: #FFFFFF; 2649 - } 2650 - 2651 - .ui.radio.checkbox input:focus:checked ~ label:after { 2652 - background-color: rgba(0, 0, 0, 0.95); 2653 - } 2654 - 2655 - /*-------------- 2656 - Slider 2657 - ---------------*/ 2658 - 2659 - .ui.slider.checkbox { 2660 - min-height: 1.25rem; 2661 - } 2662 - 2663 - /* Input */ 2664 - 2665 - .ui.slider.checkbox input { 2666 - width: 3.5rem; 2667 - height: 1.25rem; 2668 - } 2669 - 2670 - /* Label */ 2671 - 2672 - .ui.slider.checkbox label { 2673 - padding-left: 4.5rem; 2674 - line-height: 1rem; 2675 - color: rgba(0, 0, 0, 0.4); 2676 - } 2677 - 2678 - /* Line */ 2679 - 2680 - .ui.slider.checkbox label:before { 2681 - display: block; 2682 - position: absolute; 2683 - content: ''; 2684 - transform: none; 2685 - border: none !important; 2686 - left: 0; 2687 - z-index: 1; 2688 - top: 0.4rem; 2689 - background-color: rgba(0, 0, 0, 0.05); 2690 - width: 3.5rem; 2691 - height: 0.21428571rem; 2692 - border-radius: 500rem; 2693 - transition: background 0.3s ease; 2694 - } 2695 - 2696 - /* Handle */ 2697 - 2698 - .ui.slider.checkbox label:after { 2699 - background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05)); 2700 - position: absolute; 2701 - content: '' !important; 2702 - opacity: 1; 2703 - z-index: 2; 2704 - border: none; 2705 - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; 2706 - width: 1.5rem; 2707 - height: 1.5rem; 2708 - top: -0.25rem; 2709 - left: 0; 2710 - transform: none; 2711 - border-radius: 500rem; 2712 - transition: left 0.3s ease; 2713 - } 2714 - 2715 - /* Focus */ 2716 - 2717 - .ui.slider.checkbox input:focus ~ label:before { 2718 - background-color: rgba(0, 0, 0, 0.15); 2719 - border: none; 2720 - } 2721 - 2722 - /* Hover */ 2723 - 2724 - .ui.slider.checkbox label:hover { 2725 - color: rgba(0, 0, 0, 0.8); 2726 - } 2727 - 2728 - .ui.slider.checkbox label:hover::before { 2729 - background: rgba(0, 0, 0, 0.15); 2730 - } 2731 - 2732 - /* Active */ 2733 - 2734 - .ui.slider.checkbox input:checked ~ label { 2735 - color: rgba(0, 0, 0, 0.95) !important; 2736 - } 2737 - 2738 - .ui.slider.checkbox input:checked ~ label:before { 2739 - background-color: #545454 !important; 2740 - } 2741 - 2742 - .ui.slider.checkbox input:checked ~ label:after { 2743 - left: 2rem; 2744 - } 2745 - 2746 - /* Active Focus */ 2747 - 2748 - .ui.slider.checkbox input:focus:checked ~ label { 2749 - color: rgba(0, 0, 0, 0.95) !important; 2750 - } 2751 - 2752 - .ui.slider.checkbox input:focus:checked ~ label:before { 2753 - background-color: #000000 !important; 2754 - } 2755 - 2756 - /*-------------- 2757 - Toggle 2758 - ---------------*/ 2759 - 2760 - .ui.toggle.checkbox { 2761 - min-height: 1.5rem; 2762 - } 2763 - 2764 - /* Input */ 2765 - 2766 - .ui.toggle.checkbox input { 2767 - width: 3.5rem; 2768 - height: 1.5rem; 2769 - } 2770 - 2771 - /* Label */ 2772 - 2773 - .ui.toggle.checkbox label { 2774 - min-height: 1.5rem; 2775 - padding-left: 4.5rem; 2776 - color: rgba(0, 0, 0, 0.87); 2777 - } 2778 - 2779 - .ui.toggle.checkbox label { 2780 - padding-top: 0.15em; 2781 - } 2782 - 2783 - /* Switch */ 2784 - 2785 - .ui.toggle.checkbox label:before { 2786 - display: block; 2787 - position: absolute; 2788 - content: ''; 2789 - z-index: 1; 2790 - transform: none; 2791 - border: none; 2792 - top: 0; 2793 - background: rgba(0, 0, 0, 0.05); 2794 - box-shadow: none; 2795 - width: 3.5rem; 2796 - height: 1.5rem; 2797 - border-radius: 500rem; 2798 - } 2799 - 2800 - /* Handle */ 2801 - 2802 - .ui.toggle.checkbox label:after { 2803 - background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05)); 2804 - position: absolute; 2805 - content: '' !important; 2806 - opacity: 1; 2807 - z-index: 2; 2808 - border: none; 2809 - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; 2810 - width: 1.5rem; 2811 - height: 1.5rem; 2812 - top: 0; 2813 - left: 0; 2814 - border-radius: 500rem; 2815 - transition: background 0.3s ease, left 0.3s ease; 2816 - } 2817 - 2818 - .ui.toggle.checkbox input ~ label:after { 2819 - left: -0.05rem; 2820 - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; 2821 - } 2822 - 2823 - /* Focus */ 2824 - 2825 - .ui.toggle.checkbox input:focus ~ label:before { 2826 - background-color: rgba(0, 0, 0, 0.15); 2827 - border: none; 2828 - } 2829 - 2830 - /* Hover */ 2831 - 2832 - .ui.toggle.checkbox label:hover::before { 2833 - background-color: rgba(0, 0, 0, 0.15); 2834 - border: none; 2835 - } 2836 - 2837 - /* Active */ 2838 - 2839 - .ui.toggle.checkbox input:checked ~ label { 2840 - color: rgba(0, 0, 0, 0.95) !important; 2841 - } 2842 - 2843 - .ui.toggle.checkbox input:checked ~ label:before { 2844 - background-color: #2185D0 !important; 2845 - } 2846 - 2847 - .ui.toggle.checkbox input:checked ~ label:after { 2848 - left: 2.15rem; 2849 - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; 2850 - } 2851 - 2852 - /* Active Focus */ 2853 - 2854 - .ui.toggle.checkbox input:focus:checked ~ label { 2855 - color: rgba(0, 0, 0, 0.95) !important; 2856 - } 2857 - 2858 - .ui.toggle.checkbox input:focus:checked ~ label:before { 2859 - background-color: #0d71bb !important; 2860 - } 2861 - 2862 - /******************************* 2863 - Variations 2864 - *******************************/ 2865 - 2866 - /*-------------- 2867 - Fitted 2868 - ---------------*/ 2869 - 2870 - .ui.fitted.checkbox label { 2871 - padding-left: 0 !important; 2872 - } 2873 - 2874 - .ui.fitted.toggle.checkbox { 2875 - width: 3.5rem; 2876 - } 2877 - 2878 - .ui.fitted.slider.checkbox { 2879 - width: 3.5rem; 2880 - } 2881 - 2882 - /*-------------------- 2883 - Size 2884 - ---------------------*/ 2885 - 2886 - .ui.mini.checkbox { 2887 - font-size: 0.78571429em; 2888 - } 2889 - 2890 - .ui.tiny.checkbox { 2891 - font-size: 0.85714286em; 2892 - } 2893 - 2894 - .ui.small.checkbox { 2895 - font-size: 0.92857143em; 2896 - } 2897 - 2898 - .ui.large.checkbox { 2899 - font-size: 1.14285714em; 2900 - } 2901 - 2902 - .ui.large.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2903 - .ui.large.checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2904 - .ui.large.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, 2905 - .ui.large.checkbox:not(.slider):not(.toggle):not(.radio) label:before { 2906 - transform: scale(1.14285714); 2907 - transform-origin: left; 2908 - } 2909 - 2910 - .ui.large.form .checkbox.radio label:before, 2911 - .ui.large.checkbox.radio label:before { 2912 - transform: scale(1.14285714); 2913 - transform-origin: left; 2914 - } 2915 - 2916 - .ui.large.form .checkbox.radio label:after, 2917 - .ui.large.checkbox.radio label:after { 2918 - transform: scale(0.57142857); 2919 - transform-origin: left; 2920 - left: 0.33571429em; 2921 - } 2922 - 2923 - .ui.big.checkbox { 2924 - font-size: 1.28571429em; 2925 - } 2926 - 2927 - .ui.big.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2928 - .ui.big.checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2929 - .ui.big.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, 2930 - .ui.big.checkbox:not(.slider):not(.toggle):not(.radio) label:before { 2931 - transform: scale(1.28571429); 2932 - transform-origin: left; 2933 - } 2934 - 2935 - .ui.big.form .checkbox.radio label:before, 2936 - .ui.big.checkbox.radio label:before { 2937 - transform: scale(1.28571429); 2938 - transform-origin: left; 2939 - } 2940 - 2941 - .ui.big.form .checkbox.radio label:after, 2942 - .ui.big.checkbox.radio label:after { 2943 - transform: scale(0.64285714); 2944 - transform-origin: left; 2945 - left: 0.37142857em; 2946 - } 2947 - 2948 - .ui.huge.checkbox { 2949 - font-size: 1.42857143em; 2950 - } 2951 - 2952 - .ui.huge.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2953 - .ui.huge.checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2954 - .ui.huge.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, 2955 - .ui.huge.checkbox:not(.slider):not(.toggle):not(.radio) label:before { 2956 - transform: scale(1.42857143); 2957 - transform-origin: left; 2958 - } 2959 - 2960 - .ui.huge.form .checkbox.radio label:before, 2961 - .ui.huge.checkbox.radio label:before { 2962 - transform: scale(1.42857143); 2963 - transform-origin: left; 2964 - } 2965 - 2966 - .ui.huge.form .checkbox.radio label:after, 2967 - .ui.huge.checkbox.radio label:after { 2968 - transform: scale(0.71428571); 2969 - transform-origin: left; 2970 - left: 0.40714286em; 2971 - } 2972 - 2973 - .ui.massive.checkbox { 2974 - font-size: 1.71428571em; 2975 - } 2976 - 2977 - .ui.massive.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2978 - .ui.massive.checkbox:not(.slider):not(.toggle):not(.radio) label:after, 2979 - .ui.massive.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, 2980 - .ui.massive.checkbox:not(.slider):not(.toggle):not(.radio) label:before { 2981 - transform: scale(1.71428571); 2982 - transform-origin: left; 2983 - } 2984 - 2985 - .ui.massive.form .checkbox.radio label:before, 2986 - .ui.massive.checkbox.radio label:before { 2987 - transform: scale(1.71428571); 2988 - transform-origin: left; 2989 - } 2990 - 2991 - .ui.massive.form .checkbox.radio label:after, 2992 - .ui.massive.checkbox.radio label:after { 2993 - transform: scale(0.85714286); 2994 - transform-origin: left; 2995 - left: 0.47857143em; 2996 - } 2997 - 2998 - /******************************* 2999 - Theme Overrides 3000 - *******************************/ 3001 - 3002 - @font-face { 3003 - font-family: 'Checkbox'; 3004 - src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBD8AAAC8AAAAYGNtYXAYVtCJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zn4huwUAAAF4AAABYGhlYWQGPe1ZAAAC2AAAADZoaGVhB30DyAAAAxAAAAAkaG10eBBKAEUAAAM0AAAAHGxvY2EAmgESAAADUAAAABBtYXhwAAkALwAAA2AAAAAgbmFtZSC8IugAAAOAAAABknBvc3QAAwAAAAAFFAAAACAAAwMTAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADoAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6AL//f//AAAAAAAg6AD//f//AAH/4xgEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAEUAUQO7AvgAGgAAARQHAQYjIicBJjU0PwE2MzIfAQE2MzIfARYVA7sQ/hQQFhcQ/uMQEE4QFxcQqAF2EBcXEE4QAnMWEP4UEBABHRAXFhBOEBCoAXcQEE4QFwAAAAABAAABbgMlAkkAFAAAARUUBwYjISInJj0BNDc2MyEyFxYVAyUQEBf9SRcQEBAQFwK3FxAQAhJtFxAQEBAXbRcQEBAQFwAAAAABAAAASQMlA24ALAAAARUUBwYrARUUBwYrASInJj0BIyInJj0BNDc2OwE1NDc2OwEyFxYdATMyFxYVAyUQEBfuEBAXbhYQEO4XEBAQEBfuEBAWbhcQEO4XEBACEm0XEBDuFxAQEBAX7hAQF20XEBDuFxAQEBAX7hAQFwAAAQAAAAIAAHRSzT9fDzz1AAsEAAAAAADRsdR3AAAAANGx1HcAAAAAA7sDbgAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADuwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAABFAyUAAAMlAAAAAAAAAAoAFAAeAE4AcgCwAAEAAAAHAC0AAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAIAAAAAQAAAAAAAgAHAGkAAQAAAAAAAwAIADkAAQAAAAAABAAIAH4AAQAAAAAABQALABgAAQAAAAAABgAIAFEAAQAAAAAACgAaAJYAAwABBAkAAQAQAAgAAwABBAkAAgAOAHAAAwABBAkAAwAQAEEAAwABBAkABAAQAIYAAwABBAkABQAWACMAAwABBAkABgAQAFkAAwABBAkACgA0ALBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhWZXJzaW9uIDIuMABWAGUAcgBzAGkAbwBuACAAMgAuADBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhDaGVja2JveABDAGgAZQBjAGsAYgBvAHhSZWd1bGFyAFIAZQBnAHUAbABhAHJDaGVja2JveABDAGgAZQBjAGsAYgBvAHhGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype'); 3005 - } 3006 - 3007 - /* Checkmark */ 3008 - 3009 - .ui.checkbox label:after, 3010 - .ui.checkbox .box:after { 3011 - font-family: 'Checkbox'; 3012 - } 3013 - 3014 - /* Checked */ 3015 - 3016 - .ui.checkbox input:checked ~ .box:after, 3017 - .ui.checkbox input:checked ~ label:after { 3018 - content: '\e800'; 3019 - } 3020 - 3021 - /* Indeterminate */ 3022 - 3023 - .ui.checkbox input:indeterminate ~ .box:after, 3024 - .ui.checkbox input:indeterminate ~ label:after { 3025 - font-size: 12px; 3026 - content: '\e801'; 3027 - } 3028 - 3029 - /* UTF Reference 3030 - .check:before { content: '\e800'; } 3031 - .dash:before { content: '\e801'; } 3032 - .plus:before { content: '\e802'; } 3033 - */ 3034 - 3035 - /******************************* 3036 - Site Overrides 3037 - *******************************/ 3038 - /*! 3039 2330 * # Fomantic-UI - Dimmer 3040 2331 * http://github.com/fomantic/Fomantic-UI/ 3041 2332 * ··· 7185 6476 7186 6477 /******************************* 7187 6478 Site Overrides 7188 - *******************************/ 7189 - /*! 7190 - * # Fomantic-UI - Input 7191 - * http://github.com/fomantic/Fomantic-UI/ 7192 - * 7193 - * 7194 - * Released under the MIT license 7195 - * http://opensource.org/licenses/MIT 7196 - * 7197 - */ 7198 - 7199 - /******************************* 7200 - Standard 7201 - *******************************/ 7202 - 7203 - /*-------------------- 7204 - Inputs 7205 - ---------------------*/ 7206 - 7207 - .ui.input { 7208 - position: relative; 7209 - font-weight: normal; 7210 - font-style: normal; 7211 - display: inline-flex; 7212 - color: rgba(0, 0, 0, 0.87); 7213 - } 7214 - 7215 - .ui.input > input { 7216 - margin: 0; 7217 - max-width: 100%; 7218 - flex: 1 0 auto; 7219 - outline: none; 7220 - -webkit-tap-highlight-color: rgba(255, 255, 255, 0); 7221 - text-align: left; 7222 - line-height: 1.21428571em; 7223 - font-family: var(--fonts-regular); 7224 - padding: 0.67857143em 1em; 7225 - background: #FFFFFF; 7226 - border: 1px solid rgba(34, 36, 38, 0.15); 7227 - color: rgba(0, 0, 0, 0.87); 7228 - border-radius: 0.28571429rem; 7229 - transition: box-shadow 0.1s ease, border-color 0.1s ease; 7230 - box-shadow: none; 7231 - } 7232 - 7233 - /*-------------------- 7234 - Placeholder 7235 - ---------------------*/ 7236 - 7237 - /* browsers require these rules separate */ 7238 - 7239 - .ui.input > input::-webkit-input-placeholder { 7240 - color: rgba(191, 191, 191, 0.87); 7241 - } 7242 - 7243 - .ui.input > input::-moz-placeholder { 7244 - color: rgba(191, 191, 191, 0.87); 7245 - } 7246 - 7247 - .ui.input > input:-ms-input-placeholder { 7248 - color: rgba(191, 191, 191, 0.87); 7249 - } 7250 - 7251 - /******************************* 7252 - States 7253 - *******************************/ 7254 - 7255 - /*-------------------- 7256 - Disabled 7257 - ---------------------*/ 7258 - 7259 - .ui.disabled.input, 7260 - .ui.input:not(.disabled) input[disabled] { 7261 - opacity: var(--opacity-disabled); 7262 - } 7263 - 7264 - .ui.disabled.input > input, 7265 - .ui.input:not(.disabled) input[disabled] { 7266 - pointer-events: none; 7267 - } 7268 - 7269 - /*-------------------- 7270 - Active 7271 - ---------------------*/ 7272 - 7273 - .ui.input > input:active, 7274 - .ui.input.down input { 7275 - border-color: rgba(0, 0, 0, 0.3); 7276 - background: #FAFAFA; 7277 - color: rgba(0, 0, 0, 0.87); 7278 - box-shadow: none; 7279 - } 7280 - 7281 - /*-------------------- 7282 - Loading 7283 - ---------------------*/ 7284 - 7285 - .ui.loading.loading.input > i.icon:before { 7286 - position: absolute; 7287 - content: ''; 7288 - top: 50%; 7289 - left: 50%; 7290 - margin: -0.64285714em 0 0 -0.64285714em; 7291 - width: 1.28571429em; 7292 - height: 1.28571429em; 7293 - border-radius: 500rem; 7294 - border: 0.2em solid rgba(0, 0, 0, 0.1); 7295 - } 7296 - 7297 - .ui.loading.loading.input > i.icon:after { 7298 - position: absolute; 7299 - content: ''; 7300 - top: 50%; 7301 - left: 50%; 7302 - margin: -0.64285714em 0 0 -0.64285714em; 7303 - width: 1.28571429em; 7304 - height: 1.28571429em; 7305 - animation: loader 0.6s infinite linear; 7306 - border: 0.2em solid #767676; 7307 - border-radius: 500rem; 7308 - box-shadow: 0 0 0 1px transparent; 7309 - } 7310 - 7311 - /*-------------------- 7312 - Focus 7313 - ---------------------*/ 7314 - 7315 - .ui.input.focus > input, 7316 - .ui.input > input:focus { 7317 - border-color: #85B7D9; 7318 - background: #FFFFFF; 7319 - color: rgba(0, 0, 0, 0.8); 7320 - box-shadow: none; 7321 - } 7322 - 7323 - .ui.input.focus > input::-webkit-input-placeholder, 7324 - .ui.input > input:focus::-webkit-input-placeholder { 7325 - color: rgba(115, 115, 115, 0.87); 7326 - } 7327 - 7328 - .ui.input.focus > input::-moz-placeholder, 7329 - .ui.input > input:focus::-moz-placeholder { 7330 - color: rgba(115, 115, 115, 0.87); 7331 - } 7332 - 7333 - .ui.input.focus > input:-ms-input-placeholder, 7334 - .ui.input > input:focus:-ms-input-placeholder { 7335 - color: rgba(115, 115, 115, 0.87); 7336 - } 7337 - 7338 - /*-------------------- 7339 - States 7340 - ---------------------*/ 7341 - 7342 - .ui.input.error > input { 7343 - background-color: #FFF6F6; 7344 - border-color: #E0B4B4; 7345 - color: #9F3A38; 7346 - box-shadow: none; 7347 - } 7348 - 7349 - /* Placeholder */ 7350 - 7351 - .ui.input.error > input::-webkit-input-placeholder { 7352 - color: #e7bdbc; 7353 - } 7354 - 7355 - .ui.input.error > input::-moz-placeholder { 7356 - color: #e7bdbc; 7357 - } 7358 - 7359 - .ui.input.error > input:-ms-input-placeholder { 7360 - color: #e7bdbc !important; 7361 - } 7362 - 7363 - /* Focused Placeholder */ 7364 - 7365 - .ui.input.error > input:focus::-webkit-input-placeholder { 7366 - color: #da9796; 7367 - } 7368 - 7369 - .ui.input.error > input:focus::-moz-placeholder { 7370 - color: #da9796; 7371 - } 7372 - 7373 - .ui.input.error > input:focus:-ms-input-placeholder { 7374 - color: #da9796 !important; 7375 - } 7376 - 7377 - .ui.input.info > input { 7378 - background-color: #F8FFFF; 7379 - border-color: #A9D5DE; 7380 - color: #276F86; 7381 - box-shadow: none; 7382 - } 7383 - 7384 - /* Placeholder */ 7385 - 7386 - .ui.input.info > input::-webkit-input-placeholder { 7387 - color: #98cfe1; 7388 - } 7389 - 7390 - .ui.input.info > input::-moz-placeholder { 7391 - color: #98cfe1; 7392 - } 7393 - 7394 - .ui.input.info > input:-ms-input-placeholder { 7395 - color: #98cfe1 !important; 7396 - } 7397 - 7398 - /* Focused Placeholder */ 7399 - 7400 - .ui.input.info > input:focus::-webkit-input-placeholder { 7401 - color: #70bdd6; 7402 - } 7403 - 7404 - .ui.input.info > input:focus::-moz-placeholder { 7405 - color: #70bdd6; 7406 - } 7407 - 7408 - .ui.input.info > input:focus:-ms-input-placeholder { 7409 - color: #70bdd6 !important; 7410 - } 7411 - 7412 - .ui.input.success > input { 7413 - background-color: #FCFFF5; 7414 - border-color: #A3C293; 7415 - color: #2C662D; 7416 - box-shadow: none; 7417 - } 7418 - 7419 - /* Placeholder */ 7420 - 7421 - .ui.input.success > input::-webkit-input-placeholder { 7422 - color: #8fcf90; 7423 - } 7424 - 7425 - .ui.input.success > input::-moz-placeholder { 7426 - color: #8fcf90; 7427 - } 7428 - 7429 - .ui.input.success > input:-ms-input-placeholder { 7430 - color: #8fcf90 !important; 7431 - } 7432 - 7433 - /* Focused Placeholder */ 7434 - 7435 - .ui.input.success > input:focus::-webkit-input-placeholder { 7436 - color: #6cbf6d; 7437 - } 7438 - 7439 - .ui.input.success > input:focus::-moz-placeholder { 7440 - color: #6cbf6d; 7441 - } 7442 - 7443 - .ui.input.success > input:focus:-ms-input-placeholder { 7444 - color: #6cbf6d !important; 7445 - } 7446 - 7447 - .ui.input.warning > input { 7448 - background-color: #FFFAF3; 7449 - border-color: #C9BA9B; 7450 - color: #573A08; 7451 - box-shadow: none; 7452 - } 7453 - 7454 - /* Placeholder */ 7455 - 7456 - .ui.input.warning > input::-webkit-input-placeholder { 7457 - color: #edad3e; 7458 - } 7459 - 7460 - .ui.input.warning > input::-moz-placeholder { 7461 - color: #edad3e; 7462 - } 7463 - 7464 - .ui.input.warning > input:-ms-input-placeholder { 7465 - color: #edad3e !important; 7466 - } 7467 - 7468 - /* Focused Placeholder */ 7469 - 7470 - .ui.input.warning > input:focus::-webkit-input-placeholder { 7471 - color: #e39715; 7472 - } 7473 - 7474 - .ui.input.warning > input:focus::-moz-placeholder { 7475 - color: #e39715; 7476 - } 7477 - 7478 - .ui.input.warning > input:focus:-ms-input-placeholder { 7479 - color: #e39715 !important; 7480 - } 7481 - 7482 - /******************************* 7483 - Variations 7484 - *******************************/ 7485 - 7486 - /*-------------------- 7487 - Transparent 7488 - ---------------------*/ 7489 - 7490 - .ui.transparent.input > textarea, 7491 - .ui.transparent.input > input { 7492 - border-color: transparent !important; 7493 - background-color: transparent !important; 7494 - padding: 0; 7495 - box-shadow: none !important; 7496 - border-radius: 0 !important; 7497 - } 7498 - 7499 - .field .ui.transparent.input > textarea { 7500 - padding: 0.67857143em 1em; 7501 - } 7502 - 7503 - /* Transparent Icon */ 7504 - 7505 - :not(.field) > .ui.transparent.icon.input > i.icon { 7506 - width: 1.1em; 7507 - } 7508 - 7509 - :not(.field) > .ui.ui.ui.transparent.icon.input > input { 7510 - padding-left: 0; 7511 - padding-right: 2em; 7512 - } 7513 - 7514 - :not(.field) > .ui.ui.ui.transparent[class*="left icon"].input > input { 7515 - padding-left: 2em; 7516 - padding-right: 0; 7517 - } 7518 - 7519 - /*-------------------- 7520 - Icon 7521 - ---------------------*/ 7522 - 7523 - .ui.icon.input > i.icon { 7524 - cursor: default; 7525 - position: absolute; 7526 - line-height: 1; 7527 - text-align: center; 7528 - top: 0; 7529 - right: 0; 7530 - margin: 0; 7531 - height: 100%; 7532 - width: 2.67142857em; 7533 - opacity: 0.5; 7534 - border-radius: 0 0.28571429rem 0.28571429rem 0; 7535 - transition: opacity 0.3s ease; 7536 - } 7537 - 7538 - .ui.icon.input > i.icon:not(.link) { 7539 - pointer-events: none; 7540 - } 7541 - 7542 - .ui.ui.ui.ui.icon.input > textarea, 7543 - .ui.ui.ui.ui.icon.input > input { 7544 - padding-right: 2.67142857em; 7545 - } 7546 - 7547 - .ui.icon.input > i.icon:before, 7548 - .ui.icon.input > i.icon:after { 7549 - left: 0; 7550 - position: absolute; 7551 - text-align: center; 7552 - top: 50%; 7553 - width: 100%; 7554 - margin-top: -0.5em; 7555 - } 7556 - 7557 - .ui.icon.input > i.link.icon { 7558 - cursor: pointer; 7559 - } 7560 - 7561 - .ui.icon.input > i.circular.icon { 7562 - top: 0.35em; 7563 - right: 0.5em; 7564 - } 7565 - 7566 - /* Left Icon Input */ 7567 - 7568 - .ui[class*="left icon"].input > i.icon { 7569 - right: auto; 7570 - left: 1px; 7571 - border-radius: 0.28571429rem 0 0 0.28571429rem; 7572 - } 7573 - 7574 - .ui[class*="left icon"].input > i.circular.icon { 7575 - right: auto; 7576 - left: 0.5em; 7577 - } 7578 - 7579 - .ui.ui.ui.ui[class*="left icon"].input > textarea, 7580 - .ui.ui.ui.ui[class*="left icon"].input > input { 7581 - padding-left: 2.67142857em; 7582 - padding-right: 1em; 7583 - } 7584 - 7585 - /* Focus */ 7586 - 7587 - .ui.icon.input > textarea:focus ~ i.icon, 7588 - .ui.icon.input > input:focus ~ i.icon { 7589 - opacity: 1; 7590 - } 7591 - 7592 - /*-------------------- 7593 - Labeled 7594 - ---------------------*/ 7595 - 7596 - /* Adjacent Label */ 7597 - 7598 - .ui.labeled.input > .label { 7599 - flex: 0 0 auto; 7600 - margin: 0; 7601 - font-size: 1em; 7602 - } 7603 - 7604 - .ui.labeled.input > .label:not(.corner) { 7605 - padding-top: 0.78571429em; 7606 - padding-bottom: 0.78571429em; 7607 - } 7608 - 7609 - /* Regular Label on Left */ 7610 - 7611 - .ui.labeled.input:not([class*="corner labeled"]) .label:first-child { 7612 - border-top-right-radius: 0; 7613 - border-bottom-right-radius: 0; 7614 - } 7615 - 7616 - .ui.labeled.input:not([class*="corner labeled"]) .label:first-child + input { 7617 - border-top-left-radius: 0; 7618 - border-bottom-left-radius: 0; 7619 - border-left-color: transparent; 7620 - } 7621 - 7622 - .ui.labeled.input:not([class*="corner labeled"]) .label:first-child + input:focus { 7623 - border-left-color: #85B7D9; 7624 - } 7625 - 7626 - /* Regular Label on Right */ 7627 - 7628 - .ui[class*="right labeled"].input > input { 7629 - border-top-right-radius: 0 !important; 7630 - border-bottom-right-radius: 0 !important; 7631 - border-right-color: transparent !important; 7632 - } 7633 - 7634 - .ui[class*="right labeled"].input > input + .label { 7635 - border-top-left-radius: 0; 7636 - border-bottom-left-radius: 0; 7637 - } 7638 - 7639 - .ui[class*="right labeled"].input > input:focus { 7640 - border-right-color: #85B7D9 !important; 7641 - } 7642 - 7643 - /* Corner Label */ 7644 - 7645 - .ui.labeled.input .corner.label { 7646 - top: 1px; 7647 - right: 1px; 7648 - font-size: 0.64285714em; 7649 - border-radius: 0 0.28571429rem 0 0; 7650 - } 7651 - 7652 - /* Spacing with corner label */ 7653 - 7654 - .ui[class*="corner labeled"]:not([class*="left corner labeled"]).labeled.input > textarea, 7655 - .ui[class*="corner labeled"]:not([class*="left corner labeled"]).labeled.input > input { 7656 - padding-right: 2.5em !important; 7657 - } 7658 - 7659 - .ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"]) > textarea, 7660 - .ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"]) > input { 7661 - padding-right: 3.25em !important; 7662 - } 7663 - 7664 - .ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"]) > i.icon { 7665 - margin-right: 1.25em; 7666 - } 7667 - 7668 - /* Left Labeled */ 7669 - 7670 - .ui[class*="left corner labeled"].labeled.input > textarea, 7671 - .ui[class*="left corner labeled"].labeled.input > input { 7672 - padding-left: 2.5em !important; 7673 - } 7674 - 7675 - .ui[class*="left corner labeled"].icon.input > textarea, 7676 - .ui[class*="left corner labeled"].icon.input > input { 7677 - padding-left: 3.25em !important; 7678 - } 7679 - 7680 - .ui[class*="left corner labeled"].icon.input > i.icon { 7681 - margin-left: 1.25em; 7682 - } 7683 - 7684 - .ui.icon.input > textarea ~ i.icon { 7685 - height: 3em; 7686 - } 7687 - 7688 - :not(.field) > .ui.transparent.icon.input > textarea ~ i.icon { 7689 - height: 1.3em; 7690 - } 7691 - 7692 - /* Corner Label Position */ 7693 - 7694 - .ui.input > .ui.corner.label { 7695 - top: 1px; 7696 - right: 1px; 7697 - } 7698 - 7699 - .ui.input > .ui.left.corner.label { 7700 - right: auto; 7701 - left: 1px; 7702 - } 7703 - 7704 - /* Labeled and action input states */ 7705 - 7706 - .ui.form .field.error > .ui.action.input > .ui.button, 7707 - .ui.form .field.error > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, 7708 - .ui.action.input.error > .ui.button, 7709 - .ui.labeled.input.error:not([class*="corner labeled"]) > .ui.label { 7710 - border-top: 1px solid #E0B4B4; 7711 - border-bottom: 1px solid #E0B4B4; 7712 - } 7713 - 7714 - .ui.form .field.error > .ui[class*="left action"].input > .ui.button, 7715 - .ui.form .field.error > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, 7716 - .ui[class*="left action"].input.error > .ui.button, 7717 - .ui.labeled.input.error:not(.right):not([class*="corner labeled"]) > .ui.label { 7718 - border-left: 1px solid #E0B4B4; 7719 - } 7720 - 7721 - .ui.form .field.error > .ui.action.input:not([class*="left action"]) > input + .ui.button, 7722 - .ui.form .field.error > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, 7723 - .ui.action.input.error:not([class*="left action"]) > input + .ui.button, 7724 - .ui.right.labeled.input.error:not([class*="corner labeled"]) > input + .ui.label { 7725 - border-right: 1px solid #E0B4B4; 7726 - } 7727 - 7728 - .ui.form .field.error > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, 7729 - .ui.right.labeled.input.error:not([class*="corner labeled"]) > .ui.label:first-child { 7730 - border-left: 1px solid #E0B4B4; 7731 - } 7732 - 7733 - .ui.form .field.info > .ui.action.input > .ui.button, 7734 - .ui.form .field.info > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, 7735 - .ui.action.input.info > .ui.button, 7736 - .ui.labeled.input.info:not([class*="corner labeled"]) > .ui.label { 7737 - border-top: 1px solid #A9D5DE; 7738 - border-bottom: 1px solid #A9D5DE; 7739 - } 7740 - 7741 - .ui.form .field.info > .ui[class*="left action"].input > .ui.button, 7742 - .ui.form .field.info > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, 7743 - .ui[class*="left action"].input.info > .ui.button, 7744 - .ui.labeled.input.info:not(.right):not([class*="corner labeled"]) > .ui.label { 7745 - border-left: 1px solid #A9D5DE; 7746 - } 7747 - 7748 - .ui.form .field.info > .ui.action.input:not([class*="left action"]) > input + .ui.button, 7749 - .ui.form .field.info > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, 7750 - .ui.action.input.info:not([class*="left action"]) > input + .ui.button, 7751 - .ui.right.labeled.input.info:not([class*="corner labeled"]) > input + .ui.label { 7752 - border-right: 1px solid #A9D5DE; 7753 - } 7754 - 7755 - .ui.form .field.info > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, 7756 - .ui.right.labeled.input.info:not([class*="corner labeled"]) > .ui.label:first-child { 7757 - border-left: 1px solid #A9D5DE; 7758 - } 7759 - 7760 - .ui.form .field.success > .ui.action.input > .ui.button, 7761 - .ui.form .field.success > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, 7762 - .ui.action.input.success > .ui.button, 7763 - .ui.labeled.input.success:not([class*="corner labeled"]) > .ui.label { 7764 - border-top: 1px solid #A3C293; 7765 - border-bottom: 1px solid #A3C293; 7766 - } 7767 - 7768 - .ui.form .field.success > .ui[class*="left action"].input > .ui.button, 7769 - .ui.form .field.success > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, 7770 - .ui[class*="left action"].input.success > .ui.button, 7771 - .ui.labeled.input.success:not(.right):not([class*="corner labeled"]) > .ui.label { 7772 - border-left: 1px solid #A3C293; 7773 - } 7774 - 7775 - .ui.form .field.success > .ui.action.input:not([class*="left action"]) > input + .ui.button, 7776 - .ui.form .field.success > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, 7777 - .ui.action.input.success:not([class*="left action"]) > input + .ui.button, 7778 - .ui.right.labeled.input.success:not([class*="corner labeled"]) > input + .ui.label { 7779 - border-right: 1px solid #A3C293; 7780 - } 7781 - 7782 - .ui.form .field.success > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, 7783 - .ui.right.labeled.input.success:not([class*="corner labeled"]) > .ui.label:first-child { 7784 - border-left: 1px solid #A3C293; 7785 - } 7786 - 7787 - .ui.form .field.warning > .ui.action.input > .ui.button, 7788 - .ui.form .field.warning > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, 7789 - .ui.action.input.warning > .ui.button, 7790 - .ui.labeled.input.warning:not([class*="corner labeled"]) > .ui.label { 7791 - border-top: 1px solid #C9BA9B; 7792 - border-bottom: 1px solid #C9BA9B; 7793 - } 7794 - 7795 - .ui.form .field.warning > .ui[class*="left action"].input > .ui.button, 7796 - .ui.form .field.warning > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, 7797 - .ui[class*="left action"].input.warning > .ui.button, 7798 - .ui.labeled.input.warning:not(.right):not([class*="corner labeled"]) > .ui.label { 7799 - border-left: 1px solid #C9BA9B; 7800 - } 7801 - 7802 - .ui.form .field.warning > .ui.action.input:not([class*="left action"]) > input + .ui.button, 7803 - .ui.form .field.warning > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, 7804 - .ui.action.input.warning:not([class*="left action"]) > input + .ui.button, 7805 - .ui.right.labeled.input.warning:not([class*="corner labeled"]) > input + .ui.label { 7806 - border-right: 1px solid #C9BA9B; 7807 - } 7808 - 7809 - .ui.form .field.warning > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, 7810 - .ui.right.labeled.input.warning:not([class*="corner labeled"]) > .ui.label:first-child { 7811 - border-left: 1px solid #C9BA9B; 7812 - } 7813 - 7814 - /*-------------------- 7815 - Action 7816 - ---------------------*/ 7817 - 7818 - .ui.action.input > .button, 7819 - .ui.action.input > .buttons { 7820 - display: flex; 7821 - align-items: center; 7822 - flex: 0 0 auto; 7823 - } 7824 - 7825 - .ui.action.input > .button, 7826 - .ui.action.input > .buttons > .button { 7827 - padding-top: 0.78571429em; 7828 - padding-bottom: 0.78571429em; 7829 - margin: 0; 7830 - } 7831 - 7832 - /* Input when ui Left*/ 7833 - 7834 - .ui[class*="left action"].input > input { 7835 - border-top-left-radius: 0; 7836 - border-bottom-left-radius: 0; 7837 - border-left-color: transparent; 7838 - } 7839 - 7840 - /* Input when ui Right*/ 7841 - 7842 - .ui.action.input:not([class*="left action"]) > input { 7843 - border-top-right-radius: 0; 7844 - border-bottom-right-radius: 0; 7845 - border-right-color: transparent; 7846 - } 7847 - 7848 - /* Button and Dropdown */ 7849 - 7850 - .ui.action.input > .dropdown:first-child, 7851 - .ui.action.input > .button:first-child, 7852 - .ui.action.input > .buttons:first-child > .button { 7853 - border-radius: 0.28571429rem 0 0 0.28571429rem; 7854 - } 7855 - 7856 - .ui.action.input > .dropdown:not(:first-child), 7857 - .ui.action.input > .button:not(:first-child), 7858 - .ui.action.input > .buttons:not(:first-child) > .button { 7859 - border-radius: 0; 7860 - } 7861 - 7862 - .ui.action.input > .dropdown:last-child, 7863 - .ui.action.input > .button:last-child, 7864 - .ui.action.input > .buttons:last-child > .button { 7865 - border-radius: 0 0.28571429rem 0.28571429rem 0; 7866 - } 7867 - 7868 - /* Input Focus */ 7869 - 7870 - .ui.action.input:not([class*="left action"]) > input:focus { 7871 - border-right-color: #85B7D9; 7872 - } 7873 - 7874 - .ui.ui[class*="left action"].input > input:focus { 7875 - border-left-color: #85B7D9; 7876 - } 7877 - 7878 - /*-------------------- 7879 - Fluid 7880 - ---------------------*/ 7881 - 7882 - .ui.fluid.input { 7883 - display: flex; 7884 - } 7885 - 7886 - .ui.fluid.input > input { 7887 - width: 0 !important; 7888 - } 7889 - 7890 - /*-------------------- 7891 - Size 7892 - ---------------------*/ 7893 - 7894 - .ui.input { 7895 - font-size: 1em; 7896 - } 7897 - 7898 - .ui.mini.input { 7899 - font-size: 0.78571429em; 7900 - } 7901 - 7902 - .ui.tiny.input { 7903 - font-size: 0.85714286em; 7904 - } 7905 - 7906 - .ui.small.input { 7907 - font-size: 0.92857143em; 7908 - } 7909 - 7910 - .ui.large.input { 7911 - font-size: 1.14285714em; 7912 - } 7913 - 7914 - .ui.big.input { 7915 - font-size: 1.28571429em; 7916 - } 7917 - 7918 - .ui.huge.input { 7919 - font-size: 1.42857143em; 7920 - } 7921 - 7922 - .ui.massive.input { 7923 - font-size: 1.71428571em; 7924 - } 7925 - 7926 - /******************************* 7927 - Theme Overrides 7928 - *******************************/ 7929 - 7930 - /******************************* 7931 - Site Overrides 7932 - *******************************/ 7933 - /*! 7934 - * # Fomantic-UI - List 7935 - * http://github.com/fomantic/Fomantic-UI/ 7936 - * 7937 - * 7938 - * Released under the MIT license 7939 - * http://opensource.org/licenses/MIT 7940 - * 7941 - */ 7942 - 7943 - /******************************* 7944 - List 7945 - *******************************/ 7946 - 7947 - ul.ui.list, 7948 - ol.ui.list, 7949 - .ui.list { 7950 - list-style-type: none; 7951 - margin: 1em 0; 7952 - padding: 0 0; 7953 - } 7954 - 7955 - ul.ui.list:first-child, 7956 - ol.ui.list:first-child, 7957 - .ui.list:first-child { 7958 - margin-top: 0; 7959 - padding-top: 0; 7960 - } 7961 - 7962 - ul.ui.list:last-child, 7963 - ol.ui.list:last-child, 7964 - .ui.list:last-child { 7965 - margin-bottom: 0; 7966 - padding-bottom: 0; 7967 - } 7968 - 7969 - /******************************* 7970 - Content 7971 - *******************************/ 7972 - 7973 - /* List Item */ 7974 - 7975 - ul.ui.list li, 7976 - ol.ui.list li, 7977 - .ui.list > .item, 7978 - .ui.list .list > .item { 7979 - display: list-item; 7980 - table-layout: fixed; 7981 - list-style-type: none; 7982 - list-style-position: outside; 7983 - padding: 0.21428571em 0; 7984 - line-height: 1.14285714em; 7985 - } 7986 - 7987 - ul.ui.list > li:first-child:after, 7988 - ol.ui.list > li:first-child:after, 7989 - .ui.list > .list > .item:after, 7990 - .ui.list > .item:after { 7991 - content: ''; 7992 - display: block; 7993 - height: 0; 7994 - clear: both; 7995 - visibility: hidden; 7996 - } 7997 - 7998 - ul.ui.list li:first-child, 7999 - ol.ui.list li:first-child, 8000 - .ui.list .list > .item:first-child, 8001 - .ui.list > .item:first-child { 8002 - padding-top: 0; 8003 - } 8004 - 8005 - ul.ui.list li:last-child, 8006 - ol.ui.list li:last-child, 8007 - .ui.list .list > .item:last-child, 8008 - .ui.list > .item:last-child { 8009 - padding-bottom: 0; 8010 - } 8011 - 8012 - /* Child List */ 8013 - 8014 - ul.ui.list ul, 8015 - ol.ui.list ol, 8016 - .ui.list .list:not(.icon) { 8017 - clear: both; 8018 - margin: 0; 8019 - padding: 0.75em 0 0.25em 0.5em; 8020 - } 8021 - 8022 - /* Child Item */ 8023 - 8024 - ul.ui.list ul li, 8025 - ol.ui.list ol li, 8026 - .ui.list .list > .item { 8027 - padding: 0.14285714em 0; 8028 - line-height: inherit; 8029 - } 8030 - 8031 - /* Icon */ 8032 - 8033 - .ui.list .list > .item > i.icon, 8034 - .ui.list > .item > i.icon { 8035 - display: table-cell; 8036 - min-width: 1.55em; 8037 - margin: 0; 8038 - padding-top: 0; 8039 - transition: color 0.1s ease; 8040 - } 8041 - 8042 - .ui.list .list > .item > i.icon:not(.loading), 8043 - .ui.list > .item > i.icon:not(.loading) { 8044 - padding-right: 0.28571429em; 8045 - vertical-align: top; 8046 - } 8047 - 8048 - .ui.list .list > .item > i.icon:only-child, 8049 - .ui.list > .item > i.icon:only-child { 8050 - display: inline-block; 8051 - min-width: auto; 8052 - vertical-align: top; 8053 - } 8054 - 8055 - /* Image */ 8056 - 8057 - .ui.list .list > .item > .image, 8058 - .ui.list > .item > .image { 8059 - display: table-cell; 8060 - background-color: transparent; 8061 - margin: 0; 8062 - vertical-align: top; 8063 - } 8064 - 8065 - .ui.list .list > .item > .image:not(:only-child):not(img), 8066 - .ui.list > .item > .image:not(:only-child):not(img) { 8067 - padding-right: 0.5em; 8068 - } 8069 - 8070 - .ui.list .list > .item > .image img, 8071 - .ui.list > .item > .image img { 8072 - vertical-align: top; 8073 - } 8074 - 8075 - .ui.list .list > .item > img.image, 8076 - .ui.list .list > .item > .image:only-child, 8077 - .ui.list > .item > img.image, 8078 - .ui.list > .item > .image:only-child { 8079 - display: inline-block; 8080 - } 8081 - 8082 - /* Content */ 8083 - 8084 - .ui.list .list > .item > .content, 8085 - .ui.list > .item > .content { 8086 - line-height: 1.14285714em; 8087 - color: rgba(0, 0, 0, 0.87); 8088 - } 8089 - 8090 - .ui.list .list > .item > .image + .content, 8091 - .ui.list .list > .item > i.icon + .content, 8092 - .ui.list > .item > .image + .content, 8093 - .ui.list > .item > i.icon + .content { 8094 - display: table-cell; 8095 - width: 100%; 8096 - padding: 0 0 0 0.5em; 8097 - vertical-align: top; 8098 - } 8099 - 8100 - .ui.list .list > .item > i.loading.icon + .content, 8101 - .ui.list > .item > i.loading.icon + .content { 8102 - padding-left: calc(0.2857142857142857em + 0.5em); 8103 - } 8104 - 8105 - .ui.list .list > .item > img.image + .content, 8106 - .ui.list > .item > img.image + .content { 8107 - display: inline-block; 8108 - width: auto; 8109 - } 8110 - 8111 - .ui.list .list > .item > .content > .list, 8112 - .ui.list > .item > .content > .list { 8113 - margin-left: 0; 8114 - padding-left: 0; 8115 - } 8116 - 8117 - /* Header */ 8118 - 8119 - .ui.list .list > .item .header, 8120 - .ui.list > .item .header { 8121 - display: block; 8122 - margin: 0; 8123 - font-family: var(--fonts-regular); 8124 - font-weight: 500; 8125 - color: rgba(0, 0, 0, 0.87); 8126 - } 8127 - 8128 - /* Description */ 8129 - 8130 - .ui.list .list > .item .description, 8131 - .ui.list > .item .description { 8132 - display: block; 8133 - color: rgba(0, 0, 0, 0.7); 8134 - } 8135 - 8136 - /* Child Link */ 8137 - 8138 - .ui.list > .item a, 8139 - .ui.list .list > .item a { 8140 - cursor: pointer; 8141 - } 8142 - 8143 - /* Linking Item */ 8144 - 8145 - .ui.list .list > a.item, 8146 - .ui.list > a.item { 8147 - cursor: pointer; 8148 - color: #4183C4; 8149 - } 8150 - 8151 - .ui.list .list > a.item:hover, 8152 - .ui.list > a.item:hover { 8153 - color: #1e70bf; 8154 - } 8155 - 8156 - /* Linked Item Icons */ 8157 - 8158 - .ui.list .list > a.item > i.icons, 8159 - .ui.list > a.item > i.icons, 8160 - .ui.list .list > a.item > i.icon, 8161 - .ui.list > a.item > i.icon { 8162 - color: rgba(0, 0, 0, 0.4); 8163 - } 8164 - 8165 - /* Header Link */ 8166 - 8167 - .ui.list .list > .item a.header, 8168 - .ui.list > .item a.header { 8169 - cursor: pointer; 8170 - color: #4183C4 !important; 8171 - } 8172 - 8173 - .ui.list .list > .item > a.header:hover, 8174 - .ui.list > .item > a.header:hover { 8175 - color: #1e70bf !important; 8176 - } 8177 - 8178 - /* Floated Content */ 8179 - 8180 - .ui[class*="left floated"].list { 8181 - float: left; 8182 - } 8183 - 8184 - .ui[class*="right floated"].list { 8185 - float: right; 8186 - } 8187 - 8188 - .ui.list .list > .item [class*="left floated"], 8189 - .ui.list > .item [class*="left floated"] { 8190 - float: left; 8191 - margin: 0 1em 0 0; 8192 - } 8193 - 8194 - .ui.list .list > .item [class*="right floated"], 8195 - .ui.list > .item [class*="right floated"] { 8196 - float: right; 8197 - margin: 0 0 0 1em; 8198 - } 8199 - 8200 - /******************************* 8201 - Coupling 8202 - *******************************/ 8203 - 8204 - .ui.menu .ui.list > .item, 8205 - .ui.menu .ui.list .list > .item { 8206 - display: list-item; 8207 - table-layout: fixed; 8208 - background-color: transparent; 8209 - list-style-type: none; 8210 - list-style-position: outside; 8211 - padding: 0.21428571em 0; 8212 - line-height: 1.14285714em; 8213 - } 8214 - 8215 - .ui.menu .ui.list .list > .item:before, 8216 - .ui.menu .ui.list > .item:before { 8217 - border: none; 8218 - background: none; 8219 - } 8220 - 8221 - .ui.menu .ui.list .list > .item:first-child, 8222 - .ui.menu .ui.list > .item:first-child { 8223 - padding-top: 0; 8224 - } 8225 - 8226 - .ui.menu .ui.list .list > .item:last-child, 8227 - .ui.menu .ui.list > .item:last-child { 8228 - padding-bottom: 0; 8229 - } 8230 - 8231 - /******************************* 8232 - Types 8233 - *******************************/ 8234 - 8235 - /*------------------- 8236 - Horizontal 8237 - --------------------*/ 8238 - 8239 - .ui.horizontal.list { 8240 - display: inline-block; 8241 - font-size: 0; 8242 - } 8243 - 8244 - .ui.horizontal.list > .item { 8245 - display: inline-block; 8246 - margin-right: 1em; 8247 - font-size: 1rem; 8248 - } 8249 - 8250 - .ui.horizontal.list:not(.celled) > .item:last-child { 8251 - margin-right: 0; 8252 - padding-right: 0; 8253 - } 8254 - 8255 - .ui.horizontal.list .list:not(.icon) { 8256 - padding-left: 0; 8257 - padding-bottom: 0; 8258 - } 8259 - 8260 - .ui.horizontal.list > .item > .image, 8261 - .ui.horizontal.list .list > .item > .image, 8262 - .ui.horizontal.list > .item > i.icon, 8263 - .ui.horizontal.list .list > .item > i.icon, 8264 - .ui.horizontal.list > .item > .content, 8265 - .ui.horizontal.list .list > .item > .content { 8266 - vertical-align: middle; 8267 - } 8268 - 8269 - /* Padding on all elements */ 8270 - 8271 - .ui.horizontal.list > .item:first-child, 8272 - .ui.horizontal.list > .item:last-child { 8273 - padding-top: 0.21428571em; 8274 - padding-bottom: 0.21428571em; 8275 - } 8276 - 8277 - /* Horizontal List */ 8278 - 8279 - .ui.horizontal.list > .item > i.icon, 8280 - .ui.horizontal.list .item > i.icons > i.icon { 8281 - margin: 0; 8282 - padding: 0 0.25em 0 0; 8283 - } 8284 - 8285 - .ui.horizontal.list > .item > .image + .content, 8286 - .ui.horizontal.list > .item > i.icon, 8287 - .ui.horizontal.list > .item > i.icon + .content { 8288 - float: none; 8289 - display: inline-block; 8290 - width: auto; 8291 - } 8292 - 8293 - .ui.horizontal.list > .item > .image { 8294 - display: inline-block; 8295 - } 8296 - 8297 - /******************************* 8298 - States 8299 - *******************************/ 8300 - 8301 - /*------------------- 8302 - Disabled 8303 - --------------------*/ 8304 - 8305 - .ui.list .list > .disabled.item, 8306 - .ui.list > .disabled.item { 8307 - pointer-events: none; 8308 - color: rgba(40, 40, 40, 0.3) !important; 8309 - } 8310 - 8311 - /*------------------- 8312 - Hover 8313 - --------------------*/ 8314 - 8315 - .ui.list .list > a.item:hover > .icons, 8316 - .ui.list > a.item:hover > .icons, 8317 - .ui.list .list > a.item:hover > i.icon, 8318 - .ui.list > a.item:hover > i.icon { 8319 - color: rgba(0, 0, 0, 0.87); 8320 - } 8321 - 8322 - /******************************* 8323 - Variations 8324 - *******************************/ 8325 - 8326 - /*------------------- 8327 - Aligned 8328 - --------------------*/ 8329 - 8330 - .ui.list[class*="top aligned"] .image, 8331 - .ui.list[class*="top aligned"] .content, 8332 - .ui.list [class*="top aligned"] { 8333 - vertical-align: top !important; 8334 - } 8335 - 8336 - .ui.list[class*="middle aligned"] .image, 8337 - .ui.list[class*="middle aligned"] .content, 8338 - .ui.list [class*="middle aligned"] { 8339 - vertical-align: middle !important; 8340 - } 8341 - 8342 - .ui.list[class*="bottom aligned"] .image, 8343 - .ui.list[class*="bottom aligned"] .content, 8344 - .ui.list [class*="bottom aligned"] { 8345 - vertical-align: bottom !important; 8346 - } 8347 - 8348 - /*------------------- 8349 - Link 8350 - --------------------*/ 8351 - 8352 - .ui.link.list .item, 8353 - .ui.link.list a.item, 8354 - .ui.link.list .item a:not(.ui) { 8355 - color: rgba(0, 0, 0, 0.4); 8356 - transition: 0.1s color ease; 8357 - } 8358 - 8359 - .ui.link.list.list a.item:hover, 8360 - .ui.link.list.list .item a:not(.ui):hover { 8361 - color: rgba(0, 0, 0, 0.8); 8362 - } 8363 - 8364 - .ui.link.list.list a.item:active, 8365 - .ui.link.list.list .item a:not(.ui):active { 8366 - color: rgba(0, 0, 0, 0.9); 8367 - } 8368 - 8369 - .ui.link.list.list .active.item, 8370 - .ui.link.list.list .active.item a:not(.ui) { 8371 - color: rgba(0, 0, 0, 0.95); 8372 - } 8373 - 8374 - /*------------------- 8375 - Selection 8376 - --------------------*/ 8377 - 8378 - .ui.selection.list .list > .item, 8379 - .ui.selection.list > .item { 8380 - cursor: pointer; 8381 - background: transparent; 8382 - padding: 0.5em 0.5em; 8383 - margin: 0; 8384 - color: rgba(0, 0, 0, 0.4); 8385 - border-radius: 0.5em; 8386 - transition: 0.1s color ease, 0.1s padding-left ease, 0.1s background-color ease; 8387 - } 8388 - 8389 - .ui.selection.list .list > .item:last-child, 8390 - .ui.selection.list > .item:last-child { 8391 - margin-bottom: 0; 8392 - } 8393 - 8394 - .ui.selection.list .list > .item:hover, 8395 - .ui.selection.list > .item:hover { 8396 - background: rgba(0, 0, 0, 0.03); 8397 - color: rgba(0, 0, 0, 0.8); 8398 - } 8399 - 8400 - .ui.selection.list .list > .item:active, 8401 - .ui.selection.list > .item:active { 8402 - background: rgba(0, 0, 0, 0.05); 8403 - color: rgba(0, 0, 0, 0.9); 8404 - } 8405 - 8406 - .ui.selection.list .list > .item.active, 8407 - .ui.selection.list > .item.active { 8408 - background: rgba(0, 0, 0, 0.05); 8409 - color: rgba(0, 0, 0, 0.95); 8410 - } 8411 - 8412 - /* Celled / Divided Selection List */ 8413 - 8414 - .ui.celled.selection.list .list > .item, 8415 - .ui.divided.selection.list .list > .item, 8416 - .ui.celled.selection.list > .item, 8417 - .ui.divided.selection.list > .item { 8418 - border-radius: 0; 8419 - } 8420 - 8421 - /*------------------- 8422 - Animated 8423 - --------------------*/ 8424 - 8425 - .ui.animated.list > .item { 8426 - transition: 0.25s color ease 0.1s, 0.25s padding-left ease 0.1s, 0.25s background-color ease 0.1s; 8427 - } 8428 - 8429 - .ui.animated.list:not(.horizontal) > .item:hover { 8430 - padding-left: 1em; 8431 - } 8432 - 8433 - /*------------------- 8434 - Fitted 8435 - --------------------*/ 8436 - 8437 - .ui.fitted.list:not(.selection) .list > .item, 8438 - .ui.fitted.list:not(.selection) > .item { 8439 - padding-left: 0; 8440 - padding-right: 0; 8441 - } 8442 - 8443 - .ui.fitted.selection.list .list > .item, 8444 - .ui.fitted.selection.list > .item { 8445 - margin-left: -0.5em; 8446 - margin-right: -0.5em; 8447 - } 8448 - 8449 - /*------------------- 8450 - Bulleted 8451 - --------------------*/ 8452 - 8453 - ul.ui.list, 8454 - .ui.bulleted.list { 8455 - margin-left: 1.25rem; 8456 - } 8457 - 8458 - ul.ui.list li, 8459 - .ui.bulleted.list .list > .item, 8460 - .ui.bulleted.list > .item { 8461 - position: relative; 8462 - } 8463 - 8464 - ul.ui.list li:before, 8465 - .ui.bulleted.list .list > .item:before, 8466 - .ui.bulleted.list > .item:before { 8467 - -webkit-user-select: none; 8468 - -moz-user-select: none; 8469 - user-select: none; 8470 - pointer-events: none; 8471 - position: absolute; 8472 - top: auto; 8473 - left: auto; 8474 - font-weight: normal; 8475 - margin-left: -1.25rem; 8476 - content: '\2022'; 8477 - opacity: 1; 8478 - color: inherit; 8479 - vertical-align: top; 8480 - } 8481 - 8482 - ul.ui.list li:before, 8483 - .ui.bulleted.list .list > a.item:before, 8484 - .ui.bulleted.list > a.item:before { 8485 - color: rgba(0, 0, 0, 0.87); 8486 - } 8487 - 8488 - ul.ui.list ul, 8489 - .ui.bulleted.list .list:not(.icon) { 8490 - padding-left: 1.25rem; 8491 - } 8492 - 8493 - /* Horizontal Bulleted */ 8494 - 8495 - ul.ui.horizontal.bulleted.list, 8496 - .ui.horizontal.bulleted.list { 8497 - margin-left: 0; 8498 - } 8499 - 8500 - ul.ui.horizontal.bulleted.list li, 8501 - .ui.horizontal.bulleted.list > .item { 8502 - margin-left: 1.75rem; 8503 - } 8504 - 8505 - ul.ui.horizontal.bulleted.list li:first-child, 8506 - .ui.horizontal.bulleted.list > .item:first-child { 8507 - margin-left: 0; 8508 - } 8509 - 8510 - ul.ui.horizontal.bulleted.list li::before, 8511 - .ui.horizontal.bulleted.list > .item::before { 8512 - color: rgba(0, 0, 0, 0.87); 8513 - } 8514 - 8515 - ul.ui.horizontal.bulleted.list li:first-child::before, 8516 - .ui.horizontal.bulleted.list > .item:first-child::before { 8517 - display: none; 8518 - } 8519 - 8520 - /*------------------- 8521 - Ordered 8522 - --------------------*/ 8523 - 8524 - ol.ui.list, 8525 - .ui.ordered.list, 8526 - .ui.ordered.list .list:not(.icon), 8527 - ol.ui.list ol { 8528 - counter-reset: ordered; 8529 - margin-left: 1.25rem; 8530 - list-style-type: none; 8531 - } 8532 - 8533 - ol.ui.list li, 8534 - .ui.ordered.list .list > .item, 8535 - .ui.ordered.list > .item { 8536 - list-style-type: none; 8537 - position: relative; 8538 - } 8539 - 8540 - ol.ui.list li:before, 8541 - .ui.ordered.list .list > .item:before, 8542 - .ui.ordered.list > .item:before { 8543 - position: absolute; 8544 - top: auto; 8545 - left: auto; 8546 - -webkit-user-select: none; 8547 - -moz-user-select: none; 8548 - user-select: none; 8549 - pointer-events: none; 8550 - margin-left: -1.25rem; 8551 - counter-increment: ordered; 8552 - content: counters(ordered, ".") " "; 8553 - text-align: right; 8554 - color: rgba(0, 0, 0, 0.87); 8555 - vertical-align: middle; 8556 - opacity: 0.8; 8557 - } 8558 - 8559 - /* Value */ 8560 - 8561 - .ui.ordered.list .list > .item[data-value]:before, 8562 - .ui.ordered.list > .item[data-value]:before { 8563 - content: attr(data-value); 8564 - } 8565 - 8566 - ol.ui.list li[value]:before { 8567 - content: attr(value); 8568 - } 8569 - 8570 - /* Child Lists */ 8571 - 8572 - ol.ui.list ol, 8573 - .ui.ordered.list .list:not(.icon) { 8574 - margin-left: 1em; 8575 - } 8576 - 8577 - ol.ui.list ol li:before, 8578 - .ui.ordered.list .list > .item:before { 8579 - margin-left: -2em; 8580 - } 8581 - 8582 - /* Horizontal Ordered */ 8583 - 8584 - ol.ui.horizontal.list, 8585 - .ui.ordered.horizontal.list { 8586 - margin-left: 0; 8587 - } 8588 - 8589 - ol.ui.horizontal.list li:before, 8590 - .ui.ordered.horizontal.list .list > .item:before, 8591 - .ui.ordered.horizontal.list > .item:before { 8592 - position: static; 8593 - margin: 0 0.5em 0 0; 8594 - } 8595 - 8596 - /* Suffixed Ordered */ 8597 - 8598 - ol.ui.suffixed.list li:before, 8599 - .ui.suffixed.ordered.list .list > .item:before, 8600 - .ui.suffixed.ordered.list > .item:before { 8601 - content: counters(ordered, ".") "."; 8602 - } 8603 - 8604 - /*------------------- 8605 - Divided 8606 - --------------------*/ 8607 - 8608 - .ui.divided.list > .item { 8609 - border-top: 1px solid rgba(34, 36, 38, 0.15); 8610 - } 8611 - 8612 - .ui.divided.list .list > .item { 8613 - border-top: none; 8614 - } 8615 - 8616 - .ui.divided.list .item .list > .item { 8617 - border-top: none; 8618 - } 8619 - 8620 - .ui.divided.list .list > .item:first-child, 8621 - .ui.divided.list > .item:first-child { 8622 - border-top: none; 8623 - } 8624 - 8625 - /* Sub Menu */ 8626 - 8627 - .ui.divided.list:not(.horizontal) .list > .item:first-child { 8628 - border-top-width: 1px; 8629 - } 8630 - 8631 - /* Divided bulleted */ 8632 - 8633 - .ui.divided.bulleted.list:not(.horizontal), 8634 - .ui.divided.bulleted.list .list:not(.icon) { 8635 - margin-left: 0; 8636 - padding-left: 0; 8637 - } 8638 - 8639 - .ui.divided.bulleted.list > .item:not(.horizontal) { 8640 - padding-left: 1.25rem; 8641 - } 8642 - 8643 - /* Divided Ordered */ 8644 - 8645 - .ui.divided.ordered.list { 8646 - margin-left: 0; 8647 - } 8648 - 8649 - .ui.divided.ordered.list .list > .item, 8650 - .ui.divided.ordered.list > .item { 8651 - padding-left: 1.25rem; 8652 - } 8653 - 8654 - .ui.divided.ordered.list .item .list:not(.icon) { 8655 - margin-left: 0; 8656 - margin-right: 0; 8657 - padding-bottom: 0.21428571em; 8658 - } 8659 - 8660 - .ui.divided.ordered.list .item .list > .item { 8661 - padding-left: 1em; 8662 - } 8663 - 8664 - /* Divided Selection */ 8665 - 8666 - .ui.divided.selection.list .list > .item, 8667 - .ui.divided.selection.list > .item { 8668 - margin: 0; 8669 - border-radius: 0; 8670 - } 8671 - 8672 - /* Divided horizontal */ 8673 - 8674 - .ui.divided.horizontal.list { 8675 - margin-left: 0; 8676 - } 8677 - 8678 - .ui.divided.horizontal.list > .item { 8679 - padding-left: 0.5em; 8680 - } 8681 - 8682 - .ui.divided.horizontal.list > .item:not(:last-child) { 8683 - padding-right: 0.5em; 8684 - } 8685 - 8686 - .ui.divided.horizontal.list > .item { 8687 - border-top: none; 8688 - border-right: 1px solid rgba(34, 36, 38, 0.15); 8689 - margin: 0; 8690 - line-height: 0.6; 8691 - } 8692 - 8693 - .ui.horizontal.divided.list > .item:last-child { 8694 - border-right: none; 8695 - } 8696 - 8697 - /*------------------- 8698 - Celled 8699 - --------------------*/ 8700 - 8701 - .ui.celled.list > .item, 8702 - .ui.celled.list > .list { 8703 - border-top: 1px solid rgba(34, 36, 38, 0.15); 8704 - padding-left: 0.5em; 8705 - padding-right: 0.5em; 8706 - } 8707 - 8708 - .ui.celled.list > .item:last-child { 8709 - border-bottom: 1px solid rgba(34, 36, 38, 0.15); 8710 - } 8711 - 8712 - /* Padding on all elements */ 8713 - 8714 - .ui.celled.list > .item:first-child, 8715 - .ui.celled.list > .item:last-child { 8716 - padding-top: 0.21428571em; 8717 - padding-bottom: 0.21428571em; 8718 - } 8719 - 8720 - /* Sub Menu */ 8721 - 8722 - .ui.celled.list .item .list > .item { 8723 - border-width: 0; 8724 - } 8725 - 8726 - .ui.celled.list .list > .item:first-child { 8727 - border-top-width: 0; 8728 - } 8729 - 8730 - /* Celled Bulleted */ 8731 - 8732 - .ui.celled.bulleted.list { 8733 - margin-left: 0; 8734 - } 8735 - 8736 - .ui.celled.bulleted.list .list > .item, 8737 - .ui.celled.bulleted.list > .item { 8738 - padding-left: 1.25rem; 8739 - } 8740 - 8741 - .ui.celled.bulleted.list .item .list:not(.icon) { 8742 - margin-left: -1.25rem; 8743 - margin-right: -1.25rem; 8744 - padding-bottom: 0.21428571em; 8745 - } 8746 - 8747 - /* Celled Ordered */ 8748 - 8749 - .ui.celled.ordered.list { 8750 - margin-left: 0; 8751 - } 8752 - 8753 - .ui.celled.ordered.list .list > .item, 8754 - .ui.celled.ordered.list > .item { 8755 - padding-left: 1.25rem; 8756 - } 8757 - 8758 - .ui.celled.ordered.list .item .list:not(.icon) { 8759 - margin-left: 0; 8760 - margin-right: 0; 8761 - padding-bottom: 0.21428571em; 8762 - } 8763 - 8764 - .ui.celled.ordered.list .list > .item { 8765 - padding-left: 1em; 8766 - } 8767 - 8768 - /* Celled Horizontal */ 8769 - 8770 - .ui.horizontal.celled.list { 8771 - margin-left: 0; 8772 - } 8773 - 8774 - .ui.horizontal.celled.list .list > .item, 8775 - .ui.horizontal.celled.list > .item { 8776 - border-top: none; 8777 - border-left: 1px solid rgba(34, 36, 38, 0.15); 8778 - margin: 0; 8779 - padding-left: 0.5em; 8780 - padding-right: 0.5em; 8781 - line-height: 0.6; 8782 - } 8783 - 8784 - .ui.horizontal.celled.list .list > .item:last-child, 8785 - .ui.horizontal.celled.list > .item:last-child { 8786 - border-bottom: none; 8787 - border-right: 1px solid rgba(34, 36, 38, 0.15); 8788 - } 8789 - 8790 - /*------------------- 8791 - Relaxed 8792 - --------------------*/ 8793 - 8794 - .ui.relaxed.list:not(.horizontal) > .item:not(:first-child) { 8795 - padding-top: 0.42857143em; 8796 - } 8797 - 8798 - .ui.relaxed.list:not(.horizontal) > .item:not(:last-child) { 8799 - padding-bottom: 0.42857143em; 8800 - } 8801 - 8802 - .ui.horizontal.relaxed.list .list > .item:not(:first-child), 8803 - .ui.horizontal.relaxed.list > .item:not(:first-child) { 8804 - padding-left: 1rem; 8805 - } 8806 - 8807 - .ui.horizontal.relaxed.list .list > .item:not(:last-child), 8808 - .ui.horizontal.relaxed.list > .item:not(:last-child) { 8809 - padding-right: 1rem; 8810 - } 8811 - 8812 - /* Very Relaxed */ 8813 - 8814 - .ui[class*="very relaxed"].list:not(.horizontal) > .item:not(:first-child) { 8815 - padding-top: 0.85714286em; 8816 - } 8817 - 8818 - .ui[class*="very relaxed"].list:not(.horizontal) > .item:not(:last-child) { 8819 - padding-bottom: 0.85714286em; 8820 - } 8821 - 8822 - .ui.horizontal[class*="very relaxed"].list .list > .item:not(:first-child), 8823 - .ui.horizontal[class*="very relaxed"].list > .item:not(:first-child) { 8824 - padding-left: 1.5rem; 8825 - } 8826 - 8827 - .ui.horizontal[class*="very relaxed"].list .list > .item:not(:last-child), 8828 - .ui.horizontal[class*="very relaxed"].list > .item:not(:last-child) { 8829 - padding-right: 1.5rem; 8830 - } 8831 - 8832 - /*------------------- 8833 - Sizes 8834 - --------------------*/ 8835 - 8836 - .ui.list { 8837 - font-size: 1em; 8838 - } 8839 - 8840 - .ui.mini.list { 8841 - font-size: 0.78571429em; 8842 - } 8843 - 8844 - .ui.mini.horizontal.list .list > .item, 8845 - .ui.mini.horizontal.list > .item { 8846 - font-size: 0.78571429rem; 8847 - } 8848 - 8849 - .ui.tiny.list { 8850 - font-size: 0.85714286em; 8851 - } 8852 - 8853 - .ui.tiny.horizontal.list .list > .item, 8854 - .ui.tiny.horizontal.list > .item { 8855 - font-size: 0.85714286rem; 8856 - } 8857 - 8858 - .ui.small.list { 8859 - font-size: 0.92857143em; 8860 - } 8861 - 8862 - .ui.small.horizontal.list .list > .item, 8863 - .ui.small.horizontal.list > .item { 8864 - font-size: 0.92857143rem; 8865 - } 8866 - 8867 - .ui.large.list { 8868 - font-size: 1.14285714em; 8869 - } 8870 - 8871 - .ui.large.horizontal.list .list > .item, 8872 - .ui.large.horizontal.list > .item { 8873 - font-size: 1.14285714rem; 8874 - } 8875 - 8876 - .ui.big.list { 8877 - font-size: 1.28571429em; 8878 - } 8879 - 8880 - .ui.big.horizontal.list .list > .item, 8881 - .ui.big.horizontal.list > .item { 8882 - font-size: 1.28571429rem; 8883 - } 8884 - 8885 - .ui.huge.list { 8886 - font-size: 1.42857143em; 8887 - } 8888 - 8889 - .ui.huge.horizontal.list .list > .item, 8890 - .ui.huge.horizontal.list > .item { 8891 - font-size: 1.42857143rem; 8892 - } 8893 - 8894 - .ui.massive.list { 8895 - font-size: 1.71428571em; 8896 - } 8897 - 8898 - .ui.massive.horizontal.list .list > .item, 8899 - .ui.massive.horizontal.list > .item { 8900 - font-size: 1.71428571rem; 8901 - } 8902 - 8903 - /******************************* 8904 - Theme Overrides 8905 - *******************************/ 8906 - 8907 - /******************************* 8908 - User Variable Overrides 8909 6479 *******************************/ 8910 6480 /* 8911 6481 * # Fomantic - Menu
-877
web_src/fomantic/build/semantic.js
··· 1187 1187 })( jQuery, window, document ); 1188 1188 1189 1189 /*! 1190 - * # Fomantic-UI - Checkbox 1191 - * http://github.com/fomantic/Fomantic-UI/ 1192 - * 1193 - * 1194 - * Released under the MIT license 1195 - * http://opensource.org/licenses/MIT 1196 - * 1197 - */ 1198 - 1199 - ;(function ($, window, document, undefined) { 1200 - 1201 - 'use strict'; 1202 - 1203 - $.isFunction = $.isFunction || function(obj) { 1204 - return typeof obj === "function" && typeof obj.nodeType !== "number"; 1205 - }; 1206 - 1207 - window = (typeof window != 'undefined' && window.Math == Math) 1208 - ? window 1209 - : (typeof self != 'undefined' && self.Math == Math) 1210 - ? self 1211 - : Function('return this')() 1212 - ; 1213 - 1214 - $.fn.checkbox = function(parameters) { 1215 - var 1216 - $allModules = $(this), 1217 - moduleSelector = $allModules.selector || '', 1218 - 1219 - time = new Date().getTime(), 1220 - performance = [], 1221 - 1222 - query = arguments[0], 1223 - methodInvoked = (typeof query == 'string'), 1224 - queryArguments = [].slice.call(arguments, 1), 1225 - returnedValue 1226 - ; 1227 - 1228 - $allModules 1229 - .each(function() { 1230 - var 1231 - settings = $.extend(true, {}, $.fn.checkbox.settings, parameters), 1232 - 1233 - className = settings.className, 1234 - namespace = settings.namespace, 1235 - selector = settings.selector, 1236 - error = settings.error, 1237 - 1238 - eventNamespace = '.' + namespace, 1239 - moduleNamespace = 'module-' + namespace, 1240 - 1241 - $module = $(this), 1242 - $label = $(this).children(selector.label), 1243 - $input = $(this).children(selector.input), 1244 - input = $input[0], 1245 - 1246 - initialLoad = false, 1247 - shortcutPressed = false, 1248 - instance = $module.data(moduleNamespace), 1249 - 1250 - observer, 1251 - element = this, 1252 - module 1253 - ; 1254 - 1255 - module = { 1256 - 1257 - initialize: function() { 1258 - module.verbose('Initializing checkbox', settings); 1259 - 1260 - module.create.label(); 1261 - module.bind.events(); 1262 - 1263 - module.set.tabbable(); 1264 - module.hide.input(); 1265 - 1266 - module.observeChanges(); 1267 - module.instantiate(); 1268 - module.setup(); 1269 - }, 1270 - 1271 - instantiate: function() { 1272 - module.verbose('Storing instance of module', module); 1273 - instance = module; 1274 - $module 1275 - .data(moduleNamespace, module) 1276 - ; 1277 - }, 1278 - 1279 - destroy: function() { 1280 - module.verbose('Destroying module'); 1281 - module.unbind.events(); 1282 - module.show.input(); 1283 - $module.removeData(moduleNamespace); 1284 - }, 1285 - 1286 - fix: { 1287 - reference: function() { 1288 - if( $module.is(selector.input) ) { 1289 - module.debug('Behavior called on <input> adjusting invoked element'); 1290 - $module = $module.closest(selector.checkbox); 1291 - module.refresh(); 1292 - } 1293 - } 1294 - }, 1295 - 1296 - setup: function() { 1297 - module.set.initialLoad(); 1298 - if( module.is.indeterminate() ) { 1299 - module.debug('Initial value is indeterminate'); 1300 - module.indeterminate(); 1301 - } 1302 - else if( module.is.checked() ) { 1303 - module.debug('Initial value is checked'); 1304 - module.check(); 1305 - } 1306 - else { 1307 - module.debug('Initial value is unchecked'); 1308 - module.uncheck(); 1309 - } 1310 - module.remove.initialLoad(); 1311 - }, 1312 - 1313 - refresh: function() { 1314 - $label = $module.children(selector.label); 1315 - $input = $module.children(selector.input); 1316 - input = $input[0]; 1317 - }, 1318 - 1319 - hide: { 1320 - input: function() { 1321 - module.verbose('Modifying <input> z-index to be unselectable'); 1322 - $input.addClass(className.hidden); 1323 - } 1324 - }, 1325 - show: { 1326 - input: function() { 1327 - module.verbose('Modifying <input> z-index to be selectable'); 1328 - $input.removeClass(className.hidden); 1329 - } 1330 - }, 1331 - 1332 - observeChanges: function() { 1333 - if('MutationObserver' in window) { 1334 - observer = new MutationObserver(function(mutations) { 1335 - module.debug('DOM tree modified, updating selector cache'); 1336 - module.refresh(); 1337 - }); 1338 - observer.observe(element, { 1339 - childList : true, 1340 - subtree : true 1341 - }); 1342 - module.debug('Setting up mutation observer', observer); 1343 - } 1344 - }, 1345 - 1346 - attachEvents: function(selector, event) { 1347 - var 1348 - $element = $(selector) 1349 - ; 1350 - event = $.isFunction(module[event]) 1351 - ? module[event] 1352 - : module.toggle 1353 - ; 1354 - if($element.length > 0) { 1355 - module.debug('Attaching checkbox events to element', selector, event); 1356 - $element 1357 - .on('click' + eventNamespace, event) 1358 - ; 1359 - } 1360 - else { 1361 - module.error(error.notFound); 1362 - } 1363 - }, 1364 - 1365 - preventDefaultOnInputTarget: function() { 1366 - if(typeof event !== 'undefined' && event !== null && $(event.target).is(selector.input)) { 1367 - module.verbose('Preventing default check action after manual check action'); 1368 - event.preventDefault(); 1369 - } 1370 - }, 1371 - 1372 - event: { 1373 - change: function(event) { 1374 - if( !module.should.ignoreCallbacks() ) { 1375 - settings.onChange.call(input); 1376 - } 1377 - }, 1378 - click: function(event) { 1379 - var 1380 - $target = $(event.target) 1381 - ; 1382 - if( $target.is(selector.input) ) { 1383 - module.verbose('Using default check action on initialized checkbox'); 1384 - return; 1385 - } 1386 - if( $target.is(selector.link) ) { 1387 - module.debug('Clicking link inside checkbox, skipping toggle'); 1388 - return; 1389 - } 1390 - module.toggle(); 1391 - $input.focus(); 1392 - event.preventDefault(); 1393 - }, 1394 - keydown: function(event) { 1395 - var 1396 - key = event.which, 1397 - keyCode = { 1398 - enter : 13, 1399 - space : 32, 1400 - escape : 27, 1401 - left : 37, 1402 - up : 38, 1403 - right : 39, 1404 - down : 40 1405 - } 1406 - ; 1407 - 1408 - var r = module.get.radios(), 1409 - rIndex = r.index($module), 1410 - rLen = r.length, 1411 - checkIndex = false; 1412 - 1413 - if(key == keyCode.left || key == keyCode.up) { 1414 - checkIndex = (rIndex === 0 ? rLen : rIndex) - 1; 1415 - } else if(key == keyCode.right || key == keyCode.down) { 1416 - checkIndex = rIndex === rLen-1 ? 0 : rIndex+1; 1417 - } 1418 - 1419 - if (!module.should.ignoreCallbacks() && checkIndex !== false) { 1420 - if(settings.beforeUnchecked.apply(input)===false) { 1421 - module.verbose('Option not allowed to be unchecked, cancelling key navigation'); 1422 - return false; 1423 - } 1424 - if (settings.beforeChecked.apply($(r[checkIndex]).children(selector.input)[0])===false) { 1425 - module.verbose('Next option should not allow check, cancelling key navigation'); 1426 - return false; 1427 - } 1428 - } 1429 - 1430 - if(key == keyCode.escape) { 1431 - module.verbose('Escape key pressed blurring field'); 1432 - $input.blur(); 1433 - shortcutPressed = true; 1434 - } 1435 - else if(!event.ctrlKey && ( key == keyCode.space || (key == keyCode.enter && settings.enableEnterKey)) ) { 1436 - module.verbose('Enter/space key pressed, toggling checkbox'); 1437 - module.toggle(); 1438 - shortcutPressed = true; 1439 - } 1440 - else { 1441 - shortcutPressed = false; 1442 - } 1443 - }, 1444 - keyup: function(event) { 1445 - if(shortcutPressed) { 1446 - event.preventDefault(); 1447 - } 1448 - } 1449 - }, 1450 - 1451 - check: function() { 1452 - if( !module.should.allowCheck() ) { 1453 - return; 1454 - } 1455 - module.debug('Checking checkbox', $input); 1456 - module.set.checked(); 1457 - if( !module.should.ignoreCallbacks() ) { 1458 - settings.onChecked.call(input); 1459 - module.trigger.change(); 1460 - } 1461 - module.preventDefaultOnInputTarget(); 1462 - }, 1463 - 1464 - uncheck: function() { 1465 - if( !module.should.allowUncheck() ) { 1466 - return; 1467 - } 1468 - module.debug('Unchecking checkbox'); 1469 - module.set.unchecked(); 1470 - if( !module.should.ignoreCallbacks() ) { 1471 - settings.onUnchecked.call(input); 1472 - module.trigger.change(); 1473 - } 1474 - module.preventDefaultOnInputTarget(); 1475 - }, 1476 - 1477 - indeterminate: function() { 1478 - if( module.should.allowIndeterminate() ) { 1479 - module.debug('Checkbox is already indeterminate'); 1480 - return; 1481 - } 1482 - module.debug('Making checkbox indeterminate'); 1483 - module.set.indeterminate(); 1484 - if( !module.should.ignoreCallbacks() ) { 1485 - settings.onIndeterminate.call(input); 1486 - module.trigger.change(); 1487 - } 1488 - }, 1489 - 1490 - determinate: function() { 1491 - if( module.should.allowDeterminate() ) { 1492 - module.debug('Checkbox is already determinate'); 1493 - return; 1494 - } 1495 - module.debug('Making checkbox determinate'); 1496 - module.set.determinate(); 1497 - if( !module.should.ignoreCallbacks() ) { 1498 - settings.onDeterminate.call(input); 1499 - module.trigger.change(); 1500 - } 1501 - }, 1502 - 1503 - enable: function() { 1504 - if( module.is.enabled() ) { 1505 - module.debug('Checkbox is already enabled'); 1506 - return; 1507 - } 1508 - module.debug('Enabling checkbox'); 1509 - module.set.enabled(); 1510 - if( !module.should.ignoreCallbacks() ) { 1511 - settings.onEnable.call(input); 1512 - // preserve legacy callbacks 1513 - settings.onEnabled.call(input); 1514 - module.trigger.change(); 1515 - } 1516 - }, 1517 - 1518 - disable: function() { 1519 - if( module.is.disabled() ) { 1520 - module.debug('Checkbox is already disabled'); 1521 - return; 1522 - } 1523 - module.debug('Disabling checkbox'); 1524 - module.set.disabled(); 1525 - if( !module.should.ignoreCallbacks() ) { 1526 - settings.onDisable.call(input); 1527 - // preserve legacy callbacks 1528 - settings.onDisabled.call(input); 1529 - module.trigger.change(); 1530 - } 1531 - }, 1532 - 1533 - get: { 1534 - radios: function() { 1535 - var 1536 - name = module.get.name() 1537 - ; 1538 - return $('input[name="' + name + '"]').closest(selector.checkbox); 1539 - }, 1540 - otherRadios: function() { 1541 - return module.get.radios().not($module); 1542 - }, 1543 - name: function() { 1544 - return $input.attr('name'); 1545 - } 1546 - }, 1547 - 1548 - is: { 1549 - initialLoad: function() { 1550 - return initialLoad; 1551 - }, 1552 - radio: function() { 1553 - return ($input.hasClass(className.radio) || $input.attr('type') == 'radio'); 1554 - }, 1555 - indeterminate: function() { 1556 - return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate'); 1557 - }, 1558 - checked: function() { 1559 - return $input.prop('checked') !== undefined && $input.prop('checked'); 1560 - }, 1561 - disabled: function() { 1562 - return $input.prop('disabled') !== undefined && $input.prop('disabled'); 1563 - }, 1564 - enabled: function() { 1565 - return !module.is.disabled(); 1566 - }, 1567 - determinate: function() { 1568 - return !module.is.indeterminate(); 1569 - }, 1570 - unchecked: function() { 1571 - return !module.is.checked(); 1572 - } 1573 - }, 1574 - 1575 - should: { 1576 - allowCheck: function() { 1577 - if(module.is.determinate() && module.is.checked() && !module.is.initialLoad() ) { 1578 - module.debug('Should not allow check, checkbox is already checked'); 1579 - return false; 1580 - } 1581 - if(!module.should.ignoreCallbacks() && settings.beforeChecked.apply(input) === false) { 1582 - module.debug('Should not allow check, beforeChecked cancelled'); 1583 - return false; 1584 - } 1585 - return true; 1586 - }, 1587 - allowUncheck: function() { 1588 - if(module.is.determinate() && module.is.unchecked() && !module.is.initialLoad() ) { 1589 - module.debug('Should not allow uncheck, checkbox is already unchecked'); 1590 - return false; 1591 - } 1592 - if(!module.should.ignoreCallbacks() && settings.beforeUnchecked.apply(input) === false) { 1593 - module.debug('Should not allow uncheck, beforeUnchecked cancelled'); 1594 - return false; 1595 - } 1596 - return true; 1597 - }, 1598 - allowIndeterminate: function() { 1599 - if(module.is.indeterminate() && !module.is.initialLoad() ) { 1600 - module.debug('Should not allow indeterminate, checkbox is already indeterminate'); 1601 - return false; 1602 - } 1603 - if(!module.should.ignoreCallbacks() && settings.beforeIndeterminate.apply(input) === false) { 1604 - module.debug('Should not allow indeterminate, beforeIndeterminate cancelled'); 1605 - return false; 1606 - } 1607 - return true; 1608 - }, 1609 - allowDeterminate: function() { 1610 - if(module.is.determinate() && !module.is.initialLoad() ) { 1611 - module.debug('Should not allow determinate, checkbox is already determinate'); 1612 - return false; 1613 - } 1614 - if(!module.should.ignoreCallbacks() && settings.beforeDeterminate.apply(input) === false) { 1615 - module.debug('Should not allow determinate, beforeDeterminate cancelled'); 1616 - return false; 1617 - } 1618 - return true; 1619 - }, 1620 - ignoreCallbacks: function() { 1621 - return (initialLoad && !settings.fireOnInit); 1622 - } 1623 - }, 1624 - 1625 - can: { 1626 - change: function() { 1627 - return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') ); 1628 - }, 1629 - uncheck: function() { 1630 - return (typeof settings.uncheckable === 'boolean') 1631 - ? settings.uncheckable 1632 - : !module.is.radio() 1633 - ; 1634 - } 1635 - }, 1636 - 1637 - set: { 1638 - initialLoad: function() { 1639 - initialLoad = true; 1640 - }, 1641 - checked: function() { 1642 - module.verbose('Setting class to checked'); 1643 - $module 1644 - .removeClass(className.indeterminate) 1645 - .addClass(className.checked) 1646 - ; 1647 - if( module.is.radio() ) { 1648 - module.uncheckOthers(); 1649 - } 1650 - if(!module.is.indeterminate() && module.is.checked()) { 1651 - module.debug('Input is already checked, skipping input property change'); 1652 - return; 1653 - } 1654 - module.verbose('Setting state to checked', input); 1655 - $input 1656 - .prop('indeterminate', false) 1657 - .prop('checked', true) 1658 - ; 1659 - }, 1660 - unchecked: function() { 1661 - module.verbose('Removing checked class'); 1662 - $module 1663 - .removeClass(className.indeterminate) 1664 - .removeClass(className.checked) 1665 - ; 1666 - if(!module.is.indeterminate() && module.is.unchecked() ) { 1667 - module.debug('Input is already unchecked'); 1668 - return; 1669 - } 1670 - module.debug('Setting state to unchecked'); 1671 - $input 1672 - .prop('indeterminate', false) 1673 - .prop('checked', false) 1674 - ; 1675 - }, 1676 - indeterminate: function() { 1677 - module.verbose('Setting class to indeterminate'); 1678 - $module 1679 - .addClass(className.indeterminate) 1680 - ; 1681 - if( module.is.indeterminate() ) { 1682 - module.debug('Input is already indeterminate, skipping input property change'); 1683 - return; 1684 - } 1685 - module.debug('Setting state to indeterminate'); 1686 - $input 1687 - .prop('indeterminate', true) 1688 - ; 1689 - }, 1690 - determinate: function() { 1691 - module.verbose('Removing indeterminate class'); 1692 - $module 1693 - .removeClass(className.indeterminate) 1694 - ; 1695 - if( module.is.determinate() ) { 1696 - module.debug('Input is already determinate, skipping input property change'); 1697 - return; 1698 - } 1699 - module.debug('Setting state to determinate'); 1700 - $input 1701 - .prop('indeterminate', false) 1702 - ; 1703 - }, 1704 - disabled: function() { 1705 - module.verbose('Setting class to disabled'); 1706 - $module 1707 - .addClass(className.disabled) 1708 - ; 1709 - if( module.is.disabled() ) { 1710 - module.debug('Input is already disabled, skipping input property change'); 1711 - return; 1712 - } 1713 - module.debug('Setting state to disabled'); 1714 - $input 1715 - .prop('disabled', 'disabled') 1716 - ; 1717 - }, 1718 - enabled: function() { 1719 - module.verbose('Removing disabled class'); 1720 - $module.removeClass(className.disabled); 1721 - if( module.is.enabled() ) { 1722 - module.debug('Input is already enabled, skipping input property change'); 1723 - return; 1724 - } 1725 - module.debug('Setting state to enabled'); 1726 - $input 1727 - .prop('disabled', false) 1728 - ; 1729 - }, 1730 - tabbable: function() { 1731 - module.verbose('Adding tabindex to checkbox'); 1732 - if( $input.attr('tabindex') === undefined) { 1733 - $input.attr('tabindex', 0); 1734 - } 1735 - } 1736 - }, 1737 - 1738 - remove: { 1739 - initialLoad: function() { 1740 - initialLoad = false; 1741 - } 1742 - }, 1743 - 1744 - trigger: { 1745 - change: function() { 1746 - var 1747 - inputElement = $input[0] 1748 - ; 1749 - if(inputElement) { 1750 - var events = document.createEvent('HTMLEvents'); 1751 - module.verbose('Triggering native change event'); 1752 - events.initEvent('change', true, false); 1753 - inputElement.dispatchEvent(events); 1754 - } 1755 - } 1756 - }, 1757 - 1758 - 1759 - create: { 1760 - label: function() { 1761 - if($input.prevAll(selector.label).length > 0) { 1762 - $input.prev(selector.label).detach().insertAfter($input); 1763 - module.debug('Moving existing label', $label); 1764 - } 1765 - else if( !module.has.label() ) { 1766 - $label = $('<label>').insertAfter($input); 1767 - module.debug('Creating label', $label); 1768 - } 1769 - } 1770 - }, 1771 - 1772 - has: { 1773 - label: function() { 1774 - return ($label.length > 0); 1775 - } 1776 - }, 1777 - 1778 - bind: { 1779 - events: function() { 1780 - module.verbose('Attaching checkbox events'); 1781 - $module 1782 - .on('click' + eventNamespace, module.event.click) 1783 - .on('change' + eventNamespace, module.event.change) 1784 - .on('keydown' + eventNamespace, selector.input, module.event.keydown) 1785 - .on('keyup' + eventNamespace, selector.input, module.event.keyup) 1786 - ; 1787 - } 1788 - }, 1789 - 1790 - unbind: { 1791 - events: function() { 1792 - module.debug('Removing events'); 1793 - $module 1794 - .off(eventNamespace) 1795 - ; 1796 - } 1797 - }, 1798 - 1799 - uncheckOthers: function() { 1800 - var 1801 - $radios = module.get.otherRadios() 1802 - ; 1803 - module.debug('Unchecking other radios', $radios); 1804 - $radios.removeClass(className.checked); 1805 - }, 1806 - 1807 - toggle: function() { 1808 - if( !module.can.change() ) { 1809 - if(!module.is.radio()) { 1810 - module.debug('Checkbox is read-only or disabled, ignoring toggle'); 1811 - } 1812 - return; 1813 - } 1814 - if( module.is.indeterminate() || module.is.unchecked() ) { 1815 - module.debug('Currently unchecked'); 1816 - module.check(); 1817 - } 1818 - else if( module.is.checked() && module.can.uncheck() ) { 1819 - module.debug('Currently checked'); 1820 - module.uncheck(); 1821 - } 1822 - }, 1823 - setting: function(name, value) { 1824 - module.debug('Changing setting', name, value); 1825 - if( $.isPlainObject(name) ) { 1826 - $.extend(true, settings, name); 1827 - } 1828 - else if(value !== undefined) { 1829 - if($.isPlainObject(settings[name])) { 1830 - $.extend(true, settings[name], value); 1831 - } 1832 - else { 1833 - settings[name] = value; 1834 - } 1835 - } 1836 - else { 1837 - return settings[name]; 1838 - } 1839 - }, 1840 - internal: function(name, value) { 1841 - if( $.isPlainObject(name) ) { 1842 - $.extend(true, module, name); 1843 - } 1844 - else if(value !== undefined) { 1845 - module[name] = value; 1846 - } 1847 - else { 1848 - return module[name]; 1849 - } 1850 - }, 1851 - debug: function() { 1852 - if(!settings.silent && settings.debug) { 1853 - if(settings.performance) { 1854 - module.performance.log(arguments); 1855 - } 1856 - else { 1857 - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 1858 - module.debug.apply(console, arguments); 1859 - } 1860 - } 1861 - }, 1862 - verbose: function() { 1863 - if(!settings.silent && settings.verbose && settings.debug) { 1864 - if(settings.performance) { 1865 - module.performance.log(arguments); 1866 - } 1867 - else { 1868 - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 1869 - module.verbose.apply(console, arguments); 1870 - } 1871 - } 1872 - }, 1873 - error: function() { 1874 - if(!settings.silent) { 1875 - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 1876 - module.error.apply(console, arguments); 1877 - } 1878 - }, 1879 - performance: { 1880 - log: function(message) { 1881 - var 1882 - currentTime, 1883 - executionTime, 1884 - previousTime 1885 - ; 1886 - if(settings.performance) { 1887 - currentTime = new Date().getTime(); 1888 - previousTime = time || currentTime; 1889 - executionTime = currentTime - previousTime; 1890 - time = currentTime; 1891 - performance.push({ 1892 - 'Name' : message[0], 1893 - 'Arguments' : [].slice.call(message, 1) || '', 1894 - 'Element' : element, 1895 - 'Execution Time' : executionTime 1896 - }); 1897 - } 1898 - clearTimeout(module.performance.timer); 1899 - module.performance.timer = setTimeout(module.performance.display, 500); 1900 - }, 1901 - display: function() { 1902 - var 1903 - title = settings.name + ':', 1904 - totalTime = 0 1905 - ; 1906 - time = false; 1907 - clearTimeout(module.performance.timer); 1908 - $.each(performance, function(index, data) { 1909 - totalTime += data['Execution Time']; 1910 - }); 1911 - title += ' ' + totalTime + 'ms'; 1912 - if(moduleSelector) { 1913 - title += ' \'' + moduleSelector + '\''; 1914 - } 1915 - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 1916 - console.groupCollapsed(title); 1917 - if(console.table) { 1918 - console.table(performance); 1919 - } 1920 - else { 1921 - $.each(performance, function(index, data) { 1922 - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 1923 - }); 1924 - } 1925 - console.groupEnd(); 1926 - } 1927 - performance = []; 1928 - } 1929 - }, 1930 - invoke: function(query, passedArguments, context) { 1931 - var 1932 - object = instance, 1933 - maxDepth, 1934 - found, 1935 - response 1936 - ; 1937 - passedArguments = passedArguments || queryArguments; 1938 - context = element || context; 1939 - if(typeof query == 'string' && object !== undefined) { 1940 - query = query.split(/[\. ]/); 1941 - maxDepth = query.length - 1; 1942 - $.each(query, function(depth, value) { 1943 - var camelCaseValue = (depth != maxDepth) 1944 - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 1945 - : query 1946 - ; 1947 - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 1948 - object = object[camelCaseValue]; 1949 - } 1950 - else if( object[camelCaseValue] !== undefined ) { 1951 - found = object[camelCaseValue]; 1952 - return false; 1953 - } 1954 - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 1955 - object = object[value]; 1956 - } 1957 - else if( object[value] !== undefined ) { 1958 - found = object[value]; 1959 - return false; 1960 - } 1961 - else { 1962 - module.error(error.method, query); 1963 - return false; 1964 - } 1965 - }); 1966 - } 1967 - if ( $.isFunction( found ) ) { 1968 - response = found.apply(context, passedArguments); 1969 - } 1970 - else if(found !== undefined) { 1971 - response = found; 1972 - } 1973 - if(Array.isArray(returnedValue)) { 1974 - returnedValue.push(response); 1975 - } 1976 - else if(returnedValue !== undefined) { 1977 - returnedValue = [returnedValue, response]; 1978 - } 1979 - else if(response !== undefined) { 1980 - returnedValue = response; 1981 - } 1982 - return found; 1983 - } 1984 - }; 1985 - 1986 - if(methodInvoked) { 1987 - if(instance === undefined) { 1988 - module.initialize(); 1989 - } 1990 - module.invoke(query); 1991 - } 1992 - else { 1993 - if(instance !== undefined) { 1994 - instance.invoke('destroy'); 1995 - } 1996 - module.initialize(); 1997 - } 1998 - }) 1999 - ; 2000 - 2001 - return (returnedValue !== undefined) 2002 - ? returnedValue 2003 - : this 2004 - ; 2005 - }; 2006 - 2007 - $.fn.checkbox.settings = { 2008 - 2009 - name : 'Checkbox', 2010 - namespace : 'checkbox', 2011 - 2012 - silent : false, 2013 - debug : false, 2014 - verbose : true, 2015 - performance : true, 2016 - 2017 - // delegated event context 2018 - uncheckable : 'auto', 2019 - fireOnInit : false, 2020 - enableEnterKey : true, 2021 - 2022 - onChange : function(){}, 2023 - 2024 - beforeChecked : function(){}, 2025 - beforeUnchecked : function(){}, 2026 - beforeDeterminate : function(){}, 2027 - beforeIndeterminate : function(){}, 2028 - 2029 - onChecked : function(){}, 2030 - onUnchecked : function(){}, 2031 - 2032 - onDeterminate : function() {}, 2033 - onIndeterminate : function() {}, 2034 - 2035 - onEnable : function(){}, 2036 - onDisable : function(){}, 2037 - 2038 - // preserve misspelled callbacks (will be removed in 3.0) 2039 - onEnabled : function(){}, 2040 - onDisabled : function(){}, 2041 - 2042 - className : { 2043 - checked : 'checked', 2044 - indeterminate : 'indeterminate', 2045 - disabled : 'disabled', 2046 - hidden : 'hidden', 2047 - radio : 'radio', 2048 - readOnly : 'read-only' 2049 - }, 2050 - 2051 - error : { 2052 - method : 'The method you called is not defined' 2053 - }, 2054 - 2055 - selector : { 2056 - checkbox : '.ui.checkbox', 2057 - label : 'label, .box', 2058 - input : 'input[type="checkbox"], input[type="radio"]', 2059 - link : 'a[href]' 2060 - } 2061 - 2062 - }; 2063 - 2064 - })( jQuery, window, document ); 2065 - 2066 - /*! 2067 1190 * # Fomantic-UI - Dimmer 2068 1191 * http://github.com/fomantic/Fomantic-UI/ 2069 1192 *
-3
web_src/fomantic/semantic.json
··· 23 23 "components": [ 24 24 "api", 25 25 "button", 26 - "checkbox", 27 26 "dimmer", 28 27 "dropdown", 29 28 "form", 30 - "input", 31 - "list", 32 29 "menu", 33 30 "modal", 34 31 "search",
+3 -3
web_src/js/components/DashboardRepoList.vue
··· 350 350 <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span> 351 351 </div> 352 352 </h4> 353 - <div class="ui top attached segment repos-search gt-rounded-top"> 354 - <div class="ui fluid action left icon input" :class="{loading: isLoading}"> 353 + <div class="ui attached segment repos-search"> 354 + <div class="ui small fluid action left icon input"> 355 355 <input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos"> 356 - <i class="icon"><svg-icon name="octicon-search" :size="16"/></i> 356 + <i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i> 357 357 <div class="ui dropdown icon button" :title="textFilter"> 358 358 <svg-icon name="octicon-filter" :size="16"/> 359 359 <div class="menu">
+15 -8
web_src/js/features/admin/common.js
··· 218 218 }); 219 219 220 220 // Select actions 221 - const $checkboxes = $('.select.table .ui.checkbox'); 221 + const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input'); 222 + 222 223 $('.select.action').on('click', function () { 223 224 switch ($(this).data('action')) { 224 225 case 'select-all': 225 - $checkboxes.checkbox('check'); 226 + for (const checkbox of checkboxes) { 227 + checkbox.checked = true; 228 + } 226 229 break; 227 230 case 'deselect-all': 228 - $checkboxes.checkbox('uncheck'); 231 + for (const checkbox of checkboxes) { 232 + checkbox.checked = false; 233 + } 229 234 break; 230 235 case 'inverse': 231 - $checkboxes.checkbox('toggle'); 236 + for (const checkbox of checkboxes) { 237 + checkbox.checked = !checkbox.checked; 238 + } 232 239 break; 233 240 } 234 241 }); ··· 236 243 e.preventDefault(); 237 244 this.classList.add('is-loading', 'disabled'); 238 245 const data = new FormData(); 239 - $checkboxes.each(function () { 240 - if ($(this).checkbox('is checked')) { 241 - data.append('ids[]', this.getAttribute('data-id')); 246 + for (const checkbox of checkboxes) { 247 + if (checkbox.checked) { 248 + data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id')); 242 249 } 243 - }); 250 + } 244 251 await POST(this.getAttribute('data-link'), {data}); 245 252 window.location.href = this.getAttribute('data-redirect'); 246 253 });
+59 -5
web_src/js/features/colorpicker.js
··· 1 - import $ from 'jquery'; 1 + import {createTippy} from '../modules/tippy.js'; 2 2 3 - export async function createColorPicker(els) { 3 + export async function initColorPickers() { 4 + const els = document.getElementsByClassName('js-color-picker-input'); 4 5 if (!els.length) return; 5 6 6 7 await Promise.all([ 7 - import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors'), 8 - import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors/jquery.minicolors.css'), 8 + import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'), 9 + import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'), 9 10 ]); 10 11 11 - return $(els).minicolors(); 12 + for (const el of els) { 13 + initPicker(el); 14 + } 15 + } 16 + 17 + function updateSquare(el, newValue) { 18 + el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent'; 19 + } 20 + 21 + function updatePicker(el, newValue) { 22 + el.setAttribute('color', newValue); 23 + } 24 + 25 + function initPicker(el) { 26 + const input = el.querySelector('input'); 27 + 28 + const square = document.createElement('div'); 29 + square.classList.add('preview-square'); 30 + updateSquare(square, input.value); 31 + el.append(square); 32 + 33 + const picker = document.createElement('hex-color-picker'); 34 + picker.addEventListener('color-changed', (e) => { 35 + input.value = e.detail.value; 36 + input.focus(); 37 + updateSquare(square, e.detail.value); 38 + }); 39 + 40 + input.addEventListener('input', (e) => { 41 + updateSquare(square, e.target.value); 42 + updatePicker(picker, e.target.value); 43 + }); 44 + 45 + createTippy(input, { 46 + trigger: 'focus click', 47 + theme: 'bare', 48 + hideOnClick: true, 49 + content: picker, 50 + placement: 'bottom-start', 51 + interactive: true, 52 + onShow() { 53 + updatePicker(picker, input.value); 54 + }, 55 + }); 56 + 57 + // init precolors 58 + for (const colorEl of el.querySelectorAll('.precolors .color')) { 59 + colorEl.addEventListener('click', (e) => { 60 + const newValue = e.target.getAttribute('data-color-hex'); 61 + input.value = newValue; 62 + input.dispatchEvent(new Event('input', {bubbles: true})); 63 + updateSquare(square, newValue); 64 + }); 65 + } 12 66 }
+3 -9
web_src/js/features/common-global.js
··· 2 2 import '../vendor/jquery.are-you-sure.js'; 3 3 import {clippie} from 'clippie'; 4 4 import {createDropzone} from './dropzone.js'; 5 - import {initCompColorPicker} from './comp/ColorPicker.js'; 6 5 import {showGlobalErrorMessage} from '../bootstrap.js'; 7 6 import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js'; 8 7 import {svg} from '../svg.js'; ··· 110 109 showErrorToast(`${i18n.network_error} ${e}`); 111 110 } 112 111 } 113 - actionElem.classList.remove('is-loading', 'small-loading-icon'); 112 + actionElem.classList.remove('is-loading', 'loading-icon-2px'); 114 113 } 115 114 116 115 async function formFetchAction(e) { ··· 122 121 123 122 formEl.classList.add('is-loading'); 124 123 if (formEl.clientHeight < 50) { 125 - formEl.classList.add('small-loading-icon'); 124 + formEl.classList.add('loading-icon-2px'); 126 125 } 127 126 128 127 const formMethod = formEl.getAttribute('method') || 'get'; ··· 196 195 $uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward'); 197 196 $uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward'); 198 197 199 - $('.ui.checkbox').checkbox(); 200 - 201 198 $('.tabular.menu .item').tab(); 202 199 203 200 initSubmitEventPolyfill(); ··· 379 376 $attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p 380 377 } 381 378 } 382 - const $colorPickers = $modal.find('.color-picker'); 383 - if ($colorPickers.length > 0) { 384 - initCompColorPicker(); // FIXME: this might cause duplicate init 385 - } 379 + 386 380 $modal.modal('setting', { 387 381 onApprove: () => { 388 382 // "form-fetch-action" can handle network errors gracefully,
-16
web_src/js/features/comp/ColorPicker.js
··· 1 - import $ from 'jquery'; 2 - import {createColorPicker} from '../colorpicker.js'; 3 - 4 - export function initCompColorPicker() { 5 - (async () => { 6 - await createColorPicker(document.querySelectorAll('.color-picker')); 7 - 8 - for (const el of document.querySelectorAll('.precolors .color')) { 9 - el.addEventListener('click', (e) => { 10 - const color = e.target.getAttribute('data-color-hex'); 11 - const parent = e.target.closest('.color.picker'); 12 - $(parent.querySelector('.color-picker')).minicolors('value', color); 13 - }); 14 - } 15 - })(); 16 - }
+14 -3
web_src/js/features/comp/LabelEdit.js
··· 1 1 import $ from 'jquery'; 2 - import {initCompColorPicker} from './ColorPicker.js'; 3 2 4 3 function isExclusiveScopeName(name) { 5 4 return /.*[^/]\/[^/].*/.test(name); ··· 28 27 29 28 export function initCompLabelEdit(selector) { 30 29 if (!$(selector).length) return; 31 - initCompColorPicker(); 32 30 33 31 // Create label 34 32 $('.new-label.button').on('click', () => { 35 33 updateExclusiveLabelEdit('.new-label'); 36 34 $('.new-label.modal').modal({ 37 35 onApprove() { 36 + const form = document.querySelector('.new-label.form'); 37 + if (!form.checkValidity()) { 38 + form.reportValidity(); 39 + return false; 40 + } 38 41 $('.new-label.form').trigger('submit'); 39 42 }, 40 43 }).modal('show'); ··· 60 63 updateExclusiveLabelEdit('.edit-label'); 61 64 62 65 $('.edit-label .label-desc-input').val(this.getAttribute('data-description')); 63 - $('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color')); 66 + 67 + const colorInput = document.querySelector('.edit-label .js-color-picker-input input'); 68 + colorInput.value = this.getAttribute('data-color'); 69 + colorInput.dispatchEvent(new Event('input', {bubbles: true})); 64 70 65 71 $('.edit-label.modal').modal({ 66 72 onApprove() { 73 + const form = document.querySelector('.edit-label.form'); 74 + if (!form.checkValidity()) { 75 + form.reportValidity(); 76 + return false; 77 + } 67 78 $('.edit-label.form').trigger('submit'); 68 79 }, 69 80 }).modal('show');
+2 -2
web_src/js/features/copycontent.js
··· 19 19 // the text to copy is not in the DOM or it is an image which should be 20 20 // fetched to copy in full resolution 21 21 if (link) { 22 - btn.classList.add('is-loading', 'small-loading-icon'); 22 + btn.classList.add('is-loading', 'loading-icon-2px'); 23 23 try { 24 24 const res = await GET(link, {credentials: 'include', redirect: 'follow'}); 25 25 const contentType = res.headers.get('content-type'); ··· 33 33 } catch { 34 34 return showTemporaryTooltip(btn, i18n.copy_error); 35 35 } finally { 36 - btn.classList.remove('is-loading', 'small-loading-icon'); 36 + btn.classList.remove('is-loading', 'loading-icon-2px'); 37 37 } 38 38 } else { // text, read from DOM 39 39 const lineEls = document.querySelectorAll('.file-view .lines-code');
+20 -9
web_src/js/features/imagediff.js
··· 110 110 const $imagesAfter = imageInfos[0].$images; 111 111 const $imagesBefore = imageInfos[1].$images; 112 112 113 - initSideBySide(createContext($imagesAfter[0], $imagesBefore[0])); 113 + initSideBySide(this, createContext($imagesAfter[0], $imagesBefore[0])); 114 114 if ($imagesAfter.length > 0 && $imagesBefore.length > 0) { 115 115 initSwipe(createContext($imagesAfter[1], $imagesBefore[1])); 116 116 initOverlay(createContext($imagesAfter[2], $imagesBefore[2])); 117 117 } 118 118 119 - $container.find('> .image-diff-tabs').removeClass('is-loading'); 119 + this.querySelector(':scope > .image-diff-tabs')?.classList.remove('is-loading'); 120 120 121 - function initSideBySide(sizes) { 121 + function initSideBySide(container, sizes) { 122 122 let factor = 1; 123 123 if (sizes.max.width > (diffContainerWidth - 24) / 2) { 124 124 factor = (diffContainerWidth - 24) / 2 / sizes.max.width; ··· 126 126 127 127 const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth; 128 128 const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight; 129 - if (sizes.$image1.length !== 0) { 130 - $container.find('.bounds-info-after .bounds-info-width').text(`${sizes.$image1[0].naturalWidth}px`).addClass(widthChanged ? 'green' : ''); 131 - $container.find('.bounds-info-after .bounds-info-height').text(`${sizes.$image1[0].naturalHeight}px`).addClass(heightChanged ? 'green' : ''); 129 + if (sizes.$image1?.length) { 130 + const boundsInfoAfterWidth = container.querySelector('.bounds-info-after .bounds-info-width'); 131 + boundsInfoAfterWidth.textContent = `${sizes.$image1[0].naturalWidth}px`; 132 + if (widthChanged) boundsInfoAfterWidth.classList.add('green'); 133 + 134 + const boundsInfoAfterHeight = container.querySelector('.bounds-info-after .bounds-info-height'); 135 + boundsInfoAfterHeight.textContent = `${sizes.$image1[0].naturalHeight}px`; 136 + if (heightChanged) boundsInfoAfterHeight.classList.add('green'); 132 137 } 133 - if (sizes.$image2.length !== 0) { 134 - $container.find('.bounds-info-before .bounds-info-width').text(`${sizes.$image2[0].naturalWidth}px`).addClass(widthChanged ? 'red' : ''); 135 - $container.find('.bounds-info-before .bounds-info-height').text(`${sizes.$image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : ''); 138 + 139 + if (sizes.$image2?.length) { 140 + const boundsInfoBeforeWidth = container.querySelector('.bounds-info-before .bounds-info-width'); 141 + boundsInfoBeforeWidth.textContent = `${sizes.$image2[0].naturalWidth}px`; 142 + if (widthChanged) boundsInfoBeforeWidth.classList.add('red'); 143 + 144 + const boundsInfoBeforeHeight = container.querySelector('.bounds-info-before .bounds-info-height'); 145 + boundsInfoBeforeHeight.textContent = `${sizes.$image2[0].naturalHeight}px`; 146 + if (heightChanged) boundsInfoBeforeHeight.classList.add('red'); 136 147 } 137 148 138 149 const image1 = sizes.$image1[0];
+5 -7
web_src/js/features/notification.js
··· 1 1 import $ from 'jquery'; 2 2 import {GET} from '../modules/fetch.js'; 3 + import {toggleElem} from '../utils/dom.js'; 3 4 4 5 const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config; 5 6 let notificationSequenceNumber = 0; ··· 177 178 178 179 const data = await response.json(); 179 180 180 - const $notificationCount = $('.notification_count'); 181 - if (data.new === 0) { 182 - $notificationCount.addClass('tw-hidden'); 183 - } else { 184 - $notificationCount.removeClass('tw-hidden'); 181 + toggleElem('.notification_count', data.new !== 0); 182 + 183 + for (const el of document.getElementsByClassName('notification_count')) { 184 + el.textContent = `${data.new}`; 185 185 } 186 - 187 - $notificationCount.text(`${data.new}`); 188 186 189 187 return `${data.new}`; 190 188 } catch (error) {
+5 -3
web_src/js/features/repo-code.js
··· 25 25 } 26 26 27 27 function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { 28 - $linesEls.closest('tr').removeClass('active'); 28 + for (const el of $linesEls) { 29 + el.closest('tr').classList.remove('active'); 30 + } 29 31 30 32 // add hashchange to permalink 31 33 const refInNewIssue = document.querySelector('a.ref-in-new-issue'); ··· 72 74 classes.push(`[rel=L${i}]`); 73 75 } 74 76 $linesEls.filter(classes.join(',')).each(function () { 75 - $(this).closest('tr').addClass('active'); 77 + this.closest('tr').classList.add('active'); 76 78 }); 77 79 changeHash(`#L${a}-L${b}`); 78 80 ··· 82 84 return; 83 85 } 84 86 } 85 - $selectionEndEl.closest('tr').addClass('active'); 87 + $selectionEndEl[0].closest('tr').classList.add('active'); 86 88 changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`); 87 89 88 90 updateIssueHref($selectionEndEl[0].getAttribute('rel'));
+31 -24
web_src/js/features/repo-diff.js
··· 7 7 import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js'; 8 8 import {initImageDiff} from './imagediff.js'; 9 9 import {showErrorToast} from '../modules/toast.js'; 10 - import {submitEventSubmitter} from '../utils/dom.js'; 10 + import {submitEventSubmitter, queryElemSiblings, hideElem, showElem} from '../utils/dom.js'; 11 11 import {POST, GET} from '../modules/fetch.js'; 12 12 13 13 const {pageData, i18n} = window.config; ··· 16 16 const reviewBox = document.getElementById('review-box'); 17 17 if (!reviewBox) return; 18 18 19 - const $reviewBox = $(reviewBox); 20 19 const counter = reviewBox.querySelector('.review-comments-counter'); 21 20 if (!counter) return; 22 21 ··· 27 26 const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1; 28 27 counter.setAttribute('data-pending-comment-number', num); 29 28 counter.textContent = num; 30 - // Force the browser to reflow the DOM. This is to ensure that the browser replay the animation 31 - $reviewBox.removeClass('pulse'); 32 - $reviewBox.width(); 33 - $reviewBox.addClass('pulse'); 29 + 30 + reviewBox.classList.remove('pulse'); 31 + requestAnimationFrame(() => { 32 + reviewBox.classList.add('pulse'); 33 + }); 34 34 }); 35 35 }); 36 36 } 37 37 38 38 function initRepoDiffFileViewToggle() { 39 39 $('.file-view-toggle').on('click', function () { 40 - const $this = $(this); 41 - $this.parent().children().removeClass('active'); 42 - $this.addClass('active'); 40 + for (const el of queryElemSiblings(this)) { 41 + el.classList.remove('active'); 42 + } 43 + this.classList.add('active'); 44 + 45 + const target = document.querySelector(this.getAttribute('data-toggle-selector')); 46 + if (!target) return; 43 47 44 - const $target = $($this.data('toggle-selector')); 45 - $target.parent().children().addClass('tw-hidden'); 46 - $target.removeClass('tw-hidden'); 48 + hideElem(queryElemSiblings(target)); 49 + showElem(target); 47 50 }); 48 51 } 49 52 ··· 57 60 return; 58 61 } 59 62 60 - if ($form.hasClass('is-loading')) return; 63 + if (e.target.classList.contains('is-loading')) return; 61 64 try { 62 - $form.addClass('is-loading'); 65 + e.target.classList.add('is-loading'); 63 66 const formData = new FormData($form[0]); 64 67 65 68 // If the form is submitted by a button, append the button's name and value to the form data. ··· 76 79 const {path, side, idx} = $newConversationHolder.data(); 77 80 78 81 $form.closest('.conversation-holder').replaceWith($newConversationHolder); 82 + let selector; 79 83 if ($form.closest('tr').data('line-type') === 'same') { 80 - $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('tw-invisible'); 84 + selector = `[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`; 81 85 } else { 82 - $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('tw-invisible'); 86 + selector = `[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`; 87 + } 88 + for (const el of document.querySelectorAll(selector)) { 89 + el.classList.add('tw-invisible'); 83 90 } 84 91 $newConversationHolder.find('.dropdown').dropdown(); 85 92 initCompReactionSelector($newConversationHolder); ··· 87 94 console.error('error when submitting conversation', e); 88 95 showErrorToast(i18n.network_error); 89 96 } finally { 90 - $form.removeClass('is-loading'); 97 + e.target.classList.remove('is-loading'); 91 98 } 92 99 }); 93 100 ··· 147 154 } 148 155 149 156 export async function loadMoreFiles(url) { 150 - const $target = $('a#diff-show-more-files'); 151 - if ($target.hasClass('disabled') || pageData.diffFileInfo.isLoadingNewData) { 157 + const target = document.querySelector('a#diff-show-more-files'); 158 + if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) { 152 159 return; 153 160 } 154 161 155 162 pageData.diffFileInfo.isLoadingNewData = true; 156 - $target.addClass('disabled'); 163 + target?.classList.add('disabled'); 157 164 158 165 try { 159 166 const response = await GET(url); ··· 170 177 console.error('Error:', error); 171 178 showErrorToast('An error occurred while loading more files.'); 172 179 } finally { 173 - $target.removeClass('disabled'); 180 + target?.classList.remove('disabled'); 174 181 pageData.diffFileInfo.isLoadingNewData = false; 175 182 } 176 183 } ··· 187 194 e.preventDefault(); 188 195 const $target = $(e.target); 189 196 190 - if ($target.hasClass('disabled')) { 197 + if (e.target.classList.contains('disabled')) { 191 198 return; 192 199 } 193 200 194 - $target.addClass('disabled'); 201 + e.target.classList.add('disabled'); 195 202 196 203 const url = $target.data('href'); 197 204 ··· 207 214 } catch (error) { 208 215 console.error('Error:', error); 209 216 } finally { 210 - $target.removeClass('disabled'); 217 + e.target.classList.remove('disabled'); 211 218 } 212 219 }); 213 220 }
+2 -2
web_src/js/features/repo-editor.js
··· 147 147 silent: true, 148 148 dirtyClass: dirtyFileClass, 149 149 fieldSelector: ':input:not(.commit-form-wrapper :input)', 150 - change() { 151 - const dirty = $(this).hasClass(dirtyFileClass); 150 + change($form) { 151 + const dirty = $form[0]?.classList.contains(dirtyFileClass); 152 152 commitButton.disabled = !dirty; 153 153 }, 154 154 });
+25 -61
web_src/js/features/repo-home.js
··· 1 1 import $ from 'jquery'; 2 2 import {stripTags} from '../utils.js'; 3 - import {hideElem, showElem} from '../utils/dom.js'; 3 + import {hideElem, queryElemChildren, showElem} from '../utils/dom.js'; 4 4 import {POST} from '../modules/fetch.js'; 5 + import {showErrorToast} from '../modules/toast.js'; 5 6 6 7 const {appSubUrl} = window.config; 7 8 8 9 export function initRepoTopicBar() { 9 10 const mgrBtn = document.getElementById('manage_topic'); 10 11 if (!mgrBtn) return; 12 + 11 13 const editDiv = document.getElementById('topic_edit'); 12 14 const viewDiv = document.getElementById('repo-topics'); 13 - const saveBtn = document.getElementById('save_topic'); 14 - const topicDropdown = editDiv.querySelector('.dropdown'); 15 - const $topicDropdown = $(topicDropdown); 16 - const $topicForm = $(editDiv); 17 - const $topicDropdownSearch = $topicDropdown.find('input.search'); 18 - const topicPrompts = { 19 - countPrompt: topicDropdown.getAttribute('data-text-count-prompt') ?? undefined, 20 - formatPrompt: topicDropdown.getAttribute('data-text-format-prompt') ?? undefined, 21 - }; 15 + const topicDropdown = editDiv.querySelector('.ui.dropdown'); 16 + let lastErrorToast; 22 17 23 18 mgrBtn.addEventListener('click', () => { 24 19 hideElem(viewDiv); 25 20 showElem(editDiv); 26 - $topicDropdownSearch.trigger('focus'); 21 + topicDropdown.querySelector('input.search').focus(); 27 22 }); 28 23 29 - $('#cancel_topic_edit').on('click', () => { 24 + document.querySelector('#cancel_topic_edit').addEventListener('click', () => { 25 + lastErrorToast?.hideToast(); 30 26 hideElem(editDiv); 31 27 showElem(viewDiv); 32 28 mgrBtn.focus(); 33 29 }); 34 30 35 - saveBtn.addEventListener('click', async () => { 36 - const topics = $('input[name=topics]').val(); 31 + document.getElementById('save_topic').addEventListener('click', async (e) => { 32 + lastErrorToast?.hideToast(); 33 + const topics = editDiv.querySelector('input[name=topics]').value; 37 34 38 35 const data = new FormData(); 39 36 data.append('topics', topics); 40 37 41 - const response = await POST(saveBtn.getAttribute('data-link'), {data}); 38 + const response = await POST(e.target.getAttribute('data-link'), {data}); 42 39 43 40 if (response.ok) { 44 41 const responseData = await response.json(); 45 42 if (responseData.status === 'ok') { 46 - $(viewDiv).children('.topic').remove(); 43 + queryElemChildren(viewDiv, '.repo-topic', (el) => el.remove()); 47 44 if (topics.length) { 48 45 const topicArray = topics.split(','); 49 46 topicArray.sort(); 50 47 for (const topic of topicArray) { 48 + // it should match the code in repo/home.tmpl 51 49 const link = document.createElement('a'); 52 - link.classList.add('ui', 'repo-topic', 'large', 'label', 'topic', 'tw-m-0'); 50 + link.classList.add('repo-topic', 'ui', 'large', 'label'); 53 51 link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`; 54 52 link.textContent = topic; 55 53 mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button ··· 59 57 showElem(viewDiv); 60 58 } 61 59 } else if (response.status === 422) { 60 + // how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save" 62 61 const responseData = await response.json(); 62 + lastErrorToast = showErrorToast(responseData.message, {duration: 5000}); 63 63 if (responseData.invalidTopics.length > 0) { 64 - topicPrompts.formatPrompt = responseData.message; 65 - 66 64 const {invalidTopics} = responseData; 67 - const $topicLabels = $topicDropdown.children('a.ui.label'); 65 + const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label'); 68 66 for (const [index, value] of topics.split(',').entries()) { 69 67 if (invalidTopics.includes(value)) { 70 - $topicLabels.eq(index).removeClass('green').addClass('red'); 68 + topicLabels[index].classList.remove('green'); 69 + topicLabels[index].classList.add('red'); 71 70 } 72 71 } 73 - } else { 74 - topicPrompts.countPrompt = responseData.message; 75 72 } 76 73 } 77 - 78 - // Always validate the form 79 - $topicForm.form('validate form'); 80 74 }); 81 75 82 - $topicDropdown.dropdown({ 76 + $(topicDropdown).dropdown({ 83 77 allowAdditions: true, 84 78 forceSelection: false, 85 79 fullTextSearch: 'exact', ··· 102 96 const query = stripTags(this.urlData.query.trim()); 103 97 let found_query = false; 104 98 const current_topics = []; 105 - $topicDropdown.find('a.label.visible').each((_, el) => { 99 + for (const el of queryElemChildren(topicDropdown, 'a.ui.label.visible')) { 106 100 current_topics.push(el.getAttribute('data-value')); 107 - }); 101 + } 108 102 109 103 if (res.topics) { 110 104 let found = false; ··· 146 140 }, 147 141 onAdd(addedValue, _addedText, $addedChoice) { 148 142 addedValue = addedValue.toLowerCase().trim(); 149 - $($addedChoice)[0].setAttribute('data-value', addedValue); 150 - $($addedChoice)[0].setAttribute('data-text', addedValue); 151 - }, 152 - }); 153 - 154 - $.fn.form.settings.rules.validateTopic = function (_values, regExp) { 155 - const $topics = $topicDropdown.children('a.ui.label'); 156 - const status = !$topics.length || $topics.last()[0].getAttribute('data-value').match(regExp); 157 - if (!status) { 158 - $topics.last().removeClass('green').addClass('red'); 159 - } 160 - return status && !$topicDropdown.children('a.ui.label.red').length; 161 - }; 162 - 163 - $topicForm.form({ 164 - on: 'change', 165 - inline: true, 166 - fields: { 167 - topics: { 168 - identifier: 'topics', 169 - rules: [ 170 - { 171 - type: 'validateTopic', 172 - value: /^\s*[a-z0-9][-.a-z0-9]{0,35}\s*$/, 173 - prompt: topicPrompts.formatPrompt, 174 - }, 175 - { 176 - type: 'maxCount[25]', 177 - prompt: topicPrompts.countPrompt, 178 - }, 179 - ], 180 - }, 143 + $addedChoice[0].setAttribute('data-value', addedValue); 144 + $addedChoice[0].setAttribute('data-text', addedValue); 181 145 }, 182 146 }); 183 147 }
+5 -2
web_src/js/features/repo-issue-content.js
··· 2 2 import {svg} from '../svg.js'; 3 3 import {showErrorToast} from '../modules/toast.js'; 4 4 import {GET, POST} from '../modules/fetch.js'; 5 + import {showElem} from '../utils/dom.js'; 5 6 6 7 const {appSubUrl} = window.config; 7 8 let i18nTextEdited; ··· 73 74 const response = await GET(url); 74 75 const resp = await response.json(); 75 76 76 - $dialog.find('.comment-diff-data').removeClass('is-loading').html(resp.diffHtml); 77 + const commentDiffData = $dialog.find('.comment-diff-data')[0]; 78 + commentDiffData?.classList.remove('is-loading'); 79 + commentDiffData.innerHTML = resp.diffHtml; 77 80 // there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden. 78 81 if (resp.canSoftDelete) { 79 - $dialog.find('.dialog-header-options').removeClass('tw-hidden'); 82 + showElem($dialog.find('.dialog-header-options')); 80 83 } 81 84 } catch (error) { 82 85 console.error('Error:', error);
+15 -7
web_src/js/features/repo-issue-list.js
··· 6 6 import {showErrorToast} from '../modules/toast.js'; 7 7 import {createSortable} from '../modules/sortable.js'; 8 8 import {DELETE, POST} from '../modules/fetch.js'; 9 + import {parseDom} from '../utils.js'; 9 10 10 11 function initRepoIssueListCheckboxes() { 11 12 const issueSelectAll = document.querySelector('.issue-checkbox-all'); ··· 129 130 const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates'); 130 131 $searchDropdown.dropdown('internal', 'setup', dropdownSetup); 131 132 dropdownSetup.menu = function (values) { 132 - const $menu = $searchDropdown.find('> .menu'); 133 - $menu.find('> .dynamic-item').remove(); // remove old dynamic items 133 + const menu = $searchDropdown.find('> .menu')[0]; 134 + // remove old dynamic items 135 + for (const el of menu.querySelectorAll(':scope > .dynamic-item')) { 136 + el.remove(); 137 + } 134 138 135 139 const newMenuHtml = dropdownTemplates.menu(values, $searchDropdown.dropdown('setting', 'fields'), true /* html */, $searchDropdown.dropdown('setting', 'className')); 136 140 if (newMenuHtml) { 137 - const $newMenuItems = $(newMenuHtml); 138 - $newMenuItems.addClass('dynamic-item'); 141 + const newMenuItems = parseDom(newMenuHtml, 'text/html').querySelectorAll('body > div'); 142 + for (const newMenuItem of newMenuItems) { 143 + newMenuItem.classList.add('dynamic-item'); 144 + } 139 145 const div = document.createElement('div'); 140 146 div.classList.add('divider', 'dynamic-item'); 141 - $menu[0].append(div, ...$newMenuItems); 147 + menu.append(div, ...newMenuItems); 142 148 } 143 149 $searchDropdown.dropdown('refresh'); 144 150 // defer our selection to the next tick, because dropdown will set the selection item after this `menu` function 145 151 setTimeout(() => { 146 - $menu.find('.item.active, .item.selected').removeClass('active selected'); 147 - $menu.find(`.item[data-value="${selectedUserId}"]`).addClass('selected'); 152 + for (const el of menu.querySelectorAll('.item.active, .item.selected')) { 153 + el.classList.remove('active', 'selected'); 154 + } 155 + menu.querySelector(`.item[data-value="${selectedUserId}"]`)?.classList.add('selected'); 148 156 }, 0); 149 157 }; 150 158 }
+103 -94
web_src/js/features/repo-issue.js
··· 158 158 159 159 export function initRepoIssueCommentDelete() { 160 160 // Delete comment 161 - $(document).on('click', '.delete-comment', async function () { 162 - const $this = $(this); 163 - if (window.confirm($this.data('locale'))) { 161 + document.addEventListener('click', async (e) => { 162 + if (!e.target.matches('.delete-comment')) return; 163 + e.preventDefault(); 164 + 165 + const deleteButton = e.target; 166 + if (window.confirm(deleteButton.getAttribute('data-locale'))) { 164 167 try { 165 - const response = await POST($this.data('url')); 168 + const response = await POST(deleteButton.getAttribute('data-url')); 166 169 if (!response.ok) throw new Error('Failed to delete comment'); 167 - const $conversationHolder = $this.closest('.conversation-holder'); 168 - const $parentTimelineItem = $this.closest('.timeline-item'); 169 - const $parentTimelineGroup = $this.closest('.timeline-item-group'); 170 + 171 + const conversationHolder = deleteButton.closest('.conversation-holder'); 172 + const parentTimelineItem = deleteButton.closest('.timeline-item'); 173 + const parentTimelineGroup = deleteButton.closest('.timeline-item-group'); 174 + 170 175 // Check if this was a pending comment. 171 - if ($conversationHolder.find('.pending-label').length) { 176 + if (conversationHolder?.querySelector('.pending-label')) { 172 177 const counter = document.querySelector('#review-box .review-comments-counter'); 173 178 let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0; 174 179 num = Math.max(num, 0); ··· 176 181 counter.textContent = String(num); 177 182 } 178 183 179 - $(`#${$this.data('comment-id')}`).remove(); 180 - if ($conversationHolder.length && !$conversationHolder.find('.comment').length) { 181 - const path = $conversationHolder.data('path'); 182 - const side = $conversationHolder.data('side'); 183 - const idx = $conversationHolder.data('idx'); 184 - const lineType = $conversationHolder.closest('tr').data('line-type'); 184 + document.getElementById(deleteButton.getAttribute('data-comment-id'))?.remove(); 185 + 186 + if (conversationHolder && !conversationHolder.querySelector('.comment')) { 187 + const path = conversationHolder.getAttribute('data-path'); 188 + const side = conversationHolder.getAttribute('data-side'); 189 + const idx = conversationHolder.getAttribute('data-idx'); 190 + const lineType = conversationHolder.closest('tr').getAttribute('data-line-type'); 191 + 185 192 if (lineType === 'same') { 186 - $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('tw-invisible'); 193 + document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).classList.remove('tw-invisible'); 187 194 } else { 188 - $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('tw-invisible'); 195 + document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).classList.remove('tw-invisible'); 189 196 } 190 - $conversationHolder.remove(); 197 + 198 + conversationHolder.remove(); 191 199 } 200 + 192 201 // Check if there is no review content, move the time avatar upward to avoid overlapping the content below. 193 - if (!$parentTimelineGroup.find('.timeline-item.comment').length && !$parentTimelineItem.find('.conversation-holder').length) { 194 - const $timelineAvatar = $parentTimelineGroup.find('.timeline-avatar'); 195 - $timelineAvatar.removeClass('timeline-avatar-offset'); 202 + if (!parentTimelineGroup?.querySelector('.timeline-item.comment') && !parentTimelineItem?.querySelector('.conversation-holder')) { 203 + const timelineAvatar = parentTimelineGroup?.querySelector('.timeline-avatar'); 204 + timelineAvatar?.classList.remove('timeline-avatar-offset'); 196 205 } 197 206 } catch (error) { 198 207 console.error(error); 199 208 } 200 209 } 201 - return false; 202 210 }); 203 211 } 204 212 ··· 222 230 223 231 export function initRepoIssueCodeCommentCancel() { 224 232 // Cancel inline code comment 225 - $(document).on('click', '.cancel-code-comment', (e) => { 226 - const $form = $(e.currentTarget).closest('form'); 227 - if ($form.length > 0 && $form.hasClass('comment-form')) { 228 - $form.addClass('tw-hidden'); 229 - showElem($form.closest('.comment-code-cloud').find('button.comment-form-reply')); 233 + document.addEventListener('click', (e) => { 234 + if (!e.target.matches('.cancel-code-comment')) return; 235 + 236 + const form = e.target.closest('form'); 237 + if (form?.classList.contains('comment-form')) { 238 + hideElem(form); 239 + showElem(form.closest('.comment-code-cloud')?.querySelectorAll('button.comment-form-reply')); 230 240 } else { 231 - $form.closest('.comment-code-cloud').remove(); 241 + form.closest('.comment-code-cloud')?.remove(); 232 242 } 233 243 }); 234 244 } 235 245 236 246 export function initRepoPullRequestUpdate() { 237 247 // Pull Request update button 238 - const $pullUpdateButton = $('.update-button > button'); 239 - $pullUpdateButton.on('click', async function (e) { 248 + const pullUpdateButton = document.querySelector('.update-button > button'); 249 + if (!pullUpdateButton) return; 250 + 251 + pullUpdateButton.addEventListener('click', async function (e) { 240 252 e.preventDefault(); 241 - const $this = $(this); 242 - const redirect = $this.data('redirect'); 243 - $this.addClass('is-loading'); 253 + const redirect = this.getAttribute('data-redirect'); 254 + this.classList.add('is-loading'); 244 255 let response; 245 256 try { 246 - response = await POST($this.data('do')); 257 + response = await POST(this.getAttribute('data-do')); 247 258 } catch (error) { 248 259 console.error(error); 249 260 } finally { 250 - $this.removeClass('is-loading'); 261 + this.classList.remove('is-loading'); 251 262 } 252 263 let data; 253 264 try { ··· 266 277 267 278 $('.update-button > .dropdown').dropdown({ 268 279 onChange(_text, _value, $choice) { 269 - const $url = $choice.data('do'); 270 - if ($url) { 271 - $pullUpdateButton.find('.button-text').text($choice.text()); 272 - $pullUpdateButton.data('do', $url); 280 + const url = $choice[0].getAttribute('data-do'); 281 + if (url) { 282 + const buttonText = pullUpdateButton.querySelector('.button-text'); 283 + if (buttonText) { 284 + buttonText.textContent = $choice.text(); 285 + } 286 + pullUpdateButton.setAttribute('data-do', url); 273 287 } 274 288 }, 275 289 }); ··· 282 296 } 283 297 284 298 export function initRepoPullRequestAllowMaintainerEdit() { 285 - const checkbox = document.getElementById('allow-edits-from-maintainers'); 286 - if (!checkbox) return; 299 + const wrapper = document.getElementById('allow-edits-from-maintainers'); 300 + if (!wrapper) return; 287 301 288 - const $checkbox = $(checkbox); 289 - 290 - const promptError = checkbox.getAttribute('data-prompt-error'); 291 - $checkbox.checkbox({ 292 - 'onChange': async () => { 293 - const checked = $checkbox.checkbox('is checked'); 294 - let url = checkbox.getAttribute('data-url'); 295 - url += '/set_allow_maintainer_edit'; 296 - $checkbox.checkbox('set disabled'); 297 - try { 298 - const response = await POST(url, { 299 - data: {allow_maintainer_edit: checked}, 300 - }); 301 - if (!response.ok) { 302 - throw new Error('Failed to update maintainer edit permission'); 303 - } 304 - } catch (error) { 305 - console.error(error); 306 - showTemporaryTooltip(checkbox, promptError); 307 - } finally { 308 - $checkbox.checkbox('set enabled'); 302 + wrapper.querySelector('input[type="checkbox"]')?.addEventListener('change', async (e) => { 303 + const checked = e.target.checked; 304 + const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`; 305 + wrapper.classList.add('is-loading'); 306 + e.target.disabled = true; 307 + try { 308 + const response = await POST(url, {data: {allow_maintainer_edit: checked}}); 309 + if (!response.ok) { 310 + throw new Error('Failed to update maintainer edit permission'); 309 311 } 310 - }, 312 + } catch (error) { 313 + console.error(error); 314 + showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error')); 315 + } finally { 316 + wrapper.classList.remove('is-loading'); 317 + e.target.disabled = false; 318 + } 311 319 }); 312 320 } 313 321 ··· 373 381 374 382 $('.re-request-review').on('click', async function (e) { 375 383 e.preventDefault(); 376 - const url = $(this).data('update-url'); 377 - const issueId = $(this).data('issue-id'); 378 - const id = $(this).data('id'); 379 - const isChecked = $(this).hasClass('checked'); 384 + const url = this.getAttribute('data-update-url'); 385 + const issueId = this.getAttribute('data-issue-id'); 386 + const id = this.getAttribute('data-id'); 387 + const isChecked = this.classList.contains('checked'); 380 388 381 389 await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id); 382 390 window.location.reload(); ··· 403 411 export async function handleReply($el) { 404 412 hideElem($el); 405 413 const $form = $el.closest('.comment-code-cloud').find('.comment-form'); 406 - $form.removeClass('tw-hidden'); 414 + showElem($form); 407 415 408 416 const $textarea = $form.find('textarea'); 409 417 let editor = getComboMarkdownEditor($textarea); ··· 460 468 461 469 $(document).on('click', '.show-outdated', function (e) { 462 470 e.preventDefault(); 463 - const id = $(this).data('comment'); 464 - $(this).addClass('tw-hidden'); 465 - $(`#code-comments-${id}`).removeClass('tw-hidden'); 466 - $(`#code-preview-${id}`).removeClass('tw-hidden'); 467 - $(`#hide-outdated-${id}`).removeClass('tw-hidden'); 471 + const id = this.getAttribute('data-comment'); 472 + hideElem(this); 473 + showElem(`#code-comments-${id}`); 474 + showElem(`#code-preview-${id}`); 475 + showElem(`#hide-outdated-${id}`); 468 476 }); 469 477 470 478 $(document).on('click', '.hide-outdated', function (e) { 471 479 e.preventDefault(); 472 - const id = $(this).data('comment'); 473 - $(this).addClass('tw-hidden'); 474 - $(`#code-comments-${id}`).addClass('tw-hidden'); 475 - $(`#code-preview-${id}`).addClass('tw-hidden'); 476 - $(`#show-outdated-${id}`).removeClass('tw-hidden'); 480 + const id = this.getAttribute('data-comment'); 481 + hideElem(this); 482 + hideElem(`#code-comments-${id}`); 483 + hideElem(`#code-preview-${id}`); 484 + showElem(`#show-outdated-${id}`); 477 485 }); 478 486 479 487 $(document).on('click', 'button.comment-form-reply', async function (e) { ··· 510 518 } 511 519 512 520 $(document).on('click', '.add-code-comment', async function (e) { 513 - if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745 521 + if (e.target.classList.contains('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745 514 522 e.preventDefault(); 515 523 516 - const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split'); 517 - const side = $(this).data('side'); 518 - const idx = $(this).data('idx'); 519 - const path = $(this).closest('[data-path]').data('path'); 520 - const $tr = $(this).closest('tr'); 521 - const lineType = $tr.data('line-type'); 524 + const isSplit = this.closest('.code-diff')?.classList.contains('code-diff-split'); 525 + const side = this.getAttribute('data-side'); 526 + const idx = this.getAttribute('data-idx'); 527 + const path = this.closest('[data-path]')?.getAttribute('data-path'); 528 + const tr = this.closest('tr'); 529 + const lineType = tr.getAttribute('data-line-type'); 522 530 523 - let $ntr = $tr.next(); 524 - if (!$ntr.hasClass('add-comment')) { 531 + const ntr = tr.nextElementSibling; 532 + let $ntr = $(ntr); 533 + if (!ntr?.classList.contains('add-comment')) { 525 534 $ntr = $(` 526 535 <tr class="add-comment" data-line-type="${lineType}"> 527 536 ${isSplit ? ` ··· 531 540 <td class="add-comment-left add-comment-right" colspan="5"></td> 532 541 `} 533 542 </tr>`); 534 - $tr.after($ntr); 543 + $(tr).after($ntr); 535 544 } 536 545 537 546 const $td = $ntr.find(`.add-comment-${side}`); ··· 617 626 618 627 const editTitleToggle = function () { 619 628 toggleElem($issueTitle); 620 - toggleElem($('.not-in-edit')); 621 - toggleElem($('#edit-title-input')); 622 - toggleElem($('#pull-desc')); 623 - toggleElem($('#pull-desc-edit')); 624 - toggleElem($('.in-edit')); 625 - toggleElem($('.new-issue-button')); 626 - $('#issue-title-wrapper').toggleClass('edit-active'); 629 + toggleElem('.not-in-edit'); 630 + toggleElem('#edit-title-input'); 631 + toggleElem('#pull-desc'); 632 + toggleElem('#pull-desc-edit'); 633 + toggleElem('.in-edit'); 634 + toggleElem('.new-issue-button'); 635 + document.getElementById('issue-title-wrapper')?.classList.toggle('edit-active'); 627 636 $editInput[0].focus(); 628 637 $editInput[0].select(); 629 638 return false;
+23 -22
web_src/js/features/repo-projects.js
··· 94 94 } 95 95 96 96 export function initRepoProject() { 97 - if (!$('.repository.projects').length) { 97 + if (!document.querySelector('.repository.projects')) { 98 98 return; 99 99 } 100 100 101 101 const _promise = initRepoProjectSortable(); 102 102 103 - $('.edit-project-column-modal').each(function () { 104 - const $projectHeader = $(this).closest('.project-column-header'); 105 - const $projectTitleLabel = $projectHeader.find('.project-column-title'); 106 - const $projectTitleInput = $(this).find('.project-column-title-input'); 107 - const $projectColorInput = $(this).find('#new_project_column_color'); 108 - const $boardColumn = $(this).closest('.project-column'); 103 + for (const modal of document.getElementsByClassName('edit-project-column-modal')) { 104 + const projectHeader = modal.closest('.project-column-header'); 105 + const projectTitleLabel = projectHeader?.querySelector('.project-column-title'); 106 + const projectTitleInput = modal.querySelector('.project-column-title-input'); 107 + const projectColorInput = modal.querySelector('#new_project_column_color'); 108 + const boardColumn = modal.closest('.project-column'); 109 + const bgColor = boardColumn?.style.backgroundColor; 109 110 110 - const bgColor = $boardColumn[0].style.backgroundColor; 111 111 if (bgColor) { 112 - setLabelColor($projectHeader, rgbToHex(bgColor)); 112 + setLabelColor(projectHeader, rgbToHex(bgColor)); 113 113 } 114 114 115 - $(this).find('.edit-project-column-button').on('click', async function (e) { 115 + modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) { 116 116 e.preventDefault(); 117 - 118 117 try { 119 - await PUT($(this).data('url'), { 118 + await PUT(this.getAttribute('data-url'), { 120 119 data: { 121 - title: $projectTitleInput.val(), 122 - color: $projectColorInput.val(), 120 + title: projectTitleInput?.value, 121 + color: projectColorInput?.value, 123 122 }, 124 123 }); 125 124 } catch (error) { 126 125 console.error(error); 127 126 } finally { 128 - $projectTitleLabel.text($projectTitleInput.val()); 129 - $projectTitleInput.closest('form').removeClass('dirty'); 130 - if ($projectColorInput.val()) { 131 - setLabelColor($projectHeader, $projectColorInput.val()); 127 + projectTitleLabel.textContent = projectTitleInput?.value; 128 + projectTitleInput.closest('form')?.classList.remove('dirty'); 129 + if (projectColorInput?.value) { 130 + setLabelColor(projectHeader, projectColorInput.value); 132 131 } 133 - $boardColumn[0].style = `background: ${$projectColorInput.val()} !important`; 132 + boardColumn.style = `background: ${projectColorInput.value} !important`; 134 133 $('.ui.modal').modal('hide'); 135 134 } 136 135 }); 137 - }); 136 + } 138 137 139 138 $('.default-project-column-modal').each(function () { 140 139 const $boardColumn = $(this).closest('.project-column'); ··· 187 186 function setLabelColor(label, color) { 188 187 const {r, g, b} = tinycolor(color).toRgb(); 189 188 if (useLightTextOnBackground(r, g, b)) { 190 - label.removeClass('dark-label').addClass('light-label'); 189 + label.classList.remove('dark-label'); 190 + label.classList.add('light-label'); 191 191 } else { 192 - label.removeClass('light-label').addClass('dark-label'); 192 + label.classList.remove('light-label'); 193 + label.classList.add('dark-label'); 193 194 } 194 195 } 195 196
+18 -13
web_src/js/features/repo-settings.js
··· 77 77 } 78 78 79 79 export function initRepoSettingBranches() { 80 - if (!$('.repository.settings.branches').length) return; 81 - $('.toggle-target-enabled').on('change', function () { 82 - const $target = $(this.getAttribute('data-target')); 83 - $target.toggleClass('disabled', !this.checked); 84 - }); 85 - $('.toggle-target-disabled').on('change', function () { 86 - const $target = $(this.getAttribute('data-target')); 87 - if (this.checked) $target.addClass('disabled'); // only disable, do not auto enable 88 - }); 89 - $('#dismiss_stale_approvals').on('change', function () { 90 - const $target = $('#ignore_stale_approvals_box'); 91 - $target.toggleClass('disabled', this.checked); 80 + if (!document.querySelector('.repository.settings.branches')) return; 81 + 82 + for (const el of document.getElementsByClassName('toggle-target-enabled')) { 83 + el.addEventListener('change', function () { 84 + const target = document.querySelector(this.getAttribute('data-target')); 85 + target?.classList.toggle('disabled', !this.checked); 86 + }); 87 + } 88 + 89 + for (const el of document.getElementsByClassName('toggle-target-disabled')) { 90 + el.addEventListener('change', function () { 91 + const target = document.querySelector(this.getAttribute('data-target')); 92 + if (this.checked) target?.classList.add('disabled'); // only disable, do not auto enable 93 + }); 94 + } 95 + 96 + document.getElementById('dismiss_stale_approvals')?.addEventListener('change', function () { 97 + document.getElementById('ignore_stale_approvals_box')?.classList.toggle('disabled', this.checked); 92 98 }); 93 99 94 100 // show the `Matched` mark for the status checks that match the pattern ··· 106 112 break; 107 113 } 108 114 } 109 - 110 115 toggleElem(el, matched); 111 116 } 112 117 };
+2
web_src/js/index.js
··· 86 86 import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'; 87 87 import {initDirAuto} from './modules/dirauto.js'; 88 88 import {initRepositorySearch} from './features/repo-search.js'; 89 + import {initColorPickers} from './features/colorpicker.js'; 89 90 90 91 // Init Gitea's Fomantic settings 91 92 initGiteaFomantic(); ··· 188 189 initRepoDiffView(); 189 190 initPdfViewer(); 190 191 initScopedAccessTokenCategories(); 192 + initColorPickers(); 191 193 });
-2
web_src/js/modules/fomantic.js
··· 11 11 export function initGiteaFomantic() { 12 12 // Silence fomantic's error logging when tabs are used without a target content element 13 13 $.fn.tab.settings.silent = true; 14 - // Disable the behavior of fomantic to toggle the checkbox when you press enter on a checkbox element. 15 - $.fn.checkbox.settings.enableEnterKey = false; 16 14 17 15 // By default, use "exact match" for full text search 18 16 $.fn.dropdown.settings.fullTextSearch = 'exact';
+6 -11
web_src/js/modules/fomantic/aria.md
··· 41 41 <label><input type="checkbox"> ... </label> 42 42 ``` 43 43 44 - However, related CSS styles aren't supported (not implemented) yet, so at the moment, 45 - almost all the checkboxes are still using Fomantic UI checkbox. 46 - 47 - ## Fomantic UI Checkbox 44 + However, the templates still have the Fomantic-style HTML layout: 48 45 49 46 ```html 50 47 <div class="ui checkbox"> 51 - <input type="checkbox"> <!-- class "hidden" will be added by $.checkbox() --> 48 + <input type="checkbox"> 52 49 <label>...</label> 53 50 </div> 54 51 ``` 55 52 56 - Then the JS `$.checkbox()` should be called to make it work with keyboard and label-clicking, 57 - then it works like the ideal checkboxes. 58 - 59 - There is still a problem: Fomantic UI checkbox is not friendly to screen readers, 60 - so we add IDs to all the Fomantic UI checkboxes automatically by JS. 61 - If the `label` part is empty, then the checkbox needs to get the `aria-label` attribute manually. 53 + We call `initAriaCheckboxPatch` to link the `input` and `label` which makes clicking the 54 + label etc. work. There is still a problem: These checkboxes are not friendly to screen readers, 55 + so we add IDs to all the Fomantic UI checkboxes automatically by JS. If the `label` part is empty, 56 + then the checkbox needs to get the `aria-label` attribute manually. 62 57 63 58 # Fomantic Dropdown 64 59
+18 -32
web_src/js/modules/fomantic/checkbox.js
··· 1 - import $ from 'jquery'; 2 1 import {generateAriaId} from './base.js'; 3 2 4 - const ariaPatchKey = '_giteaAriaPatchCheckbox'; 5 - const fomanticCheckboxFn = $.fn.checkbox; 6 - 7 - // use our own `$.fn.checkbox` to patch Fomantic's checkbox module 8 3 export function initAriaCheckboxPatch() { 9 - if ($.fn.checkbox === ariaCheckboxFn) throw new Error('initAriaCheckboxPatch could only be called once'); 10 - $.fn.checkbox = ariaCheckboxFn; 11 - ariaCheckboxFn.settings = fomanticCheckboxFn.settings; 12 - } 4 + // link the label and the input element so it's clickable and accessible 5 + for (const el of document.querySelectorAll('.ui.checkbox')) { 6 + if (el.hasAttribute('data-checkbox-patched')) continue; 7 + const label = el.querySelector('label'); 8 + const input = el.querySelector('input'); 9 + if (!label || !input) continue; 10 + const inputId = input.getAttribute('id'); 11 + const labelFor = label.getAttribute('for'); 13 12 14 - // the patched `$.fn.checkbox` checkbox function 15 - // * it does the one-time attaching on the first call 16 - function ariaCheckboxFn(...args) { 17 - const ret = fomanticCheckboxFn.apply(this, args); 18 - for (const el of this) { 19 - if (el[ariaPatchKey]) continue; 20 - attachInit(el); 13 + if (inputId && !labelFor) { // missing "for" 14 + label.setAttribute('for', inputId); 15 + } else if (!inputId && !labelFor) { // missing both "id" and "for" 16 + const id = generateAriaId(); 17 + input.setAttribute('id', id); 18 + label.setAttribute('for', id); 19 + } else { 20 + continue; 21 + } 22 + el.setAttribute('data-checkbox-patched', 'true'); 21 23 } 22 - return ret; 23 - } 24 - 25 - function attachInit(el) { 26 - // Fomantic UI checkbox needs to be something like: <div class="ui checkbox"><label /><input /></div> 27 - // It doesn't work well with <label><input />...</label> 28 - // To make it work with aria, the "id"/"for" attributes are necessary, so add them automatically if missing. 29 - // In the future, refactor to use native checkbox directly, then this patch could be removed. 30 - el[ariaPatchKey] = {}; // record that this element has been patched 31 - const label = el.querySelector('label'); 32 - const input = el.querySelector('input'); 33 - if (!label || !input || input.getAttribute('id')) return; 34 - 35 - const id = generateAriaId(); 36 - input.setAttribute('id', id); 37 - label.setAttribute('for', id); 38 24 }
+1 -1
web_src/js/modules/fomantic/dropdown.js
··· 207 207 if (!$item) $item = $(menu).find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item 208 208 // if the selected item is clickable, then trigger the click event. 209 209 // we can not click any item without check, because Fomantic code might also handle the Enter event. that would result in double click. 210 - if ($item && ($item[0].matches('a') || $item.hasClass('js-aria-clickable'))) $item[0].click(); 210 + if ($item?.[0]?.matches('a, .js-aria-clickable')) $item[0].click(); 211 211 } 212 212 }); 213 213
+4 -3
web_src/js/modules/tippy.js
··· 3 3 import {formatDatetime} from '../utils/time.js'; 4 4 5 5 const visibleInstances = new Set(); 6 + const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`; 6 7 7 8 export function createTippy(target, opts = {}) { 8 9 // the callback functions should be destructured from opts, 9 10 // because we should use our own wrapper functions to handle them, do not let the user override them 10 - const {onHide, onShow, onDestroy, role, theme, ...other} = opts; 11 + const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; 11 12 12 13 const instance = tippy(target, { 13 14 appendTo: document.body, ··· 35 36 visibleInstances.add(instance); 36 37 return onShow?.(instance); 37 38 }, 38 - arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`, 39 + arrow: arrow || (theme === 'bare' ? false : arrowSvg), 39 40 role: role || 'menu', // HTML role attribute 40 - theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu" or "box-with-header" 41 + theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu", "box-with-header" or "bare" 41 42 plugins: [followCursor], 42 43 ...other, 43 44 });
+1
web_src/js/modules/toast.js
··· 39 39 40 40 toast.showToast(); 41 41 toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast()); 42 + return toast; 42 43 } 43 44 44 45 export function showInfoToast(message, opts) {
+16 -2
web_src/js/utils/dom.js
··· 51 51 return res[0]; 52 52 } 53 53 54 - export function queryElemSiblings(el, selector) { 55 - return Array.from(el.parentNode.children).filter((child) => child !== el && child.matches(selector)); 54 + function applyElemsCallback(elems, fn) { 55 + if (fn) { 56 + for (const el of elems) { 57 + fn(el); 58 + } 59 + } 60 + return elems; 61 + } 62 + 63 + export function queryElemSiblings(el, selector = '*', fn) { 64 + return applyElemsCallback(Array.from(el.parentNode.children).filter((child) => child !== el && child.matches(selector)), fn); 65 + } 66 + 67 + // it works like jQuery.children: only the direct children are selected 68 + export function queryElemChildren(parent, selector = '*', fn) { 69 + return applyElemsCallback(parent.querySelectorAll(`:scope > ${selector}`), fn); 56 70 } 57 71 58 72 export function onDomReady(cb) {
-7
webpack.config.js
··· 196 196 filename: 'fonts/[name].[contenthash:8][ext]', 197 197 }, 198 198 }, 199 - { 200 - test: /\.png$/i, 201 - type: 'asset/resource', 202 - generator: { 203 - filename: 'img/webpack/[name].[contenthash:8][ext]', 204 - }, 205 - }, 206 199 ], 207 200 }, 208 201 plugins: [