Merge branch 'main' into telackey/sort

This commit is contained in:
Thomas E Lackey 2025-03-11 11:33:47 -05:00 committed by GitHub
commit 1216ff96e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 13833 additions and 25616 deletions

View File

@ -79,18 +79,7 @@ cpu.out
/public/assets/fonts /public/assets/fonts
/public/assets/img/avatar /public/assets/img/avatar
/vendor /vendor
/web_src/fomantic/node_modules /web_src/fomantic
/web_src/fomantic/build/*
!/web_src/fomantic/build/semantic.js
!/web_src/fomantic/build/semantic.css
!/web_src/fomantic/build/themes
/web_src/fomantic/build/themes/*
!/web_src/fomantic/build/themes/default
/web_src/fomantic/build/themes/default/assets/*
!/web_src/fomantic/build/themes/default/assets/fonts
/web_src/fomantic/build/themes/default/assets/fonts/*
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
/VERSION /VERSION
/.air /.air
/.go-licenses /.go-licenses

2
.gitattributes vendored
View File

@ -5,7 +5,5 @@
/public/assets/img/svg/*.svg linguist-generated /public/assets/img/svg/*.svg linguist-generated
/templates/swagger/v1_json.tmpl linguist-generated /templates/swagger/v1_json.tmpl linguist-generated
/vendor/** -text -eol linguist-vendored /vendor/** -text -eol linguist-vendored
/web_src/fomantic/build/** linguist-generated
/web_src/fomantic/_site/globals/site.variables linguist-language=Less
/web_src/js/vendor/** -text -eol linguist-vendored /web_src/js/vendor/** -text -eol linguist-vendored
Dockerfile.* linguist-language=Dockerfile Dockerfile.* linguist-language=Dockerfile

12
.gitignore vendored
View File

@ -84,18 +84,6 @@ cpu.out
/public/assets/fonts /public/assets/fonts
/public/assets/licenses.txt /public/assets/licenses.txt
/vendor /vendor
/web_src/fomantic/node_modules
/web_src/fomantic/build/*
!/web_src/fomantic/build/semantic.js
!/web_src/fomantic/build/semantic.css
!/web_src/fomantic/build/themes
/web_src/fomantic/build/themes/*
!/web_src/fomantic/build/themes/default
/web_src/fomantic/build/themes/default/assets/*
!/web_src/fomantic/build/themes/default/assets/fonts
/web_src/fomantic/build/themes/default/assets/fonts/*
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
/VERSION /VERSION
/.air /.air
/.go-licenses /.go-licenses

View File

@ -115,8 +115,6 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/)) GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
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
@ -140,7 +138,7 @@ TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR) TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR)
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
@ -847,19 +845,6 @@ update-py: node-check | node_modules ## update py dependencies
poetry install poetry install
@touch .venv @touch .venv
.PHONY: fomantic
fomantic: ## build fomantic files
rm -rf $(FOMANTIC_WORK_DIR)/build
cd $(FOMANTIC_WORK_DIR) && npm install --no-save
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
$(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
$(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
.PHONY: webpack .PHONY: webpack
webpack: $(WEBPACK_DEST) ## build webpack files webpack: $(WEBPACK_DEST) ## build webpack files

View File

@ -194,7 +194,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event. // CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore. // It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error { func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
// Find all runs in the specified repository, reference, and workflow with non-final status // Find all runs in the specified repository, reference, and workflow with non-final status
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{ runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
RepoID: repoID, RepoID: repoID,
@ -204,14 +204,16 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked}, Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
}) })
if err != nil { if err != nil {
return err return nil, err
} }
// If there are no runs found, there's no need to proceed with cancellation, so return nil. // If there are no runs found, there's no need to proceed with cancellation, so return nil.
if total == 0 { if total == 0 {
return nil return nil, nil
} }
cancelledJobs := make([]*ActionRunJob, 0, total)
// Iterate over each found run and cancel its associated jobs. // Iterate over each found run and cancel its associated jobs.
for _, run := range runs { for _, run := range runs {
// Find all jobs associated with the current run. // Find all jobs associated with the current run.
@ -219,7 +221,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
RunID: run.ID, RunID: run.ID,
}) })
if err != nil { if err != nil {
return err return cancelledJobs, err
} }
// Iterate over each job and attempt to cancel it. // Iterate over each job and attempt to cancel it.
@ -238,27 +240,29 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
// Update the job's status and stopped time in the database. // Update the job's status and stopped time in the database.
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped") n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
if err != nil { if err != nil {
return err return cancelledJobs, err
} }
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again. // If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
if n == 0 { if n == 0 {
return fmt.Errorf("job has changed, try again") return cancelledJobs, fmt.Errorf("job has changed, try again")
} }
cancelledJobs = append(cancelledJobs, job)
// Continue with the next job. // Continue with the next job.
continue continue
} }
// If the job has an associated task, try to stop the task, effectively cancelling the job. // If the job has an associated task, try to stop the task, effectively cancelling the job.
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil { if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
return err return cancelledJobs, err
} }
cancelledJobs = append(cancelledJobs, job)
} }
} }
// Return nil to indicate successful cancellation of all running and waiting jobs. // Return nil to indicate successful cancellation of all running and waiting jobs.
return nil return cancelledJobs, nil
} }
// InsertRun inserts a run // InsertRun inserts a run

View File

@ -117,21 +117,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
return committer.Commit() return committer.Commit()
} }
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error { func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([]*ActionRunJob, error) {
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks // If actions disabled when there is schedule task, this will remove the outdated schedule tasks
// There is no other place we can do this because the app.ini will be changed manually // There is no other place we can do this because the app.ini will be changed manually
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil { if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
return fmt.Errorf("DeleteCronTaskByRepo: %v", err) return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
} }
// cancel running cron jobs of this repository and delete old schedules // cancel running cron jobs of this repository and delete old schedules
if err := CancelPreviousJobs( jobs, err := CancelPreviousJobs(
ctx, ctx,
repo.ID, repo.ID,
repo.DefaultBranch, repo.DefaultBranch,
"", "",
webhook_module.HookEventSchedule, webhook_module.HookEventSchedule,
); err != nil { )
return fmt.Errorf("CancelPreviousJobs: %v", err) if err != nil {
return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
} }
return nil return jobs, nil
} }

View File

@ -21,6 +21,7 @@ type materialIconRulesData struct {
FileNames map[string]string `json:"fileNames"` FileNames map[string]string `json:"fileNames"`
FolderNames map[string]string `json:"folderNames"` FolderNames map[string]string `json:"folderNames"`
FileExtensions map[string]string `json:"fileExtensions"` FileExtensions map[string]string `json:"fileExtensions"`
LanguageIDs map[string]string `json:"languageIds"`
} }
type MaterialIconProvider struct { type MaterialIconProvider struct {
@ -107,25 +108,40 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr
return svg.RenderHTML("octicon-file") return svg.RenderHTML("octicon-file")
} }
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
if _, ok := m.svgs[s]; ok {
return s
}
if s, ok := m.rules.LanguageIDs[s]; ok {
if _, ok = m.svgs[s]; ok {
return s
}
}
return ""
}
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string { func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
iconsData := m.rules
fileNameLower := strings.ToLower(path.Base(name)) fileNameLower := strings.ToLower(path.Base(name))
if isDir { if isDir {
if s, ok := iconsData.FolderNames[fileNameLower]; ok { if s, ok := m.rules.FolderNames[fileNameLower]; ok {
return s return s
} }
return "folder" return "folder"
} }
if s, ok := iconsData.FileNames[fileNameLower]; ok { if s, ok := m.rules.FileNames[fileNameLower]; ok {
return s if s = m.findIconNameWithLangID(s); s != "" {
return s
}
} }
for i := len(fileNameLower) - 1; i >= 0; i-- { for i := len(fileNameLower) - 1; i >= 0; i-- {
if fileNameLower[i] == '.' { if fileNameLower[i] == '.' {
ext := fileNameLower[i+1:] ext := fileNameLower[i+1:]
if s, ok := iconsData.FileExtensions[ext]; ok { if s, ok := m.rules.FileExtensions[ext]; ok {
return s if s = m.findIconNameWithLangID(s); s != "" {
return s
}
} }
} }
} }

View File

@ -21,4 +21,6 @@ func TestFindIconName(t *testing.T) {
p := fileicon.DefaultMaterialIconProvider() p := fileicon.DefaultMaterialIconProvider()
assert.Equal(t, "php", p.FindIconName("foo.php", false)) assert.Equal(t, "php", p.FindIconName("foo.php", false))
assert.Equal(t, "php", p.FindIconName("foo.PHP", false)) assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
assert.Equal(t, "javascript", p.FindIconName("foo.js", false))
assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false))
} }

View File

@ -7038,107 +7038,201 @@
"gnu": "gnuplot", "gnu": "gnuplot",
"yaml-tmlanguage": "yaml", "yaml-tmlanguage": "yaml",
"tmlanguage": "xml", "tmlanguage": "xml",
"git": "git", "cljx": "clojure",
"git-commit": "git",
"git-rebase": "git",
"ignore": "git",
"github-actions-workflow": "github-actions-workflow",
"yaml": "yaml",
"spring-boot-properties-yaml": "yaml",
"ansible": "yaml",
"ansible-jinja": "yaml",
"matlab": "matlab",
"makefile": "settings",
"spring-boot-properties": "settings",
"diff": "diff",
"razor": "razor",
"aspnetcorerazor": "razor",
"python": "python",
"javascript": "javascript",
"typescript": "typescript",
"handlebars": "handlebars",
"perl": "perl",
"perl6": "perl",
"haxe": "haxe",
"hxml": "haxe",
"puppet": "puppet",
"elixir": "elixir",
"livescript": "livescript",
"erlang": "erlang",
"julia": "julia",
"purescript": "purescript",
"stylus": "stylus",
"robotframework": "robot",
"testoutput": "visualstudio",
"solidity": "solidity",
"autoit": "autoit",
"terraform": "terraform",
"cucumber": "cucumber",
"postcss": "postcss",
"lang-cfml": "coldfusion",
"haskell": "haskell",
"ruby": "ruby",
"php": "php",
"hack": "hack",
"javascriptreact": "react",
"processing": "processing",
"django-html": "django",
"django-txt": "django",
"html": "html",
"gdscript": "godot",
"gdresource": "godot-assets",
"viml": "vim",
"prolog": "prolog",
"pawn": "pawn",
"reason": "reason",
"reason_lisp": "reason",
"doctex": "tex",
"latex": "tex",
"latex-expl3": "tex",
"apex": "salesforce",
"dockercompose": "docker",
"shellscript": "console",
"objective-c": "objective-c",
"objective-cpp": "objective-cpp",
"coffeescript": "coffee",
"fsharp": "fsharp",
"editorconfig": "editorconfig",
"clojure": "clojure", "clojure": "clojure",
"pip-requirements": "python-misc", "edn": "clojure",
"vue-postcss": "vue", "cppm": "cpp",
"vue-html": "vue", "ccm": "cpp",
"bibtex": "lib", "cxxm": "cpp",
"bibtex-style": "lib", "c++m": "cpp",
"jupyter": "jupyter", "ipp": "cpp",
"plaintext": "document", "ixx": "cpp",
"powershell": "powershell", "tpp": "cpp",
"rsweave": "r", "txx": "cpp",
"rust": "rust", "hpp.in": "cpp",
"ssh_config": "lock", "h.in": "cpp",
"typescriptreact": "react_ts", "diff": "diff",
"search-result": "search", "rej": "diff",
"rescript": "rescript", "fsscript": "fsharp",
"twee3": "twine", "gitignore_global": "ignore",
"twee3-harlowe-3": "twine", "gitignore": "ignore",
"twee3-chapbook-1": "twine", "git-blame-ignore-revs": "ignore",
"twee3-sugarcube-2": "twine", "gvy": "groovy",
"grain": "grain", "nf": "groovy",
"lolcode": "lolcode", "handlebars": "handlebars",
"idris": "idris", "hjs": "handlebars",
"text-gemini": "gemini", "hlsli": "hlsl",
"wolfram": "wolframlanguage", "fx": "hlsl",
"shaderlab": "shader", "fxh": "hlsl",
"cadence": "cadence", "vsh": "hlsl",
"stylable": "stylable", "psh": "hlsl",
"capnb": "cds", "cginc": "hlsl",
"cds-markdown-injection": "cds", "compute": "hlsl",
"concourse-pipeline-yaml": "concourse", "html": "html",
"concourse-task-yaml": "concourse", "shtml": "html",
"systemd-conf": "systemd", "xht": "html",
"systemd-unit-file": "systemd", "aspx": "html",
"hosts": "hosts", "jshtm": "html",
"ahk2": "ahk2", "volt": "html",
"gnuplot": "gnuplot" "rhtml": "html",
"directory": "properties",
"gitattributes": "properties",
"gitconfig": "properties",
"gitmodules": "properties",
"editorconfig": "properties",
"repo": "properties",
"jav": "java",
"js": "javascript",
"es6": "javascript",
"cjs": "javascript",
"pac": "javascript",
"bowerrc": "json",
"jscsrc": "json",
"webmanifest": "json",
"ts.map": "json",
"har": "json",
"jslintrc": "json",
"jsonld": "json",
"geojson": "json",
"vuerc": "json",
"eslintrc": "jsonc",
"eslintrc.json": "jsonc",
"jsfmtrc": "jsonc",
"jshintrc": "jsonc",
"hintrc": "jsonc",
"babelrc": "jsonc",
"jmd": "juliamarkdown",
"cls": "tex",
"bbx": "tex",
"cbx": "tex",
"ctx": "latex",
"mak": "makefile",
"mkd": "markdown",
"mdwn": "markdown",
"mdown": "markdown",
"markdn": "markdown",
"mdtxt": "markdown",
"mdtext": "markdown",
"workbook": "markdown",
"npmignore": "ignore",
"npmrc": "properties",
"m": "objective-c",
"mm": "objective-cpp",
"pod": "perl",
"t": "perl",
"psgi": "perl",
"rakumod": "raku",
"rakutest": "raku",
"rakudoc": "raku",
"nqp": "raku",
"p6": "raku",
"pl6": "raku",
"pm6": "raku",
"php": "php",
"php4": "php",
"php5": "php",
"phtml": "php",
"ctp": "php",
"psrc": "powershell",
"rpy": "python",
"pyw": "python",
"cpy": "python",
"gyp": "python",
"gypi": "python",
"pyi": "python",
"ipy": "python",
"pyt": "python",
"rhistory": "r",
"rprofile": "r",
"rt": "r",
"razor": "razor",
"rbx": "ruby",
"rjs": "ruby",
"gemspec": "ruby",
"rake": "ruby",
"ru": "ruby",
"podspec": "ruby",
"rbi": "ruby",
"bashrc": "shellscript",
"bash_aliases": "shellscript",
"bash_profile": "shellscript",
"bash_login": "shellscript",
"ebuild": "shellscript",
"eclass": "shellscript",
"profile": "shellscript",
"bash_logout": "shellscript",
"xprofile": "shellscript",
"xsession": "shellscript",
"xsessionrc": "shellscript",
"zshrc": "shellscript",
"zprofile": "shellscript",
"zlogin": "shellscript",
"zlogout": "shellscript",
"zshenv": "shellscript",
"zsh-theme": "shellscript",
"cshrc": "shellscript",
"tcshrc": "shellscript",
"yashrc": "shellscript",
"yash_profile": "shellscript",
"dsql": "sql",
"ts": "typescript",
"cts": "typescript",
"mts": "typescript",
"brs": "vb",
"bas": "vb",
"vba": "vb",
"ascx": "xml",
"atom": "xml",
"axml": "xml",
"axaml": "xml",
"bpmn": "xml",
"csl": "xml",
"csproj.user": "xml",
"dita": "xml",
"ditamap": "xml",
"ent": "xml",
"dtml": "xml",
"fxml": "xml",
"isml": "xml",
"jmx": "xml",
"launch": "xml",
"menu": "xml",
"nuspec": "xml",
"opml": "xml",
"owl": "xml",
"proj": "xml",
"pt": "xml",
"publishsettings": "xml",
"pubxml": "xml",
"pubxml.user": "xml",
"rdf": "xml",
"rng": "xml",
"rss": "xml",
"shproj": "xml",
"storyboard": "xml",
"targets": "xml",
"tld": "xml",
"tmx": "xml",
"vbproj": "xml",
"vbproj.user": "xml",
"wsdl": "xml",
"wxi": "xml",
"wxl": "xml",
"wxs": "xml",
"xbl": "xml",
"xib": "xml",
"xliff": "xml",
"xpdl": "xml",
"xul": "xml",
"xoml": "xml",
"yaml": "yaml",
"yml": "yaml",
"eyaml": "yaml",
"eyml": "yaml",
"cff": "yaml",
"yaml-tmpreferences": "yaml",
"yaml-tmtheme": "yaml",
"winget": "yaml"
}, },
"fileNames": { "fileNames": {
".pug-lintrc": "pug", ".pug-lintrc": "pug",
@ -9015,7 +9109,11 @@
"caddyfile": "caddy", "caddyfile": "caddy",
"pklproject": "pkl", "pklproject": "pkl",
"pklproject.deps.json": "pkl", "pklproject.deps.json": "pkl",
".github/funding.yml": "github-sponsors" ".github/funding.yml": "github-sponsors",
"language-configuration.json": "jsonc",
"icon-theme.json": "jsonc",
"color-theme.json": "jsonc",
"*.log.?": "log"
}, },
"languageIds": { "languageIds": {
"git": "git", "git": "git",

View File

@ -12,7 +12,6 @@ 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"
@ -1049,7 +1048,7 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return err return err
} }
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil { if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) 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)

View File

@ -11,7 +11,6 @@ import (
"strings" "strings"
"time" "time"
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"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
@ -906,7 +905,7 @@ func SettingsPost(ctx *context.Context) {
return return
} }
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil { if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err) log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
} }

View File

@ -10,10 +10,12 @@ import (
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"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"
) )
// StopZombieTasks stops the task which have running status, but haven't been updated for a long time // StopZombieTasks stops the task which have running status, but haven't been updated for a long time
@ -32,6 +34,24 @@ func StopEndlessTasks(ctx context.Context) error {
}) })
} }
func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.ActionRunJob) {
if len(jobs) > 0 {
CreateCommitStatus(ctx, jobs...)
}
}
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
jobs, err := actions_model.CancelPreviousJobs(ctx, repoID, ref, workflowID, event)
notifyWorkflowJobStatusUpdate(ctx, jobs)
return err
}
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
jobs, err := actions_model.CleanRepoScheduleTasks(ctx, repo)
notifyWorkflowJobStatusUpdate(ctx, jobs)
return err
}
func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error { func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
tasks, err := db.Find[actions_model.ActionTask](ctx, opts) tasks, err := db.Find[actions_model.ActionTask](ctx, opts)
if err != nil { if err != nil {
@ -67,7 +87,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
remove() remove()
} }
CreateCommitStatus(ctx, jobs...) notifyWorkflowJobStatusUpdate(ctx, jobs)
return nil return nil
} }

View File

@ -136,7 +136,7 @@ func notify(ctx context.Context, input *notifyInput) error {
return nil return nil
} }
if unit_model.TypeActions.UnitGlobalDisabled() { if unit_model.TypeActions.UnitGlobalDisabled() {
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil { if err := CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
log.Error("CleanRepoScheduleTasks: %v", err) log.Error("CleanRepoScheduleTasks: %v", err)
} }
return nil return nil
@ -341,7 +341,7 @@ func handleWorkflows(
// cancel running jobs if the event is push or pull_request_sync // cancel running jobs if the event is push or pull_request_sync
if run.Event == webhook_module.HookEventPush || if run.Event == webhook_module.HookEventPush ||
run.Event == webhook_module.HookEventPullRequestSync { run.Event == webhook_module.HookEventPullRequestSync {
if err := actions_model.CancelPreviousJobs( if err := CancelPreviousJobs(
ctx, ctx,
run.RepoID, run.RepoID,
run.Ref, run.Ref,
@ -472,7 +472,7 @@ func handleSchedules(
log.Error("CountSchedules: %v", err) log.Error("CountSchedules: %v", err)
return err return err
} else if count > 0 { } else if count > 0 {
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil { if err := CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
log.Error("CleanRepoScheduleTasks: %v", err) log.Error("CleanRepoScheduleTasks: %v", err)
} }
} }

View File

@ -55,7 +55,7 @@ func startTasks(ctx context.Context) error {
// cancel running jobs if the event is push // cancel running jobs if the event is push
if row.Schedule.Event == webhook_module.HookEventPush { if row.Schedule.Event == webhook_module.HookEventPush {
// cancel running jobs of the same workflow // cancel running jobs of the same workflow
if err := actions_model.CancelPreviousJobs( if err := CancelPreviousJobs(
ctx, ctx,
row.RepoID, row.RepoID,
row.Schedule.Ref, row.Schedule.Ref,

View File

@ -256,7 +256,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
} }
// cancel running jobs of the same workflow // cancel running jobs of the same workflow
if err := actions_model.CancelPreviousJobs( if err := CancelPreviousJobs(
ctx, ctx,
run.RepoID, run.RepoID,
run.Ref, run.Ref,

View File

@ -30,6 +30,7 @@ import (
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
actions_service "code.gitea.io/gitea/services/actions"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
release_service "code.gitea.io/gitea/services/release" release_service "code.gitea.io/gitea/services/release"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
@ -452,7 +453,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
log.Error("DeleteCronTaskByRepo: %v", err) log.Error("DeleteCronTaskByRepo: %v", err)
} }
// cancel running cron jobs of this repository and delete old schedules // cancel running cron jobs of this repository and delete old schedules
if err := actions_model.CancelPreviousJobs( if err := actions_service.CancelPreviousJobs(
ctx, ctx,
repo.ID, repo.ID,
from, from,
@ -639,7 +640,7 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR
log.Error("DeleteCronTaskByRepo: %v", err) log.Error("DeleteCronTaskByRepo: %v", err)
} }
// cancel running cron jobs of this repository and delete old schedules // cancel running cron jobs of this repository and delete old schedules
if err := actions_model.CancelPreviousJobs( if err := actions_service.CancelPreviousJobs(
ctx, ctx,
repo.ID, repo.ID,
oldDefaultBranchName, oldDefaultBranchName,

View File

@ -7,7 +7,6 @@ import (
"context" "context"
"slices" "slices"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
@ -29,7 +28,7 @@ func UpdateRepositoryUnits(ctx context.Context, repo *repo_model.Repository, uni
} }
if slices.Contains(deleteUnitTypes, unit.TypeActions) { if slices.Contains(deleteUnitTypes, unit.TypeActions) {
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil { if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
log.Error("CleanRepoScheduleTasks: %v", err) log.Error("CleanRepoScheduleTasks: %v", err)
} }
} }

View File

@ -7,28 +7,28 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/tests"
ap "github.com/go-ap/activitypub" ap "github.com/go-ap/activitypub"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestActivityPubPerson(t *testing.T) { func TestActivityPubPerson(t *testing.T) {
setting.Federation.Enabled = true defer tests.PrepareTestEnv(t)()
testWebRoutes = routers.NormalRoutes() defer test.MockVariableValue(&setting.Federation.Enabled, true)()
defer func() { defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes() t.Run("ExistingPerson", func(t *testing.T) {
}() defer tests.PrintCurrentTest(t)()
onGiteaRun(t, func(*testing.T, *url.URL) {
userID := 2 userID := 2
username := "user2" username := "user2"
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/activitypub/user-id/%v", userID)) req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/activitypub/user-id/%v", userID))
@ -56,41 +56,18 @@ func TestActivityPubPerson(t *testing.T) {
assert.NotNil(t, pubKeyPem) assert.NotNil(t, pubKeyPem)
assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem) assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem)
}) })
} t.Run("MissingPerson", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
func TestActivityPubMissingPerson(t *testing.T) {
setting.Federation.Enabled = true
testWebRoutes = routers.NormalRoutes()
defer func() {
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes()
}()
onGiteaRun(t, func(*testing.T, *url.URL) {
req := NewRequest(t, "GET", "/api/v1/activitypub/user-id/999999999") req := NewRequest(t, "GET", "/api/v1/activitypub/user-id/999999999")
resp := MakeRequest(t, req, http.StatusNotFound) resp := MakeRequest(t, req, http.StatusNotFound)
assert.Contains(t, resp.Body.String(), "user does not exist") assert.Contains(t, resp.Body.String(), "user does not exist")
}) })
} t.Run("MissingPersonInbox", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
srv := httptest.NewServer(testWebRoutes)
defer srv.Close()
defer test.MockVariableValue(&setting.AppURL, srv.URL+"/")()
func TestActivityPubPersonInbox(t *testing.T) {
setting.Federation.Enabled = true
testWebRoutes = routers.NormalRoutes()
defer func() {
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes()
}()
srv := httptest.NewServer(testWebRoutes)
defer srv.Close()
onGiteaRun(t, func(*testing.T, *url.URL) {
appURL := setting.AppURL
setting.AppURL = srv.URL + "/"
defer func() {
setting.Database.LogSQL = false
setting.AppURL = appURL
}()
username1 := "user1" username1 := "user1"
ctx := t.Context() ctx := t.Context()
user1, err := user_model.GetUserByName(ctx, username1) user1, err := user_model.GetUserByName(ctx, username1)

View File

@ -5,7 +5,6 @@ package integration
import ( import (
"net/http" "net/http"
"net/url"
"strings" "strings"
"testing" "testing"
@ -19,10 +18,12 @@ import (
) )
func TestAPIAdminOrgCreate(t *testing.T) { func TestAPIAdminOrgCreate(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) { defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1") session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
t.Run("CreateOrg", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
org := api.CreateOrgOption{ org := api.CreateOrgOption{
UserName: "user2_org", UserName: "user2_org",
FullName: "User2's organization", FullName: "User2's organization",
@ -51,13 +52,8 @@ func TestAPIAdminOrgCreate(t *testing.T) {
FullName: org.FullName, FullName: org.FullName,
}) })
}) })
} t.Run("CreateBadVisibility", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
func TestAPIAdminOrgCreateBadVisibility(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
org := api.CreateOrgOption{ org := api.CreateOrgOption{
UserName: "user2_org", UserName: "user2_org",
FullName: "User2's organization", FullName: "User2's organization",
@ -70,22 +66,21 @@ func TestAPIAdminOrgCreateBadVisibility(t *testing.T) {
AddTokenAuth(token) AddTokenAuth(token)
MakeRequest(t, req, http.StatusUnprocessableEntity) MakeRequest(t, req, http.StatusUnprocessableEntity)
}) })
} t.Run("CreateNotAdmin", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
func TestAPIAdminOrgCreateNotAdmin(t *testing.T) { nonAdminUsername := "user2"
defer tests.PrepareTestEnv(t)() session := loginUser(t, nonAdminUsername)
nonAdminUsername := "user2" token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
session := loginUser(t, nonAdminUsername) org := api.CreateOrgOption{
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) UserName: "user2_org",
org := api.CreateOrgOption{ FullName: "User2's organization",
UserName: "user2_org", Description: "This organization created by admin for user2",
FullName: "User2's organization", Website: "https://try.gitea.io",
Description: "This organization created by admin for user2", Location: "Shanghai",
Website: "https://try.gitea.io", Visibility: "public",
Location: "Shanghai", }
Visibility: "public", req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org).
} AddTokenAuth(token)
req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). MakeRequest(t, req, http.StatusForbidden)
AddTokenAuth(token) })
MakeRequest(t, req, http.StatusForbidden)
} }

View File

@ -5,35 +5,31 @@ package integration
import ( import (
"net/http" "net/http"
"net/url"
"testing" "testing"
"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/test"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestNodeinfo(t *testing.T) { func TestNodeinfo(t *testing.T) {
setting.Federation.Enabled = true defer tests.PrepareTestEnv(t)()
testWebRoutes = routers.NormalRoutes() defer test.MockVariableValue(&setting.Federation.Enabled, true)()
defer func() { defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes()
}()
onGiteaRun(t, func(*testing.T, *url.URL) { req := NewRequest(t, "GET", "/api/v1/nodeinfo")
req := NewRequest(t, "GET", "/api/v1/nodeinfo") resp := MakeRequest(t, req, http.StatusOK)
resp := MakeRequest(t, req, http.StatusOK) VerifyJSONSchema(t, resp, "nodeinfo_2.1.json")
VerifyJSONSchema(t, resp, "nodeinfo_2.1.json")
var nodeinfo api.NodeInfo var nodeinfo api.NodeInfo
DecodeJSON(t, resp, &nodeinfo) DecodeJSON(t, resp, &nodeinfo)
assert.True(t, nodeinfo.OpenRegistrations) assert.True(t, nodeinfo.OpenRegistrations)
assert.Equal(t, "gitea", nodeinfo.Software.Name) assert.Equal(t, "gitea", nodeinfo.Software.Name)
assert.Equal(t, 29, nodeinfo.Usage.Users.Total) assert.Equal(t, 29, nodeinfo.Usage.Users.Total)
assert.Equal(t, 22, nodeinfo.Usage.LocalPosts) assert.Equal(t, 22, nodeinfo.Usage.LocalPosts)
assert.Equal(t, 3, nodeinfo.Usage.LocalComments) assert.Equal(t, 3, nodeinfo.Usage.LocalComments)
})
} }

View File

@ -4,7 +4,6 @@
package integration package integration
import ( import (
"net/url"
"strings" "strings"
"testing" "testing"
@ -14,15 +13,17 @@ 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"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestOrgCounts(t *testing.T) { func TestOrgCounts(t *testing.T) {
onGiteaRun(t, testOrgCounts) defer tests.PrepareTestEnv(t)()
testOrgCounts(t)
} }
func testOrgCounts(t *testing.T, u *url.URL) { func testOrgCounts(t *testing.T) {
orgOwner := "user2" orgOwner := "user2"
orgName := "testOrg" orgName := "testOrg"
orgCollaborator := "user4" orgCollaborator := "user4"

View File

@ -8,12 +8,15 @@ import (
"testing" "testing"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestListPullCommits(t *testing.T) { func TestListPullCommits(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user5") session := loginUser(t, "user5")
req := NewRequest(t, "GET", "/user2/repo1/pulls/3/commits/list") req := NewRequest(t, "GET", "/user2/repo1/pulls/3/commits/list")
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
@ -30,6 +33,7 @@ func TestListPullCommits(t *testing.T) {
assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.LastReviewCommitSha) assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.LastReviewCommitSha)
t.Run("CommitBlobExcerpt", func(t *testing.T) { t.Run("CommitBlobExcerpt", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req = NewRequest(t, "GET", "/user2/repo1/blob_excerpt/985f0301dba5e7b34be866819cd15ad3d8f508ee?last_left=0&last_right=0&left=2&right=2&left_hunk_size=2&right_hunk_size=2&path=README.md&style=split&direction=up") req = NewRequest(t, "GET", "/user2/repo1/blob_excerpt/985f0301dba5e7b34be866819cd15ad3d8f508ee?last_left=0&last_right=0&left=2&right=2&left_hunk_size=2&right_hunk_size=2&path=README.md&style=split&direction=up")
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), `<td class="lines-code lines-code-new"><code class="code-inner"># repo1</code>`) assert.Contains(t, resp.Body.String(), `<td class="lines-code lines-code-new"><code class="code-inner"># repo1</code>`)

View File

@ -10,78 +10,77 @@ import (
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"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/avatar" "code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestUserAvatar(t *testing.T) { func TestUserAvatar(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) { defer tests.PrepareTestEnv(t)()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org
seed := user2.Email seed := user2.Email
if len(seed) == 0 { if len(seed) == 0 {
seed = user2.Name seed = user2.Name
} }
img, err := avatar.RandomImage([]byte(seed)) img, err := avatar.RandomImage([]byte(seed))
if err != nil { if err != nil {
assert.NoError(t, err) assert.NoError(t, err)
return return
} }
session := loginUser(t, "user2") session := loginUser(t, "user2")
csrf := GetUserCSRFToken(t, session) csrf := GetUserCSRFToken(t, session)
imgData := &bytes.Buffer{} imgData := &bytes.Buffer{}
body := &bytes.Buffer{} body := &bytes.Buffer{}
// Setup multi-part // Setup multi-part
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
writer.WriteField("source", "local") writer.WriteField("source", "local")
part, err := writer.CreateFormFile("avatar", "avatar-for-testuseravatar.png") part, err := writer.CreateFormFile("avatar", "avatar-for-testuseravatar.png")
if err != nil { if err != nil {
assert.NoError(t, err) assert.NoError(t, err)
return return
} }
if err := png.Encode(imgData, img); err != nil { if err := png.Encode(imgData, img); err != nil {
assert.NoError(t, err) assert.NoError(t, err)
return return
} }
if _, err := io.Copy(part, imgData); err != nil { if _, err := io.Copy(part, imgData); err != nil {
assert.NoError(t, err) assert.NoError(t, err)
return return
} }
if err := writer.Close(); err != nil { if err := writer.Close(); err != nil {
assert.NoError(t, err) assert.NoError(t, err)
return return
} }
req := NewRequestWithBody(t, "POST", "/user/settings/avatar", body) req := NewRequestWithBody(t, "POST", "/user/settings/avatar", body)
req.Header.Add("X-Csrf-Token", csrf) req.Header.Add("X-Csrf-Token", csrf)
req.Header.Add("Content-Type", writer.FormDataContentType()) req.Header.Add("Content-Type", writer.FormDataContentType())
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org
req = NewRequest(t, "GET", user2.AvatarLinkWithSize(db.DefaultContext, 0)) req = NewRequest(t, "GET", user2.AvatarLinkWithSize(db.DefaultContext, 0))
_ = session.MakeRequest(t, req, http.StatusOK) _ = session.MakeRequest(t, req, http.StatusOK)
testGetAvatarRedirect(t, user2) testGetAvatarRedirect(t, user2)
// Can't test if the response matches because the image is re-generated on upload but checking that this at least doesn't give a 404 should be enough. // Can't test if the response matches because the image is re-generated on upload but checking that this at least doesn't give a 404 should be enough.
})
} }
func testGetAvatarRedirect(t *testing.T, user *user_model.User) { func testGetAvatarRedirect(t *testing.T, user *user_model.User) {

View File

@ -0,0 +1,570 @@
{
"pkg:bat": {
"bat": [
".bat",
".cmd"
]
},
"pkg:clojure": {
"clojure": [
".clj",
".cljs",
".cljc",
".cljx",
".clojure",
".edn"
]
},
"pkg:coffeescript": {
"coffeescript": [
".coffee",
".cson",
".iced"
]
},
"pkg:configuration-editing": {
"jsonc": [
".code-workspace",
"language-configuration.json",
"icon-theme.json",
"color-theme.json"
],
"json": [
".code-profile"
]
},
"pkg:cpp": {
"c": [
".c",
".i"
],
"cpp": [
".cpp",
".cppm",
".cc",
".ccm",
".cxx",
".cxxm",
".c++",
".c++m",
".hpp",
".hh",
".hxx",
".h++",
".h",
".ii",
".ino",
".inl",
".ipp",
".ixx",
".tpp",
".txx",
".hpp.in",
".h.in"
],
"cuda-cpp": [
".cu",
".cuh"
]
},
"pkg:csharp": {
"csharp": [
".cs",
".csx",
".cake"
]
},
"pkg:css": {
"css": [
".css"
]
},
"pkg:dart": {
"dart": [
".dart"
]
},
"pkg:diff": {
"diff": [
".diff",
".patch",
".rej"
]
},
"pkg:docker": {
"dockerfile": [
".dockerfile",
".containerfile"
]
},
"pkg:fsharp": {
"fsharp": [
".fs",
".fsi",
".fsx",
".fsscript"
]
},
"pkg:git-base": {
"ignore": [
".gitignore_global",
".gitignore",
".git-blame-ignore-revs"
]
},
"pkg:go": {
"go": [
".go"
]
},
"pkg:groovy": {
"groovy": [
".groovy",
".gvy",
".gradle",
".jenkinsfile",
".nf"
]
},
"pkg:handlebars": {
"handlebars": [
".handlebars",
".hbs",
".hjs"
]
},
"pkg:hlsl": {
"hlsl": [
".hlsl",
".hlsli",
".fx",
".fxh",
".vsh",
".psh",
".cginc",
".compute"
]
},
"pkg:html": {
"html": [
".html",
".htm",
".shtml",
".xhtml",
".xht",
".mdoc",
".jsp",
".asp",
".aspx",
".jshtm",
".volt",
".ejs",
".rhtml"
]
},
"pkg:ini": {
"ini": [
".ini"
],
"properties": [
".conf",
".properties",
".cfg",
".directory",
".gitattributes",
".gitconfig",
".gitmodules",
".editorconfig",
".repo"
]
},
"pkg:java": {
"java": [
".java",
".jav"
]
},
"pkg:javascript": {
"javascriptreact": [
".jsx"
],
"javascript": [
".js",
".es6",
".mjs",
".cjs",
".pac"
]
},
"pkg:json": {
"json": [
".json",
".bowerrc",
".jscsrc",
".webmanifest",
".js.map",
".css.map",
".ts.map",
".har",
".jslintrc",
".jsonld",
".geojson",
".ipynb",
".vuerc"
],
"jsonc": [
".jsonc",
".eslintrc",
".eslintrc.json",
".jsfmtrc",
".jshintrc",
".swcrc",
".hintrc",
".babelrc"
],
"jsonl": [
".jsonl",
".ndjson"
],
"snippets": [
".code-snippets"
]
},
"pkg:julia": {
"julia": [
".jl"
],
"juliamarkdown": [
".jmd"
]
},
"pkg:latex": {
"tex": [
".sty",
".cls",
".bbx",
".cbx"
],
"latex": [
".tex",
".ltx",
".ctx"
],
"bibtex": [
".bib"
]
},
"pkg:less": {
"less": [
".less"
]
},
"pkg:log": {
"log": [
".log",
"*.log.?"
]
},
"pkg:lua": {
"lua": [
".lua"
]
},
"pkg:make": {
"makefile": [
".mak",
".mk"
]
},
"pkg:markdown-basics": {
"markdown": [
".md",
".mkd",
".mdwn",
".mdown",
".markdown",
".markdn",
".mdtxt",
".mdtext",
".workbook"
]
},
"pkg:ms-vscode.js-debug": {
"wat": [
".wat",
".wasm"
]
},
"pkg:npm": {
"ignore": [
".npmignore"
],
"properties": [
".npmrc"
]
},
"pkg:objective-c": {
"objective-c": [
".m"
],
"objective-cpp": [
".mm"
]
},
"pkg:perl": {
"perl": [
".pl",
".pm",
".pod",
".t",
".PL",
".psgi"
],
"raku": [
".raku",
".rakumod",
".rakutest",
".rakudoc",
".nqp",
".p6",
".pl6",
".pm6"
]
},
"pkg:php": {
"php": [
".php",
".php4",
".php5",
".phtml",
".ctp"
]
},
"pkg:powershell": {
"powershell": [
".ps1",
".psm1",
".psd1",
".pssc",
".psrc"
]
},
"pkg:pug": {
"jade": [
".pug",
".jade"
]
},
"pkg:python": {
"python": [
".py",
".rpy",
".pyw",
".cpy",
".gyp",
".gypi",
".pyi",
".ipy",
".pyt"
]
},
"pkg:r": {
"r": [
".r",
".rhistory",
".rprofile",
".rt"
]
},
"pkg:razor": {
"razor": [
".cshtml",
".razor"
]
},
"pkg:restructuredtext": {
"restructuredtext": [
".rst"
]
},
"pkg:ruby": {
"ruby": [
".rb",
".rbx",
".rjs",
".gemspec",
".rake",
".ru",
".erb",
".podspec",
".rbi"
]
},
"pkg:rust": {
"rust": [
".rs"
]
},
"pkg:scss": {
"scss": [
".scss"
]
},
"pkg:search-result": {
"search-result": [
".code-search"
]
},
"pkg:shaderlab": {
"shaderlab": [
".shader"
]
},
"pkg:shellscript": {
"shellscript": [
".sh",
".bash",
".bashrc",
".bash_aliases",
".bash_profile",
".bash_login",
".ebuild",
".eclass",
".profile",
".bash_logout",
".xprofile",
".xsession",
".xsessionrc",
".Xsession",
".zsh",
".zshrc",
".zprofile",
".zlogin",
".zlogout",
".zshenv",
".zsh-theme",
".fish",
".ksh",
".csh",
".cshrc",
".tcshrc",
".yashrc",
".yash_profile"
]
},
"pkg:sql": {
"sql": [
".sql",
".dsql"
]
},
"pkg:swift": {
"swift": [
".swift"
]
},
"pkg:typescript-basics": {
"typescript": [
".ts",
".cts",
".mts"
],
"typescriptreact": [
".tsx"
],
"json": [
".tsbuildinfo"
]
},
"pkg:vb": {
"vb": [
".vb",
".brs",
".vbs",
".bas",
".vba"
]
},
"pkg:xml": {
"xml": [
".xml",
".xsd",
".ascx",
".atom",
".axml",
".axaml",
".bpmn",
".cpt",
".csl",
".csproj",
".csproj.user",
".dita",
".ditamap",
".dtd",
".ent",
".mod",
".dtml",
".fsproj",
".fxml",
".iml",
".isml",
".jmx",
".launch",
".menu",
".mxml",
".nuspec",
".opml",
".owl",
".proj",
".props",
".pt",
".publishsettings",
".pubxml",
".pubxml.user",
".rbxlx",
".rbxmx",
".rdf",
".rng",
".rss",
".shproj",
".storyboard",
".svg",
".targets",
".tld",
".tmx",
".vbproj",
".vbproj.user",
".vcxproj",
".vcxproj.filters",
".wsdl",
".wxi",
".wxl",
".wxs",
".xaml",
".xbl",
".xib",
".xlf",
".xliff",
".xpdl",
".xul",
".xoml"
],
"xsl": [
".xsl",
".xslt"
]
},
"pkg:yaml": {
"yaml": [
".yaml",
".yml",
".eyaml",
".eyml",
".cff",
".yaml-tmlanguage",
".yaml-tmpreferences",
".yaml-tmtheme",
".winget"
]
}
}

View File

@ -63,17 +63,32 @@ async function processMaterialFileIcons() {
} }
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2)); fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2));
const vscodeExtensionsJson = await readFile(fileURLToPath(new URL(`generate-svg-vscode-extensions.json`, import.meta.url)));
const vscodeExtensions = JSON.parse(vscodeExtensionsJson);
const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url))); const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url)));
const iconRules = JSON.parse(iconRulesJson); const iconRules = JSON.parse(iconRulesJson);
// The rules are from VSCode material-icon-theme, we need to adjust them to our needs // The rules are from VSCode material-icon-theme, we need to adjust them to our needs
// 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient) // 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient)
// 2. We do not have a "Language ID" system: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers // 2. We do not have a "Language ID" system:
// * So we just treat the "Language ID" as file extension, it is not always true, but it is good enough for most cases. // * https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
// * https://github.com/microsoft/vscode/tree/1.98.0/extensions
delete iconRules.iconDefinitions; delete iconRules.iconDefinitions;
for (const [k, v] of Object.entries(iconRules.fileNames)) iconRules.fileNames[k.toLowerCase()] = v; for (const [k, v] of Object.entries(iconRules.fileNames)) iconRules.fileNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.folderNames)) iconRules.folderNames[k.toLowerCase()] = v; for (const [k, v] of Object.entries(iconRules.folderNames)) iconRules.folderNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v; for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.languageIds)) iconRules.fileExtensions[k.toLowerCase()] = v; // Use VSCode's "Language ID" mapping from its extensions
for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) {
for (const [langId, names] of Object.entries(langIdExtMap)) {
for (const name of names) {
const nameLower = name.toLowerCase();
if (nameLower[0] === '.') {
iconRules.fileExtensions[nameLower.substring(1)] ??= langId;
} else {
iconRules.fileNames[nameLower] ??= langId;
}
}
}
}
const iconRulesPretty = JSON.stringify(iconRules, null, 2); const iconRulesPretty = JSON.stringify(iconRules, null, 2);
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty); fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty);
} }

View File

@ -273,6 +273,10 @@ textarea:focus,
width: 50%; width: 50%;
} }
.ui.form.left-right-form .inline.field .ui.dropdown input.search {
width: 100%;
}
.ui.form.left-right-form .inline.field .inline-right { .ui.form.left-right-form .inline.field .inline-right {
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;

View File

@ -18,6 +18,7 @@
@import "./modules/checkbox.css"; @import "./modules/checkbox.css";
@import "./modules/dimmer.css"; @import "./modules/dimmer.css";
@import "./modules/modal.css"; @import "./modules/modal.css";
@import "./modules/tab.css";
@import "./modules/tippy.css"; @import "./modules/tippy.css";
@import "./modules/breadcrumb.css"; @import "./modules/breadcrumb.css";

View File

@ -0,0 +1,7 @@
.ui.tab {
display: none;
}
.ui.tab.active {
display: block;
}

View File

@ -1,7 +0,0 @@
audit=false
fund=false
update-notifier=false
package-lock=true
save-exact=true
lockfile-version=3
optional=false

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,703 @@
/*!
* # Fomantic-UI - Modal
* http://github.com/fomantic/Fomantic-UI/
*
*
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
/*******************************
Modal
*******************************/
.ui.modal {
position: absolute;
display: none;
z-index: 1001;
text-align: left;
background: #FFFFFF;
border: none;
box-shadow: 1px 3px 3px 0 rgba(0, 0, 0, 0.2), 1px 3px 15px 2px rgba(0, 0, 0, 0.2);
transform-origin: 50% 25%;
flex: 0 0 auto;
border-radius: 0.28571429rem;
-webkit-user-select: text;
-moz-user-select: text;
user-select: text;
will-change: top, left, margin, transform, opacity;
}
.ui.modal > :first-child:not(.icon):not(.dimmer),
.ui.modal > i.icon:first-child + *,
.ui.modal > .dimmer:first-child + *:not(.icon),
.ui.modal > .dimmer:first-child + i.icon + * {
border-top-left-radius: 0.28571429rem;
border-top-right-radius: 0.28571429rem;
}
.ui.modal > :last-child {
border-bottom-left-radius: 0.28571429rem;
border-bottom-right-radius: 0.28571429rem;
}
.ui.modal > .ui.dimmer {
border-radius: inherit;
}
/*******************************
Content
*******************************/
/*--------------
Close
---------------*/
.ui.modal > .close {
cursor: pointer;
position: absolute;
top: -2.5rem;
right: -2.5rem;
z-index: 1;
opacity: 0.8;
font-size: 1.25em;
color: #FFFFFF;
width: 2.25rem;
height: 2.25rem;
padding: 0.625rem 0 0 0;
}
.ui.modal > .close:hover {
opacity: 1;
}
/*--------------
Header
---------------*/
.ui.modal > .header {
display: block;
font-family: var(--fonts-regular);
background: #FFFFFF;
margin: 0;
padding: 1.25rem 1.5rem;
box-shadow: none;
color: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid rgba(34, 36, 38, 0.15);
}
.ui.modal > .header:not(.ui) {
font-size: 1.42857143rem;
line-height: 1.28571429em;
font-weight: 500;
}
/*--------------
Content
---------------*/
.ui.modal > .content {
display: block;
width: 100%;
font-size: 1em;
line-height: 1.4;
padding: 1.5rem;
background: #FFFFFF;
}
.ui.modal > .image.content {
display: flex;
flex-direction: row;
}
/* Image */
.ui.modal > .content > .image {
display: block;
flex: 0 1 auto;
width: '';
align-self: start;
max-width: 100%;
}
.ui.modal > [class*="top aligned"] {
align-self: start;
}
.ui.modal > [class*="middle aligned"] {
align-self: center;
}
.ui.modal > [class*="stretched"] {
align-self: stretch;
}
/* Description */
.ui.modal > .content > .description {
display: block;
flex: 1 0 auto;
min-width: 0;
align-self: start;
}
.ui.modal > .content > i.icon + .description,
.ui.modal > .content > .image + .description {
flex: 0 1 auto;
min-width: '';
width: auto;
padding-left: 2em;
}
/*rtl:ignore*/
.ui.modal > .content > .image > i.icon {
margin: 0;
opacity: 1;
width: auto;
line-height: 1;
font-size: 8rem;
}
/*--------------
Actions
---------------*/
.ui.modal > .actions {
background: #F9FAFB;
padding: 1rem 1rem;
border-top: 1px solid rgba(34, 36, 38, 0.15);
text-align: right;
}
.ui.modal .actions > .button:not(.fluid) {
margin-left: 0.75em;
}
.ui.basic.modal > .actions {
border-top: none;
}
/*-------------------
Responsive
--------------------*/
/* Modal Width */
@media only screen and (max-width: 767.98px) {
.ui.modal:not(.fullscreen) {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.modal:not(.fullscreen) {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.modal:not(.fullscreen) {
width: 850px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.modal:not(.fullscreen) {
width: 900px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.modal:not(.fullscreen) {
width: 950px;
margin: 0 0 0 0;
}
}
/* Tablet and Mobile */
@media only screen and (max-width: 991.98px) {
.ui.modal > .header {
padding-right: 2.25rem;
}
.ui.modal > .close {
top: 1.0535rem;
right: 1rem;
color: rgba(0, 0, 0, 0.87);
}
}
/* Mobile */
@media only screen and (max-width: 767.98px) {
.ui.modal > .header {
padding: 0.75rem 1rem !important;
padding-right: 2.25rem !important;
}
.ui.overlay.fullscreen.modal > .content.content.content {
min-height: calc(100vh - 8.1rem);
}
.ui.overlay.fullscreen.modal > .scrolling.content.content.content {
max-height: calc(100vh - 8.1rem);
}
.ui.modal > .content {
display: block;
padding: 1rem !important;
}
.ui.modal > .close {
top: 0.5rem !important;
right: 0.5rem !important;
}
/*rtl:ignore*/
.ui.modal .image.content {
flex-direction: column;
}
.ui.modal > .content > .image {
display: block;
max-width: 100%;
margin: 0 auto !important;
text-align: center;
padding: 0 0 1rem !important;
}
.ui.modal > .content > .image > i.icon {
font-size: 5rem;
text-align: center;
}
/*rtl:ignore*/
.ui.modal > .content > .description {
display: block;
width: 100% !important;
margin: 0 !important;
padding: 1rem 0 !important;
box-shadow: none;
}
/* Let Buttons Stack */
.ui.modal > .actions {
padding: 1rem 1rem 0rem !important;
}
.ui.modal .actions > .buttons,
.ui.modal .actions > .button {
margin-bottom: 1rem;
}
}
/*--------------
Coupling
---------------*/
.ui.inverted.dimmer > .ui.modal {
box-shadow: 1px 3px 10px 2px rgba(0, 0, 0, 0.2);
}
/*******************************
Types
*******************************/
.ui.basic.modal {
background-color: transparent;
border: none;
border-radius: 0;
box-shadow: none !important;
color: #FFFFFF;
}
.ui.basic.modal > .header,
.ui.basic.modal > .content,
.ui.basic.modal > .actions {
background-color: transparent;
}
.ui.basic.modal > .header {
color: #FFFFFF;
border-bottom: none;
}
.ui.basic.modal > .close {
top: 1rem;
right: 1.5rem;
color: #FFFFFF;
}
.ui.inverted.dimmer > .basic.modal {
color: rgba(0, 0, 0, 0.87);
}
.ui.inverted.dimmer > .ui.basic.modal > .header {
color: rgba(0, 0, 0, 0.85);
}
/* Resort to margin positioning if legacy */
.ui.legacy.legacy.modal,
.ui.legacy.legacy.page.dimmer > .ui.modal {
left: 50% !important;
}
.ui.legacy.legacy.modal:not(.aligned),
.ui.legacy.legacy.page.dimmer > .ui.modal:not(.aligned) {
top: 50%;
}
.ui.legacy.legacy.page.dimmer > .ui.scrolling.modal:not(.aligned),
.ui.page.dimmer > .ui.scrolling.legacy.legacy.modal:not(.aligned),
.ui.top.aligned.legacy.legacy.page.dimmer > .ui.modal:not(.aligned),
.ui.top.aligned.dimmer > .ui.legacy.legacy.modal:not(.aligned) {
top: auto;
}
.ui.legacy.overlay.fullscreen.modal {
margin-top: -2rem !important;
}
/*******************************
States
*******************************/
.ui.loading.modal {
display: block;
visibility: hidden;
z-index: -1;
}
.ui.active.modal {
display: block;
}
/*******************************
Variations
*******************************/
/*--------------
Aligned
---------------*/
.modals.dimmer .ui.top.aligned.modal {
top: 5vh;
}
.modals.dimmer .ui.bottom.aligned.modal {
bottom: 5vh;
}
@media only screen and (max-width: 767.98px) {
.modals.dimmer .ui.top.aligned.modal {
top: 1rem;
}
.modals.dimmer .ui.bottom.aligned.modal {
bottom: 1rem;
}
}
/*--------------
Scrolling
---------------*/
/* Scrolling Dimmer */
.scrolling.dimmable.dimmed {
overflow: hidden;
}
.scrolling.dimmable > .dimmer {
justify-content: flex-start;
position: fixed;
}
.scrolling.dimmable.dimmed > .dimmer {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.modals.dimmer .ui.scrolling.modal:not(.fullscreen) {
margin: 2rem auto;
}
/* Fix for Firefox, Edge, IE11 */
.modals.dimmer .ui.scrolling.modal:not([class*="overlay fullscreen"])::after {
content: '\00A0';
position: absolute;
height: 2rem;
}
/* Undetached Scrolling */
.scrolling.undetached.dimmable.dimmed {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.scrolling.undetached.dimmable.dimmed > .dimmer {
overflow: hidden;
}
.scrolling.undetached.dimmable .ui.scrolling.modal:not(.fullscreen) {
position: absolute;
left: 50%;
}
/* Scrolling Content */
.ui.modal > .scrolling.content {
max-height: calc(80vh - 10rem);
overflow: auto;
}
.ui.overlay.fullscreen.modal > .content {
min-height: calc(100vh - 9.1rem);
}
.ui.overlay.fullscreen.modal > .scrolling.content {
max-height: calc(100vh - 9.1rem);
}
/*--------------
Full Screen
---------------*/
.ui.fullscreen.modal {
width: 95%;
left: 2.5%;
margin: 1em auto;
}
.ui.overlay.fullscreen.modal {
width: 100%;
left: 0;
margin: 0 auto;
top: 0;
border-radius: 0;
}
.ui.modal > .close.inside + .header,
.ui.fullscreen.modal > .header {
padding-right: 2.25rem;
}
.ui.modal > .close.inside,
.ui.fullscreen.modal > .close {
top: 1.0535rem;
right: 1rem;
color: rgba(0, 0, 0, 0.87);
}
.ui.basic.fullscreen.modal > .close {
color: #FFFFFF;
}
/*--------------
Size
---------------*/
.ui.modal {
font-size: 1rem;
}
.ui.mini.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.mini.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.mini.modal {
width: 35.2%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.mini.modal {
width: 340px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.mini.modal {
width: 360px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.mini.modal {
width: 380px;
margin: 0 0 0 0;
}
}
.ui.tiny.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.tiny.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.tiny.modal {
width: 52.8%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.tiny.modal {
width: 510px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.tiny.modal {
width: 540px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.tiny.modal {
width: 570px;
margin: 0 0 0 0;
}
}
.ui.small.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.small.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.small.modal {
width: 70.4%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.small.modal {
width: 680px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.small.modal {
width: 720px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.small.modal {
width: 760px;
margin: 0 0 0 0;
}
}
.ui.large.modal > .header:not(.ui) {
font-size: 1.6em;
}
@media only screen and (max-width: 767.98px) {
.ui.large.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.large.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.large.modal {
width: 1020px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.large.modal {
width: 1080px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.large.modal {
width: 1140px;
margin: 0 0 0 0;
}
}
.ui.big.modal > .header:not(.ui) {
font-size: 1.6em;
}
@media only screen and (max-width: 767.98px) {
.ui.big.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.big.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.big.modal {
width: 1190px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.big.modal {
width: 1260px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.big.modal {
width: 1330px;
margin: 0 0 0 0;
}
}
.ui.huge.modal > .header:not(.ui) {
font-size: 1.6em;
}
@media only screen and (max-width: 767.98px) {
.ui.huge.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.huge.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.huge.modal {
width: 1360px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.huge.modal {
width: 1440px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.huge.modal {
width: 1520px;
margin: 0 0 0 0;
}
}
.ui.massive.modal > .header:not(.ui) {
font-size: 1.8em;
}
@media only screen and (max-width: 767.98px) {
.ui.massive.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.massive.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.massive.modal {
width: 1530px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.massive.modal {
width: 1620px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.massive.modal {
width: 1710px;
margin: 0 0 0 0;
}
}
/*******************************
Theme Overrides
*******************************/
/*******************************
Site Overrides
*******************************/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,520 @@
/*!
* # Fomantic-UI - Search
* http://github.com/fomantic/Fomantic-UI/
*
*
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
/*******************************
Search
*******************************/
.ui.search {
position: relative;
}
.ui.search > .prompt {
margin: 0;
outline: none;
-webkit-appearance: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
text-shadow: none;
font-style: normal;
font-weight: normal;
line-height: 1.21428571em;
padding: 0.67857143em 1em;
font-size: 1em;
background: #FFFFFF;
border: 1px solid rgba(34, 36, 38, 0.15);
color: rgba(0, 0, 0, 0.87);
box-shadow: 0 0 0 0 transparent inset;
transition: background-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease, border-color 0.1s ease;
}
.ui.search .prompt {
border-radius: 500rem;
}
/*--------------
Icon
---------------*/
.ui.search .prompt ~ .search.icon {
cursor: pointer;
}
/*--------------
Results
---------------*/
.ui.search > .results {
display: none;
position: absolute;
top: 100%;
left: 0;
transform-origin: center top;
white-space: normal;
text-align: left;
text-transform: none;
background: #FFFFFF;
margin-top: 0.5em;
width: 18em;
border-radius: 0.28571429rem;
box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15);
border: 1px solid #D4D4D5;
z-index: 998;
}
.ui.search > .results > :first-child {
border-radius: 0.28571429rem 0.28571429rem 0 0;
}
.ui.search > .results > :last-child {
border-radius: 0 0 0.28571429rem 0.28571429rem;
}
/*--------------
Result
---------------*/
.ui.search > .results .result {
cursor: pointer;
display: block;
overflow: hidden;
font-size: 1em;
padding: 0.85714286em 1.14285714em;
color: rgba(0, 0, 0, 0.87);
line-height: 1.33;
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
}
.ui.search > .results .result:last-child {
border-bottom: none !important;
}
/* Image */
.ui.search > .results .result .image {
float: right;
overflow: hidden;
background: none;
width: 5em;
height: 3em;
border-radius: 0.25em;
}
.ui.search > .results .result .image img {
display: block;
width: auto;
height: 100%;
}
/*--------------
Info
---------------*/
.ui.search > .results .result .image + .content {
margin: 0 6em 0 0;
}
.ui.search > .results .result .title {
margin: -0.14285714em 0 0;
font-family: var(--fonts-regular);
font-weight: 500;
font-size: 1em;
color: rgba(0, 0, 0, 0.85);
}
.ui.search > .results .result .description {
margin-top: 0;
font-size: 0.92857143em;
color: rgba(0, 0, 0, 0.4);
}
.ui.search > .results .result .price {
float: right;
color: #21BA45;
}
/*--------------
Message
---------------*/
.ui.search > .results > .message {
padding: 1em 1em;
}
.ui.search > .results > .message .header {
font-family: var(--fonts-regular);
font-size: 1rem;
font-weight: 500;
color: rgba(0, 0, 0, 0.87);
}
.ui.search > .results > .message .description {
margin-top: 0.25rem;
font-size: 1em;
color: rgba(0, 0, 0, 0.87);
}
/* View All Results */
.ui.search > .results > .action {
display: block;
border-top: none;
background: #F3F4F5;
padding: 0.92857143em 1em;
color: rgba(0, 0, 0, 0.87);
font-weight: 500;
text-align: center;
}
/*******************************
States
*******************************/
/*--------------------
Focus
---------------------*/
.ui.search > .prompt:focus {
border-color: rgba(34, 36, 38, 0.35);
background: #FFFFFF;
color: rgba(0, 0, 0, 0.95);
}
/*--------------------
Loading
---------------------*/
.ui.loading.search .input > i.icon:before {
position: absolute;
content: '';
top: 50%;
left: 50%;
margin: -0.64285714em 0 0 -0.64285714em;
width: 1.28571429em;
height: 1.28571429em;
border-radius: 500rem;
border: 0.2em solid rgba(0, 0, 0, 0.1);
}
.ui.loading.search .input > i.icon:after {
position: absolute;
content: '';
top: 50%;
left: 50%;
margin: -0.64285714em 0 0 -0.64285714em;
width: 1.28571429em;
height: 1.28571429em;
animation: loader 0.6s infinite linear;
border: 0.2em solid #767676;
border-radius: 500rem;
box-shadow: 0 0 0 1px transparent;
}
/*--------------
Hover
---------------*/
.ui.search > .results .result:hover,
.ui.category.search > .results .category .result:hover {
background: #F9FAFB;
}
.ui.search .action:hover:not(div) {
background: #E0E0E0;
}
/*--------------
Active
---------------*/
.ui.category.search > .results .category.active {
background: #F3F4F5;
}
.ui.category.search > .results .category.active > .name {
color: rgba(0, 0, 0, 0.87);
}
.ui.search > .results .result.active,
.ui.category.search > .results .category .result.active {
position: relative;
border-left-color: rgba(34, 36, 38, 0.1);
background: #F3F4F5;
box-shadow: none;
}
.ui.search > .results .result.active .title {
color: rgba(0, 0, 0, 0.85);
}
.ui.search > .results .result.active .description {
color: rgba(0, 0, 0, 0.85);
}
/*--------------------
Disabled
----------------------*/
/* Disabled */
.ui.disabled.search {
cursor: default;
pointer-events: none;
opacity: var(--opacity-disabled);
}
/*******************************
Types
*******************************/
/*--------------
Selection
---------------*/
.ui.search.selection .prompt {
border-radius: 0.28571429rem;
}
/* Remove input */
.ui.search.selection > .icon.input > .remove.icon {
pointer-events: none;
position: absolute;
left: auto;
opacity: 0;
color: '';
top: 0;
right: 0;
transition: color 0.1s ease, opacity 0.1s ease;
}
.ui.search.selection > .icon.input > .active.remove.icon {
cursor: pointer;
opacity: 0.8;
pointer-events: auto;
}
.ui.search.selection > .icon.input:not([class*="left icon"]) > .icon ~ .remove.icon {
right: 1.85714em;
}
.ui.search.selection > .icon.input > .remove.icon:hover {
opacity: 1;
color: #DB2828;
}
/*--------------
Category
---------------*/
.ui.category.search .results {
width: 28em;
}
.ui.category.search .results.animating,
.ui.category.search .results.visible {
display: table;
}
/* Category */
.ui.category.search > .results .category {
display: table-row;
background: #F3F4F5;
box-shadow: none;
transition: background 0.1s ease, border-color 0.1s ease;
}
/* Last Category */
.ui.category.search > .results .category:last-child {
border-bottom: none;
}
/* First / Last */
.ui.category.search > .results .category:first-child .name + .result {
border-radius: 0 0.28571429rem 0 0;
}
.ui.category.search > .results .category:last-child .result:last-child {
border-radius: 0 0 0.28571429rem 0;
}
/* Category Result Name */
.ui.category.search > .results .category > .name {
display: table-cell;
text-overflow: ellipsis;
width: 100px;
white-space: nowrap;
background: transparent;
font-family: var(--fonts-regular);
font-size: 1em;
padding: 0.4em 1em;
font-weight: 500;
color: rgba(0, 0, 0, 0.4);
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
}
/* Category Result */
.ui.category.search > .results .category .results {
display: table-cell;
background: #FFFFFF;
border-left: 1px solid rgba(34, 36, 38, 0.15);
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
}
.ui.category.search > .results .category .result {
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
transition: background 0.1s ease, border-color 0.1s ease;
padding: 0.85714286em 1.14285714em;
}
/*******************************
Variations
*******************************/
/*-------------------
Scrolling
--------------------*/
.ui.scrolling.search > .results,
.ui.search.long > .results,
.ui.search.short > .results {
overflow-x: hidden;
overflow-y: auto;
backface-visibility: hidden;
-webkit-overflow-scrolling: touch;
}
@media only screen and (max-width: 767.98px) {
.ui.scrolling.search > .results {
max-height: 12.17714286em;
}
}
@media only screen and (min-width: 768px) {
.ui.scrolling.search > .results {
max-height: 18.26571429em;
}
}
@media only screen and (min-width: 992px) {
.ui.scrolling.search > .results {
max-height: 24.35428571em;
}
}
@media only screen and (min-width: 1920px) {
.ui.scrolling.search > .results {
max-height: 36.53142857em;
}
}
@media only screen and (max-width: 767.98px) {
.ui.search.short > .results {
max-height: 12.17714286em;
}
.ui.search[class*="very short"] > .results {
max-height: 9.13285714em;
}
.ui.search.long > .results {
max-height: 24.35428571em;
}
.ui.search[class*="very long"] > .results {
max-height: 36.53142857em;
}
}
@media only screen and (min-width: 768px) {
.ui.search.short > .results {
max-height: 18.26571429em;
}
.ui.search[class*="very short"] > .results {
max-height: 13.69928571em;
}
.ui.search.long > .results {
max-height: 36.53142857em;
}
.ui.search[class*="very long"] > .results {
max-height: 54.79714286em;
}
}
@media only screen and (min-width: 992px) {
.ui.search.short > .results {
max-height: 24.35428571em;
}
.ui.search[class*="very short"] > .results {
max-height: 18.26571429em;
}
.ui.search.long > .results {
max-height: 48.70857143em;
}
.ui.search[class*="very long"] > .results {
max-height: 73.06285714em;
}
}
@media only screen and (min-width: 1920px) {
.ui.search.short > .results {
max-height: 36.53142857em;
}
.ui.search[class*="very short"] > .results {
max-height: 27.39857143em;
}
.ui.search.long > .results {
max-height: 73.06285714em;
}
.ui.search[class*="very long"] > .results {
max-height: 109.59428571em;
}
}
/*-------------------
Left / Right
--------------------*/
.ui[class*="left aligned"].search > .results {
right: auto;
left: 0;
}
.ui[class*="right aligned"].search > .results {
right: 0;
left: auto;
}
/*--------------
Fluid
---------------*/
.ui.fluid.search .results {
width: 100%;
}
/*--------------
Sizes
---------------*/
.ui.search {
font-size: 1em;
}
.ui.mini.search {
font-size: 0.78571429em;
}
.ui.tiny.search {
font-size: 0.85714286em;
}
.ui.small.search {
font-size: 0.92857143em;
}
.ui.large.search {
font-size: 1.14285714em;
}
.ui.big.search {
font-size: 1.28571429em;
}
.ui.huge.search {
font-size: 1.42857143em;
}
.ui.massive.search {
font-size: 1.71428571em;
}
/*--------------
Mobile
---------------*/
@media only screen and (max-width: 767.98px) {
.ui.search .results {
max-width: calc(100vw - 2rem);
}
}
/*******************************
Theme Overrides
*******************************/
/*******************************
Site Overrides
*******************************/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
@import "./components/dropdown.css";
@import "./components/form.css";
@import "./components/modal.css";
@import "./components/search.css";

View File

@ -0,0 +1,10 @@
import './components/api.js';
import './components/dropdown.js';
import './components/modal.js';
import './components/search.js';
// Hard forked from Fomantic 2.8.7
// TODO: need to apply the patch from Makefile
// # fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
// $(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"fomantic-ui": "2.8.7"
}
}

View File

@ -1,21 +1,4 @@
/* /* To override a theme for an individual element, specify theme name below */
████████╗██╗ ██╗███████╗███╗ ███╗███████╗███████╗
╚══██╔══╝██║ ██║██╔════╝████╗ ████║██╔════╝██╔════╝
██║ ███████║█████╗ ██╔████╔██║█████╗ ███████╗
██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══╝ ╚════██║
██║ ██║ ██║███████╗██║ ╚═╝ ██║███████╗███████║
╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝
*/
/*******************************
Theme Selection
*******************************/
/* To override a theme for an individual element
specify theme name below
*/
/* Global */ /* Global */
@site : 'default'; @site : 'default';

View File

@ -77,7 +77,7 @@ export function initGlobalDropdown() {
} }
export function initGlobalTabularMenu() { export function initGlobalTabularMenu() {
fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab({autoTabActivation: false}); fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab();
} }
// for performance considerations, it only uses performant syntax // for performance considerations, it only uses performant syntax

View File

@ -75,7 +75,7 @@ class ImageDiff {
this.containerEl = containerEl; this.containerEl = containerEl;
containerEl.setAttribute('data-image-diff-loaded', 'true'); containerEl.setAttribute('data-image-diff-loaded', 'true');
fomanticQuery(containerEl).find('.ui.menu.tabular .item').tab({autoTabActivation: false}); fomanticQuery(containerEl).find('.ui.menu.tabular .item').tab();
// the container may be hidden by "viewed" checkbox, so use the parent's width for reference // the container may be hidden by "viewed" checkbox, so use the parent's width for reference
this.diffContainerWidth = Math.max(containerEl.closest('.diff-file-box').clientWidth - 300, 100); this.diffContainerWidth = Math.max(containerEl.closest('.diff-file-box').clientWidth - 300, 100);

View File

@ -7,14 +7,13 @@ import {initAriaModalPatch} from './fomantic/modal.ts';
import {initFomanticTransition} from './fomantic/transition.ts'; import {initFomanticTransition} from './fomantic/transition.ts';
import {initFomanticDimmer} from './fomantic/dimmer.ts'; import {initFomanticDimmer} from './fomantic/dimmer.ts';
import {svg} from '../svg.ts'; import {svg} from '../svg.ts';
import {initFomanticTab} from './fomantic/tab.ts';
export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)'); export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)');
export function initGiteaFomantic() { export function initGiteaFomantic() {
// our extensions // our extensions
$.fn.fomanticExt = {}; $.fn.fomanticExt = {};
// Silence fomantic's error logging when tabs are used without a target content element
$.fn.tab.settings.silent = true;
// By default, use "exact match" for full text search // By default, use "exact match" for full text search
$.fn.dropdown.settings.fullTextSearch = 'exact'; $.fn.dropdown.settings.fullTextSearch = 'exact';
// Do not use "cursor: pointer" for dropdown labels // Do not use "cursor: pointer" for dropdown labels
@ -27,6 +26,7 @@ export function initGiteaFomantic() {
initFomanticTransition(); initFomanticTransition();
initFomanticDimmer(); initFomanticDimmer();
initFomanticTab();
initFomanticApiPatch(); initFomanticApiPatch();
// Use the patches to improve accessibility, these patches are designed to be as independent as possible, make it easy to modify or remove in the future. // Use the patches to improve accessibility, these patches are designed to be as independent as possible, make it easy to modify or remove in the future.

View File

@ -0,0 +1,19 @@
import $ from 'jquery';
import {queryElemSiblings} from '../../utils/dom.ts';
export function initFomanticTab() {
$.fn.tab = function (this: any) {
for (const elBtn of this) {
const tabName = elBtn.getAttribute('data-tab');
if (!tabName) continue;
elBtn.addEventListener('click', () => {
const elTab = document.querySelector(`.ui.tab[data-tab="${tabName}"]`);
queryElemSiblings(elTab, `.ui.tab`, (el) => el.classList.remove('active'));
queryElemSiblings(elBtn, `[data-tab]`, (el) => el.classList.remove('active'));
elBtn.classList.add('active');
elTab.classList.add('active');
});
}
return this;
};
}

View File

@ -77,10 +77,10 @@ export default {
entry: { entry: {
index: [ index: [
fileURLToPath(new URL('web_src/js/globals.ts', import.meta.url)), fileURLToPath(new URL('web_src/js/globals.ts', import.meta.url)),
fileURLToPath(new URL('web_src/fomantic/build/semantic.js', import.meta.url)), fileURLToPath(new URL('web_src/fomantic/build/fomantic.js', import.meta.url)),
fileURLToPath(new URL('web_src/js/index.ts', import.meta.url)), fileURLToPath(new URL('web_src/js/index.ts', import.meta.url)),
fileURLToPath(new URL('node_modules/easymde/dist/easymde.min.css', import.meta.url)), fileURLToPath(new URL('node_modules/easymde/dist/easymde.min.css', import.meta.url)),
fileURLToPath(new URL('web_src/fomantic/build/semantic.css', import.meta.url)), fileURLToPath(new URL('web_src/fomantic/build/fomantic.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/index.css', import.meta.url)), fileURLToPath(new URL('web_src/css/index.css', import.meta.url)),
], ],
webcomponents: [ webcomponents: [