@dxos/react-ui-editor 0.8.2-staging.4d6ad0f → 0.8.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.8.2-staging.4d6ad0f",
3
+ "version": "0.8.2",
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",
@@ -59,20 +59,20 @@
59
59
  "lodash.merge": "^4.6.2",
60
60
  "lodash.sortby": "^4.7.0",
61
61
  "style-mod": "^4.1.0",
62
- "@dxos/async": "0.8.2-staging.4d6ad0f",
63
- "@dxos/context": "0.8.2-staging.4d6ad0f",
64
- "@dxos/debug": "0.8.2-staging.4d6ad0f",
65
- "@dxos/app-graph": "0.8.2-staging.4d6ad0f",
66
- "@dxos/echo-schema": "0.8.2-staging.4d6ad0f",
67
- "@dxos/display-name": "0.8.2-staging.4d6ad0f",
68
- "@dxos/lit-ui": "0.8.2-staging.4d6ad0f",
69
- "@dxos/invariant": "0.8.2-staging.4d6ad0f",
70
- "@dxos/log": "0.8.2-staging.4d6ad0f",
71
- "@dxos/live-object": "0.8.2-staging.4d6ad0f",
72
- "@dxos/protocols": "0.8.2-staging.4d6ad0f",
73
- "@dxos/react-hooks": "0.8.2-staging.4d6ad0f",
74
- "@dxos/react-ui-menu": "0.8.2-staging.4d6ad0f",
75
- "@dxos/util": "0.8.2-staging.4d6ad0f"
62
+ "@dxos/async": "0.8.2",
63
+ "@dxos/app-graph": "0.8.2",
64
+ "@dxos/context": "0.8.2",
65
+ "@dxos/display-name": "0.8.2",
66
+ "@dxos/debug": "0.8.2",
67
+ "@dxos/echo-schema": "0.8.2",
68
+ "@dxos/invariant": "0.8.2",
69
+ "@dxos/lit-ui": "0.8.2",
70
+ "@dxos/log": "0.8.2",
71
+ "@dxos/live-object": "0.8.2",
72
+ "@dxos/react-ui-menu": "0.8.2",
73
+ "@dxos/protocols": "0.8.2",
74
+ "@dxos/react-hooks": "0.8.2",
75
+ "@dxos/util": "0.8.2"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@automerge/automerge": "3.0.0-beta.4",
@@ -101,15 +101,15 @@
101
101
  "vite": "5.4.7",
102
102
  "vite-plugin-top-level-await": "^1.4.1",
103
103
  "vite-plugin-wasm": "^3.3.0",
104
- "@dxos/config": "0.8.2-staging.4d6ad0f",
105
- "@dxos/echo-signals": "0.8.2-staging.4d6ad0f",
106
- "@dxos/random": "0.8.2-staging.4d6ad0f",
107
- "@dxos/keyboard": "0.8.2-staging.4d6ad0f",
108
- "@dxos/react-ui": "0.8.2-staging.4d6ad0f",
109
- "@dxos/react-client": "0.8.2-staging.4d6ad0f",
110
- "@dxos/react-ui-syntax-highlighter": "0.8.2-staging.4d6ad0f",
111
- "@dxos/react-ui-theme": "0.8.2-staging.4d6ad0f",
112
- "@dxos/storybook-utils": "0.8.2-staging.4d6ad0f"
104
+ "@dxos/echo-signals": "0.8.2",
105
+ "@dxos/random": "0.8.2",
106
+ "@dxos/config": "0.8.2",
107
+ "@dxos/react-client": "0.8.2",
108
+ "@dxos/react-ui": "0.8.2",
109
+ "@dxos/react-ui-syntax-highlighter": "0.8.2",
110
+ "@dxos/keyboard": "0.8.2",
111
+ "@dxos/storybook-utils": "0.8.2",
112
+ "@dxos/react-ui-theme": "0.8.2"
113
113
  },
114
114
  "peerDependencies": {
115
115
  "@effect-rx/rx-react": "^0.34.1",
@@ -118,9 +118,9 @@
118
118
  "effect": "^3.13.3",
119
119
  "react": "~18.2.0",
120
120
  "react-dom": "~18.2.0",
121
- "@dxos/react-client": "0.8.2-staging.4d6ad0f",
122
- "@dxos/react-ui-theme": "0.8.2-staging.4d6ad0f",
123
- "@dxos/react-ui": "0.8.2-staging.4d6ad0f"
121
+ "@dxos/react-ui": "0.8.2",
122
+ "@dxos/react-client": "0.8.2",
123
+ "@dxos/react-ui-theme": "0.8.2"
124
124
  },
