Lexical: Added color picker/indicator to form fields
This commit is contained in:
parent
c091f67db3
commit
04cca77ae6
|
@ -0,0 +1,10 @@
|
||||||
|
<svg version="1.1" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern2" x="0.40000001" patternTransform="scale(200)" preserveAspectRatio="xMidYMid" xlink:href="#Checkerboard"/>
|
||||||
|
<pattern id="Checkerboard" width="2" height="2" fill="#b6b6b6" patternTransform="translate(0) scale(10)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
|
||||||
|
<rect width="1" height="1"/>
|
||||||
|
<rect x="1" y="1" width="1" height="1"/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect class="editor-icon-color-display" x="103.53" y="-856.47" width="752.94" height="752.94" rx="47.059" ry="47.059" fill="url(#pattern2)" stroke="#666" stroke-linecap="square" stroke-linejoin="round" stroke-width="47.059"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 762 B |
|
@ -10,13 +10,9 @@
|
||||||
|
|
||||||
## Secondary Todo
|
## Secondary Todo
|
||||||
|
|
||||||
- Color picker support in table form color fields
|
|
||||||
- Color picker for color controls
|
|
||||||
- Table caption text support
|
- Table caption text support
|
||||||
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
|
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
|
||||||
- Deep check of translation coverage
|
- Deep check of translation coverage
|
||||||
- About button & view
|
|
||||||
- Mobile display and handling
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import subscriptIcon from "@icons/editor/subscript.svg";
|
||||||
import codeIcon from "@icons/editor/code.svg";
|
import codeIcon from "@icons/editor/code.svg";
|
||||||
import formatClearIcon from "@icons/editor/format-clear.svg";
|
import formatClearIcon from "@icons/editor/format-clear.svg";
|
||||||
import {$selectionContainsTextFormat} from "../../../utils/selection";
|
import {$selectionContainsTextFormat} from "../../../utils/selection";
|
||||||
|
import {$patchStyleText} from "@lexical/selection";
|
||||||
|
import {context} from "esbuild";
|
||||||
|
|
||||||
function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition {
|
function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition {
|
||||||
return {
|
return {
|
||||||
|
@ -32,6 +34,18 @@ export const underline: EditorButtonDefinition = buildFormatButton('Underline',
|
||||||
export const textColor: EditorBasicButtonDefinition = {label: 'Text color', icon: textColorIcon};
|
export const textColor: EditorBasicButtonDefinition = {label: 'Text color', icon: textColorIcon};
|
||||||
export const highlightColor: EditorBasicButtonDefinition = {label: 'Background color', icon: highlightIcon};
|
export const highlightColor: EditorBasicButtonDefinition = {label: 'Background color', icon: highlightIcon};
|
||||||
|
|
||||||
|
function colorAction(context: EditorUiContext, property: string, color: string): void {
|
||||||
|
context.editor.update(() => {
|
||||||
|
const selection = $getSelection();
|
||||||
|
if (selection) {
|
||||||
|
$patchStyleText(selection, {[property]: color || null});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const textColorAction = (color: string, context: EditorUiContext) => colorAction(context, 'color', color);
|
||||||
|
export const highlightColorAction = (color: string, context: EditorUiContext) => colorAction(context, 'color', color);
|
||||||
|
|
||||||
export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough', strikethroughIcon);
|
export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough', strikethroughIcon);
|
||||||
export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon);
|
export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon);
|
||||||
export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon);
|
export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
EditorFormDefinition,
|
EditorFormDefinition,
|
||||||
EditorFormFieldDefinition,
|
EditorFormFieldDefinition, EditorFormFields,
|
||||||
EditorFormTabs,
|
EditorFormTabs,
|
||||||
EditorSelectFormFieldDefinition
|
EditorSelectFormFieldDefinition
|
||||||
} from "../../framework/forms";
|
} from "../../framework/forms";
|
||||||
|
@ -17,6 +17,7 @@ import {
|
||||||
import {formatSizeValue} from "../../../utils/dom";
|
import {formatSizeValue} from "../../../utils/dom";
|
||||||
import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
|
import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
|
||||||
import {CommonBlockAlignment} from "lexical/nodes/common";
|
import {CommonBlockAlignment} from "lexical/nodes/common";
|
||||||
|
import {colorFieldBuilder} from "../../framework/blocks/color-field";
|
||||||
|
|
||||||
const borderStyleInput: EditorSelectFormFieldDefinition = {
|
const borderStyleInput: EditorSelectFormFieldDefinition = {
|
||||||
label: 'Border style',
|
label: 'Border style',
|
||||||
|
@ -145,15 +146,15 @@ export const cellProperties: EditorFormDefinition = {
|
||||||
} as EditorSelectFormFieldDefinition,
|
} as EditorSelectFormFieldDefinition,
|
||||||
];
|
];
|
||||||
|
|
||||||
const advancedFields: EditorFormFieldDefinition[] = [
|
const advancedFields: EditorFormFields = [
|
||||||
{
|
{
|
||||||
label: 'Border width', // inline-style: border-width
|
label: 'Border width', // inline-style: border-width
|
||||||
name: 'border_width',
|
name: 'border_width',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
borderStyleInput, // inline-style: border-style
|
borderStyleInput, // inline-style: border-style
|
||||||
borderColorInput, // inline-style: border-color
|
colorFieldBuilder(borderColorInput),
|
||||||
backgroundColorInput, // inline-style: background-color
|
colorFieldBuilder(backgroundColorInput),
|
||||||
];
|
];
|
||||||
|
|
||||||
return new EditorFormTabs([
|
return new EditorFormTabs([
|
||||||
|
@ -210,8 +211,8 @@ export const rowProperties: EditorFormDefinition = {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
borderStyleInput, // style on tr: height
|
borderStyleInput, // style on tr: height
|
||||||
borderColorInput, // style on tr: height
|
colorFieldBuilder(borderColorInput),
|
||||||
backgroundColorInput, // style on tr: height
|
colorFieldBuilder(backgroundColorInput),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -305,10 +306,10 @@ export const tableProperties: EditorFormDefinition = {
|
||||||
alignmentInput, // alignment class
|
alignmentInput, // alignment class
|
||||||
];
|
];
|
||||||
|
|
||||||
const advancedFields: EditorFormFieldDefinition[] = [
|
const advancedFields: EditorFormFields = [
|
||||||
borderStyleInput, // Style - border-style
|
borderStyleInput,
|
||||||
borderColorInput, // Style - border-color
|
colorFieldBuilder(borderColorInput),
|
||||||
backgroundColorInput, // Style - background-color
|
colorFieldBuilder(backgroundColorInput),
|
||||||
];
|
];
|
||||||
|
|
||||||
return new EditorFormTabs([
|
return new EditorFormTabs([
|
||||||
|
|
|
@ -44,11 +44,11 @@ import {
|
||||||
} from "./buttons/block-formats";
|
} from "./buttons/block-formats";
|
||||||
import {
|
import {
|
||||||
bold, clearFormating, code,
|
bold, clearFormating, code,
|
||||||
highlightColor,
|
highlightColor, highlightColorAction,
|
||||||
italic,
|
italic,
|
||||||
strikethrough, subscript,
|
strikethrough, subscript,
|
||||||
superscript,
|
superscript,
|
||||||
textColor,
|
textColor, textColorAction,
|
||||||
underline
|
underline
|
||||||
} from "./buttons/inline-formats";
|
} from "./buttons/inline-formats";
|
||||||
import {
|
import {
|
||||||
|
@ -114,10 +114,10 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai
|
||||||
new EditorButton(italic),
|
new EditorButton(italic),
|
||||||
new EditorButton(underline),
|
new EditorButton(underline),
|
||||||
new EditorDropdownButton({ button: new EditorColorButton(textColor, 'color') }, [
|
new EditorDropdownButton({ button: new EditorColorButton(textColor, 'color') }, [
|
||||||
new EditorColorPicker('color'),
|
new EditorColorPicker(textColorAction),
|
||||||
]),
|
]),
|
||||||
new EditorDropdownButton({button: new EditorColorButton(highlightColor, 'background-color')}, [
|
new EditorDropdownButton({button: new EditorColorButton(highlightColor, 'background-color')}, [
|
||||||
new EditorColorPicker('background-color'),
|
new EditorColorPicker(highlightColorAction),
|
||||||
]),
|
]),
|
||||||
new EditorButton(strikethrough),
|
new EditorButton(strikethrough),
|
||||||
new EditorButton(superscript),
|
new EditorButton(superscript),
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import {EditorContainerUiElement, EditorUiBuilderDefinition, EditorUiContext} from "../core";
|
||||||
|
import {EditorFormField, EditorFormFieldDefinition} from "../forms";
|
||||||
|
import {EditorColorPicker} from "./color-picker";
|
||||||
|
import {EditorDropdownButton} from "./dropdown-button";
|
||||||
|
|
||||||
|
import colorDisplayIcon from "@icons/editor/color-display.svg"
|
||||||
|
|
||||||
|
export class EditorColorField extends EditorContainerUiElement {
|
||||||
|
protected input: EditorFormField;
|
||||||
|
protected pickerButton: EditorDropdownButton;
|
||||||
|
|
||||||
|
constructor(input: EditorFormField) {
|
||||||
|
super([]);
|
||||||
|
|
||||||
|
this.input = input;
|
||||||
|
|
||||||
|
this.pickerButton = new EditorDropdownButton({
|
||||||
|
button: { icon: colorDisplayIcon, label: 'Select color'}
|
||||||
|
}, [
|
||||||
|
new EditorColorPicker(this.onColorSelect.bind(this))
|
||||||
|
]);
|
||||||
|
this.addChildren(this.pickerButton, this.input);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected buildDOM(): HTMLElement {
|
||||||
|
const dom = this.input.getDOMElement();
|
||||||
|
dom.append(this.pickerButton.getDOMElement());
|
||||||
|
dom.classList.add('editor-color-field-container');
|
||||||
|
|
||||||
|
const field = dom.querySelector('input') as HTMLInputElement;
|
||||||
|
field.addEventListener('change', () => {
|
||||||
|
this.setIconColor(field.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
onColorSelect(color: string, context: EditorUiContext): void {
|
||||||
|
this.input.setValue(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIconColor(color: string) {
|
||||||
|
const icon = this.getDOMElement().querySelector('svg .editor-icon-color-display');
|
||||||
|
if (icon) {
|
||||||
|
icon.setAttribute('fill', color || 'url(#pattern2)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function colorFieldBuilder(field: EditorFormFieldDefinition): EditorUiBuilderDefinition {
|
||||||
|
return {
|
||||||
|
build() {
|
||||||
|
return new EditorColorField(new EditorFormField(field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
import {EditorUiElement} from "../core";
|
import {EditorUiContext, EditorUiElement} from "../core";
|
||||||
import {$getSelection} from "lexical";
|
|
||||||
import {$patchStyleText} from "@lexical/selection";
|
|
||||||
import {el} from "../../../utils/dom";
|
import {el} from "../../../utils/dom";
|
||||||
|
|
||||||
import removeIcon from "@icons/editor/color-clear.svg";
|
import removeIcon from "@icons/editor/color-clear.svg";
|
||||||
|
@ -38,13 +36,15 @@ const colorChoices = [
|
||||||
|
|
||||||
const storageKey = 'bs-lexical-custom-colors';
|
const storageKey = 'bs-lexical-custom-colors';
|
||||||
|
|
||||||
|
export type EditorColorPickerCallback = (color: string, context: EditorUiContext) => void;
|
||||||
|
|
||||||
export class EditorColorPicker extends EditorUiElement {
|
export class EditorColorPicker extends EditorUiElement {
|
||||||
|
|
||||||
protected styleProperty: string;
|
protected callback: EditorColorPickerCallback;
|
||||||
|
|
||||||
constructor(styleProperty: string) {
|
constructor(callback: EditorColorPickerCallback) {
|
||||||
super();
|
super();
|
||||||
this.styleProperty = styleProperty;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDOM(): HTMLElement {
|
buildDOM(): HTMLElement {
|
||||||
|
@ -131,11 +131,6 @@ export class EditorColorPicker extends EditorUiElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color: string) {
|
setColor(color: string) {
|
||||||
this.getContext().editor.update(() => {
|
this.callback(color, this.getContext());
|
||||||
const selection = $getSelection();
|
|
||||||
if (selection) {
|
|
||||||
$patchStyleText(selection, {[this.styleProperty]: color || null});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -44,7 +44,6 @@ export class LinkField extends EditorContainerUiElement {
|
||||||
|
|
||||||
updateFormFromHeader(header: HeadingNode) {
|
updateFormFromHeader(header: HeadingNode) {
|
||||||
this.getHeaderIdAndText(header).then(({id, text}) => {
|
this.getHeaderIdAndText(header).then(({id, text}) => {
|
||||||
console.log('updating form', id, text);
|
|
||||||
const modal = this.getContext().manager.getActiveModal('link');
|
const modal = this.getContext().manager.getActiveModal('link');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.getForm().setValues({
|
modal.getForm().setValues({
|
||||||
|
@ -60,7 +59,6 @@ export class LinkField extends EditorContainerUiElement {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
this.getContext().editor.update(() => {
|
this.getContext().editor.update(() => {
|
||||||
let id = header.getId();
|
let id = header.getId();
|
||||||
console.log('header', id, header.__id);
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
id = 'header-' + uniqueIdSmall();
|
id = 'header-' + uniqueIdSmall();
|
||||||
header.setId(id);
|
header.setId(id);
|
||||||
|
|
|
@ -19,15 +19,17 @@ export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefiniti
|
||||||
valuesByLabel: Record<string, string>
|
valuesByLabel: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EditorFormFields = (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
|
||||||
|
|
||||||
interface EditorFormTabDefinition {
|
interface EditorFormTabDefinition {
|
||||||
label: string;
|
label: string;
|
||||||
contents: EditorFormFieldDefinition[];
|
contents: EditorFormFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditorFormDefinition {
|
export interface EditorFormDefinition {
|
||||||
submitText: string;
|
submitText: string;
|
||||||
action: (formData: FormData, context: EditorUiContext) => Promise<boolean>;
|
action: (formData: FormData, context: EditorUiContext) => Promise<boolean>;
|
||||||
fields: (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
|
fields: EditorFormFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditorFormField extends EditorUiElement {
|
export class EditorFormField extends EditorUiElement {
|
||||||
|
@ -41,6 +43,7 @@ export class EditorFormField extends EditorUiElement {
|
||||||
setValue(value: string) {
|
setValue(value: string) {
|
||||||
const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
|
const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
|
||||||
input.value = value;
|
input.value = value;
|
||||||
|
input.dispatchEvent(new Event('change'));
|
||||||
}
|
}
|
||||||
|
|
||||||
getName(): string {
|
getName(): string {
|
||||||
|
@ -155,11 +158,17 @@ export class EditorForm extends EditorContainerUiElement {
|
||||||
export class EditorFormTab extends EditorContainerUiElement {
|
export class EditorFormTab extends EditorContainerUiElement {
|
||||||
|
|
||||||
protected definition: EditorFormTabDefinition;
|
protected definition: EditorFormTabDefinition;
|
||||||
protected fields: EditorFormField[];
|
protected fields: EditorUiElement[];
|
||||||
protected id: string;
|
protected id: string;
|
||||||
|
|
||||||
constructor(definition: EditorFormTabDefinition) {
|
constructor(definition: EditorFormTabDefinition) {
|
||||||
const fields = definition.contents.map(fieldDef => new EditorFormField(fieldDef));
|
const fields = definition.contents.map(fieldDef => {
|
||||||
|
if (isUiBuilderDefinition(fieldDef)) {
|
||||||
|
return fieldDef.build();
|
||||||
|
}
|
||||||
|
return new EditorFormField(fieldDef)
|
||||||
|
});
|
||||||
|
|
||||||
super(fields);
|
super(fields);
|
||||||
|
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
|
|
|
@ -649,6 +649,16 @@ textarea.editor-form-field-input {
|
||||||
width: $inputWidth - 40px;
|
width: $inputWidth - 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.editor-color-field-container {
|
||||||
|
position: relative;
|
||||||
|
input {
|
||||||
|
padding-left: 36px;
|
||||||
|
}
|
||||||
|
.editor-dropdown-menu-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Editor theme styles
|
// Editor theme styles
|
||||||
.editor-theme-bold {
|
.editor-theme-bold {
|
||||||
|
|
Loading…
Reference in New Issue