mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 15:19:01 +02:00
feat(react/settings): port code block settings
This commit is contained in:
parent
3ba0bcea4e
commit
234d3997b1
@ -14,10 +14,12 @@ export default function FormGroup({ label, title, className, children, descripti
|
||||
return (
|
||||
<div className={`form-group ${className} ${disabled ? "disabled" : ""}`} title={title}
|
||||
style={{ "margin-bottom": "15px" }}>
|
||||
<label style={{ width: "100%" }} ref={labelRef}>
|
||||
{ label
|
||||
? <label style={{ width: "100%" }} ref={labelRef}>
|
||||
{label && <div style={{ "margin-bottom": "10px" }}>{label}</div> }
|
||||
{children}
|
||||
</label>
|
||||
: children}
|
||||
|
||||
{description && <small className="form-text">{description}</small>}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@ import type { ComponentChildren } from "preact";
|
||||
|
||||
type OnChangeListener = (newValue: string) => void;
|
||||
|
||||
interface FormSelectGroup<T> {
|
||||
export interface FormSelectGroup<T> {
|
||||
title: string;
|
||||
items: T[];
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ function getProps({ className, html, style }: RawHtmlProps) {
|
||||
}
|
||||
}
|
||||
|
||||
function getHtml(html: string | HTMLElement | JQuery<HTMLElement>) {
|
||||
export function getHtml(html: string | HTMLElement | JQuery<HTMLElement>) {
|
||||
if (typeof html === "object" && "length" in html) {
|
||||
html = html[0];
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||
import { t } from "../../../services/i18n";
|
||||
import FormCheckbox from "../../react/FormCheckbox";
|
||||
import FormRadioGroup from "../../react/FormRadioGroup";
|
||||
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
||||
import OptionsSection from "./components/OptionsSection";
|
||||
import { toggleBodyClass } from "../../../services/utils";
|
||||
import FormGroup from "../../react/FormGroup";
|
||||
import Column from "../../react/Column";
|
||||
import { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect";
|
||||
import { Themes, type Theme } from "@triliumnext/highlightjs";
|
||||
import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../services/syntax_highlight";
|
||||
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||
import { ComponentChildren } from "preact";
|
||||
import RawHtml, { getHtml } from "../../react/RawHtml";
|
||||
import { ParentComponent } from "../../react/ReactBasicWidget";
|
||||
import { CSSProperties } from "preact/compat";
|
||||
|
||||
export default function TextNoteSettings() {
|
||||
return (
|
||||
@ -12,6 +22,7 @@ export default function TextNoteSettings() {
|
||||
<FormattingToolbar />
|
||||
<EditorFeatures />
|
||||
<HeadingStyle />
|
||||
<CodeBlockStyle />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -90,4 +101,133 @@ function HeadingStyle() {
|
||||
/>
|
||||
</OptionsSection>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeBlockStyle() {
|
||||
const themes = useMemo(() => groupThemesByLightOrDark(), []);
|
||||
const [ codeBlockTheme, setCodeBlockTheme ] = useTriliumOption("codeBlockTheme");
|
||||
const [ codeBlockWordWrap, setCodeBlockWordWrap ] = useTriliumOptionBool("codeBlockWordWrap");
|
||||
|
||||
return (
|
||||
<OptionsSection title={t("highlighting.title")}>
|
||||
<FormGroup className="row">
|
||||
<Column md={6}>
|
||||
<label>{t("highlighting.color-scheme")}</label>
|
||||
<FormSelectWithGroups
|
||||
values={themes}
|
||||
keyProperty="val" titleProperty="title"
|
||||
currentValue={codeBlockTheme} onChange={(newTheme) => {
|
||||
loadHighlightingTheme(newTheme);
|
||||
setCodeBlockTheme(newTheme);
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<Column md={6} className="side-checkbox">
|
||||
<FormCheckbox
|
||||
name="word-wrap"
|
||||
label={t("code_block.word_wrapping")}
|
||||
currentValue={codeBlockWordWrap} onChange={setCodeBlockWordWrap}
|
||||
/>
|
||||
</Column>
|
||||
</FormGroup>
|
||||
|
||||
<CodeBlockPreview theme={codeBlockTheme} wordWrap={codeBlockWordWrap} />
|
||||
</OptionsSection>
|
||||
)
|
||||
}
|
||||
|
||||
const SAMPLE_LANGUAGE = normalizeMimeTypeForCKEditor("application/javascript;env=frontend");
|
||||
const SAMPLE_CODE = `\
|
||||
const n = 10;
|
||||
greet(n); // Print "Hello World" for n times
|
||||
|
||||
/**
|
||||
* Displays a "Hello World!" message for a given amount of times, on the standard console. The "Hello World!" text will be displayed once per line.
|
||||
*
|
||||
* @param {number} times The number of times to print the \`Hello World!\` message.
|
||||
*/
|
||||
function greet(times) {
|
||||
for (let i = 0; i++; i < times) {
|
||||
console.log("Hello World!");
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function CodeBlockPreview({ theme, wordWrap }: { theme: string, wordWrap: boolean }) {
|
||||
const [ code, setCode ] = useState<string>(SAMPLE_CODE);
|
||||
|
||||
useEffect(() => {
|
||||
if (theme !== "none") {
|
||||
import("@triliumnext/highlightjs").then(async (hljs) => {
|
||||
await ensureMimeTypesForHighlighting();
|
||||
const highlightedText = hljs.highlight(SAMPLE_CODE, {
|
||||
language: SAMPLE_LANGUAGE
|
||||
});
|
||||
if (highlightedText) {
|
||||
setCode(highlightedText.value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setCode(SAMPLE_CODE);
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
const codeStyle = useMemo<CSSProperties>(() => {
|
||||
if (wordWrap) {
|
||||
return { whiteSpace: "pre-wrap" };
|
||||
} else {
|
||||
return { whiteSpace: "pre"};
|
||||
}
|
||||
}, [ wordWrap ]);
|
||||
|
||||
return (
|
||||
<div className="note-detail-readonly-text-content ck-content code-sample-wrapper">
|
||||
<pre className="hljs" style={{ marginBottom: 0 }}>
|
||||
<code className="code-sample" style={codeStyle} dangerouslySetInnerHTML={getHtml(code)} />
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface ThemeData {
|
||||
val: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
function groupThemesByLightOrDark() {
|
||||
const darkThemes: ThemeData[] = [];
|
||||
const lightThemes: ThemeData[] = [];
|
||||
|
||||
for (const [ id, theme ] of Object.entries(Themes)) {
|
||||
const data: ThemeData = {
|
||||
val: "default:" + id,
|
||||
title: theme.name
|
||||
};
|
||||
|
||||
if (theme.name.includes("Dark")) {
|
||||
darkThemes.push(data);
|
||||
} else {
|
||||
lightThemes.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
const output: FormSelectGroup<ThemeData>[] = [
|
||||
{
|
||||
title: "",
|
||||
items: [{
|
||||
val: "none",
|
||||
title: t("code_block.theme_none")
|
||||
}]
|
||||
},
|
||||
{
|
||||
title: t("code_block.theme_group_light"),
|
||||
items: lightThemes
|
||||
},
|
||||
{
|
||||
title: t("code_block.theme_group_dark"),
|
||||
items: darkThemes
|
||||
}
|
||||
];
|
||||
return output;
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
import { normalizeMimeTypeForCKEditor, type OptionMap } from "@triliumnext/commons";
|
||||
import { t } from "../../../../services/i18n.js";
|
||||
import server from "../../../../services/server.js";
|
||||
import OptionsWidget from "../options_widget.js";
|
||||
import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../../services/syntax_highlight.js";
|
||||
import { Themes, type Theme } from "@triliumnext/highlightjs";
|
||||
|
||||
const SAMPLE_LANGUAGE = normalizeMimeTypeForCKEditor("application/javascript;env=frontend");
|
||||
const SAMPLE_CODE = `\
|
||||
const n = 10;
|
||||
greet(n); // Print "Hello World" for n times
|
||||
|
||||
/**
|
||||
* Displays a "Hello World!" message for a given amount of times, on the standard console. The "Hello World!" text will be displayed once per line.
|
||||
*
|
||||
* @param {number} times The number of times to print the \`Hello World!\` message.
|
||||
*/
|
||||
function greet(times) {
|
||||
for (let i = 0; i++; i < times) {
|
||||
console.log("Hello World!");
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="options-section">
|
||||
<h4>${t("highlighting.title")}</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="highlighting-color-scheme-select">${t("highlighting.color-scheme")}</label>
|
||||
<select id="highlighting-color-scheme-select" class="theme-select form-select"></select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 side-checkbox">
|
||||
<label class="form-check tn-checkbox">
|
||||
<input type="checkbox" class="word-wrap form-check-input" />
|
||||
${t("code_block.word_wrapping")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="note-detail-readonly-text-content ck-content code-sample-wrapper">
|
||||
<pre class="hljs"><code class="code-sample">${SAMPLE_CODE}</code></pre>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.code-sample-wrapper {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.code-sample-wrapper pre {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter.
|
||||
*/
|
||||
export default class CodeBlockOptions extends OptionsWidget {
|
||||
|
||||
private $themeSelect!: JQuery<HTMLElement>;
|
||||
private $wordWrap!: JQuery<HTMLElement>;
|
||||
private $sampleEl!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$themeSelect = this.$widget.find(".theme-select");
|
||||
|
||||
// Populate the list of themes.
|
||||
const themeGroups = groupThemesByLightOrDark();
|
||||
for (const [key, themes] of Object.entries(themeGroups)) {
|
||||
const $group = key ? $("<optgroup>").attr("label", key) : null;
|
||||
|
||||
for (const theme of themes) {
|
||||
const option = $("<option>")
|
||||
.attr("value", theme.val)
|
||||
.text(theme.title);
|
||||
|
||||
if ($group) {
|
||||
$group.append(option);
|
||||
} else {
|
||||
this.$themeSelect.append(option);
|
||||
}
|
||||
}
|
||||
if ($group) {
|
||||
this.$themeSelect.append($group);
|
||||
}
|
||||
}
|
||||
|
||||
this.$themeSelect.on("change", async () => {
|
||||
const newTheme = String(this.$themeSelect.val());
|
||||
loadHighlightingTheme(newTheme);
|
||||
await server.put(`options/codeBlockTheme/${newTheme}`);
|
||||
});
|
||||
|
||||
this.$wordWrap = this.$widget.find("input.word-wrap");
|
||||
this.$wordWrap.on("change", () => this.updateCheckboxOption("codeBlockWordWrap", this.$wordWrap));
|
||||
|
||||
// Set up preview
|
||||
this.$sampleEl = this.$widget.find(".code-sample");
|
||||
}
|
||||
|
||||
#setupPreview(shouldEnableSyntaxHighlight: boolean) {
|
||||
const text = SAMPLE_CODE;
|
||||
if (shouldEnableSyntaxHighlight) {
|
||||
import("@triliumnext/highlightjs").then(async (hljs) => {
|
||||
await ensureMimeTypesForHighlighting();
|
||||
const highlightedText = hljs.highlight(text, {
|
||||
language: SAMPLE_LANGUAGE
|
||||
});
|
||||
if (highlightedText) {
|
||||
this.$sampleEl.html(highlightedText.value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$sampleEl.text(text);
|
||||
}
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
this.$themeSelect.val(options.codeBlockTheme);
|
||||
this.setCheckboxState(this.$wordWrap, options.codeBlockWordWrap);
|
||||
this.$widget.closest(".note-detail-printable").toggleClass("word-wrap", options.codeBlockWordWrap === "true");
|
||||
|
||||
this.#setupPreview(options.codeBlockTheme !== "none");
|
||||
}
|
||||
}
|
||||
|
||||
interface ThemeData {
|
||||
val: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
function groupThemesByLightOrDark() {
|
||||
const darkThemes: ThemeData[] = [];
|
||||
const lightThemes: ThemeData[] = [];
|
||||
|
||||
for (const [ id, theme ] of Object.entries(Themes)) {
|
||||
const data: ThemeData = {
|
||||
val: "default:" + id,
|
||||
title: theme.name
|
||||
};
|
||||
|
||||
if (theme.name.includes("Dark")) {
|
||||
darkThemes.push(data);
|
||||
} else {
|
||||
lightThemes.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
const output: Record<string, ThemeData[]> = {
|
||||
"": [
|
||||
{
|
||||
val: "none",
|
||||
title: t("code_block.theme_none")
|
||||
}
|
||||
]
|
||||
};
|
||||
output[t("code_block.theme_group_light")] = lightThemes;
|
||||
output[t("code_block.theme_group_dark")] = darkThemes;
|
||||
return output;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user