feat: Intra-line double link interaction

This commit is contained in:
xiaodong zuo
2022-08-18 16:22:45 +08:00
parent 879a208034
commit 919e0d08d5
18 changed files with 859 additions and 460 deletions
@@ -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, {