125
125
  "publishConfig": {
126
126
  "access": "public"
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
5
+ import { EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
6
6
 
7
7
  import { closeEffect, openEffect } from './action';
8
8
 
@@ -33,11 +33,10 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
33
33
 
34
34
  const button = document.createElement('button');
35
35
  button.appendChild(icon);
36
- button.classList.add('grid', 'items-center', 'justify-center', 'w-8', 'h-8');
37
36
 
38
37
  // TODO(burdon): Custom tag/styles?
39
38
  this.tag = document.createElement('dx-ref-tag');
40
- this.tag.classList.add('border-none', 'fixed', 'p-0');
39
+ this.tag.classList.add('cm-ref-tag');
41
40
  this.tag.appendChild(button);
42
41
  container.appendChild(this.tag);
43
42
 
@@ -47,12 +46,24 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
47
46
  }
48
47
 
49
48
  update(update: ViewUpdate) {
49
+ this.tag.dataset.focused = update.view.hasFocus ? 'true' : 'false';
50
+ if (!update.view.hasFocus) {
51
+ return;
52
+ }
53
+
50
54
  // TODO(burdon): Timer to fade in/out.
51
55
  if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(openEffect)))) {
52
56
  this.tag.style.display = 'none';
57
+ this.tag.classList.add('opacity-10');
53
58
  } else if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(closeEffect)))) {
54
59
  this.tag.style.display = 'block';
55
- } else if (update.selectionSet || update.viewportChanged || update.docChanged || update.geometryChanged) {
60
+ } else if (
61
+ update.docChanged ||
62
+ update.focusChanged ||
63
+ update.geometryChanged ||
64
+ update.selectionSet ||
65
+ update.viewportChanged
66
+ ) {
56
67
  this.scheduleUpdate();
57
68
  }
58
69
  }
@@ -94,4 +105,24 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
94
105
  }
95
106
  },
96
107
  ),
108
+
109
+ EditorView.theme({
110
+ '.cm-ref-tag': {
111
+ position: 'fixed',
112
+ padding: '0',
113
+ border: 'none',
114
+ transition: 'opacity 0.3s ease-in-out',
115
+ opacity: 0.1,
116
+ },
117
+ '.cm-ref-tag button': {
118
+ display: 'grid',
119
+ alignItems: 'center',
120
+ justifyContent: 'center',
121
+ width: '2rem',
122
+ height: '2rem',
123
+ },
124
+ '.cm-ref-tag[data-focused="true"]': {
125
+ opacity: 1,
126
+ },
127
+ }),
97
128
  ];
@@ -123,9 +123,28 @@ export const moveItemUp: Command = (view: EditorView) => {
123
123
  // Misc commands.
124
124
  //
125
125
 
126
+ export const deleteItem: Command = (view: EditorView) => {
127
+ const tree = view.state.facet(treeFacet);
128
+ const pos = getSelection(view.state).from;
129
+ const current = tree.find(pos);
130
+ if (current) {
131
+ view.dispatch({
132
+ selection: EditorSelection.cursor(current.lineRange.from),
133
+ changes: [
134
+ {
135
+ from: current.lineRange.from,
136
+ to: Math.min(current.lineRange.to + 1, view.state.doc.length),
137
+ },
138
+ ],
139
+ });
140
+ }
141
+
142
+ return true;
143
+ };
144
+
126
145
  export const toggleTask: Command = (view: EditorView) => {
127
- const pos = getSelection(view.state)?.from;
128
146
  const tree = view.state.facet(treeFacet);
147
+ const pos = getSelection(view.state)?.from;
129
148
  const current = tree.find(pos);
130
149
  if (current) {
131
150
  const type = current.type === 'task' ? 'bullet' : 'task';
@@ -233,10 +252,19 @@ export const commands = (): Extension =>
233
252
  run: moveItemUp,
234
253
  },
235
254
  //
255
+ // Delete.
256
+ //
257
+ {
258
+ key: 'Mod-Backspace',
259
+ preventDefault: true,
260
+ run: deleteItem,
261
+ },
262
+ //
236
263
  // Misc.
237
264
  //
238
265
  {
239
266
  key: 'Alt-t',
267
+ preventDefault: true,
240
268
  run: toggleTask,
241
269
  },
242
270
  ]);
