@dxos/react-ui-editor 0.4.3-main.97fc241 → 0.4.3-main.aa1c078

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,7 @@ import {
23
23
  TextHSix,
24
24
  TextItalic,
25
25
  Quotes,
26
+ CaretDown,
26
27
  } from '@phosphor-icons/react';
27
28
  import { createContext } from '@radix-ui/react-context';
28
29
  import React, { type PropsWithChildren } from 'react';
@@ -64,7 +65,7 @@ export type ActionType =
64
65
  | 'link'
65
66
  | 'list-bullet'
66
67
  | 'list-ordered'
67
- | 'list-task'
68
+ | 'list-tasks'
68
69
  | 'mention'
69
70
  | 'prompt'
70
71
  | 'strikethrough'
@@ -98,17 +99,12 @@ const ToolbarRoot = ({ children, onAction, classNames, state }: ToolbarProps) =>
98
99
  );
99
100
  };
100
101
 
101
- const buttonStyles = 'min-bs-0 p-2';
102
- const iconStyles = getSize(5);
103
-
104
- type ButtonProps = {
105
- type: ActionType;
102
+ type ToolbarButtonProps = ToolbarToggleGroupItemProps & {
106
103
  Icon: Icon;
107
- getState: (state: Formatting) => boolean;
108
- disabled?: (state: Formatting) => boolean;
109
104
  };
110
105
 
111
- type ToolbarButtonProps = ToolbarToggleGroupItemProps & { Icon: Icon };
106
+ const buttonStyles = 'min-bs-0 p-2';
107
+ const iconStyles = getSize(4);
112
108
 
113
109
  const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
114
110
  return (
@@ -122,7 +118,7 @@ const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
122
118
  const ToolbarSeparator = () => <div role='separator' className='grow' />;
123
119
 
124
120
  const MarkdownHeading = () => {
125
- // const { t } = useTranslation(translationKey);
121
+ const { t } = useTranslation(translationKey);
126
122
  const { onAction, state } = useToolbarContext('MarkdownFormatting');
127
123
  const blockType = state ? state.blockType : 'paragraph';
128
124
  const header = blockType && /heading(\d)/.exec(blockType);
@@ -134,30 +130,21 @@ const MarkdownHeading = () => {
134
130
  value={value ?? '0'}
135
131
  onValueChange={(value) => onAction?.({ type: 'heading', data: parseInt(value) })}
136
132
  >
137
- <Select.TriggerButton>
138
- <HeadingIcon className={iconStyles} />
139
- {/* TODO(burdon): Incorrectly positioned. */}
140
- {/* <NaturalToolbar.Button variant='ghost' classNames={buttonStyles}> */}
141
- {/* <span className='sr-only'>{t('heading label')}</span> */}
142
- {/* <HeadingIcon className={iconStyles} /> */}
143
- {/* <CaretDown className={getSize(2)} weight='bold' /> */}
144
- {/* </NaturalToolbar.Button> */}
145
- </Select.TriggerButton>
133
+ <Select.Trigger asChild>
134
+ <NaturalToolbar.Button variant='ghost' classNames={buttonStyles}>
135
+ <span className='sr-only'>{t('heading label')}</span>
136
+ <HeadingIcon className={iconStyles} />
137
+ <CaretDown className={getSize(2)} weight='bold' />
138
+ </NaturalToolbar.Button>
139
+ </Select.Trigger>
146
140
  <Select.Portal>
147
141
  <Select.Content>
148
142
  <Select.ScrollUpButton />
149
143
  <Select.Viewport>
150
- <Select.Option value='0'>
151
- <Paragraph className={iconStyles} />
152
- </Select.Option>
144
+ <Select.Option value='0'>Paragraph</Select.Option>
153
145
  {[1, 2, 3, 4, 5, 6].map((level) => (
154
146
  <Select.Option key={level} value={String(level)}>
155
- {level === 1 && <TextHOne className={iconStyles} />}
156
- {level === 2 && <TextHTwo className={iconStyles} />}
157
- {level === 3 && <TextHThree className={iconStyles} />}
158
- {level === 4 && <TextHFour className={iconStyles} />}
159
- {level === 5 && <TextHFive className={iconStyles} />}
160
- {level === 6 && <TextHSix className={iconStyles} />}
147
+ Heading {level}
161
148
  </Select.Option>
162
149
  ))}
163
150
  </Select.Viewport>
@@ -169,12 +156,14 @@ const MarkdownHeading = () => {
169
156
  );
170
157
  };
171
158
 
172
- const markdownStyles: ButtonProps[] = [
173
- { type: 'strong', Icon: TextB, getState: (state) => state.strong },
174
- { type: 'emphasis', Icon: TextItalic, getState: (state) => state.emphasis },
175
- { type: 'strikethrough', Icon: TextStrikethrough, getState: (state) => state.strikethrough },
176
- { type: 'code', Icon: Code, getState: (state) => state.code },
177
- { type: 'link', Icon: Link, getState: (state) => state.link },
159
+ const markdownStyles = [
160
+ { key: 'strong' as const, Icon: TextB },
161
+ { key: 'emphasis' as const, Icon: TextItalic },
162
+ { key: 'strikethrough' as const, Icon: TextStrikethrough },
163
+ { key: 'code' as const, Icon: Code },
164
+ { key: 'link' as const, Icon: Link },
165
+ // <ToolbarButton Icon={At} title='Mention' onClick={() => ({ type: 'mention' })} />
166
+ // <ToolbarButton Icon={Image} title='Image' onClick={() => ({ type: 'image' })} />
178
167
  ];
179
168
 
180
169
  const MarkdownStyles = () => {
@@ -184,27 +173,27 @@ const MarkdownStyles = () => {
184
173
  return (
185
174
  <NaturalToolbar.ToggleGroup
186
175
  type='multiple'
187
- value={markdownStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)}
176
+ value={markdownStyles.filter(({ key }) => !!state?.[key]).map(({ key }) => key)}
188
177
  >
189
- {markdownStyles.map(({ type, getState, Icon }) => (
178
+ {markdownStyles.map(({ key, Icon }) => (
190
179
  <ToolbarButton
191
- key={type}
192
- value={type}
180
+ key={key}
181
+ value={key}
193
182
  Icon={Icon}
194
183
  disabled={state?.blockType === 'codeblock'}
195
- onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
184
+ onClick={() => onAction?.({ type: key, data: state ? !state[key] : null })}
196
185
  >
197
- {t(`${type} label`)}
186
+ {t(`${key} label`)}
198
187
  </ToolbarButton>
199
188
  ))}
200
189
  </NaturalToolbar.ToggleGroup>
201
190
  );
202
191
  };
203
192
 
204
- const markdownLists: ButtonProps[] = [
205
- { type: 'list-bullet', Icon: ListBullets, getState: (state) => state.listStyle === 'bullet' },
206
- { type: 'list-ordered', Icon: ListNumbers, getState: (state) => state.listStyle === 'ordered' },
207
- { type: 'list-task', Icon: ListChecks, getState: (state) => state.listStyle === 'task' },
193
+ const markdownLists = [
194
+ { key: 'bullet' as const, Icon: ListBullets, actionType: 'list-bullet' as const },
195
+ { key: 'ordered' as const, Icon: ListNumbers, actionType: 'list-ordered' as const },
196
+ { key: 'task' as const, Icon: ListChecks, actionType: 'list-tasks' as const },
208
197
  ];
209
198
 
210
199
  const MarkdownLists = () => {
@@ -212,58 +201,62 @@ const MarkdownLists = () => {
212
201
  const { t } = useTranslation(translationKey);
213
202
  return (
214
203
  <NaturalToolbar.ToggleGroup type='single' value={state?.listStyle ?? undefined}>
215
- {markdownLists.map(({ type, getState, Icon }) => (
204
+ {markdownLists.map(({ key, Icon, actionType }) => (
216
205
  <ToolbarButton
217
- key={type}
218
- value={type}
206
+ key={key}
207
+ value={key}
219
208
  Icon={Icon}
220
- onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
209
+ onClick={() => onAction?.({ type: actionType, data: state ? state.listStyle !== key : null })}
221
210
  >
222
- {t(`${type} label`)}
211
+ {t(`${key} label`)}
223
212
  </ToolbarButton>
224
213
  ))}
225
214
  </NaturalToolbar.ToggleGroup>
226
215
  );
227
216
  };
228
217
 
229
- const markdownBlocks: ButtonProps[] = [
218
+ const markdownBlocks = [
230
219
  {
231
- type: 'blockquote',
220
+ key: 'blockquote' as const,
232
221
  Icon: Quotes,
233
- getState: (state) => state.blockQuote,
222
+ pressed: (state: Formatting | null) => state?.blockQuote,
223
+ action: (state: Formatting | null) => ({ type: 'blockquote' as const, data: state ? !state.blockQuote : null }),
224
+ disabled: (state: Formatting | null) => false,
234
225
  },
235
226
  {
236
- type: 'codeblock',
227
+ key: 'codeblock' as const,
237
228
  Icon: CodeBlock,
238
- getState: (state) => state.blockQuote,
239
- disabled: (state) => !state.blankLine,
240
- },
241
- {
242
- type: 'table',
243
- Icon: Table,
244
- getState: (state) => state.blockType === 'tablecell',
245
- disabled: (state) => !state.blankLine,
229
+ pressed: (state: Formatting | null) => state?.blockType === 'codeblock',
230
+ action: (state: Formatting | null) => ({ type: 'blockquote' as const, data: state ? !state.blockQuote : null }),
231
+ disabled: (state: Formatting | null) => !state?.blankLine,
246
232
  },
247
233
  ];
248
234
 
249
235
  const MarkdownBlocks = () => {
250
236
  const { onAction, state } = useToolbarContext('MarkdownStyles');
251
237
  const { t } = useTranslation(translationKey);
252
- const value = markdownBlocks.find(({ getState }) => state && getState(state));
253
238
  return (
254
- <NaturalToolbar.ToggleGroup type='single' value={value?.type}>
255
- {markdownBlocks.map(({ type, disabled, getState, Icon }) => (
256
- <ToolbarButton
257
- key={type}
258
- value={type}
259
- Icon={Icon}
260
- disabled={!state || disabled?.(state)}
261
- onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
262
- >
263
- {t(`${type} label`)}
264
- </ToolbarButton>
265
- ))}
266
- </NaturalToolbar.ToggleGroup>
239
+ <>
240
+ <NaturalToolbar.ToggleGroup
241
+ type='multiple'
242
+ value={markdownBlocks.filter(({ pressed }) => pressed(state)).map(({ key }) => key)}
243
+ >
244
+ {markdownBlocks.map(({ key, Icon, action }) => (
245
+ <ToolbarButton key={key} value={key} Icon={Icon} onClick={() => onAction?.(action(state))}>
246
+ {t(`${key} label`)}
247
+ </ToolbarButton>
248
+ ))}
249
+ </NaturalToolbar.ToggleGroup>
250
+ <NaturalToolbar.Button
251
+ variant='ghost'
252
+ disabled={!state?.blankLine}
253
+ onClick={() => onAction?.({ type: 'table' })}
254
+ classNames={buttonStyles}
255
+ >
256
+ <Table className={iconStyles} />
257
+ <span className='sr-only'>{t('table label')}</span>
258
+ </NaturalToolbar.Button>
259
+ </>
267
260
  );
268
261
  };
269
262
 
@@ -370,7 +370,7 @@ describe('removeBlockquote', () => {
370
370
  });
371
371
 
372
372
  describe('addCodeblock', () => {
373
- testCommand('add a code block to a blank line', '{}', addCodeblock, '```{}\n```');
373
+ testCommand('add a code block to a blank line', '{}', addCodeblock, '```{}\n\n```');
374
374
 
375
375
  testCommand(
376
376
  'can turn a paragraph into a code block',
@@ -356,6 +356,7 @@ const snippets = {
356
356
  [
357
357
  //
358
358
  '```#{}',
359
+ '#{}',
359
360
  '```',
360
361
  ].join('\n'),
361
362
  ),
@@ -514,7 +515,6 @@ export const addLink: StateCommand = ({ state, dispatch }) => {
514
515
  // Lists
515
516
  //
516
517
 
517
- // TODO(burdon): Cursor is positioned incorrectly.
518
518
  export const addList =
519
519
  (type: List): StateCommand =>
520
520
  ({ state, dispatch }) => {
@@ -576,21 +576,18 @@ export const addList =
576
576
  // Insert a new list item if the selection is empty.
577
577
  const { from, to } = state.doc.lineAt(state.selection.main.anchor);
578
578
  if (from === to) {
579
- const insert = type === List.Bullet ? '- ' : type === List.Ordered ? '1. ' : '- [ ] ';
580
579
  dispatch(
581
580
  state.update({
582
581
  changes: [
583
582
  {
584
583
  from,
585
- insert,
584
+ insert: type === List.Bullet ? '- ' : type === List.Ordered ? '1. ' : '- [ ] ',
586
585
  },
587
586
  ],
588
- selection: { anchor: from + insert.length },
589
587
  userEvent: 'format.list.add',
590
588
  scrollIntoView: true,
591
589
  }),
592
590
  );
593
-
594
591
  return true;
595
592
  }
596
593
 
@@ -54,7 +54,7 @@ export const useActionHandler = (view?: EditorView | null): ToolbarProps['onActi
54
54
 
55
55
  case 'list-ordered':
56
56
  case 'list-bullet':
57
- case 'list-task':
57
+ case 'list-tasks':
58
58
  listType =
59
59
  action.type === 'list-ordered' ? List.Ordered : action.type === 'list-bullet' ? List.Bullet : List.Task;
60
60
  (action.data === false
@@ -12,9 +12,9 @@ export default [
12
12
  'emphasis label': 'Emphasis',
13
13
  'strikethrough label': 'Strike-through',
14
14
  'code label': 'Code',
15
- 'list-bullet label': 'Bullet list',
16
- 'list-ordered label': 'Numbered list',
17
- 'list-task label': 'Task list',
15
+ 'bullet label': 'Bullet list',
16
+ 'ordered label': 'Numbered list',
17
+ 'task label': 'Task list',
18
18
  'blockquote label': 'Block quote',
19
19
  'codeblock label': 'Code block',
20
20
  'comment label': 'Create comment',