mirror of
https://github.com/zadam/trilium.git
synced 2026-05-07 06:57:16 +02:00
feat(options/text): add for heading style & formatting toolbar
This commit is contained in:
125
apps/client/src/widgets/type_widgets/options/text_notes.css
Normal file
125
apps/client/src/widgets/type_widgets/options/text_notes.css
Normal file
@@ -0,0 +1,125 @@
|
||||
/* HeadingStyleSelector */
|
||||
.heading-style-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.heading-preview {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
min-width: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.heading-preview-markdown .heading-prefix {
|
||||
color: var(--muted-text-color);
|
||||
font-weight: normal;
|
||||
font-family: var(--font-family-monospace);
|
||||
}
|
||||
|
||||
.heading-preview-underline {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.heading-preview-underline .heading-underline {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: currentColor;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.heading-style-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Toolbar type illustration */
|
||||
.toolbar-illustration {
|
||||
width: 140px;
|
||||
height: 100px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--main-background-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.toolbar-bar {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 6px 8px;
|
||||
background: var(--left-pane-background-color);
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--muted-text-color);
|
||||
border-radius: 2px;
|
||||
opacity: 0.5;
|
||||
|
||||
&.wide {
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.document-area {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.text-line {
|
||||
height: 6px;
|
||||
background: var(--muted-text-color);
|
||||
border-radius: 2px;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.text-line-with-selection {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
height: 6px;
|
||||
|
||||
.text-segment {
|
||||
background: var(--muted-text-color);
|
||||
border-radius: 2px;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.text-selection {
|
||||
width: 30%;
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.floating-toolbar {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
padding: 4px 6px;
|
||||
background: var(--tooltip-background-color);
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
|
||||
.toolbar-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--main-background-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./text_notes.css";
|
||||
|
||||
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||
import { Themes } from "@triliumnext/highlightjs";
|
||||
import type { CSSProperties } from "preact/compat";
|
||||
@@ -9,8 +11,9 @@ import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../
|
||||
import { formatDateTime, toggleBodyClass } from "../../../services/utils";
|
||||
import FormCheckbox from "../../react/FormCheckbox";
|
||||
import FormGroup from "../../react/FormGroup";
|
||||
import FormRadioGroup from "../../react/FormRadioGroup";
|
||||
import FormSelect, { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect";
|
||||
import Dropdown from "../../react/Dropdown";
|
||||
import { FormListItem } from "../../react/FormList";
|
||||
import { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect";
|
||||
import FormText from "../../react/FormText";
|
||||
import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
|
||||
import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks";
|
||||
@@ -18,6 +21,7 @@ import { getHtml } from "../../react/RawHtml";
|
||||
import CheckboxList from "./components/CheckboxList";
|
||||
import OptionsRow, { OptionsRowWithToggle } from "./components/OptionsRow";
|
||||
import OptionsSection from "./components/OptionsSection";
|
||||
import RadioWithIllustration from "./components/RadioWithIllustration";
|
||||
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
@@ -40,19 +44,19 @@ function FormattingToolbar() {
|
||||
|
||||
return (
|
||||
<OptionsSection title={t("editing.editor_type.label")}>
|
||||
<FormRadioGroup
|
||||
name="editor-type"
|
||||
currentValue={textNoteEditorType} onChange={setTextNoteEditorType}
|
||||
<RadioWithIllustration
|
||||
currentValue={textNoteEditorType}
|
||||
onChange={setTextNoteEditorType}
|
||||
values={[
|
||||
{
|
||||
value: "ckeditor-balloon",
|
||||
label: t("editing.editor_type.floating.title"),
|
||||
inlineDescription: t("editing.editor_type.floating.description")
|
||||
key: "ckeditor-balloon",
|
||||
text: t("editing.editor_type.floating.title"),
|
||||
illustration: <ToolbarIllustration type="floating" />
|
||||
},
|
||||
{
|
||||
value: "ckeditor-classic",
|
||||
label: t("editing.editor_type.fixed.title"),
|
||||
inlineDescription: t("editing.editor_type.fixed.description")
|
||||
key: "ckeditor-classic",
|
||||
text: t("editing.editor_type.fixed.title"),
|
||||
illustration: <ToolbarIllustration type="fixed" />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -61,12 +65,54 @@ function FormattingToolbar() {
|
||||
name="multiline-toolbar"
|
||||
label={t("editing.editor_type.multiline-toolbar")}
|
||||
currentValue={textNoteEditorMultilineToolbar} onChange={setTextNoteEditorMultilineToolbar}
|
||||
containerStyle={{ marginInlineStart: "1em" }}
|
||||
containerStyle={{ marginTop: "1em" }}
|
||||
/>
|
||||
</OptionsSection>
|
||||
);
|
||||
}
|
||||
|
||||
function ToolbarIllustration({ type }: { type: "floating" | "fixed" }) {
|
||||
return (
|
||||
<div className="toolbar-illustration">
|
||||
{type === "fixed" && (
|
||||
<div className="toolbar-bar">
|
||||
<ToolbarIcon />
|
||||
<ToolbarIcon />
|
||||
<ToolbarIcon />
|
||||
<ToolbarIcon wide />
|
||||
<ToolbarIcon />
|
||||
<ToolbarIcon />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="document-area">
|
||||
<div className="text-line" style={{ width: "90%" }} />
|
||||
<div className="text-line" style={{ width: "75%" }} />
|
||||
<div className="text-line-with-selection">
|
||||
<span className="text-segment" style={{ width: "20%" }} />
|
||||
<span className="text-selection" />
|
||||
<span className="text-segment" style={{ width: "35%" }} />
|
||||
</div>
|
||||
<div className="text-line" style={{ width: "85%" }} />
|
||||
<div className="text-line" style={{ width: "60%" }} />
|
||||
</div>
|
||||
|
||||
{type === "floating" && (
|
||||
<div className="floating-toolbar">
|
||||
<ToolbarIcon />
|
||||
<ToolbarIcon />
|
||||
<ToolbarIcon />
|
||||
<ToolbarIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ToolbarIcon({ wide }: { wide?: boolean }) {
|
||||
return <div className={`toolbar-icon${wide ? " wide" : ""}`} />;
|
||||
}
|
||||
|
||||
function EditorFeatures() {
|
||||
const [emojiCompletionEnabled, setEmojiCompletionEnabled] = useTriliumOptionBool("textNoteEmojiCompletionEnabled");
|
||||
const [noteCompletionEnabled, setNoteCompletionEnabled] = useTriliumOptionBool("textNoteCompletionEnabled");
|
||||
@@ -113,15 +159,7 @@ function Editor() {
|
||||
return (
|
||||
<OptionsSection title={t("text_editor.title")}>
|
||||
<OptionsRow name="heading-style" label={t("heading_style.title")} description={t("heading_style.description")}>
|
||||
<FormSelect
|
||||
currentValue={headingStyle} onChange={setHeadingStyle}
|
||||
values={[
|
||||
{ value: "plain", title: t("heading_style.plain") },
|
||||
{ value: "underline", title: t("heading_style.underline") },
|
||||
{ value: "markdown", title: t("heading_style.markdown") }
|
||||
]}
|
||||
keyProperty="value" titleProperty="title"
|
||||
/>
|
||||
<HeadingStyleSelector currentValue={headingStyle} onChange={setHeadingStyle} />
|
||||
</OptionsRow>
|
||||
|
||||
<OptionsRow name="auto-readonly-size-text" label={t("text_auto_read_only_size.label")} description={t("text_auto_read_only_size.description")}>
|
||||
@@ -147,6 +185,44 @@ function Editor() {
|
||||
);
|
||||
}
|
||||
|
||||
const HEADING_STYLES = [
|
||||
{ value: "plain", labelKey: "heading_style.plain" },
|
||||
{ value: "underline", labelKey: "heading_style.underline" },
|
||||
{ value: "markdown", labelKey: "heading_style.markdown" }
|
||||
] as const;
|
||||
|
||||
function HeadingStyleSelector({ currentValue, onChange }: { currentValue: string, onChange: (value: string) => void }) {
|
||||
const currentStyle = HEADING_STYLES.find(s => s.value === currentValue) ?? HEADING_STYLES[0];
|
||||
|
||||
return (
|
||||
<Dropdown text={t(currentStyle.labelKey)}>
|
||||
{HEADING_STYLES.map(({ value, labelKey }) => (
|
||||
<FormListItem
|
||||
key={value}
|
||||
onClick={() => onChange(value)}
|
||||
selected={currentValue === value}
|
||||
>
|
||||
<div className="heading-style-preview">
|
||||
<HeadingPreview style={value} />
|
||||
<span className="heading-style-label">{t(labelKey)}</span>
|
||||
</div>
|
||||
</FormListItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function HeadingPreview({ style }: { style: string }) {
|
||||
const previewClass = `heading-preview heading-preview-${style}`;
|
||||
return (
|
||||
<span className={previewClass}>
|
||||
{style === "markdown" && <span className="heading-prefix">## </span>}
|
||||
Aa
|
||||
{style === "underline" && <span className="heading-underline" />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeBlockStyle() {
|
||||
const themes = useMemo(() => {
|
||||
const darkThemes: ThemeData[] = [];
|
||||
|
||||
Reference in New Issue
Block a user