Skip to content

Commit

Permalink
feat: add horizontal_rule menu item
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed May 8, 2022
1 parent fc71027 commit 608e978
Show file tree
Hide file tree
Showing 17 changed files with 208 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class AppComponent implements OnInit, OnDestroy {
['link', 'image'],
['text_color', 'background_color'],
['align_left', 'align_center', 'align_right', 'align_justify'],
['horizontal_rule'],
];
colorPresets = ['red', '#FF0000', 'rgb(255, 0, 0)'];

Expand Down
18 changes: 18 additions & 0 deletions projects/ngx-editor/helpers/canInsertNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { EditorState } from 'prosemirror-state';
import type { NodeType } from 'prosemirror-model';

export const canInsert = (state: EditorState, nodeType: NodeType) => {
const { $from } = state.selection;

for (let d = $from.depth; d >= 0; d -= 1) {
const index = $from.index(d);

if ($from.node(d).canReplaceWith(index, index, nodeType)) {
return true;
}
}

return false;
};

export default canInsert;
1 change: 1 addition & 0 deletions projects/ngx-editor/helpers/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './getSelectionMarks';
export * from './getSelectionNodes';
export * from './markApplies';
export * from './markInputRule';
export * from './canInsertNode';
28 changes: 28 additions & 0 deletions projects/ngx-editor/src/lib/commands/HorizontalRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NodeType } from 'prosemirror-model';
import { EditorState, Transaction } from 'prosemirror-state';
import { Command } from 'prosemirror-commands';

import { canInsert } from 'ngx-editor/helpers';

class HorizontalRule {
insert(): Command {
return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {
const { schema, tr } = state;

const type: NodeType = schema.nodes.horizontal_rule;

if (!type) {
return false;
}

dispatch(tr.replaceSelectionWith(type.create()).scrollIntoView());
return true;
};
}

canExecute(state: EditorState): boolean {
return canInsert(state, state.schema.nodes.horizontal_rule);
}
}

export default HorizontalRule;
2 changes: 2 additions & 0 deletions projects/ngx-editor/src/lib/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Mark from './Mark';
import Blockquote from './Blockquote';
import HorizontalRule from './HorizontalRule';
import ListItem from './ListItem';
import Heading from './Heading';
import TextAlign from './TextAlign';
Expand All @@ -13,6 +14,7 @@ export const CODE = new Mark('code');
export const UNDERLINE = new Mark('u');
export const STRIKE = new Mark('s');
export const BLOCKQUOTE = new Blockquote();
export const HORIZONTAL_RULE = new HorizontalRule();
export const UL = new ListItem(true);
export const OL = new ListItem(false);
export const H1 = new Heading(1);
Expand Down
5 changes: 5 additions & 0 deletions projects/ngx-editor/src/lib/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ export interface ToggleCommand {
isActive: (state: EditorState) => boolean;
canExecute: (state: EditorState) => boolean;
}

export interface InsertCommand {
insert: () => Command;
canExecute: (state: EditorState) => boolean;
}
6 changes: 6 additions & 0 deletions projects/ngx-editor/src/lib/icons/horizontal_rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default `
<g>
<rect fill="none" fill-rule="evenodd" height="24" width="24"/>
<rect fill-rule="evenodd" height="2" width="16" x="4" y="11"/>
</g>
`;
2 changes: 2 additions & 0 deletions projects/ngx-editor/src/lib/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import alignRight from './align_right';
import alignJustify from './align_justify';
import textColor from './text_color';
import colorFill from './color_fill';
import horizontalRule from './horizontal_rule';

const DEFAULT_ICON_HEIGHT = 20;
const DEFAULT_ICON_WIDTH = 20;
Expand All @@ -40,6 +41,7 @@ const icons: Record<string, any> = {
align_justify: alignJustify,
text_color: textColor,
color_fill: colorFill,
horizontal_rule: horizontalRule,
};

