Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of https://github.com/microsoft/roosterjs into …
Browse files Browse the repository at this point in the history
…u/acampostams/Table-Tab-key-EditPlugin
Andres-CT98 committed Apr 1, 2024
2 parents b7cddaa + 1c276af commit bc3df89
Showing 11 changed files with 256 additions and 34 deletions.
8 changes: 2 additions & 6 deletions demo/scripts/controlsV2/mainPane/MainPane.tsx
Original file line number Diff line number Diff line change
@@ -474,6 +474,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
tableMenu,
imageMenu,
watermarkText,
markdownOptions,
} = this.state.initState;
return [
pluginList.autoFormat &&
@@ -488,12 +489,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
pluginList.shortcut && new ShortcutPlugin(),
pluginList.tableEdit && new TableEditPlugin(),
pluginList.watermark && new WatermarkPlugin(watermarkText),
pluginList.markdown &&
new MarkdownPlugin({
bold: true,
italic: true,
strikethrough: true,
}),
pluginList.markdown && new MarkdownPlugin(markdownOptions),
pluginList.emoji && createEmojiPlugin(),
pluginList.pasteOption && createPasteOptionPlugin(),
pluginList.sampleEntity && new SampleEntityPlugin(),
Original file line number Diff line number Diff line change
@@ -38,6 +38,12 @@ const initialState: OptionState = {
imageMenu: true,
tableMenu: true,
listMenu: true,
markdownOptions: {
bold: true,
italic: true,
strikethrough: true,
codeFormat: {},
},
};

export class EditorOptionsPlugin extends SidePanePluginImpl<OptionsPane, OptionPaneProps> {
2 changes: 2 additions & 0 deletions demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MarkdownOptions } from 'roosterjs-content-model-plugins';
import type { ContentEditFeatureSettings } from 'roosterjs-editor-types';
import type { SidePaneElementProps } from '../SidePaneElement';
import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types';
@@ -35,6 +36,7 @@ export interface OptionState {
tableMenu: boolean;
imageMenu: boolean;
watermarkText: string;
markdownOptions: MarkdownOptions;

// Legacy plugin options
contentEditFeatures: ContentEditFeatureSettings;
Original file line number Diff line number Diff line change
@@ -138,6 +138,7 @@ export class OptionsPane extends React.Component<OptionPaneProps, OptionState> {
listMenu: this.state.listMenu,
tableMenu: this.state.tableMenu,
imageMenu: this.state.imageMenu,
markdownOptions: { ...this.state.markdownOptions },
};

if (callback) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CodeElement } from './CodeElement';
import { MarkdownOptions } from 'roosterjs-content-model-plugins';

export class MarkdownCode extends CodeElement {
constructor(private markdownOptions: MarkdownOptions) {
super();
}

getCode() {
return `new roosterjs.MarkdownPlugin({
bold: ${this.markdownOptions.bold},
italic: ${this.markdownOptions.italic},
strikethrough: ${this.markdownOptions.strikethrough},
codeFormat: ${JSON.stringify(this.markdownOptions.codeFormat)},
})`;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CodeElement } from './CodeElement';
import { ContentEditCode } from './ContentEditCode';
import { HyperLinkCode } from './HyperLinkCode';
import { MarkdownCode } from './MarkdownCode';
import { OptionState } from '../OptionState';
import { WatermarkCode } from './WatermarkCode';
import {
@@ -11,7 +12,6 @@ import {
PastePluginCode,
TableEditPluginCode,
ShortcutPluginCode,
MarkdownPluginCode,
} from './SimplePluginCode';

export class PluginsCodeBase extends CodeElement {
@@ -45,7 +45,7 @@ export class PluginsCode extends PluginsCodeBase {
pluginList.tableEdit && new TableEditPluginCode(),
pluginList.shortcut && new ShortcutPluginCode(),
pluginList.watermark && new WatermarkCode(state.watermarkText),
pluginList.markdown && new MarkdownPluginCode(),
pluginList.markdown && new MarkdownCode(state.markdownOptions),
]);
}
}
Original file line number Diff line number Diff line change
@@ -51,9 +51,3 @@ export class CustomReplaceCode extends SimplePluginCode {
super('CustomReplace', 'roosterjsLegacy');
}
}

