--- /dev/null
+<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>
## Secondary Todo
-- Color picker support in table form color fields
-- Color picker for color controls
- 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)
- Deep check of translation coverage
-- About button & view
-- Mobile display and handling
## Bugs
import codeIcon from "@icons/editor/code.svg";
import formatClearIcon from "@icons/editor/format-clear.svg";
import {$selectionContainsTextFormat} from "../../../utils/selection";
+import {$patchStyleText} from "@lexical/selection";
+import {context} from "esbuild";
function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition {
return {
export const textColor: EditorBasicButtonDefinition = {label: 'Text color', icon: textColorIcon};
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 superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon);
export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon);
import {
EditorFormDefinition,
- EditorFormFieldDefinition,
+ EditorFormFieldDefinition, EditorFormFields,
EditorFormTabs,
EditorSelectFormFieldDefinition
} from "../../framework/forms";
import {formatSizeValue} from "../../../utils/dom";
import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
import {CommonBlockAlignment} from "lexical/nodes/common";
+import {colorFieldBuilder} from "../../framework/blocks/color-field";
const borderStyleInput: EditorSelectFormFieldDefinition = {
label: 'Border style',
} as EditorSelectFormFieldDefinition,
];
- const advancedFields: EditorFormFieldDefinition[] = [
+ const advancedFields: EditorFormFields = [
{
label: 'Border width', // inline-style: border-width
name: 'border_width',
type: 'text',
},
borderStyleInput, // inline-style: border-style
- borderColorInput, // inline-style: border-color
- backgroundColorInput, // inline-style: background-color
+ colorFieldBuilder(borderColorInput),
+ colorFieldBuilder(backgroundColorInput),
];
return new EditorFormTabs([
type: 'text',
},
borderStyleInput, // style on tr: height
- borderColorInput, // style on tr: height
- backgroundColorInput, // style on tr: height
+ colorFieldBuilder(borderColorInput),
+ colorFieldBuilder(backgroundColorInput),
],
};
alignmentInput, // alignment class
];
- const advancedFields: EditorFormFieldDefinition[] = [
- borderStyleInput, // Style - border-style
- borderColorInput, // Style - border-color
- backgroundColorInput, // Style - background-color
+ const advancedFields: EditorFormFields = [
+ borderStyleInput,
+ colorFieldBuilder(borderColorInput),
+ colorFieldBuilder(backgroundColorInput),
];
return new EditorFormTabs([
} from "./buttons/block-formats";
import {
bold, clearFormating, code,
- highlightColor,
+ highlightColor, highlightColorAction,
italic,
strikethrough, subscript,
superscript,
- textColor,
+ textColor, textColorAction,
underline
} from "./buttons/inline-formats";
import {
new EditorButton(italic),
new EditorButton(underline),
new EditorDropdownButton({ button: new EditorColorButton(textColor, 'color') }, [
- new EditorColorPicker('color'),
+ new EditorColorPicker(textColorAction),
]),
new EditorDropdownButton({button: new EditorColorButton(highlightColor, 'background-color')}, [
- new EditorColorPicker('background-color'),
+ new EditorColorPicker(highlightColorAction),
]),
new EditorButton(strikethrough),
new EditorButton(superscript),
--- /dev/null
+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));
+ }
+ }
+}
\ No newline at end of file
-import {EditorUiElement} from "../core";
-import {$getSelection} from "lexical";
-import {$patchStyleText} from "@lexical/selection";
+import {EditorUiContext, EditorUiElement} from "../core";
import {el} from "../../../utils/dom";
import removeIcon from "@icons/editor/color-clear.svg";
const storageKey = 'bs-lexical-custom-colors';
+export type EditorColorPickerCallback = (color: string, context: EditorUiContext) => void;
+
export class EditorColorPicker extends EditorUiElement {
- protected styleProperty: string;
+ protected callback: EditorColorPickerCallback;
- constructor(styleProperty: string) {
+ constructor(callback: EditorColorPickerCallback) {
super();
- this.styleProperty = styleProperty;
+ this.callback = callback;
}
buildDOM(): HTMLElement {
}
setColor(color: string) {
- this.getContext().editor.update(() => {
- const selection = $getSelection();
- if (selection) {
- $patchStyleText(selection, {[this.styleProperty]: color || null});
- }
- });
+ this.callback(color, this.getContext());
}
}
\ No newline at end of file
updateFormFromHeader(header: HeadingNode) {
this.getHeaderIdAndText(header).then(({id, text}) => {
- console.log('updating form', id, text);
const modal = this.getContext().manager.getActiveModal('link');
if (modal) {
modal.getForm().setValues({
return new Promise((res) => {
this.getContext().editor.update(() => {
let id = header.getId();
- console.log('header', id, header.__id);
if (!id) {
id = 'header-' + uniqueIdSmall();
header.setId(id);
valuesByLabel: Record<string, string>
}
+export type EditorFormFields = (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
+
interface EditorFormTabDefinition {
label: string;
- contents: EditorFormFieldDefinition[];
+ contents: EditorFormFields;
}
export interface EditorFormDefinition {
submitText: string;
action: (formData: FormData, context: EditorUiContext) => Promise<boolean>;
- fields: (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
+ fields: EditorFormFields;
}
export class EditorFormField extends EditorUiElement {
setValue(value: string) {
const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
input.value = value;
+ input.dispatchEvent(new Event('change'));
}
getName(): string {
export class EditorFormTab extends EditorContainerUiElement {
protected definition: EditorFormTabDefinition;
- protected fields: EditorFormField[];
+ protected fields: EditorUiElement[];
protected id: string;
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);
this.definition = definition;
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-bold {