Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
:host(limel-text-editor-highlight-color-menu) {
animation: fade 0.2s ease forwards;
animation-delay: 0.1s; // prevents the visual glitch when the menu opens
opacity: 0;

display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem;
max-width: calc(100vw - 2rem);
border-radius: 0.5rem;
background-color: var(--lime-elevated-surface-background-color);
box-shadow: var(--shadow-depth-16);
}

.color-picker-container {
// Ensure the color picker has proper space
min-height: 3rem;
}

.actions {
display: flex;
justify-content: end;
gap: 0.5rem;
}

@keyframes fade {
0% {
scale: 0.86;
opacity: 0;
}
100% {
scale: 1;
opacity: 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Component, Prop, h, Event, EventEmitter } from '@stencil/core';
import { Languages } from '../../date-picker/date.types';
import translate from '../../../global/translations';
import { ENTER, ESCAPE } from '../../../util/keycodes';

/**
* This component is a menu for selecting highlight color in the text editor.
* It allows the user to choose a color for text highlighting.
* @beta
* @private
*/
@Component({
tag: 'limel-text-editor-highlight-color-menu',
shadow: true,
styleUrl: 'editor-highlight-color-menu.scss',
})
export class TextEditorHighlightColorMenu {
/**
* The selected color
*/
@Prop({ reflect: true })
public color: string = 'rgb(var(--color-yellow-light))';

/**
* Defines the language for translations.
*/
@Prop({ reflect: true })
public language: Languages = 'en';

/**
* Open state of the highlight-color-menu dialog
*/
@Prop({ reflect: true })
public isOpen: boolean = false;

/**
* Emitted when the menu is closed from inside the component.
* (*Not* emitted when the consumer sets the `open`-property to `false`.)
*/
@Event()
private cancel: EventEmitter<void>;

/**
* Emitted when the menu is saved from inside the component.
*/
@Event()
private save: EventEmitter<void>;

/**
* Emitted when the user selects a new color
*/
@Event()
private colorChange: EventEmitter<string>;

private colorPicker: HTMLLimelColorPickerElement;
private saveButton: HTMLLimelButtonElement;

public connectedCallback() {
this.setupGlobalHandlers();
}

public disconnectedCallback() {
this.teardownGlobalHandlers();
}

public componentDidRender() {
this.focusOnColorPicker();
}

private setupGlobalHandlers() {
document.addEventListener('keydown', this.handleKeyDown);
}

private teardownGlobalHandlers() {
document.removeEventListener('keydown', this.handleKeyDown);
}

private focusOnColorPicker() {
if (this.isOpen && this.colorPicker) {
// Focus the color picker when the menu opens
setTimeout(() => {
this.colorPicker?.focus();
}, 100);
}
}

public render() {
return [
<div class="color-picker-container">
<limel-color-picker
value={this.color}
label={this.getTranslation(
'editor-highlight-color-menu.color'
)}
tooltipLabel={this.getTranslation(
'editor-highlight-color-menu.tooltip'
)}
helperText={this.getTranslation(
'editor-highlight-color-menu.helper'
)}
onChange={this.handleColorChange}
ref={(el) =>
(this.colorPicker = el as HTMLLimelColorPickerElement)
}
/>
</div>,
<div class="actions">
<limel-button
label={this.getTranslation('cancel')}
onClick={this.handleCancel}
/>
<limel-button
primary={true}
label={this.getTranslation('save')}
onClick={this.handleSave}
ref={(el) =>
(this.saveButton = el as HTMLLimelButtonElement)
}
slot="button"
/>
</div>,
];
}

private getTranslation = (key: string) => {
return translate.get(key, this.language);
};

private handleKeyDown = (event: KeyboardEvent) => {
if (!this.isOpen) {
return;
}

if (event.key === ESCAPE) {
this.handleCancel();
} else if (event.key === ENTER) {
this.handleSave();
}
};

private handleColorChange = (event: CustomEvent<string>) => {
this.colorChange.emit(event.detail);
};

private handleCancel = () => {
this.cancel.emit();
};

private handleSave = () => {
this.save.emit();
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Component, h, State } from '@stencil/core';

/**
* Example of the highlight color menu component
*/
@Component({
tag: 'limel-example-highlight-color-menu',
shadow: true,
})
export class HighlightColorMenuExample {
@State()
private color: string = 'rgb(var(--color-yellow-light))';

@State()
private isOpen: boolean = false;

public render() {
return [
<div>
<limel-button
label="Open Highlight Color Menu"
onClick={this.openMenu}
/>
<p>Selected color: {this.color}</p>
<p>
<strong>Instructions:</strong> Click the colored square
button to open the color palette with 100 visual color
swatches!
</p>
</div>,
<limel-text-editor-highlight-color-menu
color={this.color}
isOpen={this.isOpen}
onColorChange={this.handleColorChange}
onCancel={this.handleCancel}
onSave={this.handleSave}
/>,
];
}

private openMenu = () => {
this.isOpen = true;
};

private handleColorChange = (event: CustomEvent<string>) => {
this.color = event.detail;
};

private handleCancel = () => {
this.isOpen = false;
};

private handleSave = () => {
this.isOpen = false;
console.log('Selected highlight color:', this.color);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
type CommandFunction = (
schema: Schema,
mark: EditorMenuTypes,
link?: EditorTextLink
link?: EditorTextLink,
color?: string
) => CommandWithActive;

interface CommandMapping {
Expand Down Expand Up @@ -110,7 +111,8 @@
const createToggleMarkCommand = (
schema: Schema,
markName: string,
link?: EditorTextLink
link?: EditorTextLink,
_color?: string

Check failure on line 115 in src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts

View workflow job for this annotation

GitHub Actions / Lint

'_color' is defined but never used
): CommandWithActive => {
const markType: MarkType | undefined = schema.marks[markName];
if (!markType) {
Expand All @@ -125,6 +127,25 @@
return command;
};

const createToggleHighlightCommand = (
schema: Schema,
markName: string,
_link?: EditorTextLink,
color?: string
): CommandWithActive => {
const markType: MarkType | undefined = schema.marks[markName];
if (!markType) {
throw new Error(`Mark "${markName}" not found in schema`);
}

const attrs = color ? { color } : {};

const command: CommandWithActive = toggleMark(markType, attrs);
setActiveMethodForMark(command, markType);

return command;
};

const getAttributes = (
markName: string,
link: EditorTextLink
Expand Down Expand Up @@ -301,6 +322,7 @@
underline: createToggleMarkCommand,
strikethrough: createToggleMarkCommand,
code: createToggleMarkCommand,
highlight: createToggleHighlightCommand,
link: createInsertLinkCommand,
headerlevel1: (schema) =>
createSetNodeTypeCommand(
Expand Down Expand Up @@ -338,12 +360,25 @@
this.schema = schema;
}

public getCommand(mark: EditorMenuTypes, link?: EditorTextLink) {
public getCommand(
mark: EditorMenuTypes,
link?: EditorTextLink,
color?: string
) {
const commandFunc = commandMapping[mark];
if (!commandFunc) {
throw new Error(`The Mark "${mark}" is not supported`);
}

if (mark === EditorMenuTypes.Highlight && color) {
return createToggleHighlightCommand(
this.schema,
mark,
undefined,
color
);
}

return commandFunc(this.schema, mark, link);
}

Expand All @@ -355,6 +390,7 @@
'Mod-Shift-2': this.getCommand(EditorMenuTypes.HeaderLevel2),
'Mod-Shift-3': this.getCommand(EditorMenuTypes.HeaderLevel3),
'Mod-Shift-X': this.getCommand(EditorMenuTypes.Strikethrough),
'Mod-Shift-H': this.getCommand(EditorMenuTypes.Highlight),
'Mod-`': this.getCommand(EditorMenuTypes.Code),
'Mod-Shift-C': this.getCommand(EditorMenuTypes.CodeBlock),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ const textEditorMenuItems: Array<
iconOnly: true,
selected: false,
},
{
value: EditorMenuTypes.Highlight,
text: 'Highlight',
commandText: `${mod} ${shift} H`,
icon: 'marker_pen',
iconOnly: true,
selected: false,
},
{
value: EditorMenuTypes.Link,
text: 'Link',
Expand Down Expand Up @@ -135,6 +143,7 @@ export const menuTranslationIDs = {
link: 'editor-menu.link',
strikethrough: 'editor-menu.strikethrough',
code: 'editor-menu.code',
highlight: 'editor-menu.highlight',
};

export type menuTranslationIDs =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,37 @@ export const strikethrough: MarkSpec = {
return ['s', 0];
},
};

export const highlight: MarkSpec = {
attrs: {
color: { default: 'rgb(var(--color-yellow-light))' },
},
parseDOM: [
{
tag: 'mark',
getAttrs: (dom) => ({
color:
(dom as HTMLElement).style.backgroundColor ||
'rgb(var(--color-yellow-light))',
}),
},
{
tag: 'span[style*="background-color"]',
getAttrs: (dom) => ({
color:
(dom as HTMLElement).style.backgroundColor ||
'rgb(var(--color-yellow-light))',
}),
},
],
toDOM: (node) => {
return [
'mark',
{
class: 'lime-text-highlight',
style: `background-color: ${node.attrs.color}`,
},
0,
];
},
};
Loading
Loading