Merge pull request '[gitea] week 15 cherry pick' (#3091) from algernon/forgejo:wcp/week-15 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3091 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
commit
26fc7c3461
117 changed files with 3706 additions and 4312 deletions
|
@ -22,7 +22,6 @@ package "code.gitea.io/gitea/models/actions"
|
||||||
func (ScheduleList).GetRepoIDs
|
func (ScheduleList).GetRepoIDs
|
||||||
func (ScheduleList).LoadTriggerUser
|
func (ScheduleList).LoadTriggerUser
|
||||||
func (ScheduleList).LoadRepos
|
func (ScheduleList).LoadRepos
|
||||||
func GetVariableByID
|
|
||||||
|
|
||||||
package "code.gitea.io/gitea/models/asymkey"
|
package "code.gitea.io/gitea/models/asymkey"
|
||||||
func (ErrGPGKeyAccessDenied).Error
|
func (ErrGPGKeyAccessDenied).Error
|
||||||
|
|
|
@ -77,7 +77,6 @@ cpu.out
|
||||||
/public/assets/css
|
/public/assets/css
|
||||||
/public/assets/fonts
|
/public/assets/fonts
|
||||||
/public/assets/img/avatar
|
/public/assets/img/avatar
|
||||||
/public/assets/img/webpack
|
|
||||||
/vendor
|
/vendor
|
||||||
/web_src/fomantic/node_modules
|
/web_src/fomantic/node_modules
|
||||||
/web_src/fomantic/build/*
|
/web_src/fomantic/build/*
|
||||||
|
|
|
@ -3,6 +3,7 @@ reportUnusedDisableDirectives: true
|
||||||
|
|
||||||
ignorePatterns:
|
ignorePatterns:
|
||||||
- /web_src/js/vendor
|
- /web_src/js/vendor
|
||||||
|
- /web_src/fomantic
|
||||||
|
|
||||||
parserOptions:
|
parserOptions:
|
||||||
sourceType: module
|
sourceType: module
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -83,7 +83,6 @@ cpu.out
|
||||||
/public/assets/css
|
/public/assets/css
|
||||||
/public/assets/fonts
|
/public/assets/fonts
|
||||||
/public/assets/licenses.txt
|
/public/assets/licenses.txt
|
||||||
/public/assets/img/webpack
|
|
||||||
/vendor
|
/vendor
|
||||||
/web_src/fomantic/node_modules
|
/web_src/fomantic/node_modules
|
||||||
/web_src/fomantic/build/*
|
/web_src/fomantic/build/*
|
||||||
|
|
4
.ignore
4
.ignore
|
@ -4,6 +4,8 @@
|
||||||
/modules/options/bindata.go
|
/modules/options/bindata.go
|
||||||
/modules/public/bindata.go
|
/modules/public/bindata.go
|
||||||
/modules/templates/bindata.go
|
/modules/templates/bindata.go
|
||||||
/vendor
|
/options/gitignore
|
||||||
|
/options/license
|
||||||
/public/assets
|
/public/assets
|
||||||
|
/vendor
|
||||||
node_modules
|
node_modules
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
plugins:
|
|
||||||
- stylelint-declaration-strict-value
|
|
||||||
- stylelint-declaration-block-no-ignored-properties
|
|
||||||
- "@stylistic/stylelint-plugin"
|
|
||||||
|
|
||||||
ignoreFiles:
|
|
||||||
- "**/*.go"
|
|
||||||
|
|
||||||
overrides:
|
|
||||||
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console.css", "font_i18n.css"]
|
|
||||||
rules:
|
|
||||||
scale-unlimited/declaration-strict-value: null
|
|
||||||
- files: ["**/chroma/*", "**/codemirror/*"]
|
|
||||||
rules:
|
|
||||||
block-no-empty: null
|
|
||||||
- files: ["**/*.vue"]
|
|
||||||
customSyntax: postcss-html
|
|
||||||
|
|
||||||
rules:
|
|
||||||
"@stylistic/at-rule-name-case": null
|
|
||||||
"@stylistic/at-rule-name-newline-after": null
|
|
||||||
"@stylistic/at-rule-name-space-after": null
|
|
||||||
"@stylistic/at-rule-semicolon-newline-after": null
|
|
||||||
"@stylistic/at-rule-semicolon-space-before": null
|
|
||||||
"@stylistic/block-closing-brace-empty-line-before": null
|
|
||||||
"@stylistic/block-closing-brace-newline-after": null
|
|
||||||
"@stylistic/block-closing-brace-newline-before": null
|
|
||||||
"@stylistic/block-closing-brace-space-after": null
|
|
||||||
"@stylistic/block-closing-brace-space-before": null
|
|
||||||
"@stylistic/block-opening-brace-newline-after": null
|
|
||||||
"@stylistic/block-opening-brace-newline-before": null
|
|
||||||
"@stylistic/block-opening-brace-space-after": null
|
|
||||||
"@stylistic/block-opening-brace-space-before": always
|
|
||||||
"@stylistic/color-hex-case": lower
|
|
||||||
"@stylistic/declaration-bang-space-after": never
|
|
||||||
"@stylistic/declaration-bang-space-before": null
|
|
||||||
"@stylistic/declaration-block-semicolon-newline-after": null
|
|
||||||
"@stylistic/declaration-block-semicolon-newline-before": null
|
|
||||||
"@stylistic/declaration-block-semicolon-space-after": null
|
|
||||||
"@stylistic/declaration-block-semicolon-space-before": never
|
|
||||||
"@stylistic/declaration-block-trailing-semicolon": null
|
|
||||||
"@stylistic/declaration-colon-newline-after": null
|
|
||||||
"@stylistic/declaration-colon-space-after": null
|
|
||||||
"@stylistic/declaration-colon-space-before": never
|
|
||||||
"@stylistic/function-comma-newline-after": null
|
|
||||||
"@stylistic/function-comma-newline-before": null
|
|
||||||
"@stylistic/function-comma-space-after": null
|
|
||||||
"@stylistic/function-comma-space-before": null
|
|
||||||
"@stylistic/function-max-empty-lines": 0
|
|
||||||
"@stylistic/function-parentheses-newline-inside": never-multi-line
|
|
||||||
"@stylistic/function-parentheses-space-inside": null
|
|
||||||
"@stylistic/function-whitespace-after": null
|
|
||||||
"@stylistic/indentation": 2
|
|
||||||
"@stylistic/linebreaks": null
|
|
||||||
"@stylistic/max-empty-lines": 1
|
|
||||||
"@stylistic/max-line-length": null
|
|
||||||
"@stylistic/media-feature-colon-space-after": null
|
|
||||||
"@stylistic/media-feature-colon-space-before": never
|
|
||||||
"@stylistic/media-feature-name-case": null
|
|
||||||
"@stylistic/media-feature-parentheses-space-inside": null
|
|
||||||
"@stylistic/media-feature-range-operator-space-after": always
|
|
||||||
"@stylistic/media-feature-range-operator-space-before": always
|
|
||||||
"@stylistic/media-query-list-comma-newline-after": null
|
|
||||||
"@stylistic/media-query-list-comma-newline-before": null
|
|
||||||
"@stylistic/media-query-list-comma-space-after": null
|
|
||||||
"@stylistic/media-query-list-comma-space-before": null
|
|
||||||
"@stylistic/named-grid-areas-alignment": null
|
|
||||||
"@stylistic/no-empty-first-line": null
|
|
||||||
"@stylistic/no-eol-whitespace": true
|
|
||||||
"@stylistic/no-extra-semicolons": true
|
|
||||||
"@stylistic/no-missing-end-of-source-newline": null
|
|
||||||
"@stylistic/number-leading-zero": null
|
|
||||||
"@stylistic/number-no-trailing-zeros": null
|
|
||||||
"@stylistic/property-case": lower
|
|
||||||
"@stylistic/selector-attribute-brackets-space-inside": null
|
|
||||||
"@stylistic/selector-attribute-operator-space-after": null
|
|
||||||
"@stylistic/selector-attribute-operator-space-before": null
|
|
||||||
"@stylistic/selector-combinator-space-after": null
|
|
||||||
"@stylistic/selector-combinator-space-before": null
|
|
||||||
"@stylistic/selector-descendant-combinator-no-non-space": null
|
|
||||||
"@stylistic/selector-list-comma-newline-after": null
|
|
||||||
"@stylistic/selector-list-comma-newline-before": null
|
|
||||||
"@stylistic/selector-list-comma-space-after": always-single-line
|
|
||||||
"@stylistic/selector-list-comma-space-before": never-single-line
|
|
||||||
"@stylistic/selector-max-empty-lines": 0
|
|
||||||
"@stylistic/selector-pseudo-class-case": lower
|
|
||||||
"@stylistic/selector-pseudo-class-parentheses-space-inside": never
|
|
||||||
"@stylistic/selector-pseudo-element-case": lower
|
|
||||||
"@stylistic/string-quotes": double
|
|
||||||
"@stylistic/unicode-bom": null
|
|
||||||
"@stylistic/unit-case": lower
|
|
||||||
"@stylistic/value-list-comma-newline-after": null
|
|
||||||
"@stylistic/value-list-comma-newline-before": null
|
|
||||||
"@stylistic/value-list-comma-space-after": null
|
|
||||||
"@stylistic/value-list-comma-space-before": null
|
|
||||||
"@stylistic/value-list-max-empty-lines": 0
|
|
||||||
alpha-value-notation: null
|
|
||||||
annotation-no-unknown: true
|
|
||||||
at-rule-allowed-list: null
|
|
||||||
at-rule-disallowed-list: null
|
|
||||||
at-rule-empty-line-before: null
|
|
||||||
at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}]
|
|
||||||
at-rule-no-vendor-prefix: true
|
|
||||||
at-rule-property-required-list: null
|
|
||||||
block-no-empty: true
|
|
||||||
color-function-notation: null
|
|
||||||
color-hex-alpha: null
|
|
||||||
color-hex-length: null
|
|
||||||
color-named: null
|
|
||||||
color-no-hex: null
|
|
||||||
color-no-invalid-hex: true
|
|
||||||
comment-empty-line-before: null
|
|
||||||
comment-no-empty: true
|
|
||||||
comment-pattern: null
|
|
||||||
comment-whitespace-inside: null
|
|
||||||
comment-word-disallowed-list: null
|
|
||||||
custom-media-pattern: null
|
|
||||||
custom-property-empty-line-before: null
|
|
||||||
custom-property-no-missing-var-function: true
|
|
||||||
custom-property-pattern: null
|
|
||||||
declaration-block-no-duplicate-custom-properties: true
|
|
||||||
declaration-block-no-duplicate-properties: [true, {ignore: [consecutive-duplicates-with-different-values]}]
|
|
||||||
declaration-block-no-redundant-longhand-properties: null
|
|
||||||
declaration-block-no-shorthand-property-overrides: null
|
|
||||||
declaration-block-single-line-max-declarations: null
|
|
||||||
declaration-empty-line-before: null
|
|
||||||
declaration-no-important: null
|
|
||||||
declaration-property-max-values: null
|
|
||||||
declaration-property-unit-allowed-list: null
|
|
||||||
declaration-property-unit-disallowed-list: {line-height: [em]}
|
|
||||||
declaration-property-value-allowed-list: null
|
|
||||||
declaration-property-value-disallowed-list: null
|
|
||||||
declaration-property-value-no-unknown: true
|
|
||||||
font-family-name-quotes: always-where-recommended
|
|
||||||
font-family-no-duplicate-names: true
|
|
||||||
font-family-no-missing-generic-family-keyword: true
|
|
||||||
font-weight-notation: null
|
|
||||||
function-allowed-list: null
|
|
||||||
function-calc-no-unspaced-operator: true
|
|
||||||
function-disallowed-list: null
|
|
||||||
function-linear-gradient-no-nonstandard-direction: true
|
|
||||||
function-name-case: lower
|
|
||||||
function-no-unknown: true
|
|
||||||
function-url-no-scheme-relative: null
|
|
||||||
function-url-quotes: always
|
|
||||||
function-url-scheme-allowed-list: null
|
|
||||||
function-url-scheme-disallowed-list: null
|
|
||||||
hue-degree-notation: null
|
|
||||||
import-notation: string
|
|
||||||
keyframe-block-no-duplicate-selectors: true
|
|
||||||
keyframe-declaration-no-important: true
|
|
||||||
keyframe-selector-notation: null
|
|
||||||
keyframes-name-pattern: null
|
|
||||||
length-zero-no-unit: [true, ignore: [custom-properties], ignoreFunctions: [var]]
|
|
||||||
max-nesting-depth: null
|
|
||||||
media-feature-name-allowed-list: null
|
|
||||||
media-feature-name-disallowed-list: null
|
|
||||||
media-feature-name-no-unknown: true
|
|
||||||
media-feature-name-no-vendor-prefix: true
|
|
||||||
media-feature-name-unit-allowed-list: null
|
|
||||||
media-feature-name-value-allowed-list: null
|
|
||||||
media-feature-name-value-no-unknown: true
|
|
||||||
media-feature-range-notation: null
|
|
||||||
media-query-no-invalid: true
|
|
||||||
named-grid-areas-no-invalid: true
|
|
||||||
no-descending-specificity: null
|
|
||||||
no-duplicate-at-import-rules: true
|
|
||||||
no-duplicate-selectors: true
|
|
||||||
no-empty-source: true
|
|
||||||
no-invalid-double-slash-comments: true
|
|
||||||
no-invalid-position-at-import-rule: [true, ignoreAtRules: [tailwind]]
|
|
||||||
no-irregular-whitespace: true
|
|
||||||
no-unknown-animations: null
|
|
||||||
no-unknown-custom-properties: null
|
|
||||||
number-max-precision: null
|
|
||||||
plugin/declaration-block-no-ignored-properties: true
|
|
||||||
property-allowed-list: null
|
|
||||||
property-disallowed-list: null
|
|
||||||
property-no-unknown: true
|
|
||||||
property-no-vendor-prefix: null
|
|
||||||
rule-empty-line-before: null
|
|
||||||
rule-selector-property-disallowed-list: null
|
|
||||||
scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}]
|
|
||||||
selector-anb-no-unmatchable: true
|
|
||||||
selector-attribute-name-disallowed-list: null
|
|
||||||
selector-attribute-operator-allowed-list: null
|
|
||||||
selector-attribute-operator-disallowed-list: null
|
|
||||||
selector-attribute-quotes: always
|
|
||||||
selector-class-pattern: null
|
|
||||||
selector-combinator-allowed-list: null
|
|
||||||
selector-combinator-disallowed-list: null
|
|
||||||
selector-disallowed-list: null
|
|
||||||
selector-id-pattern: null
|
|
||||||
selector-max-attribute: null
|
|
||||||
selector-max-class: null
|
|
||||||
selector-max-combinators: null
|
|
||||||
selector-max-compound-selectors: null
|
|
||||||
selector-max-id: null
|
|
||||||
selector-max-pseudo-class: null
|
|
||||||
selector-max-specificity: null
|
|
||||||
selector-max-type: null
|
|
||||||
selector-max-universal: null
|
|
||||||
selector-nested-pattern: null
|
|
||||||
selector-no-qualifying-type: null
|
|
||||||
selector-no-vendor-prefix: true
|
|
||||||
selector-not-notation: null
|
|
||||||
selector-pseudo-class-allowed-list: null
|
|
||||||
selector-pseudo-class-disallowed-list: null
|
|
||||||
selector-pseudo-class-no-unknown: true
|
|
||||||
selector-pseudo-element-allowed-list: null
|
|
||||||
selector-pseudo-element-colon-notation: double
|
|
||||||
selector-pseudo-element-disallowed-list: null
|
|
||||||
selector-pseudo-element-no-unknown: true
|
|
||||||
selector-type-case: lower
|
|
||||||
selector-type-no-unknown: [true, {ignore: [custom-elements]}]
|
|
||||||
shorthand-property-no-redundant-values: true
|
|
||||||
string-no-newline: true
|
|
||||||
time-min-milliseconds: null
|
|
||||||
unit-allowed-list: null
|
|
||||||
unit-disallowed-list: null
|
|
||||||
unit-no-unknown: true
|
|
||||||
value-keyword-case: null
|
|
||||||
value-no-vendor-prefix: [true, {ignoreValues: [box, inline-box]}]
|
|
2
Makefile
2
Makefile
|
@ -130,7 +130,7 @@ FOMANTIC_WORK_DIR := web_src/fomantic
|
||||||
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
||||||
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
||||||
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
||||||
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
|
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
|
||||||
|
|
||||||
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
|
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
|
||||||
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
|
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
|
||||||
|
|
|
@ -1503,6 +1503,11 @@ LEVEL = Info
|
||||||
;; - manage_ssh_keys: a user cannot configure ssh keys
|
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||||
;; - manage_gpg_keys: a user cannot configure gpg keys
|
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||||
;USER_DISABLED_FEATURES =
|
;USER_DISABLED_FEATURES =
|
||||||
|
;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
|
||||||
|
;; - deletion: a user cannot delete their own account
|
||||||
|
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||||
|
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||||
|
;;EXTERNAL_USER_DISABLE_FEATURES =
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -2326,6 +2331,8 @@ LEVEL = Info
|
||||||
;SHOW_FOOTER_VERSION = true
|
;SHOW_FOOTER_VERSION = true
|
||||||
;; Show template execution time in the footer
|
;; Show template execution time in the footer
|
||||||
;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
|
;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
|
||||||
|
;; Show the "powered by" text in the footer
|
||||||
|
;SHOW_FOOTER_POWERED_BY = true
|
||||||
;; Generate sitemap. Defaults to `true`.
|
;; Generate sitemap. Defaults to `true`.
|
||||||
;ENABLE_SITEMAP = true
|
;ENABLE_SITEMAP = true
|
||||||
;; Enable/Disable RSS/Atom feed
|
;; Enable/Disable RSS/Atom feed
|
||||||
|
|
|
@ -6,13 +6,11 @@ package actions
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -55,24 +53,24 @@ type FindVariablesOpts struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
OwnerID int64
|
OwnerID int64
|
||||||
RepoID int64
|
RepoID int64
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindVariablesOpts) ToConds() builder.Cond {
|
func (opts FindVariablesOpts) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
|
// Since we now support instance-level variables,
|
||||||
|
// there is no need to check for null values for `owner_id` and `repo_id`
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
|
|
||||||
|
if opts.Name != "" {
|
||||||
|
cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)})
|
||||||
|
}
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) {
|
func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) {
|
||||||
var variable ActionVariable
|
return db.Find[ActionVariable](ctx, opts)
|
||||||
has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !has {
|
|
||||||
return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist)
|
|
||||||
}
|
|
||||||
return &variable, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
|
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
|
||||||
|
@ -84,6 +82,13 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error)
|
||||||
return count != 0, err
|
return count != 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteVariable(ctx context.Context, id int64) error {
|
||||||
|
if _, err := db.DeleteByID[ActionVariable](ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
|
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
|
||||||
variables := map[string]string{}
|
variables := map[string]string{}
|
||||||
|
|
||||||
|
|
|
@ -134,3 +134,13 @@ func extractSignature(s string) (*packet.Signature, error) {
|
||||||
}
|
}
|
||||||
return sig, nil
|
return sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tryGetKeyIDFromSignature(sig *packet.Signature) string {
|
||||||
|
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||||
|
return fmt.Sprintf("%016X", *sig.IssuerKeyId)
|
||||||
|
}
|
||||||
|
if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||||
|
return fmt.Sprintf("%016X", sig.IssuerFingerprint[12:20])
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
|
@ -123,13 +123,7 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID := ""
|
keyID := tryGetKeyIDFromSignature(sig)
|
||||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
|
||||||
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
|
|
||||||
}
|
|
||||||
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
|
||||||
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
|
||||||
}
|
|
||||||
defaultReason := NoKeyFound
|
defaultReason := NoKeyFound
|
||||||
|
|
||||||
// First check if the sig has a keyID and if so just look at that
|
// First check if the sig has a keyID and if so just look at that
|
||||||
|
|
|
@ -11,7 +11,9 @@ import (
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/keybase/go-crypto/openpgp/packet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -391,3 +393,13 @@ epiDVQ==
|
||||||
assert.Equal(t, time.Unix(1586105389, 0), expire)
|
assert.Equal(t, time.Unix(1586105389, 0), expire)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTryGetKeyIDFromSignature(t *testing.T) {
|
||||||
|
assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{}))
|
||||||
|
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||||
|
IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)),
|
||||||
|
}))
|
||||||
|
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||||
|
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
|
||||||
return "", ErrGPGKeyNotExist{}
|
return "", ErrGPGKeyNotExist{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := key.LoadSubKeys(ctx); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
sig, err := extractSignature(signature)
|
sig, err := extractSignature(signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ErrGPGInvalidTokenSignature{
|
return "", ErrGPGInvalidTokenSignature{
|
||||||
|
|
|
@ -1246,3 +1246,21 @@ func GetOrderByName() string {
|
||||||
}
|
}
|
||||||
return "name"
|
return "name"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the
|
||||||
|
// user if applicable
|
||||||
|
func IsFeatureDisabledWithLoginType(user *User, feature string) bool {
|
||||||
|
// NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType
|
||||||
|
return (user != nil && user.LoginType > auth.Plain && setting.Admin.ExternalUserDisableFeatures.Contains(feature)) ||
|
||||||
|
setting.Admin.UserDisabledFeatures.Contains(feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisabledFeaturesWithLoginType returns the set of user features disabled, taking into account the login type
|
||||||
|
// of the user if applicable
|
||||||
|
func DisabledFeaturesWithLoginType(user *User) *container.Set[string] {
|
||||||
|
// NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType
|
||||||
|
if user != nil && user.LoginType > auth.Plain {
|
||||||
|
return &setting.Admin.ExternalUserDisableFeatures
|
||||||
|
}
|
||||||
|
return &setting.Admin.UserDisabledFeatures
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
"code.gitea.io/gitea/modules/auth/password/hash"
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -542,3 +543,37 @@ func Test_NormalizeUserFromEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisabledUserFeatures(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
testValues := container.SetOf(setting.UserFeatureDeletion,
|
||||||
|
setting.UserFeatureManageSSHKeys,
|
||||||
|
setting.UserFeatureManageGPGKeys)
|
||||||
|
|
||||||
|
oldSetting := setting.Admin.ExternalUserDisableFeatures
|
||||||
|
defer func() {
|
||||||
|
setting.Admin.ExternalUserDisableFeatures = oldSetting
|
||||||
|
}()
|
||||||
|
setting.Admin.ExternalUserDisableFeatures = testValues
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
assert.Len(t, setting.Admin.UserDisabledFeatures.Values(), 0)
|
||||||
|
|
||||||
|
// no features should be disabled with a plain login type
|
||||||
|
assert.LessOrEqual(t, user.LoginType, auth.Plain)
|
||||||
|
assert.Len(t, user_model.DisabledFeaturesWithLoginType(user).Values(), 0)
|
||||||
|
for _, f := range testValues.Values() {
|
||||||
|
assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check disabled features with external login type
|
||||||
|
user.LoginType = auth.OAuth2
|
||||||
|
|
||||||
|
// all features should be disabled
|
||||||
|
assert.NotEmpty(t, user_model.DisabledFeaturesWithLoginType(user).Values())
|
||||||
|
for _, f := range testValues.Values() {
|
||||||
|
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -47,6 +47,12 @@ func convertPGPSignature(c *object.Commit) *ObjectSignature {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Encoding != "" && c.Encoding != "UTF-8" {
|
||||||
|
if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
|
if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,8 @@ readLoop:
|
||||||
commit.Committer = &Signature{}
|
commit.Committer = &Signature{}
|
||||||
commit.Committer.Decode(data)
|
commit.Committer.Decode(data)
|
||||||
_, _ = payloadSB.Write(line)
|
_, _ = payloadSB.Write(line)
|
||||||
|
case "encoding":
|
||||||
|
_, _ = payloadSB.Write(line)
|
||||||
case "gpgsig":
|
case "gpgsig":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
|
case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
|
||||||
|
|
|
@ -125,6 +125,73 @@ empty commit`, commitFromReader.Signature.Payload)
|
||||||
assert.EqualValues(t, commitFromReader, commitFromReader2)
|
assert.EqualValues(t, commitFromReader, commitFromReader2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommitWithEncodingFromReader(t *testing.T) {
|
||||||
|
commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
|
||||||
|
tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
|
||||||
|
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
|
||||||
|
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||||
|
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||||
|
encoding ISO-8859-1
|
||||||
|
gpgsig -----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
||||||
|
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
||||||
|
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
||||||
|
zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr
|
||||||
|
frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j
|
||||||
|
FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd
|
||||||
|
G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn
|
||||||
|
SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
|
||||||
|
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
|
||||||
|
jw4YcO5u
|
||||||
|
=r3UU
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
|
|
||||||
|
ISO-8859-1`
|
||||||
|
|
||||||
|
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||||
|
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, gitRepo)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if !assert.NotNil(t, commitFromReader) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.EqualValues(t, sha, commitFromReader.ID)
|
||||||
|
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
||||||
|
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
||||||
|
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
||||||
|
zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr
|
||||||
|
frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j
|
||||||
|
FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd
|
||||||
|
G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn
|
||||||
|
SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
|
||||||
|
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
|
||||||
|
jw4YcO5u
|
||||||
|
=r3UU
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
|
`, commitFromReader.Signature.Signature)
|
||||||
|
assert.EqualValues(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
|
||||||
|
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
|
||||||
|
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||||
|
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||||
|
encoding ISO-8859-1
|
||||||
|
|
||||||
|
ISO-8859-1`, commitFromReader.Signature.Payload)
|
||||||
|
assert.EqualValues(t, "KN4CK3R <admin@oldschoolhack.me>", commitFromReader.Author.String())
|
||||||
|
|
||||||
|
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
commitFromReader.CommitMessage += "\n\n"
|
||||||
|
commitFromReader.Signature.Payload += "\n\n"
|
||||||
|
assert.EqualValues(t, commitFromReader, commitFromReader2)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHasPreviousCommit(t *testing.T) {
|
func TestHasPreviousCommit(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ func (c *FilesystemClient) Download(ctx context.Context, objects []Pointer, call
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
if err := callback(p, f, nil); err != nil {
|
if err := callback(p, f, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ func (c *FilesystemClient) Upload(ctx context.Context, objects []Pointer, callba
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
_, err = io.Copy(f, content)
|
_, err = io.Copy(f, content)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -505,9 +505,17 @@ func TestMathBlock(t *testing.T) {
|
||||||
`\(a\) \(b\)`,
|
`\(a\) \(b\)`,
|
||||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`$a$.`,
|
||||||
|
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.$a$`,
|
||||||
|
`<p>.$a$</p>` + nl,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`$a a$b b$`,
|
`$a a$b b$`,
|
||||||
`<p><code class="language-math is-loading">a a$b b</code></p>` + nl,
|
`<p>$a a$b b$</p>` + nl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`a a$b b`,
|
`a a$b b`,
|
||||||
|
@ -515,7 +523,15 @@ func TestMathBlock(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`a$b $a a$b b$`,
|
`a$b $a a$b b$`,
|
||||||
`<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl,
|
`<p>a$b $a a$b b$</p>` + nl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a$x$",
|
||||||
|
`<p>a$x$</p>` + nl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$x$a",
|
||||||
|
`<p>$x$a</p>` + nl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$$a$$",
|
"$$a$$",
|
||||||
|
|
|
@ -41,9 +41,12 @@ func (parser *inlineParser) Trigger() []byte {
|
||||||
return parser.start[0:1]
|
return parser.start[0:1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPunctuation(b byte) bool {
|
||||||
|
return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
|
||||||
|
}
|
||||||
|
|
||||||
func isAlphanumeric(b byte) bool {
|
func isAlphanumeric(b byte) bool {
|
||||||
// Github only cares about 0-9A-Za-z
|
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
||||||
return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses the current line and returns a result of parsing.
|
// Parse parses the current line and returns a result of parsing.
|
||||||
|
@ -56,7 +59,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
||||||
}
|
}
|
||||||
|
|
||||||
precedingCharacter := block.PrecendingCharacter()
|
precedingCharacter := block.PrecendingCharacter()
|
||||||
if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {
|
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
|
||||||
// need to exclude things like `a$` from being considered a start
|
// need to exclude things like `a$` from being considered a start
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -75,14 +78,19 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
||||||
ender += pos
|
ender += pos
|
||||||
|
|
||||||
// Now we want to check the character at the end of our parser section
|
// Now we want to check the character at the end of our parser section
|
||||||
// that is ender + len(parser.end)
|
// that is ender + len(parser.end) and check if char before ender is '\'
|
||||||
pos = ender + len(parser.end)
|
pos = ender + len(parser.end)
|
||||||
if len(line) <= pos {
|
if len(line) <= pos {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !isAlphanumeric(line[pos]) {
|
suceedingCharacter := line[pos]
|
||||||
|
if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if line[ender-1] != '\\' {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// move the pointer onwards
|
// move the pointer onwards
|
||||||
ender += len(parser.end)
|
ender += len(parser.end)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
package setting
|
package setting
|
||||||
|
|
||||||
import "code.gitea.io/gitea/modules/container"
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
)
|
||||||
|
|
||||||
// Admin settings
|
// Admin settings
|
||||||
var Admin struct {
|
var Admin struct {
|
||||||
|
@ -11,6 +13,7 @@ var Admin struct {
|
||||||
DefaultEmailNotification string
|
DefaultEmailNotification string
|
||||||
SendNotificationEmailOnNewUser bool
|
SendNotificationEmailOnNewUser bool
|
||||||
UserDisabledFeatures container.Set[string]
|
UserDisabledFeatures container.Set[string]
|
||||||
|
ExternalUserDisableFeatures container.Set[string]
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAdminFrom(rootCfg ConfigProvider) {
|
func loadAdminFrom(rootCfg ConfigProvider) {
|
||||||
|
@ -18,6 +21,7 @@ func loadAdminFrom(rootCfg ConfigProvider) {
|
||||||
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
|
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
|
||||||
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
|
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
|
||||||
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
|
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
|
||||||
|
Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -8,6 +8,7 @@ import "code.gitea.io/gitea/modules/log"
|
||||||
type OtherConfig struct {
|
type OtherConfig struct {
|
||||||
ShowFooterVersion bool
|
ShowFooterVersion bool
|
||||||
ShowFooterTemplateLoadTime bool
|
ShowFooterTemplateLoadTime bool
|
||||||
|
ShowFooterPoweredBy bool
|
||||||
EnableFeed bool
|
EnableFeed bool
|
||||||
EnableSitemap bool
|
EnableSitemap bool
|
||||||
}
|
}
|
||||||
|
@ -15,6 +16,7 @@ type OtherConfig struct {
|
||||||
var Other = OtherConfig{
|
var Other = OtherConfig{
|
||||||
ShowFooterVersion: true,
|
ShowFooterVersion: true,
|
||||||
ShowFooterTemplateLoadTime: true,
|
ShowFooterTemplateLoadTime: true,
|
||||||
|
ShowFooterPoweredBy: true,
|
||||||
EnableSitemap: true,
|
EnableSitemap: true,
|
||||||
EnableFeed: true,
|
EnableFeed: true,
|
||||||
}
|
}
|
||||||
|
|
37
modules/structs/variable.go
Normal file
37
modules/structs/variable.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package structs
|
||||||
|
|
||||||
|
// CreateVariableOption the option when creating variable
|
||||||
|
// swagger:model
|
||||||
|
type CreateVariableOption struct {
|
||||||
|
// Value of the variable to create
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
Value string `json:"value" binding:"Required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVariableOption the option when updating variable
|
||||||
|
// swagger:model
|
||||||
|
type UpdateVariableOption struct {
|
||||||
|
// New name for the variable. If the field is empty, the variable name won't be updated.
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Value of the variable to update
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
Value string `json:"value" binding:"Required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionVariable return value of the query API
|
||||||
|
// swagger:model
|
||||||
|
type ActionVariable struct {
|
||||||
|
// the owner to which the variable belongs
|
||||||
|
OwnerID int64 `json:"owner_id"`
|
||||||
|
// the repository to which the variable belongs
|
||||||
|
RepoID int64 `json:"repo_id"`
|
||||||
|
// the name of the variable
|
||||||
|
Name string `json:"name"`
|
||||||
|
// the value of the variable
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
|
@ -109,6 +109,9 @@ func NewFuncMap() template.FuncMap {
|
||||||
"ShowFooterTemplateLoadTime": func() bool {
|
"ShowFooterTemplateLoadTime": func() bool {
|
||||||
return setting.Other.ShowFooterTemplateLoadTime
|
return setting.Other.ShowFooterTemplateLoadTime
|
||||||
},
|
},
|
||||||
|
"ShowFooterPoweredBy": func() bool {
|
||||||
|
return setting.Other.ShowFooterPoweredBy
|
||||||
|
},
|
||||||
"AllowedReactions": func() []string {
|
"AllowedReactions": func() []string {
|
||||||
return setting.UI.Reactions
|
return setting.UI.Reactions
|
||||||
},
|
},
|
||||||
|
|
|
@ -212,3 +212,12 @@ func ToFloat64(number any) (float64, error) {
|
||||||
func ToPointer[T any](val T) *T {
|
func ToPointer[T any](val T) *T {
|
||||||
return &val
|
return &val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReserveLineBreakForTextarea(input string) string {
|
||||||
|
// Since the content is from a form which is a textarea, the line endings are \r\n.
|
||||||
|
// It's a standard behavior of HTML.
|
||||||
|
// But we want to store them as \n like what GitHub does.
|
||||||
|
// And users are unlikely to really need to keep the \r.
|
||||||
|
// Other than this, we should respect the original content, even leading or trailing spaces.
|
||||||
|
return strings.ReplaceAll(input, "\r\n", "\n")
|
||||||
|
}
|
||||||
|
|
|
@ -235,3 +235,8 @@ func TestToPointer(t *testing.T) {
|
||||||
val123 := 123
|
val123 := 123
|
||||||
assert.False(t, &val123 == ToPointer(val123))
|
assert.False(t, &val123 == ToPointer(val123))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReserveLineBreakForTextarea(t *testing.T) {
|
||||||
|
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata"), "test\ndata")
|
||||||
|
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata\r\n"), "test\ndata\n")
|
||||||
|
}
|
||||||
|
|
11
options/license/AMD-newlib
Normal file
11
options/license/AMD-newlib
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Copyright 1989, 1990 Advanced Micro Devices, Inc.
|
||||||
|
|
||||||
|
This software is the property of Advanced Micro Devices, Inc (AMD) which
|
||||||
|
specifically grants the user the right to modify, use and distribute this
|
||||||
|
software provided this notice is not removed or altered. All other rights
|
||||||
|
are reserved by AMD.
|
||||||
|
|
||||||
|
AMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS
|
||||||
|
SOFTWARE. IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL
|
||||||
|
DAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR
|
||||||
|
USE OF THIS SOFTWARE.
|
12
options/license/OAR
Normal file
12
options/license/OAR
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
COPYRIGHT (c) 1989-2013, 2015.
|
||||||
|
On-Line Applications Research Corporation (OAR).
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose without fee is hereby granted, provided that this entire notice
|
||||||
|
is included in all copies of any software which is or includes a copy
|
||||||
|
or modification of this software.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION
|
||||||
|
OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS
|
||||||
|
SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
12
options/license/xzoom
Normal file
12
options/license/xzoom
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Copyright Itai Nahshon 1995, 1996.
|
||||||
|
This program is distributed with no warranty.
|
||||||
|
|
||||||
|
Source files for this program may be distributed freely.
|
||||||
|
Modifications to this file are okay as long as:
|
||||||
|
a. This copyright notice and comment are preserved and
|
||||||
|
left at the top of the file.
|
||||||
|
b. The man page is fixed to reflect the change.
|
||||||
|
c. The author of this change adds his name and change
|
||||||
|
description to the list of changes below.
|
||||||
|
Executable files may be distributed with sources, or with
|
||||||
|
exact location where the source code can be obtained.
|
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -9,7 +9,6 @@
|
||||||
"@citation-js/plugin-bibtex": "0.7.9",
|
"@citation-js/plugin-bibtex": "0.7.9",
|
||||||
"@citation-js/plugin-csl": "0.7.9",
|
"@citation-js/plugin-csl": "0.7.9",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@claviska/jquery-minicolors": "2.3.6",
|
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/relative-time-element": "4.4.0",
|
"@github/relative-time-element": "4.4.0",
|
||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
|
@ -54,6 +53,7 @@
|
||||||
"toastify-js": "1.12.0",
|
"toastify-js": "1.12.0",
|
||||||
"tributejs": "5.1.3",
|
"tributejs": "5.1.3",
|
||||||
"uint8-to-base64": "0.2.0",
|
"uint8-to-base64": "0.2.0",
|
||||||
|
"vanilla-colorful": "0.7.2",
|
||||||
"vue": "3.4.21",
|
"vue": "3.4.21",
|
||||||
"vue-bar-graph": "2.0.0",
|
"vue-bar-graph": "2.0.0",
|
||||||
"vue-chartjs": "5.3.0",
|
"vue-chartjs": "5.3.0",
|
||||||
|
@ -92,6 +92,7 @@
|
||||||
"stylelint": "16.3.1",
|
"stylelint": "16.3.1",
|
||||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||||
"stylelint-declaration-strict-value": "1.10.4",
|
"stylelint-declaration-strict-value": "1.10.4",
|
||||||
|
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||||
"svgo": "3.2.0",
|
"svgo": "3.2.0",
|
||||||
"updates": "16.0.0",
|
"updates": "16.0.0",
|
||||||
"vite-string-plugin": "1.1.5",
|
"vite-string-plugin": "1.1.5",
|
||||||
|
@ -395,14 +396,6 @@
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@claviska/jquery-minicolors": {
|
|
||||||
"version": "2.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.6.tgz",
|
|
||||||
"integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"jquery": ">= 1.7.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/css-parser-algorithms": {
|
"node_modules/@csstools/css-parser-algorithms": {
|
||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
|
||||||
|
@ -11121,6 +11114,22 @@
|
||||||
"stylelint": ">=7 <=16"
|
"stylelint": ">=7 <=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylelint-value-no-unknown-custom-properties": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylelint-value-no-unknown-custom-properties/-/stylelint-value-no-unknown-custom-properties-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"postcss-value-parser": "^4.2.0",
|
||||||
|
"resolve": "^1.22.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.12.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"stylelint": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/stylelint/node_modules/ansi-regex": {
|
"node_modules/stylelint/node_modules/ansi-regex": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||||
|
@ -12090,6 +12099,11 @@
|
||||||
"builtins": "^1.0.3"
|
"builtins": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vanilla-colorful": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg=="
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.2.8",
|
"version": "5.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
"@citation-js/plugin-bibtex": "0.7.9",
|
"@citation-js/plugin-bibtex": "0.7.9",
|
||||||
"@citation-js/plugin-csl": "0.7.9",
|
"@citation-js/plugin-csl": "0.7.9",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@claviska/jquery-minicolors": "2.3.6",
|
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/relative-time-element": "4.4.0",
|
"@github/relative-time-element": "4.4.0",
|
||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
|
@ -53,6 +52,7 @@
|
||||||
"toastify-js": "1.12.0",
|
"toastify-js": "1.12.0",
|
||||||
"tributejs": "5.1.3",
|
"tributejs": "5.1.3",
|
||||||
"uint8-to-base64": "0.2.0",
|
"uint8-to-base64": "0.2.0",
|
||||||
|
"vanilla-colorful": "0.7.2",
|
||||||
"vue": "3.4.21",
|
"vue": "3.4.21",
|
||||||
"vue-bar-graph": "2.0.0",
|
"vue-bar-graph": "2.0.0",
|
||||||
"vue-chartjs": "5.3.0",
|
"vue-chartjs": "5.3.0",
|
||||||
|
@ -91,6 +91,7 @@
|
||||||
"stylelint": "16.3.1",
|
"stylelint": "16.3.1",
|
||||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||||
"stylelint-declaration-strict-value": "1.10.4",
|
"stylelint-declaration-strict-value": "1.10.4",
|
||||||
|
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||||
"svgo": "3.2.0",
|
"svgo": "3.2.0",
|
||||||
"updates": "16.0.0",
|
"updates": "16.0.0",
|
||||||
"vite-string-plugin": "1.1.5",
|
"vite-string-plugin": "1.1.5",
|
||||||
|
|
|
@ -871,6 +871,15 @@ func Routes() *web.Route {
|
||||||
Delete(user.DeleteSecret)
|
Delete(user.DeleteSecret)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
m.Group("/variables", func() {
|
||||||
|
m.Get("", user.ListVariables)
|
||||||
|
m.Combo("/{variablename}").
|
||||||
|
Get(user.GetVariable).
|
||||||
|
Delete(user.DeleteVariable).
|
||||||
|
Post(bind(api.CreateVariableOption{}), user.CreateVariable).
|
||||||
|
Put(bind(api.UpdateVariableOption{}), user.UpdateVariable)
|
||||||
|
})
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
m.Group("/runners", func() {
|
||||||
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
||||||
})
|
})
|
||||||
|
@ -990,6 +999,15 @@ func Routes() *web.Route {
|
||||||
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
|
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
m.Group("/variables", func() {
|
||||||
|
m.Get("", reqToken(), reqOwner(), repo.ListVariables)
|
||||||
|
m.Combo("/{variablename}").
|
||||||
|
Get(reqToken(), reqOwner(), repo.GetVariable).
|
||||||
|
Delete(reqToken(), reqOwner(), repo.DeleteVariable).
|
||||||
|
Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable).
|
||||||
|
Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable)
|
||||||
|
})
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
m.Group("/runners", func() {
|
||||||
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
|
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
|
||||||
})
|
})
|
||||||
|
@ -1393,6 +1411,15 @@ func Routes() *web.Route {
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
|
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
m.Group("/variables", func() {
|
||||||
|
m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables)
|
||||||
|
m.Combo("/{variablename}").
|
||||||
|
Get(reqToken(), reqOrgOwnership(), org.GetVariable).
|
||||||
|
Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable).
|
||||||
|
Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable).
|
||||||
|
Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable)
|
||||||
|
})
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
m.Group("/runners", func() {
|
||||||
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
|
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
|
||||||
})
|
})
|
||||||
|
|
291
routers/api/v1/org/variables.go
Normal file
291
routers/api/v1/org/variables.go
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package org
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListVariables list org-level variables
|
||||||
|
func ListVariables(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
||||||
|
// ---
|
||||||
|
// summary: Get an org-level variables list
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/VariableList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "FindVariables", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variables := make([]*api.ActionVariable, len(vars))
|
||||||
|
for i, v := range vars {
|
||||||
|
variables[i] = &api.ActionVariable{
|
||||||
|
OwnerID: v.OwnerID,
|
||||||
|
RepoID: v.RepoID,
|
||||||
|
Name: v.Name,
|
||||||
|
Data: v.Data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, variables)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVariable get an org-level variable
|
||||||
|
func GetVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
|
||||||
|
// ---
|
||||||
|
// summary: Get an org-level variable
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ActionVariable"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
|
Name: ctx.Params("variablename"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variable := &api.ActionVariable{
|
||||||
|
OwnerID: v.OwnerID,
|
||||||
|
RepoID: v.RepoID,
|
||||||
|
Name: v.Name,
|
||||||
|
Data: v.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVariable delete an org-level variable
|
||||||
|
func DeleteVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
|
||||||
|
// ---
|
||||||
|
// summary: Delete an org-level variable
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ActionVariable"
|
||||||
|
// "201":
|
||||||
|
// description: response when deleting a variable
|
||||||
|
// "204":
|
||||||
|
// description: response when deleting a variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVariable create an org-level variable
|
||||||
|
func CreateVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
|
||||||
|
// ---
|
||||||
|
// summary: Create an org-level variable
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateVariableOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when creating an org-level variable
|
||||||
|
// "204":
|
||||||
|
// description: response when creating an org-level variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||||
|
|
||||||
|
ownerID := ctx.Org.Organization.ID
|
||||||
|
variableName := ctx.Params("variablename")
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ownerID,
|
||||||
|
Name: variableName,
|
||||||
|
})
|
||||||
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v != nil && v.ID > 0 {
|
||||||
|
ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "CreateVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVariable update an org-level variable
|
||||||
|
func UpdateVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
|
||||||
|
// ---
|
||||||
|
// summary: Update an org-level variable
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/UpdateVariableOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when updating an org-level variable
|
||||||
|
// "204":
|
||||||
|
// description: response when updating an org-level variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.UpdateVariableOption)
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
|
Name: ctx.Params("variablename"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Name == "" {
|
||||||
|
opt.Name = ctx.Params("variablename")
|
||||||
|
}
|
||||||
|
if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
|
@ -7,9 +7,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
@ -127,3 +131,295 @@ func DeleteSecret(ctx *context.APIContext) {
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetVariable get a repo-level variable
|
||||||
|
func GetVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
|
||||||
|
// ---
|
||||||
|
// summary: Get a repo-level variable
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ActionVariable"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
Name: ctx.Params("variablename"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variable := &api.ActionVariable{
|
||||||
|
OwnerID: v.OwnerID,
|
||||||
|
RepoID: v.RepoID,
|
||||||
|
Name: v.Name,
|
||||||
|
Data: v.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVariable delete a repo-level variable
|
||||||
|
func DeleteVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
|
||||||
|
// ---
|
||||||
|
// summary: Delete a repo-level variable
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ActionVariable"
|
||||||
|
// "201":
|
||||||
|
// description: response when deleting a variable
|
||||||
|
// "204":
|
||||||
|
// description: response when deleting a variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.Params("variablename")); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVariable create a repo-level variable
|
||||||
|
func CreateVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
|
||||||
|
// ---
|
||||||
|
// summary: Create a repo-level variable
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateVariableOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when creating a repo-level variable
|
||||||
|
// "204":
|
||||||
|
// description: response when creating a repo-level variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||||
|
|
||||||
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
variableName := ctx.Params("variablename")
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
RepoID: repoID,
|
||||||
|
Name: variableName,
|
||||||
|
})
|
||||||
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v != nil && v.ID > 0 {
|
||||||
|
ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "CreateVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVariable update a repo-level variable
|
||||||
|
func UpdateVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
|
||||||
|
// ---
|
||||||
|
// summary: Update a repo-level variable
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/UpdateVariableOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when updating a repo-level variable
|
||||||
|
// "204":
|
||||||
|
// description: response when updating a repo-level variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.UpdateVariableOption)
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
Name: ctx.Params("variablename"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Name == "" {
|
||||||
|
opt.Name = ctx.Params("variablename")
|
||||||
|
}
|
||||||
|
if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVariables list repo-level variables
|
||||||
|
func ListVariables(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
|
||||||
|
// ---
|
||||||
|
// summary: Get repo-level variables list
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/VariableList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "FindVariables", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variables := make([]*api.ActionVariable, len(vars))
|
||||||
|
for i, v := range vars {
|
||||||
|
variables[i] = &api.ActionVariable{
|
||||||
|
OwnerID: v.OwnerID,
|
||||||
|
RepoID: v.RepoID,
|
||||||
|
Name: v.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, variables)
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
@ -30,6 +31,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
"code.gitea.io/gitea/services/issue"
|
"code.gitea.io/gitea/services/issue"
|
||||||
|
@ -1027,6 +1029,9 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
|
||||||
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
|
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||||
|
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||||
|
}
|
||||||
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||||
} else {
|
} else {
|
||||||
if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
|
if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
|
||||||
|
@ -1034,6 +1039,11 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
|
||||||
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
|
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) {
|
||||||
|
if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil {
|
||||||
|
log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,3 +18,17 @@ type swaggerResponseSecret struct {
|
||||||
// in:body
|
// in:body
|
||||||
Body api.Secret `json:"body"`
|
Body api.Secret `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionVariable
|
||||||
|
// swagger:response ActionVariable
|
||||||
|
type swaggerResponseActionVariable struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionVariable `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VariableList
|
||||||
|
// swagger:response VariableList
|
||||||
|
type swaggerResponseVariableList struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.ActionVariable `json:"body"`
|
||||||
|
}
|
||||||
|
|
|
@ -199,4 +199,10 @@ type swaggerParameterBodies struct {
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption
|
CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
CreateVariableOption api.CreateVariableOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
UpdateVariableOption api.UpdateVariableOption
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
@ -101,3 +105,249 @@ func DeleteSecret(ctx *context.APIContext) {
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateVariable create a user-level variable
|
||||||
|
func CreateVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /user/actions/variables/{variablename} user createUserVariable
|
||||||
|
// ---
|
||||||
|
// summary: Create a user-level variable
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateVariableOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when creating a variable
|
||||||
|
// "204":
|
||||||
|
// description: response when creating a variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||||
|
|
||||||
|
ownerID := ctx.Doer.ID
|
||||||
|
variableName := ctx.Params("variablename")
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ownerID,
|
||||||
|
Name: variableName,
|
||||||
|
})
|
||||||
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v != nil && v.ID > 0 {
|
||||||
|
ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "CreateVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVariable update a user-level variable which is created by current doer
|
||||||
|
func UpdateVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /user/actions/variables/{variablename} user updateUserVariable
|
||||||
|
// ---
|
||||||
|
// summary: Update a user-level variable which is created by current doer
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/UpdateVariableOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when updating a variable
|
||||||
|
// "204":
|
||||||
|
// description: response when updating a variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.UpdateVariableOption)
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ctx.Doer.ID,
|
||||||
|
Name: ctx.Params("variablename"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Name == "" {
|
||||||
|
opt.Name = ctx.Params("variablename")
|
||||||
|
}
|
||||||
|
if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVariable delete a user-level variable which is created by current doer
|
||||||
|
func DeleteVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /user/actions/variables/{variablename} user deleteUserVariable
|
||||||
|
// ---
|
||||||
|
// summary: Delete a user-level variable which is created by current doer
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when deleting a variable
|
||||||
|
// "204":
|
||||||
|
// description: response when deleting a variable
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.Params("variablename")); err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVariable get a user-level variable which is created by current doer
|
||||||
|
func GetVariable(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/actions/variables/{variablename} user getUserVariable
|
||||||
|
// ---
|
||||||
|
// summary: Get a user-level variable which is created by current doer
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: variablename
|
||||||
|
// in: path
|
||||||
|
// description: name of the variable
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ActionVariable"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ctx.Doer.ID,
|
||||||
|
Name: ctx.Params("variablename"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetVariable", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetVariable", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variable := &api.ActionVariable{
|
||||||
|
OwnerID: v.OwnerID,
|
||||||
|
RepoID: v.RepoID,
|
||||||
|
Name: v.Name,
|
||||||
|
Data: v.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVariables list user-level variables
|
||||||
|
func ListVariables(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/actions/variables user getUserVariablesList
|
||||||
|
// ---
|
||||||
|
// summary: Get the user-level list of variables which is created by current doer
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/VariableList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ctx.Doer.ID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "FindVariables", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variables := make([]*api.ActionVariable, len(vars))
|
||||||
|
for i, v := range vars {
|
||||||
|
variables[i] = &api.ActionVariable{
|
||||||
|
OwnerID: v.OwnerID,
|
||||||
|
RepoID: v.RepoID,
|
||||||
|
Name: v.Name,
|
||||||
|
Data: v.Data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, variables)
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -133,7 +134,7 @@ func GetGPGKey(ctx *context.APIContext) {
|
||||||
|
|
||||||
// CreateUserGPGKey creates new GPG key to given user by ID.
|
// CreateUserGPGKey creates new GPG key to given user by ID.
|
||||||
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
|
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -274,7 +275,7 @@ func DeleteGPGKey(ctx *context.APIContext) {
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ func GetPublicKey(ctx *context.APIContext) {
|
||||||
|
|
||||||
// CreateUserPublicKey creates new public key to given user by ID.
|
// CreateUserPublicKey creates new public key to given user by ID.
|
||||||
func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
|
func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,7 @@ func DeletePublicKey(ctx *context.APIContext) {
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
@ -29,6 +30,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
@ -929,6 +931,10 @@ func SettingsPost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
|
||||||
|
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
|
ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
|
||||||
|
|
||||||
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||||
|
@ -947,6 +953,12 @@ func SettingsPost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeActions) {
|
||||||
|
if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil {
|
||||||
|
log.Error("DetectAndHandleSchedules for un-archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
|
ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
|
||||||
|
|
||||||
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||||
|
|
|
@ -4,17 +4,13 @@
|
||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
|
func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
|
||||||
|
@ -29,41 +25,16 @@ func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
|
||||||
ctx.Data["Variables"] = variables
|
ctx.Data["Variables"] = variables
|
||||||
}
|
}
|
||||||
|
|
||||||
// some regular expression of `variables` and `secrets`
|
|
||||||
// reference to:
|
|
||||||
// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
|
|
||||||
// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
|
|
||||||
var (
|
|
||||||
forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
|
|
||||||
)
|
|
||||||
|
|
||||||
func envNameCIRegexMatch(name string) error {
|
|
||||||
if forbiddenEnvNameCIRx.MatchString(name) {
|
|
||||||
log.Error("Env Name cannot be ci")
|
|
||||||
return errors.New("env name cannot be ci")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
|
func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
|
||||||
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
||||||
|
|
||||||
if err := secret_service.ValidateName(form.Name); err != nil {
|
v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data)
|
||||||
ctx.JSONError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := envNameCIRegexMatch(form.Name); err != nil {
|
|
||||||
ctx.JSONError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := actions_model.InsertVariable(ctx, ownerID, repoID, form.Name, ReserveLineBreakForTextarea(form.Data))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("InsertVariable error: %v", err)
|
log.Error("CreateVariable: %v", err)
|
||||||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
|
ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
|
||||||
ctx.JSONRedirect(redirectURL)
|
ctx.JSONRedirect(redirectURL)
|
||||||
}
|
}
|
||||||
|
@ -72,23 +43,8 @@ func UpdateVariable(ctx *context.Context, redirectURL string) {
|
||||||
id := ctx.ParamsInt64(":variable_id")
|
id := ctx.ParamsInt64(":variable_id")
|
||||||
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
form := web.GetForm(ctx).(*forms.EditVariableForm)
|
||||||
|
|
||||||
if err := secret_service.ValidateName(form.Name); err != nil {
|
if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok {
|
||||||
ctx.JSONError(err.Error())
|
log.Error("UpdateVariable: %v", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := envNameCIRegexMatch(form.Name); err != nil {
|
|
||||||
ctx.JSONError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err := actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{
|
|
||||||
ID: id,
|
|
||||||
Name: strings.ToUpper(form.Name),
|
|
||||||
Data: ReserveLineBreakForTextarea(form.Data),
|
|
||||||
})
|
|
||||||
if err != nil || !ok {
|
|
||||||
log.Error("UpdateVariable error: %v", err)
|
|
||||||
ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
|
ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -99,7 +55,7 @@ func UpdateVariable(ctx *context.Context, redirectURL string) {
|
||||||
func DeleteVariable(ctx *context.Context, redirectURL string) {
|
func DeleteVariable(ctx *context.Context, redirectURL string) {
|
||||||
id := ctx.ParamsInt64(":variable_id")
|
id := ctx.ParamsInt64(":variable_id")
|
||||||
|
|
||||||
if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil {
|
if err := actions_service.DeleteVariableByID(ctx, id); err != nil {
|
||||||
log.Error("Delete variable [%d] failed: %v", id, err)
|
log.Error("Delete variable [%d] failed: %v", id, err)
|
||||||
ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
|
ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
|
||||||
return
|
return
|
||||||
|
@ -107,12 +63,3 @@ func DeleteVariable(ctx *context.Context, redirectURL string) {
|
||||||
ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
|
ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
|
||||||
ctx.JSONRedirect(redirectURL)
|
ctx.JSONRedirect(redirectURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReserveLineBreakForTextarea(input string) string {
|
|
||||||
// Since the content is from a form which is a textarea, the line endings are \r\n.
|
|
||||||
// It's a standard behavior of HTML.
|
|
||||||
// But we want to store them as \n like what GitHub does.
|
|
||||||
// And users are unlikely to really need to keep the \r.
|
|
||||||
// Other than this, we should respect the original content, even leading or trailing spaces.
|
|
||||||
return strings.ReplaceAll(input, "\r\n", "\n")
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
secret_model "code.gitea.io/gitea/models/secret"
|
secret_model "code.gitea.io/gitea/models/secret"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/web/shared/actions"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
|
@ -27,7 +27,7 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
|
||||||
func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
|
func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
|
||||||
form := web.GetForm(ctx).(*forms.AddSecretForm)
|
form := web.GetForm(ctx).(*forms.AddSecretForm)
|
||||||
|
|
||||||
s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, actions.ReserveLineBreakForTextarea(form.Data))
|
s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("CreateOrUpdateSecret failed: %v", err)
|
log.Error("CreateOrUpdateSecret failed: %v", err)
|
||||||
ctx.JSONError(ctx.Tr("secrets.creation.failed"))
|
ctx.JSONError(ctx.Tr("secrets.creation.failed"))
|
||||||
|
|
|
@ -244,7 +244,7 @@ func DeleteEmail(ctx *context.Context) {
|
||||||
|
|
||||||
// DeleteAccount render user suicide page and response for delete user himself
|
// DeleteAccount render user suicide page and response for delete user himself
|
||||||
func DeleteAccount(ctx *context.Context) {
|
func DeleteAccount(ctx *context.Context) {
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) {
|
||||||
ctx.Error(http.StatusNotFound)
|
ctx.Error(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,7 @@ func loadAccountData(ctx *context.Context) {
|
||||||
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
||||||
ctx.Data["ActivationsPending"] = pendingActivation
|
ctx.Data["ActivationsPending"] = pendingActivation
|
||||||
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
|
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
|
||||||
ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
|
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||||
|
|
||||||
if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
|
if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
|
||||||
ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
|
ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -78,7 +79,7 @@ func KeysPost(ctx *context.Context) {
|
||||||
ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))
|
ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
||||||
case "gpg":
|
case "gpg":
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,7 @@ func KeysPost(ctx *context.Context) {
|
||||||
ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID))
|
ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID))
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
||||||
case "ssh":
|
case "ssh":
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -203,7 +204,7 @@ func KeysPost(ctx *context.Context) {
|
||||||
ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
|
ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
||||||
case "verify_ssh":
|
case "verify_ssh":
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -240,7 +241,7 @@ func KeysPost(ctx *context.Context) {
|
||||||
func DeleteKey(ctx *context.Context) {
|
func DeleteKey(ctx *context.Context) {
|
||||||
switch ctx.FormString("type") {
|
switch ctx.FormString("type") {
|
||||||
case "gpg":
|
case "gpg":
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -250,7 +251,7 @@ func DeleteKey(ctx *context.Context) {
|
||||||
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
|
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
|
||||||
}
|
}
|
||||||
case "ssh":
|
case "ssh":
|
||||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
|
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||||
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -333,5 +334,5 @@ func loadKeysData(ctx *context.Context) {
|
||||||
|
|
||||||
ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
|
ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
|
||||||
ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
|
ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
|
||||||
ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
|
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||||
log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
|
log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if input.Repo.IsEmpty {
|
if input.Repo.IsEmpty || input.Repo.IsArchived {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if unit_model.TypeActions.UnitGlobalDisabled() {
|
if unit_model.TypeActions.UnitGlobalDisabled() {
|
||||||
|
@ -536,7 +536,7 @@ func handleSchedules(
|
||||||
|
|
||||||
// DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
|
// DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
|
||||||
func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
|
func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
|
||||||
if repo.IsEmpty {
|
if repo.IsEmpty || repo.IsArchived {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,11 @@ func startTasks(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if row.Repo.IsArchived {
|
||||||
|
// Skip if the repo is archived
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
|
cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if repo_model.IsErrUnitTypeNotExist(err) {
|
if repo_model.IsErrUnitTypeNotExist(err) {
|
||||||
|
|
100
services/actions/variables.go
Normal file
100
services/actions/variables.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*actions_model.ActionVariable, error) {
|
||||||
|
if err := secret_service.ValidateName(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := envNameCIRegexMatch(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateVariable(ctx context.Context, variableID int64, name, data string) (bool, error) {
|
||||||
|
if err := secret_service.ValidateName(name); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := envNameCIRegexMatch(name); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{
|
||||||
|
ID: variableID,
|
||||||
|
Name: strings.ToUpper(name),
|
||||||
|
Data: util.ReserveLineBreakForTextarea(data),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteVariableByID(ctx context.Context, variableID int64) error {
|
||||||
|
return actions_model.DeleteVariable(ctx, variableID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteVariableByName(ctx context.Context, ownerID, repoID int64, name string) error {
|
||||||
|
if err := secret_service.ValidateName(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := envNameCIRegexMatch(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||||
|
OwnerID: ownerID,
|
||||||
|
RepoID: repoID,
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions_model.DeleteVariable(ctx, v.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVariable(ctx context.Context, opts actions_model.FindVariablesOpts) (*actions_model.ActionVariable, error) {
|
||||||
|
vars, err := actions_model.FindVariables(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(vars) != 1 {
|
||||||
|
return nil, util.NewNotExistErrorf("variable not found")
|
||||||
|
}
|
||||||
|
return vars[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// some regular expression of `variables` and `secrets`
|
||||||
|
// reference to:
|
||||||
|
// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
|
||||||
|
// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
|
||||||
|
var (
|
||||||
|
forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
|
||||||
|
)
|
||||||
|
|
||||||
|
func envNameCIRegexMatch(name string) error {
|
||||||
|
if forbiddenEnvNameCIRx.MatchString(name) {
|
||||||
|
log.Error("Env Name cannot be ci")
|
||||||
|
return util.NewInvalidArgumentErrorf("env name cannot be ci")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -967,12 +967,12 @@ func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]Co
|
||||||
for _, commit := range prInfo.Commits {
|
for _, commit := range prInfo.Commits {
|
||||||
var committerOrAuthorName string
|
var committerOrAuthorName string
|
||||||
var commitTime time.Time
|
var commitTime time.Time
|
||||||
if commit.Committer != nil {
|
if commit.Author != nil {
|
||||||
committerOrAuthorName = commit.Committer.Name
|
|
||||||
commitTime = commit.Committer.When
|
|
||||||
} else {
|
|
||||||
committerOrAuthorName = commit.Author.Name
|
committerOrAuthorName = commit.Author.Name
|
||||||
commitTime = commit.Author.When
|
commitTime = commit.Author.When
|
||||||
|
} else {
|
||||||
|
committerOrAuthorName = commit.Committer.Name
|
||||||
|
commitTime = commit.Committer.When
|
||||||
}
|
}
|
||||||
|
|
||||||
commits = append(commits, CommitInfo{
|
commits = append(commits, CommitInfo{
|
||||||
|
|
246
stylelint.config.js
Normal file
246
stylelint.config.js
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
import {fileURLToPath} from 'node:url';
|
||||||
|
|
||||||
|
const cssVarFiles = [
|
||||||
|
fileURLToPath(new URL('web_src/css/base.css', import.meta.url)),
|
||||||
|
fileURLToPath(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url)),
|
||||||
|
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @type {import('stylelint').Config} */
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
'stylelint-declaration-strict-value',
|
||||||
|
'stylelint-declaration-block-no-ignored-properties',
|
||||||
|
'stylelint-value-no-unknown-custom-properties',
|
||||||
|
'@stylistic/stylelint-plugin',
|
||||||
|
],
|
||||||
|
ignoreFiles: [
|
||||||
|
'**/*.go',
|
||||||
|
'/web_src/fomantic',
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['**/chroma/*', '**/codemirror/*', '**/standalone/*', '**/console.css', 'font_i18n.css'],
|
||||||
|
rules: {
|
||||||
|
'scale-unlimited/declaration-strict-value': null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/chroma/*', '**/codemirror/*'],
|
||||||
|
rules: {
|
||||||
|
'block-no-empty': null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.vue'],
|
||||||
|
customSyntax: 'postcss-html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'@stylistic/at-rule-name-case': null,
|
||||||
|
'@stylistic/at-rule-name-newline-after': null,
|
||||||
|
'@stylistic/at-rule-name-space-after': null,
|
||||||
|
'@stylistic/at-rule-semicolon-newline-after': null,
|
||||||
|
'@stylistic/at-rule-semicolon-space-before': null,
|
||||||
|
'@stylistic/block-closing-brace-empty-line-before': null,
|
||||||
|
'@stylistic/block-closing-brace-newline-after': null,
|
||||||
|
'@stylistic/block-closing-brace-newline-before': null,
|
||||||
|
'@stylistic/block-closing-brace-space-after': null,
|
||||||
|
'@stylistic/block-closing-brace-space-before': null,
|
||||||
|
'@stylistic/block-opening-brace-newline-after': null,
|
||||||
|
'@stylistic/block-opening-brace-newline-before': null,
|
||||||
|
'@stylistic/block-opening-brace-space-after': null,
|
||||||
|
'@stylistic/block-opening-brace-space-before': 'always',
|
||||||
|
'@stylistic/color-hex-case': 'lower',
|
||||||
|
'@stylistic/declaration-bang-space-after': 'never',
|
||||||
|
'@stylistic/declaration-bang-space-before': null,
|
||||||
|
'@stylistic/declaration-block-semicolon-newline-after': null,
|
||||||
|
'@stylistic/declaration-block-semicolon-newline-before': null,
|
||||||
|
'@stylistic/declaration-block-semicolon-space-after': null,
|
||||||
|
'@stylistic/declaration-block-semicolon-space-before': 'never',
|
||||||
|
'@stylistic/declaration-block-trailing-semicolon': null,
|
||||||
|
'@stylistic/declaration-colon-newline-after': null,
|
||||||
|
'@stylistic/declaration-colon-space-after': null,
|
||||||
|
'@stylistic/declaration-colon-space-before': 'never',
|
||||||
|
'@stylistic/function-comma-newline-after': null,
|
||||||
|
'@stylistic/function-comma-newline-before': null,
|
||||||
|
'@stylistic/function-comma-space-after': null,
|
||||||
|
'@stylistic/function-comma-space-before': null,
|
||||||
|
'@stylistic/function-max-empty-lines': 0,
|
||||||
|
'@stylistic/function-parentheses-newline-inside': 'never-multi-line',
|
||||||
|
'@stylistic/function-parentheses-space-inside': null,
|
||||||
|
'@stylistic/function-whitespace-after': null,
|
||||||
|
'@stylistic/indentation': 2,
|
||||||
|
'@stylistic/linebreaks': null,
|
||||||
|
'@stylistic/max-empty-lines': 1,
|
||||||
|
'@stylistic/max-line-length': null,
|
||||||
|
'@stylistic/media-feature-colon-space-after': null,
|
||||||
|
'@stylistic/media-feature-colon-space-before': 'never',
|
||||||
|
'@stylistic/media-feature-name-case': null,
|
||||||
|
'@stylistic/media-feature-parentheses-space-inside': null,
|
||||||
|
'@stylistic/media-feature-range-operator-space-after': 'always',
|
||||||
|
'@stylistic/media-feature-range-operator-space-before': 'always',
|
||||||
|
'@stylistic/media-query-list-comma-newline-after': null,
|
||||||
|
'@stylistic/media-query-list-comma-newline-before': null,
|
||||||
|
'@stylistic/media-query-list-comma-space-after': null,
|
||||||
|
'@stylistic/media-query-list-comma-space-before': null,
|
||||||
|
'@stylistic/named-grid-areas-alignment': null,
|
||||||
|
'@stylistic/no-empty-first-line': null,
|
||||||
|
'@stylistic/no-eol-whitespace': true,
|
||||||
|
'@stylistic/no-extra-semicolons': true,
|
||||||
|
'@stylistic/no-missing-end-of-source-newline': null,
|
||||||
|
'@stylistic/number-leading-zero': null,
|
||||||
|
'@stylistic/number-no-trailing-zeros': null,
|
||||||
|
'@stylistic/property-case': 'lower',
|
||||||
|
'@stylistic/selector-attribute-brackets-space-inside': null,
|
||||||
|
'@stylistic/selector-attribute-operator-space-after': null,
|
||||||
|
'@stylistic/selector-attribute-operator-space-before': null,
|
||||||
|
'@stylistic/selector-combinator-space-after': null,
|
||||||
|
'@stylistic/selector-combinator-space-before': null,
|
||||||
|
'@stylistic/selector-descendant-combinator-no-non-space': null,
|
||||||
|
'@stylistic/selector-list-comma-newline-after': null,
|
||||||
|
'@stylistic/selector-list-comma-newline-before': null,
|
||||||
|
'@stylistic/selector-list-comma-space-after': 'always-single-line',
|
||||||
|
'@stylistic/selector-list-comma-space-before': 'never-single-line',
|
||||||
|
'@stylistic/selector-max-empty-lines': 0,
|
||||||
|
'@stylistic/selector-pseudo-class-case': 'lower',
|
||||||
|
'@stylistic/selector-pseudo-class-parentheses-space-inside': 'never',
|
||||||
|
'@stylistic/selector-pseudo-element-case': 'lower',
|
||||||
|
'@stylistic/string-quotes': 'double',
|
||||||
|
'@stylistic/unicode-bom': null,
|
||||||
|
'@stylistic/unit-case': 'lower',
|
||||||
|
'@stylistic/value-list-comma-newline-after': null,
|
||||||
|
'@stylistic/value-list-comma-newline-before': null,
|
||||||
|
'@stylistic/value-list-comma-space-after': null,
|
||||||
|
'@stylistic/value-list-comma-space-before': null,
|
||||||
|
'@stylistic/value-list-max-empty-lines': 0,
|
||||||
|
'alpha-value-notation': null,
|
||||||
|
'annotation-no-unknown': true,
|
||||||
|
'at-rule-allowed-list': null,
|
||||||
|
'at-rule-disallowed-list': null,
|
||||||
|
'at-rule-empty-line-before': null,
|
||||||
|
'at-rule-no-unknown': [true, {ignoreAtRules: ['tailwind']}],
|
||||||
|
'at-rule-no-vendor-prefix': true,
|
||||||
|
'at-rule-property-required-list': null,
|
||||||
|
'block-no-empty': true,
|
||||||
|
'color-function-notation': null,
|
||||||
|
'color-hex-alpha': null,
|
||||||
|
'color-hex-length': null,
|
||||||
|
'color-named': null,
|
||||||
|
'color-no-hex': null,
|
||||||
|
'color-no-invalid-hex': true,
|
||||||
|
'comment-empty-line-before': null,
|
||||||
|
'comment-no-empty': true,
|
||||||
|
'comment-pattern': null,
|
||||||
|
'comment-whitespace-inside': null,
|
||||||
|
'comment-word-disallowed-list': null,
|
||||||
|
'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}],
|
||||||
|
'custom-media-pattern': null,
|
||||||
|
'custom-property-empty-line-before': null,
|
||||||
|
'custom-property-no-missing-var-function': true,
|
||||||
|
'custom-property-pattern': null,
|
||||||
|
'declaration-block-no-duplicate-custom-properties': true,
|
||||||
|
'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}],
|
||||||
|
'declaration-block-no-redundant-longhand-properties': null,
|
||||||
|
'declaration-block-no-shorthand-property-overrides': null,
|
||||||
|
'declaration-block-single-line-max-declarations': null,
|
||||||
|
'declaration-empty-line-before': null,
|
||||||
|
'declaration-no-important': null,
|
||||||
|
'declaration-property-max-values': null,
|
||||||
|
'declaration-property-unit-allowed-list': null,
|
||||||
|
'declaration-property-unit-disallowed-list': {'line-height': ['em']},
|
||||||
|
'declaration-property-value-allowed-list': null,
|
||||||
|
'declaration-property-value-disallowed-list': null,
|
||||||
|
'declaration-property-value-no-unknown': true,
|
||||||
|
'font-family-name-quotes': 'always-where-recommended',
|
||||||
|
'font-family-no-duplicate-names': true,
|
||||||
|
'font-family-no-missing-generic-family-keyword': true,
|
||||||
|
'font-weight-notation': null,
|
||||||
|
'function-allowed-list': null,
|
||||||
|
'function-calc-no-unspaced-operator': true,
|
||||||
|
'function-disallowed-list': null,
|
||||||
|
'function-linear-gradient-no-nonstandard-direction': true,
|
||||||
|
'function-name-case': 'lower',
|
||||||
|
'function-no-unknown': true,
|
||||||
|
'function-url-no-scheme-relative': null,
|
||||||
|
'function-url-quotes': 'always',
|
||||||
|
'function-url-scheme-allowed-list': null,
|
||||||
|
'function-url-scheme-disallowed-list': null,
|
||||||
|
'hue-degree-notation': null,
|
||||||
|
'import-notation': 'string',
|
||||||
|
'keyframe-block-no-duplicate-selectors': true,
|
||||||
|
'keyframe-declaration-no-important': true,
|
||||||
|
'keyframe-selector-notation': null,
|
||||||
|
'keyframes-name-pattern': null,
|
||||||
|
'length-zero-no-unit': [true, {ignore: ['custom-properties']}, {ignoreFunctions: ['var']}],
|
||||||
|
'max-nesting-depth': null,
|
||||||
|
'media-feature-name-allowed-list': null,
|
||||||
|
'media-feature-name-disallowed-list': null,
|
||||||
|
'media-feature-name-no-unknown': true,
|
||||||
|
'media-feature-name-no-vendor-prefix': true,
|
||||||
|
'media-feature-name-unit-allowed-list': null,
|
||||||
|
'media-feature-name-value-allowed-list': null,
|
||||||
|
'media-feature-name-value-no-unknown': true,
|
||||||
|
'media-feature-range-notation': null,
|
||||||
|
'media-query-no-invalid': true,
|
||||||
|
'named-grid-areas-no-invalid': true,
|
||||||
|
'no-descending-specificity': null,
|
||||||
|
'no-duplicate-at-import-rules': true,
|
||||||
|
'no-duplicate-selectors': true,
|
||||||
|
'no-empty-source': true,
|
||||||
|
'no-invalid-double-slash-comments': true,
|
||||||
|
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}],
|
||||||
|
'no-irregular-whitespace': true,
|
||||||
|
'no-unknown-animations': null,
|
||||||
|
'no-unknown-custom-properties': null,
|
||||||
|
'number-max-precision': null,
|
||||||
|
'plugin/declaration-block-no-ignored-properties': true,
|
||||||
|
'property-allowed-list': null,
|
||||||
|
'property-disallowed-list': null,
|
||||||
|
'property-no-unknown': true,
|
||||||
|
'property-no-vendor-prefix': null,
|
||||||
|
'rule-empty-line-before': null,
|
||||||
|
'rule-selector-property-disallowed-list': null,
|
||||||
|
'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: false, disableFix: true, expandShorthand: true}],
|
||||||
|
'selector-anb-no-unmatchable': true,
|
||||||
|
'selector-attribute-name-disallowed-list': null,
|
||||||
|
'selector-attribute-operator-allowed-list': null,
|
||||||
|
'selector-attribute-operator-disallowed-list': null,
|
||||||
|
'selector-attribute-quotes': 'always',
|
||||||
|
'selector-class-pattern': null,
|
||||||
|
'selector-combinator-allowed-list': null,
|
||||||
|
'selector-combinator-disallowed-list': null,
|
||||||
|
'selector-disallowed-list': null,
|
||||||
|
'selector-id-pattern': null,
|
||||||
|
'selector-max-attribute': null,
|
||||||
|
'selector-max-class': null,
|
||||||
|
'selector-max-combinators': null,
|
||||||
|
'selector-max-compound-selectors': null,
|
||||||
|
'selector-max-id': null,
|
||||||
|
'selector-max-pseudo-class': null,
|
||||||
|
'selector-max-specificity': null,
|
||||||
|
'selector-max-type': null,
|
||||||
|
'selector-max-universal': null,
|
||||||
|
'selector-nested-pattern': null,
|
||||||
|
'selector-no-qualifying-type': null,
|
||||||
|
'selector-no-vendor-prefix': true,
|
||||||
|
'selector-not-notation': null,
|
||||||
|
'selector-pseudo-class-allowed-list': null,
|
||||||
|
'selector-pseudo-class-disallowed-list': null,
|
||||||
|
'selector-pseudo-class-no-unknown': true,
|
||||||
|
'selector-pseudo-element-allowed-list': null,
|
||||||
|
'selector-pseudo-element-colon-notation': 'double',
|
||||||
|
'selector-pseudo-element-disallowed-list': null,
|
||||||
|
'selector-pseudo-element-no-unknown': true,
|
||||||
|
'selector-type-case': 'lower',
|
||||||
|
'selector-type-no-unknown': [true, {ignore: ['custom-elements']}],
|
||||||
|
'shorthand-property-no-redundant-values': true,
|
||||||
|
'string-no-newline': true,
|
||||||
|
'time-min-milliseconds': null,
|
||||||
|
'unit-allowed-list': null,
|
||||||
|
'unit-disallowed-list': null,
|
||||||
|
'unit-no-unknown': true,
|
||||||
|
'value-keyword-case': null,
|
||||||
|
'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}],
|
||||||
|
},
|
||||||
|
};
|
|
@ -7,14 +7,14 @@
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}">
|
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}">
|
||||||
<input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}>
|
<input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}><label></label>
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">
|
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">
|
||||||
<input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}>
|
<input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label>
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">
|
<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">
|
||||||
<div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}">
|
<div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}">
|
||||||
|
{{if ShowFooterPoweredBy}}
|
||||||
<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a>
|
<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a>
|
||||||
|
{{end}}
|
||||||
{{if (or .ShowFooterVersion .PageIsAdmin)}}
|
{{if (or .ShowFooterVersion .PageIsAdmin)}}
|
||||||
{{ctx.Locale.Tr "version"}}:
|
{{ctx.Locale.Tr "version"}}:
|
||||||
{{if .IsAdmin}}
|
{{if .IsAdmin}}
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>Loading</h1>
|
<h1>Loading</h1>
|
||||||
<div class="is-loading small-loading-icon tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>
|
<div class="is-loading loading-icon-2px tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>
|
||||||
<div class="is-loading tw-border tw-border-secondary tw-py-4">
|
<div class="is-loading tw-border tw-border-secondary tw-py-4">
|
||||||
<p>loading ...</p>
|
<p>loading ...</p>
|
||||||
<p>loading ...</p>
|
<p>loading ...</p>
|
||||||
|
|
|
@ -42,8 +42,8 @@
|
||||||
|
|
||||||
<div class="field color-field">
|
<div class="field color-field">
|
||||||
<label for="new_project_column_color_picker">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
|
<label for="new_project_column_color_picker">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
|
||||||
<div class="color picker column">
|
<div class="js-color-picker-input column">
|
||||||
<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
|
<input maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
|
||||||
{{template "repo/issue/label_precolors"}}
|
{{template "repo/issue/label_precolors"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -114,8 +114,8 @@
|
||||||
|
|
||||||
<div class="field color-field">
|
<div class="field color-field">
|
||||||
<label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
|
<label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
|
||||||
<div class="color picker column">
|
<div class="js-color-picker-input column">
|
||||||
<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">
|
<input maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">
|
||||||
{{template "repo/issue/label_precolors"}}
|
{{template "repo/issue/label_precolors"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -164,9 +164,9 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui horizontal list tw-flex tw-items-center">
|
<div class="tw-flex tw-items-center">
|
||||||
{{if .Parents}}
|
{{if .Parents}}
|
||||||
<div class="item">
|
<div>
|
||||||
<span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>
|
<span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>
|
||||||
{{range .Parents}}
|
{{range .Parents}}
|
||||||
{{if $.PageIsWiki}}
|
{{if $.PageIsWiki}}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<td class="author tw-flex">
|
<td class="author tw-flex">
|
||||||
{{$userName := .Author.Name}}
|
{{$userName := .Author.Name}}
|
||||||
{{if .User}}
|
{{if .User}}
|
||||||
{{if .User.FullName}}
|
{{if and .User.FullName DefaultShowFullName}}
|
||||||
{{$userName = .User.FullName}}
|
{{$userName = .User.FullName}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>
|
{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<span class="author tw-flex tw-items-center tw-mr-2">
|
<span class="author tw-flex tw-items-center tw-mr-2">
|
||||||
{{$userName := $commit.Commit.Author.Name}}
|
{{$userName := $commit.Commit.Author.Name}}
|
||||||
{{if $commit.User}}
|
{{if $commit.User}}
|
||||||
{{if $commit.User.FullName}}
|
{{if and $commit.User.FullName DefaultShowFullName}}
|
||||||
{{$userName = $commit.User.FullName}}
|
{{$userName = $commit.User.FullName}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
|
<span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
|
||||||
|
|
|
@ -18,23 +18,22 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-1" id="repo-topics">
|
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" id="repo-topics">
|
||||||
{{range .Topics}}<a class="ui repo-topic large label topic tw-m-0" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
|
{{/* it should match the code in issue-home.js */}}
|
||||||
|
{{range .Topics}}<a class="repo-topic ui large label" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
|
||||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
|
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
|
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
|
||||||
<div class="ui form tw-hidden tw-flex tw-flex-col tw-mt-4" id="topic_edit">
|
<div class="ui form tw-hidden tw-flex tw-gap-2 tw-my-2" id="topic_edit">
|
||||||
<div class="field tw-flex-1 tw-mb-1">
|
<div class="ui fluid multiple search selection dropdown tw-flex-wrap tw-flex-1">
|
||||||
<div class="ui fluid multiple search selection dropdown tw-flex-wrap" data-text-count-prompt="{{ctx.Locale.Tr "repo.topic.count_prompt"}}" data-text-format-prompt="{{ctx.Locale.Tr "repo.topic.format_prompt"}}">
|
|
||||||
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
|
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
|
||||||
{{range .Topics}}
|
{{range .Topics}}
|
||||||
{{/* keey the same layout as Fomantic UI generated labels */}}
|
{{/* keep the same layout as Fomantic UI generated labels */}}
|
||||||
<a class="ui label transition visible tw-cursor-default tw-inline-block" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a>
|
<a class="ui label transition visible tw-cursor-default tw-inline-block" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="text"></div>
|
<div class="text"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
|
<button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
|
||||||
<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{ctx.Locale.Tr "save"}}</button>
|
<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{ctx.Locale.Tr "save"}}</button>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
<div class="navbar">
|
<div class="issue-navbar">
|
||||||
{{template "repo/issue/navbar" .}}
|
{{template "repo/issue/navbar" .}}
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div role="main" aria-label="{{.Title}}" class="page-content repository labels">
|
<div role="main" aria-label="{{.Title}}" class="page-content repository labels">
|
||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="navbar tw-mb-4">
|
<div class="issue-navbar tw-mb-4">
|
||||||
{{template "repo/issue/navbar" .}}
|
{{template "repo/issue/navbar" .}}
|
||||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
||||||
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
||||||
|
|
|
@ -52,8 +52,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field color-field">
|
<div class="field color-field">
|
||||||
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
||||||
<div class="color picker column">
|
<div class="column js-color-picker-input">
|
||||||
<input class="color-picker" name="color" value="#70c24a" required maxlength="7">
|
<input name="color" value="#70c24a"placeholder="#c320f6" required maxlength="7">
|
||||||
{{template "repo/issue/label_precolors"}}
|
{{template "repo/issue/label_precolors"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
|
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
|
||||||
</span>
|
</span>
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="left menu">
|
<div class="menu">
|
||||||
<a class="{{if or (eq .SortType "alphabetically") (not .SortType)}}active {{end}}item" href="?sort=alphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
|
<a class="{{if or (eq .SortType "alphabetically") (not .SortType)}}active {{end}}item" href="?sort=alphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
|
||||||
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="?sort=reversealphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
|
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="?sort=reversealphabetically&state={{$.State}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
|
||||||
<a class="{{if eq .SortType "leastissues"}}active {{end}}item" href="?sort=leastissues&state={{$.State}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_issues"}}</a>
|
<a class="{{if eq .SortType "leastissues"}}active {{end}}item" href="?sort=leastissues&state={{$.State}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_issues"}}</a>
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field color-field">
|
<div class="field color-field">
|
||||||
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
||||||
<div class="color picker column">
|
<div class="js-color-picker-input column">
|
||||||
<input class="color-picker" name="color" value="#70c24a" required maxlength="7">
|
<input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7">
|
||||||
{{template "repo/issue/label_precolors"}}
|
{{template "repo/issue/label_precolors"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div role="main" aria-label="{{.Title}}" class="page-content repository new milestone">
|
<div role="main" aria-label="{{.Title}}" class="page-content repository new milestone">
|
||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="navbar">
|
<div class="issue-navbar">
|
||||||
{{template "repo/issue/navbar" .}}
|
{{template "repo/issue/navbar" .}}
|
||||||
{{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}
|
{{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}
|
||||||
<div class="ui right floated secondary menu">
|
<div class="ui right floated secondary menu">
|
||||||
|
|
|
@ -684,7 +684,7 @@
|
||||||
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
|
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<div class="ui checkbox" id="allow-edits-from-maintainers"
|
<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
|
||||||
data-url="{{.Issue.Link}}"
|
data-url="{{.Issue.Link}}"
|
||||||
data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
|
data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
|
||||||
data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
|
data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{if .LatestCommitUser}}
|
{{if .LatestCommitUser}}
|
||||||
{{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}
|
{{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}
|
||||||
{{if .LatestCommitUser.FullName}}
|
{{if and .LatestCommitUser.FullName DefaultShowFullName}}
|
||||||
<a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
|
<a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a class="muted author-wrapper" title="{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
|
<a class="muted author-wrapper" title="{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
|
||||||
|
|
750
templates/swagger/v1_json.tmpl
generated
750
templates/swagger/v1_json.tmpl
generated
|
@ -1741,6 +1741,232 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/orgs/{org}/actions/variables": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Get an org-level variables list",
|
||||||
|
"operationId": "getOrgVariablesList",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/VariableList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/orgs/{org}/actions/variables/{variablename}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Get an org-level variable",
|
||||||
|
"operationId": "getOrgVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ActionVariable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Update an org-level variable",
|
||||||
|
"operationId": "updateOrgVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateVariableOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "response when updating an org-level variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when updating an org-level variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Create an org-level variable",
|
||||||
|
"operationId": "createOrgVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/CreateVariableOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "response when creating an org-level variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when creating an org-level variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Delete an org-level variable",
|
||||||
|
"operationId": "deleteOrgVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ActionVariable"
|
||||||
|
},
|
||||||
|
"201": {
|
||||||
|
"description": "response when deleting a variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when deleting a variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/orgs/{org}/activities/feeds": {
|
"/orgs/{org}/activities/feeds": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -3591,6 +3817,261 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/variables": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get repo-level variables list",
|
||||||
|
"operationId": "getRepoVariablesList",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/VariableList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/variables/{variablename}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get a repo-level variable",
|
||||||
|
"operationId": "getRepoVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ActionVariable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Update a repo-level variable",
|
||||||
|
"operationId": "updateRepoVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateVariableOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "response when updating a repo-level variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when updating a repo-level variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Create a repo-level variable",
|
||||||
|
"operationId": "createRepoVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/CreateVariableOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "response when creating a repo-level variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when creating a repo-level variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Delete a repo-level variable",
|
||||||
|
"operationId": "deleteRepoVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ActionVariable"
|
||||||
|
},
|
||||||
|
"201": {
|
||||||
|
"description": "response when deleting a variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when deleting a variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/activities/feeds": {
|
"/repos/{owner}/{repo}/activities/feeds": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -15375,6 +15856,194 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/user/actions/variables": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get the user-level list of variables which is created by current doer",
|
||||||
|
"operationId": "getUserVariablesList",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/VariableList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/actions/variables/{variablename}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get a user-level variable which is created by current doer",
|
||||||
|
"operationId": "getUserVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ActionVariable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Update a user-level variable which is created by current doer",
|
||||||
|
"operationId": "updateUserVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateVariableOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "response when updating a variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when updating a variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Create a user-level variable",
|
||||||
|
"operationId": "createUserVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/CreateVariableOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "response when creating a variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when creating a variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Delete a user-level variable which is created by current doer",
|
||||||
|
"operationId": "deleteUserVariable",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the variable",
|
||||||
|
"name": "variablename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "response when deleting a variable"
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "response when deleting a variable"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/applications/oauth2": {
|
"/user/applications/oauth2": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -17493,6 +18162,35 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"ActionVariable": {
|
||||||
|
"description": "ActionVariable return value of the query API",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"description": "the value of the variable",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Data"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "the name of the variable",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"description": "the owner to which the variable belongs",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "OwnerID"
|
||||||
|
},
|
||||||
|
"repo_id": {
|
||||||
|
"description": "the repository to which the variable belongs",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "RepoID"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"Activity": {
|
"Activity": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -19393,6 +20091,21 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"CreateVariableOption": {
|
||||||
|
"description": "CreateVariableOption the option when creating variable",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"value": {
|
||||||
|
"description": "Value of the variable to create",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"CreateWikiPageOptions": {
|
"CreateWikiPageOptions": {
|
||||||
"description": "CreateWikiPageOptions form for creating wiki",
|
"description": "CreateWikiPageOptions form for creating wiki",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -23773,6 +24486,26 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"UpdateVariableOption": {
|
||||||
|
"description": "UpdateVariableOption the option when updating variable",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "New name for the variable. If the field is empty, the variable name won't be updated.",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Value of the variable to update",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"User": {
|
"User": {
|
||||||
"description": "User represents a user",
|
"description": "User represents a user",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -24157,6 +24890,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ActionVariable": {
|
||||||
|
"description": "ActionVariable",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionVariable"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ActivityFeedsList": {
|
"ActivityFeedsList": {
|
||||||
"description": "ActivityFeedsList",
|
"description": "ActivityFeedsList",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -25040,6 +25779,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"VariableList": {
|
||||||
|
"description": "VariableList",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/ActionVariable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"WatchInfo": {
|
"WatchInfo": {
|
||||||
"description": "WatchInfo",
|
"description": "WatchInfo",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -25115,7 +25863,7 @@
|
||||||
"parameterBodies": {
|
"parameterBodies": {
|
||||||
"description": "parameterBodies",
|
"description": "parameterBodies",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/CreateOrUpdateSecretOption"
|
"$ref": "#/definitions/UpdateVariableOption"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redirect": {
|
"redirect": {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
{{if or .allowAdopt .allowDelete}}
|
{{if or .allowAdopt .allowDelete}}
|
||||||
{{if .Dirs}}
|
{{if .Dirs}}
|
||||||
<div class="ui middle aligned divided list">
|
<div class="ui list">
|
||||||
{{range $dirI, $dir := .Dirs}}
|
{{range $dirI, $dir := .Dirs}}
|
||||||
{{$repo := index $.ReposMap $dir}}
|
{{$repo := index $.ReposMap $dir}}
|
||||||
<div class="item {{if not $repo}}tw-py-1{{end}}">{{/* if not repo, then there are "adapt" buttons, so the padding shouldn't be that default large*/}}
|
<div class="item {{if not $repo}}tw-py-1{{end}}">{{/* if not repo, then there are "adapt" buttons, so the padding shouldn't be that default large*/}}
|
||||||
|
|
149
tests/integration/api_repo_variables_test.go
Normal file
149
tests/integration/api_repo_variables_test.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIRepoVariables(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
t.Run("CreateRepoVariable", func(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
ExpectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "-",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "_",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TEST_VAR",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test_var",
|
||||||
|
ExpectedStatus: http.StatusConflict,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ci",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "123var",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "var@test",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "github_var",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "gitea_var",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.CreateVariableOption{
|
||||||
|
Value: "value",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, c.ExpectedStatus)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateRepoVariable", func(t *testing.T) {
|
||||||
|
variableName := "test_update_var"
|
||||||
|
url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName)
|
||||||
|
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||||
|
Value: "initial_val",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
UpdateName string
|
||||||
|
ExpectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "not_found_var",
|
||||||
|
ExpectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "1invalid",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "invalid@name",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "ci",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "updated_var_name",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
ExpectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "updated_var_name",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.UpdateVariableOption{
|
||||||
|
Name: c.UpdateName,
|
||||||
|
Value: "updated_val",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, c.ExpectedStatus)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteRepoVariable", func(t *testing.T) {
|
||||||
|
variableName := "test_delete_var"
|
||||||
|
url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||||
|
Value: "initial_val",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
144
tests/integration/api_user_variables_test.go
Normal file
144
tests/integration/api_user_variables_test.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIUserVariables(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, "user1")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
|
||||||
|
t.Run("CreateRepoVariable", func(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
ExpectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "-",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "_",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TEST_VAR",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test_var",
|
||||||
|
ExpectedStatus: http.StatusConflict,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ci",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "123var",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "var@test",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "github_var",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "gitea_var",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.CreateVariableOption{
|
||||||
|
Value: "value",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, c.ExpectedStatus)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateRepoVariable", func(t *testing.T) {
|
||||||
|
variableName := "test_update_var"
|
||||||
|
url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName)
|
||||||
|
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||||
|
Value: "initial_val",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
UpdateName string
|
||||||
|
ExpectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "not_found_var",
|
||||||
|
ExpectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "1invalid",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "invalid@name",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "ci",
|
||||||
|
ExpectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
UpdateName: "updated_var_name",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: variableName,
|
||||||
|
ExpectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "updated_var_name",
|
||||||
|
ExpectedStatus: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.UpdateVariableOption{
|
||||||
|
Name: c.UpdateName,
|
||||||
|
Value: "updated_val",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, c.ExpectedStatus)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteRepoVariable", func(t *testing.T) {
|
||||||
|
variableName := "test_delete_var"
|
||||||
|
url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
|
||||||
|
Value: "initial_val",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
|
@ -24,6 +24,7 @@
|
||||||
--repo-header-issue-min-height: 41px;
|
--repo-header-issue-min-height: 41px;
|
||||||
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
|
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
|
||||||
--tab-size: 4;
|
--tab-size: 4;
|
||||||
|
--checkbox-size: 16px; /* height and width of checkbox and radio inputs */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root * {
|
:root * {
|
||||||
|
@ -44,7 +45,7 @@ html, body {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
line-height: 1.4285rem;
|
line-height: 20px;
|
||||||
font-family: var(--fonts-regular);
|
font-family: var(--fonts-regular);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
background-color: var(--color-body);
|
background-color: var(--color-body);
|
||||||
|
@ -316,61 +317,6 @@ a.label,
|
||||||
background-color: var(--color-label-bg);
|
background-color: var(--color-label-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */
|
|
||||||
.ui.input > input {
|
|
||||||
line-height: var(--line-height-default);
|
|
||||||
text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix Fomantic's line-height causing vertical scrollbars to appear */
|
|
||||||
ul.ui.list li,
|
|
||||||
ol.ui.list li,
|
|
||||||
.ui.list > .item,
|
|
||||||
.ui.list .list > .item {
|
|
||||||
line-height: var(--line-height-default);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.input.focus > input,
|
|
||||||
.ui.input > input:focus {
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.action.input .ui.ui.button {
|
|
||||||
border-color: var(--color-input-border);
|
|
||||||
padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* currently used for search bar dropdowns in repo search and explore code */
|
|
||||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection {
|
|
||||||
min-width: 10em;
|
|
||||||
}
|
|
||||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover {
|
|
||||||
border-color: var(--color-input-border);
|
|
||||||
}
|
|
||||||
.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible {
|
|
||||||
border-bottom-left-radius: 0 !important;
|
|
||||||
border-bottom-right-radius: 0 !important;
|
|
||||||
}
|
|
||||||
.ui.action.input:not([class*="left action"]) > input,
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:hover {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection,
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
|
|
||||||
border-left-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus {
|
|
||||||
border-right-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.menu {
|
.ui.menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@ -514,21 +460,6 @@ ol.ui.list li,
|
||||||
color: var(--color-text-light-2);
|
color: var(--color-text-light-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.list .list > .item .header,
|
|
||||||
.ui.list > .item .header {
|
|
||||||
color: var(--color-text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.list .list > .item > .content,
|
|
||||||
.ui.list > .item > .content {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.list .list > .item .description,
|
|
||||||
.ui.list > .item .description {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* replace item margin on secondary menu items with gap and remove both the
|
/* replace item margin on secondary menu items with gap and remove both the
|
||||||
negative margins on the menu as well as margin on the items */
|
negative margins on the menu as well as margin on the items */
|
||||||
.ui.secondary.menu {
|
.ui.secondary.menu {
|
||||||
|
@ -647,10 +578,6 @@ img.ui.avatar,
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.divided.list > .item {
|
|
||||||
border-color: var(--color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.error.message .header,
|
.ui.error.message .header,
|
||||||
.ui.warning.message .header {
|
.ui.warning.message .header {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -1457,11 +1384,6 @@ table th[data-sortt-desc] .svg {
|
||||||
vertical-align: -0.15em;
|
vertical-align: -0.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for the jquery.minicolors plugin */
|
|
||||||
.minicolors-panel {
|
|
||||||
background: var(--color-secondary-dark-1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.tabular.menu {
|
.ui.tabular.menu {
|
||||||
border-color: var(--color-secondary);
|
border-color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
@ -1625,16 +1547,6 @@ table th[data-sortt-desc] .svg {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.ui.icon.input .icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.icon.input > i.icon {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-items-block > .item,
|
.flex-items-block > .item,
|
||||||
.flex-text-block {
|
.flex-text-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
47
web_src/css/features/colorpicker.css
Normal file
47
web_src/css/features/colorpicker.css
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
.js-color-picker-input {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-color-picker-input input {
|
||||||
|
padding-top: 8px !important;
|
||||||
|
padding-bottom: 8px !important;
|
||||||
|
padding-left: 32px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-color-picker-input .preview-square {
|
||||||
|
position: absolute;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
height: 16px;
|
||||||
|
left: 10px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border-radius: 2px;
|
||||||
|
background: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); /* stylelint-disable-line scale-unlimited/declaration-strict-value */
|
||||||
|
background-position: 0 0, 4px 4px;
|
||||||
|
background-size: 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-color-picker-input .preview-square::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
background-color: currentcolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
hex-color-picker {
|
||||||
|
width: 180px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hex-color-picker::part(hue-pointer),
|
||||||
|
hex-color-picker::part(saturation-pointer) {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hex-color-picker::part(hue) {
|
||||||
|
flex-basis: 16px;
|
||||||
|
}
|
|
@ -102,26 +102,3 @@
|
||||||
.card-ghost * {
|
.card-ghost * {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-field .minicolors.minicolors-theme-default {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-field .minicolors.minicolors-theme-default .minicolors-input {
|
|
||||||
height: 38px;
|
|
||||||
padding-left: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-field .minicolors.minicolors-theme-default .minicolors-swatch {
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-project-column-modal .color.picker.column,
|
|
||||||
.new-project-column-modal .color.picker.column {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-project-column-modal .color.picker.column .minicolors,
|
|
||||||
.new-project-column-modal .color.picker.column .minicolors {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
|
@ -32,10 +32,7 @@ textarea,
|
||||||
.ui.form input[type="text"],
|
.ui.form input[type="text"],
|
||||||
.ui.form input[type="time"],
|
.ui.form input[type="time"],
|
||||||
.ui.form input[type="url"],
|
.ui.form input[type="url"],
|
||||||
.ui.selection.dropdown,
|
.ui.selection.dropdown {
|
||||||
.ui.checkbox label::before,
|
|
||||||
.ui.checkbox input:checked ~ label::before,
|
|
||||||
.ui.checkbox input:not([type="radio"]):indeterminate ~ label::before {
|
|
||||||
background: var(--color-input-background);
|
background: var(--color-input-background);
|
||||||
border-color: var(--color-input-border);
|
border-color: var(--color-input-border);
|
||||||
color: var(--color-input-text);
|
color: var(--color-input-text);
|
||||||
|
@ -63,12 +60,7 @@ textarea:hover,
|
||||||
.ui.form input[type="text"]:hover,
|
.ui.form input[type="text"]:hover,
|
||||||
.ui.form input[type="time"]:hover,
|
.ui.form input[type="time"]:hover,
|
||||||
.ui.form input[type="url"]:hover,
|
.ui.form input[type="url"]:hover,
|
||||||
.ui.selection.dropdown:hover,
|
.ui.selection.dropdown:hover {
|
||||||
.ui.checkbox label:hover::before,
|
|
||||||
.ui.checkbox label:active::before,
|
|
||||||
.ui.radio.checkbox label::after,
|
|
||||||
.ui.radio.checkbox input:focus ~ label::before,
|
|
||||||
.ui.radio.checkbox input:checked ~ label::before {
|
|
||||||
background: var(--color-input-background);
|
background: var(--color-input-background);
|
||||||
border-color: var(--color-input-border-hover);
|
border-color: var(--color-input-border-hover);
|
||||||
color: var(--color-input-text);
|
color: var(--color-input-text);
|
||||||
|
@ -91,11 +83,7 @@ textarea:focus,
|
||||||
.ui.form input[type="text"]:focus,
|
.ui.form input[type="text"]:focus,
|
||||||
.ui.form input[type="time"]:focus,
|
.ui.form input[type="time"]:focus,
|
||||||
.ui.form input[type="url"]:focus,
|
.ui.form input[type="url"]:focus,
|
||||||
.ui.selection.dropdown:focus,
|
.ui.selection.dropdown:focus {
|
||||||
.ui.checkbox input:focus ~ label::before,
|
|
||||||
.ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::before,
|
|
||||||
.ui.checkbox input:checked:focus ~ label::before,
|
|
||||||
.ui.radio.checkbox input:focus:checked ~ label::before {
|
|
||||||
background: var(--color-input-background);
|
background: var(--color-input-background);
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
color: var(--color-input-text);
|
color: var(--color-input-text);
|
||||||
|
@ -106,58 +94,21 @@ textarea:focus,
|
||||||
.ui.form .inline.fields .field > label,
|
.ui.form .inline.fields .field > label,
|
||||||
.ui.form .inline.fields .field > p,
|
.ui.form .inline.fields .field > p,
|
||||||
.ui.form .inline.field > label,
|
.ui.form .inline.field > label,
|
||||||
.ui.form .inline.field > p,
|
.ui.form .inline.field > p {
|
||||||
.ui.checkbox label,
|
|
||||||
.ui.checkbox + label,
|
|
||||||
.ui.checkbox label:hover,
|
|
||||||
.ui.checkbox + label:hover,
|
|
||||||
.ui.checkbox input:focus ~ label,
|
|
||||||
.ui.checkbox input:active ~ label {
|
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.form .required.fields:not(.grouped) > .field > label::after,
|
.ui.form .required.fields:not(.grouped) > .field > label::after,
|
||||||
.ui.form .required.fields.grouped > label::after,
|
.ui.form .required.fields.grouped > label::after,
|
||||||
.ui.form .required.field > label::after,
|
.ui.form .required.field > label::after,
|
||||||
.ui.form .required.fields:not(.grouped) > .field > .checkbox::after,
|
|
||||||
.ui.form .required.field > .checkbox::after,
|
|
||||||
.ui.form label.required::after {
|
.ui.form label.required::after {
|
||||||
color: var(--color-red);
|
color: var(--color-red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.input,
|
.ui.input {
|
||||||
.ui.checkbox input:focus ~ label::after,
|
|
||||||
.ui.checkbox input:checked ~ label::after,
|
|
||||||
.ui.checkbox label:active::after,
|
|
||||||
.ui.checkbox input:not([type="radio"]):indeterminate ~ label::after,
|
|
||||||
.ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::after,
|
|
||||||
.ui.checkbox input:checked:focus ~ label::after,
|
|
||||||
.ui.disabled.checkbox label,
|
|
||||||
.ui.checkbox input[disabled] ~ label {
|
|
||||||
color: var(--color-input-text);
|
color: var(--color-input-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.radio.checkbox input:focus ~ label::after,
|
|
||||||
.ui.radio.checkbox input:checked ~ label::after,
|
|
||||||
.ui.radio.checkbox input:focus:checked ~ label::after {
|
|
||||||
background: var(--color-input-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.toggle.checkbox label::before {
|
|
||||||
background: var(--color-input-toggle-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.toggle.checkbox label,
|
|
||||||
.ui.toggle.checkbox input:checked ~ label,
|
|
||||||
.ui.toggle.checkbox input:focus:checked ~ label {
|
|
||||||
color: var(--color-text) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.toggle.checkbox input:checked ~ label::before,
|
|
||||||
.ui.toggle.checkbox input:focus:checked ~ label::before {
|
|
||||||
background: var(--color-primary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* match <select> padding to <input> */
|
/* match <select> padding to <input> */
|
||||||
.ui.form select {
|
.ui.form select {
|
||||||
padding: 0.67857143em 1em;
|
padding: 0.67857143em 1em;
|
||||||
|
|
|
@ -63,3 +63,20 @@ only use:
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-size-1 { tab-size: 1 !important; }
|
||||||
|
.tab-size-2 { tab-size: 2 !important; }
|
||||||
|
.tab-size-3 { tab-size: 3 !important; }
|
||||||
|
.tab-size-4 { tab-size: 4 !important; }
|
||||||
|
.tab-size-5 { tab-size: 5 !important; }
|
||||||
|
.tab-size-6 { tab-size: 6 !important; }
|
||||||
|
.tab-size-7 { tab-size: 7 !important; }
|
||||||
|
.tab-size-8 { tab-size: 8 !important; }
|
||||||
|
.tab-size-9 { tab-size: 9 !important; }
|
||||||
|
.tab-size-10 { tab-size: 10 !important; }
|
||||||
|
.tab-size-11 { tab-size: 11 !important; }
|
||||||
|
.tab-size-12 { tab-size: 12 !important; }
|
||||||
|
.tab-size-13 { tab-size: 13 !important; }
|
||||||
|
.tab-size-14 { tab-size: 14 !important; }
|
||||||
|
.tab-size-15 { tab-size: 15 !important; }
|
||||||
|
.tab-size-16 { tab-size: 16 !important; }
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
@import "./modules/container.css";
|
@import "./modules/container.css";
|
||||||
@import "./modules/divider.css";
|
@import "./modules/divider.css";
|
||||||
@import "./modules/header.css";
|
@import "./modules/header.css";
|
||||||
|
@import "./modules/input.css";
|
||||||
@import "./modules/label.css";
|
@import "./modules/label.css";
|
||||||
|
@import "./modules/list.css";
|
||||||
@import "./modules/segment.css";
|
@import "./modules/segment.css";
|
||||||
@import "./modules/grid.css";
|
@import "./modules/grid.css";
|
||||||
@import "./modules/message.css";
|
@import "./modules/message.css";
|
||||||
@import "./modules/table.css";
|
@import "./modules/table.css";
|
||||||
@import "./modules/card.css";
|
@import "./modules/card.css";
|
||||||
|
@import "./modules/checkbox.css";
|
||||||
@import "./modules/modal.css";
|
@import "./modules/modal.css";
|
||||||
|
|
||||||
@import "./modules/select.css";
|
@import "./modules/select.css";
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
.is-loading {
|
.is-loading {
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-loading > * {
|
.is-loading > * {
|
||||||
|
@ -35,10 +34,14 @@
|
||||||
border-radius: var(--border-radius-circle);
|
border-radius: var(--border-radius-circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-loading.small-loading-icon::after {
|
.is-loading.loading-icon-2px::after {
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-loading.loading-icon-3px::after {
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/* for single form button, the loading state should be on the button, but not go semi-transparent, just replace the text on the button with the loader. */
|
/* for single form button, the loading state should be on the button, but not go semi-transparent, just replace the text on the button with the loader. */
|
||||||
form.single-button-form.is-loading > * {
|
form.single-button-form.is-loading > * {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
@ -63,7 +66,7 @@ form.single-button-form.is-loading .button {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: not needed, use "is-loading small-loading-icon" instead */
|
/* TODO: not needed, use "is-loading loading-icon-2px" instead */
|
||||||
code.language-math.is-loading::after {
|
code.language-math.is-loading::after {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
|
|
120
web_src/css/modules/checkbox.css
Normal file
120
web_src/css/modules/checkbox.css
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/* based on Fomantic UI checkbox module, with just the parts extracted that we use. If you find any
|
||||||
|
unused rules here after refactoring, please remove them. */
|
||||||
|
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
width: var(--checkbox-size);
|
||||||
|
height: var(--checkbox-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.checkbox {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: baseline;
|
||||||
|
min-height: var(--checkbox-size);
|
||||||
|
line-height: var(--checkbox-size);
|
||||||
|
min-width: var(--checkbox-size);
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.checkbox input[type="checkbox"],
|
||||||
|
.ui.checkbox input[type="radio"] {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: var(--checkbox-size);
|
||||||
|
height: var(--checkbox-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.checkbox input[type="checkbox"]:enabled,
|
||||||
|
.ui.checkbox input[type="radio"]:enabled,
|
||||||
|
.ui.checkbox label:enabled {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.checkbox label {
|
||||||
|
cursor: auto;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.checkbox label,
|
||||||
|
.ui.radio.checkbox label {
|
||||||
|
margin-left: 1.85714em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.checkbox + label {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.disabled.checkbox label,
|
||||||
|
.ui.checkbox input[disabled] ~ label {
|
||||||
|
cursor: default !important;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.radio.checkbox {
|
||||||
|
min-height: var(--checkbox-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "switch" styled checkbox */
|
||||||
|
|
||||||
|
.ui.toggle.checkbox {
|
||||||
|
min-height: 1.5rem;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox input {
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox label {
|
||||||
|
min-height: 1.5rem;
|
||||||
|
padding-left: 4.5rem;
|
||||||
|
padding-top: 0.15em;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox label::before {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border-radius: 500rem;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox label::after {
|
||||||
|
background: var(--color-white);
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 2;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 500rem;
|
||||||
|
transition: background 0.3s ease, left 0.3s ease;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox input ~ label::after {
|
||||||
|
left: -0.05rem;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox input:checked ~ label::after {
|
||||||
|
left: 2.15rem;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox input:focus ~ label::before,
|
||||||
|
.ui.toggle.checkbox label::before {
|
||||||
|
background: var(--color-input-toggle-background);
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox label,
|
||||||
|
.ui.toggle.checkbox input:checked ~ label,
|
||||||
|
.ui.toggle.checkbox input:focus:checked ~ label {
|
||||||
|
color: var(--color-text) !important;
|
||||||
|
}
|
||||||
|
.ui.toggle.checkbox input:checked ~ label::before,
|
||||||
|
.ui.toggle.checkbox input:focus:checked ~ label::before {
|
||||||
|
background: var(--color-primary) !important;
|
||||||
|
}
|
|
@ -135,6 +135,12 @@ h4.ui.header .sub.header {
|
||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* open dropdown menus to the left in right-attached headers */
|
||||||
|
.ui.attached.header > .ui.right .ui.dropdown .menu {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* if a .top.attached.header is followed by a .segment, add some margin */
|
/* if a .top.attached.header is followed by a .segment, add some margin */
|
||||||
.ui.segments + .ui.top.attached.header,
|
.ui.segments + .ui.top.attached.header,
|
||||||
.ui.attached.segment + .ui.top.attached.header {
|
.ui.attached.segment + .ui.top.attached.header {
|
||||||
|
|
197
web_src/css/modules/input.css
Normal file
197
web_src/css/modules/input.css
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
/* based on Fomantic UI input module, with just the parts extracted that we use. If you find any
|
||||||
|
unused rules here after refactoring, please remove them. */
|
||||||
|
|
||||||
|
.ui.input {
|
||||||
|
position: relative;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
display: inline-flex;
|
||||||
|
color: var(--color-input-text);
|
||||||
|
}
|
||||||
|
.ui.input > input {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--fonts-regular);
|
||||||
|
padding: 0.67857143em 1em;
|
||||||
|
border: 1px solid var(--color-input-border);
|
||||||
|
color: var(--color-input-text);
|
||||||
|
border-radius: 0.28571429rem;
|
||||||
|
line-height: var(--line-height-default);
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.disabled.input,
|
||||||
|
.ui.input:not(.disabled) input[disabled] {
|
||||||
|
opacity: var(--opacity-disabled);
|
||||||
|
}
|
||||||
|
.ui.disabled.input > input,
|
||||||
|
.ui.input:not(.disabled) input[disabled] {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.input.focus > input,
|
||||||
|
.ui.input > input:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.input.error > input {
|
||||||
|
background: var(--color-error-bg);
|
||||||
|
border-color: var(--color-error-border);
|
||||||
|
color: var(--color-error-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon.input > i.icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: default;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 2.67142857em;
|
||||||
|
opacity: 0.5;
|
||||||
|
border-radius: 0 0.28571429rem 0.28571429rem 0;
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon.input > i.icon.is-loading {
|
||||||
|
position: absolute !important;
|
||||||
|
height: 28px;
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon.input > i.icon.is-loading > * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.ui.ui.ui.icon.input > textarea,
|
||||||
|
.ui.ui.ui.ui.icon.input > input {
|
||||||
|
padding-right: 2.67142857em;
|
||||||
|
}
|
||||||
|
.ui.icon.input > i.link.icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ui.icon.input > i.circular.icon {
|
||||||
|
top: 0.35em;
|
||||||
|
right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui[class*="left icon"].input > i.icon {
|
||||||
|
right: auto;
|
||||||
|
left: 1px;
|
||||||
|
border-radius: 0.28571429rem 0 0 0.28571429rem;
|
||||||
|
}
|
||||||
|
.ui[class*="left icon"].input > i.circular.icon {
|
||||||
|
right: auto;
|
||||||
|
left: 0.5em;
|
||||||
|
}
|
||||||
|
.ui.ui.ui.ui[class*="left icon"].input > textarea,
|
||||||
|
.ui.ui.ui.ui[class*="left icon"].input > input {
|
||||||
|
padding-left: 2.67142857em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon.input > textarea:focus ~ .icon,
|
||||||
|
.ui.icon.input > input:focus ~ .icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon.input > textarea ~ i.icon {
|
||||||
|
height: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.form .field.error > .ui.action.input > .ui.button,
|
||||||
|
.ui.action.input.error > .ui.button {
|
||||||
|
border-top: 1px solid var(--color-error-border);
|
||||||
|
border-bottom: 1px solid var(--color-error-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.action.input > .button,
|
||||||
|
.ui.action.input > .buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.ui.action.input > .button,
|
||||||
|
.ui.action.input > .buttons > .button {
|
||||||
|
padding-top: 0.78571429em;
|
||||||
|
padding-bottom: 0.78571429em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.action.input:not([class*="left action"]) > input {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-right-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.action.input > .dropdown:first-child,
|
||||||
|
.ui.action.input > .button:first-child,
|
||||||
|
.ui.action.input > .buttons:first-child > .button {
|
||||||
|
border-radius: 0.28571429rem 0 0 0.28571429rem;
|
||||||
|
}
|
||||||
|
.ui.action.input > .dropdown:not(:first-child),
|
||||||
|
.ui.action.input > .button:not(:first-child),
|
||||||
|
.ui.action.input > .buttons:not(:first-child) > .button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.ui.action.input > .dropdown:last-child,
|
||||||
|
.ui.action.input > .button:last-child,
|
||||||
|
.ui.action.input > .buttons:last-child > .button {
|
||||||
|
border-radius: 0 0.28571429rem 0.28571429rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.fluid.input {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.ui.fluid.input > input {
|
||||||
|
width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.tiny.input {
|
||||||
|
font-size: 0.85714286em;
|
||||||
|
}
|
||||||
|
.ui.small.input {
|
||||||
|
font-size: 0.92857143em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.action.input .ui.ui.button {
|
||||||
|
border-color: var(--color-input-border);
|
||||||
|
padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* currently used for search bar dropdowns in repo search and explore code */
|
||||||
|
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection {
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover {
|
||||||
|
border-color: var(--color-input-border);
|
||||||
|
}
|
||||||
|
.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible {
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.ui.action.input:not([class*="left action"]) > input,
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:hover {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection,
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
|
||||||
|
border-left-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.ui.action.input:not([class*="left action"]) > input:focus {
|
||||||
|
border-right-color: var(--color-primary);
|
||||||
|
}
|
187
web_src/css/modules/list.css
Normal file
187
web_src/css/modules/list.css
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
/* based on Fomantic UI list module, with just the parts extracted that we use. If you find any
|
||||||
|
unused rules here after refactoring, please remove them. */
|
||||||
|
|
||||||
|
.ui.list {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list > .item,
|
||||||
|
.ui.list .list > .item {
|
||||||
|
display: list-item;
|
||||||
|
table-layout: fixed;
|
||||||
|
list-style-type: none;
|
||||||
|
list-style-position: outside;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list > .list > .item::after,
|
||||||
|
.ui.list > .item::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
clear: both;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list:not(.icon) {
|
||||||
|
clear: both;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.75em 0 0.25em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > .item {
|
||||||
|
padding: 0.14285714em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > .item > i.icon,
|
||||||
|
.ui.list > .item > i.icon {
|
||||||
|
display: table-cell;
|
||||||
|
min-width: 1.55em;
|
||||||
|
padding-top: 0;
|
||||||
|
transition: color 0.1s ease;
|
||||||
|
padding-right: 0.28571429em;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.ui.list .list > .item > i.icon:only-child,
|
||||||
|
.ui.list > .item > i.icon:only-child {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > .item > .image,
|
||||||
|
.ui.list > .item > .image {
|
||||||
|
display: table-cell;
|
||||||
|
background-color: transparent;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.ui.list .list > .item > .image:not(:only-child):not(img),
|
||||||
|
.ui.list > .item > .image:not(:only-child):not(img) {
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
.ui.list .list > .item > .image img,
|
||||||
|
.ui.list > .item > .image img {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.ui.list .list > .item > img.image,
|
||||||
|
.ui.list .list > .item > .image:only-child,
|
||||||
|
.ui.list > .item > img.image,
|
||||||
|
.ui.list > .item > .image:only-child {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > .item > .content,
|
||||||
|
.ui.list > .item > .content {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.ui.list .list > .item > .image + .content,
|
||||||
|
.ui.list .list > .item > i.icon + .content,
|
||||||
|
.ui.list > .item > .image + .content,
|
||||||
|
.ui.list > .item > i.icon + .content {
|
||||||
|
display: table-cell;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 0 0 0.5em;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.ui.list .list > .item > img.image + .content,
|
||||||
|
.ui.list > .item > img.image + .content {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.ui.list .list > .item > .content > .list,
|
||||||
|
.ui.list > .item > .content > .list {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > .item .header,
|
||||||
|
.ui.list > .item .header {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--fonts-regular);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > .item .description,
|
||||||
|
.ui.list > .item .description {
|
||||||
|
display: block;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list > .item a,
|
||||||
|
.ui.list .list > .item a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.menu .ui.list > .item,
|
||||||
|
.ui.menu .ui.list .list > .item {
|
||||||
|
display: list-item;
|
||||||
|
table-layout: fixed;
|
||||||
|
background-color: transparent;
|
||||||
|
list-style-type: none;
|
||||||
|
list-style-position: outside;
|
||||||
|
padding: 0.21428571em 0;
|
||||||
|
}
|
||||||
|
.ui.menu .ui.list .list > .item::before,
|
||||||
|
.ui.menu .ui.list > .item::before {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.ui.menu .ui.list .list > .item:first-child,
|
||||||
|
.ui.menu .ui.list > .item:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.ui.menu .ui.list .list > .item:last-child,
|
||||||
|
.ui.menu .ui.list > .item:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > .disabled.item,
|
||||||
|
.ui.list > .disabled.item {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: var(--opacity-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.list .list > a.item:hover > .icons,
|
||||||
|
.ui.list > a.item:hover > .icons,
|
||||||
|
.ui.list .list > a.item:hover > i.icon,
|
||||||
|
.ui.list > a.item:hover > i.icon {
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.divided.list > .item {
|
||||||
|
border-top: 1px solid var(--color-secondary);
|
||||||
|
}
|
||||||
|
.ui.divided.list .list > .item {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
.ui.divided.list .item .list > .item {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
.ui.divided.list .list > .item:first-child,
|
||||||
|
.ui.divided.list > .item:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
.ui.divided.list .list > .item:first-child {
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.relaxed.list > .item:not(:first-child) {
|
||||||
|
padding-top: 0.42857143em;
|
||||||
|
}
|
||||||
|
.ui.relaxed.list > .item:not(:last-child) {
|
||||||
|
padding-bottom: 0.42857143em;
|
||||||
|
}
|
|
@ -140,3 +140,8 @@
|
||||||
.secondary-nav {
|
.secondary-nav {
|
||||||
background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */
|
background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.issue-navbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,17 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* bare theme, no styling at all, except box-shadow */
|
||||||
|
.tippy-box[data-theme="bare"] {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 6px 18px var(--color-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-theme="bare"] .tippy-content {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/* tooltip theme for text tooltips */
|
/* tooltip theme for text tooltips */
|
||||||
|
|
||||||
.tippy-box[data-theme="tooltip"] {
|
.tippy-box[data-theme="tooltip"] {
|
||||||
|
|
|
@ -89,10 +89,6 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.organization.options input {
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content.organization .org-avatar {
|
.page-content.organization .org-avatar {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2299,104 +2299,6 @@
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-label.modal .form .color.picker.column,
|
|
||||||
.new-label.modal .form .color.picker.column {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-label.modal .form .color.picker.column .minicolors,
|
|
||||||
.new-label.modal .form .color.picker.column .minicolors {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-label.modal .form .minicolors-swatch.minicolors-sprite,
|
|
||||||
.new-label.modal .form .minicolors-swatch.minicolors-sprite {
|
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-1 {
|
|
||||||
tab-size: 1 !important;
|
|
||||||
-moz-tab-size: 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-2 {
|
|
||||||
tab-size: 2 !important;
|
|
||||||
-moz-tab-size: 2 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-3 {
|
|
||||||
tab-size: 3 !important;
|
|
||||||
-moz-tab-size: 3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-4 {
|
|
||||||
tab-size: 4 !important;
|
|
||||||
-moz-tab-size: 4 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-5 {
|
|
||||||
tab-size: 5 !important;
|
|
||||||
-moz-tab-size: 5 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-6 {
|
|
||||||
tab-size: 6 !important;
|
|
||||||
-moz-tab-size: 6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-7 {
|
|
||||||
tab-size: 7 !important;
|
|
||||||
-moz-tab-size: 7 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-8 {
|
|
||||||
tab-size: 8 !important;
|
|
||||||
-moz-tab-size: 8 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-9 {
|
|
||||||
tab-size: 9 !important;
|
|
||||||
-moz-tab-size: 9 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-10 {
|
|
||||||
tab-size: 10 !important;
|
|
||||||
-moz-tab-size: 10 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-11 {
|
|
||||||
tab-size: 11 !important;
|
|
||||||
-moz-tab-size: 11 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-12 {
|
|
||||||
tab-size: 12 !important;
|
|
||||||
-moz-tab-size: 12 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-13 {
|
|
||||||
tab-size: 13 !important;
|
|
||||||
-moz-tab-size: 13 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-14 {
|
|
||||||
tab-size: 14 !important;
|
|
||||||
-moz-tab-size: 14 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-15 {
|
|
||||||
tab-size: 15 !important;
|
|
||||||
-moz-tab-size: 15 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-size-16 {
|
|
||||||
tab-size: 16 !important;
|
|
||||||
-moz-tab-size: 16 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-table {
|
.stats-table {
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -2573,6 +2475,7 @@ tbody.commit-list {
|
||||||
#repo-topics .repo-topic {
|
#repo-topics .repo-topic {
|
||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#new-dependency-drop-list.ui.selection.dropdown {
|
#new-dependency-drop-list.ui.selection.dropdown {
|
||||||
|
@ -2990,6 +2893,7 @@ tbody.commit-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
.issue-list-toolbar-left {
|
.issue-list-toolbar-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-list-toolbar-right .filter.menu {
|
.issue-list-toolbar-right .filter.menu {
|
||||||
|
|
2430
web_src/fomantic/build/semantic.css
generated
2430
web_src/fomantic/build/semantic.css
generated
File diff suppressed because it is too large
Load diff
877
web_src/fomantic/build/semantic.js
generated
877
web_src/fomantic/build/semantic.js
generated
|
@ -1184,883 +1184,6 @@ $.api.settings = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})( jQuery, window, document );
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* # Fomantic-UI - Checkbox
|
|
||||||
* http://github.com/fomantic/Fomantic-UI/
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Released under the MIT license
|
|
||||||
* http://opensource.org/licenses/MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
;(function ($, window, document, undefined) {
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
$.isFunction = $.isFunction || function(obj) {
|
|
||||||
return typeof obj === "function" && typeof obj.nodeType !== "number";
|
|
||||||
};
|
|
||||||
|
|
||||||
window = (typeof window != 'undefined' && window.Math == Math)
|
|
||||||
? window
|
|
||||||
: (typeof self != 'undefined' && self.Math == Math)
|
|
||||||
? self
|
|
||||||
: Function('return this')()
|
|
||||||
;
|
|
||||||
|
|
||||||
$.fn.checkbox = function(parameters) {
|
|
||||||
var
|
|
||||||
$allModules = $(this),
|
|
||||||
moduleSelector = $allModules.selector || '',
|
|
||||||
|
|
||||||
time = new Date().getTime(),
|
|
||||||
performance = [],
|
|
||||||
|
|
||||||
query = arguments[0],
|
|
||||||
methodInvoked = (typeof query == 'string'),
|
|
||||||
queryArguments = [].slice.call(arguments, 1),
|
|
||||||
returnedValue
|
|
||||||
;
|
|
||||||
|
|
||||||
$allModules
|
|
||||||
.each(function() {
|
|
||||||
var
|
|
||||||
settings = $.extend(true, {}, $.fn.checkbox.settings, parameters),
|
|
||||||
|
|
||||||
className = settings.className,
|
|
||||||
namespace = settings.namespace,
|
|
||||||
selector = settings.selector,
|
|
||||||
error = settings.error,
|
|
||||||
|
|
||||||
eventNamespace = '.' + namespace,
|
|
||||||
moduleNamespace = 'module-' + namespace,
|
|
||||||
|
|
||||||
$module = $(this),
|
|
||||||
$label = $(this).children(selector.label),
|
|
||||||
$input = $(this).children(selector.input),
|
|
||||||
input = $input[0],
|
|
||||||
|
|
||||||
initialLoad = false,
|
|
||||||
shortcutPressed = false,
|
|
||||||
instance = $module.data(moduleNamespace),
|
|
||||||
|
|
||||||
observer,
|
|
||||||
element = this,
|
|
||||||
module
|
|
||||||
;
|
|
||||||
|
|
||||||
module = {
|
|
||||||
|
|
||||||
initialize: function() {
|
|
||||||
module.verbose('Initializing checkbox', settings);
|
|
||||||
|
|
||||||
module.create.label();
|
|
||||||
module.bind.events();
|
|
||||||
|
|
||||||
module.set.tabbable();
|
|
||||||
module.hide.input();
|
|
||||||
|
|
||||||
module.observeChanges();
|
|
||||||
module.instantiate();
|
|
||||||
module.setup();
|
|
||||||
},
|
|
||||||
|
|
||||||
instantiate: function() {
|
|
||||||
module.verbose('Storing instance of module', module);
|
|
||||||
instance = module;
|
|
||||||
$module
|
|
||||||
.data(moduleNamespace, module)
|
|
||||||
;
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy: function() {
|
|
||||||
module.verbose('Destroying module');
|
|
||||||
module.unbind.events();
|
|
||||||
module.show.input();
|
|
||||||
$module.removeData(moduleNamespace);
|
|
||||||
},
|
|
||||||
|
|
||||||
fix: {
|
|
||||||
reference: function() {
|
|
||||||
if( $module.is(selector.input) ) {
|
|
||||||
module.debug('Behavior called on <input> adjusting invoked element');
|
|
||||||
$module = $module.closest(selector.checkbox);
|
|
||||||
module.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup: function() {
|
|
||||||
module.set.initialLoad();
|
|
||||||
if( module.is.indeterminate() ) {
|
|
||||||
module.debug('Initial value is indeterminate');
|
|
||||||
module.indeterminate();
|
|
||||||
}
|
|
||||||
else if( module.is.checked() ) {
|
|
||||||
module.debug('Initial value is checked');
|
|
||||||
module.check();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
module.debug('Initial value is unchecked');
|
|
||||||
module.uncheck();
|
|
||||||
}
|
|
||||||
module.remove.initialLoad();
|
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function() {
|
|
||||||
$label = $module.children(selector.label);
|
|
||||||
$input = $module.children(selector.input);
|
|
||||||
input = $input[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
hide: {
|
|
||||||
input: function() {
|
|
||||||
module.verbose('Modifying <input> z-index to be unselectable');
|
|
||||||
$input.addClass(className.hidden);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
show: {
|
|
||||||
input: function() {
|
|
||||||
module.verbose('Modifying <input> z-index to be selectable');
|
|
||||||
$input.removeClass(className.hidden);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
observeChanges: function() {
|
|
||||||
if('MutationObserver' in window) {
|
|
||||||
observer = new MutationObserver(function(mutations) {
|
|
||||||
module.debug('DOM tree modified, updating selector cache');
|
|
||||||
module.refresh();
|
|
||||||
});
|
|
||||||
observer.observe(element, {
|
|
||||||
childList : true,
|
|
||||||
subtree : true
|
|
||||||
});
|
|
||||||
module.debug('Setting up mutation observer', observer);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
attachEvents: function(selector, event) {
|
|
||||||
var
|
|
||||||
$element = $(selector)
|
|
||||||
;
|
|
||||||
event = $.isFunction(module[event])
|
|
||||||
? module[event]
|
|
||||||
: module.toggle
|
|
||||||
;
|
|
||||||
if($element.length > 0) {
|
|
||||||
module.debug('Attaching checkbox events to element', selector, event);
|
|
||||||
$element
|
|
||||||
.on('click' + eventNamespace, event)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
module.error(error.notFound);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
preventDefaultOnInputTarget: function() {
|
|
||||||
if(typeof event !== 'undefined' && event !== null && $(event.target).is(selector.input)) {
|
|
||||||
module.verbose('Preventing default check action after manual check action');
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
event: {
|
|
||||||
change: function(event) {
|
|
||||||
if( !module.should.ignoreCallbacks() ) {
|
|
||||||
settings.onChange.call(input);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
click: function(event) {
|
|
||||||
var
|
|
||||||
$target = $(event.target)
|
|
||||||
;
|
|
||||||
if( $target.is(selector.input) ) {
|
|
||||||
module.verbose('Using default check action on initialized checkbox');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if( $target.is(selector.link) ) {
|
|
||||||
module.debug('Clicking link inside checkbox, skipping toggle');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.toggle();
|
|
||||||
$input.focus();
|
|
||||||
event.preventDefault();
|
|
||||||
},
|
|
||||||
keydown: function(event) {
|
|
||||||
var
|
|
||||||
key = event.which,
|
|
||||||
keyCode = {
|
|
||||||
enter : 13,
|
|
||||||
space : 32,
|
|
||||||
escape : 27,
|
|
||||||
left : 37,
|
|
||||||
up : 38,
|
|
||||||
right : 39,
|
|
||||||
down : 40
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
var r = module.get.radios(),
|
|
||||||
rIndex = r.index($module),
|
|
||||||
rLen = r.length,
|
|
||||||
checkIndex = false;
|
|
||||||
|
|
||||||
if(key == keyCode.left || key == keyCode.up) {
|
|
||||||
checkIndex = (rIndex === 0 ? rLen : rIndex) - 1;
|
|
||||||
} else if(key == keyCode.right || key == keyCode.down) {
|
|
||||||
checkIndex = rIndex === rLen-1 ? 0 : rIndex+1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!module.should.ignoreCallbacks() && checkIndex !== false) {
|
|
||||||
if(settings.beforeUnchecked.apply(input)===false) {
|
|
||||||
module.verbose('Option not allowed to be unchecked, cancelling key navigation');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (settings.beforeChecked.apply($(r[checkIndex]).children(selector.input)[0])===false) {
|
|
||||||
module.verbose('Next option should not allow check, cancelling key navigation');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(key == keyCode.escape) {
|
|
||||||
module.verbose('Escape key pressed blurring field');
|
|
||||||
$input.blur();
|
|
||||||
shortcutPressed = true;
|
|
||||||
}
|
|
||||||
else if(!event.ctrlKey && ( key == keyCode.space || (key == keyCode.enter && settings.enableEnterKey)) ) {
|
|
||||||
module.verbose('Enter/space key pressed, toggling checkbox');
|
|
||||||
module.toggle();
|
|
||||||
shortcutPressed = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
shortcutPressed = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyup: function(event) {
|
|
||||||
if(shortcutPressed) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
check: function() {
|
|
||||||
if( !module.should.allowCheck() ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Checking checkbox', $input);
|
|
||||||
module.set.checked();
|
|
||||||
if( !module.should.ignoreCallbacks() ) {
|
|
||||||
settings.onChecked.call(input);
|
|
||||||
module.trigger.change();
|
|
||||||
}
|
|
||||||
module.preventDefaultOnInputTarget();
|
|
||||||
},
|
|
||||||
|
|
||||||
uncheck: function() {
|
|
||||||
if( !module.should.allowUncheck() ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Unchecking checkbox');
|
|
||||||
module.set.unchecked();
|
|
||||||
if( !module.should.ignoreCallbacks() ) {
|
|
||||||
settings.onUnchecked.call(input);
|
|
||||||
module.trigger.change();
|
|
||||||
}
|
|
||||||
module.preventDefaultOnInputTarget();
|
|
||||||
},
|
|
||||||
|
|
||||||
indeterminate: function() {
|
|
||||||
if( module.should.allowIndeterminate() ) {
|
|
||||||
module.debug('Checkbox is already indeterminate');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Making checkbox indeterminate');
|
|
||||||
module.set.indeterminate();
|
|
||||||
if( !module.should.ignoreCallbacks() ) {
|
|
||||||
settings.onIndeterminate.call(input);
|
|
||||||
module.trigger.change();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
determinate: function() {
|
|
||||||
if( module.should.allowDeterminate() ) {
|
|
||||||
module.debug('Checkbox is already determinate');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Making checkbox determinate');
|
|
||||||
module.set.determinate();
|
|
||||||
if( !module.should.ignoreCallbacks() ) {
|
|
||||||
settings.onDeterminate.call(input);
|
|
||||||
module.trigger.change();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
enable: function() {
|
|
||||||
if( module.is.enabled() ) {
|
|
||||||
module.debug('Checkbox is already enabled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Enabling checkbox');
|
|
||||||
module.set.enabled();
|
|
||||||
if( !module.should.ignoreCallbacks() ) {
|
|
||||||
settings.onEnable.call(input);
|
|
||||||
// preserve legacy callbacks
|
|
||||||
settings.onEnabled.call(input);
|
|
||||||
module.trigger.change();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
disable: function() {
|
|
||||||
if( module.is.disabled() ) {
|
|
||||||
module.debug('Checkbox is already disabled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Disabling checkbox');
|
|
||||||
module.set.disabled();
|
|
||||||
if( !module.should.ignoreCallbacks() ) {
|
|
||||||
settings.onDisable.call(input);
|
|
||||||
// preserve legacy callbacks
|
|
||||||
settings.onDisabled.call(input);
|
|
||||||
module.trigger.change();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get: {
|
|
||||||
radios: function() {
|
|
||||||
var
|
|
||||||
name = module.get.name()
|
|
||||||
;
|
|
||||||
return $('input[name="' + name + '"]').closest(selector.checkbox);
|
|
||||||
},
|
|
||||||
otherRadios: function() {
|
|
||||||
return module.get.radios().not($module);
|
|
||||||
},
|
|
||||||
name: function() {
|
|
||||||
return $input.attr('name');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
is: {
|
|
||||||
initialLoad: function() {
|
|
||||||
return initialLoad;
|
|
||||||
},
|
|
||||||
radio: function() {
|
|
||||||
return ($input.hasClass(className.radio) || $input.attr('type') == 'radio');
|
|
||||||
},
|
|
||||||
indeterminate: function() {
|
|
||||||
return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate');
|
|
||||||
},
|
|
||||||
checked: function() {
|
|
||||||
return $input.prop('checked') !== undefined && $input.prop('checked');
|
|
||||||
},
|
|
||||||
disabled: function() {
|
|
||||||
return $input.prop('disabled') !== undefined && $input.prop('disabled');
|
|
||||||
},
|
|
||||||
enabled: function() {
|
|
||||||
return !module.is.disabled();
|
|
||||||
},
|
|
||||||
determinate: function() {
|
|
||||||
return !module.is.indeterminate();
|
|
||||||
},
|
|
||||||
unchecked: function() {
|
|
||||||
return !module.is.checked();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
should: {
|
|
||||||
allowCheck: function() {
|
|
||||||
if(module.is.determinate() && module.is.checked() && !module.is.initialLoad() ) {
|
|
||||||
module.debug('Should not allow check, checkbox is already checked');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!module.should.ignoreCallbacks() && settings.beforeChecked.apply(input) === false) {
|
|
||||||
module.debug('Should not allow check, beforeChecked cancelled');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
allowUncheck: function() {
|
|
||||||
if(module.is.determinate() && module.is.unchecked() && !module.is.initialLoad() ) {
|
|
||||||
module.debug('Should not allow uncheck, checkbox is already unchecked');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!module.should.ignoreCallbacks() && settings.beforeUnchecked.apply(input) === false) {
|
|
||||||
module.debug('Should not allow uncheck, beforeUnchecked cancelled');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
allowIndeterminate: function() {
|
|
||||||
if(module.is.indeterminate() && !module.is.initialLoad() ) {
|
|
||||||
module.debug('Should not allow indeterminate, checkbox is already indeterminate');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!module.should.ignoreCallbacks() && settings.beforeIndeterminate.apply(input) === false) {
|
|
||||||
module.debug('Should not allow indeterminate, beforeIndeterminate cancelled');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
allowDeterminate: function() {
|
|
||||||
if(module.is.determinate() && !module.is.initialLoad() ) {
|
|
||||||
module.debug('Should not allow determinate, checkbox is already determinate');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!module.should.ignoreCallbacks() && settings.beforeDeterminate.apply(input) === false) {
|
|
||||||
module.debug('Should not allow determinate, beforeDeterminate cancelled');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ignoreCallbacks: function() {
|
|
||||||
return (initialLoad && !settings.fireOnInit);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
can: {
|
|
||||||
change: function() {
|
|
||||||
return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') );
|
|
||||||
},
|
|
||||||
uncheck: function() {
|
|
||||||
return (typeof settings.uncheckable === 'boolean')
|
|
||||||
? settings.uncheckable
|
|
||||||
: !module.is.radio()
|
|
||||||
;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
set: {
|
|
||||||
initialLoad: function() {
|
|
||||||
initialLoad = true;
|
|
||||||
},
|
|
||||||
checked: function() {
|
|
||||||
module.verbose('Setting class to checked');
|
|
||||||
$module
|
|
||||||
.removeClass(className.indeterminate)
|
|
||||||
.addClass(className.checked)
|
|
||||||
;
|
|
||||||
if( module.is.radio() ) {
|
|
||||||
module.uncheckOthers();
|
|
||||||
}
|
|
||||||
if(!module.is.indeterminate() && module.is.checked()) {
|
|
||||||
module.debug('Input is already checked, skipping input property change');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.verbose('Setting state to checked', input);
|
|
||||||
$input
|
|
||||||
.prop('indeterminate', false)
|
|
||||||
.prop('checked', true)
|
|
||||||
;
|
|
||||||
},
|
|
||||||
unchecked: function() {
|
|
||||||
module.verbose('Removing checked class');
|
|
||||||
$module
|
|
||||||
.removeClass(className.indeterminate)
|
|
||||||
.removeClass(className.checked)
|
|
||||||
;
|
|
||||||
if(!module.is.indeterminate() && module.is.unchecked() ) {
|
|
||||||
module.debug('Input is already unchecked');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Setting state to unchecked');
|
|
||||||
$input
|
|
||||||
.prop('indeterminate', false)
|
|
||||||
.prop('checked', false)
|
|
||||||
;
|
|
||||||
},
|
|
||||||
indeterminate: function() {
|
|
||||||
module.verbose('Setting class to indeterminate');
|
|
||||||
$module
|
|
||||||
.addClass(className.indeterminate)
|
|
||||||
;
|
|
||||||
if( module.is.indeterminate() ) {
|
|
||||||
module.debug('Input is already indeterminate, skipping input property change');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Setting state to indeterminate');
|
|
||||||
$input
|
|
||||||
.prop('indeterminate', true)
|
|
||||||
;
|
|
||||||
},
|
|
||||||
determinate: function() {
|
|
||||||
module.verbose('Removing indeterminate class');
|
|
||||||
$module
|
|
||||||
.removeClass(className.indeterminate)
|
|
||||||
;
|
|
||||||
if( module.is.determinate() ) {
|
|
||||||
module.debug('Input is already determinate, skipping input property change');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Setting state to determinate');
|
|
||||||
$input
|
|
||||||
.prop('indeterminate', false)
|
|
||||||
;
|
|
||||||
},
|
|
||||||
disabled: function() {
|
|
||||||
module.verbose('Setting class to disabled');
|
|
||||||
$module
|
|
||||||
.addClass(className.disabled)
|
|
||||||
;
|
|
||||||
if( module.is.disabled() ) {
|
|
||||||
module.debug('Input is already disabled, skipping input property change');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Setting state to disabled');
|
|
||||||
$input
|
|
||||||
.prop('disabled', 'disabled')
|
|
||||||
;
|
|
||||||
},
|
|
||||||
enabled: function() {
|
|
||||||
module.verbose('Removing disabled class');
|
|
||||||
$module.removeClass(className.disabled);
|
|
||||||
if( module.is.enabled() ) {
|
|
||||||
module.debug('Input is already enabled, skipping input property change');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.debug('Setting state to enabled');
|
|
||||||
$input
|
|
||||||
.prop('disabled', false)
|
|
||||||
;
|
|
||||||
},
|
|
||||||
tabbable: function() {
|
|
||||||
module.verbose('Adding tabindex to checkbox');
|
|
||||||
if( $input.attr('tabindex') === undefined) {
|
|
||||||
$input.attr('tabindex', 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
remove: {
|
|
||||||
initialLoad: function() {
|
|
||||||
initialLoad = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
trigger: {
|
|
||||||
change: function() {
|
|
||||||
var
|
|
||||||
inputElement = $input[0]
|
|
||||||
;
|
|
||||||
if(inputElement) {
|
|
||||||
var events = document.createEvent('HTMLEvents');
|
|
||||||
module.verbose('Triggering native change event');
|
|
||||||
events.initEvent('change', true, false);
|
|
||||||
inputElement.dispatchEvent(events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
create: {
|
|
||||||
label: function() {
|
|
||||||
if($input.prevAll(selector.label).length > 0) {
|
|
||||||
$input.prev(selector.label).detach().insertAfter($input);
|
|
||||||
module.debug('Moving existing label', $label);
|
|
||||||
}
|
|
||||||
else if( !module.has.label() ) {
|
|
||||||
$label = $('<label>').insertAfter($input);
|
|
||||||
module.debug('Creating label', $label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
has: {
|
|
||||||
label: function() {
|
|
||||||
return ($label.length > 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bind: {
|
|
||||||
events: function() {
|
|
||||||
module.verbose('Attaching checkbox events');
|
|
||||||
$module
|
|
||||||
.on('click' + eventNamespace, module.event.click)
|
|
||||||
.on('change' + eventNamespace, module.event.change)
|
|
||||||
.on('keydown' + eventNamespace, selector.input, module.event.keydown)
|
|
||||||
.on('keyup' + eventNamespace, selector.input, module.event.keyup)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
unbind: {
|
|
||||||
events: function() {
|
|
||||||
module.debug('Removing events');
|
|
||||||
$module
|
|
||||||
.off(eventNamespace)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
uncheckOthers: function() {
|
|
||||||
var
|
|
||||||
$radios = module.get.otherRadios()
|
|
||||||
;
|
|
||||||
module.debug('Unchecking other radios', $radios);
|
|
||||||
$radios.removeClass(className.checked);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggle: function() {
|
|
||||||
if( !module.can.change() ) {
|
|
||||||
if(!module.is.radio()) {
|
|
||||||
module.debug('Checkbox is read-only or disabled, ignoring toggle');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if( module.is.indeterminate() || module.is.unchecked() ) {
|
|
||||||
module.debug('Currently unchecked');
|
|
||||||
module.check();
|
|
||||||
}
|
|
||||||
else if( module.is.checked() && module.can.uncheck() ) {
|
|
||||||
module.debug('Currently checked');
|
|
||||||
module.uncheck();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setting: function(name, value) {
|
|
||||||
module.debug('Changing setting', name, value);
|
|
||||||
if( $.isPlainObject(name) ) {
|
|
||||||
$.extend(true, settings, name);
|
|
||||||
}
|
|
||||||
else if(value !== undefined) {
|
|
||||||
if($.isPlainObject(settings[name])) {
|
|
||||||
$.extend(true, settings[name], value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
settings[name] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return settings[name];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
internal: function(name, value) {
|
|
||||||
if( $.isPlainObject(name) ) {
|
|
||||||
$.extend(true, module, name);
|
|
||||||
}
|
|
||||||
else if(value !== undefined) {
|
|
||||||
module[name] = value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return module[name];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
debug: function() {
|
|
||||||
if(!settings.silent && settings.debug) {
|
|
||||||
if(settings.performance) {
|
|
||||||
module.performance.log(arguments);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
|
||||||
module.debug.apply(console, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
verbose: function() {
|
|
||||||
if(!settings.silent && settings.verbose && settings.debug) {
|
|
||||||
if(settings.performance) {
|
|
||||||
module.performance.log(arguments);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
|
||||||
module.verbose.apply(console, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
if(!settings.silent) {
|
|
||||||
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
|
||||||
module.error.apply(console, arguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
log: function(message) {
|
|
||||||
var
|
|
||||||
currentTime,
|
|
||||||
executionTime,
|
|
||||||
previousTime
|
|
||||||
;
|
|
||||||
if(settings.performance) {
|
|
||||||
currentTime = new Date().getTime();
|
|
||||||
previousTime = time || currentTime;
|
|
||||||
executionTime = currentTime - previousTime;
|
|
||||||
time = currentTime;
|
|
||||||
performance.push({
|
|
||||||
'Name' : message[0],
|
|
||||||
'Arguments' : [].slice.call(message, 1) || '',
|
|
||||||
'Element' : element,
|
|
||||||
'Execution Time' : executionTime
|
|
||||||
});
|
|
||||||
}
|
|
||||||
clearTimeout(module.performance.timer);
|
|
||||||
module.performance.timer = setTimeout(module.performance.display, 500);
|
|
||||||
},
|
|
||||||
display: function() {
|
|
||||||
var
|
|
||||||
title = settings.name + ':',
|
|
||||||
totalTime = 0
|
|
||||||
;
|
|
||||||
time = false;
|
|
||||||
clearTimeout(module.performance.timer);
|
|
||||||
$.each(performance, function(index, data) {
|
|
||||||
totalTime += data['Execution Time'];
|
|
||||||
});
|
|
||||||
title += ' ' + totalTime + 'ms';
|
|
||||||
if(moduleSelector) {
|
|
||||||
title += ' \'' + moduleSelector + '\'';
|
|
||||||
}
|
|
||||||
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
|
||||||
console.groupCollapsed(title);
|
|
||||||
if(console.table) {
|
|
||||||
console.table(performance);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$.each(performance, function(index, data) {
|
|
||||||
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
performance = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
invoke: function(query, passedArguments, context) {
|
|
||||||
var
|
|
||||||
object = instance,
|
|
||||||
maxDepth,
|
|
||||||
found,
|
|
||||||
response
|
|
||||||
;
|
|
||||||
passedArguments = passedArguments || queryArguments;
|
|
||||||
context = element || context;
|
|
||||||
if(typeof query == 'string' && object !== undefined) {
|
|
||||||
query = query.split(/[\. ]/);
|
|
||||||
maxDepth = query.length - 1;
|
|
||||||
$.each(query, function(depth, value) {
|
|
||||||
var camelCaseValue = (depth != maxDepth)
|
|
||||||
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
|
||||||
: query
|
|
||||||
;
|
|
||||||
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
|
||||||
object = object[camelCaseValue];
|
|
||||||
}
|
|
||||||
else if( object[camelCaseValue] !== undefined ) {
|
|
||||||
found = object[camelCaseValue];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
|
||||||
object = object[value];
|
|
||||||
}
|
|
||||||
else if( object[value] !== undefined ) {
|
|
||||||
found = object[value];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
module.error(error.method, query);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if ( $.isFunction( found ) ) {
|
|
||||||
response = found.apply(context, passedArguments);
|
|
||||||
}
|
|
||||||
else if(found !== undefined) {
|
|
||||||
response = found;
|
|
||||||
}
|
|
||||||
if(Array.isArray(returnedValue)) {
|
|
||||||
returnedValue.push(response);
|
|
||||||
}
|
|
||||||
else if(returnedValue !== undefined) {
|
|
||||||
returnedValue = [returnedValue, response];
|
|
||||||
}
|
|
||||||
else if(response !== undefined) {
|
|
||||||
returnedValue = response;
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(methodInvoked) {
|
|
||||||
if(instance === undefined) {
|
|
||||||
module.initialize();
|
|
||||||
}
|
|
||||||
module.invoke(query);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(instance !== undefined) {
|
|
||||||
instance.invoke('destroy');
|
|
||||||
}
|
|
||||||
module.initialize();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
;
|
|
||||||
|
|
||||||
return (returnedValue !== undefined)
|
|
||||||
? returnedValue
|
|
||||||
: this
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.checkbox.settings = {
|
|
||||||
|
|
||||||
name : 'Checkbox',
|
|
||||||
namespace : 'checkbox',
|
|
||||||
|
|
||||||
silent : false,
|
|
||||||
debug : false,
|
|
||||||
verbose : true,
|
|
||||||
performance : true,
|
|
||||||
|
|
||||||
// delegated event context
|
|
||||||
uncheckable : 'auto',
|
|
||||||
fireOnInit : false,
|
|
||||||
enableEnterKey : true,
|
|
||||||
|
|
||||||
onChange : function(){},
|
|
||||||
|
|
||||||
beforeChecked : function(){},
|
|
||||||
beforeUnchecked : function(){},
|
|
||||||
beforeDeterminate : function(){},
|
|
||||||
beforeIndeterminate : function(){},
|
|
||||||
|
|
||||||
onChecked : function(){},
|
|
||||||
onUnchecked : function(){},
|
|
||||||
|
|
||||||
onDeterminate : function() {},
|
|
||||||
onIndeterminate : function() {},
|
|
||||||
|
|
||||||
onEnable : function(){},
|
|
||||||
onDisable : function(){},
|
|
||||||
|
|
||||||
// preserve misspelled callbacks (will be removed in 3.0)
|
|
||||||
onEnabled : function(){},
|
|
||||||
onDisabled : function(){},
|
|
||||||
|
|
||||||
className : {
|
|
||||||
checked : 'checked',
|
|
||||||
indeterminate : 'indeterminate',
|
|
||||||
disabled : 'disabled',
|
|
||||||
hidden : 'hidden',
|
|
||||||
radio : 'radio',
|
|
||||||
readOnly : 'read-only'
|
|
||||||
},
|
|
||||||
|
|
||||||
error : {
|
|
||||||
method : 'The method you called is not defined'
|
|
||||||
},
|
|
||||||
|
|
||||||
selector : {
|
|
||||||
checkbox : '.ui.checkbox',
|
|
||||||
label : 'label, .box',
|
|
||||||
input : 'input[type="checkbox"], input[type="radio"]',
|
|
||||||
link : 'a[href]'
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
})( jQuery, window, document );
|
})( jQuery, window, document );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -23,12 +23,9 @@
|
||||||
"components": [
|
"components": [
|
||||||
"api",
|
"api",
|
||||||
"button",
|
"button",
|
||||||
"checkbox",
|
|
||||||
"dimmer",
|
"dimmer",
|
||||||
"dropdown",
|
"dropdown",
|
||||||
"form",
|
"form",
|
||||||
"input",
|
|
||||||
"list",
|
|
||||||
"menu",
|
"menu",
|
||||||
"modal",
|
"modal",
|
||||||
"search",
|
"search",
|
||||||
|
|
|
@ -350,10 +350,10 @@ export default sfc; // activate the IDE's Vue plugin
|
||||||
<span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
|
<span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui top attached segment repos-search gt-rounded-top">
|
<div class="ui attached segment repos-search">
|
||||||
<div class="ui fluid action left icon input" :class="{loading: isLoading}">
|
<div class="ui small fluid action left icon input">
|
||||||
<input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos">
|
<input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos">
|
||||||
<i class="icon"><svg-icon name="octicon-search" :size="16"/></i>
|
<i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i>
|
||||||
<div class="ui dropdown icon button" :title="textFilter">
|
<div class="ui dropdown icon button" :title="textFilter">
|
||||||
<svg-icon name="octicon-filter" :size="16"/>
|
<svg-icon name="octicon-filter" :size="16"/>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
|
|
@ -218,17 +218,24 @@ export function initAdminCommon() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Select actions
|
// Select actions
|
||||||
const $checkboxes = $('.select.table .ui.checkbox');
|
const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input');
|
||||||
|
|
||||||
$('.select.action').on('click', function () {
|
$('.select.action').on('click', function () {
|
||||||
switch ($(this).data('action')) {
|
switch ($(this).data('action')) {
|
||||||
case 'select-all':
|
case 'select-all':
|
||||||
$checkboxes.checkbox('check');
|
for (const checkbox of checkboxes) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'deselect-all':
|
case 'deselect-all':
|
||||||
$checkboxes.checkbox('uncheck');
|
for (const checkbox of checkboxes) {
|
||||||
|
checkbox.checked = false;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'inverse':
|
case 'inverse':
|
||||||
$checkboxes.checkbox('toggle');
|
for (const checkbox of checkboxes) {
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -236,11 +243,11 @@ export function initAdminCommon() {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.classList.add('is-loading', 'disabled');
|
this.classList.add('is-loading', 'disabled');
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
$checkboxes.each(function () {
|
for (const checkbox of checkboxes) {
|
||||||
if ($(this).checkbox('is checked')) {
|
if (checkbox.checked) {
|
||||||
data.append('ids[]', this.getAttribute('data-id'));
|
data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
await POST(this.getAttribute('data-link'), {data});
|
await POST(this.getAttribute('data-link'), {data});
|
||||||
window.location.href = this.getAttribute('data-redirect');
|
window.location.href = this.getAttribute('data-redirect');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,66 @@
|
||||||
import $ from 'jquery';
|
import {createTippy} from '../modules/tippy.js';
|
||||||
|
|
||||||
export async function createColorPicker(els) {
|
export async function initColorPickers() {
|
||||||
|
const els = document.getElementsByClassName('js-color-picker-input');
|
||||||
if (!els.length) return;
|
if (!els.length) return;
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors'),
|
import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'),
|
||||||
import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors/jquery.minicolors.css'),
|
import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $(els).minicolors();
|
for (const el of els) {
|
||||||
|
initPicker(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSquare(el, newValue) {
|
||||||
|
el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePicker(el, newValue) {
|
||||||
|
el.setAttribute('color', newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPicker(el) {
|
||||||
|
const input = el.querySelector('input');
|
||||||
|
|
||||||
|
const square = document.createElement('div');
|
||||||
|
square.classList.add('preview-square');
|
||||||
|
updateSquare(square, input.value);
|
||||||
|
el.append(square);
|
||||||
|
|
||||||
|
const picker = document.createElement('hex-color-picker');
|
||||||
|
picker.addEventListener('color-changed', (e) => {
|
||||||
|
input.value = e.detail.value;
|
||||||
|
input.focus();
|
||||||
|
updateSquare(square, e.detail.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('input', (e) => {
|
||||||
|
updateSquare(square, e.target.value);
|
||||||
|
updatePicker(picker, e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
createTippy(input, {
|
||||||
|
trigger: 'focus click',
|
||||||
|
theme: 'bare',
|
||||||
|
hideOnClick: true,
|
||||||
|
content: picker,
|
||||||
|
placement: 'bottom-start',
|
||||||
|
interactive: true,
|
||||||
|
onShow() {
|
||||||
|
updatePicker(picker, input.value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// init precolors
|
||||||
|
for (const colorEl of el.querySelectorAll('.precolors .color')) {
|
||||||
|
colorEl.addEventListener('click', (e) => {
|
||||||
|
const newValue = e.target.getAttribute('data-color-hex');
|
||||||
|
input.value = newValue;
|
||||||
|
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||||
|
updateSquare(square, newValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import $ from 'jquery';
|
||||||
import '../vendor/jquery.are-you-sure.js';
|
import '../vendor/jquery.are-you-sure.js';
|
||||||
import {clippie} from 'clippie';
|
import {clippie} from 'clippie';
|
||||||
import {createDropzone} from './dropzone.js';
|
import {createDropzone} from './dropzone.js';
|
||||||
import {initCompColorPicker} from './comp/ColorPicker.js';
|
|
||||||
import {showGlobalErrorMessage} from '../bootstrap.js';
|
import {showGlobalErrorMessage} from '../bootstrap.js';
|
||||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
|
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
|
||||||
import {svg} from '../svg.js';
|
import {svg} from '../svg.js';
|
||||||
|
@ -110,7 +109,7 @@ async function fetchActionDoRequest(actionElem, url, opt) {
|
||||||
showErrorToast(`${i18n.network_error} ${e}`);
|
showErrorToast(`${i18n.network_error} ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actionElem.classList.remove('is-loading', 'small-loading-icon');
|
actionElem.classList.remove('is-loading', 'loading-icon-2px');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function formFetchAction(e) {
|
async function formFetchAction(e) {
|
||||||
|
@ -122,7 +121,7 @@ async function formFetchAction(e) {
|
||||||
|
|
||||||
formEl.classList.add('is-loading');
|
formEl.classList.add('is-loading');
|
||||||
if (formEl.clientHeight < 50) {
|
if (formEl.clientHeight < 50) {
|
||||||
formEl.classList.add('small-loading-icon');
|
formEl.classList.add('loading-icon-2px');
|
||||||
}
|
}
|
||||||
|
|
||||||
const formMethod = formEl.getAttribute('method') || 'get';
|
const formMethod = formEl.getAttribute('method') || 'get';
|
||||||
|
@ -196,8 +195,6 @@ export function initGlobalCommon() {
|
||||||
$uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');
|
$uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');
|
||||||
$uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');
|
$uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');
|
||||||
|
|
||||||
$('.ui.checkbox').checkbox();
|
|
||||||
|
|
||||||
$('.tabular.menu .item').tab();
|
$('.tabular.menu .item').tab();
|
||||||
|
|
||||||
initSubmitEventPolyfill();
|
initSubmitEventPolyfill();
|
||||||
|
@ -379,10 +376,7 @@ function initGlobalShowModal() {
|
||||||
$attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
|
$attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const $colorPickers = $modal.find('.color-picker');
|
|
||||||
if ($colorPickers.length > 0) {
|
|
||||||
initCompColorPicker(); // FIXME: this might cause duplicate init
|
|
||||||
}
|
|
||||||
$modal.modal('setting', {
|
$modal.modal('setting', {
|
||||||
onApprove: () => {
|
onApprove: () => {
|
||||||
// "form-fetch-action" can handle network errors gracefully,
|
// "form-fetch-action" can handle network errors gracefully,
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import $ from 'jquery';
|
|
||||||
import {createColorPicker} from '../colorpicker.js';
|
|
||||||
|
|
||||||
export function initCompColorPicker() {
|
|
||||||
(async () => {
|
|
||||||
await createColorPicker(document.querySelectorAll('.color-picker'));
|
|
||||||
|
|
||||||
for (const el of document.querySelectorAll('.precolors .color')) {
|
|
||||||
el.addEventListener('click', (e) => {
|
|
||||||
const color = e.target.getAttribute('data-color-hex');
|
|
||||||
const parent = e.target.closest('.color.picker');
|
|
||||||
$(parent.querySelector('.color-picker')).minicolors('value', color);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import {initCompColorPicker} from './ColorPicker.js';
|
|
||||||
|
|
||||||
function isExclusiveScopeName(name) {
|
function isExclusiveScopeName(name) {
|
||||||
return /.*[^/]\/[^/].*/.test(name);
|
return /.*[^/]\/[^/].*/.test(name);
|
||||||
|
@ -28,13 +27,17 @@ function updateExclusiveLabelEdit(form) {
|
||||||
|
|
||||||
export function initCompLabelEdit(selector) {
|
export function initCompLabelEdit(selector) {
|
||||||
if (!$(selector).length) return;
|
if (!$(selector).length) return;
|
||||||
initCompColorPicker();
|
|
||||||
|
|
||||||
// Create label
|
// Create label
|
||||||
$('.new-label.button').on('click', () => {
|
$('.new-label.button').on('click', () => {
|
||||||
updateExclusiveLabelEdit('.new-label');
|
updateExclusiveLabelEdit('.new-label');
|
||||||
$('.new-label.modal').modal({
|
$('.new-label.modal').modal({
|
||||||
onApprove() {
|
onApprove() {
|
||||||
|
const form = document.querySelector('.new-label.form');
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$('.new-label.form').trigger('submit');
|
$('.new-label.form').trigger('submit');
|
||||||
},
|
},
|
||||||
}).modal('show');
|
}).modal('show');
|
||||||
|
@ -60,10 +63,18 @@ export function initCompLabelEdit(selector) {
|
||||||
updateExclusiveLabelEdit('.edit-label');
|
updateExclusiveLabelEdit('.edit-label');
|
||||||
|
|
||||||
$('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
|
$('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
|
||||||
$('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color'));
|
|
||||||
|
const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
|
||||||
|
colorInput.value = this.getAttribute('data-color');
|
||||||
|
colorInput.dispatchEvent(new Event('input', {bubbles: true}));
|
||||||
|
|
||||||
$('.edit-label.modal').modal({
|
$('.edit-label.modal').modal({
|
||||||
onApprove() {
|
onApprove() {
|
||||||
|
const form = document.querySelector('.edit-label.form');
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$('.edit-label.form').trigger('submit');
|
$('.edit-label.form').trigger('submit');
|
||||||
},
|
},
|
||||||
}).modal('show');
|
}).modal('show');
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function initCopyContent() {
|
||||||
// the text to copy is not in the DOM or it is an image which should be
|
// the text to copy is not in the DOM or it is an image which should be
|
||||||
// fetched to copy in full resolution
|
// fetched to copy in full resolution
|
||||||
if (link) {
|
if (link) {
|
||||||
btn.classList.add('is-loading', 'small-loading-icon');
|
btn.classList.add('is-loading', 'loading-icon-2px');
|
||||||
try {
|
try {
|
||||||
const res = await GET(link, {credentials: 'include', redirect: 'follow'});
|
const res = await GET(link, {credentials: 'include', redirect: 'follow'});
|
||||||
const contentType = res.headers.get('content-type');
|
const contentType = res.headers.get('content-type');
|
||||||
|
@ -33,7 +33,7 @@ export function initCopyContent() {
|
||||||
} catch {
|
} catch {
|
||||||
return showTemporaryTooltip(btn, i18n.copy_error);
|
return showTemporaryTooltip(btn, i18n.copy_error);
|
||||||
} finally {
|
} finally {
|
||||||
btn.classList.remove('is-loading', 'small-loading-icon');
|
btn.classList.remove('is-loading', 'loading-icon-2px');
|
||||||
}
|
}
|
||||||
} else { // text, read from DOM
|
} else { // text, read from DOM
|
||||||
const lineEls = document.querySelectorAll('.file-view .lines-code');
|
const lineEls = document.querySelectorAll('.file-view .lines-code');
|
||||||
|
|
|
@ -110,15 +110,15 @@ export function initImageDiff() {
|
||||||
const $imagesAfter = imageInfos[0].$images;
|
const $imagesAfter = imageInfos[0].$images;
|
||||||
const $imagesBefore = imageInfos[1].$images;
|
const $imagesBefore = imageInfos[1].$images;
|
||||||
|
|
||||||
initSideBySide(createContext($imagesAfter[0], $imagesBefore[0]));
|
initSideBySide(this, createContext($imagesAfter[0], $imagesBefore[0]));
|
||||||
if ($imagesAfter.length > 0 && $imagesBefore.length > 0) {
|
if ($imagesAfter.length > 0 && $imagesBefore.length > 0) {
|
||||||
initSwipe(createContext($imagesAfter[1], $imagesBefore[1]));
|
initSwipe(createContext($imagesAfter[1], $imagesBefore[1]));
|
||||||
initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));
|
initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
$container.find('> .image-diff-tabs').removeClass('is-loading');
|
this.querySelector(':scope > .image-diff-tabs')?.classList.remove('is-loading');
|
||||||
|
|
||||||
function initSideBySide(sizes) {
|
function initSideBySide(container, sizes) {
|
||||||
let factor = 1;
|
let factor = 1;
|
||||||
if (sizes.max.width > (diffContainerWidth - 24) / 2) {
|
if (sizes.max.width > (diffContainerWidth - 24) / 2) {
|
||||||
factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
|
factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
|
||||||
|
@ -126,13 +126,24 @@ export function initImageDiff() {
|
||||||
|
|
||||||
const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;
|
const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;
|
||||||
const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight;
|
const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight;
|
||||||
if (sizes.$image1.length !== 0) {
|
if (sizes.$image1?.length) {
|
||||||
$container.find('.bounds-info-after .bounds-info-width').text(`${sizes.$image1[0].naturalWidth}px`).addClass(widthChanged ? 'green' : '');
|
const boundsInfoAfterWidth = container.querySelector('.bounds-info-after .bounds-info-width');
|
||||||
$container.find('.bounds-info-after .bounds-info-height').text(`${sizes.$image1[0].naturalHeight}px`).addClass(heightChanged ? 'green' : '');
|
boundsInfoAfterWidth.textContent = `${sizes.$image1[0].naturalWidth}px`;
|
||||||
|
if (widthChanged) boundsInfoAfterWidth.classList.add('green');
|
||||||
|
|
||||||
|
const boundsInfoAfterHeight = container.querySelector('.bounds-info-after .bounds-info-height');
|
||||||
|
boundsInfoAfterHeight.textContent = `${sizes.$image1[0].naturalHeight}px`;
|
||||||
|
if (heightChanged) boundsInfoAfterHeight.classList.add('green');
|
||||||
}
|
}
|
||||||
if (sizes.$image2.length !== 0) {
|
|
||||||
$container.find('.bounds-info-before .bounds-info-width').text(`${sizes.$image2[0].naturalWidth}px`).addClass(widthChanged ? 'red' : '');
|
if (sizes.$image2?.length) {
|
||||||
$container.find('.bounds-info-before .bounds-info-height').text(`${sizes.$image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : '');
|
const boundsInfoBeforeWidth = container.querySelector('.bounds-info-before .bounds-info-width');
|
||||||
|
boundsInfoBeforeWidth.textContent = `${sizes.$image2[0].naturalWidth}px`;
|
||||||
|
if (widthChanged) boundsInfoBeforeWidth.classList.add('red');
|
||||||
|
|
||||||
|
const boundsInfoBeforeHeight = container.querySelector('.bounds-info-before .bounds-info-height');
|
||||||
|
boundsInfoBeforeHeight.textContent = `${sizes.$image2[0].naturalHeight}px`;
|
||||||
|
if (heightChanged) boundsInfoBeforeHeight.classList.add('red');
|
||||||
}
|
}
|
||||||
|
|
||||||
const image1 = sizes.$image1[0];
|
const image1 = sizes.$image1[0];
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import {GET} from '../modules/fetch.js';
|
import {GET} from '../modules/fetch.js';
|
||||||
|
import {toggleElem} from '../utils/dom.js';
|
||||||
|
|
||||||
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
|
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
|
||||||
let notificationSequenceNumber = 0;
|
let notificationSequenceNumber = 0;
|
||||||
|
@ -177,14 +178,11 @@ async function updateNotificationCount() {
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
const $notificationCount = $('.notification_count');
|
toggleElem('.notification_count', data.new !== 0);
|
||||||
if (data.new === 0) {
|
|
||||||
$notificationCount.addClass('tw-hidden');
|
|
||||||
} else {
|
|
||||||
$notificationCount.removeClass('tw-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
$notificationCount.text(`${data.new}`);
|
for (const el of document.getElementsByClassName('notification_count')) {
|
||||||
|
el.textContent = `${data.new}`;
|
||||||
|
}
|
||||||
|
|
||||||
return `${data.new}`;
|
return `${data.new}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -25,7 +25,9 @@ function getLineEls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
||||||
$linesEls.closest('tr').removeClass('active');
|
for (const el of $linesEls) {
|
||||||
|
el.closest('tr').classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
// add hashchange to permalink
|
// add hashchange to permalink
|
||||||
const refInNewIssue = document.querySelector('a.ref-in-new-issue');
|
const refInNewIssue = document.querySelector('a.ref-in-new-issue');
|
||||||
|
@ -72,7 +74,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
||||||
classes.push(`[rel=L${i}]`);
|
classes.push(`[rel=L${i}]`);
|
||||||
}
|
}
|
||||||
$linesEls.filter(classes.join(',')).each(function () {
|
$linesEls.filter(classes.join(',')).each(function () {
|
||||||
$(this).closest('tr').addClass('active');
|
this.closest('tr').classList.add('active');
|
||||||
});
|
});
|
||||||
changeHash(`#L${a}-L${b}`);
|
changeHash(`#L${a}-L${b}`);
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$selectionEndEl.closest('tr').addClass('active');
|
$selectionEndEl[0].closest('tr').classList.add('active');
|
||||||
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
|
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
|
||||||
|
|
||||||
updateIssueHref($selectionEndEl[0].getAttribute('rel'));
|
updateIssueHref($selectionEndEl[0].getAttribute('rel'));
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue