feat: Typesafe Settings Definitions (#403)

Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
Justice Almanzar 2023-01-13 17:15:45 -05:00 committed by GitHub
parent 6c5fcc4119
commit ea748dfb60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 288 additions and 180 deletions

View file

@ -16,25 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { useSettings } from "@api/settings";
import { PartialExcept } from "@utils/types";
import { React } from "@webpack/common";
import { shiki } from "../api/shiki";
import { ShikiSettings } from "../types";
import { settings as pluginSettings, ShikiSettings } from "../settings";
export function useShikiSettings(settingKeys: (keyof ShikiSettings)[], overrides?: Record<string, any>) {
const settings = useSettings(settingKeys.map(key => `plugins.ShikiCodeblocks.${key}`)).plugins.ShikiCodeblocks as ShikiSettings;
export function useShikiSettings<F extends keyof ShikiSettings>(settingKeys: F[], overrides?: Partial<ShikiSettings>) {
const settings: Partial<ShikiSettings> = pluginSettings.use(settingKeys);
const [isLoading, setLoading] = React.useState(false);
const withOverrides = { ...settings, ...overrides };
const withOverrides = { ...settings, ...overrides } as PartialExcept<ShikiSettings, F>;
const themeUrl = withOverrides.customTheme || withOverrides.theme;
if (overrides) {
const willChangeTheme = shiki.currentThemeUrl && themeUrl !== shiki.currentThemeUrl;
const willChangeTheme = shiki.currentThemeUrl && themeUrl && themeUrl !== shiki.currentThemeUrl;
const noOverrides = Object.keys(overrides).length === 0;
if (isLoading && (!willChangeTheme || noOverrides)) setLoading(false);
if ((!isLoading && willChangeTheme)) {
if (!isLoading && willChangeTheme) {
setLoading(true);
shiki.setTheme(themeUrl);
}

View file

@ -18,26 +18,19 @@
import "./shiki.css";
import { disableStyle, enableStyle } from "@api/Styles";
import { enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
import { parseUrl } from "@utils/misc";
import { wordsFromPascal, wordsToTitle } from "@utils/text";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin from "@utils/types";
import previewExampleText from "~fileContent/previewExample.tsx";
import { Settings } from "../../Vencord";
import { shiki } from "./api/shiki";
import { themes } from "./api/themes";
import { createHighlighter } from "./components/Highlighter";
import deviconStyle from "./devicon.css?managed";
import { DeviconSetting, HljsSetting, ShikiSettings } from "./types";
import { settings } from "./settings";
import { DeviconSetting } from "./types";
import { clearStyles } from "./utils/createStyle";
const themeNames = Object.keys(themes);
const getSettings = () => Settings.plugins.ShikiCodeblocks as ShikiSettings;
export default definePlugin({
name: "ShikiCodeblocks",
description: "Brings vscode-style codeblocks into Discord, powered by Shiki",
@ -52,10 +45,10 @@ export default definePlugin({
},
],
start: async () => {
if (getSettings().useDevIcon !== DeviconSetting.Disabled)
if (settings.store.useDevIcon !== DeviconSetting.Disabled)
enableStyle(deviconStyle);
await shiki.init(getSettings().customTheme || getSettings().theme);
await shiki.init(settings.store.customTheme || settings.store.theme);
},
stop: () => {
shiki.destroy();
@ -67,90 +60,7 @@ export default definePlugin({
isPreview: true,
tempSettings,
}),
options: {
theme: {
type: OptionType.SELECT,
description: "Default themes",
options: themeNames.map(themeName => ({
label: wordsToTitle(wordsFromPascal(themeName)),
value: themes[themeName],
default: themes[themeName] === themes.DarkPlus,
})),
disabled: () => !!getSettings().customTheme,
onChange: shiki.setTheme,
},
customTheme: {
type: OptionType.STRING,
description: "A link to a custom vscode theme",
placeholder: themes.MaterialCandy,
isValid: value => {
if (!value) return true;
const url = parseUrl(value);
if (!url) return "Must be a valid URL";
if (!url.pathname.endsWith(".json")) return "Must be a json file";
return true;
},
onChange: value => shiki.setTheme(value || getSettings().theme),
},
tryHljs: {
type: OptionType.SELECT,
description: "Use the more lightweight default Discord highlighter and theme.",
options: [
{
label: "Never",
value: HljsSetting.Never,
},
{
label: "Prefer Shiki instead of Highlight.js",
value: HljsSetting.Secondary,
default: true,
},
{
label: "Prefer Highlight.js instead of Shiki",
value: HljsSetting.Primary,
},
{
label: "Always",
value: HljsSetting.Always,
},
],
},
useDevIcon: {
type: OptionType.SELECT,
description: "How to show language icons on codeblocks",
options: [
{
label: "Disabled",
value: DeviconSetting.Disabled,
},
{
label: "Colorless",
value: DeviconSetting.Greyscale,
default: true,
},
{
label: "Colored",
value: DeviconSetting.Color,
},
],
onChange: (newValue: DeviconSetting) => {
if (newValue === DeviconSetting.Disabled) disableStyle(deviconStyle);
else enableStyle(deviconStyle);
},
},
bgOpacity: {
type: OptionType.SLIDER,
description: "Background opacity",
markers: [0, 20, 40, 60, 80, 100],
default: 100,
componentProps: {
stickToMarkers: false,
onValueRender: null, // Defaults to percentage
},
},
},
settings,
// exports
shiki,

View file

@ -0,0 +1,123 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { parseUrl } from "@utils/misc";
import { wordsFromPascal, wordsToTitle } from "@utils/text";
import { OptionType } from "@utils/types";
import { shiki } from "./api/shiki";
import { themes } from "./api/themes";
import deviconStyle from "./devicon.css?managed";
import { DeviconSetting, HljsSetting } from "./types";
const themeNames = Object.keys(themes) as (keyof typeof themes)[];
export type ShikiSettings = typeof settings.store;
export const settings = definePluginSettings({
theme: {
type: OptionType.SELECT,
description: "Default themes",
options: themeNames.map(themeName => ({
label: wordsToTitle(wordsFromPascal(themeName)),
value: themes[themeName],
default: themes[themeName] === themes.DarkPlus,
})),
onChange: shiki.setTheme,
},
customTheme: {
type: OptionType.STRING,
description: "A link to a custom vscode theme",
placeholder: themes.MaterialCandy,
onChange: value => {
shiki.setTheme(value || settings.store.theme);
},
},
tryHljs: {
type: OptionType.SELECT,
description: "Use the more lightweight default Discord highlighter and theme.",
options: [
{
label: "Never",
value: HljsSetting.Never,
},
{
label: "Prefer Shiki instead of Highlight.js",
value: HljsSetting.Secondary,
default: true,
},
{
label: "Prefer Highlight.js instead of Shiki",
value: HljsSetting.Primary,
},
{
label: "Always",
value: HljsSetting.Always,
},
],
},
useDevIcon: {
type: OptionType.SELECT,
description: "How to show language icons on codeblocks",
options: [
{
label: "Disabled",
value: DeviconSetting.Disabled,
},
{
label: "Colorless",
value: DeviconSetting.Greyscale,
default: true,
},
{
label: "Colored",
value: DeviconSetting.Color,
},
],
onChange: (newValue: DeviconSetting) => {
if (newValue === DeviconSetting.Disabled) disableStyle(deviconStyle);
else enableStyle(deviconStyle);
},
},
bgOpacity: {
type: OptionType.SLIDER,
description: "Background opacity",
markers: [0, 20, 40, 60, 80, 100],
default: 100,
componentProps: {
stickToMarkers: false,
onValueRender: null, // Defaults to percentage
},
},
}, {
theme: {
disabled() { return !!this.store.customTheme; },
},
customTheme: {
isValid(value) {
if (!value) return true;
const url = parseUrl(value);
if (!url) return "Must be a valid URL";
if (!url.pathname.endsWith(".json")) return "Must be a json file";
return true;
},
}
});

View file

@ -23,8 +23,6 @@ import type {
IThemeRegistration,
} from "@vap/shiki";
import type { Settings } from "../../Vencord";
/** This must be atleast a subset of the `@vap/shiki-worker` spec */
export type ShikiSpec = {
setOnigasm: ({ wasm }: { wasm: string; }) => Promise<void>;
@ -64,15 +62,3 @@ export enum DeviconSetting {
Greyscale = "GREYSCALE",
Color = "COLOR"
}
type CommonSettings = {
[K in keyof Settings["plugins"][string]as K extends `${infer V}` ? K : never]: Settings["plugins"][string][K];
};
export interface ShikiSettings extends CommonSettings {
theme: string;
customTheme: string;
tryHljs: HljsSetting;
useDevIcon: DeviconSetting;
bgOpacity: number;
}

View file

@ -21,7 +21,7 @@ import { hljs } from "@webpack/common";
import { resolveLang } from "../api/languages";
import { HighlighterProps } from "../components/Highlighter";
import { HljsSetting, ShikiSettings } from "../types";
import { HljsSetting } from "../types";
export const cl = classNameFactory("shiki-");
@ -30,7 +30,7 @@ export const shouldUseHljs = ({
tryHljs,
}: {
lang: HighlighterProps["lang"],
tryHljs: ShikiSettings["tryHljs"],
tryHljs: HljsSetting,
}) => {
const hljsLang = lang ? hljs?.getLanguage?.(lang) : null;
const shikiLang = lang ? resolveLang(lang) : null;
@ -45,7 +45,6 @@ export const shouldUseHljs = ({
return !langName && !!hljsLang;
case HljsSetting.Never:
return false;
default: return false;
}
return false;
};