diff --git a/cmd/config.go b/cmd/config.go index 3ee8dab8..d62467e8 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -171,6 +171,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode) fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick) fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " ")) + fmt.Fprintf(w, "\tAce editor syntax highlighting theme:\t%s\n", set.Defaults.AceEditorTheme) fmt.Fprintf(w, "\tSorting:\n") fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By) fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc) diff --git a/cmd/root.go b/cmd/root.go index 4b6819b7..0c5f28e6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -371,9 +371,10 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { MinimumPasswordLength: settings.DefaultMinimumPasswordLength, UserHomeBasePath: settings.DefaultUsersHomeBasePath, Defaults: settings.UserDefaults{ - Scope: ".", - Locale: "en", - SingleClick: false, + Scope: ".", + Locale: "en", + SingleClick: false, + AceEditorTheme: getParam(flags, "defaults.aceEditorTheme"), Perm: users.Permissions{ Admin: false, Execute: true, diff --git a/cmd/users.go b/cmd/users.go index d3f97da6..5cd62d45 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -77,6 +77,7 @@ func addUserFlags(flags *pflag.FlagSet) { flags.String("locale", "en", "locale for users") flags.String("viewMode", string(users.ListViewMode), "view mode for users") flags.Bool("singleClick", false, "use single clicks only") + flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users") } func getViewMode(flags *pflag.FlagSet) users.ViewMode { @@ -99,6 +100,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all defaults.ViewMode = getViewMode(flags) case "singleClick": defaults.SingleClick = mustGetBool(flags, flag.Name) + case "aceEditorTheme": + defaults.AceEditorTheme = mustGetString(flags, flag.Name) case "perm.admin": defaults.Perm.Admin = mustGetBool(flags, flag.Name) case "perm.execute": diff --git a/frontend/src/components/settings/AceEditorTheme.vue b/frontend/src/components/settings/AceEditorTheme.vue new file mode 100644 index 00000000..09efe48b --- /dev/null +++ b/frontend/src/components/settings/AceEditorTheme.vue @@ -0,0 +1,24 @@ + + + diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 7de128ed..6351c539 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -154,6 +154,7 @@ "video": "Video" }, "settings": { + "aceEditorTheme": "Ace editor theme", "admin": "Admin", "administrator": "Administrator", "allowCommands": "Execute commands", diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts index 90ca6ae7..ba56c612 100644 --- a/frontend/src/types/settings.d.ts +++ b/frontend/src/types/settings.d.ts @@ -21,6 +21,7 @@ interface SettingsDefaults { commands: any[]; hideDotfiles: boolean; dateFormat: boolean; + aceEditorTheme: string; } interface SettingsBranding { diff --git a/frontend/src/types/user.d.ts b/frontend/src/types/user.d.ts index b81806fc..40c453c5 100644 --- a/frontend/src/types/user.d.ts +++ b/frontend/src/types/user.d.ts @@ -13,6 +13,7 @@ interface IUser { dateFormat: boolean; viewMode: ViewModeType; sorting?: Sorting; + aceEditorTheme: string; } type ViewModeType = "list" | "mosaic" | "mosaic gallery"; diff --git a/frontend/src/utils/theme.ts b/frontend/src/utils/theme.ts index 8058356a..0244b044 100644 --- a/frontend/src/utils/theme.ts +++ b/frontend/src/utils/theme.ts @@ -1,4 +1,6 @@ import { theme } from "./constants"; +import "ace-builds"; +import { themesByName } from "ace-builds/src-noconflict/ext-themelist"; export const getTheme = (): UserTheme => { return (document.documentElement.className as UserTheme) || theme; @@ -32,3 +34,17 @@ export const getMediaPreference = (): UserTheme => { return "light"; } }; + +export const getEditorTheme = (themeName: string) => { + if (!themeName.startsWith("ace/theme/")) { + themeName = `ace/theme/${themeName}`; + } + const themeKey = themeName.replace("ace/theme/", ""); + if (themesByName[themeKey] !== undefined) { + return themeName; + } else if (getTheme() === "dark") { + return "ace/theme/twilight"; + } else { + return "ace/theme/chrome"; + } +}; diff --git a/frontend/src/views/files/Editor.vue b/frontend/src/views/files/Editor.vue index d78e6afe..58a31e0f 100644 --- a/frontend/src/views/files/Editor.vue +++ b/frontend/src/views/files/Editor.vue @@ -52,7 +52,7 @@ import { useLayoutStore } from "@/stores/layout"; import { inject, onBeforeUnmount, onMounted, ref, watchEffect } from "vue"; import { useRoute, useRouter } from "vue-router"; import { useI18n } from "vue-i18n"; -import { getTheme } from "@/utils/theme"; +import { getEditorTheme } from "@/utils/theme"; import { marked } from "marked"; const $showError = inject("$showError")!; @@ -108,7 +108,7 @@ onMounted(() => { value: fileContent, showPrintMargin: false, readOnly: fileStore.req?.type === "textImmutable", - theme: "ace/theme/chrome", + theme: getEditorTheme(authStore.user?.aceEditorTheme ?? ""), mode: modelist.getModeForPath(fileStore.req!.name).mode, wrap: true, enableBasicAutocompletion: true, @@ -116,10 +116,6 @@ onMounted(() => { enableSnippets: true, }); - if (getTheme() === "dark") { - editor.value!.setTheme("ace/theme/twilight"); - } - editor.value.focus(); }); diff --git a/frontend/src/views/settings/Profile.vue b/frontend/src/views/settings/Profile.vue index c677092f..1b079473 100644 --- a/frontend/src/views/settings/Profile.vue +++ b/frontend/src/views/settings/Profile.vue @@ -24,6 +24,13 @@ class="input input--block" v-model:locale="locale" > + +

{{ t("settings.aceEditorTheme") }}

+
@@ -81,6 +88,7 @@ import { useAuthStore } from "@/stores/auth"; import { useLayoutStore } from "@/stores/layout"; import { users as api } from "@/api"; +import AceEditorTheme from "@/components/settings/AceEditorTheme.vue"; import Languages from "@/components/settings/Languages.vue"; import { computed, inject, onMounted, ref } from "vue"; import { useI18n } from "vue-i18n"; @@ -98,6 +106,7 @@ const hideDotfiles = ref(false); const singleClick = ref(false); const dateFormat = ref(false); const locale = ref(""); +const aceEditorTheme = ref(""); const passwordClass = computed(() => { const baseClass = "input input--block"; @@ -113,13 +122,14 @@ const passwordClass = computed(() => { return `${baseClass} input--red`; }); -onMounted(() => { +onMounted(async () => { layoutStore.loading = true; if (authStore.user === null) return false; locale.value = authStore.user.locale; hideDotfiles.value = authStore.user.hideDotfiles; singleClick.value = authStore.user.singleClick; dateFormat.value = authStore.user.dateFormat; + aceEditorTheme.value = authStore.user.aceEditorTheme; layoutStore.loading = false; return true; }); @@ -163,6 +173,7 @@ const updateSettings = async (event: Event) => { hideDotfiles: hideDotfiles.value, singleClick: singleClick.value, dateFormat: dateFormat.value, + aceEditorTheme: aceEditorTheme.value, }; await api.update(data, [ @@ -170,6 +181,7 @@ const updateSettings = async (event: Event) => { "hideDotfiles", "singleClick", "dateFormat", + "aceEditorTheme", ]); authStore.updateUser(data); $showSuccess(t("settings.settingsUpdated")); diff --git a/http/auth.go b/http/auth.go index 304762c8..23e0925e 100644 --- a/http/auth.go +++ b/http/auth.go @@ -21,15 +21,16 @@ const ( ) type userInfo struct { - ID uint `json:"id"` - Locale string `json:"locale"` - ViewMode users.ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Perm users.Permissions `json:"perm"` - Commands []string `json:"commands"` - LockPassword bool `json:"lockPassword"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` + ID uint `json:"id"` + Locale string `json:"locale"` + ViewMode users.ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + Perm users.Permissions `json:"perm"` + Commands []string `json:"commands"` + LockPassword bool `json:"lockPassword"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + AceEditorTheme string `json:"aceEditorTheme"` } type authToken struct { @@ -189,15 +190,16 @@ func renewHandler(tokenExpireTime time.Duration) handleFunc { func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User, tokenExpirationTime time.Duration) (int, error) { claims := &authToken{ User: userInfo{ - ID: user.ID, - Locale: user.Locale, - ViewMode: user.ViewMode, - SingleClick: user.SingleClick, - Perm: user.Perm, - LockPassword: user.LockPassword, - Commands: user.Commands, - HideDotfiles: user.HideDotfiles, - DateFormat: user.DateFormat, + ID: user.ID, + Locale: user.Locale, + ViewMode: user.ViewMode, + SingleClick: user.SingleClick, + Perm: user.Perm, + LockPassword: user.LockPassword, + Commands: user.Commands, + HideDotfiles: user.HideDotfiles, + DateFormat: user.DateFormat, + AceEditorTheme: user.AceEditorTheme, }, RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now()), diff --git a/settings/defaults.go b/settings/defaults.go index d60e36dc..5b6c3f2a 100644 --- a/settings/defaults.go +++ b/settings/defaults.go @@ -8,15 +8,16 @@ import ( // UserDefaults is a type that holds the default values // for some fields on User. type UserDefaults struct { - Scope string `json:"scope"` - Locale string `json:"locale"` - ViewMode users.ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Sorting files.Sorting `json:"sorting"` - Perm users.Permissions `json:"perm"` - Commands []string `json:"commands"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` + Scope string `json:"scope"` + Locale string `json:"locale"` + ViewMode users.ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + Sorting files.Sorting `json:"sorting"` + Perm users.Permissions `json:"perm"` + Commands []string `json:"commands"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + AceEditorTheme string `json:"aceEditorTheme"` } // Apply applies the default options to a user. @@ -30,4 +31,5 @@ func (d *UserDefaults) Apply(u *users.User) { u.Commands = d.Commands u.HideDotfiles = d.HideDotfiles u.DateFormat = d.DateFormat + u.AceEditorTheme = d.AceEditorTheme } diff --git a/users/users.go b/users/users.go index e0310f21..020faf11 100644 --- a/users/users.go +++ b/users/users.go @@ -20,21 +20,22 @@ const ( // User describes a user. type User struct { - ID uint `storm:"id,increment" json:"id"` - Username string `storm:"unique" json:"username"` - Password string `json:"password"` - Scope string `json:"scope"` - Locale string `json:"locale"` - LockPassword bool `json:"lockPassword"` - ViewMode ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Perm Permissions `json:"perm"` - Commands []string `json:"commands"` - Sorting files.Sorting `json:"sorting"` - Fs afero.Fs `json:"-" yaml:"-"` - Rules []rules.Rule `json:"rules"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` + ID uint `storm:"id,increment" json:"id"` + Username string `storm:"unique" json:"username"` + Password string `json:"password"` + Scope string `json:"scope"` + Locale string `json:"locale"` + LockPassword bool `json:"lockPassword"` + ViewMode ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + Perm Permissions `json:"perm"` + Commands []string `json:"commands"` + Sorting files.Sorting `json:"sorting"` + Fs afero.Fs `json:"-" yaml:"-"` + Rules []rules.Rule `json:"rules"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + AceEditorTheme string `json:"aceEditorTheme"` } // GetRules implements rules.Provider.