Skip to content

Commit edbc869

Browse files
committed
feat(json-crdt-peritext-ui): 🎸 add caret bottom state management and ability to select
1 parent 6e9ae74 commit edbc869

File tree

4 files changed

+65
-34
lines changed

4 files changed

+65
-34
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as React from 'react';
2+
import {ContextPane, ContextItem, ContextSep} from 'nice-ui/lib/4-card/ContextMenu';
3+
import {SYMBOL} from 'nano-theme';
4+
import {FormattingGenericIcon} from '../../../components/FormattingGenericIcon';
5+
import {Formatting} from '../../../state/Formatting';
6+
7+
export interface FormattingListProps {
8+
formattings: Formatting[];
9+
onSelect: (formatting: Formatting) => void;
10+
}
11+
12+
export const FormattingList: React.FC<FormattingListProps> = ({formattings, onSelect}) => {
13+
if (!formattings.length) return;
14+
15+
return (
16+
<ContextPane style={{minWidth: 'calc(max(220px, min(360px, 80vw)))'}}>
17+
<ContextSep />
18+
{formattings.map((formatting) => {
19+
const {behavior} = formatting;
20+
const data = behavior.data();
21+
const menu = data.menu;
22+
const previewText = data.previewText?.(formatting) || '';
23+
const previewTextFormatted = previewText.length < 20 ? previewText : `${previewText.slice(0, 20)}${SYMBOL.ELLIPSIS}`;
24+
return (
25+
<ContextItem inset
26+
key={formatting.key()}
27+
icon={menu?.icon?.()}
28+
right={data.renderIcon?.(formatting) || <FormattingGenericIcon formatting={formatting} />}
29+
onClick={() => onSelect(formatting)}
30+
>
31+
{menu?.name ?? behavior.name}
32+
{!!previewTextFormatted && (
33+
<span style={{opacity: 0.5}}>
34+
{previewTextFormatted}
35+
</span>
36+
)}
37+
</ContextItem>
38+
);
39+
})}
40+
<ContextSep />
41+
</ContextPane>
42+
);
43+
};
Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as React from 'react';
2-
import {ContextPane, ContextItem, ContextSep} from 'nice-ui/lib/4-card/ContextMenu';
32
import {useToolbarPlugin} from '../../../context';
4-
import {SYMBOL} from 'nano-theme';
5-
import {FormattingGenericIcon} from '../../../components/FormattingGenericIcon';
63
import {CaretBottomState} from './state';
4+
import {FormattingList} from './FormattingList';
5+
import {useBehaviorSubject} from 'nice-ui/lib/hooks/useBehaviorSubject';
76
import type {CaretViewProps} from '../../../../../web/react/cursor/CaretView';
87

98
export interface CaretBottomOverlayProps extends CaretViewProps {
@@ -14,36 +13,13 @@ export const CaretBottomOverlay: React.FC<CaretBottomOverlayProps> = (props) =>
1413
const {fwd, bwd} = props;
1514
const inline = fwd || bwd;
1615
const {toolbar} = useToolbarPlugin();
17-
const state = React.useMemo(() => new CaretBottomState(toolbar), [toolbar]);
18-
const formattings = React.useMemo(() => state.getFormatting(inline), [inline?.key()]);
16+
const state = React.useMemo(() => new CaretBottomState(toolbar, inline), [toolbar, inline?.key()]);
17+
const formattings = React.useMemo(() => state.getFormatting(), [state]);
18+
const selected = useBehaviorSubject(state.selected$);
1919

20-
if (!formattings.length) return;
20+
if (selected) {
21+
return (<div>selected: {selected.slice.toString()}</div>);
22+
}
2123

22-
return (
23-
<ContextPane style={{minWidth: 'calc(max(220px, min(360px, 80vw)))'}}>
24-
<ContextSep />
25-
{formattings.map((formatting) => {
26-
const {behavior} = formatting;
27-
const data = behavior.data();
28-
const menu = data.menu;
29-
const previewText = data.previewText?.(formatting) || '';
30-
const previewTextFormatted = previewText.length < 20 ? previewText : `${previewText.slice(0, 20)}${SYMBOL.ELLIPSIS}`;
31-
return (
32-
<ContextItem inset
33-
icon={menu?.icon?.()}
34-
right={data.renderIcon?.(formatting) || <FormattingGenericIcon formatting={formatting} />}
35-
onClick={() => {}}
36-
>
37-
{menu?.name ?? behavior.name}
38-
{!!previewTextFormatted && (
39-
<span style={{opacity: 0.5}}>
40-
{previewTextFormatted}
41-
</span>
42-
)}
43-
</ContextItem>
44-
);
45-
})}
46-
<ContextSep />
47-
</ContextPane>
48-
);
24+
return <FormattingList formattings={formattings} onSelect={state.onSelect} />;
4925
};

‎src/json-crdt-peritext-ui/plugins/toolbar/cursor/caret/CaretBottomOverlay/state.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
import {BehaviorSubject} from 'rxjs';
12
import {Formatting} from '../../../state/Formatting';
23
import type {Inline} from '../../../../../../json-crdt-extensions';
34
import type {ToolbarState} from '../../../state';
45

56
export class CaretBottomState {
7+
public readonly selected$ = new BehaviorSubject<Formatting | null>(null);
8+
69
public constructor(
710
public readonly state: ToolbarState,
11+
public readonly inline: Inline | undefined,
812
) {}
913

10-
public getFormatting(inline?: Inline): Formatting[] {
14+
public getFormatting(inline: Inline | undefined = this.inline): Formatting[] {
1115
const slices = inline?.p1.layers;
1216
const res: Formatting[] = [];
1317
if (!slices) return res;
@@ -23,4 +27,8 @@ export class CaretBottomState {
2327
}
2428
return res;
2529
};
30+
31+
public readonly onSelect = (formatting: Formatting) => {
32+
this.selected$.next(formatting);
33+
};
2634
}

‎src/json-crdt-peritext-ui/plugins/toolbar/state/Formatting.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ export class Formatting {
1212
public readonly slice: Slice<string>,
1313
public readonly behavior: ToolbarSliceBehavior,
1414
) {}
15+
16+
public key(): number {
17+
return this.slice.hash;
18+
}
1519
}

0 commit comments

Comments
 (0)