Skip to content

Commit

Permalink
Fix #74 (#236)
Browse files Browse the repository at this point in the history
* Fix #74

* Fix #74

* address comments
  • Loading branch information
JiuqingSong authored Feb 15, 2019
1 parent b1848b7 commit 9a7e983
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 15 deletions.
32 changes: 22 additions & 10 deletions packages/roosterjs-editor-core/lib/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ export default class Editor {
this.core.api.attachDomEvent(this.core, 'keydown', PluginEventType.KeyDown),
this.core.api.attachDomEvent(this.core, 'keyup', PluginEventType.KeyUp),
this.core.api.attachDomEvent(this.core, 'mousedown', PluginEventType.MouseDown),
this.core.api.attachDomEvent(this.core,
this.core.api.attachDomEvent(
this.core,
!Browser.isIE ? 'input' : 'textinput',
PluginEventType.Input
),
Expand All @@ -93,15 +94,25 @@ export default class Editor {
this.contenteditableChanged = true;
}

// 8. Disable these operations for firefox since its behavior is usually wrong
// 8. Do proper change for browsers to disable some browser-specified behaviors.
// Catch any possible exception since this should not block the initialization of editor
try {
this.core.document.execCommand(DocumentCommand.EnableObjectResizing, false, <string>(
(<any>false)
));
this.core.document.execCommand(DocumentCommand.EnableInlineTableEditing, false, <
string
>(<any>false));
// Disable these object resizing for firefox since other browsers don't have these behaviors
if (Browser.isFirefox) {
this.core.document.execCommand(DocumentCommand.EnableObjectResizing, false, <
string
>(<any>false));
this.core.document.execCommand(DocumentCommand.EnableInlineTableEditing, false, <
string
>(<any>false));
} else if (Browser.isIE) {
// Change the default paragraph separater to DIV. This is mainly for IE since its default setting is P
this.core.document.execCommand(
DocumentCommand.DefaultParagraphSeparator,
false,
'div'
);
}
} catch (e) {}

// 9. Let plugins know that we are ready
Expand Down Expand Up @@ -762,9 +773,10 @@ export default class Editor {

/**
* Get a content traverser for the whole editor
* @param startNode The node to start from. If not passed, it will start from the beginning of the body
*/
public getBodyTraverser(): ContentTraverser {
return ContentTraverser.createBodyTraverser(this.core.contentDiv);
public getBodyTraverser(startNode?: Node): ContentTraverser {
return ContentTraverser.createBodyTraverser(this.core.contentDiv, startNode);
}

/**
Expand Down
17 changes: 14 additions & 3 deletions packages/roosterjs-editor-dom/lib/contentTraverser/BodyScoper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import contains from '../utils/contains';
import getBlockElementAtNode from '../blockElements/getBlockElementAtNode';
import getInlineElementAtNode from '../inlineElements/getInlineElementAtNode';
import TraversingScoper from './TraversingScoper';
import { BlockElement, InlineElement } from 'roosterjs-editor-types';
import { getFirstBlockElement } from '../blockElements/getFirstLastBlockElement';
Expand All @@ -8,24 +10,33 @@ import { getFirstInlineElement } from '../inlineElements/getFirstLastInlineEleme
* provides scoper for traversing the entire editor body starting from the beginning
*/
class BodyScoper implements TraversingScoper {
private startNode: Node;

/**
* Construct a new instance of BodyScoper class
* @param rootNode Root node of the body
* @param startNode The node to start from. If not passed, it will start from the beginning of the body
*/
constructor(public rootNode: Node) {}
constructor(public rootNode: Node, startNode?: Node) {
this.startNode = contains(rootNode, startNode) ? startNode : null;
}

/**
* Get the start block element
*/
public getStartBlockElement(): BlockElement {
return getFirstBlockElement(this.rootNode);
return this.startNode
? getBlockElementAtNode(this.rootNode, this.startNode)
: getFirstBlockElement(this.rootNode);
}

/**
* Get the start inline element
*/
public getStartInlineElement(): InlineElement {
return getFirstInlineElement(this.rootNode);
return this.startNode
? getInlineElementAtNode(this.rootNode, this.startNode)
: getFirstInlineElement(this.rootNode);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ export default class ContentTraverser {
/**
* Create a content traverser for the whole body of given root node
* @param rootNode The root node to traverse in
* @param startNode The node to start from. If not passed, it will start from the beginning of the body
*/
public static createBodyTraverser(rootNode: Node): ContentTraverser {
return new ContentTraverser(new BodyScoper(rootNode));
public static createBodyTraverser(rootNode: Node, startNode?: Node): ContentTraverser {
return new ContentTraverser(new BodyScoper(rootNode, startNode));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ContentEditFeatures, { getDefaultContentEditFeatures } from './ContentEdi
import { AutoLink, UnlinkWhenBackspaceAfterLink } from './features/autoLinkFeatures';
import { DefaultShortcut } from './features/shortcutFeatures';
import { Editor, EditorPlugin, GenericContentEditFeature } from 'roosterjs-editor-core';
import { InsertLineBeforeStructuredNodeFeature } from './features/insertLineBeforeStructuredNodeFeature';
import { PluginEvent } from 'roosterjs-editor-types';
import { TabInTable, UpDownInTable } from './features/tableFeatures';

Expand Down Expand Up @@ -77,6 +78,7 @@ export default class ContentEdit implements EditorPlugin {
unquoteWhenEnterOnEmptyLine: UnquoteWhenEnterOnEmptyLine,
tabInTable: TabInTable,
upDownInTable: UpDownInTable,
insertLineBeforeStructuredNodeFeature: InsertLineBeforeStructuredNodeFeature,
autoBullet: AutoBullet,
autoLink: AutoLink,
unlinkWhenBackspaceAfterLink: UnlinkWhenBackspaceAfterLink,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ export default interface ContentEditFeatures {
*/
upDownInTable: boolean;

/**
* When press Enter at the beginning of first structured element (table, list) and there isn't line before the position
* we create a new line before so that user got a chance to enter content before the table or list
* @default false
*/
insertLineBeforeStructuredNodeFeature: boolean;

/**
* When press Space or Enter after a hyperlink-like string, convert the string to a hyperlink
* @default true
Expand Down Expand Up @@ -112,6 +119,7 @@ export function getDefaultContentEditFeatures(): ContentEditFeatures {
autoBullet: true,
tabInTable: true,
upDownInTable: Browser.isChrome || Browser.isSafari,
insertLineBeforeStructuredNodeFeature: false,
defaultShortcut: true,
unlinkWhenBackspaceAfterLink: false,
smartOrderedList: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { cacheGetEventData, ContentEditFeature, Editor, Keys } from 'roosterjs-editor-core';
import { PluginKeyboardEvent, PositionType } from 'roosterjs-editor-types';
import {
Browser,
fromHtml,
isPositionAtBeginningOf,
Position,
getTagOfNode,
} from 'roosterjs-editor-dom';

// Edge can sometimes lose current format when Enter to new line.
// So here we add an extra SPAN for Edge to workaround this bug
const NEWLINE_HTML = Browser.isEdge ? '<div><span><br></span></div>' : '<div><br></div>';
const CHILD_PARENT_TAG_MAP: { [childTag: string]: string } = {
TD: 'TABLE',
TH: 'TABLE',
LI: 'OL,UL',
};
const CHILD_SELECTOR = Object.keys(CHILD_PARENT_TAG_MAP).join(',');

export const InsertLineBeforeStructuredNodeFeature: ContentEditFeature = {
keys: [Keys.ENTER],
shouldHandleEvent: cacheGetStructuredElement,
handleEvent: (event, editor) => {
let element = cacheGetStructuredElement(event, editor);
let div = fromHtml(NEWLINE_HTML, editor.getDocument())[0] as HTMLElement;
editor.addUndoSnapshot(() => {
element.parentNode.insertBefore(div, element);
// Select the new line when we are in table. This is the same behavior with Word
if (getTagOfNode(element) == 'TABLE') {
editor.select(new Position(div, PositionType.Begin).normalize());
}
});
event.rawEvent.preventDefault();
},
};

function cacheGetStructuredElement(event: PluginKeyboardEvent, editor: Editor) {
return cacheGetEventData(event, 'FIRST_STRUCTURE', () => {
// Provide a chance to keep browser default behavior by pressing SHIFT
let element = event.rawEvent.shiftKey ? null : editor.getElementAtCursor(CHILD_SELECTOR);

if (element) {
let range = editor.getSelectionRange();
if (
range &&
range.collapsed &&
isPositionAtBeginningOf(Position.getStart(range), element) &&
!editor.getBodyTraverser(element).getPreviousBlockElement()
) {
return editor.getElementAtCursor(CHILD_PARENT_TAG_MAP[getTagOfNode(element)]);
}
}

return null;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ export default class Plugins extends React.Component<PluginsProps, {}> {
{this.renderContentEditItem('autoBullet', 'Auto Bullet / Numbering')}
{this.renderContentEditItem('tabInTable', 'Tab To Jump Cell In Table')}
{this.renderContentEditItem('upDownInTable', 'Up / Down To Jump Cell In Table')}
{this.renderContentEditItem(
'insertLineBeforeStructuredNodeFeature',
"Enter to create new line before table/list which doesn't have line before"
)}
{this.renderContentEditItem(
'unlinkWhenBackspaceAfterLink',
'Auto unlink when backspace right after a hyperlink'
Expand Down

0 comments on commit 9a7e983

Please sign in to comment.