mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 14:27:02 +08:00
117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
|
import { isStrictUrl } from '@blocksuite/affine-shared/utils';
|
|
import type {
|
|
BeforeinputHookCtx,
|
|
CompositionEndHookCtx,
|
|
HookContext,
|
|
} from '@blocksuite/block-std/inline';
|
|
|
|
const EDGE_IGNORED_ATTRIBUTES = ['code', 'link'] as const;
|
|
const GLOBAL_IGNORED_ATTRIBUTES = [] as const;
|
|
|
|
const autoIdentifyLink = (ctx: HookContext<AffineTextAttributes>) => {
|
|
// auto identify link only on pressing space
|
|
if (ctx.data !== ' ') {
|
|
return;
|
|
}
|
|
|
|
// space is typed at the end of link, remove the link attribute on typed space
|
|
if (ctx.attributes?.link) {
|
|
if (ctx.inlineRange.index === ctx.inlineEditor.yText.length) {
|
|
delete ctx.attributes['link'];
|
|
}
|
|
return;
|
|
}
|
|
|
|
const lineInfo = ctx.inlineEditor.getLine(ctx.inlineRange.index);
|
|
if (!lineInfo) {
|
|
return;
|
|
}
|
|
const { line, lineIndex, rangeIndexRelatedToLine } = lineInfo;
|
|
|
|
if (lineIndex !== 0) {
|
|
return;
|
|
}
|
|
|
|
const verifyData = line.vTextContent
|
|
.slice(0, rangeIndexRelatedToLine)
|
|
.split(' ');
|
|
|
|
const verifyStr = verifyData[verifyData.length - 1];
|
|
|
|
const isUrl = isStrictUrl(verifyStr);
|
|
|
|
if (!isUrl) {
|
|
return;
|
|
}
|
|
|
|
const startIndex = ctx.inlineRange.index - verifyStr.length;
|
|
|
|
ctx.inlineEditor.formatText(
|
|
{
|
|
index: startIndex,
|
|
length: verifyStr.length,
|
|
},
|
|
{
|
|
link: verifyStr,
|
|
}
|
|
);
|
|
};
|
|
|
|
function handleExtendedAttributes(
|
|
ctx:
|
|
| BeforeinputHookCtx<AffineTextAttributes>
|
|
| CompositionEndHookCtx<AffineTextAttributes>
|
|
) {
|
|
const { data, inlineEditor, inlineRange } = ctx;
|
|
const deltas = inlineEditor.getDeltasByInlineRange(inlineRange);
|
|
// eslint-disable-next-line sonarjs/no-collapsible-if
|
|
if (data && data.length > 0 && data !== '\n') {
|
|
if (
|
|
// cursor is in the between of two deltas
|
|
(deltas.length > 1 ||
|
|
// cursor is in the end of line or in the middle of a delta
|
|
(deltas.length === 1 && inlineRange.index !== 0)) &&
|
|
!inlineEditor.isEmbed(deltas[0][0]) // embeds should not be extended
|
|
) {
|
|
// each new text inserted by inline editor will not contain any attributes,
|
|
// but we want to keep the attributes of previous text or current text where the cursor is in
|
|
// here are two cases:
|
|
// 1. aaa**b|bb**ccc --input 'd'--> aaa**bdbb**ccc, d should extend the bold attribute
|
|
// 2. aaa**bbb|**ccc --input 'd'--> aaa**bbbd**ccc, d should extend the bold attribute
|
|
const { attributes } = deltas[0][0];
|
|
if (
|
|
deltas.length !== 1 ||
|
|
inlineRange.index === inlineEditor.yText.length
|
|
) {
|
|
// `EDGE_IGNORED_ATTRIBUTES` is which attributes should be ignored in case 2
|
|
EDGE_IGNORED_ATTRIBUTES.forEach(attr => {
|
|
delete attributes?.[attr];
|
|
});
|
|
}
|
|
|
|
// `GLOBAL_IGNORED_ATTRIBUTES` is which attributes should be ignored in case 1, 2
|
|
GLOBAL_IGNORED_ATTRIBUTES.forEach(attr => {
|
|
delete attributes?.[attr];
|
|
});
|
|
|
|
ctx.attributes = attributes ?? {};
|
|
}
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
export const onVBeforeinput = (
|
|
ctx: BeforeinputHookCtx<AffineTextAttributes>
|
|
) => {
|
|
handleExtendedAttributes(ctx);
|
|
autoIdentifyLink(ctx);
|
|
};
|
|
|
|
export const onVCompositionEnd = (
|
|
ctx: CompositionEndHookCtx<AffineTextAttributes>
|
|
) => {
|
|
handleExtendedAttributes(ctx);
|
|
};
|