Skip to content

Commit

Permalink
feat(icons): add support for custom icons in menu (#455)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgeucano authored Feb 26, 2023
1 parent 5f72124 commit 5706bc4
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 76 deletions.
19 changes: 6 additions & 13 deletions docs/src/content/docs/en/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ Then in HTML
```html
<div class="NgxEditor__Wrapper">
<ngx-editor-menu [editor]="editor"> </ngx-editor-menu>
<ngx-editor
[editor]="editor"
[ngModel]="html"
[disabled]="false"
[placeholder]="'Type here...'"
></ngx-editor>
<ngx-editor [editor]="editor" [ngModel]="html" [disabled]="false" [placeholder]="'Type here...'"></ngx-editor>
</div>
```

Expand All @@ -89,19 +84,14 @@ const jsonDoc = toDoc(html);
### Commands

```ts
this.editor.commands
.textColor('red')
.insertText('Hello world!')
.focus()
.scrollIntoView()
.exec();
this.editor.commands.textColor('red').insertText('Hello world!').focus().scrollIntoView().exec();
```

Run `exec` to apply the changes to the editor.

### Optional Configuration

You can specify locals to be used in the editor
You can specify locals and icons to be used in the editor

```ts
NgxEditorModule.forRoot({
Expand All @@ -112,6 +102,9 @@ NgxEditorModule.forRoot({
underline: 'Underline',
// ...
},
icons: {
bold: '<img src="https://example.com/icon.png" width="15" height="15" alt="">',
},
});
```

Expand Down
51 changes: 39 additions & 12 deletions docs/src/content/docs/en/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,7 @@ export class AppComponent implements OnInit, OnDestroy {
**component.html**

```html
<ngx-editor-menu
[editor]="editor"
[toolbar]="toolbar"
[colorPresets]="colorPresets"
>
</ngx-editor-menu>
<ngx-editor-menu [editor]="editor" [toolbar]="toolbar" [colorPresets]="colorPresets"> </ngx-editor-menu>
```

## Custom Menu
Expand All @@ -62,12 +57,7 @@ Note: The input is just a `TemplateRef`, the menu component will render whatever
#### Editor

```html
<ngx-editor-menu
[editor]="editor"
[toolbar]="toolbar"
[customMenuRef]="customMenu"
>
</ngx-editor-menu>
<ngx-editor-menu [editor]="editor" [toolbar]="toolbar" [customMenuRef]="customMenu"> </ngx-editor-menu>

<!-- Create template reference -->
<ng-template #customMenu>
Expand Down Expand Up @@ -172,3 +162,40 @@ OR

- **editor** - (`Required`) editor instance
- **autoPlace** - (`Optional`) positions automatically to the top or bottom based on the space available. `false` by default

## Custom Icons in ngx-editor-menu

By default ngx-editor add a list of google font icons, but have an option for replace with the icons you need.
Support svg/img/tags/etc, have the option for modify one or more icons.
You can specify the icons in the forRoot

```ts
NgxEditorModule.forRoot({
icons: {
bold: '<img src="https://cdn-icons-png.flaticon.com/512/1827/1827924.png " width="15" height="15" alt="" title="" class="img-small">',
},
});
```

The list of icon you can change is:

- align_center
- align_justify
- align_left
- align_right
- bold
- bullet_list
- code
- color_fill
- format_clear
- horizontal_rule
- image
- italic
- link
- ordered_list
- quote
- strike
- text_color
- underline
- unlink
- path
43 changes: 43 additions & 0 deletions projects/ngx-editor/src/lib/Icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable @typescript-eslint/quotes */
export const defaults: Record<string, string> = {
// menu
align_center: `<path d="M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z"/>`,
align_justify: `<path d="M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zm0-6v2h18V3H3z"/>`,
align_left: `<path d="M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z"/>`,
align_right: `<path d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z"/>`,
bold: `<path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" />`,
bullet_list: `<path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/>`,
code: `<path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/>`,
color_fill: `<path d="M16.56,8.94L7.62,0L6.21,1.41l2.38,2.38L3.44,8.94c-0.59,0.59-0.59,1.54,0,2.12l5.5,5.5C9.23,16.85,9.62,17,10,17 s0.77-0.15,1.06-0.44l5.5-5.5C17.15,10.48,17.15,9.53,16.56,8.94z M5.21,10L10,5.21L14.79,10H5.21z M19,11.5c0,0-2,2.17-2,3.5 c0,1.1,0.9,2,2,2s2-0.9,2-2C21,13.67,19,11.5,19,11.5z M2,20h20v4H2V20z"/>`,
format_clear: `<path d="M0 0h24v24H0z" fill="none"/><path d="M3.27 5L2 6.27l6.97 6.97L6.5 19h3l1.57-3.66L16.73 21 18 19.73 3.55 5.27 3.27 5zM6 5v.18L8.82 8h2.4l-.72 1.68 2.1 2.1L14.21 8H20V5H6z"/>`,
horizontal_rule: `<g>
<rect fill="none" fill-rule="evenodd" height="24" width="24"/>
<rect fill-rule="evenodd" height="2" width="16" x="4" y="11"/>
</g>`,
image: `<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>`,
italic: `<path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z" />`,
link: `<path d="M0 0h24v24H0z" fill="none"/><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>`,
ordered_list: `<path d="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z"/>`,
quote: `<path d="M0 0h24v24H0z" fill="none"/><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/>`,
strike: `<path d="M6.85,7.08C6.85,4.37,9.45,3,12.24,3c1.64,0,3,0.49,3.9,1.28c0.77,0.65,1.46,1.73,1.46,3.24h-3.01 c0-0.31-0.05-0.59-0.15-0.85c-0.29-0.86-1.2-1.28-2.25-1.28c-1.86,0-2.34,1.02-2.34,1.7c0,0.48,0.25,0.88,0.74,1.21 C10.97,8.55,11.36,8.78,12,9H7.39C7.18,8.66,6.85,8.11,6.85,7.08z M21,12v-2H3v2h9.62c1.15,0.45,1.96,0.75,1.96,1.97 c0,1-0.81,1.67-2.28,1.67c-1.54,0-2.93-0.54-2.93-2.51H6.4c0,0.55,0.08,1.13,0.24,1.58c0.81,2.29,3.29,3.3,5.67,3.3 c2.27,0,5.3-0.89,5.3-4.05c0-0.3-0.01-1.16-0.48-1.94H21V12z"/>`,
text_color: `<path d="M2,20h20v4H2V20z M5.49,17h2.42l1.27-3.58h5.65L16.09,17h2.42L13.25,3h-2.5L5.49,17z M9.91,11.39l2.03-5.79h0.12l2.03,5.79 H9.91z"/>`,
underline: `<path d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"/>`,
unlink: `<path d="M17 7h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1 0 1.43-.98 2.63-2.31 2.98l1.46 1.46C20.88 15.61 22 13.95 22 12c0-2.76-2.24-5-5-5zm-1 4h-2.19l2 2H16zM2 4.27l3.11 3.11C3.29 8.12 2 9.91 2 12c0 2.76 2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1 0-1.59 1.21-2.9 2.76-3.07L8.73 11H8v2h2.73L13 15.27V17h1.73l4.01 4L20 19.74 3.27 3 2 4.27z"/>`,
path: `<path></path>`,
};

export type IconsKeys = keyof typeof defaults;

class Icons {
icons = defaults;

constructor(newIcons: Partial<Record<IconsKeys, string>> = {}) {
this.icons = { ...defaults, ...newIcons };
}

get = (key: string):string => {
return this.icons[key] ?? '';
};
}

export default Icons;
1 change: 1 addition & 0 deletions projects/ngx-editor/src/lib/editor-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import { Injectable } from '@angular/core';
})
export class NgxEditorServiceConfig {
public locals = {};
public icons = {};
}
2 changes: 2 additions & 0 deletions projects/ngx-editor/src/lib/editor.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';

import { NgxEditorConfig } from './types';
import { defaults as defaultLocals } from './Locals';
import { defaults as icons } from './Icons';

import { NgxEditorComponent } from './editor.component';
import { NgxEditorServiceConfig } from './editor-config.service';
Expand All @@ -17,6 +18,7 @@ export const NGX_EDITOR_CONFIG_TOKEN = new InjectionToken<NgxEditorConfig>('NgxE

const defaultConfig: NgxEditorConfig = {
locals: defaultLocals,
icons,
};

@NgModule({
Expand Down
6 changes: 6 additions & 0 deletions projects/ngx-editor/src/lib/editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Injectable, Optional } from '@angular/core';
import { NgxEditorConfig } from './types';
import Locals from './Locals';
import { NgxEditorServiceConfig } from './editor-config.service';
import Icon from './icons/index';

@Injectable({
providedIn: 'root',
Expand All @@ -17,10 +18,15 @@ export class NgxEditorService {
get locals(): Locals {
return new Locals(this.config.locals);
}

geticon(icon: string): string {
return this.config.icons[icon] ? this.config.icons[icon] : Icon.get(icon);
}
}

export const provideMyServiceOptions = (config?: NgxEditorConfig): NgxEditorServiceConfig => {
return {
locals: config.locals ?? {},
icons: config.icons ?? {},
};
};
45 changes: 45 additions & 0 deletions projects/ngx-editor/src/lib/icons.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { TestBed, ComponentFixture } from '@angular/core/testing';

import { NgxEditorModule } from 'ngx-editor';
import Editor from './Editor';
import { NgxEditorComponent } from './editor.component';

describe('NgxEditorModule', () => {
let component: NgxEditorComponent;
let fixture: ComponentFixture<NgxEditorComponent>;

beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
NgxEditorComponent,
],
imports: [
NgxEditorModule.forRoot({
icons: {
bold: '<img src="https://cdn-icons-png.flaticon.com/512/1827/1827924.png" id="iconBold" width="15" height="15" alt="" title="" class="img-small">',
},
}),
],
});
await TestBed.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(NgxEditorComponent);
component = fixture.componentInstance;
component.editor = new Editor();
fixture.detectChanges();
});

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

it('should create the editor component correctly', () => {
expect(component).toBeTruthy();
});
it('should create the icon correctly', () => {
const icon = document.getElementsByClassName('img-small');
expect(icon).toBeTruthy();
});
});
92 changes: 51 additions & 41 deletions projects/ngx-editor/src/lib/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
/* eslint-disable @typescript-eslint/naming-convention */
// Icons source: https://material.io/

import bold from './bold';
import italic from './italic';
import code from './code';
import underline from './underline';
import strike from './strike';
import orderedList from './ordered_list';
import bulletList from './bullet_list';
import quote from './quote';
import link from './link';
import unlink from './unlink';
import image from './image';
import alignLeft from './align_left';
import alignCenter from './align_center';
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';
import formatClear from './format_clear';
import Icons from '../Icons';

const ICON_LIST = new Icons();

const bold = ICON_LIST.get('bold');
const italic = ICON_LIST.get('italic');
const code = ICON_LIST.get('code');
const underline = ICON_LIST.get('underline');
const strike = ICON_LIST.get('strike');
const ordered_list = ICON_LIST.get('ordered_list');
const bullet_list = ICON_LIST.get('bullet_list');
const quote = ICON_LIST.get('quote');
const link = ICON_LIST.get('link');
const unlink = ICON_LIST.get('unlink');
const image = ICON_LIST.get('image');
const align_left = ICON_LIST.get('align_left');
const align_center = ICON_LIST.get('align_center');
const align_right = ICON_LIST.get('align_right');
const align_justify = ICON_LIST.get('align_justify');
const text_color = ICON_LIST.get('text_color');
const color_fill = ICON_LIST.get('color_fill');
const horizontal_rule = ICON_LIST.get('horizontal_rule');
const format_clear = ICON_LIST.get('format_clear');
const path = ICON_LIST.get('path');

const DEFAULT_ICON_HEIGHT = 20;
const DEFAULT_ICON_WIDTH = 20;
Expand All @@ -30,36 +36,40 @@ const icons: Record<string, any> = {
code,
underline,
strike,
ordered_list: orderedList,
bullet_list: bulletList,
ordered_list,
bullet_list,
blockquote: quote,
link,
unlink,
image,
align_left: alignLeft,
align_center: alignCenter,
align_right: alignRight,
align_justify: alignJustify,
text_color: textColor,
color_fill: colorFill,
horizontal_rule: horizontalRule,
format_clear: formatClear,
align_left,
align_center,
align_right,
align_justify,
text_color,
color_fill,
horizontal_rule,
format_clear,
path,
};

class Icon {
static get(name: keyof typeof icons, fill = DEFAULT_ICON_FILL): string {
const path = icons[name] || '<path></path>';
return `
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill=${fill}
height=${DEFAULT_ICON_HEIGHT}
width=${DEFAULT_ICON_WIDTH}
>
${path}
</svg>
`;
const fullPath = icons[name];
if (fullPath && (fullPath.includes('<path') || fullPath.includes('<g'))) {
return `--
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill=${fill}
height=${DEFAULT_ICON_HEIGHT}
width=${DEFAULT_ICON_WIDTH}
>
${fullPath}
</svg>
`;
}
return fullPath;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { EditorView } from 'prosemirror-view';
import { Subscription } from 'rxjs';

import Editor from '../../../Editor';
import Icon from '../../../icons';
import { TBItems } from '../../../types';
import { SanitizeHtmlPipe } from '../../../pipes/sanitize/sanitize-html.pipe';
import { ToggleCommands } from '../MenuCommands';
Expand Down Expand Up @@ -50,8 +49,7 @@ export class BubbleComponent implements OnInit, OnDestroy {
];

getIcon(name: TBItems): SafeHtml {
const icon = Icon.get(name);
return this.sanitizeHTML.transform(icon);
return this.sanitizeHTML.transform(this.ngxeService.geticon(name));
}

getTitle(name: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
import { EditorView } from 'prosemirror-view';
import { Subscription } from 'rxjs';

import Icon from '../../../icons';
import Icon from '../../../icons/index';
import { NgxEditorService } from '../../../editor.service';
import { MenuService } from '../menu.service';
import { TextColor, TextBackgroundColor } from '../MenuCommands';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Subscription } from 'rxjs';

import { NgxEditorService } from '../../../editor.service';
import { MenuService } from '../menu.service';
import Icon from '../../../icons';
import Icon from '../../../icons/index';
import { Image as ImageCommand } from '../MenuCommands';

@Component({
Expand Down
Loading

0 comments on commit 5706bc4

Please sign in to comment.