univer icon indicating copy to clipboard operation
univer copied to clipboard

feat(docs): support setting read-only

Open electroluxcode opened this issue 5 months ago • 3 comments

close #xxx

Description

This PR implements read-only mode for Univer Docs

Key Changes

  1. Permission System

    • Added DocumentEditablePermission permission point (similar to Sheets' WorkbookEditablePermission)
    • Integrated with IPermissionService for centralized permission management
  2. Facade API

    • Added FDocument.setEditable(value: boolean) method
    • Usage: univerAPI.getActiveDocument().setEditable(false)
  3. Command & UI Protection

    • Added permission checks to all editing commands (insert, delete, paste, list operations, etc.)
    • Added getCurrentDocRangeDisable$() observable for reactive UI updates
    • Updated all menu items and context menus to respect read-only state

Testing

  • step1: replace with the following code examples/src/docs/main.ts pnpm run dev
  • step2: pnpm run dev
  • step3: go to localhost:3000/docs

Then you will find that the docs are set to read-only status after 3 seconds



import { LocaleType, LogLevel, Univer, UniverInstanceType, UserManagerService } from '@univerjs/core';
import { FUniver } from '@univerjs/core/facade';
import { UniverDebuggerPlugin } from '@univerjs/debugger';
import { UniverDocsPlugin } from '@univerjs/docs';
import { UniverDocsDrawingUIPlugin } from '@univerjs/docs-drawing-ui';
import { UniverDocsHyperLinkUIPlugin } from '@univerjs/docs-hyper-link-ui';
import { UniverDocsMentionUIPlugin } from '@univerjs/docs-mention-ui';
import { UniverDocsQuickInsertUIPlugin } from '@univerjs/docs-quick-insert-ui';
import { UniverDocsThreadCommentUIPlugin } from '@univerjs/docs-thread-comment-ui';
import { UniverDocsUIPlugin } from '@univerjs/docs-ui';
import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula';
import { UniverRenderEnginePlugin } from '@univerjs/engine-render';
import { DEFAULT_DOCUMENT_DATA_SIMPLE } from '@univerjs/mockdata';
import zhCN from '@univerjs/mockdata/locales/zh-CN';
import { UniverUIPlugin } from '@univerjs/ui';

import '@univerjs/docs-ui/facade';

import '../global.css';

/* eslint-disable node/prefer-global/process */
const IS_E2E: boolean = !!process.env.IS_E2E;

// univer
const univer = new Univer({
    locale: LocaleType.ZH_CN,
    locales: {
        [LocaleType.ZH_CN]: zhCN,
    },
    logLevel: LogLevel.VERBOSE,
});

// core plugins
univer.registerPlugin(UniverRenderEnginePlugin);
univer.registerPlugin(UniverFormulaEnginePlugin);
univer.registerPlugin(UniverUIPlugin, {
    container: 'app',
});

univer.registerPlugin(UniverDocsPlugin);
univer.registerPlugin(UniverDocsUIPlugin, {
    container: 'univerdoc',
    layout: {
        docContainerConfig: {
            innerLeft: false,
        },
    },
    disableAutoFocus: true, // Disable auto-focus
});

univer.registerPlugin(UniverDocsDrawingUIPlugin);
univer.registerPlugin(UniverDocsThreadCommentUIPlugin);
univer.registerPlugin(UniverDocsHyperLinkUIPlugin);
univer.registerPlugin(UniverDocsMentionUIPlugin);
univer.registerPlugin(UniverDocsQuickInsertUIPlugin);

if (!IS_E2E) {
    univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_SIMPLE);
    univer.registerPlugin(UniverDebuggerPlugin);
} else {
    univer.registerPlugin(UniverDebuggerPlugin, {
        fab: false,
        performanceMonitor: {
            enabled: false,
        },
    });
}

// use for console test
declare global {
    // eslint-disable-next-line ts/naming-convention
    interface Window {
        univer?: Univer;
    }
}

window.univer = univer;
const injector = univer.__getInjector();
const userManagerService = injector.get(UserManagerService);

const mockUser = {
    userID: 'Owner_qxVnhPbQ',
    name: 'Owner',
    avatar: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAInSURBVHgBtZU9TxtBEIbfWRzFSIdkikhBSqRQkJqkCKTCFkqVInSUSaT0wC8w/gXxD4gU2nRJkXQWhAZowDUUWKIwEgWWbEEB3mVmx3dn4DA2nB/ppNuPeWd29mMIPXDr+RxwtgRHeW6+guNPRxogqnL7Dwz9psJ27S4NShaeZTH3kwXy6I81dlRKcmRui88swdq9AcSFL7Buz1Vmlns64MiLsCjzwnIYHLH57tbfFbs7KRaXyEU8FVZofqccOfA5l7Q8LPIkGrwnb2RPNEXWFVMUF3L+kDCk0btDDAMzOm5YfAHDwp4tG74wnzAsiOYMnJ3GoDybA7IT98/jm5+JNnfiIzAS6LlqHQBN/i6b2t/cV1Hh6BfwYlHnHP4AXi5q/8kmMMpOs8+BixZw/Fd6xUEHEbnkgclvQP2fGp7uShRKnQ3G32rkjV1th8JhIGG7tR/JyjGteSOZELwGMmNqIIigRCLRh2OZIE6BjItdd7pCW6Uhm1zzkUtungSxwEUzNpQ+GQumtH1ej1MqgmNT6vwmhCq5yuwq56EYTbgeQUz3yvrpV1b4ok3nYJ+eYhgYmjRUqErx2EDq0Fr8FhG++iqVGqxlUJI/70Ar0UgJaWHj6hYVHJrfKssAHot1JfqwE9WVWzXZVd5z2Ws/4PnmtEjkXeKJDvxUecLbWOXH/DP6QQ4J72NS0adedp1aseBfXP8odlZFfPvBF7SN/8hky1TYuPOAXAEipMx15u5ToAAAAABJRU5ErkJggg==',
    anonymous: false,
    canBindAnonymous: false,
};
userManagerService.setCurrentUser(mockUser);
window.univerAPI = FUniver.newAPI(univer);

// core: set readonly
setTimeout(() => {
    const univerAPI = window.univerAPI;
    const activeDocument = univerAPI?.getActiveDocument();

    if (activeDocument) {
        // 设置文档为只读模式(类似 Excel 的 setEditable)
        activeDocument.setEditable(false);

        console.log('✅ 文档已设置为只读模式');
        console.log('💡 可以在控制台使用以下命令切换:');
        console.log('   - window.univerAPI.getActiveDocument().setEditable(false)  // 只读');
        console.log('   - window.univerAPI.getActiveDocument().setEditable(true)   // 可编辑');
    }
}, 3000);

Pull Request Checklist

  • [x] Related tickets or issues have been linked in the PR description (or missing issue).
  • [x] Naming convention is followed.
  • [x] Unit tests have been added for the changes (if applicable).
  • [x] Breaking changes have been documented (or no breaking changes introduced in this PR).

electroluxcode avatar Nov 09 '25 20:11 electroluxcode