feat: allow setting ace editor theme (#3826)

Co-authored-by: Henrique Dias <mail@hacdias.com>
This commit is contained in:
Adam 2025-09-25 16:47:00 +02:00 committed by GitHub
parent dec7a02737
commit b9787c78f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 104 additions and 35 deletions

View File

@ -221,6 +221,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tFile Creation Mode:\t%O\n", set.FileMode) fmt.Fprintf(w, "\tFile Creation Mode:\t%O\n", set.FileMode)
fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode) fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode)
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " ")) 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, "\tSorting:\n")
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By) fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc) fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)

View File

@ -424,9 +424,10 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
MinimumPasswordLength: settings.DefaultMinimumPasswordLength, MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
UserHomeBasePath: settings.DefaultUsersHomeBasePath, UserHomeBasePath: settings.DefaultUsersHomeBasePath,
Defaults: settings.UserDefaults{ Defaults: settings.UserDefaults{
Scope: ".", Scope: ".",
Locale: "en", Locale: "en",
SingleClick: false, SingleClick: false,
AceEditorTheme: getStringParam(flags, "defaults.aceEditorTheme"),
Perm: users.Permissions{ Perm: users.Permissions{
Admin: false, Admin: false,
Execute: true, Execute: true,

View File

@ -79,6 +79,7 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.Bool("singleClick", false, "use single clicks only") flags.Bool("singleClick", false, "use single clicks only")
flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)") flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)")
flags.Bool("hideDotfiles", false, "hide dotfiles") flags.Bool("hideDotfiles", false, "hide dotfiles")
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
} }
func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) { func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
@ -110,6 +111,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
defaults.ViewMode, err = getViewMode(flags) defaults.ViewMode, err = getViewMode(flags)
case "singleClick": case "singleClick":
defaults.SingleClick, err = getBool(flags, flag.Name) defaults.SingleClick, err = getBool(flags, flag.Name)
case "aceEditorTheme":
defaults.AceEditorTheme, err = getString(flags, flag.Name)
case "perm.admin": case "perm.admin":
defaults.Perm.Admin, err = getBool(flags, flag.Name) defaults.Perm.Admin, err = getBool(flags, flag.Name)
case "perm.execute": case "perm.execute":

View File

@ -0,0 +1,24 @@
<template>
<select name="selectAceEditorTheme" v-on:change="change" :value="aceEditorTheme">
<option v-for="theme in themes" :value="theme.theme" :key="theme.theme">
{{ theme.name }}
</option>
</select>
</template>
<script setup lang="ts">
import { type SelectHTMLAttributes } from "vue";
import { themes } from "ace-builds/src-noconflict/ext-themelist";
defineProps<{
aceEditorTheme: string;
}>();
const emit = defineEmits<{
(e: "update:aceEditorTheme", val: string | null): void;
}>();
const change = (event: Event) => {
emit("update:aceEditorTheme", (event.target as SelectHTMLAttributes)?.value);
};
</script>

View File

@ -158,6 +158,7 @@
"video": "Video" "video": "Video"
}, },
"settings": { "settings": {
"aceEditorTheme": "Ace editor theme",
"admin": "Admin", "admin": "Admin",
"administrator": "Administrator", "administrator": "Administrator",
"allowCommands": "Execute commands", "allowCommands": "Execute commands",

View File

@ -21,6 +21,7 @@ interface SettingsDefaults {
commands: any[]; commands: any[];
hideDotfiles: boolean; hideDotfiles: boolean;
dateFormat: boolean; dateFormat: boolean;
aceEditorTheme: string;
} }
interface SettingsBranding { interface SettingsBranding {

View File

@ -13,6 +13,7 @@ interface IUser {
dateFormat: boolean; dateFormat: boolean;
viewMode: ViewModeType; viewMode: ViewModeType;
sorting?: Sorting; sorting?: Sorting;
aceEditorTheme: string;
} }
type ViewModeType = "list" | "mosaic" | "mosaic gallery"; type ViewModeType = "list" | "mosaic" | "mosaic gallery";

View File

@ -1,4 +1,6 @@
import { theme } from "./constants"; import { theme } from "./constants";
import "ace-builds";
import { themesByName } from "ace-builds/src-noconflict/ext-themelist";
export const getTheme = (): UserTheme => { export const getTheme = (): UserTheme => {
return (document.documentElement.className as UserTheme) || theme; return (document.documentElement.className as UserTheme) || theme;
@ -32,3 +34,17 @@ export const getMediaPreference = (): UserTheme => {
return "light"; 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";
}
};

View File

@ -69,7 +69,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { useFileStore } from "@/stores/file"; import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { getTheme } from "@/utils/theme"; import { getEditorTheme } from "@/utils/theme";
import { marked } from "marked"; import { marked } from "marked";
import { inject, onBeforeUnmount, onMounted, ref, watchEffect } from "vue"; import { inject, onBeforeUnmount, onMounted, ref, watchEffect } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -122,7 +122,7 @@ onMounted(() => {
value: fileContent, value: fileContent,
showPrintMargin: false, showPrintMargin: false,
readOnly: fileStore.req?.type === "textImmutable", readOnly: fileStore.req?.type === "textImmutable",
theme: "ace/theme/chrome", theme: getEditorTheme(authStore.user?.aceEditorTheme ?? ""),
mode: modelist.getModeForPath(fileStore.req!.name).mode, mode: modelist.getModeForPath(fileStore.req!.name).mode,
wrap: true, wrap: true,
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
@ -130,10 +130,6 @@ onMounted(() => {
enableSnippets: true, enableSnippets: true,
}); });
if (getTheme() === "dark") {
editor.value!.setTheme("ace/theme/twilight");
}
editor.value.setFontSize(fontSize.value); editor.value.setFontSize(fontSize.value);
editor.value.focus(); editor.value.focus();
}); });
@ -219,6 +215,13 @@ const decreaseFontSize = () => {
}; };
const close = () => { const close = () => {
if (!editor.value?.session.getUndoManager().isClean()) {
layoutStore.showHover("discardEditorChanges");
return;
}
fileStore.updateRequest(null);
const uri = url.removeLastDir(route.path) + "/"; const uri = url.removeLastDir(route.path) + "/";
router.push({ path: uri }); router.push({ path: uri });
}; };