@@ -101,11 +101,15 @@ export const editor = () => [
101
101
  const match = line.text.match(LIST_ITEM_REGEX);
102
102
  if (match) {
103
103
  // Check cursor was in a valid position.
104
- // const startTree = tr.startState.facet(treeFacet);
105
- // const startItem = startTree.find(tr.startState.selection.main.from);
104
+ const startTree = tr.startState.facet(treeFacet);
105
+ const startItem = startTree.find(tr.startState.selection.main.from);
106
106
 
107
107
  // Check if entire line was deleted (which is ok).
108
- // const deleteLine = fromA === startItem?.lineRange.from && toA === startItem?.lineRange.to;
108
+ const deleteLine = fromA === startItem?.lineRange.from && toA === startItem?.lineRange.to + 1;
109
+ if (deleteLine) {
110
+ return;
111
+ }
112
+
109
113
  // if (!deleteLine && (!startItem || fromA < startItem.contentRange.from || toA > startItem.contentRange.to)) {
110
114
  // cancel = true;
111
115
  // return;
@@ -2,5 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './commands';
5
6
  export * from './outliner';
6
7
  export * from './tree';
@@ -29,8 +29,8 @@ const getPos = (line: number) => {
29
29
 
30
30
  const extensions = [createMarkdownExtensions(), outlinerTree()];
31
31
 
32
- // Flaky
33
- describe.skip('outliner', () => {
32
+ // TODO(burdon): Flaky.
33
+ describe.runIf(!process.env.CI)('outliner', () => {
34
34
  const state = EditorState.create({ doc: str(...lines), extensions });
35
35
 
36
36
  test('sanity', ({ expect }) => {
@@ -28,6 +28,10 @@ import { decorateMarkdown } from '../markdown';
28
28
  // TODO(burdon): Smart Cut-and-paste.
29
29
  // TODO(burdon): DND.
30
30
 
31
+ export type OutlinerProps = {
32
+ showSelected?: boolean;
33
+ };
34
+
31
35
  /**
32
36
  * Outliner extension.
33
37
  * - Stores outline as a standard markdown document with task and list markers.
@@ -35,7 +39,7 @@ import { decorateMarkdown } from '../markdown';
35
39
  * - Constrains editor to outline structure.
36
40
  * - Supports smart cut-and-paste.
37
41
  */
38
- export const outliner = (): Extension => [
42
+ export const outliner = (options: OutlinerProps = {}): Extension => [
39
43
  // Commands.
40
44
  Prec.highest(commands()),
41
45
 
@@ -52,7 +56,7 @@ export const outliner = (): Extension => [
52
56
  floatingMenu(),
53
57
 
54
58
  // Line decorations.
55
- decorations(),
59
+ decorations(options),
56
60
 
57
61
  // Default markdown decorations.
58
62
  decorateMarkdown({ listPaddingLeft: 8 }),
@@ -64,7 +68,7 @@ export const outliner = (): Extension => [
64
68
  /**
65
69
  * Line decorations (for border and selection).
66
70
  */
67
- const decorations = () => [
71
+ const decorations = (options: OutlinerProps) => [
68
72
  ViewPlugin.fromClass(
69
73
  class {
70
74
  decorations: DecorationSet = Decoration.none;
@@ -125,38 +129,40 @@ const decorations = () => [
125
129
  ),
126
130
 
127
131
  // Theme.
128
- EditorView.theme({
129
- '.cm-list-item': {
130
- borderLeftWidth: '1px',
131
- borderRightWidth: '1px',
132
- paddingLeft: '32px',
133
- borderColor: 'transparent',
134
- },
135
- '.cm-list-item.cm-codeblock-start': {
136
- borderRadius: '0',
137
- },
138
-
139
- '.cm-list-item-start': {
140
- borderTopWidth: '1px',
141
- borderTopLeftRadius: '4px',
142
- borderTopRightRadius: '4px',
143
- paddingTop: '4px',
144
- marginTop: '8px',
145
- },
146
-
147
- '.cm-list-item-end': {
148
- borderBottomWidth: '1px',
149
- borderBottomLeftRadius: '4px',
150
- borderBottomRightRadius: '4px',
151
- paddingBottom: '4px',
152
- marginBottom: '8px',
153
- },
154
-
155
- '.cm-list-item-selected': {
156
- borderColor: 'var(--dx-separator)',
157
- },
158
- '.cm-list-item-focused': {
159
- borderColor: 'var(--dx-accentFocusIndicator)',
160
- },
161
- }),
132
+ EditorView.theme(
133
+ Object.assign({
134
+ '.cm-list-item': {
135
+ borderLeftWidth: '1px',
136
+ borderRightWidth: '1px',
137
+ paddingLeft: '32px',
138
+ borderColor: 'transparent',
139
+ },
140
+ '.cm-list-item.cm-codeblock-start': {
141
+ borderRadius: '0',
142
+ },
143
+
144
+ '.cm-list-item-start': {
145
+ borderTopWidth: '1px',
146
+ borderTopLeftRadius: '4px',
147
+ borderTopRightRadius: '4px',
148
+ paddingTop: '4px',
149
+ marginTop: '2px',
150
+ },
151
+
152
+ '.cm-list-item-end': {
153
+ borderBottomWidth: '1px',
154
+ borderBottomLeftRadius: '4px',
155
+ borderBottomRightRadius: '4px',
156
+ paddingBottom: '4px',
157
+ marginBottom: '2px',
158
+ },
159
+
160
+ '.cm-list-item-selected': {
161
+ borderColor: options.showSelected ? 'var(--dx-separator)' : undefined,
162
+ },
163
+ '.cm-list-item-focused': {
164
+ borderColor: 'var(--dx-accentFocusIndicator)',
165
+ },
166
+ }),
167
+ ),
162
168
  ];