@dxos/react-ui-editor 0.6.14-main.7bd9c89 → 0.6.14-main.8b352a0
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 +650 -479
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +726 -545
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +650 -478
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +1 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/focus.d.ts +7 -0
- package/dist/types/src/extensions/focus.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/image.d.ts +3 -6
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
- package/dist/types/src/extensions/selection.d.ts +24 -0
- package/dist/types/src/extensions/selection.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextEditor.d.ts +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/{state → util}/cursor.d.ts +7 -1
- package/dist/types/src/util/cursor.d.ts.map +1 -0
- package/dist/types/src/{util.d.ts → util/debug.d.ts} +6 -2
- package/dist/types/src/util/debug.d.ts.map +1 -0
- package/dist/types/src/util/dom.d.ts.map +1 -0
- package/dist/types/src/{state/util.d.ts → util/facet.d.ts} +1 -1
- package/dist/types/src/util/facet.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +6 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/src/util/react.d.ts.map +1 -0
- package/package.json +28 -42
- package/src/TextEditor.stories.tsx +8 -6
- package/src/extensions/annotations.ts +1 -1
- package/src/extensions/automerge/automerge.ts +1 -1
- package/src/extensions/automerge/cursor.ts +1 -1
- package/src/extensions/awareness/awareness.ts +1 -1
- package/src/extensions/command/hint.ts +1 -1
- package/src/extensions/command/state.ts +1 -1
- package/src/extensions/comments.ts +3 -4
- package/src/extensions/factories.ts +5 -1
- package/src/extensions/focus.ts +35 -0
- package/src/extensions/folding.tsx +1 -1
- package/src/extensions/index.ts +2 -0
- package/src/extensions/markdown/decorate.ts +1 -1
- package/src/extensions/markdown/image.ts +53 -42
- package/src/extensions/modes.ts +1 -1
- package/src/{state/state.ts → extensions/selection.ts} +22 -22
- package/src/hooks/useTextEditor.ts +2 -3
- package/src/index.ts +1 -1
- package/src/{state → util}/cursor.ts +9 -3
- package/src/{util.ts → util/debug.ts} +15 -2
- package/src/{extensions/util → util}/index.ts +3 -2
- package/dist/lib/browser/chunk-CIQSMP7K.mjs +0 -148
- package/dist/lib/browser/chunk-CIQSMP7K.mjs.map +0 -7
- package/dist/lib/browser/state/index.mjs +0 -17
- package/dist/lib/browser/state/index.mjs.map +0 -7
- package/dist/lib/node/chunk-GZWIENFM.cjs +0 -169
- package/dist/lib/node/chunk-GZWIENFM.cjs.map +0 -7
- package/dist/lib/node/state/index.cjs +0 -39
- package/dist/lib/node/state/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GP5RCZ3X.mjs +0 -150
- package/dist/lib/node-esm/chunk-GP5RCZ3X.mjs.map +0 -7
- package/dist/lib/node-esm/state/index.mjs +0 -18
- package/dist/lib/node-esm/state/index.mjs.map +0 -7
- package/dist/types/src/extensions/util/dom.d.ts.map +0 -1
- package/dist/types/src/extensions/util/error.d.ts +0 -2
- package/dist/types/src/extensions/util/error.d.ts.map +0 -1
- package/dist/types/src/extensions/util/index.d.ts +0 -5
- package/dist/types/src/extensions/util/index.d.ts.map +0 -1
- package/dist/types/src/extensions/util/overlap.d.ts +0 -8
- package/dist/types/src/extensions/util/overlap.d.ts.map +0 -1
- package/dist/types/src/extensions/util/react.d.ts.map +0 -1
- package/dist/types/src/state/cursor.d.ts.map +0 -1
- package/dist/types/src/state/doc.d.ts +0 -5
- package/dist/types/src/state/doc.d.ts.map +0 -1
- package/dist/types/src/state/index.d.ts +0 -6
- package/dist/types/src/state/index.d.ts.map +0 -1
- package/dist/types/src/state/state.d.ts +0 -20
- package/dist/types/src/state/state.d.ts.map +0 -1
- package/dist/types/src/state/types.d.ts.map +0 -1
- package/dist/types/src/state/util.d.ts.map +0 -1
- package/dist/types/src/util.d.ts.map +0 -1
- package/src/extensions/util/error.ts +0 -15
- package/src/extensions/util/overlap.ts +0 -12
- package/src/state/doc.ts +0 -10
- package/src/state/index.ts +0 -11
- /package/dist/types/src/{state/types.d.ts → types.d.ts} +0 -0
- /package/dist/types/src/{extensions/util → util}/dom.d.ts +0 -0
- /package/dist/types/src/{extensions/util → util}/react.d.ts +0 -0
- /package/src/{state/types.ts → types.ts} +0 -0
- /package/src/{extensions/util → util}/dom.ts +0 -0
- /package/src/{state/util.ts → util/facet.ts} +0 -0
- /package/src/{extensions/util → util}/react.tsx +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"facet.d.ts","sourceRoot":"","sources":["../../../../src/util/facet.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,CAAC,qBAAqB,CAAC,gBAMvD,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../../src/util/react.tsx"],"names":[],"mappings":"AAIA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQ9C,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,aAAa,QAAS,MAAM,YAAY,cAAc,aAAa,SAAS,KAAG,WAU3F,CAAC;AAIF,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,OAAO,QAAQ,CAAC,QAAQ,SAAS,KAAG,CAGxE,CAAC"}
|
package/package.json
CHANGED
@@ -1,36 +1,22 @@
|
|
1
1
|
{
|
2
2
|
"name": "@dxos/react-ui-editor",
|
3
|
-
"version": "0.6.14-main.
|
3
|
+
"version": "0.6.14-main.8b352a0",
|
4
4
|
"description": "Document editing experience within a DXOS shell.",
|
5
5
|
"homepage": "https://dxos.org",
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
7
7
|
"license": "MIT",
|
8
8
|
"author": "DXOS.org",
|
9
|
+
"sideEffects": true,
|
9
10
|
"exports": {
|
10
11
|
".": {
|
12
|
+
"types": "./dist/types/src/index.d.ts",
|
11
13
|
"browser": "./dist/lib/browser/index.mjs",
|
12
|
-
"node":
|
13
|
-
"require": "./dist/lib/node/index.cjs",
|
14
|
-
"default": "./dist/lib/node-esm/index.mjs"
|
15
|
-
},
|
16
|
-
"types": "./dist/types/src/index.d.ts"
|
17
|
-
},
|
18
|
-
"./state": {
|
19
|
-
"browser": "./dist/lib/browser/state/index.mjs",
|
20
|
-
"node": {
|
21
|
-
"require": "./dist/lib/node/state/index.cjs",
|
22
|
-
"default": "./dist/lib/node-esm/state/index.mjs"
|
23
|
-
},
|
24
|
-
"types": "./dist/types/src/state/index.d.ts"
|
14
|
+
"node": "./dist/lib/node-esm/index.mjs"
|
25
15
|
}
|
26
16
|
},
|
27
17
|
"types": "dist/types/src/index.d.ts",
|
28
18
|
"typesVersions": {
|
29
|
-
"*": {
|
30
|
-
"state": [
|
31
|
-
"dist/types/src/state/index.d.ts"
|
32
|
-
]
|
33
|
-
}
|
19
|
+
"*": {}
|
34
20
|
},
|
35
21
|
"files": [
|
36
22
|
"dist",
|
@@ -64,17 +50,17 @@
|
|
64
50
|
"lodash.sortby": "^4.7.0",
|
65
51
|
"react-dropzone": "^14.2.3",
|
66
52
|
"style-mod": "^4.1.0",
|
67
|
-
"@dxos/async": "0.6.14-main.
|
68
|
-
"@dxos/
|
69
|
-
"@dxos/
|
70
|
-
"@dxos/
|
71
|
-
"@dxos/
|
72
|
-
"@dxos/
|
73
|
-
"@dxos/invariant": "0.6.14-main.
|
74
|
-
"@dxos/log": "0.6.14-main.
|
75
|
-
"@dxos/react-hooks": "0.6.14-main.
|
76
|
-
"@dxos/protocols": "0.6.14-main.
|
77
|
-
"@dxos/util": "0.6.14-main.
|
53
|
+
"@dxos/async": "0.6.14-main.8b352a0",
|
54
|
+
"@dxos/context": "0.6.14-main.8b352a0",
|
55
|
+
"@dxos/automerge": "0.6.14-main.8b352a0",
|
56
|
+
"@dxos/echo-schema": "0.6.14-main.8b352a0",
|
57
|
+
"@dxos/debug": "0.6.14-main.8b352a0",
|
58
|
+
"@dxos/display-name": "0.6.14-main.8b352a0",
|
59
|
+
"@dxos/invariant": "0.6.14-main.8b352a0",
|
60
|
+
"@dxos/log": "0.6.14-main.8b352a0",
|
61
|
+
"@dxos/react-hooks": "0.6.14-main.8b352a0",
|
62
|
+
"@dxos/protocols": "0.6.14-main.8b352a0",
|
63
|
+
"@dxos/util": "0.6.14-main.8b352a0"
|
78
64
|
},
|
79
65
|
"devDependencies": {
|
80
66
|
"@phosphor-icons/react": "^2.1.5",
|
@@ -99,23 +85,23 @@
|
|
99
85
|
"vite": "5.4.7",
|
100
86
|
"vite-plugin-top-level-await": "^1.4.1",
|
101
87
|
"vite-plugin-wasm": "^3.3.0",
|
102
|
-
"@dxos/automerge": "0.6.14-main.
|
103
|
-
"@dxos/config": "0.6.14-main.
|
104
|
-
"@dxos/echo-signals": "0.6.14-main.
|
105
|
-
"@dxos/
|
106
|
-
"@dxos/
|
107
|
-
"@dxos/
|
108
|
-
"@dxos/
|
109
|
-
"@dxos/react-ui-theme": "0.6.14-main.
|
110
|
-
"@dxos/
|
88
|
+
"@dxos/automerge": "0.6.14-main.8b352a0",
|
89
|
+
"@dxos/config": "0.6.14-main.8b352a0",
|
90
|
+
"@dxos/echo-signals": "0.6.14-main.8b352a0",
|
91
|
+
"@dxos/keyboard": "0.6.14-main.8b352a0",
|
92
|
+
"@dxos/random": "0.6.14-main.8b352a0",
|
93
|
+
"@dxos/react-client": "0.6.14-main.8b352a0",
|
94
|
+
"@dxos/storybook-utils": "0.6.14-main.8b352a0",
|
95
|
+
"@dxos/react-ui-theme": "0.6.14-main.8b352a0",
|
96
|
+
"@dxos/react-ui": "0.6.14-main.8b352a0"
|
111
97
|
},
|
112
98
|
"peerDependencies": {
|
113
99
|
"@phosphor-icons/react": "^2.1.5",
|
114
100
|
"react": "~18.2.0",
|
115
101
|
"react-dom": "~18.2.0",
|
116
|
-
"@dxos/react-
|
117
|
-
"@dxos/react-ui-theme": "0.6.14-main.
|
118
|
-
"@dxos/react-
|
102
|
+
"@dxos/react-client": "0.6.14-main.8b352a0",
|
103
|
+
"@dxos/react-ui-theme": "0.6.14-main.8b352a0",
|
104
|
+
"@dxos/react-ui": "0.6.14-main.8b352a0"
|
119
105
|
},
|
120
106
|
"publishConfig": {
|
121
107
|
"access": "public"
|
@@ -12,7 +12,7 @@ import { type EditorView } from '@codemirror/view';
|
|
12
12
|
import { ArrowSquareOut, X } from '@phosphor-icons/react';
|
13
13
|
import { effect, useSignal } from '@preact/signals-react';
|
14
14
|
import defaultsDeep from 'lodash.defaultsdeep';
|
15
|
-
import React, { type FC, type KeyboardEvent
|
15
|
+
import React, { useEffect, useState, type FC, type KeyboardEvent } from 'react';
|
16
16
|
import { createRoot } from 'react-dom/client';
|
17
17
|
|
18
18
|
import { create, Expando } from '@dxos/echo-schema';
|
@@ -22,12 +22,11 @@ import { log } from '@dxos/log';
|
|
22
22
|
import { faker } from '@dxos/random';
|
23
23
|
import { createDocAccessor, createObject } from '@dxos/react-client/echo';
|
24
24
|
import { Button, Input, useThemeContext } from '@dxos/react-ui';
|
25
|
-
import { baseSurface,
|
25
|
+
import { baseSurface, getSize, mx } from '@dxos/react-ui-theme';
|
26
26
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
27
27
|
|
28
28
|
import { editorContent, editorGutter, editorMonospace } from './defaults';
|
29
29
|
import {
|
30
|
-
InputModeExtensions,
|
31
30
|
annotations,
|
32
31
|
autocomplete,
|
33
32
|
blast,
|
@@ -45,19 +44,22 @@ import {
|
|
45
44
|
folding,
|
46
45
|
formattingKeymap,
|
47
46
|
image,
|
47
|
+
InputModeExtensions,
|
48
48
|
linkTooltip,
|
49
49
|
listener,
|
50
50
|
mention,
|
51
|
+
selectionState,
|
51
52
|
table,
|
52
53
|
typewriter,
|
53
54
|
type CommandAction,
|
54
55
|
type CommentsOptions,
|
55
56
|
type DebugNode,
|
57
|
+
type EditorSelectionState,
|
56
58
|
} from './extensions';
|
57
|
-
import { renderRoot } from './extensions/util';
|
58
59
|
import { useTextEditor, type UseTextEditorProps } from './hooks';
|
59
|
-
import { type Comment, type EditorSelectionState, state } from './state';
|
60
60
|
import translations from './translations';
|
61
|
+
import { type Comment } from './types';
|
62
|
+
import { renderRoot } from './util';
|
61
63
|
|
62
64
|
faker.seed(101);
|
63
65
|
|
@@ -416,7 +418,7 @@ export const Scrolling = {
|
|
416
418
|
render: () => (
|
417
419
|
<DefaultStory
|
418
420
|
text={str('# Large Document', '', longText)}
|
419
|
-
extensions={
|
421
|
+
extensions={selectionState({
|
420
422
|
setState: (id, state) => global.set(id, state),
|
421
423
|
getState: (id) => global.get(id),
|
422
424
|
})}
|
@@ -13,7 +13,7 @@ import { type DocAccessor } from '@dxos/react-client/echo';
|
|
13
13
|
import { cursorConverter } from './cursor';
|
14
14
|
import { updateHeadsEffect, isReconcile, type State } from './defs';
|
15
15
|
import { Syncer } from './sync';
|
16
|
-
import { Cursor } from '../../
|
16
|
+
import { Cursor } from '../../util';
|
17
17
|
|
18
18
|
export const automerge = (accessor: DocAccessor): Extension => {
|
19
19
|
const syncState = StateField.define<State>({
|
@@ -5,7 +5,7 @@
|
|
5
5
|
import { log } from '@dxos/log';
|
6
6
|
import { type DocAccessor, fromCursor, toCursor } from '@dxos/react-client/echo';
|
7
7
|
|
8
|
-
import { type CursorConverter } from '../../
|
8
|
+
import { type CursorConverter } from '../../util';
|
9
9
|
|
10
10
|
export const cursorConverter = (accessor: DocAccessor): CursorConverter => ({
|
11
11
|
toCursor: (pos, assoc) => {
|
@@ -16,7 +16,7 @@ import {
|
|
16
16
|
import { Event } from '@dxos/async';
|
17
17
|
import { Context } from '@dxos/context';
|
18
18
|
|
19
|
-
import { singleValueFacet, Cursor, type CursorConverter } from '../../
|
19
|
+
import { singleValueFacet, Cursor, type CursorConverter } from '../../util';
|
20
20
|
|
21
21
|
export interface AwarenessProvider {
|
22
22
|
remoteStateChange: Event<void>;
|
@@ -7,7 +7,7 @@ import { Decoration, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from
|
|
7
7
|
|
8
8
|
import { type CommandOptions } from './command';
|
9
9
|
import { commandState } from './state';
|
10
|
-
import { clientRectsFor, flattenRect } from '
|
10
|
+
import { clientRectsFor, flattenRect } from '../../util';
|
11
11
|
|
12
12
|
class CommandHint extends WidgetType {
|
13
13
|
constructor(readonly content: string | HTMLElement) {
|
@@ -28,10 +28,9 @@ import { debounce, type UnsubscribeCallback } from '@dxos/async';
|
|
28
28
|
import { log } from '@dxos/log';
|
29
29
|
import { nonNullable } from '@dxos/util';
|
30
30
|
|
31
|
-
import {
|
32
|
-
import {
|
33
|
-
import {
|
34
|
-
import { callbackWrapper } from '../util';
|
31
|
+
import { documentId } from './selection';
|
32
|
+
import { type Comment, type Range } from '../types';
|
33
|
+
import { Cursor, overlap, singleValueFacet, callbackWrapper } from '../util';
|
35
34
|
|
36
35
|
//
|
37
36
|
// State management.
|
@@ -31,7 +31,8 @@ import { type HuePalette, hueTokens } from '@dxos/react-ui-theme';
|
|
31
31
|
import { hexToHue, isNotFalsy } from '@dxos/util';
|
32
32
|
|
33
33
|
import { automerge } from './automerge';
|
34
|
-
import {
|
34
|
+
import { SpaceAwarenessProvider, awareness } from './awareness';
|
35
|
+
import { focus } from './focus';
|
35
36
|
import { type ThemeStyles, defaultTheme } from '../styles';
|
36
37
|
|
37
38
|
//
|
@@ -52,6 +53,7 @@ export type BasicExtensionsOptions = {
|
|
52
53
|
dropCursor?: boolean;
|
53
54
|
drawSelection?: boolean;
|
54
55
|
editable?: boolean;
|
56
|
+
focus?: boolean;
|
55
57
|
highlightActiveLine?: boolean;
|
56
58
|
history?: boolean;
|
57
59
|
indentWithTab?: boolean;
|
@@ -72,6 +74,7 @@ const defaultBasicOptions: BasicExtensionsOptions = {
|
|
72
74
|
closeBrackets: true,
|
73
75
|
drawSelection: true,
|
74
76
|
editable: true,
|
77
|
+
focus: true,
|
75
78
|
history: true,
|
76
79
|
keymap: 'standard',
|
77
80
|
lineWrapping: true,
|
@@ -98,6 +101,7 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
98
101
|
props.closeBrackets && closeBrackets(),
|
99
102
|
props.dropCursor && dropCursor(),
|
100
103
|
props.drawSelection && drawSelection({ cursorBlinkRate: 1_200 }),
|
104
|
+
props.focus && focus,
|
101
105
|
props.highlightActiveLine && highlightActiveLine(),
|
102
106
|
props.history && history(),
|
103
107
|
props.lineNumbers && lineNumbers(),
|
@@ -0,0 +1,35 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2024 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { StateEffect, StateField } from '@codemirror/state';
|
6
|
+
import { EditorView } from '@codemirror/view';
|
7
|
+
|
8
|
+
const focusEffect = StateEffect.define<boolean>();
|
9
|
+
|
10
|
+
export const focusField = StateField.define<boolean>({
|
11
|
+
create: () => false,
|
12
|
+
update: (value, tr) => {
|
13
|
+
for (const effect of tr.effects) {
|
14
|
+
if (effect.is(focusEffect)) {
|
15
|
+
return effect.value;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
return value;
|
19
|
+
},
|
20
|
+
});
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Manage focus.
|
24
|
+
*/
|
25
|
+
export const focus = [
|
26
|
+
focusField,
|
27
|
+
EditorView.domEventHandlers({
|
28
|
+
focus: (event, view) => {
|
29
|
+
setTimeout(() => view.dispatch({ effects: focusEffect.of(true) }));
|
30
|
+
},
|
31
|
+
blur: (event, view) => {
|
32
|
+
setTimeout(() => view.dispatch({ effects: focusEffect.of(false) }));
|
33
|
+
},
|
34
|
+
}),
|
35
|
+
];
|
package/src/extensions/index.ts
CHANGED
@@ -12,9 +12,11 @@ export * from './comments';
|
|
12
12
|
export * from './debug';
|
13
13
|
export * from './dnd';
|
14
14
|
export * from './factories';
|
15
|
+
export * from './focus';
|
15
16
|
export * from './folding';
|
16
17
|
export * from './listener';
|
17
18
|
export * from './markdown';
|
18
19
|
export * from './mention';
|
19
20
|
export * from './modes';
|
21
|
+
export * from './selection';
|
20
22
|
export * from './typewriter';
|
@@ -15,7 +15,7 @@ import { image } from './image';
|
|
15
15
|
import { formattingStyles, bulletListIndentationWidth, orderedListIndentationWidth } from './styles';
|
16
16
|
import { table } from './table';
|
17
17
|
import { theme, type HeadingLevel } from '../../styles';
|
18
|
-
import { wrapWithCatch } from '
|
18
|
+
import { wrapWithCatch } from '../../util';
|
19
19
|
|
20
20
|
/**
|
21
21
|
* Unicode characters.
|
@@ -3,43 +3,52 @@
|
|
3
3
|
//
|
4
4
|
|
5
5
|
import { syntaxTree } from '@codemirror/language';
|
6
|
-
import { type EditorState, type Extension, StateField, type Transaction
|
6
|
+
import { type EditorState, type Extension, type Range, StateField, type Transaction } from '@codemirror/state';
|
7
7
|
import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
|
8
8
|
|
9
|
+
import { focusField } from '../focus';
|
10
|
+
|
9
11
|
export type ImageOptions = {};
|
10
12
|
|
13
|
+
/**
|
14
|
+
* Create image decorations.
|
15
|
+
*/
|
11
16
|
export const image = (_options: ImageOptions = {}): Extension => {
|
12
|
-
return
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
return [
|
18
|
+
StateField.define<DecorationSet>({
|
19
|
+
create: (state) => {
|
20
|
+
// Process all images.
|
21
|
+
return Decoration.set(buildDecorations(0, state.doc.length, state));
|
22
|
+
},
|
23
|
+
update: (value: DecorationSet, tr: Transaction) => {
|
24
|
+
if (!tr.docChanged && !tr.selection) {
|
25
|
+
return value;
|
26
|
+
}
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
28
|
+
// Find range of changes and cursor changes.
|
29
|
+
const cursor = tr.state.selection.main.head;
|
30
|
+
const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
|
31
|
+
let from = Math.min(cursor, oldCursor);
|
32
|
+
let to = Math.max(cursor, oldCursor);
|
33
|
+
tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
|
34
|
+
from = Math.min(from, fromB);
|
35
|
+
to = Math.max(to, toB);
|
36
|
+
});
|
37
|
+
|
38
|
+
// Expand to cover lines.
|
39
|
+
from = tr.state.doc.lineAt(from).from;
|
40
|
+
to = tr.state.doc.lineAt(to).to;
|
41
|
+
|
42
|
+
return value.map(tr.changes).update({
|
43
|
+
filterFrom: from,
|
44
|
+
filterTo: to,
|
45
|
+
filter: () => false,
|
46
|
+
add: buildDecorations(from, to, tr.state),
|
47
|
+
});
|
48
|
+
},
|
49
|
+
provide: (field) => EditorView.decorations.from(field),
|
50
|
+
}),
|
51
|
+
];
|
43
52
|
};
|
44
53
|
|
45
54
|
const preloaded = new Set<string>();
|
@@ -55,15 +64,19 @@ const preloadImage = (url: string) => {
|
|
55
64
|
const buildDecorations = (from: number, to: number, state: EditorState) => {
|
56
65
|
const decorations: Range<Decoration>[] = [];
|
57
66
|
const cursor = state.selection.main.head;
|
58
|
-
|
59
67
|
syntaxTree(state).iterate({
|
60
68
|
enter: (node) => {
|
61
69
|
if (node.name === 'Image') {
|
62
70
|
const urlNode = node.node.getChild('URL');
|
63
71
|
if (urlNode) {
|
64
|
-
const hide = state.readOnly || cursor < node.from || cursor > node.to;
|
72
|
+
const hide = state.readOnly || cursor < node.from || cursor > node.to || !state.field(focusField);
|
73
|
+
|
65
74
|
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
66
|
-
//
|
75
|
+
// Some plugins might be using custom URLs; avoid attempts to render those URLs.
|
76
|
+
if (url.match(/^https?:\/\//) === null && url.match(/^file?:\/\//) === null) {
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
67
80
|
preloadImage(url);
|
68
81
|
decorations.push(
|
69
82
|
Decoration.replace({
|
@@ -94,14 +107,12 @@ class ImageWidget extends WidgetType {
|
|
94
107
|
const img = document.createElement('img');
|
95
108
|
img.setAttribute('src', this._url);
|
96
109
|
img.setAttribute('class', 'cm-image');
|
97
|
-
//
|
98
|
-
|
110
|
+
// If focused, hide image until successfully loaded to avoid flickering effects.
|
111
|
+
if (view.state.field(focusField)) {
|
112
|
+
img.onload = () => img.classList.add('cm-loaded-image');
|
113
|
+
} else {
|
114
|
+
img.classList.add('cm-loaded-image');
|
115
|
+
}
|
99
116
|
return img;
|
100
117
|
}
|
101
118
|
}
|
102
|
-
|
103
|
-
export type ImageUploadOptions = {
|
104
|
-
onSelect: () => { url: string };
|
105
|
-
};
|
106
|
-
|
107
|
-
export const imageUpload = (options: ImageOptions = {}) => {};
|
package/src/extensions/modes.ts
CHANGED
@@ -7,7 +7,7 @@ import { keymap } from '@codemirror/view';
|
|
7
7
|
import { vim } from '@replit/codemirror-vim';
|
8
8
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
9
9
|
|
10
|
-
import { singleValueFacet } from '../
|
10
|
+
import { singleValueFacet } from '../util';
|
11
11
|
|
12
12
|
export const focusEvent = 'focus.container';
|
13
13
|
|
@@ -2,16 +2,19 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
-
import { type
|
5
|
+
import { type Extension, Transaction, type TransactionSpec } from '@codemirror/state';
|
6
6
|
import { EditorView, keymap } from '@codemirror/view';
|
7
7
|
|
8
8
|
import { debounce } from '@dxos/async';
|
9
9
|
import { invariant } from '@dxos/invariant';
|
10
10
|
import { isNotFalsy } from '@dxos/util';
|
11
11
|
|
12
|
-
import {
|
12
|
+
import { singleValueFacet } from '../util';
|
13
13
|
|
14
|
-
|
14
|
+
/**
|
15
|
+
* Currently edited document id as FQ string.
|
16
|
+
*/
|
17
|
+
export const documentId = singleValueFacet<string>();
|
15
18
|
|
16
19
|
export type EditorSelection = {
|
17
20
|
anchor: number;
|
@@ -23,13 +26,23 @@ export type EditorSelectionState = {
|
|
23
26
|
selection?: EditorSelection;
|
24
27
|
};
|
25
28
|
|
26
|
-
export type
|
29
|
+
export type EditorStateStore = {
|
27
30
|
setState: (id: string, state: EditorSelectionState) => void;
|
28
31
|
getState: (id: string) => EditorSelectionState | undefined;
|
29
32
|
};
|
30
33
|
|
31
|
-
const
|
32
|
-
|
34
|
+
const stateRestoreAnnotation = 'dxos.org/cm/state-restore';
|
35
|
+
|
36
|
+
export const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {
|
37
|
+
return {
|
38
|
+
selection,
|
39
|
+
scrollIntoView: !scrollTo,
|
40
|
+
effects: scrollTo ? EditorView.scrollIntoView(scrollTo, { yMargin: 96 }) : undefined,
|
41
|
+
annotations: Transaction.userEvent.of(stateRestoreAnnotation),
|
42
|
+
};
|
43
|
+
};
|
44
|
+
|
45
|
+
export const createEditorStateStore = (keyPrefix: string): EditorStateStore => ({
|
33
46
|
getState: (id) => {
|
34
47
|
invariant(id);
|
35
48
|
const state = localStorage.getItem(`${keyPrefix}/${id}`);
|
@@ -40,25 +53,12 @@ export const localStorageStateStoreAdapter: EditorStateOptions = {
|
|
40
53
|
invariant(id);
|
41
54
|
localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
|
42
55
|
},
|
43
|
-
};
|
44
|
-
|
45
|
-
export const createEditorStateTransaction = (
|
46
|
-
state: EditorState,
|
47
|
-
{ scrollTo, selection }: EditorSelectionState,
|
48
|
-
): TransactionSpec => {
|
49
|
-
return {
|
50
|
-
selection,
|
51
|
-
scrollIntoView: !scrollTo,
|
52
|
-
effects: scrollTo ? EditorView.scrollIntoView(scrollTo, { yMargin: 96 }) : undefined,
|
53
|
-
annotations: Transaction.userEvent.of(stateRestoreAnnotation),
|
54
|
-
};
|
55
|
-
};
|
56
|
+
});
|
56
57
|
|
57
58
|
/**
|
58
59
|
* Track scrolling and selection state to be restored when switching to document.
|
59
60
|
*/
|
60
|
-
|
61
|
-
export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}): Extension => {
|
61
|
+
export const selectionState = ({ getState, setState }: Partial<EditorStateStore> = {}): Extension => {
|
62
62
|
const setStateDebounced = debounce(setState!, 1_000);
|
63
63
|
|
64
64
|
return [
|
@@ -90,7 +90,7 @@ export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}):
|
|
90
90
|
run: (view) => {
|
91
91
|
const state = getState(view.state.facet(documentId));
|
92
92
|
if (state) {
|
93
|
-
view.dispatch(createEditorStateTransaction(
|
93
|
+
view.dispatch(createEditorStateTransaction(state));
|
94
94
|
}
|
95
95
|
return true;
|
96
96
|
},
|
@@ -19,8 +19,7 @@ import {
|
|
19
19
|
import { log } from '@dxos/log';
|
20
20
|
import { getProviderValue, isNotFalsy, type MaybeProvider } from '@dxos/util';
|
21
21
|
|
22
|
-
import { editorInputMode } from '../extensions';
|
23
|
-
import { type EditorSelection, documentId, createEditorStateTransaction } from '../state';
|
22
|
+
import { editorInputMode, type EditorSelection, documentId, createEditorStateTransaction } from '../extensions';
|
24
23
|
import { debugDispatcher } from '../util';
|
25
24
|
|
26
25
|
export type UseTextEditor = {
|
@@ -150,7 +149,7 @@ export const useTextEditor = (
|
|
150
149
|
return;
|
151
150
|
}
|
152
151
|
|
153
|
-
view.dispatch(createEditorStateTransaction(
|
152
|
+
view.dispatch(createEditorStateTransaction({ scrollTo, selection }));
|
154
153
|
}
|
155
154
|
}
|
156
155
|
}, [view, scrollTo, selection]);
|
package/src/index.ts
CHANGED
@@ -4,8 +4,15 @@
|
|
4
4
|
|
5
5
|
import { type EditorState } from '@codemirror/state';
|
6
6
|
|
7
|
-
import {
|
8
|
-
import {
|
7
|
+
import { singleValueFacet } from './facet';
|
8
|
+
import { type Range } from '../types';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Determines if two ranges overlap.
|
12
|
+
* A range is considered to overlap if there is any intersection
|
13
|
+
* between the two ranges, inclusive of their boundaries.
|
14
|
+
*/
|
15
|
+
export const overlap = (a: Range, b: Range): boolean => a.from <= b.to && a.to >= b.from;
|
9
16
|
|
10
17
|
/**
|
11
18
|
* Converts indexes into the text document into stable peer-independent cursors.
|
@@ -32,7 +39,6 @@ export class Cursor {
|
|
32
39
|
|
33
40
|
static readonly getCursorFromRange = (state: EditorState, range: Range) => {
|
34
41
|
const cursorConverter = state.facet(Cursor.converter);
|
35
|
-
|
36
42
|
const from = cursorConverter.toCursor(range.from);
|
37
43
|
const to = cursorConverter.toCursor(range.to, -1);
|
38
44
|
return [from, to].join(':');
|