/* * This file is part of ORY Editor. * * ORY Editor is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ORY Editor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ORY Editor. If not, see . * * @license LGPL-3.0 * @copyright 2016-2018 Aeneas Rekkas * @author Aeneas Rekkas * */ import { List } from 'immutable'; import head from 'ramda/src/head'; import map from 'ramda/src/map'; import path from 'ramda/src/path'; import reduce from 'ramda/src/reduce'; import tail from 'ramda/src/tail'; import React from 'react'; import AlignmentPlugin from './plugins/alignment'; import BlockquotePlugin from './plugins/blockquote'; import CodePlugin from './plugins/code/index'; import EmphasizePlugin from './plugins/emphasize'; import HeadingsPlugin from './plugins/headings'; import LinkPlugin from './plugins/link/index'; import ListsPlugin from './plugins/lists'; import ParagraphPlugin, { P } from './plugins/paragraph/index'; import parse5 from 'parse5'; import Plugin from './plugins/Plugin'; // FIXME #126 import { Document, Value, BlockJSON, ValueJSON } from 'slate'; import Html from 'slate-html-serializer'; import Plain from 'slate-plain-serializer'; import { SlateState } from './types/state'; import { AbstractCell } from 'ory-editor-core/lib/types/editable'; const DEFAULT_NODE = P; export const defaultPlugins: Plugin[] = [ new ParagraphPlugin(), new EmphasizePlugin(), new HeadingsPlugin({ DEFAULT_NODE }), new LinkPlugin(), new CodePlugin({ DEFAULT_NODE }), new ListsPlugin({ DEFAULT_NODE }), new BlockquotePlugin({ DEFAULT_NODE }), new AlignmentPlugin({ DEFAULT_NODE }), ]; export const lineBreakSerializer = { // tslint:disable-next-line:no-any deserialize(el: any) { if (el.tagName.toLowerCase() === 'br') { return { object: 'text', text: '\n' }; } if (el.nodeName === '#text') { if (el.value && el.value.match(//)) { return; } return { object: 'text', leaves: [ { object: 'leaf', text: el.value, }, ], }; } }, // tslint:disable-next-line:no-any serialize(object: any, children: string) { if (object.type === 'text' || children === '\n') { return
; } }, }; export const html = new Html({ rules: [...defaultPlugins, lineBreakSerializer], parseHtml: parse5.parseFragment, }); export const createInitialState = () => ({ editorState: Value.fromJSON({ document: { nodes: [ { object: 'block', type: P, nodes: [ { object: 'text', leaves: [ { text: '', }, ], }, ], } as BlockJSON, ], }, }), }); export const unserialize = ({ importFromHtml, serialized, editorState, }: // tslint:disable-next-line:no-any SlateState): SlateState => { if (serialized) { // tslint:disable-next-line:no-any return { editorState: (Value.fromJSON as any)(serialized) }; } else if (importFromHtml) { return { editorState: html.deserialize(importFromHtml) }; } else if (editorState) { return { editorState }; } return createInitialState(); }; // tslint:disable-next-line:no-any export const serialize = ({ editorState, }: SlateState): { serialized: ValueJSON } => ({ // tslint:disable-next-line:no-any serialized: (editorState.toJSON as any)(editorState), }); export const merge = (states: Object[]): Object => { const nodes = map(path(['editorState', 'document', 'nodes']), states); const mergedNodes = reduce( // tslint:disable-next-line:no-any (a: List, b: List) => a.concat(b), head(nodes), tail(nodes) ); const mergedDocument = Document.create({ nodes: mergedNodes }); const mergedEditorState = Value.create({ document: mergedDocument }); return { editorState: mergedEditorState }; }; export const split = (state: Object): Object[] => { const nodes = path(['editorState', 'document', 'nodes'], state); return nodes ? nodes.toArray().map(node => { const splittedDocument = Document.create({ nodes: List([node]) }); const splittedEditorState = Value.create({ document: splittedDocument, }); return { editorState: splittedEditorState }; }) : []; }; // const position = (): { // top: ?number, // right: ?number, // left: ?number, // bottom: ?number // } => { // if (window && window.getSelection) { // const selection = window.getSelection() // if (!selection.rangeCount) { // return { // top: null, // right: null, // left: null, // bottom: null, // } // } // // return selection.getRangeAt(0).getBoundingClientRect() // } // // if (window.document.selection) { // return window.document.selection // .createRange() // .getBoundingClientRect() // } // // return { // top: null, // right: null, // left: null, // bottom: null, // } // } // if editor state is empty, remove cell when backspace or delete was pressed. export const handleRemoveHotKey = ( _: Event, { content: { state: { editorState }, }, }: AbstractCell ): Promise => new Promise((resolve: Function, reject: Function) => Plain.serialize(editorState).length < 1 ? resolve() : reject() ); const windowSelectionWaitTime = 1; export const handleFocusPreviousHotKey = ( e: KeyboardEvent, { content: { state: { editorState }, }, }: AbstractCell ): Promise => { // const isArrowUp = e.keyCode === 38 return new Promise((resolve: Function, reject: Function) => { if (editorState.selection.isExpanded) { return reject(); } setTimeout(() => { // if (isArrowUp && next.top === current.top) { // return resolve() // } else if ( editorState.selection.isCollapsed && editorState.selection.anchor.isAtStartOfNode( editorState.document.nodes.first() ) ) { return resolve(); } reject(); }, windowSelectionWaitTime); }); }; export const handleFocusNextHotKey = ( e: KeyboardEvent, { content: { state: { editorState }, }, }: AbstractCell ): Promise => { // const isArrowDown = e.keyCode === 40 return new Promise((resolve: Function, reject: Function) => { if (editorState.selection.isExpanded) { return reject(); } setTimeout(() => { // if (isArrowDown && next.top === current.top) { // return resolve() // } else if ( editorState.selection.isCollapsed && editorState.selection.anchor.isAtEndOfNode( editorState.document.nodes.last() ) ) { return resolve(); } reject(); }, windowSelectionWaitTime); }); };