Skip to content

Commit

Permalink
feat: add support for list
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed May 4, 2020
1 parent d4fd2d2 commit 274dcc7
Show file tree
Hide file tree
Showing 29 changed files with 425 additions and 151 deletions.
5 changes: 4 additions & 1 deletion demo/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.container {
width: 900px;
width: 100%;
max-width: 900px;
min-width: 320px;
margin: auto;
display: flex;
align-items: center;
Expand All @@ -18,6 +20,7 @@
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}

.badges {
Expand Down
40 changes: 40 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@types/prosemirror-keymap": "^1.0.1",
"@types/prosemirror-model": "^1.7.2",
"@types/prosemirror-schema-basic": "^1.0.1",
"@types/prosemirror-schema-list": "^1.0.1",
"@types/prosemirror-state": "^1.2.3",
"@types/prosemirror-view": "^1.11.4",
"chalk": "^4.0.0",
Expand All @@ -77,8 +78,12 @@
"prosemirror-commands": "^1.1.4",
"prosemirror-history": "^1.1.3",
"prosemirror-keymap": "^1.1.3",
"prosemirror-model": "^1.9.1",
"prosemirror-schema-basic": "^1.1.2",
"prosemirror-schema-list": "^1.1.2",
"prosemirror-state": "^1.3.3",
"prosemirror-tables": "^1.1.0",
"prosemirror-utils": "^0.9.6",
"prosemirror-view": "^1.14.7",
"protractor": "~5.4.3",
"ts-node": "~8.3.0",
Expand Down
1 change: 1 addition & 0 deletions src/lib/ngx-editor.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ $icon-size: 30px;

.NgxEditor-Content {
padding: .5rem;
white-space: pre-wrap;

p {
margin: 0;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/ngx-editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { EditorState, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Node as ProsemirrorNode } from 'prosemirror-model';
import { schema } from 'prosemirror-schema-basic';

import schema from './schema';

import { Config, ComputedOptions } from './types';

Expand Down
69 changes: 20 additions & 49 deletions src/lib/plugins/menu/MenuBarView.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,41 @@
import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
import { MarkType, Mark } from 'prosemirror-model';

import { MenuItem, Toolbar } from '../../types';
import { MENU_WRAPPER_CLASSNAME, MENU_ITEM_CLASSNAME } from './constants';
import getMenu from './getMenu';
import { Toolbar } from '../../types';

import MenuItems from './MenuItems';

class MenuBarView {
menu: MenuItem[];
editorView: EditorView;
toolbar: Toolbar;
view: EditorView;

dom: HTMLElement;

constructor(toolbar: Toolbar, editorView: EditorView) {
const menu = getMenu(toolbar);
this.editorView = editorView;

// remove elements without commands
this.menu = menu.filter(menuItem => menuItem.command);

this.dom = document.createElement('div');
this.dom.className = MENU_WRAPPER_CLASSNAME;
updateMenuItems: (state: EditorState) => void;

menu.forEach(({ dom }) => this.dom.appendChild(dom));
constructor(toolbar: Toolbar, editorView: EditorView) {
// const menu = getMenu(toolbar);
this.view = editorView;
this.toolbar = toolbar;

this.render();
this.update();

this.dom.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault();
editorView.focus();

this.menu.forEach(({ command, dom }) => {
if (dom.contains(e.target as HTMLElement)) {
command(editorView.state, editorView.dispatch, editorView);
}
});
});
}

getIsElementActive(state: EditorState, type: MarkType): boolean | Mark {
const { from, $from, to, empty } = state.selection;
render() {
const menuDom = document.createElement('div');
menuDom.className = 'NgxEditor-MenuBar';

if (empty) {
return type.isInSet(state.storedMarks || $from.marks());
} else {
return state.doc.rangeHasMark(from, to, type);
}
}

update() {
this.menu.forEach(({ command, dom, key }) => {
const canShowItem = command(this.editorView.state, null, this.editorView);
const state = this.editorView.state;
const menuItems = new MenuItems(this.toolbar, this.view, menuDom);
menuItems.render();

const markType = state.schema.marks[key];
this.updateMenuItems = menuItems.update.bind(menuItems);

const isActive = !!this.getIsElementActive(state, markType);

dom.style.display = canShowItem ? '' : 'none';

dom.classList.toggle(`${MENU_ITEM_CLASSNAME}__active`, isActive);
});
this.view.dom.parentNode.insertBefore(menuDom, this.view.dom);
}

destroy() {
this.dom.remove();
update() {
this.updateMenuItems(this.view.state);
}
}

Expand Down
139 changes: 139 additions & 0 deletions src/lib/plugins/menu/MenuItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { toggleMark } from 'prosemirror-commands';
import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
import { MarkType, NodeType } from 'prosemirror-model';

import schema from '../../schema';

import isNodeActive from '../../pm-tools/helpers/isNodeActive';
import isMarkActive from '../../pm-tools/helpers/isMarkActive';
import toggleList from '../../pm-tools/commands/toggleList';

import { getIconSvg } from '../../utils/icons';
import { Toolbar, MenuItemMeta } from '../../types';
import menuItemsMeta from './meta';

const MENU_ITEM_CLASSNAME = 'NgxEditor-MenuItem';

const isListItem = (type: NodeType) => {
return (
type === schema.nodes.list_item ||
type === schema.nodes.ordered_list ||
type === schema.nodes.bullet_list
);
};

class MenuItemView {
menuItem: MenuItemMeta;
dom: HTMLElement;
editorView: EditorView;

constructor(menuItem: MenuItemMeta, editorView: EditorView) {
this.menuItem = menuItem;
this.editorView = editorView;
this.dom = this.getDom();
this.setupEventListeners();
}

getDom(): HTMLElement {
const div = document.createElement('div');

div.classList.add(MENU_ITEM_CLASSNAME);
div.classList.add(`${MENU_ITEM_CLASSNAME}__${name}`);
div.title = name;
div.innerHTML = getIconSvg(this.menuItem.icon);

return div;
}

private setupEventListeners() {
this.dom.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault();

if (this.menuItem.type === 'mark') {
const command = toggleMark(schema.marks[this.menuItem.key]);
command(this.editorView.state, this.editorView.dispatch);
}

if (this.menuItem.type === 'node') {
const type = schema.nodes[this.menuItem.key];

if (isListItem(type)) {
const command = toggleList(type, schema.nodes.list_item);
command(this.editorView.state, this.editorView.dispatch);
}
}
});
}

update(state: EditorState): void {
const menuItem = this.menuItem;
let isActive = false;

if (menuItem.type === 'mark') {
const type: MarkType = schema.marks[menuItem.key];
isActive = isMarkActive(state, type);
}

if (menuItem.type === 'node') {
const type: NodeType = schema.nodes[menuItem.key];
isActive = isNodeActive(state, type);
}

this.dom.classList.toggle(`${MENU_ITEM_CLASSNAME}__active`, isActive);
}
}

function getSeperatorDom(): HTMLElement {
const div = document.createElement('div');
div.className = `${MENU_ITEM_CLASSNAME}__Seperator`;
return div;
}

class MenuItems {
private toolbar: Toolbar;
private menuDom: HTMLElement;
private editorView: EditorView;

updates = [];

constructor(toolbar: Toolbar, editorView: EditorView, menuDom: HTMLElement) {
this.toolbar = toolbar;
this.editorView = editorView;
this.menuDom = menuDom;
}

render() {
this.toolbar.forEach((group, toolbarIndex) => {
const isLastMenuGroup = this.toolbar.length - 1 === toolbarIndex;

group.forEach((menuName, menuIndex) => {
const isLastMenuItem = group.length - 1 === menuIndex;

const menuItem = menuItemsMeta[menuName];

if (menuItem) {
const menuItemView = new MenuItemView(menuItem, this.editorView);
const update = menuItemView.update.bind(menuItemView);

this.menuDom.appendChild(menuItemView.dom);
this.updates.push(update);

if (isLastMenuItem && !isLastMenuGroup) {
const seperatorDom = getSeperatorDom();
this.menuDom.appendChild(seperatorDom);
}
}
});
});
}

update(state: EditorState) {
this.updates.forEach(update => {
update(state);
});
}
}


export default MenuItems;
2 changes: 0 additions & 2 deletions src/lib/plugins/menu/constants.ts

This file was deleted.

Loading

0 comments on commit 274dcc7

Please sign in to comment.