1
1
import type { DefaultOpenProperty } from '@affine/core/components/doc-properties' ;
2
- import type {
3
- DocMode ,
2
+ import {
3
+ type DocMode ,
4
4
EdgelessRootService ,
5
- ReferenceParams ,
5
+ type ReferenceParams ,
6
6
} from '@blocksuite/affine/blocks' ;
7
7
import type {
8
8
AffineEditorContainer ,
@@ -13,6 +13,7 @@ import { effect } from '@preact/signals-core';
13
13
import type { DocService , WorkspaceService } from '@toeverything/infra' ;
14
14
import { Entity , LiveData } from '@toeverything/infra' ;
15
15
import { defaults , isEqual , omit } from 'lodash-es' ;
16
+ import { skip } from 'rxjs' ;
16
17
17
18
import { paramsParseOptions , preprocessParams } from '../../navigation/utils' ;
18
19
import type { WorkbenchView } from '../../workbench' ;
@@ -34,6 +35,34 @@ export class Editor extends Entity {
34
35
readonly defaultOpenProperty$ = new LiveData < DefaultOpenProperty | undefined > (
35
36
undefined
36
37
) ;
38
+ workbenchView : WorkbenchView | null = null ;
39
+ defaultScrollPosition :
40
+ | number
41
+ | {
42
+ centerX : number ;
43
+ centerY : number ;
44
+ zoom : number ;
45
+ }
46
+ | null = null ;
47
+
48
+ private readonly focusAt$ = LiveData . computed ( get => {
49
+ const selector = get ( this . selector$ ) ;
50
+ const mode = get ( this . mode$ ) ;
51
+ let id = selector ?. blockIds ?. [ 0 ] ;
52
+ let key = 'blockIds' ;
53
+
54
+ if ( mode === 'edgeless' ) {
55
+ const elementId = selector ?. elementIds ?. [ 0 ] ;
56
+ if ( elementId ) {
57
+ id = elementId ;
58
+ key = 'elementIds' ;
59
+ }
60
+ }
61
+
62
+ if ( ! id ) return null ;
63
+
64
+ return { id, key, mode, refreshKey : selector ?. refreshKey } ;
65
+ } ) ;
37
66
38
67
isPresenting$ = new LiveData < boolean > ( false ) ;
39
68
@@ -61,10 +90,6 @@ export class Editor extends Entity {
61
90
this . mode$ . next ( mode ) ;
62
91
}
63
92
64
- setEditorContainer ( editorContainer : AffineEditorContainer | null ) {
65
- this . editorContainer$ . next ( editorContainer ) ;
66
- }
67
-
68
93
setDefaultOpenProperty ( defaultOpenProperty : DefaultOpenProperty | undefined ) {
69
94
this . defaultOpenProperty$ . next ( defaultOpenProperty ) ;
70
95
}
@@ -73,6 +98,12 @@ export class Editor extends Entity {
73
98
* sync editor params with view query string
74
99
*/
75
100
bindWorkbenchView ( view : WorkbenchView ) {
101
+ if ( this . workbenchView ) {
102
+ throw new Error ( 'already bound' ) ;
103
+ }
104
+ this . workbenchView = view ;
105
+ this . defaultScrollPosition = view . getScrollPosition ( ) ?? null ;
106
+
76
107
const stablePrimaryMode = this . doc . getPrimaryMode ( ) ;
77
108
78
109
// eslint-disable-next-line rxjs/finnish
@@ -147,49 +178,103 @@ export class Editor extends Entity {
147
178
} ) ;
148
179
149
180
return ( ) => {
181
+ this . workbenchView = null ;
150
182
unsubscribeEditorParams . unsubscribe ( ) ;
151
183
unsubscribeViewParams . unsubscribe ( ) ;
152
184
} ;
153
185
}
154
186
155
187
bindEditorContainer (
156
188
editorContainer : AffineEditorContainer ,
157
- docTitle : DocTitle | null
189
+ docTitle ?: DocTitle | null ,
190
+ scrollViewport ?: HTMLElement | null
158
191
) {
192
+ if ( this . editorContainer$ . value ) {
193
+ throw new Error ( 'already bound' ) ;
194
+ }
195
+ this . editorContainer$ . next ( editorContainer ) ;
159
196
const unsubs : ( ( ) => void ) [ ] = [ ] ;
160
197
161
- const focusAt$ = LiveData . computed ( get => {
162
- const selector = get ( this . selector$ ) ;
163
- const mode = get ( this . mode$ ) ;
164
- let id = selector ?. blockIds ?. [ 0 ] ;
165
- let key = 'blockIds' ;
166
-
167
- if ( mode === 'edgeless' ) {
168
- const elementId = selector ?. elementIds ?. [ 0 ] ;
169
- if ( elementId ) {
170
- id = elementId ;
171
- key = 'elementIds' ;
172
- }
198
+ const rootService = editorContainer . host ?. std . getService ( 'affine:page' ) ;
199
+
200
+ // ----- Scroll Position and Selection -----
201
+ if ( this . defaultScrollPosition ) {
202
+ // if we have default scroll position, we should restore it
203
+ if (
204
+ this . mode$ . value === 'page' &&
205
+ typeof this . defaultScrollPosition === 'number'
206
+ ) {
207
+ scrollViewport ?. scrollTo ( 0 , this . defaultScrollPosition || 0 ) ;
208
+ } else if (
209
+ this . mode$ . value === 'edgeless' &&
210
+ typeof this . defaultScrollPosition === 'object' &&
211
+ rootService instanceof EdgelessRootService
212
+ ) {
213
+ rootService . viewport . setViewport ( this . defaultScrollPosition . zoom , [
214
+ this . defaultScrollPosition . centerX ,
215
+ this . defaultScrollPosition . centerY ,
216
+ ] ) ;
173
217
}
174
218
175
- if ( ! id ) return null ;
219
+ this . defaultScrollPosition = null ; // reset default scroll position
220
+ } else {
221
+ const initialFocusAt = this . focusAt$ . value ;
222
+
223
+ if ( initialFocusAt === null ) {
224
+ const title = docTitle ?. querySelector <
225
+ HTMLElement & { inlineEditor : InlineEditor | null }
226
+ > ( 'rich-text' ) ;
227
+ title ?. inlineEditor ?. focusEnd ( ) ;
228
+ } else {
229
+ const selection = editorContainer . host ?. std . selection ;
230
+
231
+ const { id, key, mode } = initialFocusAt ;
176
232
177
- return { id, key, mode, refreshKey : selector ?. refreshKey } ;
233
+ if ( mode === this . mode$ . value ) {
234
+ selection ?. setGroup ( 'scene' , [
235
+ selection ?. create ( 'highlight' , {
236
+ mode,
237
+ [ key ] : [ id ] ,
238
+ } ) ,
239
+ ] ) ;
240
+ }
241
+ }
242
+ }
243
+
244
+ // update scroll position when scrollViewport scroll
245
+ const saveScrollPosition = ( ) => {
246
+ if ( this . mode$ . value === 'page' && scrollViewport ) {
247
+ this . workbenchView ?. setScrollPosition ( scrollViewport . scrollTop ) ;
248
+ } else if (
249
+ this . mode$ . value === 'edgeless' &&
250
+ rootService instanceof EdgelessRootService
251
+ ) {
252
+ this . workbenchView ?. setScrollPosition ( {
253
+ centerX : rootService . viewport . centerX ,
254
+ centerY : rootService . viewport . centerY ,
255
+ zoom : rootService . viewport . zoom ,
256
+ } ) ;
257
+ }
258
+ } ;
259
+ scrollViewport ?. addEventListener ( 'scroll' , saveScrollPosition ) ;
260
+ unsubs . push ( ( ) => {
261
+ scrollViewport ?. removeEventListener ( 'scroll' , saveScrollPosition ) ;
178
262
} ) ;
179
- if ( focusAt$ . value === null && docTitle ) {
180
- const title = docTitle . querySelector <
181
- HTMLElement & { inlineEditor : InlineEditor | null }
182
- > ( 'rich-text' ) ;
183
- title ?. inlineEditor ?. focusEnd ( ) ;
263
+ if ( rootService instanceof EdgelessRootService ) {
264
+ unsubs . push (
265
+ rootService . viewport . viewportUpdated . on ( saveScrollPosition ) . dispose
266
+ ) ;
184
267
}
185
268
186
- const subscription = focusAt$
269
+ // update selection when focusAt$ changed
270
+ const subscription = this . focusAt$
187
271
. distinctUntilChanged (
188
272
( a , b ) =>
189
273
a ?. id === b ?. id &&
190
274
a ?. key === b ?. key &&
191
275
a ?. refreshKey === b ?. refreshKey
192
276
)
277
+ . pipe ( skip ( 1 ) )
193
278
. subscribe ( anchor => {
194
279
if ( ! anchor ) return ;
195
280
@@ -207,6 +292,7 @@ export class Editor extends Entity {
207
292
} ) ;
208
293
unsubs . push ( subscription . unsubscribe . bind ( subscription ) ) ;
209
294
295
+ // ----- Presenting -----
210
296
const edgelessPage = editorContainer . host ?. querySelector (
211
297
'affine-edgeless-root'
212
298
) ;
@@ -226,6 +312,7 @@ export class Editor extends Entity {
226
312
}
227
313
228
314
return ( ) => {
315
+ this . editorContainer$ . next ( null ) ;
229
316
for ( const unsub of unsubs ) {
230
317
unsub ( ) ;
231
318
}
0 commit comments