History

Merge pull request '[gitea] week 15 cherry pick' (#3091) from algernon/forgejo:wcp/week-15 into forgejo

Earl Warren committed 2 years ago

Commit 26fc7c32 parents 7179ae8 9bd261a117 files · +6611 −7217Browse files

Unified · Split

117 files
  1. .deadcode-out +0 −1
  2. .dockerignore +0 −1
  3. .eslintrc.yaml +1 −0
  4. .gitignore +0 −1
  5. .ignore +3 −1
  6. .stylelintrc.yaml +0 −223
  7. Makefile +1 −1
  8. custom/conf/app.example.ini +7 −0
  9. models/actions/variable.go +16 −11
  10. models/asymkey/gpg_key_common.go +10 −0
  11. models/asymkey/gpg_key_object_verification.go +1 −7
  12. models/asymkey/gpg_key_test.go +12 −0
  13. models/asymkey/gpg_key_verify.go +4 −0
  14. models/user/user.go +18 −0
  15. models/user/user_test.go +35 −0
  16. modules/git/commit_convert_gogit.go +6 −0
  17. modules/git/commit_reader.go +2 −0
  18. modules/git/commit_test.go +67 −0
  19. modules/lfs/filesystem_client.go +2 −2
  20. modules/markup/markdown/markdown_test.go +18 −2
  21. modules/markup/markdown/math/inline_parser.go +13 −5
  22. modules/setting/admin.go +5 −1
  23. modules/setting/other.go +2 −0
  24. modules/structs/variable.go +37 −0
  25. modules/templates/helper.go +3 −0
  26. modules/util/util.go +9 −0
  27. modules/util/util_test.go +5 −0
  28. options/license/AMD-newlib +11 −0
  29. options/license/OAR +12 −0
  30. options/license/xzoom +12 −0
  31. package-lock.json +23 −9
  32. package.json +2 −1
  33. routers/api/v1/api.go +27 −0
  34. routers/api/v1/org/variables.go +291 −0
  35. routers/api/v1/repo/action.go +296 −0
  36. routers/api/v1/repo/repo.go +10 −0
  37. routers/api/v1/swagger/action.go +14 −0
  38. routers/api/v1/swagger/options.go +6 −0
  39. routers/api/v1/user/action.go +250 −0
  40. routers/api/v1/user/gpg_key.go +3 −2
  41. routers/api/v1/user/key.go +2 −2
  42. routers/web/repo/setting/setting.go +12 −0
  43. routers/web/shared/actions/variables.go +7 −60
  44. routers/web/shared/secrets/secrets.go +2 −2
  45. routers/web/user/setting/account.go +2 −2
  46. routers/web/user/setting/keys.go +7 −6
  47. services/actions/notifier_helper.go +2 −2
  48. services/actions/schedule_tasks.go +5 −0
  49. services/actions/variables.go +100 −0
  50. services/pull/pull.go +4 −4
  51. stylelint.config.js +246 −0
  52. templates/admin/config_settings.tmpl +2 −2
  53. templates/base/footer_content.tmpl +3 −1
  54. templates/devtest/gitea-ui.tmpl +1 −1
  55. templates/projects/view.tmpl +4 −4
  56. templates/repo/commit_page.tmpl +2 −2
  57. templates/repo/commits_list.tmpl +1 −1
  58. templates/repo/graph/commits.tmpl +1 −1
  59. templates/repo/home.tmpl +11 −12
  60. templates/repo/issue/choose.tmpl +1 −1
  61. templates/repo/issue/labels.tmpl +1 −1
  62. templates/repo/issue/labels/edit_delete_label.tmpl +2 −2
  63. templates/repo/issue/labels/label_list.tmpl +1 −1
  64. templates/repo/issue/labels/label_new.tmpl +2 −2
  65. templates/repo/issue/milestone_new.tmpl +1 −1
  66. templates/repo/issue/view_content/sidebar.tmpl +1 −1
  67. templates/repo/latest_commit.tmpl +1 −1
  68. templates/swagger/v1_json.tmpl +784 −36
  69. templates/user/settings/repos.tmpl +1 −1
  70. tests/integration/api_repo_variables_test.go +149 −0
  71. tests/integration/api_user_variables_test.go +144 −0
  72. web_src/css/base.css +2 −90
  73. web_src/css/features/colorpicker.css +47 −0
  74. web_src/css/features/projects.css +0 −23
  75. web_src/css/form.css +5 −54
  76. web_src/css/helpers.css +17 −0
  77. web_src/css/index.css +3 −0
  78. web_src/css/modules/animations.css +6 −3
  79. web_src/css/modules/checkbox.css +120 −0
  80. web_src/css/modules/header.css +6 −0
  81. web_src/css/modules/input.css +197 −0
  82. web_src/css/modules/list.css +187 −0
  83. web_src/css/modules/navbar.css +5 −0
  84. web_src/css/modules/tippy.css +11 −0
  85. web_src/css/org.css +0 −4
  86. web_src/css/repo.css +2 −98
  87. web_src/css/repo/issue-list.css +1 −0
  88. web_src/fomantic/build/semantic.css +2869 −5299
  89. web_src/fomantic/build/semantic.js +0 −877
  90. web_src/fomantic/semantic.json +0 −3
  91. web_src/js/components/DashboardRepoList.vue +3 −3
  92. web_src/js/features/admin/common.js +15 −8
  93. web_src/js/features/colorpicker.js +59 −5
  94. web_src/js/features/common-global.js +3 −9
  95. web_src/js/features/comp/ColorPicker.js +0 −16
  96. web_src/js/features/comp/LabelEdit.js +14 −3
  97. web_src/js/features/copycontent.js +2 −2
  98. web_src/js/features/imagediff.js +20 −9
  99. web_src/js/features/notification.js +5 −7
  100. web_src/js/features/repo-code.js +5 −3
  101. web_src/js/features/repo-diff.js +31 −24
  102. web_src/js/features/repo-editor.js +2 −2
  103. web_src/js/features/repo-home.js +25 −61
  104. web_src/js/features/repo-issue-content.js +5 −2
  105. web_src/js/features/repo-issue-list.js +15 −7
  106. web_src/js/features/repo-issue.js +104 −95
  107. web_src/js/features/repo-projects.js +23 −22
  108. web_src/js/features/repo-settings.js +18 −13
  109. web_src/js/index.js +2 −0
  110. web_src/js/modules/fomantic.js +0 −2
  111. web_src/js/modules/fomantic/aria.md +6 −11
  112. web_src/js/modules/fomantic/checkbox.js +18 −32
  113. web_src/js/modules/fomantic/dropdown.js +1 −1
  114. web_src/js/modules/tippy.js +4 −3
  115. web_src/js/modules/toast.js +1 −0
  116. web_src/js/utils/dom.js +16 −2
  117. webpack.config.js +0 −7
.deadcode-out+0−1
@@ -22,7 +22,6 @@ package "code.gitea.io/gitea/models/actions" · 21 unmodified lines
2222 func (ScheduleList).GetRepoIDs
2323 func (ScheduleList).LoadTriggerUser
2424 func (ScheduleList).LoadRepos
25− func GetVariableByID
2625
2726 package "code.gitea.io/gitea/models/asymkey"
2827 func (ErrGPGKeyAccessDenied).Error
.dockerignore+0−1
@@ -77,7 +77,6 @@ cpu.out · 76 unmodified lines
7777 /public/assets/css
7878 /public/assets/fonts
7979 /public/assets/img/avatar
80−/public/assets/img/webpack
8180 /vendor
8281 /web_src/fomantic/node_modules
8382 /web_src/fomantic/build/*
.eslintrc.yaml+1−0
@@ -3,6 +3,7 @@ reportUnusedDisableDirectives: true · 2 unmodified lines
33
44 ignorePatterns:
55 - /web_src/js/vendor
6+ - /web_src/fomantic
67
78 parserOptions:
89 sourceType: module
.gitignore+0−1
@@ -83,7 +83,6 @@ cpu.out · 82 unmodified lines
8383 /public/assets/css
8484 /public/assets/fonts
8585 /public/assets/licenses.txt
86−/public/assets/img/webpack
8786 /vendor
8887 /web_src/fomantic/node_modules
8988 /web_src/fomantic/build/*
.ignore+3−1
@@ -4,6 +4,8 @@ · 3 unmodified lines
44 /modules/options/bindata.go
55 /modules/public/bindata.go
66 /modules/templates/bindata.go
7−/vendor
7+/options/gitignore
8+/options/license
89 /public/assets
10+/vendor
911 node_modules
.stylelintrc.yaml+0−223
@@ -1,223 +0,0 @@
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]}]
Makefile+1−1
@@ -130,7 +130,7 @@ FOMANTIC_WORK_DIR := web_src/fomantic · 129 unmodified lines
130130 WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
131131 WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
132132 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
134134
135135 BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
136136 BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
custom/conf/app.example.ini+7−0
@@ -1503,6 +1503,11 @@ LEVEL = Info · 1502 unmodified lines
15031503 ;; - manage_ssh_keys: a user cannot configure ssh keys
15041504 ;; - manage_gpg_keys: a user cannot configure gpg keys
15051505 ;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 =
15061511
15071512 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
15081513 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -2326,6 +2331,8 @@ LEVEL = Info · 817 unmodified lines
23262331 ;SHOW_FOOTER_VERSION = true
23272332 ;; Show template execution time in the footer
23282333 ;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
2334+;; Show the "powered by" text in the footer
2335+;SHOW_FOOTER_POWERED_BY = true
23292336 ;; Generate sitemap. Defaults to `true`.
23302337 ;ENABLE_SITEMAP = true
23312338 ;; Enable/Disable RSS/Atom feed
models/actions/variable.go+16−11
@@ -6,13 +6,11 @@ package actions · 5 unmodified lines
66 import (
77 "context"
88 "errors"
9− "fmt"
109 "strings"
1110
1211 "code.gitea.io/gitea/models/db"
1312 "code.gitea.io/gitea/modules/log"
1413 "code.gitea.io/gitea/modules/timeutil"
15− "code.gitea.io/gitea/modules/util"
1614
1715 "xorm.io/builder"
1816 )
@@ -55,24 +53,24 @@ type FindVariablesOpts struct { · 36 unmodified lines
5553 db.ListOptions
5654 OwnerID int64
5755 RepoID int64
56+ Name string
5857 }
5958
6059 func (opts FindVariablesOpts) ToConds() builder.Cond {
6160 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`
6263 cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
6364 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+ }
6469 return cond
6570 }
6671
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)
7674 }
7775
7876 func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
@@ -84,6 +82,13 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) · 5 unmodified lines
8482 return count != 0, err
8583 }
8684
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
90+}
91+
8792 func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
8893 variables := map[string]string{}
8994
models/asymkey/gpg_key_common.go+10−0
@@ -134,3 +134,13 @@ func extractSignature(s string) (*packet.Signature, error) { · 133 unmodified lines
134134 }
135135 return sig, nil
136136 }
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+}
models/asymkey/gpg_key_object_verification.go+1−7
@@ -123,13 +123,7 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica · 122 unmodified lines
123123 }
124124 }
125125
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)
133127 defaultReason := NoKeyFound
134128
135129 // First check if the sig has a keyID and if so just look at that
models/asymkey/gpg_key_test.go+12−0
@@ -11,7 +11,9 @@ import ( · 10 unmodified lines
1111 "code.gitea.io/gitea/models/unittest"
1212 user_model "code.gitea.io/gitea/models/user"
1313 "code.gitea.io/gitea/modules/timeutil"
14+ "code.gitea.io/gitea/modules/util"
1415
16+ "github.com/keybase/go-crypto/openpgp/packet"
1517 "github.com/stretchr/testify/assert"
1618 )
1719
@@ -391,3 +393,13 @@ epiDVQ== · 373 unmodified lines
391393 assert.Equal(t, time.Unix(1586105389, 0), expire)
392394 }
393395 }
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+}
models/asymkey/gpg_key_verify.go+4−0
@@ -46,6 +46,10 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st · 45 unmodified lines
4646 return "", ErrGPGKeyNotExist{}
4747 }
4848
49+ if err := key.LoadSubKeys(ctx); err != nil {
50+ return "", err
51+ }
52+
4953 sig, err := extractSignature(signature)
5054 if err != nil {
5155 return "", ErrGPGInvalidTokenSignature{
models/user/user.go+18−0
@@ -1246,3 +1246,21 @@ func GetOrderByName() string { · 1245 unmodified lines
12461246 }
12471247 return "name"
12481248 }
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+}
models/user/user_test.go+35−0
@@ -16,6 +16,7 @@ import ( · 15 unmodified lines
1616 "code.gitea.io/gitea/models/unittest"
1717 user_model "code.gitea.io/gitea/models/user"
1818 "code.gitea.io/gitea/modules/auth/password/hash"
19+ "code.gitea.io/gitea/modules/container"
1920 "code.gitea.io/gitea/modules/optional"
2021 "code.gitea.io/gitea/modules/setting"
2122 "code.gitea.io/gitea/modules/structs"
@@ -542,3 +543,37 @@ func Test_NormalizeUserFromEmail(t *testing.T) { · 520 unmodified lines
542543 }
543544 }
544545 }
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+}
modules/git/commit_convert_gogit.go+6−0
@@ -47,6 +47,12 @@ func convertPGPSignature(c *object.Commit) *ObjectSignature { · 46 unmodified lines
4747 return nil
4848 }
4949
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+
5056 if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
5157 return nil
5258 }
modules/git/commit_reader.go+2−0
@@ -84,6 +84,8 @@ readLoop: · 83 unmodified lines
8484 commit.Committer = &Signature{}
8585 commit.Committer.Decode(data)
8686 _, _ = payloadSB.Write(line)
87+ case "encoding":
88+ _, _ = payloadSB.Write(line)
8789 case "gpgsig":
8890 fallthrough
8991 case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
modules/git/commit_test.go+67−0
@@ -125,6 +125,73 @@ empty commit`, commitFromReader.Signature.Payload) · 124 unmodified lines
125125 assert.EqualValues(t, commitFromReader, commitFromReader2)
126126 }
127127
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+
128195 func TestHasPreviousCommit(t *testing.T) {
129196 bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
130197
modules/lfs/filesystem_client.go+2−2
@@ -44,7 +44,7 @@ func (c *FilesystemClient) Download(ctx context.Context, objects []Pointer, call · 43 unmodified lines
4444 if err != nil {
4545 return err
4646 }
47−
47+ defer f.Close()
4848 if err := callback(p, f, nil); err != nil {
4949 return err
5050 }
@@ -75,7 +75,7 @@ func (c *FilesystemClient) Upload(ctx context.Context, objects []Pointer, callba · 24 unmodified lines
7575 if err != nil {
7676 return err
7777 }
78−
78+ defer f.Close()
7979 _, err = io.Copy(f, content)
8080
8181 return err
modules/markup/markdown/markdown_test.go+18−2
@@ -505,9 +505,17 @@ func TestMathBlock(t *testing.T) { · 504 unmodified lines
505505 `\(a\) \(b\)`,
506506 `<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
507507 },
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+ },
508516 {
509517 `$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,
511519 },
512520 {
513521 `a a$b b`,
@@ -515,7 +523,15 @@ func TestMathBlock(t *testing.T) { · 1 unmodified line
515523 },
516524 {
517525 `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,
519535 },
520536 {
521537 "$$a$$",
modules/markup/markdown/math/inline_parser.go+13−5
@@ -41,9 +41,12 @@ func (parser *inlineParser) Trigger() []byte { · 40 unmodified lines
4141 return parser.start[0:1]
4242 }
4343
44+func isPunctuation(b byte) bool {
45+ return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
46+}
47+
4448 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')
4750 }
4851
4952 // Parse parses the current line and returns a result of parsing.
@@ -56,7 +59,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. · 6 unmodified lines
5659 }
5760
5861 precedingCharacter := block.PrecendingCharacter()
59− if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {
62+ if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
6063 // need to exclude things like `a$` from being considered a start
6164 return nil
6265 }
@@ -75,14 +78,19 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. · 12 unmodified lines
7578 ender += pos
7679
7780 // 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 '\'
7982 pos = ender + len(parser.end)
8083 if len(line) <= pos {
8184 break
8285 }
83− if !isAlphanumeric(line[pos]) {
86+ suceedingCharacter := line[pos]
87+ if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') {
88+ return nil
89+ }
90+ if line[ender-1] != '\\' {
8491 break
8592 }
93+
8694 // move the pointer onwards
8795 ender += len(parser.end)
8896 }
modules/setting/admin.go+5−1
@@ -3,7 +3,9 @@ · 2 unmodified lines
33
44 package setting
55
6−import "code.gitea.io/gitea/modules/container"
6+import (
7+ "code.gitea.io/gitea/modules/container"
8+)
79
810 // Admin settings
911 var Admin struct {
@@ -11,6 +13,7 @@ var Admin struct { · 1 unmodified line
1113 DefaultEmailNotification string
1214 SendNotificationEmailOnNewUser bool
1315 UserDisabledFeatures container.Set[string]
16+ ExternalUserDisableFeatures container.Set[string]
1417 }
1518
1619 func loadAdminFrom(rootCfg ConfigProvider) {
@@ -18,6 +21,7 @@ func loadAdminFrom(rootCfg ConfigProvider) { · 1 unmodified line
1821 Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
1922 Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
2023 Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
24+ Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...)
2125 }
2226
2327 const (
modules/setting/other.go+2−0
@@ -8,6 +8,7 @@ import "code.gitea.io/gitea/modules/log" · 7 unmodified lines
88 type OtherConfig struct {
99 ShowFooterVersion bool
1010 ShowFooterTemplateLoadTime bool
11+ ShowFooterPoweredBy bool
1112 EnableFeed bool
1213 EnableSitemap bool
1314 }
@@ -15,6 +16,7 @@ type OtherConfig struct { · 1 unmodified line
1516 var Other = OtherConfig{
1617 ShowFooterVersion: true,
1718 ShowFooterTemplateLoadTime: true,
19+ ShowFooterPoweredBy: true,
1820 EnableSitemap: true,
1921 EnableFeed: true,
2022 }
modules/structs/variable.go+37−0
@@ -0,0 +1,37 @@
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+}
modules/templates/helper.go+3−0
@@ -109,6 +109,9 @@ func NewFuncMap() template.FuncMap { · 108 unmodified lines
109109 "ShowFooterTemplateLoadTime": func() bool {
110110 return setting.Other.ShowFooterTemplateLoadTime
111111 },
112+ "ShowFooterPoweredBy": func() bool {
113+ return setting.Other.ShowFooterPoweredBy
114+ },
112115 "AllowedReactions": func() []string {
113116 return setting.UI.Reactions
114117 },
modules/util/util.go+9−0
@@ -212,3 +212,12 @@ func ToFloat64(number any) (float64, error) { · 211 unmodified lines
212212 func ToPointer[T any](val T) *T {
213213 return &val
214214 }
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+}
modules/util/util_test.go+5−0
@@ -235,3 +235,8 @@ func TestToPointer(t *testing.T) { · 234 unmodified lines
235235 val123 := 123
236236 assert.False(t, &val123 == ToPointer(val123))
237237 }
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+}
options/license/AMD-newlib+11−0
@@ -0,0 +1,11 @@
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.
options/license/OAR+12−0
@@ -0,0 +1,12 @@
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.
options/license/xzoom+12−0
@@ -0,0 +1,12 @@
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.
package-lock.json+23−9
@@ -9,7 +9,6 @@ · 8 unmodified lines
99 "@citation-js/plugin-bibtex": "0.7.9",
1010 "@citation-js/plugin-csl": "0.7.9",
1111 "@citation-js/plugin-software-formats": "0.6.1",
12− "@claviska/jquery-minicolors": "2.3.6",
1312 "@github/markdown-toolbar-element": "2.2.3",
1413 "@github/relative-time-element": "4.4.0",
1514 "@github/text-expander-element": "2.6.1",
@@ -54,6 +53,7 @@ · 38 unmodified lines
5453 "toastify-js": "1.12.0",
5554 "tributejs": "5.1.3",
5655 "uint8-to-base64": "0.2.0",
56+ "vanilla-colorful": "0.7.2",
5757 "vue": "3.4.21",
5858 "vue-bar-graph": "2.0.0",
5959 "vue-chartjs": "5.3.0",
@@ -92,6 +92,7 @@ · 32 unmodified lines
9292 "stylelint": "16.3.1",
9393 "stylelint-declaration-block-no-ignored-properties": "2.8.0",
9494 "stylelint-declaration-strict-value": "1.10.4",
95+ "stylelint-value-no-unknown-custom-properties": "6.0.1",
9596 "svgo": "3.2.0",
9697 "updates": "16.0.0",
9798 "vite-string-plugin": "1.1.5",
@@ -395,14 +396,6 @@ · 297 unmodified lines
395396 "node": ">=14.0.0"
396397 }
397398 },
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− }
405− },
406399 "node_modules/@csstools/css-parser-algorithms": {
407400 "version": "2.6.1",
408401 "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
@@ -11121,6 +11114,22 @@ · 10712 unmodified lines
1112111114 "stylelint": ">=7 <=16"
1112211115 }
1112311116 },
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+ },
1112411133 "node_modules/stylelint/node_modules/ansi-regex": {
1112511134 "version": "6.0.1",
1112611135 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@@ -12090,6 +12099,11 @@ · 963 unmodified lines
1209012099 "builtins": "^1.0.3"
1209112100 }
1209212101 },
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=="
12106+ },
1209312107 "node_modules/vite": {
1209412108 "version": "5.2.8",
1209512109 "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
package.json+2−1
@@ -8,7 +8,6 @@ · 7 unmodified lines
88 "@citation-js/plugin-bibtex": "0.7.9",
99 "@citation-js/plugin-csl": "0.7.9",
1010 "@citation-js/plugin-software-formats": "0.6.1",
11− "@claviska/jquery-minicolors": "2.3.6",
1211 "@github/markdown-toolbar-element": "2.2.3",
1312 "@github/relative-time-element": "4.4.0",
1413 "@github/text-expander-element": "2.6.1",
@@ -53,6 +52,7 @@ · 38 unmodified lines
5352 "toastify-js": "1.12.0",
5453 "tributejs": "5.1.3",
5554 "uint8-to-base64": "0.2.0",
55+ "vanilla-colorful": "0.7.2",
5656 "vue": "3.4.21",
5757 "vue-bar-graph": "2.0.0",
5858 "vue-chartjs": "5.3.0",
@@ -91,6 +91,7 @@ · 32 unmodified lines
9191 "stylelint": "16.3.1",
9292 "stylelint-declaration-block-no-ignored-properties": "2.8.0",
9393 "stylelint-declaration-strict-value": "1.10.4",
94+ "stylelint-value-no-unknown-custom-properties": "6.0.1",
9495 "svgo": "3.2.0",
9596 "updates": "16.0.0",
9697 "vite-string-plugin": "1.1.5",
routers/api/v1/api.go+27−0
@@ -871,6 +871,15 @@ func Routes() *web.Route { · 870 unmodified lines
871871 Delete(user.DeleteSecret)
872872 })
873873
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+
874883 m.Group("/runners", func() {
875884 m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
876885 })
@@ -990,6 +999,15 @@ func Routes() *web.Route { · 113 unmodified lines
990999 Delete(reqToken(), reqOwner(), repo.DeleteSecret)
9911000 })
9921001
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)
1009+ })
1010+
9931011 m.Group("/runners", func() {
9941012 m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
9951013 })
@@ -1393,6 +1411,15 @@ func Routes() *web.Route { · 397 unmodified lines
13931411 Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
13941412 })
13951413
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)
1421+ })
1422+
13961423 m.Group("/runners", func() {
13971424 m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
13981425 })
routers/api/v1/org/variables.go+291−0
@@ -0,0 +1,291 @@
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+}
routers/api/v1/repo/action.go+296−0
@@ -7,9 +7,13 @@ import ( · 6 unmodified lines
77 "errors"
88 "net/http"
99
10+ actions_model "code.gitea.io/gitea/models/actions"
11+ "code.gitea.io/gitea/models/db"
1012 api "code.gitea.io/gitea/modules/structs"
1113 "code.gitea.io/gitea/modules/util"
1214 "code.gitea.io/gitea/modules/web"
15+ "code.gitea.io/gitea/routers/api/v1/utils"
16+ actions_service "code.gitea.io/gitea/services/actions"
1317 "code.gitea.io/gitea/services/context"
1418 secret_service "code.gitea.io/gitea/services/secrets"
1519 )
@@ -127,3 +131,295 @@ func DeleteSecret(ctx *context.APIContext) { · 111 unmodified lines
127131
128132 ctx.Status(http.StatusNoContent)
129133 }
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+}
routers/api/v1/repo/repo.go+10−0
@@ -11,6 +11,7 @@ import ( · 10 unmodified lines
1111 "strings"
1212 "time"
1313
14+ actions_model "code.gitea.io/gitea/models/actions"
1415 activities_model "code.gitea.io/gitea/models/activities"
1516 "code.gitea.io/gitea/models/db"
1617 "code.gitea.io/gitea/models/organization"
@@ -30,6 +31,7 @@ import ( · 13 unmodified lines
3031 "code.gitea.io/gitea/modules/validation"
3132 "code.gitea.io/gitea/modules/web"
3233 "code.gitea.io/gitea/routers/api/v1/utils"
34+ actions_service "code.gitea.io/gitea/services/actions"
3335 "code.gitea.io/gitea/services/context"
3436 "code.gitea.io/gitea/services/convert"
3537 "code.gitea.io/gitea/services/issue"
@@ -1027,6 +1029,9 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e · 991 unmodified lines
10271029 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
10281030 return err
10291031 }
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+ }
10301035 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
10311036 } else {
10321037 if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
@@ -1034,6 +1039,11 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e · 1 unmodified line
10341039 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
10351040 return err
10361041 }
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+ }
1046+ }
10371047 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
10381048 }
10391049 }
routers/api/v1/swagger/action.go+14−0
@@ -18,3 +18,17 @@ type swaggerResponseSecret struct { · 17 unmodified lines
1818 // in:body
1919 Body api.Secret `json:"body"`
2020 }
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+}
routers/api/v1/swagger/options.go+6−0
@@ -199,4 +199,10 @@ type swaggerParameterBodies struct { · 198 unmodified lines
199199
200200 // in:body
201201 CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption
202+
203+ // in:body
204+ CreateVariableOption api.CreateVariableOption
205+
206+ // in:body
207+ UpdateVariableOption api.UpdateVariableOption
202208 }
routers/api/v1/user/action.go+250−0
@@ -7,9 +7,13 @@ import ( · 6 unmodified lines
77 "errors"
88 "net/http"
99
10+ actions_model "code.gitea.io/gitea/models/actions"
11+ "code.gitea.io/gitea/models/db"
1012 api "code.gitea.io/gitea/modules/structs"
1113 "code.gitea.io/gitea/modules/util"
1214 "code.gitea.io/gitea/modules/web"
15+ "code.gitea.io/gitea/routers/api/v1/utils"
16+ actions_service "code.gitea.io/gitea/services/actions"
1317 "code.gitea.io/gitea/services/context"
1418 secret_service "code.gitea.io/gitea/services/secrets"
1519 )
@@ -101,3 +105,249 @@ func DeleteSecret(ctx *context.APIContext) { · 85 unmodified lines
101105
102106 ctx.Status(http.StatusNoContent)
103107 }
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+}
routers/api/v1/user/gpg_key.go+3−2
@@ -10,6 +10,7 @@ import ( · 9 unmodified lines
1010
1111 asymkey_model "code.gitea.io/gitea/models/asymkey"
1212 "code.gitea.io/gitea/models/db"
13+ user_model "code.gitea.io/gitea/models/user"
1314 "code.gitea.io/gitea/modules/setting"
1415 api "code.gitea.io/gitea/modules/structs"
1516 "code.gitea.io/gitea/modules/web"
@@ -133,7 +134,7 @@ func GetGPGKey(ctx *context.APIContext) { · 117 unmodified lines
133134
134135 // CreateUserGPGKey creates new GPG key to given user by ID.
135136 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) {
137138 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
138139 return
139140 }
@@ -274,7 +275,7 @@ func DeleteGPGKey(ctx *context.APIContext) { · 134 unmodified lines
274275 // "404":
275276 // "$ref": "#/responses/notFound"
276277
277− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
278+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
278279 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
279280 return
280281 }
routers/api/v1/user/key.go+2−2
@@ -199,7 +199,7 @@ func GetPublicKey(ctx *context.APIContext) { · 198 unmodified lines
199199
200200 // CreateUserPublicKey creates new public key to given user by ID.
201201 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) {
203203 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
204204 return
205205 }
@@ -269,7 +269,7 @@ func DeletePublicKey(ctx *context.APIContext) { · 63 unmodified lines
269269 // "404":
270270 // "$ref": "#/responses/notFound"
271271
272− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
272+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
273273 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
274274 return
275275 }
routers/web/repo/setting/setting.go+12−0
@@ -13,6 +13,7 @@ import ( · 12 unmodified lines
1313 "time"
1414
1515 "code.gitea.io/gitea/models"
16+ actions_model "code.gitea.io/gitea/models/actions"
1617 "code.gitea.io/gitea/models/db"
1718 "code.gitea.io/gitea/models/organization"
1819 repo_model "code.gitea.io/gitea/models/repo"
@@ -29,6 +30,7 @@ import ( · 10 unmodified lines
2930 "code.gitea.io/gitea/modules/util"
3031 "code.gitea.io/gitea/modules/validation"
3132 "code.gitea.io/gitea/modules/web"
33+ actions_service "code.gitea.io/gitea/services/actions"
3234 asymkey_service "code.gitea.io/gitea/services/asymkey"
3335 "code.gitea.io/gitea/services/context"
3436 "code.gitea.io/gitea/services/forms"
@@ -929,6 +931,10 @@ func SettingsPost(ctx *context.Context) { · 894 unmodified lines
929931 return
930932 }
931933
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+
932938 ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
933939
934940 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
@@ -947,6 +953,12 @@ func SettingsPost(ctx *context.Context) { · 12 unmodified lines
947953 return
948954 }
949955
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+ }
960+ }
961+
950962 ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
951963
952964 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
routers/web/shared/actions/variables.go+7−60
@@ -4,17 +4,13 @@ · 3 unmodified lines
44 package actions
55
66 import (
7− "errors"
8− "regexp"
9− "strings"
10−
117 actions_model "code.gitea.io/gitea/models/actions"
128 "code.gitea.io/gitea/models/db"
139 "code.gitea.io/gitea/modules/log"
1410 "code.gitea.io/gitea/modules/web"
11+ actions_service "code.gitea.io/gitea/services/actions"
1512 "code.gitea.io/gitea/services/context"
1613 "code.gitea.io/gitea/services/forms"
17− secret_service "code.gitea.io/gitea/services/secrets"
1814 )
1915
2016 func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
@@ -29,41 +25,16 @@ func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { · 8 unmodified lines
2925 ctx.Data["Variables"] = variables
3026 }
3127
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−
4828 func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
4929 form := web.GetForm(ctx).(*forms.EditVariableForm)
5030
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)
6232 if err != nil {
63− log.Error("InsertVariable error: %v", err)
33+ log.Error("CreateVariable: %v", err)
6434 ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
6535 return
6636 }
37+
6738 ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
6839 ctx.JSONRedirect(redirectURL)
6940 }
@@ -72,23 +43,8 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { · 2 unmodified lines
7243 id := ctx.ParamsInt64(":variable_id")
7344 form := web.GetForm(ctx).(*forms.EditVariableForm)
7445
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)
9248 ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
9349 return
9450 }
@@ -99,7 +55,7 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { · 4 unmodified lines
9955 func DeleteVariable(ctx *context.Context, redirectURL string) {
10056 id := ctx.ParamsInt64(":variable_id")
10157
102− if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil {
58+ if err := actions_service.DeleteVariableByID(ctx, id); err != nil {
10359 log.Error("Delete variable [%d] failed: %v", id, err)
10460 ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
10561 return
@@ -107,12 +63,3 @@ func DeleteVariable(ctx *context.Context, redirectURL string) { · 1 unmodified line
10763 ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
10864 ctx.JSONRedirect(redirectURL)
10965 }
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−}
routers/web/shared/secrets/secrets.go+2−2
@@ -7,8 +7,8 @@ import ( · 6 unmodified lines
77 "code.gitea.io/gitea/models/db"
88 secret_model "code.gitea.io/gitea/models/secret"
99 "code.gitea.io/gitea/modules/log"
10+ "code.gitea.io/gitea/modules/util"
1011 "code.gitea.io/gitea/modules/web"
11− "code.gitea.io/gitea/routers/web/shared/actions"
1212 "code.gitea.io/gitea/services/context"
1313 "code.gitea.io/gitea/services/forms"
1414 secret_service "code.gitea.io/gitea/services/secrets"
@@ -27,7 +27,7 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) { · 12 unmodified lines
2727 func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
2828 form := web.GetForm(ctx).(*forms.AddSecretForm)
2929
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))
3131 if err != nil {
3232 log.Error("CreateOrUpdateSecret failed: %v", err)
3333 ctx.JSONError(ctx.Tr("secrets.creation.failed"))
routers/web/user/setting/account.go+2−2
@@ -244,7 +244,7 @@ func DeleteEmail(ctx *context.Context) { · 243 unmodified lines
244244
245245 // DeleteAccount render user suicide page and response for delete user himself
246246 func DeleteAccount(ctx *context.Context) {
247− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) {
247+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) {
248248 ctx.Error(http.StatusNotFound)
249249 return
250250 }
@@ -328,7 +328,7 @@ func loadAccountData(ctx *context.Context) { · 77 unmodified lines
328328 ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
329329 ctx.Data["ActivationsPending"] = pendingActivation
330330 ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
331− ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
331+ ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
332332
333333 if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
334334 ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
routers/web/user/setting/keys.go+7−6
@@ -10,6 +10,7 @@ import ( · 9 unmodified lines
1010
1111 asymkey_model "code.gitea.io/gitea/models/asymkey"
1212 "code.gitea.io/gitea/models/db"
13+ user_model "code.gitea.io/gitea/models/user"
1314 "code.gitea.io/gitea/modules/base"
1415 "code.gitea.io/gitea/modules/setting"
1516 "code.gitea.io/gitea/modules/web"
@@ -78,7 +79,7 @@ func KeysPost(ctx *context.Context) { · 62 unmodified lines
7879 ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))
7980 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
8081 case "gpg":
81− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
82+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
8283 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
8384 return
8485 }
@@ -159,7 +160,7 @@ func KeysPost(ctx *context.Context) { · 74 unmodified lines
159160 ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID))
160161 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
161162 case "ssh":
162− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
163+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
163164 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
164165 return
165166 }
@@ -203,7 +204,7 @@ func KeysPost(ctx *context.Context) { · 37 unmodified lines
203204 ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
204205 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
205206 case "verify_ssh":
206− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
207+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
207208 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
208209 return
209210 }
@@ -240,7 +241,7 @@ func KeysPost(ctx *context.Context) { · 30 unmodified lines
240241 func DeleteKey(ctx *context.Context) {
241242 switch ctx.FormString("type") {
242243 case "gpg":
243− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
244+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
244245 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
245246 return
246247 }
@@ -250,7 +251,7 @@ func DeleteKey(ctx *context.Context) { · 3 unmodified lines
250251 ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
251252 }
252253 case "ssh":
253− if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
254+ if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
254255 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
255256 return
256257 }
@@ -333,5 +334,5 @@ func loadKeysData(ctx *context.Context) { · 76 unmodified lines
333334
334335 ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
335336 ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
336− ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
337+ ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
337338 }
services/actions/notifier_helper.go+2−2
@@ -119,7 +119,7 @@ func notify(ctx context.Context, input *notifyInput) error { · 118 unmodified lines
119119 log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
120120 return nil
121121 }
122− if input.Repo.IsEmpty {
122+ if input.Repo.IsEmpty || input.Repo.IsArchived {
123123 return nil
124124 }
125125 if unit_model.TypeActions.UnitGlobalDisabled() {
@@ -536,7 +536,7 @@ func handleSchedules( · 410 unmodified lines
536536
537537 // DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
538538 func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
539− if repo.IsEmpty {
539+ if repo.IsEmpty || repo.IsArchived {
540540 return nil
541541 }
542542
services/actions/schedule_tasks.go+5−0
@@ -66,6 +66,11 @@ func startTasks(ctx context.Context) error { · 65 unmodified lines
6666 }
6767 }
6868
69+ if row.Repo.IsArchived {
70+ // Skip if the repo is archived
71+ continue
72+ }
73+
6974 cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
7075 if err != nil {
7176 if repo_model.IsErrUnitTypeNotExist(err) {
services/actions/variables.go+100−0
@@ -0,0 +1,100 @@
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+}
services/pull/pull.go+4−4
@@ -967,12 +967,12 @@ func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]Co · 966 unmodified lines
967967 for _, commit := range prInfo.Commits {
968968 var committerOrAuthorName string
969969 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 {
974971 committerOrAuthorName = commit.Author.Name
975972 commitTime = commit.Author.When
973+ } else {
974+ committerOrAuthorName = commit.Committer.Name
975+ commitTime = commit.Committer.When
976976 }
977977
978978 commits = append(commits, CommitInfo{
stylelint.config.js+246−0
@@ -0,0 +1,246 @@
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+};
templates/admin/config_settings.tmpl+2−2
@@ -7,14 +7,14 @@ · 6 unmodified lines
77 <dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>
88 <dd>
99 <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>
1111 </div>
1212 </dd>
1313 <div class="divider"></div>
1414 <dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
1515 <dd>
1616 <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>
1818 </div>
1919 </dd>
2020 </dl>
templates/base/footer_content.tmpl+3−1
@@ -1,6 +1,8 @@
11 <footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">
22 <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}}
46 {{if (or .ShowFooterVersion .PageIsAdmin)}}
57 {{ctx.Locale.Tr "version"}}:
68 {{if .IsAdmin}}
templates/devtest/gitea-ui.tmpl+1−1
@@ -102,7 +102,7 @@ · 101 unmodified lines
102102
103103 <div>
104104 <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>
106106 <div class="is-loading tw-border tw-border-secondary tw-py-4">
107107 <p>loading ...</p>
108108 <p>loading ...</p>
templates/projects/view.tmpl+4−4
@@ -42,8 +42,8 @@ · 41 unmodified lines
4242
4343 <div class="field color-field">
4444 <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">
4747 {{template "repo/issue/label_precolors"}}
4848 </div>
4949 </div>
@@ -114,8 +114,8 @@ · 64 unmodified lines
114114
115115 <div class="field color-field">
116116 <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}}">
119119 {{template "repo/issue/label_precolors"}}
120120 </div>
121121 </div>
templates/repo/commit_page.tmpl+2−2
@@ -164,9 +164,9 @@ · 163 unmodified lines
164164 {{end}}
165165 {{end}}
166166 </div>
167− <div class="ui horizontal list tw-flex tw-items-center">
167+ <div class="tw-flex tw-items-center">
168168 {{if .Parents}}
169− <div class="item">
169+ <div>
170170 <span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>
171171 {{range .Parents}}
172172 {{if $.PageIsWiki}}
templates/repo/commits_list.tmpl+1−1
@@ -16,7 +16,7 @@ · 15 unmodified lines
1616 <td class="author tw-flex">
1717 {{$userName := .Author.Name}}
1818 {{if .User}}
19− {{if .User.FullName}}
19+ {{if and .User.FullName DefaultShowFullName}}
2020 {{$userName = .User.FullName}}
2121 {{end}}
2222 {{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>
templates/repo/graph/commits.tmpl+1−1
@@ -61,7 +61,7 @@ · 60 unmodified lines
6161 <span class="author tw-flex tw-items-center tw-mr-2">
6262 {{$userName := $commit.Commit.Author.Name}}
6363 {{if $commit.User}}
64− {{if $commit.User.FullName}}
64+ {{if and $commit.User.FullName DefaultShowFullName}}
6565 {{$userName = $commit.User.FullName}}
6666 {{end}}
6767 <span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
templates/repo/home.tmpl+11−12
@@ -18,22 +18,21 @@ · 17 unmodified lines
1818 </div>
1919 </form>
2020 </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}}
2324 {{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}}
2425 </div>
2526 {{end}}
2627 {{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>
3736 </div>
3837 <div>
3938 <button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
templates/repo/issue/choose.tmpl+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
33 {{template "repo/header" .}}
44 <div class="ui container">
55 {{template "base/alert" .}}
6− <div class="navbar">
6+ <div class="issue-navbar">
77 {{template "repo/issue/navbar" .}}
88 </div>
99 <div class="divider"></div>
templates/repo/issue/labels.tmpl+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
22 <div role="main" aria-label="{{.Title}}" class="page-content repository labels">
33 {{template "repo/header" .}}
44 <div class="ui container">
5− <div class="navbar tw-mb-4">
5+ <div class="issue-navbar tw-mb-4">
66 {{template "repo/issue/navbar" .}}
77 {{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
88 <button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
templates/repo/issue/labels/edit_delete_label.tmpl+2−2
@@ -52,8 +52,8 @@ · 51 unmodified lines
5252 </div>
5353 <div class="field color-field">
5454 <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">
5757 {{template "repo/issue/label_precolors"}}
5858 </div>
5959 </div>
templates/repo/issue/labels/label_list.tmpl+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
88 {{ctx.Locale.Tr "repo.issues.filter_sort"}}
99 </span>
1010 {{svg "octicon-triangle-down" 14 "dropdown icon"}}
11− <div class="left menu">
11+ <div class="menu">
1212 <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>
1313 <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>
1414 <a class="{{if eq .SortType "leastissues"}}active {{end}}item" href="?sort=leastissues&state={{$.State}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_issues"}}</a>
templates/repo/issue/labels/label_new.tmpl+2−2
@@ -27,8 +27,8 @@ · 26 unmodified lines
2727 </div>
2828 <div class="field color-field">
2929 <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">
3232 {{template "repo/issue/label_precolors"}}
3333 </div>
3434 </div>
templates/repo/issue/milestone_new.tmpl+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
22 <div role="main" aria-label="{{.Title}}" class="page-content repository new milestone">
33 {{template "repo/header" .}}
44 <div class="ui container">
5− <div class="navbar">
5+ <div class="issue-navbar">
66 {{template "repo/issue/navbar" .}}
77 {{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}
88 <div class="ui right floated secondary menu">
templates/repo/issue/view_content/sidebar.tmpl+1−1
@@ -684,7 +684,7 @@ · 683 unmodified lines
684684 {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
685685 <div class="divider"></div>
686686 <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"
688688 data-url="{{.Issue.Link}}"
689689 data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
690690 data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
templates/repo/latest_commit.tmpl+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
33 {{else}}
44 {{if .LatestCommitUser}}
55 {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}
6− {{if .LatestCommitUser.FullName}}
6+ {{if and .LatestCommitUser.FullName DefaultShowFullName}}
77 <a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
88 {{else}}
99 <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>
templates/swagger/v1_json.tmpl+784−36
@@ -1741,6 +1741,232 @@ · 1740 unmodified lines
17411741 }
17421742 }
17431743 },
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+ },
17441970 "/orgs/{org}/activities/feeds": {
17451971 "get": {
17461972 "produces": [
@@ -3455,53 +3681,305 @@ · 1708 unmodified lines
34553681 },
34563682 {
34573683 "type": "string",
3458− "description": "name of the repo to edit",
3459− "name": "repo",
3684+ "description": "name of the repo to edit",
3685+ "name": "repo",
3686+ "in": "path",
3687+ "required": true
3688+ },
3689+ {
3690+ "description": "Properties of a repo that you can edit",
3691+ "name": "body",
3692+ "in": "body",
3693+ "schema": {
3694+ "$ref": "#/definitions/EditRepoOption"
3695+ }
3696+ }
3697+ ],
3698+ "responses": {
3699+ "200": {
3700+ "$ref": "#/responses/Repository"
3701+ },
3702+ "403": {
3703+ "$ref": "#/responses/forbidden"
3704+ },
3705+ "404": {
3706+ "$ref": "#/responses/notFound"
3707+ },
3708+ "422": {
3709+ "$ref": "#/responses/validationError"
3710+ }
3711+ }
3712+ }
3713+ },
3714+ "/repos/{owner}/{repo}/actions/secrets/{secretname}": {
3715+ "put": {
3716+ "consumes": [
3717+ "application/json"
3718+ ],
3719+ "produces": [
3720+ "application/json"
3721+ ],
3722+ "tags": [
3723+ "repository"
3724+ ],
3725+ "summary": "Create or Update a secret value in a repository",
3726+ "operationId": "updateRepoSecret",
3727+ "parameters": [
3728+ {
3729+ "type": "string",
3730+ "description": "owner of the repository",
3731+ "name": "owner",
3732+ "in": "path",
3733+ "required": true
3734+ },
3735+ {
3736+ "type": "string",
3737+ "description": "name of the repository",
3738+ "name": "repo",
3739+ "in": "path",
3740+ "required": true
3741+ },
3742+ {
3743+ "type": "string",
3744+ "description": "name of the secret",
3745+ "name": "secretname",
3746+ "in": "path",
3747+ "required": true
3748+ },
3749+ {
3750+ "name": "body",
3751+ "in": "body",
3752+ "schema": {
3753+ "$ref": "#/definitions/CreateOrUpdateSecretOption"
3754+ }
3755+ }
3756+ ],
3757+ "responses": {
3758+ "201": {
3759+ "description": "response when creating a secret"
3760+ },
3761+ "204": {
3762+ "description": "response when updating a secret"
3763+ },
3764+ "400": {
3765+ "$ref": "#/responses/error"
3766+ },
3767+ "404": {
3768+ "$ref": "#/responses/notFound"
3769+ }
3770+ }
3771+ },
3772+ "delete": {
3773+ "consumes": [
3774+ "application/json"
3775+ ],
3776+ "produces": [
3777+ "application/json"
3778+ ],
3779+ "tags": [
3780+ "repository"
3781+ ],
3782+ "summary": "Delete a secret in a repository",
3783+ "operationId": "deleteRepoSecret",
3784+ "parameters": [
3785+ {
3786+ "type": "string",
3787+ "description": "owner of the repository",
3788+ "name": "owner",
3789+ "in": "path",
3790+ "required": true
3791+ },
3792+ {
3793+ "type": "string",
3794+ "description": "name of the repository",
3795+ "name": "repo",
3796+ "in": "path",
3797+ "required": true
3798+ },
3799+ {
3800+ "type": "string",
3801+ "description": "name of the secret",
3802+ "name": "secretname",
3803+ "in": "path",
3804+ "required": true
3805+ }
3806+ ],
3807+ "responses": {
3808+ "204": {
3809+ "description": "delete one secret of the organization"
3810+ },
3811+ "400": {
3812+ "$ref": "#/responses/error"
3813+ },
3814+ "404": {
3815+ "$ref": "#/responses/notFound"
3816+ }
3817+ }
3818+ }
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",
34603944 "in": "path",
34613945 "required": true
34623946 },
34633947 {
3464− "description": "Properties of a repo that you can edit",
34653948 "name": "body",
34663949 "in": "body",
34673950 "schema": {
3468− "$ref": "#/definitions/EditRepoOption"
3951+ "$ref": "#/definitions/UpdateVariableOption"
34693952 }
34703953 }
34713954 ],
34723955 "responses": {
3473− "200": {
3474− "$ref": "#/responses/Repository"
3956+ "201": {
3957+ "description": "response when updating a repo-level variable"
34753958 },
3476− "403": {
3477− "$ref": "#/responses/forbidden"
3959+ "204": {
3960+ "description": "response when updating a repo-level variable"
3961+ },
3962+ "400": {
3963+ "$ref": "#/responses/error"
34783964 },
34793965 "404": {
34803966 "$ref": "#/responses/notFound"
3481− },
3482− "422": {
3483− "$ref": "#/responses/validationError"
34843967 }
34853968 }
3486− }
3487− },
3488− "/repos/{owner}/{repo}/actions/secrets/{secretname}": {
3489− "put": {
3490− "consumes": [
3491− "application/json"
3492− ],
3969+ },
3970+ "post": {
34933971 "produces": [
34943972 "application/json"
34953973 ],
34963974 "tags": [
34973975 "repository"
34983976 ],
3499− "summary": "Create or Update a secret value in a repository",
3500− "operationId": "updateRepoSecret",
3977+ "summary": "Create a repo-level variable",
3978+ "operationId": "createRepoVariable",
35013979 "parameters": [
35023980 {
35033981 "type": "string",
3504− "description": "owner of the repository",
3982+ "description": "name of the owner",
35053983 "name": "owner",
35063984 "in": "path",
35073985 "required": true
@@ -3515,8 +3993,8 @@ · 7 unmodified lines
35153993 },
35163994 {
35173995 "type": "string",
3518− "description": "name of the secret",
3519− "name": "secretname",
3996+ "description": "name of the variable",
3997+ "name": "variablename",
35203998 "in": "path",
35213999 "required": true
35224000 },
@@ -3524,16 +4002,16 @@ · 1 unmodified line
35244002 "name": "body",
35254003 "in": "body",
35264004 "schema": {
3527− "$ref": "#/definitions/CreateOrUpdateSecretOption"
4005+ "$ref": "#/definitions/CreateVariableOption"
35284006 }
35294007 }
35304008 ],
35314009 "responses": {
35324010 "201": {
3533− "description": "response when creating a secret"
4011+ "description": "response when creating a repo-level variable"
35344012 },
35354013 "204": {
3536− "description": "response when updating a secret"
4014+ "description": "response when creating a repo-level variable"
35374015 },
35384016 "400": {
35394017 "$ref": "#/responses/error"
@@ -3544,21 +4022,18 @@ · 4 unmodified lines
35444022 }
35454023 },
35464024 "delete": {
3547− "consumes": [
3548− "application/json"
3549− ],
35504025 "produces": [
35514026 "application/json"
35524027 ],
35534028 "tags": [
35544029 "repository"
35554030 ],
3556− "summary": "Delete a secret in a repository",
3557− "operationId": "deleteRepoSecret",
4031+ "summary": "Delete a repo-level variable",
4032+ "operationId": "deleteRepoVariable",
35584033 "parameters": [
35594034 {
35604035 "type": "string",
3561− "description": "owner of the repository",
4036+ "description": "name of the owner",
35624037 "name": "owner",
35634038 "in": "path",
35644039 "required": true
@@ -3572,15 +4047,21 @@ · 7 unmodified lines
35724047 },
35734048 {
35744049 "type": "string",
3575− "description": "name of the secret",
3576− "name": "secretname",
4050+ "description": "name of the variable",
4051+ "name": "variablename",
35774052 "in": "path",
35784053 "required": true
35794054 }
35804055 ],
35814056 "responses": {
4057+ "200": {
4058+ "$ref": "#/responses/ActionVariable"
4059+ },
4060+ "201": {
4061+ "description": "response when deleting a variable"
4062+ },
35824063 "204": {
3583− "description": "delete one secret of the organization"
4064+ "description": "response when deleting a variable"
35844065 },
35854066 "400": {
35864067 "$ref": "#/responses/error"
@@ -15375,6 +15856,194 @@ · 11788 unmodified lines
1537515856 }
1537615857 }
1537715858 },
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+ },
1537816047 "/user/applications/oauth2": {
1537916048 "get": {
1538016049 "produces": [
@@ -17493,6 +18162,35 @@ · 2112 unmodified lines
1749318162 },
1749418163 "x-go-package": "code.gitea.io/gitea/modules/structs"
1749518164 },
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+ },
1749618194 "Activity": {
1749718195 "type": "object",
1749818196 "properties": {
@@ -19393,6 +20091,21 @@ · 1894 unmodified lines
1939320091 },
1939420092 "x-go-package": "code.gitea.io/gitea/modules/structs"
1939520093 },
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"
20105+ }
20106+ },
20107+ "x-go-package": "code.gitea.io/gitea/modules/structs"
20108+ },
1939620109 "CreateWikiPageOptions": {
1939720110 "description": "CreateWikiPageOptions form for creating wiki",
1939820111 "type": "object",
@@ -23773,6 +24486,26 @@ · 4374 unmodified lines
2377324486 },
2377424487 "x-go-package": "code.gitea.io/gitea/modules/structs"
2377524488 },
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+ },
2377624509 "User": {
2377724510 "description": "User represents a user",
2377824511 "type": "object",
@@ -24157,6 +24890,12 @@ · 378 unmodified lines
2415724890 }
2415824891 }
2415924892 },
24893+ "ActionVariable": {
24894+ "description": "ActionVariable",
24895+ "schema": {
24896+ "$ref": "#/definitions/ActionVariable"
24897+ }
24898+ },
2416024899 "ActivityFeedsList": {
2416124900 "description": "ActivityFeedsList",
2416224901 "schema": {
@@ -25040,6 +25779,15 @@ · 877 unmodified lines
2504025779 }
2504125780 }
2504225781 },
25782+ "VariableList": {
25783+ "description": "VariableList",
25784+ "schema": {
25785+ "type": "array",
25786+ "items": {
25787+ "$ref": "#/definitions/ActionVariable"
25788+ }
25789+ }
25790+ },
2504325791 "WatchInfo": {
2504425792 "description": "WatchInfo",
2504525793 "schema": {
@@ -25115,7 +25863,7 @@ · 69 unmodified lines
2511525863 "parameterBodies": {
2511625864 "description": "parameterBodies",
2511725865 "schema": {
25118− "$ref": "#/definitions/CreateOrUpdateSecretOption"
25866+ "$ref": "#/definitions/UpdateVariableOption"
2511925867 }
2512025868 },
2512125869 "redirect": {
templates/user/settings/repos.tmpl+1−1
@@ -6,7 +6,7 @@ · 5 unmodified lines
66 <div class="ui attached segment">
77 {{if or .allowAdopt .allowDelete}}
88 {{if .Dirs}}
9− <div class="ui middle aligned divided list">
9+ <div class="ui list">
1010 {{range $dirI, $dir := .Dirs}}
1111 {{$repo := index $.ReposMap $dir}}
1212 <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*/}}
tests/integration/api_repo_variables_test.go+149−0
@@ -0,0 +1,149 @@
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+}
tests/integration/api_user_variables_test.go+144−0
@@ -0,0 +1,144 @@
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+}
web_src/css/base.css+2−90
@@ -24,6 +24,7 @@ · 23 unmodified lines
2424 --repo-header-issue-min-height: 41px;
2525 --min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
2626 --tab-size: 4;
27+ --checkbox-size: 16px; /* height and width of checkbox and radio inputs */
2728 }
2829
2930 :root * {
@@ -44,7 +45,7 @@ html, body { · 14 unmodified lines
4445 }
4546
4647 body {
47− line-height: 1.4285rem;
48+ line-height: 20px;
4849 font-family: var(--fonts-regular);
4950 color: var(--color-text);
5051 background-color: var(--color-body);
@@ -316,61 +317,6 @@ a.label, · 265 unmodified lines
316317 background-color: var(--color-label-bg);
317318 }
318319
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−
374320 .ui.menu {
375321 display: flex;
376322 }
@@ -514,21 +460,6 @@ ol.ui.list li, · 137 unmodified lines
514460 color: var(--color-text-light-2);
515461 }
516462
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−
532463 /* replace item margin on secondary menu items with gap and remove both the
533464 negative margins on the menu as well as margin on the items */
534465 .ui.secondary.menu {
@@ -647,10 +578,6 @@ img.ui.avatar, · 112 unmodified lines
647578 aspect-ratio: 1;
648579 }
649580
650−.ui.divided.list > .item {
651− border-color: var(--color-secondary);
652−}
653−
654581 .ui.error.message .header,
655582 .ui.warning.message .header {
656583 color: inherit;
@@ -1457,11 +1384,6 @@ table th[data-sortt-desc] .svg { · 800 unmodified lines
14571384 vertical-align: -0.15em;
14581385 }
14591386
1460−/* for the jquery.minicolors plugin */
1461−.minicolors-panel {
1462− background: var(--color-secondary-dark-1) !important;
1463−}
1464−
14651387 .ui.tabular.menu {
14661388 border-color: var(--color-secondary);
14671389 }
@@ -1625,16 +1547,6 @@ table th[data-sortt-desc] .svg { · 157 unmodified lines
16251547 align-items: stretch;
16261548 }
16271549
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−}
1637−
16381550 .flex-items-block > .item,
16391551 .flex-text-block {
16401552 display: flex;
web_src/css/features/colorpicker.css+47−0
@@ -0,0 +1,47 @@
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+}
web_src/css/features/projects.css+0−23
@@ -102,26 +102,3 @@ · 101 unmodified lines
102102 .card-ghost * {
103103 opacity: 0;
104104 }
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−}
web_src/css/form.css+5−54
@@ -32,10 +32,7 @@ textarea, · 31 unmodified lines
3232 .ui.form input[type="text"],
3333 .ui.form input[type="time"],
3434 .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 {
3936 background: var(--color-input-background);
4037 border-color: var(--color-input-border);
4138 color: var(--color-input-text);
@@ -63,12 +60,7 @@ textarea:hover, · 21 unmodified lines
6360 .ui.form input[type="text"]:hover,
6461 .ui.form input[type="time"]:hover,
6562 .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 {
7264 background: var(--color-input-background);
7365 border-color: var(--color-input-border-hover);
7466 color: var(--color-input-text);
@@ -91,11 +83,7 @@ textarea:focus, · 16 unmodified lines
9183 .ui.form input[type="text"]:focus,
9284 .ui.form input[type="time"]:focus,
9385 .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 {
9987 background: var(--color-input-background);
10088 border-color: var(--color-primary);
10189 color: var(--color-input-text);
@@ -106,58 +94,21 @@ textarea:focus, · 4 unmodified lines
10694 .ui.form .inline.fields .field > label,
10795 .ui.form .inline.fields .field > p,
10896 .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 {
11698 color: var(--color-text);
11799 }
118100
119101 .ui.form .required.fields:not(.grouped) > .field > label::after,
120102 .ui.form .required.fields.grouped > label::after,
121103 .ui.form .required.field > label::after,
122−.ui.form .required.fields:not(.grouped) > .field > .checkbox::after,
123−.ui.form .required.field > .checkbox::after,
124104 .ui.form label.required::after {
125105 color: var(--color-red);
126106 }
127107
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 {
137109 color: var(--color-input-text);
138110 }
139111
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−}
160−
161112 /* match <select> padding to <input> */
162113 .ui.form select {
163114 padding: 0.67857143em 1em;
web_src/css/helpers.css+17−0
@@ -63,3 +63,20 @@ only use: · 62 unmodified lines
6363 display: none !important;
6464 }
6565 }
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; }
web_src/css/index.css+3−0
@@ -6,12 +6,15 @@ · 5 unmodified lines
66 @import "./modules/container.css";
77 @import "./modules/divider.css";
88 @import "./modules/header.css";
9+@import "./modules/input.css";
910 @import "./modules/label.css";
11+@import "./modules/list.css";
1012 @import "./modules/segment.css";
1113 @import "./modules/grid.css";
1214 @import "./modules/message.css";
1315 @import "./modules/table.css";
1416 @import "./modules/card.css";
17+@import "./modules/checkbox.css";
1518 @import "./modules/modal.css";
1619
1720 @import "./modules/select.css";
web_src/css/modules/animations.css+6−3
@@ -6,7 +6,6 @@ · 5 unmodified lines
66 .is-loading {
77 pointer-events: none !important;
88 position: relative !important;
9− overflow: hidden !important;
109 }
1110
1211 .is-loading > * {
@@ -35,10 +34,14 @@ · 22 unmodified lines
3534 border-radius: var(--border-radius-circle);
3635 }
3736
38−.is-loading.small-loading-icon::after {
37+.is-loading.loading-icon-2px::after {
3938 border-width: 2px;
4039 }
4140
41+.is-loading.loading-icon-3px::after {
42+ border-width: 3px;
43+}
44+
4245 /* 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. */
4346 form.single-button-form.is-loading > * {
4447 opacity: 1;
@@ -63,7 +66,7 @@ form.single-button-form.is-loading .button { · 18 unmodified lines
6366 background: transparent;
6467 }
6568
66−/* TODO: not needed, use "is-loading small-loading-icon" instead */
69+/* TODO: not needed, use "is-loading loading-icon-2px" instead */
6770 code.language-math.is-loading::after {
6871 padding: 0;
6972 border-width: 2px;
web_src/css/modules/checkbox.css+120−0
@@ -0,0 +1,120 @@
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+}
web_src/css/modules/header.css+6−0
@@ -135,6 +135,12 @@ h4.ui.header .sub.header { · 134 unmodified lines
135135 font-weight: var(--font-weight-normal);
136136 }
137137
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+
138144 /* if a .top.attached.header is followed by a .segment, add some margin */
139145 .ui.segments + .ui.top.attached.header,
140146 .ui.attached.segment + .ui.top.attached.header {
web_src/css/modules/input.css+197−0
@@ -0,0 +1,197 @@
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+}
web_src/css/modules/list.css+187−0
@@ -0,0 +1,187 @@
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+}
web_src/css/modules/navbar.css+5−0
@@ -140,3 +140,8 @@ · 139 unmodified lines
140140 .secondary-nav {
141141 background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */
142142 }
143+
144+.issue-navbar {
145+ display: flex;
146+ justify-content: space-between;
147+}
web_src/css/modules/tippy.css+11−0
@@ -29,6 +29,17 @@ · 28 unmodified lines
2929 z-index: 1;
3030 }
3131
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+
3243 /* tooltip theme for text tooltips */
3344
3445 .tippy-box[data-theme="tooltip"] {
web_src/css/org.css+0−4
@@ -89,10 +89,6 @@ · 88 unmodified lines
8989 text-align: center;
9090 }
9191
92−.organization.options input {
93− min-width: 300px;
94−}
95−
9692 .page-content.organization .org-avatar {
9793 margin-right: 15px;
9894 }
web_src/css/repo.css+2−98
@@ -2299,104 +2299,6 @@ · 2298 unmodified lines
22992299 padding-top: 15px;
23002300 }
23012301
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−
24002302 .stats-table {
24012303 display: table;
24022304 width: 100%;
@@ -2573,6 +2475,7 @@ tbody.commit-list { · 170 unmodified lines
25732475 #repo-topics .repo-topic {
25742476 font-weight: var(--font-weight-normal);
25752477 cursor: pointer;
2478+ margin: 0;
25762479 }
25772480
25782481 #new-dependency-drop-list.ui.selection.dropdown {
@@ -2990,6 +2893,7 @@ tbody.commit-list { · 411 unmodified lines
29902893 display: flex;
29912894 align-items: center;
29922895 justify-content: flex-end;
2896+ gap: 8px;
29932897 }
29942898
29952899 @media (max-width: 767.98px) {
web_src/css/repo/issue-list.css+1−0
@@ -9,6 +9,7 @@ · 8 unmodified lines
99
1010 .issue-list-toolbar-left {
1111 display: flex;
12+ align-items: center;
1213 }
1314
1415 .issue-list-toolbar-right .filter.menu {
web_src/fomantic/build/semantic.css+2869−5299
@@ -2327,7 +2327,7 @@ · 2326 unmodified lines
23272327 Site Overrides
23282328 *******************************/
23292329 /*!
2330− * # Fomantic-UI - Checkbox
2330+ * # Fomantic-UI - Dimmer
23312331 * http://github.com/fomantic/Fomantic-UI/
23322332 *
23332333 *
@@ -2337,4857 +2337,2318 @@ · 3 unmodified lines
23372337 */
23382338
23392339 /*******************************
2340− Checkbox
2340+ Dimmer
23412341 *******************************/
23422342
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;
2343+.dimmable:not(body) {
23772344 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;
23952345 }
23962346
2397−/*--------------
2398− Checkmark
2399−---------------*/
2400−
2401−.ui.checkbox label:after {
2347+.ui.dimmer {
2348+ display: none;
24022349 position: absolute;
2403− font-size: 14px;
2404− top: 0;
2405− left: 0;
2406− width: 17px;
2407− height: 17px;
2350+ top: 0 !important;
2351+ left: 0 !important;
2352+ width: 100%;
2353+ height: 100%;
24082354 text-align: center;
2355+ vertical-align: middle;
2356+ padding: 1em;
2357+ background: rgba(0, 0, 0, 0.85);
24092358 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;
2359+ line-height: 1;
2360+ animation-fill-mode: both;
2361+ animation-duration: 0.5s;
2362+ transition: background-color 0.5s linear;
2363+ flex-direction: column;
2364+ align-items: center;
2365+ justify-content: center;
2366+ -webkit-user-select: none;
2367+ -moz-user-select: none;
2368+ user-select: none;
2369+ will-change: opacity;
2370+ z-index: 1000;
24122371 }
24132372
2414−/*--------------
2415− Label
2416−---------------*/
2417−
2418−/* Inside */
2373+/* Dimmer Content */
24192374
2420−.ui.checkbox label,
2421−.ui.checkbox + label {
2422− color: rgba(0, 0, 0, 0.87);
2423− transition: color 0.1s ease;
2375+.ui.dimmer > .content {
2376+ -webkit-user-select: text;
2377+ -moz-user-select: text;
2378+ user-select: text;
2379+ color: #FFFFFF;
24242380 }
24252381
2426−/* Outside */
2382+/* Loose Coupling */
24272383
2428−.ui.checkbox + label {
2429− vertical-align: middle;
2384+.ui.segment > .ui.dimmer:not(.page) {
2385+ border-radius: inherit;
24302386 }
24312387
2388+/* Scrollbars */
2389+
24322390 /*******************************
2433− States
2391+ States
24342392 *******************************/
24352393
2436−/*--------------
2437− Hover
2438−---------------*/
2439−
2440−.ui.checkbox label:hover::before {
2441− background: #FFFFFF;
2442− border-color: rgba(34, 36, 38, 0.35);
2443−}
2394+/* Animating */
24442395
2445−.ui.checkbox label:hover,
2446−.ui.checkbox + label:hover {
2447− color: rgba(0, 0, 0, 0.8);
2396+.animating.dimmable:not(body),
2397+.dimmed.dimmable:not(body) {
2398+ overflow: hidden;
24482399 }
24492400
2450−/*--------------
2451− Down
2452−---------------*/
2401+/* Animating / Active / Visible */
24532402
2454−.ui.checkbox label:active::before {
2455− background: #F9FAFB;
2456− border-color: rgba(34, 36, 38, 0.35);
2403+.dimmed.dimmable > .ui.animating.dimmer,
2404+.dimmed.dimmable > .ui.visible.dimmer,
2405+.ui.active.dimmer {
2406+ display: flex;
2407+ opacity: 1;
24572408 }
24582409
2459−.ui.checkbox label:active::after {
2460− color: rgba(0, 0, 0, 0.95);
2461−}
2410+/* Disabled */
24622411
2463−.ui.checkbox input:active ~ label {
2464− color: rgba(0, 0, 0, 0.95);
2412+.ui.disabled.dimmer {
2413+ width: 0 !important;
2414+ height: 0 !important;
24652415 }
24662416
2467−/*--------------
2468− Focus
2469−---------------*/
2417+/*******************************
2418+ Variations
2419+*******************************/
24702420
2471−.ui.checkbox input:focus ~ label:before {
2472− background: #FFFFFF;
2473− border-color: #96C8DA;
2474−}
2421+/*--------------
2422+ Legacy
2423+ ---------------*/
24752424
2476−.ui.checkbox input:focus ~ label:after {
2477− color: rgba(0, 0, 0, 0.95);
2478−}
2425+/* Animating / Active / Visible */
24792426
2480−.ui.checkbox input:focus ~ label {
2481− color: rgba(0, 0, 0, 0.95);
2427+.dimmed.dimmable > .ui.animating.legacy.dimmer,
2428+.dimmed.dimmable > .ui.visible.legacy.dimmer,
2429+.ui.active.legacy.dimmer {
2430+ display: block;
24822431 }
24832432
24842433 /*--------------
2485− Active
2486−---------------*/
2434+ Alignment
2435+ ---------------*/
24872436
2488−.ui.checkbox input:checked ~ label:before {
2489− background: #FFFFFF;
2490− border-color: rgba(34, 36, 38, 0.35);
2437+.ui[class*="top aligned"].dimmer {
2438+ justify-content: flex-start;
24912439 }
24922440
2493−.ui.checkbox input:checked ~ label:after {
2494− opacity: 1;
2495− color: rgba(0, 0, 0, 0.95);
2441+.ui[class*="bottom aligned"].dimmer {
2442+ justify-content: flex-end;
24962443 }
24972444
24982445 /*--------------
2499− Indeterminate
2446+ Page
25002447 ---------------*/
25012448
2502−.ui.checkbox input:not([type=radio]):indeterminate ~ label:before {
2503− background: #FFFFFF;
2504− border-color: rgba(34, 36, 38, 0.35);
2449+.ui.page.dimmer {
2450+ position: fixed;
2451+ transform-style: '';
2452+ perspective: 2000px;
2453+ transform-origin: center center;
25052454 }
25062455
2507−.ui.checkbox input:not([type=radio]):indeterminate ~ label:after {
2508− opacity: 1;
2509− color: rgba(0, 0, 0, 0.95);
2456+.ui.page.dimmer.modals {
2457+ -moz-perspective: none;
25102458 }
25112459
2512−.ui.indeterminate.toggle.checkbox input:not([type=radio]):indeterminate ~ label:before {
2513− background: rgba(0, 0, 0, 0.15);
2460+body.animating.in.dimmable,
2461+body.dimmed.dimmable {
2462+ overflow: hidden;
25142463 }
25152464
2516−.ui.indeterminate.toggle.checkbox input:not([type=radio]) ~ label:after {
2517− left: 1.075rem;
2465+body.dimmable > .dimmer {
2466+ position: fixed;
25182467 }
25192468
25202469 /*--------------
2521− Active Focus
2522−---------------*/
2470+ Blurring
2471+ ---------------*/
25232472
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;
2473+.blurring.dimmable > :not(.dimmer) {
2474+ filter: initial;
2475+ transition: 800ms filter ease;
25282476 }
25292477
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);
2478+.blurring.dimmed.dimmable > :not(.dimmer):not(.popup) {
2479+ filter: blur(5px) grayscale(0.7);
25332480 }
25342481
2535−/*--------------
2536− Read-Only
2537−---------------*/
2482+/* Dimmer Color */
25382483
2539−.ui.read-only.checkbox,
2540−.ui.read-only.checkbox label {
2541− cursor: default;
2484+.blurring.dimmable > .dimmer {
2485+ background: rgba(0, 0, 0, 0.6);
25422486 }
25432487
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;
2488+.blurring.dimmable > .inverted.dimmer {
2489+ background: rgba(255, 255, 255, 0.6);
25542490 }
25552491
25562492 /*--------------
2557− Hidden
2558−---------------*/
2559−
2560−/* Initialized checkbox moves input below element
2561− to prevent manually triggering */
2493+ Aligned
2494+ ---------------*/
25622495
2563−.ui.checkbox input.hidden {
2564− z-index: -1;
2496+.ui.dimmer > .top.aligned.content > * {
2497+ vertical-align: top;
25652498 }
25662499
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;
2500+.ui.dimmer > .bottom.aligned.content > * {
2501+ vertical-align: bottom;
25742502 }
25752503
2576−/*******************************
2577− Types
2578−*******************************/
2579−
25802504 /*--------------
2581− Radio
2505+ Shades
25822506 ---------------*/
25832507
2584−.ui.radio.checkbox {
2585− min-height: 15px;
2508+.medium.medium.medium.medium.medium.dimmer {
2509+ background: rgba(0, 0, 0, 0.65);
25862510 }
25872511
2588−.ui.radio.checkbox label {
2589− padding-left: 1.85714em;
2512+.light.light.light.light.light.dimmer {
2513+ background: rgba(0, 0, 0, 0.45);
25902514 }
25912515
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;
2516+.very.light.light.light.light.dimmer {
2517+ background: rgba(0, 0, 0, 0.25);
26022518 }
26032519
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−}
2520+/*--------------
2521+ Simple
2522+ ---------------*/
26182523
2619−/* Focus */
2524+/* Displays without javascript */
26202525
2621−.ui.radio.checkbox input:focus ~ label:before {
2622− background-color: #FFFFFF;
2526+.ui.simple.dimmer {
2527+ display: block;
2528+ overflow: hidden;
2529+ opacity: 0;
2530+ width: 0;
2531+ height: 0;
2532+ z-index: -100;
2533+ background: rgba(0, 0, 0, 0);
26232534 }
26242535
2625−.ui.radio.checkbox input:focus ~ label:after {
2626− background-color: rgba(0, 0, 0, 0.95);
2536+.dimmed.dimmable > .ui.simple.dimmer {
2537+ overflow: visible;
2538+ opacity: 1;
2539+ width: 100%;
2540+ height: 100%;
2541+ background: rgba(0, 0, 0, 0.85);
2542+ z-index: 1;
26272543 }
26282544
2629−/* Indeterminate */
2545+.ui.simple.inverted.dimmer {
2546+ background: rgba(255, 255, 255, 0);
2547+}
26302548
2631−.ui.radio.checkbox input:indeterminate ~ label:after {
2632− opacity: 0;
2549+.dimmed.dimmable > .ui.simple.inverted.dimmer {
2550+ background: rgba(255, 255, 255, 0.85);
26332551 }
26342552
2635−/* Active */
2553+/*--------------
2554+ Partially
2555+ ----------------*/
26362556
2637−.ui.radio.checkbox input:checked ~ label:before {
2638− background-color: #FFFFFF;
2557+.ui[class*="top dimmer"],
2558+.ui[class*="center dimmer"],
2559+.ui[class*="bottom dimmer"] {
2560+ height: auto;
26392561 }
26402562
2641−.ui.radio.checkbox input:checked ~ label:after {
2642− background-color: rgba(0, 0, 0, 0.95);
2563+.ui[class*="bottom dimmer"] {
2564+ top: auto !important;
2565+ bottom: 0;
26432566 }
26442567
2645−/* Active Focus */
2646−
2647−.ui.radio.checkbox input:focus:checked ~ label:before {
2648− background-color: #FFFFFF;
2568+.ui[class*="center dimmer"] {
2569+ top: 50% !important;
2570+ transform: translateY(-50%);
2571+ -webkit-transform: translateY(calc(-50% - 0.5px));
26492572 }
26502573
2651−.ui.radio.checkbox input:focus:checked ~ label:after {
2652− background-color: rgba(0, 0, 0, 0.95);
2574+.ui.segment > .ui.ui[class*="top dimmer"] {
2575+ border-bottom-left-radius: 0;
2576+ border-bottom-right-radius: 0;
26532577 }
26542578
2655−/*--------------
2656− Slider
2657− ---------------*/
2658−
2659−.ui.slider.checkbox {
2660− min-height: 1.25rem;
2579+.ui.segment > .ui.ui[class*="center dimmer"] {
2580+ border-radius: 0;
26612581 }
26622582
2663−/* Input */
2583+.ui.segment > .ui.ui[class*="bottom dimmer"] {
2584+ border-top-left-radius: 0;
2585+ border-top-right-radius: 0;
2586+}
26642587
2665−.ui.slider.checkbox input {
2666− width: 3.5rem;
2667− height: 1.25rem;
2588+.ui[class*="center dimmer"].transition[class*="fade up"].in {
2589+ animation-name: fadeInUpCenter;
26682590 }
26692591
2670−/* Label */
2592+.ui[class*="center dimmer"].transition[class*="fade down"].in {
2593+ animation-name: fadeInDownCenter;
2594+}
26712595
2672−.ui.slider.checkbox label {
2673− padding-left: 4.5rem;
2674− line-height: 1rem;
2675− color: rgba(0, 0, 0, 0.4);
2596+.ui[class*="center dimmer"].transition[class*="fade up"].out {
2597+ animation-name: fadeOutUpCenter;
26762598 }
26772599
2678−/* Line */
2600+.ui[class*="center dimmer"].transition[class*="fade down"].out {
2601+ animation-name: fadeOutDownCenter;
2602+}
26792603
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;
2604+.ui[class*="center dimmer"].bounce.transition {
2605+ animation-name: bounceCenter;
26942606 }
26952607
2696−/* Handle */
2608+@keyframes fadeInUpCenter {
2609+ 0% {
2610+ opacity: 0;
2611+ transform: translateY(-40%);
2612+ -webkit-transform: translateY(calc(-40% - 0.5px));
2613+ }
26972614
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;
2615+ 100% {
2616+ opacity: 1;
2617+ transform: translateY(-50%);
2618+ -webkit-transform: translateY(calc(-50% - 0.5px));
2619+ }
27132620 }
27142621
2715−/* Focus */
2622+@keyframes fadeInDownCenter {
2623+ 0% {
2624+ opacity: 0;
2625+ transform: translateY(-60%);
2626+ -webkit-transform: translateY(calc(-60% - 0.5px));
2627+ }
27162628
2717−.ui.slider.checkbox input:focus ~ label:before {
2718− background-color: rgba(0, 0, 0, 0.15);
2719− border: none;
2629+ 100% {
2630+ opacity: 1;
2631+ transform: translateY(-50%);
2632+ -webkit-transform: translateY(calc(-50% - 0.5px));
2633+ }
27202634 }
27212635
2722−/* Hover */
2636+@keyframes fadeOutUpCenter {
2637+ 0% {
2638+ opacity: 1;
2639+ transform: translateY(-50%);
2640+ -webkit-transform: translateY(calc(-50% - 0.5px));
2641+ }
27232642
2724−.ui.slider.checkbox label:hover {
2725− color: rgba(0, 0, 0, 0.8);
2643+ 100% {
2644+ opacity: 0;
2645+ transform: translateY(-45%);
2646+ -webkit-transform: translateY(calc(-45% - 0.5px));
2647+ }
27262648 }
27272649
2728−.ui.slider.checkbox label:hover::before {
2729− background: rgba(0, 0, 0, 0.15);
2650+@keyframes fadeOutDownCenter {
2651+ 0% {
2652+ opacity: 1;
2653+ transform: translateY(-50%);
2654+ -webkit-transform: translateY(calc(-50% - 0.5px));
2655+ }
2656+
2657+ 100% {
2658+ opacity: 0;
2659+ transform: translateY(-55%);
2660+ -webkit-transform: translateY(calc(-55% - 0.5px));
2661+ }
27302662 }
27312663
2732−/* Active */
2664+@keyframes bounceCenter {
2665+ 0%, 20%, 50%, 80%, 100% {
2666+ transform: translateY(-50%);
2667+ -webkit-transform: translateY(calc(-50% - 0.5px));
2668+ }
27332669
2734−.ui.slider.checkbox input:checked ~ label {
2735− color: rgba(0, 0, 0, 0.95) !important;
2736−}
2670+ 40% {
2671+ transform: translateY(calc(-50% - 30px));
2672+ }
27372673
2738−.ui.slider.checkbox input:checked ~ label:before {
2739− background-color: #545454 !important;
2674+ 60% {
2675+ transform: translateY(calc(-50% - 15px));
2676+ }
27402677 }
27412678
2742−.ui.slider.checkbox input:checked ~ label:after {
2743− left: 2rem;
2679+/*******************************
2680+ Theme Overrides
2681+*******************************/
2682+
2683+/*******************************
2684+ User Overrides
2685+*******************************/
2686+/*!
2687+ * # Fomantic-UI - Dropdown
2688+ * http://github.com/fomantic/Fomantic-UI/
2689+ *
2690+ *
2691+ * Released under the MIT license
2692+ * http://opensource.org/licenses/MIT
2693+ *
2694+ */
2695+
2696+/*******************************
2697+ Dropdown
2698+*******************************/
2699+
2700+.ui.dropdown {
2701+ cursor: pointer;
2702+ position: relative;
2703+ display: inline-block;
2704+ outline: none;
2705+ text-align: left;
2706+ transition: box-shadow 0.1s ease, width 0.1s ease;
2707+ -webkit-user-select: none;
2708+ -moz-user-select: none;
2709+ user-select: none;
2710+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
27442711 }
27452712
2746−/* Active Focus */
2713+/*******************************
2714+ Content
2715+*******************************/
27472716
2748−.ui.slider.checkbox input:focus:checked ~ label {
2749− color: rgba(0, 0, 0, 0.95) !important;
2717+/*--------------
2718+ Menu
2719+---------------*/
2720+
2721+.ui.dropdown .menu {
2722+ cursor: auto;
2723+ position: absolute;
2724+ display: none;
2725+ outline: none;
2726+ top: 100%;
2727+ min-width: -moz-max-content;
2728+ min-width: max-content;
2729+ margin: 0;
2730+ padding: 0 0;
2731+ background: #FFFFFF;
2732+ font-size: 1em;
2733+ text-shadow: none;
2734+ text-align: left;
2735+ box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
2736+ border: 1px solid rgba(34, 36, 38, 0.15);
2737+ border-radius: 0.28571429rem;
2738+ transition: opacity 0.1s ease;
2739+ z-index: 11;
2740+ will-change: transform, opacity;
27502741 }
27512742
2752−.ui.slider.checkbox input:focus:checked ~ label:before {
2753− background-color: #000000 !important;
2743+.ui.dropdown .menu > * {
2744+ white-space: nowrap;
27542745 }
27552746
27562747 /*--------------
2757− Toggle
2758− ---------------*/
2748+ Hidden Input
2749+---------------*/
27592750
2760−.ui.toggle.checkbox {
2761− min-height: 1.5rem;
2751+.ui.dropdown > input:not(.search):first-child,
2752+.ui.dropdown > select {
2753+ display: none !important;
27622754 }
27632755
2764−/* Input */
2756+/*--------------
2757+ Dropdown Icon
2758+---------------*/
27652759
2766−.ui.toggle.checkbox input {
2767− width: 3.5rem;
2768− height: 1.5rem;
2760+.ui.dropdown:not(.labeled) > .dropdown.icon {
2761+ position: relative;
2762+ width: auto;
2763+ font-size: 0.85714286em;
2764+ margin: 0 0 0 1em;
27692765 }
27702766
2771−/* Label */
2767+.ui.dropdown .menu > .item .dropdown.icon {
2768+ width: auto;
2769+ float: right;
2770+ margin: 0em 0 0 1em;
2771+}
27722772
2773−.ui.toggle.checkbox label {
2774− min-height: 1.5rem;
2775− padding-left: 4.5rem;
2776− color: rgba(0, 0, 0, 0.87);
2773+.ui.dropdown .menu > .item .dropdown.icon + .text {
2774+ margin-right: 1em;
27772775 }
27782776
2779−.ui.toggle.checkbox label {
2780− padding-top: 0.15em;
2777+/*--------------
2778+ Text
2779+---------------*/
2780+
2781+.ui.dropdown > .text {
2782+ display: inline-block;
2783+ transition: none;
27812784 }
27822785
2783−/* Switch */
2786+/*--------------
2787+ Menu Item
2788+---------------*/
27842789
2785−.ui.toggle.checkbox label:before {
2790+.ui.dropdown .menu > .item {
2791+ position: relative;
2792+ cursor: pointer;
27862793 display: block;
2787− position: absolute;
2788− content: '';
2789− z-index: 1;
2790− transform: none;
27912794 border: none;
2792− top: 0;
2793− background: rgba(0, 0, 0, 0.05);
2795+ height: auto;
2796+ min-height: 2.57142857rem;
2797+ text-align: left;
2798+ border-top: none;
2799+ line-height: 1em;
2800+ font-size: 1rem;
2801+ color: rgba(0, 0, 0, 0.87);
2802+ padding: 0.78571429rem 1.14285714rem !important;
2803+ text-transform: none;
2804+ font-weight: normal;
27942805 box-shadow: none;
2795− width: 3.5rem;
2796− height: 1.5rem;
2797− border-radius: 500rem;
2806+ -webkit-touch-callout: none;
27982807 }
27992808
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;
2809+.ui.dropdown .menu > .item:first-child {
2810+ border-top-width: 0;
28162811 }
28172812
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;
2813+.ui.dropdown .menu > .item.vertical {
2814+ display: flex;
2815+ flex-direction: column-reverse;
28212816 }
28222817
2823−/* Focus */
2818+/*--------------
2819+ Floated Content
2820+---------------*/
28242821
2825−.ui.toggle.checkbox input:focus ~ label:before {
2826− background-color: rgba(0, 0, 0, 0.15);
2827− border: none;
2822+.ui.dropdown > .text > [class*="right floated"],
2823+.ui.dropdown .menu .item > [class*="right floated"] {
2824+ float: right !important;
2825+ margin-right: 0 !important;
2826+ margin-left: 1em !important;
28282827 }
28292828
2830−/* Hover */
2831−
2832−.ui.toggle.checkbox label:hover::before {
2833− background-color: rgba(0, 0, 0, 0.15);
2834− border: none;
2829+.ui.dropdown > .text > [class*="left floated"],
2830+.ui.dropdown .menu .item > [class*="left floated"] {
2831+ float: left !important;
2832+ margin-left: 0 !important;
2833+ margin-right: 1em !important;
28352834 }
28362835
2837−/* Active */
2838−
2839−.ui.toggle.checkbox input:checked ~ label {
2840− color: rgba(0, 0, 0, 0.95) !important;
2836+.ui.dropdown .menu .item > i.icon.floated,
2837+.ui.dropdown .menu .item > .flag.floated,
2838+.ui.dropdown .menu .item > .image.floated,
2839+.ui.dropdown .menu .item > img.floated {
2840+ margin-top: 0em;
28412841 }
28422842
2843−.ui.toggle.checkbox input:checked ~ label:before {
2844− background-color: #2185D0 !important;
2845−}
2843+/*--------------
2844+ Menu Divider
2845+---------------*/
28462846
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;
2847+.ui.dropdown .menu > .header {
2848+ margin: 1rem 0 0.75rem;
2849+ padding: 0 1.14285714rem;
2850+ font-weight: 500;
2851+ text-transform: uppercase;
28502852 }
28512853
2852−/* Active Focus */
2853−
2854−.ui.toggle.checkbox input:focus:checked ~ label {
2855− color: rgba(0, 0, 0, 0.95) !important;
2854+.ui.dropdown .menu > .header:not(.ui) {
2855+ color: rgba(0, 0, 0, 0.85);
2856+ font-size: 0.78571429em;
28562857 }
28572858
2858−.ui.toggle.checkbox input:focus:checked ~ label:before {
2859− background-color: #0d71bb !important;
2859+.ui.dropdown .menu > .divider {
2860+ border-top: 1px solid rgba(34, 36, 38, 0.1);
2861+ height: 0;
2862+ margin: 0.5em 0;
28602863 }
28612864
2862−/*******************************
2863− Variations
2864−*******************************/
2865−
2866−/*--------------
2867− Fitted
2868− ---------------*/
2869−
2870−.ui.fitted.checkbox label {
2871− padding-left: 0 !important;
2865+.ui.dropdown .menu > .horizontal.divider {
2866+ border-top: none;
28722867 }
28732868
2874−.ui.fitted.toggle.checkbox {
2875− width: 3.5rem;
2869+.ui.dropdown.dropdown .menu > .input {
2870+ width: auto;
2871+ display: flex;
2872+ margin: 1.14285714rem 0.78571429rem;
2873+ min-width: 10rem;
28762874 }
28772875
2878−.ui.fitted.slider.checkbox {
2879− width: 3.5rem;
2876+.ui.dropdown .menu > .header + .input {
2877+ margin-top: 0;
28802878 }
28812879
2882−/*--------------------
2883− Size
2884−---------------------*/
2885−
2886−.ui.mini.checkbox {
2887− font-size: 0.78571429em;
2880+.ui.dropdown .menu > .input:not(.transparent) input {
2881+ padding: 0.5em 1em;
28882882 }
28892883
2890−.ui.tiny.checkbox {
2891− font-size: 0.85714286em;
2884+.ui.dropdown .menu > .input:not(.transparent) .button,
2885+.ui.dropdown .menu > .input:not(.transparent) i.icon,
2886+.ui.dropdown .menu > .input:not(.transparent) .label {
2887+ padding-top: 0.5em;
2888+ padding-bottom: 0.5em;
28922889 }
28932890
2894−.ui.small.checkbox {
2895− font-size: 0.92857143em;
2896−}
2891+/*-----------------
2892+ Item Description
2893+-------------------*/
28972894
2898−.ui.large.checkbox {
2899− font-size: 1.14285714em;
2895+.ui.dropdown > .text > .description,
2896+.ui.dropdown .menu > .item > .description {
2897+ float: right;
2898+ margin: 0 0 0 1em;
2899+ color: rgba(0, 0, 0, 0.4);
29002900 }
29012901
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;
2902+.ui.dropdown .menu > .item.vertical > .description {
2903+ margin: 0;
29082904 }
29092905
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−}
2906+/*-----------------
2907+ Item Text
2908+-------------------*/
29152909
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;
2910+.ui.dropdown .menu > .item.vertical > .text {
2911+ margin-bottom: 0.25em;
29212912 }
29222913
2923−.ui.big.checkbox {
2924− font-size: 1.28571429em;
2925−}
2914+/*-----------------
2915+ Message
2916+-------------------*/
29262917
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;
2918+.ui.dropdown .menu > .message {
2919+ padding: 0.78571429rem 1.14285714rem;
2920+ font-weight: normal;
29332921 }
29342922
2935−.ui.big.form .checkbox.radio label:before,
2936−.ui.big.checkbox.radio label:before {
2937− transform: scale(1.28571429);
2938− transform-origin: left;
2923+.ui.dropdown .menu > .message:not(.ui) {
2924+ color: rgba(0, 0, 0, 0.4);
29392925 }
29402926
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−}
2927+/*--------------
2928+ Sub Menu
2929+---------------*/
29472930
2948−.ui.huge.checkbox {
2949− font-size: 1.42857143em;
2931+.ui.dropdown .menu .menu {
2932+ top: 0;
2933+ left: 100%;
2934+ right: auto;
2935+ margin: 0 -0.5em !important;
2936+ border-radius: 0.28571429rem !important;
2937+ z-index: 21 !important;
29502938 }
29512939
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−}
2940+/* Hide Arrow */
29592941
2960−.ui.huge.form .checkbox.radio label:before,
2961−.ui.huge.checkbox.radio label:before {
2962− transform: scale(1.42857143);
2963− transform-origin: left;
2942+.ui.dropdown .menu .menu:after {
2943+ display: none;
29642944 }
29652945
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−}
2946+/*--------------
2947+ Sub Elements
2948+---------------*/
29722949
2973−.ui.massive.checkbox {
2974− font-size: 1.71428571em;
2950+/* Icons / Flags / Labels / Image */
2951+
2952+.ui.dropdown > .text > i.icon,
2953+.ui.dropdown > .text > .label,
2954+.ui.dropdown > .text > .flag,
2955+.ui.dropdown > .text > img,
2956+.ui.dropdown > .text > .image {
2957+ margin-top: 0em;
29752958 }
29762959
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;
2960+.ui.dropdown .menu > .item > i.icon,
2961+.ui.dropdown .menu > .item > .label,
2962+.ui.dropdown .menu > .item > .flag,
2963+.ui.dropdown .menu > .item > .image,
2964+.ui.dropdown .menu > .item > img {
2965+ margin-top: 0em;
29832966 }
29842967
2985−.ui.massive.form .checkbox.radio label:before,
2986−.ui.massive.checkbox.radio label:before {
2987− transform: scale(1.71428571);
2988− transform-origin: left;
2968+.ui.dropdown > .text > i.icon,
2969+.ui.dropdown > .text > .label,
2970+.ui.dropdown > .text > .flag,
2971+.ui.dropdown > .text > img,
2972+.ui.dropdown > .text > .image,
2973+.ui.dropdown .menu > .item > i.icon,
2974+.ui.dropdown .menu > .item > .label,
2975+.ui.dropdown .menu > .item > .flag,
2976+.ui.dropdown .menu > .item > .image,
2977+.ui.dropdown .menu > .item > img {
2978+ margin-left: 0;
2979+ float: none;
2980+ margin-right: 0.78571429rem;
29892981 }
29902982
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;
2983+/*--------------
2984+ Image
2985+---------------*/
2986+
2987+.ui.dropdown > .text > img,
2988+.ui.dropdown > .text > .image:not(.icon),
2989+.ui.dropdown .menu > .item > .image:not(.icon),
2990+.ui.dropdown .menu > .item > img {
2991+ display: inline-block;
2992+ vertical-align: top;
2993+ width: auto;
2994+ margin-top: -0.5em;
2995+ margin-bottom: -0.5em;
2996+ max-height: 2em;
29962997 }
29972998
29982999 /*******************************
2999− Theme Overrides
3000+ Coupling
30003001 *******************************/
30013002
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−}
3003+/*--------------
3004+ Menu
3005+---------------*/
30063006
3007−/* Checkmark */
3007+/* Remove Menu Item Divider */
30083008
3009−.ui.checkbox label:after,
3010−.ui.checkbox .box:after {
3011− font-family: 'Checkbox';
3009+.ui.dropdown .ui.menu > .item:before,
3010+.ui.menu .ui.dropdown .menu > .item:before {
3011+ display: none;
30123012 }
30133013
3014−/* Checked */
3014+/* Prevent Menu Item Border */
30153015
3016−.ui.checkbox input:checked ~ .box:after,
3017−.ui.checkbox input:checked ~ label:after {
3018− content: '\e800';
3016+.ui.menu .ui.dropdown .menu .active.item {
3017+ border-left: none;
30193018 }
30203019
3021−/* Indeterminate */
3020+/* Automatically float dropdown menu right on last menu item */
30223021
3023−.ui.checkbox input:indeterminate ~ .box:after,
3024−.ui.checkbox input:indeterminate ~ label:after {
3025− font-size: 12px;
3026− content: '\e801';
3022+.ui.menu .right.menu .dropdown:last-child > .menu:not(.left),
3023+.ui.menu .right.dropdown.item > .menu:not(.left),
3024+.ui.buttons > .ui.dropdown:last-child > .menu:not(.left) {
3025+ left: auto;
3026+ right: 0;
30273027 }
30283028
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− * # Fomantic-UI - Dimmer
3040− * http://github.com/fomantic/Fomantic-UI/
3041− *
3042− *
3043− * Released under the MIT license
3044− * http://opensource.org/licenses/MIT
3045− *
3046− */
3029+/*--------------
3030+ Label
3031+ ---------------*/
30473032
3048−/*******************************
3049− Dimmer
3050−*******************************/
3033+/* Dropdown Menu */
30513034
3052−.dimmable:not(body) {
3053− position: relative;
3035+.ui.label.dropdown .menu {
3036+ min-width: 100%;
30543037 }
30553038
3056−.ui.dimmer {
3057− display: none;
3058− position: absolute;
3059− top: 0 !important;
3060− left: 0 !important;
3061− width: 100%;
3062− height: 100%;
3063− text-align: center;
3064− vertical-align: middle;
3065− padding: 1em;
3066− background: rgba(0, 0, 0, 0.85);
3067− opacity: 0;
3068− line-height: 1;
3069− animation-fill-mode: both;
3070− animation-duration: 0.5s;
3071− transition: background-color 0.5s linear;
3072− flex-direction: column;
3073− align-items: center;
3074− justify-content: center;
3075− -webkit-user-select: none;
3076− -moz-user-select: none;
3077− user-select: none;
3078− will-change: opacity;
3079− z-index: 1000;
3080−}
3039+/*--------------
3040+ Button
3041+ ---------------*/
30813042
3082−/* Dimmer Content */
3043+/* No Margin On Icon Button */
30833044
3084−.ui.dimmer > .content {
3085− -webkit-user-select: text;
3086− -moz-user-select: text;
3087− user-select: text;
3088− color: #FFFFFF;
3045+.ui.dropdown.icon.button > .dropdown.icon {
3046+ margin: 0;
30893047 }
30903048
3091−/* Loose Coupling */
3092−
3093−.ui.segment > .ui.dimmer:not(.page) {
3094− border-radius: inherit;
3049+.ui.button.dropdown .menu {
3050+ min-width: 100%;
30953051 }
30963052
3097−/* Scrollbars */
3098−
30993053 /*******************************
3100− States
3054+ Types
31013055 *******************************/
31023056
3103−/* Animating */
3104−
3105−.animating.dimmable:not(body),
3106−.dimmed.dimmable:not(body) {
3107− overflow: hidden;
3057+select.ui.dropdown {
3058+ height: 38px;
3059+ padding: 0.5em;
3060+ border: 1px solid rgba(34, 36, 38, 0.15);
3061+ visibility: visible;
31083062 }
31093063
3110−/* Animating / Active / Visible */
3064+/*--------------
3065+ Selection
3066+ ---------------*/
31113067
3112−.dimmed.dimmable > .ui.animating.dimmer,
3113−.dimmed.dimmable > .ui.visible.dimmer,
3114−.ui.active.dimmer {
3115− display: flex;
3116− opacity: 1;
3117−}
3118−
3119−/* Disabled */
3068+/* Displays like a select box */
31203069
3121−.ui.disabled.dimmer {
3122− width: 0 !important;
3123− height: 0 !important;
3070+.ui.selection.dropdown {
3071+ cursor: pointer;
3072+ word-wrap: break-word;
3073+ line-height: 1em;
3074+ white-space: normal;
3075+ outline: 0;
3076+ transform: rotateZ(0deg);
3077+ min-width: 14em;
3078+ min-height: 2.71428571em;
3079+ background: #FFFFFF;
3080+ display: inline-block;
3081+ padding: 0.78571429em 3.2em 0.78571429em 1em;
3082+ color: rgba(0, 0, 0, 0.87);
3083+ box-shadow: none;
3084+ border: 1px solid rgba(34, 36, 38, 0.15);
3085+ border-radius: 0.28571429rem;
3086+ transition: box-shadow 0.1s ease, width 0.1s ease;
31243087 }
31253088
3126−/*******************************
3127− Variations
3128−*******************************/
3089+.ui.selection.dropdown.visible,
3090+.ui.selection.dropdown.active {
3091+ z-index: 10;
3092+}
31293093
3130−/*--------------
3131− Legacy
3132− ---------------*/
3094+.ui.selection.dropdown > .search.icon,
3095+.ui.selection.dropdown > .delete.icon,
3096+.ui.selection.dropdown > .dropdown.icon {
3097+ cursor: pointer;
3098+ position: absolute;
3099+ width: auto;
3100+ height: auto;
3101+ line-height: 1.21428571em;
3102+ top: 0.78571429em;
3103+ right: 1em;
3104+ z-index: 3;
3105+ margin: -0.78571429em;
3106+ padding: 0.91666667em;
3107+ opacity: 0.8;
3108+ transition: opacity 0.1s ease;
3109+}
31333110
3134−/* Animating / Active / Visible */
3111+/* Compact */
31353112
3136−.dimmed.dimmable > .ui.animating.legacy.dimmer,
3137−.dimmed.dimmable > .ui.visible.legacy.dimmer,
3138−.ui.active.legacy.dimmer {
3139− display: block;
3113+.ui.compact.selection.dropdown {
3114+ min-width: 0;
31403115 }
31413116
3142−/*--------------
3143− Alignment
3144− ---------------*/
3117+/* Selection Menu */
31453118
3146−.ui[class*="top aligned"].dimmer {
3147− justify-content: flex-start;
3119+.ui.selection.dropdown .menu {
3120+ overflow-x: hidden;
3121+ overflow-y: auto;
3122+ backface-visibility: hidden;
3123+ -webkit-overflow-scrolling: touch;
3124+ border-top-width: 0 !important;
3125+ width: auto;
3126+ outline: none;
3127+ margin: 0 -1px;
3128+ min-width: calc(100% + 2px);
3129+ width: calc(100% + 2px);
3130+ border-radius: 0 0 0.28571429rem 0.28571429rem;
3131+ box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3132+ transition: opacity 0.1s ease;
31483133 }
31493134
3150−.ui[class*="bottom aligned"].dimmer {
3151− justify-content: flex-end;
3135+.ui.selection.dropdown .menu:after,
3136+.ui.selection.dropdown .menu:before {
3137+ display: none;
31523138 }
31533139
31543140 /*--------------
3155− Page
3141+ Message
31563142 ---------------*/
31573143
3158−.ui.page.dimmer {
3159− position: fixed;
3160− transform-style: '';
3161− perspective: 2000px;
3162− transform-origin: center center;
3144+.ui.selection.dropdown .menu > .message {
3145+ padding: 0.78571429rem 1.14285714rem;
31633146 }
31643147
3165−.ui.page.dimmer.modals {
3166− -moz-perspective: none;
3167−}
3148+@media only screen and (max-width: 767.98px) {
3149+ .ui.selection.dropdown.short .menu {
3150+ max-height: 6.01071429rem;
3151+ }
31683152
3169−body.animating.in.dimmable,
3170−body.dimmed.dimmable {
3171− overflow: hidden;
3172−}
3153+ .ui.selection.dropdown[class*="very short"] .menu {
3154+ max-height: 4.00714286rem;
3155+ }
31733156
3174−body.dimmable > .dimmer {
3175− position: fixed;
3176−}
3157+ .ui.selection.dropdown .menu {
3158+ max-height: 8.01428571rem;
3159+ }
31773160
3178−/*--------------
3179− Blurring
3180− ---------------*/
3161+ .ui.selection.dropdown.long .menu {
3162+ max-height: 16.02857143rem;
3163+ }
31813164
3182−.blurring.dimmable > :not(.dimmer) {
3183− filter: initial;
3184− transition: 800ms filter ease;
3165+ .ui.selection.dropdown[class*="very long"] .menu {
3166+ max-height: 24.04285714rem;
3167+ }
31853168 }
31863169
3187−.blurring.dimmed.dimmable > :not(.dimmer):not(.popup) {
3188− filter: blur(5px) grayscale(0.7);
3189−}
3170+@media only screen and (min-width: 768px) {
3171+ .ui.selection.dropdown.short .menu {
3172+ max-height: 8.01428571rem;
3173+ }
31903174
3191−/* Dimmer Color */
3175+ .ui.selection.dropdown[class*="very short"] .menu {
3176+ max-height: 5.34285714rem;
3177+ }
31923178
3193−.blurring.dimmable > .dimmer {
3194− background: rgba(0, 0, 0, 0.6);
3195−}
3179+ .ui.selection.dropdown .menu {
3180+ max-height: 10.68571429rem;
3181+ }
31963182
3197−.blurring.dimmable > .inverted.dimmer {
3198− background: rgba(255, 255, 255, 0.6);
3183+ .ui.selection.dropdown.long .menu {
3184+ max-height: 21.37142857rem;
3185+ }
3186+
3187+ .ui.selection.dropdown[class*="very long"] .menu {
3188+ max-height: 32.05714286rem;
3189+ }
31993190 }
32003191
3201−/*--------------
3202− Aligned
3203− ---------------*/
3192+@media only screen and (min-width: 992px) {
3193+ .ui.selection.dropdown.short .menu {
3194+ max-height: 12.02142857rem;
3195+ }
32043196
3205−.ui.dimmer > .top.aligned.content > * {
3206− vertical-align: top;
3207−}
3197+ .ui.selection.dropdown[class*="very short"] .menu {
3198+ max-height: 8.01428571rem;
3199+ }
32083200
3209−.ui.dimmer > .bottom.aligned.content > * {
3210− vertical-align: bottom;
3211−}
3201+ .ui.selection.dropdown .menu {
3202+ max-height: 16.02857143rem;
3203+ }
32123204
3213−/*--------------
3214− Shades
3215− ---------------*/
3205+ .ui.selection.dropdown.long .menu {
3206+ max-height: 32.05714286rem;
3207+ }
32163208
3217−.medium.medium.medium.medium.medium.dimmer {
3218− background: rgba(0, 0, 0, 0.65);
3209+ .ui.selection.dropdown[class*="very long"] .menu {
3210+ max-height: 48.08571429rem;
3211+ }
32193212 }
32203213
3221−.light.light.light.light.light.dimmer {
3222− background: rgba(0, 0, 0, 0.45);
3223−}
3214+@media only screen and (min-width: 1920px) {
3215+ .ui.selection.dropdown.short .menu {
3216+ max-height: 16.02857143rem;
3217+ }
32243218
3225−.very.light.light.light.light.dimmer {
3226− background: rgba(0, 0, 0, 0.25);
3227−}
3219+ .ui.selection.dropdown[class*="very short"] .menu {
3220+ max-height: 10.68571429rem;
3221+ }
32283222
3229−/*--------------
3230− Simple
3231− ---------------*/
3223+ .ui.selection.dropdown .menu {
3224+ max-height: 21.37142857rem;
3225+ }
32323226
3233−/* Displays without javascript */
3227+ .ui.selection.dropdown.long .menu {
3228+ max-height: 42.74285714rem;
3229+ }
32343230
3235−.ui.simple.dimmer {
3236− display: block;
3237− overflow: hidden;
3238− opacity: 0;
3239− width: 0;
3240− height: 0;
3241− z-index: -100;
3242− background: rgba(0, 0, 0, 0);
3231+ .ui.selection.dropdown[class*="very long"] .menu {
3232+ max-height: 64.11428571rem;
3233+ }
32433234 }
32443235
3245−.dimmed.dimmable > .ui.simple.dimmer {
3246− overflow: visible;
3247− opacity: 1;
3248− width: 100%;
3249− height: 100%;
3250− background: rgba(0, 0, 0, 0.85);
3251− z-index: 1;
3252−}
3236+/* Menu Item */
32533237
3254−.ui.simple.inverted.dimmer {
3255− background: rgba(255, 255, 255, 0);
3238+.ui.selection.dropdown .menu > .item {
3239+ border-top: 1px solid #FAFAFA;
3240+ padding: 0.78571429rem 1.14285714rem !important;
3241+ white-space: normal;
3242+ word-wrap: normal;
32563243 }
32573244
3258−.dimmed.dimmable > .ui.simple.inverted.dimmer {
3259− background: rgba(255, 255, 255, 0.85);
3245+/* User Item */
3246+
3247+.ui.selection.dropdown .menu > .hidden.addition.item {
3248+ display: none;
32603249 }
32613250
3262−/*--------------
3263− Partially
3264− ----------------*/
3251+/* Hover */
32653252
3266−.ui[class*="top dimmer"],
3267−.ui[class*="center dimmer"],
3268−.ui[class*="bottom dimmer"] {
3269− height: auto;
3253+.ui.selection.dropdown:hover {
3254+ border-color: rgba(34, 36, 38, 0.35);
3255+ box-shadow: none;
32703256 }
32713257
3272−.ui[class*="bottom dimmer"] {
3273− top: auto !important;
3274− bottom: 0;
3258+/* Active */
3259+
3260+.ui.selection.active.dropdown {
3261+ border-color: #96C8DA;
3262+ box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
32753263 }
32763264
3277−.ui[class*="center dimmer"] {
3278− top: 50% !important;
3279− transform: translateY(-50%);
3280− -webkit-transform: translateY(calc(-50% - 0.5px));
3281−}
3282−
3283−.ui.segment > .ui.ui[class*="top dimmer"] {
3284− border-bottom-left-radius: 0;
3285− border-bottom-right-radius: 0;
3286−}
3287−
3288−.ui.segment > .ui.ui[class*="center dimmer"] {
3289− border-radius: 0;
3290−}
3291−
3292−.ui.segment > .ui.ui[class*="bottom dimmer"] {
3293− border-top-left-radius: 0;
3294− border-top-right-radius: 0;
3295−}
3296−
3297−.ui[class*="center dimmer"].transition[class*="fade up"].in {
3298− animation-name: fadeInUpCenter;
3299−}
3300−
3301−.ui[class*="center dimmer"].transition[class*="fade down"].in {
3302− animation-name: fadeInDownCenter;
3303−}
3304−
3305−.ui[class*="center dimmer"].transition[class*="fade up"].out {
3306− animation-name: fadeOutUpCenter;
3307−}
3308−
3309−.ui[class*="center dimmer"].transition[class*="fade down"].out {
3310− animation-name: fadeOutDownCenter;
3311−}
3312−
3313−.ui[class*="center dimmer"].bounce.transition {
3314− animation-name: bounceCenter;
3315−}
3316−
3317−@keyframes fadeInUpCenter {
3318− 0% {
3319− opacity: 0;
3320− transform: translateY(-40%);
3321− -webkit-transform: translateY(calc(-40% - 0.5px));
3322− }
3323−
3324− 100% {
3325− opacity: 1;
3326− transform: translateY(-50%);
3327− -webkit-transform: translateY(calc(-50% - 0.5px));
3328− }
3329−}
3330−
3331−@keyframes fadeInDownCenter {
3332− 0% {
3333− opacity: 0;
3334− transform: translateY(-60%);
3335− -webkit-transform: translateY(calc(-60% - 0.5px));
3336− }
3337−
3338− 100% {
3339− opacity: 1;
3340− transform: translateY(-50%);
3341− -webkit-transform: translateY(calc(-50% - 0.5px));
3342− }
3343−}
3344−
3345−@keyframes fadeOutUpCenter {
3346− 0% {
3347− opacity: 1;
3348− transform: translateY(-50%);
3349− -webkit-transform: translateY(calc(-50% - 0.5px));
3350− }
3351−
3352− 100% {
3353− opacity: 0;
3354− transform: translateY(-45%);
3355− -webkit-transform: translateY(calc(-45% - 0.5px));
3356− }
3357−}
3358−
3359−@keyframes fadeOutDownCenter {
3360− 0% {
3361− opacity: 1;
3362− transform: translateY(-50%);
3363− -webkit-transform: translateY(calc(-50% - 0.5px));
3364− }
3365−
3366− 100% {
3367− opacity: 0;
3368− transform: translateY(-55%);
3369− -webkit-transform: translateY(calc(-55% - 0.5px));
3370− }
3371−}
3372−
3373−@keyframes bounceCenter {
3374− 0%, 20%, 50%, 80%, 100% {
3375− transform: translateY(-50%);
3376− -webkit-transform: translateY(calc(-50% - 0.5px));
3377− }
3378−
3379− 40% {
3380− transform: translateY(calc(-50% - 30px));
3381− }
3382−
3383− 60% {
3384− transform: translateY(calc(-50% - 15px));
3385− }
3386−}
3387−
3388−/*******************************
3389− Theme Overrides
3390−*******************************/
3391−
3392−/*******************************
3393− User Overrides
3394−*******************************/
3395−/*!
3396− * # Fomantic-UI - Dropdown
3397− * http://github.com/fomantic/Fomantic-UI/
3398− *
3399− *
3400− * Released under the MIT license
3401− * http://opensource.org/licenses/MIT
3402− *
3403− */
3404−
3405−/*******************************
3406− Dropdown
3407−*******************************/
3408−
3409−.ui.dropdown {
3410− cursor: pointer;
3411− position: relative;
3412− display: inline-block;
3413− outline: none;
3414− text-align: left;
3415− transition: box-shadow 0.1s ease, width 0.1s ease;
3416− -webkit-user-select: none;
3417− -moz-user-select: none;
3418− user-select: none;
3419− -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
3420−}
3421−
3422−/*******************************
3423− Content
3424−*******************************/
3425−
3426−/*--------------
3427− Menu
3428−---------------*/
3429−
3430−.ui.dropdown .menu {
3431− cursor: auto;
3432− position: absolute;
3433− display: none;
3434− outline: none;
3435− top: 100%;
3436− min-width: -moz-max-content;
3437− min-width: max-content;
3438− margin: 0;
3439− padding: 0 0;
3440− background: #FFFFFF;
3441− font-size: 1em;
3442− text-shadow: none;
3443− text-align: left;
3265+.ui.selection.active.dropdown .menu {
3266+ border-color: #96C8DA;
34443267 box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3445− border: 1px solid rgba(34, 36, 38, 0.15);
3446− border-radius: 0.28571429rem;
3447− transition: opacity 0.1s ease;
3448− z-index: 11;
3449− will-change: transform, opacity;
3450−}
3451−
3452−.ui.dropdown .menu > * {
3453− white-space: nowrap;
3454−}
3455−
3456−/*--------------
3457− Hidden Input
3458−---------------*/
3459−
3460−.ui.dropdown > input:not(.search):first-child,
3461−.ui.dropdown > select {
3462− display: none !important;
3463−}
3464−
3465−/*--------------
3466− Dropdown Icon
3467−---------------*/
3468−
3469−.ui.dropdown:not(.labeled) > .dropdown.icon {
3470− position: relative;
3471− width: auto;
3472− font-size: 0.85714286em;
3473− margin: 0 0 0 1em;
34743268 }
34753269
3476−.ui.dropdown .menu > .item .dropdown.icon {
3477− width: auto;
3478− float: right;
3479− margin: 0em 0 0 1em;
3480−}
3270+/* Focus */
34813271
3482−.ui.dropdown .menu > .item .dropdown.icon + .text {
3483− margin-right: 1em;
3272+.ui.selection.dropdown:focus {
3273+ border-color: #96C8DA;
3274+ box-shadow: none;
34843275 }
34853276
3486−/*--------------
3487− Text
3488−---------------*/
3489−
3490−.ui.dropdown > .text {
3491− display: inline-block;
3492− transition: none;
3277+.ui.selection.dropdown:focus .menu {
3278+ border-color: #96C8DA;
3279+ box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
34933280 }
34943281
3495−/*--------------
3496− Menu Item
3497−---------------*/
3282+/* Visible */
34983283
3499−.ui.dropdown .menu > .item {
3500− position: relative;
3501− cursor: pointer;
3502− display: block;
3503− border: none;
3504− height: auto;
3505− min-height: 2.57142857rem;
3506− text-align: left;
3507− border-top: none;
3508− line-height: 1em;
3509− font-size: 1rem;
3510− color: rgba(0, 0, 0, 0.87);
3511− padding: 0.78571429rem 1.14285714rem !important;
3512− text-transform: none;
3284+.ui.selection.visible.dropdown > .text:not(.default) {
35133285 font-weight: normal;
3514− box-shadow: none;
3515− -webkit-touch-callout: none;
3286+ color: rgba(0, 0, 0, 0.8);
35163287 }
35173288
3518−.ui.dropdown .menu > .item:first-child {
3519− border-top-width: 0;
3520−}
3289+/* Visible Hover */
35213290
3522−.ui.dropdown .menu > .item.vertical {
3523− display: flex;
3524− flex-direction: column-reverse;
3291+.ui.selection.active.dropdown:hover {
3292+ border-color: #96C8DA;
3293+ box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
35253294 }
35263295
3527−/*--------------
3528− Floated Content
3529−---------------*/
3530−
3531−.ui.dropdown > .text > [class*="right floated"],
3532−.ui.dropdown .menu .item > [class*="right floated"] {
3533− float: right !important;
3534− margin-right: 0 !important;
3535− margin-left: 1em !important;
3536−}
3537−
3538−.ui.dropdown > .text > [class*="left floated"],
3539−.ui.dropdown .menu .item > [class*="left floated"] {
3540− float: left !important;
3541− margin-left: 0 !important;
3542− margin-right: 1em !important;
3543−}
3544−
3545−.ui.dropdown .menu .item > i.icon.floated,
3546−.ui.dropdown .menu .item > .flag.floated,
3547−.ui.dropdown .menu .item > .image.floated,
3548−.ui.dropdown .menu .item > img.floated {
3549− margin-top: 0em;
3550−}
3551−
3552−/*--------------
3553− Menu Divider
3554−---------------*/
3555−
3556−.ui.dropdown .menu > .header {
3557− margin: 1rem 0 0.75rem;
3558− padding: 0 1.14285714rem;
3559− font-weight: 500;
3560− text-transform: uppercase;
3561−}
3562−
3563−.ui.dropdown .menu > .header:not(.ui) {
3564− color: rgba(0, 0, 0, 0.85);
3565− font-size: 0.78571429em;
3566−}
3567−
3568−.ui.dropdown .menu > .divider {
3569− border-top: 1px solid rgba(34, 36, 38, 0.1);
3570− height: 0;
3571− margin: 0.5em 0;
3572−}
3573−
3574−.ui.dropdown .menu > .horizontal.divider {
3575− border-top: none;
3576−}
3577−
3578−.ui.dropdown.dropdown .menu > .input {
3579− width: auto;
3580− display: flex;
3581− margin: 1.14285714rem 0.78571429rem;
3582− min-width: 10rem;
3583−}
3584−
3585−.ui.dropdown .menu > .header + .input {
3586− margin-top: 0;
3587−}
3588−
3589−.ui.dropdown .menu > .input:not(.transparent) input {
3590− padding: 0.5em 1em;
3591−}
3592−
3593−.ui.dropdown .menu > .input:not(.transparent) .button,
3594−.ui.dropdown .menu > .input:not(.transparent) i.icon,
3595−.ui.dropdown .menu > .input:not(.transparent) .label {
3596− padding-top: 0.5em;
3597− padding-bottom: 0.5em;
3598−}
3599−
3600−/*-----------------
3601− Item Description
3602−-------------------*/
3603−
3604−.ui.dropdown > .text > .description,
3605−.ui.dropdown .menu > .item > .description {
3606− float: right;
3607− margin: 0 0 0 1em;
3608− color: rgba(0, 0, 0, 0.4);
3609−}
3610−
3611−.ui.dropdown .menu > .item.vertical > .description {
3612− margin: 0;
3613−}
3614−
3615−/*-----------------
3616− Item Text
3617−-------------------*/
3618−
3619−.ui.dropdown .menu > .item.vertical > .text {
3620− margin-bottom: 0.25em;
3621−}
3622−
3623−/*-----------------
3624− Message
3625−-------------------*/
3626−
3627−.ui.dropdown .menu > .message {
3628− padding: 0.78571429rem 1.14285714rem;
3629− font-weight: normal;
3630−}
3631−
3632−.ui.dropdown .menu > .message:not(.ui) {
3633− color: rgba(0, 0, 0, 0.4);
3634−}
3635−
3636−/*--------------
3637− Sub Menu
3638−---------------*/
3639−
3640−.ui.dropdown .menu .menu {
3641− top: 0;
3642− left: 100%;
3643− right: auto;
3644− margin: 0 -0.5em !important;
3645− border-radius: 0.28571429rem !important;
3646− z-index: 21 !important;
3647−}
3648−
3649−/* Hide Arrow */
3650−
3651−.ui.dropdown .menu .menu:after {
3652− display: none;
3653−}
3654−
3655−/*--------------
3656− Sub Elements
3657−---------------*/
3658−
3659−/* Icons / Flags / Labels / Image */
3660−
3661−.ui.dropdown > .text > i.icon,
3662−.ui.dropdown > .text > .label,
3663−.ui.dropdown > .text > .flag,
3664−.ui.dropdown > .text > img,
3665−.ui.dropdown > .text > .image {
3666− margin-top: 0em;
3667−}
3668−
3669−.ui.dropdown .menu > .item > i.icon,
3670−.ui.dropdown .menu > .item > .label,
3671−.ui.dropdown .menu > .item > .flag,
3672−.ui.dropdown .menu > .item > .image,
3673−.ui.dropdown .menu > .item > img {
3674− margin-top: 0em;
3675−}
3676−
3677−.ui.dropdown > .text > i.icon,
3678−.ui.dropdown > .text > .label,
3679−.ui.dropdown > .text > .flag,
3680−.ui.dropdown > .text > img,
3681−.ui.dropdown > .text > .image,
3682−.ui.dropdown .menu > .item > i.icon,
3683−.ui.dropdown .menu > .item > .label,
3684−.ui.dropdown .menu > .item > .flag,
3685−.ui.dropdown .menu > .item > .image,
3686−.ui.dropdown .menu > .item > img {
3687− margin-left: 0;
3688− float: none;
3689− margin-right: 0.78571429rem;
3690−}
3691−
3692−/*--------------
3693− Image
3694−---------------*/
3695−
3696−.ui.dropdown > .text > img,
3697−.ui.dropdown > .text > .image:not(.icon),
3698−.ui.dropdown .menu > .item > .image:not(.icon),
3699−.ui.dropdown .menu > .item > img {
3700− display: inline-block;
3701− vertical-align: top;
3702− width: auto;
3703− margin-top: -0.5em;
3704− margin-bottom: -0.5em;
3705− max-height: 2em;
3706−}
3707−
3708−/*******************************
3709− Coupling
3710−*******************************/
3711−
3712−/*--------------
3713− Menu
3714−---------------*/
3715−
3716−/* Remove Menu Item Divider */
3717−
3718−.ui.dropdown .ui.menu > .item:before,
3719−.ui.menu .ui.dropdown .menu > .item:before {
3720− display: none;
3721−}
3722−
3723−/* Prevent Menu Item Border */
3724−
3725−.ui.menu .ui.dropdown .menu .active.item {
3726− border-left: none;
3727−}
3728−
3729−/* Automatically float dropdown menu right on last menu item */
3730−
3731−.ui.menu .right.menu .dropdown:last-child > .menu:not(.left),
3732−.ui.menu .right.dropdown.item > .menu:not(.left),
3733−.ui.buttons > .ui.dropdown:last-child > .menu:not(.left) {
3734− left: auto;
3735− right: 0;
3736−}
3737−
3738−/*--------------
3739− Label
3740− ---------------*/
3741−
3742−/* Dropdown Menu */
3743−
3744−.ui.label.dropdown .menu {
3745− min-width: 100%;
3746−}
3747−
3748−/*--------------
3749− Button
3750− ---------------*/
3751−
3752−/* No Margin On Icon Button */
3753−
3754−.ui.dropdown.icon.button > .dropdown.icon {
3755− margin: 0;
3756−}
3757−
3758−.ui.button.dropdown .menu {
3759− min-width: 100%;
3760−}
3761−
3762−/*******************************
3763− Types
3764−*******************************/
3765−
3766−select.ui.dropdown {
3767− height: 38px;
3768− padding: 0.5em;
3769− border: 1px solid rgba(34, 36, 38, 0.15);
3770− visibility: visible;
3771−}
3772−
3773−/*--------------
3774− Selection
3775− ---------------*/
3776−
3777−/* Displays like a select box */
3778−
3779−.ui.selection.dropdown {
3780− cursor: pointer;
3781− word-wrap: break-word;
3782− line-height: 1em;
3783− white-space: normal;
3784− outline: 0;
3785− transform: rotateZ(0deg);
3786− min-width: 14em;
3787− min-height: 2.71428571em;
3788− background: #FFFFFF;
3789− display: inline-block;
3790− padding: 0.78571429em 3.2em 0.78571429em 1em;
3791− color: rgba(0, 0, 0, 0.87);
3792− box-shadow: none;
3793− border: 1px solid rgba(34, 36, 38, 0.15);
3794− border-radius: 0.28571429rem;
3795− transition: box-shadow 0.1s ease, width 0.1s ease;
3796−}
3797−
3798−.ui.selection.dropdown.visible,
3799−.ui.selection.dropdown.active {
3800− z-index: 10;
3801−}
3802−
3803−.ui.selection.dropdown > .search.icon,
3804−.ui.selection.dropdown > .delete.icon,
3805−.ui.selection.dropdown > .dropdown.icon {
3806− cursor: pointer;
3807− position: absolute;
3808− width: auto;
3809− height: auto;
3810− line-height: 1.21428571em;
3811− top: 0.78571429em;
3812− right: 1em;
3813− z-index: 3;
3814− margin: -0.78571429em;
3815− padding: 0.91666667em;
3816− opacity: 0.8;
3817− transition: opacity 0.1s ease;
3818−}
3819−
3820−/* Compact */
3821−
3822−.ui.compact.selection.dropdown {
3823− min-width: 0;
3824−}
3825−
3826−/* Selection Menu */
3827−
3828−.ui.selection.dropdown .menu {
3829− overflow-x: hidden;
3830− overflow-y: auto;
3831− backface-visibility: hidden;
3832− -webkit-overflow-scrolling: touch;
3833− border-top-width: 0 !important;
3834− width: auto;
3835− outline: none;
3836− margin: 0 -1px;
3837− min-width: calc(100% + 2px);
3838− width: calc(100% + 2px);
3839− border-radius: 0 0 0.28571429rem 0.28571429rem;
3840− box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3841− transition: opacity 0.1s ease;
3842−}
3843−
3844−.ui.selection.dropdown .menu:after,
3845−.ui.selection.dropdown .menu:before {
3846− display: none;
3847−}
3848−
3849−/*--------------
3850− Message
3851− ---------------*/
3852−
3853−.ui.selection.dropdown .menu > .message {
3854− padding: 0.78571429rem 1.14285714rem;
3855−}
3856−
3857−@media only screen and (max-width: 767.98px) {
3858− .ui.selection.dropdown.short .menu {
3859− max-height: 6.01071429rem;
3860− }
3861−
3862− .ui.selection.dropdown[class*="very short"] .menu {
3863− max-height: 4.00714286rem;
3864− }
3865−
3866− .ui.selection.dropdown .menu {
3867− max-height: 8.01428571rem;
3868− }
3869−
3870− .ui.selection.dropdown.long .menu {
3871− max-height: 16.02857143rem;
3872− }
3873−
3874− .ui.selection.dropdown[class*="very long"] .menu {
3875− max-height: 24.04285714rem;
3876− }
3877−}
3878−
3879−@media only screen and (min-width: 768px) {
3880− .ui.selection.dropdown.short .menu {
3881− max-height: 8.01428571rem;
3882− }
3883−
3884− .ui.selection.dropdown[class*="very short"] .menu {
3885− max-height: 5.34285714rem;
3886− }
3887−
3888− .ui.selection.dropdown .menu {
3889− max-height: 10.68571429rem;
3890− }
3891−
3892− .ui.selection.dropdown.long .menu {
3893− max-height: 21.37142857rem;
3894− }
3895−
3896− .ui.selection.dropdown[class*="very long"] .menu {
3897− max-height: 32.05714286rem;
3898− }
3899−}
3900−
3901−@media only screen and (min-width: 992px) {
3902− .ui.selection.dropdown.short .menu {
3903− max-height: 12.02142857rem;
3904− }
3905−
3906− .ui.selection.dropdown[class*="very short"] .menu {
3907− max-height: 8.01428571rem;
3908− }
3909−
3910− .ui.selection.dropdown .menu {
3911− max-height: 16.02857143rem;
3912− }
3913−
3914− .ui.selection.dropdown.long .menu {
3915− max-height: 32.05714286rem;
3916− }
3917−
3918− .ui.selection.dropdown[class*="very long"] .menu {
3919− max-height: 48.08571429rem;
3920− }
3921−}
3922−
3923−@media only screen and (min-width: 1920px) {
3924− .ui.selection.dropdown.short .menu {
3925− max-height: 16.02857143rem;
3926− }
3927−
3928− .ui.selection.dropdown[class*="very short"] .menu {
3929− max-height: 10.68571429rem;
3930− }
3931−
3932− .ui.selection.dropdown .menu {
3933− max-height: 21.37142857rem;
3934− }
3935−
3936− .ui.selection.dropdown.long .menu {
3937− max-height: 42.74285714rem;
3938− }
3939−
3940− .ui.selection.dropdown[class*="very long"] .menu {
3941− max-height: 64.11428571rem;
3942− }
3943−}
3944−
3945−/* Menu Item */
3946−
3947−.ui.selection.dropdown .menu > .item {
3948− border-top: 1px solid #FAFAFA;
3949− padding: 0.78571429rem 1.14285714rem !important;
3950− white-space: normal;
3951− word-wrap: normal;
3952−}
3953−
3954−/* User Item */
3955−
3956−.ui.selection.dropdown .menu > .hidden.addition.item {
3957− display: none;
3958−}
3959−
3960−/* Hover */
3961−
3962−.ui.selection.dropdown:hover {
3963− border-color: rgba(34, 36, 38, 0.35);
3964− box-shadow: none;
3965−}
3966−
3967−/* Active */
3968−
3969−.ui.selection.active.dropdown {
3970− border-color: #96C8DA;
3971− box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3972−}
3973−
3974−.ui.selection.active.dropdown .menu {
3975− border-color: #96C8DA;
3976− box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3977−}
3978−
3979−/* Focus */
3980−
3981−.ui.selection.dropdown:focus {
3982− border-color: #96C8DA;
3983− box-shadow: none;
3984−}
3985−
3986−.ui.selection.dropdown:focus .menu {
3987− border-color: #96C8DA;
3988− box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3989−}
3990−
3991−/* Visible */
3992−
3993−.ui.selection.visible.dropdown > .text:not(.default) {
3994− font-weight: normal;
3995− color: rgba(0, 0, 0, 0.8);
3996−}
3997−
3998−/* Visible Hover */
3999−
4000−.ui.selection.active.dropdown:hover {
4001− border-color: #96C8DA;
4002− box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
4003−}
4004−
4005−.ui.selection.active.dropdown:hover .menu {
4006− border-color: #96C8DA;
4007− box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
4008−}
4009−
4010−/* Dropdown Icon */
4011−
4012−.ui.active.selection.dropdown > .dropdown.icon,
4013−.ui.visible.selection.dropdown > .dropdown.icon {
4014− opacity: '';
4015− z-index: 3;
4016−}
4017−
4018−/* Connecting Border */
4019−
4020−.ui.active.selection.dropdown {
4021− border-bottom-left-radius: 0 !important;
4022− border-bottom-right-radius: 0 !important;
4023−}
4024−
4025−/* Empty Connecting Border */
4026−
4027−.ui.active.empty.selection.dropdown {
4028− border-radius: 0.28571429rem !important;
4029− box-shadow: none !important;
4030−}
4031−
4032−.ui.active.empty.selection.dropdown .menu {
4033− border: none !important;
4034− box-shadow: none !important;
4035−}
4036−
4037−/* CSS specific to iOS devices or firefox mobile only */
4038−
4039−@supports (-webkit-touch-callout: none) or (-webkit-overflow-scrolling: touch) or (-moz-appearance:none) {
4040−@media (-moz-touch-enabled), (pointer: coarse) {
4041− .ui.dropdown .scrollhint.menu:not(.hidden):before {
4042− animation: scrollhint 2s ease 2;
4043− content: '';
4044− z-index: 15;
4045− display: block;
4046− position: absolute;
4047− opacity: 0;
4048− right: 0.25em;
4049− top: 0;
4050− height: 100%;
4051− border-right: 0.25em solid;
4052− border-left: 0;
4053− -o-border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%;
4054− border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%;
4055− }
4056−
4057− .ui.inverted.dropdown .scrollhint.menu:not(.hidden):before {
4058− -o-border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%;
4059− border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%;
4060− }
4061−
4062−@keyframes scrollhint {
4063− 0% {
4064− opacity: 1;
4065− top: 100%;
4066− }
4067−
4068− 100% {
4069− opacity: 0;
4070− top: 0;
4071− }
4072−}
4073−}
4074−}
4075−
4076−/*--------------
4077− Searchable
4078− ---------------*/
4079−
4080−/* Search Selection */
4081−
4082−.ui.search.dropdown {
4083− min-width: '';
4084−}
4085−
4086−/* Search Dropdown */
4087−
4088−.ui.search.dropdown > input.search {
4089− background: none transparent !important;
4090− border: none !important;
4091− box-shadow: none !important;
4092− cursor: text;
4093− top: 0;
4094− left: 1px;
4095− width: 100%;
4096− outline: none;
4097− -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
4098− padding: inherit;
4099−}
4100−
4101−/* Text Layering */
4102−
4103−.ui.search.dropdown > input.search {
4104− position: absolute;
4105− z-index: 2;
4106−}
4107−
4108−.ui.search.dropdown > .text {
4109− cursor: text;
4110− position: relative;
4111− left: 1px;
4112− z-index: auto;
4113−}
4114−
4115−/* Search Selection */
4116−
4117−.ui.search.selection.dropdown > input.search {
4118− line-height: 1.21428571em;
4119− padding: 0.67857143em 3.2em 0.67857143em 1em;
4120−}
4121−
4122−/* Used to size multi select input to character width */
4123−
4124−.ui.search.selection.dropdown > span.sizer {
4125− line-height: 1.21428571em;
4126− padding: 0.67857143em 3.2em 0.67857143em 1em;
4127− display: none;
4128− white-space: pre;
4129−}
4130−
4131−/* Active/Visible Search */
4132−
4133−.ui.search.dropdown.active > input.search,
4134−.ui.search.dropdown.visible > input.search {
4135− cursor: auto;
4136−}
4137−
4138−.ui.search.dropdown.active > .text,
4139−.ui.search.dropdown.visible > .text {
4140− pointer-events: none;
4141−}
4142−
4143−/* Filtered Text */
4144−
4145−.ui.active.search.dropdown input.search:focus + .text i.icon,
4146−.ui.active.search.dropdown input.search:focus + .text .flag {
4147− opacity: var(--opacity-disabled);
4148−}
4149−
4150−.ui.active.search.dropdown input.search:focus + .text {
4151− color: rgba(115, 115, 115, 0.87) !important;
4152−}
4153−
4154−.ui.search.dropdown.button > span.sizer {
4155− display: none;
4156−}
4157−
4158−/* Search Menu */
4159−
4160−.ui.search.dropdown .menu {
4161− overflow-x: hidden;
4162− overflow-y: auto;
4163− backface-visibility: hidden;
4164− -webkit-overflow-scrolling: touch;
4165−}
4166−
4167−@media only screen and (max-width: 767.98px) {
4168− .ui.search.dropdown .menu {
4169− max-height: 8.01428571rem;
4170− }
4171−}
4172−
4173−@media only screen and (min-width: 768px) {
4174− .ui.search.dropdown .menu {
4175− max-height: 10.68571429rem;
4176− }
4177−}
4178−
4179−@media only screen and (min-width: 992px) {
4180− .ui.search.dropdown .menu {
4181− max-height: 16.02857143rem;
4182− }
4183−}
4184−
4185−@media only screen and (min-width: 1920px) {
4186− .ui.search.dropdown .menu {
4187− max-height: 21.37142857rem;
4188− }
4189−}
4190−
4191−/* Clearable Selection */
4192−
4193−.ui.dropdown > .remove.icon {
4194− cursor: pointer;
4195− font-size: 0.85714286em;
4196− margin: -0.78571429em;
4197− padding: 0.91666667em;
4198− right: 3em;
4199− top: 0.78571429em;
4200− position: absolute;
4201− opacity: 0.6;
4202− z-index: 3;
4203−}
4204−
4205−.ui.clearable.dropdown .text,
4206−.ui.clearable.dropdown a:last-of-type {
4207− margin-right: 1.5em;
4208−}
4209−
4210−.ui.dropdown select.noselection ~ .remove.icon,
4211−.ui.dropdown input[value=''] ~ .remove.icon,
4212−.ui.dropdown input:not([value]) ~ .remove.icon,
4213−.ui.dropdown.loading > .remove.icon {
4214− display: none;
4215−}
4216−
4217−/*--------------
4218− Multiple
4219− ---------------*/
4220−
4221−/* Multiple Selection */
4222−
4223−.ui.ui.multiple.dropdown {
4224− padding: 0.22619048em 3.2em 0.22619048em 0.35714286em;
4225−}
4226−
4227−.ui.multiple.dropdown .menu {
4228− cursor: auto;
4229−}
4230−
4231−/* Selection Label */
4232−
4233−.ui.multiple.dropdown > .label {
4234− display: inline-block;
4235− white-space: normal;
4236− font-size: 1em;
4237− padding: 0.35714286em 0.78571429em;
4238− margin: 0.14285714rem 0.28571429rem 0.14285714rem 0;
4239− box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.15) inset;
4240−}
4241−
4242−/* Dropdown Icon */
4243−
4244−.ui.multiple.dropdown .dropdown.icon {
4245− margin: '';
4246− padding: '';
4247−}
4248−
4249−/* Text */
4250−
4251−.ui.multiple.dropdown > .text {
4252− position: static;
4253− padding: 0;
4254− max-width: 100%;
4255− margin: 0.45238095em 0 0.45238095em 0.64285714em;
4256− line-height: 1.21428571em;
4257−}
4258−
4259−.ui.multiple.dropdown > .text.default {
4260− white-space: nowrap;
4261− overflow: hidden;
4262− text-overflow: ellipsis;
4263−}
4264−
4265−.ui.multiple.dropdown > .label ~ input.search {
4266− margin-left: 0.14285714em !important;
4267−}
4268−
4269−.ui.multiple.dropdown > .label ~ .text {
4270− display: none;
4271−}
4272−
4273−.ui.multiple.dropdown > .label:not(.image) > img:not(.centered) {
4274− margin-right: 0.78571429rem;
4275−}
4276−
4277−.ui.multiple.dropdown > .label:not(.image) > img.ui:not(.avatar) {
4278− margin-bottom: 0.39285714rem;
4279−}
4280−
4281−.ui.multiple.dropdown > .image.label img {
4282− margin: -0.35714286em 0.78571429em -0.35714286em -0.78571429em;
4283− height: 1.71428571em;
4284−}
4285−
4286−/*-----------------
4287− Multiple Search
4288− -----------------*/
4289−
4290−/* Multiple Search Selection */
4291−
4292−.ui.multiple.search.dropdown,
4293−.ui.multiple.search.dropdown > input.search {
4294− cursor: text;
4295−}
4296−
4297−/* Prompt Text */
4298−
4299−.ui.multiple.search.dropdown > .text {
4300− display: inline-block;
4301− position: absolute;
4302− top: 0;
4303− left: 0;
4304− padding: inherit;
4305− margin: 0.45238095em 0 0.45238095em 0.64285714em;
4306− line-height: 1.21428571em;
4307−}
4308−
4309−.ui.multiple.search.dropdown > .label ~ .text {
4310− display: none;
4311−}
4312−
4313−/* Search */
4314−
4315−.ui.multiple.search.dropdown > input.search {
4316− position: static;
4317− padding: 0;
4318− max-width: 100%;
4319− margin: 0.45238095em 0 0.45238095em 0.64285714em;
4320− width: 2.2em;
4321− line-height: 1.21428571em;
4322−}
4323−
4324−.ui.multiple.search.dropdown.button {
4325− min-width: 14em;
4326−}
4327−
4328−/*--------------
4329− Inline
4330− ---------------*/
4331−
4332−.ui.inline.dropdown {
4333− cursor: pointer;
4334− display: inline-block;
4335− color: inherit;
4336−}
4337−
4338−.ui.inline.dropdown .dropdown.icon {
4339− margin: 0 0.21428571em 0 0.21428571em;
4340− vertical-align: baseline;
4341−}
4342−
4343−.ui.inline.dropdown > .text {
4344− font-weight: 500;
4345−}
4346−
4347−.ui.inline.dropdown .menu {
4348− cursor: auto;
4349− margin-top: 0.21428571em;
4350− border-radius: 0.28571429rem;
4351−}
4352−
4353−/*******************************
4354− States
4355−*******************************/
4356−
4357−/*--------------------
4358− Active
4359−----------------------*/
4360−
4361−/* Menu Item Active */
4362−
4363−.ui.dropdown .menu .active.item {
4364− background: transparent;
4365− font-weight: 500;
4366− color: rgba(0, 0, 0, 0.95);
4367− box-shadow: none;
4368− z-index: 12;
4369−}
4370−
4371−/*--------------------
4372− Hover
4373−----------------------*/
4374−
4375−/* Menu Item Hover */
4376−
4377−.ui.dropdown .menu > .item:hover {
4378− background: rgba(0, 0, 0, 0.05);
4379− color: rgba(0, 0, 0, 0.95);
4380− z-index: 13;
4381−}
4382−
4383−/*--------------------
4384− Default Text
4385−----------------------*/
4386−
4387−.ui.dropdown:not(.button) > .default.text,
4388−.ui.default.dropdown:not(.button) > .text {
4389− color: rgba(191, 191, 191, 0.87);
4390−}
4391−
4392−.ui.dropdown:not(.button) > input:focus ~ .default.text,
4393−.ui.default.dropdown:not(.button) > input:focus ~ .text {
4394− color: rgba(115, 115, 115, 0.87);
4395−}
4396−
4397−/*--------------------
4398− Loading
4399− ---------------------*/
4400−
4401−.ui.loading.dropdown > i.icon {
4402− height: 1em !important;
4403−}
4404−
4405−.ui.loading.selection.dropdown > i.icon {
4406− padding: 1.5em 1.28571429em !important;
4407−}
4408−
4409−.ui.loading.dropdown > i.icon:before {
4410− position: absolute;
4411− content: '';
4412− top: 50%;
4413− left: 50%;
4414− margin: -0.64285714em 0 0 -0.64285714em;
4415− width: 1.28571429em;
4416− height: 1.28571429em;
4417− border-radius: 500rem;
4418− border: 0.2em solid rgba(0, 0, 0, 0.1);
4419−}
4420−
4421−.ui.loading.dropdown > i.icon:after {
4422− position: absolute;
4423− content: '';
4424− top: 50%;
4425− left: 50%;
4426− box-shadow: 0 0 0 1px transparent;
4427− margin: -0.64285714em 0 0 -0.64285714em;
4428− width: 1.28571429em;
4429− height: 1.28571429em;
4430− animation: loader 0.6s infinite linear;
4431− border: 0.2em solid #767676;
4432− border-radius: 500rem;
4433−}
4434−
4435−/* Coupling */
4436−
4437−.ui.loading.dropdown.button > i.icon:before,
4438−.ui.loading.dropdown.button > i.icon:after {
4439− display: none;
4440−}
4441−
4442−.ui.loading.dropdown > .text {
4443− transition: none;
4444−}
4445−
4446−/* Used To Check Position */
4447−
4448−.ui.dropdown .loading.menu {
4449− display: block;
4450− visibility: hidden;
4451− z-index: -1;
4452−}
4453−
4454−.ui.dropdown > .loading.menu {
4455− left: 0 !important;
4456− right: auto !important;
4457−}
4458−
4459−.ui.dropdown > .menu .loading.menu {
4460− left: 100% !important;
4461− right: auto !important;
4462−}
4463−
4464−/*--------------------
4465− Keyboard Select
4466−----------------------*/
4467−
4468−/* Selected Item */
4469−
4470−.ui.dropdown.selected,
4471−.ui.dropdown .menu .selected.item {
4472− background: rgba(0, 0, 0, 0.03);
4473− color: rgba(0, 0, 0, 0.95);
4474−}
4475−
4476−/*--------------------
4477− Search Filtered
4478−----------------------*/
4479−
4480−/* Filtered Item */
4481−
4482−.ui.dropdown > .filtered.text {
4483− visibility: hidden;
4484−}
4485−
4486−.ui.dropdown .filtered.item {
4487− display: none !important;
4488−}
4489−
4490−/*--------------------
4491− States
4492− ----------------------*/
4493−
4494−.ui.dropdown.error,
4495−.ui.dropdown.error > .text,
4496−.ui.dropdown.error > .default.text {
4497− color: #9F3A38;
4498−}
4499−
4500−.ui.selection.dropdown.error {
4501− background: #FFF6F6;
4502− border-color: #E0B4B4;
4503−}
4504−
4505−.ui.selection.dropdown.error:hover {
4506− border-color: #E0B4B4;
4507−}
4508−
4509−.ui.multiple.selection.error.dropdown > .label {
4510− border-color: #E0B4B4;
4511−}
4512−
4513−.ui.dropdown.error > .menu,
4514−.ui.dropdown.error > .menu .menu {
4515− border-color: #E0B4B4;
4516−}
4517−
4518−.ui.dropdown.error > .menu > .item {
4519− color: #9F3A38;
4520−}
4521−
4522−/* Item Hover */
4523−
4524−.ui.dropdown.error > .menu > .item:hover {
4525− background-color: #FBE7E7;
4526−}
4527−
4528−/* Item Active */
4529−
4530−.ui.dropdown.error > .menu .active.item {
4531− background-color: #FDCFCF;
4532−}
4533−
4534−.ui.dropdown.info,
4535−.ui.dropdown.info > .text,
4536−.ui.dropdown.info > .default.text {
4537− color: #276F86;
4538−}
4539−
4540−.ui.selection.dropdown.info {
4541− background: #F8FFFF;
4542− border-color: #A9D5DE;
4543−}
4544−
4545−.ui.selection.dropdown.info:hover {
4546− border-color: #A9D5DE;
4547−}
4548−
4549−.ui.multiple.selection.info.dropdown > .label {
4550− border-color: #A9D5DE;
4551−}
4552−
4553−.ui.dropdown.info > .menu,
4554−.ui.dropdown.info > .menu .menu {
4555− border-color: #A9D5DE;
4556−}
4557−
4558−.ui.dropdown.info > .menu > .item {
4559− color: #276F86;
4560−}
4561−
4562−/* Item Hover */
4563−
4564−.ui.dropdown.info > .menu > .item:hover {
4565− background-color: #e9f2fb;
4566−}
4567−
4568−/* Item Active */
4569−
4570−.ui.dropdown.info > .menu .active.item {
4571− background-color: #cef1fd;
4572−}
4573−
4574−.ui.dropdown.success,
4575−.ui.dropdown.success > .text,
4576−.ui.dropdown.success > .default.text {
4577− color: #2C662D;
4578−}
4579−
4580−.ui.selection.dropdown.success {
4581− background: #FCFFF5;
4582− border-color: #A3C293;
4583−}
4584−
4585−.ui.selection.dropdown.success:hover {
4586− border-color: #A3C293;
4587−}
4588−
4589−.ui.multiple.selection.success.dropdown > .label {
4590− border-color: #A3C293;
4591−}
4592−
4593−.ui.dropdown.success > .menu,
4594−.ui.dropdown.success > .menu .menu {
4595− border-color: #A3C293;
4596−}
4597−
4598−.ui.dropdown.success > .menu > .item {
4599− color: #2C662D;
4600−}
4601−
4602−/* Item Hover */
4603−
4604−.ui.dropdown.success > .menu > .item:hover {
4605− background-color: #e9fbe9;
4606−}
4607−
4608−/* Item Active */
4609−
4610−.ui.dropdown.success > .menu .active.item {
4611− background-color: #dafdce;
4612−}
4613−
4614−.ui.dropdown.warning,
4615−.ui.dropdown.warning > .text,
4616−.ui.dropdown.warning > .default.text {
4617− color: #573A08;
4618−}
4619−
4620−.ui.selection.dropdown.warning {
4621− background: #FFFAF3;
4622− border-color: #C9BA9B;
4623−}
4624−
4625−.ui.selection.dropdown.warning:hover {
4626− border-color: #C9BA9B;
4627−}
4628−
4629−.ui.multiple.selection.warning.dropdown > .label {
4630− border-color: #C9BA9B;
4631−}
4632−
4633−.ui.dropdown.warning > .menu,
4634−.ui.dropdown.warning > .menu .menu {
4635− border-color: #C9BA9B;
4636−}
4637−
4638−.ui.dropdown.warning > .menu > .item {
4639− color: #573A08;
4640−}
4641−
4642−/* Item Hover */
4643−
4644−.ui.dropdown.warning > .menu > .item:hover {
4645− background-color: #fbfbe9;
4646−}
4647−
4648−/* Item Active */
4649−
4650−.ui.dropdown.warning > .menu .active.item {
4651− background-color: #fdfdce;
4652−}
4653−
4654−/*--------------------
4655− Clear
4656−----------------------*/
4657−
4658−.ui.dropdown > .clear.dropdown.icon {
4659− opacity: 0.8;
4660− transition: opacity 0.1s ease;
4661−}
4662−
4663−.ui.dropdown > .clear.dropdown.icon:hover {
4664− opacity: 1;
4665−}
4666−
4667−/*--------------------
4668− Disabled
4669− ----------------------*/
4670−
4671−/* Disabled */
4672−
4673−.ui.disabled.dropdown,
4674−.ui.dropdown .menu > .disabled.item {
4675− cursor: default;
4676− pointer-events: none;
4677− opacity: var(--opacity-disabled);
4678−}
4679−
4680−/*******************************
4681− Variations
4682−*******************************/
4683−
4684−/*--------------
4685− Direction
4686−---------------*/
4687−
4688−/* Flyout Direction */
4689−
4690−.ui.dropdown .menu {
4691− left: 0;
4692−}
4693−
4694−/* Default Side (Right) */
4695−
4696−.ui.dropdown .right.menu > .menu,
4697−.ui.dropdown .menu .right.menu {
4698− left: 100% !important;
4699− right: auto !important;
4700− border-radius: 0.28571429rem !important;
4701−}
4702−
4703−/* Leftward Opening Menu */
4704−
4705−.ui.dropdown > .left.menu {
4706− left: auto !important;
4707− right: 0 !important;
4708−}
4709−
4710−.ui.dropdown > .left.menu .menu,
4711−.ui.dropdown .menu .left.menu {
4712− left: auto;
4713− right: 100%;
4714− margin: 0 -0.5em 0 0 !important;
4715− border-radius: 0.28571429rem !important;
4716−}
4717−
4718−.ui.dropdown .item .left.dropdown.icon,
4719−.ui.dropdown .left.menu .item .dropdown.icon {
4720− width: auto;
4721− float: left;
4722− margin: 0em 0 0 0;
4723−}
4724−
4725−.ui.dropdown .item .left.dropdown.icon,
4726−.ui.dropdown .left.menu .item .dropdown.icon {
4727− width: auto;
4728− float: left;
4729− margin: 0em 0 0 0;
4730−}
4731−
4732−.ui.dropdown .item .left.dropdown.icon + .text,
4733−.ui.dropdown .left.menu .item .dropdown.icon + .text {
4734− margin-left: 1em;
4735− margin-right: 0;
4736−}
4737−
4738−/*--------------
4739− Upward
4740− ---------------*/
4741−
4742−/* Upward Main Menu */
4743−
4744−.ui.upward.dropdown > .menu {
4745− top: auto;
4746− bottom: 100%;
4747− box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.08);
4748− border-radius: 0.28571429rem 0.28571429rem 0 0;
4749−}
4750−
4751−/* Upward Sub Menu */
4752−
4753−.ui.dropdown .upward.menu {
4754− top: auto !important;
4755− bottom: 0 !important;
4756−}
4757−
4758−/* Active Upward */
4759−
4760−.ui.simple.upward.active.dropdown,
4761−.ui.simple.upward.dropdown:hover {
4762− border-radius: 0.28571429rem 0.28571429rem 0 0 !important;
4763−}
4764−
4765−.ui.upward.dropdown.button:not(.pointing):not(.floating).active {
4766− border-radius: 0.28571429rem 0.28571429rem 0 0;
4767−}
4768−
4769−/* Selection */
4770−
4771−.ui.upward.selection.dropdown .menu {
4772− border-top-width: 1px !important;
4773− border-bottom-width: 0 !important;
4774− box-shadow: 0 -2px 3px 0 rgba(0, 0, 0, 0.08);
4775−}
4776−
4777−.ui.upward.selection.dropdown:hover {
4778− box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05);
4779−}
4780−
4781−/* Active Upward */
4782−
4783−.ui.active.upward.selection.dropdown {
4784− border-radius: 0 0 0.28571429rem 0.28571429rem !important;
4785−}
4786−
4787−/* Visible Upward */
4788−
4789−.ui.upward.selection.dropdown.visible {
4790− box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.08);
4791− border-radius: 0 0 0.28571429rem 0.28571429rem !important;
4792−}
4793−
4794−/* Visible Hover Upward */
4795−
4796−.ui.upward.active.selection.dropdown:hover {
4797− box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.05);
4798−}
4799−
4800−.ui.upward.active.selection.dropdown:hover .menu {
4801− box-shadow: 0 -2px 3px 0 rgba(0, 0, 0, 0.08);
4802−}
4803−
4804−/*--------------
4805− Scrolling
4806− ---------------*/
4807−
4808−/* Selection Menu */
4809−
4810−.ui.scrolling.dropdown .menu,
4811−.ui.dropdown .scrolling.menu {
4812− overflow-x: hidden;
4813− overflow-y: auto;
4814−}
4815−
4816−.ui.scrolling.dropdown .menu {
4817− overflow-x: hidden;
4818− overflow-y: auto;
4819− backface-visibility: hidden;
4820− -webkit-overflow-scrolling: touch;
4821− min-width: 100% !important;
4822− width: auto !important;
4823−}
4824−
4825−.ui.dropdown .scrolling.menu {
4826− position: static;
4827− overflow-y: auto;
4828− border: none;
4829− box-shadow: none !important;
4830− border-radius: 0 !important;
4831− margin: 0 !important;
4832− min-width: 100% !important;
4833− width: auto !important;
4834− border-top: 1px solid rgba(34, 36, 38, 0.15);
4835−}
4836−
4837−.ui.scrolling.dropdown .menu .item.item.item,
4838−.ui.dropdown .scrolling.menu > .item.item.item {
4839− border-top: none;
4840−}
4841−
4842−.ui.scrolling.dropdown .menu .item:first-child,
4843−.ui.dropdown .scrolling.menu .item:first-child {
4844− border-top: none;
4845−}
4846−
4847−.ui.dropdown > .animating.menu .scrolling.menu,
4848−.ui.dropdown > .visible.menu .scrolling.menu {
4849− display: block;
4850−}
4851−
4852−/* Scrollbar in IE */
4853−
4854−@media all and (-ms-high-contrast: none) {
4855− .ui.scrolling.dropdown .menu,
4856− .ui.dropdown .scrolling.menu {
4857− min-width: calc(100% - 17px);
4858− }
4859−}
4860−
4861−@media only screen and (max-width: 767.98px) {
4862− .ui.scrolling.dropdown .menu,
4863− .ui.dropdown .scrolling.menu {
4864− max-height: 10.28571429rem;
4865− }
4866−}
4867−
4868−@media only screen and (min-width: 768px) {
4869− .ui.scrolling.dropdown .menu,
4870− .ui.dropdown .scrolling.menu {
4871− max-height: 15.42857143rem;
4872− }
4873−}
4874−
4875−@media only screen and (min-width: 992px) {
4876− .ui.scrolling.dropdown .menu,
4877− .ui.dropdown .scrolling.menu {
4878− max-height: 20.57142857rem;
4879− }
4880−}
4881−
4882−@media only screen and (min-width: 1920px) {
4883− .ui.scrolling.dropdown .menu,
4884− .ui.dropdown .scrolling.menu {
4885− max-height: 20.57142857rem;
4886− }
4887−}
4888−
4889−/*--------------
4890− Columnar
4891−---------------*/
4892−
4893−.ui.column.dropdown > .menu {
4894− flex-wrap: wrap;
4895−}
4896−
4897−.ui.dropdown[class*="two column"] > .menu > .item {
4898− width: 50%;
4899−}
4900−
4901−.ui.dropdown[class*="three column"] > .menu > .item {
4902− width: 33%;
4903−}
4904−
4905−.ui.dropdown[class*="four column"] > .menu > .item {
4906− width: 25%;
4907−}
4908−
4909−.ui.dropdown[class*="five column"] > .menu > .item {
4910− width: 20%;
4911−}
4912−
4913−/*--------------
4914− Simple
4915− ---------------*/
4916−
4917−/* Displays without javascript */
4918−
4919−.ui.simple.dropdown .menu:before,
4920−.ui.simple.dropdown .menu:after {
4921− display: none;
4922−}
4923−
4924−.ui.simple.dropdown .menu {
4925− position: absolute;
4926− /* IE hack to make dropdown icons appear inline */
4927− display: -ms-inline-flexbox !important;
4928− display: block;
4929− overflow: hidden;
4930− top: -9999px;
4931− opacity: 0;
4932− width: 0;
4933− height: 0;
4934− transition: opacity 0.1s ease;
4935− margin-top: 0 !important;
4936−}
4937−
4938−.ui.simple.active.dropdown,
4939−.ui.simple.dropdown:hover {
4940− border-bottom-left-radius: 0 !important;
4941− border-bottom-right-radius: 0 !important;
4942−}
4943−
4944−.ui.simple.active.dropdown > .menu,
4945−.ui.simple.dropdown:hover > .menu {
4946− overflow: visible;
4947− width: auto;
4948− height: auto;
4949− top: 100%;
4950− opacity: 1;
4951−}
4952−
4953−.ui.simple.dropdown > .menu > .item:active > .menu,
4954−.ui.simple.dropdown .menu .item:hover > .menu {
4955− overflow: visible;
4956− width: auto;
4957− height: auto;
4958− top: 0 !important;
4959− left: 100%;
4960− opacity: 1;
4961−}
4962−
4963−.ui.simple.dropdown > .menu > .item:active > .left.menu,
4964−.ui.simple.dropdown .menu .item:hover > .left.menu,
4965−.right.menu .ui.simple.dropdown > .menu > .item:active > .menu:not(.right),
4966−.right.menu .ui.simple.dropdown > .menu .item:hover > .menu:not(.right) {
4967− left: auto;
4968− right: 100%;
4969−}
4970−
4971−.ui.simple.disabled.dropdown:hover .menu {
4972− display: none;
4973− height: 0;
4974− width: 0;
4975− overflow: hidden;
4976−}
4977−
4978−/* Visible */
4979−
4980−.ui.simple.visible.dropdown > .menu {
4981− display: block;
4982−}
4983−
4984−/* Scrolling */
4985−
4986−.ui.simple.scrolling.active.dropdown > .menu,
4987−.ui.simple.scrolling.dropdown:hover > .menu {
4988− overflow-x: hidden;
4989− overflow-y: auto;
4990−}
4991−
4992−/*--------------
4993− Fluid
4994− ---------------*/
4995−
4996−.ui.fluid.dropdown {
4997− display: block;
4998− width: 100% !important;
4999− min-width: 0;
5000−}
5001−
5002−.ui.fluid.dropdown > .dropdown.icon {
5003− float: right;
5004−}
5005−
5006−/*--------------
5007− Floating
5008− ---------------*/
5009−
5010−.ui.floating.dropdown .menu {
5011− left: 0;
5012− right: auto;
5013− box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important;
5014− border-radius: 0.28571429rem !important;
5015−}
5016−
5017−.ui.floating.dropdown > .menu {
5018− border-radius: 0.28571429rem !important;
5019−}
5020−
5021−.ui:not(.upward).floating.dropdown > .menu {
5022− margin-top: 0.5em;
5023−}
5024−
5025−.ui.upward.floating.dropdown > .menu {
5026− margin-bottom: 0.5em;
5027−}
5028−
5029−/*--------------
5030− Pointing
5031− ---------------*/
5032−
5033−.ui.pointing.dropdown > .menu {
5034− top: 100%;
5035− margin-top: 0.78571429rem;
5036− border-radius: 0.28571429rem;
5037−}
5038−
5039−.ui.pointing.dropdown > .menu:not(.hidden):after {
5040− display: block;
5041− position: absolute;
5042− pointer-events: none;
5043− content: '';
5044− visibility: visible;
5045− transform: rotate(45deg);
5046− width: 0.5em;
5047− height: 0.5em;
5048− box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15);
5049− background: #FFFFFF;
5050− z-index: 2;
5051−}
5052−
5053−.ui.pointing.dropdown > .menu:not(.hidden):after {
5054− top: -0.25em;
5055− left: 50%;
5056− margin: 0 0 0 -0.25em;
5057−}
5058−
5059−/* Top Left Pointing */
5060−
5061−.ui.top.left.pointing.dropdown > .menu {
5062− top: 100%;
5063− bottom: auto;
5064− left: 0;
5065− right: auto;
5066− margin: 1em 0 0;
5067−}
5068−
5069−.ui.top.left.pointing.dropdown > .menu {
5070− top: 100%;
5071− bottom: auto;
5072− left: 0;
5073− right: auto;
5074− margin: 1em 0 0;
5075−}
5076−
5077−.ui.top.left.pointing.dropdown > .menu:after {
5078− top: -0.25em;
5079− left: 1em;
5080− right: auto;
5081− margin: 0;
5082− transform: rotate(45deg);
5083−}
5084−
5085−/* Top Right Pointing */
5086−
5087−.ui.top.right.pointing.dropdown > .menu {
5088− top: 100%;
5089− bottom: auto;
5090− right: 0;
5091− left: auto;
5092− margin: 1em 0 0;
5093−}
5094−
5095−.ui.top.pointing.dropdown > .left.menu:after,
5096−.ui.top.right.pointing.dropdown > .menu:after {
5097− top: -0.25em;
5098− left: auto !important;
5099− right: 1em !important;
5100− margin: 0;
5101− transform: rotate(45deg);
5102−}
5103−
5104−/* Left Pointing */
5105−
5106−.ui.left.pointing.dropdown > .menu {
5107− top: 0;
5108− left: 100%;
5109− right: auto;
5110− margin: 0 0 0 1em;
5111−}
5112−
5113−.ui.left.pointing.dropdown > .menu:after {
5114− top: 1em;
5115− left: -0.25em;
5116− margin: 0 0 0 0;
5117− transform: rotate(-45deg);
5118−}
5119−
5120−.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu {
5121− left: auto !important;
5122− right: 100% !important;
5123− margin: 0 1em 0 0;
5124−}
5125−
5126−.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu:after {
5127− top: 1em;
5128− left: auto;
5129− right: -0.25em;
5130− margin: 0 0 0 0;
5131− transform: rotate(135deg);
5132−}
5133−
5134−/* Right Pointing */
5135−
5136−.ui.right.pointing.dropdown > .menu {
5137− top: 0;
5138− left: auto;
5139− right: 100%;
5140− margin: 0 1em 0 0;
5141−}
5142−
5143−.ui.right.pointing.dropdown > .menu:after {
5144− top: 1em;
5145− left: auto;
5146− right: -0.25em;
5147− margin: 0 0 0 0;
5148− transform: rotate(135deg);
5149−}
5150−
5151−/* Bottom Pointing */
5152−
5153−.ui.bottom.pointing.dropdown > .menu {
5154− top: auto;
5155− bottom: 100%;
5156− left: 0;
5157− right: auto;
5158− margin: 0 0 1em;
5159−}
5160−
5161−.ui.bottom.pointing.dropdown > .menu:after {
5162− top: auto;
5163− bottom: -0.25em;
5164− right: auto;
5165− margin: 0;
5166− transform: rotate(-135deg);
5167−}
5168−
5169−/* Reverse Sub-Menu Direction */
5170−
5171−.ui.bottom.pointing.dropdown > .menu .menu {
5172− top: auto !important;
5173− bottom: 0 !important;
5174−}
5175−
5176−/* Bottom Left */
5177−
5178−.ui.bottom.left.pointing.dropdown > .menu {
5179− left: 0;
5180− right: auto;
5181−}
5182−
5183−.ui.bottom.left.pointing.dropdown > .menu:after {
5184− left: 1em;
5185− right: auto;
5186−}
5187−
5188−/* Bottom Right */
5189−
5190−.ui.bottom.right.pointing.dropdown > .menu {
5191− right: 0;
5192− left: auto;
5193−}
5194−
5195−.ui.bottom.right.pointing.dropdown > .menu:after {
5196− left: auto;
5197− right: 1em;
5198−}
5199−
5200−/* Upward pointing */
5201−
5202−.ui.pointing.upward.dropdown .menu,
5203−.ui.top.pointing.upward.dropdown .menu {
5204− top: auto !important;
5205− bottom: 100% !important;
5206− margin: 0 0 0.78571429rem;
5207− border-radius: 0.28571429rem;
5208−}
5209−
5210−.ui.pointing.upward.dropdown .menu:after,
5211−.ui.top.pointing.upward.dropdown .menu:after {
5212− top: 100% !important;
5213− bottom: auto !important;
5214− box-shadow: 1px 1px 0 0 rgba(34, 36, 38, 0.15);
5215− margin: -0.25em 0 0;
5216−}
5217−
5218−/* Right Pointing Upward */
5219−
5220−.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu {
5221− top: auto !important;
5222− bottom: 0 !important;
5223− margin: 0 1em 0 0;
5224−}
5225−
5226−.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after {
5227− top: auto !important;
5228− bottom: 0 !important;
5229− margin: 0 0 1em 0;
5230− box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15);
5231−}
5232−
5233−/* Left Pointing Upward */
5234−
5235−.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu {
5236− top: auto !important;
5237− bottom: 0 !important;
5238− margin: 0 0 0 1em;
5239−}
5240−
5241−.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after {
5242− top: auto !important;
5243− bottom: 0 !important;
5244− margin: 0 0 1em 0;
5245− box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15);
5246−}
5247−
5248−/*--------------------
5249− Sizes
5250−---------------------*/
5251−
5252−.ui.dropdown,
5253−.ui.dropdown .menu > .item {
5254− font-size: 1rem;
5255−}
5256−
5257−.ui.mini.dropdown,
5258−.ui.mini.dropdown .menu > .item {
5259− font-size: 0.78571429rem;
5260−}
5261−
5262−.ui.tiny.dropdown,
5263−.ui.tiny.dropdown .menu > .item {
5264− font-size: 0.85714286rem;
5265−}
5266−
5267−.ui.small.dropdown,
5268−.ui.small.dropdown .menu > .item {
5269− font-size: 0.92857143rem;
5270−}
5271−
5272−.ui.large.dropdown,
5273−.ui.large.dropdown .menu > .item {
5274− font-size: 1.14285714rem;
5275−}
5276−
5277−.ui.big.dropdown,
5278−.ui.big.dropdown .menu > .item {
5279− font-size: 1.28571429rem;
5280−}
5281−
5282−.ui.huge.dropdown,
5283−.ui.huge.dropdown .menu > .item {
5284− font-size: 1.42857143rem;
5285−}
5286−
5287−.ui.massive.dropdown,
5288−.ui.massive.dropdown .menu > .item {
5289− font-size: 1.71428571rem;
5290−}
5291−
5292−/*******************************
5293− Theme Overrides
5294−*******************************/
5295−
5296−/* Dropdown Carets */
5297−
5298−@font-face {
5299− font-family: 'Dropdown';
5300− src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfuIIAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zjo82LgAAAFwAAABVGhlYWQAQ88bAAACxAAAADZoaGVhAwcB6QAAAvwAAAAkaG10eAS4ABIAAAMgAAAAIGxvY2EBNgDeAAADQAAAABJtYXhwAAoAFgAAA1QAAAAgbmFtZVcZpu4AAAN0AAABRXBvc3QAAwAAAAAEvAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDX//3//wAB/+MPLQADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAIABJQElABMAABM0NzY3BTYXFhUUDwEGJwYvASY1AAUGBwEACAUGBoAFCAcGgAUBEgcGBQEBAQcECQYHfwYBAQZ/BwYAAQAAAG4BJQESABMAADc0PwE2MzIfARYVFAcGIyEiJyY1AAWABgcIBYAGBgUI/wAHBgWABwaABQWABgcHBgUFBgcAAAABABIASQC3AW4AEwAANzQ/ATYXNhcWHQEUBwYnBi8BJjUSBoAFCAcFBgYFBwgFgAbbBwZ/BwEBBwQJ/wgEBwEBB38GBgAAAAABAAAASQClAW4AEwAANxE0NzYzMh8BFhUUDwEGIyInJjUABQYHCAWABgaABQgHBgVbAQAIBQYGgAUIBwWABgYFBwAAAAEAAAABAADZuaKOXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAAAAACgAUAB4AQgBkAIgAqgAAAAEAAAAIABQAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAOAAAAAQAAAAAAAgAOAEcAAQAAAAAAAwAOACQAAQAAAAAABAAOAFUAAQAAAAAABQAWAA4AAQAAAAAABgAHADIAAQAAAAAACgA0AGMAAwABBAkAAQAOAAAAAwABBAkAAgAOAEcAAwABBAkAAwAOACQAAwABBAkABAAOAFUAAwABBAkABQAWAA4AAwABBAkABgAOADkAAwABBAkACgA0AGMAaQBjAG8AbQBvAG8AbgBWAGUAcgBzAGkAbwBuACAAMQAuADAAaQBjAG8AbQBvAG8Abmljb21vb24AaQBjAG8AbQBvAG8AbgBSAGUAZwB1AGwAYQByAGkAYwBvAG0AbwBvAG4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype'), url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAAVwAAoAAAAABSgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAdkAAAHZLDXE/09TLzIAAALQAAAAYAAAAGAIIweQY21hcAAAAzAAAABMAAAATA9+4ghnYXNwAAADfAAAAAgAAAAIAAAAEGhlYWQAAAOEAAAANgAAADYAQ88baGhlYQAAA7wAAAAkAAAAJAMHAelobXR4AAAD4AAAACAAAAAgBLgAEm1heHAAAAQAAAAABgAAAAYACFAAbmFtZQAABAgAAAFFAAABRVcZpu5wb3N0AAAFUAAAACAAAAAgAAMAAAEABAQAAQEBCGljb21vb24AAQIAAQA6+BwC+BsD+BgEHgoAGVP/i4seCgAZU/+LiwwHi2v4lPh0BR0AAACIDx0AAACNER0AAAAJHQAAAdASAAkBAQgPERMWGyAlKmljb21vb25pY29tb29udTB1MXUyMHVGMEQ3dUYwRDh1RjBEOXVGMERBAAACAYkABgAIAgABAAQABwAKAA0AVgCfAOgBL/yUDvyUDvyUDvuUDvtvi/emFYuQjZCOjo+Pj42Qiwj3lIsFkIuQiY6Hj4iNhouGi4aJh4eHCPsU+xQFiIiGiYaLhouHjYeOCPsU9xQFiI+Jj4uQCA77b4v3FBWLkI2Pjo8I9xT3FAWPjo+NkIuQi5CJjogI9xT7FAWPh42Hi4aLhomHh4eIiIaJhosI+5SLBYaLh42HjoiPiY+LkAgO+92d928Vi5CNkI+OCPcU9xQFjo+QjZCLkIuPiY6Hj4iNhouGCIv7lAWLhomHh4iIh4eJhouGi4aNiI8I+xT3FAWHjomPi5AIDvvdi+YVi/eUBYuQjZCOjo+Pj42Qi5CLkImOhwj3FPsUBY+IjYaLhouGiYeHiAj7FPsUBYiHhomGi4aLh42Hj4iOiY+LkAgO+JQU+JQViwwKAAAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA8NoB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDw2v/9//8AAAAAACDw1//9//8AAf/jDy0AAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAABAAA5emozXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAUAAACAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIADgBHAAEAAAAAAAMADgAkAAEAAAAAAAQADgBVAAEAAAAAAAUAFgAOAAEAAAAAAAYABwAyAAEAAAAAAAoANABjAAMAAQQJAAEADgAAAAMAAQQJAAIADgBHAAMAAQQJAAMADgAkAAMAAQQJAAQADgBVAAMAAQQJAAUAFgAOAAMAAQQJAAYADgA5AAMAAQQJAAoANABjAGkAYwBvAG0AbwBvAG4AVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG4AUgBlAGcAdQBsAGEAcgBpAGMAbwBtAG8AbwBuAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff');
5301− font-weight: normal;
5302− font-style: normal;
5303−}
5304−
5305−.ui.dropdown > .dropdown.icon {
5306− font-family: 'Dropdown';
5307− line-height: 1;
5308− height: 1em;
5309− width: 1.23em;
5310− backface-visibility: hidden;
5311− font-weight: normal;
5312− font-style: normal;
5313− text-align: center;
5314−}
5315−
5316−.ui.dropdown > .dropdown.icon {
5317− width: auto;
5318−}
5319−
5320−.ui.dropdown > .dropdown.icon:before {
5321− content: '\f0d7';
5322−}
5323−
5324−/* Sub Menu */
5325−
5326−.ui.dropdown .menu .item .dropdown.icon:before {
5327− content: '\f0da' ;
5328−}
5329−
5330−.ui.dropdown .item .left.dropdown.icon:before,
5331−.ui.dropdown .left.menu .item .dropdown.icon:before {
5332− content: "\f0d9" ;
5333−}
5334−
5335−/* Vertical Menu Dropdown */
5336−
5337−.ui.vertical.menu .dropdown.item > .dropdown.icon:before {
5338− content: "\f0da" ;
5339−}
5340−
5341−/* Icons for Reference
5342−.dropdown.down.icon {
5343− content: "\f0d7";
5344−}
5345−.dropdown.up.icon {
5346− content: "\f0d8";
5347−}
5348−.dropdown.left.icon {
5349− content: "\f0d9";
5350−}
5351−.dropdown.icon.icon {
5352− content: "\f0da";
5353−}
5354−*/
5355−
5356−/*******************************
5357− User Overrides
5358−*******************************/
5359−/*!
5360− * # Fomantic-UI - Form
5361− * http://github.com/fomantic/Fomantic-UI/
5362− *
5363− *
5364− * Released under the MIT license
5365− * http://opensource.org/licenses/MIT
5366− *
5367− */
5368−
5369−/*******************************
5370− Elements
5371−*******************************/
5372−
5373−/*--------------------
5374− Form
5375−---------------------*/
5376−
5377−.ui.form {
5378− position: relative;
5379− max-width: 100%;
5380−}
5381−
5382−/*--------------------
5383− Content
5384−---------------------*/
5385−
5386−.ui.form > p {
5387− margin: 1em 0;
5388−}
5389−
5390−/*--------------------
5391− Field
5392−---------------------*/
5393−
5394−.ui.form .field {
5395− clear: both;
5396− margin: 0 0 1em;
5397−}
5398−
5399−.ui.form .fields .fields,
5400−.ui.form .field:last-child,
5401−.ui.form .fields:last-child .field {
5402− margin-bottom: 0;
5403−}
5404−
5405−.ui.form .fields .field {
5406− clear: both;
5407− margin: 0;
5408−}
5409−
5410−/*--------------------
5411− Labels
5412−---------------------*/
5413−
5414−.ui.form .field > label {
5415− display: block;
5416− margin: 0 0 0.28571429rem 0;
5417− color: rgba(0, 0, 0, 0.87);
5418− font-size: 0.92857143em;
5419− font-weight: 500;
5420− text-transform: none;
5421−}
5422−
5423−/*--------------------
5424− Standard Inputs
5425−---------------------*/
5426−
5427−.ui.form textarea,
5428−.ui.form input:not([type]),
5429−.ui.form input[type="date"],
5430−.ui.form input[type="datetime-local"],
5431−.ui.form input[type="email"],
5432−.ui.form input[type="number"],
5433−.ui.form input[type="password"],
5434−.ui.form input[type="search"],
5435−.ui.form input[type="tel"],
5436−.ui.form input[type="time"],
5437−.ui.form input[type="text"],
5438−.ui.form input[type="file"],
5439−.ui.form input[type="url"] {
5440− width: 100%;
5441− vertical-align: top;
5442−}
5443−
5444−/* Set max height on unusual input */
5445−
5446−.ui.form ::-webkit-datetime-edit,
5447−.ui.form ::-webkit-inner-spin-button {
5448− height: 1.21428571em;
5449−}
5450−
5451−.ui.form input:not([type]),
5452−.ui.form input[type="date"],
5453−.ui.form input[type="datetime-local"],
5454−.ui.form input[type="email"],
5455−.ui.form input[type="number"],
5456−.ui.form input[type="password"],
5457−.ui.form input[type="search"],
5458−.ui.form input[type="tel"],
5459−.ui.form input[type="time"],
5460−.ui.form input[type="text"],
5461−.ui.form input[type="file"],
5462−.ui.form input[type="url"] {
5463− font-family: var(--fonts-regular);
5464− margin: 0;
5465− outline: none;
5466− -webkit-appearance: none;
5467− -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
5468− line-height: 1.21428571em;
5469− padding: 0.67857143em 1em;
5470− font-size: 1em;
5471− background: #FFFFFF;
5472− border: 1px solid rgba(34, 36, 38, 0.15);
5473− color: rgba(0, 0, 0, 0.87);
5474− border-radius: 0.28571429rem;
5475− box-shadow: 0 0 0 0 transparent inset;
5476− transition: color 0.1s ease, border-color 0.1s ease;
5477−}
5478−
5479−/* Text Area */
5480−
5481−.ui.input textarea,
5482−.ui.form textarea {
5483− margin: 0;
5484− -webkit-appearance: none;
5485− -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
5486− padding: 0.78571429em 1em;
5487− background: #FFFFFF;
5488− border: 1px solid rgba(34, 36, 38, 0.15);
5489− outline: none;
5490− color: rgba(0, 0, 0, 0.87);
5491− border-radius: 0.28571429rem;
5492− box-shadow: 0 0 0 0 transparent inset;
5493− transition: color 0.1s ease, border-color 0.1s ease;
5494− font-size: 1em;
5495− font-family: var(--fonts-regular);
5496− line-height: 1.2857;
5497− resize: vertical;
5498−}
5499−
5500−.ui.form textarea:not([rows]) {
5501− height: 12em;
5502− min-height: 8em;
5503− max-height: 24em;
5504−}
5505−
5506−.ui.form textarea,
5507−.ui.form input[type="checkbox"] {
5508− vertical-align: top;
5509−}
5510−
5511−/*--------------------
5512− Checkbox margin
5513−---------------------*/
5514−
5515−.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) label + .ui.ui.checkbox {
5516− margin-top: 0.7em;
5517−}
5518−
5519−.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.checkbox {
5520− margin-top: 2.41428571em;
5521−}
5522−
5523−.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.toggle.checkbox {
5524− margin-top: 2.21428571em;
5525−}
5526−
5527−.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.slider.checkbox {
5528− margin-top: 2.61428571em;
5529−}
5530−
5531−.ui.ui.form .field .fields .field:not(:only-child) .ui.checkbox {
5532− margin-top: 0.6em;
5533−}
5534−
5535−.ui.ui.form .field .fields .field:not(:only-child) .ui.toggle.checkbox {
5536− margin-top: 0.5em;
5537−}
5538−
5539−.ui.ui.form .field .fields .field:not(:only-child) .ui.slider.checkbox {
5540− margin-top: 0.7em;
5541−}
5542−
5543−/*--------------------------
5544− Input w/ attached Button
5545−---------------------------*/
5546−
5547−.ui.form input.attached {
5548− width: auto;
5549−}
5550−
5551−/*--------------------
5552− Basic Select
5553−---------------------*/
5554−
5555−.ui.form select {
5556− display: block;
5557− height: auto;
5558− width: 100%;
5559− background: #FFFFFF;
5560− border: 1px solid rgba(34, 36, 38, 0.15);
5561− border-radius: 0.28571429rem;
5562− box-shadow: 0 0 0 0 transparent inset;
5563− padding: 0.62em 1em;
5564− color: rgba(0, 0, 0, 0.87);
5565− transition: color 0.1s ease, border-color 0.1s ease;
5566−}
5567−
5568−/*--------------------
5569− Dropdown
5570−---------------------*/
5571−
5572−/* Block */
5573−
5574−.ui.form .field > .selection.dropdown {
5575− min-width: auto;
5576− width: 100%;
5577−}
5578−
5579−.ui.form .field > .selection.dropdown > .dropdown.icon {
5580− float: right;
5581−}
5582−
5583−/* Inline */
5584−
5585−.ui.form .inline.fields .field > .selection.dropdown,
5586−.ui.form .inline.field > .selection.dropdown {
5587− width: auto;
5588−}
5589−
5590−.ui.form .inline.fields .field > .selection.dropdown > .dropdown.icon,
5591−.ui.form .inline.field > .selection.dropdown > .dropdown.icon {
5592− float: none;
5593−}
5594−
5595−/*--------------------
5596− UI Input
5597−---------------------*/
5598−
5599−/* Block */
5600−
5601−.ui.form .field .ui.input,
5602−.ui.form .fields .field .ui.input,
5603−.ui.form .wide.field .ui.input {
5604− width: 100%;
5605−}
5606−
5607−/* Inline */
5608−
5609−.ui.form .inline.fields .field:not(.wide) .ui.input,
5610−.ui.form .inline.field:not(.wide) .ui.input {
5611− width: auto;
5612− vertical-align: middle;
5613−}
5614−
5615−/* Auto Input */
5616−
5617−.ui.form .fields .field .ui.input input,
5618−.ui.form .field .ui.input input {
5619− width: auto;
5620−}
5621−
5622−/* Full Width Input */
5623−
5624−.ui.form .ten.fields .ui.input input,
5625−.ui.form .nine.fields .ui.input input,
5626−.ui.form .eight.fields .ui.input input,
5627−.ui.form .seven.fields .ui.input input,
5628−.ui.form .six.fields .ui.input input,
5629−.ui.form .five.fields .ui.input input,
5630−.ui.form .four.fields .ui.input input,
5631−.ui.form .three.fields .ui.input input,
5632−.ui.form .two.fields .ui.input input,
5633−.ui.form .wide.field .ui.input input {
5634− flex: 1 0 auto;
5635− width: 0;
5636−}
5637−
5638−/*--------------------
5639− Types of Messages
5640−---------------------*/
5641−
5642−.ui.form .error.message,
5643−.ui.form .error.message:empty {
5644− display: none;
5645−}
5646−
5647−.ui.form .info.message,
5648−.ui.form .info.message:empty {
5649− display: none;
5650−}
5651−
5652−.ui.form .success.message,
5653−.ui.form .success.message:empty {
5654− display: none;
5655−}
5656−
5657−.ui.form .warning.message,
5658−.ui.form .warning.message:empty {
5659− display: none;
5660−}
5661−
5662−/* Assumptions */
5663−
5664−.ui.form .message:first-child {
5665− margin-top: 0;
5666−}
5667−
5668−/*--------------------
5669− Validation Prompt
5670−---------------------*/
5671−
5672−.ui.form .field .prompt.label {
5673− white-space: normal;
5674− background: #FFFFFF !important;
5675− border: 1px solid #E0B4B4 !important;
5676− color: #9F3A38 !important;
5677−}
5678−
5679−.ui.form .inline.fields .field .prompt,
5680−.ui.form .inline.field .prompt {
5681− vertical-align: top;
5682− margin: -0.25em 0 -0.5em 0.5em;
5683−}
5684−
5685−.ui.form .inline.fields .field .prompt:before,
5686−.ui.form .inline.field .prompt:before {
5687− border-width: 0 0 1px 1px;
5688− bottom: auto;
5689− right: auto;
5690− top: 50%;
5691− left: 0;
5692−}
5693−
5694−/*******************************
5695− States
5696−*******************************/
5697−
5698−/*--------------------
5699− Autofilled
5700−---------------------*/
5701−
5702−.ui.form .field.field input:-webkit-autofill {
5703− box-shadow: 0 0 0 100px #FFFFF0 inset !important;
5704− border-color: #E5DFA1 !important;
5705−}
5706−
5707−/* Focus */
5708−
5709−.ui.form .field.field input:-webkit-autofill:focus {
5710− box-shadow: 0 0 0 100px #FFFFF0 inset !important;
5711− border-color: #D5C315 !important;
5712−}
5713−
5714−/*--------------------
5715− Placeholder
5716−---------------------*/
5717−
5718−/* browsers require these rules separate */
5719−
5720−.ui.form ::-webkit-input-placeholder {
5721− color: rgba(191, 191, 191, 0.87);
5722−}
5723−
5724−.ui.form :-ms-input-placeholder {
5725− color: rgba(191, 191, 191, 0.87) !important;
5726−}
5727−
5728−.ui.form ::-moz-placeholder {
5729− color: rgba(191, 191, 191, 0.87);
5730−}
5731−
5732−.ui.form :focus::-webkit-input-placeholder {
5733− color: rgba(115, 115, 115, 0.87);
5734−}
5735−
5736−.ui.form :focus:-ms-input-placeholder {
5737− color: rgba(115, 115, 115, 0.87) !important;
5738−}
5739−
5740−.ui.form :focus::-moz-placeholder {
5741− color: rgba(115, 115, 115, 0.87);
5742−}
5743−
5744−/*--------------------
5745− Focus
5746−---------------------*/
5747−
5748−.ui.form input:not([type]):focus,
5749−.ui.form input[type="date"]:focus,
5750−.ui.form input[type="datetime-local"]:focus,
5751−.ui.form input[type="email"]:focus,
5752−.ui.form input[type="number"]:focus,
5753−.ui.form input[type="password"]:focus,
5754−.ui.form input[type="search"]:focus,
5755−.ui.form input[type="tel"]:focus,
5756−.ui.form input[type="time"]:focus,
5757−.ui.form input[type="text"]:focus,
5758−.ui.form input[type="file"]:focus,
5759−.ui.form input[type="url"]:focus {
5760− color: rgba(0, 0, 0, 0.95);
5761− border-color: #85B7D9;
5762− border-radius: 0.28571429rem;
5763− background: #FFFFFF;
5764− box-shadow: 0 0 0 0 rgba(34, 36, 38, 0.35) inset;
5765−}
5766−
5767−.ui.form .ui.action.input:not([class*="left action"]) input:not([type]):focus,
5768−.ui.form .ui.action.input:not([class*="left action"]) input[type="date"]:focus,
5769−.ui.form .ui.action.input:not([class*="left action"]) input[type="datetime-local"]:focus,
5770−.ui.form .ui.action.input:not([class*="left action"]) input[type="email"]:focus,
5771−.ui.form .ui.action.input:not([class*="left action"]) input[type="number"]:focus,
5772−.ui.form .ui.action.input:not([class*="left action"]) input[type="password"]:focus,
5773−.ui.form .ui.action.input:not([class*="left action"]) input[type="search"]:focus,
5774−.ui.form .ui.action.input:not([class*="left action"]) input[type="tel"]:focus,
5775−.ui.form .ui.action.input:not([class*="left action"]) input[type="time"]:focus,
5776−.ui.form .ui.action.input:not([class*="left action"]) input[type="text"]:focus,
5777−.ui.form .ui.action.input:not([class*="left action"]) input[type="file"]:focus,
5778−.ui.form .ui.action.input:not([class*="left action"]) input[type="url"]:focus {
5779− border-top-right-radius: 0;
5780− border-bottom-right-radius: 0;
5781−}
5782−
5783−.ui.form .ui[class*="left action"].input input:not([type]),
5784−.ui.form .ui[class*="left action"].input input[type="date"],
5785−.ui.form .ui[class*="left action"].input input[type="datetime-local"],
5786−.ui.form .ui[class*="left action"].input input[type="email"],
5787−.ui.form .ui[class*="left action"].input input[type="number"],
5788−.ui.form .ui[class*="left action"].input input[type="password"],
5789−.ui.form .ui[class*="left action"].input input[type="search"],
5790−.ui.form .ui[class*="left action"].input input[type="tel"],
5791−.ui.form .ui[class*="left action"].input input[type="time"],
5792−.ui.form .ui[class*="left action"].input input[type="text"],
5793−.ui.form .ui[class*="left action"].input input[type="file"],
5794−.ui.form .ui[class*="left action"].input input[type="url"] {
5795− border-bottom-left-radius: 0;
5796− border-top-left-radius: 0;
5797−}
5798−
5799−.ui.form textarea:focus {
5800− color: rgba(0, 0, 0, 0.95);
5801− border-color: #85B7D9;
5802− border-radius: 0.28571429rem;
5803− background: #FFFFFF;
5804− box-shadow: 0 0 0 0 rgba(34, 36, 38, 0.35) inset;
5805− -webkit-appearance: none;
5806−}
5807−
5808−/*--------------------
5809− States
5810− ---------------------*/
5811−
5812−/* On Form */
5813−
5814−.ui.form.error .error.message:not(:empty) {
5815− display: block;
5816−}
5817−
5818−.ui.form.error .compact.error.message:not(:empty) {
5819− display: inline-block;
5820−}
5821−
5822−.ui.form.error .icon.error.message:not(:empty) {
5823− display: flex;
5824−}
5825−
5826−/* On Field(s) */
5827−
5828−.ui.form .fields.error .error.message:not(:empty),
5829−.ui.form .field.error .error.message:not(:empty) {
5830− display: block;
5831−}
5832−
5833−.ui.form .fields.error .compact.error.message:not(:empty),
5834−.ui.form .field.error .compact.error.message:not(:empty) {
5835− display: inline-block;
5836−}
5837−
5838−.ui.form .fields.error .icon.error.message:not(:empty),
5839−.ui.form .field.error .icon.error.message:not(:empty) {
5840− display: flex;
5841−}
5842−
5843−.ui.ui.form .fields.error .field label,
5844−.ui.ui.form .field.error label,
5845−.ui.ui.form .fields.error .field .input,
5846−.ui.ui.form .field.error .input {
5847− color: #9F3A38;
5848−}
5849−
5850−.ui.form .fields.error .field .corner.label,
5851−.ui.form .field.error .corner.label {
5852− border-color: #9F3A38;
5853− color: #FFFFFF;
5854−}
5855−
5856−.ui.form .fields.error .field textarea,
5857−.ui.form .fields.error .field select,
5858−.ui.form .fields.error .field input:not([type]),
5859−.ui.form .fields.error .field input[type="date"],
5860−.ui.form .fields.error .field input[type="datetime-local"],
5861−.ui.form .fields.error .field input[type="email"],
5862−.ui.form .fields.error .field input[type="number"],
5863−.ui.form .fields.error .field input[type="password"],
5864−.ui.form .fields.error .field input[type="search"],
5865−.ui.form .fields.error .field input[type="tel"],
5866−.ui.form .fields.error .field input[type="time"],
5867−.ui.form .fields.error .field input[type="text"],
5868−.ui.form .fields.error .field input[type="file"],
5869−.ui.form .fields.error .field input[type="url"],
5870−.ui.form .field.error textarea,
5871−.ui.form .field.error select,
5872−.ui.form .field.error input:not([type]),
5873−.ui.form .field.error input[type="date"],
5874−.ui.form .field.error input[type="datetime-local"],
5875−.ui.form .field.error input[type="email"],
5876−.ui.form .field.error input[type="number"],
5877−.ui.form .field.error input[type="password"],
5878−.ui.form .field.error input[type="search"],
5879−.ui.form .field.error input[type="tel"],
5880−.ui.form .field.error input[type="time"],
5881−.ui.form .field.error input[type="text"],
5882−.ui.form .field.error input[type="file"],
5883−.ui.form .field.error input[type="url"] {
5884− color: #9F3A38;
5885− background: #FFF6F6;
5886− border-color: #E0B4B4;
5887− border-radius: '';
5888− box-shadow: none;
3296+.ui.selection.active.dropdown:hover .menu {
3297+ border-color: #96C8DA;
3298+ box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
58893299 }
58903300
5891−.ui.form .field.error textarea:focus,
5892−.ui.form .field.error select:focus,
5893−.ui.form .field.error input:not([type]):focus,
5894−.ui.form .field.error input[type="date"]:focus,
5895−.ui.form .field.error input[type="datetime-local"]:focus,
5896−.ui.form .field.error input[type="email"]:focus,
5897−.ui.form .field.error input[type="number"]:focus,
5898−.ui.form .field.error input[type="password"]:focus,
5899−.ui.form .field.error input[type="search"]:focus,
5900−.ui.form .field.error input[type="tel"]:focus,
5901−.ui.form .field.error input[type="time"]:focus,
5902−.ui.form .field.error input[type="text"]:focus,
5903−.ui.form .field.error input[type="file"]:focus,
5904−.ui.form .field.error input[type="url"]:focus {
5905− background: #FFF6F6;
5906− border-color: #E0B4B4;
5907− color: #9F3A38;
5908− box-shadow: none;
3301+/* Dropdown Icon */
3302+
3303+.ui.active.selection.dropdown > .dropdown.icon,
3304+.ui.visible.selection.dropdown > .dropdown.icon {
3305+ opacity: '';
3306+ z-index: 3;
59093307 }
59103308
5911−/* Preserve Native Select Stylings */
3309+/* Connecting Border */
59123310
5913−.ui.form .field.error select {
5914− -webkit-appearance: menulist-button;
3311+.ui.active.selection.dropdown {
3312+ border-bottom-left-radius: 0 !important;
3313+ border-bottom-right-radius: 0 !important;
59153314 }
59163315
5917−/*------------------
5918− Input State
5919− --------------------*/
3316+/* Empty Connecting Border */
59203317
5921−/* Transparent */
3318+.ui.active.empty.selection.dropdown {
3319+ border-radius: 0.28571429rem !important;
3320+ box-shadow: none !important;
3321+}
59223322
5923−.ui.form .field.error .transparent.input input,
5924−.ui.form .field.error .transparent.input textarea,
5925−.ui.form .field.error input.transparent,
5926−.ui.form .field.error textarea.transparent {
5927− background-color: #FFF6F6 !important;
5928− color: #9F3A38 !important;
3323+.ui.active.empty.selection.dropdown .menu {
3324+ border: none !important;
3325+ box-shadow: none !important;
59293326 }
59303327
5931−/* Autofilled */
3328+/* CSS specific to iOS devices or firefox mobile only */
59323329
5933−.ui.form .error.error input:-webkit-autofill {
5934− box-shadow: 0 0 0 100px #FFFAF0 inset !important;
5935− border-color: #E0B4B4 !important;
3330+@supports (-webkit-touch-callout: none) or (-webkit-overflow-scrolling: touch) or (-moz-appearance:none) {
3331+@media (-moz-touch-enabled), (pointer: coarse) {
3332+ .ui.dropdown .scrollhint.menu:not(.hidden):before {
3333+ animation: scrollhint 2s ease 2;
3334+ content: '';
3335+ z-index: 15;
3336+ display: block;
3337+ position: absolute;
3338+ opacity: 0;
3339+ right: 0.25em;
3340+ top: 0;
3341+ height: 100%;
3342+ border-right: 0.25em solid;
3343+ border-left: 0;
3344+ -o-border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%;
3345+ border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%;
3346+ }
3347+
3348+ .ui.inverted.dropdown .scrollhint.menu:not(.hidden):before {
3349+ -o-border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%;
3350+ border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%;
3351+ }
3352+
3353+@keyframes scrollhint {
3354+ 0% {
3355+ opacity: 1;
3356+ top: 100%;
3357+ }
3358+
3359+ 100% {
3360+ opacity: 0;
3361+ top: 0;
3362+ }
3363+}
3364+}
59363365 }
59373366
5938−/* Placeholder */
3367+/*--------------
3368+ Searchable
3369+ ---------------*/
59393370
5940−.ui.form .error ::-webkit-input-placeholder {
5941− color: #e7bdbc;
3371+/* Search Selection */
3372+
3373+.ui.search.dropdown {
3374+ min-width: '';
59423375 }
59433376
5944−.ui.form .error :-ms-input-placeholder {
5945− color: #e7bdbc !important;
3377+/* Search Dropdown */
3378+
3379+.ui.search.dropdown > input.search {
3380+ background: none transparent !important;
3381+ border: none !important;
3382+ box-shadow: none !important;
3383+ cursor: text;
3384+ top: 0;
3385+ left: 1px;
3386+ width: 100%;
3387+ outline: none;
3388+ -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
3389+ padding: inherit;
59463390 }
59473391
5948−.ui.form .error ::-moz-placeholder {
5949− color: #e7bdbc;
3392+/* Text Layering */
3393+
3394+.ui.search.dropdown > input.search {
3395+ position: absolute;
3396+ z-index: 2;
59503397 }
59513398
5952−.ui.form .error :focus::-webkit-input-placeholder {
5953− color: #da9796;
3399+.ui.search.dropdown > .text {
3400+ cursor: text;
3401+ position: relative;
3402+ left: 1px;
3403+ z-index: auto;
59543404 }
59553405
5956−.ui.form .error :focus:-ms-input-placeholder {
5957− color: #da9796 !important;
3406+/* Search Selection */
3407+
3408+.ui.search.selection.dropdown > input.search {
3409+ line-height: 1.21428571em;
3410+ padding: 0.67857143em 3.2em 0.67857143em 1em;
59583411 }
59593412
5960−.ui.form .error :focus::-moz-placeholder {
5961− color: #da9796;
3413+/* Used to size multi select input to character width */
3414+
3415+.ui.search.selection.dropdown > span.sizer {
3416+ line-height: 1.21428571em;
3417+ padding: 0.67857143em 3.2em 0.67857143em 1em;
3418+ display: none;
3419+ white-space: pre;
59623420 }
59633421
5964−/*------------------
5965− Dropdown State
5966− --------------------*/
3422+/* Active/Visible Search */
59673423
5968−.ui.form .fields.error .field .ui.dropdown,
5969−.ui.form .fields.error .field .ui.dropdown .item,
5970−.ui.form .field.error .ui.dropdown,
5971−.ui.form .field.error .ui.dropdown .text,
5972−.ui.form .field.error .ui.dropdown .item {
5973− background: #FFF6F6;
5974− color: #9F3A38;
3424+.ui.search.dropdown.active > input.search,
3425+.ui.search.dropdown.visible > input.search {
3426+ cursor: auto;
59753427 }
59763428
5977−.ui.form .fields.error .field .ui.dropdown,
5978−.ui.form .field.error .ui.dropdown {
5979− border-color: #E0B4B4 !important;
3429+.ui.search.dropdown.active > .text,
3430+.ui.search.dropdown.visible > .text {
3431+ pointer-events: none;
59803432 }
59813433
5982−.ui.form .fields.error .field .ui.dropdown:hover,
5983−.ui.form .field.error .ui.dropdown:hover {
5984− border-color: #E0B4B4 !important;
3434+/* Filtered Text */
3435+
3436+.ui.active.search.dropdown input.search:focus + .text i.icon,
3437+.ui.active.search.dropdown input.search:focus + .text .flag {
3438+ opacity: var(--opacity-disabled);
59853439 }
59863440
5987−.ui.form .fields.error .field .ui.dropdown:hover .menu,
5988−.ui.form .field.error .ui.dropdown:hover .menu {
5989− border-color: #E0B4B4;
3441+.ui.active.search.dropdown input.search:focus + .text {
3442+ color: rgba(115, 115, 115, 0.87) !important;
59903443 }
59913444
5992−.ui.form .fields.error .field .ui.multiple.selection.dropdown > .label,
5993−.ui.form .field.error .ui.multiple.selection.dropdown > .label {
5994− background-color: #EACBCB;
5995− color: #9F3A38;
3445+.ui.search.dropdown.button > span.sizer {
3446+ display: none;
59963447 }
59973448
5998−/* Hover */
3449+/* Search Menu */
59993450
6000−.ui.form .fields.error .field .ui.dropdown .menu .item:hover,
6001−.ui.form .field.error .ui.dropdown .menu .item:hover {
6002− background-color: #FBE7E7;
3451+.ui.search.dropdown .menu {
3452+ overflow-x: hidden;
3453+ overflow-y: auto;
3454+ backface-visibility: hidden;
3455+ -webkit-overflow-scrolling: touch;
60033456 }
60043457
6005−/* Selected */
3458+@media only screen and (max-width: 767.98px) {
3459+ .ui.search.dropdown .menu {
3460+ max-height: 8.01428571rem;
3461+ }
3462+}
60063463
6007−.ui.form .fields.error .field .ui.dropdown .menu .selected.item,
6008−.ui.form .field.error .ui.dropdown .menu .selected.item {
6009− background-color: #FBE7E7;
3464+@media only screen and (min-width: 768px) {
3465+ .ui.search.dropdown .menu {
3466+ max-height: 10.68571429rem;
3467+ }
60103468 }
60113469
6012−/* Active */
3470+@media only screen and (min-width: 992px) {
3471+ .ui.search.dropdown .menu {
3472+ max-height: 16.02857143rem;
3473+ }
3474+}
60133475
6014−.ui.form .fields.error .field .ui.dropdown .menu .active.item,
6015−.ui.form .field.error .ui.dropdown .menu .active.item {
6016− background-color: #FDCFCF !important;
3476+@media only screen and (min-width: 1920px) {
3477+ .ui.search.dropdown .menu {
3478+ max-height: 21.37142857rem;
3479+ }
60173480 }
60183481
6019−/*--------------------
6020− Checkbox State
6021− ---------------------*/
3482+/* Clearable Selection */
60223483
6023−.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label,
6024−.ui.form .field.error .checkbox:not(.toggle):not(.slider) label,
6025−.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box,
6026−.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box {
6027− color: #9F3A38;
3484+.ui.dropdown > .remove.icon {
3485+ cursor: pointer;
3486+ font-size: 0.85714286em;
3487+ margin: -0.78571429em;
3488+ padding: 0.91666667em;
3489+ right: 3em;
3490+ top: 0.78571429em;
3491+ position: absolute;
3492+ opacity: 0.6;
3493+ z-index: 3;
60283494 }
60293495
6030−.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label:before,
6031−.ui.form .field.error .checkbox:not(.toggle):not(.slider) label:before,
6032−.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box:before,
6033−.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box:before {
6034− background: #FFF6F6;
6035− border-color: #E0B4B4;
3496+.ui.clearable.dropdown .text,
3497+.ui.clearable.dropdown a:last-of-type {
3498+ margin-right: 1.5em;
3499+}
3500+
3501+.ui.dropdown select.noselection ~ .remove.icon,
3502+.ui.dropdown input[value=''] ~ .remove.icon,
3503+.ui.dropdown input:not([value]) ~ .remove.icon,
3504+.ui.dropdown.loading > .remove.icon {
3505+ display: none;
60363506 }
60373507
6038−.ui.form .fields.error .field .checkbox label:after,
6039−.ui.form .field.error .checkbox label:after,
6040−.ui.form .fields.error .field .checkbox .box:after,
6041−.ui.form .field.error .checkbox .box:after {
6042− color: #9F3A38;
6043−}
3508+/*--------------
3509+ Multiple
3510+ ---------------*/
60443511
6045−/* On Form */
3512+/* Multiple Selection */
60463513
6047−.ui.form.info .info.message:not(:empty) {
6048− display: block;
3514+.ui.ui.multiple.dropdown {
3515+ padding: 0.22619048em 3.2em 0.22619048em 0.35714286em;
60493516 }
60503517
6051−.ui.form.info .compact.info.message:not(:empty) {
6052− display: inline-block;
3518+.ui.multiple.dropdown .menu {
3519+ cursor: auto;
60533520 }
60543521
6055−.ui.form.info .icon.info.message:not(:empty) {
6056− display: flex;
3522+/* Selection Label */
3523+
3524+.ui.multiple.dropdown > .label {
3525+ display: inline-block;
3526+ white-space: normal;
3527+ font-size: 1em;
3528+ padding: 0.35714286em 0.78571429em;
3529+ margin: 0.14285714rem 0.28571429rem 0.14285714rem 0;
3530+ box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.15) inset;
60573531 }
60583532
6059−/* On Field(s) */
3533+/* Dropdown Icon */
60603534
6061−.ui.form .fields.info .info.message:not(:empty),
6062−.ui.form .field.info .info.message:not(:empty) {
6063− display: block;
3535+.ui.multiple.dropdown .dropdown.icon {
3536+ margin: '';
3537+ padding: '';
60643538 }
60653539
6066−.ui.form .fields.info .compact.info.message:not(:empty),
6067−.ui.form .field.info .compact.info.message:not(:empty) {
6068− display: inline-block;
6069−}
3540+/* Text */
60703541
6071−.ui.form .fields.info .icon.info.message:not(:empty),
6072−.ui.form .field.info .icon.info.message:not(:empty) {
6073− display: flex;
3542+.ui.multiple.dropdown > .text {
3543+ position: static;
3544+ padding: 0;
3545+ max-width: 100%;
3546+ margin: 0.45238095em 0 0.45238095em 0.64285714em;
3547+ line-height: 1.21428571em;
60743548 }
60753549
6076−.ui.ui.form .fields.info .field label,
6077−.ui.ui.form .field.info label,
6078−.ui.ui.form .fields.info .field .input,
6079−.ui.ui.form .field.info .input {
6080− color: #276F86;
3550+.ui.multiple.dropdown > .text.default {
3551+ white-space: nowrap;
3552+ overflow: hidden;
3553+ text-overflow: ellipsis;
60813554 }
60823555
6083−.ui.form .fields.info .field .corner.label,
6084−.ui.form .field.info .corner.label {
6085− border-color: #276F86;
6086− color: #FFFFFF;
3556+.ui.multiple.dropdown > .label ~ input.search {
3557+ margin-left: 0.14285714em !important;
60873558 }
60883559
6089−.ui.form .fields.info .field textarea,
6090−.ui.form .fields.info .field select,
6091−.ui.form .fields.info .field input:not([type]),
6092−.ui.form .fields.info .field input[type="date"],
6093−.ui.form .fields.info .field input[type="datetime-local"],
6094−.ui.form .fields.info .field input[type="email"],
6095−.ui.form .fields.info .field input[type="number"],
6096−.ui.form .fields.info .field input[type="password"],
6097−.ui.form .fields.info .field input[type="search"],
6098−.ui.form .fields.info .field input[type="tel"],
6099−.ui.form .fields.info .field input[type="time"],
6100−.ui.form .fields.info .field input[type="text"],
6101−.ui.form .fields.info .field input[type="file"],
6102−.ui.form .fields.info .field input[type="url"],
6103−.ui.form .field.info textarea,
6104−.ui.form .field.info select,
6105−.ui.form .field.info input:not([type]),
6106−.ui.form .field.info input[type="date"],
6107−.ui.form .field.info input[type="datetime-local"],
6108−.ui.form .field.info input[type="email"],
6109−.ui.form .field.info input[type="number"],
6110−.ui.form .field.info input[type="password"],
6111−.ui.form .field.info input[type="search"],
6112−.ui.form .field.info input[type="tel"],
6113−.ui.form .field.info input[type="time"],
6114−.ui.form .field.info input[type="text"],
6115−.ui.form .field.info input[type="file"],
6116−.ui.form .field.info input[type="url"] {
6117− color: #276F86;
6118− background: #F8FFFF;
6119− border-color: #A9D5DE;
6120− border-radius: '';
6121− box-shadow: none;
3560+.ui.multiple.dropdown > .label ~ .text {
3561+ display: none;
61223562 }
61233563
6124−.ui.form .field.info textarea:focus,
6125−.ui.form .field.info select:focus,
6126−.ui.form .field.info input:not([type]):focus,
6127−.ui.form .field.info input[type="date"]:focus,
6128−.ui.form .field.info input[type="datetime-local"]:focus,
6129−.ui.form .field.info input[type="email"]:focus,
6130−.ui.form .field.info input[type="number"]:focus,
6131−.ui.form .field.info input[type="password"]:focus,
6132−.ui.form .field.info input[type="search"]:focus,
6133−.ui.form .field.info input[type="tel"]:focus,
6134−.ui.form .field.info input[type="time"]:focus,
6135−.ui.form .field.info input[type="text"]:focus,
6136−.ui.form .field.info input[type="file"]:focus,
6137−.ui.form .field.info input[type="url"]:focus {
6138− background: #F8FFFF;
6139− border-color: #A9D5DE;
6140− color: #276F86;
6141− box-shadow: none;
3564+.ui.multiple.dropdown > .label:not(.image) > img:not(.centered) {
3565+ margin-right: 0.78571429rem;
61423566 }
61433567
6144−/* Preserve Native Select Stylings */
3568+.ui.multiple.dropdown > .label:not(.image) > img.ui:not(.avatar) {
3569+ margin-bottom: 0.39285714rem;
3570+}
61453571
6146−.ui.form .field.info select {
6147− -webkit-appearance: menulist-button;
3572+.ui.multiple.dropdown > .image.label img {
3573+ margin: -0.35714286em 0.78571429em -0.35714286em -0.78571429em;
3574+ height: 1.71428571em;
61483575 }
61493576
6150−/*------------------
6151− Input State
6152− --------------------*/
3577+/*-----------------
3578+ Multiple Search
3579+ -----------------*/
61533580
6154−/* Transparent */
3581+/* Multiple Search Selection */
61553582
6156−.ui.form .field.info .transparent.input input,
6157−.ui.form .field.info .transparent.input textarea,
6158−.ui.form .field.info input.transparent,
6159−.ui.form .field.info textarea.transparent {
6160− background-color: #F8FFFF !important;
6161− color: #276F86 !important;
3583+.ui.multiple.search.dropdown,
3584+.ui.multiple.search.dropdown > input.search {
3585+ cursor: text;
61623586 }
61633587
6164−/* Autofilled */
3588+/* Prompt Text */
61653589
6166−.ui.form .info.info input:-webkit-autofill {
6167− box-shadow: 0 0 0 100px #F0FAFF inset !important;
6168− border-color: #b3e0e0 !important;
3590+.ui.multiple.search.dropdown > .text {
3591+ display: inline-block;
3592+ position: absolute;
3593+ top: 0;
3594+ left: 0;
3595+ padding: inherit;
3596+ margin: 0.45238095em 0 0.45238095em 0.64285714em;
3597+ line-height: 1.21428571em;
61693598 }
61703599
6171−/* Placeholder */
6172−
6173−.ui.form .info ::-webkit-input-placeholder {
6174− color: #98cfe1;
3600+.ui.multiple.search.dropdown > .label ~ .text {
3601+ display: none;
61753602 }
61763603
6177−.ui.form .info :-ms-input-placeholder {
6178− color: #98cfe1 !important;
6179−}
3604+/* Search */
61803605
6181−.ui.form .info ::-moz-placeholder {
6182− color: #98cfe1;
3606+.ui.multiple.search.dropdown > input.search {
3607+ position: static;
3608+ padding: 0;
3609+ max-width: 100%;
3610+ margin: 0.45238095em 0 0.45238095em 0.64285714em;
3611+ width: 2.2em;
3612+ line-height: 1.21428571em;
61833613 }
61843614
6185−.ui.form .info :focus::-webkit-input-placeholder {
6186− color: #70bdd6;
3615+.ui.multiple.search.dropdown.button {
3616+ min-width: 14em;
61873617 }
61883618
6189−.ui.form .info :focus:-ms-input-placeholder {
6190− color: #70bdd6 !important;
6191−}
3619+/*--------------
3620+ Inline
3621+ ---------------*/
61923622
6193−.ui.form .info :focus::-moz-placeholder {
6194− color: #70bdd6;
3623+.ui.inline.dropdown {
3624+ cursor: pointer;
3625+ display: inline-block;
3626+ color: inherit;
61953627 }
61963628
6197−/*------------------
6198− Dropdown State
6199− --------------------*/
6200−
6201−.ui.form .fields.info .field .ui.dropdown,
6202−.ui.form .fields.info .field .ui.dropdown .item,
6203−.ui.form .field.info .ui.dropdown,
6204−.ui.form .field.info .ui.dropdown .text,
6205−.ui.form .field.info .ui.dropdown .item {
6206− background: #F8FFFF;
6207− color: #276F86;
3629+.ui.inline.dropdown .dropdown.icon {
3630+ margin: 0 0.21428571em 0 0.21428571em;
3631+ vertical-align: baseline;
62083632 }
62093633
6210−.ui.form .fields.info .field .ui.dropdown,
6211−.ui.form .field.info .ui.dropdown {
6212− border-color: #A9D5DE !important;
3634+.ui.inline.dropdown > .text {
3635+ font-weight: 500;
62133636 }
62143637
6215−.ui.form .fields.info .field .ui.dropdown:hover,
6216−.ui.form .field.info .ui.dropdown:hover {
6217− border-color: #A9D5DE !important;
3638+.ui.inline.dropdown .menu {
3639+ cursor: auto;
3640+ margin-top: 0.21428571em;
3641+ border-radius: 0.28571429rem;
62183642 }
62193643
6220−.ui.form .fields.info .field .ui.dropdown:hover .menu,
6221−.ui.form .field.info .ui.dropdown:hover .menu {
6222− border-color: #A9D5DE;
6223−}
3644+/*******************************
3645+ States
3646+*******************************/
62243647
6225−.ui.form .fields.info .field .ui.multiple.selection.dropdown > .label,
6226−.ui.form .field.info .ui.multiple.selection.dropdown > .label {
6227− background-color: #cce3ea;
6228− color: #276F86;
3648+/*--------------------
3649+ Active
3650+----------------------*/
3651+
3652+/* Menu Item Active */
3653+
3654+.ui.dropdown .menu .active.item {
3655+ background: transparent;
3656+ font-weight: 500;
3657+ color: rgba(0, 0, 0, 0.95);
3658+ box-shadow: none;
3659+ z-index: 12;
62293660 }
62303661
6231−/* Hover */
3662+/*--------------------
3663+ Hover
3664+----------------------*/
62323665
6233−.ui.form .fields.info .field .ui.dropdown .menu .item:hover,
6234−.ui.form .field.info .ui.dropdown .menu .item:hover {
6235− background-color: #e9f2fb;
3666+/* Menu Item Hover */
3667+
3668+.ui.dropdown .menu > .item:hover {
3669+ background: rgba(0, 0, 0, 0.05);
3670+ color: rgba(0, 0, 0, 0.95);
3671+ z-index: 13;
62363672 }
62373673
6238−/* Selected */
3674+/*--------------------
3675+ Default Text
3676+----------------------*/
62393677
6240−.ui.form .fields.info .field .ui.dropdown .menu .selected.item,
6241−.ui.form .field.info .ui.dropdown .menu .selected.item {
6242− background-color: #e9f2fb;
3678+.ui.dropdown:not(.button) > .default.text,
3679+.ui.default.dropdown:not(.button) > .text {
3680+ color: rgba(191, 191, 191, 0.87);
62433681 }
62443682
6245−/* Active */
6246−
6247−.ui.form .fields.info .field .ui.dropdown .menu .active.item,
6248−.ui.form .field.info .ui.dropdown .menu .active.item {
6249− background-color: #cef1fd !important;
3683+.ui.dropdown:not(.button) > input:focus ~ .default.text,
3684+.ui.default.dropdown:not(.button) > input:focus ~ .text {
3685+ color: rgba(115, 115, 115, 0.87);
62503686 }
62513687
62523688 /*--------------------
6253− Checkbox State
6254− ---------------------*/
3689+ Loading
3690+ ---------------------*/
62553691
6256−.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label,
6257−.ui.form .field.info .checkbox:not(.toggle):not(.slider) label,
6258−.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) .box,
6259−.ui.form .field.info .checkbox:not(.toggle):not(.slider) .box {
6260− color: #276F86;
3692+.ui.loading.dropdown > i.icon {
3693+ height: 1em !important;
62613694 }
62623695
6263−.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label:before,
6264−.ui.form .field.info .checkbox:not(.toggle):not(.slider) label:before,
6265−.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) .box:before,
6266−.ui.form .field.info .checkbox:not(.toggle):not(.slider) .box:before {
6267− background: #F8FFFF;
6268− border-color: #A9D5DE;
3696+.ui.loading.selection.dropdown > i.icon {
3697+ padding: 1.5em 1.28571429em !important;
62693698 }
62703699
6271−.ui.form .fields.info .field .checkbox label:after,
6272−.ui.form .field.info .checkbox label:after,
6273−.ui.form .fields.info .field .checkbox .box:after,
6274−.ui.form .field.info .checkbox .box:after {
6275− color: #276F86;
3700+.ui.loading.dropdown > i.icon:before {
3701+ position: absolute;
3702+ content: '';
3703+ top: 50%;
3704+ left: 50%;
3705+ margin: -0.64285714em 0 0 -0.64285714em;
3706+ width: 1.28571429em;
3707+ height: 1.28571429em;
3708+ border-radius: 500rem;
3709+ border: 0.2em solid rgba(0, 0, 0, 0.1);
62763710 }
62773711
6278−/* On Form */
6279−
6280−.ui.form.success .success.message:not(:empty) {
6281− display: block;
3712+.ui.loading.dropdown > i.icon:after {
3713+ position: absolute;
3714+ content: '';
3715+ top: 50%;
3716+ left: 50%;
3717+ box-shadow: 0 0 0 1px transparent;
3718+ margin: -0.64285714em 0 0 -0.64285714em;
3719+ width: 1.28571429em;
3720+ height: 1.28571429em;
3721+ animation: loader 0.6s infinite linear;
3722+ border: 0.2em solid #767676;
3723+ border-radius: 500rem;
62823724 }
62833725
6284−.ui.form.success .compact.success.message:not(:empty) {
6285− display: inline-block;
3726+/* Coupling */
3727+
3728+.ui.loading.dropdown.button > i.icon:before,
3729+.ui.loading.dropdown.button > i.icon:after {
3730+ display: none;
62863731 }
62873732
6288−.ui.form.success .icon.success.message:not(:empty) {
6289− display: flex;
3733+.ui.loading.dropdown > .text {
3734+ transition: none;
62903735 }
62913736
6292−/* On Field(s) */
3737+/* Used To Check Position */
62933738
6294−.ui.form .fields.success .success.message:not(:empty),
6295−.ui.form .field.success .success.message:not(:empty) {
3739+.ui.dropdown .loading.menu {
62963740 display: block;
3741+ visibility: hidden;
3742+ z-index: -1;
62973743 }
62983744
6299−.ui.form .fields.success .compact.success.message:not(:empty),
6300−.ui.form .field.success .compact.success.message:not(:empty) {
6301− display: inline-block;
3745+.ui.dropdown > .loading.menu {
3746+ left: 0 !important;
3747+ right: auto !important;
63023748 }
63033749
6304−.ui.form .fields.success .icon.success.message:not(:empty),
6305−.ui.form .field.success .icon.success.message:not(:empty) {
6306− display: flex;
3750+.ui.dropdown > .menu .loading.menu {
3751+ left: 100% !important;
3752+ right: auto !important;
63073753 }
63083754
6309−.ui.ui.form .fields.success .field label,
6310−.ui.ui.form .field.success label,
6311−.ui.ui.form .fields.success .field .input,
6312−.ui.ui.form .field.success .input {
6313− color: #2C662D;
6314−}
3755+/*--------------------
3756+ Keyboard Select
3757+----------------------*/
63153758
6316−.ui.form .fields.success .field .corner.label,
6317−.ui.form .field.success .corner.label {
6318− border-color: #2C662D;
6319− color: #FFFFFF;
6320−}
3759+/* Selected Item */
63213760
6322−.ui.form .fields.success .field textarea,
6323−.ui.form .fields.success .field select,
6324−.ui.form .fields.success .field input:not([type]),
6325−.ui.form .fields.success .field input[type="date"],
6326−.ui.form .fields.success .field input[type="datetime-local"],
6327−.ui.form .fields.success .field input[type="email"],
6328−.ui.form .fields.success .field input[type="number"],
6329−.ui.form .fields.success .field input[type="password"],
6330−.ui.form .fields.success .field input[type="search"],
6331−.ui.form .fields.success .field input[type="tel"],
6332−.ui.form .fields.success .field input[type="time"],
6333−.ui.form .fields.success .field input[type="text"],
6334−.ui.form .fields.success .field input[type="file"],
6335−.ui.form .fields.success .field input[type="url"],
6336−.ui.form .field.success textarea,

4450 more lines aren’t shown here.

web_src/fomantic/build/semantic.js+0−877
@@ -1184,883 +1184,6 @@ $.api.settings = { · 1183 unmodified lines
11841184
11851185
11861186
1187−})( jQuery, window, document );
1188−
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−
20641187 })( jQuery, window, document );
20651188
20661189 /*!
web_src/fomantic/semantic.json+0−3
@@ -23,12 +23,9 @@ · 22 unmodified lines
2323 "components": [
2424 "api",
2525 "button",
26− "checkbox",
2726 "dimmer",
2827 "dropdown",
2928 "form",
30− "input",
31− "list",
3229 "menu",
3330 "modal",
3431 "search",
web_src/js/components/DashboardRepoList.vue+3−3
@@ -350,10 +350,10 @@ export default sfc; // activate the IDE's Vue plugin · 349 unmodified lines
350350 <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
351351 </div>
352352 </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">
355355 <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>
357357 <div class="ui dropdown icon button" :title="textFilter">
358358 <svg-icon name="octicon-filter" :size="16"/>
359359 <div class="menu">
web_src/js/features/admin/common.js+15−8
@@ -218,17 +218,24 @@ export function initAdminCommon() { · 217 unmodified lines
218218 });
219219
220220 // Select actions
221− const $checkboxes = $('.select.table .ui.checkbox');
221+ const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input');
222+
222223 $('.select.action').on('click', function () {
223224 switch ($(this).data('action')) {
224225 case 'select-all':
225− $checkboxes.checkbox('check');
226+ for (const checkbox of checkboxes) {
227+ checkbox.checked = true;
228+ }
226229 break;
227230 case 'deselect-all':
228− $checkboxes.checkbox('uncheck');
231+ for (const checkbox of checkboxes) {
232+ checkbox.checked = false;
233+ }
229234 break;
230235 case 'inverse':
231− $checkboxes.checkbox('toggle');
236+ for (const checkbox of checkboxes) {
237+ checkbox.checked = !checkbox.checked;
238+ }
232239 break;
233240 }
234241 });
@@ -236,11 +243,11 @@ export function initAdminCommon() { · 1 unmodified line
236243 e.preventDefault();
237244 this.classList.add('is-loading', 'disabled');
238245 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'));
242249 }
243− });
250+ }
244251 await POST(this.getAttribute('data-link'), {data});
245252 window.location.href = this.getAttribute('data-redirect');
246253 });
web_src/js/features/colorpicker.js+59−5
@@ -1,12 +1,66 @@
1−import $ from 'jquery';
1+import {createTippy} from '../modules/tippy.js';
22
3−export async function createColorPicker(els) {
3+export async function initColorPickers() {
4+ const els = document.getElementsByClassName('js-color-picker-input');
45 if (!els.length) return;
56
67 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'),
910 ]);
1011
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+ }
1266 }
web_src/js/features/common-global.js+3−9
@@ -2,7 +2,6 @@ import $ from 'jquery'; · 1 unmodified line
22 import '../vendor/jquery.are-you-sure.js';
33 import {clippie} from 'clippie';
44 import {createDropzone} from './dropzone.js';
5−import {initCompColorPicker} from './comp/ColorPicker.js';
65 import {showGlobalErrorMessage} from '../bootstrap.js';
76 import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
87 import {svg} from '../svg.js';
@@ -110,7 +109,7 @@ async function fetchActionDoRequest(actionElem, url, opt) { · 101 unmodified lines
110109 showErrorToast(`${i18n.network_error} ${e}`);
111110 }
112111 }
113− actionElem.classList.remove('is-loading', 'small-loading-icon');
112+ actionElem.classList.remove('is-loading', 'loading-icon-2px');
114113 }
115114
116115 async function formFetchAction(e) {
@@ -122,7 +121,7 @@ async function formFetchAction(e) { · 5 unmodified lines
122121
123122 formEl.classList.add('is-loading');
124123 if (formEl.clientHeight < 50) {
125− formEl.classList.add('small-loading-icon');
124+ formEl.classList.add('loading-icon-2px');
126125 }
127126
128127 const formMethod = formEl.getAttribute('method') || 'get';
@@ -196,8 +195,6 @@ export function initGlobalCommon() { · 67 unmodified lines
196195 $uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');
197196 $uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');
198197
199− $('.ui.checkbox').checkbox();
200−
201198 $('.tabular.menu .item').tab();
202199
203200 initSubmitEventPolyfill();
@@ -379,10 +376,7 @@ function initGlobalShowModal() { · 175 unmodified lines
379376 $attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
380377 }
381378 }
382− const $colorPickers = $modal.find('.color-picker');
383− if ($colorPickers.length > 0) {
384− initCompColorPicker(); // FIXME: this might cause duplicate init
385− }
379+
386380 $modal.modal('setting', {
387381 onApprove: () => {
388382 // "form-fetch-action" can handle network errors gracefully,
web_src/js/features/comp/ColorPicker.js+0−16
@@ -1,16 +0,0 @@
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−}
web_src/js/features/comp/LabelEdit.js+14−3
@@ -1,5 +1,4 @@
11 import $ from 'jquery';
2−import {initCompColorPicker} from './ColorPicker.js';
32
43 function isExclusiveScopeName(name) {
54 return /.*[^/]\/[^/].*/.test(name);
@@ -28,13 +27,17 @@ function updateExclusiveLabelEdit(form) { · 22 unmodified lines
2827
2928 export function initCompLabelEdit(selector) {
3029 if (!$(selector).length) return;
31− initCompColorPicker();
3230
3331 // Create label
3432 $('.new-label.button').on('click', () => {
3533 updateExclusiveLabelEdit('.new-label');
3634 $('.new-label.modal').modal({
3735 onApprove() {
36+ const form = document.querySelector('.new-label.form');
37+ if (!form.checkValidity()) {
38+ form.reportValidity();
39+ return false;
40+ }
3841 $('.new-label.form').trigger('submit');
3942 },
4043 }).modal('show');
@@ -60,10 +63,18 @@ export function initCompLabelEdit(selector) { · 19 unmodified lines
6063 updateExclusiveLabelEdit('.edit-label');
6164
6265 $('.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}));
6470
6571 $('.edit-label.modal').modal({
6672 onApprove() {
73+ const form = document.querySelector('.edit-label.form');
74+ if (!form.checkValidity()) {
75+ form.reportValidity();
76+ return false;
77+ }
6778 $('.edit-label.form').trigger('submit');
6879 },
6980 }).modal('show');
web_src/js/features/copycontent.js+2−2
@@ -19,7 +19,7 @@ export function initCopyContent() { · 18 unmodified lines
1919 // the text to copy is not in the DOM or it is an image which should be
2020 // fetched to copy in full resolution
2121 if (link) {
22− btn.classList.add('is-loading', 'small-loading-icon');
22+ btn.classList.add('is-loading', 'loading-icon-2px');
2323 try {
2424 const res = await GET(link, {credentials: 'include', redirect: 'follow'});
2525 const contentType = res.headers.get('content-type');
@@ -33,7 +33,7 @@ export function initCopyContent() { · 7 unmodified lines
3333 } catch {
3434 return showTemporaryTooltip(btn, i18n.copy_error);
3535 } finally {
36− btn.classList.remove('is-loading', 'small-loading-icon');
36+ btn.classList.remove('is-loading', 'loading-icon-2px');
3737 }
3838 } else { // text, read from DOM
3939 const lineEls = document.querySelectorAll('.file-view .lines-code');
web_src/js/features/imagediff.js+20−9
@@ -110,15 +110,15 @@ export function initImageDiff() { · 109 unmodified lines
110110 const $imagesAfter = imageInfos[0].$images;
111111 const $imagesBefore = imageInfos[1].$images;
112112
113− initSideBySide(createContext($imagesAfter[0], $imagesBefore[0]));
113+ initSideBySide(this, createContext($imagesAfter[0], $imagesBefore[0]));
114114 if ($imagesAfter.length > 0 && $imagesBefore.length > 0) {
115115 initSwipe(createContext($imagesAfter[1], $imagesBefore[1]));
116116 initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));
117117 }
118118
119− $container.find('> .image-diff-tabs').removeClass('is-loading');
119+ this.querySelector(':scope > .image-diff-tabs')?.classList.remove('is-loading');
120120
121− function initSideBySide(sizes) {
121+ function initSideBySide(container, sizes) {
122122 let factor = 1;
123123 if (sizes.max.width > (diffContainerWidth - 24) / 2) {
124124 factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
@@ -126,13 +126,24 @@ export function initImageDiff() { · 1 unmodified line
126126
127127 const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;
128128 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');
132137 }
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');
136147 }
137148
138149 const image1 = sizes.$image1[0];
web_src/js/features/notification.js+5−7
@@ -1,5 +1,6 @@
11 import $ from 'jquery';
22 import {GET} from '../modules/fetch.js';
3+import {toggleElem} from '../utils/dom.js';
34
45 const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
56 let notificationSequenceNumber = 0;
@@ -177,14 +178,11 @@ async function updateNotificationCount() { · 171 unmodified lines
177178
178179 const data = await response.json();
179180
180− const $notificationCount = $('.notification_count');
181− if (data.new === 0) {
182− $notificationCount.addClass('tw-hidden');
183− } else {
184− $notificationCount.removeClass('tw-hidden');
185− }
181+ toggleElem('.notification_count', data.new !== 0);
186182
187− $notificationCount.text(`${data.new}`);
183+ for (const el of document.getElementsByClassName('notification_count')) {
184+ el.textContent = `${data.new}`;
185+ }
188186
189187 return `${data.new}`;
190188 } catch (error) {
web_src/js/features/repo-code.js+5−3
@@ -25,7 +25,9 @@ function getLineEls() { · 24 unmodified lines
2525 }
2626
2727 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+ }
2931
3032 // add hashchange to permalink
3133 const refInNewIssue = document.querySelector('a.ref-in-new-issue');
@@ -72,7 +74,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { · 40 unmodified lines
7274 classes.push(`[rel=L${i}]`);
7375 }
7476 $linesEls.filter(classes.join(',')).each(function () {
75− $(this).closest('tr').addClass('active');
77+ this.closest('tr').classList.add('active');
7678 });
7779 changeHash(`#L${a}-L${b}`);
7880
@@ -82,7 +84,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { · 3 unmodified lines
8284 return;
8385 }
8486 }
85− $selectionEndEl.closest('tr').addClass('active');
87+ $selectionEndEl[0].closest('tr').classList.add('active');
8688 changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
8789
8890 updateIssueHref($selectionEndEl[0].getAttribute('rel'));
web_src/js/features/repo-diff.js+31−24
@@ -7,7 +7,7 @@ import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js'; · 6 unmodified lines
77 import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
88 import {initImageDiff} from './imagediff.js';
99 import {showErrorToast} from '../modules/toast.js';
10−import {submitEventSubmitter} from '../utils/dom.js';
10+import {submitEventSubmitter, queryElemSiblings, hideElem, showElem} from '../utils/dom.js';
1111 import {POST, GET} from '../modules/fetch.js';
1212
1313 const {pageData, i18n} = window.config;
@@ -16,7 +16,6 @@ function initRepoDiffReviewButton() { · 2 unmodified lines
1616 const reviewBox = document.getElementById('review-box');
1717 if (!reviewBox) return;
1818
19− const $reviewBox = $(reviewBox);
2019 const counter = reviewBox.querySelector('.review-comments-counter');
2120 if (!counter) return;
2221
@@ -27,23 +26,27 @@ function initRepoDiffReviewButton() { · 4 unmodified lines
2726 const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1;
2827 counter.setAttribute('data-pending-comment-number', num);
2928 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+ });
3434 });
3535 });
3636 }
3737
3838 function initRepoDiffFileViewToggle() {
3939 $('.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');
4344
44− const $target = $($this.data('toggle-selector'));
45− $target.parent().children().addClass('tw-hidden');
46− $target.removeClass('tw-hidden');
45+ const target = document.querySelector(this.getAttribute('data-toggle-selector'));
46+ if (!target) return;
47+
48+ hideElem(queryElemSiblings(target));
49+ showElem(target);
4750 });
4851 }
4952
@@ -57,9 +60,9 @@ function initRepoDiffConversationForm() { · 7 unmodified lines
5760 return;
5861 }
5962
60− if ($form.hasClass('is-loading')) return;
63+ if (e.target.classList.contains('is-loading')) return;
6164 try {
62− $form.addClass('is-loading');
65+ e.target.classList.add('is-loading');
6366 const formData = new FormData($form[0]);
6467
6568 // If the form is submitted by a button, append the button's name and value to the form data.
@@ -76,10 +79,14 @@ function initRepoDiffConversationForm() { · 10 unmodified lines
7679 const {path, side, idx} = $newConversationHolder.data();
7780
7881 $form.closest('.conversation-holder').replaceWith($newConversationHolder);
82+ let selector;
7983 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}"]`;
8185 } 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');
8390 }
8491 $newConversationHolder.find('.dropdown').dropdown();
8592 initCompReactionSelector($newConversationHolder);
@@ -87,7 +94,7 @@ function initRepoDiffConversationForm() { · 1 unmodified line
8794 console.error('error when submitting conversation', e);
8895 showErrorToast(i18n.network_error);
8996 } finally {
90− $form.removeClass('is-loading');
97+ e.target.classList.remove('is-loading');
9198 }
9299 });
93100
@@ -147,13 +154,13 @@ function onShowMoreFiles() { · 53 unmodified lines
147154 }
148155
149156 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) {
152159 return;
153160 }
154161
155162 pageData.diffFileInfo.isLoadingNewData = true;
156− $target.addClass('disabled');
163+ target?.classList.add('disabled');
157164
158165 try {
159166 const response = await GET(url);
@@ -170,7 +177,7 @@ export async function loadMoreFiles(url) { · 10 unmodified lines
170177 console.error('Error:', error);
171178 showErrorToast('An error occurred while loading more files.');
172179 } finally {
173− $target.removeClass('disabled');
180+ target?.classList.remove('disabled');
174181 pageData.diffFileInfo.isLoadingNewData = false;
175182 }
176183 }
@@ -187,11 +194,11 @@ function initRepoDiffShowMore() { · 10 unmodified lines
187194 e.preventDefault();
188195 const $target = $(e.target);
189196
190− if ($target.hasClass('disabled')) {
197+ if (e.target.classList.contains('disabled')) {
191198 return;
192199 }
193200
194− $target.addClass('disabled');
201+ e.target.classList.add('disabled');
195202
196203 const url = $target.data('href');
197204
@@ -207,7 +214,7 @@ function initRepoDiffShowMore() { · 9 unmodified lines
207214 } catch (error) {
208215 console.error('Error:', error);
209216 } finally {
210− $target.removeClass('disabled');
217+ e.target.classList.remove('disabled');
211218 }
212219 });
213220 }
web_src/js/features/repo-editor.js+2−2
@@ -147,8 +147,8 @@ export function initRepoEditor() { · 146 unmodified lines
147147 silent: true,
148148 dirtyClass: dirtyFileClass,
149149 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);
152152 commitButton.disabled = !dirty;
153153 },
154154 });
web_src/js/features/repo-home.js+25−61
@@ -1,55 +1,53 @@
11 import $ from 'jquery';
22 import {stripTags} from '../utils.js';
3−import {hideElem, showElem} from '../utils/dom.js';
3+import {hideElem, queryElemChildren, showElem} from '../utils/dom.js';
44 import {POST} from '../modules/fetch.js';
5+import {showErrorToast} from '../modules/toast.js';
56
67 const {appSubUrl} = window.config;
78
89 export function initRepoTopicBar() {
910 const mgrBtn = document.getElementById('manage_topic');
1011 if (!mgrBtn) return;
12+
1113 const editDiv = document.getElementById('topic_edit');
1214 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;
2217
2318 mgrBtn.addEventListener('click', () => {
2419 hideElem(viewDiv);
2520 showElem(editDiv);
26− $topicDropdownSearch.trigger('focus');
21+ topicDropdown.querySelector('input.search').focus();
2722 });
2823
29− $('#cancel_topic_edit').on('click', () => {
24+ document.querySelector('#cancel_topic_edit').addEventListener('click', () => {
25+ lastErrorToast?.hideToast();
3026 hideElem(editDiv);
3127 showElem(viewDiv);
3228 mgrBtn.focus();
3329 });
3430
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;
3734
3835 const data = new FormData();
3936 data.append('topics', topics);
4037
41− const response = await POST(saveBtn.getAttribute('data-link'), {data});
38+ const response = await POST(e.target.getAttribute('data-link'), {data});
4239
4340 if (response.ok) {
4441 const responseData = await response.json();
4542 if (responseData.status === 'ok') {
46− $(viewDiv).children('.topic').remove();
43+ queryElemChildren(viewDiv, '.repo-topic', (el) => el.remove());
4744 if (topics.length) {
4845 const topicArray = topics.split(',');
4946 topicArray.sort();
5047 for (const topic of topicArray) {
48+ // it should match the code in repo/home.tmpl
5149 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');
5351 link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`;
5452 link.textContent = topic;
5553 mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button
@@ -59,27 +57,23 @@ export function initRepoTopicBar() { · 3 unmodified lines
5957 showElem(viewDiv);
6058 }
6159 } else if (response.status === 422) {
60+ // how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save"
6261 const responseData = await response.json();
62+ lastErrorToast = showErrorToast(responseData.message, {duration: 5000});
6363 if (responseData.invalidTopics.length > 0) {
64− topicPrompts.formatPrompt = responseData.message;
65−
6664 const {invalidTopics} = responseData;
67− const $topicLabels = $topicDropdown.children('a.ui.label');
65+ const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label');
6866 for (const [index, value] of topics.split(',').entries()) {
6967 if (invalidTopics.includes(value)) {
70− $topicLabels.eq(index).removeClass('green').addClass('red');
68+ topicLabels[index].classList.remove('green');
69+ topicLabels[index].classList.add('red');
7170 }
7271 }
73− } else {
74− topicPrompts.countPrompt = responseData.message;
7572 }
7673 }
77−
78− // Always validate the form
79− $topicForm.form('validate form');
8074 });
8175
82− $topicDropdown.dropdown({
76+ $(topicDropdown).dropdown({
8377 allowAdditions: true,
8478 forceSelection: false,
8579 fullTextSearch: 'exact',
@@ -102,9 +96,9 @@ export function initRepoTopicBar() { · 16 unmodified lines
10296 const query = stripTags(this.urlData.query.trim());
10397 let found_query = false;
10498 const current_topics = [];
105− $topicDropdown.find('a.label.visible').each((_, el) => {
99+ for (const el of queryElemChildren(topicDropdown, 'a.ui.label.visible')) {
106100 current_topics.push(el.getAttribute('data-value'));
107− });
101+ }
108102
109103 if (res.topics) {
110104 let found = false;
@@ -146,38 +140,8 @@ export function initRepoTopicBar() { · 35 unmodified lines
146140 },
147141 onAdd(addedValue, _addedText, $addedChoice) {
148142 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);
181145 },
182146 });
183147 }
web_src/js/features/repo-issue-content.js+5−2
@@ -2,6 +2,7 @@ import $ from 'jquery'; · 1 unmodified line
22 import {svg} from '../svg.js';
33 import {showErrorToast} from '../modules/toast.js';
44 import {GET, POST} from '../modules/fetch.js';
5+import {showElem} from '../utils/dom.js';
56
67 const {appSubUrl} = window.config;
78 let i18nTextEdited;
@@ -73,10 +74,12 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH · 65 unmodified lines
7374 const response = await GET(url);
7475 const resp = await response.json();
7576
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;
7780 // there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden.
7881 if (resp.canSoftDelete) {
79− $dialog.find('.dialog-header-options').removeClass('tw-hidden');
82+ showElem($dialog.find('.dialog-header-options'));
8083 }
8184 } catch (error) {
8285 console.error('Error:', error);
web_src/js/features/repo-issue-list.js+15−7
@@ -6,6 +6,7 @@ import {confirmModal} from './comp/ConfirmModal.js'; · 5 unmodified lines
66 import {showErrorToast} from '../modules/toast.js';
77 import {createSortable} from '../modules/sortable.js';
88 import {DELETE, POST} from '../modules/fetch.js';
9+import {parseDom} from '../utils.js';
910
1011 function initRepoIssueListCheckboxes() {
1112 const issueSelectAll = document.querySelector('.issue-checkbox-all');
@@ -129,22 +130,29 @@ function initRepoIssueListAuthorDropdown() { · 117 unmodified lines
129130 const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');
130131 $searchDropdown.dropdown('internal', 'setup', dropdownSetup);
131132 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+ }
134138
135139 const newMenuHtml = dropdownTemplates.menu(values, $searchDropdown.dropdown('setting', 'fields'), true /* html */, $searchDropdown.dropdown('setting', 'className'));
136140 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+ }
139145 const div = document.createElement('div');
140146 div.classList.add('divider', 'dynamic-item');
141− $menu[0].append(div, ...$newMenuItems);
147+ menu.append(div, ...newMenuItems);
142148 }
143149 $searchDropdown.dropdown('refresh');
144150 // defer our selection to the next tick, because dropdown will set the selection item after this `menu` function
145151 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');
148156 }, 0);
149157 };
150158 }
web_src/js/features/repo-issue.js+104−95
@@ -158,17 +158,22 @@ export function initRepoIssueSidebarList() { · 157 unmodified lines
158158
159159 export function initRepoIssueCommentDelete() {
160160 // 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'))) {
164167 try {
165− const response = await POST($this.data('url'));
168+ const response = await POST(deleteButton.getAttribute('data-url'));
166169 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+
170175 // Check if this was a pending comment.
171− if ($conversationHolder.find('.pending-label').length) {
176+ if (conversationHolder?.querySelector('.pending-label')) {
172177 const counter = document.querySelector('#review-box .review-comments-counter');
173178 let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
174179 num = Math.max(num, 0);
@@ -176,29 +181,32 @@ export function initRepoIssueCommentDelete() { · 1 unmodified line
176181 counter.textContent = String(num);
177182 }
178183
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+
185192 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');
187194 } 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');
189196 }
190− $conversationHolder.remove();
197+
198+ conversationHolder.remove();
191199 }
200+
192201 // 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');
196205 }
197206 } catch (error) {
198207 console.error(error);
199208 }
200209 }
201− return false;
202210 });
203211 }
204212
@@ -222,32 +230,35 @@ export function initRepoIssueDependencyDelete() { · 17 unmodified lines
222230
223231 export function initRepoIssueCodeCommentCancel() {
224232 // 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'));
230240 } else {
231− $form.closest('.comment-code-cloud').remove();
241+ form.closest('.comment-code-cloud')?.remove();
232242 }
233243 });
234244 }
235245
236246 export function initRepoPullRequestUpdate() {
237247 // 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) {
240252 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');
244255 let response;
245256 try {
246− response = await POST($this.data('do'));
257+ response = await POST(this.getAttribute('data-do'));
247258 } catch (error) {
248259 console.error(error);
249260 } finally {
250− $this.removeClass('is-loading');
261+ this.classList.remove('is-loading');
251262 }
252263 let data;
253264 try {
@@ -266,10 +277,13 @@ export function initRepoPullRequestUpdate() { · 12 unmodified lines
266277
267278 $('.update-button > .dropdown').dropdown({
268279 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);
273287 }
274288 },
275289 });
@@ -282,32 +296,26 @@ export function initRepoPullRequestMergeInstruction() { · 6 unmodified lines
282296 }
283297
284298 export function initRepoPullRequestAllowMaintainerEdit() {
285− const checkbox = document.getElementById('allow-edits-from-maintainers');
286− if (!checkbox) return;
287−
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');
299+ const wrapper = document.getElementById('allow-edits-from-maintainers');
300+ if (!wrapper) return;
301+
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');
309311 }
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+ }
311319 });
312320 }
313321
@@ -373,10 +381,10 @@ export function initRepoIssueComments() { · 59 unmodified lines
373381
374382 $('.re-request-review').on('click', async function (e) {
375383 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');
380388
381389 await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);
382390 window.location.reload();
@@ -403,7 +411,7 @@ export function initRepoIssueComments() { · 20 unmodified lines
403411 export async function handleReply($el) {
404412 hideElem($el);
405413 const $form = $el.closest('.comment-code-cloud').find('.comment-form');
406− $form.removeClass('tw-hidden');
414+ showElem($form);
407415
408416 const $textarea = $form.find('textarea');
409417 let editor = getComboMarkdownEditor($textarea);
@@ -460,20 +468,20 @@ export function initRepoPullRequestReview() { · 50 unmodified lines
460468
461469 $(document).on('click', '.show-outdated', function (e) {
462470 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}`);
468476 });
469477
470478 $(document).on('click', '.hide-outdated', function (e) {
471479 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}`);
477485 });
478486
479487 $(document).on('click', 'button.comment-form-reply', async function (e) {
@@ -510,18 +518,19 @@ export function initRepoPullRequestReview() { · 30 unmodified lines
510518 }
511519
512520 $(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
514522 e.preventDefault();
515523
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');
522530
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')) {
525534 $ntr = $(`
526535 <tr class="add-comment" data-line-type="${lineType}">
527536 ${isSplit ? `
@@ -531,7 +540,7 @@ export function initRepoPullRequestReview() { · 3 unmodified lines
531540 <td class="add-comment-left add-comment-right" colspan="5"></td>
532541 `}
533542 </tr>`);
534− $tr.after($ntr);
543+ $(tr).after($ntr);
535544 }
536545
537546 const $td = $ntr.find(`.add-comment-${side}`);
@@ -617,13 +626,13 @@ export function initRepoIssueTitleEdit() { · 79 unmodified lines
617626
618627 const editTitleToggle = function () {
619628 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');
627636 $editInput[0].focus();
628637 $editInput[0].select();
629638 return false;
web_src/js/features/repo-projects.js+23−22
@@ -94,47 +94,46 @@ async function initRepoProjectSortable() { · 93 unmodified lines
9494 }
9595
9696 export function initRepoProject() {
97− if (!$('.repository.projects').length) {
97+ if (!document.querySelector('.repository.projects')) {
9898 return;
9999 }
100100
101101 const _promise = initRepoProjectSortable();
102102
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;
109110
110− const bgColor = $boardColumn[0].style.backgroundColor;
111111 if (bgColor) {
112− setLabelColor($projectHeader, rgbToHex(bgColor));
112+ setLabelColor(projectHeader, rgbToHex(bgColor));
113113 }
114114
115− $(this).find('.edit-project-column-button').on('click', async function (e) {
115+ modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) {
116116 e.preventDefault();
117−
118117 try {
119− await PUT($(this).data('url'), {
118+ await PUT(this.getAttribute('data-url'), {
120119 data: {
121− title: $projectTitleInput.val(),
122− color: $projectColorInput.val(),
120+ title: projectTitleInput?.value,
121+ color: projectColorInput?.value,
123122 },
124123 });
125124 } catch (error) {
126125 console.error(error);
127126 } 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);
132131 }
133− $boardColumn[0].style = `background: ${$projectColorInput.val()} !important`;
132+ boardColumn.style = `background: ${projectColorInput.value} !important`;
134133 $('.ui.modal').modal('hide');
135134 }
136135 });
137− });
136+ }
138137
139138 $('.default-project-column-modal').each(function () {
140139 const $boardColumn = $(this).closest('.project-column');
@@ -187,9 +186,11 @@ export function initRepoProject() { · 46 unmodified lines
187186 function setLabelColor(label, color) {
188187 const {r, g, b} = tinycolor(color).toRgb();
189188 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');
191191 } else {
192− label.removeClass('light-label').addClass('dark-label');
192+ label.classList.remove('light-label');
193+ label.classList.add('dark-label');
193194 }
194195 }
195196
web_src/js/features/repo-settings.js+18−13
@@ -77,18 +77,24 @@ export function initRepoSettingGitHook() { · 76 unmodified lines
7777 }
7878
7979 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);
9298 });
9399
94100 // show the `Matched` mark for the status checks that match the pattern
@@ -106,7 +112,6 @@ export function initRepoSettingBranches() { · 11 unmodified lines
106112 break;
107113 }
108114 }
109−
110115 toggleElem(el, matched);
111116 }
112117 };
web_src/js/index.js+2−0
@@ -86,6 +86,7 @@ import {initRepoRecentCommits} from './features/recent-commits.js'; · 85 unmodified lines
8686 import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
8787 import {initDirAuto} from './modules/dirauto.js';
8888 import {initRepositorySearch} from './features/repo-search.js';
89+import {initColorPickers} from './features/colorpicker.js';
8990
9091 // Init Gitea's Fomantic settings
9192 initGiteaFomantic();
@@ -188,4 +189,5 @@ onDomReady(() => { · 96 unmodified lines
188189 initRepoDiffView();
189190 initPdfViewer();
190191 initScopedAccessTokenCategories();
192+ initColorPickers();
191193 });
web_src/js/modules/fomantic.js+0−2
@@ -11,8 +11,6 @@ export const fomanticMobileScreen = window.matchMedia('only screen and (max-widt · 10 unmodified lines
1111 export function initGiteaFomantic() {
1212 // Silence fomantic's error logging when tabs are used without a target content element
1313 $.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;
1614
1715 // By default, use "exact match" for full text search
1816 $.fn.dropdown.settings.fullTextSearch = 'exact';
web_src/js/modules/fomantic/aria.md+6−11
@@ -41,24 +41,19 @@ The ideal checkboxes should be: · 40 unmodified lines
4141 <label><input type="checkbox"> ... </label>
4242 ```
4343
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:
4845
4946 ```html
5047 <div class="ui checkbox">
51− <input type="checkbox"> <!-- class "hidden" will be added by $.checkbox() -->
48+ <input type="checkbox">
5249 <label>...</label>
5350 </div>
5451 ```
5552
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.
6257
6358 # Fomantic Dropdown
6459
web_src/js/modules/fomantic/checkbox.js+18−32
@@ -1,38 +1,24 @@
1−import $ from 'jquery';
21 import {generateAriaId} from './base.js';
32
4−const ariaPatchKey = '_giteaAriaPatchCheckbox';
5−const fomanticCheckboxFn = $.fn.checkbox;
6−
7−// use our own `$.fn.checkbox` to patch Fomantic's checkbox module
83 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');
1312
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');
2123 }
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);
3824 }
web_src/js/modules/fomantic/dropdown.js+1−1
@@ -207,7 +207,7 @@ function attachDomEvents(dropdown, focusable, menu) { · 206 unmodified lines
207207 if (!$item) $item = $(menu).find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item
208208 // if the selected item is clickable, then trigger the click event.
209209 // 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();
211211 }
212212 });
213213
web_src/js/modules/tippy.js+4−3
@@ -3,11 +3,12 @@ import {isDocumentFragmentOrElementNode} from '../utils/dom.js'; · 2 unmodified lines
33 import {formatDatetime} from '../utils/time.js';
44
55 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>`;
67
78 export function createTippy(target, opts = {}) {
89 // the callback functions should be destructured from opts,
910 // 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;
1112
1213 const instance = tippy(target, {
1314 appendTo: document.body,
@@ -35,9 +36,9 @@ export function createTippy(target, opts = {}) { · 21 unmodified lines
3536 visibleInstances.add(instance);
3637 return onShow?.(instance);
3738 },
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),
3940 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"
4142 plugins: [followCursor],
4243 ...other,
4344 });
web_src/js/modules/toast.js+1−0
@@ -39,6 +39,7 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, .. · 38 unmodified lines
3939
4040 toast.showToast();
4141 toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
42+ return toast;
4243 }
4344
4445 export function showInfoToast(message, opts) {
web_src/js/utils/dom.js+16−2
@@ -51,8 +51,22 @@ export function isElemHidden(el) { · 50 unmodified lines
5151 return res[0];
5252 }
5353
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);
5670 }
5771
5872 export function onDomReady(cb) {
webpack.config.js+0−7
@@ -196,13 +196,6 @@ export default { · 195 unmodified lines
196196 filename: 'fonts/[name].[contenthash:8][ext]',
197197 },
198198 },
199− {
200− test: /\.png$/i,
201− type: 'asset/resource',
202− generator: {
203− filename: 'img/webpack/[name].[contenthash:8][ext]',
204− },
205− },
206199 ],
207200 },
208201 plugins: [