export class MarkdownPluginCode extends SimplePluginCode {
constructor() {
super('MarkdownPlugin');
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { setFormat } from './utils/setFormat';
import type {
ContentChangedEvent,
ContentModelCodeFormat,
EditorInputEvent,
EditorPlugin,
IEditor,
@@ -9,18 +10,24 @@ import type {
} from 'roosterjs-content-model-types';

/**
*
* Options for Markdown plugin
* - strikethrough: If true text between ~ will receive strikethrough format.
* - bold: If true text between * will receive bold format.
* - italic: If true text between _ will receive italic format.
* - codeFormat: If provided, text between ` will receive code format. If equal to {}, it will set the default code format.
*/
export interface MarkdownOptions {
strikethrough?: boolean;
bold?: boolean;
italic?: boolean;
codeFormat?: ContentModelCodeFormat;
}

/**
* @internal
*/
const DefaultOptions: Required<MarkdownOptions> = {
const DefaultOptions: Partial<MarkdownOptions> = {
strikethrough: false,
bold: false,
italic: false,
@@ -34,13 +41,15 @@ export class MarkdownPlugin implements EditorPlugin {
private shouldBold = false;
private shouldItalic = false;
private shouldStrikethrough = false;
private shouldCode = false;
private lastKeyTyped: string | null = null;

/**
* @param options An optional parameter that takes in an object of type MarkdownOptions, which includes the following properties:
* - strikethrough: If true text between ~ will receive strikethrough format. Defaults to true.
* - bold: If true text between * will receive bold format. Defaults to true.
* - italic: If true text between _ will receive italic format. Defaults to true.
* - strikethrough: If true text between ~ will receive strikethrough format. Defaults to false.
* - bold: If true text between * will receive bold format. Defaults to false.
* - italic: If true text between _ will receive italic format. Defaults to false.
* - codeFormat: If provided, text between ` will receive code format. Defaults to undefined.
*/
constructor(private options: MarkdownOptions = DefaultOptions) {}

@@ -68,9 +77,7 @@ export class MarkdownPlugin implements EditorPlugin {
*/
dispose() {
this.editor = null;
this.shouldBold = false;
this.shouldItalic = false;
this.shouldStrikethrough = false;
this.disableAllFeatures();
this.lastKeyTyped = null;
}

@@ -138,6 +145,16 @@ export class MarkdownPlugin implements EditorPlugin {
}
}
break;
case '`':
if (this.options.codeFormat) {
if (this.shouldCode) {
setFormat(editor, '`', {} /* format */, this.options.codeFormat);
this.shouldCode = false;
} else {
this.shouldCode = true;
}
}
break;
}
}
}
@@ -147,9 +164,7 @@ export class MarkdownPlugin implements EditorPlugin {
if (!event.handledByEditFeature && !rawEvent.defaultPrevented) {
switch (rawEvent.key) {
case 'Enter':
this.shouldBold = false;
this.shouldItalic = false;
this.shouldStrikethrough = false;
this.disableAllFeatures();
this.lastKeyTyped = null;
break;
case ' ':
@@ -159,6 +174,8 @@ export class MarkdownPlugin implements EditorPlugin {
this.shouldStrikethrough = false;
} else if (this.lastKeyTyped === '_' && this.shouldItalic) {
this.shouldItalic = false;
} else if (this.lastKeyTyped === '`' && this.shouldCode) {
this.shouldCode = false;
}
this.lastKeyTyped = null;
break;
@@ -177,16 +194,23 @@ export class MarkdownPlugin implements EditorPlugin {
this.shouldStrikethrough = false;
} else if (this.lastKeyTyped === '_' && this.shouldItalic) {
this.shouldItalic = false;
} else if (this.lastKeyTyped === '`' && this.shouldCode) {
this.shouldCode = false;
}
this.lastKeyTyped = null;
}
}

private handleContentChangedEvent(event: ContentChangedEvent) {
if (event.source == 'Format') {
this.shouldBold = false;
this.shouldItalic = false;
this.shouldStrikethrough = false;
this.disableAllFeatures();
}
}

private disableAllFeatures() {
this.shouldBold = false;
this.shouldItalic = false;
this.shouldStrikethrough = false;
this.shouldCode = false;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-dom';
import { splitTextSegment } from '../../pluginUtils/splitTextSegment';

import type { ContentModelSegmentFormat, IEditor } from 'roosterjs-content-model-types';
import type {
ContentModelCodeFormat,
ContentModelSegmentFormat,
IEditor,
} from 'roosterjs-content-model-types';

/**
* @internal
*/
export function setFormat(editor: IEditor, character: string, format: ContentModelSegmentFormat) {
export function setFormat(
editor: IEditor,
character: string,
format: ContentModelSegmentFormat,
codeFormat?: ContentModelCodeFormat
) {
editor.formatContentModel((model, context) => {
const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs(
model,
@@ -53,6 +62,11 @@ export function setFormat(editor: IEditor, character: string, format: ContentMod
...formattedText.format,
...format,
};
if (codeFormat) {
formattedText.code = {
format: codeFormat,
};
}

context.canUndoByBackspace = true;
return true;
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import * as setFormat from '../../lib/markdown/utils/setFormat';
import { MarkdownOptions, MarkdownPlugin } from '../../lib/markdown/MarkdownPlugin';
import {
ContentChangedEvent,
ContentModelCodeFormat,
ContentModelSegmentFormat,
EditorInputEvent,
IEditor,
@@ -32,15 +33,25 @@ describe('MarkdownPlugin', () => {
shouldCallTrigger: boolean,
options?: MarkdownOptions,
expectedChar?: string,
expectedFormat?: ContentModelSegmentFormat
expectedFormat?: ContentModelSegmentFormat,
expectedCode?: ContentModelCodeFormat
) {
const plugin = new MarkdownPlugin(options);
plugin.initialize(editor);

events.forEach(event => plugin.onPluginEvent(event));

if (shouldCallTrigger) {
expect(setFormatSpy).toHaveBeenCalledWith(editor, expectedChar, expectedFormat);
if (expectedCode) {
expect(setFormatSpy).toHaveBeenCalledWith(
editor,
expectedChar,
expectedFormat,
expectedCode
);
} else {
expect(setFormatSpy).toHaveBeenCalledWith(editor, expectedChar, expectedFormat);
}
} else {
expect(setFormatSpy).not.toHaveBeenCalled();
}
@@ -178,6 +189,51 @@ describe('MarkdownPlugin', () => {
);
});

it('should trigger setFormat for code', () => {
runTest(
[
{
rawEvent: { data: '`', inputType: 'insertText' },
eventType: 'input',
} as EditorInputEvent,
{
rawEvent: { data: 't', inputType: 'insertText' },
eventType: 'input',
} as EditorInputEvent,
{
rawEvent: { data: '`', inputType: 'insertText' },
eventType: 'input',
} as EditorInputEvent,
],
true,
{ bold: true, italic: true, strikethrough: true, codeFormat: {} },
'`',
{},
{}
);
});

it('Feature disabled - should not trigger setFormat for code', () => {
runTest(
[
{
rawEvent: { data: '`', inputType: 'insertText' },
eventType: 'input',
} as EditorInputEvent,
{
rawEvent: { data: 't', inputType: 'insertText' },
eventType: 'input',
} as EditorInputEvent,
{
rawEvent: { data: '`', inputType: 'insertText' },
eventType: 'input',
} as EditorInputEvent,
],
false,
{ bold: true, italic: true, strikethrough: true, codeFormat: undefined }
);
});

it('Backspace - should not trigger setFormat for bold', () => {
runTest(
[
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { ContentModelDocument, ContentModelSegmentFormat } from 'roosterjs-content-model-types';
import { setFormat } from '../../../lib/markdown/utils/setFormat';
import {
ContentModelCodeFormat,
ContentModelDocument,
ContentModelSegmentFormat,
} from 'roosterjs-content-model-types';

describe('setFormat', () => {
function runTest(
input: ContentModelDocument,
char: string,
format: ContentModelSegmentFormat,
expectedModel: ContentModelDocument,
expectedResult: boolean
expectedResult: boolean,
code: ContentModelCodeFormat | undefined = undefined
) {
const formatWithContentModelSpy = jasmine
.createSpy('formatWithContentModel')
@@ -27,7 +32,8 @@ describe('setFormat', () => {
formatContentModel: formatWithContentModelSpy,
} as any,
char,
format
format,
code
);

expect(formatWithContentModelSpy).toHaveBeenCalled();
@@ -214,6 +220,112 @@ describe('setFormat', () => {
runTest(input, '_', { italic: true }, expectedModel, true);
});

it('should set code', () => {
const input: ContentModelDocument = {
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: '`test`',
format: {},
},
{
segmentType: 'SelectionMarker',
format: {},
isSelected: true,
},
],
format: {},
},
],
format: {},
};
const expectedModel: ContentModelDocument = {
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'test',
format: {},
code: {
format: {},
},
isSelected: undefined,
},
{
segmentType: 'SelectionMarker',
format: {},
isSelected: true,
},
],
format: {},
},
],
format: {},
};
runTest(input, '`', {}, expectedModel, true, {});
});

it('should set code with format', () => {
const input: ContentModelDocument = {
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: '`test`',
format: {},
},
{
segmentType: 'SelectionMarker',
format: {},
isSelected: true,
},
],
format: {},
},
],
format: {},
};
const expectedModel: ContentModelDocument = {
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'test',
format: {},
code: {
format: {
fontFamily: 'arial',
},
},
isSelected: undefined,
},
{
segmentType: 'SelectionMarker',
format: {},
isSelected: true,
},
],
format: {},
},
],
format: {},
};
runTest(input, '`', {}, expectedModel, true, { fontFamily: 'arial' });
});

it('should set bold in multiple words', () => {
const input: ContentModelDocument = {
blockGroupType: 'Document',

0 comments on commit bc3df89

Please sign in to comment.