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

Auto link enhancements #2798

Merged
merged 8 commits into from
Sep 23, 2024
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
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
Loading