class Icon {
Expand Down
6 changes: 5 additions & 1 deletion projects/ngx-editor/src/lib/modules/menu/MenuCommands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Commands from '../../commands';

import { ToggleCommand } from '../../commands/types';
import { InsertCommand, ToggleCommand } from '../../commands/types';

export const ToggleCommands: Record<string, ToggleCommand> = {
bold: Commands.STRONG,
Expand All @@ -23,6 +23,10 @@ export const ToggleCommands: Record<string, ToggleCommand> = {
align_justify: Commands.ALIGN_JUSTIFY,
};

export const InsertCommands: Record<string, InsertCommand> = {
horizontal_rule: Commands.HORIZONTAL_RULE,
};

export const Link = Commands.LINK;
export const Image = Commands.IMAGE;
export const TextColor = Commands.TEXT_COLOR;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="NgxEditor__MenuItem--IconContainer" [innerHTML]="html | sanitizeHtml" (mousedown)="insert($event)"
[title]="getTitle(name)">
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { InsertCommandComponent } from './insert-command.component';
import { SanitizeHtmlPipe } from '../../../pipes/sanitize/sanitize-html.pipe';
import { MenuService } from '../menu.service';
import Editor from '../../../Editor';

describe('InsertCommandComponent', () => {
let component: InsertCommandComponent;
let fixture: ComponentFixture<InsertCommandComponent>;
let menuService: MenuService;
let editor: Editor;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
InsertCommandComponent,
SanitizeHtmlPipe,
],
providers: [MenuService],
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(InsertCommandComponent);
component = fixture.componentInstance;
menuService = fixture.debugElement.injector.get(MenuService);

editor = new Editor();
menuService.editor = editor;

fixture.detectChanges();
});

afterEach(() => {
editor.destroy();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Component, HostBinding, Input, OnDestroy, OnInit } from '@angular/core';
import { EditorView } from 'prosemirror-view';
import { Subscription } from 'rxjs';

import Icon from '../../../icons';
import { InsertCommands } from '../MenuCommands';
import { NgxEditorService } from '../../../editor.service';
import { MenuService } from '../menu.service';
import { TBItems, ToolbarItem } from '../../../types';

@Component({
selector: 'ngx-insert-command',
templateUrl: './insert-command.component.html',
styleUrls: ['./insert-command.component.scss'],
})

export class InsertCommandComponent implements OnInit, OnDestroy {
@Input() toolbarItem: ToolbarItem;

get name(): TBItems {
return this.toolbarItem as TBItems;
}

html: string;
editorView: EditorView;
private updateSubscription: Subscription;

constructor(
private ngxeService: NgxEditorService,
private menuService: MenuService,
) { }

@HostBinding('class.NgxEditor--Disabled') disabled = false;

insert(e: MouseEvent): void {
e.preventDefault();

if (e.button !== 0) {
return;
}

const { state, dispatch } = this.editorView;
const command = InsertCommands[this.name];
command.insert()(state, dispatch);
}

update = (view: EditorView): void => {
const { state } = view;
const command = InsertCommands[this.name];
this.disabled = !command.canExecute(state);
};

getTitle(name: string): string {
return this.ngxeService.locals.get(name);
}

ngOnInit(): void {
this.html = Icon.get(this.name);

this.editorView = this.menuService.editor.view;

this.updateSubscription = this.menuService.editor.update.subscribe((view: EditorView) => {
this.update(view);
});
}

ngOnDestroy(): void {
this.updateSubscription.unsubscribe();
}
}
3 changes: 3 additions & 0 deletions projects/ngx-editor/src/lib/modules/menu/menu.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<ngx-toggle-command [toolbarItem]="item" [class]="iconContainerClass" *ngIf="toggleCommands.includes(item)">
</ngx-toggle-command>

<ngx-insert-command [toolbarItem]="item" [class]="iconContainerClass" *ngIf="insertCommands.includes(item)">
</ngx-insert-command>

<!-- link -->
<ngx-link [class]="iconContainerClass" *ngIf="item === 'link'"></ngx-link>

Expand Down
18 changes: 17 additions & 1 deletion projects/ngx-editor/src/lib/modules/menu/menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ export const TOOLBAR_MINIMAL: Toolbar = [
['text_color', 'background_color'],
];

export const TOOLBAR_FULL: Toolbar = [
['bold', 'italic'],
['code', 'blockquote'],
['underline', 'strike'],
['ordered_list', 'bullet_list'],
[{ heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] }],
['link', 'image'],
['text_color', 'background_color'],
['align_left', 'align_center', 'align_right', 'align_justify'],
['horizontal_rule'],
];

const DEFAULT_COLOR_PRESETS = [
'#b60205',
'#d93f0b',
Expand Down Expand Up @@ -60,7 +72,7 @@ export class MenuComponent implements OnInit {
@Input() customMenuRef: TemplateRef<any> | null = null;
@Input() dropdownPlacement: 'top' | 'bottom' = 'bottom';

toggleCommands: any[] = [
toggleCommands: ToolbarItem[] = [
'bold',
'italic',
'underline',
Expand All @@ -75,6 +87,10 @@ export class MenuComponent implements OnInit {
'align_justify',
];

insertCommands: ToolbarItem [] = [
'horizontal_rule',
];

iconContainerClass = ['NgxEditor__MenuItem', 'NgxEditor__MenuItem--Icon'];
dropdownContainerClass = ['NgxEditor__Dropdown'];
seperatorClass = ['NgxEditor__Seperator'];
Expand Down
2 changes: 2 additions & 0 deletions projects/ngx-editor/src/lib/modules/menu/menu.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { FloatingMenuComponent } from './floating-menu/floating-menu.component';
import { BubbleComponent } from './bubble/bubble.component';

import { SanitizeHtmlPipe } from '../../pipes/sanitize/sanitize-html.pipe';
import { InsertCommandComponent } from './insert-command/insert-command.component';

@NgModule({
imports: [
Expand All @@ -25,6 +26,7 @@ import { SanitizeHtmlPipe } from '../../pipes/sanitize/sanitize-html.pipe';
// components
MenuComponent,
ToggleCommandComponent,
InsertCommandComponent,
LinkComponent,
DropdownComponent,
ImageComponent,
Expand Down
3 changes: 2 additions & 1 deletion projects/ngx-editor/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export type TBItems = 'bold'
| 'align_left'
| 'align_center'
| 'align_right'
| 'align_justify';
| 'align_justify'
| 'horizontal_rule';

export type ToolbarDropdown = { heading?: TBHeadingItems[] };
export type ToolbarCustomMenuItem = (editorView: EditorView) => TCR;
Expand Down

0 comments on commit 608e978

Please sign in to comment.