Skip to content

Commit 68c4b9f

Browse files
committed
✨(frontend) interlinking custom inline content
We want to be able to interlink documents in the editor. We created a custom inline content that allows users to interlink documents.
1 parent 4e34135 commit 68c4b9f

File tree

16 files changed

+426
-10
lines changed

16 files changed

+426
-10
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to
88

99
## [Unreleased]
1010

11+
## Added
12+
13+
- ✨(frontend) Interlinking doc #904
14+
1115
## Fixed
1216

1317
- 🐛(helm) charts generate invalid YAML for collaboration API / WS #890

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,4 +706,53 @@ test.describe('Doc Editor', () => {
706706
'pink',
707707
);
708708
});
709+
710+
test('it checks interlink feature', async ({ page, browserName }) => {
711+
const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1);
712+
713+
await verifyDocName(page, randomDoc);
714+
715+
const [docChild1] = await createDoc(
716+
page,
717+
'doc-interlink-child-1',
718+
browserName,
719+
1,
720+
true,
721+
);
722+
723+
await verifyDocName(page, docChild1);
724+
725+
const [docChild2] = await createDoc(
726+
page,
727+
'doc-interlink-child-2',
728+
browserName,
729+
1,
730+
true,
731+
);
732+
733+
await verifyDocName(page, docChild2);
734+
735+
await page.locator('.bn-block-outer').last().fill('/');
736+
await page.getByText('Link to a page').first().click();
737+
738+
await page
739+
.locator(
740+
"span[data-inline-content-type='interlinkingSearchInline'] input",
741+
)
742+
.fill('interlink-child-1');
743+
744+
await page
745+
.locator('.quick-search-container')
746+
.getByText('interlink-child-1')
747+
.click();
748+
749+
const interlink = page.getByRole('link', {
750+
name: 'child-1',
751+
});
752+
753+
await expect(interlink).toBeVisible();
754+
await interlink.click();
755+
756+
await verifyDocName(page, docChild1);
757+
});
709758
});

src/frontend/apps/impress/src/components/Box.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,12 @@ export interface BoxProps {
4444
export type BoxType = ComponentPropsWithRef<typeof Box>;
4545

4646
export const Box = styled('div')<BoxProps>`
47-
display: flex;
48-
flex-direction: column;
4947
${({ $align }) => $align && `align-items: ${$align};`}
5048
${({ $background }) => $background && `background: ${$background};`}
5149
${({ $color }) => $color && `color: ${$color};`}
52-
${({ $direction }) => $direction && `flex-direction: ${$direction};`}
53-
${({ $display }) => $display && `display: ${$display};`}
50+
${({ $direction }) => `flex-direction: ${$direction || 'column'};`}
51+
${({ $display, as }) =>
52+
`display: ${$display || as?.match('span|input') ? 'inline-flex' : 'flex'};`}
5453
${({ $flex }) => $flex && `flex: ${$flex};`}
5554
${({ $gap }) => $gap && `gap: ${$gap};`}
5655
${({ $height }) => $height && `height: ${$height};`}

src/frontend/apps/impress/src/components/DropdownMenu.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type DropdownMenuProps = {
2525
arrowCss?: BoxProps['$css'];
2626
buttonCss?: BoxProps['$css'];
2727
disabled?: boolean;
28+
opened?: boolean;
2829
topMessage?: string;
2930
selectedValues?: string[];
3031
afterOpenChange?: (isOpen: boolean) => void;
@@ -38,12 +39,13 @@ export const DropdownMenu = ({
3839
arrowCss,
3940
buttonCss,
4041
label,
42+
opened,
4143
topMessage,
4244
afterOpenChange,
4345
selectedValues,
4446
}: PropsWithChildren<DropdownMenuProps>) => {
4547
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
46-
const [isOpen, setIsOpen] = useState(false);
48+
const [isOpen, setIsOpen] = useState(opened ?? false);
4749
const blockButtonRef = useRef<HTMLDivElement>(null);
4850

4951
const onOpenChange = (isOpen: boolean) => {

src/frontend/apps/impress/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from './BoxButton';
44
export * from './Card';
55
export * from './DropButton';
66
export * from './DropdownMenu';
7+
export * from './quick-search';
78
export * from './Icon';
89
export * from './InfiniteScroll';
910
export * from './Link';
Lines changed: 14 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading

src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { codeBlock } from '@blocknote/code-block';
22
import {
33
BlockNoteSchema,
44
defaultBlockSpecs,
5+
defaultInlineContentSpecs,
56
withPageBreak,
67
} from '@blocknote/core';
78
import '@blocknote/core/fonts/inter.css';
@@ -28,6 +29,10 @@ import { randomColor } from '../utils';
2829
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
2930
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
3031
import { CalloutBlock, DividerBlock } from './custom-blocks';
32+
import {
33+
InterlinkingLinkInlineContent,
34+
InterlinkingSearchInlineContent,
35+
} from './custom-inline-content';
3136

3237
export const blockNoteSchema = withPageBreak(
3338
BlockNoteSchema.create({
@@ -36,6 +41,11 @@ export const blockNoteSchema = withPageBreak(
3641
callout: CalloutBlock,
3742
divider: DividerBlock,
3843
},
44+
inlineContentSpecs: {
45+
...defaultInlineContentSpecs,
46+
interlinkingSearchInline: InterlinkingSearchInlineContent,
47+
interlinkingLinkInline: InterlinkingLinkInlineContent,
48+
},
3949
}),
4050
);
4151

src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const BlockNoteSuggestionMenu = () => {
3737
const index = defaultMenu.findIndex((item) => item.title === 'Code Block');
3838
const newSlashMenuItems = [
3939
...defaultMenu.slice(0, index + 1),
40-
...getInterlinkingMenuItems(t),
40+
...getInterlinkingMenuItems(editor, t),
4141
...defaultMenu.slice(index + 1),
4242
];
4343

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* eslint-disable react-hooks/rules-of-hooks */
2+
import { createReactInlineContentSpec } from '@blocknote/react';
3+
import { css } from 'styled-components';
4+
5+
import { StyledLink, Text } from '@/components';
6+
import { useCunninghamTheme } from '@/cunningham';
7+
import SelectedPageIcon from '@/docs/doc-editor/assets/doc-selected.svg';
8+
9+
export const InterlinkingLinkInlineContent = createReactInlineContentSpec(
10+
{
11+
type: 'interlinkingLinkInline',
12+
propSchema: {
13+
url: {
14+
default: '',
15+
},
16+
title: {
17+
default: '',
18+
},
19+
},
20+
content: 'none',
21+
},
22+
{
23+
render: (props) => {
24+
return <LinkSelected {...props.inlineContent.props} />;
25+
},
26+
},
27+
);
28+
29+
interface LinkSelectedProps {
30+
url: string;
31+
title: string;
32+
}
33+
const LinkSelected = ({ url, title }: LinkSelectedProps) => {
34+
const { colorsTokens } = useCunninghamTheme();
35+
36+
return (
37+
<StyledLink
38+
href={url}
39+
$css={css`
40+
display: inline;
41+
padding: 0.1rem 0.4rem;
42+
border-radius: 4px;
43+
& svg {
44+
position: relative;
45+
top: 2px;
46+
margin-right: 0.2rem;
47+
}
48+
&:hover {
49+
background-color: ${colorsTokens['greyscale-100']};
50+
}
51+
transition: background-color 0.2s ease-in-out;
52+
`}
53+
>
54+
<SelectedPageIcon width={11.5} />
55+
<Text $weight="500" spellCheck="false" $size="16px" $display="inline">
56+
{title}
57+
</Text>
58+
</StyledLink>
59+
);
60+
};

0 commit comments

Comments
 (0)