Skip to content

Commit

Permalink
Merge pull request microsoft#2522 from microsoft/u/juliaroldi/undo-au…
Browse files Browse the repository at this point in the history
…to-link

Undo Auto link by backspace
  • Loading branch information
juliaroldi authored Mar 28, 2024
2 parents d6af272 + 091fdff commit 2c11a0d
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,11 @@ export class AutoFormatPlugin implements EditorPlugin {
if (rawEvent.inputType === 'insertText') {
switch (rawEvent.data) {
case ' ':
const { autoBullet, autoNumbering } = this.options;
const { autoBullet, autoNumbering, autoLink } = this.options;
keyboardListTrigger(editor, autoBullet, autoNumbering);
if (autoLink) {
createLinkAfterSpace(editor);
}
break;
}
}
Expand All @@ -126,11 +129,6 @@ export class AutoFormatPlugin implements EditorPlugin {
const rawEvent = event.rawEvent;
if (!rawEvent.defaultPrevented && !event.handledByEditFeature) {
switch (rawEvent.key) {
case ' ':
if (this.options.autoLink) {
createLinkAfterSpace(editor);
}
break;
case 'Backspace':
if (this.options.autoUnlink) {
unlink(editor, rawEvent);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
import { createText, getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-dom';
import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-dom';
import { matchLink } from 'roosterjs-content-model-api';
import type { IEditor } from 'roosterjs-content-model-types';
import { splitTextSegment } from '../../pluginUtils/splitTextSegment';
import type { IEditor, LinkData } from 'roosterjs-content-model-types';

/**
* @internal
*/
export function createLinkAfterSpace(editor: IEditor) {
editor.formatContentModel(model => {
editor.formatContentModel((model, context) => {
const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs(
model,
false /* includingFormatHolder */
);
if (selectedSegmentsAndParagraphs[0] && selectedSegmentsAndParagraphs[0][1]) {
const length = selectedSegmentsAndParagraphs[0][1].segments.length;
const marker = selectedSegmentsAndParagraphs[0][1].segments[length - 1];
const textSegment = selectedSegmentsAndParagraphs[0][1].segments[length - 2];
if (
marker.segmentType == 'SelectionMarker' &&
textSegment.segmentType == 'Text' &&
!textSegment.link
) {
const link = textSegment.text.split(' ').pop();
if (link && matchLink(link)) {
textSegment.text = textSegment.text.replace(link, '');
const linkSegment = createText(link, marker.format, {
format: {
href: link,
underline: true,
},
dataset: {},
});
selectedSegmentsAndParagraphs[0][1].segments.splice(length - 1, 0, linkSegment);
return true;
if (selectedSegmentsAndParagraphs.length > 0 && selectedSegmentsAndParagraphs[0][1]) {
const markerIndex = selectedSegmentsAndParagraphs[0][1].segments.findIndex(
segment => segment.segmentType == 'SelectionMarker'
);
const paragraph = selectedSegmentsAndParagraphs[0][1];
if (markerIndex > 0) {
const textSegment = paragraph.segments[markerIndex - 1];
const marker = paragraph.segments[markerIndex];
if (
marker.segmentType == 'SelectionMarker' &&
textSegment &&
textSegment.segmentType == 'Text' &&
!textSegment.link
) {
const link = textSegment.text.split(' ').pop();
const url = link?.trim();
let linkData: LinkData | null = null;
if (url && link && (linkData = matchLink(url))) {
const linkSegment = splitTextSegment(
textSegment,
paragraph,
textSegment.text.length - link.trimLeft().length,
textSegment.text.trimRight().length
);
linkSegment.link = {
format: {
href: linkData.normalizedUrl,
underline: true,
},
dataset: {},
};

context.canUndoByBackspace = true;

return true;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createText } from 'roosterjs-content-model-dom';
import type { ContentModelParagraph, ContentModelText } from 'roosterjs-content-model-types';

/**
* @internal
*/
export function splitTextSegment(
textSegment: ContentModelText,
parent: ContentModelParagraph,
start: number,
end: number
): ContentModelText {
const text = textSegment.text;
const index = parent.segments.indexOf(textSegment);
const middleSegment = createText(
text.substring(start, end),
textSegment.format,
textSegment.link,
textSegment.code
);

const newSegments: ContentModelText[] = [middleSegment];
if (start > 0) {
newSegments.unshift(
createText(
text.substring(0, start),
textSegment.format,
textSegment.link,
textSegment.code
)
);
}
if (end < text.length) {
newSegments.push(
createText(text.substring(end), textSegment.format, textSegment.link, textSegment.code)
);
}

newSegments.forEach(segment => (segment.isSelected = textSegment.isSelected));
parent.segments.splice(index, 1, ...newSegments);

return middleSegment;
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ describe('Content Model Auto Format Plugin Test', () => {
});

function runTest(
event: KeyDownEvent,
event: EditorInputEvent,
shouldCallTrigger: boolean,
options?: {
autoLink: boolean;
Expand All @@ -247,29 +247,33 @@ describe('Content Model Auto Format Plugin Test', () => {
}

it('should call createLinkAfterSpace', () => {
const event: KeyDownEvent = {
eventType: 'keyDown',
rawEvent: { key: ' ', preventDefault: () => {} } as any,
const event: EditorInputEvent = {
eventType: 'input',
rawEvent: { data: ' ', preventDefault: () => {}, inputType: 'insertText' } as any,
};
runTest(event, true, {
autoLink: true,
});
});

it('should not call createLinkAfterSpace - disable options', () => {
const event: KeyDownEvent = {
eventType: 'keyDown',
rawEvent: { key: ' ', preventDefault: () => {} } as any,
const event: EditorInputEvent = {
eventType: 'input',
rawEvent: { data: ' ', preventDefault: () => {}, inputType: 'insertText' } as any,
};
runTest(event, false, {
autoLink: false,
});
});

it('should not call createLinkAfterSpace - not space', () => {
const event: KeyDownEvent = {
eventType: 'keyDown',
rawEvent: { key: 'Backspace', preventDefault: () => {} } as any,
const event: EditorInputEvent = {
eventType: 'input',
rawEvent: {
data: 'Backspace',
preventDefault: () => {},
inputType: 'insertText',
} as any,
};
runTest(event, false, {
autoLink: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('createLinkAfterSpace', () => {
newEntities: [],
deletedEntities: [],
newImages: [],
canUndoByBackspace: true,
});
expect(result).toBe(expectedResult);
});
Expand Down Expand Up @@ -105,18 +106,14 @@ describe('createLinkAfterSpace', () => {
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: '',
format: {},
},
{
segmentType: 'Text',
text: 'www.bing.com',
format: {},
isSelected: undefined,
link: {
format: {
href: 'www.bing.com',
href: 'http://www.bing.com',
underline: true,
},
dataset: {},
Expand Down Expand Up @@ -203,15 +200,17 @@ describe('createLinkAfterSpace', () => {
segmentType: 'Text',
text: 'this is the link ',
format: {},
isSelected: undefined,
},
{
segmentType: 'Text',
text: 'www.bing.com',
format: {},
isSelected: undefined,
link: {
format: {
underline: true,
href: 'www.bing.com',
href: 'http://www.bing.com',
},
dataset: {},
},
Expand Down Expand Up @@ -256,4 +255,90 @@ describe('createLinkAfterSpace', () => {
};
runTest(input, input, false);
});

it('link after link', () => {
const input: ContentModelDocument = {
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'www.bing.com',
format: {},
link: {
format: {
href: 'www.bing.com',
underline: true,
},
dataset: {},
},
},
{
segmentType: 'Text',
text: ' www.bing.com',
format: {},
},
{
segmentType: 'SelectionMarker',
isSelected: true,
format: {},
},
],
format: {},
},
],
format: {},
};
const expected: ContentModelDocument = {
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'www.bing.com',
format: {},
link: {
format: {
href: 'www.bing.com',
underline: true,
},
dataset: {},
},
},
{
segmentType: 'Text',
text: ' ',
format: {},
isSelected: undefined,
},
{
segmentType: 'Text',
text: 'www.bing.com',
format: {},
isSelected: undefined,
link: {
format: {
href: 'http://www.bing.com',
underline: true,
},
dataset: {},
},
},
{
segmentType: 'SelectionMarker',
isSelected: true,
format: {},
},
],
format: {},
},
],
format: {},
};
runTest(input, expected, true);
});
});
Loading

0 comments on commit 2c11a0d

Please sign in to comment.