Skip to content

Commit

Permalink
Merge pull request #2847 from microsoft/u/nguyenvi/versionbump
Browse files Browse the repository at this point in the history
Version bump to main 9.12.0 and legacyAdapter 8.62.2
  • Loading branch information
vinguyen12 authored Oct 25, 2024
2 parents db6f8d8 + 76b7282 commit 1ac4373
Show file tree
Hide file tree
Showing 31 changed files with 3,313 additions and 381 deletions.
1 change: 1 addition & 0 deletions packages/roosterjs-content-model-api/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,5 @@ export { setModelListStartNumber } from './modelApi/list/setModelListStartNumber
export { findListItemsInSameThread } from './modelApi/list/findListItemsInSameThread';
export { setModelIndentation } from './modelApi/block/setModelIndentation';
export { matchLink } from './modelApi/link/matchLink';
export { promoteLink } from './modelApi/link/promoteLink';
export { getListAnnounceData } from './modelApi/list/getListAnnounceData';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { matchLink } from 'roosterjs-content-model-api';
import type { AutoLinkOptions } from '../interface/AutoLinkOptions';
import { matchLink } from './matchLink';
import type { AutoLinkOptions } from 'roosterjs-content-model-types';

const COMMON_REGEX = `[\s]*[a-zA-Z0-9+][\s]*`;
const TELEPHONE_REGEX = `(T|t)el:${COMMON_REGEX}`;
Expand All @@ -8,8 +8,8 @@ const MAILTO_REGEX = `(M|m)ailto:${COMMON_REGEX}`;
/**
* @internal
*/
export function getLinkUrl(text: string, autoLinkOptions: AutoLinkOptions): string | undefined {
const { autoLink, autoMailto, autoTel } = autoLinkOptions;
export function getLinkUrl(text: string, autoLinkOptions?: AutoLinkOptions): string | undefined {
const { autoLink, autoMailto, autoTel } = autoLinkOptions ?? {};
const linkMatch = autoLink ? matchLink(text)?.normalizedUrl : undefined;
const telMatch = autoTel ? matchTel(text) : undefined;
const mailtoMatch = autoMailto ? matchMailTo(text) : undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getLinkUrl } from './getLinkUrl';
import { splitTextSegment } from '../../publicApi/segment/splitTextSegment';
import type {
AutoLinkOptions,
ContentModelText,
ShallowMutableContentModelParagraph,
} from 'roosterjs-content-model-types';

/**
* Promote the given text segment to a hyper link when the segment text is ending with a valid link format.
* When the whole text segment if of a link, promote the whole segment.
* When the text segment ends with a link format, split the segment and promote the second part
* When link is in middle of the text segment, no action.
* This is mainly used for doing auto link when there is a link before cursor
* @param segment The text segment to search link text from
* @param paragraph Parent paragraph of the segment
* @param options Options of auto link
* @returns If a link is promoted, return this segment. Otherwise return null
*/
export function promoteLink(
segment: ContentModelText,
paragraph: ShallowMutableContentModelParagraph,
autoLinkOptions: AutoLinkOptions
): ContentModelText | null {
const link = segment.text.split(' ').pop();
const url = link?.trim();
let linkUrl: string | undefined = undefined;

if (url && link && (linkUrl = getLinkUrl(url, autoLinkOptions))) {
const linkSegment = splitTextSegment(
segment,
paragraph,
segment.text.length - link.trimLeft().length,
segment.text.trimRight().length
);
linkSegment.link = {
format: {
href: linkUrl,
underline: true,
},
dataset: {},
};

return linkSegment;
}

return null;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AutoLinkOptions } from '../../../lib/autoFormat/interface/AutoLinkOptions';
import { getLinkUrl } from '../../../lib/autoFormat/link/getLinkUrl';
import { AutoLinkOptions } from 'roosterjs-content-model-types';
import { getLinkUrl } from '../../../lib/modelApi/link/getLinkUrl';

describe('getLinkUrl', () => {
function runTest(text: string, options: AutoLinkOptions, expectedResult: string | undefined) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import { createLinkAfterSpace } from '../../../lib/autoFormat/link/createLinkAfterSpace';
import { formatTextSegmentBeforeSelectionMarker } from 'roosterjs-content-model-api';
import { promoteLink } from '../../../lib/modelApi/link/promoteLink';
import {
ContentModelDocument,
ContentModelParagraph,
ContentModelText,
FormatContentModelContext,
} from 'roosterjs-content-model-types';

describe('createLinkAfterSpace', () => {
describe('promoteLink', () => {
function runTest(
previousSegment: ContentModelText,
paragraph: ContentModelParagraph,
context: FormatContentModelContext,
expectedResult: boolean
expectedResult: ContentModelText | null
) {
const result = createLinkAfterSpace(previousSegment, paragraph, context, {
const result = promoteLink(previousSegment, paragraph, {
autoLink: true,
autoMailto: true,
autoTel: true,
});
expect(result).toBe(expectedResult);
expect(result).toEqual(expectedResult);
}

it('with link', () => {
Expand All @@ -33,7 +31,19 @@ describe('createLinkAfterSpace', () => {
segments: [segment],
format: {},
};
runTest(segment, paragraph, { canUndoByBackspace: true } as any, true);
runTest(segment, paragraph, {
segmentType: 'Text',
text: 'http://bing.com',
isSelected: undefined,
format: {},
link: {
format: {
href: 'http://bing.com',
underline: true,
},
dataset: {},
},
});
});

it('No link', () => {
Expand All @@ -47,7 +57,7 @@ describe('createLinkAfterSpace', () => {
segments: [segment],
format: {},
};
runTest(segment, paragraph, { canUndoByBackspace: true } as any, false);
runTest(segment, paragraph, null);
});

it('with text after link ', () => {
Expand All @@ -61,7 +71,7 @@ describe('createLinkAfterSpace', () => {
segments: [segment],
format: {},
};
runTest(segment, paragraph, { canUndoByBackspace: true } as any, false);
runTest(segment, paragraph, null);
});
});

Expand All @@ -88,8 +98,8 @@ describe('formatTextSegmentBeforeSelectionMarker - createLinkAfterSpace', () =>
focus: () => {},
formatContentModel: formatWithContentModelSpy,
} as any,
(_model, previousSegment, paragraph, _markerFormat, context) => {
return createLinkAfterSpace(previousSegment, paragraph, context, {
(_model, previousSegment, paragraph, _markerFormat) => {
return !!promoteLink(previousSegment, paragraph, {
autoLink: true,
autoMailto: true,
autoTel: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ export const htmlAlignFormatHandler: FormatHandler<
DirectionFormat & HtmlAlignFormat & TextAlignFormat
> = {
parse: (format, element, context, defaultStyle) => {
directionFormatHandler.parse(format, element, context, defaultStyle);
// When there is text-align in CSS style on the same element, we should ignore HTML align
if (!element.style.textAlign) {
directionFormatHandler.parse(format, element, context, defaultStyle);

const htmlAlign = element.getAttribute('align');
const htmlAlign = element.getAttribute('align');

if (htmlAlign) {
format.htmlAlign = calcAlign(htmlAlign, format.direction);
delete format.textAlign;
delete context.blockFormat.textAlign;
if (htmlAlign) {
format.htmlAlign = calcAlign(htmlAlign, format.direction);
delete format.textAlign;
delete context.blockFormat.textAlign;
}
}
},
apply: (format, element) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function deleteExpandedSelection(
// so we can put cursor here after delete
paragraph = block;
insertMarkerIndex = indexes[0];
markerFormat = getSegmentTextFormat(segments[0]);
markerFormat = getSegmentTextFormat(segments[0], true /*includingBIU*/);

context.lastParagraph = paragraph;
context.lastTableContext = tableContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@ import type {
/**
* Get the text format of a segment, this function will return only format that is applicable to text
* @param segment The segment to get format from
* @param includingBIU When pass true, also get Bold/Italic/Underline format
* @returns
*/
export function getSegmentTextFormat(
segment: ReadonlyContentModelSegment
segment: ReadonlyContentModelSegment,
includingBIU?: boolean
): ContentModelSegmentFormat {
const { fontFamily, fontSize, textColor, backgroundColor, letterSpacing, lineHeight } =
segment?.format ?? {};

const format = segment.format ?? {};
const textFormat: ContentModelSegmentFormat = {
fontFamily,
fontSize,
textColor,
backgroundColor,
letterSpacing,
lineHeight,
fontFamily: format.fontFamily,
fontSize: format.fontSize,
textColor: format.textColor,
backgroundColor: format.backgroundColor,
letterSpacing: format.letterSpacing,
lineHeight: format.lineHeight,
fontWeight: includingBIU ? format.fontWeight : undefined,
italic: includingBIU ? format.italic : undefined,
underline: includingBIU ? format.underline : undefined,
};

return removeUndefinedValues(textFormat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import type {
const HeadingTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const KeysOfSegmentFormat = getObjectKeys(EmptySegmentFormat);

type MergeFormatTypes = 'mergeAll' | 'keepSourceEmphasisFormat' | 'preferSource' | 'preferTarget';

/**
* Merge source model into target mode
* @param target Target Content Model that will merge content into
Expand Down Expand Up @@ -333,7 +335,7 @@ function insertBlock(markerPosition: InsertPoint, block: ContentModelBlock) {
function applyDefaultFormat(
group: ReadonlyContentModelBlockGroup,
format: ContentModelSegmentFormat,
applyDefaultFormatOption: 'mergeAll' | 'keepSourceEmphasisFormat'
applyDefaultFormatOption: MergeFormatTypes
) {
group.blocks.forEach(block => {
mergeBlockFormat(applyDefaultFormatOption, block);
Expand Down Expand Up @@ -414,39 +416,51 @@ function getSegmentFormatInLinkFormat(
}

function mergeLinkFormat(
applyDefaultFormatOption: 'mergeAll' | 'keepSourceEmphasisFormat',
applyDefaultFormatOption: MergeFormatTypes,
targetFormat: ContentModelSegmentFormat,
sourceFormat: ContentModelHyperLinkFormat
) {
return applyDefaultFormatOption == 'mergeAll'
? { ...getSegmentFormatInLinkFormat(targetFormat), ...sourceFormat }
: {
// Hyperlink segment format contains other attributes such as LinkFormat
// so we have to retain them
...getFormatWithoutSegmentFormat(sourceFormat),
// Link format only have Text color, background color, Underline, but only
// text color + background color should be merged from the target
...getSegmentFormatInLinkFormat(targetFormat),
// Get the semantic format of the source
...getSemanticFormat(sourceFormat),
// The text color of the hyperlink should not be merged and
// we should always retain the source text color
...getHyperlinkTextColor(sourceFormat),
};
switch (applyDefaultFormatOption) {
case 'mergeAll':
case 'preferSource':
return { ...getSegmentFormatInLinkFormat(targetFormat), ...sourceFormat };
case 'keepSourceEmphasisFormat':
return {
// Hyperlink segment format contains other attributes such as LinkFormat
// so we have to retain them
...getFormatWithoutSegmentFormat(sourceFormat),
// Link format only have Text color, background color, Underline, but only
// text color + background color should be merged from the target
...getSegmentFormatInLinkFormat(targetFormat),
// Get the semantic format of the source
...getSemanticFormat(sourceFormat),
// The text color of the hyperlink should not be merged and
// we should always retain the source text color
...getHyperlinkTextColor(sourceFormat),
};
case 'preferTarget':
return { ...sourceFormat, ...getSegmentFormatInLinkFormat(targetFormat) };
}
}

function mergeSegmentFormat(
applyDefaultFormatOption: 'mergeAll' | 'keepSourceEmphasisFormat',
applyDefaultFormatOption: MergeFormatTypes,
targetFormat: ContentModelSegmentFormat,
sourceFormat: ContentModelSegmentFormat
): ContentModelSegmentFormat {
return applyDefaultFormatOption == 'mergeAll'
? { ...targetFormat, ...sourceFormat }
: {
...getFormatWithoutSegmentFormat(sourceFormat),
...targetFormat,
...getSemanticFormat(sourceFormat),
};
switch (applyDefaultFormatOption) {
case 'mergeAll':
case 'preferSource':
return { ...targetFormat, ...sourceFormat };
case 'preferTarget':
return { ...sourceFormat, ...targetFormat };
case 'keepSourceEmphasisFormat':
return {
...getFormatWithoutSegmentFormat(sourceFormat),
...targetFormat,
...getSemanticFormat(sourceFormat),
};
}
}

function getSemanticFormat(segmentFormat: ContentModelSegmentFormat): ContentModelSegmentFormat {
Expand Down
27 changes: 27 additions & 0 deletions packages/roosterjs-content-model-dom/test/endToEndTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2199,4 +2199,31 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => {
'<div style="font-family: Calibri; font-size: 11pt; color: rgb(245, 212, 39);"><a href="http://www.bing.com" style="color: rgb(245, 212, 39);">www.bing.com</a></div>'
);
});

it('HTML align together with CSS text-align', () => {
runTest(
'<div align="left" style="text-align:center">test</div>',
{
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'test',
format: {},
},
],
format: {
textAlign: 'center',
},
isImplicit: false,
},
],
},
'test',
'<div style="text-align: center;">test</div>'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ describe('htmlAlignFormatHandler.parse', () => {
htmlAlign: 'start',
});
});

it('Ignore HTML align when there is CSS text-align', () => {
div.setAttribute('align', 'left');
div.style.textAlign = 'center';

htmlAlignFormatHandler.parse(format, div, context, {});

expect(format.htmlAlign).toBeUndefined();
});
});

describe('htmlAlignFormatHandler.apply', () => {
Expand Down
Loading

0 comments on commit 1ac4373

Please sign in to comment.