Skip to content

Commit 1265362

Browse files
committed
feat: align document read-only menu behavior
1 parent bbbde0a commit 1265362

3 files changed

Lines changed: 89 additions & 5 deletions

File tree

packages/docs-ui/src/menu/menu.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
DocumentFlavor,
2828
HorizontalAlign,
2929
ICommandService,
30+
IPermissionService,
3031
IUniverInstanceService,
3132
NAMED_STYLE_MAP,
3233
NamedStyleType,
@@ -39,10 +40,12 @@ import {
3940
import {
4041
DocSelectionManagerService,
4142
DocSkeletonManagerService,
43+
getDocumentPermissionValue,
4244
RichTextEditingMutation,
4345
SetTextSelectionsOperation,
4446
} from '@univerjs/docs';
4547
import { DocumentEditArea, IRenderManagerService } from '@univerjs/engine-render';
48+
import { UnitAction } from '@univerjs/protocol';
4649
import {
4750
COLOR_PICKER_COMPONENT,
4851
COMMON_LABEL_COMPONENT,
@@ -58,7 +61,7 @@ import {
5861
SYMBOL_PICKER_COMPONENT,
5962
} from '@univerjs/ui';
6063

61-
import { combineLatest, distinctUntilChanged, map, Observable, shareReplay } from 'rxjs';
64+
import { combineLatest, distinctUntilChanged, map, Observable, shareReplay, startWith } from 'rxjs';
6265
import { OpenHeaderFooterPanelCommand } from '../commands/commands/doc-header-footer.command';
6366
import { HorizontalLineCommand } from '../commands/commands/doc-horizontal-line.command';
6467
import {
@@ -327,8 +330,9 @@ function getTableDisabledObservable(accessor: IAccessor): Observable<boolean> {
327330
export function disableMenuWhenNoDocRange(accessor: IAccessor): Observable<boolean> {
328331
const docSelectionManagerService = accessor.get(DocSelectionManagerService);
329332
const univerInstanceService = accessor.get(IUniverInstanceService);
333+
const permissionService = accessor.get(IPermissionService);
330334

331-
return new Observable((subscriber) => {
335+
const selectionDisabled$ = new Observable<boolean>((subscriber) => {
332336
const subscription = docSelectionManagerService.textSelection$.subscribe((selection) => {
333337
if (selection == null) {
334338
subscriber.next(true);
@@ -356,6 +360,20 @@ export function disableMenuWhenNoDocRange(accessor: IAccessor): Observable<boole
356360

357361
return () => subscription.unsubscribe();
358362
});
363+
364+
const permissionDisabled$ = combineLatest([
365+
univerInstanceService.getCurrentTypeOfUnit$<DocumentDataModel>(UniverInstanceType.UNIVER_DOC),
366+
permissionService.permissionPointUpdate$.pipe(startWith(undefined)),
367+
]).pipe(map(([document]) => !document || !getDocumentPermissionValue(
368+
permissionService,
369+
document.getUnitId(),
370+
document.getUnitId(),
371+
UnitAction.Edit
372+
)));
373+
374+
return combineLatest([selectionDisabled$, permissionDisabled$]).pipe(
375+
map(([selectionDisabled, permissionDisabled]) => selectionDisabled || permissionDisabled)
376+
);
359377
}
360378

361379
export const DOC_INSERT_EMOJI_MENU_ID = 'doc.menu.insert-emoji';

packages/docs-ui/src/services/__tests__/float-menu.service.spec.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ import {
3030
IContextService,
3131
ILogService,
3232
Injector,
33+
IPermissionService,
3334
IUniverInstanceService,
35+
PermissionService,
3436
RANGE_DIRECTION,
3537
UniverInstanceService,
3638
} from '@univerjs/core';
37-
import { DocSelectionManagerService, SetTextSelectionsOperation } from '@univerjs/docs';
39+
import { DocSelectionManagerService, setDocumentPermissionValue, SetTextSelectionsOperation } from '@univerjs/docs';
3840
import { NORMAL_TEXT_SELECTION_PLUGIN_STYLE } from '@univerjs/engine-render';
41+
import { UnitAction } from '@univerjs/protocol';
3942
import { ComponentManager } from '@univerjs/ui';
4043
import { Subject } from 'rxjs';
4144
import { describe, expect, it } from 'vitest';
@@ -93,6 +96,7 @@ function createActiveFloatMenuHarness(
9396
runtimeFocusCoordinator?: EmbedRuntimeFocusCoordinator
9497
) {
9598
const injector = new Injector();
99+
injector.add([IPermissionService, { useClass: PermissionService }]);
96100
injector.add([ILogService, { useClass: DesktopLogService }]);
97101
injector.add([IConfigService, { useClass: ConfigService }]);
98102
injector.add([IContextService, { useClass: ContextService }]);
@@ -125,6 +129,7 @@ function createActiveFloatMenuHarness(
125129
describe('DocFloatMenuService', () => {
126130
it('does not register or show a floating toolbar inside internal document editors', () => {
127131
const injector = new Injector();
132+
injector.add([IPermissionService, { useClass: PermissionService }]);
128133
injector.add([ILogService, { useClass: DesktopLogService }]);
129134
injector.add([IConfigService, { useClass: ConfigService }]);
130135
injector.add([IContextService, { useClass: ContextService }]);
@@ -143,6 +148,7 @@ describe('DocFloatMenuService', () => {
143148

144149
it('shows one floating toolbar for a text selection and hides it when selection restarts', () => {
145150
const injector = new Injector();
151+
injector.add([IPermissionService, { useClass: PermissionService }]);
146152
injector.add([ILogService, { useClass: DesktopLogService }]);
147153
injector.add([IConfigService, { useClass: ConfigService }]);
148154
injector.add([IContextService, { useClass: ContextService }]);
@@ -207,8 +213,47 @@ describe('DocFloatMenuService', () => {
207213
expect(popupService.disposedCount).toBe(1);
208214
});
209215

216+
it('hides the floating toolbar when document edit permission is revoked', () => {
217+
const unitId = 'doc-read-only-float-menu';
218+
const harness = createActiveFloatMenuHarness(unitId, {
219+
dataStream: 'Hello world\r\n',
220+
paragraphs: [{ paragraphId: 'para_docs_ui_read_only_float_menu', startIndex: 11 }],
221+
sectionBreaks: [],
222+
customRanges: [],
223+
tables: [],
224+
textRuns: [],
225+
});
226+
const selection = {
227+
textRanges: [{ startOffset: 0, endOffset: 5, collapsed: false }],
228+
rectRanges: [],
229+
segmentId: '',
230+
segmentPage: -1,
231+
style: NORMAL_TEXT_SELECTION_PLUGIN_STYLE,
232+
isEditing: true,
233+
};
234+
harness.selectionManager.__replaceTextRangesWithNoRefresh(selection, { unitId, subUnitId: unitId });
235+
expect(harness.service.floatMenu).toMatchObject({ start: 0, end: 5 });
236+
237+
setDocumentPermissionValue(
238+
harness.injector.get(IPermissionService),
239+
unitId,
240+
unitId,
241+
UnitAction.Edit,
242+
false
243+
);
244+
245+
expect(harness.service.floatMenu).toBeNull();
246+
expect(harness.popupService.disposedCount).toBe(1);
247+
harness.selectionManager.__replaceTextRangesWithNoRefresh({
248+
...selection,
249+
textRanges: [{ startOffset: 6, endOffset: 11, collapsed: false }],
250+
}, { unitId, subUnitId: unitId });
251+
expect(harness.popupService.ranges).toEqual(['0:5']);
252+
});
253+
210254
it('does not show the floating toolbar for selections inside code blocks', () => {
211255
const injector = new Injector();
256+
injector.add([IPermissionService, { useClass: PermissionService }]);
212257
injector.add([ILogService, { useClass: DesktopLogService }]);
213258
injector.add([IConfigService, { useClass: ConfigService }]);
214259
injector.add([IContextService, { useClass: ContextService }]);

packages/docs-ui/src/services/float-menu.service.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import {
2424
FOCUSING_COMMON_DRAWINGS,
2525
IContextService,
2626
Inject,
27+
IPermissionService,
2728
isInternalEditorID,
2829
IUniverInstanceService,
2930
Optional,
3031
toDisposable,
3132
UniverInstanceType,
3233
} from '@univerjs/core';
33-
import { DocSelectionManagerService } from '@univerjs/docs';
34+
import { DocSelectionManagerService, getDocumentPermissionValue } from '@univerjs/docs';
35+
import { UnitAction } from '@univerjs/protocol';
3436
import { FLOAT_MENU_COMPONENT_KEY } from '../views/float-toolbar/FloatToolbar';
3537
import { IDocEmbedRuntimeFocusCoordinator } from './doc-embed-integration.service';
3638
import { DocCanvasPopManagerService } from './doc-popup-manager.service';
@@ -64,13 +66,15 @@ export class DocFloatMenuService extends Disposable implements IRenderModule {
6466
@Inject(IUniverInstanceService) private readonly _univerInstanceService: IUniverInstanceService,
6567
@Inject(DocSelectionRenderService) private readonly _docSelectionRenderService: DocSelectionRenderService,
6668
@IContextService private readonly _contextService: IContextService,
69+
@IPermissionService private readonly _permissionService: IPermissionService,
6770
@Optional(IDocEmbedRuntimeFocusCoordinator) private readonly _embedRuntimeFocusCoordinator?: IDocEmbedRuntimeFocusCoordinator
6871
) {
6972
super();
7073

7174
if (isInternalEditorID(this._context.unitId)) {
7275
return;
7376
}
77+
this._initPermissionLifecycle();
7478
this._initSelectionChange();
7579
this._initEmbedRuntimeLifecycle();
7680

@@ -136,6 +140,23 @@ export class DocFloatMenuService extends Disposable implements IRenderModule {
136140
}));
137141
}
138142

143+
private _initPermissionLifecycle(): void {
144+
this.disposeWithMe(this._permissionService.permissionPointUpdate$.subscribe(() => {
145+
if (!this._canEditDocument()) {
146+
this._hideFloatMenu();
147+
}
148+
}));
149+
}
150+
151+
private _canEditDocument(): boolean {
152+
return getDocumentPermissionValue(
153+
this._permissionService,
154+
this._context.unitId,
155+
this._context.unitId,
156+
UnitAction.Edit
157+
);
158+
}
159+
139160
private _initEmbedRuntimeLifecycle(): void {
140161
if (!this._embedRuntimeFocusCoordinator) {
141162
return;
@@ -176,7 +197,7 @@ export class DocFloatMenuService extends Disposable implements IRenderModule {
176197

177198
private _showFloatMenu(unitId: string, range: ITextRangeParam) {
178199
const documentDataModel = this._univerInstanceService.getUnit<DocumentDataModel>(unitId, UniverInstanceType.UNIVER_DOC);
179-
if (!documentDataModel || documentDataModel.getDisabled()) {
200+
if (!documentDataModel || documentDataModel.getDisabled() || !this._canEditDocument()) {
180201
return;
181202
}
182203

0 commit comments

Comments
 (0)