View File

@ -24,6 +24,13 @@
class="input input--block" class="input input--block"
v-model:locale="locale" v-model:locale="locale"
></languages> ></languages>
<h3>{{ t("settings.aceEditorTheme") }}</h3>
<AceEditorTheme
class="input input--block"
v-model:aceEditorTheme="aceEditorTheme"
id="aceTheme"
></AceEditorTheme>
</div> </div>
<div class="card-action"> <div class="card-action">
@ -81,6 +88,7 @@
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { users as api } from "@/api"; import { users as api } from "@/api";
import AceEditorTheme from "@/components/settings/AceEditorTheme.vue";
import Languages from "@/components/settings/Languages.vue"; import Languages from "@/components/settings/Languages.vue";
import { computed, inject, onMounted, ref } from "vue"; import { computed, inject, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -98,6 +106,7 @@ const hideDotfiles = ref<boolean>(false);
const singleClick = ref<boolean>(false); const singleClick = ref<boolean>(false);
const dateFormat = ref<boolean>(false); const dateFormat = ref<boolean>(false);
const locale = ref<string>(""); const locale = ref<string>("");
const aceEditorTheme = ref<string>("");
const passwordClass = computed(() => { const passwordClass = computed(() => {
const baseClass = "input input--block"; const baseClass = "input input--block";
@ -113,13 +122,14 @@ const passwordClass = computed(() => {
return `${baseClass} input--red`; return `${baseClass} input--red`;
}); });
onMounted(() => { onMounted(async () => {
layoutStore.loading = true; layoutStore.loading = true;
if (authStore.user === null) return false; if (authStore.user === null) return false;
locale.value = authStore.user.locale; locale.value = authStore.user.locale;
hideDotfiles.value = authStore.user.hideDotfiles; hideDotfiles.value = authStore.user.hideDotfiles;
singleClick.value = authStore.user.singleClick; singleClick.value = authStore.user.singleClick;
dateFormat.value = authStore.user.dateFormat; dateFormat.value = authStore.user.dateFormat;
aceEditorTheme.value = authStore.user.aceEditorTheme;
layoutStore.loading = false; layoutStore.loading = false;
return true; return true;
}); });
@ -163,6 +173,7 @@ const updateSettings = async (event: Event) => {
hideDotfiles: hideDotfiles.value, hideDotfiles: hideDotfiles.value,
singleClick: singleClick.value, singleClick: singleClick.value,
dateFormat: dateFormat.value, dateFormat: dateFormat.value,
aceEditorTheme: aceEditorTheme.value,
}; };
await api.update(data, [ await api.update(data, [
@ -170,6 +181,7 @@ const updateSettings = async (event: Event) => {
"hideDotfiles", "hideDotfiles",
"singleClick", "singleClick",
"dateFormat", "dateFormat",
"aceEditorTheme",
]); ]);
authStore.updateUser(data); authStore.updateUser(data);
$showSuccess(t("settings.settingsUpdated")); $showSuccess(t("settings.settingsUpdated"));

View File

@ -31,6 +31,7 @@ type userInfo struct {
HideDotfiles bool `json:"hideDotfiles"` HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"` DateFormat bool `json:"dateFormat"`
Username string `json:"username"` Username string `json:"username"`
AceEditorTheme string `json:"aceEditorTheme"`
} }
type authToken struct { type authToken struct {
@ -200,6 +201,7 @@ func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.Use
HideDotfiles: user.HideDotfiles, HideDotfiles: user.HideDotfiles,
DateFormat: user.DateFormat, DateFormat: user.DateFormat,
Username: user.Username, Username: user.Username,
AceEditorTheme: user.AceEditorTheme,
}, },
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),

View File

@ -8,15 +8,16 @@ import (
// UserDefaults is a type that holds the default values // UserDefaults is a type that holds the default values
// for some fields on User. // for some fields on User.
type UserDefaults struct { type UserDefaults struct {
Scope string `json:"scope"` Scope string `json:"scope"`
Locale string `json:"locale"` Locale string `json:"locale"`
ViewMode users.ViewMode `json:"viewMode"` ViewMode users.ViewMode `json:"viewMode"`
SingleClick bool `json:"singleClick"` SingleClick bool `json:"singleClick"`
Sorting files.Sorting `json:"sorting"` Sorting files.Sorting `json:"sorting"`
Perm users.Permissions `json:"perm"` Perm users.Permissions `json:"perm"`
Commands []string `json:"commands"` Commands []string `json:"commands"`
HideDotfiles bool `json:"hideDotfiles"` HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"` DateFormat bool `json:"dateFormat"`
AceEditorTheme string `json:"aceEditorTheme"`
} }
// Apply applies the default options to a user. // Apply applies the default options to a user.
@ -30,4 +31,5 @@ func (d *UserDefaults) Apply(u *users.User) {
u.Commands = d.Commands u.Commands = d.Commands
u.HideDotfiles = d.HideDotfiles u.HideDotfiles = d.HideDotfiles
u.DateFormat = d.DateFormat u.DateFormat = d.DateFormat
u.AceEditorTheme = d.AceEditorTheme
} }

View File

@ -3,5 +3,6 @@
"@commitlint/cli": "^15.0.0", "@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0", "@commitlint/config-conventional": "^15.0.0",
"standard-version": "^9.3.2" "standard-version": "^9.3.2"
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@ -20,21 +20,22 @@ const (
// User describes a user. // User describes a user.
type User struct { type User struct {
ID uint `storm:"id,increment" json:"id"` ID uint `storm:"id,increment" json:"id"`
Username string `storm:"unique" json:"username"` Username string `storm:"unique" json:"username"`
Password string `json:"password"` Password string `json:"password"`
Scope string `json:"scope"` Scope string `json:"scope"`
Locale string `json:"locale"` Locale string `json:"locale"`
LockPassword bool `json:"lockPassword"` LockPassword bool `json:"lockPassword"`
ViewMode ViewMode `json:"viewMode"` ViewMode ViewMode `json:"viewMode"`
SingleClick bool `json:"singleClick"` SingleClick bool `json:"singleClick"`
Perm Permissions `json:"perm"` Perm Permissions `json:"perm"`
Commands []string `json:"commands"` Commands []string `json:"commands"`
Sorting files.Sorting `json:"sorting"` Sorting files.Sorting `json:"sorting"`
Fs afero.Fs `json:"-" yaml:"-"` Fs afero.Fs `json:"-" yaml:"-"`
Rules []rules.Rule `json:"rules"` Rules []rules.Rule `json:"rules"`
HideDotfiles bool `json:"hideDotfiles"` HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"` DateFormat bool `json:"dateFormat"`
AceEditorTheme string `json:"aceEditorTheme"`
} }
// GetRules implements rules.Provider. // GetRules implements rules.Provider.