improve text editor toolbar
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
This commit is contained in:
parent
de1b8a5638
commit
896ae23ff2
6 changed files with 148 additions and 11 deletions
|
|
@ -210,8 +210,9 @@
|
|||
"boldTitle": "Gras",
|
||||
"italicTitle": "Italique",
|
||||
"underlineTitle": "Souligné",
|
||||
"paragraphTitle": "Paragraphe",
|
||||
"strikeTitle": "Barré",
|
||||
"headingTitle": "Titre",
|
||||
"listGroupLabel": "Listes",
|
||||
"bulletListTitle": "Liste à puces",
|
||||
"numberListTitle": "Liste numérotée",
|
||||
"colorGroupLabel": "Couleurs",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Rôle : fournit l'outil bloc-notes riche avec annotations dessinées.
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
|
||||
import { DrawingControls, FormattingColorDropdown } from "./DrawingControls.jsx";
|
||||
import { DrawingOverlay } from "./DrawingOverlay.jsx";
|
||||
|
||||
|
|
@ -56,6 +57,56 @@ function ToolbarButton({ active, title, children, onClick }) {
|
|||
);
|
||||
}
|
||||
|
||||
function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
|
||||
return (
|
||||
<CompactDropdown
|
||||
className="notepad-list-dropdown"
|
||||
menuClassName="notepad-list-menu"
|
||||
label={textContent.listGroupLabel || "Listes"}
|
||||
closeOnMouseLeave
|
||||
preventMouseDown
|
||||
renderTrigger={({ toggle }) => (
|
||||
<button
|
||||
className={`notepad-toolbar-button ${activeFormats.bulletList || activeFormats.numberList ? "active" : ""}`}
|
||||
type="button"
|
||||
aria-pressed={activeFormats.bulletList || activeFormats.numberList}
|
||||
title={textContent.listGroupLabel || "Listes"}
|
||||
onClick={toggle}
|
||||
>
|
||||
<span className="notepad-list-icon" aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{({ close }) => (
|
||||
<>
|
||||
<button
|
||||
className={`notepad-list-option ${activeFormats.bulletList ? "active" : ""}`}
|
||||
type="button"
|
||||
aria-pressed={activeFormats.bulletList}
|
||||
onClick={() => {
|
||||
onCommand("insertUnorderedList");
|
||||
close();
|
||||
}}
|
||||
>
|
||||
•
|
||||
</button>
|
||||
<button
|
||||
className={`notepad-list-option ${activeFormats.numberList ? "active" : ""}`}
|
||||
type="button"
|
||||
aria-pressed={activeFormats.numberList}
|
||||
onClick={() => {
|
||||
onCommand("insertOrderedList");
|
||||
close();
|
||||
}}
|
||||
>
|
||||
1.
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</CompactDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export function NotepadModule({ toolboxId, moduleId, context }) {
|
||||
const storedData = context.getModuleData(toolboxId, moduleId, { text: "" });
|
||||
const normalizedData = useMemo(() => context.normalizeNotepadData(storedData), [context, storedData]);
|
||||
|
|
@ -76,7 +127,7 @@ export function NotepadModule({ toolboxId, moduleId, context }) {
|
|||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
paragraph: false,
|
||||
strikeThrough: false,
|
||||
heading: false,
|
||||
bulletList: false,
|
||||
numberList: false
|
||||
|
|
@ -104,7 +155,7 @@ export function NotepadModule({ toolboxId, moduleId, context }) {
|
|||
const selection = document.getSelection();
|
||||
if (!editor || !selection?.rangeCount || !editor.contains(selection.anchorNode)) {
|
||||
setActiveFormats((current) => Object.values(current).some(Boolean)
|
||||
? { bold: false, italic: false, underline: false, paragraph: false, heading: false, bulletList: false, numberList: false }
|
||||
? { bold: false, italic: false, underline: false, strikeThrough: false, heading: false, bulletList: false, numberList: false }
|
||||
: current);
|
||||
return;
|
||||
}
|
||||
|
|
@ -113,7 +164,7 @@ export function NotepadModule({ toolboxId, moduleId, context }) {
|
|||
bold: queryCommandState("bold"),
|
||||
italic: queryCommandState("italic"),
|
||||
underline: queryCommandState("underline"),
|
||||
paragraph: block === "p" || block === "div",
|
||||
strikeThrough: queryCommandState("strikeThrough"),
|
||||
heading: block === "h3" || block === "heading 3",
|
||||
bulletList: queryCommandState("insertUnorderedList"),
|
||||
numberList: queryCommandState("insertOrderedList")
|
||||
|
|
@ -206,6 +257,10 @@ export function NotepadModule({ toolboxId, moduleId, context }) {
|
|||
runCommand("formatBlock", tag);
|
||||
}
|
||||
|
||||
function toggleHeading() {
|
||||
applyBlock(activeFormats.heading ? "p" : "h3");
|
||||
}
|
||||
|
||||
function applyTextColor(color) {
|
||||
setTextColor(color);
|
||||
runCommand("foreColor", color);
|
||||
|
|
@ -253,12 +308,11 @@ export function NotepadModule({ toolboxId, moduleId, context }) {
|
|||
<ToolbarButton active={activeFormats.bold} title={textContent.boldTitle || "Gras"} onClick={() => runCommand("bold")}>B</ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.italic} title={textContent.italicTitle || "Italique"} onClick={() => runCommand("italic")}><em>I</em></ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.underline} title={textContent.underlineTitle || "Souligné"} onClick={() => runCommand("underline")}><u>U</u></ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.paragraph} title={textContent.paragraphTitle || "Paragraphe"} onClick={() => applyBlock("p")}>P</ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.heading} title={textContent.headingTitle || "Titre"} onClick={() => applyBlock("h3")}>H</ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.bulletList} title={textContent.bulletListTitle || "Liste à puces"} onClick={() => runCommand("insertUnorderedList")}>•</ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.numberList} title={textContent.numberListTitle || "Liste numérotée"} onClick={() => runCommand("insertOrderedList")}>1.</ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.strikeThrough} title={textContent.strikeTitle || "Barré"} onClick={() => runCommand("strikeThrough")}><s>S</s></ToolbarButton>
|
||||
<ToolbarButton active={activeFormats.heading} title={textContent.headingTitle || "Titre"} onClick={toggleHeading}>T</ToolbarButton>
|
||||
</div>
|
||||
<div className="notepad-toolbar-group" role="group" aria-label={textContent.colorGroupLabel || "Couleurs"}>
|
||||
<ListTypeDropdown textContent={textContent} activeFormats={activeFormats} onCommand={runCommand} />
|
||||
<FormattingColorDropdown
|
||||
label={textContent.colorGroupLabel || "Couleurs"}
|
||||
sections={[
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", calc: "r", marker: "k", timer: "z", task: "a", relation: "e", stroke: "d" };
|
||||
const ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const NOTEPAD_ALLOWED_COLORS = new Set(["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef", "#101426", "#202745"]);
|
||||
const NOTEPAD_ALLOWED_TAGS = new Set(["p", "div", "br", "strong", "b", "em", "i", "u", "ul", "ol", "li", "h3", "h4", "span", "mark"]);
|
||||
const NOTEPAD_ALLOWED_TAGS = new Set(["p", "div", "br", "strong", "b", "em", "i", "u", "s", "strike", "ul", "ol", "li", "h3", "h4", "span", "mark"]);
|
||||
const NOTEPAD_RGB_COLOR_MAP = {
|
||||
"rgb(245, 247, 255)": "#f5f7ff",
|
||||
"rgb(180, 189, 211)": "#b4bdd3",
|
||||
|
|
|
|||
|
|
@ -857,6 +857,39 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.notepad-list-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notepad-list-icon,
|
||||
.notepad-list-icon::before,
|
||||
.notepad-list-icon::after {
|
||||
display: block;
|
||||
width: 17px;
|
||||
height: 2px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.notepad-list-icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notepad-list-icon::before,
|
||||
.notepad-list-icon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.notepad-list-icon::before {
|
||||
top: -6px;
|
||||
}
|
||||
|
||||
.notepad-list-icon::after {
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.notepad-color-toggle::before,
|
||||
.notepad-swatch::before {
|
||||
content: "";
|
||||
|
|
@ -907,6 +940,52 @@
|
|||
transform 140ms ease;
|
||||
}
|
||||
|
||||
.notepad-list-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 50%;
|
||||
z-index: 50;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 34px);
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(10, 13, 28, 0.99);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.notepad-list-menu::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: calc(50% - 7px);
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid rgba(10, 13, 28, 0.99);
|
||||
border-left: 7px solid transparent;
|
||||
}
|
||||
|
||||
.notepad-list-option {
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
min-width: 34px;
|
||||
min-height: 34px;
|
||||
padding: 0;
|
||||
border-color: rgba(150, 165, 205, 0.18);
|
||||
background: rgba(16, 20, 38, 0.74);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 800;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.notepad-list-option:hover,
|
||||
.notepad-list-option.active {
|
||||
border-color: rgba(246, 196, 83, 0.48);
|
||||
background: rgba(246, 196, 83, 0.1);
|
||||
color: var(--color-accent-gold);
|
||||
}
|
||||
|
||||
.notepad-color-menu.is-sectioned {
|
||||
grid-template-columns: 1fr;
|
||||
width: 168px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue