Skip to content

Commit

Permalink
feat: add text and background color picker
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed Dec 24, 2020
1 parent 506523f commit 9c38412
Show file tree
Hide file tree
Showing 29 changed files with 528 additions and 16 deletions.
1 change: 1 addition & 0 deletions demo/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { CustomMenuComponent } from './components/custom-menu/custom-menu.compon
['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'],
]
}),
Expand Down
36 changes: 34 additions & 2 deletions demo/src/app/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,23 @@ export default {
content: [
{
type: 'text',
text: 'This is editable text. You can focus it and start typing.'
text: 'This is editable text. '
},
{
type: 'text',
marks: [
{
type: 'text_color',
attrs: {
color: '#d93f0b'
}
}
],
text: 'You can focus it and start typing'
},
{
type: 'text',
text: '.'
}
]
},
Expand Down Expand Up @@ -63,7 +79,23 @@ export default {
},
{
type: 'text',
text: ' is simply dummy text of the printing and typesetting industry. '
text: ' is '
},
{
type: 'text',
marks: [
{
type: 'text_background_color',
attrs: {
backgroundColor: '#fbca04'
}
}
],
text: 'simply dummy'
},
{
type: 'text',
text: ' text of the printing and typesetting industry. '
},
{
type: 'text',
Expand Down
4 changes: 4 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ NgxEditorModule.forRoot({
['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'],
],
locals: {
Expand All @@ -34,6 +35,8 @@ NgxEditorModule.forRoot({
align_center: 'Center Align',
align_right: 'Right Align',
align_justify: 'Justify',
text_color: 'Text Color',
background_color: 'Background Color',

// pupups, forms, others...
url: 'URL',
Expand All @@ -42,6 +45,7 @@ NgxEditorModule.forRoot({
insert: 'Insert',
altText: 'Alt Text',
title: 'Title',
remove: 'Remove',
},
nodeViews: {}, // optional, for example see https://prosemirror.net/examples/footnote/
});
Expand Down
4 changes: 2 additions & 2 deletions docs/doc-html-doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
```ts
import { toHTML } from 'ngx-editor';

const html = toHTML(this.jsonDoc) // -> html string
const html = toHTML(this.jsonDoc); // -> html string

// schema is optional, use it if you modified the default schema
const html = toHTML(this.jsonDoc, schema) // -> html string
const html = toHTML(this.jsonDoc, schema); // -> html string
```

## Generating JSON from HTML
Expand Down
4 changes: 4 additions & 0 deletions docs/full-featured-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import plugins from './plugins';
['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'],
],
locals: {
Expand All @@ -119,6 +120,8 @@ import plugins from './plugins';
align_center: 'Center Align',
align_right: 'Right Align',
align_justify: 'Justify',
text_color: 'Text Color',
background_color: 'Background Color',

// pupups, forms, others...
url: 'URL',
Expand All @@ -127,6 +130,7 @@ import plugins from './plugins';
insert: 'Insert',
altText: 'Alt Text',
title: 'Title',
remove: 'Remove',
},
}),
],
Expand Down
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"
/>
<link rel="icon" href="favicon_64x64.ico" type="image/gif" sizes="64x64">
<link rel="icon" href="favicon_64x64.ico" type="image/gif" sizes="64x64" />
</head>

