Skip to content

Commit

Permalink
Merge pull request #2798 from microsoft/u/juliaroldi/link-enhancements
Browse files Browse the repository at this point in the history
Auto link enhancements
  • Loading branch information
juliaroldi authored Sep 23, 2024
2 parents 5d08bab + 1424e67 commit e3bc1f3
Show file tree
Hide file tree
Showing 13 changed files with 1,176 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const initialState: OptionState = {
autoHyphen: true,
autoFraction: true,
autoOrdinals: true,
autoMailto: true,
autoTel: true,
},
markdownOptions: {
bold: true,
Expand Down
14 changes: 14 additions & 0 deletions demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export class Plugins extends PluginsBase<keyof BuildInPluginList> {
private autoHyphen = React.createRef<HTMLInputElement>();
private autoFraction = React.createRef<HTMLInputElement>();
private autoOrdinals = React.createRef<HTMLInputElement>();
private autoTel = React.createRef<HTMLInputElement>();
private autoMailto = React.createRef<HTMLInputElement>();
private markdownBold = React.createRef<HTMLInputElement>();
private markdownItalic = React.createRef<HTMLInputElement>();
private markdownStrikethrough = React.createRef<HTMLInputElement>();
Expand Down Expand Up @@ -166,6 +168,18 @@ export class Plugins extends PluginsBase<keyof BuildInPluginList> {
this.props.state.autoFormatOptions.autoOrdinals,
(state, value) => (state.autoFormatOptions.autoOrdinals = value)
)}
{this.renderCheckBox(
'Telephone',
this.autoTel,
this.props.state.autoFormatOptions.autoTel,
(state, value) => (state.autoFormatOptions.autoTel = value)
)}
{this.renderCheckBox(
'Email',
this.autoMailto,
this.props.state.autoFormatOptions.autoMailto,
(state, value) => (state.autoFormatOptions.autoMailto = value)
)}
</>
)}
{this.renderPluginItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { transformFraction } from './numbers/transformFraction';
import { transformHyphen } from './hyphen/transformHyphen';
import { transformOrdinals } from './numbers/transformOrdinals';
import { unlink } from './link/unlink';
import type { AutoFormatOptions } from './interface/AutoFormatOptions';
import type {
ContentChangedEvent,
EditorInputEvent,
Expand All @@ -17,46 +18,6 @@ import type {
PluginEvent,
} from 'roosterjs-content-model-types';

/**
* Options to customize the Content Model Auto Format Plugin
*/
export type AutoFormatOptions = {
/**
* When true, after type *, ->, -, --, => , —, > and space key a type of bullet list will be triggered. @default true
*/
autoBullet?: boolean;

/**
* When true, after type 1, A, a, i, I followed by ., ), - or between () and space key a type of numbering list will be triggered. @default true
*/
autoNumbering?: boolean;

/**
* When press backspace before a link, remove the hyperlink
*/
autoUnlink?: boolean;

/**
* When paste content, create hyperlink for the pasted link
*/
autoLink?: boolean;

/**
* Transform -- into hyphen, if typed between two words
*/
autoHyphen?: boolean;

/**
* Transform 1/2, 1/4, 3/4 into fraction character
*/
autoFraction?: boolean;

/**
* Transform ordinal numbers into superscript
*/
autoOrdinals?: boolean;
};

/**
* @internal
*/
Expand All @@ -80,11 +41,13 @@ export class AutoFormatPlugin implements EditorPlugin {
* @param options An optional parameter that takes in an object of type AutoFormatOptions, which includes the following properties:
* - autoBullet: A boolean that enables or disables automatic bullet list formatting. Defaults to false.
* - autoNumbering: A boolean that enables or disables automatic numbering formatting. Defaults to false.
* - autoLink: A boolean that enables or disables automatic hyperlink creation when pasting or typing content. Defaults to false.
* - autoUnlink: A boolean that enables or disables automatic hyperlink removal when pressing backspace. Defaults to false.
* - autoHyphen: A boolean that enables or disables automatic hyphen transformation. Defaults to false.
* - autoFraction: A boolean that enables or disables automatic fraction transformation. Defaults to false.
* - autoOrdinals: A boolean that enables or disables automatic ordinal number transformation. Defaults to false.
* - autoLink: A boolean that enables or disables automatic hyperlink url address creation when pasting or typing content. Defaults to false.
* - autoUnlink: A boolean that enables or disables automatic hyperlink removal when pressing backspace. Defaults to false.
* - autoTel: A boolean that enables or disables automatic hyperlink telephone numbers transformation. Defaults to false.
* - autoMailto: A boolean that enables or disables automatic hyperlink email address transformation. Defaults to false.
*/
constructor(private options: AutoFormatOptions = DefaultOptions) {}

Expand Down Expand Up @@ -161,6 +124,8 @@ export class AutoFormatPlugin implements EditorPlugin {
autoHyphen,
autoFraction,
autoOrdinals,
autoMailto,
autoTel,
} = this.options;
let shouldHyphen = false;
let shouldLink = false;
Expand All @@ -178,11 +143,16 @@ export class AutoFormatPlugin implements EditorPlugin {
);
}

if (autoLink) {
if (autoLink || autoTel || autoMailto) {
shouldLink = createLinkAfterSpace(
previousSegment,
paragraph,
context
context,
{
autoLink,
autoTel,
autoMailto,
}
);
}

Expand Down Expand Up @@ -243,9 +213,13 @@ export class AutoFormatPlugin implements EditorPlugin {
}

private handleContentChangedEvent(editor: IEditor, event: ContentChangedEvent) {
const { autoLink } = this.options;
if (event.source == 'Paste' && autoLink) {
createLink(editor);
const { autoLink, autoTel, autoMailto } = this.options;
if (event.source == 'Paste' && (autoLink || autoTel || autoMailto)) {
createLink(editor, {
autoLink,
autoTel,
autoMailto,
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { AutoLinkOptions } from './AutoLinkOptions';

/**
* Options to customize the Content Model Auto Format Plugin
*/
export interface AutoFormatOptions extends AutoLinkOptions {
/**
* When true, after type *, ->, -, --, => , —, > and space key a type of bullet list will be triggered.
*/
autoBullet?: boolean;

/**
* When true, after type 1, A, a, i, I followed by ., ), - or between () and space key a type of numbering list will be triggered.
*/
autoNumbering?: boolean;

/**
* Transform -- into hyphen, if typed between two words
*/
autoHyphen?: boolean;

/**
* Transform 1/2, 1/4, 3/4 into fraction character
*/
autoFraction?: boolean;

/**
* Transform ordinal numbers into superscript
*/
autoOrdinals?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Options to customize the Auto link options in Auto Format Plugin
*/
export interface AutoLinkOptions {
/**
* When press backspace before a link, remove the hyperlink
*/
autoUnlink?: boolean;

/**
* When paste or type content with a link, create hyperlink for the link
*/
autoLink?: boolean;

/**
* When paste content or type content with telephone, create hyperlink for the telephone number
*/
autoTel?: boolean;

/**
* When paste or type a content with mailto, create hyperlink for the content
*/
autoMailto?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { addLink, ChangeSource } from 'roosterjs-content-model-dom';
import { formatTextSegmentBeforeSelectionMarker, matchLink } from 'roosterjs-content-model-api';
import type { ContentModelLink, IEditor, LinkData } from 'roosterjs-content-model-types';
import { formatTextSegmentBeforeSelectionMarker } from 'roosterjs-content-model-api';
import { getLinkUrl } from './getLinkUrl';
import type { AutoLinkOptions } from '../interface/AutoLinkOptions';
import type { ContentModelLink, IEditor } from 'roosterjs-content-model-types';

/**
* @internal
*/
export function createLink(editor: IEditor) {
export function createLink(editor: IEditor, autoLinkOptions: AutoLinkOptions) {
let anchorNode: Node | null = null;
const links: ContentModelLink[] = [];
formatTextSegmentBeforeSelectionMarker(
Expand All @@ -15,11 +17,11 @@ export function createLink(editor: IEditor) {
links.push(linkSegment.link);
return true;
}
let linkData: LinkData | null = null;
if (!linkSegment.link && (linkData = matchLink(linkSegment.text))) {
let linkUrl: string | undefined = undefined;
if (!linkSegment.link && (linkUrl = getLinkUrl(linkSegment.text, autoLinkOptions))) {
addLink(linkSegment, {
format: {
href: linkData.normalizedUrl,
href: linkUrl,
underline: true,
},
dataset: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { matchLink, splitTextSegment } from 'roosterjs-content-model-api';
import { getLinkUrl } from './getLinkUrl';
import { splitTextSegment } from 'roosterjs-content-model-api';
import type { AutoLinkOptions } from '../interface/AutoLinkOptions';
import type {
ContentModelText,
FormatContentModelContext,
LinkData,
ShallowMutableContentModelParagraph,
} from 'roosterjs-content-model-types';

Expand All @@ -12,12 +13,13 @@ import type {
export function createLinkAfterSpace(
previousSegment: ContentModelText,
paragraph: ShallowMutableContentModelParagraph,
context: FormatContentModelContext
context: FormatContentModelContext,
autoLinkOptions: AutoLinkOptions
) {
const link = previousSegment.text.split(' ').pop();
const url = link?.trim();
let linkData: LinkData | null = null;
if (url && link && (linkData = matchLink(url))) {
let linkUrl: string | undefined = undefined;
if (url && link && (linkUrl = getLinkUrl(url, autoLinkOptions))) {
const linkSegment = splitTextSegment(
previousSegment,
paragraph,
Expand All @@ -26,7 +28,7 @@ export function createLinkAfterSpace(
);
linkSegment.link = {
format: {
href: linkData.normalizedUrl,
href: linkUrl,
underline: true,
},
dataset: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { matchLink } from 'roosterjs-content-model-api';
import type { AutoLinkOptions } from '../interface/AutoLinkOptions';

const COMMON_REGEX = `[\s]*[a-zA-Z0-9+][\s]*`;
const TELEPHONE_REGEX = `(T|t)el:${COMMON_REGEX}`;
const MAILTO_REGEX = `(M|m)ailto:${COMMON_REGEX}`;

/**
* @internal
*/
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;

return linkMatch || telMatch || mailtoMatch;
}

function matchTel(text: string) {
return text.match(TELEPHONE_REGEX) ? text.toLocaleLowerCase() : undefined;
}

function matchMailTo(text: string) {
return text.match(MAILTO_REGEX) ? text.toLocaleLowerCase() : undefined;
}
4 changes: 3 additions & 1 deletion packages/roosterjs-content-model-plugins/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export { TableEditFeatureName } from './tableEdit/editors/features/TableEditFeat
export { PastePlugin } from './paste/PastePlugin';
export { DefaultSanitizers } from './paste/DefaultSanitizers';
export { EditPlugin, EditOptions } from './edit/EditPlugin';
export { AutoFormatPlugin, AutoFormatOptions } from './autoFormat/AutoFormatPlugin';
export { AutoFormatPlugin } from './autoFormat/AutoFormatPlugin';
export { AutoFormatOptions } from './autoFormat/interface/AutoFormatOptions';
export { AutoLinkOptions } from './autoFormat/interface/AutoLinkOptions';

export {
ShortcutBold,
Expand Down
Loading

0 comments on commit e3bc1f3

Please sign in to comment.