Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge in current master in preperation for additional changes prior to 7.1.3 #225

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions packages/roosterjs-editor-api/lib/format/replaceWithNode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ContentPosition } from 'roosterjs-editor-types';
import { Editor } from 'roosterjs-editor-core';
import { PositionContentSearcher } from 'roosterjs-editor-dom';
import { PositionType } from 'roosterjs-editor-types';

/**
* Replace text before current selection with a node, current selection will be kept if possible
Expand Down Expand Up @@ -56,9 +56,9 @@ export default function replaceWithNode(
}

if (range) {
let backupRange = editor.getSelectionRange();
const backupRange = editor.getSelectionRange();

// If the range to replace is rgith before current cursor, it is actually an exact match
// If the range to replace is right before current cursor, it is actually an exact match
if (
backupRange.collapsed &&
range.endContainer == backupRange.startContainer &&
Expand All @@ -67,14 +67,13 @@ export default function replaceWithNode(
exactMatch = true;
}

range.deleteContents();
range.insertNode(node);

if (exactMatch) {
editor.select(node, PositionType.After);
} else {
editor.select(backupRange);
}
editor.insertNode(node, {
position: ContentPosition.Range,
updateCursor: exactMatch,
replaceSelection: true,
insertOnNewLine: false,
range: range,
});

return true;
}
Expand Down
50 changes: 33 additions & 17 deletions packages/roosterjs-editor-core/lib/coreAPI/insertNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,30 @@ import {
} from 'roosterjs-editor-dom';

const insertNode: InsertNode = (core: EditorCore, node: Node, option: InsertOption) => {
let position = option ? option.position : ContentPosition.SelectionStart;
let updateCursor = option ? option.updateCursor : true;
let replaceSelection = option ? option.replaceSelection : true;
let insertOnNewLine = option ? option.insertOnNewLine : false;
if (!option) {
Lego6245 marked this conversation as resolved.
Show resolved Hide resolved
option = <InsertOption>{
Lego6245 marked this conversation as resolved.
Show resolved Hide resolved
position: ContentPosition.SelectionStart,
insertOnNewLine: false,
updateCursor: true,
replaceSelection: true,
}
}
let contentDiv = core.contentDiv;

if (updateCursor) {
if (option.updateCursor) {
core.api.focus(core);
}

switch (position) {
switch (option.position) {
case ContentPosition.Begin:
case ContentPosition.End:
let isBegin = position == ContentPosition.Begin;
let isBegin = option.position == ContentPosition.Begin;
let block = getFirstLastBlockElement(contentDiv, isBegin);
let insertedNode: Node;
if (block) {
let refNode = isBegin ? block.getStartNode() : block.getEndNode();
if (
insertOnNewLine ||
option.insertOnNewLine ||
refNode.nodeType == NodeType.Text ||
isVoidHtmlElement(refNode)
) {
Expand All @@ -60,29 +64,41 @@ const insertNode: InsertNode = (core: EditorCore, node: Node, option: InsertOpti

// Final check to see if the inserted node is a block. If not block and the ask is to insert on new line,
// add a DIV wrapping
if (insertedNode && insertOnNewLine && !isBlockElement(insertedNode)) {
if (insertedNode && option.insertOnNewLine && !isBlockElement(insertedNode)) {
wrap(insertedNode);
}

break;
case ContentPosition.Range:
case ContentPosition.SelectionStart:
// Selection start replaces based on the current selection.
// Range inserts based on a provided range.
// Both have the potential to use the current selection to restore cursor position
// So in both cases we need to store the selection state.
let range = core.api.getSelectionRange(core, true /*tryGetFromCache*/);
if (!range) {
return;
let rangeToRestore = null;
if (option.position == ContentPosition.Range) {
rangeToRestore = range;
range = option.range;
} else {
if (!range) {
return;
} else {
// Create a clone (backup) for the selection first as we may need to restore to it later
rangeToRestore = range.cloneRange();
Lego6245 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// if to replace the selection and the selection is not collapsed, remove the the content at selection first
if (replaceSelection && !range.collapsed) {
if (option.replaceSelection && !range.collapsed) {
range.deleteContents();
}

// Create a clone (backup) for the selection first as we may need to restore to it later
let clonedRange = range.cloneRange();
let pos = Position.getStart(range);
let blockElement: BlockElement;

if (
insertOnNewLine &&
option.insertOnNewLine &&
(blockElement = getBlockElementAtNode(contentDiv, pos.normalize().node))
) {
pos = new Position(blockElement.getEndNode(), PositionType.After);
Expand All @@ -93,10 +109,10 @@ const insertNode: InsertNode = (core: EditorCore, node: Node, option: InsertOpti
let nodeForCursor = node.nodeType == NodeType.DocumentFragment ? node.lastChild : node;
range = createRange(pos);
range.insertNode(node);
if (updateCursor && nodeForCursor) {
if (option.updateCursor && nodeForCursor) {
core.api.select(core, new Position(nodeForCursor, PositionType.After).normalize());
} else {
core.api.select(core, clonedRange);
core.api.select(core, rangeToRestore);
}

break;
Expand Down
2 changes: 1 addition & 1 deletion packages/roosterjs-editor-core/lib/editor/Editor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import createEditorCore from './createEditorCore';
import EditorCore from '../interfaces/EditorCore';
import EditorOptions from '../interfaces/EditorOptions';
import { Browser, getRangeFromSelectionPath, getSelectionPath } from 'roosterjs-editor-dom';
import { GenericContentEditFeature } from '../interfaces/ContentEditFeature';
import { getRangeFromSelectionPath, getSelectionPath, Browser } from 'roosterjs-editor-dom';
import {
BlockElement,
ChangeSource,
Expand Down
5 changes: 5 additions & 0 deletions packages/roosterjs-editor-types/lib/enum/ContentPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ export const enum ContentPosition {
* Outside of editor
*/
Outside,

/**
* Manually defined range
*/
Range,
}
2 changes: 1 addition & 1 deletion packages/roosterjs-editor-types/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export { default as DarkModeOptions } from './interface/DarkModeOptions';
export { default as DefaultFormat } from './interface/DefaultFormat';
export { default as FormatState } from './interface/FormatState';
export { default as InlineElement } from './interface/InlineElement';
export { default as InsertOption } from './interface/InsertOption';
export { default as InsertOption, InsertOptionBase, InsertOptionBasic, InsertOptionRange } from './interface/InsertOption';
export { default as LinkData } from './interface/LinkData';
export { default as ModeIndependentColor } from './interface/ModeIndependentColor';
export { default as NodePosition } from './interface/NodePosition';
Expand Down
46 changes: 36 additions & 10 deletions packages/roosterjs-editor-types/lib/interface/InsertOption.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,52 @@
import { ContentPosition } from '../enum/ContentPosition';

/**
* Options for insertContent API
* Shared options for insertNode related APIs
*/
export default interface InsertOption {
export interface InsertOptionBase {
/**
* Target position
* Whether need to update cursor.
*/
position: ContentPosition;
updateCursor?: boolean;

/**
* Whether need to update cursor
* Boolean flag for inserting the content onto a new line.
* No-op for ContentPosition.Outside
*/
updateCursor: boolean;
insertOnNewLine?: boolean;

/**
* Whether need to replace current selection
* Boolean flag for inserting the content onto a new line.
* No-op for ContentPosition.Begin, End, and Outside
*/
replaceSelection: boolean;
replaceSelection?: boolean;
}

/**
* The "basic" insertNode related ContentPositions that require no additional parameters to use.
*/
export interface InsertOptionBasic extends InsertOptionBase {
position: ContentPosition.Begin | ContentPosition.End | ContentPosition.Outside | ContentPosition.SelectionStart;
}

/**
* The Range varient where insertNode will opperate on a range disjointed from the current selection state.
*/
export interface InsertOptionRange extends InsertOptionBase {
position: ContentPosition.Range;

/**
* Whether need to insert the content into a new line
* The range to be targeted when insertion happens.
*/
insertOnNewLine: boolean;
range: Range;
}

/**
* Type definition for the InsertOption, used in the insertNode API.
* The position parameter defines how the node will be inserted.
* In a future revision, this will become strongly typed
* Only parameters applicable to the given position will be accepted.
*/
type InsertOption = InsertOptionRange | InsertOptionBasic;

export default InsertOption;
4 changes: 4 additions & 0 deletions packages/roosterjs-plugin-picker/lib/PickerDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ export interface PickerDataProvider {

// Function that returns the current cursor position as an anchor point for where to show UX.
setCursorPoint?: (targetPoint: { x: number; y: number }, buffer: number) => void;

// Function that is called when the plugin detects the editor's content has changed.
// Provides a list of current picker placed elements in the document.
onContentChanged?: (elementIds: string[]) => void;
}
21 changes: 18 additions & 3 deletions packages/roosterjs-plugin-picker/lib/PickerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PluginEvent,
PluginEventType,
PositionType,
ChangeSource,
} from 'roosterjs-editor-types';

// Character codes.
Expand Down Expand Up @@ -38,7 +39,7 @@ export default class PickerPlugin implements EditorPickerPluginInterface {
constructor(
public readonly dataProvider: PickerDataProvider,
private pickerOptions: PickerPluginOptions
) {}
) { }

/**
* Get a friendly name
Expand Down Expand Up @@ -123,6 +124,20 @@ export default class PickerPlugin implements EditorPickerPluginInterface {
* @param event PluginEvent object
*/
public onPluginEvent(event: PluginEvent) {
if (event.eventType == PluginEventType.ContentChanged &&
event.source == ChangeSource.SetContent && this.dataProvider.onContentChanged) {
// Undos and other major changes to document content fire this type of event.
// Inform the data provider of the current picker placed elements in the body.
let elementIds: string[] = [];
this.editor.queryElements(
"[id^='" + this.pickerOptions.elementIdPrefix + "']",
(element) => {
if (element.id) {
elementIds.push(element.id);
}
});
this.dataProvider.onContentChanged(elementIds);
}
if (event.eventType == PluginEventType.KeyDown) {
this.eventHandledOnKeyDown = false;
this.onKeyDownEvent(event);
Expand Down Expand Up @@ -305,9 +320,9 @@ export default class PickerPlugin implements EditorPickerPluginInterface {
this.dataProvider.shiftHighlight &&
(this.pickerOptions.isHorizontal
? keyboardEvent.key == LEFT_ARROW_CHARCODE ||
keyboardEvent.key == RIGHT_ARROW_CHARCODE
keyboardEvent.key == RIGHT_ARROW_CHARCODE
: keyboardEvent.key == UP_ARROW_CHARCODE ||
keyboardEvent.key == DOWN_ARROW_CHARCODE)
keyboardEvent.key == DOWN_ARROW_CHARCODE)
) {
this.dataProvider.shiftHighlight(
this.pickerOptions.isHorizontal
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import * as React from 'react';
import ApiPaneProps from '../ApiPaneProps';
import { ContentPosition, InsertOption } from 'roosterjs-editor-types';
import { ContentPosition, InsertOptionBasic } from 'roosterjs-editor-types';

const styles = require('./InsertContentPane.scss');

export interface InsertContentPaneState extends InsertOption {
export interface InsertContentPaneState {
content: string;
position: ContentPosition,
updateCursor: boolean,
replaceSelection: boolean,
insertOnNewLine: boolean,
}

export default class InsertContentPane extends React.Component<
Expand Down Expand Up @@ -92,7 +96,7 @@ export default class InsertContentPane extends React.Component<
id="insertUpdateCursor"
checked={this.state.updateCursor}
onClick={() =>
this.setState({ updateCursor: !this.state.updateCursor })
this.setState({updateCursor: !this.state.updateCursor })
}
/>
<label htmlFor="insertUpdateCursor">Update cursor</label>
Expand Down Expand Up @@ -143,6 +147,14 @@ export default class InsertContentPane extends React.Component<

private onClick = () => {
let editor = this.props.getEditor();
editor.addUndoSnapshot(() => editor.insertContent(this.state.content, this.state));
if (this.state.position != ContentPosition.Range) {
const inputOption: InsertOptionBasic = {
position: this.state.position,
updateCursor: this.state.updateCursor,
replaceSelection: this.state.replaceSelection,
insertOnNewLine: this.state.insertOnNewLine,
}
Lego6245 marked this conversation as resolved.
Show resolved Hide resolved
editor.addUndoSnapshot(() => editor.insertContent(this.state.content, inputOption));
}
};
}