@dxos/react-ui-editor 0.6.4 → 0.6.5-staging.097cf0c
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.
- package/dist/lib/browser/index.mjs +24 -20
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +4 -3
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/linkPaste.d.ts +1 -0
- package/dist/types/src/extensions/markdown/linkPaste.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/linkPaste.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/linkPaste.test.d.ts.map +1 -0
- package/package.json +48 -40
- package/src/components/Toolbar/Toolbar.tsx +42 -15
- package/src/extensions/automerge/automerge.stories.tsx +14 -12
- package/src/extensions/factories.ts +5 -3
- package/src/extensions/markdown/linkPaste.test.ts +45 -0
- package/src/extensions/markdown/linkPaste.ts +12 -3
|
@@ -32,7 +32,9 @@ import React, { type PropsWithChildren, useEffect, useRef, useState } from 'reac
|
|
|
32
32
|
import { useDropzone } from 'react-dropzone';
|
|
33
33
|
|
|
34
34
|
import {
|
|
35
|
+
Button,
|
|
35
36
|
DensityProvider,
|
|
37
|
+
DropdownMenu,
|
|
36
38
|
ElevationProvider,
|
|
37
39
|
Toolbar as NaturalToolbar,
|
|
38
40
|
Tooltip,
|
|
@@ -40,25 +42,21 @@ import {
|
|
|
40
42
|
type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
|
|
41
43
|
type ToolbarButtonProps as NaturalToolbarButtonProps,
|
|
42
44
|
useTranslation,
|
|
43
|
-
DropdownMenu,
|
|
44
|
-
Button,
|
|
45
45
|
} from '@dxos/react-ui';
|
|
46
46
|
import { getSize } from '@dxos/react-ui-theme';
|
|
47
47
|
|
|
48
48
|
import { type Action, type ActionType, type Formatting } from '../../extensions';
|
|
49
49
|
import { translationKey } from '../../translations';
|
|
50
50
|
|
|
51
|
+
const iconStyles = getSize(5);
|
|
52
|
+
const buttonStyles = 'min-bs-0 p-2';
|
|
51
53
|
const tooltipProps = { side: 'top' as const, classNames: 'z-10' };
|
|
52
54
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'4': TextHFour,
|
|
59
|
-
'5': TextHFive,
|
|
60
|
-
'6': TextHSix,
|
|
61
|
-
};
|
|
55
|
+
const ToolbarSeparator = () => <div role='separator' className='grow' />;
|
|
56
|
+
|
|
57
|
+
//
|
|
58
|
+
// Root
|
|
59
|
+
//
|
|
62
60
|
|
|
63
61
|
export type ToolbarProps = ThemedClassName<
|
|
64
62
|
PropsWithChildren<{
|
|
@@ -83,8 +81,9 @@ const ToolbarRoot = ({ children, onAction, classNames, state }: ToolbarProps) =>
|
|
|
83
81
|
);
|
|
84
82
|
};
|
|
85
83
|
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
//
|
|
85
|
+
// Button
|
|
86
|
+
//
|
|
88
87
|
|
|
89
88
|
type ButtonProps = {
|
|
90
89
|
type: ActionType;
|
|
@@ -135,7 +134,19 @@ const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
|
|
|
135
134
|
);
|
|
136
135
|
};
|
|
137
136
|
|
|
138
|
-
|
|
137
|
+
//
|
|
138
|
+
// Heading
|
|
139
|
+
//
|
|
140
|
+
|
|
141
|
+
const HeadingIcons: { [key: string]: Icon } = {
|
|
142
|
+
'0': Paragraph,
|
|
143
|
+
'1': TextHOne,
|
|
144
|
+
'2': TextHTwo,
|
|
145
|
+
'3': TextHThree,
|
|
146
|
+
'4': TextHFour,
|
|
147
|
+
'5': TextHFive,
|
|
148
|
+
'6': TextHSix,
|
|
149
|
+
};
|
|
139
150
|
|
|
140
151
|
const MarkdownHeading = () => {
|
|
141
152
|
const { t } = useTranslation(translationKey);
|
|
@@ -215,6 +226,10 @@ const MarkdownHeading = () => {
|
|
|
215
226
|
);
|
|
216
227
|
};
|
|
217
228
|
|
|
229
|
+
//
|
|
230
|
+
// Markdown
|
|
231
|
+
//
|
|
232
|
+
|
|
218
233
|
const markdownStyles: ButtonProps[] = [
|
|
219
234
|
{ type: 'strong', Icon: TextB, getState: (state) => state.strong },
|
|
220
235
|
{ type: 'emphasis', Icon: TextItalic, getState: (state) => state.emphasis },
|
|
@@ -321,6 +336,10 @@ const MarkdownStandard = () => (
|
|
|
321
336
|
</>
|
|
322
337
|
);
|
|
323
338
|
|
|
339
|
+
//
|
|
340
|
+
// Custom
|
|
341
|
+
//
|
|
342
|
+
|
|
324
343
|
// TODO(burdon): Make extensible.
|
|
325
344
|
export type MarkdownCustomOptions = {
|
|
326
345
|
onUpload?: (file: File) => Promise<{ url?: string } | undefined>;
|
|
@@ -366,9 +385,13 @@ const MarkdownCustom = ({ onUpload }: MarkdownCustomOptions = {}) => {
|
|
|
366
385
|
);
|
|
367
386
|
};
|
|
368
387
|
|
|
388
|
+
//
|
|
389
|
+
// Actions
|
|
390
|
+
//
|
|
391
|
+
|
|
369
392
|
// TODO(burdon): Make extensible.
|
|
370
393
|
const MarkdownActions = () => {
|
|
371
|
-
const { onAction, state } = useToolbarContext('
|
|
394
|
+
const { onAction, state } = useToolbarContext('MarkdownActions');
|
|
372
395
|
const { t } = useTranslation(translationKey);
|
|
373
396
|
return (
|
|
374
397
|
<>
|
|
@@ -389,6 +412,10 @@ const MarkdownActions = () => {
|
|
|
389
412
|
);
|
|
390
413
|
};
|
|
391
414
|
|
|
415
|
+
//
|
|
416
|
+
// Toolbar
|
|
417
|
+
//
|
|
418
|
+
|
|
392
419
|
export const Toolbar = {
|
|
393
420
|
Root: ToolbarRoot,
|
|
394
421
|
Button: ToolbarToggleButton,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
6
|
|
|
7
7
|
import '@preact/signals-react';
|
|
8
|
-
import React, { useEffect,
|
|
8
|
+
import React, { useEffect, useState } from 'react';
|
|
9
9
|
|
|
10
10
|
import { TextType } from '@braneframe/types';
|
|
11
11
|
import { Repo } from '@dxos/automerge/automerge-repo';
|
|
@@ -40,7 +40,7 @@ const Editor = ({ source, autoFocus }: EditorProps) => {
|
|
|
40
40
|
doc: DocAccessor.getValue(source),
|
|
41
41
|
extensions: [
|
|
42
42
|
createBasicExtensions({ placeholder: 'Type here...' }),
|
|
43
|
-
createThemeExtensions({ themeMode, slots: { editor: { className: 'p-2 bg-white dark:bg-black' } } }),
|
|
43
|
+
createThemeExtensions({ themeMode, slots: { editor: { className: 'w-full p-2 bg-white dark:bg-black' } } }),
|
|
44
44
|
automerge(source),
|
|
45
45
|
],
|
|
46
46
|
autoFocus,
|
|
@@ -48,7 +48,7 @@ const Editor = ({ source, autoFocus }: EditorProps) => {
|
|
|
48
48
|
[source, themeMode],
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
return <div ref={parentRef} />;
|
|
51
|
+
return <div ref={parentRef} className='flex w-full' />;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const Story = () => {
|
|
@@ -94,14 +94,18 @@ export default {
|
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
const EchoStory = ({ spaceKey }: { spaceKey: PublicKey }) => {
|
|
97
|
-
// TODO(burdon): Test identity.
|
|
98
|
-
// const identity = useIdentity();
|
|
99
97
|
const space = useSpace(spaceKey);
|
|
100
|
-
const source =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
const [source, setSource] = useState<DocAccessor>();
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setTimeout(async () => {
|
|
101
|
+
if (space) {
|
|
102
|
+
const { objects = [] } = await space.db.query<Expando>(Filter.from({ type: 'test' })).run();
|
|
103
|
+
if (objects.length) {
|
|
104
|
+
const source = createDocAccessor(objects[0].content, ['content']);
|
|
105
|
+
setSource(source);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
105
109
|
}, [space]);
|
|
106
110
|
|
|
107
111
|
if (!source) {
|
|
@@ -113,8 +117,6 @@ const EchoStory = ({ spaceKey }: { spaceKey: PublicKey }) => {
|
|
|
113
117
|
|
|
114
118
|
export const Default = {};
|
|
115
119
|
|
|
116
|
-
// TODO(burdon): Error:
|
|
117
|
-
// chunk-6NX3RPDS.mjs:2021 ControlPipeline#5 Error: invariant violation: Feed already added
|
|
118
120
|
export const WithEcho = {
|
|
119
121
|
decorators: [withTheme],
|
|
120
122
|
render: () => {
|
|
@@ -37,6 +37,8 @@ import { defaultTheme } from '../themes';
|
|
|
37
37
|
// Basic
|
|
38
38
|
//
|
|
39
39
|
|
|
40
|
+
export const preventNewline = EditorState.transactionFilter.of((tr) => (tr.newDoc.lines > 1 ? [] : tr));
|
|
41
|
+
|
|
40
42
|
/**
|
|
41
43
|
* https://codemirror.net/docs/extensions
|
|
42
44
|
* https://github.com/codemirror/basic-setup
|
|
@@ -159,15 +161,15 @@ export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: Theme
|
|
|
159
161
|
// Data
|
|
160
162
|
//
|
|
161
163
|
|
|
162
|
-
export type DataExtensionsProps = {
|
|
164
|
+
export type DataExtensionsProps<T> = {
|
|
163
165
|
id: string;
|
|
164
|
-
text?: DocAccessor
|
|
166
|
+
text?: DocAccessor<T>;
|
|
165
167
|
space?: Space;
|
|
166
168
|
identity?: Identity | null;
|
|
167
169
|
};
|
|
168
170
|
|
|
169
171
|
// TODO(burdon): Move out of react-ui-editor (remove echo deps).
|
|
170
|
-
export const createDataExtensions = ({ id, text, space, identity }: DataExtensionsProps): Extension[] => {
|
|
172
|
+
export const createDataExtensions = <T>({ id, text, space, identity }: DataExtensionsProps<T>): Extension[] => {
|
|
171
173
|
const extensions: Extension[] = text ? [automerge(text)] : [];
|
|
172
174
|
|
|
173
175
|
if (space && identity) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
|
|
7
|
+
import { describe, test } from '@dxos/test';
|
|
8
|
+
|
|
9
|
+
import { formatUrlForDisplay } from './linkPaste';
|
|
10
|
+
|
|
11
|
+
const testCases = [
|
|
12
|
+
{ input: 'https://www.example.com', expected: 'example.com' },
|
|
13
|
+
{ input: 'http://example.com', expected: 'example.com' },
|
|
14
|
+
{ input: 'https://example.com/', expected: 'example.com' },
|
|
15
|
+
{ input: 'https://www.example.com/', expected: 'example.com' },
|
|
16
|
+
{ input: 'https://example.com/test', expected: 'example.com/test' },
|
|
17
|
+
{ input: 'https://www.example.com/test', expected: 'example.com/test' },
|
|
18
|
+
{ input: 'http://example.com/test', expected: 'example.com/test' },
|
|
19
|
+
{
|
|
20
|
+
input: 'https://example.com/some/path?name=value&another_name=another_value',
|
|
21
|
+
expected: 'example.com/some/path?name=value&anot...',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
input: 'https://www.example.com/some/path?name=value&another_name=another_value',
|
|
25
|
+
expected: 'example.com/some/path?name=value&anot...',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
input: 'http://example.com/some/path?name=value&another_name=another_value',
|
|
29
|
+
expected: 'example.com/some/path?name=value&anot...',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
input: 'https://www.example.com?name=value&another_name=another_value',
|
|
33
|
+
expected: 'example.com?name=value&anot...',
|
|
34
|
+
},
|
|
35
|
+
{ input: 'http://example.com?name=value&another_name=another_value', expected: 'example.com?name=value&anot...' },
|
|
36
|
+
{ input: 'https://example.com?name=value', expected: 'example.com?name=value' },
|
|
37
|
+
{ input: 'http://example.com?name=value', expected: 'example.com?name=value' },
|
|
38
|
+
{ input: 'https://www.example.com?name=value', expected: 'example.com?name=value' },
|
|
39
|
+
{ input: 'ftp://example.com', expected: 'ftp://example.com' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
describe('formatUrlForDisplay', () =>
|
|
43
|
+
testCases.forEach(({ input, expected }) =>
|
|
44
|
+
test(`formats ${input} into ${expected}`, () => expect(formatUrlForDisplay(input)).equals(expected)),
|
|
45
|
+
));
|
|
@@ -15,9 +15,18 @@ const createUrlLink = (url: string): string => {
|
|
|
15
15
|
return `[${displayUrl}](${url})`;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const formatUrlForDisplay = (url: string): string => {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export const formatUrlForDisplay = (url: string): string => {
|
|
19
|
+
// Remove protocol (http:// or https://)
|
|
20
|
+
let formattedUrl = url.replace(/^https?:\/\//, '');
|
|
21
|
+
|
|
22
|
+
// NOTE(Zan): Consult: https://github.com/dxos/dxos/issues/7331 before changing this.
|
|
23
|
+
// Remove 'www.' if at the beginning of the URL
|
|
24
|
+
formattedUrl = formattedUrl.replace(/^www\./, '');
|
|
25
|
+
|
|
26
|
+
// Remove trailing slash if the URL ends with `.com/`
|
|
27
|
+
formattedUrl = formattedUrl.replace(/\.com\/$/, '.com');
|
|
28
|
+
|
|
29
|
+
return truncateQueryParams(formattedUrl);
|
|
21
30
|
};
|
|
22
31
|
|
|
23
32
|
const truncateQueryParams = (url: string, maxQueryLength: number = 15): string => {
|