Skip to content

Commit 5e81da9

Browse files
committed
feat(json-crdt-peritext-ui): 🎸 persist <a> link annotation
1 parent 8b394e6 commit 5e81da9

File tree

5 files changed

+1614
-1587
lines changed

5 files changed

+1614
-1587
lines changed

src/json-crdt-peritext-ui/plugins/toolbar/cards/InlineConfigCard.tsx

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import {ContextSep} from 'nice-ui/lib/4-card/ContextMenu';
1616

1717
export interface InlineConfigCardProps {
1818
config: SliceConfigState<any>;
19+
onSave: () => void;
1920
}
2021

21-
export const InlineConfigCard: React.FC<InlineConfigCardProps> = ({config}) => {
22+
export const InlineConfigCard: React.FC<InlineConfigCardProps> = ({config, onSave}) => {
2223
const {toolbar} = useToolbarPlugin();
2324
const api = config.conf();
2425
const href = React.useMemo(() => () => config.conf().str(['href']), [config]);
@@ -30,42 +31,52 @@ export const InlineConfigCard: React.FC<InlineConfigCardProps> = ({config}) => {
3031

3132
return (
3233
<ContextPane style={{display: 'block', minWidth: 'calc(min(600px, max(50vw, 260px)))'}}>
33-
<ContextPaneHeader onCloseClick={() => toolbar.newSliceConfig.next(void 0)}>
34-
{icon ? (
35-
<Flex style={{alignItems: 'center', display: 'flex', fontSize: '14px'}}>
36-
{/* <Avatar width={24} height={24} badge={icon} /> */}
37-
<div style={{transform: 'scale(.8)', width: 28, height: 28, borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,.1)', opacity: .7, margin: '0 6px 0 8px'}}>
38-
{icon}
39-
</div>
40-
{name}
41-
{/* <Breadcrumbs crumbs={[<Breadcrumb compact>{name}</Breadcrumb>]} /> */}
42-
</Flex>
43-
) : (
44-
<Breadcrumbs crumbs={[<Breadcrumb compact>{name}</Breadcrumb>]} />
45-
)}
46-
</ContextPaneHeader>
47-
<div style={{padding: '8px 16px'}}>
48-
<FormRow title={'Address'}>
49-
<CollaborativeInput str={href}
50-
input={(ref) => <Input focus inp={ref} type={'text'} size={-1} placeholder={'https://'} />} />
51-
</FormRow>
52-
{/* <FormRow title={'Title'}>
53-
<CollaborativeInput str={title}
54-
input={(ref) => <Input inp={ref} type={'text'} size={-1} placeholder={'Title'} />} />
55-
</FormRow> */}
56-
</div>
34+
<form onSubmit={(e) => {
35+
e.preventDefault();
36+
onSave();
37+
}}>
38+
<ContextPaneHeader onCloseClick={() => toolbar.newSliceConfig.next(void 0)}>
39+
{icon ? (
40+
<Flex style={{alignItems: 'center', display: 'flex', fontSize: '14px'}}>
41+
{/* <Avatar width={24} height={24} badge={icon} /> */}
42+
<div style={{transform: 'scale(.8)', width: 28, height: 28, borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,.1)', opacity: .7, margin: '0 6px 0 8px'}}>
43+
{icon}
44+
</div>
45+
{name}
46+
{/* <Breadcrumbs crumbs={[<Breadcrumb compact>{name}</Breadcrumb>]} /> */}
47+
</Flex>
48+
) : (
49+
<Breadcrumbs crumbs={[<Breadcrumb compact>{name}</Breadcrumb>]} />
50+
)}
51+
</ContextPaneHeader>
52+
<div style={{padding: '8px 16px'}}>
53+
<FormRow title={'Address'}>
54+
<CollaborativeInput str={href}
55+
input={(ref) => <Input focus inp={ref} type={'text'} size={-1} placeholder={'https://'} onKeyDown={(e) => {
56+
if (e.key === 'Enter') {
57+
e.preventDefault();
58+
onSave();
59+
}
60+
}} />} />
61+
</FormRow>
62+
{/* <FormRow title={'Title'}>
63+
<CollaborativeInput str={title}
64+
input={(ref) => <Input inp={ref} type={'text'} size={-1} placeholder={'Title'} />} />
65+
</FormRow> */}
66+
</div>
5767

58-
<ContextSep line />
59-
<ContextTitle>Preview</ContextTitle>
60-
<EmptyState emoji=' ' title=' ' />
61-
<ContextSep line />
62-
63-
{/* <div style={{padding: '4px 16px'}}> */}
64-
<div style={{padding: '0px 16px'}}>
65-
<FormRow>
66-
<Button lite block disabled={!hrefView}>Save</Button>
67-
</FormRow>
68-
</div>
68+
<ContextSep line />
69+
<ContextTitle>Preview</ContextTitle>
70+
<EmptyState emoji=' ' title=' ' />
71+
<ContextSep line />
72+
73+
{/* <div style={{padding: '4px 16px'}}> */}
74+
<div style={{padding: '0px 16px'}}>
75+
<FormRow>
76+
<Button small lite block disabled={!hrefView} submit>Save</Button>
77+
</FormRow>
78+
</div>
79+
</form>
6980
</ContextPane>
7081
);
7182
};

src/json-crdt-peritext-ui/plugins/toolbar/cursor/RenderFocus.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const RenderFocus: React.FC<RenderFocusProps> = ({children, cursor}) => {
1717
const [showInlineToolbarValue, toolbarVisibilityChangeTime] = useSyncStore(showInlineToolbar);
1818
const enableAfterCoolDown = useTimeout(300, [toolbarVisibilityChangeTime]);
1919
const isScrubbing = useSyncStoreOpt(surface.dom?.cursor.mouseDown) || false;
20-
const newSliceConfig = useSyncStore(toolbar.newSliceConfig);
20+
const config = useSyncStore(toolbar.newSliceConfig);
2121
// const focus = useSyncStoreOpt(surface.dom?.cursor.focus) || false;
2222
// const blurTimeout = useTimeout(300, [focus]);
2323

@@ -29,7 +29,7 @@ export const RenderFocus: React.FC<RenderFocusProps> = ({children, cursor}) => {
2929
let over: React.ReactNode | undefined;
3030
let under: React.ReactNode | undefined;
3131

32-
if (!newSliceConfig && showInlineToolbarValue && !isScrubbing && toolbar.txt.editor.mainCursor() === cursor)
32+
if (!config && showInlineToolbarValue && !isScrubbing && toolbar.txt.editor.mainCursor() === cursor)
3333
over = (
3434
<MoveToViewport>
3535
<CaretToolbar
@@ -40,8 +40,8 @@ export const RenderFocus: React.FC<RenderFocusProps> = ({children, cursor}) => {
4040
</MoveToViewport>
4141
);
4242

43-
if (!!newSliceConfig && showInlineToolbarValue && !isScrubbing && toolbar.txt.editor.mainCursor() === cursor) {
44-
under = <InlineConfigCard config={newSliceConfig} />;
43+
if (!!config && showInlineToolbarValue && !isScrubbing && toolbar.txt.editor.mainCursor() === cursor) {
44+
under = <InlineConfigCard config={config} onSave={config.save} />;
4545
}
4646

4747
return (

src/json-crdt-peritext-ui/plugins/toolbar/state/format.ts renamed to src/json-crdt-peritext-ui/plugins/toolbar/state/NewSliceConfig.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import type {SliceRegistryEntry} from '../../../../json-crdt-extensions/peritext
44
import type {SliceConfigState} from './types';
55
import type {ObjNode} from '../../../../json-crdt/nodes';
66
import type {MenuItem} from '../types';
7+
import type {ToolbarState} from '.';
78

89
export class NewSliceConfig<Node extends ObjNode = ObjNode> implements SliceConfigState<Node> {
910
public readonly model: Model<ObjNode<{conf: any}>>;
1011

1112
constructor(
13+
public readonly state: ToolbarState,
1214
public readonly def: SliceRegistryEntry,
1315
public readonly menu?: MenuItem,
1416
) {
@@ -19,4 +21,16 @@ export class NewSliceConfig<Node extends ObjNode = ObjNode> implements SliceConf
1921
public conf(): ObjApi<Node> {
2022
return this.model.api.obj(['conf']) as ObjApi<Node>;
2123
}
24+
25+
public readonly save = () => {
26+
const state = this.state;
27+
state.newSliceConfig.next(void 0);
28+
const view = this.model.view();
29+
const data = view.conf as Record<string, unknown>;
30+
if (!data || typeof data !== 'object') return;
31+
if (!data.title) delete data.title;
32+
const et = state.surface.events.et;
33+
et.format(this.def.tag, 'many', data);
34+
et.cursor({move: [['focus', 'char', 0, true]]});
35+
};
2236
}

0 commit comments

Comments
 (0)