feat(editor): latex inline package (#11051)

This commit is contained in:
Saul-Mirone
2025-03-21 01:38:08 +00:00
parent 76f5c73fbc
commit e50d09db50
44 changed files with 526 additions and 342 deletions
+2
View File
@@ -36,6 +36,7 @@
"@blocksuite/affine-gfx-text": "workspace:*",
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
"@blocksuite/affine-inline-footnote": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
"@blocksuite/affine-inline-reference": "workspace:*",
@@ -99,6 +100,7 @@
"./inlines/reference": "./src/inlines/reference.ts",
"./inlines/preset": "./src/inlines/preset.ts",
"./inlines/footnote": "./src/inlines/footnote.ts",
"./inlines/latex": "./src/inlines/latex.ts",
"./widgets/drag-handle": "./src/widgets/drag-handle.ts",
"./widgets/edgeless-auto-connect": "./src/widgets/edgeless-auto-connect.ts",
"./widgets/frame-title": "./src/widgets/frame-title.ts",
+2
View File
@@ -46,6 +46,7 @@ import { effects as fragmentDocTitleEffects } from '@blocksuite/affine-fragment-
import { effects as fragmentFramePanelEffects } from '@blocksuite/affine-fragment-frame-panel/effects';
import { effects as fragmentOutlineEffects } from '@blocksuite/affine-fragment-outline/effects';
import { effects as inlineFootnoteEffects } from '@blocksuite/affine-inline-footnote/effects';
import { effects as inlineLatexEffects } from '@blocksuite/affine-inline-latex/effects';
import { effects as inlineLinkEffects } from '@blocksuite/affine-inline-link/effects';
import { effects as inlinePresetEffects } from '@blocksuite/affine-inline-preset/effects';
import { effects as inlineReferenceEffects } from '@blocksuite/affine-inline-reference/effects';
@@ -121,6 +122,7 @@ export function effects() {
inlinePresetEffects();
inlineLinkEffects();
inlineFootnoteEffects();
inlineLatexEffects();
blockNoteEffects();
blockAttachmentEffects();
@@ -0,0 +1 @@
export * from '@blocksuite/affine-inline-latex';
+1
View File
@@ -33,6 +33,7 @@
{ "path": "../gfx/text" },
{ "path": "../gfx/turbo-renderer" },
{ "path": "../inlines/footnote" },
{ "path": "../inlines/latex" },
{ "path": "../inlines/link" },
{ "path": "../inlines/preset" },
{ "path": "../inlines/reference" },
@@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
@@ -1,3 +1,4 @@
import { LatexInlineSpecExtension } from '@blocksuite/affine-inline-latex';
import { LinkInlineSpecExtension } from '@blocksuite/affine-inline-link';
import {
BackgroundInlineSpecExtension,
@@ -5,7 +6,6 @@ import {
CodeInlineSpecExtension,
ColorInlineSpecExtension,
ItalicInlineSpecExtension,
LatexInlineSpecExtension,
StrikeInlineSpecExtension,
UnderlineInlineSpecExtension,
} from '@blocksuite/affine-inline-preset';
@@ -8,6 +8,7 @@
"include": ["./src"],
"references": [
{ "path": "../../components" },
{ "path": "../../inlines/latex" },
{ "path": "../../inlines/link" },
{ "path": "../../inlines/preset" },
{ "path": "../../model" },
@@ -12,7 +12,7 @@
"dependencies": {
"@blocksuite/affine-block-note": "workspace:*",
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-rich-text": "workspace:*",
"@blocksuite/affine-shared": "workspace:*",
@@ -1,4 +1,4 @@
import { insertInlineLatex } from '@blocksuite/affine-inline-preset';
import { insertInlineLatex } from '@blocksuite/affine-inline-latex';
import {
getSelectedModelsCommand,
getTextSelectionCommand,
@@ -9,7 +9,7 @@
"references": [
{ "path": "../block-note" },
{ "path": "../../components" },
{ "path": "../../inlines/preset" },
{ "path": "../../inlines/latex" },
{ "path": "../../model" },
{ "path": "../../rich-text" },
{ "path": "../../shared" },
@@ -28,6 +28,7 @@
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-fragment-doc-title": "workspace:*",
"@blocksuite/affine-gfx-text": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
"@blocksuite/affine-inline-reference": "workspace:*",
@@ -20,13 +20,13 @@ import { getSurfaceBlock } from '@blocksuite/affine-block-surface';
import { insertSurfaceRefBlockCommand } from '@blocksuite/affine-block-surface-ref';
import { toggleEmbedCardCreateModal } from '@blocksuite/affine-components/embed-card-modal';
import { toast } from '@blocksuite/affine-components/toast';
import { insertInlineLatex } from '@blocksuite/affine-inline-latex';
import { toggleLink } from '@blocksuite/affine-inline-link';
import {
formatBlockCommand,
formatNativeCommand,
formatTextCommand,
getTextStyle,
insertInlineLatex,
toggleBold,
toggleCode,
toggleItalic,
@@ -25,6 +25,7 @@
{ "path": "../../components" },
{ "path": "../../fragments/fragment-doc-title" },
{ "path": "../../gfx/text" },
{ "path": "../../inlines/latex" },
{ "path": "../../inlines/link" },
{ "path": "../../inlines/preset" },
{ "path": "../../inlines/reference" },
@@ -0,0 +1,52 @@
{
"name": "@blocksuite/affine-inline-latex",
"description": "Inline latex for BlockSuite.",
"type": "module",
"scripts": {
"build": "tsc"
},
"sideEffects": false,
"keywords": [],
"author": "toeverything",
"license": "MIT",
"dependencies": {
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-inline-reference": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-rich-text": "workspace:*",
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/block-std": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.6",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.12",
"@types/hast": "^3.0.4",
"@types/katex": "^0.16.7",
"@types/lodash-es": "^4.17.12",
"collapse-white-space": "^2.1.0",
"date-fns": "^4.0.0",
"katex": "^0.16.11",
"lit": "^3.2.0",
"lit-html": "^3.2.1",
"lodash-es": "^4.17.21",
"remark-math": "^6.0.0",
"rxjs": "^7.8.1",
"shiki": "^3.0.0",
"yjs": "^13.6.21",
"zod": "^3.23.8"
},
"exports": {
".": "./src/index.ts",
"./effects": "./src/effects.ts"
},
"files": [
"src",
"dist",
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.20.0"
}
@@ -0,0 +1,3 @@
export * from './markdown/inline-delta';
export * from './markdown/markdown-inline';
export * from './plain-text/inline-delta';
@@ -0,0 +1,21 @@
import { InlineDeltaToMarkdownAdapterExtension } from '@blocksuite/affine-shared/adapters';
import type { PhrasingContent } from 'mdast';
export const latexDeltaToMarkdownAdapterMatcher =
InlineDeltaToMarkdownAdapterExtension({
name: 'inlineLatex',
match: delta => !!delta.attributes?.latex,
toAST: delta => {
const mdast: PhrasingContent = {
type: 'text',
value: delta.insert,
};
if (delta.attributes?.latex) {
return {
type: 'inlineMath',
value: delta.attributes.latex,
};
}
return mdast;
},
});
@@ -0,0 +1,12 @@
import { MarkdownASTToDeltaExtension } from '@blocksuite/affine-shared/adapters';
export const markdownInlineMathToDeltaMatcher = MarkdownASTToDeltaExtension({
name: 'inlineMath',
match: ast => ast.type === 'inlineMath',
toDelta: ast => {
if (!('value' in ast)) {
return [];
}
return [{ insert: ' ', attributes: { latex: ast.value } }];
},
});
@@ -0,0 +1,21 @@
import {
InlineDeltaToPlainTextAdapterExtension,
type TextBuffer,
} from '@blocksuite/affine-shared/adapters';
export const latexDeltaMarkdownAdapterMatch =
InlineDeltaToPlainTextAdapterExtension({
name: 'inlineLatex',
match: delta => !!delta.attributes?.latex,
toAST: delta => {
const node: TextBuffer = {
content: delta.insert,
};
if (!delta.attributes?.latex) {
return node;
}
return {
content: delta.attributes?.latex,
};
},
});
@@ -0,0 +1,17 @@
import { LatexEditorMenu } from './latex-node/latex-editor-menu';
import { LatexEditorUnit } from './latex-node/latex-editor-unit';
import { AffineLatexNode } from './latex-node/latex-node';
export function effects() {
customElements.define('latex-editor-menu', LatexEditorMenu);
customElements.define('latex-editor-unit', LatexEditorUnit);
customElements.define('affine-latex-node', AffineLatexNode);
}
declare global {
interface HTMLElementTagNameMap {
'affine-latex-node': AffineLatexNode;
'latex-editor-unit': LatexEditorUnit;
'latex-editor-menu': LatexEditorMenu;
}
}
@@ -0,0 +1,11 @@
import {
LatexEditorUnitSpecExtension,
LatexInlineSpecExtension,
} from './inline-spec';
import { LatexEditorInlineManagerExtension } from './latex-node/latex-editor-menu';
export const inlineLatexExtensions = [
LatexInlineSpecExtension,
LatexEditorUnitSpecExtension,
LatexEditorInlineManagerExtension,
];
@@ -0,0 +1,10 @@
import type * as RichTextEffects from '@blocksuite/affine-rich-text/effects';
import type RemarkMath from 'remark-math';
export * from './adapters';
export * from './command';
export * from './exts';
export * from './inline-spec';
export * from './markdown';
declare type _GLOBAL_ = typeof RichTextEffects | typeof RemarkMath;
@@ -0,0 +1,36 @@
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { StdIdentifier } from '@blocksuite/block-std';
import { InlineSpecExtension } from '@blocksuite/block-std/inline';
import { html } from 'lit';
import { z } from 'zod';
export const LatexInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>('latex', provider => {
const std = provider.get(StdIdentifier);
return {
name: 'latex',
schema: z.string().optional().nullable().catch(undefined),
match: delta => typeof delta.attributes?.latex === 'string',
renderer: ({ delta, selected, editor, startOffset, endOffset }) => {
return html`<affine-latex-node
.std=${std}
.delta=${delta}
.selected=${selected}
.editor=${editor}
.startOffset=${startOffset}
.endOffset=${endOffset}
></affine-latex-node>`;
},
embed: true,
};
});
export const LatexEditorUnitSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'latex-editor-unit',
schema: z.undefined(),
match: () => true,
renderer: ({ delta }) => {
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
},
});
@@ -1,4 +1,5 @@
import { ColorScheme } from '@blocksuite/affine-model';
import type { RichText } from '@blocksuite/affine-rich-text';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
@@ -13,7 +14,7 @@ import { property } from 'lit/decorators.js';
import { codeToTokensBase, type ThemedToken } from 'shiki';
import * as Y from 'yjs';
import { LatexEditorUnitSpecExtension } from '../../inline-spec';
import { LatexEditorUnitSpecExtension } from '../inline-spec';
export const LatexEditorInlineManagerExtension =
InlineManagerExtension<AffineTextAttributes>({
@@ -93,7 +94,7 @@ export class LatexEditorMenu extends SignalWatcher(
}
get richText() {
return this.querySelector('rich-text');
return this.querySelector<RichText>('rich-text');
}
private _updateHighlightTokens(text: string) {
@@ -0,0 +1,173 @@
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import type { BlockComponent } from '@blocksuite/block-std';
import { InlineMarkdownExtension } from '@blocksuite/block-std/inline';
export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'latex',
pattern:
/(?:\$\$)(?<content>[^$]+)(?:\$\$)$|(?<blockPrefix>\$\$\$\$)|(?<inlinePrefix>\$\$)$/g,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = pattern.exec(prefixText);
if (!match || !match.groups) return;
const content = match.groups['content'];
const inlinePrefix = match.groups['inlinePrefix'];
const blockPrefix = match.groups['blockPrefix'];
if (blockPrefix === '$$$$') {
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
if (!inlineEditor.rootElement) return;
const blockComponent =
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
if (!blockComponent) return;
const doc = blockComponent.doc;
const parentComponent = blockComponent.parentComponent;
if (!parentComponent) return;
const index = parentComponent.model.children.indexOf(
blockComponent.model
);
if (index === -1) return;
inlineEditor.deleteText({
index: inlineRange.index - 4,
length: 5,
});
const id = doc.addBlock(
'affine:latex',
{
latex: '',
},
parentComponent.model,
index + 1
);
blockComponent.host.updateComplete
.then(() => {
const latexBlock = blockComponent.std.view.getBlock(id);
if (!latexBlock || latexBlock.flavour !== 'affine:latex') return;
//FIXME(@Flrande): wait for refactor
// @ts-expect-error BS-2241
latexBlock.toggleEditor();
})
.catch(console.error);
return;
}
if (inlinePrefix === '$$') {
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
inlineEditor.deleteText({
index: inlineRange.index - 2,
length: 3,
});
inlineEditor.insertText(
{
index: inlineRange.index - 2,
length: 0,
},
' '
);
inlineEditor.formatText(
{
index: inlineRange.index - 2,
length: 1,
},
{
latex: '',
}
);
inlineEditor
.waitForUpdate()
.then(async () => {
await inlineEditor.waitForUpdate();
const textPoint = inlineEditor.getTextPoint(
inlineRange.index - 2 + 1
);
if (!textPoint) return;
const [text] = textPoint;
const latexNode = text.parentElement?.closest('affine-latex-node');
if (!latexNode) return;
latexNode.toggleEditor();
})
.catch(console.error);
return;
}
if (!content || content.length === 0) return;
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
const startIndex = inlineRange.index - 2 - content.length - 2;
inlineEditor.deleteText({
index: startIndex,
length: 2 + content.length + 2 + 1,
});
inlineEditor.insertText(
{
index: startIndex,
length: 0,
},
' '
);
inlineEditor.formatText(
{
index: startIndex,
length: 1,
},
{
latex: String.raw`${content}`,
}
);
inlineEditor.setInlineRange({
index: startIndex + 1,
length: 0,
});
},
});
@@ -0,0 +1,19 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
},
"include": ["./src"],
"references": [
{ "path": "../../components" },
{ "path": "../reference" },
{ "path": "../../model" },
{ "path": "../../rich-text" },
{ "path": "../../shared" },
{ "path": "../../../framework/block-std" },
{ "path": "../../../framework/global" },
{ "path": "../../../framework/store" }
]
}
@@ -3,4 +3,5 @@ export * from './command';
export * from './exts';
export * from './inline-spec';
export * from './link-node';
export * from './markdown';
export * from './toolbar';
@@ -0,0 +1,62 @@
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { InlineMarkdownExtension } from '@blocksuite/block-std/inline';
export const LinkExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'link',
pattern: /.*\[(.+?)\]\((.+?)\)$/,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = prefixText.match(pattern);
if (!match) return;
const linkText = match[1];
const linkUrl = match[2];
const annotatedText = match[0].slice(-linkText.length - linkUrl.length - 4);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
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(
{
index: startIndex,
length: linkText.length,
},
{
link: linkUrl,
}
);
inlineEditor.setInlineRange({
index: startIndex + linkText.length,
length: 0,
});
},
});
@@ -12,6 +12,7 @@
"dependencies": {
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-inline-footnote": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
"@blocksuite/affine-inline-reference": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
@@ -1,11 +1,8 @@
import { footnoteReferenceDeltaToMarkdownAdapterMatcher } from '@blocksuite/affine-inline-footnote';
import { latexDeltaToMarkdownAdapterMatcher } from '@blocksuite/affine-inline-latex';
import { linkDeltaToMarkdownAdapterMatcher } from '@blocksuite/affine-inline-link';
import { referenceDeltaToMarkdownAdapterMatcher } from '@blocksuite/affine-inline-reference';
import { InlineDeltaToMarkdownAdapterExtension } from '@blocksuite/affine-shared/adapters';
import type { PhrasingContent } from 'mdast';
import type RemarkMath from 'remark-math';
declare type _GLOBAL_ = typeof RemarkMath;
export const boldDeltaToMarkdownAdapterMatcher =
InlineDeltaToMarkdownAdapterExtension({
@@ -56,25 +53,6 @@ export const inlineCodeDeltaToMarkdownAdapterMatcher =
}),
});
export const latexDeltaToMarkdownAdapterMatcher =
InlineDeltaToMarkdownAdapterExtension({
name: 'inlineLatex',
match: delta => !!delta.attributes?.latex,
toAST: delta => {
const mdast: PhrasingContent = {
type: 'text',
value: delta.insert,
};
if (delta.attributes?.latex) {
return {
type: 'inlineMath',
value: delta.attributes.latex,
};
}
return mdast;
},
});
export const InlineDeltaToMarkdownAdapterExtensions = [
referenceDeltaToMarkdownAdapterMatcher,
linkDeltaToMarkdownAdapterMatcher,
@@ -1,4 +1,5 @@
import { markdownFootnoteReferenceToDeltaMatcher } from '@blocksuite/affine-inline-footnote';
import { markdownInlineMathToDeltaMatcher } from '@blocksuite/affine-inline-latex';
import { markdownLinkToDeltaMatcher } from '@blocksuite/affine-inline-link';
import { MarkdownASTToDeltaExtension } from '@blocksuite/affine-shared/adapters';
@@ -78,17 +79,6 @@ export const markdownListToDeltaMatcher = MarkdownASTToDeltaExtension({
toDelta: () => [],
});
export const markdownInlineMathToDeltaMatcher = MarkdownASTToDeltaExtension({
name: 'inlineMath',
match: ast => ast.type === 'inlineMath',
toDelta: ast => {
if (!('value' in ast)) {
return [];
}
return [{ insert: ' ', attributes: { latex: ast.value } }];
},
});
export const MarkdownInlineToDeltaAdapterExtensions = [
markdownTextToDeltaMatcher,
markdownInlineCodeToDeltaMatcher,
@@ -1,28 +1,8 @@
import { latexDeltaMarkdownAdapterMatch } from '@blocksuite/affine-inline-latex';
import { linkDeltaMarkdownAdapterMatch } from '@blocksuite/affine-inline-link';
import { referenceDeltaMarkdownAdapterMatch } from '@blocksuite/affine-inline-reference';
import {
InlineDeltaToPlainTextAdapterExtension,
type TextBuffer,
} from '@blocksuite/affine-shared/adapters';
import type { ExtensionType } from '@blocksuite/store';
export const latexDeltaMarkdownAdapterMatch =
InlineDeltaToPlainTextAdapterExtension({
name: 'inlineLatex',
match: delta => !!delta.attributes?.latex,
toAST: delta => {
const node: TextBuffer = {
content: delta.insert,
};
if (!delta.attributes?.latex) {
return node;
}
return {
content: delta.attributes?.latex,
};
},
});
export const InlineDeltaToPlainTextAdapterExtensions: ExtensionType[] = [
referenceDeltaMarkdownAdapterMatch,
linkDeltaMarkdownAdapterMatch,
@@ -9,7 +9,6 @@ export { deleteTextCommand } from './delete-text.js';
export { formatBlockCommand } from './format-block.js';
export { formatNativeCommand } from './format-native.js';
export { formatTextCommand } from './format-text.js';
export { insertInlineLatex } from './insert-inline-latex.js';
export {
getTextStyle,
isTextStyleActive,
@@ -1,4 +1,5 @@
import { FootNoteInlineSpecExtension } from '@blocksuite/affine-inline-footnote';
import { LatexInlineSpecExtension } from '@blocksuite/affine-inline-latex';
import { LinkInlineSpecExtension } from '@blocksuite/affine-inline-link';
import { ReferenceInlineSpecExtension } from '@blocksuite/affine-inline-reference';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
@@ -10,7 +11,6 @@ import {
CodeInlineSpecExtension,
ColorInlineSpecExtension,
ItalicInlineSpecExtension,
LatexInlineSpecExtension,
StrikeInlineSpecExtension,
UnderlineInlineSpecExtension,
} from './inline-spec';
@@ -1,20 +1,11 @@
import { AffineText } from './nodes/affine-text';
import { LatexEditorMenu } from './nodes/latex-node/latex-editor-menu';
import { LatexEditorUnit } from './nodes/latex-node/latex-editor-unit';
import { AffineLatexNode } from './nodes/latex-node/latex-node';
export function effects() {
customElements.define('affine-text', AffineText);
customElements.define('latex-editor-menu', LatexEditorMenu);
customElements.define('latex-editor-unit', LatexEditorUnit);
customElements.define('affine-latex-node', AffineLatexNode);
}
declare global {
interface HTMLElementTagNameMap {
'affine-latex-node': AffineLatexNode;
'affine-text': AffineText;
'latex-editor-unit': LatexEditorUnit;
'latex-editor-menu': LatexEditorMenu;
}
}
@@ -2,12 +2,10 @@ import { InlineAdapterExtensions } from './adapters/extensions';
import { DefaultInlineManagerExtension } from './default-inline-manager';
import { InlineSpecExtensions } from './inline-spec';
import { MarkdownExtensions } from './markdown';
import { LatexEditorInlineManagerExtension } from './nodes/latex-node/latex-editor-menu';
export const inlinePresetExtensions = [
DefaultInlineManagerExtension,
...MarkdownExtensions,
LatexEditorInlineManagerExtension,
...InlineSpecExtensions,
...InlineAdapterExtensions,
];
@@ -1,8 +1,8 @@
import { inlineFootnoteExtensions } from '@blocksuite/affine-inline-footnote';
import { inlineLatexExtensions } from '@blocksuite/affine-inline-latex';
import { inlineLinkExtensions } from '@blocksuite/affine-inline-link';
import { inlineReferenceExtensions } from '@blocksuite/affine-inline-reference';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { StdIdentifier } from '@blocksuite/block-std';
import {
type InlineRootElement,
InlineSpecExtension,
@@ -97,37 +97,6 @@ export const ColorInlineSpecExtension =
},
});
export const LatexInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>('latex', provider => {
const std = provider.get(StdIdentifier);
return {
name: 'latex',
schema: z.string().optional().nullable().catch(undefined),
match: delta => typeof delta.attributes?.latex === 'string',
renderer: ({ delta, selected, editor, startOffset, endOffset }) => {
return html`<affine-latex-node
.std=${std}
.delta=${delta}
.selected=${selected}
.editor=${editor}
.startOffset=${startOffset}
.endOffset=${endOffset}
></affine-latex-node>`;
},
embed: true,
};
});
export const LatexEditorUnitSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'latex-editor-unit',
schema: z.undefined(),
match: () => true,
renderer: ({ delta }) => {
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
},
});
export const InlineSpecExtensions: ExtensionType[] = [
BoldInlineSpecExtension,
ItalicInlineSpecExtension,
@@ -136,9 +105,8 @@ export const InlineSpecExtensions: ExtensionType[] = [
CodeInlineSpecExtension,
BackgroundInlineSpecExtension,
ColorInlineSpecExtension,
LatexInlineSpecExtension,
...inlineLinkExtensions,
...inlineReferenceExtensions,
LatexEditorUnitSpecExtension,
...inlineLatexExtensions,
...inlineFootnoteExtensions,
];
@@ -1,5 +1,6 @@
import { LatexExtension } from '@blocksuite/affine-inline-latex';
import { LinkExtension } from '@blocksuite/affine-inline-link';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import type { BlockComponent } from '@blocksuite/block-std';
import { InlineMarkdownExtension } from '@blocksuite/block-std/inline';
import type { ExtensionType } from '@blocksuite/store';
@@ -363,236 +364,6 @@ export const CodeExtension = InlineMarkdownExtension<AffineTextAttributes>({
},
});
export const LinkExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'link',
pattern: /.*\[(.+?)\]\((.+?)\)$/,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = prefixText.match(pattern);
if (!match) return;
const linkText = match[1];
const linkUrl = match[2];
const annotatedText = match[0].slice(-linkText.length - linkUrl.length - 4);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
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(
{
index: startIndex,
length: linkText.length,
},
{
link: linkUrl,
}
);
inlineEditor.setInlineRange({
index: startIndex + linkText.length,
length: 0,
});
},
});
export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'latex',
pattern:
/(?:\$\$)(?<content>[^$]+)(?:\$\$)$|(?<blockPrefix>\$\$\$\$)|(?<inlinePrefix>\$\$)$/g,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = pattern.exec(prefixText);
if (!match || !match.groups) return;
const content = match.groups['content'];
const inlinePrefix = match.groups['inlinePrefix'];
const blockPrefix = match.groups['blockPrefix'];
if (blockPrefix === '$$$$') {
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
if (!inlineEditor.rootElement) return;
const blockComponent =
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
if (!blockComponent) return;
const doc = blockComponent.doc;
const parentComponent = blockComponent.parentComponent;
if (!parentComponent) return;
const index = parentComponent.model.children.indexOf(
blockComponent.model
);
if (index === -1) return;
inlineEditor.deleteText({
index: inlineRange.index - 4,
length: 5,
});
const id = doc.addBlock(
'affine:latex',
{
latex: '',
},
parentComponent.model,
index + 1
);
blockComponent.host.updateComplete
.then(() => {
const latexBlock = blockComponent.std.view.getBlock(id);
if (!latexBlock || latexBlock.flavour !== 'affine:latex') return;
//FIXME(@Flrande): wait for refactor
// @ts-expect-error BS-2241
latexBlock.toggleEditor();
})
.catch(console.error);
return;
}
if (inlinePrefix === '$$') {
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
inlineEditor.deleteText({
index: inlineRange.index - 2,
length: 3,
});
inlineEditor.insertText(
{
index: inlineRange.index - 2,
length: 0,
},
' '
);
inlineEditor.formatText(
{
index: inlineRange.index - 2,
length: 1,
},
{
latex: '',
}
);
inlineEditor
.waitForUpdate()
.then(async () => {
await inlineEditor.waitForUpdate();
const textPoint = inlineEditor.getTextPoint(
inlineRange.index - 2 + 1
);
if (!textPoint) return;
const [text] = textPoint;
const latexNode = text.parentElement?.closest('affine-latex-node');
if (!latexNode) return;
latexNode.toggleEditor();
})
.catch(console.error);
return;
}
if (!content || content.length === 0) return;
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
const startIndex = inlineRange.index - 2 - content.length - 2;
inlineEditor.deleteText({
index: startIndex,
length: 2 + content.length + 2 + 1,
});
inlineEditor.insertText(
{
index: startIndex,
length: 0,
},
' '
);
inlineEditor.formatText(
{
index: startIndex,
length: 1,
},
{
latex: String.raw`${content}`,
}
);
inlineEditor.setInlineRange({
index: startIndex + 1,
length: 0,
});
},
});
export const MarkdownExtensions: ExtensionType[] = [
BoldItalicMarkdown,
BoldMarkdown,
@@ -9,6 +9,7 @@
"references": [
{ "path": "../../components" },
{ "path": "../footnote" },
{ "path": "../latex" },
{ "path": "../link" },
{ "path": "../reference" },
{ "path": "../../model" },
+20 -1
View File
@@ -31,6 +31,7 @@ export const PackageList = [
'blocksuite/affine/gfx/text',
'blocksuite/affine/gfx/turbo-renderer',
'blocksuite/affine/inlines/footnote',
'blocksuite/affine/inlines/latex',
'blocksuite/affine/inlines/link',
'blocksuite/affine/inlines/preset',
'blocksuite/affine/inlines/reference',
@@ -101,6 +102,7 @@ export const PackageList = [
name: '@blocksuite/affine-block-code',
workspaceDependencies: [
'blocksuite/affine/components',
'blocksuite/affine/inlines/latex',
'blocksuite/affine/inlines/link',
'blocksuite/affine/inlines/preset',
'blocksuite/affine/model',
@@ -223,7 +225,7 @@ export const PackageList = [
workspaceDependencies: [
'blocksuite/affine/blocks/block-note',
'blocksuite/affine/components',
'blocksuite/affine/inlines/preset',
'blocksuite/affine/inlines/latex',
'blocksuite/affine/model',
'blocksuite/affine/rich-text',
'blocksuite/affine/shared',
@@ -302,6 +304,7 @@ export const PackageList = [
'blocksuite/affine/components',
'blocksuite/affine/fragments/fragment-doc-title',
'blocksuite/affine/gfx/text',
'blocksuite/affine/inlines/latex',
'blocksuite/affine/inlines/link',
'blocksuite/affine/inlines/preset',
'blocksuite/affine/inlines/reference',
@@ -469,6 +472,20 @@ export const PackageList = [
'blocksuite/framework/store',
],
},
{
location: 'blocksuite/affine/inlines/latex',
name: '@blocksuite/affine-inline-latex',
workspaceDependencies: [
'blocksuite/affine/components',
'blocksuite/affine/inlines/reference',
'blocksuite/affine/model',
'blocksuite/affine/rich-text',
'blocksuite/affine/shared',
'blocksuite/framework/block-std',
'blocksuite/framework/global',
'blocksuite/framework/store',
],
},
{
location: 'blocksuite/affine/inlines/link',
name: '@blocksuite/affine-inline-link',
@@ -488,6 +505,7 @@ export const PackageList = [
workspaceDependencies: [
'blocksuite/affine/components',
'blocksuite/affine/inlines/footnote',
'blocksuite/affine/inlines/latex',
'blocksuite/affine/inlines/link',
'blocksuite/affine/inlines/reference',
'blocksuite/affine/model',
@@ -1005,6 +1023,7 @@ export type PackageName =
| '@blocksuite/affine-gfx-text'
| '@blocksuite/affine-gfx-turbo-renderer'
| '@blocksuite/affine-inline-footnote'
| '@blocksuite/affine-inline-latex'
| '@blocksuite/affine-inline-link'
| '@blocksuite/affine-inline-preset'
| '@blocksuite/affine-inline-reference'
+1
View File
@@ -78,6 +78,7 @@
{ "path": "./blocksuite/affine/gfx/text" },
{ "path": "./blocksuite/affine/gfx/turbo-renderer" },
{ "path": "./blocksuite/affine/inlines/footnote" },
{ "path": "./blocksuite/affine/inlines/latex" },
{ "path": "./blocksuite/affine/inlines/link" },
{ "path": "./blocksuite/affine/inlines/preset" },
{ "path": "./blocksuite/affine/inlines/reference" },
+39 -1
View File
@@ -2395,6 +2395,7 @@ __metadata:
resolution: "@blocksuite/affine-block-code@workspace:blocksuite/affine/blocks/block-code"
dependencies:
"@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-inline-latex": "workspace:*"
"@blocksuite/affine-inline-link": "workspace:*"
"@blocksuite/affine-inline-preset": "workspace:*"
"@blocksuite/affine-model": "workspace:*"
@@ -2611,7 +2612,7 @@ __metadata:
dependencies:
"@blocksuite/affine-block-note": "workspace:*"
"@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-inline-preset": "workspace:*"
"@blocksuite/affine-inline-latex": "workspace:*"
"@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-rich-text": "workspace:*"
"@blocksuite/affine-shared": "workspace:*"
@@ -2739,6 +2740,7 @@ __metadata:
"@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-fragment-doc-title": "workspace:*"
"@blocksuite/affine-gfx-text": "workspace:*"
"@blocksuite/affine-inline-latex": "workspace:*"
"@blocksuite/affine-inline-link": "workspace:*"
"@blocksuite/affine-inline-preset": "workspace:*"
"@blocksuite/affine-inline-reference": "workspace:*"
@@ -3038,6 +3040,40 @@ __metadata:
languageName: unknown
linkType: soft
"@blocksuite/affine-inline-latex@workspace:*, @blocksuite/affine-inline-latex@workspace:blocksuite/affine/inlines/latex":
version: 0.0.0-use.local
resolution: "@blocksuite/affine-inline-latex@workspace:blocksuite/affine/inlines/latex"
dependencies:
"@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-inline-reference": "workspace:*"
"@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-rich-text": "workspace:*"
"@blocksuite/affine-shared": "workspace:*"
"@blocksuite/block-std": "workspace:*"
"@blocksuite/global": "workspace:*"
"@blocksuite/icons": "npm:^2.2.6"
"@blocksuite/store": "workspace:*"
"@floating-ui/dom": "npm:^1.6.13"
"@lit/context": "npm:^1.1.2"
"@preact/signals-core": "npm:^1.8.0"
"@toeverything/theme": "npm:^1.1.12"
"@types/hast": "npm:^3.0.4"
"@types/katex": "npm:^0.16.7"
"@types/lodash-es": "npm:^4.17.12"
collapse-white-space: "npm:^2.1.0"
date-fns: "npm:^4.0.0"
katex: "npm:^0.16.11"
lit: "npm:^3.2.0"
lit-html: "npm:^3.2.1"
lodash-es: "npm:^4.17.21"
remark-math: "npm:^6.0.0"
rxjs: "npm:^7.8.1"
shiki: "npm:^3.0.0"
yjs: "npm:^13.6.21"
zod: "npm:^3.23.8"
languageName: unknown
linkType: soft
"@blocksuite/affine-inline-link@workspace:*, @blocksuite/affine-inline-link@workspace:blocksuite/affine/inlines/link":
version: 0.0.0-use.local
resolution: "@blocksuite/affine-inline-link@workspace:blocksuite/affine/inlines/link"
@@ -3072,6 +3108,7 @@ __metadata:
dependencies:
"@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-inline-footnote": "workspace:*"
"@blocksuite/affine-inline-latex": "workspace:*"
"@blocksuite/affine-inline-link": "workspace:*"
"@blocksuite/affine-inline-reference": "workspace:*"
"@blocksuite/affine-model": "workspace:*"
@@ -3399,6 +3436,7 @@ __metadata:
"@blocksuite/affine-gfx-text": "workspace:*"
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*"
"@blocksuite/affine-inline-footnote": "workspace:*"
"@blocksuite/affine-inline-latex": "workspace:*"
"@blocksuite/affine-inline-link": "workspace:*"
"@blocksuite/affine-inline-preset": "workspace:*"
"@blocksuite/affine-inline-reference": "workspace:*"