TipTap-powered rich text editing library with WYSIWYG editor, markdown editor, template editor, collaboration support (Yjs), mentions, track changes, comments, code blocks, and table insertion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
import {
|
|
Component,
|
|
ChangeDetectionStrategy,
|
|
input,
|
|
output,
|
|
inject,
|
|
} from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { ButtonComponent, IconComponent, IconRegistry, SelectComponent, type SelectOption, TooltipDirective } from '@sda/base-ui';
|
|
import type { RtToolbarGroup, RtToolbarAction } from '../../types/toolbar.types';
|
|
import type { RtActiveMarks, RtActiveNodes } from '../../types/editor.types';
|
|
import { registerRtIcons } from '../../utils/icons.utils';
|
|
|
|
@Component({
|
|
selector: 'rt-toolbar',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule, ButtonComponent, IconComponent, SelectComponent, TooltipDirective],
|
|
templateUrl: './rt-toolbar.component.html',
|
|
styleUrl: './rt-toolbar.component.scss',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class RtToolbarComponent {
|
|
readonly groups = input.required<RtToolbarGroup[]>();
|
|
readonly activeMarks = input.required<RtActiveMarks>();
|
|
readonly activeNodes = input.required<RtActiveNodes>();
|
|
readonly canUndo = input(false);
|
|
readonly canRedo = input(false);
|
|
|
|
readonly action = output<{ action: RtToolbarAction; value?: string }>();
|
|
|
|
readonly headingOptions: SelectOption[] = [
|
|
{ value: '0', label: 'Normal text' },
|
|
{ value: '1', label: 'Heading 1' },
|
|
{ value: '2', label: 'Heading 2' },
|
|
{ value: '3', label: 'Heading 3' },
|
|
{ value: '4', label: 'Heading 4' },
|
|
];
|
|
|
|
constructor() {
|
|
registerRtIcons(inject(IconRegistry));
|
|
}
|
|
|
|
/** Check if a toolbar action is currently active */
|
|
isActive(actionName: RtToolbarAction): boolean {
|
|
const marks = this.activeMarks();
|
|
const nodes = this.activeNodes();
|
|
|
|
switch (actionName) {
|
|
case 'bold': return marks.bold;
|
|
case 'italic': return marks.italic;
|
|
case 'underline': return marks.underline;
|
|
case 'strikethrough': return marks.strike;
|
|
case 'code': return marks.code;
|
|
case 'subscript': return marks.subscript;
|
|
case 'superscript': return marks.superscript;
|
|
case 'link': return marks.link;
|
|
case 'highlight-color': return marks.highlight;
|
|
case 'bullet-list': return nodes.bulletList;
|
|
case 'ordered-list': return nodes.orderedList;
|
|
case 'task-list': return nodes.taskList;
|
|
case 'blockquote': return nodes.blockquote;
|
|
case 'code-block': return nodes.codeBlock;
|
|
case 'align-left': return nodes.textAlign === 'left';
|
|
case 'align-center': return nodes.textAlign === 'center';
|
|
case 'align-right': return nodes.textAlign === 'right';
|
|
case 'align-justify': return nodes.textAlign === 'justify';
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
/** Check if a toolbar action is disabled */
|
|
isDisabled(actionName: RtToolbarAction): boolean {
|
|
switch (actionName) {
|
|
case 'undo': return !this.canUndo();
|
|
case 'redo': return !this.canRedo();
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
/** Get the current heading level as a string value */
|
|
getCurrentHeadingValue(): string {
|
|
const heading = this.activeNodes().heading;
|
|
return heading ? String(heading) : '0';
|
|
}
|
|
|
|
/** Emit action event */
|
|
onAction(actionName: RtToolbarAction, value?: string): void {
|
|
this.action.emit({ action: actionName, value });
|
|
}
|
|
|
|
/** Handle heading select change */
|
|
onHeadingChange(value: string | number): void {
|
|
this.action.emit({ action: 'heading', value: String(value) });
|
|
}
|
|
}
|