mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 18:12:29 +08:00
chore(highlightjs): reintroduce grouping of themes
This commit is contained in:
parent
5a186e6853
commit
4537dfa660
@ -1621,7 +1621,10 @@
|
|||||||
"color-scheme": "颜色方案"
|
"color-scheme": "颜色方案"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "自动换行"
|
"word_wrapping": "自动换行",
|
||||||
|
"theme_none": "无语法高亮",
|
||||||
|
"theme_group_light": "浅色主题",
|
||||||
|
"theme_group_dark": "深色主题"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "格式化"
|
"title": "格式化"
|
||||||
|
@ -1573,7 +1573,10 @@
|
|||||||
"color-scheme": "Farbschema"
|
"color-scheme": "Farbschema"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Wortumbruch"
|
"word_wrapping": "Wortumbruch",
|
||||||
|
"theme_none": "Keine Syntax-Hervorhebung",
|
||||||
|
"theme_group_light": "Helle Themen",
|
||||||
|
"theme_group_dark": "Dunkle Themen"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Format"
|
"title": "Format"
|
||||||
|
@ -1827,7 +1827,10 @@
|
|||||||
"color-scheme": "Color Scheme"
|
"color-scheme": "Color Scheme"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Word wrapping"
|
"word_wrapping": "Word wrapping",
|
||||||
|
"theme_none": "No syntax highlighting",
|
||||||
|
"theme_group_light": "Light themes",
|
||||||
|
"theme_group_dark": "Dark themes"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Formatting"
|
"title": "Formatting"
|
||||||
|
@ -1589,7 +1589,10 @@
|
|||||||
"color-scheme": "Esquema de color"
|
"color-scheme": "Esquema de color"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Ajuste de palabras"
|
"word_wrapping": "Ajuste de palabras",
|
||||||
|
"theme_none": "Sin resaltado de sintaxis",
|
||||||
|
"theme_group_light": "Temas claros",
|
||||||
|
"theme_group_dark": "Temas oscuros"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Formato"
|
"title": "Formato"
|
||||||
|
@ -1579,7 +1579,10 @@
|
|||||||
"color-scheme": "Jeu de couleurs"
|
"color-scheme": "Jeu de couleurs"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Saut à la ligne automatique suivant la largeur"
|
"word_wrapping": "Saut à la ligne automatique suivant la largeur",
|
||||||
|
"theme_none": "Pas de coloration syntaxique",
|
||||||
|
"theme_group_light": "Thèmes clairs",
|
||||||
|
"theme_group_dark": "Thèmes sombres"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Mise en forme"
|
"title": "Mise en forme"
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"revisions": {
|
"revisions": {
|
||||||
"delete_button": ""
|
"delete_button": ""
|
||||||
|
},
|
||||||
|
"code_block": {
|
||||||
|
"theme_none": "Sem destaque de sintaxe",
|
||||||
|
"theme_group_light": "Temas claros",
|
||||||
|
"theme_group_dark": "Temas escuros"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1585,7 +1585,10 @@
|
|||||||
"description": "Controlează evidențierea de sintaxă pentru blocurile de cod în interiorul notițelor text, notițele de tip cod nu vor fi afectate de aceste setări."
|
"description": "Controlează evidențierea de sintaxă pentru blocurile de cod în interiorul notițelor text, notițele de tip cod nu vor fi afectate de aceste setări."
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Încadrare text"
|
"word_wrapping": "Încadrare text",
|
||||||
|
"theme_none": "Fără evidențiere de sintaxă",
|
||||||
|
"theme_group_dark": "Teme întunecate",
|
||||||
|
"theme_group_light": "Teme luminoase"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Formatare"
|
"title": "Formatare"
|
||||||
|
@ -1519,7 +1519,10 @@
|
|||||||
"color-scheme": "顏色方案"
|
"color-scheme": "顏色方案"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "自動換行"
|
"word_wrapping": "自動換行",
|
||||||
|
"theme_none": "無格式高亮",
|
||||||
|
"theme_group_light": "淺色主題",
|
||||||
|
"theme_group_dark": "深色主題"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "格式化"
|
"title": "格式化"
|
||||||
|
@ -3,7 +3,7 @@ import { t } from "../../../../services/i18n.js";
|
|||||||
import server from "../../../../services/server.js";
|
import server from "../../../../services/server.js";
|
||||||
import OptionsWidget from "../options_widget.js";
|
import OptionsWidget from "../options_widget.js";
|
||||||
import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../../services/syntax_highlight.js";
|
import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../../services/syntax_highlight.js";
|
||||||
import { Themes } from "@triliumnext/highlightjs";
|
import { Themes, type Theme } from "@triliumnext/highlightjs";
|
||||||
|
|
||||||
const SAMPLE_LANGUAGE = normalizeMimeTypeForCKEditor("application/javascript;env=frontend");
|
const SAMPLE_LANGUAGE = normalizeMimeTypeForCKEditor("application/javascript;env=frontend");
|
||||||
const SAMPLE_CODE = `\
|
const SAMPLE_CODE = `\
|
||||||
@ -68,13 +68,28 @@ export default class CodeBlockOptions extends OptionsWidget {
|
|||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.$themeSelect = this.$widget.find(".theme-select");
|
this.$themeSelect = this.$widget.find(".theme-select");
|
||||||
|
|
||||||
// Populate the list of themes.
|
// Populate the list of themes.
|
||||||
for (const [ id, theme ] of Object.entries(Themes)) {
|
const themeGroups = groupThemesByLightOrDark();
|
||||||
const option = $("<option>")
|
for (const [key, themes] of Object.entries(themeGroups)) {
|
||||||
.attr("value", `default:${id}`)
|
const $group = key ? $("<optgroup>").attr("label", key) : null;
|
||||||
.text(theme.name);
|
|
||||||
this.$themeSelect.append(option);
|
for (const [id, theme] of Object.entries(themes)) {
|
||||||
|
const option = $("<option>")
|
||||||
|
.attr("value", "default:" + id)
|
||||||
|
.text(theme.name);
|
||||||
|
|
||||||
|
if ($group) {
|
||||||
|
$group.append(option);
|
||||||
|
} else {
|
||||||
|
this.$themeSelect.append(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($group) {
|
||||||
|
this.$themeSelect.append($group);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$themeSelect.on("change", async () => {
|
this.$themeSelect.on("change", async () => {
|
||||||
const newTheme = String(this.$themeSelect.val());
|
const newTheme = String(this.$themeSelect.val());
|
||||||
loadHighlightingTheme(newTheme);
|
loadHighlightingTheme(newTheme);
|
||||||
@ -113,3 +128,21 @@ export default class CodeBlockOptions extends OptionsWidget {
|
|||||||
this.#setupPreview(options.codeBlockTheme !== "none");
|
this.#setupPreview(options.codeBlockTheme !== "none");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function groupThemesByLightOrDark() {
|
||||||
|
const darkThemes: Record<string, Theme> = {};
|
||||||
|
const lightThemes: Record<string, Theme> = {};
|
||||||
|
|
||||||
|
for (const [ id, theme ] of Object.entries(Themes)) {
|
||||||
|
if (theme.name.includes("Dark")) {
|
||||||
|
darkThemes[id] = theme;
|
||||||
|
} else {
|
||||||
|
lightThemes[id] = theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const output: Record<string, Record<string, Theme>> = {};
|
||||||
|
output[t("code_block.theme_group_light")] = lightThemes;
|
||||||
|
output[t("code_block.theme_group_dark")] = darkThemes;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
@ -193,11 +193,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "搜索:"
|
"search_prefix": "搜索:"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "无语法高亮",
|
|
||||||
"theme_group_light": "浅色主题",
|
|
||||||
"theme_group_dark": "深色主题"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "同步服务器主机未配置。请先配置同步。",
|
"not-configured": "同步服务器主机未配置。请先配置同步。",
|
||||||
"successful": "同步服务器握手成功,同步已开始。"
|
"successful": "同步服务器握手成功,同步已开始。"
|
||||||
|
@ -185,11 +185,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "Suche:"
|
"search_prefix": "Suche:"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "Keine Syntax-Hervorhebung",
|
|
||||||
"theme_group_light": "Helle Themen",
|
|
||||||
"theme_group_dark": "Dunkle Themen"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.",
|
"not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.",
|
||||||
"successful": "Die Server-Verbindung wurde erfolgreich hergestellt, die Synchronisation wurde gestartet."
|
"successful": "Die Server-Verbindung wurde erfolgreich hergestellt, die Synchronisation wurde gestartet."
|
||||||
|
@ -193,11 +193,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "Search:"
|
"search_prefix": "Search:"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "No syntax highlighting",
|
|
||||||
"theme_group_light": "Light themes",
|
|
||||||
"theme_group_dark": "Dark themes"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "Sync server host is not configured. Please configure sync first.",
|
"not-configured": "Sync server host is not configured. Please configure sync first.",
|
||||||
"successful": "Sync server handshake has been successful, sync has been started."
|
"successful": "Sync server handshake has been successful, sync has been started."
|
||||||
|
@ -189,11 +189,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "Buscar:"
|
"search_prefix": "Buscar:"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "Sin resaltado de sintaxis",
|
|
||||||
"theme_group_light": "Temas claros",
|
|
||||||
"theme_group_dark": "Temas oscuros"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "El servidor de sincronización no está configurado. Por favor configure primero la sincronización.",
|
"not-configured": "El servidor de sincronización no está configurado. Por favor configure primero la sincronización.",
|
||||||
"successful": "El protocolo de enlace del servidor de sincronización ha sido exitoso, la sincronización ha comenzado."
|
"successful": "El protocolo de enlace del servidor de sincronización ha sido exitoso, la sincronización ha comenzado."
|
||||||
|
@ -189,11 +189,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "Recherche :"
|
"search_prefix": "Recherche :"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "Pas de coloration syntaxique",
|
|
||||||
"theme_group_light": "Thèmes clairs",
|
|
||||||
"theme_group_dark": "Thèmes sombres"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "L'hôte du serveur de synchronisation n'est pas configuré. Veuillez d'abord configurer la synchronisation.",
|
"not-configured": "L'hôte du serveur de synchronisation n'est pas configuré. Veuillez d'abord configurer la synchronisation.",
|
||||||
"successful": "L'établissement de liaison du serveur de synchronisation a été réussi, la synchronisation a été démarrée."
|
"successful": "L'établissement de liaison du serveur de synchronisation a été réussi, la synchronisation a été démarrée."
|
||||||
|
@ -186,11 +186,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "Pesquisar:"
|
"search_prefix": "Pesquisar:"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "Sem destaque de sintaxe",
|
|
||||||
"theme_group_light": "Temas claros",
|
|
||||||
"theme_group_dark": "Temas escuros"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "O host do servidor de sincronização não está configurado. Por favor, configure a sincronização primeiro.",
|
"not-configured": "O host do servidor de sincronização não está configurado. Por favor, configure a sincronização primeiro.",
|
||||||
"successful": "A comunicação com o servidor de sincronização foi bem-sucedida, a sincronização foi iniciada."
|
"successful": "A comunicação com o servidor de sincronização foi bem-sucedida, a sincronização foi iniciada."
|
||||||
|
@ -189,11 +189,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "Căutare:"
|
"search_prefix": "Căutare:"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "Fără evidențiere de sintaxă",
|
|
||||||
"theme_group_dark": "Teme întunecate",
|
|
||||||
"theme_group_light": "Teme luminoase"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "Calea către serverul de sincronizare nu este configurată. Configurați sincronizarea înainte.",
|
"not-configured": "Calea către serverul de sincronizare nu este configurată. Configurați sincronizarea înainte.",
|
||||||
"successful": "Comunicarea cu serverul de sincronizare a avut loc cu succes, s-a început sincronizarea."
|
"successful": "Comunicarea cu serverul de sincronizare a avut loc cu succes, s-a început sincronizarea."
|
||||||
|
@ -185,11 +185,6 @@
|
|||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "搜尋:"
|
"search_prefix": "搜尋:"
|
||||||
},
|
},
|
||||||
"code_block": {
|
|
||||||
"theme_none": "無格式高亮",
|
|
||||||
"theme_group_light": "淺色主題",
|
|
||||||
"theme_group_dark": "深色主題"
|
|
||||||
},
|
|
||||||
"test_sync": {
|
"test_sync": {
|
||||||
"not-configured": "並未設定同步伺服器主機,請先設定同步",
|
"not-configured": "並未設定同步伺服器主機,請先設定同步",
|
||||||
"successful": "成功與同步伺服器握手,現在開始同步"
|
"successful": "成功與同步伺服器握手,現在開始同步"
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import { readThemesFromFileSystem } from "./code_block_theme.js";
|
|
||||||
|
|
||||||
import themeNames from "./code_block_theme_names.json" with { type: "json" };
|
|
||||||
import path = require("path");
|
|
||||||
|
|
||||||
describe("Code block theme", () => {
|
|
||||||
it("all themes are mapped", () => {
|
|
||||||
const themes = readThemesFromFileSystem(path.join(__dirname, "../../node_modules/@highlightjs/cdn-assets/styles"));
|
|
||||||
|
|
||||||
const mappedThemeNames = new Set(Object.values(themeNames));
|
|
||||||
const unmappedThemeNames = new Set<string>();
|
|
||||||
|
|
||||||
for (const theme of themes) {
|
|
||||||
if (!mappedThemeNames.has(theme.title)) {
|
|
||||||
unmappedThemeNames.add(theme.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(unmappedThemeNames.size, `Unmapped themes: ${Array.from(unmappedThemeNames).join(", ")}`).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,102 +0,0 @@
|
|||||||
/**
|
|
||||||
* @module
|
|
||||||
*
|
|
||||||
* Manages the server-side functionality of the code blocks feature, mostly for obtaining the available themes for syntax highlighting.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from "fs";
|
|
||||||
import { t } from "i18next";
|
|
||||||
import { join } from "path";
|
|
||||||
import { isDev, isElectron, getResourceDir } from "./utils.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a color scheme for the code block syntax highlight.
|
|
||||||
*/
|
|
||||||
interface ColorTheme {
|
|
||||||
/** The ID of the color scheme which should be stored in the options. */
|
|
||||||
val: string;
|
|
||||||
/** A user-friendly name of the theme. The name is already localized. */
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the supported syntax highlighting themes for code blocks, in groups.
|
|
||||||
*
|
|
||||||
* The return value is an object where the keys represent groups in their human-readable name (e.g. "Light theme")
|
|
||||||
* and the values are an array containing the information about every theme. There is also a special group with no
|
|
||||||
* title (empty string) which should be displayed at the top of the listing pages, without a group.
|
|
||||||
*
|
|
||||||
* @returns the supported themes, grouped.
|
|
||||||
*/
|
|
||||||
export function listSyntaxHighlightingThemes() {
|
|
||||||
const path = getStylesDirectory();
|
|
||||||
const systemThemes = readThemesFromFileSystem(path);
|
|
||||||
|
|
||||||
return {
|
|
||||||
"": [
|
|
||||||
{
|
|
||||||
val: "none",
|
|
||||||
title: t("code_block.theme_none")
|
|
||||||
}
|
|
||||||
],
|
|
||||||
...groupThemesByLightOrDark(systemThemes)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStylesDirectory() {
|
|
||||||
if (isElectron && !isDev) {
|
|
||||||
return join(getResourceDir(), "styles");
|
|
||||||
} else if (!isDev) {
|
|
||||||
return join(getResourceDir(), "node_modules/@highlightjs/cdn-assets/styles");
|
|
||||||
} else {
|
|
||||||
return join(__dirname, "../node_modules/@highlightjs/cdn-assets/styles");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads all the predefined themes by listing all minified CSSes from a given directory.
|
|
||||||
*
|
|
||||||
* The theme names are mapped against a known list in order to provide more descriptive names such as "Visual Studio 2015 (Dark)" instead of "vs2015".
|
|
||||||
*
|
|
||||||
* @param path the path to read from. Usually this is the highlight.js `styles` directory.
|
|
||||||
* @returns the list of themes.
|
|
||||||
*/
|
|
||||||
export function readThemesFromFileSystem(path: string): ColorTheme[] {
|
|
||||||
return fs
|
|
||||||
.readdirSync(path)
|
|
||||||
.filter((el) => el.endsWith(".min.css"))
|
|
||||||
.map((name) => {
|
|
||||||
const nameWithoutExtension = name.replace(".min.css", "");
|
|
||||||
let title = nameWithoutExtension.replace(/-/g, " ");
|
|
||||||
|
|
||||||
return {
|
|
||||||
val: `default:${nameWithoutExtension}`,
|
|
||||||
title: title
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Groups a list of themes by dark or light themes. This is done simply by checking whether "Dark" is present in the given theme, otherwise it's considered a light theme.
|
|
||||||
* This generally only works if the theme has a known human-readable name (see {@link #readThemesFromFileSystem()})
|
|
||||||
*
|
|
||||||
* @param listOfThemes the list of themes to be grouped.
|
|
||||||
* @returns the grouped themes by light or dark.
|
|
||||||
*/
|
|
||||||
function groupThemesByLightOrDark(listOfThemes: ColorTheme[]) {
|
|
||||||
const darkThemes = [];
|
|
||||||
const lightThemes = [];
|
|
||||||
|
|
||||||
for (const theme of listOfThemes) {
|
|
||||||
if (theme.title.includes("Dark")) {
|
|
||||||
darkThemes.push(theme);
|
|
||||||
} else {
|
|
||||||
lightThemes.push(theme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const output: Record<string, ColorTheme[]> = {};
|
|
||||||
output[t("code_block.theme_group_light")] = lightThemes;
|
|
||||||
output[t("code_block.theme_group_dark")] = darkThemes;
|
|
||||||
return output;
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import syntaxDefinitions from "./syntax_highlighting.js";
|
|||||||
import { type Theme } from "./themes.js";
|
import { type Theme } from "./themes.js";
|
||||||
import { type HighlightOptions } from "highlight.js";
|
import { type HighlightOptions } from "highlight.js";
|
||||||
|
|
||||||
export { default as Themes } from "./themes.js";
|
export { default as Themes, type Theme } from "./themes.js";
|
||||||
|
|
||||||
const registeredMimeTypes = new Set<string>();
|
const registeredMimeTypes = new Set<string>();
|
||||||
const unsupportedMimeTypes = new Set<string>();
|
const unsupportedMimeTypes = new Set<string>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user