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:
50
packages/sheets-hyper-link/README.md
Normal file
50
packages/sheets-hyper-link/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# @univerjs/sheets-hyper-link
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/sheets-hyper-link)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-hyper-link)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-hyper-link)
|
||||
|
||||
`@univerjs/sheets-hyper-link` adds the core hyperlink model, commands, and sheet-specific services for Univer Sheets.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/sheets-hyper-link` | `UniverSheetsHyperLink` | No | Yes | Yes |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/sheets-hyper-link
|
||||
# or
|
||||
npm install @univerjs/sheets-hyper-link
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { LocaleType, mergeLocales, Univer } from '@univerjs/core';
|
||||
import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link';
|
||||
import EnUS from '@univerjs/sheets-hyper-link/locale/en-US';
|
||||
|
||||
const univer = new Univer({
|
||||
locale: LocaleType.EN_US,
|
||||
locales: {
|
||||
[LocaleType.EN_US]: mergeLocales(EnUS),
|
||||
},
|
||||
});
|
||||
|
||||
univer.registerPlugin(UniverSheetsHyperLinkPlugin);
|
||||
```
|
||||
|
||||
## Integration Notes
|
||||
|
||||
Use this package with `@univerjs/sheets-hyper-link-ui` when users need hyperlink editing UI.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/sheets-hyper-link)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
98
packages/sheets-hyper-link/package.json
Normal file
98
packages/sheets-hyper-link/package.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-hyper-link",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Hyperlink model and commands for Univer Sheets.",
|
||||
"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",
|
||||
"sheets",
|
||||
"hyperlink",
|
||||
"link",
|
||||
"plugin"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./facade": "./src/facade/index.ts",
|
||||
"./locale/*": "./src/locale/*.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"
|
||||
},
|
||||
"./locale/*": {
|
||||
"import": "./lib/es/locale/*.js",
|
||||
"require": "./lib/cjs/locale/*.js",
|
||||
"types": "./lib/types/locale/*.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/docs": "workspace:*",
|
||||
"@univerjs/engine-formula": "workspace:*",
|
||||
"@univerjs/sheets": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"rxjs": "^7.8.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
/**
|
||||
* 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 { DocumentDataModel, ICommand, ICustomRange, IDocumentData, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import {
|
||||
CellValueType,
|
||||
createIdentifier,
|
||||
CustomRangeType,
|
||||
ICommandService,
|
||||
IUniverInstanceService,
|
||||
LocaleType,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { DocSelectionManagerService, DocStateEmitService, RichTextEditingMutation } from '@univerjs/docs';
|
||||
import { SetRangeValuesMutation, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { HyperLinkModel } from '../../../models/hyper-link.model';
|
||||
import { AddHyperLinkMutation } from '../../mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../../mutations/remove-hyper-link.mutation';
|
||||
import { UpdateHyperLinkMutation } from '../../mutations/update-hyper-link.mutation';
|
||||
import { AddHyperLinkCommand, AddRichHyperLinkCommand } from '../add-hyper-link.command';
|
||||
import { CancelHyperLinkCommand, CancelRichHyperLinkCommand } from '../remove-hyper-link.command';
|
||||
import { UpdateHyperLinkCommand, UpdateRichHyperLinkCommand } from '../update-hyper-link.command';
|
||||
|
||||
const unitId = 'hyper-link-command-workbook';
|
||||
const subUnitId = 'sheet-1';
|
||||
const documentId = 'rich-hyper-link-doc';
|
||||
const IRenderManagerServiceIdentifier = createIdentifier<TestRenderManagerService>('engine-render.render-manager.service');
|
||||
|
||||
class TestRenderManagerService {
|
||||
getRenderUnitById() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createCellDocument(text: string, rangeId: string, url: string) {
|
||||
return {
|
||||
id: `${rangeId}-doc`,
|
||||
documentStyle: {
|
||||
pageSize: { width: 100, height: 40 },
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
},
|
||||
body: {
|
||||
dataStream: `${text}\r\n`,
|
||||
customRanges: [{
|
||||
rangeId,
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: text.length - 1,
|
||||
properties: {
|
||||
url,
|
||||
},
|
||||
}],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createWorkbookData(): IWorkbookData {
|
||||
return {
|
||||
id: unitId,
|
||||
appVersion: '3.0.0-alpha',
|
||||
name: 'hyperlink command workbook',
|
||||
locale: LocaleType.EN_US,
|
||||
sheetOrder: [subUnitId],
|
||||
styles: {},
|
||||
sheets: {
|
||||
[subUnitId]: {
|
||||
id: subUnitId,
|
||||
name: 'Sheet 1',
|
||||
cellData: {
|
||||
1: {
|
||||
1: {
|
||||
p: createCellDocument('Old', 'cancel-link', 'https://old.invalid'),
|
||||
t: CellValueType.STRING,
|
||||
},
|
||||
},
|
||||
2: {
|
||||
2: {
|
||||
p: createCellDocument('Before', 'update-link', 'https://before.invalid'),
|
||||
t: CellValueType.STRING,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createDocData(): IDocumentData {
|
||||
return {
|
||||
id: documentId,
|
||||
body: {
|
||||
dataStream: 'Existing\r\n',
|
||||
customRanges: [{
|
||||
rangeId: 'rich-existing',
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: 7,
|
||||
properties: {
|
||||
url: 'https://before.invalid',
|
||||
},
|
||||
}],
|
||||
},
|
||||
documentStyle: {
|
||||
pageSize: {
|
||||
width: 594.3,
|
||||
height: 840.51,
|
||||
},
|
||||
marginTop: 72,
|
||||
marginBottom: 72,
|
||||
marginRight: 90,
|
||||
marginLeft: 90,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function findHyperLinkRange(workbook: Workbook, row: number, column: number, rangeId: string): ICustomRange | undefined {
|
||||
const ranges = workbook.getSheetBySheetId(subUnitId)?.getCellRaw(row, column)?.p?.body?.customRanges ?? [];
|
||||
for (const range of ranges) {
|
||||
if (range.rangeId === rangeId) {
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
describe('sheet hyperlink commands', () => {
|
||||
let univer: Univer;
|
||||
let commandService: ICommandService;
|
||||
let workbook: Workbook;
|
||||
let doc: DocumentDataModel;
|
||||
let hyperLinkModel: HyperLinkModel;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([SheetInterceptorService]);
|
||||
injector.add([HyperLinkModel]);
|
||||
injector.add([DocSelectionManagerService]);
|
||||
injector.add([DocStateEmitService]);
|
||||
injector.add([IRenderManagerServiceIdentifier, { useClass: TestRenderManagerService }]);
|
||||
|
||||
workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
doc = univer.createUnit<IDocumentData, DocumentDataModel>(UniverInstanceType.UNIVER_DOC, createDocData());
|
||||
injector.get(IUniverInstanceService).focusUnit(unitId);
|
||||
|
||||
commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(SetRangeValuesMutation);
|
||||
commandService.registerCommand(AddHyperLinkMutation);
|
||||
commandService.registerCommand(UpdateHyperLinkMutation);
|
||||
commandService.registerCommand(RemoveHyperLinkMutation);
|
||||
commandService.registerCommand(AddHyperLinkCommand);
|
||||
commandService.registerCommand(UpdateHyperLinkCommand);
|
||||
commandService.registerCommand(CancelHyperLinkCommand);
|
||||
commandService.registerCommand(AddRichHyperLinkCommand);
|
||||
commandService.registerCommand(UpdateRichHyperLinkCommand);
|
||||
commandService.registerCommand(CancelRichHyperLinkCommand);
|
||||
commandService.registerCommand(RichTextEditingMutation as unknown as ICommand);
|
||||
hyperLinkModel = injector.get(HyperLinkModel);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('adds a hyperlink display value to the selected sheet cell', async () => {
|
||||
const result = await commandService.executeCommand(AddHyperLinkCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: {
|
||||
id: 'added-link',
|
||||
row: 0,
|
||||
column: 0,
|
||||
payload: 'https://added.invalid',
|
||||
display: 'Added',
|
||||
},
|
||||
});
|
||||
|
||||
const cell = workbook.getSheetBySheetId(subUnitId)?.getCellRaw(0, 0);
|
||||
const range = findHyperLinkRange(workbook, 0, 0, 'added-link');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(cell?.p?.body?.dataStream).toBe('Added\r\n');
|
||||
expect(range).toMatchObject({
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: 4,
|
||||
properties: {
|
||||
url: 'https://added.invalid',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('replaces the previous hyperlink model when adding a new link to the same cell', async () => {
|
||||
hyperLinkModel.addHyperLink(unitId, subUnitId, {
|
||||
id: 'cancel-link',
|
||||
row: 1,
|
||||
column: 1,
|
||||
payload: 'https://old.invalid',
|
||||
display: 'Old',
|
||||
});
|
||||
|
||||
const result = await commandService.executeCommand(AddHyperLinkCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: {
|
||||
id: 'replacement-link',
|
||||
row: 1,
|
||||
column: 1,
|
||||
payload: 'https://replacement.invalid',
|
||||
display: 'Replacement',
|
||||
},
|
||||
});
|
||||
|
||||
const cell = workbook.getSheetBySheetId(subUnitId)?.getCellRaw(1, 1);
|
||||
const range = findHyperLinkRange(workbook, 1, 1, 'replacement-link');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(cell?.p?.body?.dataStream).toBe('Replacement\r\n');
|
||||
expect(range).toMatchObject({
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: 10,
|
||||
properties: {
|
||||
url: 'https://replacement.invalid',
|
||||
},
|
||||
});
|
||||
expect(hyperLinkModel.getHyperLink(unitId, subUnitId, 'cancel-link')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('removes a hyperlink from the cell document and hyperlink model', async () => {
|
||||
hyperLinkModel.addHyperLink(unitId, subUnitId, {
|
||||
id: 'cancel-link',
|
||||
row: 1,
|
||||
column: 1,
|
||||
payload: 'https://old.invalid',
|
||||
display: 'Old',
|
||||
});
|
||||
|
||||
const result = await commandService.executeCommand(CancelHyperLinkCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: 'cancel-link',
|
||||
row: 1,
|
||||
column: 1,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(findHyperLinkRange(workbook, 1, 1, 'cancel-link')).toBeUndefined();
|
||||
expect(hyperLinkModel.getHyperLink(unitId, subUnitId, 'cancel-link')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('updates a hyperlink label and target in the cell document', async () => {
|
||||
hyperLinkModel.addHyperLink(unitId, subUnitId, {
|
||||
id: 'update-link',
|
||||
row: 2,
|
||||
column: 2,
|
||||
payload: 'https://before.invalid',
|
||||
display: 'Before',
|
||||
});
|
||||
|
||||
const result = await commandService.executeCommand(UpdateHyperLinkCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: 'update-link',
|
||||
row: 2,
|
||||
column: 2,
|
||||
payload: {
|
||||
payload: 'https://after.invalid',
|
||||
display: 'After',
|
||||
},
|
||||
});
|
||||
|
||||
const cell = workbook.getSheetBySheetId(subUnitId)?.getCellRaw(2, 2);
|
||||
const updatedRange = cell?.p?.body?.customRanges?.[0];
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(cell?.p?.body?.dataStream).toBe('After\r\n');
|
||||
expect(updatedRange).toMatchObject({
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: 4,
|
||||
properties: {
|
||||
url: 'https://after.invalid',
|
||||
},
|
||||
});
|
||||
expect(hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, 2, 2)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('adds a rich hyperlink to the selected document text', async () => {
|
||||
const selectionManager = univer.__getInjector().get(DocSelectionManagerService);
|
||||
selectionManager.__TEST_ONLY_setCurrentSelection({
|
||||
unitId: documentId,
|
||||
subUnitId: documentId,
|
||||
});
|
||||
selectionManager.__TEST_ONLY_add([{
|
||||
startOffset: 0,
|
||||
endOffset: 8,
|
||||
collapsed: false,
|
||||
isActive: true,
|
||||
segmentId: '',
|
||||
style: null as never,
|
||||
}]);
|
||||
|
||||
const result = await commandService.executeCommand(AddRichHyperLinkCommand.id, {
|
||||
documentId,
|
||||
link: {
|
||||
id: 'ignored-source-id',
|
||||
row: 0,
|
||||
column: 0,
|
||||
payload: 'https://rich-added.invalid',
|
||||
},
|
||||
});
|
||||
|
||||
let addedRange;
|
||||
for (const range of doc.getBody()?.customRanges ?? []) {
|
||||
if (range.properties?.url === 'https://rich-added.invalid') {
|
||||
addedRange = range;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(addedRange).toMatchObject({
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: 7,
|
||||
properties: {
|
||||
url: 'https://rich-added.invalid',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('updates a rich hyperlink label and URL in the document body', async () => {
|
||||
const result = await commandService.executeCommand(UpdateRichHyperLinkCommand.id, {
|
||||
documentId,
|
||||
id: 'rich-existing',
|
||||
payload: {
|
||||
payload: 'https://rich-after.invalid',
|
||||
display: 'Updated',
|
||||
},
|
||||
});
|
||||
|
||||
const range = doc.getBody()?.customRanges?.[0];
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(doc.getBody()?.dataStream).toBe('Updated\r\n');
|
||||
expect(range).toMatchObject({
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: 6,
|
||||
properties: {
|
||||
url: 'https://rich-after.invalid',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('removes a rich hyperlink from the document body', async () => {
|
||||
const result = await commandService.executeCommand(CancelRichHyperLinkCommand.id, {
|
||||
documentId,
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: 'rich-existing',
|
||||
row: 0,
|
||||
column: 0,
|
||||
});
|
||||
|
||||
let existingRange;
|
||||
for (const range of doc.getBody()?.customRanges ?? []) {
|
||||
if (range.rangeId === 'rich-existing') {
|
||||
existingRange = range;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(existingRange).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 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 { ICellData, ICommand, IDocumentData, IMutationInfo } from '@univerjs/core';
|
||||
import type { ISetRangeValuesMutationParams } from '@univerjs/sheets';
|
||||
import type { ISheetHyperLink } from '../../types/interfaces/i-hyper-link';
|
||||
import { BuildTextUtils, CellValueType, CommandType, CustomRangeType, generateRandomId, ICommandService, IUndoRedoService, IUniverInstanceService, sequenceExecute, TextX, Tools } from '@univerjs/core';
|
||||
import { addCustomRangeBySelectionFactory } from '@univerjs/docs';
|
||||
import { getSheetCommandTarget, SetRangeValuesMutation, SetRangeValuesUndoMutationFactory, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { HyperLinkModel } from '../../models/hyper-link.model';
|
||||
import { AddHyperLinkMutation } from '../mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../mutations/remove-hyper-link.mutation';
|
||||
|
||||
export interface IAddHyperLinkCommandParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
link: ISheetHyperLink;
|
||||
}
|
||||
/**
|
||||
* Command for add hyperlink
|
||||
*/
|
||||
export const AddHyperLinkCommand: ICommand<IAddHyperLinkCommandParams> = {
|
||||
type: CommandType.COMMAND,
|
||||
id: 'sheets.command.add-hyper-link',
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
async handler(accessor, params) {
|
||||
if (!params) return false;
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const undoRedoService = accessor.get(IUndoRedoService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const hyperLinkModel = accessor.get(HyperLinkModel);
|
||||
const sheetInterceptorService = accessor.get(SheetInterceptorService);
|
||||
|
||||
const target = getSheetCommandTarget(univerInstanceService, params);
|
||||
if (!target) return false;
|
||||
|
||||
const { unitId, subUnitId, workbook, worksheet } = target;
|
||||
const { link } = params;
|
||||
const { payload, display, row, column, id } = link;
|
||||
|
||||
const cellData = worksheet.getCell(row, column);
|
||||
const doc = worksheet.getBlankCellDocumentModel(cellData, row, column);
|
||||
const snapshot = doc.documentModel!.getSnapshot();
|
||||
const body = Tools.deepClone(snapshot.body);
|
||||
if (!body) return false;
|
||||
|
||||
let textX: TextX | false;
|
||||
if (display) {
|
||||
textX = BuildTextUtils.selection.replace({
|
||||
selection: {
|
||||
startOffset: 0,
|
||||
endOffset: body.dataStream.length - 2,
|
||||
collapsed: body.dataStream.length - 2 === 0,
|
||||
},
|
||||
body: {
|
||||
dataStream: `${display}`,
|
||||
customRanges: [{
|
||||
startIndex: 0,
|
||||
endIndex: display.length - 1,
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
rangeId: id,
|
||||
properties: {
|
||||
url: payload,
|
||||
// refId: id,
|
||||
},
|
||||
}],
|
||||
},
|
||||
doc: doc.documentModel!,
|
||||
});
|
||||
} else {
|
||||
textX = BuildTextUtils.customRange.add({
|
||||
body,
|
||||
ranges: [{ startOffset: 0, endOffset: body.dataStream.length - 2, collapsed: false }],
|
||||
rangeId: id,
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
properties: {
|
||||
url: payload,
|
||||
refId: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!textX) return false;
|
||||
|
||||
const newBody = TextX.apply(body, textX.serialize());
|
||||
const rangeValue: IDocumentData = {
|
||||
...snapshot,
|
||||
body: newBody,
|
||||
};
|
||||
|
||||
const newCellData: ICellData = {
|
||||
p: rangeValue,
|
||||
t: CellValueType.STRING,
|
||||
};
|
||||
|
||||
const finalCellData = sheetInterceptorService.onWriteCell(workbook, worksheet, row, column, newCellData);
|
||||
const redoParams: ISetRangeValuesMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
cellValue: {
|
||||
[link.row]: {
|
||||
[link.column]: finalCellData,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const redo: IMutationInfo = {
|
||||
id: SetRangeValuesMutation.id,
|
||||
params: redoParams,
|
||||
};
|
||||
const undoParams = SetRangeValuesUndoMutationFactory(accessor, redoParams);
|
||||
|
||||
const undo: IMutationInfo = {
|
||||
id: SetRangeValuesMutation.id,
|
||||
params: undoParams,
|
||||
};
|
||||
const redos = [redo];
|
||||
const undos = [undo];
|
||||
const modelLink = hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, row, column);
|
||||
if (modelLink) {
|
||||
redos.push({
|
||||
id: RemoveHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: modelLink.id,
|
||||
},
|
||||
});
|
||||
undos.push({
|
||||
id: AddHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: modelLink,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const res = await sequenceExecute(redos, commandService);
|
||||
if (res) {
|
||||
const isValid = await sheetInterceptorService.onValidateCell(workbook, worksheet, row, column);
|
||||
if (isValid === false) {
|
||||
sequenceExecute(undos, commandService);
|
||||
return false;
|
||||
}
|
||||
undoRedoService.pushUndoRedo({
|
||||
redoMutations: redos,
|
||||
undoMutations: undos,
|
||||
unitID: unitId,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
export interface IAddRichHyperLinkCommandParams {
|
||||
/**
|
||||
* url of link
|
||||
*/
|
||||
documentId: string;
|
||||
link: ISheetHyperLink;
|
||||
}
|
||||
|
||||
export const AddRichHyperLinkCommand: ICommand<IAddRichHyperLinkCommandParams> = {
|
||||
id: 'sheets.command.add-rich-hyper-link',
|
||||
type: CommandType.COMMAND,
|
||||
handler: async (accessor, params) => {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
const { documentId, link } = params;
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const newId = generateRandomId();
|
||||
const { payload } = link;
|
||||
|
||||
const replaceSelection = addCustomRangeBySelectionFactory(accessor, {
|
||||
unitId: documentId,
|
||||
rangeId: newId,
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
properties: {
|
||||
url: payload,
|
||||
refId: newId,
|
||||
},
|
||||
});
|
||||
|
||||
if (replaceSelection) {
|
||||
return commandService.syncExecuteCommand(replaceSelection.id, replaceSelection.params);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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 { DocumentDataModel, ICommand, IDocumentBody, IMutationInfo, Nullable } from '@univerjs/core';
|
||||
import type { IAddHyperLinkMutationParams } from '../mutations/add-hyper-link.mutation';
|
||||
import { BuildTextUtils, CellValueType, CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, sequenceExecute, TextX, Tools, UniverInstanceType } from '@univerjs/core';
|
||||
import { deleteCustomRangeFactory } from '@univerjs/docs';
|
||||
import { getSheetCommandTarget, SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '@univerjs/sheets';
|
||||
import { HyperLinkModel } from '../../models/hyper-link.model';
|
||||
import { AddHyperLinkMutation } from '../mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../mutations/remove-hyper-link.mutation';
|
||||
|
||||
export interface ICancelHyperLinkCommandParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
/**
|
||||
* id of link
|
||||
*/
|
||||
id: string;
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export const CancelHyperLinkCommand: ICommand<ICancelHyperLinkCommandParams> = {
|
||||
type: CommandType.COMMAND,
|
||||
id: 'sheets.command.cancel-hyper-link',
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
handler(accessor, params) {
|
||||
if (!params) return false;
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const undoRedoService = accessor.get(IUndoRedoService);
|
||||
const instanceSrv = accessor.get(IUniverInstanceService);
|
||||
const hyperLinkModel = accessor.get(HyperLinkModel);
|
||||
|
||||
const target = getSheetCommandTarget(instanceSrv, params);
|
||||
if (!target) return false;
|
||||
|
||||
const { row, column, id } = params;
|
||||
const { unitId, subUnitId, worksheet } = target;
|
||||
|
||||
const cellData = worksheet.getCell(row, column);
|
||||
if (!cellData) return false;
|
||||
|
||||
const doc = worksheet.getCellDocumentModelWithFormula(cellData, row, column);
|
||||
if (!doc?.documentModel) return false;
|
||||
|
||||
const snapshot = Tools.deepClone(doc.documentModel!.getSnapshot());
|
||||
const range = snapshot.body?.customRanges?.find((range) => `${range.rangeId}` === id);
|
||||
if (!range) return false;
|
||||
|
||||
const textX = BuildTextUtils.customRange.delete({ documentDataModel: doc.documentModel, rangeId: range.rangeId });
|
||||
if (!textX) return false;
|
||||
|
||||
const newBody = TextX.apply(snapshot.body!, textX.serialize());
|
||||
const redos: IMutationInfo[] = [];
|
||||
const undos: IMutationInfo[] = [];
|
||||
const setRangeParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
cellValue: {
|
||||
[row]: {
|
||||
[column]: {
|
||||
p: {
|
||||
...snapshot,
|
||||
body: newBody,
|
||||
},
|
||||
t: CellValueType.STRING,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
redos.push({
|
||||
id: SetRangeValuesMutation.id,
|
||||
params: setRangeParams,
|
||||
});
|
||||
|
||||
const undoParams = SetRangeValuesUndoMutationFactory(accessor, setRangeParams);
|
||||
|
||||
undos.push({
|
||||
id: SetRangeValuesMutation.id,
|
||||
params: undoParams,
|
||||
});
|
||||
|
||||
const link = hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, row, column);
|
||||
if (link) {
|
||||
redos.push({
|
||||
id: RemoveHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: link.id,
|
||||
},
|
||||
});
|
||||
undos.push({
|
||||
id: AddHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: {
|
||||
...link,
|
||||
},
|
||||
} as IAddHyperLinkMutationParams,
|
||||
});
|
||||
}
|
||||
|
||||
const res = sequenceExecute(redos, commandService).result;
|
||||
|
||||
if (res) {
|
||||
undoRedoService.pushUndoRedo({
|
||||
redoMutations: redos,
|
||||
undoMutations: undos,
|
||||
unitID: unitId,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
export interface ICancelRichHyperLinkCommandParams extends ICancelHyperLinkCommandParams {
|
||||
/**
|
||||
* document id
|
||||
*/
|
||||
documentId: string;
|
||||
}
|
||||
|
||||
export const CancelRichHyperLinkCommand: ICommand<ICancelRichHyperLinkCommandParams> = {
|
||||
type: CommandType.COMMAND,
|
||||
id: 'sheets.command.cancel-rich-hyper-link',
|
||||
handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
const { id: linkId, documentId } = params;
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const doc = univerInstanceService.getUnit<DocumentDataModel>(documentId, UniverInstanceType.UNIVER_DOC);
|
||||
const link = doc?.getBody()?.customRanges?.find((i) => i.rangeId === linkId);
|
||||
let insert: Nullable<IDocumentBody> = null;
|
||||
if (link && link.endIndex === doc!.getBody()!.dataStream.length - 3) {
|
||||
insert = {
|
||||
dataStream: ' ',
|
||||
};
|
||||
}
|
||||
const doMutation = deleteCustomRangeFactory(accessor, { unitId: documentId, rangeId: linkId, insert });
|
||||
if (!doMutation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return commandService.syncExecuteCommand(doMutation.id, doMutation.params);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* 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 { DocumentDataModel, ICellData, ICommand, IMutationInfo } from '@univerjs/core';
|
||||
import type { ICellLinkContent } from '../../types/interfaces/i-hyper-link';
|
||||
import { CellValueType, CommandType, CustomRangeType, generateRandomId, getBodySlice, ICommandService, IUndoRedoService, IUniverInstanceService, sequenceExecute, TextX, Tools, UniverInstanceType } from '@univerjs/core';
|
||||
import { replaceSelectionFactory } from '@univerjs/docs';
|
||||
import { getSheetCommandTarget, SetRangeValuesMutation, SetRangeValuesUndoMutationFactory, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { HyperLinkModel } from '../../models/hyper-link.model';
|
||||
import { AddHyperLinkMutation } from '../mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../mutations/remove-hyper-link.mutation';
|
||||
|
||||
export interface IUpdateHyperLinkCommandParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
id: string;
|
||||
payload: ICellLinkContent;
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export const UpdateHyperLinkCommand: ICommand<IUpdateHyperLinkCommandParams> = {
|
||||
type: CommandType.COMMAND,
|
||||
id: 'sheets.command.update-hyper-link',
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
async handler(accessor, params) {
|
||||
if (!params) return false;
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const undoRedoService = accessor.get(IUndoRedoService);
|
||||
const instanceSrv = accessor.get(IUniverInstanceService);
|
||||
const hyperLinkModel = accessor.get(HyperLinkModel);
|
||||
const interceptorService = accessor.get(SheetInterceptorService);
|
||||
|
||||
const target = getSheetCommandTarget(instanceSrv, {
|
||||
unitId: params.unitId,
|
||||
subUnitId: params.subUnitId,
|
||||
});
|
||||
|
||||
if (!target) return false;
|
||||
|
||||
const { payload: link, row, column, id } = params;
|
||||
const { workbook, worksheet, unitId, subUnitId } = target;
|
||||
|
||||
const { payload, display = '' } = link;
|
||||
const cellData = worksheet.getCell(row, column);
|
||||
if (!cellData) return false;
|
||||
|
||||
const doc = worksheet.getCellDocumentModelWithFormula(cellData, row, column);
|
||||
if (!doc?.documentModel) return false;
|
||||
|
||||
const snapshot = doc.documentModel.getSnapshot();
|
||||
const range = snapshot.body?.customRanges?.find((range) => `${range.rangeId}` === id);
|
||||
if (!range) return false;
|
||||
|
||||
const newId = generateRandomId();
|
||||
const oldBody = getBodySlice(doc.documentModel.getBody()!, range.startIndex, range.endIndex + 1);
|
||||
const textRun = oldBody.textRuns?.[0];
|
||||
if (textRun) {
|
||||
textRun.ed = display.length + 1;
|
||||
}
|
||||
|
||||
const replaceSelection = replaceSelectionFactory(accessor, {
|
||||
unitId,
|
||||
body: {
|
||||
dataStream: `${display}`,
|
||||
customRanges: [{
|
||||
rangeId: newId,
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: display.length - 1,
|
||||
properties: {
|
||||
url: payload,
|
||||
},
|
||||
}],
|
||||
textRuns: textRun ? [textRun] : undefined,
|
||||
},
|
||||
selection: {
|
||||
startOffset: range.startIndex,
|
||||
endOffset: range.endIndex + 1,
|
||||
collapsed: false,
|
||||
},
|
||||
doc: doc.documentModel,
|
||||
});
|
||||
if (!replaceSelection) {
|
||||
return false;
|
||||
}
|
||||
const newBody = TextX.apply(Tools.deepClone(snapshot.body!), replaceSelection.textX.serialize());
|
||||
const newCellData: ICellData = {
|
||||
p: {
|
||||
...snapshot,
|
||||
body: newBody,
|
||||
},
|
||||
t: CellValueType.STRING,
|
||||
};
|
||||
|
||||
const finalCellData = interceptorService.onWriteCell(workbook, worksheet, row, column, newCellData);
|
||||
|
||||
const redo = {
|
||||
id: SetRangeValuesMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
cellValue: {
|
||||
[row]: {
|
||||
[column]: finalCellData,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const undoParams = SetRangeValuesUndoMutationFactory(accessor, redo.params);
|
||||
|
||||
const undo = {
|
||||
id: SetRangeValuesMutation.id,
|
||||
params: undoParams,
|
||||
};
|
||||
const redos: IMutationInfo[] = [redo];
|
||||
const undos: IMutationInfo[] = [undo];
|
||||
const modelLink = hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, row, column);
|
||||
if (modelLink) {
|
||||
redos.push({
|
||||
id: RemoveHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: modelLink.id,
|
||||
},
|
||||
});
|
||||
undos.push({
|
||||
id: AddHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: modelLink,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const res = sequenceExecute(redos, commandService);
|
||||
if (res.result) {
|
||||
const isValid = await interceptorService.onValidateCell(workbook, worksheet, row, column);
|
||||
if (isValid === false) {
|
||||
sequenceExecute(undos, commandService);
|
||||
return false;
|
||||
}
|
||||
undoRedoService.pushUndoRedo({
|
||||
redoMutations: redos,
|
||||
undoMutations: undos,
|
||||
unitID: unitId,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
export interface IUpdateRichHyperLinkCommandParams {
|
||||
documentId: string;
|
||||
id: string;
|
||||
payload: ICellLinkContent;
|
||||
}
|
||||
|
||||
export const UpdateRichHyperLinkCommand: ICommand<IUpdateRichHyperLinkCommandParams> = {
|
||||
type: CommandType.COMMAND,
|
||||
id: 'sheets.command.update-rich-hyper-link',
|
||||
handler: (accessor, params) => {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
const { documentId: unitId, payload, id: rangeId } = params;
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const doc = univerInstanceService.getUnit<DocumentDataModel>(unitId, UniverInstanceType.UNIVER_DOC);
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
const range = doc.getBody()?.customRanges?.find((range) => range.rangeId === rangeId);
|
||||
if (!range) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const display = params.payload.display ?? '';
|
||||
const newId = generateRandomId();
|
||||
const oldBody = getBodySlice(doc.getBody()!, range.startIndex, range.endIndex + 1);
|
||||
const textRun = oldBody.textRuns?.[0];
|
||||
if (textRun) {
|
||||
textRun.ed = display.length + 1;
|
||||
}
|
||||
|
||||
const replaceSelection = replaceSelectionFactory(accessor, {
|
||||
unitId,
|
||||
body: {
|
||||
dataStream: `${display}`,
|
||||
customRanges: [{
|
||||
rangeId: newId,
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: display.length - 1,
|
||||
properties: {
|
||||
url: payload.payload,
|
||||
},
|
||||
}],
|
||||
textRuns: textRun ? [textRun] : undefined,
|
||||
},
|
||||
selection: {
|
||||
startOffset: range.startIndex,
|
||||
endOffset: range.endIndex + 1,
|
||||
collapsed: false,
|
||||
},
|
||||
doc,
|
||||
});
|
||||
|
||||
if (!replaceSelection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return commandService.syncExecuteCommand(replaceSelection.id, replaceSelection.params);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* 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 { IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import type { ISheetHyperLink } from '../../../types/interfaces/i-hyper-link';
|
||||
import { CustomRangeType, ICommandService, LocaleType, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { HyperLinkModel } from '../../../models/hyper-link.model';
|
||||
import { AddHyperLinkMutation } from '../add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../remove-hyper-link.mutation';
|
||||
import { UpdateHyperLinkMutation, UpdateHyperLinkRefMutation, UpdateRichHyperLinkMutation } from '../update-hyper-link.mutation';
|
||||
|
||||
const unitId = 'hyper-link-workbook';
|
||||
const subUnitId = 'sheet-1';
|
||||
|
||||
const workbookData: IWorkbookData = {
|
||||
id: unitId,
|
||||
appVersion: '3.0.0-alpha',
|
||||
name: 'hyperlink workbook',
|
||||
locale: LocaleType.EN_US,
|
||||
sheetOrder: [subUnitId],
|
||||
styles: {},
|
||||
sheets: {
|
||||
[subUnitId]: {
|
||||
id: subUnitId,
|
||||
name: 'Sheet 1',
|
||||
cellData: {
|
||||
4: {
|
||||
3: {
|
||||
p: {
|
||||
id: 'cell-doc',
|
||||
documentStyle: {
|
||||
pageSize: {
|
||||
width: 100,
|
||||
height: 40,
|
||||
},
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
},
|
||||
body: {
|
||||
dataStream: 'Docs\r\n',
|
||||
customRanges: [{
|
||||
rangeId: 'rich-link',
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
startIndex: 0,
|
||||
endIndex: 3,
|
||||
properties: {
|
||||
url: 'https://old.invalid',
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function createLink(overrides: Partial<ISheetHyperLink> = {}): ISheetHyperLink {
|
||||
return {
|
||||
id: 'link-1',
|
||||
row: 0,
|
||||
column: 0,
|
||||
payload: 'https://univer.ai',
|
||||
display: 'Univer',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('sheet hyperlink mutations', () => {
|
||||
let univer: Univer;
|
||||
let commandService: ICommandService;
|
||||
let hyperLinkModel: HyperLinkModel;
|
||||
let workbook: Workbook;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([HyperLinkModel]);
|
||||
|
||||
commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(AddHyperLinkMutation);
|
||||
commandService.registerCommand(UpdateHyperLinkMutation);
|
||||
commandService.registerCommand(UpdateHyperLinkRefMutation);
|
||||
commandService.registerCommand(UpdateRichHyperLinkMutation);
|
||||
commandService.registerCommand(RemoveHyperLinkMutation);
|
||||
hyperLinkModel = injector.get(HyperLinkModel);
|
||||
workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, workbookData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('adds and updates the hyperlink attached to a cell', async () => {
|
||||
const addResult = await commandService.executeCommand(AddHyperLinkMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: createLink(),
|
||||
});
|
||||
const updateResult = await commandService.executeCommand(UpdateHyperLinkMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: 'link-1',
|
||||
payload: {
|
||||
payload: 'https://docs.univer.ai',
|
||||
display: 'Docs',
|
||||
},
|
||||
});
|
||||
|
||||
expect(addResult).toBe(true);
|
||||
expect(updateResult).toBe(true);
|
||||
expect(hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, 0, 0)).toMatchObject({
|
||||
id: 'link-1',
|
||||
payload: 'https://docs.univer.ai',
|
||||
display: 'Docs',
|
||||
});
|
||||
});
|
||||
|
||||
it('moves a hyperlink to a new cell and clears its original location', async () => {
|
||||
await commandService.executeCommand(AddHyperLinkMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: createLink(),
|
||||
});
|
||||
|
||||
const moveResult = await commandService.executeCommand(UpdateHyperLinkRefMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: 'link-1',
|
||||
row: 3,
|
||||
column: 2,
|
||||
});
|
||||
|
||||
expect(moveResult).toBe(true);
|
||||
expect(hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, 0, 0)).toBeUndefined();
|
||||
expect(hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, 3, 2)).toMatchObject({
|
||||
id: 'link-1',
|
||||
row: 3,
|
||||
column: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('removes a hyperlink from both id and cell lookup', async () => {
|
||||
await commandService.executeCommand(AddHyperLinkMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link: createLink(),
|
||||
});
|
||||
|
||||
const removeResult = await commandService.executeCommand(RemoveHyperLinkMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: 'link-1',
|
||||
});
|
||||
|
||||
expect(removeResult).toBe(true);
|
||||
expect(hyperLinkModel.getHyperLink(unitId, subUnitId, 'link-1')).toBeUndefined();
|
||||
expect(hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, 0, 0)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('updates the URL stored in a rich hyperlink cell range', async () => {
|
||||
const result = await commandService.executeCommand(UpdateRichHyperLinkMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 4,
|
||||
col: 3,
|
||||
id: 'rich-link',
|
||||
url: 'https://new.invalid',
|
||||
});
|
||||
|
||||
let updatedUrl = '';
|
||||
const ranges = workbook.getSheetBySheetId(subUnitId)?.getCellRaw(4, 3)?.p?.body?.customRanges ?? [];
|
||||
for (const range of ranges) {
|
||||
if (range.rangeId === 'rich-link') {
|
||||
updatedUrl = range.properties?.url ?? '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(updatedUrl).toBe('https://new.invalid');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 { ICommand } from '@univerjs/core';
|
||||
import type { ISheetHyperLink } from '../../types/interfaces/i-hyper-link';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { HyperLinkModel } from '../../models/hyper-link.model';
|
||||
|
||||
export interface IAddHyperLinkMutationParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
link: ISheetHyperLink;
|
||||
}
|
||||
|
||||
export const AddHyperLinkMutation: ICommand<IAddHyperLinkMutationParams> = {
|
||||
type: CommandType.MUTATION,
|
||||
id: 'sheets.mutation.add-hyper-link',
|
||||
handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
const model = accessor.get(HyperLinkModel);
|
||||
const { unitId, subUnitId, link } = params;
|
||||
return model.addHyperLink(unitId, subUnitId, link);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 { ICommand } from '@univerjs/core';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { HyperLinkModel } from '../../models/hyper-link.model';
|
||||
|
||||
export interface IRemoveHyperLinkMutationParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const RemoveHyperLinkMutation: ICommand<IRemoveHyperLinkMutationParams> = {
|
||||
type: CommandType.MUTATION,
|
||||
id: 'sheets.mutation.remove-hyper-link',
|
||||
handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const model = accessor.get(HyperLinkModel);
|
||||
const { unitId, subUnitId, id } = params;
|
||||
return model.removeHyperLink(unitId, subUnitId, id);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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 { ICommand } from '@univerjs/core';
|
||||
import type { ICellLinkContent } from '../../types/interfaces/i-hyper-link';
|
||||
import { CommandType, CustomRangeType, IUniverInstanceService } from '@univerjs/core';
|
||||
import { getSheetCommandTarget } from '@univerjs/sheets';
|
||||
import { HyperLinkModel } from '../../models/hyper-link.model';
|
||||
|
||||
export interface IUpdateHyperLinkMutationParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
id: string;
|
||||
payload: ICellLinkContent;
|
||||
}
|
||||
|
||||
export const UpdateHyperLinkMutation: ICommand<IUpdateHyperLinkMutationParams> = {
|
||||
type: CommandType.MUTATION,
|
||||
id: 'sheets.mutation.update-hyper-link',
|
||||
handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const model = accessor.get(HyperLinkModel);
|
||||
const { unitId, subUnitId, payload, id } = params;
|
||||
return model.updateHyperLink(unitId, subUnitId, id, payload, false);
|
||||
},
|
||||
};
|
||||
|
||||
export interface IUpdateHyperLinkRefMutationParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
id: string;
|
||||
row: number;
|
||||
column: number;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
export const UpdateHyperLinkRefMutation: ICommand<IUpdateHyperLinkRefMutationParams> = {
|
||||
type: CommandType.MUTATION,
|
||||
id: 'sheets.mutation.update-hyper-link-ref',
|
||||
handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const model = accessor.get(HyperLinkModel);
|
||||
const { unitId, subUnitId, id, row, column, silent } = params;
|
||||
return model.updateHyperLinkRef(unitId, subUnitId, id, { row, column }, silent);
|
||||
},
|
||||
};
|
||||
|
||||
export interface IUpdateRichHyperLinkMutationParams {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
row: number;
|
||||
col: number;
|
||||
id: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const UpdateRichHyperLinkMutation: ICommand<IUpdateRichHyperLinkMutationParams> = {
|
||||
type: CommandType.MUTATION,
|
||||
id: 'sheets.mutation.update-rich-hyper-link',
|
||||
handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { unitId, subUnitId, row, col, id, url } = params;
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const sheetTarget = getSheetCommandTarget(univerInstanceService, { unitId, subUnitId });
|
||||
if (!sheetTarget) {
|
||||
return false;
|
||||
}
|
||||
const { worksheet } = sheetTarget;
|
||||
const cell = worksheet.getCellRaw(row, col);
|
||||
|
||||
const link = cell?.p?.body?.customRanges?.find((range) => range.rangeType === CustomRangeType.HYPERLINK && range.rangeId === id);
|
||||
if (!link) {
|
||||
return true;
|
||||
}
|
||||
|
||||
link.properties!.url = url;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
24
packages/sheets-hyper-link/src/config/config.ts
Normal file
24
packages/sheets-hyper-link/src/config/config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 const SHEETS_HYPER_LINK_PLUGIN_CONFIG_KEY = 'sheets-hyper-link.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_HYPER_LINK_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsHyperLinkConfig {
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsHyperLinkConfig = {};
|
||||
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* 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 { IDisposable, IRange, Nullable } from '@univerjs/core';
|
||||
import type { EffectRefRangeParams } from '@univerjs/sheets';
|
||||
import type { IAddHyperLinkMutationParams } from '../commands/mutations/add-hyper-link.mutation';
|
||||
import type { IRemoveHyperLinkMutationParams } from '../commands/mutations/remove-hyper-link.mutation';
|
||||
import type { IUpdateHyperLinkMutationParams, IUpdateHyperLinkRefMutationParams } from '../commands/mutations/update-hyper-link.mutation';
|
||||
import type { ISheetHyperLink } from '../types/interfaces/i-hyper-link';
|
||||
import { Disposable, ICommandService, Inject, isValidRange, sequenceExecuteAsync, toDisposable } from '@univerjs/core';
|
||||
import { deserializeRangeWithSheet, serializeRange } from '@univerjs/engine-formula';
|
||||
import { handleCommonRangeChangeWithEffectRefCommandsSkipNoInterests, handleDefaultRangeChangeWithEffectRefCommandsSkipNoInterests, RefRangeService, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { AddHyperLinkMutation } from '../commands/mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../commands/mutations/remove-hyper-link.mutation';
|
||||
import { UpdateHyperLinkMutation, UpdateHyperLinkRefMutation } from '../commands/mutations/update-hyper-link.mutation';
|
||||
import { HyperLinkModel } from '../models/hyper-link.model';
|
||||
import { ERROR_RANGE } from '../types/const';
|
||||
|
||||
export class SheetsHyperLinkRefRangeController extends Disposable {
|
||||
private _disposableMap = new Map<string, IDisposable>();
|
||||
private _watchDisposableMap = new Map<string, IDisposable>();
|
||||
private _rangeDisableMap = new Map<string, IDisposable>();
|
||||
private _rangeWatcherMap = new Map<string, IDisposable>();
|
||||
|
||||
constructor(
|
||||
@Inject(RefRangeService) private readonly _refRangeService: RefRangeService,
|
||||
@Inject(HyperLinkModel) private readonly _hyperLinkModel: HyperLinkModel,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
this._initData();
|
||||
this._initRefRange();
|
||||
}
|
||||
|
||||
private _handlePositionChange = (unitId: string, subUnitId: string, link: ISheetHyperLink, resultRange: Nullable<IRange>, silent: boolean) => {
|
||||
const oldRange: IRange = {
|
||||
startColumn: link.column,
|
||||
endColumn: link.column,
|
||||
startRow: link.row,
|
||||
endRow: link.row,
|
||||
};
|
||||
|
||||
if (!resultRange) {
|
||||
return {
|
||||
redos: [{
|
||||
id: RemoveHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: link.id,
|
||||
} as IRemoveHyperLinkMutationParams,
|
||||
}],
|
||||
undos: [{
|
||||
id: AddHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link,
|
||||
} as IAddHyperLinkMutationParams,
|
||||
}],
|
||||
};
|
||||
}
|
||||
return {
|
||||
redos: [{
|
||||
id: UpdateHyperLinkRefMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: link.id,
|
||||
row: resultRange.startRow,
|
||||
column: resultRange.startColumn,
|
||||
silent,
|
||||
} as IUpdateHyperLinkRefMutationParams,
|
||||
}],
|
||||
undos: [{
|
||||
id: UpdateHyperLinkRefMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: link.id,
|
||||
row: oldRange.startRow,
|
||||
column: oldRange.startColumn,
|
||||
silent,
|
||||
} as IUpdateHyperLinkRefMutationParams,
|
||||
}],
|
||||
};
|
||||
};
|
||||
|
||||
private _registerPosition(unitId: string, subUnitId: string, link: ISheetHyperLink) {
|
||||
const id = link.id;
|
||||
const oldRange: IRange = {
|
||||
startColumn: link.column,
|
||||
endColumn: link.column,
|
||||
startRow: link.row,
|
||||
endRow: link.row,
|
||||
};
|
||||
|
||||
const handleRefRangeChange = (commandInfo: EffectRefRangeParams) => {
|
||||
const resultRanges = handleCommonRangeChangeWithEffectRefCommandsSkipNoInterests(oldRange, commandInfo, { selectionManagerService: this._selectionManagerService });
|
||||
const resultRange = Array.isArray(resultRanges) ? resultRanges[0] : resultRanges;
|
||||
if (resultRange && resultRange.startColumn === oldRange.startColumn && resultRange.startRow === oldRange.startRow) {
|
||||
return {
|
||||
undos: [],
|
||||
redos: [],
|
||||
};
|
||||
}
|
||||
|
||||
const res = this._handlePositionChange(unitId, subUnitId, link, resultRange, false);
|
||||
return res;
|
||||
};
|
||||
this._disposableMap.set(id, this._refRangeService.registerRefRange(oldRange, handleRefRangeChange, unitId, subUnitId));
|
||||
}
|
||||
|
||||
private _watchPosition(unitId: string, subUnitId: string, link: ISheetHyperLink) {
|
||||
const id = link.id;
|
||||
const oldRange: IRange = {
|
||||
startColumn: link.column,
|
||||
endColumn: link.column,
|
||||
startRow: link.row,
|
||||
endRow: link.row,
|
||||
};
|
||||
this._watchDisposableMap.set(id, this._refRangeService.watchRange(unitId, subUnitId, oldRange, (before, after) => {
|
||||
const { redos } = this._handlePositionChange(unitId, subUnitId, link, after, true);
|
||||
sequenceExecuteAsync(redos, this._commandService, { onlyLocal: true });
|
||||
}, true));
|
||||
}
|
||||
|
||||
private _unregisterPosition(id: string) {
|
||||
const disposable = this._disposableMap.get(id);
|
||||
disposable?.dispose();
|
||||
this._disposableMap.delete(id);
|
||||
}
|
||||
|
||||
private _unwatchPosition(id: string) {
|
||||
const disposable = this._watchDisposableMap.get(id);
|
||||
disposable?.dispose();
|
||||
this._watchDisposableMap.delete(id);
|
||||
}
|
||||
|
||||
private _registerRange(unitId: string, id: string, payload: string, silent = false) {
|
||||
if (payload.startsWith('#')) {
|
||||
const search = new URLSearchParams(payload.slice(1));
|
||||
// range, gid, rangeid
|
||||
const searchObj = {
|
||||
gid: search.get('gid') ?? '',
|
||||
range: search.get('range') ?? '',
|
||||
rangeid: search.get('rangeid') ?? '',
|
||||
};
|
||||
|
||||
if (searchObj.range && searchObj.gid) {
|
||||
const subUnitId = searchObj.gid;
|
||||
const range = deserializeRangeWithSheet(searchObj.range).range;
|
||||
|
||||
if (isValidRange(range) && searchObj.range !== ERROR_RANGE) {
|
||||
const handleRangeChange = (commandInfo: EffectRefRangeParams) => {
|
||||
const resultRange = handleDefaultRangeChangeWithEffectRefCommandsSkipNoInterests(range, commandInfo, { selectionManagerService: this._selectionManagerService });
|
||||
if (resultRange && serializeRange(resultRange) === serializeRange(range)) {
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
redos: [{
|
||||
id: UpdateHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id,
|
||||
payload: {
|
||||
payload: `#gid=${subUnitId}&range=${resultRange ? serializeRange(resultRange) : 'err'}`,
|
||||
},
|
||||
} as IUpdateHyperLinkMutationParams,
|
||||
}],
|
||||
undos: [{
|
||||
id: UpdateHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id,
|
||||
payload: {
|
||||
payload,
|
||||
},
|
||||
} as IUpdateHyperLinkMutationParams,
|
||||
}],
|
||||
};
|
||||
};
|
||||
this._rangeDisableMap.set(id, this._refRangeService.registerRefRange(range, handleRangeChange, unitId, subUnitId));
|
||||
|
||||
if (!silent) {
|
||||
this._rangeWatcherMap.set(id, this._refRangeService.watchRange(unitId, subUnitId, range, (before, after) => {
|
||||
this._hyperLinkModel.updateHyperLink(unitId, subUnitId, id, {
|
||||
payload: `#gid=${subUnitId}&range=${after ? serializeRange(after) : 'err'}`,
|
||||
}, true);
|
||||
}, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _unregisterRange(id: string) {
|
||||
const disposable = this._rangeDisableMap.get(id);
|
||||
disposable?.dispose();
|
||||
this._rangeDisableMap.delete(id);
|
||||
}
|
||||
|
||||
private _unwatchRange(id: string) {
|
||||
const disposable = this._rangeWatcherMap.get(id);
|
||||
disposable?.dispose();
|
||||
this._rangeWatcherMap.delete(id);
|
||||
}
|
||||
|
||||
private _initData() {
|
||||
const data = this._hyperLinkModel.getAll();
|
||||
|
||||
data.forEach((unitData) => {
|
||||
unitData.forEach((subUnitData) => {
|
||||
const { unitId, subUnitId, links } = subUnitData;
|
||||
|
||||
links.forEach((link) => {
|
||||
this._registerPosition(unitId, subUnitId, link);
|
||||
this._watchPosition(unitId, subUnitId, link);
|
||||
this._registerRange(unitId, link.id, link.payload);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _initRefRange() {
|
||||
this.disposeWithMe(
|
||||
this._hyperLinkModel.linkUpdate$.subscribe((option) => {
|
||||
switch (option.type) {
|
||||
case 'add': {
|
||||
this._registerPosition(option.unitId, option.subUnitId, option.payload);
|
||||
this._watchPosition(option.unitId, option.subUnitId, option.payload);
|
||||
this._registerRange(option.unitId, option.payload.id, option.payload.payload);
|
||||
break;
|
||||
}
|
||||
case 'remove': {
|
||||
this._unregisterPosition(option.payload.id);
|
||||
this._unwatchPosition(option.payload.id);
|
||||
this._unregisterRange(option.payload.id);
|
||||
this._unwatchRange(option.payload.id);
|
||||
break;
|
||||
}
|
||||
case 'updateRef': {
|
||||
const { unitId, subUnitId, id, silent } = option;
|
||||
const link = this._hyperLinkModel.getHyperLink(unitId, subUnitId, id);
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
this._unregisterPosition(id);
|
||||
this._registerPosition(unitId, subUnitId, link);
|
||||
if (!silent) {
|
||||
this._unwatchPosition(id);
|
||||
this._watchPosition(unitId, subUnitId, link);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'unload': {
|
||||
const { unitLinks } = option;
|
||||
unitLinks.forEach((subUnitData) => {
|
||||
const { links } = subUnitData;
|
||||
links.forEach((link) => {
|
||||
this._unregisterPosition(link.id);
|
||||
this._unwatchPosition(link.id);
|
||||
this._unregisterRange(link.id);
|
||||
this._unwatchRange(link.id);
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'update': {
|
||||
if (!option.silent) {
|
||||
this._unwatchRange(option.id);
|
||||
}
|
||||
this._unregisterRange(option.id);
|
||||
this._registerRange(option.unitId, option.id, option.payload.payload, option.silent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.disposeWithMe(toDisposable(() => {
|
||||
this._disposableMap.forEach((item) => {
|
||||
item.dispose();
|
||||
});
|
||||
this._disposableMap.clear();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 { Workbook } from '@univerjs/core';
|
||||
import type { IRemoveSheetCommandParams } from '@univerjs/sheets';
|
||||
import { Disposable, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { RemoveSheetCommand, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { AddHyperLinkMutation } from '../commands/mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../commands/mutations/remove-hyper-link.mutation';
|
||||
import { HyperLinkModel } from '../models/hyper-link.model';
|
||||
|
||||
export class SheetsHyperLinkRemoveSheetController extends Disposable {
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService,
|
||||
@IUniverInstanceService private _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(HyperLinkModel) private _hyperLinkModel: HyperLinkModel
|
||||
) {
|
||||
super();
|
||||
this._initSheetChange();
|
||||
}
|
||||
|
||||
private _initSheetChange() {
|
||||
this.disposeWithMe(
|
||||
this._sheetInterceptorService.interceptCommand({
|
||||
getMutations: (commandInfo) => {
|
||||
if (commandInfo.id === RemoveSheetCommand.id) {
|
||||
const params = commandInfo.params as IRemoveSheetCommandParams;
|
||||
const workbook = params.unitId ? this._univerInstanceService.getUnit<Workbook>(params.unitId) : this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!workbook) {
|
||||
return { redos: [], undos: [] };
|
||||
}
|
||||
const unitId = workbook.getUnitId();
|
||||
const subUnitId = params.subUnitId || workbook.getActiveSheet()?.getSheetId();
|
||||
if (!subUnitId) {
|
||||
return { redos: [], undos: [] };
|
||||
}
|
||||
const links = this._hyperLinkModel.getSubUnit(unitId, subUnitId);
|
||||
|
||||
const redos = links.map((link) => ({
|
||||
id: RemoveHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: link.id,
|
||||
},
|
||||
}));
|
||||
|
||||
const undos = links.map((link) => ({
|
||||
id: AddHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link,
|
||||
},
|
||||
}));
|
||||
return { redos, undos };
|
||||
}
|
||||
return { redos: [], undos: [] };
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* 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 { IDisposable, IDocumentData, Workbook } from '@univerjs/core';
|
||||
import type { ISetRangeValuesMutationParams } from '@univerjs/sheets';
|
||||
import type { IUpdateRichHyperLinkMutationParams } from '../commands/mutations/update-hyper-link.mutation';
|
||||
import { CustomRangeType, Disposable, DisposableCollection, ICommandService, Inject, isValidRange, IUniverInstanceService, ObjectMatrix, Rectangle, UniverInstanceType } from '@univerjs/core';
|
||||
import { deserializeRangeWithSheet, serializeRange } from '@univerjs/engine-formula';
|
||||
import { getSheetCommandTarget, handleDefaultRangeChangeWithEffectRefCommands, RefRangeService, SetRangeValuesMutation } from '@univerjs/sheets';
|
||||
import { UpdateRichHyperLinkMutation } from '../commands/mutations/update-hyper-link.mutation';
|
||||
import { ERROR_RANGE } from '../types/const';
|
||||
|
||||
export class SheetsHyperLinkRichTextRefRangeController extends Disposable {
|
||||
private _refRangeMap: Map<string, Map<string, ObjectMatrix<IDisposable>>> = new Map();
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(RefRangeService) private readonly _refRangeService: RefRangeService
|
||||
) {
|
||||
super();
|
||||
this._initWorkbookLoad();
|
||||
this._initWorkbookUnload();
|
||||
this._initSetRangesListener();
|
||||
}
|
||||
|
||||
private _enusreMap(unitId: string, subUnitId: string) {
|
||||
let unitMap = this._refRangeMap.get(unitId);
|
||||
if (!unitMap) {
|
||||
unitMap = new Map();
|
||||
this._refRangeMap.set(unitId, unitMap);
|
||||
}
|
||||
let subUnitMap = unitMap.get(subUnitId);
|
||||
if (!subUnitMap) {
|
||||
subUnitMap = new ObjectMatrix();
|
||||
unitMap.set(subUnitId, subUnitMap);
|
||||
}
|
||||
return subUnitMap;
|
||||
}
|
||||
|
||||
private _isLegalRangeUrl(unitId: string, payload: string) {
|
||||
const workbook = this._univerInstanceService.getUnit<Workbook>(unitId, UniverInstanceType.UNIVER_SHEET);
|
||||
if (!workbook) {
|
||||
return null;
|
||||
}
|
||||
if (payload && payload.startsWith('#')) {
|
||||
const search = new URLSearchParams(payload.slice(1));
|
||||
|
||||
// range, gid, rangeid
|
||||
const searchObj = {
|
||||
gid: search.get('gid') ?? '',
|
||||
range: search.get('range') ?? '',
|
||||
rangeid: search.get('rangeid') ?? '',
|
||||
};
|
||||
|
||||
if (searchObj.range && searchObj.gid) {
|
||||
const subUnitId = searchObj.gid;
|
||||
const worksheet = workbook.getSheetBySheetId(subUnitId);
|
||||
if (!worksheet) {
|
||||
return null;
|
||||
}
|
||||
const range = deserializeRangeWithSheet(searchObj.range).range;
|
||||
if (isValidRange(range, worksheet) && searchObj.range !== ERROR_RANGE) {
|
||||
return {
|
||||
range,
|
||||
worksheet,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private _registerRange(unitId: string, subUnitId: string, row: number, col: number, p: IDocumentData) {
|
||||
const map = this._enusreMap(unitId, subUnitId);
|
||||
|
||||
if (p.body?.customRanges?.some((customRange) => customRange.rangeType === CustomRangeType.HYPERLINK && this._isLegalRangeUrl(unitId, customRange.properties?.url))) {
|
||||
const disposableCollection = new DisposableCollection();
|
||||
let hasWatch = false;
|
||||
p.body?.customRanges?.forEach((customRange) => {
|
||||
if (customRange.rangeType === CustomRangeType.HYPERLINK) {
|
||||
const payload = customRange.properties?.url;
|
||||
const rangeInfo = this._isLegalRangeUrl(unitId, payload);
|
||||
|
||||
if (rangeInfo) {
|
||||
const { range, worksheet } = rangeInfo;
|
||||
hasWatch = true;
|
||||
disposableCollection.add(
|
||||
this._refRangeService.registerRefRange(
|
||||
range,
|
||||
(commandInfo) => {
|
||||
const newRange = handleDefaultRangeChangeWithEffectRefCommands(range, commandInfo);
|
||||
if (newRange && Rectangle.equals(newRange, range)) {
|
||||
return {
|
||||
preRedos: [],
|
||||
preUndos: [],
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
preRedos: [{
|
||||
id: UpdateRichHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
row,
|
||||
col,
|
||||
id: customRange.rangeId,
|
||||
url: `#gid=${subUnitId}&range=${newRange ? serializeRange(newRange) : ERROR_RANGE}`,
|
||||
},
|
||||
}],
|
||||
undos: [{
|
||||
id: UpdateRichHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
row,
|
||||
col,
|
||||
id: customRange.rangeId,
|
||||
url: payload,
|
||||
},
|
||||
}],
|
||||
redos: [],
|
||||
};
|
||||
},
|
||||
worksheet.getUnitId(),
|
||||
worksheet.getSheetId()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hasWatch) {
|
||||
map.setValue(row, col, disposableCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _initWorkbookLoad() {
|
||||
const handleWorkbook = (workbook: Workbook) => {
|
||||
const unitId = workbook.getUnitId();
|
||||
workbook.getSheets().forEach((sheet) => {
|
||||
const subUnitId = sheet.getSheetId();
|
||||
const map = this._enusreMap(unitId, subUnitId);
|
||||
sheet.getCellMatrix().forValue((row, col, cell) => {
|
||||
const dispose = map.getValue(row, col);
|
||||
if (dispose) {
|
||||
dispose.dispose();
|
||||
}
|
||||
|
||||
if (cell && cell.p) {
|
||||
this._registerRange(unitId, subUnitId, row, col, cell.p);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this._univerInstanceService.getAllUnitsForType<Workbook>(UniverInstanceType.UNIVER_SHEET).forEach((workbook) => {
|
||||
handleWorkbook(workbook);
|
||||
});
|
||||
this.disposeWithMe(
|
||||
this._univerInstanceService.unitAdded$.subscribe((event) => {
|
||||
const { unit } = event;
|
||||
if (unit.type === UniverInstanceType.UNIVER_SHEET) {
|
||||
const workbook = unit as Workbook;
|
||||
handleWorkbook(workbook);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _initWorkbookUnload() {
|
||||
this.disposeWithMe(
|
||||
this._univerInstanceService.unitDisposed$.subscribe((unit) => {
|
||||
if (unit.type === UniverInstanceType.UNIVER_SHEET) {
|
||||
const workbook = unit as Workbook;
|
||||
const unitId = workbook.getUnitId();
|
||||
workbook.getSheets().forEach((sheet) => {
|
||||
const subUnitId = sheet.getSheetId();
|
||||
const map = this._enusreMap(unitId, subUnitId);
|
||||
map.forValue((row, col, dispose) => {
|
||||
if (dispose) {
|
||||
dispose.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
this._refRangeMap.delete(unitId);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _initSetRangesListener() {
|
||||
this.disposeWithMe(
|
||||
this._commandService.onCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id === SetRangeValuesMutation.id) {
|
||||
const params = commandInfo.params as ISetRangeValuesMutationParams;
|
||||
const { unitId, subUnitId, cellValue } = params;
|
||||
const map = this._enusreMap(unitId, subUnitId);
|
||||
if (cellValue) {
|
||||
new ObjectMatrix(cellValue).forValue((row, col, cell) => {
|
||||
const dispose = map.getValue(row, col);
|
||||
if (dispose) {
|
||||
dispose.dispose();
|
||||
}
|
||||
if (cell && cell.p) {
|
||||
this._registerRange(unitId, subUnitId, row, col, cell.p);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this._commandService.onCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id === UpdateRichHyperLinkMutation.id) {
|
||||
const params = commandInfo.params as IUpdateRichHyperLinkMutationParams;
|
||||
const { unitId, subUnitId, row, col } = params;
|
||||
const sheetTarget = getSheetCommandTarget(this._univerInstanceService, { unitId, subUnitId });
|
||||
const map = this._enusreMap(unitId, subUnitId);
|
||||
const dispose = map.getValue(row, col);
|
||||
if (dispose) {
|
||||
dispose.dispose();
|
||||
}
|
||||
if (sheetTarget) {
|
||||
const { worksheet } = sheetTarget;
|
||||
const cell = worksheet.getCellRaw(row, col);
|
||||
if (cell && cell.p) {
|
||||
this._registerRange(unitId, subUnitId, row, col, cell.p);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 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 { IMutationInfo, Workbook } from '@univerjs/core';
|
||||
import type { ISetRangeValuesMutationParams } from '@univerjs/sheets';
|
||||
import { BuildTextUtils, CustomRangeType, Disposable, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, generateRandomId, Inject, IUniverInstanceService, ObjectMatrix, Range, TextX, Tools, UniverInstanceType } from '@univerjs/core';
|
||||
import { AFTER_CELL_EDIT, ClearSelectionAllCommand, ClearSelectionContentCommand, ClearSelectionFormatCommand, getSheetCommandTarget, SetRangeValuesCommand, SheetInterceptorService, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { AddHyperLinkMutation } from '../commands/mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../commands/mutations/remove-hyper-link.mutation';
|
||||
import { HyperLinkModel } from '../models/hyper-link.model';
|
||||
|
||||
export class SheetHyperLinkSetRangeController extends Disposable {
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(HyperLinkModel) private readonly _hyperLinkModel: HyperLinkModel,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommandInterceptor();
|
||||
this._initAfterEditor();
|
||||
}
|
||||
|
||||
private _initCommandInterceptor() {
|
||||
this._initSetRangeValuesCommandInterceptor();
|
||||
this._initClearSelectionCommandInterceptor();
|
||||
}
|
||||
|
||||
private _initSetRangeValuesCommandInterceptor() {
|
||||
this.disposeWithMe(this._sheetInterceptorService.interceptCommand({
|
||||
getMutations: (command) => {
|
||||
if (command.id === SetRangeValuesCommand.id) {
|
||||
const params = command.params as ISetRangeValuesMutationParams;
|
||||
const { unitId, subUnitId } = params;
|
||||
const redos: IMutationInfo[] = [];
|
||||
const undos: IMutationInfo[] = [];
|
||||
if (params.cellValue) {
|
||||
new ObjectMatrix(params.cellValue).forValue((row, col) => {
|
||||
const link = this._hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, row, col);
|
||||
if (link) {
|
||||
// rich-text can store link in custom-range, don't save to link model
|
||||
redos.push({
|
||||
id: RemoveHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: link.id,
|
||||
},
|
||||
});
|
||||
|
||||
undos.push({
|
||||
id: AddHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
undos,
|
||||
redos,
|
||||
};
|
||||
}
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
private _initClearSelectionCommandInterceptor() {
|
||||
this.disposeWithMe(this._sheetInterceptorService.interceptCommand({
|
||||
getMutations: (command) => {
|
||||
if (
|
||||
command.id === ClearSelectionContentCommand.id ||
|
||||
command.id === ClearSelectionAllCommand.id ||
|
||||
command.id === ClearSelectionFormatCommand.id
|
||||
) {
|
||||
const redos: IMutationInfo[] = [];
|
||||
const undos: IMutationInfo[] = [];
|
||||
const selection = this._selectionManagerService.getCurrentLastSelection();
|
||||
const target = getSheetCommandTarget(this._univerInstanceService, command.params);
|
||||
if (selection && target) {
|
||||
const { unitId, subUnitId } = target;
|
||||
Range.foreach(selection.range, (row, col) => {
|
||||
const link = this._hyperLinkModel.getHyperLinkByLocation(unitId, subUnitId, row, col);
|
||||
if (link) {
|
||||
redos.push({
|
||||
id: RemoveHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
id: link.id,
|
||||
},
|
||||
});
|
||||
undos.push({
|
||||
id: AddHyperLinkMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
link,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
redos,
|
||||
undos,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
private _initAfterEditor() {
|
||||
this.disposeWithMe(this._sheetInterceptorService.writeCellInterceptor.intercept(AFTER_CELL_EDIT, {
|
||||
handler: (cell, context, next) => {
|
||||
if (!cell || cell.p) {
|
||||
return next(cell);
|
||||
}
|
||||
|
||||
if (typeof cell.v === 'string' && Tools.isLegalUrl(cell.v) && cell.v[cell.v.length - 1] !== ' ') {
|
||||
const { unitId, subUnitId, row, col } = context;
|
||||
|
||||
const link = Tools.normalizeUrl(cell.v);
|
||||
const workbook = this._univerInstanceService.getUnit<Workbook>(unitId, UniverInstanceType.UNIVER_SHEET);
|
||||
const worksheet = workbook?.getSheetBySheetId(subUnitId);
|
||||
if (!worksheet) {
|
||||
return next(cell);
|
||||
}
|
||||
// const renderer = this._renderManagerService.getRenderUnitById(unitId);
|
||||
// const skeleton = renderer?.with(SheetSkeletonManagerService).getSkeletonParam(subUnitId);
|
||||
// if (!skeleton) {
|
||||
// return next(cell);
|
||||
// }
|
||||
const doc = worksheet.getBlankCellDocumentModel(cell, row, col);
|
||||
if (!doc.documentModel) {
|
||||
return next(cell);
|
||||
}
|
||||
const textX = BuildTextUtils.selection.replace({
|
||||
selection: {
|
||||
startOffset: 0,
|
||||
endOffset: cell.v.length,
|
||||
collapsed: false,
|
||||
},
|
||||
body: {
|
||||
dataStream: `${cell.v}`,
|
||||
customRanges: [{
|
||||
startIndex: 0,
|
||||
endIndex: cell.v.length - 1,
|
||||
rangeId: generateRandomId(),
|
||||
rangeType: CustomRangeType.HYPERLINK,
|
||||
properties: {
|
||||
url: link,
|
||||
},
|
||||
}],
|
||||
},
|
||||
doc: doc.documentModel,
|
||||
});
|
||||
if (!textX) {
|
||||
return next(cell);
|
||||
}
|
||||
const body = doc.documentModel.getBody()!;
|
||||
TextX.apply(body, textX.serialize());
|
||||
return next({
|
||||
...cell,
|
||||
p: {
|
||||
id: DOCS_NORMAL_EDITOR_UNIT_ID_KEY,
|
||||
body,
|
||||
documentStyle: {
|
||||
pageSize: {
|
||||
width: Infinity,
|
||||
height: Infinity,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return next(cell);
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 { Disposable, ICommandService } from '@univerjs/core';
|
||||
import { AddHyperLinkCommand, AddRichHyperLinkCommand } from '../commands/commands/add-hyper-link.command';
|
||||
import { CancelHyperLinkCommand, CancelRichHyperLinkCommand } from '../commands/commands/remove-hyper-link.command';
|
||||
import { UpdateHyperLinkCommand, UpdateRichHyperLinkCommand } from '../commands/commands/update-hyper-link.command';
|
||||
import { AddHyperLinkMutation } from '../commands/mutations/add-hyper-link.mutation';
|
||||
import { RemoveHyperLinkMutation } from '../commands/mutations/remove-hyper-link.mutation';
|
||||
import { UpdateHyperLinkMutation, UpdateHyperLinkRefMutation, UpdateRichHyperLinkMutation } from '../commands/mutations/update-hyper-link.mutation';
|
||||
|
||||
export class SheetsHyperLinkController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._registerCommands();
|
||||
}
|
||||
|
||||
private _registerCommands() {
|
||||
[
|
||||
AddHyperLinkCommand,
|
||||
UpdateHyperLinkCommand,
|
||||
CancelHyperLinkCommand,
|
||||
UpdateRichHyperLinkCommand,
|
||||
CancelRichHyperLinkCommand,
|
||||
AddRichHyperLinkCommand,
|
||||
AddHyperLinkMutation,
|
||||
UpdateHyperLinkMutation,
|
||||
RemoveHyperLinkMutation,
|
||||
UpdateHyperLinkRefMutation,
|
||||
UpdateRichHyperLinkMutation,
|
||||
].forEach((command) => {
|
||||
this._commandService.registerCommand(command);
|
||||
});
|
||||
}
|
||||
}
|
||||
148
packages/sheets-hyper-link/src/facade/__tests__/f-range.spec.ts
Normal file
148
packages/sheets-hyper-link/src/facade/__tests__/f-range.spec.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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 { IWorkbookData, UnitModel } from '@univerjs/core';
|
||||
import { ILogService, IUniverInstanceService, LocaleType, LogLevel, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
import { DocSelectionManagerService } from '@univerjs/docs';
|
||||
import { IDefinedNamesService } from '@univerjs/engine-formula';
|
||||
import { SheetHyperLinkType, UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import '@univerjs/sheets/facade';
|
||||
import '../index';
|
||||
|
||||
function createWorkbookData(): IWorkbookData {
|
||||
return {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
locale: LocaleType.EN_US,
|
||||
name: 'Workbook',
|
||||
sheetOrder: ['sheet1'],
|
||||
styles: {},
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
name: 'Sheet1',
|
||||
cellData: {},
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('FRangeSheetsHyperlinkMixin', () => {
|
||||
let univer: Univer;
|
||||
let univerAPI: FUniver;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
univer.registerPlugin(UniverSheetsHyperLinkPlugin);
|
||||
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([DocSelectionManagerService]);
|
||||
|
||||
const workbook = univer.createUnit<IWorkbookData, UnitModel<IWorkbookData>>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
|
||||
injector.get(IUniverInstanceService).focusUnit(workbook.getUnitId());
|
||||
injector.get(ILogService).setLogLevel(LogLevel.SILENT);
|
||||
univerAPI = FUniver.newAPI(injector);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('should set, read, update and cancel hyperlinks through the real facade API', async () => {
|
||||
const worksheet = univerAPI.getActiveWorkbook()!.getActiveSheet();
|
||||
const a1 = worksheet.getRange('A1');
|
||||
const b2 = worksheet.getRange('B2');
|
||||
const events: string[] = [];
|
||||
const addDisposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetLinkAdd, (event) => {
|
||||
events.push(`add:${event.row}:${event.col}:${event.link.payload}`);
|
||||
});
|
||||
const updateDisposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetLinkUpdate, (event) => {
|
||||
events.push(`update:${event.row}:${event.column}:${event.payload.payload}`);
|
||||
});
|
||||
const cancelDisposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetLinkCancel, (event) => {
|
||||
events.push(`cancel:${event.row}:${event.column}`);
|
||||
});
|
||||
|
||||
await expect(a1.setHyperLink('https://univer.ai', 'Univer')).resolves.toBe(true);
|
||||
await expect(b2.setHyperLink('https://docs.univer.ai', 'Docs')).resolves.toBe(true);
|
||||
|
||||
expect(worksheet.getRange('A1:B2').getHyperLinks()).toEqual([
|
||||
{
|
||||
id: expect.any(String),
|
||||
row: 0,
|
||||
column: 0,
|
||||
url: 'https://univer.ai',
|
||||
label: 'Univer',
|
||||
},
|
||||
{
|
||||
id: expect.any(String),
|
||||
row: 1,
|
||||
column: 1,
|
||||
url: 'https://docs.univer.ai',
|
||||
label: 'Docs',
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(a1.updateHyperLink('https://univer.ai/docs', 'Univer Docs')).resolves.toBe(true);
|
||||
expect(a1.getHyperLinks()).toEqual([{
|
||||
id: expect.any(String),
|
||||
row: 0,
|
||||
column: 0,
|
||||
url: 'https://univer.ai/docs',
|
||||
label: 'Univer Docs',
|
||||
}]);
|
||||
|
||||
expect(worksheet.getRange('A1:B2').cancelHyperLink()).toBe(true);
|
||||
expect(worksheet.getRange('A1:B2').getHyperLinks()).toEqual([]);
|
||||
expect(events).toEqual([
|
||||
'add:0:0:https://univer.ai',
|
||||
'add:1:1:https://docs.univer.ai',
|
||||
'update:0:0:https://univer.ai/docs',
|
||||
'cancel:0:0',
|
||||
'cancel:1:1',
|
||||
]);
|
||||
|
||||
addDisposable.dispose();
|
||||
updateDisposable.dispose();
|
||||
cancelDisposable.dispose();
|
||||
});
|
||||
|
||||
it('should create hyperlink urls from sheet ranges', () => {
|
||||
const workbook = univerAPI.getActiveWorkbook()!;
|
||||
const worksheet = workbook.getActiveSheet();
|
||||
|
||||
expect(worksheet.getUrl()).toBe('#gid=sheet1');
|
||||
expect(worksheet.getRange('A1:B2').getUrl()).toBe('#gid=sheet1&range=A1:B2');
|
||||
|
||||
univer.__getInjector().get(IDefinedNamesService).registerDefinedName(workbook.getId(), {
|
||||
id: 'range-1',
|
||||
name: 'Total',
|
||||
formulaOrRefString: 'Sheet1!A1:B2',
|
||||
});
|
||||
expect(workbook.getUrlOfDefineName('Total')).toBe('#rangeid=range-1');
|
||||
expect(workbook.parseSheetHyperlink('https://univer.ai')).toEqual({
|
||||
type: SheetHyperLinkType.URL,
|
||||
name: 'https://univer.ai',
|
||||
url: 'https://univer.ai',
|
||||
searchObj: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
152
packages/sheets-hyper-link/src/facade/f-event.ts
Normal file
152
packages/sheets-hyper-link/src/facade/f-event.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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 { IEventBase } from '@univerjs/core/facade';
|
||||
import type { ICellLinkContent, ISheetHyperLink } from '@univerjs/sheets-hyper-link';
|
||||
import type { FWorkbook, FWorksheet } from '@univerjs/sheets/facade';
|
||||
import { FEventName } from '@univerjs/core/facade';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
interface IFSheetsHyperlinkEventNameMixin {
|
||||
/**
|
||||
* Event triggered before adding a link
|
||||
* @see {@link IBeforeSheetLinkAddEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetLinkAdd, (params) => {
|
||||
* const { workbook, worksheet, row, col, link } = params;
|
||||
* console.log('before sheet link add', params);
|
||||
*
|
||||
* // Cancel the sheet link add operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetLinkAdd: 'BeforeSheetLinkAdd';
|
||||
|
||||
/**
|
||||
* Event triggered before canceling a link
|
||||
* @see {@link IBeforeSheetLinkCancelEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetLinkCancel, (params) => {
|
||||
* const { workbook, worksheet, row, column, id } = params;
|
||||
* console.log('before sheet link cancel', params);
|
||||
*
|
||||
* // Cancel the sheet link cancel operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetLinkCancel: 'BeforeSheetLinkCancel';
|
||||
|
||||
/**
|
||||
* Event triggered before updating a link
|
||||
* @see {@link IBeforeSheetLinkUpdateEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetLinkUpdate, (params) => {
|
||||
* const { workbook, worksheet, row, column, id, payload } = params;
|
||||
* console.log('before sheet link update', params);
|
||||
*
|
||||
* // Cancel the sheet link update operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetLinkUpdate: 'BeforeSheetLinkUpdate';
|
||||
}
|
||||
|
||||
export class FSheetsHyperlinkEventNameMixin extends FEventName implements IFSheetsHyperlinkEventNameMixin {
|
||||
override get BeforeSheetLinkAdd(): 'BeforeSheetLinkAdd' {
|
||||
return 'BeforeSheetLinkAdd' as const;
|
||||
}
|
||||
|
||||
override get BeforeSheetLinkCancel(): 'BeforeSheetLinkCancel' {
|
||||
return 'BeforeSheetLinkCancel' as const;
|
||||
}
|
||||
|
||||
override get BeforeSheetLinkUpdate(): 'BeforeSheetLinkUpdate' {
|
||||
return 'BeforeSheetLinkUpdate' as const;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IBeforeSheetLinkAddEventParams extends IEventBase {
|
||||
/** The workbook instance */
|
||||
workbook: FWorkbook;
|
||||
/** The worksheet where the link will be added */
|
||||
worksheet: FWorksheet;
|
||||
/** The row index of the target cell */
|
||||
row: number;
|
||||
/** The column index of the target cell */
|
||||
col: number;
|
||||
/** The hyperlink information to be added */
|
||||
link: ISheetHyperLink;
|
||||
}
|
||||
|
||||
export interface IBeforeSheetLinkCancelEventParams extends IEventBase {
|
||||
/** The workbook instance */
|
||||
workbook: FWorkbook;
|
||||
/** The worksheet containing the link */
|
||||
worksheet: FWorksheet;
|
||||
/** The row index of the cell */
|
||||
row: number;
|
||||
/** The column index of the cell */
|
||||
column: number;
|
||||
/** The unique identifier of the hyperlink */
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface IBeforeSheetLinkUpdateEventParams extends IEventBase {
|
||||
/** The workbook instance */
|
||||
workbook: FWorkbook;
|
||||
/** The worksheet containing the link */
|
||||
worksheet: FWorksheet;
|
||||
/** The row index of the cell */
|
||||
row: number;
|
||||
/** The column index of the cell */
|
||||
column: number;
|
||||
/** The unique identifier of the hyperlink */
|
||||
id: string;
|
||||
/** The new hyperlink content/information */
|
||||
payload: ICellLinkContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface ISheetsHyperlinkEventParamConfig {
|
||||
BeforeSheetLinkAdd: IBeforeSheetLinkAddEventParams;
|
||||
BeforeSheetLinkCancel: IBeforeSheetLinkCancelEventParams;
|
||||
BeforeSheetLinkUpdate: IBeforeSheetLinkUpdateEventParams;
|
||||
}
|
||||
|
||||
FEventName.extend(FSheetsHyperlinkEventNameMixin);
|
||||
|
||||
declare module '@univerjs/core/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FEventName extends IFSheetsHyperlinkEventNameMixin {}
|
||||
|
||||
interface IEventParamConfig extends ISheetsHyperlinkEventParamConfig { }
|
||||
}
|
||||
266
packages/sheets-hyper-link/src/facade/f-range.ts
Normal file
266
packages/sheets-hyper-link/src/facade/f-range.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* 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 { IAddHyperLinkCommandParams, ICancelHyperLinkCommandParams, IUpdateHyperLinkCommandParams } from '@univerjs/sheets-hyper-link';
|
||||
import { CustomRangeType, DataStreamTreeTokenType, generateRandomId } from '@univerjs/core';
|
||||
import { AddHyperLinkCommand, CancelHyperLinkCommand, SheetsHyperLinkParserService, UpdateHyperLinkCommand } from '@univerjs/sheets-hyper-link';
|
||||
import { FRange } from '@univerjs/sheets/facade';
|
||||
|
||||
export interface ICellHyperLink {
|
||||
id: string;
|
||||
row: number;
|
||||
column: number;
|
||||
url: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFRangeSheetsHyperlinkMixin {
|
||||
/**
|
||||
* Set a hyperlink for this range top left cell.
|
||||
* The hyperlink can be a URL, a range link, or a sheet link.
|
||||
* When the hyperlink is a range link or a sheet link, the url should be the url of the target range or sheet.
|
||||
* @param {string} url - The hyperlink url, can be a URL, a range link, or a sheet link.
|
||||
* @param {string} [label] - The display text of the hyperlink. If omitted, the existing cell text is linked. Supply a label when linking an empty cell.
|
||||
* @return {Promise<boolean>} A promise that resolves to true if the hyperlink is set successfully, otherwise false.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
*
|
||||
* // Create a hyperlink to Univer on cell A1
|
||||
* const fRange = fWorksheet.getRange('A1');
|
||||
* await fRange.setHyperLink('https://univer.ai/', 'Univer');
|
||||
*
|
||||
* // Create a hyperlink to Sheet1 range B2:D4 on cell A2
|
||||
* const fRange2 = fWorksheet.getRange('A2');
|
||||
* const rangeUrl = fWorksheet.getRange('B2:D4').getUrl();
|
||||
* await fRange2.setHyperLink(rangeUrl, 'Link to B2:D4');
|
||||
*
|
||||
* // Create a hyperlink to another sheet range on cell A3
|
||||
* const anotherSheet = fWorkbook.getSheetByName('Another Sheet');
|
||||
* if (anotherSheet) {
|
||||
* const anotherSheetUrl = anotherSheet.getUrl();
|
||||
* const fRange3 = fWorksheet.getRange('A3');
|
||||
* await fRange3.setHyperLink(anotherSheetUrl, 'Link to Another Sheet');
|
||||
* }
|
||||
*
|
||||
* // Create a hyperlink to a defined name on cell A4
|
||||
* const fRange4 = fWorksheet.getRange('A4');
|
||||
* const definedNameHyperlinkUrl = fWorkbook.getUrlOfDefineName('MyDefinedName');
|
||||
* await fRange4.setHyperLink(definedNameHyperlinkUrl, 'Link to MyDefinedName');
|
||||
* ```
|
||||
*/
|
||||
setHyperLink(url: string, label?: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Gets the first hyperlink from each cell containing hyperlinks in this range.
|
||||
* @returns {ICellHyperLink[]} At most one hyperlink per cell, with absolute, zero-based worksheet coordinates.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* console.log(fWorksheet.getRange('A1:T100').getHyperLinks());
|
||||
* ```
|
||||
*/
|
||||
getHyperLinks(): ICellHyperLink[];
|
||||
|
||||
/**
|
||||
* Update the hyperlink of this range top left cell.
|
||||
* @param {string} url - The new hyperlink url, can be a URL, a range link, or a sheet link.
|
||||
* @param {string} [label] - The new display text of the hyperlink. If omitted, the replacement display text is empty. Supply a label to keep the link visible.
|
||||
*
|
||||
* @returns {Promise<boolean>} A promise resolving to whether the update succeeded.
|
||||
* @throws {Error} The promise rejects if the top-left cell contains no hyperlink.
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
*
|
||||
* // Create a hyperlink to Univer on cell A1
|
||||
* const fRange = fWorksheet.getRange('A1');
|
||||
* await fRange.setHyperLink('https://univer.ai/', 'Univer');
|
||||
*
|
||||
* // Update hyperlink after 3 seconds
|
||||
* await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
*
|
||||
* const rangeUrl = fWorksheet.getRange('B2:D4').getUrl();
|
||||
* await fRange.updateHyperLink(rangeUrl, 'Link to B2:D4');
|
||||
* ```
|
||||
*/
|
||||
updateHyperLink(url: string, label?: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Cancel all hyperlinks in this range. If a hyperlink is provided, only cancel the specified hyperlink.
|
||||
* @param {ICellHyperLink} [hyperlink] - The hyperlink to be cancelled. If not provided, all hyperlinks in this range will be cancelled.
|
||||
* @return {boolean} True if the hyperlink(s) is cancelled successfully, otherwise false.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
*
|
||||
* // Cancel the hyperlink in cell A1
|
||||
* const fRange = fWorksheet.getRange('A1');
|
||||
* fRange.cancelHyperLink();
|
||||
*
|
||||
* // Cancel all hyperlinks in range A2:B4
|
||||
* const fRange2 = fWorksheet.getRange('A2:B4');
|
||||
* fRange2.cancelHyperLink();
|
||||
*
|
||||
* // Cancel a specific hyperlink in range A1:T100
|
||||
* const fRange3 = fWorksheet.getRange('A1:T100');
|
||||
* const hyperlinks = fRange3.getHyperLinks();
|
||||
* if (hyperlinks.length > 1) {
|
||||
* fRange3.cancelHyperLink(hyperlinks[1]);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
cancelHyperLink(hyperlink?: ICellHyperLink): boolean;
|
||||
|
||||
/**
|
||||
* Create a hyperlink url to this range
|
||||
* @returns {string} The hyperlink url of this range
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const fRange = fWorksheet.getRange('A1');
|
||||
* const url = fRange.getUrl();
|
||||
* console.log(url);
|
||||
* ```
|
||||
*/
|
||||
getUrl(): string;
|
||||
}
|
||||
|
||||
export class FRangeSheetsHyperlinkMixin extends FRange implements IFRangeSheetsHyperlinkMixin {
|
||||
override setHyperLink(url: string, label?: string): Promise<boolean> {
|
||||
return this._commandService.executeCommand<IAddHyperLinkCommandParams>(AddHyperLinkCommand.id, {
|
||||
unitId: this.getUnitId(),
|
||||
subUnitId: this._worksheet.getSheetId(),
|
||||
link: {
|
||||
id: generateRandomId(),
|
||||
row: this._range.startRow,
|
||||
column: this._range.startColumn,
|
||||
payload: url,
|
||||
display: label,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override getHyperLinks(): ICellHyperLink[] {
|
||||
const hyperlinks: ICellHyperLink[] = [];
|
||||
|
||||
this.forEach((row, column, cell) => {
|
||||
if (!cell.p) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ranges = cell.p.body?.customRanges?.filter((range) => range.rangeType === CustomRangeType.HYPERLINK) ?? [];
|
||||
|
||||
if (ranges.length > 0) {
|
||||
const dataStream = cell.p?.body?.dataStream;
|
||||
const { rangeId, properties, startIndex, endIndex } = ranges[0];
|
||||
const url = properties?.url ?? '';
|
||||
const label = dataStream?.slice(startIndex, endIndex + 1).replaceAll(DataStreamTreeTokenType.CUSTOM_RANGE_START, '').replaceAll(DataStreamTreeTokenType.CUSTOM_RANGE_END, '') ?? '';
|
||||
|
||||
hyperlinks.push({
|
||||
id: `${rangeId}`,
|
||||
row,
|
||||
column,
|
||||
url,
|
||||
label,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return hyperlinks;
|
||||
}
|
||||
|
||||
override updateHyperLink(url: string, label?: string): Promise<boolean> {
|
||||
const hyperlink = this.getHyperLinks().find((link) => link.row === this._range.startRow && link.column === this._range.startColumn);
|
||||
|
||||
if (!hyperlink) {
|
||||
return Promise.reject(new Error('No hyperlink found in the top left cell of the range'));
|
||||
}
|
||||
|
||||
const { id, row, column } = hyperlink;
|
||||
|
||||
return this._commandService.executeCommand<IUpdateHyperLinkCommandParams>(UpdateHyperLinkCommand.id, {
|
||||
unitId: this.getUnitId(),
|
||||
subUnitId: this._worksheet.getSheetId(),
|
||||
id,
|
||||
row,
|
||||
column,
|
||||
payload: {
|
||||
payload: url,
|
||||
display: label,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override cancelHyperLink(hyperlink?: ICellHyperLink): boolean {
|
||||
if (hyperlink) {
|
||||
const { id, row, column } = hyperlink;
|
||||
|
||||
return this._commandService.syncExecuteCommand<ICancelHyperLinkCommandParams>(CancelHyperLinkCommand.id, {
|
||||
unitId: this.getUnitId(),
|
||||
subUnitId: this._worksheet.getSheetId(),
|
||||
id,
|
||||
row,
|
||||
column,
|
||||
});
|
||||
} else {
|
||||
const hyperlinks = this.getHyperLinks();
|
||||
|
||||
if (hyperlinks.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hyperlinks.every((link) => {
|
||||
const { id, row, column } = link;
|
||||
|
||||
return this._commandService.syncExecuteCommand<ICancelHyperLinkCommandParams>(CancelHyperLinkCommand.id, {
|
||||
unitId: this.getUnitId(),
|
||||
subUnitId: this._worksheet.getSheetId(),
|
||||
id,
|
||||
row,
|
||||
column,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
override getUrl(): string {
|
||||
const parserService = this._injector.get(SheetsHyperLinkParserService);
|
||||
return parserService.buildHyperLink(this.getRange(), this.getSheetId());
|
||||
}
|
||||
}
|
||||
|
||||
FRange.extend(FRangeSheetsHyperlinkMixin);
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
interface FRange extends IFRangeSheetsHyperlinkMixin {}
|
||||
}
|
||||
123
packages/sheets-hyper-link/src/facade/f-univer.ts
Normal file
123
packages/sheets-hyper-link/src/facade/f-univer.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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 { Injector } from '@univerjs/core';
|
||||
import type { IAddHyperLinkCommandParams, ICancelHyperLinkCommandParams, IUpdateHyperLinkCommandParams } from '@univerjs/sheets-hyper-link';
|
||||
import type { IBeforeSheetLinkAddEventParams, IBeforeSheetLinkCancelEventParams, IBeforeSheetLinkUpdateEventParams } from './f-event';
|
||||
import { CanceledError, ICommandService } from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
import { AddHyperLinkCommand, CancelHyperLinkCommand, UpdateHyperLinkCommand } from '@univerjs/sheets-hyper-link';
|
||||
|
||||
export class FUniverSheetsHyperlinkMixin extends FUniver {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
override _initialize(injector: Injector): void {
|
||||
const commandService = injector.get(ICommandService);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetLinkAdd,
|
||||
() => commandService.beforeCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id !== AddHyperLinkCommand.id) return;
|
||||
|
||||
const params = commandInfo.params as IAddHyperLinkCommandParams;
|
||||
const target = this.getSheetCommandTarget(params);
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
const { link } = params;
|
||||
const { row, column: col } = link;
|
||||
|
||||
const eventParams: IBeforeSheetLinkAddEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
col,
|
||||
link,
|
||||
};
|
||||
this.fireEvent(this.Event.BeforeSheetLinkAdd, eventParams);
|
||||
|
||||
if (eventParams.cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetLinkUpdate,
|
||||
() => commandService.beforeCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id !== UpdateHyperLinkCommand.id) return;
|
||||
|
||||
const params = commandInfo.params as IUpdateHyperLinkCommandParams;
|
||||
const target = this.getSheetCommandTarget(params);
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
const { row, column, id, payload } = params;
|
||||
|
||||
const eventParams: IBeforeSheetLinkUpdateEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
column,
|
||||
id,
|
||||
payload,
|
||||
};
|
||||
this.fireEvent(this.Event.BeforeSheetLinkUpdate, eventParams);
|
||||
|
||||
if (eventParams.cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetLinkCancel,
|
||||
() => commandService.beforeCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id !== CancelHyperLinkCommand.id) return;
|
||||
|
||||
const params = commandInfo.params as ICancelHyperLinkCommandParams;
|
||||
const target = this.getSheetCommandTarget(params);
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
const { row, column, id } = params;
|
||||
|
||||
const eventParams: IBeforeSheetLinkCancelEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
column,
|
||||
id,
|
||||
};
|
||||
this.fireEvent(this.Event.BeforeSheetLinkCancel, eventParams);
|
||||
|
||||
if (eventParams.cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FUniver.extend(FUniverSheetsHyperlinkMixin);
|
||||
111
packages/sheets-hyper-link/src/facade/f-workbook.ts
Normal file
111
packages/sheets-hyper-link/src/facade/f-workbook.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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 { ISheetHyperLinkInfo } from '@univerjs/sheets-hyper-link';
|
||||
import { IDefinedNamesService, operatorToken } from '@univerjs/engine-formula';
|
||||
import { SheetsHyperLinkParserService } from '@univerjs/sheets-hyper-link';
|
||||
import { FWorkbook } from '@univerjs/sheets/facade';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFWorkbookHyperlinkMixin {
|
||||
/**
|
||||
* Create a hyperlink url for the defined name.
|
||||
* The defined name must exist in the current workbook and must be a reference to a range, otherwise an error will be thrown.
|
||||
* @param {string} name - The name of the defined name.
|
||||
* @returns {string} The hyperlink url of the defined name.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
*
|
||||
* // Create a defined name "TestRange" for the range A1:B10 of the active sheet
|
||||
* const definedNameParam = fWorkbook.newDefinedNameBuilder()
|
||||
* .setName('TestRange')
|
||||
* .setRef('Sheet1!$A$1:$B$10')
|
||||
* .build();
|
||||
* fWorkbook.insertDefinedNameBuilder(definedNameParam);
|
||||
*
|
||||
* // Create a hyperlink to the defined name "TestRange" on cell C1
|
||||
* const url = fWorkbook.getUrlOfDefineName('TestRange');
|
||||
* console.log(url);
|
||||
* const fRange = fWorksheet.getRange('C1');
|
||||
* fRange.setHyperLink(url, 'Link to TestRange');
|
||||
*
|
||||
* // Create a hyperlink to the exiting defined name on cell C2
|
||||
* const definedNames = fWorkbook.getDefinedNames();
|
||||
* console.log(definedNames);
|
||||
* const exitsDefinedNameUrl = fWorkbook.getUrlOfDefineName(definedNames[0].getName());
|
||||
* console.log(exitsDefinedNameUrl);
|
||||
* const fRange2 = fWorksheet.getRange('C2');
|
||||
* fRange2.setHyperLink(exitsDefinedNameUrl, `Link to ${definedNames[0].getName()}`);
|
||||
* ```
|
||||
*/
|
||||
getUrlOfDefineName(name: string): string;
|
||||
|
||||
/**
|
||||
* Parse the hyperlink string to get the hyperlink info.
|
||||
* @param {string} hyperlink - The hyperlink string.
|
||||
* @returns {ISheetHyperLinkInfo} The hyperlink info.
|
||||
*
|
||||
* @example
|
||||
* ``` ts
|
||||
* // Create a hyperlink to the range A1:D10 of the current sheet
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const fRange = fWorksheet.getRange('A1:D10');
|
||||
* const hyperlink = fRange.getUrl();
|
||||
*
|
||||
* // Parse the hyperlink
|
||||
* const hyperlinkInfo = fWorkbook.parseSheetHyperlink(hyperlink);
|
||||
* console.log(hyperlinkInfo);
|
||||
* ```
|
||||
*/
|
||||
parseSheetHyperlink(hyperlink: string): ISheetHyperLinkInfo;
|
||||
}
|
||||
|
||||
export class FWorkbookHyperlinkMixin extends FWorkbook implements IFWorkbookHyperlinkMixin {
|
||||
override getUrlOfDefineName(name: string): string {
|
||||
const definedNameService = this._injector.get(IDefinedNamesService);
|
||||
const parserService = this._injector.get(SheetsHyperLinkParserService);
|
||||
|
||||
const definedName = definedNameService.getValueByName(this._workbook.getUnitId(), name);
|
||||
if (!definedName) {
|
||||
throw new Error(`Defined name "${name}" does not exist.`);
|
||||
}
|
||||
|
||||
if (definedName.formulaOrRefString.startsWith(operatorToken.EQUALS)) {
|
||||
throw new Error(`Defined name "${name}" is not a reference range, cannot be converted to hyperlink.`);
|
||||
}
|
||||
|
||||
return parserService.buildHyperLink(definedName.id);
|
||||
}
|
||||
|
||||
override parseSheetHyperlink(hyperlink: string): ISheetHyperLinkInfo {
|
||||
const parserService = this._injector.get(SheetsHyperLinkParserService);
|
||||
return parserService.parseHyperLink(hyperlink);
|
||||
}
|
||||
}
|
||||
|
||||
FWorkbook.extend(FWorkbookHyperlinkMixin);
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FWorkbook extends IFWorkbookHyperlinkMixin { }
|
||||
}
|
||||
51
packages/sheets-hyper-link/src/facade/f-worksheet.ts
Normal file
51
packages/sheets-hyper-link/src/facade/f-worksheet.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 { SheetsHyperLinkParserService } from '@univerjs/sheets-hyper-link';
|
||||
import { FWorksheet } from '@univerjs/sheets/facade';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFWorksheetHyperlinkMixin {
|
||||
/**
|
||||
* Create a hyperlink url to this sheet
|
||||
* @returns {string} The hyperlink url of this sheet
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const url = fWorksheet.getUrl();
|
||||
* console.log(url);
|
||||
* ```
|
||||
*/
|
||||
getUrl(): string;
|
||||
}
|
||||
|
||||
export class FWorksheetHyperlinkMixin extends FWorksheet implements IFWorksheetHyperlinkMixin {
|
||||
override getUrl(): string {
|
||||
const parserService = this._injector.get(SheetsHyperLinkParserService);
|
||||
return parserService.buildHyperLink(null, this._worksheet.getSheetId());
|
||||
}
|
||||
}
|
||||
|
||||
FWorksheet.extend(FWorksheetHyperlinkMixin);
|
||||
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FWorksheet extends IFWorksheetHyperlinkMixin {}
|
||||
}
|
||||
28
packages/sheets-hyper-link/src/facade/index.ts
Normal file
28
packages/sheets-hyper-link/src/facade/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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-workbook';
|
||||
import './f-worksheet';
|
||||
import './f-range';
|
||||
import './f-event';
|
||||
import './f-univer';
|
||||
|
||||
export type * from './f-event';
|
||||
export type * from './f-range';
|
||||
export type * from './f-univer';
|
||||
export { FWorkbookHyperlinkMixin } from './f-workbook';
|
||||
export type * from './f-workbook';
|
||||
export type * from './f-worksheet';
|
||||
48
packages/sheets-hyper-link/src/index.ts
Normal file
48
packages/sheets-hyper-link/src/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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 { AddHyperLinkCommand, AddRichHyperLinkCommand } from './commands/commands/add-hyper-link.command';
|
||||
export type {
|
||||
IAddHyperLinkCommandParams,
|
||||
IAddRichHyperLinkCommandParams,
|
||||
} from './commands/commands/add-hyper-link.command';
|
||||
export { CancelHyperLinkCommand, CancelRichHyperLinkCommand } from './commands/commands/remove-hyper-link.command';
|
||||
export type {
|
||||
ICancelHyperLinkCommandParams,
|
||||
ICancelRichHyperLinkCommandParams,
|
||||
} from './commands/commands/remove-hyper-link.command';
|
||||
export { UpdateHyperLinkCommand, UpdateRichHyperLinkCommand } from './commands/commands/update-hyper-link.command';
|
||||
export type {
|
||||
IUpdateHyperLinkCommandParams,
|
||||
IUpdateRichHyperLinkCommandParams,
|
||||
} from './commands/commands/update-hyper-link.command';
|
||||
export { AddHyperLinkMutation } from './commands/mutations/add-hyper-link.mutation';
|
||||
export type { IAddHyperLinkMutationParams } from './commands/mutations/add-hyper-link.mutation';
|
||||
export { RemoveHyperLinkMutation } from './commands/mutations/remove-hyper-link.mutation';
|
||||
export type { IRemoveHyperLinkMutationParams } from './commands/mutations/remove-hyper-link.mutation';
|
||||
export { UpdateHyperLinkMutation, UpdateHyperLinkRefMutation } from './commands/mutations/update-hyper-link.mutation';
|
||||
export type {
|
||||
IUpdateHyperLinkMutationParams,
|
||||
IUpdateHyperLinkRefMutationParams,
|
||||
} from './commands/mutations/update-hyper-link.mutation';
|
||||
export type { IUniverSheetsHyperLinkConfig } from './config/config';
|
||||
export { HyperLinkModel } from './models/hyper-link.model';
|
||||
export { UniverSheetsHyperLinkPlugin } from './plugin';
|
||||
export { SheetsHyperLinkParserService } from './services/parser.service';
|
||||
export type { ISheetHyperLinkInfo, ISheetUrlParams } from './services/parser.service';
|
||||
export { ERROR_RANGE } from './types/const';
|
||||
export { SheetHyperLinkType } from './types/enums/hyper-link-type';
|
||||
export type { ICellLinkContent, ISheetHyperLink } from './types/interfaces/i-hyper-link';
|
||||
27
packages/sheets-hyper-link/src/locale/ar-SA.ts
Normal file
27
packages/sheets-hyper-link/src/locale/ar-SA.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'نطاق غير صالح',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/ca-ES.ts
Normal file
27
packages/sheets-hyper-link/src/locale/ca-ES.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Interval no vàlid',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/de-DE.ts
Normal file
27
packages/sheets-hyper-link/src/locale/de-DE.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Ungültiger Bereich',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
25
packages/sheets-hyper-link/src/locale/en-US.ts
Normal file
25
packages/sheets-hyper-link/src/locale/en-US.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.
|
||||
*/
|
||||
|
||||
const locale = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Invalid Range',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/es-ES.ts
Normal file
27
packages/sheets-hyper-link/src/locale/es-ES.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Rango no válido',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/fa-IR.ts
Normal file
27
packages/sheets-hyper-link/src/locale/fa-IR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'محدوده نامعتبر',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/fr-FR.ts
Normal file
27
packages/sheets-hyper-link/src/locale/fr-FR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Plage invalide',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/id-ID.ts
Normal file
27
packages/sheets-hyper-link/src/locale/id-ID.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Rentang Tidak Valid',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/it-IT.ts
Normal file
27
packages/sheets-hyper-link/src/locale/it-IT.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Intervallo non valido',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/ja-JP.ts
Normal file
27
packages/sheets-hyper-link/src/locale/ja-JP.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: '無効な範囲です',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/ko-KR.ts
Normal file
27
packages/sheets-hyper-link/src/locale/ko-KR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: '잘못된 범위입니다',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/pl-PL.ts
Normal file
27
packages/sheets-hyper-link/src/locale/pl-PL.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Nieprawidłowy zakres',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/pt-BR.ts
Normal file
27
packages/sheets-hyper-link/src/locale/pt-BR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Intervalo Inválido',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/ru-RU.ts
Normal file
27
packages/sheets-hyper-link/src/locale/ru-RU.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Недопустимый диапазон',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/sk-SK.ts
Normal file
27
packages/sheets-hyper-link/src/locale/sk-SK.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Neplatný rozsah',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
20
packages/sheets-hyper-link/src/locale/types.ts
Normal file
20
packages/sheets-hyper-link/src/locale/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { LocaleLeafKeys } from '@univerjs/core';
|
||||
import type enUS from './en-US';
|
||||
|
||||
export type LocaleKey = LocaleLeafKeys<typeof enUS>;
|
||||
27
packages/sheets-hyper-link/src/locale/vi-VN.ts
Normal file
27
packages/sheets-hyper-link/src/locale/vi-VN.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Tham chiếu lỗi',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/zh-CN.ts
Normal file
27
packages/sheets-hyper-link/src/locale/zh-CN.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: '错误的引用',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/zh-HK.ts
Normal file
27
packages/sheets-hyper-link/src/locale/zh-HK.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: '錯誤的引用',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-hyper-link/src/locale/zh-TW.ts
Normal file
27
packages/sheets-hyper-link/src/locale/zh-TW.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: '錯誤的引用',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 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 { IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import type { ISheetHyperLink } from '../../types/interfaces/i-hyper-link';
|
||||
import { IUniverInstanceService, LocaleType, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { HyperLinkModel } from '../hyper-link.model';
|
||||
|
||||
function createWorkbookData(unitId = 'book-1', sheetId = 'sheet-1'): IWorkbookData {
|
||||
return {
|
||||
id: unitId,
|
||||
appVersion: '3.0.0-alpha',
|
||||
locale: LocaleType.EN_US,
|
||||
name: 'Workbook',
|
||||
sheetOrder: [sheetId],
|
||||
styles: {},
|
||||
sheets: {
|
||||
[sheetId]: {
|
||||
id: sheetId,
|
||||
name: 'Sheet1',
|
||||
cellData: {
|
||||
0: {
|
||||
0: { v: 'Display From Cell' },
|
||||
1: { v: 'Other Cell' },
|
||||
},
|
||||
1: {
|
||||
1: { v: 'Moved Cell' },
|
||||
},
|
||||
},
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createLink(id: string, row: number, column: number, payload = 'https://univer.ai'): ISheetHyperLink {
|
||||
return {
|
||||
id,
|
||||
row,
|
||||
column,
|
||||
payload,
|
||||
display: `${id}-display`,
|
||||
};
|
||||
}
|
||||
|
||||
describe('HyperLinkModel', () => {
|
||||
let univer: Univer;
|
||||
let model: HyperLinkModel;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
|
||||
injector.add([HyperLinkModel]);
|
||||
univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
injector.get(IUniverInstanceService).focusUnit('book-1');
|
||||
|
||||
model = injector.get(HyperLinkModel);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('should add, update, remove and unload hyperlinks while emitting update events', () => {
|
||||
const link = createLink('link-1', 0, 0);
|
||||
const events: string[] = [];
|
||||
|
||||
model.linkUpdate$.subscribe((event) => {
|
||||
events.push(event.type);
|
||||
});
|
||||
|
||||
expect(model.addHyperLink('book-1', 'sheet-1', link)).toBe(true);
|
||||
expect(model.getHyperLink('book-1', 'sheet-1', 'link-1')).toEqual(link);
|
||||
|
||||
expect(model.updateHyperLink('book-1', 'sheet-1', 'link-1', { payload: 'https://docs.univer.ai' }, true)).toBe(true);
|
||||
expect(model.getHyperLink('book-1', 'sheet-1', 'link-1')?.payload).toBe('https://docs.univer.ai');
|
||||
|
||||
expect(model.removeHyperLink('book-1', 'sheet-1', 'link-1')).toBe(true);
|
||||
expect(model.removeHyperLink('book-1', 'sheet-1', 'missing')).toBe(false);
|
||||
|
||||
model.deleteUnit('book-1');
|
||||
|
||||
expect(events).toEqual(['add', 'update', 'remove', 'unload']);
|
||||
expect(model.getUnit('book-1')).toEqual([]);
|
||||
expect(model.getAll()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should move hyperlink references and clear the original matrix position when the original link is still there', () => {
|
||||
const link = createLink('link-1', 0, 0);
|
||||
|
||||
model.addHyperLink('book-1', 'sheet-1', link);
|
||||
expect(model.updateHyperLinkRef('book-1', 'sheet-1', 'link-1', { row: 1, column: 1 }, true)).toBe(true);
|
||||
|
||||
expect(model.getHyperLinkByLocation('book-1', 'sheet-1', 0, 0)).toBeUndefined();
|
||||
expect(model.getHyperLinkByLocation('book-1', 'sheet-1', 1, 1)).toEqual({
|
||||
...link,
|
||||
row: 1,
|
||||
column: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should move hyperlink references from the cached position when the matrix slot has been overwritten', () => {
|
||||
const original = createLink('original', 0, 0, 'https://origin');
|
||||
const overwritten = createLink('overwritten', 0, 0, 'https://other');
|
||||
|
||||
model.addHyperLink('book-1', 'sheet-1', original);
|
||||
model.addHyperLink('book-1', 'sheet-1', overwritten);
|
||||
model.updateHyperLinkRef('book-1', 'sheet-1', 'original', { row: 1, column: 1 });
|
||||
|
||||
expect(model.getHyperLink('book-1', 'sheet-1', 'original')).toEqual({
|
||||
...original,
|
||||
row: 1,
|
||||
column: 1,
|
||||
});
|
||||
expect(model.getHyperLinkByLocation('book-1', 'sheet-1', 0, 0)).toEqual(overwritten);
|
||||
});
|
||||
|
||||
it('should resolve display text from worksheet cells when reading synchronously by location', () => {
|
||||
const link = createLink('link-1', 0, 0);
|
||||
|
||||
model.addHyperLink('book-1', 'sheet-1', link);
|
||||
|
||||
expect(model.getHyperLinkByLocationSync('book-1', 'sheet-1', 0, 0)).toEqual({
|
||||
...link,
|
||||
display: 'Display From Cell',
|
||||
});
|
||||
expect(model.getHyperLinkByLocationSync('book-1', 'sheet-1', 9, 9)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should expose links by sub unit and whole unit', () => {
|
||||
const first = createLink('link-1', 0, 0);
|
||||
const second = createLink('link-2', 1, 1);
|
||||
|
||||
model.addHyperLink('book-1', 'sheet-1', first);
|
||||
model.addHyperLink('book-1', 'sheet-1', second);
|
||||
|
||||
expect(model.getSubUnit('book-1', 'sheet-1')).toEqual([first, second]);
|
||||
expect(model.getUnit('book-1')).toEqual([
|
||||
{
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-1',
|
||||
links: [first, second],
|
||||
},
|
||||
]);
|
||||
expect(model.getAll()).toEqual([
|
||||
[
|
||||
{
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-1',
|
||||
links: [first, second],
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
277
packages/sheets-hyper-link/src/models/hyper-link.model.ts
Normal file
277
packages/sheets-hyper-link/src/models/hyper-link.model.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* 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 { Workbook } from '@univerjs/core';
|
||||
import type { ICellLinkContent, ISheetHyperLink } from '../types/interfaces/i-hyper-link';
|
||||
import { Disposable, IUniverInstanceService, ObjectMatrix, UniverInstanceType } from '@univerjs/core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
type LinkUpdate = {
|
||||
type: 'add';
|
||||
payload: ISheetHyperLink;
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
silent?: boolean;
|
||||
} | {
|
||||
type: 'remove';
|
||||
payload: ISheetHyperLink;
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
silent?: boolean;
|
||||
} | {
|
||||
type: 'update';
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
payload: ICellLinkContent;
|
||||
id: string;
|
||||
silent?: boolean;
|
||||
} | {
|
||||
type: 'updateRef';
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
id: string;
|
||||
payload: { row: number; column: number };
|
||||
silent?: boolean;
|
||||
} | {
|
||||
type: 'unload';
|
||||
unitId: string;
|
||||
unitLinks: {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
links: ISheetHyperLink[];
|
||||
}[];
|
||||
silent?: boolean;
|
||||
};
|
||||
|
||||
export class HyperLinkModel extends Disposable {
|
||||
private _linkUpdate$ = new Subject<LinkUpdate>();
|
||||
linkUpdate$ = this._linkUpdate$.asObservable();
|
||||
|
||||
private _linkMap: Map<string, Map<string, ObjectMatrix<ISheetHyperLink>>> = new Map();
|
||||
private _linkPositionMap: Map<string, Map<string, Map<string, { row: number; column: number; link: ISheetHyperLink }>>> = new Map();
|
||||
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService
|
||||
) {
|
||||
super();
|
||||
this.disposeWithMe({
|
||||
dispose: () => {
|
||||
this._linkUpdate$.complete();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _ensureMap(unitId: string, subUnitId: string) {
|
||||
let unitMap = this._linkMap.get(unitId);
|
||||
|
||||
if (!unitMap) {
|
||||
unitMap = new Map();
|
||||
this._linkMap.set(unitId, unitMap);
|
||||
}
|
||||
let matrix = unitMap.get(subUnitId);
|
||||
if (!matrix) {
|
||||
matrix = new ObjectMatrix();
|
||||
unitMap.set(subUnitId, matrix);
|
||||
}
|
||||
|
||||
let positionUnitMap = this._linkPositionMap.get(unitId);
|
||||
if (!positionUnitMap) {
|
||||
positionUnitMap = new Map();
|
||||
this._linkPositionMap.set(unitId, positionUnitMap);
|
||||
}
|
||||
|
||||
let positionSubUnitMap = positionUnitMap.get(subUnitId);
|
||||
if (!positionSubUnitMap) {
|
||||
positionSubUnitMap = new Map();
|
||||
positionUnitMap.set(subUnitId, positionSubUnitMap);
|
||||
}
|
||||
return {
|
||||
matrix,
|
||||
positionMap: positionSubUnitMap,
|
||||
};
|
||||
}
|
||||
|
||||
addHyperLink(unitId: string, subUnitId: string, link: ISheetHyperLink) {
|
||||
const { matrix, positionMap } = this._ensureMap(unitId, subUnitId);
|
||||
matrix.setValue(link.row, link.column, link);
|
||||
positionMap.set(link.id, { row: link.row, column: link.column, link });
|
||||
this._linkUpdate$.next({
|
||||
unitId,
|
||||
subUnitId,
|
||||
payload: link,
|
||||
type: 'add',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
updateHyperLink(
|
||||
unitId: string,
|
||||
subUnitId: string,
|
||||
id: string,
|
||||
payload: Partial<ICellLinkContent>,
|
||||
silent = false
|
||||
) {
|
||||
const { matrix, positionMap } = this._ensureMap(unitId, subUnitId);
|
||||
const position = positionMap.get(id);
|
||||
if (!position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const link = matrix.getValue(position.row, position.column);
|
||||
if (!link) {
|
||||
return true;
|
||||
}
|
||||
Object.assign(link, payload);
|
||||
|
||||
this._linkUpdate$.next({
|
||||
unitId,
|
||||
subUnitId,
|
||||
payload: {
|
||||
display: link.display,
|
||||
payload: link.payload,
|
||||
},
|
||||
id,
|
||||
type: 'update',
|
||||
silent,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
updateHyperLinkRef(unitId: string, subUnitId: string, id: string, payload: { row: number; column: number }, silent = false) {
|
||||
const { matrix, positionMap } = this._ensureMap(unitId, subUnitId);
|
||||
const position = positionMap.get(id);
|
||||
if (!position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let link = matrix.getValue(position.row, position.column);
|
||||
if (!link || link.id !== id) {
|
||||
link = position.link;
|
||||
} else {
|
||||
matrix.realDeleteValue(position.row, position.column);
|
||||
}
|
||||
Object.assign(link, payload);
|
||||
positionMap.set(id, { ...payload, link });
|
||||
matrix.setValue(payload.row, payload.column, link);
|
||||
this._linkUpdate$.next({
|
||||
unitId,
|
||||
subUnitId,
|
||||
payload,
|
||||
id,
|
||||
type: 'updateRef',
|
||||
silent,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
removeHyperLink(unitId: string, subUnitId: string, id: string) {
|
||||
const { matrix, positionMap } = this._ensureMap(unitId, subUnitId);
|
||||
const position = positionMap.get(id);
|
||||
if (!position) {
|
||||
return false;
|
||||
}
|
||||
positionMap.delete(id);
|
||||
const link = matrix.getValue(position.row, position.column);
|
||||
if (link && link.id === id) {
|
||||
matrix.realDeleteValue(position.row, position.column);
|
||||
}
|
||||
|
||||
this._linkUpdate$.next({
|
||||
unitId,
|
||||
subUnitId,
|
||||
payload: position.link,
|
||||
type: 'remove',
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getHyperLink(unitId: string, subUnitId: string, id: string) {
|
||||
const { matrix, positionMap } = this._ensureMap(unitId, subUnitId);
|
||||
const position = positionMap.get(id);
|
||||
if (!position) {
|
||||
return undefined;
|
||||
}
|
||||
return matrix.getValue(position.row, position.column);
|
||||
}
|
||||
|
||||
getHyperLinkByLocation(unitId: string, subUnitId: string, row: number, column: number): ISheetHyperLink | undefined {
|
||||
const { matrix } = this._ensureMap(unitId, subUnitId);
|
||||
return matrix.getValue(row, column)!;
|
||||
}
|
||||
|
||||
getHyperLinkByLocationSync(unitId: string, subUnitId: string, row: number, column: number) {
|
||||
const { matrix } = this._ensureMap(unitId, subUnitId);
|
||||
const workbook = this._univerInstanceService.getUnit<Workbook>(unitId, UniverInstanceType.UNIVER_SHEET);
|
||||
const cell = workbook?.getSheetBySheetId(subUnitId)?.getCellRaw(row, column);
|
||||
const cellValueStr = (cell?.v ?? cell?.p?.body?.dataStream.slice(0, -2) ?? '').toString();
|
||||
const link = matrix.getValue(row, column);
|
||||
|
||||
if (!link) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...link,
|
||||
display: cellValueStr,
|
||||
};
|
||||
}
|
||||
|
||||
getSubUnit(unitId: string, subUnitId: string) {
|
||||
const { matrix } = this._ensureMap(unitId, subUnitId);
|
||||
|
||||
const links: ISheetHyperLink[] = [];
|
||||
matrix.forValue((row, col, value) => {
|
||||
if (value) {
|
||||
links.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
getUnit(unitId: string) {
|
||||
const unitMap = this._linkMap.get(unitId);
|
||||
if (!unitMap) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(unitMap.keys()).map((subUnitId) => {
|
||||
const links = this.getSubUnit(unitId, subUnitId);
|
||||
return {
|
||||
unitId,
|
||||
subUnitId,
|
||||
links,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
deleteUnit(unitId: string) {
|
||||
const links = this.getUnit(unitId);
|
||||
this._linkMap.delete(unitId);
|
||||
this._linkPositionMap.delete(unitId);
|
||||
|
||||
this._linkUpdate$.next({
|
||||
type: 'unload',
|
||||
unitId,
|
||||
unitLinks: links,
|
||||
});
|
||||
}
|
||||
|
||||
getAll() {
|
||||
const unitIds = Array.from(this._linkMap.keys());
|
||||
return unitIds.map((unitId) => this.getUnit(unitId));
|
||||
}
|
||||
}
|
||||
75
packages/sheets-hyper-link/src/plugin.ts
Normal file
75
packages/sheets-hyper-link/src/plugin.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 { IUniverSheetsHyperLinkConfig } from './config/config';
|
||||
import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_HYPER_LINK_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { SheetsHyperLinkRefRangeController } from './controllers/ref-range.controller';
|
||||
import { SheetsHyperLinkRemoveSheetController } from './controllers/remove-sheet.controller';
|
||||
import { SheetsHyperLinkRichTextRefRangeController } from './controllers/rich-text-ref-range.controller';
|
||||
import { SheetHyperLinkSetRangeController } from './controllers/set-range.controller';
|
||||
import { SheetsHyperLinkController } from './controllers/sheet-hyper-link.controller';
|
||||
import { HyperLinkModel } from './models/hyper-link.model';
|
||||
import { SheetsHyperLinkParserService } from './services/parser.service';
|
||||
import { SHEET_HYPER_LINK_PLUGIN } from './types/const';
|
||||
|
||||
@DependentOn(UniverFormulaEnginePlugin, UniverSheetsPlugin)
|
||||
export class UniverSheetsHyperLinkPlugin extends Plugin {
|
||||
static override pluginName = SHEET_HYPER_LINK_PLUGIN;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsHyperLinkConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Manage the plugin configuration.
|
||||
const { ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
this._configService.setConfig(SHEETS_HYPER_LINK_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
registerDependencies(this._injector, [
|
||||
[HyperLinkModel],
|
||||
[SheetsHyperLinkParserService],
|
||||
[SheetsHyperLinkController],
|
||||
[SheetsHyperLinkRefRangeController],
|
||||
[SheetHyperLinkSetRangeController],
|
||||
[SheetsHyperLinkRemoveSheetController],
|
||||
[SheetsHyperLinkRichTextRefRangeController],
|
||||
|
||||
]);
|
||||
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsHyperLinkRefRangeController],
|
||||
[SheetsHyperLinkController],
|
||||
[SheetHyperLinkSetRangeController],
|
||||
[SheetsHyperLinkRemoveSheetController],
|
||||
[SheetsHyperLinkRichTextRefRangeController],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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 { IRange, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import {
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
LocaleType,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { DefinedNamesService, IDefinedNamesService } from '@univerjs/engine-formula';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { ERROR_RANGE } from '../../types/const';
|
||||
import { SheetHyperLinkType } from '../../types/enums/hyper-link-type';
|
||||
import { SheetsHyperLinkParserService } from '../parser.service';
|
||||
|
||||
function createWorkbookData(unitId = 'book-1', sheetId = 'sheet-1'): IWorkbookData {
|
||||
return {
|
||||
id: unitId,
|
||||
appVersion: '3.0.0-alpha',
|
||||
locale: LocaleType.EN_US,
|
||||
name: 'Workbook',
|
||||
sheetOrder: [sheetId],
|
||||
styles: {},
|
||||
sheets: {
|
||||
[sheetId]: {
|
||||
id: sheetId,
|
||||
name: 'Sheet1',
|
||||
cellData: {
|
||||
0: {
|
||||
0: { v: 'Origin' },
|
||||
},
|
||||
},
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('SheetsHyperLinkParserService', () => {
|
||||
let univer: Univer;
|
||||
let service: SheetsHyperLinkParserService;
|
||||
let localeService: LocaleService;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([IDefinedNamesService, { useClass: DefinedNamesService }]);
|
||||
injector.add([SheetsHyperLinkParserService]);
|
||||
|
||||
localeService = injector.get(LocaleService);
|
||||
localeService.load({
|
||||
[LocaleType.EN_US]: {
|
||||
'sheets-hyper-link': {
|
||||
message: {
|
||||
refError: 'Invalid Range',
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
});
|
||||
localeService.setLocale(LocaleType.EN_US);
|
||||
|
||||
service = injector.get(SheetsHyperLinkParserService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('should build hyperlinks for sheet, defined name and range object', () => {
|
||||
const range: IRange = {
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
};
|
||||
|
||||
expect(service.buildHyperLink(null, 'sheet-1')).toBe('#gid=sheet-1');
|
||||
expect(service.buildHyperLink('name-1')).toBe('#rangeid=name-1');
|
||||
expect(service.buildHyperLink(range, 'sheet-1')).toBe('#gid=sheet-1&range=A1:B2');
|
||||
});
|
||||
|
||||
it('should parse external URLs as url links', () => {
|
||||
const url = 'https://univer.ai';
|
||||
|
||||
expect(service.parseHyperLink(url)).toEqual({
|
||||
type: SheetHyperLinkType.URL,
|
||||
name: url,
|
||||
url,
|
||||
searchObj: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse range links from the focused workbook when unitid is absent', () => {
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
const univerInstanceService = univer.__getInjector().get(IUniverInstanceService);
|
||||
univerInstanceService.focusUnit(workbook.getUnitId());
|
||||
|
||||
const url = '#gid=sheet-1&range=A1:B2';
|
||||
expect(service.parseHyperLink(url)).toEqual({
|
||||
type: SheetHyperLinkType.RANGE,
|
||||
name: 'Sheet1!A1:B2',
|
||||
url,
|
||||
searchObj: {
|
||||
gid: 'sheet-1',
|
||||
range: 'A1:B2',
|
||||
rangeid: '',
|
||||
unitid: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse defined name links through the real defined names service', () => {
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
const definedNamesService = univer.__getInjector().get(IDefinedNamesService);
|
||||
|
||||
definedNamesService.registerDefinedName(workbook.getUnitId(), {
|
||||
id: 'name-1',
|
||||
name: 'Total',
|
||||
formulaOrRefString: 'Sheet1!A1:B2',
|
||||
});
|
||||
|
||||
const url = `#gid=sheet-1&rangeid=name-1&unitid=${workbook.getUnitId()}`;
|
||||
expect(service.parseHyperLink(url)).toEqual({
|
||||
type: SheetHyperLinkType.DEFINE_NAME,
|
||||
name: 'Sheet1!A1:B2',
|
||||
url,
|
||||
searchObj: {
|
||||
gid: 'sheet-1',
|
||||
range: '',
|
||||
rangeid: 'name-1',
|
||||
unitid: workbook.getUnitId(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse defined name links from the focused workbook when unitid is absent', () => {
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
const univerInstanceService = univer.__getInjector().get(IUniverInstanceService);
|
||||
const definedNamesService = univer.__getInjector().get(IDefinedNamesService);
|
||||
|
||||
univerInstanceService.focusUnit(workbook.getUnitId());
|
||||
definedNamesService.registerDefinedName(workbook.getUnitId(), {
|
||||
id: 'name-1',
|
||||
name: 'Total',
|
||||
formulaOrRefString: 'Sheet1!A1:B2',
|
||||
});
|
||||
|
||||
const url = service.buildHyperLink('name-1');
|
||||
expect(service.parseHyperLink(url)).toEqual({
|
||||
type: SheetHyperLinkType.DEFINE_NAME,
|
||||
name: 'Sheet1!A1:B2',
|
||||
url,
|
||||
searchObj: {
|
||||
gid: '',
|
||||
range: '',
|
||||
rangeid: 'name-1',
|
||||
unitid: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse sheet links by sheet id', () => {
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
|
||||
const url = `#gid=sheet-1&unitid=${workbook.getUnitId()}`;
|
||||
expect(service.parseHyperLink(url)).toEqual({
|
||||
type: SheetHyperLinkType.SHEET,
|
||||
name: 'Sheet1',
|
||||
url,
|
||||
searchObj: {
|
||||
gid: 'sheet-1',
|
||||
range: '',
|
||||
rangeid: '',
|
||||
unitid: workbook.getUnitId(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return invalid for missing workbook, invalid range and missing defined names', () => {
|
||||
univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
|
||||
expect(service.parseHyperLink('#gid=sheet-1&range=A1&unitid=missing').type).toBe(SheetHyperLinkType.INVALID);
|
||||
expect(service.parseHyperLink(`#gid=sheet-1&range=${ERROR_RANGE}&unitid=book-1`).type).toBe(SheetHyperLinkType.INVALID);
|
||||
expect(service.parseHyperLink('#gid=sheet-1&rangeid=missing&unitid=book-1')).toEqual({
|
||||
type: SheetHyperLinkType.INVALID,
|
||||
name: 'Invalid Range',
|
||||
url: '#gid=sheet-1&rangeid=missing&unitid=book-1',
|
||||
searchObj: {
|
||||
gid: 'sheet-1',
|
||||
range: '',
|
||||
rangeid: 'missing',
|
||||
unitid: 'book-1',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
146
packages/sheets-hyper-link/src/services/parser.service.ts
Normal file
146
packages/sheets-hyper-link/src/services/parser.service.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 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 { IRange, Nullable, Workbook } from '@univerjs/core';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { Inject, isValidRange, IUniverInstanceService, LocaleService, UniverInstanceType } from '@univerjs/core';
|
||||
import {
|
||||
deserializeRangeWithSheet,
|
||||
IDefinedNamesService,
|
||||
serializeRange,
|
||||
serializeRangeWithSheet,
|
||||
} from '@univerjs/engine-formula';
|
||||
import { ERROR_RANGE } from '../types/const';
|
||||
import { SheetHyperLinkType } from '../types/enums/hyper-link-type';
|
||||
|
||||
export interface ISheetUrlParams {
|
||||
gid?: string;
|
||||
range?: string;
|
||||
rangeid?: string;
|
||||
unitid?: string;
|
||||
}
|
||||
|
||||
export interface ISheetHyperLinkInfo {
|
||||
type: SheetHyperLinkType;
|
||||
name: string;
|
||||
url: string;
|
||||
searchObj: Nullable<ISheetUrlParams>;
|
||||
}
|
||||
|
||||
export class SheetsHyperLinkParserService {
|
||||
constructor(
|
||||
@IUniverInstanceService private _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(LocaleService) private _localeService: LocaleService,
|
||||
@IDefinedNamesService private _definedNamesService: IDefinedNamesService
|
||||
) {}
|
||||
|
||||
buildHyperLink(defineNameId: string): string;
|
||||
buildHyperLink(range: IRange | null, sheetId: string): string;
|
||||
buildHyperLink(defineNameIdOrRange: string | IRange | null, sheetId?: string): string {
|
||||
if (typeof defineNameIdOrRange === 'string') {
|
||||
return `#${SheetHyperLinkType.DEFINE_NAME}=${defineNameIdOrRange}`;
|
||||
}
|
||||
|
||||
let result = `#${SheetHyperLinkType.SHEET}=${sheetId}`;
|
||||
|
||||
if (defineNameIdOrRange) {
|
||||
result += `&${SheetHyperLinkType.RANGE}=${serializeRange(defineNameIdOrRange)}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
parseHyperLink(urlStr: string): ISheetHyperLinkInfo {
|
||||
if (urlStr.startsWith('#')) {
|
||||
const search = new URLSearchParams(urlStr.slice(1));
|
||||
const searchObj: ISheetUrlParams = {
|
||||
gid: search.get('gid') ?? '',
|
||||
range: search.get('range') ?? '',
|
||||
rangeid: search.get('rangeid') ?? '',
|
||||
unitid: search.get('unitid') ?? '',
|
||||
};
|
||||
const urlInfo = this._getURLName(searchObj);
|
||||
|
||||
return {
|
||||
type: urlInfo.type,
|
||||
name: urlInfo.name,
|
||||
url: urlStr,
|
||||
searchObj,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: SheetHyperLinkType.URL,
|
||||
name: urlStr,
|
||||
url: urlStr,
|
||||
searchObj: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _getURLName(params: ISheetUrlParams) {
|
||||
const { gid, range, rangeid, unitid } = params;
|
||||
const workbook = unitid ?
|
||||
this._univerInstanceService.getUnit<Workbook>(unitid, UniverInstanceType.UNIVER_SHEET)
|
||||
: this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
const invalidLink = {
|
||||
type: SheetHyperLinkType.INVALID,
|
||||
name: this._localeService.t<LocaleKey>('sheets-hyper-link.message.refError'),
|
||||
};
|
||||
|
||||
if (!workbook) {
|
||||
return invalidLink;
|
||||
}
|
||||
|
||||
const sheet = gid ? workbook.getSheetBySheetId(gid) : workbook.getActiveSheet();
|
||||
const sheetName = sheet?.getName() ?? '';
|
||||
|
||||
if (range) {
|
||||
if (!sheet) return invalidLink;
|
||||
const rangeObj = deserializeRangeWithSheet(range).range;
|
||||
if (isValidRange(rangeObj, sheet) && range !== ERROR_RANGE) {
|
||||
return {
|
||||
type: SheetHyperLinkType.RANGE,
|
||||
name: serializeRangeWithSheet(sheetName, rangeObj),
|
||||
};
|
||||
}
|
||||
return invalidLink;
|
||||
}
|
||||
|
||||
if (rangeid) {
|
||||
const range = this._definedNamesService.getValueById(workbook.getUnitId(), rangeid);
|
||||
if (range) {
|
||||
return {
|
||||
type: SheetHyperLinkType.DEFINE_NAME,
|
||||
name: range.formulaOrRefString,
|
||||
};
|
||||
}
|
||||
return invalidLink;
|
||||
}
|
||||
|
||||
if (gid) {
|
||||
const worksheet = workbook.getSheetBySheetId(gid);
|
||||
if (worksheet) {
|
||||
return {
|
||||
type: SheetHyperLinkType.SHEET,
|
||||
name: worksheet.getName(),
|
||||
};
|
||||
}
|
||||
return invalidLink;
|
||||
}
|
||||
|
||||
return invalidLink;
|
||||
}
|
||||
}
|
||||
18
packages/sheets-hyper-link/src/types/const.ts
Normal file
18
packages/sheets-hyper-link/src/types/const.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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 const SHEET_HYPER_LINK_PLUGIN = 'SHEET_HYPER_LINK_PLUGIN';
|
||||
export const ERROR_RANGE = 'err';
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 enum SheetHyperLinkType {
|
||||
SHEET = 'gid',
|
||||
RANGE = 'range',
|
||||
DEFINE_NAME = 'rangeid',
|
||||
INVALID = 'invalid',
|
||||
URL = 'url',
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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 interface ICellLinkContent {
|
||||
/**
|
||||
* #gid=sheet1&range=A1
|
||||
* sub sheet: #gid=sheet1
|
||||
* namedRange: #rangeid=123
|
||||
* http: https://baidu.com
|
||||
* file: file://a.xlsx
|
||||
*/
|
||||
payload: string;
|
||||
/**
|
||||
* only for notify, if you wan't to read the display of text, use cell-value of cell-matrix instead
|
||||
*/
|
||||
display?: string;
|
||||
}
|
||||
|
||||
export interface ISheetHyperLink extends ICellLinkContent {
|
||||
/**
|
||||
* unique id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* row of link
|
||||
*/
|
||||
row: number;
|
||||
/**
|
||||
* col of link
|
||||
*/
|
||||
column: number;
|
||||
}
|
||||
8
packages/sheets-hyper-link/tsconfig.json
Normal file
8
packages/sheets-hyper-link/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
18
packages/sheets-hyper-link/tsconfig.node.json
Normal file
18
packages/sheets-hyper-link/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/sheets-hyper-link/vitest.config.ts
Normal file
3
packages/sheets-hyper-link/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