diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 18d6fe37a8ba2..9780822e07838 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1242,6 +1242,9 @@ LEVEL = Info ;; Change the sort type of the explore pages. ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". ;EXPLORE_PAGING_DEFAULT_SORT = recentupdate +;; +;; Newline format for the web editor. Either "LF" or "CRLF". Can be overridden per-file with .editorconfig. +;EDITOR_EOL = LF ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index c9e6a937c3482..48053ddebfd2e 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -232,6 +232,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). - `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest" +- `EDITOR_EOL`: **LF**: Newline format for the web editor. Either "LF" or "CRLF". Can be overridden per-file with .editorconfig. ### UI - Admin (`ui.admin`) diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 31042d3ee0dda..4a6def6cb6393 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -34,6 +34,7 @@ var UI = struct { SearchRepoDescription bool OnlyShowRelevantRepos bool ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` + EditorEol string `ini:"EDITOR_EOL"` Notification struct { MinTimeout time.Duration @@ -82,6 +83,7 @@ var UI = struct { Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, + EditorEol: `LF`, Notification: struct { MinTimeout time.Duration TimeoutStep time.Duration diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 235fd96b73d8f..9fb0d76c3a19f 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -149,6 +149,9 @@ func NewFuncMap() template.FuncMap { "MermaidMaxSourceCharacters": func() int { return setting.MermaidMaxSourceCharacters }, + "EditorEol": func() string { + return setting.UI.EditorEol + }, // ----------------------------------------------------------------- // render diff --git a/modules/util/string.go b/modules/util/string.go index f2def7b0ece2c..dd2c9b6564c56 100644 --- a/modules/util/string.go +++ b/modules/util/string.go @@ -3,7 +3,11 @@ package util -import "github.com/yuin/goldmark/util" +import ( + "strings" + + "github.com/yuin/goldmark/util" +) func isSnakeCaseUpper(c byte) bool { return 'A' <= c && c <= 'Z' @@ -85,3 +89,13 @@ func ToSnakeCase(input string) string { } return util.BytesToReadOnlyString(res) } + +// convert all newlines in string to \n +func ToLF(str string) string { + return strings.ReplaceAll(str, "\r", "") +} + +// convert all newlines in string to \r\n +func ToCRLF(str string) string { + return strings.ReplaceAll(ToLF(str), "\n", "\r\n") +} diff --git a/modules/util/string_test.go b/modules/util/string_test.go index 0a4a8bbcfbf9d..720560c70cf06 100644 --- a/modules/util/string_test.go +++ b/modules/util/string_test.go @@ -45,3 +45,11 @@ func TestToSnakeCase(t *testing.T) { assert.Equal(t, expected, ToSnakeCase(input)) } } + +func TestToLF(t *testing.T) { + assert.Equal(t, "\na\nbc\n\n", ToLF("\r\na\r\nb\rc\n\n")) +} + +func TestToCRLF(t *testing.T) { + assert.Equal(t, "\r\na\r\nbc\r\n\r\n", ToCRLF("\r\na\r\nb\rc\n\n")) +} diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 1ad091b70fd9d..79335de6dda4b 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -277,6 +277,22 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b operation = "create" } + eol := setting.UI.EditorEol + ec, _, err := ctx.Repo.GetEditorconfig() + if err == nil { + def, err := ec.GetDefinitionForFilename(form.TreePath) + if err == nil { + eol = strings.ToUpper(def.EndOfLine) + } + } + + content := form.Content + if eol == "CRLF" { + content = util.ToCRLF(content) + } else { // convert to LF, this was hardcoded before 1.21 + content = util.ToLF(content) + } + if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ LastCommitID: form.LastCommit, OldBranch: ctx.Repo.BranchName, @@ -287,7 +303,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b Operation: operation, FromTreePath: ctx.Repo.TreePath, TreePath: form.TreePath, - ContentReader: strings.NewReader(form.Content), + ContentReader: strings.NewReader(content), }, }, Signoff: form.Signoff, diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 236f10bb0ad4e..3b339eb8b2529 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -40,7 +40,8 @@ data-context="{{.RepoLink}}" data-previewable-extensions="{{.PreviewableExtensions}}" data-line-wrap-extensions="{{.LineWrapExtensions}}" - data-initial-value="{{JsonUtils.EncodeToString .FileContent}}"> + data-initial-value="{{JsonUtils.EncodeToString .FileContent}}" + data-editor-eol="{{EditorEol}}">