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
22 func (ScheduleList).GetRepoIDs22 func (ScheduleList).GetRepoIDs
23 func (ScheduleList).LoadTriggerUser23 func (ScheduleList).LoadTriggerUser
24 func (ScheduleList).LoadRepos24 func (ScheduleList).LoadRepos
25 func GetVariableByID
26 25
27package "code.gitea.io/gitea/models/asymkey"26package "code.gitea.io/gitea/models/asymkey"
28 func (ErrGPGKeyAccessDenied).Error27 func (ErrGPGKeyAccessDenied).Error
.dockerignore+0−1
@@ -77,7 +77,6 @@ cpu.out · 76 unmodified lines
77/public/assets/css77/public/assets/css
78/public/assets/fonts78/public/assets/fonts
79/public/assets/img/avatar79/public/assets/img/avatar
80/public/assets/img/webpack
81/vendor80/vendor
82/web_src/fomantic/node_modules81/web_src/fomantic/node_modules
83/web_src/fomantic/build/*82/web_src/fomantic/build/*
.eslintrc.yaml+1−0
@@ -3,6 +3,7 @@ reportUnusedDisableDirectives: true · 2 unmodified lines
3 3
4ignorePatterns:4ignorePatterns:
5 - /web_src/js/vendor5 - /web_src/js/vendor
6 - /web_src/fomantic
6 7
7parserOptions:8parserOptions:
8 sourceType: module9 sourceType: module
.gitignore+0−1
@@ -83,7 +83,6 @@ cpu.out · 82 unmodified lines
83/public/assets/css83/public/assets/css
84/public/assets/fonts84/public/assets/fonts
85/public/assets/licenses.txt85/public/assets/licenses.txt
86/public/assets/img/webpack
87/vendor86/vendor
88/web_src/fomantic/node_modules87/web_src/fomantic/node_modules
89/web_src/fomantic/build/*88/web_src/fomantic/build/*
.ignore+3−1
@@ -4,6 +4,8 @@ · 3 unmodified lines
4/modules/options/bindata.go4/modules/options/bindata.go
5/modules/public/bindata.go5/modules/public/bindata.go
6/modules/templates/bindata.go6/modules/templates/bindata.go
7/vendor7/options/gitignore
8/options/license
8/public/assets9/public/assets
10/vendor
9node_modules11node_modules
.stylelintrc.yaml+0−223
@@ -1,223 +0,0 @@
1plugins:
2 - stylelint-declaration-strict-value
3 - stylelint-declaration-block-no-ignored-properties
4 - "@stylistic/stylelint-plugin"
5
6ignoreFiles:
7 - "**/*.go"
8
9overrides:
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
19rules:
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
130WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)130WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
131WEBPACK_CONFIGS := webpack.config.js tailwind.config.js131WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
132WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css132WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
133WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack133WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
134 134
135BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go135BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
136BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))136BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
custom/conf/app.example.ini+7−0
@@ -1503,6 +1503,11 @@ LEVEL = Info · 1502 unmodified lines
1503;; - manage_ssh_keys: a user cannot configure ssh keys1503;; - manage_ssh_keys: a user cannot configure ssh keys
1504;; - manage_gpg_keys: a user cannot configure gpg keys1504;; - manage_gpg_keys: a user cannot configure gpg keys
1505;USER_DISABLED_FEATURES =1505;USER_DISABLED_FEATURES =
1506;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
1507;; - deletion: a user cannot delete their own account
1508;; - manage_ssh_keys: a user cannot configure ssh keys
1509;; - manage_gpg_keys: a user cannot configure gpg keys
1510;;EXTERNAL_USER_DISABLE_FEATURES =
1506 1511
1507;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1512;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1508;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1513;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -2326,6 +2331,8 @@ LEVEL = Info · 817 unmodified lines
2326;SHOW_FOOTER_VERSION = true2331;SHOW_FOOTER_VERSION = true
2327;; Show template execution time in the footer2332;; Show template execution time in the footer
2328;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true2333;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
2334;; Show the "powered by" text in the footer
2335;SHOW_FOOTER_POWERED_BY = true
2329;; Generate sitemap. Defaults to `true`.2336;; Generate sitemap. Defaults to `true`.
2330;ENABLE_SITEMAP = true2337;ENABLE_SITEMAP = true
2331;; Enable/Disable RSS/Atom feed2338;; Enable/Disable RSS/Atom feed
models/actions/variable.go+16−11
@@ -6,13 +6,11 @@ package actions · 5 unmodified lines
6import (6import (
7 "context"7 "context"
8 "errors"8 "errors"
9 "fmt"
10 "strings"9 "strings"
11 10
12 "code.gitea.io/gitea/models/db"11 "code.gitea.io/gitea/models/db"
13 "code.gitea.io/gitea/modules/log"12 "code.gitea.io/gitea/modules/log"
14 "code.gitea.io/gitea/modules/timeutil"13 "code.gitea.io/gitea/modules/timeutil"
15 "code.gitea.io/gitea/modules/util"
16 14
17 "xorm.io/builder"15 "xorm.io/builder"
18)16)
@@ -55,24 +53,24 @@ type FindVariablesOpts struct { · 36 unmodified lines
55 db.ListOptions53 db.ListOptions
56 OwnerID int6454 OwnerID int64
57 RepoID int6455 RepoID int64
56 Name string
58}57}
59 58
60func (opts FindVariablesOpts) ToConds() builder.Cond {59func (opts FindVariablesOpts) ToConds() builder.Cond {
61 cond := builder.NewCond()60 cond := builder.NewCond()
61 // Since we now support instance-level variables,
62 // there is no need to check for null values for `owner_id` and `repo_id`
62 cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})63 cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
63 cond = cond.And(builder.Eq{"repo_id": opts.RepoID})64 cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
65
66 if opts.Name != "" {
67 cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)})
68 }
64 return cond69 return cond
65}70}
66 71
67func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) {72func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) {
68 var variable ActionVariable73 return db.Find[ActionVariable](ctx, opts)
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
76}74}
77 75
78func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {76func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
@@ -84,6 +82,13 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) · 5 unmodified lines
84 return count != 0, err82 return count != 0, err
85}83}
86 84
85func 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
87func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {92func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
88 variables := map[string]string{}93 variables := map[string]string{}
89 94
models/asymkey/gpg_key_common.go+10−0
@@ -134,3 +134,13 @@ func extractSignature(s string) (*packet.Signature, error) { · 133 unmodified lines
134 }134 }
135 return sig, nil135 return sig, nil
136}136}
137
138func 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
123 }123 }
124 }124 }
125 125
126 keyID := ""126 keyID := tryGetKeyIDFromSignature(sig)
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 }
133 defaultReason := NoKeyFound127 defaultReason := NoKeyFound
134 128
135 // First check if the sig has a keyID and if so just look at that129 // 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
11 "code.gitea.io/gitea/models/unittest"11 "code.gitea.io/gitea/models/unittest"
12 user_model "code.gitea.io/gitea/models/user"12 user_model "code.gitea.io/gitea/models/user"
13 "code.gitea.io/gitea/modules/timeutil"13 "code.gitea.io/gitea/modules/timeutil"
14 "code.gitea.io/gitea/modules/util"
14 15
16 "github.com/keybase/go-crypto/openpgp/packet"
15 "github.com/stretchr/testify/assert"17 "github.com/stretchr/testify/assert"
16)18)
17 19
@@ -391,3 +393,13 @@ epiDVQ== · 373 unmodified lines
391 assert.Equal(t, time.Unix(1586105389, 0), expire)393 assert.Equal(t, time.Unix(1586105389, 0), expire)
392 }394 }
393}395}
396
397func 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
46 return "", ErrGPGKeyNotExist{}46 return "", ErrGPGKeyNotExist{}
47 }47 }
48 48
49 if err := key.LoadSubKeys(ctx); err != nil {
50 return "", err
51 }
52
49 sig, err := extractSignature(signature)53 sig, err := extractSignature(signature)
50 if err != nil {54 if err != nil {
51 return "", ErrGPGInvalidTokenSignature{55 return "", ErrGPGInvalidTokenSignature{
models/user/user.go+18−0
@@ -1246,3 +1246,21 @@ func GetOrderByName() string { · 1245 unmodified lines
1246 }1246 }
1247 return "name"1247 return "name"
1248}1248}
1249
1250// IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the
1251// user if applicable
1252func 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
1260func 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
16 "code.gitea.io/gitea/models/unittest"16 "code.gitea.io/gitea/models/unittest"
17 user_model "code.gitea.io/gitea/models/user"17 user_model "code.gitea.io/gitea/models/user"
18 "code.gitea.io/gitea/modules/auth/password/hash"18 "code.gitea.io/gitea/modules/auth/password/hash"
19 "code.gitea.io/gitea/modules/container"
19 "code.gitea.io/gitea/modules/optional"20 "code.gitea.io/gitea/modules/optional"
20 "code.gitea.io/gitea/modules/setting"21 "code.gitea.io/gitea/modules/setting"
21 "code.gitea.io/gitea/modules/structs"22 "code.gitea.io/gitea/modules/structs"
@@ -542,3 +543,37 @@ func Test_NormalizeUserFromEmail(t *testing.T) { · 520 unmodified lines
542 }543 }
543 }544 }
544}545}
546
547func 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
47 return nil47 return nil
48 }48 }
49 49
50 if c.Encoding != "" && c.Encoding != "UTF-8" {
51 if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil {
52 return nil
53 }
54 }
55
50 if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {56 if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
51 return nil57 return nil
52 }58 }
modules/git/commit_reader.go+2−0
@@ -84,6 +84,8 @@ readLoop: · 83 unmodified lines
84 commit.Committer = &Signature{}84 commit.Committer = &Signature{}
85 commit.Committer.Decode(data)85 commit.Committer.Decode(data)
86 _, _ = payloadSB.Write(line)86 _, _ = payloadSB.Write(line)
87 case "encoding":
88 _, _ = payloadSB.Write(line)
87 case "gpgsig":89 case "gpgsig":
88 fallthrough90 fallthrough
89 case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.91 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
125 assert.EqualValues(t, commitFromReader, commitFromReader2)125 assert.EqualValues(t, commitFromReader, commitFromReader2)
126}126}
127 127
128func TestCommitWithEncodingFromReader(t *testing.T) {
129 commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
130tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
131parent 47b24e7ab977ed31c5a39989d570847d6d0052af
132author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
133committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
134encoding ISO-8859-1
135gpgsig -----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
150ISO-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
166iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
167Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
168gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
169zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr
170frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j
171FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd
172G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn
173SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
174yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
175jw4YcO5u
176=r3UU
177-----END PGP SIGNATURE-----
178`, commitFromReader.Signature.Signature)
179 assert.EqualValues(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
180parent 47b24e7ab977ed31c5a39989d570847d6d0052af
181author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
182committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
183encoding ISO-8859-1
184
185ISO-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
128func TestHasPreviousCommit(t *testing.T) {195func TestHasPreviousCommit(t *testing.T) {
129 bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")196 bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
130 197
modules/lfs/filesystem_client.go+2−2
@@ -44,7 +44,7 @@ func (c *FilesystemClient) Download(ctx context.Context, objects []Pointer, call · 43 unmodified lines
44 if err != nil {44 if err != nil {
45 return err45 return err
46 }46 }
47 47 defer f.Close()
48 if err := callback(p, f, nil); err != nil {48 if err := callback(p, f, nil); err != nil {
49 return err49 return err
50 }50 }
@@ -75,7 +75,7 @@ func (c *FilesystemClient) Upload(ctx context.Context, objects []Pointer, callba · 24 unmodified lines
75 if err != nil {75 if err != nil {
76 return err76 return err
77 }77 }
78 78 defer f.Close()
79 _, err = io.Copy(f, content)79 _, err = io.Copy(f, content)
80 80
81 return err81 return err
modules/markup/markdown/markdown_test.go+18−2
@@ -505,9 +505,17 @@ func TestMathBlock(t *testing.T) { · 504 unmodified lines
505 `\(a\) \(b\)`,505 `\(a\) \(b\)`,
506 `<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,506 `<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
507 },507 },
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 },
508 {516 {
509 `$a a$b b$`,517 `$a a$b b$`,
510 `<p><code class="language-math is-loading">a a$b b</code></p>` + nl,518 `<p>$a a$b b$</p>` + nl,
511 },519 },
512 {520 {
513 `a a$b b`,521 `a a$b b`,
@@ -515,7 +523,15 @@ func TestMathBlock(t *testing.T) { · 1 unmodified line
515 },523 },
516 {524 {
517 `a$b $a a$b b$`,525 `a$b $a a$b b$`,
518 `<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl,526 `<p>a$b $a a$b b$</p>` + nl,
527 },
528 {
529 "a$x$",
530 `<p>a$x$</p>` + nl,
531 },
532 {
533 "$x$a",
534 `<p>$x$a</p>` + nl,
519 },535 },
520 {536 {
521 "$$a$$",537 "$$a$$",
modules/markup/markdown/math/inline_parser.go+13−5
@@ -41,9 +41,12 @@ func (parser *inlineParser) Trigger() []byte { · 40 unmodified lines
41 return parser.start[0:1]41 return parser.start[0:1]
42}42}
43 43
44func isPunctuation(b byte) bool {
45 return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
46}
47
44func isAlphanumeric(b byte) bool {48func isAlphanumeric(b byte) bool {
45 // Github only cares about 0-9A-Za-z49 return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
46 return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
47}50}
48 51
49// Parse parses the current line and returns a result of parsing.52// 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
56 }59 }
57 60
58 precedingCharacter := block.PrecendingCharacter()61 precedingCharacter := block.PrecendingCharacter()
59 if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {62 if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
60 // need to exclude things like `a$` from being considered a start63 // need to exclude things like `a$` from being considered a start
61 return nil64 return nil
62 }65 }
@@ -75,14 +78,19 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. · 12 unmodified lines
75 ender += pos78 ender += pos
76 79
77 // Now we want to check the character at the end of our parser section80 // Now we want to check the character at the end of our parser section
78 // that is ender + len(parser.end)81 // that is ender + len(parser.end) and check if char before ender is '\'
79 pos = ender + len(parser.end)82 pos = ender + len(parser.end)
80 if len(line) <= pos {83 if len(line) <= pos {
81 break84 break
82 }85 }
83 if !isAlphanumeric(line[pos]) {86 suceedingCharacter := line[pos]
87 if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') {
88 return nil
89 }
90 if line[ender-1] != '\\' {
84 break91 break
85 }92 }
93
86 // move the pointer onwards94 // move the pointer onwards
87 ender += len(parser.end)95 ender += len(parser.end)
88 }96 }
modules/setting/admin.go+5−1
@@ -3,7 +3,9 @@ · 2 unmodified lines
3 3
4package setting4package setting
5 5
6import "code.gitea.io/gitea/modules/container"6import (
7 "code.gitea.io/gitea/modules/container"
8)
7 9
8// Admin settings10// Admin settings
9var Admin struct {11var Admin struct {
@@ -11,6 +13,7 @@ var Admin struct { · 1 unmodified line
11 DefaultEmailNotification string13 DefaultEmailNotification string
12 SendNotificationEmailOnNewUser bool14 SendNotificationEmailOnNewUser bool
13 UserDisabledFeatures container.Set[string]15 UserDisabledFeatures container.Set[string]
16 ExternalUserDisableFeatures container.Set[string]
14}17}
15 18
16func loadAdminFrom(rootCfg ConfigProvider) {19func loadAdminFrom(rootCfg ConfigProvider) {
@@ -18,6 +21,7 @@ func loadAdminFrom(rootCfg ConfigProvider) { · 1 unmodified line
18 Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)21 Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
19 Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")22 Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
20 Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)23 Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
24 Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...)
21}25}
22 26
23const (27const (
modules/setting/other.go+2−0
@@ -8,6 +8,7 @@ import "code.gitea.io/gitea/modules/log" · 7 unmodified lines
8type OtherConfig struct {8type OtherConfig struct {
9 ShowFooterVersion bool9 ShowFooterVersion bool
10 ShowFooterTemplateLoadTime bool10 ShowFooterTemplateLoadTime bool
11 ShowFooterPoweredBy bool
11 EnableFeed bool12 EnableFeed bool
12 EnableSitemap bool13 EnableSitemap bool
13}14}
@@ -15,6 +16,7 @@ type OtherConfig struct { · 1 unmodified line
15var Other = OtherConfig{16var Other = OtherConfig{
16 ShowFooterVersion: true,17 ShowFooterVersion: true,
17 ShowFooterTemplateLoadTime: true,18 ShowFooterTemplateLoadTime: true,
19 ShowFooterPoweredBy: true,
18 EnableSitemap: true,20 EnableSitemap: true,
19 EnableFeed: true,21 EnableFeed: true,
20}22}
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
4package structs
5
6// CreateVariableOption the option when creating variable
7// swagger:model
8type 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
17type 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
28type 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
109 "ShowFooterTemplateLoadTime": func() bool {109 "ShowFooterTemplateLoadTime": func() bool {
110 return setting.Other.ShowFooterTemplateLoadTime110 return setting.Other.ShowFooterTemplateLoadTime
111 },111 },
112 "ShowFooterPoweredBy": func() bool {
113 return setting.Other.ShowFooterPoweredBy
114 },
112 "AllowedReactions": func() []string {115 "AllowedReactions": func() []string {
113 return setting.UI.Reactions116 return setting.UI.Reactions
114 },117 },
modules/util/util.go+9−0
@@ -212,3 +212,12 @@ func ToFloat64(number any) (float64, error) { · 211 unmodified lines
212func ToPointer[T any](val T) *T {212func ToPointer[T any](val T) *T {
213 return &val213 return &val
214}214}
215
216func 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
235 val123 := 123235 val123 := 123
236 assert.False(t, &val123 == ToPointer(val123))236 assert.False(t, &val123 == ToPointer(val123))
237}237}
238
239func 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 @@
1Copyright 1989, 1990 Advanced Micro Devices, Inc.
2
3This software is the property of Advanced Micro Devices, Inc (AMD) which
4specifically grants the user the right to modify, use and distribute this
5software provided this notice is not removed or altered. All other rights
6are reserved by AMD.
7
8AMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS
9SOFTWARE. IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL
10DAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR
11USE OF THIS SOFTWARE.
options/license/OAR+12−0
@@ -0,0 +1,12 @@
1COPYRIGHT (c) 1989-2013, 2015.
2On-Line Applications Research Corporation (OAR).
3
4Permission to use, copy, modify, and distribute this software for any
5purpose without fee is hereby granted, provided that this entire notice
6is included in all copies of any software which is or includes a copy
7or modification of this software.
8
9THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
10WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION
11OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS
12SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
options/license/xzoom+12−0
@@ -0,0 +1,12 @@
1Copyright Itai Nahshon 1995, 1996.
2This program is distributed with no warranty.
3
4Source files for this program may be distributed freely.
5Modifications 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.
11Executable files may be distributed with sources, or with
12exact location where the source code can be obtained.
package-lock.json+23−9
@@ -9,7 +9,6 @@ · 8 unmodified lines
9 "@citation-js/plugin-bibtex": "0.7.9",9 "@citation-js/plugin-bibtex": "0.7.9",
10 "@citation-js/plugin-csl": "0.7.9",10 "@citation-js/plugin-csl": "0.7.9",
11 "@citation-js/plugin-software-formats": "0.6.1",11 "@citation-js/plugin-software-formats": "0.6.1",
12 "@claviska/jquery-minicolors": "2.3.6",
13 "@github/markdown-toolbar-element": "2.2.3",12 "@github/markdown-toolbar-element": "2.2.3",
14 "@github/relative-time-element": "4.4.0",13 "@github/relative-time-element": "4.4.0",
15 "@github/text-expander-element": "2.6.1",14 "@github/text-expander-element": "2.6.1",
@@ -54,6 +53,7 @@ · 38 unmodified lines
54 "toastify-js": "1.12.0",53 "toastify-js": "1.12.0",
55 "tributejs": "5.1.3",54 "tributejs": "5.1.3",
56 "uint8-to-base64": "0.2.0",55 "uint8-to-base64": "0.2.0",
56 "vanilla-colorful": "0.7.2",
57 "vue": "3.4.21",57 "vue": "3.4.21",
58 "vue-bar-graph": "2.0.0",58 "vue-bar-graph": "2.0.0",
59 "vue-chartjs": "5.3.0",59 "vue-chartjs": "5.3.0",
@@ -92,6 +92,7 @@ · 32 unmodified lines
92 "stylelint": "16.3.1",92 "stylelint": "16.3.1",
93 "stylelint-declaration-block-no-ignored-properties": "2.8.0",93 "stylelint-declaration-block-no-ignored-properties": "2.8.0",
94 "stylelint-declaration-strict-value": "1.10.4",94 "stylelint-declaration-strict-value": "1.10.4",
95 "stylelint-value-no-unknown-custom-properties": "6.0.1",
95 "svgo": "3.2.0",96 "svgo": "3.2.0",
96 "updates": "16.0.0",97 "updates": "16.0.0",
97 "vite-string-plugin": "1.1.5",98 "vite-string-plugin": "1.1.5",
@@ -395,14 +396,6 @@ · 297 unmodified lines
395 "node": ">=14.0.0"396 "node": ">=14.0.0"
396 }397 }
397 },398 },
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 },
406 "node_modules/@csstools/css-parser-algorithms": {399 "node_modules/@csstools/css-parser-algorithms": {
407 "version": "2.6.1",400 "version": "2.6.1",
408 "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",401 "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
@@ -11121,6 +11114,22 @@ · 10712 unmodified lines
11121 "stylelint": ">=7 <=16"11114 "stylelint": ">=7 <=16"
11122 }11115 }
11123 },11116 },
11117 "node_modules/stylelint-value-no-unknown-custom-properties": {
11118 "version": "6.0.1",
11119 "resolved": "https://registry.npmjs.org/stylelint-value-no-unknown-custom-properties/-/stylelint-value-no-unknown-custom-properties-6.0.1.tgz",
11120 "integrity": "sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==",
11121 "dev": true,
11122 "dependencies": {
11123 "postcss-value-parser": "^4.2.0",
11124 "resolve": "^1.22.8"
11125 },
11126 "engines": {
11127 "node": ">=18.12.0"
11128 },
11129 "peerDependencies": {
11130 "stylelint": ">=16"
11131 }
11132 },
11124 "node_modules/stylelint/node_modules/ansi-regex": {11133 "node_modules/stylelint/node_modules/ansi-regex": {
11125 "version": "6.0.1",11134 "version": "6.0.1",
11126 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",11135 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@@ -12090,6 +12099,11 @@ · 963 unmodified lines
12090 "builtins": "^1.0.3"12099 "builtins": "^1.0.3"
12091 }12100 }
12092 },12101 },
12102 "node_modules/vanilla-colorful": {
12103 "version": "0.7.2",
12104 "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz",
12105 "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg=="
12106 },
12093 "node_modules/vite": {12107 "node_modules/vite": {
12094 "version": "5.2.8",12108 "version": "5.2.8",
12095 "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",12109 "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
package.json+2−1
@@ -8,7 +8,6 @@ · 7 unmodified lines
8 "@citation-js/plugin-bibtex": "0.7.9",8 "@citation-js/plugin-bibtex": "0.7.9",
9 "@citation-js/plugin-csl": "0.7.9",9 "@citation-js/plugin-csl": "0.7.9",
10 "@citation-js/plugin-software-formats": "0.6.1",10 "@citation-js/plugin-software-formats": "0.6.1",
11 "@claviska/jquery-minicolors": "2.3.6",
12 "@github/markdown-toolbar-element": "2.2.3",11 "@github/markdown-toolbar-element": "2.2.3",
13 "@github/relative-time-element": "4.4.0",12 "@github/relative-time-element": "4.4.0",
14 "@github/text-expander-element": "2.6.1",13 "@github/text-expander-element": "2.6.1",
@@ -53,6 +52,7 @@ · 38 unmodified lines
53 "toastify-js": "1.12.0",52 "toastify-js": "1.12.0",
54 "tributejs": "5.1.3",53 "tributejs": "5.1.3",
55 "uint8-to-base64": "0.2.0",54 "uint8-to-base64": "0.2.0",
55 "vanilla-colorful": "0.7.2",
56 "vue": "3.4.21",56 "vue": "3.4.21",
57 "vue-bar-graph": "2.0.0",57 "vue-bar-graph": "2.0.0",
58 "vue-chartjs": "5.3.0",58 "vue-chartjs": "5.3.0",
@@ -91,6 +91,7 @@ · 32 unmodified lines
91 "stylelint": "16.3.1",91 "stylelint": "16.3.1",
92 "stylelint-declaration-block-no-ignored-properties": "2.8.0",92 "stylelint-declaration-block-no-ignored-properties": "2.8.0",
93 "stylelint-declaration-strict-value": "1.10.4",93 "stylelint-declaration-strict-value": "1.10.4",
94 "stylelint-value-no-unknown-custom-properties": "6.0.1",
94 "svgo": "3.2.0",95 "svgo": "3.2.0",
95 "updates": "16.0.0",96 "updates": "16.0.0",
96 "vite-string-plugin": "1.1.5",97 "vite-string-plugin": "1.1.5",
routers/api/v1/api.go+27−0
@@ -871,6 +871,15 @@ func Routes() *web.Route { · 870 unmodified lines
871 Delete(user.DeleteSecret)871 Delete(user.DeleteSecret)
872 })872 })
873 873
874 m.Group("/variables", func() {
875 m.Get("", user.ListVariables)
876 m.Combo("/{variablename}").
877 Get(user.GetVariable).
878 Delete(user.DeleteVariable).
879 Post(bind(api.CreateVariableOption{}), user.CreateVariable).
880 Put(bind(api.UpdateVariableOption{}), user.UpdateVariable)
881 })
882
874 m.Group("/runners", func() {883 m.Group("/runners", func() {
875 m.Get("/registration-token", reqToken(), user.GetRegistrationToken)884 m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
876 })885 })
@@ -990,6 +999,15 @@ func Routes() *web.Route { · 113 unmodified lines
990 Delete(reqToken(), reqOwner(), repo.DeleteSecret)999 Delete(reqToken(), reqOwner(), repo.DeleteSecret)
991 })1000 })
992 1001
1002 m.Group("/variables", func() {
1003 m.Get("", reqToken(), reqOwner(), repo.ListVariables)
1004 m.Combo("/{variablename}").
1005 Get(reqToken(), reqOwner(), repo.GetVariable).
1006 Delete(reqToken(), reqOwner(), repo.DeleteVariable).
1007 Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable).
1008 Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable)
1009 })
1010
993 m.Group("/runners", func() {1011 m.Group("/runners", func() {
994 m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)1012 m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
995 })1013 })
@@ -1393,6 +1411,15 @@ func Routes() *web.Route { · 397 unmodified lines
1393 Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)1411 Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
1394 })1412 })
1395 1413
1414 m.Group("/variables", func() {
1415 m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables)
1416 m.Combo("/{variablename}").
1417 Get(reqToken(), reqOrgOwnership(), org.GetVariable).
1418 Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable).
1419 Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable).
1420 Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable)
1421 })
1422
1396 m.Group("/runners", func() {1423 m.Group("/runners", func() {
1397 m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)1424 m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
1398 })1425 })
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
4package org
5
6import (
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
21func 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
73func 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
122func 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
166func 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
230func 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
7 "errors"7 "errors"
8 "net/http"8 "net/http"
9 9
10 actions_model "code.gitea.io/gitea/models/actions"
11 "code.gitea.io/gitea/models/db"
10 api "code.gitea.io/gitea/modules/structs"12 api "code.gitea.io/gitea/modules/structs"
11 "code.gitea.io/gitea/modules/util"13 "code.gitea.io/gitea/modules/util"
12 "code.gitea.io/gitea/modules/web"14 "code.gitea.io/gitea/modules/web"
15 "code.gitea.io/gitea/routers/api/v1/utils"
16 actions_service "code.gitea.io/gitea/services/actions"
13 "code.gitea.io/gitea/services/context"17 "code.gitea.io/gitea/services/context"
14 secret_service "code.gitea.io/gitea/services/secrets"18 secret_service "code.gitea.io/gitea/services/secrets"
15)19)
@@ -127,3 +131,295 @@ func DeleteSecret(ctx *context.APIContext) { · 111 unmodified lines
127 131
128 ctx.Status(http.StatusNoContent)132 ctx.Status(http.StatusNoContent)
129}133}
134
135// GetVariable get a repo-level variable
136func 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
189func 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
238func 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
305func 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
372func 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
11 "strings"11 "strings"
12 "time"12 "time"
13 13
14 actions_model "code.gitea.io/gitea/models/actions"
14 activities_model "code.gitea.io/gitea/models/activities"15 activities_model "code.gitea.io/gitea/models/activities"
15 "code.gitea.io/gitea/models/db"16 "code.gitea.io/gitea/models/db"
16 "code.gitea.io/gitea/models/organization"17 "code.gitea.io/gitea/models/organization"
@@ -30,6 +31,7 @@ import ( · 13 unmodified lines
30 "code.gitea.io/gitea/modules/validation"31 "code.gitea.io/gitea/modules/validation"
31 "code.gitea.io/gitea/modules/web"32 "code.gitea.io/gitea/modules/web"
32 "code.gitea.io/gitea/routers/api/v1/utils"33 "code.gitea.io/gitea/routers/api/v1/utils"
34 actions_service "code.gitea.io/gitea/services/actions"
33 "code.gitea.io/gitea/services/context"35 "code.gitea.io/gitea/services/context"
34 "code.gitea.io/gitea/services/convert"36 "code.gitea.io/gitea/services/convert"
35 "code.gitea.io/gitea/services/issue"37 "code.gitea.io/gitea/services/issue"
@@ -1027,6 +1029,9 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e · 991 unmodified lines
1027 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)1029 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
1028 return err1030 return err
1029 }1031 }
1032 if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
1033 log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
1034 }
1030 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)1035 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
1031 } else {1036 } else {
1032 if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {1037 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
1034 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)1039 ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
1035 return err1040 return err
1036 }1041 }
1042 if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) {
1043 if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil {
1044 log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
1045 }
1046 }
1037 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)1047 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
1038 }1048 }
1039 }1049 }
routers/api/v1/swagger/action.go+14−0
@@ -18,3 +18,17 @@ type swaggerResponseSecret struct { · 17 unmodified lines
18 // in:body18 // in:body
19 Body api.Secret `json:"body"`19 Body api.Secret `json:"body"`
20}20}
21
22// ActionVariable
23// swagger:response ActionVariable
24type swaggerResponseActionVariable struct {
25 // in:body
26 Body api.ActionVariable `json:"body"`
27}
28
29// VariableList
30// swagger:response VariableList
31type 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
199 199
200 // in:body200 // in:body
201 CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption201 CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption
202
203 // in:body
204 CreateVariableOption api.CreateVariableOption
205
206 // in:body
207 UpdateVariableOption api.UpdateVariableOption
202}208}
routers/api/v1/user/action.go+250−0
@@ -7,9 +7,13 @@ import ( · 6 unmodified lines
7 "errors"7 "errors"
8 "net/http"8 "net/http"
9 9
10 actions_model "code.gitea.io/gitea/models/actions"
11 "code.gitea.io/gitea/models/db"
10 api "code.gitea.io/gitea/modules/structs"12 api "code.gitea.io/gitea/modules/structs"
11 "code.gitea.io/gitea/modules/util"13 "code.gitea.io/gitea/modules/util"
12 "code.gitea.io/gitea/modules/web"14 "code.gitea.io/gitea/modules/web"
15 "code.gitea.io/gitea/routers/api/v1/utils"
16 actions_service "code.gitea.io/gitea/services/actions"
13 "code.gitea.io/gitea/services/context"17 "code.gitea.io/gitea/services/context"
14 secret_service "code.gitea.io/gitea/services/secrets"18 secret_service "code.gitea.io/gitea/services/secrets"
15)19)
@@ -101,3 +105,249 @@ func DeleteSecret(ctx *context.APIContext) { · 85 unmodified lines
101 105
102 ctx.Status(http.StatusNoContent)106 ctx.Status(http.StatusNoContent)
103}107}
108
109// CreateVariable create a user-level variable
110func 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
169func 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
228func 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
265func 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
309func 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
10 10
11 asymkey_model "code.gitea.io/gitea/models/asymkey"11 asymkey_model "code.gitea.io/gitea/models/asymkey"
12 "code.gitea.io/gitea/models/db"12 "code.gitea.io/gitea/models/db"
13 user_model "code.gitea.io/gitea/models/user"
13 "code.gitea.io/gitea/modules/setting"14 "code.gitea.io/gitea/modules/setting"
14 api "code.gitea.io/gitea/modules/structs"15 api "code.gitea.io/gitea/modules/structs"
15 "code.gitea.io/gitea/modules/web"16 "code.gitea.io/gitea/modules/web"
@@ -133,7 +134,7 @@ func GetGPGKey(ctx *context.APIContext) { · 117 unmodified lines
133 134
134// CreateUserGPGKey creates new GPG key to given user by ID.135// CreateUserGPGKey creates new GPG key to given user by ID.
135func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {136func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
136 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {137 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
137 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))138 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
138 return139 return
139 }140 }
@@ -274,7 +275,7 @@ func DeleteGPGKey(ctx *context.APIContext) { · 134 unmodified lines
274 // "404":275 // "404":
275 // "$ref": "#/responses/notFound"276 // "$ref": "#/responses/notFound"
276 277
277 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {278 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
278 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))279 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
279 return280 return
280 }281 }
routers/api/v1/user/key.go+2−2
@@ -199,7 +199,7 @@ func GetPublicKey(ctx *context.APIContext) { · 198 unmodified lines
199 199
200// CreateUserPublicKey creates new public key to given user by ID.200// CreateUserPublicKey creates new public key to given user by ID.
201func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {201func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
202 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {202 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
203 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))203 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
204 return204 return
205 }205 }
@@ -269,7 +269,7 @@ func DeletePublicKey(ctx *context.APIContext) { · 63 unmodified lines
269 // "404":269 // "404":
270 // "$ref": "#/responses/notFound"270 // "$ref": "#/responses/notFound"
271 271
272 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {272 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
273 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))273 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
274 return274 return
275 }275 }
routers/web/repo/setting/setting.go+12−0
@@ -13,6 +13,7 @@ import ( · 12 unmodified lines
13 "time"13 "time"
14 14
15 "code.gitea.io/gitea/models"15 "code.gitea.io/gitea/models"
16 actions_model "code.gitea.io/gitea/models/actions"
16 "code.gitea.io/gitea/models/db"17 "code.gitea.io/gitea/models/db"
17 "code.gitea.io/gitea/models/organization"18 "code.gitea.io/gitea/models/organization"
18 repo_model "code.gitea.io/gitea/models/repo"19 repo_model "code.gitea.io/gitea/models/repo"
@@ -29,6 +30,7 @@ import ( · 10 unmodified lines
29 "code.gitea.io/gitea/modules/util"30 "code.gitea.io/gitea/modules/util"
30 "code.gitea.io/gitea/modules/validation"31 "code.gitea.io/gitea/modules/validation"
31 "code.gitea.io/gitea/modules/web"32 "code.gitea.io/gitea/modules/web"
33 actions_service "code.gitea.io/gitea/services/actions"
32 asymkey_service "code.gitea.io/gitea/services/asymkey"34 asymkey_service "code.gitea.io/gitea/services/asymkey"
33 "code.gitea.io/gitea/services/context"35 "code.gitea.io/gitea/services/context"
34 "code.gitea.io/gitea/services/forms"36 "code.gitea.io/gitea/services/forms"
@@ -929,6 +931,10 @@ func SettingsPost(ctx *context.Context) { · 894 unmodified lines
929 return931 return
930 }932 }
931 933
934 if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
935 log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
936 }
937
932 ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))938 ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
933 939
934 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)940 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
947 return953 return
948 }954 }
949 955
956 if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) {
957 if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil {
958 log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
959 }
960 }
961
950 ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))962 ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
951 963
952 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)964 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
4package actions4package actions
5 5
6import (6import (
7 "errors"
8 "regexp"
9 "strings"
10
11 actions_model "code.gitea.io/gitea/models/actions"7 actions_model "code.gitea.io/gitea/models/actions"
12 "code.gitea.io/gitea/models/db"8 "code.gitea.io/gitea/models/db"
13 "code.gitea.io/gitea/modules/log"9 "code.gitea.io/gitea/modules/log"
14 "code.gitea.io/gitea/modules/web"10 "code.gitea.io/gitea/modules/web"
11 actions_service "code.gitea.io/gitea/services/actions"
15 "code.gitea.io/gitea/services/context"12 "code.gitea.io/gitea/services/context"
16 "code.gitea.io/gitea/services/forms"13 "code.gitea.io/gitea/services/forms"
17 secret_service "code.gitea.io/gitea/services/secrets"
18)14)
19 15
20func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {16func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
@@ -29,41 +25,16 @@ func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { · 8 unmodified lines
29 ctx.Data["Variables"] = variables25 ctx.Data["Variables"] = variables
30}26}
31 27
32// some regular expression of `variables` and `secrets`
33// reference to:
34// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
35// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
36var (
37 forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
38)
39
40func 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
48func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {28func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
49 form := web.GetForm(ctx).(*forms.EditVariableForm)29 form := web.GetForm(ctx).(*forms.EditVariableForm)
50 30
51 if err := secret_service.ValidateName(form.Name); err != nil {31 v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data)
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))
62 if err != nil {32 if err != nil {
63 log.Error("InsertVariable error: %v", err)33 log.Error("CreateVariable: %v", err)
64 ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))34 ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
65 return35 return
66 }36 }
37
67 ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))38 ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
68 ctx.JSONRedirect(redirectURL)39 ctx.JSONRedirect(redirectURL)
69}40}
@@ -72,23 +43,8 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { · 2 unmodified lines
72 id := ctx.ParamsInt64(":variable_id")43 id := ctx.ParamsInt64(":variable_id")
73 form := web.GetForm(ctx).(*forms.EditVariableForm)44 form := web.GetForm(ctx).(*forms.EditVariableForm)
74 45
75 if err := secret_service.ValidateName(form.Name); err != nil {46 if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok {
76 ctx.JSONError(err.Error())47 log.Error("UpdateVariable: %v", err)
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)
92 ctx.JSONError(ctx.Tr("actions.variables.update.failed"))48 ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
93 return49 return
94 }50 }
@@ -99,7 +55,7 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { · 4 unmodified lines
99func DeleteVariable(ctx *context.Context, redirectURL string) {55func DeleteVariable(ctx *context.Context, redirectURL string) {
100 id := ctx.ParamsInt64(":variable_id")56 id := ctx.ParamsInt64(":variable_id")
101 57
102 if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil {58 if err := actions_service.DeleteVariableByID(ctx, id); err != nil {
103 log.Error("Delete variable [%d] failed: %v", id, err)59 log.Error("Delete variable [%d] failed: %v", id, err)
104 ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))60 ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
105 return61 return
@@ -107,12 +63,3 @@ func DeleteVariable(ctx *context.Context, redirectURL string) { · 1 unmodified line
107 ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))63 ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
108 ctx.JSONRedirect(redirectURL)64 ctx.JSONRedirect(redirectURL)
109}65}
110
111func 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
7 "code.gitea.io/gitea/models/db"7 "code.gitea.io/gitea/models/db"
8 secret_model "code.gitea.io/gitea/models/secret"8 secret_model "code.gitea.io/gitea/models/secret"
9 "code.gitea.io/gitea/modules/log"9 "code.gitea.io/gitea/modules/log"
10 "code.gitea.io/gitea/modules/util"
10 "code.gitea.io/gitea/modules/web"11 "code.gitea.io/gitea/modules/web"
11 "code.gitea.io/gitea/routers/web/shared/actions"
12 "code.gitea.io/gitea/services/context"12 "code.gitea.io/gitea/services/context"
13 "code.gitea.io/gitea/services/forms"13 "code.gitea.io/gitea/services/forms"
14 secret_service "code.gitea.io/gitea/services/secrets"14 secret_service "code.gitea.io/gitea/services/secrets"
@@ -27,7 +27,7 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) { · 12 unmodified lines
27func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {27func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
28 form := web.GetForm(ctx).(*forms.AddSecretForm)28 form := web.GetForm(ctx).(*forms.AddSecretForm)
29 29
30 s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, actions.ReserveLineBreakForTextarea(form.Data))30 s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data))
31 if err != nil {31 if err != nil {
32 log.Error("CreateOrUpdateSecret failed: %v", err)32 log.Error("CreateOrUpdateSecret failed: %v", err)
33 ctx.JSONError(ctx.Tr("secrets.creation.failed"))33 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
244 244
245// DeleteAccount render user suicide page and response for delete user himself245// DeleteAccount render user suicide page and response for delete user himself
246func DeleteAccount(ctx *context.Context) {246func DeleteAccount(ctx *context.Context) {
247 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) {247 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) {
248 ctx.Error(http.StatusNotFound)248 ctx.Error(http.StatusNotFound)
249 return249 return
250 }250 }
@@ -328,7 +328,7 @@ func loadAccountData(ctx *context.Context) { · 77 unmodified lines
328 ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference328 ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
329 ctx.Data["ActivationsPending"] = pendingActivation329 ctx.Data["ActivationsPending"] = pendingActivation
330 ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm330 ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
331 ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures331 ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
332 332
333 if setting.Service.UserDeleteWithCommentsMaxTime != 0 {333 if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
334 ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()334 ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
routers/web/user/setting/keys.go+7−6
@@ -10,6 +10,7 @@ import ( · 9 unmodified lines
10 10
11 asymkey_model "code.gitea.io/gitea/models/asymkey"11 asymkey_model "code.gitea.io/gitea/models/asymkey"
12 "code.gitea.io/gitea/models/db"12 "code.gitea.io/gitea/models/db"
13 user_model "code.gitea.io/gitea/models/user"
13 "code.gitea.io/gitea/modules/base"14 "code.gitea.io/gitea/modules/base"
14 "code.gitea.io/gitea/modules/setting"15 "code.gitea.io/gitea/modules/setting"
15 "code.gitea.io/gitea/modules/web"16 "code.gitea.io/gitea/modules/web"
@@ -78,7 +79,7 @@ func KeysPost(ctx *context.Context) { · 62 unmodified lines
78 ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))79 ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))
79 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")80 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
80 case "gpg":81 case "gpg":
81 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {82 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
82 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))83 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
83 return84 return
84 }85 }
@@ -159,7 +160,7 @@ func KeysPost(ctx *context.Context) { · 74 unmodified lines
159 ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID))160 ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID))
160 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")161 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
161 case "ssh":162 case "ssh":
162 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {163 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
163 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))164 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
164 return165 return
165 }166 }
@@ -203,7 +204,7 @@ func KeysPost(ctx *context.Context) { · 37 unmodified lines
203 ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))204 ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
204 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")205 ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
205 case "verify_ssh":206 case "verify_ssh":
206 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {207 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
207 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))208 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
208 return209 return
209 }210 }
@@ -240,7 +241,7 @@ func KeysPost(ctx *context.Context) { · 30 unmodified lines
240func DeleteKey(ctx *context.Context) {241func DeleteKey(ctx *context.Context) {
241 switch ctx.FormString("type") {242 switch ctx.FormString("type") {
242 case "gpg":243 case "gpg":
243 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {244 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
244 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))245 ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
245 return246 return
246 }247 }
@@ -250,7 +251,7 @@ func DeleteKey(ctx *context.Context) { · 3 unmodified lines
250 ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))251 ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
251 }252 }
252 case "ssh":253 case "ssh":
253 if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {254 if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
254 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))255 ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
255 return256 return
256 }257 }
@@ -333,5 +334,5 @@ func loadKeysData(ctx *context.Context) { · 76 unmodified lines
333 334
334 ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")335 ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
335 ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")336 ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
336 ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures337 ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
337}338}
services/actions/notifier_helper.go+2−2
@@ -119,7 +119,7 @@ func notify(ctx context.Context, input *notifyInput) error { · 118 unmodified lines
119 log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)119 log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
120 return nil120 return nil
121 }121 }
122 if input.Repo.IsEmpty {122 if input.Repo.IsEmpty || input.Repo.IsArchived {
123 return nil123 return nil
124 }124 }
125 if unit_model.TypeActions.UnitGlobalDisabled() {125 if unit_model.TypeActions.UnitGlobalDisabled() {
@@ -536,7 +536,7 @@ func handleSchedules( · 410 unmodified lines
536 536
537// DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks537// DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
538func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {538func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
539 if repo.IsEmpty {539 if repo.IsEmpty || repo.IsArchived {
540 return nil540 return nil
541 }541 }
542 542
services/actions/schedule_tasks.go+5−0
@@ -66,6 +66,11 @@ func startTasks(ctx context.Context) error { · 65 unmodified lines
66 }66 }
67 }67 }
68 68
69 if row.Repo.IsArchived {
70 // Skip if the repo is archived
71 continue
72 }
73
69 cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)74 cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
70 if err != nil {75 if err != nil {
71 if repo_model.IsErrUnitTypeNotExist(err) {76 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
4package actions
5
6import (
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
17func 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
34func 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
50func DeleteVariableByID(ctx context.Context, variableID int64) error {
51 return actions_model.DeleteVariable(ctx, variableID)
52}
53
54func 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
75func 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
90var (
91 forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
92)
93
94func 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
967 for _, commit := range prInfo.Commits {967 for _, commit := range prInfo.Commits {
968 var committerOrAuthorName string968 var committerOrAuthorName string
969 var commitTime time.Time969 var commitTime time.Time
970 if commit.Committer != nil {970 if commit.Author != nil {
971 committerOrAuthorName = commit.Committer.Name
972 commitTime = commit.Committer.When
973 } else {
974 committerOrAuthorName = commit.Author.Name971 committerOrAuthorName = commit.Author.Name
975 commitTime = commit.Author.When972 commitTime = commit.Author.When
973 } else {
974 committerOrAuthorName = commit.Committer.Name
975 commitTime = commit.Committer.When
976 }976 }
977 977
978 commits = append(commits, CommitInfo{978 commits = append(commits, CommitInfo{
stylelint.config.js+246−0
@@ -0,0 +1,246 @@
1import {fileURLToPath} from 'node:url';
2
3const 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} */
10export 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
7 <dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>7 <dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>
8 <dd>8 <dd>
9 <div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}">9 <div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}">
10 <input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}>10 <input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}><label></label>
11 </div>11 </div>
12 </dd>12 </dd>
13 <div class="divider"></div>13 <div class="divider"></div>
14 <dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>14 <dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
15 <dd>15 <dd>
16 <div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">16 <div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">
17 <input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}>17 <input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label>
18 </div>18 </div>
19 </dd>19 </dd>
20 </dl>20 </dl>
templates/base/footer_content.tmpl+3−1
@@ -1,6 +1,8 @@
1<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">1<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">
2 <div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}">2 <div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}">
3 <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a>3 {{if ShowFooterPoweredBy}}
4 <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a>
5 {{end}}
4 {{if (or .ShowFooterVersion .PageIsAdmin)}}6 {{if (or .ShowFooterVersion .PageIsAdmin)}}
5 {{ctx.Locale.Tr "version"}}:7 {{ctx.Locale.Tr "version"}}:
6 {{if .IsAdmin}}8 {{if .IsAdmin}}
templates/devtest/gitea-ui.tmpl+1−1
@@ -102,7 +102,7 @@ · 101 unmodified lines
102 102
103 <div>103 <div>
104 <h1>Loading</h1>104 <h1>Loading</h1>
105 <div class="is-loading small-loading-icon tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>105 <div class="is-loading loading-icon-2px tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>
106 <div class="is-loading tw-border tw-border-secondary tw-py-4">106 <div class="is-loading tw-border tw-border-secondary tw-py-4">
107 <p>loading ...</p>107 <p>loading ...</p>
108 <p>loading ...</p>108 <p>loading ...</p>
templates/projects/view.tmpl+4−4
@@ -42,8 +42,8 @@ · 41 unmodified lines
42 42
43 <div class="field color-field">43 <div class="field color-field">
44 <label for="new_project_column_color_picker">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>44 <label for="new_project_column_color_picker">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
45 <div class="color picker column">45 <div class="js-color-picker-input column">
46 <input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">46 <input maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
47 {{template "repo/issue/label_precolors"}}47 {{template "repo/issue/label_precolors"}}
48 </div>48 </div>
49 </div>49 </div>
@@ -114,8 +114,8 @@ · 64 unmodified lines
114 114
115 <div class="field color-field">115 <div class="field color-field">
116 <label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>116 <label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
117 <div class="color picker column">117 <div class="js-color-picker-input column">
118 <input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">118 <input maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">
119 {{template "repo/issue/label_precolors"}}119 {{template "repo/issue/label_precolors"}}
120 </div>120 </div>
121 </div>121 </div>
templates/repo/commit_page.tmpl+2−2
@@ -164,9 +164,9 @@ · 163 unmodified lines
164 {{end}}164 {{end}}
165 {{end}}165 {{end}}
166 </div>166 </div>
167 <div class="ui horizontal list tw-flex tw-items-center">167 <div class="tw-flex tw-items-center">
168 {{if .Parents}}168 {{if .Parents}}
169 <div class="item">169 <div>
170 <span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>170 <span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>
171 {{range .Parents}}171 {{range .Parents}}
172 {{if $.PageIsWiki}}172 {{if $.PageIsWiki}}
templates/repo/commits_list.tmpl+1−1
@@ -16,7 +16,7 @@ · 15 unmodified lines
16 <td class="author tw-flex">16 <td class="author tw-flex">
17 {{$userName := .Author.Name}}17 {{$userName := .Author.Name}}
18 {{if .User}}18 {{if .User}}
19 {{if .User.FullName}}19 {{if and .User.FullName DefaultShowFullName}}
20 {{$userName = .User.FullName}}20 {{$userName = .User.FullName}}
21 {{end}}21 {{end}}
22 {{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>22 {{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
61 <span class="author tw-flex tw-items-center tw-mr-2">61 <span class="author tw-flex tw-items-center tw-mr-2">
62 {{$userName := $commit.Commit.Author.Name}}62 {{$userName := $commit.Commit.Author.Name}}
63 {{if $commit.User}}63 {{if $commit.User}}
64 {{if $commit.User.FullName}}64 {{if and $commit.User.FullName DefaultShowFullName}}
65 {{$userName = $commit.User.FullName}}65 {{$userName = $commit.User.FullName}}
66 {{end}}66 {{end}}
67 <span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>67 <span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
templates/repo/home.tmpl+11−12
@@ -18,22 +18,21 @@ · 17 unmodified lines
18 </div>18 </div>
19 </form>19 </form>
20 </div>20 </div>
21 <div class="tw-flex tw-items-center tw-flex-wrap tw-gap-1" id="repo-topics">21 <div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" 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}}22 {{/* it should match the code in issue-home.js */}}
23 {{range .Topics}}<a class="repo-topic ui large label" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
23 {{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}24 {{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
24 </div>25 </div>
25 {{end}}26 {{end}}
26 {{if and .Permission.IsAdmin (not .Repository.IsArchived)}}27 {{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
27 <div class="ui form tw-hidden tw-flex tw-flex-col tw-mt-4" id="topic_edit">28 <div class="ui form tw-hidden tw-flex tw-gap-2 tw-my-2" 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 tw-flex-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}}">
30 <input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">31 {{range .Topics}}
31 {{range .Topics}}32 {{/* keep the same layout as Fomantic UI generated labels */}}
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>
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}}
34 {{end}}35 <div class="text"></div>
35 <div class="text"></div>
36 </div>
37 </div>36 </div>
38 <div>37 <div>
39 <button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>38 <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
3 {{template "repo/header" .}}3 {{template "repo/header" .}}
4 <div class="ui container">4 <div class="ui container">
5 {{template "base/alert" .}}5 {{template "base/alert" .}}
6 <div class="navbar">6 <div class="issue-navbar">
7 {{template "repo/issue/navbar" .}}7 {{template "repo/issue/navbar" .}}
8 </div>8 </div>
9 <div class="divider"></div>9 <div class="divider"></div>
templates/repo/issue/labels.tmpl+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
2<div role="main" aria-label="{{.Title}}" class="page-content repository labels">2<div role="main" aria-label="{{.Title}}" class="page-content repository labels">
3 {{template "repo/header" .}}3 {{template "repo/header" .}}
4 <div class="ui container">4 <div class="ui container">
5 <div class="navbar tw-mb-4">5 <div class="issue-navbar tw-mb-4">
6 {{template "repo/issue/navbar" .}}6 {{template "repo/issue/navbar" .}}
7 {{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}7 {{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
8 <button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>8 <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
52 </div>52 </div>
53 <div class="field color-field">53 <div class="field color-field">
54 <label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>54 <label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
55 <div class="color picker column">55 <div class="column js-color-picker-input">
56 <input class="color-picker" name="color" value="#70c24a" required maxlength="7">56 <input name="color" value="#70c24a"placeholder="#c320f6" required maxlength="7">
57 {{template "repo/issue/label_precolors"}}57 {{template "repo/issue/label_precolors"}}
58 </div>58 </div>
59 </div>59 </div>
templates/repo/issue/labels/label_list.tmpl+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
8 {{ctx.Locale.Tr "repo.issues.filter_sort"}}8 {{ctx.Locale.Tr "repo.issues.filter_sort"}}
9 </span>9 </span>
10 {{svg "octicon-triangle-down" 14 "dropdown icon"}}10 {{svg "octicon-triangle-down" 14 "dropdown icon"}}
11 <div class="left menu">11 <div class="menu">
12 <a class="{{if or (eq .SortType "alphabetically") (not .SortType)}}active {{end}}item" href="?sort=alphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>12 <a class="{{if or (eq .SortType "alphabetically") (not .SortType)}}active {{end}}item" href="?sort=alphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
13 <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>13 <a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="?sort=reversealphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
14 <a class="{{if eq .SortType "leastissues"}}active {{end}}item" href="?sort=leastissues&state={{$.State}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_issues"}}</a>14 <a class="{{if eq .SortType "leastissues"}}active {{end}}item" href="?sort=leastissues&state={{$.State}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_issues"}}</a>
templates/repo/issue/labels/label_new.tmpl+2−2
@@ -27,8 +27,8 @@ · 26 unmodified lines
27 </div>27 </div>
28 <div class="field color-field">28 <div class="field color-field">
29 <label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>29 <label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
30 <div class="color picker column">30 <div class="js-color-picker-input column">
31 <input class="color-picker" name="color" value="#70c24a" required maxlength="7">31 <input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7">
32 {{template "repo/issue/label_precolors"}}32 {{template "repo/issue/label_precolors"}}
33 </div>33 </div>
34 </div>34 </div>
templates/repo/issue/milestone_new.tmpl+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
2<div role="main" aria-label="{{.Title}}" class="page-content repository new milestone">2<div role="main" aria-label="{{.Title}}" class="page-content repository new milestone">
3 {{template "repo/header" .}}3 {{template "repo/header" .}}
4 <div class="ui container">4 <div class="ui container">
5 <div class="navbar">5 <div class="issue-navbar">
6 {{template "repo/issue/navbar" .}}6 {{template "repo/issue/navbar" .}}
7 {{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}7 {{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}
8 <div class="ui right floated secondary menu">8 <div class="ui right floated secondary menu">
templates/repo/issue/view_content/sidebar.tmpl+1−1
@@ -684,7 +684,7 @@ · 683 unmodified lines
684 {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}684 {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
685 <div class="divider"></div>685 <div class="divider"></div>
686 <div class="inline field">686 <div class="inline field">
687 <div class="ui checkbox" id="allow-edits-from-maintainers"687 <div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
688 data-url="{{.Issue.Link}}"688 data-url="{{.Issue.Link}}"
689 data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"689 data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
690 data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"690 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
3{{else}}3{{else}}
4 {{if .LatestCommitUser}}4 {{if .LatestCommitUser}}
5 {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}5 {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}
6 {{if .LatestCommitUser.FullName}}6 {{if and .LatestCommitUser.FullName DefaultShowFullName}}
7 <a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>7 <a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
8 {{else}}8 {{else}}
9 <a class="muted author-wrapper" title="{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>9 <a class="muted author-wrapper" title="{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
templates/swagger/v1_json.tmpl+784−36
@@ -1741,6 +1741,232 @@ · 1740 unmodified lines
1741 }1741 }
1742 }1742 }
1743 },1743 },
1744 "/orgs/{org}/actions/variables": {
1745 "get": {
1746 "produces": [
1747 "application/json"
1748 ],
1749 "tags": [
1750 "organization"
1751 ],
1752 "summary": "Get an org-level variables list",
1753 "operationId": "getOrgVariablesList",
1754 "parameters": [
1755 {
1756 "type": "string",
1757 "description": "name of the organization",
1758 "name": "org",
1759 "in": "path",
1760 "required": true
1761 },
1762 {
1763 "type": "integer",
1764 "description": "page number of results to return (1-based)",
1765 "name": "page",
1766 "in": "query"
1767 },
1768 {
1769 "type": "integer",
1770 "description": "page size of results",
1771 "name": "limit",
1772 "in": "query"
1773 }
1774 ],
1775 "responses": {
1776 "200": {
1777 "$ref": "#/responses/VariableList"
1778 },
1779 "400": {
1780 "$ref": "#/responses/error"
1781 },
1782 "404": {
1783 "$ref": "#/responses/notFound"
1784 }
1785 }
1786 }
1787 },
1788 "/orgs/{org}/actions/variables/{variablename}": {
1789 "get": {
1790 "produces": [
1791 "application/json"
1792 ],
1793 "tags": [
1794 "organization"
1795 ],
1796 "summary": "Get an org-level variable",
1797 "operationId": "getOrgVariable",
1798 "parameters": [
1799 {
1800 "type": "string",
1801 "description": "name of the organization",
1802 "name": "org",
1803 "in": "path",
1804 "required": true
1805 },
1806 {
1807 "type": "string",
1808 "description": "name of the variable",
1809 "name": "variablename",
1810 "in": "path",
1811 "required": true
1812 }
1813 ],
1814 "responses": {
1815 "200": {
1816 "$ref": "#/responses/ActionVariable"
1817 },
1818 "400": {
1819 "$ref": "#/responses/error"
1820 },
1821 "404": {
1822 "$ref": "#/responses/notFound"
1823 }
1824 }
1825 },
1826 "put": {
1827 "consumes": [
1828 "application/json"
1829 ],
1830 "produces": [
1831 "application/json"
1832 ],
1833 "tags": [
1834 "organization"
1835 ],
1836 "summary": "Update an org-level variable",
1837 "operationId": "updateOrgVariable",
1838 "parameters": [
1839 {
1840 "type": "string",
1841 "description": "name of the organization",
1842 "name": "org",
1843 "in": "path",
1844 "required": true
1845 },
1846 {
1847 "type": "string",
1848 "description": "name of the variable",
1849 "name": "variablename",
1850 "in": "path",
1851 "required": true
1852 },
1853 {
1854 "name": "body",
1855 "in": "body",
1856 "schema": {
1857 "$ref": "#/definitions/UpdateVariableOption"
1858 }
1859 }
1860 ],
1861 "responses": {
1862 "201": {
1863 "description": "response when updating an org-level variable"
1864 },
1865 "204": {
1866 "description": "response when updating an org-level variable"
1867 },
1868 "400": {
1869 "$ref": "#/responses/error"
1870 },
1871 "404": {
1872 "$ref": "#/responses/notFound"
1873 }
1874 }
1875 },
1876 "post": {
1877 "consumes": [
1878 "application/json"
1879 ],
1880 "produces": [
1881 "application/json"
1882 ],
1883 "tags": [
1884 "organization"
1885 ],
1886 "summary": "Create an org-level variable",
1887 "operationId": "createOrgVariable",
1888 "parameters": [
1889 {
1890 "type": "string",
1891 "description": "name of the organization",
1892 "name": "org",
1893 "in": "path",
1894 "required": true
1895 },
1896 {
1897 "type": "string",
1898 "description": "name of the variable",
1899 "name": "variablename",
1900 "in": "path",
1901 "required": true
1902 },
1903 {
1904 "name": "body",
1905 "in": "body",
1906 "schema": {
1907 "$ref": "#/definitions/CreateVariableOption"
1908 }
1909 }
1910 ],
1911 "responses": {
1912 "201": {
1913 "description": "response when creating an org-level variable"
1914 },
1915 "204": {
1916 "description": "response when creating an org-level variable"
1917 },
1918 "400": {
1919 "$ref": "#/responses/error"
1920 },
1921 "404": {
1922 "$ref": "#/responses/notFound"
1923 }
1924 }
1925 },
1926 "delete": {
1927 "produces": [
1928 "application/json"
1929 ],
1930 "tags": [
1931 "organization"
1932 ],
1933 "summary": "Delete an org-level variable",
1934 "operationId": "deleteOrgVariable",
1935 "parameters": [
1936 {
1937 "type": "string",
1938 "description": "name of the organization",
1939 "name": "org",
1940 "in": "path",
1941 "required": true
1942 },
1943 {
1944 "type": "string",
1945 "description": "name of the variable",
1946 "name": "variablename",
1947 "in": "path",
1948 "required": true
1949 }
1950 ],
1951 "responses": {
1952 "200": {
1953 "$ref": "#/responses/ActionVariable"
1954 },
1955 "201": {
1956 "description": "response when deleting a variable"
1957 },
1958 "204": {
1959 "description": "response when deleting a variable"
1960 },
1961 "400": {
1962 "$ref": "#/responses/error"
1963 },
1964 "404": {
1965 "$ref": "#/responses/notFound"
1966 }
1967 }
1968 }
1969 },
1744 "/orgs/{org}/activities/feeds": {1970 "/orgs/{org}/activities/feeds": {
1745 "get": {1971 "get": {
1746 "produces": [1972 "produces": [
@@ -3455,53 +3681,305 @@ · 1708 unmodified lines
3455 },3681 },
3456 {3682 {
3457 "type": "string",3683 "type": "string",
3458 "description": "name of the repo to edit",3684 "description": "name of the repo to edit",
3459 "name": "repo",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",
3460 "in": "path",3944 "in": "path",
3461 "required": true3945 "required": true
3462 },3946 },
3463 {3947 {
3464 "description": "Properties of a repo that you can edit",
3465 "name": "body",3948 "name": "body",
3466 "in": "body",3949 "in": "body",
3467 "schema": {3950 "schema": {
3468 "$ref": "#/definitions/EditRepoOption"3951 "$ref": "#/definitions/UpdateVariableOption"
3469 }3952 }
3470 }3953 }
3471 ],3954 ],
3472 "responses": {3955 "responses": {
3473 "200": {3956 "201": {
3474 "$ref": "#/responses/Repository"3957 "description": "response when updating a repo-level variable"
3475 },3958 },
3476 "403": {3959 "204": {
3477 "$ref": "#/responses/forbidden"3960 "description": "response when updating a repo-level variable"
3961 },
3962 "400": {
3963 "$ref": "#/responses/error"
3478 },3964 },
3479 "404": {3965 "404": {
3480 "$ref": "#/responses/notFound"3966 "$ref": "#/responses/notFound"
3481 },
3482 "422": {
3483 "$ref": "#/responses/validationError"
3484 }3967 }
3485 }3968 }
3486 }3969 },
3487 },3970 "post": {
3488 "/repos/{owner}/{repo}/actions/secrets/{secretname}": {
3489 "put": {
3490 "consumes": [
3491 "application/json"
3492 ],
3493 "produces": [3971 "produces": [
3494 "application/json"3972 "application/json"
3495 ],3973 ],
3496 "tags": [3974 "tags": [
3497 "repository"3975 "repository"
3498 ],3976 ],
3499 "summary": "Create or Update a secret value in a repository",3977 "summary": "Create a repo-level variable",
3500 "operationId": "updateRepoSecret",3978 "operationId": "createRepoVariable",
3501 "parameters": [3979 "parameters": [
3502 {3980 {
3503 "type": "string",3981 "type": "string",
3504 "description": "owner of the repository",3982 "description": "name of the owner",
3505 "name": "owner",3983 "name": "owner",
3506 "in": "path",3984 "in": "path",
3507 "required": true3985 "required": true
@@ -3515,8 +3993,8 @@ · 7 unmodified lines
3515 },3993 },
3516 {3994 {
3517 "type": "string",3995 "type": "string",
3518 "description": "name of the secret",3996 "description": "name of the variable",
3519 "name": "secretname",3997 "name": "variablename",
3520 "in": "path",3998 "in": "path",
3521 "required": true3999 "required": true
3522 },4000 },
@@ -3524,16 +4002,16 @@ · 1 unmodified line
3524 "name": "body",4002 "name": "body",
3525 "in": "body",4003 "in": "body",
3526 "schema": {4004 "schema": {
3527 "$ref": "#/definitions/CreateOrUpdateSecretOption"4005 "$ref": "#/definitions/CreateVariableOption"
3528 }4006 }
3529 }4007 }
3530 ],4008 ],
3531 "responses": {4009 "responses": {
3532 "201": {4010 "201": {
3533 "description": "response when creating a secret"4011 "description": "response when creating a repo-level variable"
3534 },4012 },
3535 "204": {4013 "204": {
3536 "description": "response when updating a secret"4014 "description": "response when creating a repo-level variable"
3537 },4015 },
3538 "400": {4016 "400": {
3539 "$ref": "#/responses/error"4017 "$ref": "#/responses/error"
@@ -3544,21 +4022,18 @@ · 4 unmodified lines
3544 }4022 }
3545 },4023 },
3546 "delete": {4024 "delete": {
3547 "consumes": [
3548 "application/json"
3549 ],
3550 "produces": [4025 "produces": [
3551 "application/json"4026 "application/json"
3552 ],4027 ],
3553 "tags": [4028 "tags": [
3554 "repository"4029 "repository"
3555 ],4030 ],
3556 "summary": "Delete a secret in a repository",4031 "summary": "Delete a repo-level variable",
3557 "operationId": "deleteRepoSecret",4032 "operationId": "deleteRepoVariable",
3558 "parameters": [4033 "parameters": [
3559 {4034 {
3560 "type": "string",4035 "type": "string",
3561 "description": "owner of the repository",4036 "description": "name of the owner",
3562 "name": "owner",4037 "name": "owner",
3563 "in": "path",4038 "in": "path",
3564 "required": true4039 "required": true
@@ -3572,15 +4047,21 @@ · 7 unmodified lines
3572 },4047 },
3573 {4048 {
3574 "type": "string",4049 "type": "string",
3575 "description": "name of the secret",4050 "description": "name of the variable",
3576 "name": "secretname",4051 "name": "variablename",
3577 "in": "path",4052 "in": "path",
3578 "required": true4053 "required": true
3579 }4054 }
3580 ],4055 ],
3581 "responses": {4056 "responses": {
4057 "200": {
4058 "$ref": "#/responses/ActionVariable"
4059 },
4060 "201": {
4061 "description": "response when deleting a variable"
4062 },
3582 "204": {4063 "204": {
3583 "description": "delete one secret of the organization"4064 "description": "response when deleting a variable"
3584 },4065 },
3585 "400": {4066 "400": {
3586 "$ref": "#/responses/error"4067 "$ref": "#/responses/error"
@@ -15375,6 +15856,194 @@ · 11788 unmodified lines
15375 }15856 }
15376 }15857 }
15377 },15858 },
15859 "/user/actions/variables": {
15860 "get": {
15861 "produces": [
15862 "application/json"
15863 ],
15864 "tags": [
15865 "user"
15866 ],
15867 "summary": "Get the user-level list of variables which is created by current doer",
15868 "operationId": "getUserVariablesList",
15869 "parameters": [
15870 {
15871 "type": "integer",
15872 "description": "page number of results to return (1-based)",
15873 "name": "page",
15874 "in": "query"
15875 },
15876 {
15877 "type": "integer",
15878 "description": "page size of results",
15879 "name": "limit",
15880 "in": "query"
15881 }
15882 ],
15883 "responses": {
15884 "200": {
15885 "$ref": "#/responses/VariableList"
15886 },
15887 "400": {
15888 "$ref": "#/responses/error"
15889 },
15890 "404": {
15891 "$ref": "#/responses/notFound"
15892 }
15893 }
15894 }
15895 },
15896 "/user/actions/variables/{variablename}": {
15897 "get": {
15898 "produces": [
15899 "application/json"
15900 ],
15901 "tags": [
15902 "user"
15903 ],
15904 "summary": "Get a user-level variable which is created by current doer",
15905 "operationId": "getUserVariable",
15906 "parameters": [
15907 {
15908 "type": "string",
15909 "description": "name of the variable",
15910 "name": "variablename",
15911 "in": "path",
15912 "required": true
15913 }
15914 ],
15915 "responses": {
15916 "200": {
15917 "$ref": "#/responses/ActionVariable"
15918 },
15919 "400": {
15920 "$ref": "#/responses/error"
15921 },
15922 "404": {
15923 "$ref": "#/responses/notFound"
15924 }
15925 }
15926 },
15927 "put": {
15928 "consumes": [
15929 "application/json"
15930 ],
15931 "produces": [
15932 "application/json"
15933 ],
15934 "tags": [
15935 "user"
15936 ],
15937 "summary": "Update a user-level variable which is created by current doer",
15938 "operationId": "updateUserVariable",
15939 "parameters": [
15940 {
15941 "type": "string",
15942 "description": "name of the variable",
15943 "name": "variablename",
15944 "in": "path",
15945 "required": true
15946 },
15947 {
15948 "name": "body",
15949 "in": "body",
15950 "schema": {
15951 "$ref": "#/definitions/UpdateVariableOption"
15952 }
15953 }
15954 ],
15955 "responses": {
15956 "201": {
15957 "description": "response when updating a variable"
15958 },
15959 "204": {
15960 "description": "response when updating a variable"
15961 },
15962 "400": {
15963 "$ref": "#/responses/error"
15964 },
15965 "404": {
15966 "$ref": "#/responses/notFound"
15967 }
15968 }
15969 },
15970 "post": {
15971 "consumes": [
15972 "application/json"
15973 ],
15974 "produces": [
15975 "application/json"
15976 ],
15977 "tags": [
15978 "user"
15979 ],
15980 "summary": "Create a user-level variable",
15981 "operationId": "createUserVariable",
15982 "parameters": [
15983 {
15984 "type": "string",
15985 "description": "name of the variable",
15986 "name": "variablename",
15987 "in": "path",
15988 "required": true
15989 },
15990 {
15991 "name": "body",
15992 "in": "body",
15993 "schema": {
15994 "$ref": "#/definitions/CreateVariableOption"
15995 }
15996 }
15997 ],
15998 "responses": {
15999 "201": {
16000 "description": "response when creating a variable"
16001 },
16002 "204": {
16003 "description": "response when creating a variable"
16004 },
16005 "400": {
16006 "$ref": "#/responses/error"
16007 },
16008 "404": {
16009 "$ref": "#/responses/notFound"
16010 }
16011 }
16012 },
16013 "delete": {
16014 "produces": [
16015 "application/json"
16016 ],
16017 "tags": [
16018 "user"
16019 ],
16020 "summary": "Delete a user-level variable which is created by current doer",
16021 "operationId": "deleteUserVariable",
16022 "parameters": [
16023 {
16024 "type": "string",
16025 "description": "name of the variable",
16026 "name": "variablename",
16027 "in": "path",
16028 "required": true
16029 }
16030 ],
16031 "responses": {
16032 "201": {
16033 "description": "response when deleting a variable"
16034 },
16035 "204": {
16036 "description": "response when deleting a variable"
16037 },
16038 "400": {
16039 "$ref": "#/responses/error"
16040 },
16041 "404": {
16042 "$ref": "#/responses/notFound"
16043 }
16044 }
16045 }
16046 },
15378 "/user/applications/oauth2": {16047 "/user/applications/oauth2": {
15379 "get": {16048 "get": {
15380 "produces": [16049 "produces": [
@@ -17493,6 +18162,35 @@ · 2112 unmodified lines
17493 },18162 },
17494 "x-go-package": "code.gitea.io/gitea/modules/structs"18163 "x-go-package": "code.gitea.io/gitea/modules/structs"
17495 },18164 },
18165 "ActionVariable": {
18166 "description": "ActionVariable return value of the query API",
18167 "type": "object",
18168 "properties": {
18169 "data": {
18170 "description": "the value of the variable",
18171 "type": "string",
18172 "x-go-name": "Data"
18173 },
18174 "name": {
18175 "description": "the name of the variable",
18176 "type": "string",
18177 "x-go-name": "Name"
18178 },
18179 "owner_id": {
18180 "description": "the owner to which the variable belongs",
18181 "type": "integer",
18182 "format": "int64",
18183 "x-go-name": "OwnerID"
18184 },
18185 "repo_id": {
18186 "description": "the repository to which the variable belongs",
18187 "type": "integer",
18188 "format": "int64",
18189 "x-go-name": "RepoID"
18190 }
18191 },
18192 "x-go-package": "code.gitea.io/gitea/modules/structs"
18193 },
17496 "Activity": {18194 "Activity": {
17497 "type": "object",18195 "type": "object",
17498 "properties": {18196 "properties": {
@@ -19393,6 +20091,21 @@ · 1894 unmodified lines
19393 },20091 },
19394 "x-go-package": "code.gitea.io/gitea/modules/structs"20092 "x-go-package": "code.gitea.io/gitea/modules/structs"
19395 },20093 },
20094 "CreateVariableOption": {
20095 "description": "CreateVariableOption the option when creating variable",
20096 "type": "object",
20097 "required": [
20098 "value"
20099 ],
20100 "properties": {
20101 "value": {
20102 "description": "Value of the variable to create",
20103 "type": "string",
20104 "x-go-name": "Value"
20105 }
20106 },
20107 "x-go-package": "code.gitea.io/gitea/modules/structs"
20108 },
19396 "CreateWikiPageOptions": {20109 "CreateWikiPageOptions": {
19397 "description": "CreateWikiPageOptions form for creating wiki",20110 "description": "CreateWikiPageOptions form for creating wiki",
19398 "type": "object",20111 "type": "object",
@@ -23773,6 +24486,26 @@ · 4374 unmodified lines
23773 },24486 },
23774 "x-go-package": "code.gitea.io/gitea/modules/structs"24487 "x-go-package": "code.gitea.io/gitea/modules/structs"
23775 },24488 },
24489 "UpdateVariableOption": {
24490 "description": "UpdateVariableOption the option when updating variable",
24491 "type": "object",
24492 "required": [
24493 "value"
24494 ],
24495 "properties": {
24496 "name": {
24497 "description": "New name for the variable. If the field is empty, the variable name won't be updated.",
24498 "type": "string",
24499 "x-go-name": "Name"
24500 },
24501 "value": {
24502 "description": "Value of the variable to update",
24503 "type": "string",
24504 "x-go-name": "Value"
24505 }
24506 },
24507 "x-go-package": "code.gitea.io/gitea/modules/structs"
24508 },
23776 "User": {24509 "User": {
23777 "description": "User represents a user",24510 "description": "User represents a user",
23778 "type": "object",24511 "type": "object",
@@ -24157,6 +24890,12 @@ · 378 unmodified lines
24157 }24890 }
24158 }24891 }
24159 },24892 },
24893 "ActionVariable": {
24894 "description": "ActionVariable",
24895 "schema": {
24896 "$ref": "#/definitions/ActionVariable"
24897 }
24898 },
24160 "ActivityFeedsList": {24899 "ActivityFeedsList": {
24161 "description": "ActivityFeedsList",24900 "description": "ActivityFeedsList",
24162 "schema": {24901 "schema": {
@@ -25040,6 +25779,15 @@ · 877 unmodified lines
25040 }25779 }
25041 }25780 }
25042 },25781 },
25782 "VariableList": {
25783 "description": "VariableList",
25784 "schema": {
25785 "type": "array",
25786 "items": {
25787 "$ref": "#/definitions/ActionVariable"
25788 }
25789 }
25790 },
25043 "WatchInfo": {25791 "WatchInfo": {
25044 "description": "WatchInfo",25792 "description": "WatchInfo",
25045 "schema": {25793 "schema": {
@@ -25115,7 +25863,7 @@ · 69 unmodified lines
25115 "parameterBodies": {25863 "parameterBodies": {
25116 "description": "parameterBodies",25864 "description": "parameterBodies",
25117 "schema": {25865 "schema": {
25118 "$ref": "#/definitions/CreateOrUpdateSecretOption"25866 "$ref": "#/definitions/UpdateVariableOption"
25119 }25867 }
25120 },25868 },
25121 "redirect": {25869 "redirect": {
templates/user/settings/repos.tmpl+1−1
@@ -6,7 +6,7 @@ · 5 unmodified lines
6 <div class="ui attached segment">6 <div class="ui attached segment">
7 {{if or .allowAdopt .allowDelete}}7 {{if or .allowAdopt .allowDelete}}
8 {{if .Dirs}}8 {{if .Dirs}}
9 <div class="ui middle aligned divided list">9 <div class="ui list">
10 {{range $dirI, $dir := .Dirs}}10 {{range $dirI, $dir := .Dirs}}
11 {{$repo := index $.ReposMap $dir}}11 {{$repo := index $.ReposMap $dir}}
12 <div class="item {{if not $repo}}tw-py-1{{end}}">{{/* if not repo, then there are "adapt" buttons, so the padding shouldn't be that default large*/}}12 <div class="item {{if not $repo}}tw-py-1{{end}}">{{/* if not repo, then there are "adapt" buttons, so the padding shouldn't be that default large*/}}
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
4package integration
5
6import (
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
19func 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
4package integration
5
6import (
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
16func 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
24 --repo-header-issue-min-height: 41px;24 --repo-header-issue-min-height: 41px;
25 --min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */25 --min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
26 --tab-size: 4;26 --tab-size: 4;
27 --checkbox-size: 16px; /* height and width of checkbox and radio inputs */
27}28}
28 29
29:root * {30:root * {
@@ -44,7 +45,7 @@ html, body { · 14 unmodified lines
44}45}
45 46
46body {47body {
47 line-height: 1.4285rem;48 line-height: 20px;
48 font-family: var(--fonts-regular);49 font-family: var(--fonts-regular);
49 color: var(--color-text);50 color: var(--color-text);
50 background-color: var(--color-body);51 background-color: var(--color-body);
@@ -316,61 +317,6 @@ a.label, · 265 unmodified lines
316 background-color: var(--color-label-bg);317 background-color: var(--color-label-bg);
317}318}
318 319
319/* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */
320.ui.input > input {
321 line-height: var(--line-height-default);
322 text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */
323}
324
325/* fix Fomantic's line-height causing vertical scrollbars to appear */
326ul.ui.list li,
327ol.ui.list li,
328.ui.list > .item,
329.ui.list .list > .item {
330 line-height: var(--line-height-default);
331}
332
333.ui.input.focus > input,
334.ui.input > input:focus {
335 border-color: var(--color-primary);
336}
337
338.ui.action.input .ui.ui.button {
339 border-color: var(--color-input-border);
340 padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */
341 padding-bottom: 0;
342}
343
344/* currently used for search bar dropdowns in repo search and explore code */
345.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection {
346 min-width: 10em;
347}
348.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) {
349 border-right: none;
350}
351.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover {
352 border-color: var(--color-input-border);
353}
354.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible {
355 border-bottom-left-radius: 0 !important;
356 border-bottom-right-radius: 0 !important;
357}
358.ui.action.input:not([class*="left action"]) > input,
359.ui.action.input:not([class*="left action"]) > input:hover {
360 border-right: none;
361}
362.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection,
363.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
364.ui.action.input:not([class*="left action"]) > input:focus + .button,
365.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
366.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
367.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
368 border-left-color: var(--color-primary);
369}
370.ui.action.input:not([class*="left action"]) > input:focus {
371 border-right-color: var(--color-primary);
372}
373
374.ui.menu {320.ui.menu {
375 display: flex;321 display: flex;
376}322}
@@ -514,21 +460,6 @@ ol.ui.list li, · 137 unmodified lines
514 color: var(--color-text-light-2);460 color: var(--color-text-light-2);
515}461}
516 462
517.ui.list .list > .item .header,
518.ui.list > .item .header {
519 color: var(--color-text-dark);
520}
521
522.ui.list .list > .item > .content,
523.ui.list > .item > .content {
524 color: var(--color-text);
525}
526
527.ui.list .list > .item .description,
528.ui.list > .item .description {
529 color: var(--color-text);
530}
531
532/* replace item margin on secondary menu items with gap and remove both the463/* replace item margin on secondary menu items with gap and remove both the
533 negative margins on the menu as well as margin on the items */464 negative margins on the menu as well as margin on the items */
534.ui.secondary.menu {465.ui.secondary.menu {
@@ -647,10 +578,6 @@ img.ui.avatar, · 112 unmodified lines
647 aspect-ratio: 1;578 aspect-ratio: 1;
648}579}
649 580
650.ui.divided.list > .item {
651 border-color: var(--color-secondary);
652}
653
654.ui.error.message .header,581.ui.error.message .header,
655.ui.warning.message .header {582.ui.warning.message .header {
656 color: inherit;583 color: inherit;
@@ -1457,11 +1384,6 @@ table th[data-sortt-desc] .svg { · 800 unmodified lines
1457 vertical-align: -0.15em;1384 vertical-align: -0.15em;
1458}1385}
1459 1386
1460/* for the jquery.minicolors plugin */
1461.minicolors-panel {
1462 background: var(--color-secondary-dark-1) !important;
1463}
1464
1465.ui.tabular.menu {1387.ui.tabular.menu {
1466 border-color: var(--color-secondary);1388 border-color: var(--color-secondary);
1467}1389}
@@ -1625,16 +1547,6 @@ table th[data-sortt-desc] .svg { · 157 unmodified lines
1625 align-items: stretch;1547 align-items: stretch;
1626}1548}
1627 1549
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
1638.flex-items-block > .item,1550.flex-items-block > .item,
1639.flex-text-block {1551.flex-text-block {
1640 display: flex;1552 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
34hex-color-picker {
35 width: 180px;
36 height: 120px;
37}
38
39hex-color-picker::part(hue-pointer),
40hex-color-picker::part(saturation-pointer) {
41 width: 22px;
42 height: 22px;
43}
44
45hex-color-picker::part(hue) {
46 flex-basis: 16px;
47}
web_src/css/features/projects.css+0−23
@@ -102,26 +102,3 @@ · 101 unmodified lines
102.card-ghost * {102.card-ghost * {
103 opacity: 0;103 opacity: 0;
104}104}
105
106.color-field .minicolors.minicolors-theme-default {
107 display: block;
108}
109
110.color-field .minicolors.minicolors-theme-default .minicolors-input {
111 height: 38px;
112 padding-left: 2rem;
113}
114
115.color-field .minicolors.minicolors-theme-default .minicolors-swatch {
116 top: 10px;
117}
118
119.edit-project-column-modal .color.picker.column,
120.new-project-column-modal .color.picker.column {
121 display: flex;
122}
123
124.edit-project-column-modal .color.picker.column .minicolors,
125.new-project-column-modal .color.picker.column .minicolors {
126 flex: 1;
127}
web_src/css/form.css+5−54
@@ -32,10 +32,7 @@ textarea, · 31 unmodified lines
32.ui.form input[type="text"],32.ui.form input[type="text"],
33.ui.form input[type="time"],33.ui.form input[type="time"],
34.ui.form input[type="url"],34.ui.form input[type="url"],
35.ui.selection.dropdown,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 {
39 background: var(--color-input-background);36 background: var(--color-input-background);
40 border-color: var(--color-input-border);37 border-color: var(--color-input-border);
41 color: var(--color-input-text);38 color: var(--color-input-text);
@@ -63,12 +60,7 @@ textarea:hover, · 21 unmodified lines
63.ui.form input[type="text"]:hover,60.ui.form input[type="text"]:hover,
64.ui.form input[type="time"]:hover,61.ui.form input[type="time"]:hover,
65.ui.form input[type="url"]:hover,62.ui.form input[type="url"]:hover,
66.ui.selection.dropdown:hover,63.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 {
72 background: var(--color-input-background);64 background: var(--color-input-background);
73 border-color: var(--color-input-border-hover);65 border-color: var(--color-input-border-hover);
74 color: var(--color-input-text);66 color: var(--color-input-text);
@@ -91,11 +83,7 @@ textarea:focus, · 16 unmodified lines
91.ui.form input[type="text"]:focus,83.ui.form input[type="text"]:focus,
92.ui.form input[type="time"]:focus,84.ui.form input[type="time"]:focus,
93.ui.form input[type="url"]:focus,85.ui.form input[type="url"]:focus,
94.ui.selection.dropdown:focus,86.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 {
99 background: var(--color-input-background);87 background: var(--color-input-background);
100 border-color: var(--color-primary);88 border-color: var(--color-primary);
101 color: var(--color-input-text);89 color: var(--color-input-text);
@@ -106,58 +94,21 @@ textarea:focus, · 4 unmodified lines
106.ui.form .inline.fields .field > label,94.ui.form .inline.fields .field > label,
107.ui.form .inline.fields .field > p,95.ui.form .inline.fields .field > p,
108.ui.form .inline.field > label,96.ui.form .inline.field > label,
109.ui.form .inline.field > p,97.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 {
116 color: var(--color-text);98 color: var(--color-text);
117}99}
118 100
119.ui.form .required.fields:not(.grouped) > .field > label::after,101.ui.form .required.fields:not(.grouped) > .field > label::after,
120.ui.form .required.fields.grouped > label::after,102.ui.form .required.fields.grouped > label::after,
121.ui.form .required.field > label::after,103.ui.form .required.field > label::after,
122.ui.form .required.fields:not(.grouped) > .field > .checkbox::after,
123.ui.form .required.field > .checkbox::after,
124.ui.form label.required::after {104.ui.form label.required::after {
125 color: var(--color-red);105 color: var(--color-red);
126}106}
127 107
128.ui.input,108.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 {
137 color: var(--color-input-text);109 color: var(--color-input-text);
138}110}
139 111
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
161/* match <select> padding to <input> */112/* match <select> padding to <input> */
162.ui.form select {113.ui.form select {
163 padding: 0.67857143em 1em;114 padding: 0.67857143em 1em;
web_src/css/helpers.css+17−0
@@ -63,3 +63,20 @@ only use: · 62 unmodified lines
63 display: none !important;63 display: none !important;
64 }64 }
65}65}
66
67.tab-size-1 { tab-size: 1 !important; }
68.tab-size-2 { tab-size: 2 !important; }
69.tab-size-3 { tab-size: 3 !important; }
70.tab-size-4 { tab-size: 4 !important; }
71.tab-size-5 { tab-size: 5 !important; }
72.tab-size-6 { tab-size: 6 !important; }
73.tab-size-7 { tab-size: 7 !important; }
74.tab-size-8 { tab-size: 8 !important; }
75.tab-size-9 { tab-size: 9 !important; }
76.tab-size-10 { tab-size: 10 !important; }
77.tab-size-11 { tab-size: 11 !important; }
78.tab-size-12 { tab-size: 12 !important; }
79.tab-size-13 { tab-size: 13 !important; }
80.tab-size-14 { tab-size: 14 !important; }
81.tab-size-15 { tab-size: 15 !important; }
82.tab-size-16 { tab-size: 16 !important; }
web_src/css/index.css+3−0
@@ -6,12 +6,15 @@ · 5 unmodified lines
6@import "./modules/container.css";6@import "./modules/container.css";
7@import "./modules/divider.css";7@import "./modules/divider.css";
8@import "./modules/header.css";8@import "./modules/header.css";
9@import "./modules/input.css";
9@import "./modules/label.css";10@import "./modules/label.css";
11@import "./modules/list.css";
10@import "./modules/segment.css";12@import "./modules/segment.css";
11@import "./modules/grid.css";13@import "./modules/grid.css";
12@import "./modules/message.css";14@import "./modules/message.css";
13@import "./modules/table.css";15@import "./modules/table.css";
14@import "./modules/card.css";16@import "./modules/card.css";
17@import "./modules/checkbox.css";
15@import "./modules/modal.css";18@import "./modules/modal.css";
16 19
17@import "./modules/select.css";20@import "./modules/select.css";
web_src/css/modules/animations.css+6−3
@@ -6,7 +6,6 @@ · 5 unmodified lines
6.is-loading {6.is-loading {
7 pointer-events: none !important;7 pointer-events: none !important;
8 position: relative !important;8 position: relative !important;
9 overflow: hidden !important;
10}9}
11 10
12.is-loading > * {11.is-loading > * {
@@ -35,10 +34,14 @@ · 22 unmodified lines
35 border-radius: var(--border-radius-circle);34 border-radius: var(--border-radius-circle);
36}35}
37 36
38.is-loading.small-loading-icon::after {37.is-loading.loading-icon-2px::after {
39 border-width: 2px;38 border-width: 2px;
40}39}
41 40
41.is-loading.loading-icon-3px::after {
42 border-width: 3px;
43}
44
42/* 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. */45/* for single form button, the loading state should be on the button, but not go semi-transparent, just replace the text on the button with the loader. */
43form.single-button-form.is-loading > * {46form.single-button-form.is-loading > * {
44 opacity: 1;47 opacity: 1;
@@ -63,7 +66,7 @@ form.single-button-form.is-loading .button { · 18 unmodified lines
63 background: transparent;66 background: transparent;
64}67}
65 68
66/* TODO: not needed, use "is-loading small-loading-icon" instead */69/* TODO: not needed, use "is-loading loading-icon-2px" instead */
67code.language-math.is-loading::after {70code.language-math.is-loading::after {
68 padding: 0;71 padding: 0;
69 border-width: 2px;72 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
4input[type="checkbox"],
5input[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
135 font-weight: var(--font-weight-normal);135 font-weight: var(--font-weight-normal);
136}136}
137 137
138/* open dropdown menus to the left in right-attached headers */
139.ui.attached.header > .ui.right .ui.dropdown .menu {
140 right: 0;
141 left: auto;
142}
143
138/* if a .top.attached.header is followed by a .segment, add some margin */144/* if a .top.attached.header is followed by a .segment, add some margin */
139.ui.segments + .ui.top.attached.header,145.ui.segments + .ui.top.attached.header,
140.ui.attached.segment + .ui.top.attached.header {146.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
140.secondary-nav {140.secondary-nav {
141 background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */141 background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */
142}142}
143
144.issue-navbar {
145 display: flex;
146 justify-content: space-between;
147}
web_src/css/modules/tippy.css+11−0
@@ -29,6 +29,17 @@ · 28 unmodified lines
29 z-index: 1;29 z-index: 1;
30}30}
31 31
32/* bare theme, no styling at all, except box-shadow */
33.tippy-box[data-theme="bare"] {
34 border: none;
35 box-shadow: 0 6px 18px var(--color-shadow);
36}
37
38.tippy-box[data-theme="bare"] .tippy-content {
39 padding: 0;
40 background: transparent;
41}
42
32/* tooltip theme for text tooltips */43/* tooltip theme for text tooltips */
33 44
34.tippy-box[data-theme="tooltip"] {45.tippy-box[data-theme="tooltip"] {
web_src/css/org.css+0−4
@@ -89,10 +89,6 @@ · 88 unmodified lines
89 text-align: center;89 text-align: center;
90}90}
91 91
92.organization.options input {
93 min-width: 300px;
94}
95
96.page-content.organization .org-avatar {92.page-content.organization .org-avatar {
97 margin-right: 15px;93 margin-right: 15px;
98}94}
web_src/css/repo.css+2−98
@@ -2299,104 +2299,6 @@ · 2298 unmodified lines
2299 padding-top: 15px;2299 padding-top: 15px;
2300}2300}
2301 2301
2302.edit-label.modal .form .color.picker.column,
2303.new-label.modal .form .color.picker.column {
2304 display: flex;
2305}
2306
2307.edit-label.modal .form .color.picker.column .minicolors,
2308.new-label.modal .form .color.picker.column .minicolors {
2309 flex: 1;
2310}
2311
2312.edit-label.modal .form .minicolors-swatch.minicolors-sprite,
2313.new-label.modal .form .minicolors-swatch.minicolors-sprite {
2314 top: 10px;
2315 left: 10px;
2316 width: 15px;
2317 height: 15px;
2318}
2319
2320.tab-size-1 {
2321 tab-size: 1 !important;
2322 -moz-tab-size: 1 !important;
2323}
2324
2325.tab-size-2 {
2326 tab-size: 2 !important;
2327 -moz-tab-size: 2 !important;
2328}
2329
2330.tab-size-3 {
2331 tab-size: 3 !important;
2332 -moz-tab-size: 3 !important;
2333}
2334
2335.tab-size-4 {
2336 tab-size: 4 !important;
2337 -moz-tab-size: 4 !important;
2338}
2339
2340.tab-size-5 {
2341 tab-size: 5 !important;
2342 -moz-tab-size: 5 !important;
2343}
2344
2345.tab-size-6 {
2346 tab-size: 6 !important;
2347 -moz-tab-size: 6 !important;
2348}
2349
2350.tab-size-7 {
2351 tab-size: 7 !important;
2352 -moz-tab-size: 7 !important;
2353}
2354
2355.tab-size-8 {
2356 tab-size: 8 !important;
2357 -moz-tab-size: 8 !important;
2358}
2359
2360.tab-size-9 {
2361 tab-size: 9 !important;
2362 -moz-tab-size: 9 !important;
2363}
2364
2365.tab-size-10 {
2366 tab-size: 10 !important;
2367 -moz-tab-size: 10 !important;
2368}
2369
2370.tab-size-11 {
2371 tab-size: 11 !important;
2372 -moz-tab-size: 11 !important;
2373}
2374
2375.tab-size-12 {
2376 tab-size: 12 !important;
2377 -moz-tab-size: 12 !important;
2378}
2379
2380.tab-size-13 {
2381 tab-size: 13 !important;
2382 -moz-tab-size: 13 !important;
2383}
2384
2385.tab-size-14 {
2386 tab-size: 14 !important;
2387 -moz-tab-size: 14 !important;
2388}
2389
2390.tab-size-15 {
2391 tab-size: 15 !important;
2392 -moz-tab-size: 15 !important;
2393}
2394
2395.tab-size-16 {
2396 tab-size: 16 !important;
2397 -moz-tab-size: 16 !important;
2398}
2399
2400.stats-table {2302.stats-table {
2401 display: table;2303 display: table;
2402 width: 100%;2304 width: 100%;
@@ -2573,6 +2475,7 @@ tbody.commit-list { · 170 unmodified lines
2573#repo-topics .repo-topic {2475#repo-topics .repo-topic {
2574 font-weight: var(--font-weight-normal);2476 font-weight: var(--font-weight-normal);
2575 cursor: pointer;2477 cursor: pointer;
2478 margin: 0;
2576}2479}
2577 2480
2578#new-dependency-drop-list.ui.selection.dropdown {2481#new-dependency-drop-list.ui.selection.dropdown {
@@ -2990,6 +2893,7 @@ tbody.commit-list { · 411 unmodified lines
2990 display: flex;2893 display: flex;
2991 align-items: center;2894 align-items: center;
2992 justify-content: flex-end;2895 justify-content: flex-end;
2896 gap: 8px;
2993}2897}
2994 2898
2995@media (max-width: 767.98px) {2899@media (max-width: 767.98px) {
web_src/css/repo/issue-list.css+1−0
@@ -9,6 +9,7 @@ · 8 unmodified lines
9 9
10.issue-list-toolbar-left {10.issue-list-toolbar-left {
11 display: flex;11 display: flex;
12 align-items: center;
12}13}
13 14
14.issue-list-toolbar-right .filter.menu {15.issue-list-toolbar-right .filter.menu {
web_src/fomantic/build/semantic.css+2869−5299
@@ -2327,7 +2327,7 @@ · 2326 unmodified lines
2327 Site Overrides2327 Site Overrides
2328*******************************/2328*******************************/
2329/*!2329/*!
2330 * # Fomantic-UI - Checkbox2330 * # Fomantic-UI - Dimmer
2331 * http://github.com/fomantic/Fomantic-UI/2331 * http://github.com/fomantic/Fomantic-UI/
2332 *2332 *
2333 *2333 *
@@ -2337,4857 +2337,2318 @@ · 3 unmodified lines
2337 */2337 */
2338 2338
2339/*******************************2339/*******************************
2340 Checkbox2340 Dimmer
2341*******************************/2341*******************************/
2342 2342
2343/*--------------2343.dimmable:not(body) {
2344 Content
2345---------------*/
2346
2347.ui.checkbox {
2348 position: relative;
2349 display: inline-block;
2350 backface-visibility: hidden;
2351 outline: none;
2352 vertical-align: baseline;
2353 font-style: normal;
2354 min-height: 17px;
2355 font-size: 1em;
2356 line-height: 17px;
2357 min-width: 17px;
2358}
2359
2360/* HTML Checkbox */
2361
2362.ui.checkbox input[type="checkbox"],
2363.ui.checkbox input[type="radio"] {
2364 cursor: pointer;
2365 position: absolute;
2366 top: 0;
2367 left: 0;
2368 opacity: 0 !important;
2369 outline: none;
2370 z-index: 3;
2371 width: 17px;
2372 height: 17px;
2373}
2374
2375.ui.checkbox label {
2376 cursor: auto;
2377 position: relative;2344 position: relative;
2378 display: block;
2379 padding-left: 1.85714em;
2380 outline: none;
2381 font-size: 1em;
2382}
2383
2384.ui.checkbox label:before {
2385 position: absolute;
2386 top: 0;
2387 left: 0;
2388 width: 17px;
2389 height: 17px;
2390 content: '';
2391 background: #FFFFFF;
2392 border-radius: 0.21428571rem;
2393 transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease;
2394 border: 1px solid #D4D4D5;
2395}2345}
2396 2346
2397/*--------------2347.ui.dimmer {
2398 Checkmark2348 display: none;
2399---------------*/
2400
2401.ui.checkbox label:after {
2402 position: absolute;2349 position: absolute;
2403 font-size: 14px;2350 top: 0 !important;
2404 top: 0;2351 left: 0 !important;
2405 left: 0;2352 width: 100%;
2406 width: 17px;2353 height: 100%;
2407 height: 17px;
2408 text-align: center;2354 text-align: center;
2355 vertical-align: middle;
2356 padding: 1em;
2357 background: rgba(0, 0, 0, 0.85);
2409 opacity: 0;2358 opacity: 0;
2410 color: rgba(0, 0, 0, 0.87);2359 line-height: 1;
2411 transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease;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;
2412}2371}
2413 2372
2414/*--------------2373/* Dimmer Content */
2415 Label
2416---------------*/
2417
2418/* Inside */
2419 2374
2420.ui.checkbox label,2375.ui.dimmer > .content {
2421.ui.checkbox + label {2376 -webkit-user-select: text;
2422 color: rgba(0, 0, 0, 0.87);2377 -moz-user-select: text;
2423 transition: color 0.1s ease;2378 user-select: text;
2379 color: #FFFFFF;
2424}2380}
2425 2381
2426/* Outside */2382/* Loose Coupling */
2427 2383
2428.ui.checkbox + label {2384.ui.segment > .ui.dimmer:not(.page) {
2429 vertical-align: middle;2385 border-radius: inherit;
2430}2386}
2431 2387
2388/* Scrollbars */
2389
2432/*******************************2390/*******************************
2433 States2391 States
2434*******************************/2392*******************************/
2435 2393
2436/*--------------2394/* Animating */
2437 Hover
2438---------------*/
2439
2440.ui.checkbox label:hover::before {
2441 background: #FFFFFF;
2442 border-color: rgba(34, 36, 38, 0.35);
2443}
2444 2395
2445.ui.checkbox label:hover,2396.animating.dimmable:not(body),
2446.ui.checkbox + label:hover {2397.dimmed.dimmable:not(body) {
2447 color: rgba(0, 0, 0, 0.8);2398 overflow: hidden;
2448}2399}
2449 2400
2450/*--------------2401/* Animating / Active / Visible */
2451 Down
2452---------------*/
2453 2402
2454.ui.checkbox label:active::before {2403.dimmed.dimmable > .ui.animating.dimmer,
2455 background: #F9FAFB;2404.dimmed.dimmable > .ui.visible.dimmer,
2456 border-color: rgba(34, 36, 38, 0.35);2405.ui.active.dimmer {
2406 display: flex;
2407 opacity: 1;
2457}2408}
2458 2409
2459.ui.checkbox label:active::after {2410/* Disabled */
2460 color: rgba(0, 0, 0, 0.95);
2461}
2462 2411
2463.ui.checkbox input:active ~ label {2412.ui.disabled.dimmer {
2464 color: rgba(0, 0, 0, 0.95);2413 width: 0 !important;
2414 height: 0 !important;
2465}2415}
2466 2416
2467/*--------------2417/*******************************
2468 Focus2418 Variations
2469---------------*/2419*******************************/
2470 2420
2471.ui.checkbox input:focus ~ label:before {2421/*--------------
2472 background: #FFFFFF;2422 Legacy
2473 border-color: #96C8DA;2423 ---------------*/
2474}
2475 2424
2476.ui.checkbox input:focus ~ label:after {2425/* Animating / Active / Visible */
2477 color: rgba(0, 0, 0, 0.95);
2478}
2479 2426
2480.ui.checkbox input:focus ~ label {2427.dimmed.dimmable > .ui.animating.legacy.dimmer,
2481 color: rgba(0, 0, 0, 0.95);2428.dimmed.dimmable > .ui.visible.legacy.dimmer,
2429.ui.active.legacy.dimmer {
2430 display: block;
2482}2431}
2483 2432
2484/*--------------2433/*--------------
2485 Active2434 Alignment
2486---------------*/2435 ---------------*/
2487 2436
2488.ui.checkbox input:checked ~ label:before {2437.ui[class*="top aligned"].dimmer {
2489 background: #FFFFFF;2438 justify-content: flex-start;
2490 border-color: rgba(34, 36, 38, 0.35);
2491}2439}
2492 2440
2493.ui.checkbox input:checked ~ label:after {2441.ui[class*="bottom aligned"].dimmer {
2494 opacity: 1;2442 justify-content: flex-end;
2495 color: rgba(0, 0, 0, 0.95);
2496}2443}
2497 2444
2498/*--------------2445/*--------------
2499 Indeterminate2446 Page
2500 ---------------*/2447 ---------------*/
2501 2448
2502.ui.checkbox input:not([type=radio]):indeterminate ~ label:before {2449.ui.page.dimmer {
2503 background: #FFFFFF;2450 position: fixed;
2504 border-color: rgba(34, 36, 38, 0.35);2451 transform-style: '';
2452 perspective: 2000px;
2453 transform-origin: center center;
2505}2454}
2506 2455
2507.ui.checkbox input:not([type=radio]):indeterminate ~ label:after {2456.ui.page.dimmer.modals {
2508 opacity: 1;2457 -moz-perspective: none;
2509 color: rgba(0, 0, 0, 0.95);
2510}2458}
2511 2459
2512.ui.indeterminate.toggle.checkbox input:not([type=radio]):indeterminate ~ label:before {2460body.animating.in.dimmable,
2513 background: rgba(0, 0, 0, 0.15);2461body.dimmed.dimmable {
2462 overflow: hidden;
2514}2463}
2515 2464
2516.ui.indeterminate.toggle.checkbox input:not([type=radio]) ~ label:after {2465body.dimmable > .dimmer {
2517 left: 1.075rem;2466 position: fixed;
2518}2467}
2519 2468
2520/*--------------2469/*--------------
2521 Active Focus2470 Blurring
2522---------------*/2471 ---------------*/
2523 2472
2524.ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:before,2473.blurring.dimmable > :not(.dimmer) {
2525.ui.checkbox input:checked:focus ~ label:before {2474 filter: initial;
2526 background: #FFFFFF;2475 transition: 800ms filter ease;
2527 border-color: #96C8DA;
2528}2476}
2529 2477
2530.ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:after,2478.blurring.dimmed.dimmable > :not(.dimmer):not(.popup) {
2531.ui.checkbox input:checked:focus ~ label:after {2479 filter: blur(5px) grayscale(0.7);
2532 color: rgba(0, 0, 0, 0.95);
2533}2480}
2534 2481
2535/*--------------2482/* Dimmer Color */
2536 Read-Only
2537---------------*/
2538 2483
2539.ui.read-only.checkbox,2484.blurring.dimmable > .dimmer {
2540.ui.read-only.checkbox label {2485 background: rgba(0, 0, 0, 0.6);
2541 cursor: default;
2542}2486}
2543 2487
2544/*--------------2488.blurring.dimmable > .inverted.dimmer {
2545 Disabled2489 background: rgba(255, 255, 255, 0.6);
2546 ---------------*/
2547
2548.ui.disabled.checkbox label,
2549.ui.checkbox input[disabled] ~ label {
2550 cursor: default !important;
2551 opacity: 0.5;
2552 color: #000000;
2553 pointer-events: none;
2554}2490}
2555 2491
2556/*--------------2492/*--------------
2557 Hidden2493 Aligned
2558---------------*/2494 ---------------*/
2559
2560/* Initialized checkbox moves input below element
2561 to prevent manually triggering */
2562 2495
2563.ui.checkbox input.hidden {2496.ui.dimmer > .top.aligned.content > * {
2564 z-index: -1;2497 vertical-align: top;
2565}2498}
2566 2499
2567/* Selectable Label */2500.ui.dimmer > .bottom.aligned.content > * {
2568 2501 vertical-align: bottom;
2569.ui.checkbox input.hidden + label {
2570 cursor: pointer;
2571 -webkit-user-select: none;
2572 -moz-user-select: none;
2573 user-select: none;
2574}2502}
2575 2503
2576/*******************************
2577 Types
2578*******************************/
2579
2580/*--------------2504/*--------------
2581 Radio2505 Shades
2582 ---------------*/2506 ---------------*/
2583 2507
2584.ui.radio.checkbox {2508.medium.medium.medium.medium.medium.dimmer {
2585 min-height: 15px;2509 background: rgba(0, 0, 0, 0.65);
2586}2510}
2587 2511
2588.ui.radio.checkbox label {2512.light.light.light.light.light.dimmer {
2589 padding-left: 1.85714em;2513 background: rgba(0, 0, 0, 0.45);
2590}2514}
2591 2515
2592/* Box */2516.very.light.light.light.light.dimmer {
2593 2517 background: rgba(0, 0, 0, 0.25);
2594.ui.radio.checkbox label:before {
2595 content: '';
2596 transform: none;
2597 width: 15px;
2598 height: 15px;
2599 border-radius: 500rem;
2600 top: 1px;
2601 left: 0;
2602}2518}
2603 2519
2604/* Bullet */2520/*--------------
2605 2521 Simple
2606.ui.radio.checkbox label:after {2522 ---------------*/
2607 border: none;
2608 content: '' !important;
2609 line-height: 15px;
2610 top: 1px;
2611 left: 0;
2612 width: 15px;
2613 height: 15px;
2614 border-radius: 500rem;
2615 transform: scale(0.46666667);
2616 background-color: rgba(0, 0, 0, 0.87);
2617}
2618 2523
2619/* Focus */2524/* Displays without javascript */
2620 2525
2621.ui.radio.checkbox input:focus ~ label:before {2526.ui.simple.dimmer {
2622 background-color: #FFFFFF;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);
2623}2534}
2624 2535
2625.ui.radio.checkbox input:focus ~ label:after {2536.dimmed.dimmable > .ui.simple.dimmer {
2626 background-color: rgba(0, 0, 0, 0.95);2537 overflow: visible;
2538 opacity: 1;
2539 width: 100%;
2540 height: 100%;
2541 background: rgba(0, 0, 0, 0.85);
2542 z-index: 1;
2627}2543}
2628 2544
2629/* Indeterminate */2545.ui.simple.inverted.dimmer {
2546 background: rgba(255, 255, 255, 0);
2547}
2630 2548
2631.ui.radio.checkbox input:indeterminate ~ label:after {2549.dimmed.dimmable > .ui.simple.inverted.dimmer {
2632 opacity: 0;2550 background: rgba(255, 255, 255, 0.85);
2633}2551}
2634 2552
2635/* Active */2553/*--------------
2554 Partially
2555 ----------------*/
2636 2556
2637.ui.radio.checkbox input:checked ~ label:before {2557.ui[class*="top dimmer"],
2638 background-color: #FFFFFF;2558.ui[class*="center dimmer"],
2559.ui[class*="bottom dimmer"] {
2560 height: auto;
2639}2561}
2640 2562
2641.ui.radio.checkbox input:checked ~ label:after {2563.ui[class*="bottom dimmer"] {
2642 background-color: rgba(0, 0, 0, 0.95);2564 top: auto !important;
2565 bottom: 0;
2643}2566}
2644 2567
2645/* Active Focus */2568.ui[class*="center dimmer"] {
2646 2569 top: 50% !important;
2647.ui.radio.checkbox input:focus:checked ~ label:before {2570 transform: translateY(-50%);
2648 background-color: #FFFFFF;2571 -webkit-transform: translateY(calc(-50% - 0.5px));
2649}2572}
2650 2573
2651.ui.radio.checkbox input:focus:checked ~ label:after {2574.ui.segment > .ui.ui[class*="top dimmer"] {
2652 background-color: rgba(0, 0, 0, 0.95);2575 border-bottom-left-radius: 0;
2576 border-bottom-right-radius: 0;
2653}2577}
2654 2578
2655/*--------------2579.ui.segment > .ui.ui[class*="center dimmer"] {
2656 Slider2580 border-radius: 0;
2657 ---------------*/
2658
2659.ui.slider.checkbox {
2660 min-height: 1.25rem;
2661}2581}
2662 2582
2663/* Input */2583.ui.segment > .ui.ui[class*="bottom dimmer"] {
2584 border-top-left-radius: 0;
2585 border-top-right-radius: 0;
2586}
2664 2587
2665.ui.slider.checkbox input {2588.ui[class*="center dimmer"].transition[class*="fade up"].in {
2666 width: 3.5rem;2589 animation-name: fadeInUpCenter;
2667 height: 1.25rem;
2668}2590}
2669 2591
2670/* Label */2592.ui[class*="center dimmer"].transition[class*="fade down"].in {
2593 animation-name: fadeInDownCenter;
2594}
2671 2595
2672.ui.slider.checkbox label {2596.ui[class*="center dimmer"].transition[class*="fade up"].out {
2673 padding-left: 4.5rem;2597 animation-name: fadeOutUpCenter;
2674 line-height: 1rem;
2675 color: rgba(0, 0, 0, 0.4);
2676}2598}
2677 2599
2678/* Line */2600.ui[class*="center dimmer"].transition[class*="fade down"].out {
2601 animation-name: fadeOutDownCenter;
2602}
2679 2603
2680.ui.slider.checkbox label:before {2604.ui[class*="center dimmer"].bounce.transition {
2681 display: block;2605 animation-name: bounceCenter;
2682 position: absolute;
2683 content: '';
2684 transform: none;
2685 border: none !important;
2686 left: 0;
2687 z-index: 1;
2688 top: 0.4rem;
2689 background-color: rgba(0, 0, 0, 0.05);
2690 width: 3.5rem;
2691 height: 0.21428571rem;
2692 border-radius: 500rem;
2693 transition: background 0.3s ease;
2694}2606}
2695 2607
2696/* Handle */2608@keyframes fadeInUpCenter {
2609 0% {
2610 opacity: 0;
2611 transform: translateY(-40%);
2612 -webkit-transform: translateY(calc(-40% - 0.5px));
2613 }
2697 2614
2698.ui.slider.checkbox label:after {2615 100% {
2699 background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05));2616 opacity: 1;
2700 position: absolute;2617 transform: translateY(-50%);
2701 content: '' !important;2618 -webkit-transform: translateY(calc(-50% - 0.5px));
2702 opacity: 1;2619 }
2703 z-index: 2;
2704 border: none;
2705 box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset;
2706 width: 1.5rem;
2707 height: 1.5rem;
2708 top: -0.25rem;
2709 left: 0;
2710 transform: none;
2711 border-radius: 500rem;
2712 transition: left 0.3s ease;
2713}2620}
2714 2621
2715/* Focus */2622@keyframes fadeInDownCenter {
2623 0% {
2624 opacity: 0;
2625 transform: translateY(-60%);
2626 -webkit-transform: translateY(calc(-60% - 0.5px));
2627 }
2716 2628
2717.ui.slider.checkbox input:focus ~ label:before {2629 100% {
2718 background-color: rgba(0, 0, 0, 0.15);2630 opacity: 1;
2719 border: none;2631 transform: translateY(-50%);
2632 -webkit-transform: translateY(calc(-50% - 0.5px));
2633 }
2720}2634}
2721 2635
2722/* Hover */2636@keyframes fadeOutUpCenter {
2637 0% {
2638 opacity: 1;
2639 transform: translateY(-50%);
2640 -webkit-transform: translateY(calc(-50% - 0.5px));
2641 }
2723 2642
2724.ui.slider.checkbox label:hover {2643 100% {
2725 color: rgba(0, 0, 0, 0.8);2644 opacity: 0;
2645 transform: translateY(-45%);
2646 -webkit-transform: translateY(calc(-45% - 0.5px));
2647 }
2726}2648}
2727 2649
2728.ui.slider.checkbox label:hover::before {2650@keyframes fadeOutDownCenter {
2729 background: rgba(0, 0, 0, 0.15);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 }
2730}2662}
2731 2663
2732/* Active */2664@keyframes bounceCenter {
2665 0%, 20%, 50%, 80%, 100% {
2666 transform: translateY(-50%);
2667 -webkit-transform: translateY(calc(-50% - 0.5px));
2668 }
2733 2669
2734.ui.slider.checkbox input:checked ~ label {2670 40% {
2735 color: rgba(0, 0, 0, 0.95) !important;2671 transform: translateY(calc(-50% - 30px));
2736}2672 }
2737 2673
2738.ui.slider.checkbox input:checked ~ label:before {2674 60% {
2739 background-color: #545454 !important;2675 transform: translateY(calc(-50% - 15px));
2676 }
2740}2677}
2741 2678
2742.ui.slider.checkbox input:checked ~ label:after {2679/*******************************
2743 left: 2rem;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);
2744}2711}
2745 2712
2746/* Active Focus */2713/*******************************
2714 Content
2715*******************************/
2747 2716
2748.ui.slider.checkbox input:focus:checked ~ label {2717/*--------------
2749 color: rgba(0, 0, 0, 0.95) !important;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;
2750}2741}
2751 2742
2752.ui.slider.checkbox input:focus:checked ~ label:before {2743.ui.dropdown .menu > * {
2753 background-color: #000000 !important;2744 white-space: nowrap;
2754}2745}
2755 2746
2756/*--------------2747/*--------------
2757 Toggle2748 Hidden Input
2758 ---------------*/2749---------------*/
2759 2750
2760.ui.toggle.checkbox {2751.ui.dropdown > input:not(.search):first-child,
2761 min-height: 1.5rem;2752.ui.dropdown > select {
2753 display: none !important;
2762}2754}
2763 2755
2764/* Input */2756/*--------------
2757 Dropdown Icon
2758---------------*/
2765 2759
2766.ui.toggle.checkbox input {2760.ui.dropdown:not(.labeled) > .dropdown.icon {
2767 width: 3.5rem;2761 position: relative;
2768 height: 1.5rem;2762 width: auto;
2763 font-size: 0.85714286em;
2764 margin: 0 0 0 1em;
2769}2765}
2770 2766
2771/* Label */2767.ui.dropdown .menu > .item .dropdown.icon {
2768 width: auto;
2769 float: right;
2770 margin: 0em 0 0 1em;
2771}
2772 2772
2773.ui.toggle.checkbox label {2773.ui.dropdown .menu > .item .dropdown.icon + .text {
2774 min-height: 1.5rem;2774 margin-right: 1em;
2775 padding-left: 4.5rem;
2776 color: rgba(0, 0, 0, 0.87);
2777}2775}
2778 2776
2779.ui.toggle.checkbox label {2777/*--------------
2780 padding-top: 0.15em;2778 Text
2779---------------*/
2780
2781.ui.dropdown > .text {
2782 display: inline-block;
2783 transition: none;
2781}2784}
2782 2785
2783/* Switch */2786/*--------------
2787 Menu Item
2788---------------*/
2784 2789
2785.ui.toggle.checkbox label:before {2790.ui.dropdown .menu > .item {
2791 position: relative;
2792 cursor: pointer;
2786 display: block;2793 display: block;
2787 position: absolute;
2788 content: '';
2789 z-index: 1;
2790 transform: none;
2791 border: none;2794 border: none;
2792 top: 0;2795 height: auto;
2793 background: rgba(0, 0, 0, 0.05);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;
2794 box-shadow: none;2805 box-shadow: none;
2795 width: 3.5rem;2806 -webkit-touch-callout: none;
2796 height: 1.5rem;
2797 border-radius: 500rem;
2798}2807}
2799 2808
2800/* Handle */2809.ui.dropdown .menu > .item:first-child {
2801 2810 border-top-width: 0;
2802.ui.toggle.checkbox label:after {
2803 background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05));
2804 position: absolute;
2805 content: '' !important;
2806 opacity: 1;
2807 z-index: 2;
2808 border: none;
2809 box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset;
2810 width: 1.5rem;
2811 height: 1.5rem;
2812 top: 0;
2813 left: 0;
2814 border-radius: 500rem;
2815 transition: background 0.3s ease, left 0.3s ease;
2816}2811}
2817 2812
2818.ui.toggle.checkbox input ~ label:after {2813.ui.dropdown .menu > .item.vertical {
2819 left: -0.05rem;2814 display: flex;
2820 box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset;2815 flex-direction: column-reverse;
2821}2816}
2822 2817
2823/* Focus */2818/*--------------
2819 Floated Content
2820---------------*/
2824 2821
2825.ui.toggle.checkbox input:focus ~ label:before {2822.ui.dropdown > .text > [class*="right floated"],
2826 background-color: rgba(0, 0, 0, 0.15);2823.ui.dropdown .menu .item > [class*="right floated"] {
2827 border: none;2824 float: right !important;
2825 margin-right: 0 !important;
2826 margin-left: 1em !important;
2828}2827}
2829 2828
2830/* Hover */2829.ui.dropdown > .text > [class*="left floated"],
2831 2830.ui.dropdown .menu .item > [class*="left floated"] {
2832.ui.toggle.checkbox label:hover::before {2831 float: left !important;
2833 background-color: rgba(0, 0, 0, 0.15);2832 margin-left: 0 !important;
2834 border: none;2833 margin-right: 1em !important;
2835}2834}
2836 2835
2837/* Active */2836.ui.dropdown .menu .item > i.icon.floated,
2838 2837.ui.dropdown .menu .item > .flag.floated,
2839.ui.toggle.checkbox input:checked ~ label {2838.ui.dropdown .menu .item > .image.floated,
2840 color: rgba(0, 0, 0, 0.95) !important;2839.ui.dropdown .menu .item > img.floated {
2840 margin-top: 0em;
2841}2841}
2842 2842
2843.ui.toggle.checkbox input:checked ~ label:before {2843/*--------------
2844 background-color: #2185D0 !important;2844 Menu Divider
2845}2845---------------*/
2846 2846
2847.ui.toggle.checkbox input:checked ~ label:after {2847.ui.dropdown .menu > .header {
2848 left: 2.15rem;2848 margin: 1rem 0 0.75rem;
2849 box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset;2849 padding: 0 1.14285714rem;
2850 font-weight: 500;
2851 text-transform: uppercase;
2850}2852}
2851 2853
2852/* Active Focus */2854.ui.dropdown .menu > .header:not(.ui) {
2853 2855 color: rgba(0, 0, 0, 0.85);
2854.ui.toggle.checkbox input:focus:checked ~ label {2856 font-size: 0.78571429em;
2855 color: rgba(0, 0, 0, 0.95) !important;
2856}2857}
2857 2858
2858.ui.toggle.checkbox input:focus:checked ~ label:before {2859.ui.dropdown .menu > .divider {
2859 background-color: #0d71bb !important;2860 border-top: 1px solid rgba(34, 36, 38, 0.1);
2861 height: 0;
2862 margin: 0.5em 0;
2860}2863}
2861 2864
2862/*******************************2865.ui.dropdown .menu > .horizontal.divider {
2863 Variations2866 border-top: none;
2864*******************************/
2865
2866/*--------------
2867 Fitted
2868 ---------------*/
2869
2870.ui.fitted.checkbox label {
2871 padding-left: 0 !important;
2872}2867}
2873 2868
2874.ui.fitted.toggle.checkbox {2869.ui.dropdown.dropdown .menu > .input {
2875 width: 3.5rem;2870 width: auto;
2871 display: flex;
2872 margin: 1.14285714rem 0.78571429rem;
2873 min-width: 10rem;
2876}2874}
2877 2875
2878.ui.fitted.slider.checkbox {2876.ui.dropdown .menu > .header + .input {
2879 width: 3.5rem;2877 margin-top: 0;
2880}2878}
2881 2879
2882/*--------------------2880.ui.dropdown .menu > .input:not(.transparent) input {
2883 Size2881 padding: 0.5em 1em;
2884---------------------*/
2885
2886.ui.mini.checkbox {
2887 font-size: 0.78571429em;
2888}2882}
2889 2883
2890.ui.tiny.checkbox {2884.ui.dropdown .menu > .input:not(.transparent) .button,
2891 font-size: 0.85714286em;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;
2892}2889}
2893 2890
2894.ui.small.checkbox {2891/*-----------------
2895 font-size: 0.92857143em;2892 Item Description
2896}2893-------------------*/
2897 2894
2898.ui.large.checkbox {2895.ui.dropdown > .text > .description,
2899 font-size: 1.14285714em;2896.ui.dropdown .menu > .item > .description {
2897 float: right;
2898 margin: 0 0 0 1em;
2899 color: rgba(0, 0, 0, 0.4);
2900}2900}
2901 2901
2902.ui.large.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after,2902.ui.dropdown .menu > .item.vertical > .description {
2903.ui.large.checkbox:not(.slider):not(.toggle):not(.radio) label:after,2903 margin: 0;
2904.ui.large.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before,
2905.ui.large.checkbox:not(.slider):not(.toggle):not(.radio) label:before {
2906 transform: scale(1.14285714);
2907 transform-origin: left;
2908}2904}
2909 2905
2910.ui.large.form .checkbox.radio label:before,2906/*-----------------
2911.ui.large.checkbox.radio label:before {2907 Item Text
2912 transform: scale(1.14285714);2908-------------------*/
2913 transform-origin: left;
2914}
2915 2909
2916.ui.large.form .checkbox.radio label:after,2910.ui.dropdown .menu > .item.vertical > .text {
2917.ui.large.checkbox.radio label:after {2911 margin-bottom: 0.25em;
2918 transform: scale(0.57142857);
2919 transform-origin: left;
2920 left: 0.33571429em;
2921}2912}
2922 2913
2923.ui.big.checkbox {2914/*-----------------
2924 font-size: 1.28571429em;2915 Message
2925}2916-------------------*/
2926 2917
2927.ui.big.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after,2918.ui.dropdown .menu > .message {
2928.ui.big.checkbox:not(.slider):not(.toggle):not(.radio) label:after,2919 padding: 0.78571429rem 1.14285714rem;
2929.ui.big.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before,2920 font-weight: normal;
2930.ui.big.checkbox:not(.slider):not(.toggle):not(.radio) label:before {
2931 transform: scale(1.28571429);
2932 transform-origin: left;
2933}2921}
2934 2922
2935.ui.big.form .checkbox.radio label:before,2923.ui.dropdown .menu > .message:not(.ui) {
2936.ui.big.checkbox.radio label:before {2924 color: rgba(0, 0, 0, 0.4);
2937 transform: scale(1.28571429);
2938 transform-origin: left;
2939}2925}
2940 2926
2941.ui.big.form .checkbox.radio label:after,2927/*--------------
2942.ui.big.checkbox.radio label:after {2928 Sub Menu
2943 transform: scale(0.64285714);2929---------------*/
2944 transform-origin: left;
2945 left: 0.37142857em;
2946}
2947 2930
2948.ui.huge.checkbox {2931.ui.dropdown .menu .menu {
2949 font-size: 1.42857143em;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;
2950}2938}
2951 2939
2952.ui.huge.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after,2940/* Hide Arrow */
2953.ui.huge.checkbox:not(.slider):not(.toggle):not(.radio) label:after,
2954.ui.huge.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before,
2955.ui.huge.checkbox:not(.slider):not(.toggle):not(.radio) label:before {
2956 transform: scale(1.42857143);
2957 transform-origin: left;
2958}
2959 2941
2960.ui.huge.form .checkbox.radio label:before,2942.ui.dropdown .menu .menu:after {
2961.ui.huge.checkbox.radio label:before {2943 display: none;
2962 transform: scale(1.42857143);
2963 transform-origin: left;
2964}2944}
2965 2945
2966.ui.huge.form .checkbox.radio label:after,2946/*--------------
2967.ui.huge.checkbox.radio label:after {2947 Sub Elements
2968 transform: scale(0.71428571);2948---------------*/
2969 transform-origin: left;
2970 left: 0.40714286em;
2971}
2972 2949
2973.ui.massive.checkbox {2950/* Icons / Flags / Labels / Image */
2974 font-size: 1.71428571em;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;
2975}2958}
2976 2959
2977.ui.massive.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after,2960.ui.dropdown .menu > .item > i.icon,
2978.ui.massive.checkbox:not(.slider):not(.toggle):not(.radio) label:after,2961.ui.dropdown .menu > .item > .label,
2979.ui.massive.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before,2962.ui.dropdown .menu > .item > .flag,
2980.ui.massive.checkbox:not(.slider):not(.toggle):not(.radio) label:before {2963.ui.dropdown .menu > .item > .image,
2981 transform: scale(1.71428571);2964.ui.dropdown .menu > .item > img {
2982 transform-origin: left;2965 margin-top: 0em;
2983}2966}
2984 2967
2985.ui.massive.form .checkbox.radio label:before,2968.ui.dropdown > .text > i.icon,
2986.ui.massive.checkbox.radio label:before {2969.ui.dropdown > .text > .label,
2987 transform: scale(1.71428571);2970.ui.dropdown > .text > .flag,
2988 transform-origin: left;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;
2989}2981}
2990 2982
2991.ui.massive.form .checkbox.radio label:after,2983/*--------------
2992.ui.massive.checkbox.radio label:after {2984 Image
2993 transform: scale(0.85714286);2985---------------*/
2994 transform-origin: left;2986
2995 left: 0.47857143em;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;
2996}2997}
2997 2998
2998/*******************************2999/*******************************
2999 Theme Overrides3000 Coupling
3000*******************************/3001*******************************/
3001 3002
3002@font-face {3003/*--------------
3003 font-family: 'Checkbox';3004 Menu
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---------------*/
3005}
3006 3006
3007/* Checkmark */3007/* Remove Menu Item Divider */
3008 3008
3009.ui.checkbox label:after,3009.ui.dropdown .ui.menu > .item:before,
3010.ui.checkbox .box:after {3010.ui.menu .ui.dropdown .menu > .item:before {
3011 font-family: 'Checkbox';3011 display: none;
3012}3012}
3013 3013
3014/* Checked */3014/* Prevent Menu Item Border */
3015 3015
3016.ui.checkbox input:checked ~ .box:after,3016.ui.menu .ui.dropdown .menu .active.item {
3017.ui.checkbox input:checked ~ label:after {3017 border-left: none;
3018 content: '\e800';
3019}3018}
3020 3019
3021/* Indeterminate */3020/* Automatically float dropdown menu right on last menu item */
3022 3021
3023.ui.checkbox input:indeterminate ~ .box:after,3022.ui.menu .right.menu .dropdown:last-child > .menu:not(.left),
3024.ui.checkbox input:indeterminate ~ label:after {3023.ui.menu .right.dropdown.item > .menu:not(.left),
3025 font-size: 12px;3024.ui.buttons > .ui.dropdown:last-child > .menu:not(.left) {
3026 content: '\e801';3025 left: auto;
3026 right: 0;
3027}3027}
3028 3028
3029/* UTF Reference3029/*--------------
3030.check:before { content: '\e800'; }3030 Label
3031.dash:before { content: '\e801'; }3031 ---------------*/
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 */
3047 3032
3048/*******************************3033/* Dropdown Menu */
3049 Dimmer
3050*******************************/
3051 3034
3052.dimmable:not(body) {3035.ui.label.dropdown .menu {
3053 position: relative;3036 min-width: 100%;
3054}3037}
3055 3038
3056.ui.dimmer {3039/*--------------
3057 display: none;3040 Button
3058 position: absolute;3041 ---------------*/
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}
3081 3042
3082/* Dimmer Content */3043/* No Margin On Icon Button */
3083 3044
3084.ui.dimmer > .content {3045.ui.dropdown.icon.button > .dropdown.icon {
3085 -webkit-user-select: text;3046 margin: 0;
3086 -moz-user-select: text;
3087 user-select: text;
3088 color: #FFFFFF;
3089}3047}
3090 3048
3091/* Loose Coupling */3049.ui.button.dropdown .menu {
3092 3050 min-width: 100%;
3093.ui.segment > .ui.dimmer:not(.page) {
3094 border-radius: inherit;
3095}3051}
3096 3052
3097/* Scrollbars */
3098
3099/*******************************3053/*******************************
3100 States3054 Types
3101*******************************/3055*******************************/
3102 3056
3103/* Animating */3057select.ui.dropdown {
3104 3058 height: 38px;
3105.animating.dimmable:not(body),3059 padding: 0.5em;
3106.dimmed.dimmable:not(body) {3060 border: 1px solid rgba(34, 36, 38, 0.15);
3107 overflow: hidden;3061 visibility: visible;
3108}3062}
3109 3063
3110/* Animating / Active / Visible */3064/*--------------
3065 Selection
3066 ---------------*/
3111 3067
3112.dimmed.dimmable > .ui.animating.dimmer,3068/* Displays like a select box */
3113.dimmed.dimmable > .ui.visible.dimmer,
3114.ui.active.dimmer {
3115 display: flex;
3116 opacity: 1;
3117}
3118
3119/* Disabled */
3120 3069
3121.ui.disabled.dimmer {3070.ui.selection.dropdown {
3122 width: 0 !important;3071 cursor: pointer;
3123 height: 0 !important;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;
3124}3087}
3125 3088
3126/*******************************3089.ui.selection.dropdown.visible,
3127 Variations3090.ui.selection.dropdown.active {
3128*******************************/3091 z-index: 10;
3092}
3129 3093
3130/*--------------3094.ui.selection.dropdown > .search.icon,
3131 Legacy3095.ui.selection.dropdown > .delete.icon,
3132 ---------------*/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}
3133 3110
3134/* Animating / Active / Visible */3111/* Compact */
3135 3112
3136.dimmed.dimmable > .ui.animating.legacy.dimmer,3113.ui.compact.selection.dropdown {
3137.dimmed.dimmable > .ui.visible.legacy.dimmer,3114 min-width: 0;
3138.ui.active.legacy.dimmer {
3139 display: block;
3140}3115}
3141 3116
3142/*--------------3117/* Selection Menu */
3143 Alignment
3144 ---------------*/
3145 3118
3146.ui[class*="top aligned"].dimmer {3119.ui.selection.dropdown .menu {
3147 justify-content: flex-start;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;
3148}3133}
3149 3134
3150.ui[class*="bottom aligned"].dimmer {3135.ui.selection.dropdown .menu:after,
3151 justify-content: flex-end;3136.ui.selection.dropdown .menu:before {
3137 display: none;
3152}3138}
3153 3139
3154/*--------------3140/*--------------
3155 Page3141 Message
3156 ---------------*/3142 ---------------*/
3157 3143
3158.ui.page.dimmer {3144.ui.selection.dropdown .menu > .message {
3159 position: fixed;3145 padding: 0.78571429rem 1.14285714rem;
3160 transform-style: '';
3161 perspective: 2000px;
3162 transform-origin: center center;
3163}3146}
3164 3147
3165.ui.page.dimmer.modals {3148@media only screen and (max-width: 767.98px) {
3166 -moz-perspective: none;3149 .ui.selection.dropdown.short .menu {
3167}3150 max-height: 6.01071429rem;
3151 }
3168 3152
3169body.animating.in.dimmable,3153 .ui.selection.dropdown[class*="very short"] .menu {
3170body.dimmed.dimmable {3154 max-height: 4.00714286rem;
3171 overflow: hidden;3155 }
3172}
3173 3156
3174body.dimmable > .dimmer {3157 .ui.selection.dropdown .menu {
3175 position: fixed;3158 max-height: 8.01428571rem;
3176}3159 }
3177 3160
3178/*--------------3161 .ui.selection.dropdown.long .menu {
3179 Blurring3162 max-height: 16.02857143rem;
3180 ---------------*/3163 }
3181 3164
3182.blurring.dimmable > :not(.dimmer) {3165 .ui.selection.dropdown[class*="very long"] .menu {
3183 filter: initial;3166 max-height: 24.04285714rem;
3184 transition: 800ms filter ease;3167 }
3185}3168}
3186 3169
3187.blurring.dimmed.dimmable > :not(.dimmer):not(.popup) {3170@media only screen and (min-width: 768px) {
3188 filter: blur(5px) grayscale(0.7);3171 .ui.selection.dropdown.short .menu {
3189}3172 max-height: 8.01428571rem;
3173 }
3190 3174
3191/* Dimmer Color */3175 .ui.selection.dropdown[class*="very short"] .menu {
3176 max-height: 5.34285714rem;
3177 }
3192 3178
3193.blurring.dimmable > .dimmer {3179 .ui.selection.dropdown .menu {
3194 background: rgba(0, 0, 0, 0.6);3180 max-height: 10.68571429rem;
3195}3181 }
3196 3182
3197.blurring.dimmable > .inverted.dimmer {3183 .ui.selection.dropdown.long .menu {
3198 background: rgba(255, 255, 255, 0.6);3184 max-height: 21.37142857rem;
3185 }
3186
3187 .ui.selection.dropdown[class*="very long"] .menu {
3188 max-height: 32.05714286rem;
3189 }
3199}3190}
3200 3191
3201/*--------------3192@media only screen and (min-width: 992px) {
3202 Aligned3193 .ui.selection.dropdown.short .menu {
3203 ---------------*/3194 max-height: 12.02142857rem;
3195 }
3204 3196
3205.ui.dimmer > .top.aligned.content > * {3197 .ui.selection.dropdown[class*="very short"] .menu {
3206 vertical-align: top;3198 max-height: 8.01428571rem;
3207}3199 }
3208 3200
3209.ui.dimmer > .bottom.aligned.content > * {3201 .ui.selection.dropdown .menu {
3210 vertical-align: bottom;3202 max-height: 16.02857143rem;
3211}3203 }
3212 3204
3213/*--------------3205 .ui.selection.dropdown.long .menu {
3214 Shades3206 max-height: 32.05714286rem;
3215 ---------------*/3207 }
3216 3208
3217.medium.medium.medium.medium.medium.dimmer {3209 .ui.selection.dropdown[class*="very long"] .menu {
3218 background: rgba(0, 0, 0, 0.65);3210 max-height: 48.08571429rem;
3211 }
3219}3212}
3220 3213
3221.light.light.light.light.light.dimmer {3214@media only screen and (min-width: 1920px) {
3222 background: rgba(0, 0, 0, 0.45);3215 .ui.selection.dropdown.short .menu {
3223}3216 max-height: 16.02857143rem;
3217 }
3224 3218
3225.very.light.light.light.light.dimmer {3219 .ui.selection.dropdown[class*="very short"] .menu {
3226 background: rgba(0, 0, 0, 0.25);3220 max-height: 10.68571429rem;
3227}3221 }
3228 3222
3229/*--------------3223 .ui.selection.dropdown .menu {
3230 Simple3224 max-height: 21.37142857rem;
3231 ---------------*/3225 }
3232 3226
3233/* Displays without javascript */3227 .ui.selection.dropdown.long .menu {
3228 max-height: 42.74285714rem;
3229 }
3234 3230
3235.ui.simple.dimmer {3231 .ui.selection.dropdown[class*="very long"] .menu {
3236 display: block;3232 max-height: 64.11428571rem;
3237 overflow: hidden;3233 }
3238 opacity: 0;
3239 width: 0;
3240 height: 0;
3241 z-index: -100;
3242 background: rgba(0, 0, 0, 0);
3243}3234}
3244 3235
3245.dimmed.dimmable > .ui.simple.dimmer {3236/* Menu Item */
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}
3253 3237
3254.ui.simple.inverted.dimmer {3238.ui.selection.dropdown .menu > .item {
3255 background: rgba(255, 255, 255, 0);3239 border-top: 1px solid #FAFAFA;
3240 padding: 0.78571429rem 1.14285714rem !important;
3241 white-space: normal;
3242 word-wrap: normal;
3256}3243}
3257 3244
3258.dimmed.dimmable > .ui.simple.inverted.dimmer {3245/* User Item */
3259 background: rgba(255, 255, 255, 0.85);3246
3247.ui.selection.dropdown .menu > .hidden.addition.item {
3248 display: none;
3260}3249}
3261 3250
3262/*--------------3251/* Hover */
3263 Partially
3264 ----------------*/
3265 3252
3266.ui[class*="top dimmer"],3253.ui.selection.dropdown:hover {
3267.ui[class*="center dimmer"],3254 border-color: rgba(34, 36, 38, 0.35);
3268.ui[class*="bottom dimmer"] {3255 box-shadow: none;
3269 height: auto;
3270}3256}
3271 3257
3272.ui[class*="bottom dimmer"] {3258/* Active */
3273 top: auto !important;3259
3274 bottom: 0;3260.ui.selection.active.dropdown {
3261 border-color: #96C8DA;
3262 box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3275}3263}
3276 3264
3277.ui[class*="center dimmer"] {3265.ui.selection.active.dropdown .menu {
3278 top: 50% !important;3266 border-color: #96C8DA;
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;
3444 box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);3267 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;
3474}3268}
3475 3269
3476.ui.dropdown .menu > .item .dropdown.icon {3270/* Focus */
3477 width: auto;
3478 float: right;
3479 margin: 0em 0 0 1em;
3480}
3481 3271
3482.ui.dropdown .menu > .item .dropdown.icon + .text {3272.ui.selection.dropdown:focus {
3483 margin-right: 1em;3273 border-color: #96C8DA;
3274 box-shadow: none;
3484}3275}
3485 3276
3486/*--------------3277.ui.selection.dropdown:focus .menu {
3487 Text3278 border-color: #96C8DA;
3488---------------*/3279 box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3489
3490.ui.dropdown > .text {
3491 display: inline-block;
3492 transition: none;
3493}3280}
3494 3281
3495/*--------------3282/* Visible */
3496 Menu Item
3497---------------*/
3498 3283
3499.ui.dropdown .menu > .item {3284.ui.selection.visible.dropdown > .text:not(.default) {
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;
3513 font-weight: normal;3285 font-weight: normal;
3514 box-shadow: none;3286 color: rgba(0, 0, 0, 0.8);
3515 -webkit-touch-callout: none;
3516}3287}
3517 3288
3518.ui.dropdown .menu > .item:first-child {3289/* Visible Hover */
3519 border-top-width: 0;
3520}
3521 3290
3522.ui.dropdown .menu > .item.vertical {3291.ui.selection.active.dropdown:hover {
3523 display: flex;3292 border-color: #96C8DA;
3524 flex-direction: column-reverse;3293 box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
3525}3294}
3526 3295
3527/*--------------3296.ui.selection.active.dropdown:hover .menu {
3528 Floated Content3297 border-color: #96C8DA;
3529---------------*/3298 box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
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
3766select.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;
5889}3299}
5890 3300
5891.ui.form .field.error textarea:focus,3301/* Dropdown Icon */
5892.ui.form .field.error select:focus,3302
5893.ui.form .field.error input:not([type]):focus,3303.ui.active.selection.dropdown > .dropdown.icon,
5894.ui.form .field.error input[type="date"]:focus,3304.ui.visible.selection.dropdown > .dropdown.icon {
5895.ui.form .field.error input[type="datetime-local"]:focus,3305 opacity: '';
5896.ui.form .field.error input[type="email"]:focus,3306 z-index: 3;
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;
5909}3307}
5910 3308
5911/* Preserve Native Select Stylings */3309/* Connecting Border */
5912 3310
5913.ui.form .field.error select {3311.ui.active.selection.dropdown {
5914 -webkit-appearance: menulist-button;3312 border-bottom-left-radius: 0 !important;
3313 border-bottom-right-radius: 0 !important;
5915}3314}
5916 3315
5917/*------------------3316/* Empty Connecting Border */
5918 Input State
5919 --------------------*/
5920 3317
5921/* Transparent */3318.ui.active.empty.selection.dropdown {
3319 border-radius: 0.28571429rem !important;
3320 box-shadow: none !important;
3321}
5922 3322
5923.ui.form .field.error .transparent.input input,3323.ui.active.empty.selection.dropdown .menu {
5924.ui.form .field.error .transparent.input textarea,3324 border: none !important;
5925.ui.form .field.error input.transparent,3325 box-shadow: none !important;
5926.ui.form .field.error textarea.transparent {
5927 background-color: #FFF6F6 !important;
5928 color: #9F3A38 !important;
5929}3326}
5930 3327
5931/* Autofilled */3328/* CSS specific to iOS devices or firefox mobile only */
5932 3329
5933.ui.form .error.error input:-webkit-autofill {3330@supports (-webkit-touch-callout: none) or (-webkit-overflow-scrolling: touch) or (-moz-appearance:none) {
5934 box-shadow: 0 0 0 100px #FFFAF0 inset !important;3331@media (-moz-touch-enabled), (pointer: coarse) {
5935 border-color: #E0B4B4 !important;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}
5936}3365}
5937 3366
5938/* Placeholder */3367/*--------------
3368 Searchable
3369 ---------------*/
5939 3370
5940.ui.form .error ::-webkit-input-placeholder {3371/* Search Selection */
5941 color: #e7bdbc;3372
3373.ui.search.dropdown {
3374 min-width: '';
5942}3375}
5943 3376
5944.ui.form .error :-ms-input-placeholder {3377/* Search Dropdown */
5945 color: #e7bdbc !important;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;
5946}3390}
5947 3391
5948.ui.form .error ::-moz-placeholder {3392/* Text Layering */
5949 color: #e7bdbc;3393
3394.ui.search.dropdown > input.search {
3395 position: absolute;
3396 z-index: 2;
5950}3397}
5951 3398
5952.ui.form .error :focus::-webkit-input-placeholder {3399.ui.search.dropdown > .text {
5953 color: #da9796;3400 cursor: text;
3401 position: relative;
3402 left: 1px;
3403 z-index: auto;
5954}3404}
5955 3405
5956.ui.form .error :focus:-ms-input-placeholder {3406/* Search Selection */
5957 color: #da9796 !important;3407
3408.ui.search.selection.dropdown > input.search {
3409 line-height: 1.21428571em;
3410 padding: 0.67857143em 3.2em 0.67857143em 1em;
5958}3411}
5959 3412
5960.ui.form .error :focus::-moz-placeholder {3413/* Used to size multi select input to character width */
5961 color: #da9796;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;
5962}3420}
5963 3421
5964/*------------------3422/* Active/Visible Search */
5965 Dropdown State
5966 --------------------*/
5967 3423
5968.ui.form .fields.error .field .ui.dropdown,3424.ui.search.dropdown.active > input.search,
5969.ui.form .fields.error .field .ui.dropdown .item,3425.ui.search.dropdown.visible > input.search {
5970.ui.form .field.error .ui.dropdown,3426 cursor: auto;
5971.ui.form .field.error .ui.dropdown .text,
5972.ui.form .field.error .ui.dropdown .item {
5973 background: #FFF6F6;
5974 color: #9F3A38;
5975}3427}
5976 3428
5977.ui.form .fields.error .field .ui.dropdown,3429.ui.search.dropdown.active > .text,
5978.ui.form .field.error .ui.dropdown {3430.ui.search.dropdown.visible > .text {
5979 border-color: #E0B4B4 !important;3431 pointer-events: none;
5980}3432}
5981 3433
5982.ui.form .fields.error .field .ui.dropdown:hover,3434/* Filtered Text */
5983.ui.form .field.error .ui.dropdown:hover {3435
5984 border-color: #E0B4B4 !important;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);
5985}3439}
5986 3440
5987.ui.form .fields.error .field .ui.dropdown:hover .menu,3441.ui.active.search.dropdown input.search:focus + .text {
5988.ui.form .field.error .ui.dropdown:hover .menu {3442 color: rgba(115, 115, 115, 0.87) !important;
5989 border-color: #E0B4B4;
5990}3443}
5991 3444
5992.ui.form .fields.error .field .ui.multiple.selection.dropdown > .label,3445.ui.search.dropdown.button > span.sizer {
5993.ui.form .field.error .ui.multiple.selection.dropdown > .label {3446 display: none;
5994 background-color: #EACBCB;
5995 color: #9F3A38;
5996}3447}
5997 3448
5998/* Hover */3449/* Search Menu */
5999 3450
6000.ui.form .fields.error .field .ui.dropdown .menu .item:hover,3451.ui.search.dropdown .menu {
6001.ui.form .field.error .ui.dropdown .menu .item:hover {3452 overflow-x: hidden;
6002 background-color: #FBE7E7;3453 overflow-y: auto;
3454 backface-visibility: hidden;
3455 -webkit-overflow-scrolling: touch;
6003}3456}
6004 3457
6005/* Selected */3458@media only screen and (max-width: 767.98px) {
3459 .ui.search.dropdown .menu {
3460 max-height: 8.01428571rem;
3461 }
3462}
6006 3463
6007.ui.form .fields.error .field .ui.dropdown .menu .selected.item,3464@media only screen and (min-width: 768px) {
6008.ui.form .field.error .ui.dropdown .menu .selected.item {3465 .ui.search.dropdown .menu {
6009 background-color: #FBE7E7;3466 max-height: 10.68571429rem;
3467 }
6010}3468}
6011 3469
6012/* Active */3470@media only screen and (min-width: 992px) {
3471 .ui.search.dropdown .menu {
3472 max-height: 16.02857143rem;
3473 }
3474}
6013 3475
6014.ui.form .fields.error .field .ui.dropdown .menu .active.item,3476@media only screen and (min-width: 1920px) {
6015.ui.form .field.error .ui.dropdown .menu .active.item {3477 .ui.search.dropdown .menu {
6016 background-color: #FDCFCF !important;3478 max-height: 21.37142857rem;
3479 }
6017}3480}
6018 3481
6019/*--------------------3482/* Clearable Selection */
6020 Checkbox State
6021 ---------------------*/
6022 3483
6023.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label,3484.ui.dropdown > .remove.icon {
6024.ui.form .field.error .checkbox:not(.toggle):not(.slider) label,3485 cursor: pointer;
6025.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box,3486 font-size: 0.85714286em;
6026.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box {3487 margin: -0.78571429em;
6027 color: #9F3A38;3488 padding: 0.91666667em;
3489 right: 3em;
3490 top: 0.78571429em;
3491 position: absolute;
3492 opacity: 0.6;
3493 z-index: 3;
6028}3494}
6029 3495
6030.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label:before,3496.ui.clearable.dropdown .text,
6031.ui.form .field.error .checkbox:not(.toggle):not(.slider) label:before,3497.ui.clearable.dropdown a:last-of-type {
6032.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box:before,3498 margin-right: 1.5em;
6033.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box:before {3499}
6034 background: #FFF6F6;3500
6035 border-color: #E0B4B4;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;
6036}3506}
6037 3507
6038.ui.form .fields.error .field .checkbox label:after,3508/*--------------
6039.ui.form .field.error .checkbox label:after,3509 Multiple
6040.ui.form .fields.error .field .checkbox .box:after,3510 ---------------*/
6041.ui.form .field.error .checkbox .box:after {
6042 color: #9F3A38;
6043}
6044 3511
6045/* On Form */3512/* Multiple Selection */
6046 3513
6047.ui.form.info .info.message:not(:empty) {3514.ui.ui.multiple.dropdown {
6048 display: block;3515 padding: 0.22619048em 3.2em 0.22619048em 0.35714286em;
6049}3516}
6050 3517
6051.ui.form.info .compact.info.message:not(:empty) {3518.ui.multiple.dropdown .menu {
6052 display: inline-block;3519 cursor: auto;
6053}3520}
6054 3521
6055.ui.form.info .icon.info.message:not(:empty) {3522/* Selection Label */
6056 display: flex;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;
6057}3531}
6058 3532
6059/* On Field(s) */3533/* Dropdown Icon */
6060 3534
6061.ui.form .fields.info .info.message:not(:empty),3535.ui.multiple.dropdown .dropdown.icon {
6062.ui.form .field.info .info.message:not(:empty) {3536 margin: '';
6063 display: block;3537 padding: '';
6064}3538}
6065 3539
6066.ui.form .fields.info .compact.info.message:not(:empty),3540/* Text */
6067.ui.form .field.info .compact.info.message:not(:empty) {
6068 display: inline-block;
6069}
6070 3541
6071.ui.form .fields.info .icon.info.message:not(:empty),3542.ui.multiple.dropdown > .text {
6072.ui.form .field.info .icon.info.message:not(:empty) {3543 position: static;
6073 display: flex;3544 padding: 0;
3545 max-width: 100%;
3546 margin: 0.45238095em 0 0.45238095em 0.64285714em;
3547 line-height: 1.21428571em;
6074}3548}
6075 3549
6076.ui.ui.form .fields.info .field label,3550.ui.multiple.dropdown > .text.default {
6077.ui.ui.form .field.info label,3551 white-space: nowrap;
6078.ui.ui.form .fields.info .field .input,3552 overflow: hidden;
6079.ui.ui.form .field.info .input {3553 text-overflow: ellipsis;
6080 color: #276F86;
6081}3554}
6082 3555
6083.ui.form .fields.info .field .corner.label,3556.ui.multiple.dropdown > .label ~ input.search {
6084.ui.form .field.info .corner.label {3557 margin-left: 0.14285714em !important;
6085 border-color: #276F86;
6086 color: #FFFFFF;
6087}3558}
6088 3559
6089.ui.form .fields.info .field textarea,3560.ui.multiple.dropdown > .label ~ .text {
6090.ui.form .fields.info .field select,3561 display: none;
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;
6122}3562}
6123 3563
6124.ui.form .field.info textarea:focus,3564.ui.multiple.dropdown > .label:not(.image) > img:not(.centered) {
6125.ui.form .field.info select:focus,3565 margin-right: 0.78571429rem;
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;
6142}3566}
6143 3567
6144/* Preserve Native Select Stylings */3568.ui.multiple.dropdown > .label:not(.image) > img.ui:not(.avatar) {
3569 margin-bottom: 0.39285714rem;
3570}
6145 3571
6146.ui.form .field.info select {3572.ui.multiple.dropdown > .image.label img {
6147 -webkit-appearance: menulist-button;3573 margin: -0.35714286em 0.78571429em -0.35714286em -0.78571429em;
3574 height: 1.71428571em;
6148}3575}
6149 3576
6150/*------------------3577/*-----------------
6151 Input State3578 Multiple Search
6152 --------------------*/3579 -----------------*/
6153 3580
6154/* Transparent */3581/* Multiple Search Selection */
6155 3582
6156.ui.form .field.info .transparent.input input,3583.ui.multiple.search.dropdown,
6157.ui.form .field.info .transparent.input textarea,3584.ui.multiple.search.dropdown > input.search {
6158.ui.form .field.info input.transparent,3585 cursor: text;
6159.ui.form .field.info textarea.transparent {
6160 background-color: #F8FFFF !important;
6161 color: #276F86 !important;
6162}3586}
6163 3587
6164/* Autofilled */3588/* Prompt Text */
6165 3589
6166.ui.form .info.info input:-webkit-autofill {3590.ui.multiple.search.dropdown > .text {
6167 box-shadow: 0 0 0 100px #F0FAFF inset !important;3591 display: inline-block;
6168 border-color: #b3e0e0 !important;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;
6169}3598}
6170 3599
6171/* Placeholder */3600.ui.multiple.search.dropdown > .label ~ .text {
6172 3601 display: none;
6173.ui.form .info ::-webkit-input-placeholder {
6174 color: #98cfe1;
6175}3602}
6176 3603
6177.ui.form .info :-ms-input-placeholder {3604/* Search */
6178 color: #98cfe1 !important;
6179}
6180 3605
6181.ui.form .info ::-moz-placeholder {3606.ui.multiple.search.dropdown > input.search {
6182 color: #98cfe1;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;
6183}3613}
6184 3614
6185.ui.form .info :focus::-webkit-input-placeholder {3615.ui.multiple.search.dropdown.button {
6186 color: #70bdd6;3616 min-width: 14em;
6187}3617}
6188 3618
6189.ui.form .info :focus:-ms-input-placeholder {3619/*--------------
6190 color: #70bdd6 !important;3620 Inline
6191}3621 ---------------*/
6192 3622
6193.ui.form .info :focus::-moz-placeholder {3623.ui.inline.dropdown {
6194 color: #70bdd6;3624 cursor: pointer;
3625 display: inline-block;
3626 color: inherit;
6195}3627}
6196 3628
6197/*------------------3629.ui.inline.dropdown .dropdown.icon {
6198 Dropdown State3630 margin: 0 0.21428571em 0 0.21428571em;
6199 --------------------*/3631 vertical-align: baseline;
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;
6208}3632}
6209 3633
6210.ui.form .fields.info .field .ui.dropdown,3634.ui.inline.dropdown > .text {
6211.ui.form .field.info .ui.dropdown {3635 font-weight: 500;
6212 border-color: #A9D5DE !important;
6213}3636}
6214 3637
6215.ui.form .fields.info .field .ui.dropdown:hover,3638.ui.inline.dropdown .menu {
6216.ui.form .field.info .ui.dropdown:hover {3639 cursor: auto;
6217 border-color: #A9D5DE !important;3640 margin-top: 0.21428571em;
3641 border-radius: 0.28571429rem;
6218}3642}
6219 3643
6220.ui.form .fields.info .field .ui.dropdown:hover .menu,3644/*******************************
6221.ui.form .field.info .ui.dropdown:hover .menu {3645 States
6222 border-color: #A9D5DE;3646*******************************/
6223}
6224 3647
6225.ui.form .fields.info .field .ui.multiple.selection.dropdown > .label,3648/*--------------------
6226.ui.form .field.info .ui.multiple.selection.dropdown > .label {3649 Active
6227 background-color: #cce3ea;3650----------------------*/
6228 color: #276F86;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;
6229}3660}
6230 3661
6231/* Hover */3662/*--------------------
3663 Hover
3664----------------------*/
6232 3665
6233.ui.form .fields.info .field .ui.dropdown .menu .item:hover,3666/* Menu Item Hover */
6234.ui.form .field.info .ui.dropdown .menu .item:hover {3667
6235 background-color: #e9f2fb;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;
6236}3672}
6237 3673
6238/* Selected */3674/*--------------------
3675 Default Text
3676----------------------*/
6239 3677
6240.ui.form .fields.info .field .ui.dropdown .menu .selected.item,3678.ui.dropdown:not(.button) > .default.text,
6241.ui.form .field.info .ui.dropdown .menu .selected.item {3679.ui.default.dropdown:not(.button) > .text {
6242 background-color: #e9f2fb;3680 color: rgba(191, 191, 191, 0.87);
6243}3681}
6244 3682
6245/* Active */3683.ui.dropdown:not(.button) > input:focus ~ .default.text,
6246 3684.ui.default.dropdown:not(.button) > input:focus ~ .text {
6247.ui.form .fields.info .field .ui.dropdown .menu .active.item,3685 color: rgba(115, 115, 115, 0.87);
6248.ui.form .field.info .ui.dropdown .menu .active.item {
6249 background-color: #cef1fd !important;
6250}3686}
6251 3687
6252/*--------------------3688/*--------------------
6253 Checkbox State3689 Loading
6254 ---------------------*/3690 ---------------------*/
6255 3691
6256.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label,3692.ui.loading.dropdown > i.icon {
6257.ui.form .field.info .checkbox:not(.toggle):not(.slider) label,3693 height: 1em !important;
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;
6261}3694}
6262 3695
6263.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label:before,3696.ui.loading.selection.dropdown > i.icon {
6264.ui.form .field.info .checkbox:not(.toggle):not(.slider) label:before,3697 padding: 1.5em 1.28571429em !important;
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;
6269}3698}
6270 3699
6271.ui.form .fields.info .field .checkbox label:after,3700.ui.loading.dropdown > i.icon:before {
6272.ui.form .field.info .checkbox label:after,3701 position: absolute;
6273.ui.form .fields.info .field .checkbox .box:after,3702 content: '';
6274.ui.form .field.info .checkbox .box:after {3703 top: 50%;
6275 color: #276F86;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);
6276}3710}
6277 3711
6278/* On Form */3712.ui.loading.dropdown > i.icon:after {
6279 3713 position: absolute;
6280.ui.form.success .success.message:not(:empty) {3714 content: '';
6281 display: block;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;
6282}3724}
6283 3725
6284.ui.form.success .compact.success.message:not(:empty) {3726/* Coupling */
6285 display: inline-block;3727
3728.ui.loading.dropdown.button > i.icon:before,
3729.ui.loading.dropdown.button > i.icon:after {
3730 display: none;
6286}3731}
6287 3732
6288.ui.form.success .icon.success.message:not(:empty) {3733.ui.loading.dropdown > .text {
6289 display: flex;3734 transition: none;
6290}3735}
6291 3736
6292/* On Field(s) */3737/* Used To Check Position */
6293 3738
6294.ui.form .fields.success .success.message:not(:empty),3739.ui.dropdown .loading.menu {
6295.ui.form .field.success .success.message:not(:empty) {
6296 display: block;3740 display: block;
3741 visibility: hidden;
3742 z-index: -1;
6297}3743}
6298 3744
6299.ui.form .fields.success .compact.success.message:not(:empty),3745.ui.dropdown > .loading.menu {
6300.ui.form .field.success .compact.success.message:not(:empty) {3746 left: 0 !important;
6301 display: inline-block;3747 right: auto !important;
6302}3748}
6303 3749
6304.ui.form .fields.success .icon.success.message:not(:empty),3750.ui.dropdown > .menu .loading.menu {
6305.ui.form .field.success .icon.success.message:not(:empty) {3751 left: 100% !important;
6306 display: flex;3752 right: auto !important;
6307}3753}
6308 3754
6309.ui.ui.form .fields.success .field label,3755/*--------------------
6310.ui.ui.form .field.success label,3756 Keyboard Select
6311.ui.ui.form .fields.success .field .input,3757----------------------*/
6312.ui.ui.form .field.success .input {
6313 color: #2C662D;
6314}
6315 3758
6316.ui.form .fields.success .field .corner.label,3759/* Selected Item */
6317.ui.form .field.success .corner.label {
6318 border-color: #2C662D;
6319 color: #FFFFFF;
6320}
6321 3760
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
1184 1184
1185 1185
1186 1186
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
1207window = (typeof window != 'undefined' && window.Math == Math)
1208 ? window
1209 : (typeof self != 'undefined' && self.Math == Math)
1210 ? self
1211 : Function('return this')()
1212;
1213
1214$.fn.checkbox = function(parameters) {
1215 var
1216 $allModules = $(this),
1217 moduleSelector = $allModules.selector || '',
1218
1219 time = new Date().getTime(),
1220 performance = [],
1221
1222 query = arguments[0],
1223 methodInvoked = (typeof query == 'string'),
1224 queryArguments = [].slice.call(arguments, 1),
1225 returnedValue
1226 ;
1227
1228 $allModules
1229 .each(function() {
1230 var
1231 settings = $.extend(true, {}, $.fn.checkbox.settings, parameters),
1232
1233 className = settings.className,
1234 namespace = settings.namespace,
1235 selector = settings.selector,
1236 error = settings.error,
1237
1238 eventNamespace = '.' + namespace,
1239 moduleNamespace = 'module-' + namespace,
1240
1241 $module = $(this),
1242 $label = $(this).children(selector.label),
1243 $input = $(this).children(selector.input),
1244 input = $input[0],
1245
1246 initialLoad = false,
1247 shortcutPressed = false,
1248 instance = $module.data(moduleNamespace),
1249
1250 observer,
1251 element = this,
1252 module
1253 ;
1254
1255 module = {
1256
1257 initialize: function() {
1258 module.verbose('Initializing checkbox', settings);
1259
1260 module.create.label();
1261 module.bind.events();
1262
1263 module.set.tabbable();
1264 module.hide.input();
1265
1266 module.observeChanges();
1267 module.instantiate();
1268 module.setup();
1269 },
1270
1271 instantiate: function() {
1272 module.verbose('Storing instance of module', module);
1273 instance = module;
1274 $module
1275 .data(moduleNamespace, module)
1276 ;
1277 },
1278
1279 destroy: function() {
1280 module.verbose('Destroying module');
1281 module.unbind.events();
1282 module.show.input();
1283 $module.removeData(moduleNamespace);
1284 },
1285
1286 fix: {
1287 reference: function() {
1288 if( $module.is(selector.input) ) {
1289 module.debug('Behavior called on <input> adjusting invoked element');
1290 $module = $module.closest(selector.checkbox);
1291 module.refresh();
1292 }
1293 }
1294 },
1295
1296 setup: function() {
1297 module.set.initialLoad();
1298 if( module.is.indeterminate() ) {
1299 module.debug('Initial value is indeterminate');
1300 module.indeterminate();
1301 }
1302 else if( module.is.checked() ) {
1303 module.debug('Initial value is checked');
1304 module.check();
1305 }
1306 else {
1307 module.debug('Initial value is unchecked');
1308 module.uncheck();
1309 }
1310 module.remove.initialLoad();
1311 },
1312
1313 refresh: function() {
1314 $label = $module.children(selector.label);
1315 $input = $module.children(selector.input);
1316 input = $input[0];
1317 },
1318
1319 hide: {
1320 input: function() {
1321 module.verbose('Modifying <input> z-index to be unselectable');
1322 $input.addClass(className.hidden);
1323 }
1324 },
1325 show: {
1326 input: function() {
1327 module.verbose('Modifying <input> z-index to be selectable');
1328 $input.removeClass(className.hidden);
1329 }
1330 },
1331
1332 observeChanges: function() {
1333 if('MutationObserver' in window) {
1334 observer = new MutationObserver(function(mutations) {
1335 module.debug('DOM tree modified, updating selector cache');
1336 module.refresh();
1337 });
1338 observer.observe(element, {
1339 childList : true,
1340 subtree : true
1341 });
1342 module.debug('Setting up mutation observer', observer);
1343 }
1344 },
1345
1346 attachEvents: function(selector, event) {
1347 var
1348 $element = $(selector)
1349 ;
1350 event = $.isFunction(module[event])
1351 ? module[event]
1352 : module.toggle
1353 ;
1354 if($element.length > 0) {
1355 module.debug('Attaching checkbox events to element', selector, event);
1356 $element
1357 .on('click' + eventNamespace, event)
1358 ;
1359 }
1360 else {
1361 module.error(error.notFound);
1362 }
1363 },
1364
1365 preventDefaultOnInputTarget: function() {
1366 if(typeof event !== 'undefined' && event !== null && $(event.target).is(selector.input)) {
1367 module.verbose('Preventing default check action after manual check action');
1368 event.preventDefault();
1369 }
1370 },
1371
1372 event: {
1373 change: function(event) {
1374 if( !module.should.ignoreCallbacks() ) {
1375 settings.onChange.call(input);
1376 }
1377 },
1378 click: function(event) {
1379 var
1380 $target = $(event.target)
1381 ;
1382 if( $target.is(selector.input) ) {
1383 module.verbose('Using default check action on initialized checkbox');
1384 return;
1385 }
1386 if( $target.is(selector.link) ) {
1387 module.debug('Clicking link inside checkbox, skipping toggle');
1388 return;
1389 }
1390 module.toggle();
1391 $input.focus();
1392 event.preventDefault();
1393 },
1394 keydown: function(event) {
1395 var
1396 key = event.which,
1397 keyCode = {
1398 enter : 13,
1399 space : 32,
1400 escape : 27,
1401 left : 37,
1402 up : 38,
1403 right : 39,
1404 down : 40
1405 }
1406 ;
1407
1408 var r = module.get.radios(),
1409 rIndex = r.index($module),
1410 rLen = r.length,
1411 checkIndex = false;
1412
1413 if(key == keyCode.left || key == keyCode.up) {
1414 checkIndex = (rIndex === 0 ? rLen : rIndex) - 1;
1415 } else if(key == keyCode.right || key == keyCode.down) {
1416 checkIndex = rIndex === rLen-1 ? 0 : rIndex+1;
1417 }
1418
1419 if (!module.should.ignoreCallbacks() && checkIndex !== false) {
1420 if(settings.beforeUnchecked.apply(input)===false) {
1421 module.verbose('Option not allowed to be unchecked, cancelling key navigation');
1422 return false;
1423 }
1424 if (settings.beforeChecked.apply($(r[checkIndex]).children(selector.input)[0])===false) {
1425 module.verbose('Next option should not allow check, cancelling key navigation');
1426 return false;
1427 }
1428 }
1429
1430 if(key == keyCode.escape) {
1431 module.verbose('Escape key pressed blurring field');
1432 $input.blur();
1433 shortcutPressed = true;
1434 }
1435 else if(!event.ctrlKey && ( key == keyCode.space || (key == keyCode.enter && settings.enableEnterKey)) ) {
1436 module.verbose('Enter/space key pressed, toggling checkbox');
1437 module.toggle();
1438 shortcutPressed = true;
1439 }
1440 else {
1441 shortcutPressed = false;
1442 }
1443 },
1444 keyup: function(event) {
1445 if(shortcutPressed) {
1446 event.preventDefault();
1447 }
1448 }
1449 },
1450
1451 check: function() {
1452 if( !module.should.allowCheck() ) {
1453 return;
1454 }
1455 module.debug('Checking checkbox', $input);
1456 module.set.checked();
1457 if( !module.should.ignoreCallbacks() ) {
1458 settings.onChecked.call(input);
1459 module.trigger.change();
1460 }
1461 module.preventDefaultOnInputTarget();
1462 },
1463
1464 uncheck: function() {
1465 if( !module.should.allowUncheck() ) {
1466 return;
1467 }
1468 module.debug('Unchecking checkbox');
1469 module.set.unchecked();
1470 if( !module.should.ignoreCallbacks() ) {
1471 settings.onUnchecked.call(input);
1472 module.trigger.change();
1473 }
1474 module.preventDefaultOnInputTarget();
1475 },
1476
1477 indeterminate: function() {
1478 if( module.should.allowIndeterminate() ) {
1479 module.debug('Checkbox is already indeterminate');
1480 return;
1481 }
1482 module.debug('Making checkbox indeterminate');
1483 module.set.indeterminate();
1484 if( !module.should.ignoreCallbacks() ) {
1485 settings.onIndeterminate.call(input);
1486 module.trigger.change();
1487 }
1488 },
1489
1490 determinate: function() {
1491 if( module.should.allowDeterminate() ) {
1492 module.debug('Checkbox is already determinate');
1493 return;
1494 }
1495 module.debug('Making checkbox determinate');
1496 module.set.determinate();
1497 if( !module.should.ignoreCallbacks() ) {
1498 settings.onDeterminate.call(input);
1499 module.trigger.change();
1500 }
1501 },
1502
1503 enable: function() {
1504 if( module.is.enabled() ) {
1505 module.debug('Checkbox is already enabled');
1506 return;
1507 }
1508 module.debug('Enabling checkbox');
1509 module.set.enabled();
1510 if( !module.should.ignoreCallbacks() ) {
1511 settings.onEnable.call(input);
1512 // preserve legacy callbacks
1513 settings.onEnabled.call(input);
1514 module.trigger.change();
1515 }
1516 },
1517
1518 disable: function() {
1519 if( module.is.disabled() ) {
1520 module.debug('Checkbox is already disabled');
1521 return;
1522 }
1523 module.debug('Disabling checkbox');
1524 module.set.disabled();
1525 if( !module.should.ignoreCallbacks() ) {
1526 settings.onDisable.call(input);
1527 // preserve legacy callbacks
1528 settings.onDisabled.call(input);
1529 module.trigger.change();
1530 }
1531 },
1532
1533 get: {
1534 radios: function() {
1535 var
1536 name = module.get.name()
1537 ;
1538 return $('input[name="' + name + '"]').closest(selector.checkbox);
1539 },
1540 otherRadios: function() {
1541 return module.get.radios().not($module);
1542 },
1543 name: function() {
1544 return $input.attr('name');
1545 }
1546 },
1547
1548 is: {
1549 initialLoad: function() {
1550 return initialLoad;
1551 },
1552 radio: function() {
1553 return ($input.hasClass(className.radio) || $input.attr('type') == 'radio');
1554 },
1555 indeterminate: function() {
1556 return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate');
1557 },
1558 checked: function() {
1559 return $input.prop('checked') !== undefined && $input.prop('checked');
1560 },
1561 disabled: function() {
1562 return $input.prop('disabled') !== undefined && $input.prop('disabled');
1563 },
1564 enabled: function() {
1565 return !module.is.disabled();
1566 },
1567 determinate: function() {
1568 return !module.is.indeterminate();
1569 },
1570 unchecked: function() {
1571 return !module.is.checked();
1572 }
1573 },
1574
1575 should: {
1576 allowCheck: function() {
1577 if(module.is.determinate() && module.is.checked() && !module.is.initialLoad() ) {
1578 module.debug('Should not allow check, checkbox is already checked');
1579 return false;
1580 }
1581 if(!module.should.ignoreCallbacks() && settings.beforeChecked.apply(input) === false) {
1582 module.debug('Should not allow check, beforeChecked cancelled');
1583 return false;
1584 }
1585 return true;
1586 },
1587 allowUncheck: function() {
1588 if(module.is.determinate() && module.is.unchecked() && !module.is.initialLoad() ) {
1589 module.debug('Should not allow uncheck, checkbox is already unchecked');
1590 return false;
1591 }
1592 if(!module.should.ignoreCallbacks() && settings.beforeUnchecked.apply(input) === false) {
1593 module.debug('Should not allow uncheck, beforeUnchecked cancelled');
1594 return false;
1595 }
1596 return true;
1597 },
1598 allowIndeterminate: function() {
1599 if(module.is.indeterminate() && !module.is.initialLoad() ) {
1600 module.debug('Should not allow indeterminate, checkbox is already indeterminate');
1601 return false;
1602 }
1603 if(!module.should.ignoreCallbacks() && settings.beforeIndeterminate.apply(input) === false) {
1604 module.debug('Should not allow indeterminate, beforeIndeterminate cancelled');
1605 return false;
1606 }
1607 return true;
1608 },
1609 allowDeterminate: function() {
1610 if(module.is.determinate() && !module.is.initialLoad() ) {
1611 module.debug('Should not allow determinate, checkbox is already determinate');
1612 return false;
1613 }
1614 if(!module.should.ignoreCallbacks() && settings.beforeDeterminate.apply(input) === false) {
1615 module.debug('Should not allow determinate, beforeDeterminate cancelled');
1616 return false;
1617 }
1618 return true;
1619 },
1620 ignoreCallbacks: function() {
1621 return (initialLoad && !settings.fireOnInit);
1622 }
1623 },
1624
1625 can: {
1626 change: function() {
1627 return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') );
1628 },
1629 uncheck: function() {
1630 return (typeof settings.uncheckable === 'boolean')
1631 ? settings.uncheckable
1632 : !module.is.radio()
1633 ;
1634 }
1635 },
1636
1637 set: {
1638 initialLoad: function() {
1639 initialLoad = true;
1640 },
1641 checked: function() {
1642 module.verbose('Setting class to checked');
1643 $module
1644 .removeClass(className.indeterminate)
1645 .addClass(className.checked)
1646 ;
1647 if( module.is.radio() ) {
1648 module.uncheckOthers();
1649 }
1650 if(!module.is.indeterminate() && module.is.checked()) {
1651 module.debug('Input is already checked, skipping input property change');
1652 return;
1653 }
1654 module.verbose('Setting state to checked', input);
1655 $input
1656 .prop('indeterminate', false)
1657 .prop('checked', true)
1658 ;
1659 },
1660 unchecked: function() {
1661 module.verbose('Removing checked class');
1662 $module
1663 .removeClass(className.indeterminate)
1664 .removeClass(className.checked)
1665 ;
1666 if(!module.is.indeterminate() && module.is.unchecked() ) {
1667 module.debug('Input is already unchecked');
1668 return;
1669 }
1670 module.debug('Setting state to unchecked');
1671 $input
1672 .prop('indeterminate', false)
1673 .prop('checked', false)
1674 ;
1675 },
1676 indeterminate: function() {
1677 module.verbose('Setting class to indeterminate');
1678 $module
1679 .addClass(className.indeterminate)
1680 ;
1681 if( module.is.indeterminate() ) {
1682 module.debug('Input is already indeterminate, skipping input property change');
1683 return;
1684 }
1685 module.debug('Setting state to indeterminate');
1686 $input
1687 .prop('indeterminate', true)
1688 ;
1689 },
1690 determinate: function() {
1691 module.verbose('Removing indeterminate class');
1692 $module
1693 .removeClass(className.indeterminate)
1694 ;
1695 if( module.is.determinate() ) {
1696 module.debug('Input is already determinate, skipping input property change');
1697 return;
1698 }
1699 module.debug('Setting state to determinate');
1700 $input
1701 .prop('indeterminate', false)
1702 ;
1703 },
1704 disabled: function() {
1705 module.verbose('Setting class to disabled');
1706 $module
1707 .addClass(className.disabled)
1708 ;
1709 if( module.is.disabled() ) {
1710 module.debug('Input is already disabled, skipping input property change');
1711 return;
1712 }
1713 module.debug('Setting state to disabled');
1714 $input
1715 .prop('disabled', 'disabled')
1716 ;
1717 },
1718 enabled: function() {
1719 module.verbose('Removing disabled class');
1720 $module.removeClass(className.disabled);
1721 if( module.is.enabled() ) {
1722 module.debug('Input is already enabled, skipping input property change');
1723 return;
1724 }
1725 module.debug('Setting state to enabled');
1726 $input
1727 .prop('disabled', false)
1728 ;
1729 },
1730 tabbable: function() {
1731 module.verbose('Adding tabindex to checkbox');
1732 if( $input.attr('tabindex') === undefined) {
1733 $input.attr('tabindex', 0);
1734 }
1735 }
1736 },
1737
1738 remove: {
1739 initialLoad: function() {
1740 initialLoad = false;
1741 }
1742 },
1743
1744 trigger: {
1745 change: function() {
1746 var
1747 inputElement = $input[0]
1748 ;
1749 if(inputElement) {
1750 var events = document.createEvent('HTMLEvents');
1751 module.verbose('Triggering native change event');
1752 events.initEvent('change', true, false);
1753 inputElement.dispatchEvent(events);
1754 }
1755 }
1756 },
1757
1758
1759 create: {
1760 label: function() {
1761 if($input.prevAll(selector.label).length > 0) {
1762 $input.prev(selector.label).detach().insertAfter($input);
1763 module.debug('Moving existing label', $label);
1764 }
1765 else if( !module.has.label() ) {
1766 $label = $('<label>').insertAfter($input);
1767 module.debug('Creating label', $label);
1768 }
1769 }
1770 },
1771
1772 has: {
1773 label: function() {
1774 return ($label.length > 0);
1775 }
1776 },
1777
1778 bind: {
1779 events: function() {
1780 module.verbose('Attaching checkbox events');
1781 $module
1782 .on('click' + eventNamespace, module.event.click)
1783 .on('change' + eventNamespace, module.event.change)
1784 .on('keydown' + eventNamespace, selector.input, module.event.keydown)
1785 .on('keyup' + eventNamespace, selector.input, module.event.keyup)
1786 ;
1787 }
1788 },
1789
1790 unbind: {
1791 events: function() {
1792 module.debug('Removing events');
1793 $module
1794 .off(eventNamespace)
1795 ;
1796 }
1797 },
1798
1799 uncheckOthers: function() {
1800 var
1801 $radios = module.get.otherRadios()
1802 ;
1803 module.debug('Unchecking other radios', $radios);
1804 $radios.removeClass(className.checked);
1805 },
1806
1807 toggle: function() {
1808 if( !module.can.change() ) {
1809 if(!module.is.radio()) {
1810 module.debug('Checkbox is read-only or disabled, ignoring toggle');
1811 }
1812 return;
1813 }
1814 if( module.is.indeterminate() || module.is.unchecked() ) {
1815 module.debug('Currently unchecked');
1816 module.check();
1817 }
1818 else if( module.is.checked() && module.can.uncheck() ) {
1819 module.debug('Currently checked');
1820 module.uncheck();
1821 }
1822 },
1823 setting: function(name, value) {
1824 module.debug('Changing setting', name, value);
1825 if( $.isPlainObject(name) ) {
1826 $.extend(true, settings, name);
1827 }
1828 else if(value !== undefined) {
1829 if($.isPlainObject(settings[name])) {
1830 $.extend(true, settings[name], value);
1831 }
1832 else {
1833 settings[name] = value;
1834 }
1835 }
1836 else {
1837 return settings[name];
1838 }
1839 },
1840 internal: function(name, value) {
1841 if( $.isPlainObject(name) ) {
1842 $.extend(true, module, name);
1843 }
1844 else if(value !== undefined) {
1845 module[name] = value;
1846 }
1847 else {
1848 return module[name];
1849 }
1850 },
1851 debug: function() {
1852 if(!settings.silent && settings.debug) {
1853 if(settings.performance) {
1854 module.performance.log(arguments);
1855 }
1856 else {
1857 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
1858 module.debug.apply(console, arguments);
1859 }
1860 }
1861 },
1862 verbose: function() {
1863 if(!settings.silent && settings.verbose && settings.debug) {
1864 if(settings.performance) {
1865 module.performance.log(arguments);
1866 }
1867 else {
1868 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
1869 module.verbose.apply(console, arguments);
1870 }
1871 }
1872 },
1873 error: function() {
1874 if(!settings.silent) {
1875 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
1876 module.error.apply(console, arguments);
1877 }
1878 },
1879 performance: {
1880 log: function(message) {
1881 var
1882 currentTime,
1883 executionTime,
1884 previousTime
1885 ;
1886 if(settings.performance) {
1887 currentTime = new Date().getTime();
1888 previousTime = time || currentTime;
1889 executionTime = currentTime - previousTime;
1890 time = currentTime;
1891 performance.push({
1892 'Name' : message[0],
1893 'Arguments' : [].slice.call(message, 1) || '',
1894 'Element' : element,
1895 'Execution Time' : executionTime
1896 });
1897 }
1898 clearTimeout(module.performance.timer);
1899 module.performance.timer = setTimeout(module.performance.display, 500);
1900 },
1901 display: function() {
1902 var
1903 title = settings.name + ':',
1904 totalTime = 0
1905 ;
1906 time = false;
1907 clearTimeout(module.performance.timer);
1908 $.each(performance, function(index, data) {
1909 totalTime += data['Execution Time'];
1910 });
1911 title += ' ' + totalTime + 'ms';
1912 if(moduleSelector) {
1913 title += ' \'' + moduleSelector + '\'';
1914 }
1915 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
1916 console.groupCollapsed(title);
1917 if(console.table) {
1918 console.table(performance);
1919 }
1920 else {
1921 $.each(performance, function(index, data) {
1922 console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
1923 });
1924 }
1925 console.groupEnd();
1926 }
1927 performance = [];
1928 }
1929 },
1930 invoke: function(query, passedArguments, context) {
1931 var
1932 object = instance,
1933 maxDepth,
1934 found,
1935 response
1936 ;
1937 passedArguments = passedArguments || queryArguments;
1938 context = element || context;
1939 if(typeof query == 'string' && object !== undefined) {
1940 query = query.split(/[\. ]/);
1941 maxDepth = query.length - 1;
1942 $.each(query, function(depth, value) {
1943 var camelCaseValue = (depth != maxDepth)
1944 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
1945 : query
1946 ;
1947 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
1948 object = object[camelCaseValue];
1949 }
1950 else if( object[camelCaseValue] !== undefined ) {
1951 found = object[camelCaseValue];
1952 return false;
1953 }
1954 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
1955 object = object[value];
1956 }
1957 else if( object[value] !== undefined ) {
1958 found = object[value];
1959 return false;
1960 }
1961 else {
1962 module.error(error.method, query);
1963 return false;
1964 }
1965 });
1966 }
1967 if ( $.isFunction( found ) ) {
1968 response = found.apply(context, passedArguments);
1969 }
1970 else if(found !== undefined) {
1971 response = found;
1972 }
1973 if(Array.isArray(returnedValue)) {
1974 returnedValue.push(response);
1975 }
1976 else if(returnedValue !== undefined) {
1977 returnedValue = [returnedValue, response];
1978 }
1979 else if(response !== undefined) {
1980 returnedValue = response;
1981 }
1982 return found;
1983 }
1984 };
1985
1986 if(methodInvoked) {
1987 if(instance === undefined) {
1988 module.initialize();
1989 }
1990 module.invoke(query);
1991 }
1992 else {
1993 if(instance !== undefined) {
1994 instance.invoke('destroy');
1995 }
1996 module.initialize();
1997 }
1998 })
1999 ;
2000
2001 return (returnedValue !== undefined)
2002 ? returnedValue
2003 : this
2004 ;
2005};
2006
2007$.fn.checkbox.settings = {
2008
2009 name : 'Checkbox',
2010 namespace : 'checkbox',
2011
2012 silent : false,
2013 debug : false,
2014 verbose : true,
2015 performance : true,
2016
2017 // delegated event context
2018 uncheckable : 'auto',
2019 fireOnInit : false,
2020 enableEnterKey : true,
2021
2022 onChange : function(){},
2023
2024 beforeChecked : function(){},
2025 beforeUnchecked : function(){},
2026 beforeDeterminate : function(){},
2027 beforeIndeterminate : function(){},
2028
2029 onChecked : function(){},
2030 onUnchecked : function(){},
2031
2032 onDeterminate : function() {},
2033 onIndeterminate : function() {},
2034
2035 onEnable : function(){},
2036 onDisable : function(){},
2037
2038 // preserve misspelled callbacks (will be removed in 3.0)
2039 onEnabled : function(){},
2040 onDisabled : function(){},
2041
2042 className : {
2043 checked : 'checked',
2044 indeterminate : 'indeterminate',
2045 disabled : 'disabled',
2046 hidden : 'hidden',
2047 radio : 'radio',
2048 readOnly : 'read-only'
2049 },
2050
2051 error : {
2052 method : 'The method you called is not defined'
2053 },
2054
2055 selector : {
2056 checkbox : '.ui.checkbox',
2057 label : 'label, .box',
2058 input : 'input[type="checkbox"], input[type="radio"]',
2059 link : 'a[href]'
2060 }
2061
2062};
2063
2064})( jQuery, window, document );1187})( jQuery, window, document );
2065 1188
2066/*!1189/*!
web_src/fomantic/semantic.json+0−3
@@ -23,12 +23,9 @@ · 22 unmodified lines
23 "components": [23 "components": [
24 "api",24 "api",
25 "button",25 "button",
26 "checkbox",
27 "dimmer",26 "dimmer",
28 "dropdown",27 "dropdown",
29 "form",28 "form",
30 "input",
31 "list",
32 "menu",29 "menu",
33 "modal",30 "modal",
34 "search",31 "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
350 <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>350 <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
351 </div>351 </div>
352 </h4>352 </h4>
353 <div class="ui top attached segment repos-search gt-rounded-top">353 <div class="ui attached segment repos-search">
354 <div class="ui fluid action left icon input" :class="{loading: isLoading}">354 <div class="ui small fluid action left icon input">
355 <input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos">355 <input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos">
356 <i class="icon"><svg-icon name="octicon-search" :size="16"/></i>356 <i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i>
357 <div class="ui dropdown icon button" :title="textFilter">357 <div class="ui dropdown icon button" :title="textFilter">
358 <svg-icon name="octicon-filter" :size="16"/>358 <svg-icon name="octicon-filter" :size="16"/>
359 <div class="menu">359 <div class="menu">
web_src/js/features/admin/common.js+15−8
@@ -218,17 +218,24 @@ export function initAdminCommon() { · 217 unmodified lines
218 });218 });
219 219
220 // Select actions220 // Select actions
221 const $checkboxes = $('.select.table .ui.checkbox');221 const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input');
222
222 $('.select.action').on('click', function () {223 $('.select.action').on('click', function () {
223 switch ($(this).data('action')) {224 switch ($(this).data('action')) {
224 case 'select-all':225 case 'select-all':
225 $checkboxes.checkbox('check');226 for (const checkbox of checkboxes) {
227 checkbox.checked = true;
228 }
226 break;229 break;
227 case 'deselect-all':230 case 'deselect-all':
228 $checkboxes.checkbox('uncheck');231 for (const checkbox of checkboxes) {
232 checkbox.checked = false;
233 }
229 break;234 break;
230 case 'inverse':235 case 'inverse':
231 $checkboxes.checkbox('toggle');236 for (const checkbox of checkboxes) {
237 checkbox.checked = !checkbox.checked;
238 }
232 break;239 break;
233 }240 }
234 });241 });
@@ -236,11 +243,11 @@ export function initAdminCommon() { · 1 unmodified line
236 e.preventDefault();243 e.preventDefault();
237 this.classList.add('is-loading', 'disabled');244 this.classList.add('is-loading', 'disabled');
238 const data = new FormData();245 const data = new FormData();
239 $checkboxes.each(function () {246 for (const checkbox of checkboxes) {
240 if ($(this).checkbox('is checked')) {247 if (checkbox.checked) {
241 data.append('ids[]', this.getAttribute('data-id'));248 data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id'));
242 }249 }
243 });250 }
244 await POST(this.getAttribute('data-link'), {data});251 await POST(this.getAttribute('data-link'), {data});
245 window.location.href = this.getAttribute('data-redirect');252 window.location.href = this.getAttribute('data-redirect');
246 });253 });
web_src/js/features/colorpicker.js+59−5
@@ -1,12 +1,66 @@
1import $ from 'jquery';1import {createTippy} from '../modules/tippy.js';
2 2
3export async function createColorPicker(els) {3export async function initColorPickers() {
4 const els = document.getElementsByClassName('js-color-picker-input');
4 if (!els.length) return;5 if (!els.length) return;
5 6
6 await Promise.all([7 await Promise.all([
7 import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors'),8 import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'),
8 import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors/jquery.minicolors.css'),9 import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'),
9 ]);10 ]);
10 11
11 return $(els).minicolors();12 for (const el of els) {
13 initPicker(el);
14 }
15}
16
17function updateSquare(el, newValue) {
18 el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent';
19}
20
21function updatePicker(el, newValue) {
22 el.setAttribute('color', newValue);
23}
24
25function initPicker(el) {
26 const input = el.querySelector('input');
27
28 const square = document.createElement('div');
29 square.classList.add('preview-square');
30 updateSquare(square, input.value);
31 el.append(square);
32
33 const picker = document.createElement('hex-color-picker');
34 picker.addEventListener('color-changed', (e) => {
35 input.value = e.detail.value;
36 input.focus();
37 updateSquare(square, e.detail.value);
38 });
39
40 input.addEventListener('input', (e) => {
41 updateSquare(square, e.target.value);
42 updatePicker(picker, e.target.value);
43 });
44
45 createTippy(input, {
46 trigger: 'focus click',
47 theme: 'bare',
48 hideOnClick: true,
49 content: picker,
50 placement: 'bottom-start',
51 interactive: true,
52 onShow() {
53 updatePicker(picker, input.value);
54 },
55 });
56
57 // init precolors
58 for (const colorEl of el.querySelectorAll('.precolors .color')) {
59 colorEl.addEventListener('click', (e) => {
60 const newValue = e.target.getAttribute('data-color-hex');
61 input.value = newValue;
62 input.dispatchEvent(new Event('input', {bubbles: true}));
63 updateSquare(square, newValue);
64 });
65 }
12}66}
web_src/js/features/common-global.js+3−9
@@ -2,7 +2,6 @@ import $ from 'jquery'; · 1 unmodified line
2import '../vendor/jquery.are-you-sure.js';2import '../vendor/jquery.are-you-sure.js';
3import {clippie} from 'clippie';3import {clippie} from 'clippie';
4import {createDropzone} from './dropzone.js';4import {createDropzone} from './dropzone.js';
5import {initCompColorPicker} from './comp/ColorPicker.js';
6import {showGlobalErrorMessage} from '../bootstrap.js';5import {showGlobalErrorMessage} from '../bootstrap.js';
7import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';6import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
8import {svg} from '../svg.js';7import {svg} from '../svg.js';
@@ -110,7 +109,7 @@ async function fetchActionDoRequest(actionElem, url, opt) { · 101 unmodified lines
110 showErrorToast(`${i18n.network_error} ${e}`);109 showErrorToast(`${i18n.network_error} ${e}`);
111 }110 }
112 }111 }
113 actionElem.classList.remove('is-loading', 'small-loading-icon');112 actionElem.classList.remove('is-loading', 'loading-icon-2px');
114}113}
115 114
116async function formFetchAction(e) {115async function formFetchAction(e) {
@@ -122,7 +121,7 @@ async function formFetchAction(e) { · 5 unmodified lines
122 121
123 formEl.classList.add('is-loading');122 formEl.classList.add('is-loading');
124 if (formEl.clientHeight < 50) {123 if (formEl.clientHeight < 50) {
125 formEl.classList.add('small-loading-icon');124 formEl.classList.add('loading-icon-2px');
126 }125 }
127 126
128 const formMethod = formEl.getAttribute('method') || 'get';127 const formMethod = formEl.getAttribute('method') || 'get';
@@ -196,8 +195,6 @@ export function initGlobalCommon() { · 67 unmodified lines
196 $uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');195 $uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');
197 $uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');196 $uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');
198 197
199 $('.ui.checkbox').checkbox();
200
201 $('.tabular.menu .item').tab();198 $('.tabular.menu .item').tab();
202 199
203 initSubmitEventPolyfill();200 initSubmitEventPolyfill();
@@ -379,10 +376,7 @@ function initGlobalShowModal() { · 175 unmodified lines
379 $attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p376 $attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
380 }377 }
381 }378 }
382 const $colorPickers = $modal.find('.color-picker');379
383 if ($colorPickers.length > 0) {
384 initCompColorPicker(); // FIXME: this might cause duplicate init
385 }
386 $modal.modal('setting', {380 $modal.modal('setting', {
387 onApprove: () => {381 onApprove: () => {
388 // "form-fetch-action" can handle network errors gracefully,382 // "form-fetch-action" can handle network errors gracefully,
web_src/js/features/comp/ColorPicker.js+0−16
@@ -1,16 +0,0 @@
1import $ from 'jquery';
2import {createColorPicker} from '../colorpicker.js';
3
4export 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 @@
1import $ from 'jquery';1import $ from 'jquery';
2import {initCompColorPicker} from './ColorPicker.js';
3 2
4function isExclusiveScopeName(name) {3function isExclusiveScopeName(name) {
5 return /.*[^/]\/[^/].*/.test(name);4 return /.*[^/]\/[^/].*/.test(name);
@@ -28,13 +27,17 @@ function updateExclusiveLabelEdit(form) { · 22 unmodified lines
28 27
29export function initCompLabelEdit(selector) {28export function initCompLabelEdit(selector) {
30 if (!$(selector).length) return;29 if (!$(selector).length) return;
31 initCompColorPicker();
32 30
33 // Create label31 // Create label
34 $('.new-label.button').on('click', () => {32 $('.new-label.button').on('click', () => {
35 updateExclusiveLabelEdit('.new-label');33 updateExclusiveLabelEdit('.new-label');
36 $('.new-label.modal').modal({34 $('.new-label.modal').modal({
37 onApprove() {35 onApprove() {
36 const form = document.querySelector('.new-label.form');
37 if (!form.checkValidity()) {
38 form.reportValidity();
39 return false;
40 }
38 $('.new-label.form').trigger('submit');41 $('.new-label.form').trigger('submit');
39 },42 },
40 }).modal('show');43 }).modal('show');
@@ -60,10 +63,18 @@ export function initCompLabelEdit(selector) { · 19 unmodified lines
60 updateExclusiveLabelEdit('.edit-label');63 updateExclusiveLabelEdit('.edit-label');
61 64
62 $('.edit-label .label-desc-input').val(this.getAttribute('data-description'));65 $('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
63 $('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color'));66
67 const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
68 colorInput.value = this.getAttribute('data-color');
69 colorInput.dispatchEvent(new Event('input', {bubbles: true}));
64 70
65 $('.edit-label.modal').modal({71 $('.edit-label.modal').modal({
66 onApprove() {72 onApprove() {
73 const form = document.querySelector('.edit-label.form');
74 if (!form.checkValidity()) {
75 form.reportValidity();
76 return false;
77 }
67 $('.edit-label.form').trigger('submit');78 $('.edit-label.form').trigger('submit');
68 },79 },
69 }).modal('show');80 }).modal('show');
web_src/js/features/copycontent.js+2−2
@@ -19,7 +19,7 @@ export function initCopyContent() { · 18 unmodified lines
19 // the text to copy is not in the DOM or it is an image which should be19 // the text to copy is not in the DOM or it is an image which should be
20 // fetched to copy in full resolution20 // fetched to copy in full resolution
21 if (link) {21 if (link) {
22 btn.classList.add('is-loading', 'small-loading-icon');22 btn.classList.add('is-loading', 'loading-icon-2px');
23 try {23 try {
24 const res = await GET(link, {credentials: 'include', redirect: 'follow'});24 const res = await GET(link, {credentials: 'include', redirect: 'follow'});
25 const contentType = res.headers.get('content-type');25 const contentType = res.headers.get('content-type');
@@ -33,7 +33,7 @@ export function initCopyContent() { · 7 unmodified lines
33 } catch {33 } catch {
34 return showTemporaryTooltip(btn, i18n.copy_error);34 return showTemporaryTooltip(btn, i18n.copy_error);
35 } finally {35 } finally {
36 btn.classList.remove('is-loading', 'small-loading-icon');36 btn.classList.remove('is-loading', 'loading-icon-2px');
37 }37 }
38 } else { // text, read from DOM38 } else { // text, read from DOM
39 const lineEls = document.querySelectorAll('.file-view .lines-code');39 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
110 const $imagesAfter = imageInfos[0].$images;110 const $imagesAfter = imageInfos[0].$images;
111 const $imagesBefore = imageInfos[1].$images;111 const $imagesBefore = imageInfos[1].$images;
112 112
113 initSideBySide(createContext($imagesAfter[0], $imagesBefore[0]));113 initSideBySide(this, createContext($imagesAfter[0], $imagesBefore[0]));
114 if ($imagesAfter.length > 0 && $imagesBefore.length > 0) {114 if ($imagesAfter.length > 0 && $imagesBefore.length > 0) {
115 initSwipe(createContext($imagesAfter[1], $imagesBefore[1]));115 initSwipe(createContext($imagesAfter[1], $imagesBefore[1]));
116 initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));116 initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));
117 }117 }
118 118
119 $container.find('> .image-diff-tabs').removeClass('is-loading');119 this.querySelector(':scope > .image-diff-tabs')?.classList.remove('is-loading');
120 120
121 function initSideBySide(sizes) {121 function initSideBySide(container, sizes) {
122 let factor = 1;122 let factor = 1;
123 if (sizes.max.width > (diffContainerWidth - 24) / 2) {123 if (sizes.max.width > (diffContainerWidth - 24) / 2) {
124 factor = (diffContainerWidth - 24) / 2 / sizes.max.width;124 factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
@@ -126,13 +126,24 @@ export function initImageDiff() { · 1 unmodified line
126 126
127 const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;127 const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;
128 const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight;128 const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight;
129 if (sizes.$image1.length !== 0) {129 if (sizes.$image1?.length) {
130 $container.find('.bounds-info-after .bounds-info-width').text(`${sizes.$image1[0].naturalWidth}px`).addClass(widthChanged ? 'green' : '');130 const boundsInfoAfterWidth = container.querySelector('.bounds-info-after .bounds-info-width');
131 $container.find('.bounds-info-after .bounds-info-height').text(`${sizes.$image1[0].naturalHeight}px`).addClass(heightChanged ? 'green' : '');131 boundsInfoAfterWidth.textContent = `${sizes.$image1[0].naturalWidth}px`;
132 if (widthChanged) boundsInfoAfterWidth.classList.add('green');
133
134 const boundsInfoAfterHeight = container.querySelector('.bounds-info-after .bounds-info-height');
135 boundsInfoAfterHeight.textContent = `${sizes.$image1[0].naturalHeight}px`;
136 if (heightChanged) boundsInfoAfterHeight.classList.add('green');
132 }137 }
133 if (sizes.$image2.length !== 0) {138
134 $container.find('.bounds-info-before .bounds-info-width').text(`${sizes.$image2[0].naturalWidth}px`).addClass(widthChanged ? 'red' : '');139 if (sizes.$image2?.length) {
135 $container.find('.bounds-info-before .bounds-info-height').text(`${sizes.$image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : '');140 const boundsInfoBeforeWidth = container.querySelector('.bounds-info-before .bounds-info-width');
141 boundsInfoBeforeWidth.textContent = `${sizes.$image2[0].naturalWidth}px`;
142 if (widthChanged) boundsInfoBeforeWidth.classList.add('red');
143
144 const boundsInfoBeforeHeight = container.querySelector('.bounds-info-before .bounds-info-height');
145 boundsInfoBeforeHeight.textContent = `${sizes.$image2[0].naturalHeight}px`;
146 if (heightChanged) boundsInfoBeforeHeight.classList.add('red');
136 }147 }
137 148
138 const image1 = sizes.$image1[0];149 const image1 = sizes.$image1[0];
web_src/js/features/notification.js+5−7
@@ -1,5 +1,6 @@
1import $ from 'jquery';1import $ from 'jquery';
2import {GET} from '../modules/fetch.js';2import {GET} from '../modules/fetch.js';
3import {toggleElem} from '../utils/dom.js';
3 4
4const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;5const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
5let notificationSequenceNumber = 0;6let notificationSequenceNumber = 0;
@@ -177,14 +178,11 @@ async function updateNotificationCount() { · 171 unmodified lines
177 178
178 const data = await response.json();179 const data = await response.json();
179 180
180 const $notificationCount = $('.notification_count');181 toggleElem('.notification_count', data.new !== 0);
181 if (data.new === 0) {
182 $notificationCount.addClass('tw-hidden');
183 } else {
184 $notificationCount.removeClass('tw-hidden');
185 }
186 182
187 $notificationCount.text(`${data.new}`);183 for (const el of document.getElementsByClassName('notification_count')) {
184 el.textContent = `${data.new}`;
185 }
188 186
189 return `${data.new}`;187 return `${data.new}`;
190 } catch (error) {188 } catch (error) {
web_src/js/features/repo-code.js+5−3
@@ -25,7 +25,9 @@ function getLineEls() { · 24 unmodified lines
25}25}
26 26
27function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {27function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
28 $linesEls.closest('tr').removeClass('active');28 for (const el of $linesEls) {
29 el.closest('tr').classList.remove('active');
30 }
29 31
30 // add hashchange to permalink32 // add hashchange to permalink
31 const refInNewIssue = document.querySelector('a.ref-in-new-issue');33 const refInNewIssue = document.querySelector('a.ref-in-new-issue');
@@ -72,7 +74,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { · 40 unmodified lines
72 classes.push(`[rel=L${i}]`);74 classes.push(`[rel=L${i}]`);
73 }75 }
74 $linesEls.filter(classes.join(',')).each(function () {76 $linesEls.filter(classes.join(',')).each(function () {
75 $(this).closest('tr').addClass('active');77 this.closest('tr').classList.add('active');
76 });78 });
77 changeHash(`#L${a}-L${b}`);79 changeHash(`#L${a}-L${b}`);
78 80
@@ -82,7 +84,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) { · 3 unmodified lines
82 return;84 return;
83 }85 }
84 }86 }
85 $selectionEndEl.closest('tr').addClass('active');87 $selectionEndEl[0].closest('tr').classList.add('active');
86 changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);88 changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
87 89
88 updateIssueHref($selectionEndEl[0].getAttribute('rel'));90 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
7import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';7import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
8import {initImageDiff} from './imagediff.js';8import {initImageDiff} from './imagediff.js';
9import {showErrorToast} from '../modules/toast.js';9import {showErrorToast} from '../modules/toast.js';
10import {submitEventSubmitter} from '../utils/dom.js';10import {submitEventSubmitter, queryElemSiblings, hideElem, showElem} from '../utils/dom.js';
11import {POST, GET} from '../modules/fetch.js';11import {POST, GET} from '../modules/fetch.js';
12 12
13const {pageData, i18n} = window.config;13const {pageData, i18n} = window.config;
@@ -16,7 +16,6 @@ function initRepoDiffReviewButton() { · 2 unmodified lines
16 const reviewBox = document.getElementById('review-box');16 const reviewBox = document.getElementById('review-box');
17 if (!reviewBox) return;17 if (!reviewBox) return;
18 18
19 const $reviewBox = $(reviewBox);
20 const counter = reviewBox.querySelector('.review-comments-counter');19 const counter = reviewBox.querySelector('.review-comments-counter');
21 if (!counter) return;20 if (!counter) return;
22 21
@@ -27,23 +26,27 @@ function initRepoDiffReviewButton() { · 4 unmodified lines
27 const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1;26 const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1;
28 counter.setAttribute('data-pending-comment-number', num);27 counter.setAttribute('data-pending-comment-number', num);
29 counter.textContent = num;28 counter.textContent = num;
30 // Force the browser to reflow the DOM. This is to ensure that the browser replay the animation29
31 $reviewBox.removeClass('pulse');30 reviewBox.classList.remove('pulse');
32 $reviewBox.width();31 requestAnimationFrame(() => {
33 $reviewBox.addClass('pulse');32 reviewBox.classList.add('pulse');
33 });
34 });34 });
35 });35 });
36}36}
37 37
38function initRepoDiffFileViewToggle() {38function initRepoDiffFileViewToggle() {
39 $('.file-view-toggle').on('click', function () {39 $('.file-view-toggle').on('click', function () {
40 const $this = $(this);40 for (const el of queryElemSiblings(this)) {
41 $this.parent().children().removeClass('active');41 el.classList.remove('active');
42 $this.addClass('active');42 }
43 this.classList.add('active');
43 44
44 const $target = $($this.data('toggle-selector'));45 const target = document.querySelector(this.getAttribute('data-toggle-selector'));
45 $target.parent().children().addClass('tw-hidden');46 if (!target) return;
46 $target.removeClass('tw-hidden');47
48 hideElem(queryElemSiblings(target));
49 showElem(target);
47 });50 });
48}51}
49 52
@@ -57,9 +60,9 @@ function initRepoDiffConversationForm() { · 7 unmodified lines
57 return;60 return;
58 }61 }
59 62
60 if ($form.hasClass('is-loading')) return;63 if (e.target.classList.contains('is-loading')) return;
61 try {64 try {
62 $form.addClass('is-loading');65 e.target.classList.add('is-loading');
63 const formData = new FormData($form[0]);66 const formData = new FormData($form[0]);
64 67
65 // If the form is submitted by a button, append the button's name and value to the form data.68 // 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
76 const {path, side, idx} = $newConversationHolder.data();79 const {path, side, idx} = $newConversationHolder.data();
77 80
78 $form.closest('.conversation-holder').replaceWith($newConversationHolder);81 $form.closest('.conversation-holder').replaceWith($newConversationHolder);
82 let selector;
79 if ($form.closest('tr').data('line-type') === 'same') {83 if ($form.closest('tr').data('line-type') === 'same') {
80 $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('tw-invisible');84 selector = `[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`;
81 } else {85 } else {
82 $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('tw-invisible');86 selector = `[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`;
87 }
88 for (const el of document.querySelectorAll(selector)) {
89 el.classList.add('tw-invisible');
83 }90 }
84 $newConversationHolder.find('.dropdown').dropdown();91 $newConversationHolder.find('.dropdown').dropdown();
85 initCompReactionSelector($newConversationHolder);92 initCompReactionSelector($newConversationHolder);
@@ -87,7 +94,7 @@ function initRepoDiffConversationForm() { · 1 unmodified line
87 console.error('error when submitting conversation', e);94 console.error('error when submitting conversation', e);
88 showErrorToast(i18n.network_error);95 showErrorToast(i18n.network_error);
89 } finally {96 } finally {
90 $form.removeClass('is-loading');97 e.target.classList.remove('is-loading');
91 }98 }
92 });99 });
93 100
@@ -147,13 +154,13 @@ function onShowMoreFiles() { · 53 unmodified lines
147}154}
148 155
149export async function loadMoreFiles(url) {156export async function loadMoreFiles(url) {
150 const $target = $('a#diff-show-more-files');157 const target = document.querySelector('a#diff-show-more-files');
151 if ($target.hasClass('disabled') || pageData.diffFileInfo.isLoadingNewData) {158 if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) {
152 return;159 return;
153 }160 }
154 161
155 pageData.diffFileInfo.isLoadingNewData = true;162 pageData.diffFileInfo.isLoadingNewData = true;
156 $target.addClass('disabled');163 target?.classList.add('disabled');
157 164
158 try {165 try {
159 const response = await GET(url);166 const response = await GET(url);
@@ -170,7 +177,7 @@ export async function loadMoreFiles(url) { · 10 unmodified lines
170 console.error('Error:', error);177 console.error('Error:', error);
171 showErrorToast('An error occurred while loading more files.');178 showErrorToast('An error occurred while loading more files.');
172 } finally {179 } finally {
173 $target.removeClass('disabled');180 target?.classList.remove('disabled');
174 pageData.diffFileInfo.isLoadingNewData = false;181 pageData.diffFileInfo.isLoadingNewData = false;
175 }182 }
176}183}
@@ -187,11 +194,11 @@ function initRepoDiffShowMore() { · 10 unmodified lines
187 e.preventDefault();194 e.preventDefault();
188 const $target = $(e.target);195 const $target = $(e.target);
189 196
190 if ($target.hasClass('disabled')) {197 if (e.target.classList.contains('disabled')) {
191 return;198 return;
192 }199 }
193 200
194 $target.addClass('disabled');201 e.target.classList.add('disabled');
195 202
196 const url = $target.data('href');203 const url = $target.data('href');
197 204
@@ -207,7 +214,7 @@ function initRepoDiffShowMore() { · 9 unmodified lines
207 } catch (error) {214 } catch (error) {
208 console.error('Error:', error);215 console.error('Error:', error);
209 } finally {216 } finally {
210 $target.removeClass('disabled');217 e.target.classList.remove('disabled');
211 }218 }
212 });219 });
213}220}
web_src/js/features/repo-editor.js+2−2
@@ -147,8 +147,8 @@ export function initRepoEditor() { · 146 unmodified lines
147 silent: true,147 silent: true,
148 dirtyClass: dirtyFileClass,148 dirtyClass: dirtyFileClass,
149 fieldSelector: ':input:not(.commit-form-wrapper :input)',149 fieldSelector: ':input:not(.commit-form-wrapper :input)',
150 change() {150 change($form) {
151 const dirty = $(this).hasClass(dirtyFileClass);151 const dirty = $form[0]?.classList.contains(dirtyFileClass);
152 commitButton.disabled = !dirty;152 commitButton.disabled = !dirty;
153 },153 },
154 });154 });
web_src/js/features/repo-home.js+25−61
@@ -1,55 +1,53 @@
1import $ from 'jquery';1import $ from 'jquery';
2import {stripTags} from '../utils.js';2import {stripTags} from '../utils.js';
3import {hideElem, showElem} from '../utils/dom.js';3import {hideElem, queryElemChildren, showElem} from '../utils/dom.js';
4import {POST} from '../modules/fetch.js';4import {POST} from '../modules/fetch.js';
5import {showErrorToast} from '../modules/toast.js';
5 6
6const {appSubUrl} = window.config;7const {appSubUrl} = window.config;
7 8
8export function initRepoTopicBar() {9export function initRepoTopicBar() {
9 const mgrBtn = document.getElementById('manage_topic');10 const mgrBtn = document.getElementById('manage_topic');
10 if (!mgrBtn) return;11 if (!mgrBtn) return;
12
11 const editDiv = document.getElementById('topic_edit');13 const editDiv = document.getElementById('topic_edit');
12 const viewDiv = document.getElementById('repo-topics');14 const viewDiv = document.getElementById('repo-topics');
13 const saveBtn = document.getElementById('save_topic');15 const topicDropdown = editDiv.querySelector('.ui.dropdown');
14 const topicDropdown = editDiv.querySelector('.dropdown');16 let lastErrorToast;
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 };
22 17
23 mgrBtn.addEventListener('click', () => {18 mgrBtn.addEventListener('click', () => {
24 hideElem(viewDiv);19 hideElem(viewDiv);
25 showElem(editDiv);20 showElem(editDiv);
26 $topicDropdownSearch.trigger('focus');21 topicDropdown.querySelector('input.search').focus();
27 });22 });
28 23
29 $('#cancel_topic_edit').on('click', () => {24 document.querySelector('#cancel_topic_edit').addEventListener('click', () => {
25 lastErrorToast?.hideToast();
30 hideElem(editDiv);26 hideElem(editDiv);
31 showElem(viewDiv);27 showElem(viewDiv);
32 mgrBtn.focus();28 mgrBtn.focus();
33 });29 });
34 30
35 saveBtn.addEventListener('click', async () => {31 document.getElementById('save_topic').addEventListener('click', async (e) => {
36 const topics = $('input[name=topics]').val();32 lastErrorToast?.hideToast();
33 const topics = editDiv.querySelector('input[name=topics]').value;
37 34
38 const data = new FormData();35 const data = new FormData();
39 data.append('topics', topics);36 data.append('topics', topics);
40 37
41 const response = await POST(saveBtn.getAttribute('data-link'), {data});38 const response = await POST(e.target.getAttribute('data-link'), {data});
42 39
43 if (response.ok) {40 if (response.ok) {
44 const responseData = await response.json();41 const responseData = await response.json();
45 if (responseData.status === 'ok') {42 if (responseData.status === 'ok') {
46 $(viewDiv).children('.topic').remove();43 queryElemChildren(viewDiv, '.repo-topic', (el) => el.remove());
47 if (topics.length) {44 if (topics.length) {
48 const topicArray = topics.split(',');45 const topicArray = topics.split(',');
49 topicArray.sort();46 topicArray.sort();
50 for (const topic of topicArray) {47 for (const topic of topicArray) {
48 // it should match the code in repo/home.tmpl
51 const link = document.createElement('a');49 const link = document.createElement('a');
52 link.classList.add('ui', 'repo-topic', 'large', 'label', 'topic', 'tw-m-0');50 link.classList.add('repo-topic', 'ui', 'large', 'label');
53 link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`;51 link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`;
54 link.textContent = topic;52 link.textContent = topic;
55 mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button53 mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button
@@ -59,27 +57,23 @@ export function initRepoTopicBar() { · 3 unmodified lines
59 showElem(viewDiv);57 showElem(viewDiv);
60 }58 }
61 } else if (response.status === 422) {59 } else if (response.status === 422) {
60 // how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save"
62 const responseData = await response.json();61 const responseData = await response.json();
62 lastErrorToast = showErrorToast(responseData.message, {duration: 5000});
63 if (responseData.invalidTopics.length > 0) {63 if (responseData.invalidTopics.length > 0) {
64 topicPrompts.formatPrompt = responseData.message;
65
66 const {invalidTopics} = responseData;64 const {invalidTopics} = responseData;
67 const $topicLabels = $topicDropdown.children('a.ui.label');65 const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label');
68 for (const [index, value] of topics.split(',').entries()) {66 for (const [index, value] of topics.split(',').entries()) {
69 if (invalidTopics.includes(value)) {67 if (invalidTopics.includes(value)) {
70 $topicLabels.eq(index).removeClass('green').addClass('red');68 topicLabels[index].classList.remove('green');
69 topicLabels[index].classList.add('red');
71 }70 }
72 }71 }
73 } else {
74 topicPrompts.countPrompt = responseData.message;
75 }72 }
76 }73 }
77
78 // Always validate the form
79 $topicForm.form('validate form');
80 });74 });
81 75
82 $topicDropdown.dropdown({76 $(topicDropdown).dropdown({
83 allowAdditions: true,77 allowAdditions: true,
84 forceSelection: false,78 forceSelection: false,
85 fullTextSearch: 'exact',79 fullTextSearch: 'exact',
@@ -102,9 +96,9 @@ export function initRepoTopicBar() { · 16 unmodified lines
102 const query = stripTags(this.urlData.query.trim());96 const query = stripTags(this.urlData.query.trim());
103 let found_query = false;97 let found_query = false;
104 const current_topics = [];98 const current_topics = [];
105 $topicDropdown.find('a.label.visible').each((_, el) => {99 for (const el of queryElemChildren(topicDropdown, 'a.ui.label.visible')) {
106 current_topics.push(el.getAttribute('data-value'));100 current_topics.push(el.getAttribute('data-value'));
107 });101 }
108 102
109 if (res.topics) {103 if (res.topics) {
110 let found = false;104 let found = false;
@@ -146,38 +140,8 @@ export function initRepoTopicBar() { · 35 unmodified lines
146 },140 },
147 onAdd(addedValue, _addedText, $addedChoice) {141 onAdd(addedValue, _addedText, $addedChoice) {
148 addedValue = addedValue.toLowerCase().trim();142 addedValue = addedValue.toLowerCase().trim();
149 $($addedChoice)[0].setAttribute('data-value', addedValue);143 $addedChoice[0].setAttribute('data-value', addedValue);
150 $($addedChoice)[0].setAttribute('data-text', addedValue);144 $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 },
181 },145 },
182 });146 });
183}147}
web_src/js/features/repo-issue-content.js+5−2
@@ -2,6 +2,7 @@ import $ from 'jquery'; · 1 unmodified line
2import {svg} from '../svg.js';2import {svg} from '../svg.js';
3import {showErrorToast} from '../modules/toast.js';3import {showErrorToast} from '../modules/toast.js';
4import {GET, POST} from '../modules/fetch.js';4import {GET, POST} from '../modules/fetch.js';
5import {showElem} from '../utils/dom.js';
5 6
6const {appSubUrl} = window.config;7const {appSubUrl} = window.config;
7let i18nTextEdited;8let i18nTextEdited;
@@ -73,10 +74,12 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH · 65 unmodified lines
73 const response = await GET(url);74 const response = await GET(url);
74 const resp = await response.json();75 const resp = await response.json();
75 76
76 $dialog.find('.comment-diff-data').removeClass('is-loading').html(resp.diffHtml);77 const commentDiffData = $dialog.find('.comment-diff-data')[0];
78 commentDiffData?.classList.remove('is-loading');
79 commentDiffData.innerHTML = resp.diffHtml;
77 // there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden.80 // there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden.
78 if (resp.canSoftDelete) {81 if (resp.canSoftDelete) {
79 $dialog.find('.dialog-header-options').removeClass('tw-hidden');82 showElem($dialog.find('.dialog-header-options'));
80 }83 }
81 } catch (error) {84 } catch (error) {
82 console.error('Error:', error);85 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
6import {showErrorToast} from '../modules/toast.js';6import {showErrorToast} from '../modules/toast.js';
7import {createSortable} from '../modules/sortable.js';7import {createSortable} from '../modules/sortable.js';
8import {DELETE, POST} from '../modules/fetch.js';8import {DELETE, POST} from '../modules/fetch.js';
9import {parseDom} from '../utils.js';
9 10
10function initRepoIssueListCheckboxes() {11function initRepoIssueListCheckboxes() {
11 const issueSelectAll = document.querySelector('.issue-checkbox-all');12 const issueSelectAll = document.querySelector('.issue-checkbox-all');
@@ -129,22 +130,29 @@ function initRepoIssueListAuthorDropdown() { · 117 unmodified lines
129 const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');130 const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');
130 $searchDropdown.dropdown('internal', 'setup', dropdownSetup);131 $searchDropdown.dropdown('internal', 'setup', dropdownSetup);
131 dropdownSetup.menu = function (values) {132 dropdownSetup.menu = function (values) {
132 const $menu = $searchDropdown.find('> .menu');133 const menu = $searchDropdown.find('> .menu')[0];
133 $menu.find('> .dynamic-item').remove(); // remove old dynamic items134 // remove old dynamic items
135 for (const el of menu.querySelectorAll(':scope > .dynamic-item')) {
136 el.remove();
137 }
134 138
135 const newMenuHtml = dropdownTemplates.menu(values, $searchDropdown.dropdown('setting', 'fields'), true /* html */, $searchDropdown.dropdown('setting', 'className'));139 const newMenuHtml = dropdownTemplates.menu(values, $searchDropdown.dropdown('setting', 'fields'), true /* html */, $searchDropdown.dropdown('setting', 'className'));
136 if (newMenuHtml) {140 if (newMenuHtml) {
137 const $newMenuItems = $(newMenuHtml);141 const newMenuItems = parseDom(newMenuHtml, 'text/html').querySelectorAll('body > div');
138 $newMenuItems.addClass('dynamic-item');142 for (const newMenuItem of newMenuItems) {
143 newMenuItem.classList.add('dynamic-item');
144 }
139 const div = document.createElement('div');145 const div = document.createElement('div');
140 div.classList.add('divider', 'dynamic-item');146 div.classList.add('divider', 'dynamic-item');
141 $menu[0].append(div, ...$newMenuItems);147 menu.append(div, ...newMenuItems);
142 }148 }
143 $searchDropdown.dropdown('refresh');149 $searchDropdown.dropdown('refresh');
144 // defer our selection to the next tick, because dropdown will set the selection item after this `menu` function150 // defer our selection to the next tick, because dropdown will set the selection item after this `menu` function
145 setTimeout(() => {151 setTimeout(() => {
146 $menu.find('.item.active, .item.selected').removeClass('active selected');152 for (const el of menu.querySelectorAll('.item.active, .item.selected')) {
147 $menu.find(`.item[data-value="${selectedUserId}"]`).addClass('selected');153 el.classList.remove('active', 'selected');
154 }
155 menu.querySelector(`.item[data-value="${selectedUserId}"]`)?.classList.add('selected');
148 }, 0);156 }, 0);
149 };157 };
150}158}
web_src/js/features/repo-issue.js+104−95
@@ -158,17 +158,22 @@ export function initRepoIssueSidebarList() { · 157 unmodified lines
158 158
159export function initRepoIssueCommentDelete() {159export function initRepoIssueCommentDelete() {
160 // Delete comment160 // Delete comment
161 $(document).on('click', '.delete-comment', async function () {161 document.addEventListener('click', async (e) => {
162 const $this = $(this);162 if (!e.target.matches('.delete-comment')) return;
163 if (window.confirm($this.data('locale'))) {163 e.preventDefault();
164
165 const deleteButton = e.target;
166 if (window.confirm(deleteButton.getAttribute('data-locale'))) {
164 try {167 try {
165 const response = await POST($this.data('url'));168 const response = await POST(deleteButton.getAttribute('data-url'));
166 if (!response.ok) throw new Error('Failed to delete comment');169 if (!response.ok) throw new Error('Failed to delete comment');
167 const $conversationHolder = $this.closest('.conversation-holder');170
168 const $parentTimelineItem = $this.closest('.timeline-item');171 const conversationHolder = deleteButton.closest('.conversation-holder');
169 const $parentTimelineGroup = $this.closest('.timeline-item-group');172 const parentTimelineItem = deleteButton.closest('.timeline-item');
173 const parentTimelineGroup = deleteButton.closest('.timeline-item-group');
174
170 // Check if this was a pending comment.175 // Check if this was a pending comment.
171 if ($conversationHolder.find('.pending-label').length) {176 if (conversationHolder?.querySelector('.pending-label')) {
172 const counter = document.querySelector('#review-box .review-comments-counter');177 const counter = document.querySelector('#review-box .review-comments-counter');
173 let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;178 let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
174 num = Math.max(num, 0);179 num = Math.max(num, 0);
@@ -176,29 +181,32 @@ export function initRepoIssueCommentDelete() { · 1 unmodified line
176 counter.textContent = String(num);181 counter.textContent = String(num);
177 }182 }
178 183
179 $(`#${$this.data('comment-id')}`).remove();184 document.getElementById(deleteButton.getAttribute('data-comment-id'))?.remove();
180 if ($conversationHolder.length && !$conversationHolder.find('.comment').length) {185
181 const path = $conversationHolder.data('path');186 if (conversationHolder && !conversationHolder.querySelector('.comment')) {
182 const side = $conversationHolder.data('side');187 const path = conversationHolder.getAttribute('data-path');
183 const idx = $conversationHolder.data('idx');188 const side = conversationHolder.getAttribute('data-side');
184 const lineType = $conversationHolder.closest('tr').data('line-type');189 const idx = conversationHolder.getAttribute('data-idx');
190 const lineType = conversationHolder.closest('tr').getAttribute('data-line-type');
191
185 if (lineType === 'same') {192 if (lineType === 'same') {
186 $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('tw-invisible');193 document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).classList.remove('tw-invisible');
187 } else {194 } else {
188 $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('tw-invisible');195 document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).classList.remove('tw-invisible');
189 }196 }
190 $conversationHolder.remove();197
198 conversationHolder.remove();
191 }199 }
200
192 // Check if there is no review content, move the time avatar upward to avoid overlapping the content below.201 // Check if there is no review content, move the time avatar upward to avoid overlapping the content below.
193 if (!$parentTimelineGroup.find('.timeline-item.comment').length && !$parentTimelineItem.find('.conversation-holder').length) {202 if (!parentTimelineGroup?.querySelector('.timeline-item.comment') && !parentTimelineItem?.querySelector('.conversation-holder')) {
194 const $timelineAvatar = $parentTimelineGroup.find('.timeline-avatar');203 const timelineAvatar = parentTimelineGroup?.querySelector('.timeline-avatar');
195 $timelineAvatar.removeClass('timeline-avatar-offset');204 timelineAvatar?.classList.remove('timeline-avatar-offset');
196 }205 }
197 } catch (error) {206 } catch (error) {
198 console.error(error);207 console.error(error);
199 }208 }
200 }209 }
201 return false;
202 });210 });
203}211}
204 212
@@ -222,32 +230,35 @@ export function initRepoIssueDependencyDelete() { · 17 unmodified lines
222 230
223export function initRepoIssueCodeCommentCancel() {231export function initRepoIssueCodeCommentCancel() {
224 // Cancel inline code comment232 // Cancel inline code comment
225 $(document).on('click', '.cancel-code-comment', (e) => {233 document.addEventListener('click', (e) => {
226 const $form = $(e.currentTarget).closest('form');234 if (!e.target.matches('.cancel-code-comment')) return;
227 if ($form.length > 0 && $form.hasClass('comment-form')) {235
228 $form.addClass('tw-hidden');236 const form = e.target.closest('form');
229 showElem($form.closest('.comment-code-cloud').find('button.comment-form-reply'));237 if (form?.classList.contains('comment-form')) {
238 hideElem(form);
239 showElem(form.closest('.comment-code-cloud')?.querySelectorAll('button.comment-form-reply'));
230 } else {240 } else {
231 $form.closest('.comment-code-cloud').remove();241 form.closest('.comment-code-cloud')?.remove();
232 }242 }
233 });243 });
234}244}
235 245
236export function initRepoPullRequestUpdate() {246export function initRepoPullRequestUpdate() {
237 // Pull Request update button247 // Pull Request update button
238 const $pullUpdateButton = $('.update-button > button');248 const pullUpdateButton = document.querySelector('.update-button > button');
239 $pullUpdateButton.on('click', async function (e) {249 if (!pullUpdateButton) return;
250
251 pullUpdateButton.addEventListener('click', async function (e) {
240 e.preventDefault();252 e.preventDefault();
241 const $this = $(this);253 const redirect = this.getAttribute('data-redirect');
242 const redirect = $this.data('redirect');254 this.classList.add('is-loading');
243 $this.addClass('is-loading');
244 let response;255 let response;
245 try {256 try {
246 response = await POST($this.data('do'));257 response = await POST(this.getAttribute('data-do'));
247 } catch (error) {258 } catch (error) {
248 console.error(error);259 console.error(error);
249 } finally {260 } finally {
250 $this.removeClass('is-loading');261 this.classList.remove('is-loading');
251 }262 }
252 let data;263 let data;
253 try {264 try {
@@ -266,10 +277,13 @@ export function initRepoPullRequestUpdate() { · 12 unmodified lines
266 277
267 $('.update-button > .dropdown').dropdown({278 $('.update-button > .dropdown').dropdown({
268 onChange(_text, _value, $choice) {279 onChange(_text, _value, $choice) {
269 const $url = $choice.data('do');280 const url = $choice[0].getAttribute('data-do');
270 if ($url) {281 if (url) {
271 $pullUpdateButton.find('.button-text').text($choice.text());282 const buttonText = pullUpdateButton.querySelector('.button-text');
272 $pullUpdateButton.data('do', $url);283 if (buttonText) {
284 buttonText.textContent = $choice.text();
285 }
286 pullUpdateButton.setAttribute('data-do', url);
273 }287 }
274 },288 },
275 });289 });
@@ -282,32 +296,26 @@ export function initRepoPullRequestMergeInstruction() { · 6 unmodified lines
282}296}
283 297
284export function initRepoPullRequestAllowMaintainerEdit() {298export function initRepoPullRequestAllowMaintainerEdit() {
285 const checkbox = document.getElementById('allow-edits-from-maintainers');299 const wrapper = document.getElementById('allow-edits-from-maintainers');
286 if (!checkbox) return;300 if (!wrapper) return;
287 301
288 const $checkbox = $(checkbox);302 wrapper.querySelector('input[type="checkbox"]')?.addEventListener('change', async (e) => {
289 303 const checked = e.target.checked;
290 const promptError = checkbox.getAttribute('data-prompt-error');304 const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
291 $checkbox.checkbox({305 wrapper.classList.add('is-loading');
292 'onChange': async () => {306 e.target.disabled = true;
293 const checked = $checkbox.checkbox('is checked');307 try {
294 let url = checkbox.getAttribute('data-url');308 const response = await POST(url, {data: {allow_maintainer_edit: checked}});
295 url += '/set_allow_maintainer_edit';309 if (!response.ok) {
296 $checkbox.checkbox('set disabled');310 throw new Error('Failed to update maintainer edit permission');
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');
309 }311 }
310 },312 } catch (error) {
313 console.error(error);
314 showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error'));
315 } finally {
316 wrapper.classList.remove('is-loading');
317 e.target.disabled = false;
318 }
311 });319 });
312}320}
313 321
@@ -373,10 +381,10 @@ export function initRepoIssueComments() { · 59 unmodified lines
373 381
374 $('.re-request-review').on('click', async function (e) {382 $('.re-request-review').on('click', async function (e) {
375 e.preventDefault();383 e.preventDefault();
376 const url = $(this).data('update-url');384 const url = this.getAttribute('data-update-url');
377 const issueId = $(this).data('issue-id');385 const issueId = this.getAttribute('data-issue-id');
378 const id = $(this).data('id');386 const id = this.getAttribute('data-id');
379 const isChecked = $(this).hasClass('checked');387 const isChecked = this.classList.contains('checked');
380 388
381 await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);389 await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);
382 window.location.reload();390 window.location.reload();
@@ -403,7 +411,7 @@ export function initRepoIssueComments() { · 20 unmodified lines
403export async function handleReply($el) {411export async function handleReply($el) {
404 hideElem($el);412 hideElem($el);
405 const $form = $el.closest('.comment-code-cloud').find('.comment-form');413 const $form = $el.closest('.comment-code-cloud').find('.comment-form');
406 $form.removeClass('tw-hidden');414 showElem($form);
407 415
408 const $textarea = $form.find('textarea');416 const $textarea = $form.find('textarea');
409 let editor = getComboMarkdownEditor($textarea);417 let editor = getComboMarkdownEditor($textarea);
@@ -460,20 +468,20 @@ export function initRepoPullRequestReview() { · 50 unmodified lines
460 468
461 $(document).on('click', '.show-outdated', function (e) {469 $(document).on('click', '.show-outdated', function (e) {
462 e.preventDefault();470 e.preventDefault();
463 const id = $(this).data('comment');471 const id = this.getAttribute('data-comment');
464 $(this).addClass('tw-hidden');472 hideElem(this);
465 $(`#code-comments-${id}`).removeClass('tw-hidden');473 showElem(`#code-comments-${id}`);
466 $(`#code-preview-${id}`).removeClass('tw-hidden');474 showElem(`#code-preview-${id}`);
467 $(`#hide-outdated-${id}`).removeClass('tw-hidden');475 showElem(`#hide-outdated-${id}`);
468 });476 });
469 477
470 $(document).on('click', '.hide-outdated', function (e) {478 $(document).on('click', '.hide-outdated', function (e) {
471 e.preventDefault();479 e.preventDefault();
472 const id = $(this).data('comment');480 const id = this.getAttribute('data-comment');
473 $(this).addClass('tw-hidden');481 hideElem(this);
474 $(`#code-comments-${id}`).addClass('tw-hidden');482 hideElem(`#code-comments-${id}`);
475 $(`#code-preview-${id}`).addClass('tw-hidden');483 hideElem(`#code-preview-${id}`);
476 $(`#show-outdated-${id}`).removeClass('tw-hidden');484 showElem(`#show-outdated-${id}`);
477 });485 });
478 486
479 $(document).on('click', 'button.comment-form-reply', async function (e) {487 $(document).on('click', 'button.comment-form-reply', async function (e) {
@@ -510,18 +518,19 @@ export function initRepoPullRequestReview() { · 30 unmodified lines
510 }518 }
511 519
512 $(document).on('click', '.add-code-comment', async function (e) {520 $(document).on('click', '.add-code-comment', async function (e) {
513 if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745521 if (e.target.classList.contains('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
514 e.preventDefault();522 e.preventDefault();
515 523
516 const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split');524 const isSplit = this.closest('.code-diff')?.classList.contains('code-diff-split');
517 const side = $(this).data('side');525 const side = this.getAttribute('data-side');
518 const idx = $(this).data('idx');526 const idx = this.getAttribute('data-idx');
519 const path = $(this).closest('[data-path]').data('path');527 const path = this.closest('[data-path]')?.getAttribute('data-path');
520 const $tr = $(this).closest('tr');528 const tr = this.closest('tr');
521 const lineType = $tr.data('line-type');529 const lineType = tr.getAttribute('data-line-type');
522 530
523 let $ntr = $tr.next();531 const ntr = tr.nextElementSibling;
524 if (!$ntr.hasClass('add-comment')) {532 let $ntr = $(ntr);
533 if (!ntr?.classList.contains('add-comment')) {
525 $ntr = $(`534 $ntr = $(`
526 <tr class="add-comment" data-line-type="${lineType}">535 <tr class="add-comment" data-line-type="${lineType}">
527 ${isSplit ? `536 ${isSplit ? `
@@ -531,7 +540,7 @@ export function initRepoPullRequestReview() { · 3 unmodified lines
531 <td class="add-comment-left add-comment-right" colspan="5"></td>540 <td class="add-comment-left add-comment-right" colspan="5"></td>
532 `}541 `}
533 </tr>`);542 </tr>`);
534 $tr.after($ntr);543 $(tr).after($ntr);
535 }544 }
536 545
537 const $td = $ntr.find(`.add-comment-${side}`);546 const $td = $ntr.find(`.add-comment-${side}`);
@@ -617,13 +626,13 @@ export function initRepoIssueTitleEdit() { · 79 unmodified lines
617 626
618 const editTitleToggle = function () {627 const editTitleToggle = function () {
619 toggleElem($issueTitle);628 toggleElem($issueTitle);
620 toggleElem($('.not-in-edit'));629 toggleElem('.not-in-edit');
621 toggleElem($('#edit-title-input'));630 toggleElem('#edit-title-input');
622 toggleElem($('#pull-desc'));631 toggleElem('#pull-desc');
623 toggleElem($('#pull-desc-edit'));632 toggleElem('#pull-desc-edit');
624 toggleElem($('.in-edit'));633 toggleElem('.in-edit');
625 toggleElem($('.new-issue-button'));634 toggleElem('.new-issue-button');
626 $('#issue-title-wrapper').toggleClass('edit-active');635 document.getElementById('issue-title-wrapper')?.classList.toggle('edit-active');
627 $editInput[0].focus();636 $editInput[0].focus();
628 $editInput[0].select();637 $editInput[0].select();
629 return false;638 return false;
web_src/js/features/repo-projects.js+23−22
@@ -94,47 +94,46 @@ async function initRepoProjectSortable() { · 93 unmodified lines
94}94}
95 95
96export function initRepoProject() {96export function initRepoProject() {
97 if (!$('.repository.projects').length) {97 if (!document.querySelector('.repository.projects')) {
98 return;98 return;
99 }99 }
100 100
101 const _promise = initRepoProjectSortable();101 const _promise = initRepoProjectSortable();
102 102
103 $('.edit-project-column-modal').each(function () {103 for (const modal of document.getElementsByClassName('edit-project-column-modal')) {
104 const $projectHeader = $(this).closest('.project-column-header');104 const projectHeader = modal.closest('.project-column-header');
105 const $projectTitleLabel = $projectHeader.find('.project-column-title');105 const projectTitleLabel = projectHeader?.querySelector('.project-column-title');
106 const $projectTitleInput = $(this).find('.project-column-title-input');106 const projectTitleInput = modal.querySelector('.project-column-title-input');
107 const $projectColorInput = $(this).find('#new_project_column_color');107 const projectColorInput = modal.querySelector('#new_project_column_color');
108 const $boardColumn = $(this).closest('.project-column');108 const boardColumn = modal.closest('.project-column');
109 const bgColor = boardColumn?.style.backgroundColor;
109 110
110 const bgColor = $boardColumn[0].style.backgroundColor;
111 if (bgColor) {111 if (bgColor) {
112 setLabelColor($projectHeader, rgbToHex(bgColor));112 setLabelColor(projectHeader, rgbToHex(bgColor));
113 }113 }
114 114
115 $(this).find('.edit-project-column-button').on('click', async function (e) {115 modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) {
116 e.preventDefault();116 e.preventDefault();
117
118 try {117 try {
119 await PUT($(this).data('url'), {118 await PUT(this.getAttribute('data-url'), {
120 data: {119 data: {
121 title: $projectTitleInput.val(),120 title: projectTitleInput?.value,
122 color: $projectColorInput.val(),121 color: projectColorInput?.value,
123 },122 },
124 });123 });
125 } catch (error) {124 } catch (error) {
126 console.error(error);125 console.error(error);
127 } finally {126 } finally {
128 $projectTitleLabel.text($projectTitleInput.val());127 projectTitleLabel.textContent = projectTitleInput?.value;
129 $projectTitleInput.closest('form').removeClass('dirty');128 projectTitleInput.closest('form')?.classList.remove('dirty');
130 if ($projectColorInput.val()) {129 if (projectColorInput?.value) {
131 setLabelColor($projectHeader, $projectColorInput.val());130 setLabelColor(projectHeader, projectColorInput.value);
132 }131 }
133 $boardColumn[0].style = `background: ${$projectColorInput.val()} !important`;132 boardColumn.style = `background: ${projectColorInput.value} !important`;
134 $('.ui.modal').modal('hide');133 $('.ui.modal').modal('hide');
135 }134 }
136 });135 });
137 });136 }
138 137
139 $('.default-project-column-modal').each(function () {138 $('.default-project-column-modal').each(function () {
140 const $boardColumn = $(this).closest('.project-column');139 const $boardColumn = $(this).closest('.project-column');
@@ -187,9 +186,11 @@ export function initRepoProject() { · 46 unmodified lines
187function setLabelColor(label, color) {186function setLabelColor(label, color) {
188 const {r, g, b} = tinycolor(color).toRgb();187 const {r, g, b} = tinycolor(color).toRgb();
189 if (useLightTextOnBackground(r, g, b)) {188 if (useLightTextOnBackground(r, g, b)) {
190 label.removeClass('dark-label').addClass('light-label');189 label.classList.remove('dark-label');
190 label.classList.add('light-label');
191 } else {191 } else {
192 label.removeClass('light-label').addClass('dark-label');192 label.classList.remove('light-label');
193 label.classList.add('dark-label');
193 }194 }
194}195}
195 196
web_src/js/features/repo-settings.js+18−13
@@ -77,18 +77,24 @@ export function initRepoSettingGitHook() { · 76 unmodified lines
77}77}
78 78
79export function initRepoSettingBranches() {79export function initRepoSettingBranches() {
80 if (!$('.repository.settings.branches').length) return;80 if (!document.querySelector('.repository.settings.branches')) return;
81 $('.toggle-target-enabled').on('change', function () {81
82 const $target = $(this.getAttribute('data-target'));82 for (const el of document.getElementsByClassName('toggle-target-enabled')) {
83 $target.toggleClass('disabled', !this.checked);83 el.addEventListener('change', function () {
84 });84 const target = document.querySelector(this.getAttribute('data-target'));
85 $('.toggle-target-disabled').on('change', function () {85 target?.classList.toggle('disabled', !this.checked);
86 const $target = $(this.getAttribute('data-target'));86 });
87 if (this.checked) $target.addClass('disabled'); // only disable, do not auto enable87 }
88 });88
89 $('#dismiss_stale_approvals').on('change', function () {89 for (const el of document.getElementsByClassName('toggle-target-disabled')) {
90 const $target = $('#ignore_stale_approvals_box');90 el.addEventListener('change', function () {
91 $target.toggleClass('disabled', this.checked);91 const target = document.querySelector(this.getAttribute('data-target'));
92 if (this.checked) target?.classList.add('disabled'); // only disable, do not auto enable
93 });
94 }
95
96 document.getElementById('dismiss_stale_approvals')?.addEventListener('change', function () {
97 document.getElementById('ignore_stale_approvals_box')?.classList.toggle('disabled', this.checked);
92 });98 });
93 99
94 // show the `Matched` mark for the status checks that match the pattern100 // show the `Matched` mark for the status checks that match the pattern
@@ -106,7 +112,6 @@ export function initRepoSettingBranches() { · 11 unmodified lines
106 break;112 break;
107 }113 }
108 }114 }
109
110 toggleElem(el, matched);115 toggleElem(el, matched);
111 }116 }
112 };117 };
web_src/js/index.js+2−0
@@ -86,6 +86,7 @@ import {initRepoRecentCommits} from './features/recent-commits.js'; · 85 unmodified lines
86import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';86import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
87import {initDirAuto} from './modules/dirauto.js';87import {initDirAuto} from './modules/dirauto.js';
88import {initRepositorySearch} from './features/repo-search.js';88import {initRepositorySearch} from './features/repo-search.js';
89import {initColorPickers} from './features/colorpicker.js';
89 90
90// Init Gitea's Fomantic settings91// Init Gitea's Fomantic settings
91initGiteaFomantic();92initGiteaFomantic();
@@ -188,4 +189,5 @@ onDomReady(() => { · 96 unmodified lines
188 initRepoDiffView();189 initRepoDiffView();
189 initPdfViewer();190 initPdfViewer();
190 initScopedAccessTokenCategories();191 initScopedAccessTokenCategories();
192 initColorPickers();
191});193});
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
11export function initGiteaFomantic() {11export function initGiteaFomantic() {
12 // Silence fomantic's error logging when tabs are used without a target content element12 // Silence fomantic's error logging when tabs are used without a target content element
13 $.fn.tab.settings.silent = true;13 $.fn.tab.settings.silent = true;
14 // Disable the behavior of fomantic to toggle the checkbox when you press enter on a checkbox element.
15 $.fn.checkbox.settings.enableEnterKey = false;
16 14
17 // By default, use "exact match" for full text search15 // By default, use "exact match" for full text search
18 $.fn.dropdown.settings.fullTextSearch = 'exact';16 $.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
41<label><input type="checkbox"> ... </label>41<label><input type="checkbox"> ... </label>
42```42```
43 43
44However, related CSS styles aren't supported (not implemented) yet, so at the moment,44However, the templates still have the Fomantic-style HTML layout:
45almost all the checkboxes are still using Fomantic UI checkbox.
46
47## Fomantic UI Checkbox
48 45
49```html46```html
50<div class="ui checkbox">47<div class="ui checkbox">
51 <input type="checkbox"> <!-- class "hidden" will be added by $.checkbox() -->48 <input type="checkbox">
52 <label>...</label>49 <label>...</label>
53</div>50</div>
54```51```
55 52
56Then the JS `$.checkbox()` should be called to make it work with keyboard and label-clicking,53We call `initAriaCheckboxPatch` to link the `input` and `label` which makes clicking the
57then it works like the ideal checkboxes.54label etc. work. There is still a problem: These checkboxes are not friendly to screen readers,
58 55so we add IDs to all the Fomantic UI checkboxes automatically by JS. If the `label` part is empty,
59There is still a problem: Fomantic UI checkbox is not friendly to screen readers,56then the checkbox needs to get the `aria-label` attribute manually.
60so we add IDs to all the Fomantic UI checkboxes automatically by JS.
61If the `label` part is empty, then the checkbox needs to get the `aria-label` attribute manually.
62 57
63# Fomantic Dropdown58# Fomantic Dropdown
64 59
web_src/js/modules/fomantic/checkbox.js+18−32
@@ -1,38 +1,24 @@
1import $ from 'jquery';
2import {generateAriaId} from './base.js';1import {generateAriaId} from './base.js';
3 2
4const ariaPatchKey = '_giteaAriaPatchCheckbox';
5const fomanticCheckboxFn = $.fn.checkbox;
6
7// use our own `$.fn.checkbox` to patch Fomantic's checkbox module
8export function initAriaCheckboxPatch() {3export function initAriaCheckboxPatch() {
9 if ($.fn.checkbox === ariaCheckboxFn) throw new Error('initAriaCheckboxPatch could only be called once');4 // link the label and the input element so it's clickable and accessible
10 $.fn.checkbox = ariaCheckboxFn;5 for (const el of document.querySelectorAll('.ui.checkbox')) {
11 ariaCheckboxFn.settings = fomanticCheckboxFn.settings;6 if (el.hasAttribute('data-checkbox-patched')) continue;
12}7 const label = el.querySelector('label');
8 const input = el.querySelector('input');
9 if (!label || !input) continue;
10 const inputId = input.getAttribute('id');
11 const labelFor = label.getAttribute('for');
13 12
14// the patched `$.fn.checkbox` checkbox function13 if (inputId && !labelFor) { // missing "for"
15// * it does the one-time attaching on the first call14 label.setAttribute('for', inputId);
16function ariaCheckboxFn(...args) {15 } else if (!inputId && !labelFor) { // missing both "id" and "for"
17 const ret = fomanticCheckboxFn.apply(this, args);16 const id = generateAriaId();
18 for (const el of this) {17 input.setAttribute('id', id);
19 if (el[ariaPatchKey]) continue;18 label.setAttribute('for', id);
20 attachInit(el);19 } else {
20 continue;
21 }
22 el.setAttribute('data-checkbox-patched', 'true');
21 }23 }
22 return ret;
23}
24
25function attachInit(el) {
26 // Fomantic UI checkbox needs to be something like: <div class="ui checkbox"><label /><input /></div>
27 // It doesn't work well with <label><input />...</label>
28 // To make it work with aria, the "id"/"for" attributes are necessary, so add them automatically if missing.
29 // In the future, refactor to use native checkbox directly, then this patch could be removed.
30 el[ariaPatchKey] = {}; // record that this element has been patched
31 const label = el.querySelector('label');
32 const input = el.querySelector('input');
33 if (!label || !input || input.getAttribute('id')) return;
34
35 const id = generateAriaId();
36 input.setAttribute('id', id);
37 label.setAttribute('for', id);
38}24}
web_src/js/modules/fomantic/dropdown.js+1−1
@@ -207,7 +207,7 @@ function attachDomEvents(dropdown, focusable, menu) { · 206 unmodified lines
207 if (!$item) $item = $(menu).find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item207 if (!$item) $item = $(menu).find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item
208 // if the selected item is clickable, then trigger the click event.208 // if the selected item is clickable, then trigger the click event.
209 // we can not click any item without check, because Fomantic code might also handle the Enter event. that would result in double click.209 // we can not click any item without check, because Fomantic code might also handle the Enter event. that would result in double click.
210 if ($item && ($item[0].matches('a') || $item.hasClass('js-aria-clickable'))) $item[0].click();210 if ($item?.[0]?.matches('a, .js-aria-clickable')) $item[0].click();
211 }211 }
212 });212 });
213 213
web_src/js/modules/tippy.js+4−3
@@ -3,11 +3,12 @@ import {isDocumentFragmentOrElementNode} from '../utils/dom.js'; · 2 unmodified lines
3import {formatDatetime} from '../utils/time.js';3import {formatDatetime} from '../utils/time.js';
4 4
5const visibleInstances = new Set();5const visibleInstances = new Set();
6const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`;
6 7
7export function createTippy(target, opts = {}) {8export function createTippy(target, opts = {}) {
8 // the callback functions should be destructured from opts,9 // the callback functions should be destructured from opts,
9 // because we should use our own wrapper functions to handle them, do not let the user override them10 // because we should use our own wrapper functions to handle them, do not let the user override them
10 const {onHide, onShow, onDestroy, role, theme, ...other} = opts;11 const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts;
11 12
12 const instance = tippy(target, {13 const instance = tippy(target, {
13 appendTo: document.body,14 appendTo: document.body,
@@ -35,9 +36,9 @@ export function createTippy(target, opts = {}) { · 21 unmodified lines
35 visibleInstances.add(instance);36 visibleInstances.add(instance);
36 return onShow?.(instance);37 return onShow?.(instance);
37 },38 },
38 arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`,39 arrow: arrow || (theme === 'bare' ? false : arrowSvg),
39 role: role || 'menu', // HTML role attribute40 role: role || 'menu', // HTML role attribute
40 theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu" or "box-with-header"41 theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu", "box-with-header" or "bare"
41 plugins: [followCursor],42 plugins: [followCursor],
42 ...other,43 ...other,
43 });44 });
web_src/js/modules/toast.js+1−0
@@ -39,6 +39,7 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, .. · 38 unmodified lines
39 39
40 toast.showToast();40 toast.showToast();
41 toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());41 toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
42 return toast;
42}43}
43 44
44export function showInfoToast(message, opts) {45export function showInfoToast(message, opts) {
web_src/js/utils/dom.js+16−2
@@ -51,8 +51,22 @@ export function isElemHidden(el) { · 50 unmodified lines
51 return res[0];51 return res[0];
52}52}
53 53
54export function queryElemSiblings(el, selector) {54function applyElemsCallback(elems, fn) {
55 return Array.from(el.parentNode.children).filter((child) => child !== el && child.matches(selector));55 if (fn) {
56 for (const el of elems) {
57 fn(el);
58 }
59 }
60 return elems;
61}
62
63export 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
68export function queryElemChildren(parent, selector = '*', fn) {
69 return applyElemsCallback(parent.querySelectorAll(`:scope > ${selector}`), fn);
56}70}
57 71
58export function onDomReady(cb) {72export function onDomReady(cb) {
webpack.config.js+0−7
@@ -196,13 +196,6 @@ export default { · 195 unmodified lines
196 filename: 'fonts/[name].[contenthash:8][ext]',196 filename: 'fonts/[name].[contenthash:8][ext]',
197 },197 },
198 },198 },
199 {
200 test: /\.png$/i,
201 type: 'asset/resource',
202 generator: {
203 filename: 'img/webpack/[name].[contenthash:8][ext]',
204 },
205 },
206 ],199 ],
207 },200 },
208 plugins: [201 plugins: [