Skip to content

Commit c3d87f1

Browse files
committed
feat(json-crdt-peritext-ui): 🎸 update formatting behavior registration
1 parent ade1e3f commit c3d87f1

File tree

10 files changed

+80
-53
lines changed

10 files changed

+80
-53
lines changed

src/json-crdt-peritext-ui/plugins/toolbar/formatting/ManageFormattingsCard/state.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ export class CaretBottomState {
1414
) {}
1515

1616
public getFormattings$(inline: Inline | undefined = this.inline): BehaviorSubject<SavedFormatting[]> {
17-
return subject(this.state.surface.render$, () => {
17+
const state = this.state;
18+
return subject(state.surface.render$, () => {
1819
const slices = inline?.p1.layers;
1920
const res: SavedFormatting[] = [];
2021
if (!slices) return res;
21-
const registry = this.state.txt.editor.getRegistry();
22+
const registry = state.txt.editor.getRegistry();
2223
for (const slice of slices) {
2324
const tag = slice.type;
2425
if (typeof tag !== 'number' && typeof tag !== 'string') continue;
@@ -27,7 +28,7 @@ export class CaretBottomState {
2728
const isConfigurable = !!behavior.schema;
2829
if (!isConfigurable) continue;
2930
if (!(slice instanceof PersistedSlice)) continue;
30-
res.push(new SavedFormatting(behavior, slice));
31+
res.push(new SavedFormatting(behavior, slice, state));
3132
}
3233
return res;
3334
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as React from 'react';
2+
import type {EditProps} from '../../types';
3+
4+
export interface FormattingNewProps extends EditProps {}
5+
6+
export const FormattingNew: React.FC<FormattingNewProps> = (props) => {
7+
const New = props.formatting.behavior.data().Edit;
8+
9+
return (
10+
New ? <New {...props} /> : <div>new renderer not provided</div>
11+
);
12+
};

src/json-crdt-peritext-ui/plugins/toolbar/formatting/tags/a/New.tsx renamed to src/json-crdt-peritext-ui/plugins/toolbar/formatting/tags/a/Edit.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ import {useSyncStoreOpt} from '../../../../../web/react/hooks';
77
import {ContextSep} from 'nice-ui/lib/4-card/ContextMenu';
88
import {BasicButtonClose} from 'nice-ui/lib/2-inline-block/BasicButton/BasicButtonClose';
99
import {UrlDisplayCard} from '../../../cards/UrlDisplayCard';
10-
import {NewProps} from '../../../types';
10+
import {EditableFormatting} from '../../../state/formattings';
1111
import type {CollaborativeStr} from 'collaborative-editor';
1212

13-
export const New: React.FC<NewProps> = ({formatting}) => {
13+
export interface EditProps {
14+
formatting: EditableFormatting;
15+
}
16+
17+
export const Edit: React.FC<EditProps> = ({formatting}) => {
1418
const inpRef = React.useRef<HTMLInputElement | null>(null);
1519
const href = React.useMemo(() => () => formatting.conf()?.str(['href']), [formatting]);
1620
const hrefView = useSyncStoreOpt(href()?.events) || '';

src/json-crdt-peritext-ui/plugins/toolbar/formatting/tags/a/behavior.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ import * as React from 'react';
22
import {Iconista} from 'nice-ui/lib/icons/Iconista';
33
import {Sidetip} from 'nice-ui/lib/1-inline/Sidetip';
44
import {renderIcon} from './renderIcon';
5-
import {New} from './New';
65
import {View} from './View';
6+
import {Edit} from './Edit';
77
import {ToolbarSliceBehaviorData} from '../../../types';
88
import {getDomain} from '../../../../../web/util';
99

1010
export const behavior = {
11+
menu: {
12+
name: 'Link',
13+
icon: () => <Iconista width={15} height={15} set="lucide" icon="link" />,
14+
// icon: () => <Iconista width={15} height={15} set="radix" icon="link-2" />,
15+
right: () => <Sidetip small>⌘ K</Sidetip>,
16+
keys: ['⌘', 'k'],
17+
},
1118
validate: (formatting) => {
1219
const obj = formatting.conf()?.view() as {href: string};
1320
if (!obj || typeof obj !== 'object') return [{code: 'INVALID_CONFIG'}];
@@ -17,19 +24,12 @@ export const behavior = {
1724
const domain = getDomain(href);
1825
return domain ? 'good' : 'fine';
1926
},
20-
menu: {
21-
name: 'Link',
22-
icon: () => <Iconista width={15} height={15} set="lucide" icon="link" />,
23-
// icon: () => <Iconista width={15} height={15} set="radix" icon="link-2" />,
24-
right: () => <Sidetip small>⌘ K</Sidetip>,
25-
keys: ['⌘', 'k'],
26-
},
2727
previewText: (formatting) => {
2828
const data = formatting.conf()?.view() as {href: string};
2929
if (!data || typeof data !== 'object') return '';
3030
return (data.href || '').replace(/^(https?:\/\/)?(www\.)?/, '');
3131
},
3232
renderIcon,
33-
New,
33+
Edit,
3434
View,
3535
} satisfies ToolbarSliceBehaviorData;
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as React from 'react';
2-
import type {NewProps} from '../../../types';
2+
import type {EditProps} from '../../../types';
33

4-
export interface FormattingNewProps extends NewProps {}
4+
export interface FormattingNewProps extends EditProps {}
55

66
export const FormattingNew: React.FC<FormattingNewProps> = (props) => {
7-
const New = props.formatting.behavior.data().New;
7+
const Edit = props.formatting.behavior.data().Edit;
88

99
return (
10-
New ? <New {...props} /> : <div>new renderer not provided</div>
10+
Edit ? <Edit isNew {...props} /> : <div>new renderer not provided</div>
1111
);
1212
};

src/json-crdt-peritext-ui/plugins/toolbar/formatting/views/new/FormattingNewCard.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {useT} from 'use-t';
12
import * as React from 'react';
23
import {ContextPane} from 'nice-ui/lib/4-card/ContextMenu/ContextPane';
34
import {useToolbarPlugin} from '../../../context';
@@ -20,6 +21,7 @@ export interface FormattingNewCardProps {
2021
}
2122

2223
export const FormattingNewCard: React.FC<FormattingNewCardProps> = ({formatting}) => {
24+
const [t] = useT();
2325
const {toolbar} = useToolbarPlugin();
2426
useSyncStoreOpt(formatting.conf()?.api);
2527
const validation = formatting.validate();
@@ -51,7 +53,15 @@ export const FormattingNewCard: React.FC<FormattingNewCardProps> = ({formatting}
5153
<ContextSep line />
5254

5355
<div style={{padding: '16px'}}>
54-
<Button small lite={!valid} positive={validation === 'good'} block disabled={!valid} submit>Save</Button>
56+
<Button
57+
small
58+
lite={!valid}
59+
positive={validation === 'good'}
60+
block
61+
disabled={!valid}
62+
submit
63+
onClick={formatting.save}
64+
>{t('Save')}</Button>
5565
</div>
5666
</form>
5767
</ContextPane>

src/json-crdt-peritext-ui/plugins/toolbar/formatting/views/view/FormattingView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
2-
import type {EditProps} from '../../../types';
2+
import type {ViewProps} from '../../../types';
33

4-
export interface FormattingViewProps extends EditProps {}
4+
export interface FormattingViewProps extends ViewProps {}
55

66
export const FormattingView: React.FC<FormattingViewProps> = (props) => {
77
const View = props.formatting.behavior.data().View;

src/json-crdt-peritext-ui/plugins/toolbar/inline/Link.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
import * as React from 'react';
33
import {rule} from 'nano-theme';
44

5+
const col = '#05f';
6+
57
const blockClass = rule({
6-
col: '#05f',
8+
col,
79
td: 'underline',
8-
textDecorationColor: '#05f',
10+
textDecorationColor: col,
911
textDecorationThickness: '1px',
1012
textDecorationStyle: 'wavy',
1113
textUnderlineOffset: '.25em',
1214
// textDecorationSkipInk: 'all',
1315

1416
'&:hover': {
15-
col: '#05f',
17+
col: col,
1618
},
1719
pd: 0,
1820
mr: 0,

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

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ export interface FormattingWithConfig<Node extends ObjNode = ObjNode> {
1919

2020
export interface ToolbarFormatting<R extends Range<string> = Range<string>, Node extends ObjNode = ObjNode> extends FormattingBase<ToolbarSliceBehavior, R>, FormattingWithConfig<Node> {}
2121

22-
export class RangeFormatting<R extends Range<string> = Range<string>, Node extends ObjNode = ObjNode> implements ToolbarFormatting<R, Node> {
22+
export abstract class EditableFormatting<R extends Range<string> = Range<string>, Node extends ObjNode = ObjNode> implements ToolbarFormatting<R, Node> {
2323
public constructor(
2424
public readonly behavior: ToolbarSliceBehavior,
2525
public readonly range: R,
26+
public readonly state: ToolbarState,
2627
) {}
2728

2829
public conf(): ObjApi<Node> | undefined {
@@ -32,6 +33,8 @@ export class RangeFormatting<R extends Range<string> = Range<string>, Node exten
3233
public validate(): ValidationResult {
3334
return this.behavior.data()?.validate?.(this) ?? 'fine';
3435
}
36+
37+
public abstract readonly save: () => void;
3538
}
3639

3740
/**
@@ -40,14 +43,7 @@ export class RangeFormatting<R extends Range<string> = Range<string>, Node exten
4043
* state (location, data) of the formatting and a {@link ToolbarSliceBehavior}
4144
* which defines the formatting behavior.
4245
*/
43-
export class SavedFormatting<Node extends ObjNode = ObjNode> extends RangeFormatting<PersistedSlice<string>, Node> {
44-
public constructor(
45-
public readonly behavior: ToolbarSliceBehavior,
46-
public readonly range: PersistedSlice<string>,
47-
) {
48-
super(behavior, range);
49-
}
50-
46+
export class SavedFormatting<Node extends ObjNode = ObjNode> extends EditableFormatting<PersistedSlice<string>, Node> {
5147
/**
5248
* @returns Unique key for this formatting. This is the hash of the slice.
5349
* This is used to identify the formatting in the UI.
@@ -60,21 +56,25 @@ export class SavedFormatting<Node extends ObjNode = ObjNode> extends RangeFormat
6056
const node = this.range.dataNode();
6157
return node instanceof ObjApi ? node : undefined;
6258
}
59+
60+
public readonly save = () => {
61+
throw new Error('save() not implemented');
62+
};
6363
}
6464

6565
/**
6666
* New formatting which is being created. Once created, it will be promoted to
6767
* a {@link SavedFormatting} instance.
6868
*/
69-
export class NewFormatting<Node extends ObjNode = ObjNode> extends RangeFormatting<Range<string>, Node> {
69+
export class NewFormatting<Node extends ObjNode = ObjNode> extends EditableFormatting<Range<string>, Node> {
7070
public readonly model: Model<ObjNode<{conf: any}>>;
7171

7272
constructor(
7373
public readonly behavior: ToolbarSliceBehavior,
7474
public readonly range: Range<string>,
7575
public readonly state: ToolbarState,
7676
) {
77-
super(behavior, range);
77+
super(behavior, range, state);
7878
const schema = s.obj({conf: behavior.schema || s.con(void 0)});
7979
this.model = Model.create(schema);
8080
}
@@ -86,12 +86,11 @@ export class NewFormatting<Node extends ObjNode = ObjNode> extends RangeFormatti
8686
public readonly save = () => {
8787
const state = this.state;
8888
state.newSlice.next(void 0);
89-
const view = this.model.view();
90-
const data = view.conf as Record<string, unknown>;
91-
if (!data || typeof data !== 'object') return;
92-
if (!data.title) delete data.title;
89+
const view = this.conf()?.view() as Record<string, unknown>;
90+
if (!view || typeof view !== 'object') return;
91+
if (!view.title) delete view.title;
9392
const et = state.surface.events.et;
94-
et.format('tog', this.behavior.tag, 'many', data);
93+
et.format('tog', this.behavior.tag, 'many', view);
9594
et.cursor({move: [['focus', 'char', 0, true]]});
9695
};
9796
}

src/json-crdt-peritext-ui/plugins/toolbar/types.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {SliceBehavior} from '../../../json-crdt-extensions/peritext/registr
44
import type {SliceStacking} from '../../../json-crdt-extensions/peritext/slice/constants';
55
import type {TypeTag} from '../../../json-crdt-extensions';
66
import type {NodeBuilder} from '../../../json-crdt-patch';
7-
import type {NewFormatting, SavedFormatting, ToolbarFormatting} from './state/formattings';
7+
import type {EditableFormatting, SavedFormatting, ToolbarFormatting} from './state/formattings';
88

99
export type {MenuItem};
1010

@@ -35,20 +35,15 @@ export interface ToolbarSliceBehaviorData extends Record<string, unknown> {
3535
*/
3636
renderIcon?: (props: IconProps) => React.ReactNode;
3737

38-
/**
39-
* Render a small card-sized form which configures the initial state of the
40-
* formatting, for it to be created.
41-
*/
42-
New?: React.FC<NewProps>;
43-
4438
/**
4539
* Render a small card-sized view, which can be placed in a popup, to
4640
* preview the formatting.
4741
*/
4842
View?: React.FC<ViewProps>;
4943

5044
/**
51-
* Render a small card-sized form to edit the formatting.
45+
* Render a small card-sized form which configures the state of the
46+
* formatting.
5247
*/
5348
Edit?: React.FC<EditProps>;
5449
}
@@ -67,16 +62,20 @@ export interface IconProps {
6762
formatting: ToolbarFormatting;
6863
}
6964

70-
export interface NewProps {
71-
formatting: NewFormatting;
72-
}
73-
7465
export interface ViewProps {
7566
formatting: SavedFormatting;
7667
}
7768

7869
export interface EditProps {
79-
formatting: SavedFormatting;
70+
/**
71+
* The formatting slice to be edited.
72+
*/
73+
formatting: EditableFormatting;
74+
75+
/**
76+
* Set to `true` if the formatting is new and not yet saved.
77+
*/
78+
isNew?: boolean;
8079
}
8180

8281
/**

0 commit comments

Comments
 (0)