Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support system/user default clone editors #22378

Closed
wants to merge 16 commits into from
16 changes: 11 additions & 5 deletions models/user/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions modules/context/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -587,6 +588,12 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
}
ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS
ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH
editor, err := dev.GetUserDefaultEditorWithFallback(ctx.Doer)
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
Expand Down
6 changes: 6 additions & 0 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func NewFuncMap() []template.FuncMap {
},
"Safe": Safe,
"SafeJS": SafeJS,
"SafeURL": SafeURL,
"JSEscape": JSEscape,
"Str2html": Str2html,
"TimeSince": timeutil.TimeSince,
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions public/img/svg/gitea-vscodium.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions routers/web/admin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

Expand Down
39 changes: 39 additions & 0 deletions routers/web/user/setting/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -404,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)
Expand Down Expand Up @@ -1203,6 +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)
if err != nil {
ctx.ServerError("dev.GetDefaultEditor", err)
return
}
ctx.Data["CloneEditor"] = editor
ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink()
})

Expand Down
125 changes: 125 additions & 0 deletions services/dev/editor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
lunny marked this conversation as resolved.
Show resolved Hide resolved
// SPDX-License-Identifier: MIT

package dev

import (
"html/template"
"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"

type Editor struct {
Name string
URL string
Icon string
}

func (e *Editor) RenderURL(repoURL string) template.URL {
return template.URL(strings.ReplaceAll(e.URL, "${repo_url}", repoURL))
}

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
}
lunny marked this conversation as resolved.
Show resolved Hide resolved

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
}

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
}
26 changes: 26 additions & 0 deletions templates/admin/config.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,32 @@
{{end}}
</dl>
</div>

<h4 class="ui top attached header">
{{.locale.Tr "admin.config.dev_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dt>{{$.locale.Tr "admin.config.dev_default_editor"}}</dt>
<dd>
<div class="ui floatting selection dropdown">
lunny marked this conversation as resolved.
Show resolved Hide resolved
<input type="hidden" name="dev.default_editor" version="{{$.DevDefaultEditorVersion}}">
<div class="default text">
{{svg $.DevDefaultEditor.Icon 14}}
{{$.DevDefaultEditor.Name}} - {{$.DevDefaultEditor.URL}}
</div>
<div class="menu floatting">
lunny marked this conversation as resolved.
Show resolved Hide resolved
{{range .DevEditors}}
<div class="item" data-value="{{.Name}}">
{{svg .Icon 14}}
{{.Name}} - {{.URL}}
</div>
{{end}}
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
{{template "base/footer" .}}
8 changes: 5 additions & 3 deletions templates/repo/clone_script.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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');
}
})();
</script>
Loading