From a0bd5842e8719266233d8cbc131dce023171e24b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 9 Jan 2023 15:50:52 +0800 Subject: [PATCH 01/10] Support default clone editor --- modules/context/repo.go | 7 +++ modules/templates/helper.go | 6 +++ options/locale/locale_en-US.ini | 2 +- public/img/svg/gitea-vscodium.svg | 1 + routers/web/admin/config.go | 18 +++++++ routers/web/web.go | 7 +++ services/dev/editor.go | 79 +++++++++++++++++++++++++++++ templates/admin/config.tmpl | 26 ++++++++++ templates/repo/clone_script.tmpl | 8 +-- templates/repo/home.tmpl | 2 +- web_src/js/features/admin/config.js | 29 +++++++++++ web_src/svg/gitea-vscodium.svg | 1 + 12 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 public/img/svg/gitea-vscodium.svg create mode 100644 services/dev/editor.go create mode 100644 web_src/svg/gitea-vscodium.svg diff --git a/modules/context/repo.go b/modules/context/repo.go index d15d28cab7834..9590d469d179e 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -31,6 +31,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" asymkey_service "code.gitea.io/gitea/services/asymkey" + "code.gitea.io/gitea/services/dev" "github.com/editorconfig/editorconfig-core-go/v2" ) @@ -587,6 +588,12 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH + editor, err := dev.GetDefaultEditor() + if err != nil { + ctx.ServerError("dev.GetDefaultEditor", err) + return + } + ctx.Data["CloneEditor"] = editor ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 7b997b49d9e7d..0eff2ee84cb20 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -106,6 +106,7 @@ func NewFuncMap() []template.FuncMap { }, "Safe": Safe, "SafeJS": SafeJS, + "SafeURL": SafeURL, "JSEscape": JSEscape, "Str2html": Str2html, "TimeSince": timeutil.TimeSince, @@ -664,6 +665,11 @@ func Safe(raw string) template.HTML { return template.HTML(raw) } +// SafeURL render raw as URL +func SafeURL(raw string) template.URL { + return template.URL(raw) +} + // SafeJS renders raw as JS func SafeJS(raw string) template.JS { return template.JS(raw) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ddc0ee25d51e4..f8f39df9c30c2 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -856,7 +856,7 @@ already_forked = You've already forked %s fork_to_different_account = Fork to a different account fork_visibility_helper = The visibility of a forked repository cannot be changed. use_template = Use this template -clone_in_vsc = Clone in VS Code +clone_in_editor = Clone in %s download_zip = Download ZIP download_tar = Download TAR.GZ download_bundle = Download BUNDLE diff --git a/public/img/svg/gitea-vscodium.svg b/public/img/svg/gitea-vscodium.svg new file mode 100644 index 0000000000000..0c0b0eb9fbdb4 --- /dev/null +++ b/public/img/svg/gitea-vscodium.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index eaa02588ca5c9..0436fea9322ff 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/dev" "code.gitea.io/gitea/services/mailer" "gitea.com/go-chi/session" @@ -187,6 +188,23 @@ func Config(ctx *context.Context) { ctx.Data["EnableXORMLog"] = setting.EnableXORMLog ctx.Data["LogSQL"] = setting.Database.LogSQL + editors, err := dev.GetEditors() + if err != nil { + ctx.ServerError("system_model.GetAllSettings", err) + return + } + + defaultEditorS := systemSettings.Get(dev.KeyDevDefaultEditor) + if defaultEditorS.SettingValue == "" { + defaultEditorS = system_model.Setting{ + SettingKey: dev.KeyDevDefaultEditor, + SettingValue: dev.DefaultEditorName(), + } + } + + ctx.Data["DevEditors"] = editors + ctx.Data["DevDefaultEditor"] = dev.GetEditorByName(defaultEditorS.SettingValue) + ctx.HTML(http.StatusOK, tplConfig) } diff --git a/routers/web/web.go b/routers/web/web.go index 997185974c201..3f356b90f12b5 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -39,6 +39,7 @@ import ( "code.gitea.io/gitea/routers/web/user/setting/security" auth_service "code.gitea.io/gitea/services/auth" context_service "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/dev" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/lfs" @@ -1203,6 +1204,12 @@ func RegisterRoutes(m *web.Route) { m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) }, repo.MustEnableWiki, func(ctx *context.Context) { ctx.Data["PageIsWiki"] = true + editor, err := dev.GetDefaultEditor() + if err != nil { + ctx.ServerError("dev.GetDefaultEditor", err) + return + } + ctx.Data["CloneEditor"] = editor ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink() }) diff --git a/services/dev/editor.go b/services/dev/editor.go new file mode 100644 index 0000000000000..38903ae4f4c46 --- /dev/null +++ b/services/dev/editor.go @@ -0,0 +1,79 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package dev + +import ( + "html/template" + "strings" + + "code.gitea.io/gitea/models/system" +) + +const KeyDevDefaultEditor = "dev.default_editor" + +type Editor struct { + Name string + URL string + Icon string +} + +func (e *Editor) RenderURL(repoURL string) template.URL { + return template.URL(strings.Replace(e.URL, "${repo_url}", repoURL, -1)) +} + +var defaultEditors = []Editor{ + { + Name: "VS Code", + URL: "vscode://vscode.git/clone?url=${repo_url}", + Icon: `gitea-vscode`, + }, + { + Name: "VSCodium", + URL: "vscodium://vscode.git/clone?url=${repo_url}", + Icon: `gitea-vscodium`, + }, +} + +func GetEditorByName(name string) *Editor { + for _, editor := range defaultEditors { + if editor.Name == name { + return &editor + } + } + return nil +} + +// GetEditors returns all editors +func GetEditors() ([]Editor, error) { + return defaultEditors, nil +} + +func DefaultEditorName() string { + return defaultEditors[0].Name +} + +func GetDefaultEditor() (*Editor, error) { + defaultName, err := system.GetSetting(KeyDevDefaultEditor) + if err != nil && !system.IsErrSettingIsNotExist(err) { + return nil, err + } + for _, editor := range defaultEditors { + if editor.Name == defaultName { + return &editor, nil + } + } + return &defaultEditors[0], nil +} + +func SetDefaultEditor(name string) error { + for _, editor := range defaultEditors { + if editor.Name == name { + return system.SetSetting(&system.Setting{ + SettingKey: KeyDevDefaultEditor, + SettingValue: name, + }) + } + } + return nil +} diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 982cfb2800815..fa15cfc9feb97 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -413,6 +413,32 @@ {{end}} + +

+ {{.locale.Tr "admin.config.dev_config"}} +

+
+
+
{{$.locale.Tr "admin.config.dev_default_editor"}}
+
+ +
+
+
{{template "base/footer" .}} diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl index afd90040fb4e7..6435fde373cd2 100644 --- a/templates/repo/clone_script.tmpl +++ b/templates/repo/clone_script.tmpl @@ -2,7 +2,7 @@ // synchronously set clone button states and urls here to avoid flickering // on page load. initRepoCloneLink calls this when proto changes. // this applies the protocol-dependant clone url to all elements with the - // `js-clone-url` and `js-clone-url-vsc` classes. + // `js-clone-url` and `js-clone-url-editor` classes. // TODO: This localStorage setting should be moved to backend user config // so it's available during rendering, then this inline script can be removed. (window.updateCloneStates = function() { @@ -21,8 +21,10 @@ for (const el of document.getElementsByClassName('js-clone-url')) { el[el.nodeName === 'INPUT' ? 'value' : 'textContent'] = link; } - for (const el of document.getElementsByClassName('js-clone-url-vsc')) { - el['href'] = 'vscode://vscode.git/clone?url=' + encodeURIComponent(link); + for (const el of document.getElementsByClassName('js-clone-url-editor')) { + let repo_url = encodeURIComponent(link); + console.info(el, el.getAttribute('data-url'), repo_url); + el['href'] = el.getAttribute('data-url'); } })(); diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 82e6626e333a5..aac8a93d5691d 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -128,7 +128,7 @@ {{svg "octicon-cross-reference" 16 "mr-3"}}{{.locale.Tr "repo.cite_this_repo"}} {{end}} {{end}} - {{svg "gitea-vscode" 16 "mr-3"}}{{.locale.Tr "repo.clone_in_vsc"}} + {{svg .CloneEditor.Icon 16 "mr-3"}}{{.locale.Tr "repo.clone_in_editor" .CloneEditor.Name}} {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} diff --git a/web_src/js/features/admin/config.js b/web_src/js/features/admin/config.js index f5d8fae8facf3..15348cafcc78e 100644 --- a/web_src/js/features/admin/config.js +++ b/web_src/js/features/admin/config.js @@ -34,4 +34,33 @@ export function initAdminConfigs() { e.preventDefault(); return false; }); + + $('.selection').dropdown({ + onChange: (_text, _value, $choice) => { + const $this = $choice.parent().parent(); + const input = $this.find('input'); + console.info($this, input); + $.ajax({ + url: `${appSubUrl}/admin/config`, + type: 'POST', + data: { + _csrf: csrfToken, + key: input.attr('name'), + value: input.attr('value'), + version: input.attr('version'), + } + }).done((resp) => { + if (resp) { + if (resp.redirect) { + window.location.href = resp.redirect; + } else if (resp.version) { + input.attr('version', resp.version); + } else if (resp.err) { + showTemporaryTooltip($this, resp.err); + } + } + }); + return false; + } + }); } diff --git a/web_src/svg/gitea-vscodium.svg b/web_src/svg/gitea-vscodium.svg new file mode 100644 index 0000000000000..483676fe71f7e --- /dev/null +++ b/web_src/svg/gitea-vscodium.svg @@ -0,0 +1 @@ + \ No newline at end of file From 46a0375de865b862d7f40901aebac72f3763e3d2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 9 Jan 2023 15:58:06 +0800 Subject: [PATCH 02/10] fix lint --- options/locale/locale_en-US.ini | 3 +++ public/img/svg/gitea-vscodium.svg | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f8f39df9c30c2..0abcd4a0903e3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2911,6 +2911,9 @@ config.xorm_log_sql = Log SQL config.get_setting_failed = Get setting %s failed config.set_setting_failed = Set setting %s failed +config.dev_config = Development +config.dev_default_editor = Default Editor + monitor.cron = Cron Tasks monitor.name = Name monitor.schedule = Schedule diff --git a/public/img/svg/gitea-vscodium.svg b/public/img/svg/gitea-vscodium.svg index 0c0b0eb9fbdb4..b12b0e3b13c8a 100644 --- a/public/img/svg/gitea-vscodium.svg +++ b/public/img/svg/gitea-vscodium.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 1c717e9da847aea852c811fc18311357d415c517 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 9 Jan 2023 17:15:17 +0800 Subject: [PATCH 03/10] User could select his favorate editor --- models/user/setting.go | 16 ++++++--- modules/context/repo.go | 2 +- routers/web/user/setting/profile.go | 39 +++++++++++++++++++++ routers/web/web.go | 3 +- services/dev/editor.go | 46 +++++++++++++++++++++++++ templates/user/settings/appearance.tmpl | 31 ++++++++++++++++- web_src/js/features/user-settings.js | 31 +++++++++++++++++ 7 files changed, 160 insertions(+), 8 deletions(-) diff --git a/models/user/setting.go b/models/user/setting.go index f5cfef5b33945..2cbd2f75a8720 100644 --- a/models/user/setting.go +++ b/models/user/setting.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/cache" + setting_module "code.gitea.io/gitea/modules/setting" "xorm.io/builder" ) @@ -149,16 +150,21 @@ func DeleteUserSetting(userID int64, key string) error { } // SetUserSetting updates a users' setting for a specific key -func SetUserSetting(userID int64, key, value string) error { +func SetUserSetting(uid int64, key, value string) error { if err := validateUserSettingKey(key); err != nil { return err } - _, err := cache.GetString(genSettingCacheKey(userID, key), func() (string, error) { - return value, upsertUserSettingValue(userID, key, value) - }) + if err := upsertUserSettingValue(uid, strings.ToLower(key), value); err != nil { + return err + } - return err + cc := cache.GetCache() + if cc != nil { + return cc.Put(genSettingCacheKey(uid, key), value, setting_module.CacheService.TTLSeconds()) + } + + return nil } func upsertUserSettingValue(userID int64, key, value string) error { diff --git a/modules/context/repo.go b/modules/context/repo.go index 6bcd649f6ccb9..32e21401305b0 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -588,7 +588,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH - editor, err := dev.GetDefaultEditor() + editor, err := dev.GetUserDefaultEditorWithFallback(ctx.Doer) if err != nil { ctx.ServerError("dev.GetDefaultEditor", err) return diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index ef45ad8a862d1..455b76e64452f 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -28,6 +28,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/agit" + "code.gitea.io/gitea/services/dev" "code.gitea.io/gitea/services/forms" container_service "code.gitea.io/gitea/services/packages/container" user_service "code.gitea.io/gitea/services/user" @@ -375,9 +376,47 @@ func Appearance(ctx *context.Context) { return forms.IsUserHiddenCommentTypeGroupChecked(commentTypeGroup, hiddenCommentTypes) } + editors, err := dev.GetEditors() + if err != nil { + ctx.ServerError("dev.GetEditors", err) + return + } + + myDefaultEditor, err := dev.GetUserDefaultEditorWithFallback(ctx.Doer) + if err != nil { + ctx.ServerError("dev.GetEditors", err) + return + } + + ctx.Data["DevEditors"] = editors + ctx.Data["DevDefaultEditor"] = myDefaultEditor + ctx.HTML(http.StatusOK, tplSettingsAppearance) } +func ChangeConfig(ctx *context.Context) { + key := strings.TrimSpace(ctx.FormString("key")) + if key == "" { + ctx.JSON(http.StatusOK, map[string]string{ + "redirect": ctx.Req.URL.String(), + }) + return + } + value := ctx.FormString("value") + + if err := user_model.SetUserSetting(ctx.Doer.ID, key, value); err != nil { + log.Error("set setting failed: %v", err) + ctx.JSON(http.StatusOK, map[string]string{ + "err": ctx.Tr("admin.config.set_setting_failed", key), + }) + return + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "version": 1, + }) +} + // UpdateUIThemePost is used to update users' specific theme func UpdateUIThemePost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.UpdateThemeForm) diff --git a/routers/web/web.go b/routers/web/web.go index 3f356b90f12b5..3338b0688733d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -405,6 +405,7 @@ func RegisterRoutes(m *web.Route) { m.Group("/user/settings", func() { m.Get("", user_setting.Profile) m.Post("", web.Bind(forms.UpdateProfileForm{}), user_setting.ProfilePost) + m.Post("/config", user_setting.ChangeConfig) m.Get("/change_password", auth.MustChangePassword) m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost) m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost) @@ -1204,7 +1205,7 @@ func RegisterRoutes(m *web.Route) { m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) }, repo.MustEnableWiki, func(ctx *context.Context) { ctx.Data["PageIsWiki"] = true - editor, err := dev.GetDefaultEditor() + editor, err := dev.GetUserDefaultEditorWithFallback(ctx.Doer) if err != nil { ctx.ServerError("dev.GetDefaultEditor", err) return diff --git a/services/dev/editor.go b/services/dev/editor.go index 38903ae4f4c46..222cd641accd3 100644 --- a/services/dev/editor.go +++ b/services/dev/editor.go @@ -8,6 +8,8 @@ import ( "strings" "code.gitea.io/gitea/models/system" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" ) const KeyDevDefaultEditor = "dev.default_editor" @@ -77,3 +79,47 @@ func SetDefaultEditor(name string) error { } return nil } + +type ErrUnknownEditor struct { + editorName string +} + +func (e ErrUnknownEditor) Error() string { + return "Unknown editor: " + e.editorName +} + +func GetUserDefaultEditor(userID int64) (*Editor, error) { + defaultName, err := user_model.GetSetting(userID, KeyDevDefaultEditor) + if err != nil { + return nil, err + } + for _, editor := range defaultEditors { + if editor.Name == defaultName { + return &editor, nil + } + } + return nil, ErrUnknownEditor{defaultName} +} + +func SetUserDefaultEditor(userID int64, name string) error { + return user_model.SetUserSetting(userID, KeyDevDefaultEditor, name) +} + +func GetUserDefaultEditorWithFallback(user *user_model.User) (*Editor, error) { + if user == nil || user.ID <= 0 { + return GetDefaultEditor() + } + editor, err := GetUserDefaultEditor(user.ID) + if err == nil { + return editor, nil + } + + if theErr, ok := err.(ErrUnknownEditor); ok { + log.Error("Unknown editor for user %d: %s, fallback to system default", user.ID, theErr.editorName) + return GetDefaultEditor() + } + if user_model.IsErrUserSettingIsNotExist(err) { + return GetDefaultEditor() + } + return nil, err +} diff --git a/templates/user/settings/appearance.tmpl b/templates/user/settings/appearance.tmpl index 233e682621bb9..a8787368cc8f5 100644 --- a/templates/user/settings/appearance.tmpl +++ b/templates/user/settings/appearance.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -
+
{{template "user/settings/navbar" .}}
{{template "base/alert" .}} @@ -69,6 +69,35 @@
+ +

+ {{.locale.Tr "admin.config.dev_config"}} +

+
+ +
+

{{.locale.Tr "settings.hidden_comment_types"}} diff --git a/web_src/js/features/user-settings.js b/web_src/js/features/user-settings.js index 3ed29f39b1440..ff2aa8c235287 100644 --- a/web_src/js/features/user-settings.js +++ b/web_src/js/features/user-settings.js @@ -1,4 +1,7 @@ import $ from 'jquery'; +import {showTemporaryTooltip} from '../modules/tippy.js'; + +const {appSubUrl, csrfToken} = window.config; export function initUserSettings() { if ($('.user.settings.profile').length > 0) { @@ -13,5 +16,33 @@ export function initUserSettings() { $prompt_redirect.hide(); } }); + + $('.selection').dropdown({ + onChange: (_text, _value, $choice) => { + const $this = $choice.parent().parent(); + const input = $this.find('input'); + console.info($this, input); + $.ajax({ + url: `${appSubUrl}/user/settings/config`, + type: 'POST', + data: { + _csrf: csrfToken, + key: input.attr('name'), + value: input.attr('value'), + } + }).done((resp) => { + if (resp) { + if (resp.redirect) { + window.location.href = resp.redirect; + } else if (resp.version) { + input.attr('version', resp.version); + } else if (resp.err) { + showTemporaryTooltip($this, resp.err); + } + } + }); + return false; + } + }); } } From efcfc96acdea4aed64b34a6f369d9c38f4359258 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 9 Jan 2023 17:20:03 +0800 Subject: [PATCH 04/10] fix lint --- services/dev/editor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/dev/editor.go b/services/dev/editor.go index 222cd641accd3..724918c486431 100644 --- a/services/dev/editor.go +++ b/services/dev/editor.go @@ -21,7 +21,7 @@ type Editor struct { } func (e *Editor) RenderURL(repoURL string) template.URL { - return template.URL(strings.Replace(e.URL, "${repo_url}", repoURL, -1)) + return template.URL(strings.ReplaceAll(e.URL, "${repo_url}", repoURL)) } var defaultEditors = []Editor{ From e2717c10c0015c1440a4a525360fa6d607366b7d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Jan 2023 15:57:43 +0800 Subject: [PATCH 05/10] Allow chose multiple editors --- modules/context/repo.go | 4 +- modules/util/slice.go | 16 +++++ options/locale/locale_en-US.ini | 3 +- public/img/svg/gitea-idea.svg | 1 + routers/web/admin/config.go | 9 +-- routers/web/user/setting/profile.go | 12 +++- routers/web/web.go | 4 +- services/dev/editor.go | 83 +++++++++++++++---------- templates/admin/config.tmpl | 23 +++---- templates/repo/clone_script.tmpl | 4 +- templates/repo/home.tmpl | 4 +- templates/user/settings/appearance.tmpl | 13 ++-- web_src/js/features/admin/config.js | 3 +- web_src/svg/gitea-idea.svg | 63 +++++++++++++++++++ 14 files changed, 175 insertions(+), 67 deletions(-) create mode 100644 public/img/svg/gitea-idea.svg create mode 100644 web_src/svg/gitea-idea.svg diff --git a/modules/context/repo.go b/modules/context/repo.go index 32e21401305b0..dbf96746beb2f 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -588,12 +588,12 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH - editor, err := dev.GetUserDefaultEditorWithFallback(ctx.Doer) + editors, err := dev.GetUserDefaultEditorsWithFallback(ctx.Doer) if err != nil { ctx.ServerError("dev.GetDefaultEditor", err) return } - ctx.Data["CloneEditor"] = editor + ctx.Data["CloneEditors"] = editors ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled diff --git a/modules/util/slice.go b/modules/util/slice.go index 17345cbc49d97..97b47490051b2 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -3,6 +3,8 @@ package util +import "strings" + // RemoveIDFromList removes the given ID from the slice, if found. // It does not preserve order, and assumes the ID is unique. func RemoveIDFromList(list []int64, id int64) ([]int64, bool) { @@ -15,3 +17,17 @@ func RemoveIDFromList(list []int64, id int64) ([]int64, bool) { } return list, false } + +func SplitStringWithTrim(s, sep string) []string { + if len(s) == 0 { + return nil + } + var result []string + for _, word := range strings.Split(s, sep) { + word = strings.TrimSpace(word) + if len(word) > 0 { + result = append(result, word) + } + } + return result +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 0abcd4a0903e3..5ee9394b19134 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2912,7 +2912,8 @@ config.get_setting_failed = Get setting %s failed config.set_setting_failed = Set setting %s failed config.dev_config = Development -config.dev_default_editor = Default Editor +config.dev_default_editors = Default Editors +config.dev_default_editors_desc = Chose default editors monitor.cron = Cron Tasks monitor.name = Name diff --git a/public/img/svg/gitea-idea.svg b/public/img/svg/gitea-idea.svg new file mode 100644 index 0000000000000..78d2912fe88fb --- /dev/null +++ b/public/img/svg/gitea-idea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index 0436fea9322ff..3db2fca05d74f 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -194,16 +194,17 @@ func Config(ctx *context.Context) { return } - defaultEditorS := systemSettings.Get(dev.KeyDevDefaultEditor) + defaultEditorS := systemSettings.Get(dev.KeyDevDefaultEditors) if defaultEditorS.SettingValue == "" { defaultEditorS = system_model.Setting{ - SettingKey: dev.KeyDevDefaultEditor, - SettingValue: dev.DefaultEditorName(), + SettingKey: dev.KeyDevDefaultEditors, + SettingValue: dev.DefaultEditorsNames(), } } ctx.Data["DevEditors"] = editors - ctx.Data["DevDefaultEditor"] = dev.GetEditorByName(defaultEditorS.SettingValue) + ctx.Data["DevDefaultEditorNames"] = defaultEditorS.SettingValue + ctx.Data["DevDefaultEditorVersion"] = defaultEditorS.Version ctx.HTML(http.StatusOK, tplConfig) } diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 455b76e64452f..6091c6042a7b1 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -382,14 +382,22 @@ func Appearance(ctx *context.Context) { return } - myDefaultEditor, err := dev.GetUserDefaultEditorWithFallback(ctx.Doer) + myDefaultEditors, err := dev.GetUserDefaultEditorsWithFallback(ctx.Doer) if err != nil { ctx.ServerError("dev.GetEditors", err) return } + var myEditorNames string + for i, editor := range myDefaultEditors { + if i > 0 { + myEditorNames = myEditorNames + "," + } + myEditorNames = myEditorNames + editor.Name + } + ctx.Data["DevEditors"] = editors - ctx.Data["DevDefaultEditor"] = myDefaultEditor + ctx.Data["DevDefaultEditorNames"] = myEditorNames ctx.HTML(http.StatusOK, tplSettingsAppearance) } diff --git a/routers/web/web.go b/routers/web/web.go index 3338b0688733d..2ce983f3d2928 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1205,12 +1205,12 @@ func RegisterRoutes(m *web.Route) { m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) }, repo.MustEnableWiki, func(ctx *context.Context) { ctx.Data["PageIsWiki"] = true - editor, err := dev.GetUserDefaultEditorWithFallback(ctx.Doer) + editors, err := dev.GetUserDefaultEditorsWithFallback(ctx.Doer) if err != nil { ctx.ServerError("dev.GetDefaultEditor", err) return } - ctx.Data["CloneEditor"] = editor + ctx.Data["CloneEditors"] = editors ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink() }) diff --git a/services/dev/editor.go b/services/dev/editor.go index 724918c486431..1979852d7516d 100644 --- a/services/dev/editor.go +++ b/services/dev/editor.go @@ -12,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" ) -const KeyDevDefaultEditor = "dev.default_editor" +const KeyDevDefaultEditors = "dev.default_editors" type Editor struct { Name string @@ -35,6 +35,11 @@ var defaultEditors = []Editor{ URL: "vscodium://vscode.git/clone?url=${repo_url}", Icon: `gitea-vscodium`, }, + { + Name: "IDEA", + URL: "jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo=${repo-url}", + Icon: `gitea-idea`, + }, } func GetEditorByName(name string) *Editor { @@ -46,38 +51,48 @@ func GetEditorByName(name string) *Editor { return nil } +func GetEditorsByNames(names []string) []*Editor { + editors := make([]*Editor, 0, len(names)) + for _, name := range names { + if editor := GetEditorByName(name); editor != nil { + editors = append(editors, editor) + } else { + log.Error("Unknown editor: %s", name) + } + } + return editors +} + // GetEditors returns all editors func GetEditors() ([]Editor, error) { return defaultEditors, nil } -func DefaultEditorName() string { +func DefaultEditorsNames() string { return defaultEditors[0].Name } -func GetDefaultEditor() (*Editor, error) { - defaultName, err := system.GetSetting(KeyDevDefaultEditor) +func GetDefaultEditors() ([]*Editor, error) { + defaultNames, err := system.GetSetting(KeyDevDefaultEditors) if err != nil && !system.IsErrSettingIsNotExist(err) { return nil, err } - for _, editor := range defaultEditors { - if editor.Name == defaultName { - return &editor, nil - } - } - return &defaultEditors[0], nil + names := strings.Split(defaultNames, ",") + return GetEditorsByNames(names), nil } -func SetDefaultEditor(name string) error { - for _, editor := range defaultEditors { - if editor.Name == name { - return system.SetSetting(&system.Setting{ - SettingKey: KeyDevDefaultEditor, - SettingValue: name, - }) +func SetDefaultEditors(names []string) error { + var validateNames []string + for _, name := range names { + if editor := GetEditorByName(name); editor != nil { + validateNames = append(validateNames, name) } } - return nil + + return system.SetSetting(&system.Setting{ + SettingKey: KeyDevDefaultEditors, + SettingValue: strings.Join(validateNames, ","), + }) } type ErrUnknownEditor struct { @@ -88,38 +103,40 @@ func (e ErrUnknownEditor) Error() string { return "Unknown editor: " + e.editorName } -func GetUserDefaultEditor(userID int64) (*Editor, error) { - defaultName, err := user_model.GetSetting(userID, KeyDevDefaultEditor) +func GetUserDefaultEditors(userID int64) ([]*Editor, error) { + defaultNames, err := user_model.GetSetting(userID, KeyDevDefaultEditors) if err != nil { return nil, err } - for _, editor := range defaultEditors { - if editor.Name == defaultName { - return &editor, nil - } - } - return nil, ErrUnknownEditor{defaultName} + names := strings.Split(defaultNames, ",") + return GetEditorsByNames(names), nil } -func SetUserDefaultEditor(userID int64, name string) error { - return user_model.SetUserSetting(userID, KeyDevDefaultEditor, name) +func SetUserDefaultEditors(userID int64, names []string) error { + var validateNames []string + for _, name := range names { + if editor := GetEditorByName(name); editor != nil { + validateNames = append(validateNames, name) + } + } + return user_model.SetUserSetting(userID, KeyDevDefaultEditors, strings.Join(validateNames, ",")) } -func GetUserDefaultEditorWithFallback(user *user_model.User) (*Editor, error) { +func GetUserDefaultEditorsWithFallback(user *user_model.User) ([]*Editor, error) { if user == nil || user.ID <= 0 { - return GetDefaultEditor() + return GetDefaultEditors() } - editor, err := GetUserDefaultEditor(user.ID) + editor, err := GetUserDefaultEditors(user.ID) if err == nil { return editor, nil } if theErr, ok := err.(ErrUnknownEditor); ok { log.Error("Unknown editor for user %d: %s, fallback to system default", user.ID, theErr.editorName) - return GetDefaultEditor() + return GetDefaultEditors() } if user_model.IsErrUserSettingIsNotExist(err) { - return GetDefaultEditor() + return GetDefaultEditors() } return nil, err } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index fa15cfc9feb97..88111c5d8a2d0 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -417,17 +417,18 @@

{{.locale.Tr "admin.config.dev_config"}}

-
-
-
{{$.locale.Tr "admin.config.dev_default_editor"}}
-
-
-
+
+

diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl index 6435fde373cd2..16aa0fdaf2cd8 100644 --- a/templates/repo/clone_script.tmpl +++ b/templates/repo/clone_script.tmpl @@ -23,8 +23,8 @@ } for (const el of document.getElementsByClassName('js-clone-url-editor')) { let repo_url = encodeURIComponent(link); - console.info(el, el.getAttribute('data-url'), repo_url); - el['href'] = el.getAttribute('data-url'); + let data_url = decodeURI(el.getAttribute('data-url')) + el['href'] = data_url.replaceAll('${repo_url}', repo_url); } })(); diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index aac8a93d5691d..a6ce618c2377a 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -128,7 +128,9 @@ {{svg "octicon-cross-reference" 16 "mr-3"}}{{.locale.Tr "repo.cite_this_repo"}} {{end}} {{end}} - {{svg .CloneEditor.Icon 16 "mr-3"}}{{.locale.Tr "repo.clone_in_editor" .CloneEditor.Name}} + {{range .CloneEditors}} + {{svg .Icon 16 "mr-3"}}{{$.locale.Tr "repo.clone_in_editor" .Name}} + {{end}} {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} diff --git a/templates/user/settings/appearance.tmpl b/templates/user/settings/appearance.tmpl index a8787368cc8f5..cdcf015638c59 100644 --- a/templates/user/settings/appearance.tmpl +++ b/templates/user/settings/appearance.tmpl @@ -74,18 +74,17 @@ {{.locale.Tr "admin.config.dev_config"}}
-