mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-14 16:46:22 +08:00
feat: Intra-line double link interaction
This commit is contained in:
@@ -1,8 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import clsx from 'clsx';
|
||||
import style9 from 'style9';
|
||||
|
||||
import { BackwardUndoIcon } from '@toeverything/components/icons';
|
||||
import {
|
||||
BaseButton,
|
||||
ListButton,
|
||||
@@ -12,9 +8,11 @@ import {
|
||||
} from '@toeverything/components/ui';
|
||||
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
|
||||
import { BlockSearchItem } from '@toeverything/datasource/jwt';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import style9 from 'style9';
|
||||
import { BlockPreview } from '../block-preview';
|
||||
import { BackwardUndoIcon } from '@toeverything/components/icons';
|
||||
|
||||
export const commonListContainer = 'commonListContainer';
|
||||
|
||||
@@ -28,6 +26,7 @@ export type CommonListItem = {
|
||||
divider?: string;
|
||||
content?: Content;
|
||||
block?: BlockSearchItem;
|
||||
renderCustom?: (props: CommonListItem) => JSX.Element;
|
||||
};
|
||||
|
||||
type MenuItemsProps = {
|
||||
@@ -45,7 +44,7 @@ export const CommonList = (props: MenuItemsProps) => {
|
||||
// ]);
|
||||
// TODO Insert bidirectional link to be developed
|
||||
const JSONUnsupportedBlockTypes = ['page'];
|
||||
let usedItems = items.filter(item => {
|
||||
const usedItems = items.filter(item => {
|
||||
return !JSONUnsupportedBlockTypes.includes(item?.content?.id);
|
||||
});
|
||||
return (
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { SearchIcon } from '@toeverything/components/icons';
|
||||
import { ErrorBoundary, isEqual } from '@toeverything/utils';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import isUrl from 'is-url';
|
||||
import React, {
|
||||
CSSProperties,
|
||||
DragEvent,
|
||||
forwardRef,
|
||||
KeyboardEvent,
|
||||
KeyboardEventHandler,
|
||||
MouseEvent,
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
forwardRef,
|
||||
MouseEventHandler,
|
||||
useLayoutEffect,
|
||||
CSSProperties,
|
||||
MouseEvent,
|
||||
DragEvent,
|
||||
} from 'react';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import {
|
||||
createEditor,
|
||||
Descendant,
|
||||
Range,
|
||||
Element as SlateElement,
|
||||
Editor,
|
||||
Transforms,
|
||||
Element as SlateElement,
|
||||
Node,
|
||||
Path,
|
||||
Range,
|
||||
Transforms,
|
||||
} from 'slate';
|
||||
import {
|
||||
Editable,
|
||||
withReact,
|
||||
Slate,
|
||||
ReactEditor,
|
||||
Slate,
|
||||
useSlateStatic,
|
||||
withReact,
|
||||
} from 'slate-react';
|
||||
|
||||
import { ErrorBoundary, isEqual, uaHelper } from '@toeverything/utils';
|
||||
|
||||
import { Contents, SlateUtils, isSelectAll } from './slate-utils';
|
||||
import { CustomElement } from '..';
|
||||
import { HOTKEYS, INLINE_STYLES } from './constants';
|
||||
import { TextWithComments } from './element-leaf/TextWithComments';
|
||||
import { InlineDate, withDate } from './plugins/date';
|
||||
import { DoubleLinkComponent } from './plugins/DoubleLink';
|
||||
import { LinkComponent, LinkModal, withLinks, wrapLink } from './plugins/link';
|
||||
import { InlineRefLink } from './plugins/reflink';
|
||||
import { Contents, isSelectAll, SlateUtils } from './slate-utils';
|
||||
import {
|
||||
getCommentsIdsOnTextNode,
|
||||
getExtraPropertiesFromEditorOutmostNode,
|
||||
isInterceptCharacter,
|
||||
matchMarkdown,
|
||||
} from './utils';
|
||||
import { HOTKEYS, INLINE_STYLES } from './constants';
|
||||
import { LinkComponent, LinkModal, withLinks, wrapLink } from './plugins/link';
|
||||
import { withDate, InlineDate } from './plugins/date';
|
||||
import { CustomElement } from '..';
|
||||
import isUrl from 'is-url';
|
||||
import { InlineRefLink } from './plugins/reflink';
|
||||
import { TextWithComments } from './element-leaf/TextWithComments';
|
||||
|
||||
export interface TextProps {
|
||||
/** read only */
|
||||
@@ -739,6 +739,9 @@ const EditorElement = (props: any) => {
|
||||
|
||||
switch (element.type) {
|
||||
case 'link': {
|
||||
if (element.linkType === 'doubleLink') {
|
||||
return <DoubleLinkComponent {...props} editor={editor} />;
|
||||
}
|
||||
return (
|
||||
<LinkComponent
|
||||
{...props}
|
||||
@@ -839,6 +842,15 @@ const EditorLeaf = ({ attributes, children, leaf }: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (leaf.doubleLinkSearch) {
|
||||
customChildren = (
|
||||
<span style={{ backgroundColor: '#eee' }}>
|
||||
<SearchIcon style={{ width: '16px', height: '16px' }} />
|
||||
{customChildren}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
customChildren = (
|
||||
<TextWithComments commentsIds={commentsIds}>
|
||||
{customChildren}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { PagesIcon } from '@toeverything/components/icons';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const DoubleLinkComponent = ({ attributes, children, element }: any) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClickLinkText = useCallback(
|
||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const { workspaceId, blockId } = element;
|
||||
navigate(`/${workspaceId}/${blockId}`);
|
||||
},
|
||||
[element, navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
<span>
|
||||
<PagesIcon style={{ verticalAlign: 'middle', height: '20px' }} />
|
||||
<a
|
||||
{...attributes}
|
||||
href={`/${element.workspaceId}/${element.blockId}`}
|
||||
>
|
||||
<span onClick={handleClickLinkText}>{children}</span>
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
Transforms,
|
||||
} from 'slate';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
|
||||
import {
|
||||
fontBgColorPalette,
|
||||
fontColorPalette,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
import {
|
||||
getCommentsIdsOnTextNode,
|
||||
getEditorMarkForCommentId,
|
||||
getRandomString,
|
||||
MARKDOWN_STYLE_MAP,
|
||||
MatchRes,
|
||||
} from './utils';
|
||||
@@ -246,7 +246,11 @@ Editor.insertText = function (
|
||||
const { path, offset } = at;
|
||||
if (text.length > 0) {
|
||||
const marks = Editor.marks(editor);
|
||||
if (text === '\u0020' && Object.keys(marks).length) {
|
||||
if (
|
||||
text === '\u0020' &&
|
||||
Object.keys(marks).length &&
|
||||
!(marks as any).doubleLinkSearch
|
||||
) {
|
||||
// If the input is a space and the mark has content, remove the mark
|
||||
const newPath = [path[0], path[1] + 1];
|
||||
const newPoint = {
|
||||
@@ -1084,34 +1088,130 @@ class SlateUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public insertReference(reference: string) {
|
||||
try {
|
||||
// Transforms.setSelection(this.editor, this.getEndSelection());
|
||||
const { anchor, focus } = this.getEndSelection();
|
||||
Transforms.insertNodes(
|
||||
public setDoubleLinkSearchSlash(point: Point) {
|
||||
const str = Editor.string(this.editor, {
|
||||
anchor: this.getStart(),
|
||||
focus: point,
|
||||
});
|
||||
if (str.endsWith('[[')) {
|
||||
Transforms.select(this.editor, {
|
||||
anchor: point,
|
||||
focus: Object.assign({}, point, {
|
||||
offset: point.offset - 2,
|
||||
}),
|
||||
});
|
||||
Editor.addMark(this.editor, 'doubleLinkSearch', true);
|
||||
Transforms.select(this.editor, {
|
||||
anchor: this.editor.selection.anchor,
|
||||
focus: this.editor.selection.anchor,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getDoubleLinkSearchSlashText() {
|
||||
const nodes = Editor.nodes(this.editor, {
|
||||
at: [],
|
||||
//@ts-ignore
|
||||
match: node => !!node.doubleLinkSearch,
|
||||
});
|
||||
const searchNode = nodes.next().value;
|
||||
if (searchNode && (searchNode[0] as { text?: string }).text) {
|
||||
return (searchNode[0] as { text?: string }).text;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public setSelectDoubleLinkSearchSlash() {
|
||||
const nodes = Editor.nodes(this.editor, {
|
||||
at: [],
|
||||
//@ts-ignore
|
||||
match: node => !!node.doubleLinkSearch,
|
||||
});
|
||||
const searchNode = nodes.next().value;
|
||||
if (searchNode) {
|
||||
const text = (searchNode[0] as { text?: string })?.text || '';
|
||||
const path = searchNode[1];
|
||||
const anchor = Editor.before(
|
||||
this.editor,
|
||||
{
|
||||
type: 'reflink',
|
||||
reference,
|
||||
children: [],
|
||||
path,
|
||||
offset: 1,
|
||||
},
|
||||
{ at: focus || anchor }
|
||||
{
|
||||
unit: 'offset',
|
||||
}
|
||||
);
|
||||
|
||||
// requestAnimationFrame(() => {
|
||||
// console.log(this.editor.selection, this.editor.insertNode);
|
||||
// this.editor.insertNode({
|
||||
// type: 'reflink',
|
||||
// reference,
|
||||
// children: [{ text: '' }]
|
||||
// });
|
||||
// // Transforms.select();
|
||||
// });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
const focus = Editor.after(
|
||||
this.editor,
|
||||
{
|
||||
path,
|
||||
offset: text.length - 1,
|
||||
},
|
||||
{
|
||||
unit: 'offset',
|
||||
}
|
||||
);
|
||||
Transforms.select(this.editor, { anchor, focus });
|
||||
}
|
||||
}
|
||||
|
||||
public removeDoubleLinkSearchSlash(isRemoveSlash?: boolean) {
|
||||
if (isRemoveSlash) {
|
||||
const nodes = Editor.nodes(this.editor, {
|
||||
at: [],
|
||||
//@ts-ignore
|
||||
match: node => !!node.doubleLinkSearch,
|
||||
});
|
||||
const searchNode = nodes.next().value;
|
||||
if (searchNode) {
|
||||
const text = (searchNode[0] as { text?: string })?.text || '';
|
||||
if (text.startsWith('[[')) {
|
||||
const path = searchNode[1];
|
||||
Transforms.delete(this.editor, {
|
||||
at: {
|
||||
path,
|
||||
offset: 0,
|
||||
},
|
||||
distance: text.length,
|
||||
unit: 'character',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Transforms.setNodes(
|
||||
this.editor,
|
||||
{ doubleLinkSearch: null } as Partial<SlateNode>,
|
||||
{
|
||||
at: [],
|
||||
match: node => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
return !!node.doubleLinkSearch;
|
||||
},
|
||||
}
|
||||
);
|
||||
this.editor.removeMark('doubleLinkSearch');
|
||||
}
|
||||
|
||||
public insertDoubleLink(
|
||||
workspaceId: string,
|
||||
linkBlockId: string,
|
||||
children: any[]
|
||||
) {
|
||||
const link = {
|
||||
type: 'link',
|
||||
linkType: 'doubleLink',
|
||||
workspaceId: workspaceId,
|
||||
blockId: linkBlockId,
|
||||
children: children,
|
||||
id: getRandomString('link'),
|
||||
};
|
||||
Transforms.insertNodes(this.editor, link);
|
||||
requestAnimationFrame(() => {
|
||||
ReactEditor.focus(this.editor);
|
||||
});
|
||||
}
|
||||
|
||||
/** todo improve if selection is collapsed */
|
||||
public getCommentsIdsBySelection() {
|
||||
const commentedTextNodes = Editor.nodes(this.editor, {
|
||||
|
||||
Reference in New Issue
Block a user