diff --git a/.github/labeler.yml b/.github/labeler.yml index 980b9c337c..4acdb6f6f5 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -53,7 +53,7 @@ modifies/internal: - ".gitpod.yml" - ".markdownlint.yaml" - ".spectral.yaml" - - ".stylelintrc.yaml" + - "stylelint.config.js" - ".yamllint.yaml" - ".github/**" - ".gitea/" diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index b8535cb42b..9a609e0551 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -58,7 +58,7 @@ jobs: - "package-lock.json" - "Makefile" - ".eslintrc.yaml" - - ".stylelintrc.yaml" + - "stylelint.config.js" - ".npmrc" docs: diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml deleted file mode 100644 index 60cce7dbf7..0000000000 --- a/.stylelintrc.yaml +++ /dev/null @@ -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]}] diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index e2723bd8ae..1584b10301 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1485,6 +1485,11 @@ LEVEL = Info ;; - manage_ssh_keys: a user cannot configure ssh keys ;; - manage_gpg_keys: a user cannot configure gpg keys ;USER_DISABLED_FEATURES = +;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. +;; - deletion: a user cannot delete their own account +;; - manage_ssh_keys: a user cannot configure ssh keys +;; - manage_gpg_keys: a user cannot configure gpg keys +;;EXTERNAL_USER_DISABLE_FEATURES = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 03ab517d80..f2209d269a 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -522,6 +522,10 @@ And the following unique queues: - `deletion`: User cannot delete their own account. - `manage_ssh_keys`: User cannot configure ssh keys. - `manage_gpg_keys`: User cannot configure gpg keys. +- `EXTERNAL_USER_DISABLE_FEATURES`: **_empty_**: 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`: User cannot delete their own account. + - `manage_ssh_keys`: User cannot configure ssh keys. + - `manage_gpg_keys`: User cannot configure gpg keys. ## Security (`security`) diff --git a/models/user/user.go b/models/user/user.go index 22a3099643..d459ec239e 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1232,3 +1232,21 @@ func GetOrderByName() string { } return "name" } + +// IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the +// user if applicable +func IsFeatureDisabledWithLoginType(user *User, feature string) bool { + // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType + return (user != nil && user.LoginType > auth.Plain && setting.Admin.ExternalUserDisableFeatures.Contains(feature)) || + setting.Admin.UserDisabledFeatures.Contains(feature) +} + +// DisabledFeaturesWithLoginType returns the set of user features disabled, taking into account the login type +// of the user if applicable +func DisabledFeaturesWithLoginType(user *User) *container.Set[string] { + // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType + if user != nil && user.LoginType > auth.Plain { + return &setting.Admin.ExternalUserDisableFeatures + } + return &setting.Admin.UserDisabledFeatures +} diff --git a/models/user/user_test.go b/models/user/user_test.go index f4efd071ea..a4550fa655 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password/hash" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" @@ -526,3 +527,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)) + } +} diff --git a/modules/git/commit_convert_gogit.go b/modules/git/commit_convert_gogit.go index 819ea0d1db..33ef2f4487 100644 --- a/modules/git/commit_convert_gogit.go +++ b/modules/git/commit_convert_gogit.go @@ -47,6 +47,12 @@ func convertPGPSignature(c *object.Commit) *CommitGPGSignature { return nil } + if c.Encoding != "" && c.Encoding != "UTF-8" { + if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil { + return nil + } + } + if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { return nil } diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go index 4809d6c7ed..56c41dc473 100644 --- a/modules/git/commit_reader.go +++ b/modules/git/commit_reader.go @@ -84,6 +84,8 @@ readLoop: commit.Committer = &Signature{} commit.Committer.Decode(data) _, _ = payloadSB.Write(line) + case "encoding": + _, _ = payloadSB.Write(line) case "gpgsig": fallthrough case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present. diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index e512eecc56..a33e7df31a 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -125,6 +125,73 @@ empty commit`, commitFromReader.Signature.Payload) assert.EqualValues(t, commitFromReader, commitFromReader2) } +func TestCommitWithEncodingFromReader(t *testing.T) { + commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074 +tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 +parent 47b24e7ab977ed31c5a39989d570847d6d0052af +author KN4CK3R 1711702962 +0100 +committer KN4CK3R 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 1711702962 +0100 +committer KN4CK3R 1711702962 +0100 +encoding ISO-8859-1 + +ISO-8859-1`, commitFromReader.Signature.Payload) + assert.EqualValues(t, "KN4CK3R ", commitFromReader.Author.String()) + + commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) + assert.NoError(t, err) + commitFromReader.CommitMessage += "\n\n" + commitFromReader.Signature.Payload += "\n\n" + assert.EqualValues(t, commitFromReader, commitFromReader2) +} + func TestHasPreviousCommit(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") diff --git a/modules/setting/admin.go b/modules/setting/admin.go index be214a58ce..8aebc76154 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -3,13 +3,16 @@ package setting -import "code.gitea.io/gitea/modules/container" +import ( + "code.gitea.io/gitea/modules/container" +) // Admin settings var Admin struct { - DisableRegularOrgCreation bool - DefaultEmailNotification string - UserDisabledFeatures container.Set[string] + DisableRegularOrgCreation bool + DefaultEmailNotification string + UserDisabledFeatures container.Set[string] + ExternalUserDisableFeatures container.Set[string] } func loadAdminFrom(rootCfg ConfigProvider) { @@ -17,6 +20,7 @@ func loadAdminFrom(rootCfg ConfigProvider) { Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false) Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled") Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...) + Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...) } const ( diff --git a/package-lock.json b/package-lock.json index 25fe14e1a6..21de79387f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "stylelint": "16.3.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", + "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "updates": "16.0.0", "vite-string-plugin": "1.1.5", @@ -10867,6 +10868,22 @@ "stylelint": ">=7 <=16" } }, + "node_modules/stylelint-value-no-unknown-custom-properties": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/stylelint-value-no-unknown-custom-properties/-/stylelint-value-no-unknown-custom-properties-6.0.1.tgz", + "integrity": "sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": ">=16" + } + }, "node_modules/stylelint/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", diff --git a/package.json b/package.json index d5a1d46056..beea0e5d86 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "stylelint": "16.3.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", + "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "updates": "16.0.0", "vite-string-plugin": "1.1.5", diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index dcf5da0b2e..5a2f995e1b 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -10,6 +10,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" @@ -133,7 +134,7 @@ func GetGPGKey(ctx *context.APIContext) { // CreateUserGPGKey creates new GPG key to given user by ID. func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) { - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } @@ -274,7 +275,7 @@ func DeleteGPGKey(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index bcbfd93bd3..d9456e7ec6 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -199,7 +199,7 @@ func GetPublicKey(ctx *context.APIContext) { // CreateUserPublicKey creates new public key to given user by ID. func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) { - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -269,7 +269,7 @@ func DeletePublicKey(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index d69bda6663..c93b70af76 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -235,7 +235,7 @@ func DeleteEmail(ctx *context.Context) { // DeleteAccount render user suicide page and response for delete user himself func DeleteAccount(ctx *context.Context) { - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) { ctx.Error(http.StatusNotFound) return } @@ -319,7 +319,7 @@ func loadAccountData(ctx *context.Context) { ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference ctx.Data["ActivationsPending"] = pendingActivation ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm - ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) if setting.Service.UserDeleteWithCommentsMaxTime != 0 { ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String() diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 056fcc0ace..9e969e045d 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -10,6 +10,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" @@ -78,7 +79,7 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } @@ -159,7 +160,7 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "ssh": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -203,7 +204,7 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "verify_ssh": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -240,7 +241,7 @@ func KeysPost(ctx *context.Context) { func DeleteKey(ctx *context.Context) { switch ctx.FormString("type") { case "gpg": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } @@ -250,7 +251,7 @@ func DeleteKey(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) } case "ssh": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -333,5 +334,5 @@ func loadKeysData(ctx *context.Context) { ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg") ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh") - ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) } diff --git a/stylelint.config.js b/stylelint.config.js new file mode 100644 index 0000000000..c34181233e --- /dev/null +++ b/stylelint.config.js @@ -0,0 +1,245 @@ +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', + ], + 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']}], + }, +}; diff --git a/templates/admin/config_settings.tmpl b/templates/admin/config_settings.tmpl index d7fb022274..02ab5fd0fb 100644 --- a/templates/admin/config_settings.tmpl +++ b/templates/admin/config_settings.tmpl @@ -7,14 +7,14 @@
{{ctx.Locale.Tr "admin.config.disable_gravatar"}}
- +
{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}
- +
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 5913916ae4..c917c78e68 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -677,7 +677,7 @@ {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
-
label, .ui.form .inline.fields .field > p, .ui.form .inline.field > label, -.ui.form .inline.field > p, -.ui.checkbox label, -.ui.checkbox + label, -.ui.checkbox label:hover, -.ui.checkbox + label:hover, -.ui.checkbox input:focus ~ label, -.ui.checkbox input:active ~ label { +.ui.form .inline.field > p { color: var(--color-text); } .ui.form .required.fields:not(.grouped) > .field > label::after, .ui.form .required.fields.grouped > label::after, .ui.form .required.field > label::after, -.ui.form .required.fields:not(.grouped) > .field > .checkbox::after, -.ui.form .required.field > .checkbox::after, .ui.form label.required::after { color: var(--color-red); } -.ui.input, -.ui.checkbox input:focus ~ label::after, -.ui.checkbox input:checked ~ label::after, -.ui.checkbox label:active::after, -.ui.checkbox input:not([type="radio"]):indeterminate ~ label::after, -.ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::after, -.ui.checkbox input:checked:focus ~ label::after, -.ui.disabled.checkbox label, -.ui.checkbox input[disabled] ~ label { +.ui.input { color: var(--color-input-text); } -.ui.radio.checkbox input:focus ~ label::after, -.ui.radio.checkbox input:checked ~ label::after, -.ui.radio.checkbox input:focus:checked ~ label::after { - background: var(--color-input-text); -} - -.ui.toggle.checkbox label::before { - background: var(--color-input-toggle-background); -} - -.ui.toggle.checkbox label, -.ui.toggle.checkbox input:checked ~ label, -.ui.toggle.checkbox input:focus:checked ~ label { - color: var(--color-text) !important; -} - -.ui.toggle.checkbox input:checked ~ label::before, -.ui.toggle.checkbox input:focus:checked ~ label::before { - background: var(--color-primary) !important; -} - /* match */ .ui.form select { padding: 0.67857143em 1em; diff --git a/web_src/css/index.css b/web_src/css/index.css index aa3f6ac48e..373a84cf6a 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -12,6 +12,7 @@ @import "./modules/message.css"; @import "./modules/table.css"; @import "./modules/card.css"; +@import "./modules/checkbox.css"; @import "./modules/modal.css"; @import "./modules/select.css"; diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index 788a4ed6ed..0f78ad25cb 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -6,7 +6,6 @@ .is-loading { pointer-events: none !important; position: relative !important; - overflow: hidden !important; } .is-loading > * { diff --git a/web_src/css/modules/checkbox.css b/web_src/css/modules/checkbox.css new file mode 100644 index 0000000000..fc44a7c115 --- /dev/null +++ b/web_src/css/modules/checkbox.css @@ -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 { + padding-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; +} diff --git a/web_src/css/modules/segment.css b/web_src/css/modules/segment.css index 8bdd25bfe7..bbd39c385f 100644 --- a/web_src/css/modules/segment.css +++ b/web_src/css/modules/segment.css @@ -69,7 +69,8 @@ border-radius: 0 0 0.28571429rem 0.28571429rem; } -.ui.segments:not(.horizontal) > .segment:only-child { +.ui.segments:not(.horizontal) > .segment:only-child, +.ui.segments:not(.horizontal) > .segment:has(~ .tw-hidden) { /* workaround issue with :last-child ignoring hidden elements */ border-radius: 0.28571429rem; } diff --git a/web_src/css/org.css b/web_src/css/org.css index 8b3684d0c0..32e8a914fa 100644 --- a/web_src/css/org.css +++ b/web_src/css/org.css @@ -89,10 +89,6 @@ text-align: center; } -.organization.options input { - min-width: 300px; -} - .page-content.organization .org-avatar { margin-right: 15px; } diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css index 1421577af2..fe8231d718 100644 --- a/web_src/css/repo/issue-list.css +++ b/web_src/css/repo/issue-list.css @@ -9,6 +9,7 @@ .issue-list-toolbar-left { display: flex; + align-items: center; } .issue-list-toolbar-right .filter.menu { diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css index 21c41a6161..525a3af8c6 100644 --- a/web_src/fomantic/build/semantic.css +++ b/web_src/fomantic/build/semantic.css @@ -2323,715 +2323,6 @@ Theme Overrides *******************************/ -/******************************* - Site Overrides -*******************************/ -/*! - * # Fomantic-UI - Checkbox - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Checkbox -*******************************/ - -/*-------------- - Content ----------------*/ - -.ui.checkbox { - position: relative; - display: inline-block; - backface-visibility: hidden; - outline: none; - vertical-align: baseline; - font-style: normal; - min-height: 17px; - font-size: 1em; - line-height: 17px; - min-width: 17px; -} - -/* HTML Checkbox */ - -.ui.checkbox input[type="checkbox"], -.ui.checkbox input[type="radio"] { - cursor: pointer; - position: absolute; - top: 0; - left: 0; - opacity: 0 !important; - outline: none; - z-index: 3; - width: 17px; - height: 17px; -} - -.ui.checkbox label { - cursor: auto; - position: relative; - display: block; - padding-left: 1.85714em; - outline: none; - font-size: 1em; -} - -.ui.checkbox label:before { - position: absolute; - top: 0; - left: 0; - width: 17px; - height: 17px; - content: ''; - background: #FFFFFF; - border-radius: 0.21428571rem; - transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease; - border: 1px solid #D4D4D5; -} - -/*-------------- - Checkmark ----------------*/ - -.ui.checkbox label:after { - position: absolute; - font-size: 14px; - top: 0; - left: 0; - width: 17px; - height: 17px; - text-align: center; - opacity: 0; - color: rgba(0, 0, 0, 0.87); - transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease; -} - -/*-------------- - Label ----------------*/ - -/* Inside */ - -.ui.checkbox label, -.ui.checkbox + label { - color: rgba(0, 0, 0, 0.87); - transition: color 0.1s ease; -} - -/* Outside */ - -.ui.checkbox + label { - vertical-align: middle; -} - -/******************************* - States -*******************************/ - -/*-------------- - Hover ----------------*/ - -.ui.checkbox label:hover::before { - background: #FFFFFF; - border-color: rgba(34, 36, 38, 0.35); -} - -.ui.checkbox label:hover, -.ui.checkbox + label:hover { - color: rgba(0, 0, 0, 0.8); -} - -/*-------------- - Down ----------------*/ - -.ui.checkbox label:active::before { - background: #F9FAFB; - border-color: rgba(34, 36, 38, 0.35); -} - -.ui.checkbox label:active::after { - color: rgba(0, 0, 0, 0.95); -} - -.ui.checkbox input:active ~ label { - color: rgba(0, 0, 0, 0.95); -} - -/*-------------- - Focus ----------------*/ - -.ui.checkbox input:focus ~ label:before { - background: #FFFFFF; - border-color: #96C8DA; -} - -.ui.checkbox input:focus ~ label:after { - color: rgba(0, 0, 0, 0.95); -} - -.ui.checkbox input:focus ~ label { - color: rgba(0, 0, 0, 0.95); -} - -/*-------------- - Active ----------------*/ - -.ui.checkbox input:checked ~ label:before { - background: #FFFFFF; - border-color: rgba(34, 36, 38, 0.35); -} - -.ui.checkbox input:checked ~ label:after { - opacity: 1; - color: rgba(0, 0, 0, 0.95); -} - -/*-------------- - Indeterminate - ---------------*/ - -.ui.checkbox input:not([type=radio]):indeterminate ~ label:before { - background: #FFFFFF; - border-color: rgba(34, 36, 38, 0.35); -} - -.ui.checkbox input:not([type=radio]):indeterminate ~ label:after { - opacity: 1; - color: rgba(0, 0, 0, 0.95); -} - -.ui.indeterminate.toggle.checkbox input:not([type=radio]):indeterminate ~ label:before { - background: rgba(0, 0, 0, 0.15); -} - -.ui.indeterminate.toggle.checkbox input:not([type=radio]) ~ label:after { - left: 1.075rem; -} - -/*-------------- - Active Focus ----------------*/ - -.ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:before, -.ui.checkbox input:checked:focus ~ label:before { - background: #FFFFFF; - border-color: #96C8DA; -} - -.ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:after, -.ui.checkbox input:checked:focus ~ label:after { - color: rgba(0, 0, 0, 0.95); -} - -/*-------------- - Read-Only ----------------*/ - -.ui.read-only.checkbox, -.ui.read-only.checkbox label { - cursor: default; -} - -/*-------------- - Disabled - ---------------*/ - -.ui.disabled.checkbox label, -.ui.checkbox input[disabled] ~ label { - cursor: default !important; - opacity: 0.5; - color: #000000; - pointer-events: none; -} - -/*-------------- - Hidden ----------------*/ - -/* Initialized checkbox moves input below element - to prevent manually triggering */ - -.ui.checkbox input.hidden { - z-index: -1; -} - -/* Selectable Label */ - -.ui.checkbox input.hidden + label { - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} - -/******************************* - Types -*******************************/ - -/*-------------- - Radio - ---------------*/ - -.ui.radio.checkbox { - min-height: 15px; -} - -.ui.radio.checkbox label { - padding-left: 1.85714em; -} - -/* Box */ - -.ui.radio.checkbox label:before { - content: ''; - transform: none; - width: 15px; - height: 15px; - border-radius: 500rem; - top: 1px; - left: 0; -} - -/* Bullet */ - -.ui.radio.checkbox label:after { - border: none; - content: '' !important; - line-height: 15px; - top: 1px; - left: 0; - width: 15px; - height: 15px; - border-radius: 500rem; - transform: scale(0.46666667); - background-color: rgba(0, 0, 0, 0.87); -} - -/* Focus */ - -.ui.radio.checkbox input:focus ~ label:before { - background-color: #FFFFFF; -} - -.ui.radio.checkbox input:focus ~ label:after { - background-color: rgba(0, 0, 0, 0.95); -} - -/* Indeterminate */ - -.ui.radio.checkbox input:indeterminate ~ label:after { - opacity: 0; -} - -/* Active */ - -.ui.radio.checkbox input:checked ~ label:before { - background-color: #FFFFFF; -} - -.ui.radio.checkbox input:checked ~ label:after { - background-color: rgba(0, 0, 0, 0.95); -} - -/* Active Focus */ - -.ui.radio.checkbox input:focus:checked ~ label:before { - background-color: #FFFFFF; -} - -.ui.radio.checkbox input:focus:checked ~ label:after { - background-color: rgba(0, 0, 0, 0.95); -} - -/*-------------- - Slider - ---------------*/ - -.ui.slider.checkbox { - min-height: 1.25rem; -} - -/* Input */ - -.ui.slider.checkbox input { - width: 3.5rem; - height: 1.25rem; -} - -/* Label */ - -.ui.slider.checkbox label { - padding-left: 4.5rem; - line-height: 1rem; - color: rgba(0, 0, 0, 0.4); -} - -/* Line */ - -.ui.slider.checkbox label:before { - display: block; - position: absolute; - content: ''; - transform: none; - border: none !important; - left: 0; - z-index: 1; - top: 0.4rem; - background-color: rgba(0, 0, 0, 0.05); - width: 3.5rem; - height: 0.21428571rem; - border-radius: 500rem; - transition: background 0.3s ease; -} - -/* Handle */ - -.ui.slider.checkbox label:after { - background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05)); - position: absolute; - content: '' !important; - opacity: 1; - z-index: 2; - border: none; - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; - width: 1.5rem; - height: 1.5rem; - top: -0.25rem; - left: 0; - transform: none; - border-radius: 500rem; - transition: left 0.3s ease; -} - -/* Focus */ - -.ui.slider.checkbox input:focus ~ label:before { - background-color: rgba(0, 0, 0, 0.15); - border: none; -} - -/* Hover */ - -.ui.slider.checkbox label:hover { - color: rgba(0, 0, 0, 0.8); -} - -.ui.slider.checkbox label:hover::before { - background: rgba(0, 0, 0, 0.15); -} - -/* Active */ - -.ui.slider.checkbox input:checked ~ label { - color: rgba(0, 0, 0, 0.95) !important; -} - -.ui.slider.checkbox input:checked ~ label:before { - background-color: #545454 !important; -} - -.ui.slider.checkbox input:checked ~ label:after { - left: 2rem; -} - -/* Active Focus */ - -.ui.slider.checkbox input:focus:checked ~ label { - color: rgba(0, 0, 0, 0.95) !important; -} - -.ui.slider.checkbox input:focus:checked ~ label:before { - background-color: #000000 !important; -} - -/*-------------- - Toggle - ---------------*/ - -.ui.toggle.checkbox { - min-height: 1.5rem; -} - -/* Input */ - -.ui.toggle.checkbox input { - width: 3.5rem; - height: 1.5rem; -} - -/* Label */ - -.ui.toggle.checkbox label { - min-height: 1.5rem; - padding-left: 4.5rem; - color: rgba(0, 0, 0, 0.87); -} - -.ui.toggle.checkbox label { - padding-top: 0.15em; -} - -/* Switch */ - -.ui.toggle.checkbox label:before { - display: block; - position: absolute; - content: ''; - z-index: 1; - transform: none; - border: none; - top: 0; - background: rgba(0, 0, 0, 0.05); - box-shadow: none; - width: 3.5rem; - height: 1.5rem; - border-radius: 500rem; -} - -/* Handle */ - -.ui.toggle.checkbox label:after { - background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05)); - position: absolute; - content: '' !important; - opacity: 1; - z-index: 2; - border: none; - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; - 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; - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; -} - -/* Focus */ - -.ui.toggle.checkbox input:focus ~ label:before { - background-color: rgba(0, 0, 0, 0.15); - border: none; -} - -/* Hover */ - -.ui.toggle.checkbox label:hover::before { - background-color: rgba(0, 0, 0, 0.15); - border: none; -} - -/* Active */ - -.ui.toggle.checkbox input:checked ~ label { - color: rgba(0, 0, 0, 0.95) !important; -} - -.ui.toggle.checkbox input:checked ~ label:before { - background-color: #2185D0 !important; -} - -.ui.toggle.checkbox input:checked ~ label:after { - left: 2.15rem; - box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), 0 0 0 1px rgba(34, 36, 38, 0.15) inset; -} - -/* Active Focus */ - -.ui.toggle.checkbox input:focus:checked ~ label { - color: rgba(0, 0, 0, 0.95) !important; -} - -.ui.toggle.checkbox input:focus:checked ~ label:before { - background-color: #0d71bb !important; -} - -/******************************* - Variations -*******************************/ - -/*-------------- - Fitted - ---------------*/ - -.ui.fitted.checkbox label { - padding-left: 0 !important; -} - -.ui.fitted.toggle.checkbox { - width: 3.5rem; -} - -.ui.fitted.slider.checkbox { - width: 3.5rem; -} - -/*-------------------- - Size ----------------------*/ - -.ui.mini.checkbox { - font-size: 0.78571429em; -} - -.ui.tiny.checkbox { - font-size: 0.85714286em; -} - -.ui.small.checkbox { - font-size: 0.92857143em; -} - -.ui.large.checkbox { - font-size: 1.14285714em; -} - -.ui.large.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.large.checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.large.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, -.ui.large.checkbox:not(.slider):not(.toggle):not(.radio) label:before { - transform: scale(1.14285714); - transform-origin: left; -} - -.ui.large.form .checkbox.radio label:before, -.ui.large.checkbox.radio label:before { - transform: scale(1.14285714); - transform-origin: left; -} - -.ui.large.form .checkbox.radio label:after, -.ui.large.checkbox.radio label:after { - transform: scale(0.57142857); - transform-origin: left; - left: 0.33571429em; -} - -.ui.big.checkbox { - font-size: 1.28571429em; -} - -.ui.big.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.big.checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.big.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, -.ui.big.checkbox:not(.slider):not(.toggle):not(.radio) label:before { - transform: scale(1.28571429); - transform-origin: left; -} - -.ui.big.form .checkbox.radio label:before, -.ui.big.checkbox.radio label:before { - transform: scale(1.28571429); - transform-origin: left; -} - -.ui.big.form .checkbox.radio label:after, -.ui.big.checkbox.radio label:after { - transform: scale(0.64285714); - transform-origin: left; - left: 0.37142857em; -} - -.ui.huge.checkbox { - font-size: 1.42857143em; -} - -.ui.huge.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.huge.checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.huge.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, -.ui.huge.checkbox:not(.slider):not(.toggle):not(.radio) label:before { - transform: scale(1.42857143); - transform-origin: left; -} - -.ui.huge.form .checkbox.radio label:before, -.ui.huge.checkbox.radio label:before { - transform: scale(1.42857143); - transform-origin: left; -} - -.ui.huge.form .checkbox.radio label:after, -.ui.huge.checkbox.radio label:after { - transform: scale(0.71428571); - transform-origin: left; - left: 0.40714286em; -} - -.ui.massive.checkbox { - font-size: 1.71428571em; -} - -.ui.massive.form .checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.massive.checkbox:not(.slider):not(.toggle):not(.radio) label:after, -.ui.massive.form .checkbox:not(.slider):not(.toggle):not(.radio) label:before, -.ui.massive.checkbox:not(.slider):not(.toggle):not(.radio) label:before { - transform: scale(1.71428571); - transform-origin: left; -} - -.ui.massive.form .checkbox.radio label:before, -.ui.massive.checkbox.radio label:before { - transform: scale(1.71428571); - transform-origin: left; -} - -.ui.massive.form .checkbox.radio label:after, -.ui.massive.checkbox.radio label:after { - transform: scale(0.85714286); - transform-origin: left; - left: 0.47857143em; -} - -/******************************* - Theme Overrides -*******************************/ - -@font-face { - font-family: 'Checkbox'; - src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBD8AAAC8AAAAYGNtYXAYVtCJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zn4huwUAAAF4AAABYGhlYWQGPe1ZAAAC2AAAADZoaGVhB30DyAAAAxAAAAAkaG10eBBKAEUAAAM0AAAAHGxvY2EAmgESAAADUAAAABBtYXhwAAkALwAAA2AAAAAgbmFtZSC8IugAAAOAAAABknBvc3QAAwAAAAAFFAAAACAAAwMTAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADoAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6AL//f//AAAAAAAg6AD//f//AAH/4xgEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAEUAUQO7AvgAGgAAARQHAQYjIicBJjU0PwE2MzIfAQE2MzIfARYVA7sQ/hQQFhcQ/uMQEE4QFxcQqAF2EBcXEE4QAnMWEP4UEBABHRAXFhBOEBCoAXcQEE4QFwAAAAABAAABbgMlAkkAFAAAARUUBwYjISInJj0BNDc2MyEyFxYVAyUQEBf9SRcQEBAQFwK3FxAQAhJtFxAQEBAXbRcQEBAQFwAAAAABAAAASQMlA24ALAAAARUUBwYrARUUBwYrASInJj0BIyInJj0BNDc2OwE1NDc2OwEyFxYdATMyFxYVAyUQEBfuEBAXbhYQEO4XEBAQEBfuEBAWbhcQEO4XEBACEm0XEBDuFxAQEBAX7hAQF20XEBDuFxAQEBAX7hAQFwAAAQAAAAIAAHRSzT9fDzz1AAsEAAAAAADRsdR3AAAAANGx1HcAAAAAA7sDbgAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADuwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAABFAyUAAAMlAAAAAAAAAAoAFAAeAE4AcgCwAAEAAAAHAC0AAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAIAAAAAQAAAAAAAgAHAGkAAQAAAAAAAwAIADkAAQAAAAAABAAIAH4AAQAAAAAABQALABgAAQAAAAAABgAIAFEAAQAAAAAACgAaAJYAAwABBAkAAQAQAAgAAwABBAkAAgAOAHAAAwABBAkAAwAQAEEAAwABBAkABAAQAIYAAwABBAkABQAWACMAAwABBAkABgAQAFkAAwABBAkACgA0ALBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhWZXJzaW9uIDIuMABWAGUAcgBzAGkAbwBuACAAMgAuADBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhDaGVja2JveABDAGgAZQBjAGsAYgBvAHhSZWd1bGFyAFIAZQBnAHUAbABhAHJDaGVja2JveABDAGgAZQBjAGsAYgBvAHhGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype'); -} - -/* Checkmark */ - -.ui.checkbox label:after, -.ui.checkbox .box:after { - font-family: 'Checkbox'; -} - -/* Checked */ - -.ui.checkbox input:checked ~ .box:after, -.ui.checkbox input:checked ~ label:after { - content: '\e800'; -} - -/* Indeterminate */ - -.ui.checkbox input:indeterminate ~ .box:after, -.ui.checkbox input:indeterminate ~ label:after { - font-size: 12px; - content: '\e801'; -} - -/* UTF Reference -.check:before { content: '\e800'; } -.dash:before { content: '\e801'; } -.plus:before { content: '\e802'; } -*/ - /******************************* Site Overrides *******************************/ diff --git a/web_src/fomantic/build/semantic.js b/web_src/fomantic/build/semantic.js index 1199e9c82f..c150c8d9db 100644 --- a/web_src/fomantic/build/semantic.js +++ b/web_src/fomantic/build/semantic.js @@ -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 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 z-index to be unselectable'); - $input.addClass(className.hidden); - } - }, - show: { - input: function() { - module.verbose('Modifying 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 = $('