snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
This commit is contained in:
37
packages/watermark/README.md
Normal file
37
packages/watermark/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# @univerjs/watermark
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/watermark)
|
||||
[](https://npmjs.com/package/@univerjs/watermark)
|
||||
[](https://npmjs.com/package/@univerjs/watermark)
|
||||
|
||||
`@univerjs/watermark` adds configurable watermark rendering to Univer documents and sheets through the render engine.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/watermark` | `UniverWatermark` | No | No | Yes |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/watermark
|
||||
# or
|
||||
npm install @univerjs/watermark
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { UniverWatermarkPlugin } from '@univerjs/watermark';
|
||||
|
||||
univer.registerPlugin(UniverWatermarkPlugin);
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/watermark)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
90
packages/watermark/package.json
Normal file
90
packages/watermark/package.json
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "@univerjs/watermark",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Watermark rendering plugin for Univer.",
|
||||
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/univer"
|
||||
},
|
||||
"homepage": "https://univer.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dream-num/univer"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/dream-num/univer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"univer",
|
||||
"watermark",
|
||||
"rendering",
|
||||
"documents",
|
||||
"plugin"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./facade": "./src/facade/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"rxjs": "^7.8.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
75
packages/watermark/src/common/const.ts
Normal file
75
packages/watermark/src/common/const.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IImageWatermarkConfig, ITextWatermarkConfig, IUserInfoWatermarkConfig } from '@univerjs/engine-render';
|
||||
|
||||
export const UNIVER_WATERMARK_MENU = 'UNIVER_WATERMARK_MENU';
|
||||
|
||||
export const WATERMARK_PANEL = 'WATERMARK_PANEL';
|
||||
|
||||
export const WATERMARK_PANEL_FOOTER = 'WATERMARK_PANEL_FOOTER';
|
||||
|
||||
export const WATERMARK_IMAGE_ALLOW_IMAGE_LIST = ['image/png', 'image/jpeg', 'image/jpg', 'image/bmp'];
|
||||
|
||||
export const WatermarkTextBaseConfig: ITextWatermarkConfig = {
|
||||
content: '',
|
||||
fontSize: 16,
|
||||
color: 'rgb(0,0,0)',
|
||||
bold: false,
|
||||
italic: false,
|
||||
direction: 'ltr',
|
||||
x: 60,
|
||||
y: 36,
|
||||
repeat: true,
|
||||
spacingX: 200,
|
||||
spacingY: 100,
|
||||
rotate: 0,
|
||||
opacity: 0.15,
|
||||
};
|
||||
|
||||
export const WatermarkImageBaseConfig: IImageWatermarkConfig = {
|
||||
url: '',
|
||||
width: 100,
|
||||
height: 100,
|
||||
maintainAspectRatio: true,
|
||||
originRatio: 1,
|
||||
x: 60,
|
||||
y: 36,
|
||||
repeat: true,
|
||||
spacingX: 200,
|
||||
spacingY: 100,
|
||||
rotate: 0,
|
||||
opacity: 0.15,
|
||||
};
|
||||
|
||||
export const WatermarkUserInfoBaseConfig: IUserInfoWatermarkConfig = {
|
||||
name: true,
|
||||
email: false,
|
||||
phone: false,
|
||||
uid: false,
|
||||
fontSize: 16,
|
||||
color: 'rgb(0,0,0)',
|
||||
bold: false,
|
||||
italic: false,
|
||||
direction: 'ltr',
|
||||
x: 60,
|
||||
y: 60,
|
||||
repeat: true,
|
||||
spacingX: 200,
|
||||
spacingY: 100,
|
||||
rotate: -30,
|
||||
opacity: 0.15,
|
||||
};
|
||||
29
packages/watermark/src/config/config.ts
Normal file
29
packages/watermark/src/config/config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IImageWatermarkConfig, ITextWatermarkConfig, IUserInfoWatermarkConfig } from '@univerjs/engine-render';
|
||||
|
||||
export const WATERMARK_PLUGIN_CONFIG_KEY = 'watermark.config';
|
||||
|
||||
export const configSymbol = Symbol(WATERMARK_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverWatermarkConfig {
|
||||
userWatermarkSettings?: Partial<IUserInfoWatermarkConfig>;
|
||||
textWatermarkSettings?: Partial<ITextWatermarkConfig>;
|
||||
imageWatermarkSettings?: Partial<IImageWatermarkConfig>;
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverWatermarkConfig = {};
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { awaitTime } from '@univerjs/core';
|
||||
import { IWatermarkTypeEnum, UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render';
|
||||
import { Subject } from 'rxjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { WatermarkRenderController } from '../watermark.render.controller';
|
||||
|
||||
const layerInstances: Array<{ updateConfig: ReturnType<typeof vi.fn> }> = [];
|
||||
|
||||
vi.mock('@univerjs/engine-render', async () => {
|
||||
const actual = await vi.importActual<typeof import('@univerjs/engine-render')>('@univerjs/engine-render');
|
||||
|
||||
class WatermarkLayer {
|
||||
readonly updateConfig = vi.fn();
|
||||
|
||||
constructor() {
|
||||
layerInstances.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...actual,
|
||||
IWatermarkTypeEnum: {
|
||||
UserInfo: 'userInfo',
|
||||
Text: 'text',
|
||||
Image: 'image',
|
||||
},
|
||||
UNIVER_WATERMARK_LAYER_INDEX: 1000,
|
||||
UNIVER_WATERMARK_STORAGE_KEY: 'UNIVER_WATERMARK_STORAGE_KEY',
|
||||
WatermarkLayer,
|
||||
};
|
||||
});
|
||||
|
||||
describe('WatermarkRenderController', () => {
|
||||
it('should init layer, load config and react to updates', async () => {
|
||||
layerInstances.length = 0;
|
||||
|
||||
const updateConfig$ = new Subject<unknown>();
|
||||
const updateWatermarkConfig = vi.fn();
|
||||
const scene = {
|
||||
addLayer: vi.fn(),
|
||||
};
|
||||
const makeDirty = vi.fn();
|
||||
const localStorageService = {
|
||||
getItem: vi.fn(async () => ({
|
||||
type: IWatermarkTypeEnum.Text,
|
||||
config: { text: { content: 'init' } },
|
||||
})),
|
||||
};
|
||||
const userManagerService = {
|
||||
getCurrentUser: vi.fn(() => ({ id: 'u1' })),
|
||||
};
|
||||
|
||||
const controller = new WatermarkRenderController(
|
||||
{
|
||||
scene,
|
||||
mainComponent: { makeDirty },
|
||||
} as never,
|
||||
{
|
||||
updateConfig$: updateConfig$.asObservable(),
|
||||
updateWatermarkConfig,
|
||||
} as never,
|
||||
localStorageService as never,
|
||||
userManagerService as never
|
||||
);
|
||||
|
||||
expect(scene.addLayer).toHaveBeenCalledTimes(1);
|
||||
expect(localStorageService.getItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
|
||||
await awaitTime(0);
|
||||
expect(updateWatermarkConfig).toHaveBeenCalledWith({
|
||||
type: IWatermarkTypeEnum.Text,
|
||||
config: { text: { content: 'init' } },
|
||||
});
|
||||
expect(makeDirty).toHaveBeenCalled();
|
||||
|
||||
const layer = layerInstances[0];
|
||||
expect(layer).toBeDefined();
|
||||
|
||||
updateConfig$.next(null);
|
||||
expect(layer.updateConfig).toHaveBeenCalledWith();
|
||||
|
||||
updateConfig$.next({
|
||||
type: IWatermarkTypeEnum.UserInfo,
|
||||
config: { userInfo: { name: true } },
|
||||
});
|
||||
expect(layer.updateConfig).toHaveBeenCalledWith(
|
||||
{ type: IWatermarkTypeEnum.UserInfo, config: { userInfo: { name: true } } },
|
||||
{ id: 'u1' }
|
||||
);
|
||||
|
||||
updateConfig$.next({
|
||||
type: IWatermarkTypeEnum.Image,
|
||||
config: { image: { url: 'img' } },
|
||||
});
|
||||
expect(layer.updateConfig).toHaveBeenCalledWith({
|
||||
type: IWatermarkTypeEnum.Image,
|
||||
config: { image: { url: 'img' } },
|
||||
});
|
||||
|
||||
await controller.dispose();
|
||||
});
|
||||
|
||||
it('should skip init config update when storage is empty', async () => {
|
||||
layerInstances.length = 0;
|
||||
|
||||
const updateConfig$ = new Subject<unknown>();
|
||||
const updateWatermarkConfig = vi.fn();
|
||||
const makeDirty = vi.fn();
|
||||
const _controller = new WatermarkRenderController(
|
||||
{
|
||||
scene: { addLayer: vi.fn() },
|
||||
mainComponent: { makeDirty },
|
||||
} as never,
|
||||
{
|
||||
updateConfig$: updateConfig$.asObservable(),
|
||||
updateWatermarkConfig,
|
||||
} as never,
|
||||
{ getItem: vi.fn(async () => undefined) } as never,
|
||||
{ getCurrentUser: vi.fn(() => ({ id: 'u1' })) } as never
|
||||
);
|
||||
expect(_controller).toBeDefined();
|
||||
|
||||
await awaitTime(0);
|
||||
expect(updateWatermarkConfig).not.toHaveBeenCalled();
|
||||
expect(makeDirty).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { UnitModel } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderModule, IWatermarkConfigWithType } from '@univerjs/engine-render';
|
||||
import { ILocalStorageService, Inject, RxDisposable, UserManagerService } from '@univerjs/core';
|
||||
import {
|
||||
IWatermarkTypeEnum,
|
||||
UNIVER_WATERMARK_LAYER_INDEX,
|
||||
UNIVER_WATERMARK_STORAGE_KEY,
|
||||
WatermarkLayer,
|
||||
} from '@univerjs/engine-render';
|
||||
import { WatermarkService } from '../services/watermark.service';
|
||||
|
||||
export class WatermarkRenderController extends RxDisposable implements IRenderModule {
|
||||
private readonly _watermarkLayer: WatermarkLayer;
|
||||
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<UnitModel>,
|
||||
@Inject(WatermarkService) private _watermarkService: WatermarkService,
|
||||
@Inject(ILocalStorageService) private _localStorageService: ILocalStorageService,
|
||||
@Inject(UserManagerService) private _userManagerService: UserManagerService
|
||||
) {
|
||||
super();
|
||||
this._watermarkLayer = new WatermarkLayer(_context.scene, [], UNIVER_WATERMARK_LAYER_INDEX);
|
||||
this._initAddRender();
|
||||
this._initWatermarkUpdate();
|
||||
this._initWatermarkConfig();
|
||||
}
|
||||
|
||||
private _initAddRender() {
|
||||
const { scene } = this._context;
|
||||
scene.addLayer(this._watermarkLayer);
|
||||
}
|
||||
|
||||
private async _initWatermarkConfig() {
|
||||
const config = await this._localStorageService.getItem<IWatermarkConfigWithType>(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
if (config) {
|
||||
this._watermarkService.updateWatermarkConfig(config);
|
||||
this._context.mainComponent?.makeDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private _initWatermarkUpdate() {
|
||||
this.disposeWithMe(
|
||||
this._watermarkService.updateConfig$.subscribe((_config) => {
|
||||
if (!_config) {
|
||||
this._watermarkLayer.updateConfig();
|
||||
this._context.mainComponent?.makeDirty();
|
||||
return;
|
||||
}
|
||||
if (_config.type === IWatermarkTypeEnum.UserInfo) {
|
||||
this._watermarkLayer.updateConfig(_config, this._userManagerService.getCurrentUser());
|
||||
} else {
|
||||
this._watermarkLayer.updateConfig(_config);
|
||||
}
|
||||
this._context.mainComponent?.makeDirty();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
104
packages/watermark/src/facade/__tests__/f-univer.spec.ts
Normal file
104
packages/watermark/src/facade/__tests__/f-univer.spec.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injector } from '@univerjs/core';
|
||||
import { FEnum, FUniver } from '@univerjs/core/facade';
|
||||
import { IWatermarkTypeEnum } from '@univerjs/engine-render';
|
||||
import { WatermarkService, WatermarkTextBaseConfig } from '@univerjs/watermark';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { FUniverWatermarkMixin, FWatermarkEnumMixin } from '../f-univer';
|
||||
|
||||
class TestWatermarkService {
|
||||
readonly updatedConfigs: unknown[] = [];
|
||||
deleteCount = 0;
|
||||
|
||||
updateWatermarkConfig(config: unknown): void {
|
||||
this.updatedConfigs.push(config);
|
||||
}
|
||||
|
||||
deleteWatermarkConfig(): void {
|
||||
this.deleteCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
describe('watermark facade', () => {
|
||||
it('adds text and image watermarks through the injected watermark service', () => {
|
||||
const injector = new Injector();
|
||||
injector.add([WatermarkService, { useClass: TestWatermarkService as never }]);
|
||||
const service = injector.get(WatermarkService) as unknown as TestWatermarkService;
|
||||
const thisArg = {
|
||||
_injector: injector,
|
||||
};
|
||||
const addWatermark = FUniverWatermarkMixin.prototype.addWatermark as (
|
||||
this: typeof thisArg,
|
||||
type: IWatermarkTypeEnum,
|
||||
config: unknown
|
||||
) => FUniver;
|
||||
|
||||
expect(addWatermark.call(
|
||||
thisArg,
|
||||
IWatermarkTypeEnum.Text,
|
||||
{ content: 'Confidential' } as never
|
||||
)).toBe(thisArg);
|
||||
expect(service.updatedConfigs.at(-1)).toEqual({
|
||||
type: IWatermarkTypeEnum.Text,
|
||||
config: {
|
||||
text: {
|
||||
...WatermarkTextBaseConfig,
|
||||
content: 'Confidential',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addWatermark.call(
|
||||
thisArg,
|
||||
IWatermarkTypeEnum.Image,
|
||||
{ url: 'https://example.com/watermark.png', width: 320 } as never
|
||||
);
|
||||
expect(service.updatedConfigs.at(-1)).toMatchObject({
|
||||
type: IWatermarkTypeEnum.Image,
|
||||
config: {
|
||||
image: {
|
||||
url: 'https://example.com/watermark.png',
|
||||
width: 320,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes watermark config, rejects unknown types and exposes watermark enum on real facade classes', () => {
|
||||
const injector = new Injector();
|
||||
injector.add([WatermarkService, { useClass: TestWatermarkService as never }]);
|
||||
const service = injector.get(WatermarkService) as unknown as TestWatermarkService;
|
||||
const thisArg = {
|
||||
_injector: injector,
|
||||
};
|
||||
const addWatermark = FUniverWatermarkMixin.prototype.addWatermark as (
|
||||
this: typeof thisArg,
|
||||
type: IWatermarkTypeEnum,
|
||||
config: unknown
|
||||
) => FUniver;
|
||||
|
||||
expect(() =>
|
||||
addWatermark.call(thisArg, 'unknown' as never, {} as never)
|
||||
).toThrow('Unknown watermark type');
|
||||
expect(FUniverWatermarkMixin.prototype.deleteWatermark.call(thisArg as never)).toBe(thisArg);
|
||||
expect(service.deleteCount).toBe(1);
|
||||
expect(FEnum.get().IWatermarkTypeEnum).toBe(IWatermarkTypeEnum);
|
||||
expect(Object.create(FWatermarkEnumMixin.prototype).IWatermarkTypeEnum).toBe(IWatermarkTypeEnum);
|
||||
expect(typeof FUniver.prototype.addWatermark).toBe('function');
|
||||
});
|
||||
});
|
||||
137
packages/watermark/src/facade/f-univer.ts
Normal file
137
packages/watermark/src/facade/f-univer.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IImageWatermarkConfig, ITextWatermarkConfig } from '@univerjs/engine-render';
|
||||
import { FEnum, FUniver } from '@univerjs/core/facade';
|
||||
import { IWatermarkTypeEnum } from '@univerjs/engine-render';
|
||||
import { WatermarkImageBaseConfig, WatermarkService, WatermarkTextBaseConfig } from '@univerjs/watermark';
|
||||
|
||||
interface IFWatermarkEnumMixin {
|
||||
/** Text and image watermark types accepted by `addWatermark`. */
|
||||
IWatermarkTypeEnum: typeof IWatermarkTypeEnum;
|
||||
}
|
||||
|
||||
export class FWatermarkEnumMixin extends FEnum implements IFWatermarkEnumMixin {
|
||||
override get IWatermarkTypeEnum(): typeof IWatermarkTypeEnum {
|
||||
return IWatermarkTypeEnum;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFUniverWatermarkMixin {
|
||||
/**
|
||||
* Adds a watermark to the unit. Supports both text and image watermarks based on the specified type.
|
||||
* @param {IWatermarkTypeEnum.Text} type - The type of watermark to add is Text.
|
||||
* @param {ITextWatermarkConfig} config - The configuration object for the text type watermark.
|
||||
* @returns {FUniver} The {@link FUniver} instance for chaining.
|
||||
* @throws {Error} Throws an error if the watermark type is unknown.
|
||||
* @example
|
||||
* ```ts
|
||||
* univerAPI.addWatermark('text', {
|
||||
* content: 'Univer',
|
||||
* fontSize: 20,
|
||||
* repeat: true,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
addWatermark(type: IWatermarkTypeEnum.Text, config: ITextWatermarkConfig): FUniver;
|
||||
/**
|
||||
* Adds a watermark to the unit. Supports both text and image watermarks based on the specified type.
|
||||
* @param {IWatermarkTypeEnum.Image} type - The type of watermark to add is Image.
|
||||
* @param {IImageWatermarkConfig} config - The configuration object for the image type watermark.
|
||||
* @returns {FUniver} The {@link FUniver} instance for chaining.
|
||||
* @throws {Error} Throws an error if the watermark type is unknown.
|
||||
* @example
|
||||
* ```ts
|
||||
* univerAPI.addWatermark('image', {
|
||||
* url: 'https://avatars.githubusercontent.com/u/61444807?s=48&v=4',
|
||||
* width: 100,
|
||||
* height: 100,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
addWatermark(type: IWatermarkTypeEnum.Image, config: IImageWatermarkConfig): FUniver;
|
||||
addWatermark(
|
||||
type: IWatermarkTypeEnum.Text | IWatermarkTypeEnum.Image,
|
||||
config: ITextWatermarkConfig | IImageWatermarkConfig
|
||||
): FUniver;
|
||||
|
||||
/**
|
||||
* Deletes the currently applied watermark from the unit.
|
||||
* This function retrieves the watermark service and invokes the method to remove any existing watermark configuration.
|
||||
* @returns {FUniver} The {@link FUniver} instance for chaining.
|
||||
* @example
|
||||
* ```ts
|
||||
* univerAPI.deleteWatermark();
|
||||
* ```
|
||||
*/
|
||||
deleteWatermark(): FUniver;
|
||||
}
|
||||
|
||||
export class FUniverWatermarkMixin extends FUniver {
|
||||
// #region watermark
|
||||
|
||||
override addWatermark(type: IWatermarkTypeEnum.Text, config: ITextWatermarkConfig): FUniver;
|
||||
override addWatermark(type: IWatermarkTypeEnum.Image, config: IImageWatermarkConfig): FUniver;
|
||||
override addWatermark(
|
||||
type: IWatermarkTypeEnum.Text | IWatermarkTypeEnum.Image,
|
||||
config: ITextWatermarkConfig | IImageWatermarkConfig
|
||||
): FUniver {
|
||||
const watermarkService = this._injector.get(WatermarkService);
|
||||
if (type === IWatermarkTypeEnum.Text) {
|
||||
watermarkService.updateWatermarkConfig({
|
||||
type: IWatermarkTypeEnum.Text,
|
||||
config: {
|
||||
text: {
|
||||
...WatermarkTextBaseConfig,
|
||||
...config,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (type === IWatermarkTypeEnum.Image) {
|
||||
watermarkService.updateWatermarkConfig({
|
||||
type: IWatermarkTypeEnum.Image,
|
||||
config: {
|
||||
image: {
|
||||
...WatermarkImageBaseConfig,
|
||||
...config,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
throw new Error('Unknown watermark type');
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
override deleteWatermark(): FUniver {
|
||||
const watermarkService = this._injector.get(WatermarkService);
|
||||
watermarkService.deleteWatermarkConfig();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
FUniver.extend(FUniverWatermarkMixin);
|
||||
FEnum.extend(FWatermarkEnumMixin);
|
||||
|
||||
declare module '@univerjs/core/facade' {
|
||||
interface FUniver extends IFUniverWatermarkMixin { }
|
||||
export interface FEnum extends IFWatermarkEnumMixin {
|
||||
}
|
||||
}
|
||||
19
packages/watermark/src/facade/index.ts
Normal file
19
packages/watermark/src/facade/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './f-univer';
|
||||
|
||||
export type * from './f-univer';
|
||||
25
packages/watermark/src/index.ts
Normal file
25
packages/watermark/src/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
WATERMARK_IMAGE_ALLOW_IMAGE_LIST,
|
||||
WatermarkImageBaseConfig,
|
||||
WatermarkTextBaseConfig,
|
||||
WatermarkUserInfoBaseConfig,
|
||||
} from './common/const';
|
||||
export type { IUniverWatermarkConfig } from './config/config';
|
||||
export { UniverWatermarkPlugin } from './plugin';
|
||||
export { WatermarkService } from './services/watermark.service';
|
||||
126
packages/watermark/src/plugin.ts
Normal file
126
packages/watermark/src/plugin.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
import type { IWatermarkConfigWithType } from '@univerjs/engine-render';
|
||||
import type { IUniverWatermarkConfig } from './config/config';
|
||||
import {
|
||||
DependentOn,
|
||||
IConfigService,
|
||||
ILocalStorageService,
|
||||
Inject,
|
||||
Injector,
|
||||
merge,
|
||||
Plugin,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import {
|
||||
IRenderManagerService,
|
||||
IWatermarkTypeEnum,
|
||||
UNIVER_WATERMARK_STORAGE_KEY,
|
||||
UniverRenderEnginePlugin,
|
||||
} from '@univerjs/engine-render';
|
||||
import pkg from '../package.json';
|
||||
import { WatermarkImageBaseConfig, WatermarkTextBaseConfig, WatermarkUserInfoBaseConfig } from './common/const';
|
||||
import { defaultPluginConfig, WATERMARK_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { WatermarkRenderController } from './controllers/watermark.render.controller';
|
||||
import { WatermarkService } from './services/watermark.service';
|
||||
|
||||
@DependentOn(UniverRenderEnginePlugin)
|
||||
export class UniverWatermarkPlugin extends Plugin {
|
||||
static override pluginName = 'UNIVER_WATERMARK_PLUGIN';
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverWatermarkConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected override _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@IRenderManagerService private readonly _renderManagerSrv: IRenderManagerService,
|
||||
@Inject(ILocalStorageService) private readonly _localStorageService: ILocalStorageService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Manage the plugin configuration.
|
||||
const { ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
this._configService.setConfig(WATERMARK_PLUGIN_CONFIG_KEY, rest);
|
||||
|
||||
this._initWatermarkStorage();
|
||||
this._initDependencies();
|
||||
}
|
||||
|
||||
private async _initWatermarkStorage() {
|
||||
const config = this._configService.getConfig<IUniverWatermarkConfig>(WATERMARK_PLUGIN_CONFIG_KEY);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
const { userWatermarkSettings, textWatermarkSettings, imageWatermarkSettings } = config;
|
||||
if (userWatermarkSettings) {
|
||||
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, {
|
||||
type: IWatermarkTypeEnum.UserInfo,
|
||||
config: {
|
||||
userInfo: merge({}, WatermarkUserInfoBaseConfig, userWatermarkSettings),
|
||||
},
|
||||
});
|
||||
} else if (textWatermarkSettings) {
|
||||
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, {
|
||||
type: IWatermarkTypeEnum.Text,
|
||||
config: {
|
||||
text: merge({}, WatermarkTextBaseConfig, textWatermarkSettings),
|
||||
},
|
||||
});
|
||||
} else if (imageWatermarkSettings) {
|
||||
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, {
|
||||
type: IWatermarkTypeEnum.Image,
|
||||
config: {
|
||||
image: merge({}, WatermarkImageBaseConfig, imageWatermarkSettings),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const config = await this._localStorageService.getItem<IWatermarkConfigWithType>(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
if (config?.type === IWatermarkTypeEnum.UserInfo) {
|
||||
this._localStorageService.removeItem(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _initDependencies(): void {
|
||||
([[WatermarkService]] as Dependency[]).forEach((d) => {
|
||||
this._injector.add(d);
|
||||
});
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
const injector = this._injector;
|
||||
injector.get(WatermarkService);
|
||||
this._initRenderDependencies();
|
||||
}
|
||||
|
||||
private _initRenderDependencies(): void {
|
||||
([
|
||||
[WatermarkRenderController],
|
||||
] as Dependency[]).forEach((d) => {
|
||||
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_SHEET, d);
|
||||
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_DOC, d);
|
||||
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_SLIDE, d);
|
||||
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_BASE, d);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ILocalStorageService, Injector } from '@univerjs/core';
|
||||
import { UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { WatermarkService } from '../watermark.service';
|
||||
|
||||
const getItem = vi.fn(async () => ({ type: 'text' }));
|
||||
const setItem = vi.fn();
|
||||
const removeItem = vi.fn();
|
||||
|
||||
class TestLocalStorage {
|
||||
getItem = getItem;
|
||||
setItem = setItem;
|
||||
removeItem = removeItem;
|
||||
}
|
||||
|
||||
describe('WatermarkService', () => {
|
||||
it('should get/update/delete/refresh and dispose', async () => {
|
||||
getItem.mockClear();
|
||||
setItem.mockClear();
|
||||
removeItem.mockClear();
|
||||
const injector = new Injector();
|
||||
injector.add([ILocalStorageService, { useClass: TestLocalStorage as never }]);
|
||||
injector.add([WatermarkService]);
|
||||
const service = injector.get(WatermarkService);
|
||||
|
||||
await expect(service.getWatermarkConfig()).resolves.toEqual({ type: 'text' });
|
||||
expect(getItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
|
||||
const updateValues: unknown[] = [];
|
||||
const refreshValues: number[] = [];
|
||||
service.updateConfig$.subscribe((value) => updateValues.push(value));
|
||||
service.refresh$.subscribe((value) => refreshValues.push(value));
|
||||
|
||||
service.updateWatermarkConfig({ type: 'image', config: { image: { url: 'x' } } } as never);
|
||||
expect(setItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY, {
|
||||
type: 'image',
|
||||
config: { image: { url: 'x' } },
|
||||
});
|
||||
|
||||
service.deleteWatermarkConfig();
|
||||
expect(removeItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
expect(updateValues).toEqual([
|
||||
{ type: 'image', config: { image: { url: 'x' } } },
|
||||
null,
|
||||
]);
|
||||
|
||||
service.refresh();
|
||||
expect(refreshValues.length).toBe(1);
|
||||
expect(refreshValues[0]).toBeTypeOf('number');
|
||||
|
||||
service.dispose();
|
||||
});
|
||||
});
|
||||
58
packages/watermark/src/services/watermark.service.ts
Normal file
58
packages/watermark/src/services/watermark.service.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Nullable } from '@univerjs/core';
|
||||
import type { IWatermarkConfigWithType } from '@univerjs/engine-render';
|
||||
import { Disposable, ILocalStorageService, Inject } from '@univerjs/core';
|
||||
import { UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export class WatermarkService extends Disposable {
|
||||
private readonly _updateConfig$ = new Subject<Nullable<IWatermarkConfigWithType>>();
|
||||
public readonly updateConfig$ = this._updateConfig$.asObservable();
|
||||
private readonly _refresh$ = new Subject<number>();
|
||||
public readonly refresh$ = this._refresh$.asObservable();
|
||||
|
||||
constructor(
|
||||
@Inject(ILocalStorageService) private _localStorageService: ILocalStorageService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getWatermarkConfig() {
|
||||
const res = await this._localStorageService.getItem<IWatermarkConfigWithType>(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
return res;
|
||||
}
|
||||
|
||||
public updateWatermarkConfig(config: IWatermarkConfigWithType) {
|
||||
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, config);
|
||||
this._updateConfig$.next(config);
|
||||
}
|
||||
|
||||
public deleteWatermarkConfig() {
|
||||
this._localStorageService.removeItem(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
this._updateConfig$.next(null);
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
this._refresh$.next(Math.random());
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._refresh$.complete();
|
||||
this._updateConfig$.complete();
|
||||
}
|
||||
}
|
||||
8
packages/watermark/tsconfig.json
Normal file
8
packages/watermark/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
18
packages/watermark/tsconfig.node.json
Normal file
18
packages/watermark/tsconfig.node.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/node",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
3
packages/watermark/vitest.config.ts
Normal file
3
packages/watermark/vitest.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
Reference in New Issue
Block a user