<body>
Expand Down
4 changes: 4 additions & 0 deletions docs/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ NgxEditorModule.forRoot({
['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'],
],
locals: {
Expand All @@ -29,6 +30,8 @@ NgxEditorModule.forRoot({
align_center: 'Center Align',
align_right: 'Right Align',
align_justify: 'Justify',
text_color: 'Text Color',
background_color: 'Background Color',

// pupups, forms, others...
url: 'URL',
Expand All @@ -37,6 +40,7 @@ NgxEditorModule.forRoot({
insert: 'Insert',
altText: 'Alt Text',
title: 'Title',
remove: 'Remove',
},
});
```
Expand Down
53 changes: 53 additions & 0 deletions src/commands/applyMark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { MarkType, Node as PrmosemirroNode } from 'prosemirror-model';
import { EditorState, SelectionRange, TextSelection, Transaction } from 'prosemirror-state';

// Ref: https://github.com/ProseMirror/prosemirror-commands/blob/master/src/commands.js

function markApplies(doc: PrmosemirroNode, ranges: SelectionRange[], type: MarkType): boolean {
for (const range of ranges) {
const { $from, $to } = range;

let canApply = $from.depth === 0 ? doc.type.allowsMarkType(type) : false;

doc.nodesBetween($from.pos, $to.pos, (node: PrmosemirroNode): boolean => {
if (canApply) {
return false;
}

canApply = node.inlineContent && node.type.allowsMarkType(type);
return true;
});

if (canApply) {
return true;
}
}
return false;
}

export const applyMark = (type: MarkType, attrs: { [key: string]: any } = {}) => {
return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {
const { tr, selection } = state;
const { $from, $to, empty, ranges } = selection;

if (empty && selection instanceof TextSelection) {
const { $cursor } = selection;

if (!$cursor || !markApplies(state.doc, ranges, type)) {
return false;
}


tr.addStoredMark(type.create(attrs));
} else {
tr.addMark($from.pos, $to.pos, type.create(attrs));

if (!tr.docChanged) {
return false;
}
}

dispatch?.(tr.scrollIntoView());
return true;
};
};
1 change: 1 addition & 0 deletions src/commands/public_api.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './removeLink';
export * from './applyMark';
12 changes: 8 additions & 4 deletions src/helpers/getSelectionMarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { Mark } from 'prosemirror-model';
export const getSelectionMarks = (state: EditorState): Mark[] => {
let marks: Mark[] = [];

const { selection: { from, to } } = state;
const { selection: { from, to, empty, $from }, storedMarks } = state;

state.doc.nodesBetween(from, to, node => {
marks = [...marks, ...node.marks];
});
if (empty) {
marks = storedMarks || $from.marks();
} else {
state.doc.nodesBetween(from, to, node => {
marks = [...marks, ...node.marks];
});
}

return marks;
};
Expand Down
5 changes: 4 additions & 1 deletion src/lib/Locals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ const defaults = {
align_center: 'Center Align',
align_right: 'Right Align',
align_justify: 'Justify',
text_color: 'Text Color',
background_color: 'Background Color',

// pupups, forms, others...
url: 'URL',
text: 'Text',
openInNewTab: 'Open in new tab',
insert: 'Insert',
altText: 'Alt Text',
title: 'Title'
title: 'Title',
remove: 'Remove',
};

export type LocalsKeys = keyof typeof defaults;
Expand Down
99 changes: 99 additions & 0 deletions src/lib/commands/TextColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { MarkType } from 'prosemirror-model';
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';

import { getSelectionMarks, isMarkActive } from 'ngx-editor/helpers';
import { applyMark } from 'ngx-editor/commands';

import { Dispatch } from './types';

type Execute = (state: EditorState, dispatch?: Dispatch) => boolean;

type Name = 'text_color' | 'text_background_color';

class TextColor {
name: Name;

constructor(name: Name) {
this.name = name;
}

execute(attrs: {}): Execute {
return (state: EditorState, dispatch?: Dispatch): boolean => {
const { schema, selection, doc } = state;

const type: MarkType = schema.marks[this.name];
if (!type) {
return false;
}

const { from, empty } = selection;
if (!empty) {
const node = doc.nodeAt(from);
if (node.isAtom && !node.isText && node.isLeaf) {
// An atomic node (e.g. Image) is selected.
return false;
}
}

return applyMark(type, attrs)(state, dispatch);
};
}

isActive(state: EditorState): boolean {
const { schema } = state;
const type: MarkType = schema.marks[this.name];

if (!type) {
return false;
}

return isMarkActive(state, type);
}

getActiveColors(state: EditorState): string[] {
if (!this.isActive(state)) {
return [];
}

const { schema } = state;
const marks = getSelectionMarks(state);

const colors = marks
.filter(mark => mark.type === schema.marks[this.name])
.map(mark => mark.attrs.color)
.filter(Boolean);

return colors;
}

remove(state: EditorState, dispatch: Dispatch): boolean {
const { tr } = state;
const { selection, schema } = state;

const { empty, from, to } = selection;

const type = schema.marks[this.name];
if (!type) {
return false;
}

if (empty) {
tr.removeStoredMark(type);
} else {
tr.removeMark(from, to, type);

if (!tr.docChanged) {
return false;
}
}

dispatch(tr.scrollIntoView());
return true;
}

canExecute(state: EditorState): boolean {
return this.execute({})(state, null);
}
}

export default TextColor;
3 changes: 3 additions & 0 deletions src/lib/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Heading from './Heading';
import TextAlign from './TextAlign';
import Link from './Link';
import Image from './Image';
import TextColor from './TextColor';

export const STRONG = new MarkToggle('strong');
export const EM = new MarkToggle('em');
Expand All @@ -24,3 +25,5 @@ export const ALIGN_RIGHT = new TextAlign('right');
export const ALIGN_JUSTIFY = new TextAlign('justify');
export const LINK = new Link();
export const IMAGE = new Image();
export const TEXT_COLOR = new TextColor('text_color');
export const TEXT_BACKGROUND_COLOR = new TextColor('text_background_color');
1 change: 1 addition & 0 deletions src/lib/editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const DEFAULT_MENU: Toolbar = [
['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'],
];

Expand Down
3 changes: 3 additions & 0 deletions src/lib/icons/color_fill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default `
<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"/>
`;
6 changes: 5 additions & 1 deletion src/lib/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ 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';

const DEFAULT_ICON_HEIGHT = 20;
const DEFAULT_ICON_WIDTH = 20;
Expand All @@ -30,7 +32,9 @@ const icons = {
align_left: alignLeft,
align_center: alignCenter,
align_right: alignRight,
align_justify: alignJustify
align_justify: alignJustify,
text_color: textColor,
color_fill: colorFill
};

class Icon {
Expand Down
Loading

0 comments on commit 9c38412

Please sign in to comment.