mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-04 19:15:33 +08:00
refactor(editor): remove inline editor keyboard utils and add markdown property in rich-text (#10375)
This commit is contained in:
@@ -127,7 +127,7 @@ export class BlockRenderer
|
|||||||
.attributesSchema=${this.attributesSchema}
|
.attributesSchema=${this.attributesSchema}
|
||||||
.attributeRenderer=${this.attributeRenderer}
|
.attributeRenderer=${this.attributeRenderer}
|
||||||
.embedChecker=${this.inlineManager.embedChecker}
|
.embedChecker=${this.inlineManager.embedChecker}
|
||||||
.markdownShortcutHandler=${this.inlineManager.markdownShortcutHandler}
|
.markdownMatches=${this.inlineManager.markdownMatches}
|
||||||
class="inline-editor"
|
class="inline-editor"
|
||||||
></rich-text>
|
></rich-text>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export class RichTextCell extends BaseRichTextCell {
|
|||||||
.attributesSchema=${this.attributesSchema}
|
.attributesSchema=${this.attributesSchema}
|
||||||
.attributeRenderer=${this.attributeRenderer}
|
.attributeRenderer=${this.attributeRenderer}
|
||||||
.embedChecker=${this.inlineManager?.embedChecker}
|
.embedChecker=${this.inlineManager?.embedChecker}
|
||||||
.markdownShortcutHandler=${this.inlineManager?.markdownShortcutHandler}
|
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||||
.readonly=${true}
|
.readonly=${true}
|
||||||
class="affine-database-rich-text inline-editor"
|
class="affine-database-rich-text inline-editor"
|
||||||
></rich-text>`
|
></rich-text>`
|
||||||
@@ -525,7 +525,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
|||||||
.attributesSchema=${this.attributesSchema}
|
.attributesSchema=${this.attributesSchema}
|
||||||
.attributeRenderer=${this.attributeRenderer}
|
.attributeRenderer=${this.attributeRenderer}
|
||||||
.embedChecker=${this.inlineManager?.embedChecker}
|
.embedChecker=${this.inlineManager?.embedChecker}
|
||||||
.markdownShortcutHandler=${this.inlineManager?.markdownShortcutHandler}
|
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||||
.verticalScrollContainerGetter=${() =>
|
.verticalScrollContainerGetter=${() =>
|
||||||
this.topContenteditableElement?.host
|
this.topContenteditableElement?.host
|
||||||
? getViewportElement(this.topContenteditableElement.host)
|
? getViewportElement(this.topContenteditableElement.host)
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ export class HeaderAreaTextCell extends BaseTextCell {
|
|||||||
.attributesSchema="${this.attributesSchema}"
|
.attributesSchema="${this.attributesSchema}"
|
||||||
.attributeRenderer="${this.attributeRenderer}"
|
.attributeRenderer="${this.attributeRenderer}"
|
||||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||||
.markdownShortcutHandler="${this.inlineManager?.markdownShortcutHandler}"
|
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||||
.readonly="${true}"
|
.readonly="${true}"
|
||||||
class="data-view-header-area-rich-text"
|
class="data-view-header-area-rich-text"
|
||||||
></rich-text>`;
|
></rich-text>`;
|
||||||
@@ -391,7 +391,7 @@ export class HeaderAreaTextCellEditing extends BaseTextCell {
|
|||||||
.attributesSchema="${this.attributesSchema}"
|
.attributesSchema="${this.attributesSchema}"
|
||||||
.attributeRenderer="${this.attributeRenderer}"
|
.attributeRenderer="${this.attributeRenderer}"
|
||||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||||
.markdownShortcutHandler="${this.inlineManager?.markdownShortcutHandler}"
|
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||||
.readonly="${this.readonly}"
|
.readonly="${this.readonly}"
|
||||||
.enableClipboard="${false}"
|
.enableClipboard="${false}"
|
||||||
.verticalScrollContainerGetter="${() =>
|
.verticalScrollContainerGetter="${() =>
|
||||||
|
|||||||
@@ -85,10 +85,6 @@ export class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel>
|
|||||||
return this.std.get(DefaultInlineManagerExtension.identifier);
|
return this.std.get(DefaultInlineManagerExtension.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
get markdownShortcutHandler() {
|
|
||||||
return this.inlineManager.markdownShortcutHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get topContenteditableElement() {
|
override get topContenteditableElement() {
|
||||||
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
||||||
return this.closest<BlockComponent>(NOTE_SELECTOR);
|
return this.closest<BlockComponent>(NOTE_SELECTOR);
|
||||||
@@ -193,7 +189,7 @@ export class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel>
|
|||||||
.undoManager=${this.doc.history}
|
.undoManager=${this.doc.history}
|
||||||
.attributeRenderer=${this.attributeRenderer}
|
.attributeRenderer=${this.attributeRenderer}
|
||||||
.attributesSchema=${this.attributesSchema}
|
.attributesSchema=${this.attributesSchema}
|
||||||
.markdownShortcutHandler=${this.markdownShortcutHandler}
|
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||||
.embedChecker=${this.embedChecker}
|
.embedChecker=${this.embedChecker}
|
||||||
.readonly=${this.doc.readonly}
|
.readonly=${this.doc.readonly}
|
||||||
.inlineRangeProvider=${this._inlineRangeProvider}
|
.inlineRangeProvider=${this._inlineRangeProvider}
|
||||||
|
|||||||
@@ -90,10 +90,6 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<
|
|||||||
return this.std.get(DefaultInlineManagerExtension.identifier);
|
return this.std.get(DefaultInlineManagerExtension.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
get markdownShortcutHandler() {
|
|
||||||
return this.inlineManager.markdownShortcutHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get topContenteditableElement() {
|
override get topContenteditableElement() {
|
||||||
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
||||||
return this.closest<BlockComponent>(NOTE_SELECTOR);
|
return this.closest<BlockComponent>(NOTE_SELECTOR);
|
||||||
@@ -294,7 +290,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<
|
|||||||
.undoManager=${this.doc.history}
|
.undoManager=${this.doc.history}
|
||||||
.attributesSchema=${this.attributesSchema}
|
.attributesSchema=${this.attributesSchema}
|
||||||
.attributeRenderer=${this.attributeRenderer}
|
.attributeRenderer=${this.attributeRenderer}
|
||||||
.markdownShortcutHandler=${this.markdownShortcutHandler}
|
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||||
.embedChecker=${this.embedChecker}
|
.embedChecker=${this.embedChecker}
|
||||||
.readonly=${this.doc.readonly}
|
.readonly=${this.doc.readonly}
|
||||||
.inlineRangeProvider=${this._inlineRangeProvider}
|
.inlineRangeProvider=${this._inlineRangeProvider}
|
||||||
|
|||||||
@@ -745,8 +745,7 @@ export class TableCell extends SignalWatcher(
|
|||||||
.attributesSchema="${this.inlineManager?.getSchema()}"
|
.attributesSchema="${this.inlineManager?.getSchema()}"
|
||||||
.attributeRenderer="${this.inlineManager?.getRenderer()}"
|
.attributeRenderer="${this.inlineManager?.getRenderer()}"
|
||||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||||
.markdownShortcutHandler="${this.inlineManager
|
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||||
?.markdownShortcutHandler}"
|
|
||||||
.readonly="${this.readonly}"
|
.readonly="${this.readonly}"
|
||||||
.enableClipboard="${true}"
|
.enableClipboard="${true}"
|
||||||
.verticalScrollContainerGetter="${() =>
|
.verticalScrollContainerGetter="${() =>
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ import {
|
|||||||
baseTextAttributes,
|
baseTextAttributes,
|
||||||
type DeltaInsert,
|
type DeltaInsert,
|
||||||
getDefaultAttributeRenderer,
|
getDefaultAttributeRenderer,
|
||||||
KEYBOARD_ALLOW_DEFAULT,
|
|
||||||
type KeyboardBindingContext,
|
|
||||||
} from '@blocksuite/inline';
|
} from '@blocksuite/inline';
|
||||||
import type { ExtensionType } from '@blocksuite/store';
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
import type * as Y from 'yjs';
|
|
||||||
import { z, type ZodObject, type ZodTypeAny } from 'zod';
|
import { z, type ZodObject, type ZodTypeAny } from 'zod';
|
||||||
|
|
||||||
import { MarkdownMatcherIdentifier } from './markdown-matcher.js';
|
import { MarkdownMatcherIdentifier } from './markdown-matcher.js';
|
||||||
@@ -61,27 +58,6 @@ export class InlineManager {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
markdownShortcutHandler = (
|
|
||||||
context: KeyboardBindingContext<AffineTextAttributes>,
|
|
||||||
undoManager: Y.UndoManager
|
|
||||||
) => {
|
|
||||||
const { inlineEditor, prefixText, inlineRange } = context;
|
|
||||||
for (const match of this.markdownMatches) {
|
|
||||||
const matchedText = prefixText.match(match.pattern);
|
|
||||||
if (matchedText) {
|
|
||||||
return match.action({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern: match.pattern,
|
|
||||||
undoManager,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
};
|
|
||||||
|
|
||||||
readonly specs: Array<InlineSpecs<AffineTextAttributes>>;
|
readonly specs: Array<InlineSpecs<AffineTextAttributes>>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type {
|
|||||||
DeltaInsert,
|
DeltaInsert,
|
||||||
InlineEditor,
|
InlineEditor,
|
||||||
InlineRange,
|
InlineRange,
|
||||||
KeyboardBindingHandler,
|
|
||||||
} from '@blocksuite/inline';
|
} from '@blocksuite/inline';
|
||||||
import type * as Y from 'yjs';
|
import type * as Y from 'yjs';
|
||||||
import type { ZodTypeAny } from 'zod';
|
import type { ZodTypeAny } from 'zod';
|
||||||
@@ -28,7 +27,7 @@ export type InlineMarkdownMatchAction<
|
|||||||
inlineRange: InlineRange;
|
inlineRange: InlineRange;
|
||||||
pattern: RegExp;
|
pattern: RegExp;
|
||||||
undoManager: Y.UndoManager;
|
undoManager: Y.UndoManager;
|
||||||
}) => ReturnType<KeyboardBindingHandler>;
|
}) => void;
|
||||||
|
|
||||||
export type InlineMarkdownMatch<
|
export type InlineMarkdownMatch<
|
||||||
AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import type { BlockComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
import {
|
|
||||||
KEYBOARD_ALLOW_DEFAULT,
|
|
||||||
KEYBOARD_PREVENT_DEFAULT,
|
|
||||||
} from '@blocksuite/inline';
|
|
||||||
import type { ExtensionType } from '@blocksuite/store';
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
import { InlineMarkdownExtension } from '../../extension/markdown-matcher.js';
|
import { InlineMarkdownExtension } from '../../extension/markdown-matcher.js';
|
||||||
@@ -16,14 +12,13 @@ import { InlineMarkdownExtension } from '../../extension/markdown-matcher.js';
|
|||||||
|
|
||||||
export const BoldItalicMarkdown = InlineMarkdownExtension({
|
export const BoldItalicMarkdown = InlineMarkdownExtension({
|
||||||
name: 'bolditalic',
|
name: 'bolditalic',
|
||||||
pattern: /(?:\*\*\*)([^\s*](?:[^*]*?[^\s*])?)(?:\*\*\*)$/g,
|
pattern: /.*\*{3}([^\s*][^*]*[^\s*])\*{3}$|.*\*{3}([^\s*])\*{3}$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const match = pattern.exec(prefixText);
|
const match = prefixText.match(pattern);
|
||||||
if (!match) {
|
if (!match) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const annotatedText = match[0];
|
const targetText = match[1] ?? match[2];
|
||||||
|
const annotatedText = match[0].slice(-targetText.length - 3 * 2);
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
@@ -68,20 +63,18 @@ export const BoldItalicMarkdown = InlineMarkdownExtension({
|
|||||||
index: startIndex + annotatedText.length - 6,
|
index: startIndex + annotatedText.length - 6,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BoldMarkdown = InlineMarkdownExtension({
|
export const BoldMarkdown = InlineMarkdownExtension({
|
||||||
name: 'bold',
|
name: 'bold',
|
||||||
pattern: /(?:\*\*)([^\s*](?:[^*]*?[^\s*])?)(?:\*\*)$/g,
|
pattern: /.*\*{2}([^\s][^*]*[^\s*])\*{2}$|.*\*{2}([^\s*])\*{2}$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const match = pattern.exec(prefixText);
|
const match = prefixText.match(pattern);
|
||||||
if (!match) {
|
if (!match) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
const targetText = match[1] ?? match[2];
|
||||||
const annotatedText = match[0];
|
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
@@ -125,20 +118,18 @@ export const BoldMarkdown = InlineMarkdownExtension({
|
|||||||
index: startIndex + annotatedText.length - 4,
|
index: startIndex + annotatedText.length - 4,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ItalicExtension = InlineMarkdownExtension({
|
export const ItalicExtension = InlineMarkdownExtension({
|
||||||
name: 'italic',
|
name: 'italic',
|
||||||
pattern: /(?:\*)([^\s*](?:[^*]*?[^\s*])?)(?:\*)$/g,
|
pattern: /.*\*{1}([^\s][^*]*[^\s*])\*{1}$|.*\*{1}([^\s*])\*{1}$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const match = pattern.exec(prefixText);
|
const match = prefixText.match(pattern);
|
||||||
if (!match) {
|
if (!match) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
const targetText = match[1] ?? match[2];
|
||||||
const annotatedText = match[0];
|
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
@@ -182,20 +173,18 @@ export const ItalicExtension = InlineMarkdownExtension({
|
|||||||
index: startIndex + annotatedText.length - 2,
|
index: startIndex + annotatedText.length - 2,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StrikethroughExtension = InlineMarkdownExtension({
|
export const StrikethroughExtension = InlineMarkdownExtension({
|
||||||
name: 'strikethrough',
|
name: 'strikethrough',
|
||||||
pattern: /(?:~~)([^\s~](?:[^~]*?[^\s~])?)(?:~~)$/g,
|
pattern: /.*~{2}([^\s][^~]*[^\s])~{2}$|.*~{2}([^\s~])~{2}$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const match = pattern.exec(prefixText);
|
const match = prefixText.match(pattern);
|
||||||
if (!match) {
|
if (!match) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
const targetText = match[1] ?? match[2];
|
||||||
const annotatedText = match[0];
|
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
@@ -239,20 +228,18 @@ export const StrikethroughExtension = InlineMarkdownExtension({
|
|||||||
index: startIndex + annotatedText.length - 4,
|
index: startIndex + annotatedText.length - 4,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UnderthroughExtension = InlineMarkdownExtension({
|
export const UnderthroughExtension = InlineMarkdownExtension({
|
||||||
name: 'underthrough',
|
name: 'underthrough',
|
||||||
pattern: /(?:~)([^\s~](?:[^~]*?[^\s~])?)(?:~)$/g,
|
pattern: /.*~{1}([^\s][^~]*[^\s~])~{1}$|.*~{1}([^\s~])~{1}$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const match = pattern.exec(prefixText);
|
const match = prefixText.match(pattern);
|
||||||
if (!match) {
|
if (!match) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
const targetText = match[1] ?? match[2];
|
||||||
const annotatedText = match[0];
|
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
@@ -296,25 +283,19 @@ export const UnderthroughExtension = InlineMarkdownExtension({
|
|||||||
index: startIndex + annotatedText.length - 2,
|
index: startIndex + annotatedText.length - 2,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CodeExtension = InlineMarkdownExtension({
|
export const CodeExtension = InlineMarkdownExtension({
|
||||||
name: 'code',
|
name: 'code',
|
||||||
pattern: /(?:`)([^\s`](?:[^`]*?[^\s`])?)(?:`)$/g,
|
pattern: /.*`([^\s][^`]*[^\s])`$|.*`([^\s`])`$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const match = pattern.exec(prefixText);
|
const match = prefixText.match(pattern);
|
||||||
if (!match) {
|
if (!match) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
const annotatedText = match[0];
|
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
|
||||||
|
|
||||||
if (prefixText.match(/^([* \n]+)$/g)) {
|
const targetText = match[1] ?? match[2];
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
|
||||||
}
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
{
|
{
|
||||||
@@ -357,23 +338,20 @@ export const CodeExtension = InlineMarkdownExtension({
|
|||||||
index: startIndex + annotatedText.length - 2,
|
index: startIndex + annotatedText.length - 2,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LinkExtension = InlineMarkdownExtension({
|
export const LinkExtension = InlineMarkdownExtension({
|
||||||
name: 'link',
|
name: 'link',
|
||||||
pattern: /(?:\[(.+?)\])(?:\((.+?)\))$/g,
|
pattern: /.*\[(.+?)\]\((.+?)\)$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const startIndex = prefixText.search(pattern);
|
const match = prefixText.match(pattern);
|
||||||
const matchedText = prefixText.match(pattern)?.[0];
|
if (!match) return;
|
||||||
const hrefText = prefixText.match(/(?:\[(.*?)\])/g)?.[0];
|
|
||||||
const hrefLink = prefixText.match(/(?:\((.*?)\))/g)?.[0];
|
const linkText = match[1];
|
||||||
if (startIndex === -1 || !matchedText || !hrefText || !hrefLink) {
|
const linkUrl = match[2];
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
const annotatedText = match[0].slice(-linkText.length - linkUrl.length - 4);
|
||||||
}
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
const start = inlineRange.index - matchedText.length;
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
{
|
{
|
||||||
@@ -389,35 +367,37 @@ export const LinkExtension = InlineMarkdownExtension({
|
|||||||
|
|
||||||
undoManager.stopCapturing();
|
undoManager.stopCapturing();
|
||||||
|
|
||||||
|
// aaa[bbb](baidu.com) + space
|
||||||
|
|
||||||
|
// delete (baidu.com) + space
|
||||||
|
inlineEditor.deleteText({
|
||||||
|
index: startIndex + 1 + linkText.length + 1,
|
||||||
|
length: 1 + linkUrl.length + 1 + 1,
|
||||||
|
});
|
||||||
|
// delete [ and ]
|
||||||
|
inlineEditor.deleteText({
|
||||||
|
index: startIndex + 1 + linkText.length,
|
||||||
|
length: 1,
|
||||||
|
});
|
||||||
|
inlineEditor.deleteText({
|
||||||
|
index: startIndex,
|
||||||
|
length: 1,
|
||||||
|
});
|
||||||
|
|
||||||
inlineEditor.formatText(
|
inlineEditor.formatText(
|
||||||
{
|
{
|
||||||
index: start,
|
index: startIndex,
|
||||||
length: hrefText.length,
|
length: linkText.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: hrefLink.slice(1, hrefLink.length - 1),
|
link: linkUrl,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: inlineRange.index + matchedText.length,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: inlineRange.index - hrefLink.length - 1,
|
|
||||||
length: hrefLink.length + 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: start,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
inlineEditor.setInlineRange({
|
||||||
index: start + hrefText.length - 1,
|
index: startIndex + linkText.length,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -428,9 +408,7 @@ export const LatexExtension = InlineMarkdownExtension({
|
|||||||
/(?:\$\$)(?<content>[^$]+)(?:\$\$)$|(?<blockPrefix>\$\$\$\$)|(?<inlinePrefix>\$\$)$/g,
|
/(?:\$\$)(?<content>[^$]+)(?:\$\$)$|(?<blockPrefix>\$\$\$\$)|(?<inlinePrefix>\$\$)$/g,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
const match = pattern.exec(prefixText);
|
const match = pattern.exec(prefixText);
|
||||||
if (!match || !match.groups) {
|
if (!match || !match.groups) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
const content = match.groups['content'];
|
const content = match.groups['content'];
|
||||||
const inlinePrefix = match.groups['inlinePrefix'];
|
const inlinePrefix = match.groups['inlinePrefix'];
|
||||||
const blockPrefix = match.groups['blockPrefix'];
|
const blockPrefix = match.groups['blockPrefix'];
|
||||||
@@ -450,19 +428,19 @@ export const LatexExtension = InlineMarkdownExtension({
|
|||||||
|
|
||||||
undoManager.stopCapturing();
|
undoManager.stopCapturing();
|
||||||
|
|
||||||
if (!inlineEditor.rootElement) return KEYBOARD_ALLOW_DEFAULT;
|
if (!inlineEditor.rootElement) return;
|
||||||
const blockComponent =
|
const blockComponent =
|
||||||
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
|
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
|
||||||
if (!blockComponent) return KEYBOARD_ALLOW_DEFAULT;
|
if (!blockComponent) return;
|
||||||
|
|
||||||
const doc = blockComponent.doc;
|
const doc = blockComponent.doc;
|
||||||
const parentComponent = blockComponent.parentComponent;
|
const parentComponent = blockComponent.parentComponent;
|
||||||
if (!parentComponent) return KEYBOARD_ALLOW_DEFAULT;
|
if (!parentComponent) return;
|
||||||
|
|
||||||
const index = parentComponent.model.children.indexOf(
|
const index = parentComponent.model.children.indexOf(
|
||||||
blockComponent.model
|
blockComponent.model
|
||||||
);
|
);
|
||||||
if (index === -1) return KEYBOARD_ALLOW_DEFAULT;
|
if (index === -1) return;
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
inlineEditor.deleteText({
|
||||||
index: inlineRange.index - 4,
|
index: inlineRange.index - 4,
|
||||||
@@ -488,7 +466,7 @@ export const LatexExtension = InlineMarkdownExtension({
|
|||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inlinePrefix === '$$') {
|
if (inlinePrefix === '$$') {
|
||||||
@@ -545,12 +523,10 @@ export const LatexExtension = InlineMarkdownExtension({
|
|||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content || content.length === 0) {
|
if (!content || content.length === 0) return;
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
{
|
{
|
||||||
@@ -592,8 +568,6 @@ export const LatexExtension = InlineMarkdownExtension({
|
|||||||
index: startIndex + 1,
|
index: startIndex + 1,
|
||||||
length: 0,
|
length: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ import { ShadowlessElement } from '@blocksuite/block-std';
|
|||||||
import { assertExists, WithDisposable } from '@blocksuite/global/utils';
|
import { assertExists, WithDisposable } from '@blocksuite/global/utils';
|
||||||
import {
|
import {
|
||||||
type AttributeRenderer,
|
type AttributeRenderer,
|
||||||
createInlineKeyDownHandler,
|
|
||||||
type DeltaInsert,
|
type DeltaInsert,
|
||||||
InlineEditor,
|
InlineEditor,
|
||||||
type InlineRange,
|
type InlineRange,
|
||||||
type InlineRangeProvider,
|
type InlineRangeProvider,
|
||||||
type KeyboardBindingContext,
|
|
||||||
type VLine,
|
type VLine,
|
||||||
} from '@blocksuite/inline';
|
} from '@blocksuite/inline';
|
||||||
import { Text } from '@blocksuite/store';
|
import { Text } from '@blocksuite/store';
|
||||||
@@ -19,6 +17,7 @@ import { classMap } from 'lit/directives/class-map.js';
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { InlineMarkdownMatch } from './extension/type.js';
|
||||||
import { onVBeforeinput, onVCompositionEnd } from './hooks.js';
|
import { onVBeforeinput, onVCompositionEnd } from './hooks.js';
|
||||||
import type { AffineInlineEditor } from './inline/index.js';
|
import type { AffineInlineEditor } from './inline/index.js';
|
||||||
|
|
||||||
@@ -181,20 +180,40 @@ export class RichText extends WithDisposable(ShadowlessElement) {
|
|||||||
}
|
}
|
||||||
const inlineEditor = this._inlineEditor;
|
const inlineEditor = this._inlineEditor;
|
||||||
|
|
||||||
const markdownShortcutHandler = this.markdownShortcutHandler;
|
const markdownMatches = this.markdownMatches;
|
||||||
if (markdownShortcutHandler) {
|
if (markdownMatches) {
|
||||||
const keyDownHandler = createInlineKeyDownHandler(inlineEditor, {
|
|
||||||
inputRule: {
|
|
||||||
key: [' ', 'Enter'],
|
|
||||||
handler: context =>
|
|
||||||
markdownShortcutHandler(context, this.undoManager),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.disposables.addFromEvent(
|
inlineEditor.disposables.addFromEvent(
|
||||||
this.inlineEventSource ?? this.inlineEditorContainer,
|
this.inlineEventSource ?? this.inlineEditorContainer,
|
||||||
'keydown',
|
'keydown',
|
||||||
keyDownHandler
|
(e: KeyboardEvent) => {
|
||||||
|
if (e.key !== ' ' && e.key !== 'Enter') return;
|
||||||
|
|
||||||
|
const inlineRange = inlineEditor.getInlineRange();
|
||||||
|
if (!inlineRange || inlineRange.length > 0) return;
|
||||||
|
|
||||||
|
const nearestLineBreakIndex = inlineEditor.yTextString
|
||||||
|
.slice(0, inlineRange.index)
|
||||||
|
.lastIndexOf('\n');
|
||||||
|
const prefixText = inlineEditor.yTextString.slice(
|
||||||
|
nearestLineBreakIndex + 1,
|
||||||
|
inlineRange.index
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const match of markdownMatches) {
|
||||||
|
const { pattern, action } = match;
|
||||||
|
if (prefixText.match(pattern)) {
|
||||||
|
action({
|
||||||
|
inlineEditor,
|
||||||
|
prefixText,
|
||||||
|
inlineRange,
|
||||||
|
pattern,
|
||||||
|
undoManager: this.undoManager,
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,12 +428,7 @@ export class RichText extends WithDisposable(ShadowlessElement) {
|
|||||||
accessor inlineRangeProvider: InlineRangeProvider | undefined = undefined;
|
accessor inlineRangeProvider: InlineRangeProvider | undefined = undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor markdownShortcutHandler:
|
accessor markdownMatches: InlineMarkdownMatch<AffineTextAttributes>[] = [];
|
||||||
| (<TextAttributes extends AffineTextAttributes = AffineTextAttributes>(
|
|
||||||
context: KeyboardBindingContext<TextAttributes>,
|
|
||||||
undoManager: Y.UndoManager
|
|
||||||
) => boolean)
|
|
||||||
| undefined = undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor readonly = false;
|
accessor readonly = false;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ export * from './base-attributes.js';
|
|||||||
export * from './delta-convert.js';
|
export * from './delta-convert.js';
|
||||||
export * from './embed.js';
|
export * from './embed.js';
|
||||||
export * from './guard.js';
|
export * from './guard.js';
|
||||||
export * from './keyboard.js';
|
|
||||||
export * from './point-conversion.js';
|
export * from './point-conversion.js';
|
||||||
export * from './query.js';
|
export * from './query.js';
|
||||||
export * from './range-conversion.js';
|
export * from './range-conversion.js';
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
import { IS_IOS, IS_MAC } from '@blocksuite/global/env';
|
|
||||||
|
|
||||||
import type { InlineEditor } from '../inline-editor.js';
|
|
||||||
import type { InlineRange } from '../types.js';
|
|
||||||
import type { BaseTextAttributes } from './base-attributes.js';
|
|
||||||
|
|
||||||
const SHORT_KEY_PROPERTY = IS_IOS || IS_MAC ? 'metaKey' : 'ctrlKey';
|
|
||||||
|
|
||||||
export const KEYBOARD_PREVENT_DEFAULT = false;
|
|
||||||
export const KEYBOARD_ALLOW_DEFAULT = true;
|
|
||||||
|
|
||||||
export interface KeyboardBinding {
|
|
||||||
key: number | string | string[];
|
|
||||||
handler: KeyboardBindingHandler;
|
|
||||||
prefix?: RegExp;
|
|
||||||
suffix?: RegExp;
|
|
||||||
shortKey?: boolean;
|
|
||||||
shiftKey?: boolean;
|
|
||||||
altKey?: boolean;
|
|
||||||
metaKey?: boolean;
|
|
||||||
ctrlKey?: boolean;
|
|
||||||
}
|
|
||||||
export type KeyboardBindingRecord = Record<string, KeyboardBinding>;
|
|
||||||
|
|
||||||
export interface KeyboardBindingContext<
|
|
||||||
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
|
||||||
> {
|
|
||||||
inlineRange: InlineRange;
|
|
||||||
inlineEditor: InlineEditor<TextAttributes>;
|
|
||||||
collapsed: boolean;
|
|
||||||
prefixText: string;
|
|
||||||
suffixText: string;
|
|
||||||
raw: KeyboardEvent;
|
|
||||||
}
|
|
||||||
export type KeyboardBindingHandler = (
|
|
||||||
context: KeyboardBindingContext
|
|
||||||
) => typeof KEYBOARD_PREVENT_DEFAULT | typeof KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
|
|
||||||
export function createInlineKeyDownHandler(
|
|
||||||
inlineEditor: InlineEditor,
|
|
||||||
bindings: KeyboardBindingRecord
|
|
||||||
): (evt: KeyboardEvent) => void {
|
|
||||||
const bindingStore: Record<string, KeyboardBinding[]> = {};
|
|
||||||
|
|
||||||
function normalize(binding: KeyboardBinding): KeyboardBinding {
|
|
||||||
if (binding.shortKey) {
|
|
||||||
binding[SHORT_KEY_PROPERTY] = binding.shortKey;
|
|
||||||
delete binding.shortKey;
|
|
||||||
}
|
|
||||||
return binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyMatch(evt: KeyboardEvent, binding: KeyboardBinding) {
|
|
||||||
if (
|
|
||||||
(['altKey', 'ctrlKey', 'metaKey', 'shiftKey'] as const).some(
|
|
||||||
key => Object.hasOwn(binding, key) && binding[key] !== evt[key]
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return binding.key === evt.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addBinding(keyBinding: KeyboardBinding) {
|
|
||||||
const binding = normalize(keyBinding);
|
|
||||||
const keys = Array.isArray(binding.key) ? binding.key : [binding.key];
|
|
||||||
keys.forEach(key => {
|
|
||||||
const singleBinding = {
|
|
||||||
...binding,
|
|
||||||
key,
|
|
||||||
};
|
|
||||||
bindingStore[key] = bindingStore[key] ?? [];
|
|
||||||
bindingStore[key].push(singleBinding);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(bindings).forEach(binding => {
|
|
||||||
addBinding(binding);
|
|
||||||
});
|
|
||||||
|
|
||||||
function keyDownHandler(evt: KeyboardEvent) {
|
|
||||||
if (evt.defaultPrevented || evt.isComposing) return;
|
|
||||||
const keyBindings = bindingStore[evt.key] ?? [];
|
|
||||||
|
|
||||||
const keyMatches = keyBindings.filter(binding => keyMatch(evt, binding));
|
|
||||||
if (keyMatches.length === 0) return;
|
|
||||||
|
|
||||||
const inlineRange = inlineEditor.getInlineRange();
|
|
||||||
if (!inlineRange) return;
|
|
||||||
|
|
||||||
const startTextPoint = inlineEditor.getTextPoint(inlineRange.index);
|
|
||||||
if (!startTextPoint) return;
|
|
||||||
const [leafStart, offsetStart] = startTextPoint;
|
|
||||||
let leafEnd: Text;
|
|
||||||
let offsetEnd: number;
|
|
||||||
if (inlineRange.length === 0) {
|
|
||||||
leafEnd = leafStart;
|
|
||||||
offsetEnd = offsetStart;
|
|
||||||
} else {
|
|
||||||
const endTextPoint = inlineEditor.getTextPoint(
|
|
||||||
inlineRange.index + inlineRange.length
|
|
||||||
);
|
|
||||||
if (!endTextPoint) return;
|
|
||||||
[leafEnd, offsetEnd] = endTextPoint;
|
|
||||||
}
|
|
||||||
const prefixText = leafStart.textContent
|
|
||||||
? leafStart.textContent.slice(0, offsetStart)
|
|
||||||
: '';
|
|
||||||
const suffixText = leafEnd.textContent
|
|
||||||
? leafEnd.textContent.slice(offsetEnd)
|
|
||||||
: '';
|
|
||||||
const currContext: KeyboardBindingContext = {
|
|
||||||
inlineRange,
|
|
||||||
inlineEditor: inlineEditor,
|
|
||||||
collapsed: inlineRange.length === 0,
|
|
||||||
prefixText,
|
|
||||||
suffixText,
|
|
||||||
raw: evt,
|
|
||||||
};
|
|
||||||
const prevented = keyMatches.some(binding => {
|
|
||||||
if (binding.prefix && !binding.prefix.test(currContext.prefixText)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (binding.suffix && !binding.suffix.test(currContext.suffixText)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return binding.handler(currContext) === KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
});
|
|
||||||
if (prevented) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyDownHandler;
|
|
||||||
}
|
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
import {
|
|
||||||
type InlineEditor,
|
|
||||||
type InlineRange,
|
|
||||||
KEYBOARD_ALLOW_DEFAULT,
|
|
||||||
KEYBOARD_PREVENT_DEFAULT,
|
|
||||||
} from '@blocksuite/inline';
|
|
||||||
import type * as Y from 'yjs';
|
|
||||||
|
|
||||||
interface MarkdownMatch {
|
|
||||||
name: string;
|
|
||||||
pattern: RegExp;
|
|
||||||
action: (props: {
|
|
||||||
inlineEditor: InlineEditor;
|
|
||||||
prefixText: string;
|
|
||||||
inlineRange: InlineRange;
|
|
||||||
pattern: RegExp;
|
|
||||||
undoManager: Y.UndoManager;
|
|
||||||
}) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const markdownMatches: MarkdownMatch[] = [
|
|
||||||
{
|
|
||||||
name: 'bolditalic',
|
|
||||||
pattern: /(?:\*){3}([^* \n](.+?[^* \n])?)(?:\*){3}$/g,
|
|
||||||
action: ({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern,
|
|
||||||
undoManager,
|
|
||||||
}) => {
|
|
||||||
const match = pattern.exec(prefixText);
|
|
||||||
if (!match) {
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const annotatedText = match[0];
|
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
|
||||||
{
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 0,
|
|
||||||
},
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
|
||||||
|
|
||||||
inlineEditor.formatText(
|
|
||||||
{
|
|
||||||
index: startIndex,
|
|
||||||
length: annotatedText.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bold: true,
|
|
||||||
italic: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length - 3,
|
|
||||||
length: 3,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIndex + annotatedText.length - 6,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bold',
|
|
||||||
pattern: /(?:\*){2}([^* \n](.+?[^* \n])?)(?:\*){2}$/g,
|
|
||||||
action: ({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern,
|
|
||||||
undoManager,
|
|
||||||
}) => {
|
|
||||||
const match = pattern.exec(prefixText);
|
|
||||||
if (!match) {
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
const annotatedText = match[0];
|
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
|
||||||
{
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 0,
|
|
||||||
},
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
|
||||||
|
|
||||||
inlineEditor.formatText(
|
|
||||||
{
|
|
||||||
index: startIndex,
|
|
||||||
length: annotatedText.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bold: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length - 2,
|
|
||||||
length: 2,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIndex + annotatedText.length - 4,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'italic',
|
|
||||||
pattern: /(?:\*){1}([^* \n](.+?[^* \n])?)(?:\*){1}$/g,
|
|
||||||
action: ({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern,
|
|
||||||
undoManager,
|
|
||||||
}) => {
|
|
||||||
const match = pattern.exec(prefixText);
|
|
||||||
if (!match) {
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
const annotatedText = match[0];
|
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
|
||||||
{
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 0,
|
|
||||||
},
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
|
||||||
|
|
||||||
inlineEditor.formatText(
|
|
||||||
{
|
|
||||||
index: startIndex,
|
|
||||||
length: annotatedText.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
italic: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length - 1,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIndex + annotatedText.length - 2,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'strikethrough',
|
|
||||||
pattern: /(?:~~)([^~ \n](.+?[^~ \n])?)(?:~~)$/g,
|
|
||||||
action: ({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern,
|
|
||||||
undoManager,
|
|
||||||
}) => {
|
|
||||||
const match = pattern.exec(prefixText);
|
|
||||||
if (!match) {
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
const annotatedText = match[0];
|
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
|
||||||
{
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 0,
|
|
||||||
},
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
|
||||||
|
|
||||||
inlineEditor.formatText(
|
|
||||||
{
|
|
||||||
index: startIndex,
|
|
||||||
length: annotatedText.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
strike: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length - 2,
|
|
||||||
length: 2,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIndex + annotatedText.length - 4,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'underthrough',
|
|
||||||
pattern: /(?:~)([^~ \n](.+?[^~ \n])?)(?:~)$/g,
|
|
||||||
action: ({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern,
|
|
||||||
undoManager,
|
|
||||||
}) => {
|
|
||||||
const match = pattern.exec(prefixText);
|
|
||||||
if (!match) {
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
const annotatedText = match[0];
|
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
|
||||||
{
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 0,
|
|
||||||
},
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
|
||||||
|
|
||||||
inlineEditor.formatText(
|
|
||||||
{
|
|
||||||
index: startIndex,
|
|
||||||
length: annotatedText.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
underline: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: inlineRange.index - 1,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIndex + annotatedText.length - 2,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'code',
|
|
||||||
pattern: /(?:`)(`{2,}?|[^`]+)(?:`)$/g,
|
|
||||||
action: ({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern,
|
|
||||||
undoManager,
|
|
||||||
}) => {
|
|
||||||
const match = pattern.exec(prefixText);
|
|
||||||
if (!match) {
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
const annotatedText = match[0];
|
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
|
||||||
|
|
||||||
if (prefixText.match(/^([* \n]+)$/g)) {
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
inlineEditor.insertText(
|
|
||||||
{
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 0,
|
|
||||||
},
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
|
||||||
|
|
||||||
inlineEditor.formatText(
|
|
||||||
{
|
|
||||||
index: startIndex,
|
|
||||||
length: annotatedText.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex + annotatedText.length - 1,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIndex + annotatedText.length - 2,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return KEYBOARD_PREVENT_DEFAULT;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -5,9 +5,7 @@ import {
|
|||||||
type AttributeRenderer,
|
type AttributeRenderer,
|
||||||
type BaseTextAttributes,
|
type BaseTextAttributes,
|
||||||
baseTextAttributes,
|
baseTextAttributes,
|
||||||
createInlineKeyDownHandler,
|
|
||||||
InlineEditor,
|
InlineEditor,
|
||||||
KEYBOARD_ALLOW_DEFAULT,
|
|
||||||
ZERO_WIDTH_NON_JOINER,
|
ZERO_WIDTH_NON_JOINER,
|
||||||
} from '@blocksuite/inline';
|
} from '@blocksuite/inline';
|
||||||
import { effects } from '@blocksuite/inline/effects';
|
import { effects } from '@blocksuite/inline/effects';
|
||||||
@@ -18,8 +16,6 @@ import { styleMap } from 'lit/directives/style-map.js';
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { markdownMatches } from './markdown.js';
|
|
||||||
|
|
||||||
effects();
|
effects();
|
||||||
|
|
||||||
function inlineTextStyles(
|
function inlineTextStyles(
|
||||||
@@ -132,30 +128,6 @@ export class TestRichText extends ShadowlessElement {
|
|||||||
this.style.outline = 'none';
|
this.style.outline = 'none';
|
||||||
this.inlineEditor.mount(this._container, this);
|
this.inlineEditor.mount(this._container, this);
|
||||||
|
|
||||||
const keydownHandler = createInlineKeyDownHandler(this.inlineEditor, {
|
|
||||||
inputRule: {
|
|
||||||
key: ' ',
|
|
||||||
handler: context => {
|
|
||||||
const { inlineEditor, prefixText, inlineRange } = context;
|
|
||||||
for (const match of markdownMatches) {
|
|
||||||
const matchedText = prefixText.match(match.pattern);
|
|
||||||
if (matchedText) {
|
|
||||||
return match.action({
|
|
||||||
inlineEditor,
|
|
||||||
prefixText,
|
|
||||||
inlineRange,
|
|
||||||
pattern: match.pattern,
|
|
||||||
undoManager: this.undoManager,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return KEYBOARD_ALLOW_DEFAULT;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.addEventListener('keydown', keydownHandler);
|
|
||||||
|
|
||||||
this.inlineEditor.slots.textChange.on(() => {
|
this.inlineEditor.slots.textChange.on(() => {
|
||||||
const el = this.querySelector('.y-text');
|
const el = this.querySelector('.y-text');
|
||||||
if (el) {
|
if (el) {
|
||||||
|
|||||||
@@ -1106,31 +1106,6 @@ test('delete embed when pressing backspace after embed', async ({ page }) => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('markdown shortcut using keyboard util', async ({ page }) => {
|
|
||||||
await enterInlineEditorPlayground(page);
|
|
||||||
await focusInlineRichText(page);
|
|
||||||
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
await type(page, 'aaa**bbb** ccc');
|
|
||||||
|
|
||||||
const delta = await getDeltaFromInlineRichText(page);
|
|
||||||
expect(delta).toEqual([
|
|
||||||
{
|
|
||||||
insert: 'aaa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
insert: 'bbb',
|
|
||||||
attributes: {
|
|
||||||
bold: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
insert: 'ccc',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('triple click to select line', async ({ page }) => {
|
test('triple click to select line', async ({ page }) => {
|
||||||
await enterInlineEditorPlayground(page);
|
await enterInlineEditorPlayground(page);
|
||||||
await focusInlineRichText(page);
|
await focusInlineRichText(page);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
getCursorBlockIdAndHeight,
|
getCursorBlockIdAndHeight,
|
||||||
initEmptyParagraphState,
|
initEmptyParagraphState,
|
||||||
pressArrowLeft,
|
pressArrowLeft,
|
||||||
|
pressArrowRight,
|
||||||
pressBackspace,
|
pressBackspace,
|
||||||
pressEnter,
|
pressEnter,
|
||||||
pressSpace,
|
pressSpace,
|
||||||
@@ -228,6 +229,24 @@ test.describe('markdown inline-text', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('bolditalic', async ({ page }) => {
|
test('bolditalic', async ({ page }) => {
|
||||||
|
await type(page, 'aa***b*** ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'aa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'b',
|
||||||
|
attributes: {
|
||||||
|
bold: true,
|
||||||
|
italic: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await assertRichTexts(page, ['']);
|
||||||
|
|
||||||
await type(page, 'aa***bb*** ');
|
await type(page, 'aa***bb*** ');
|
||||||
await assertRichTextInlineDeltas(page, [
|
await assertRichTextInlineDeltas(page, [
|
||||||
{
|
{
|
||||||
@@ -282,6 +301,23 @@ test.describe('markdown inline-text', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('bold', async ({ page }) => {
|
test('bold', async ({ page }) => {
|
||||||
|
await type(page, 'aa**b** ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'aa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'b',
|
||||||
|
attributes: {
|
||||||
|
bold: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await assertRichTexts(page, ['']);
|
||||||
|
|
||||||
await type(page, 'aa**bb** ');
|
await type(page, 'aa**bb** ');
|
||||||
await assertRichTextInlineDeltas(page, [
|
await assertRichTextInlineDeltas(page, [
|
||||||
{
|
{
|
||||||
@@ -333,6 +369,23 @@ test.describe('markdown inline-text', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('italic', async ({ page }) => {
|
test('italic', async ({ page }) => {
|
||||||
|
await type(page, 'aa*b* ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'aa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'b',
|
||||||
|
attributes: {
|
||||||
|
italic: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await assertRichTexts(page, ['']);
|
||||||
|
|
||||||
await type(page, 'aa*bb* ');
|
await type(page, 'aa*bb* ');
|
||||||
await assertRichTextInlineDeltas(page, [
|
await assertRichTextInlineDeltas(page, [
|
||||||
{
|
{
|
||||||
@@ -385,6 +438,23 @@ test.describe('markdown inline-text', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('strike', async ({ page }) => {
|
test('strike', async ({ page }) => {
|
||||||
|
await type(page, 'aa~~b~~ ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'aa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'b',
|
||||||
|
attributes: {
|
||||||
|
strike: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await assertRichTexts(page, ['']);
|
||||||
|
|
||||||
await type(page, 'aa~~bb~~ ');
|
await type(page, 'aa~~bb~~ ');
|
||||||
await assertRichTextInlineDeltas(page, [
|
await assertRichTextInlineDeltas(page, [
|
||||||
{
|
{
|
||||||
@@ -436,6 +506,23 @@ test.describe('markdown inline-text', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('underline', async ({ page }) => {
|
test('underline', async ({ page }) => {
|
||||||
|
await type(page, 'aa~b~ ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'aa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'b',
|
||||||
|
attributes: {
|
||||||
|
underline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await assertRichTexts(page, ['']);
|
||||||
|
|
||||||
await type(page, 'aa~bb~ ');
|
await type(page, 'aa~bb~ ');
|
||||||
await assertRichTextInlineDeltas(page, [
|
await assertRichTextInlineDeltas(page, [
|
||||||
{
|
{
|
||||||
@@ -487,6 +574,23 @@ test.describe('markdown inline-text', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('code', async ({ page }) => {
|
test('code', async ({ page }) => {
|
||||||
|
await type(page, 'aa`b` ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'aa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'b',
|
||||||
|
attributes: {
|
||||||
|
code: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await undoByKeyboard(page);
|
||||||
|
await assertRichTexts(page, ['']);
|
||||||
|
|
||||||
await type(page, 'aa`bb` ');
|
await type(page, 'aa`bb` ');
|
||||||
await assertRichTextInlineDeltas(page, [
|
await assertRichTextInlineDeltas(page, [
|
||||||
{
|
{
|
||||||
@@ -538,6 +642,40 @@ test.describe('markdown inline-text', () => {
|
|||||||
await assertRichTexts(page, ['` test` ']);
|
await assertRichTexts(page, ['` test` ']);
|
||||||
await undoByKeyboard(page);
|
await undoByKeyboard(page);
|
||||||
await assertRichTexts(page, ['']);
|
await assertRichTexts(page, ['']);
|
||||||
|
|
||||||
|
// https://github.com/toeverything/AFFiNE/issues/9410
|
||||||
|
await waitNextFrame(page);
|
||||||
|
await type(page, 'test**bold** ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'test',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'bold',
|
||||||
|
attributes: {
|
||||||
|
bold: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await pressArrowLeft(page, 8);
|
||||||
|
await type(page, '`');
|
||||||
|
await pressArrowRight(page, 8);
|
||||||
|
await type(page, '` ');
|
||||||
|
await assertRichTextInlineDeltas(page, [
|
||||||
|
{
|
||||||
|
insert: 'test',
|
||||||
|
attributes: {
|
||||||
|
code: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'bold',
|
||||||
|
attributes: {
|
||||||
|
bold: true,
|
||||||
|
code: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user