feat(facade): add comment & rich-text & data-validation & edit API (#4423)

Co-authored-by: Wenzhao Hu <wzhudev@gmail.com>
Co-authored-by: jocs <ransixi@gmail.com>
This commit is contained in:
zhangw
2025-01-07 10:31:50 +08:00
committed by GitHub
co-authored by Wenzhao Hu jocs
parent bf69eab2ea
commit cff224fb24
59 changed files with 6002 additions and 265 deletions
+1
View File
@@ -58,6 +58,7 @@ import '@univerjs/sheets-thread-comment/facade';
import '@univerjs/sheets-conditional-formatting/facade';
import '@univerjs/sheets-find-replace/facade';
import '@univerjs/sheets-drawing-ui/facade';
import '@univerjs/sheets-zen-editor/facade';
import '../global.css';
/* eslint-disable-next-line node/prefer-global/process */
+7
View File
@@ -20,3 +20,10 @@ export class CustomCommandExecutionError extends Error {
this.name = 'CustomCommandExecutionError';
}
}
export class CanceledError extends Error {
constructor() {
super('Canceled by facade');
this.name = 'CanceledError';
}
}
File diff suppressed because it is too large Load Diff
@@ -20,7 +20,7 @@ import { addDrawing } from './drawings';
import { changeParagraphBulletNestLevel, setParagraphBullet, switchParagraphBullet, toggleChecklistParagraph } from './paragraph';
import { fromPlainText, getPlainText, isEmptyDocument } from './parse';
import { isSegmentIntersects, makeSelection, normalizeSelection } from './selection';
import { addCustomRangeTextX, deleteCustomRangeTextX, deleteSelectionTextX, replaceSelectionTextX } from './text-x-utils';
import { addCustomRangeTextX, deleteCustomRangeTextX, deleteSelectionTextX, replaceSelectionTextX, retainSelectionTextX } from './text-x-utils';
export class BuildTextUtils {
static customRange = {
@@ -41,6 +41,7 @@ export class BuildTextUtils {
makeSelection,
normalizeSelection,
delete: deleteSelectionTextX,
retain: retainSelectionTextX,
};
static range = {
@@ -14,13 +14,12 @@
* limitations under the License.
*/
import type { IAccessor } from '@wendellhu/redi';
import type { ITextRange, ITextRangeParam } from '../../../../sheets/typedef';
import type { CustomRangeType, IDocumentBody } from '../../../../types/interfaces';
import type { DocumentDataModel } from '../../document-data-model';
import type { TextXAction } from '../action-types';
import type { TextXSelection } from '../text-x';
import { type Nullable, UpdateDocsAttributeType } from '../../../../shared';
import { type Nullable, Tools, UpdateDocsAttributeType } from '../../../../shared';
import { textDiff } from '../../../../shared/text-diff';
import { TextXActionType } from '../action-types';
import { TextX } from '../text-x';
@@ -34,7 +33,7 @@ export interface IDeleteCustomRangeParam {
insert?: Nullable<IDocumentBody>;
}
export function deleteCustomRangeTextX(accessor: IAccessor, params: IDeleteCustomRangeParam) {
export function deleteCustomRangeTextX(params: IDeleteCustomRangeParam) {
const { rangeId, segmentId, documentDataModel, insert } = params;
const range = documentDataModel.getSelfOrHeaderFooterModel(segmentId).getBody()?.customRanges?.find((r) => r.rangeId === rangeId);
if (!range) {
@@ -234,6 +233,34 @@ export function deleteSelectionTextX(
return dos;
}
export function retainSelectionTextX(selections: ITextRange[], body: IDocumentBody, memoryCursor: number = 0) {
const dos: Array<TextXAction> = [];
let cursor = memoryCursor;
selections.forEach((selection) => {
const { startOffset, endOffset } = selection;
if (startOffset > cursor) {
dos.push({
t: TextXActionType.RETAIN,
len: startOffset - cursor,
});
cursor = startOffset;
}
if (endOffset > cursor) {
dos.push({
t: TextXActionType.RETAIN,
len: endOffset - cursor,
body: {
...Tools.deepClone(body),
dataStream: '',
},
});
cursor = endOffset;
}
});
return dos;
}
export interface IReplaceSelectionTextXParams {
/**
* range to be replaced.
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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 } from '../docs';
import { Inject, Injector } from '../common/di';
import { FBaseInitialable } from './f-base';
export class FDoc extends FBaseInitialable {
constructor(
protected doc: DocumentDataModel,
@Inject(Injector) _injector: Injector
) {
super(_injector);
}
}
+73
View File
@@ -15,7 +15,9 @@
*/
import { UniverInstanceType } from '../common/unit';
import { CommandType } from '../services/command/command.service';
import { LifecycleStages } from '../services/lifecycle/lifecycle';
import { BaselineOffset, BooleanNumber, HorizontalAlign, TextDecoration, TextDirection, VerticalAlign } from '../types/enum';
import { DataValidationErrorStyle } from '../types/enum/data-validation-error-style';
import { DataValidationOperator } from '../types/enum/data-validation-operator';
import { DataValidationRenderMode } from '../types/enum/data-validation-render-mode';
@@ -24,6 +26,7 @@ import { DataValidationType } from '../types/enum/data-validation-type';
export class FEnum {
static _instance: FEnum | null;
static get() {
if (this._instance) {
return this._instance;
@@ -57,31 +60,101 @@ export class FEnum {
}
}
/**
* Defines different types of Univer instances
*/
get UniverInstanceType() {
return UniverInstanceType;
}
/**
* Represents different stages in the lifecycle
*/
get LifecycleStages() {
return LifecycleStages;
}
/**
* Different types of data validation
*/
get DataValidationType() {
return DataValidationType;
}
/**
* Different error display styles
*/
get DataValidationErrorStyle() {
return DataValidationErrorStyle;
}
/**
* Different validation rendering modes
*/
get DataValidationRenderMode() {
return DataValidationRenderMode;
}
/**
* Different validation operators
*/
get DataValidationOperator() {
return DataValidationOperator;
}
/**
* Different validation states
*/
get DataValidationStatus() {
return DataValidationStatus;
}
/**
* Different types of commands
*/
get CommandType() {
return CommandType;
}
/**
* Different baseline offsets for text baseline positioning
*/
get BaselineOffset() {
return BaselineOffset;
}
/**
* Boolean number representations
*/
get BooleanNumber() {
return BooleanNumber;
}
/**
* Different horizontal text alignment options
*/
get HorizontalAlign() {
return HorizontalAlign;
}
/**
* Different text decoration styles
*/
get TextDecoration() {
return TextDecoration;
}
/**
* Different text direction options
*/
get TextDirection() {
return TextDirection;
}
/**
* Different vertical text alignment options
*/
get VerticalAlign() {
return VerticalAlign;
}
}
+197 -21
View File
@@ -15,31 +15,73 @@
*/
import type { UniverInstanceType } from '../common/unit';
import type { CommandType } from '../services/command/command.service';
import type { LifecycleStages } from '../services/lifecycle/lifecycle';
import type { IWorkbookData } from '../sheets/typedef';
import type { IDocumentData } from '../types/interfaces';
import type { FDoc } from './f-doc';
export interface ISheetCreateParam {
unitId: string;
type: UniverInstanceType.UNIVER_SHEET;
data: IWorkbookData;
}
export interface IDocumentCreateParam {
unitId: string;
type: UniverInstanceType.UNIVER_DOC;
data: IDocumentData;
}
export interface ILifeCycleChangedParam {
stage: LifecycleStages;
}
/**
* Base interface for all event parameters
* @interface IEventBase
*/
export interface IEventBase {
/** Flag to cancel the event if supported */
cancel?: boolean;
}
export type IUnitCreateEvent = IEventBase & (ISheetCreateParam | IDocumentCreateParam);
export type ILifeCycleChangedEvent = IEventBase & ILifeCycleChangedParam;
/**
* Event interface triggered when a document is created
* @interface IDocCreatedParam
* @augments {IEventBase}
*/
export interface IDocCreatedParam extends IEventBase {
/** Unique identifier of the document unit */
unitId: string;
/** Type identifier for document instances */
type: UniverInstanceType.UNIVER_DOC;
/** The created document instance */
doc: FDoc;
/** Reference to the document unit */
unit: FDoc;
}
/**
* Event interface triggered when a document is disposed
* @interface IDocDisposedEvent
* @augments {IEventBase}
*/
export interface IDocDisposedEvent extends IEventBase {
/** Unique identifier of the disposed document unit */
unitId: string;
/** Type identifier for document instances */
unitType: UniverInstanceType.UNIVER_DOC;
/** Final state snapshot of the disposed document */
snapshot: IDocumentData;
}
/**
* Event interface for lifecycle stage changes
* @interface ILifeCycleChangedEvent
* @augments {IEventBase}
*/
export interface ILifeCycleChangedEvent extends IEventBase {
/** Current stage of the lifecycle */
stage: LifecycleStages;
}
/**
* Event interface for command execution
* @interface ICommandEvent
* @augments {IEventBase}
*/
export interface ICommandEvent extends IEventBase {
/** Parameters passed to the command */
params: any;
/** Unique identifier of the command */
id: string;
/** Type of the command */
type: CommandType;
}
export class FEventName {
static _instance: FEventName | null;
@@ -76,16 +118,150 @@ export class FEventName {
}
}
get UnitCreated() {
return 'UnitCreated' as const;
/**
* Event fired when a document is created
* @see {@link IDocCreatedParam}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.DocCreated, (params) => {
* const { unitId, type, doc, unit } = params;
* console.log('doc created', params);
* });
* ```
*/
get DocCreated() {
return 'DocCreated' as const;
}
/**
* Event fired when a document is disposed
* @see {@link IDocDisposedEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.DocDisposed, (params) => {
* const { unitId, unitType, snapshot } = params;
* console.log('doc disposed', params);
* });
* ```
*/
get DocDisposed() {
return 'DocDisposed' as const;
}
/**
* Event fired when life cycle is changed
* @see {@link ILifeCycleChangedEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.LifeCycleChanged, (params) => {
* const { stage } = params;
* console.log('life cycle changed', params);
* });
* ```
*/
get LifeCycleChanged() {
return 'LifeCycleChanged' as const;
}
/**
* Event fired when a redo command is executed
* @see {@link ICommandEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.Redo, (event) => {
* const { params, id, type } = event;
* console.log('command executed', event);
* });
* ```
*/
get Redo() {
return 'Redo' as const;
}
/**
* Event fired when an undo command is executed
* @see {@link ICommandEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.Undo, (event) => {
* const { params, id, type } = event;
* console.log('command executed', event);
* });
* ```
*/
get Undo() {
return 'Undo' as const;
}
/**
* Event fired before a redo command is executed
* @see {@link ICommandEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.BeforeRedo, (event) => {
* const { params, id, type } = event;
* console.log('command executed', event);
* });
* ```
*/
get BeforeRedo() {
return 'BeforeRedo' as const;
}
/**
* Event fired before an undo command is executed
* @see {@link ICommandEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.BeforeUndo, (event) => {
* const { params, id, type } = event;
* console.log('command executed', event);
* });
* ```
*/
get BeforeUndo() {
return 'BeforeUndo' as const;
}
/**
* Event fired when a command is executed
* @see {@link ICommandEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.CommandExecuted, (event) => {
* const { params, id, type } = event;
* console.log('command executed', event);
* });
* ```
*/
get CommandExecuted() {
return 'CommandExecuted' as const;
}
/**
* Event fired before a command is executed
* @see {@link ICommandEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.BeforeCommandExecute, (event) => {
* const { params, id, type } = event;
* console.log('command executed', event);
* });
* ```
*/
get BeforeCommandExecute() {
return 'BeforeCommandExecute' as const;
}
}
export interface IEventParamConfig {
// UnitCreated: IUnitCreateEvent;
LifeCycleChanged: ILifeCycleChangedEvent;
DocDisposed: IDocDisposedEvent;
DocCreated: IDocCreatedParam;
Redo: ICommandEvent;
Undo: ICommandEvent;
BeforeRedo: ICommandEvent;
BeforeUndo: ICommandEvent;
CommandExecuted: ICommandEvent;
BeforeCommandExecute: ICommandEvent;
}
+221 -19
View File
@@ -15,11 +15,16 @@
*/
import type { IDisposable } from '../common/di';
import type { DocumentDataModel } from '../docs';
import type { CommandListener, IExecutionOptions } from '../services/command/command.service';
import type { LifecycleStages } from '../services/lifecycle/lifecycle';
import type { IEventParamConfig } from './f-event';
import type { IDocumentData, IParagraphStyle, ITextDecoration, ITextStyle } from '../types/interfaces';
import type { ICommandEvent, IEventParamConfig } from './f-event';
import { Inject, Injector } from '../common/di';
import { CanceledError } from '../common/error';
import { Registry } from '../common/registry';
import { UniverInstanceType } from '../common/unit';
import { ParagraphStyleBuilder, ParagraphStyleValue, RichTextBuilder, RichTextValue, TextDecorationBuilder, TextStyleBuilder, TextStyleValue } from '../docs/data-model/rich-text-builder';
import { ICommandService } from '../services/command/command.service';
import { IUniverInstanceService } from '../services/instance/instance.service';
import { LifecycleService } from '../services/lifecycle/lifecycle.service';
@@ -28,10 +33,12 @@ import { ColorBuilder, toDisposable } from '../shared';
import { Univer } from '../univer';
import { FBaseInitialable } from './f-base';
import { FBlob } from './f-blob';
import { FDoc } from './f-doc';
import { FEnum } from './f-enum';
import { FEventName } from './f-event';
import { FHooks } from './f-hooks';
import { FUserManager } from './f-usermanager';
import { FUtil } from './f-util';
export class FUniver extends FBaseInitialable {
/**
@@ -45,9 +52,9 @@ export class FUniver extends FBaseInitialable {
return injector.createInstance(FUniver);
}
private _eventRegistry: Map<string, Registry<(param: any) => void>> = new Map();
protected _eventRegistry: Map<string, Registry<(param: any) => void>> = new Map();
private _ensureEventRegistry(event: string) {
protected _ensureEventRegistry(event: string) {
if (!this._eventRegistry.has(event)) {
this._eventRegistry.set(event, new Registry());
}
@@ -69,11 +76,111 @@ export class FUniver extends FBaseInitialable {
})
);
this.disposeWithMe(
this._commandService.beforeCommandExecuted((commandInfo) => {
if (
!this._eventRegistry.get(this.Event.BeforeRedo) &&
!this._eventRegistry.get(this.Event.BeforeUndo) &&
!this._eventRegistry.get(this.Event.BeforeCommandExecute)
) {
return;
}
const { id, type: propType, params } = commandInfo;
const type = propType!;
const eventParams: ICommandEvent = { id, type, params };
switch (commandInfo.id) {
case RedoCommand.id:
this.fireEvent(this.Event.BeforeRedo, eventParams);
break;
case UndoCommand.id:
this.fireEvent(this.Event.BeforeUndo, eventParams);
break;
default:
this.fireEvent(this.Event.BeforeCommandExecute, eventParams);
break;
}
if (eventParams.cancel) {
throw new CanceledError();
}
})
);
this.disposeWithMe(
this._commandService.onCommandExecuted((commandInfo) => {
if (
!this._eventRegistry.get(this.Event.Redo) &&
!this._eventRegistry.get(this.Event.Undo) &&
!this._eventRegistry.get(this.Event.CommandExecuted)
) {
return;
}
const { id, type: propType, params } = commandInfo;
const type = propType!;
const eventParams: ICommandEvent = { id, type, params };
switch (commandInfo.id) {
case RedoCommand.id:
this.fireEvent(this.Event.Redo, eventParams);
break;
case UndoCommand.id:
this.fireEvent(this.Event.Undo, eventParams);
break;
default:
this.fireEvent(this.Event.CommandExecuted, eventParams);
break;
}
})
);
this._initUnitEvent(this._injector);
this._injector.onDispose(() => {
this.dispose();
});
}
private _initUnitEvent(injector: Injector): void {
const univerInstanceService = injector.get(IUniverInstanceService);
this.disposeWithMe(
univerInstanceService.unitDisposed$.subscribe((unit) => {
if (!this._eventRegistry.get(this.Event.DocDisposed)) return;
if (unit.type === UniverInstanceType.UNIVER_DOC) {
this.fireEvent(this.Event.DocDisposed,
{
unitId: unit.getUnitId(),
unitType: unit.type,
snapshot: unit.getSnapshot() as IDocumentData,
}
);
}
})
);
this.disposeWithMe(
univerInstanceService.unitAdded$.subscribe((unit) => {
if (!this._eventRegistry.get(this.Event.DocCreated)) return;
if (unit.type === UniverInstanceType.UNIVER_DOC) {
const doc = unit as DocumentDataModel;
const docUnit = injector.createInstance(FDoc, doc);
this.fireEvent(this.Event.DocCreated,
{
unitId: unit.getUnitId(),
type: unit.type,
doc: docUnit,
unit: docUnit,
}
);
}
})
);
}
protected _eventListend(key: string) {
return this._eventRegistry.get(key);
}
/**
* Dispose the UniverSheet by the `unitId`. The UniverSheet would be unload from the application.
* @param unitId The unit id of the UniverSheet.
@@ -171,22 +278,6 @@ export class FUniver extends FBaseInitialable {
return this._injector.createInstance(FHooks);
}
/**
* Create a new blob.
* @returns {FBlob} The new blob instance
* @example
* ```ts
* const blob = univerAPI.newBlob();
* ```
*/
newBlob(): FBlob {
return this._injector.createInstance(FBlob);
}
newColor(): ColorBuilder {
return new ColorBuilder();
}
get Enum() {
return FEnum.get();
}
@@ -195,6 +286,10 @@ export class FUniver extends FBaseInitialable {
return FEventName.get();
}
get Util() {
return FUtil.get();
}
/**
* Add an event listener
* @param event key of event
@@ -232,6 +327,7 @@ export class FUniver extends FBaseInitialable {
/**
* Get the callback map corresponding to the event
* @param event
* @returns {number} The number of callbacks
*/
protected hasEventCallback(event: keyof IEventParamConfig): boolean {
@@ -242,4 +338,110 @@ export class FUniver extends FBaseInitialable {
getUserManager(): FUserManager {
return this._injector.createInstance(FUserManager);
}
/**
* Create a new blob.
* @returns {FBlob} The new blob instance
* @example
* ```ts
* const blob = univerApi.newBlob();
* ```
*/
newBlob(): FBlob {
return this._injector.createInstance(FBlob);
}
/**
* Create a new color.
* @returns {ColorBuilder} The new color instance
* @example
* ```ts
* const color = univerApi.newColor();
* ```
*/
newColor(): ColorBuilder {
return new ColorBuilder();
}
/**
* Create a new rich text.
* @param data
* @returns {RichTextBuilder} The new rich text instance
* @example
* ```ts
* const richText = univerApi.newRichText();
* ```
*/
newRichText(data?: IDocumentData): RichTextBuilder {
return RichTextBuilder.create(data);
}
/**
* Create a new rich text value.
* @param data - The rich text data
* @returns {RichTextValue} The new rich text value instance
* @example
* ```ts
* const richTextValue = univerApi.newRichTextValue();
* ```
*/
newRichTextValue(data: IDocumentData): RichTextValue {
return RichTextValue.create(data);
}
/**
* Create a new paragraph style.
* @param style - The paragraph style
* @returns {ParagraphStyleBuilder} The new paragraph style instance
* @example
* ```ts
* const paragraphStyle = univerApi.newParagraphStyle();
* ```
*/
newParagraphStyle(style?: IParagraphStyle): ParagraphStyleBuilder {
return ParagraphStyleBuilder.create(style);
}
/**
* Create a new paragraph style value.
* @param style - The paragraph style
* @returns {ParagraphStyleValue} The new paragraph style value instance
* @example
* ```ts
* const paragraphStyleValue = univerApi.newParagraphStyleValue();
* ```
*/
newParagraphStyleValue(style?: IParagraphStyle): ParagraphStyleValue {
return ParagraphStyleValue.create(style);
}
/**
* Create a new text style.
* @param style - The text style
* @returns {TextStyleBuilder} The new text style instance
* @example
* ```ts
* const textStyle = univerApi.newTextStyle();
* ```
*/
newTextStyle(style?: ITextStyle): TextStyleBuilder {
return TextStyleBuilder.create(style);
}
/**
* Create a new text style value.
* @param style - The text style
* @returns {TextStyleValue} The new text style value instance
* @example
* ```ts
* const textStyleValue = univerApi.newTextStyleValue();
* ```
*/
newTextStyleValue(style?: ITextStyle): TextStyleValue {
return TextStyleValue.create(style);
}
newTextDecoration(decoration?: ITextDecoration): TextDecorationBuilder {
return new TextDecorationBuilder(decoration);
}
}
+49 -1
View File
@@ -14,6 +14,54 @@
* limitations under the License.
*/
import { numfmt, Rectangle, Tools } from '../shared';
export class FUtil {
static Range = Range;
static _instance: FUtil | null;
static get() {
if (this._instance) {
return this._instance;
}
const instance = new FUtil();
this._instance = instance;
return instance;
}
static extend(source: any): void {
Object.getOwnPropertyNames(source.prototype).forEach((name) => {
if (name !== 'constructor') {
// @ts-ignore
this.prototype[name] = source.prototype[name];
}
});
Object.getOwnPropertyNames(source).forEach((name) => {
if (name !== 'prototype' && name !== 'name' && name !== 'length') {
// @ts-ignore
this[name] = source[name];
}
});
}
/**
* Rectangle utils, including range operations likes merge, subtract, split
*/
get rectangle() {
return Rectangle;
}
/**
* Number format utils, including parse and strigify about date, price, etc
*/
get numfmt() {
return numfmt;
}
/**
* common tools
*/
get tools() {
return Tools;
}
}
+3 -1
View File
@@ -33,7 +33,8 @@ export {
} from './common/const';
export * from './common/di';
export { shallowEqual } from './common/equal';
export { CustomCommandExecutionError } from './common/error';
export { ParagraphStyleBuilder, ParagraphStyleValue, RichTextBuilder, RichTextValue, TextDecorationBuilder, TextStyleBuilder, TextStyleValue } from './docs/data-model/rich-text-builder';
export { CanceledError, CustomCommandExecutionError } from './common/error';
export { throttle } from './common/function';
export type { IAsyncInterceptor, ICellInterceptor, IComposeInterceptors, IInterceptor, InterceptorHandler } from './common/interceptor';
export { AsyncInterceptorManager, composeInterceptors, createAsyncInterceptorKey, createInterceptorKey, InterceptorEffectEnum, InterceptorManager } from './common/interceptor';
@@ -46,6 +47,7 @@ export { FHooks } from './facade/f-hooks';
export { FBlob, type IFBlobSource } from './facade/f-blob';
export { FEventName, type IEventBase, type IEventParamConfig } from './facade/f-event';
export { FEnum } from './facade/f-enum';
export { FUtil } from './facade/f-util';
export { isNumeric, isSafeNumeric } from './common/number';
export { Registry, RegistryAsMap } from './common/registry';
export { requestImmediateMacroTask } from './common/request-immediate-macro-task';
+292 -48
View File
@@ -19,9 +19,31 @@ import { AbsoluteRefType, type IRange, type IRectLTRB, RANGE_TYPE } from '../she
import { mergeRanges, multiSubtractSingleRange, splitIntoGrid } from './range';
/**
* This class provides a set of methods to calculate `IRange`.
* This class provides a set of methods to calculate and manipulate rectangular ranges (IRange).
* A range represents a rectangular area in a grid, defined by start/end rows and columns.
* @example
* ```typescript
* // Example range representing cells from A1 to C3
* const range: IRange = {
* startRow: 0,
* startColumn: 0,
* endRow: 2,
* endColumn: 2,
* rangeType: RANGE_TYPE.NORMAL
* };
* ```
*/
export class Rectangle {
/**
* Creates a deep copy of an IRange object
* @param src
* @example
* ```typescript
* const original = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 };
* const copy = Rectangle.clone(original);
* // copy = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 }
* ```
*/
static clone(src: IRange): IRange {
if (src.rangeType !== undefined) {
return {
@@ -41,6 +63,17 @@ export class Rectangle {
};
}
/**
* Checks if two ranges are equal by comparing their properties
* @param src
* @param target
* @example
* ```typescript
* const range1 = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 };
* const range2 = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 };
* const areEqual = Rectangle.equals(range1, range2); // true
* ```
*/
static equals(src: IRange, target: IRange): boolean {
if (src == null || target == null) {
return false;
@@ -57,11 +90,16 @@ export class Rectangle {
}
/**
* Check intersects of normal range(RANGE_TYPE.NORMAL)
* For other types of ranges, please consider using the intersects method.
* Quickly checks if two normal ranges intersect. For specialized range types,
* use the intersects() method instead.
* @param rangeA
* @param rangeB
* @returns boolean
* @example
* ```typescript
* const range1 = { startRow: 0, startColumn: 0, endRow: 2, endColumn: 2 };
* const range2 = { startRow: 1, startColumn: 1, endRow: 3, endColumn: 3 };
* const doIntersect = Rectangle.simpleRangesIntersect(range1, range2); // true
* ```
*/
static simpleRangesIntersect(rangeA: IRange, rangeB: IRange): boolean {
const { startRow: startRowA, endRow: endRowA, startColumn: startColumnA, endColumn: endColumnA } = rangeA;
@@ -73,6 +111,25 @@ export class Rectangle {
return rowsOverlap && columnsOverlap;
}
/**
* Checks if two ranges intersect, handling special range types (ROW, COLUMN)
* @param src
* @param target
* @example
* ```typescript
* const rowRange = {
* startRow: 0, endRow: 2,
* startColumn: NaN, endColumn: NaN,
* rangeType: RANGE_TYPE.ROW
* };
* const colRange = {
* startRow: NaN, endRow: NaN,
* startColumn: 0, endColumn: 2,
* rangeType: RANGE_TYPE.COLUMN
* };
* const doIntersect = Rectangle.intersects(rowRange, colRange); // true
* ```
*/
static intersects(src: IRange, target: IRange): boolean {
if (src.rangeType === RANGE_TYPE.ROW && target.rangeType === RANGE_TYPE.COLUMN) {
return true;
@@ -110,8 +167,17 @@ export class Rectangle {
}
/**
*
* @deprecated use `getIntersectRange` instead. This method does not handle NaN and does not return the correct rangeType
* Gets the intersection range between two ranges
* @param src
* @param target
* @deprecated use `getIntersectRange` instead
* @example
* ```typescript
* const range1 = { startRow: 0, startColumn: 0, endRow: 2, endColumn: 2 };
* const range2 = { startRow: 1, startColumn: 1, endRow: 3, endColumn: 3 };
* const intersection = Rectangle.getIntersects(range1, range2);
* // intersection = { startRow: 1, startColumn: 1, endRow: 2, endColumn: 2 }
* ```
*/
static getIntersects(src: IRange, target: IRange): Nullable<IRange> {
const currentStartRow = src.startRow;
@@ -177,19 +243,17 @@ export class Rectangle {
};
}
// static subtract(src: IRange, target: IRange): Nullable<IRange[]> {
// const intersected = Rectangle.getIntersects(src, target);
// if (!intersected) {
// return [src];
// }
// const result: IRange[] = [];
// const { startRow, endRow, startColumn, endColumn } = intersected;
// const { startRow: srcStartRow, endRow: srcEndRow, startColumn: srcStartColumn, endColumn: srcEndColumn } = src;
// // subtract could result in eight pieces and these eight pieces and be merged to at most four pieces
// }
/**
* Checks if one range completely contains another range
* @param src
* @param target
* @example
* ```typescript
* const outer = { startRow: 0, startColumn: 0, endRow: 3, endColumn: 3 };
* const inner = { startRow: 1, startColumn: 1, endRow: 2, endColumn: 2 };
* const contains = Rectangle.contains(outer, inner); // true
* ```
*/
static contains(src: IRange, target: IRange): boolean {
return (
src.startRow <= target.startRow &&
@@ -199,6 +263,17 @@ export class Rectangle {
);
}
/**
* Checks if one range strictly contains another range (not equal)
* @param src
* @param target
* @example
* ```typescript
* const outer = { startRow: 0, startColumn: 0, endRow: 3, endColumn: 3 };
* const same = { startRow: 0, startColumn: 0, endRow: 3, endColumn: 3 };
* const realContains = Rectangle.realContain(outer, same); // false
* ```
*/
static realContain(src: IRange, target: IRange): boolean {
return (
Rectangle.contains(src, target) &&
@@ -209,6 +284,17 @@ export class Rectangle {
);
}
/**
* Creates a union range that encompasses all input ranges
* @param {...any} ranges
* @example
* ```typescript
* const range1 = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 };
* const range2 = { startRow: 2, startColumn: 2, endRow: 3, endColumn: 3 };
* const union = Rectangle.union(range1, range2);
* // union = { startRow: 0, startColumn: 0, endRow: 3, endColumn: 3 }
* ```
*/
static union(...ranges: IRange[]): IRange {
// TODO: range type may not be accurate
return ranges.reduce(
@@ -223,6 +309,23 @@ export class Rectangle {
);
}
/**
* Creates a union range considering special range types (ROW, COLUMN)
* @param {...any} ranges
* @example
* ```typescript
* const rowRange = {
* startRow: 0, endRow: 2,
* rangeType: RANGE_TYPE.ROW
* };
* const normalRange = {
* startRow: 1, startColumn: 1,
* endRow: 3, endColumn: 3
* };
* const union = Rectangle.realUnion(rowRange, normalRange);
* // Result will have NaN for columns due to ROW type
* ```
*/
static realUnion(...ranges: IRange[]): IRange {
const hasColRange = ranges.some((range) => range.rangeType === RANGE_TYPE.COLUMN);
const hasRowRange = ranges.some((range) => range.rangeType === RANGE_TYPE.ROW);
@@ -236,6 +339,18 @@ export class Rectangle {
};
}
/**
* Converts an absolute range to a relative range based on an origin range
* @param range
* @param originRange
* @example
* ```typescript
* const range = { startRow: 5, startColumn: 5, endRow: 7, endColumn: 7 };
* const origin = { startRow: 3, startColumn: 3, endRow: 8, endColumn: 8 };
* const relative = Rectangle.getRelativeRange(range, origin);
* // relative = { startRow: 2, startColumn: 2, endRow: 2, endColumn: 2 }
* ```
*/
static getRelativeRange = (range: IRange, originRange: IRange) =>
({
startRow: range.startRow - originRange.startRow,
@@ -244,41 +359,98 @@ export class Rectangle {
endColumn: range.endColumn - range.startColumn,
}) as IRange;
static getPositionRange = (relativeRange: IRange, originRange: IRange, absoluteRange?: IRange) => {
return ({
/**
* Converts a relative range back to an absolute range based on origin
* @param relativeRange
* @param originRange
* @param absoluteRange
* @example
* ```typescript
* const relative = { startRow: 2, startColumn: 2, endRow: 2, endColumn: 2 };
* const origin = { startRow: 3, startColumn: 3, endRow: 8, endColumn: 8 };
* const absolute = Rectangle.getPositionRange(relative, origin);
* // absolute = { startRow: 5, startColumn: 5, endRow: 7, endColumn: 7 }
* ```
*/
static getPositionRange = (relativeRange: IRange, originRange: IRange, absoluteRange?: IRange) =>
({
...(absoluteRange || {}),
startRow: absoluteRange ? ([AbsoluteRefType.ROW, AbsoluteRefType.ALL].includes(absoluteRange.startAbsoluteRefType || 0) ? absoluteRange.startRow : relativeRange.startRow + originRange.startRow) : (relativeRange.startRow + originRange.startRow),
endRow: absoluteRange ? ([AbsoluteRefType.ROW, AbsoluteRefType.ALL].includes(absoluteRange.endAbsoluteRefType || 0) ? absoluteRange.endRow : relativeRange.endRow + relativeRange.startRow + originRange.startRow) : (relativeRange.endRow + relativeRange.startRow + originRange.startRow),
startColumn: absoluteRange ? ([AbsoluteRefType.COLUMN, AbsoluteRefType.ALL].includes(absoluteRange.startAbsoluteRefType || 0) ? absoluteRange.startColumn : relativeRange.startColumn + originRange.startColumn) : (relativeRange.startColumn + originRange.startColumn),
endColumn: absoluteRange ? ([AbsoluteRefType.COLUMN, AbsoluteRefType.ALL].includes(absoluteRange.endAbsoluteRefType || 0) ? absoluteRange.endColumn : relativeRange.endColumn + relativeRange.startColumn + originRange.startColumn) : (relativeRange.endColumn + relativeRange.startColumn + originRange.startColumn),
}) as IRange;
};
static moveHorizontal = (range: IRange, step: number = 0, length: number = 0): IRange => ({
...range,
startColumn: range.startColumn + step,
endColumn: range.endColumn + step + length,
});
static moveVertical = (range: IRange, step: number = 0, length: number = 0): IRange => ({
...range,
startRow: range.startRow + step,
endRow: range.endRow + step + length,
});
static moveOffset = (range: IRange, offsetX: number, offsetY: number): IRange => ({
...range,
startRow: range.startRow + offsetY,
endRow: range.endRow + offsetY,
startColumn: range.startColumn + offsetX,
endColumn: range.endColumn + offsetX,
});
/**
* Subtract range2 from range1, the result is is horizontal first then vertical
* @param {IRange} range1 The source range
* @param {IRange} range2 The range to be subtracted
* @returns {IRange[]} Returns the array of ranges, which are the result not intersected with range1
* Moves a range horizontally by a specified step and optionally extends it
* @param range
* @param step
* @param length
* @example
* ```typescript
* const range = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 };
* const moved = Rectangle.moveHorizontal(range, 2, 1);
* // moved = { startRow: 0, startColumn: 2, endRow: 1, endColumn: 4 }
* ```
*/
static moveHorizontal = (range: IRange, step: number = 0, length: number = 0): IRange =>
({
...range,
startColumn: range.startColumn + step,
endColumn: range.endColumn + step + length,
});
/**
* Moves a range vertically by a specified step and optionally extends it
* @param range
* @param step
* @param length
* @example
* ```typescript
* const range = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 };
* const moved = Rectangle.moveVertical(range, 2, 1);
* // moved = { startRow: 2, startColumn: 0, endRow: 4, endColumn: 1 }
* ```
*/
static moveVertical = (range: IRange, step: number = 0, length: number = 0): IRange =>
({
...range,
startRow: range.startRow + step,
endRow: range.endRow + step + length,
});
/**
* Moves a range by specified offsets in both directions
* @param range
* @param offsetX
* @param offsetY
* @example
* ```typescript
* const range = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 };
* const moved = Rectangle.moveOffset(range, 2, 3);
* // moved = { startRow: 3, startColumn: 2, endRow: 4, endColumn: 3 }
* ```
*/
static moveOffset = (range: IRange, offsetX: number, offsetY: number): IRange =>
({
...range,
startRow: range.startRow + offsetY,
endRow: range.endRow + offsetY,
startColumn: range.startColumn + offsetX,
endColumn: range.endColumn + offsetX,
});
/**
* Subtracts one range from another, returning the remaining areas as separate ranges
* @param range1
* @param range2
* @example
* ```typescript
* const range1 = { startRow: 0, startColumn: 0, endRow: 3, endColumn: 3 };
* const range2 = { startRow: 1, startColumn: 1, endRow: 2, endColumn: 2 };
* const result = Rectangle.subtract(range1, range2);
* // Results in up to 4 ranges representing the non-overlapping areas
* ```
*/
static subtract(range1: IRange, range2: IRange): IRange[] {
// 如果没有交集,则返回 range1
@@ -342,18 +514,54 @@ export class Rectangle {
}
/**
* Combine smaller rectangles into larger ones
* Merges overlapping or adjacent ranges into larger ranges
* @param ranges
* @returns
* @example
* ```typescript
* const ranges = [
* { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 },
* { startRow: 1, startColumn: 1, endRow: 2, endColumn: 2 }
* ];
* const merged = Rectangle.mergeRanges(ranges);
* // Combines overlapping ranges into larger ones
* ```
*/
static mergeRanges(ranges: IRange[]): IRange[] {
return mergeRanges(ranges);
}
/**
* Splits overlapping ranges into a grid of non-overlapping ranges
* @param ranges
* @example
* ```typescript
* const ranges = [
* { startRow: 0, startColumn: 0, endRow: 2, endColumn: 2 },
* { startRow: 1, startColumn: 1, endRow: 3, endColumn: 3 }
* ];
* const grid = Rectangle.splitIntoGrid(ranges);
* // Splits into non-overlapping grid sections
* ```
*/
static splitIntoGrid(ranges: IRange[]): IRange[] {
return splitIntoGrid(ranges);
}
/**
* Subtracts multiple ranges from multiple ranges
* @param ranges1
* @param ranges2
* @example
* ```typescript
* const ranges1 = [{ startRow: 0, startColumn: 0, endRow: 3, endColumn: 3 }];
* const ranges2 = [
* { startRow: 1, startColumn: 1, endRow: 2, endColumn: 2 },
* { startRow: 2, startColumn: 2, endRow: 3, endColumn: 3 }
* ];
* const result = Rectangle.subtractMulti(ranges1, ranges2);
* // Returns remaining non-overlapping areas
* ```
*/
static subtractMulti(ranges1: IRange[], ranges2: IRange[]): IRange[] {
if (!ranges2.length) {
return ranges1;
@@ -367,6 +575,17 @@ export class Rectangle {
return res;
}
/**
* Checks if two rectangles defined by left, top, right, bottom coordinates intersect
* @param rect1
* @param rect2
* @example
* ```typescript
* const rect1 = { left: 0, top: 0, right: 10, bottom: 10 };
* const rect2 = { left: 5, top: 5, right: 15, bottom: 15 };
* const intersects = Rectangle.hasIntersectionBetweenTwoRect(rect1, rect2); // true
* ```
*/
static hasIntersectionBetweenTwoRect(rect1: IRectLTRB, rect2: IRectLTRB) {
if (
rect1.left > rect2.right || // rect1 在 rect2 右侧
@@ -380,6 +599,18 @@ export class Rectangle {
return true;
}
/**
* Gets the intersection area between two rectangles defined by LTRB coordinates
* @param rect1
* @param rect2
* @example
* ```typescript
* const rect1 = { left: 0, top: 0, right: 10, bottom: 10 };
* const rect2 = { left: 5, top: 5, right: 15, bottom: 15 };
* const intersection = Rectangle.getIntersectionBetweenTwoRect(rect1, rect2);
* // Returns { left: 5, top: 5, right: 10, bottom: 10, width: 5, height: 5 }
* ```
*/
static getIntersectionBetweenTwoRect(rect1: IRectLTRB, rect2: IRectLTRB) {
// 计算两个矩形的交集部分的坐标
const left = Math.max(rect1.left, rect2.left);
@@ -403,6 +634,19 @@ export class Rectangle {
} as Required<IRectLTRB>;
}
/**
* Sorts an array of ranges by startRow, then by startColumn
* @param ranges
* @example
* ```typescript
* const ranges = [
* { startRow: 1, startColumn: 0, endRow: 2, endColumn: 1 },
* { startRow: 0, startColumn: 0, endRow: 1, endColumn: 1 }
* ];
* const sorted = Rectangle.sort(ranges);
* // Ranges will be sorted by startRow first, then startColumn
* ```
*/
static sort(ranges: IRange[]) {
return ranges.sort((a, b) => a.startRow - b.startRow || a.startColumn - b.startColumn);
}
+13 -1
View File
@@ -17,9 +17,10 @@
import type { IObjectArrayPrimitiveType } from '../shared/object-matrix';
import type { Nullable } from '../shared/types';
import type { IStyleData } from '../types/interfaces';
import type { CustomData, IColumnData, IRange, IWorksheetData } from './typedef';
import { getArrayLength } from '../shared/object-matrix';
import { BooleanNumber } from '../types/enum';
import { type IColumnData, type IRange, type IWorksheetData, RANGE_TYPE } from './typedef';
import { RANGE_TYPE } from './typedef';
/**
* Manage configuration information of all columns, get column width, column length, set column width, etc.
@@ -226,4 +227,15 @@ export class ColumnManager {
this._columnData[columnPos] = create;
return create;
}
setCustomMetadata(index: number, custom: CustomData | undefined) {
const row = this.getColumn(index);
if (row) {
row.custom = custom;
}
}
getCustomMetadata(index: number): CustomData | undefined {
return this.getColumn(index)?.custom;
}
}
+13 -1
View File
@@ -16,10 +16,11 @@
import type { Nullable } from '../shared/types';
import type { IStyleData } from '../types/interfaces';
import type { CustomData, IRange, IRowData, IWorksheetData } from './typedef';
import type { SheetViewModel } from './view-model';
import { getArrayLength, type IObjectArrayPrimitiveType } from '../shared/object-matrix';
import { BooleanNumber } from '../types/enum';
import { type IRange, type IRowData, type IWorksheetData, RANGE_TYPE } from './typedef';
import { RANGE_TYPE } from './typedef';
/**
* Manage configuration information of all rows, get row height, row length, set row height, etc.
@@ -216,4 +217,15 @@ export class RowManager {
getSize(): number {
return getArrayLength(this._rowData);
}
setCustomMetadata(index: number, custom: CustomData | undefined) {
const row = this.getRow(index);
if (row) {
row.custom = custom;
}
}
getCustomMetadata(index: number): CustomData | undefined {
return this.getRow(index)?.custom;
}
}
+10
View File
@@ -74,6 +74,11 @@ export interface IWorkbookData {
* Resources of the Univer Sheet. It is used to store the data of other plugins.
*/
resources?: IResources;
/**
* User stored custom fields
*/
custom?: CustomData;
}
/**
@@ -143,6 +148,11 @@ export interface IWorksheetData {
gridlinesColor?: string;
rightToLeft: BooleanNumber;
/**
* User stored custom fields
*/
custom?: CustomData;
}
export type CustomData = Nullable<Record<string, any>>;
+17 -1
View File
@@ -17,7 +17,7 @@
import type { Observable } from 'rxjs';
import type { Nullable } from '../shared';
import type { IRangeType, IWorkbookData, IWorksheetData } from './typedef';
import type { CustomData, IRangeType, IWorkbookData, IWorksheetData } from './typedef';
import { BehaviorSubject, Subject } from 'rxjs';
import { UnitModel, UniverInstanceType } from '../common/unit';
import { ILogService } from '../services/log/log.service';
@@ -424,4 +424,20 @@ export class Workbook extends UnitModel<IWorkbookData, UniverInstanceType.UNIVER
// Active the first sheet.
this.ensureActiveSheet();
}
/**
* Get custom metadata of workbook
* @returns {CustomData | undefined} custom metadata
*/
getCustomMetadata(): CustomData | undefined {
return this._snapshot.custom;
}
/**
* Set custom metadata of workbook
* @param {CustomData | undefined} custom custom metadata
*/
setCustomMetadata(custom: CustomData | undefined) {
this._snapshot.custom = custom;
}
}
+17 -1
View File
@@ -18,7 +18,7 @@ import type { IInterceptor } from '../common/interceptor';
import type { IObjectMatrixPrimitiveType, Nullable } from '../shared';
import type { IDocumentData, IDocumentRenderConfig, IPaddingData, IStyleData, ITextRotation } from '../types/interfaces';
import type { Styles } from './styles';
import type { ICellData, ICellDataForSheetInterceptor, ICellDataWithSpanAndDisplay, IFreeze, IRange, ISelectionCell, IWorksheetData } from './typedef';
import type { CustomData, ICellData, ICellDataForSheetInterceptor, ICellDataWithSpanAndDisplay, IFreeze, IRange, ISelectionCell, IWorksheetData } from './typedef';
import { BuildTextUtils, DocumentDataModel } from '../docs';
import { convertTextRotation, getFontStyleString } from '../docs/data-model/utils';
import { composeStyles, ObjectMatrix, Tools } from '../shared';
@@ -1221,6 +1221,22 @@ export class Worksheet {
ignoreTextRotation: true,
});
}
/**
* Get custom metadata of worksheet
* @returns {CustomData | undefined} custom metadata
*/
getCustomMetadata(): CustomData | undefined {
return this._snapshot.custom;
}
/**
* Set custom metadata of workbook
* @param {CustomData | undefined} custom custom metadata
*/
setCustomMetadata(custom: CustomData | undefined) {
this._snapshot.custom = custom;
}
}
/**
@@ -31,10 +31,22 @@ import type { ThemeColorType } from '../enum/theme-color-type';
* Properties of text decoration
*/
export interface ITextDecoration {
s: BooleanNumber; // show
c?: BooleanNumber; // color is follow the font color. the default value is TRUE, it's also TRUE if it is undefined. the cl has no effect when `c` is TRUE.
cl?: IColorStyle; // color
t?: TextDecoration; // lineType
/**
* show
*/
s: BooleanNumber;
/**
* color is follow the font color. the default value is TRUE, it's also TRUE if it is undefined. the cl has no effect when `c` is TRUE.
*/
c?: BooleanNumber;
/**
* color
*/
cl?: IColorStyle;
/**
* lineType
*/
t?: TextDecoration;
}
/**
+1 -2
View File
@@ -20,8 +20,7 @@ export {
} from './common/util';
export { DataValidationResourceController } from './controllers/dv-resource.controller';
export { type DataValidationChangeSource, DataValidationModel, type IRuleChange } from './models/data-validation-model';
export { type DataValidationChangeSource, type DataValidationChangeType, DataValidationModel, type IRuleChange } from './models/data-validation-model';
export { UniverDataValidationPlugin } from './plugin';
export { DataValidatorRegistryScope, DataValidatorRegistryService } from './services/data-validator-registry.service';
export { TextLengthErrorTitleMap } from './types/const/operator-text-map';
@@ -21,7 +21,7 @@ import { debounceTime, Subject } from 'rxjs';
import { getRuleOptions, getRuleSetting } from '../common/util';
import { UpdateRuleType } from '../types/enum/update-rule-type';
type DataValidationChangeType = 'update' | 'add' | 'remove';
export type DataValidationChangeType = 'update' | 'add' | 'remove';
export type DataValidationChangeSource = 'command' | 'patched';
export interface IRuleChange {
@@ -158,7 +158,7 @@ export function deleteCustomRangeFactory(accessor: IAccessor, params: IDeleteCus
};
const jsonX = JSONX.getInstance();
const textX = BuildTextUtils.customRange.delete(accessor, {
const textX = BuildTextUtils.customRange.delete({
documentDataModel,
rangeId: params.rangeId,
insert,
+1
View File
@@ -75,6 +75,7 @@
"@univerjs/sheets-numfmt": "workspace:*",
"@univerjs/sheets-thread-comment": "workspace:*",
"@univerjs/sheets-ui": "workspace:*",
"@univerjs/sheets-zen-editor": "workspace:*",
"@univerjs/ui": "workspace:*"
},
"devDependencies": {
+1
View File
@@ -18,6 +18,7 @@ import '@univerjs/sheets/facade';
import '@univerjs/ui/facade';
import '@univerjs/docs-ui/facade';
import '@univerjs/sheets-ui/facade';
import '@univerjs/sheets-zen-editor/facade';
import '@univerjs/sheets-data-validation/facade';
import '@univerjs/engine-formula/facade';
import '@univerjs/sheets-filter/facade';
@@ -111,7 +111,6 @@ export interface IFindReplaceService {
/**
* Register a find replace provider to the service. The provider is the actual bearer to
* perform the find in different kinds of documents or different environments.
*
* @param provider the find replace provider
*/
registerFindReplaceProvider(provider: IFindReplaceProvider): IDisposable;
@@ -123,7 +122,6 @@ export interface IFindReplaceService {
/**
* Start a find & replace session.
*
* @returns execution result
*/
start(revealReplace?: boolean): boolean;
@@ -174,6 +172,10 @@ export interface IFindQuery extends Pick<
| 'matchesTheWholeCell'
> { }
/**
*
* @param statusUpdate
*/
function shouldStateUpdateTriggerResearch(statusUpdate: Partial<IFindReplaceState>): boolean {
if (typeof statusUpdate.findString !== 'undefined') return true;
if (typeof statusUpdate.inputtingFindString !== 'undefined') return true;
@@ -500,6 +502,9 @@ export interface IFindReplaceState {
findBy: FindBy;
}
/**
*
*/
export function createInitFindReplaceState(): IFindReplaceState {
return {
caseSensitive: false,
@@ -0,0 +1,324 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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 { DataValidationStatus, IDataValidationRule, IDataValidationRuleBase, IDataValidationRuleOptions, IEventBase, IRange, ISheetDataValidationRule } from '@univerjs/core';
import type { DataValidationChangeType, IRuleChange } from '@univerjs/data-validation';
import type { FWorkbook, FWorksheet } from '@univerjs/sheets/facade';
import type { FDataValidation } from './f-data-validation';
import { FEventName } from '@univerjs/core';
/**
* Event interface triggered when a data validation rule is changed
* @interface ISheetDataValidationChangedEvent
* @augments {IEventBase}
*/
export interface ISheetDataValidationChangedEvent extends IEventBase {
/** The source of the rule change */
origin: IRuleChange;
/** The worksheet containing the validation rule */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** Type of change made to the validation rule */
changeType: DataValidationChangeType;
/** The previous validation rule, if it exists */
oldRule?: IDataValidationRule;
/** The new or modified validation rule */
rule: FDataValidation;
}
/**
* Event interface triggered when a data validation status changes
* @interface ISheetDataValidatorStatusChangedEvent
* @augments {IEventBase}
*/
export interface ISheetDataValidatorStatusChangedEvent extends IEventBase {
/** The worksheet containing the validation */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** Row index of the validated cell */
row: number;
/** Column index of the validated cell */
column: number;
/** Current validation status */
status: DataValidationStatus;
/** The validation rule that was checked */
rule: FDataValidation;
}
/**
* Event interface triggered before adding a new data validation rule
* @interface IBeforeSheetDataValidationAddEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetDataValidationAddEvent extends IEventBase {
/** The worksheet to add the validation to */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** The validation rule to be added */
rule: ISheetDataValidationRule;
}
/**
* Event interface triggered before deleting a data validation rule
* @interface IBeforeSheetDataValidationDeleteEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetDataValidationDeleteEvent extends IEventBase {
/** The worksheet containing the validation */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** Unique identifier of the rule to be deleted */
ruleId: string;
/** The validation rule to be deleted */
rule: FDataValidation;
}
/**
* Event interface triggered before updating a data validation rule's criteria
* @interface IBeforeSheetDataValidationCriteriaUpdateEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetDataValidationCriteriaUpdateEvent extends IEventBase {
/** The worksheet containing the validation */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** Unique identifier of the rule to be updated */
ruleId: string;
/** The current validation rule */
rule: FDataValidation;
/** The new criteria to be applied */
newCriteria: IDataValidationRuleBase;
}
/**
* Event interface triggered before updating a data validation rule's ranges
* @interface IBeforeSheetDataValidationRangeUpdateEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetDataValidationRangeUpdateEvent extends IEventBase {
/** The worksheet containing the validation */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** Unique identifier of the rule to be updated */
ruleId: string;
/** The current validation rule */
rule: FDataValidation;
/** The new ranges to be applied */
newRanges: IRange[];
}
/**
* Event interface triggered before updating a data validation rule's options
* @interface IBeforeSheetDataValidationOptionsUpdateEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetDataValidationOptionsUpdateEvent extends IEventBase {
/** The worksheet containing the validation */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** Unique identifier of the rule to be updated */
ruleId: string;
/** The current validation rule */
rule: FDataValidation;
/** The new options to be applied */
newOptions: IDataValidationRuleOptions;
}
/**
* Event interface triggered before deleting all data validation rules
* @interface IBeforeSheetDataValidationDeleteAllEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetDataValidationDeleteAllEvent extends IEventBase {
/** The worksheet containing the validations */
worksheet: FWorksheet;
/** The workbook instance */
workbook: FWorkbook;
/** Array of all validation rules to be deleted */
rules: FDataValidation[];
}
export interface IDataValidationEventParamConfig {
SheetDataValidationChanged: ISheetDataValidationChangedEvent;
SheetDataValidatorStatusChanged: ISheetDataValidatorStatusChangedEvent;
BeforeSheetDataValidationAdd: IBeforeSheetDataValidationAddEvent;
BeforeSheetDataValidationDelete: IBeforeSheetDataValidationDeleteEvent;
BeforeSheetDataValidationDeleteAll: IBeforeSheetDataValidationDeleteAllEvent;
BeforeSheetDataValidationCriteriaUpdate: IBeforeSheetDataValidationCriteriaUpdateEvent;
BeforeSheetDataValidationRangeUpdate: IBeforeSheetDataValidationRangeUpdateEvent;
BeforeSheetDataValidationOptionsUpdate: IBeforeSheetDataValidationOptionsUpdateEvent;
}
interface IDataValidationEvent {
/**
* Event fired when a rule is added, deleted, or modified
* @see {@link ISheetDataValidationChangedEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.SheetDataValidationChanged, (event) => {
* const { worksheet, workbook, changeType, oldRule, rule } = event;
* console.log(event);
* });
* ```
*/
readonly SheetDataValidationChanged: 'SheetDataValidationChanged';
/**
* Event fired when a cell validator status is changed
* @see {@link ISheetDataValidatorStatusChangedEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.SheetDataValidatorStatusChanged, (event) => {
* const { worksheet, workbook, row, column, status, rule } = event;
* console.log(event);
* });
* ```
*/
readonly SheetDataValidatorStatusChanged: 'SheetDataValidatorStatusChanged';
/**
* Event fired before a rule is added
* @see {@link IBeforeSheetDataValidationAddEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.BeforeSheetDataValidationAdd, (event) => {
* const { worksheet, workbook, rule } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeSheetDataValidationAdd: 'BeforeSheetDataValidationAdd';
/**
* Event fired before a rule is deleted
* @see {@link IBeforeSheetDataValidationDeleteEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.BeforeSheetDataValidationDelete, (event) => {
* const { worksheet, workbook, rule } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeSheetDataValidationDelete: 'BeforeSheetDataValidationDelete';
/**
* Event fired before all rules are deleted
* @see {@link IBeforeSheetDataValidationDeleteAllEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.BeforeSheetDataValidationDeleteAll, (event) => {
* const { worksheet, workbook, rules } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeSheetDataValidationDeleteAll: 'BeforeSheetDataValidationDeleteAll';
/**
* Event fired before the criteria of a rule are updated
* @see {@link IBeforeSheetDataValidationCriteriaUpdateEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.BeforeSheetDataValidationCriteriaUpdate, (event) => {
* const { worksheet, workbook, rule, newCriteria } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeSheetDataValidationCriteriaUpdate: 'BeforeSheetDataValidationCriteriaUpdate';
/**
* Event fired before the range of a rule is updated
* @see {@link IBeforeSheetDataValidationRangeUpdateEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.BeforeSheetDataValidationRangeUpdate, (event) => {
* const { worksheet, workbook, rule, newRanges } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeSheetDataValidationRangeUpdate: 'BeforeSheetDataValidationRangeUpdate';
/**
* Event fired before the options of a rule are updated
* @see {@link IBeforeSheetDataValidationOptionsUpdateEvent}
* @example
* ```ts
* univerAPI.on(univerAPI.Event.BeforeSheetDataValidationOptionsUpdate, (event) => {
* const { worksheet, workbook, rule, newOptions } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeSheetDataValidationOptionsUpdate: 'BeforeSheetDataValidationOptionsUpdate';
}
export class FDataValidationEvent implements IDataValidationEvent {
get SheetDataValidationChanged(): 'SheetDataValidationChanged' {
return 'SheetDataValidationChanged';
}
get SheetDataValidatorStatusChanged(): 'SheetDataValidatorStatusChanged' {
return 'SheetDataValidatorStatusChanged';
}
get BeforeSheetDataValidationAdd(): 'BeforeSheetDataValidationAdd' {
return 'BeforeSheetDataValidationAdd';
}
get BeforeSheetDataValidationDelete(): 'BeforeSheetDataValidationDelete' {
return 'BeforeSheetDataValidationDelete';
}
get BeforeSheetDataValidationDeleteAll(): 'BeforeSheetDataValidationDeleteAll' {
return 'BeforeSheetDataValidationDeleteAll';
}
get BeforeSheetDataValidationCriteriaUpdate(): 'BeforeSheetDataValidationCriteriaUpdate' {
return 'BeforeSheetDataValidationCriteriaUpdate';
}
get BeforeSheetDataValidationRangeUpdate(): 'BeforeSheetDataValidationRangeUpdate' {
return 'BeforeSheetDataValidationRangeUpdate';
}
get BeforeSheetDataValidationOptionsUpdate(): 'BeforeSheetDataValidationOptionsUpdate' {
return 'BeforeSheetDataValidationOptionsUpdate';
}
}
export interface IDataValidationEventConfig {
SheetDataValidationChanged: ISheetDataValidationChangedEvent;
SheetDataValidatorStatusChanged: ISheetDataValidatorStatusChangedEvent;
BeforeSheetDataValidationAdd: IBeforeSheetDataValidationAddEvent;
BeforeSheetDataValidationDelete: IBeforeSheetDataValidationDeleteEvent;
BeforeSheetDataValidationDeleteAll: IBeforeSheetDataValidationDeleteAllEvent;
BeforeSheetDataValidationCriteriaUpdate: IBeforeSheetDataValidationCriteriaUpdateEvent;
BeforeSheetDataValidationRangeUpdate: IBeforeSheetDataValidationRangeUpdateEvent;
BeforeSheetDataValidationOptionsUpdate: IBeforeSheetDataValidationOptionsUpdateEvent;
}
FEventName.extend(FDataValidationEvent);
declare module '@univerjs/core' {
// eslint-disable-next-line ts/naming-convention
interface FEventName extends IDataValidationEvent {
}
interface IEventParamConfig extends IDataValidationEventParamConfig {
}
}
@@ -14,20 +14,218 @@
* limitations under the License.
*/
import { FUniver } from '@univerjs/core';
import type { Injector } from '@univerjs/core';
import type { IAddSheetDataValidationCommandParams, IRemoveSheetAllDataValidationCommandParams, IRemoveSheetDataValidationCommandParams, IUpdateSheetDataValidationOptionsCommandParams, IUpdateSheetDataValidationRangeCommandParams, IUpdateSheetDataValidationSettingCommandParams } from '@univerjs/sheets-data-validation';
import type { IBeforeSheetDataValidationAddEvent, IBeforeSheetDataValidationCriteriaUpdateEvent, IBeforeSheetDataValidationDeleteAllEvent, IBeforeSheetDataValidationDeleteEvent, IBeforeSheetDataValidationOptionsUpdateEvent, IBeforeSheetDataValidationRangeUpdateEvent } from './f-event';
import { CanceledError, FUniver, ICommandService } from '@univerjs/core';
import { AddSheetDataValidationCommand, RemoveSheetAllDataValidationCommand, RemoveSheetDataValidationCommand, SheetDataValidationModel, UpdateSheetDataValidationOptionsCommand, UpdateSheetDataValidationRangeCommand, UpdateSheetDataValidationSettingCommand } from '@univerjs/sheets-data-validation';
import { FDataValidation } from './f-data-validation';
import { FDataValidationBuilder } from './f-data-validation-builder';
export class FUnvierDataValidationMixin {
export class FUnvierDataValidationMixin extends FUniver {
/**
/**
* @deparecated use `univerAPI.newDataValidation()` as instead.
*/
static newDataValidation(): FDataValidationBuilder {
static override newDataValidation(): FDataValidationBuilder {
return new FDataValidationBuilder();
}
newDataValidation(): FDataValidationBuilder {
return new FDataValidationBuilder();
}
// eslint-disable-next-line max-lines-per-function
override _initialize(injector: Injector): void {
if (!injector.has(SheetDataValidationModel)) return;
const sheetDataValidationModel = injector.get(SheetDataValidationModel);
const commadnService = injector.get(ICommandService);
this.disposeWithMe(sheetDataValidationModel.ruleChange$.subscribe((ruleChange) => {
const { unitId, subUnitId, rule, oldRule, type } = ruleChange;
const target = this.getSheetTarget(unitId, subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const fRule = new FDataValidation(rule, worksheet.getSheet(), this._injector);
this.fireEvent(this.Event.SheetDataValidationChanged, {
origin: ruleChange,
worksheet,
workbook,
changeType: type,
oldRule,
rule: fRule,
});
}));
this.disposeWithMe(sheetDataValidationModel.validStatusChange$.subscribe((statusChange) => {
const { unitId, subUnitId, ruleId, status, row, col } = statusChange;
const target = this.getSheetTarget(unitId, subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const rule = worksheet.getDataValidation(ruleId);
if (!rule) {
return;
}
this.fireEvent(this.Event.SheetDataValidatorStatusChanged, {
workbook,
worksheet,
row,
column: col,
rule,
status,
});
}));
// eslint-disable-next-line max-lines-per-function, complexity
this.disposeWithMe(commadnService.beforeCommandExecuted((commandInfo) => {
switch (commandInfo.id) {
case AddSheetDataValidationCommand.id: {
const params = commandInfo.params as IAddSheetDataValidationCommandParams;
const target = this.getSheetTarget(params.unitId, params.subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const eventParams: IBeforeSheetDataValidationAddEvent = {
worksheet,
workbook,
rule: params.rule,
};
this.fireEvent(this.Event.BeforeSheetDataValidationAdd, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
break;
}
case UpdateSheetDataValidationSettingCommand.id: {
const params = commandInfo.params as IUpdateSheetDataValidationSettingCommandParams;
const target = this.getSheetTarget(params.unitId, params.subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const rule = worksheet.getDataValidation(params.ruleId);
if (!rule) {
return;
}
const eventParams: IBeforeSheetDataValidationCriteriaUpdateEvent = {
worksheet,
workbook,
rule,
ruleId: params.ruleId,
newCriteria: params.setting,
};
this.fireEvent(this.Event.BeforeSheetDataValidationCriteriaUpdate, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
break;
}
case UpdateSheetDataValidationRangeCommand.id: {
const params = commandInfo.params as IUpdateSheetDataValidationRangeCommandParams;
const target = this.getSheetTarget(params.unitId, params.subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const rule = worksheet.getDataValidation(params.ruleId);
if (!rule) {
return;
}
const eventParams: IBeforeSheetDataValidationRangeUpdateEvent = {
worksheet,
workbook,
rule,
ruleId: params.ruleId,
newRanges: params.ranges,
};
this.fireEvent(this.Event.BeforeSheetDataValidationRangeUpdate, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
break;
}
case UpdateSheetDataValidationOptionsCommand.id: {
const params = commandInfo.params as IUpdateSheetDataValidationOptionsCommandParams;
const target = this.getSheetTarget(params.unitId, params.subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const rule = worksheet.getDataValidation(params.ruleId);
if (!rule) {
return;
}
const eventParams: IBeforeSheetDataValidationOptionsUpdateEvent = {
worksheet,
workbook,
rule,
ruleId: params.ruleId,
newOptions: params.options,
};
this.fireEvent(this.Event.BeforeSheetDataValidationOptionsUpdate, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
break;
}
case RemoveSheetDataValidationCommand.id: {
const params = commandInfo.params as IRemoveSheetDataValidationCommandParams;
const target = this.getSheetTarget(params.unitId, params.subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const rule = worksheet.getDataValidation(params.ruleId);
if (!rule) {
return;
}
const eventParams: IBeforeSheetDataValidationDeleteEvent = {
worksheet,
workbook,
rule,
ruleId: params.ruleId,
};
this.fireEvent(this.Event.BeforeSheetDataValidationDelete, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
break;
}
case RemoveSheetAllDataValidationCommand.id: {
const params = commandInfo.params as IRemoveSheetAllDataValidationCommandParams;
const target = this.getSheetTarget(params.unitId, params.subUnitId);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const eventParams: IBeforeSheetDataValidationDeleteAllEvent = {
worksheet,
workbook,
rules: worksheet.getDataValidations(),
};
this.fireEvent(this.Event.BeforeSheetDataValidationDeleteAll, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
break;
}
default:
break;
}
}));
}
}
FUniver.extend(FUnvierDataValidationMixin);
@@ -24,13 +24,36 @@ export interface IFWorksheetDataValidationMixin {
/**
* Get all data validation rules in current sheet.
* @returns all data validation rules
* ```ts
* const workbook = univerAPI.getActiveUniverSheet();
* const worksheet = workbook.getWorksheet('sheet1');
* const dataValidations = worksheet.getDataValidations();
* ```
*/
getDataValidations(): FDataValidation[];
/**
* Get data validation validator status for current sheet.
* @returns matrix of validator status
* ```ts
* const workbook = univerAPI.getActiveUniverSheet();
* const worksheet = workbook.getWorksheet('sheet1');
* const validatorStatus = worksheet.getValidatorStatus();
* ```
*/
getValidatorStatus(): Promise<ObjectMatrix<Nullable<DataValidationStatus>>>;
/**
* get data validation rule by rule id
* @param ruleId - the rule id
* @returns data validation rule
* ```ts
* const workbook = univerAPI.getActiveUniverSheet();
* const worksheet = workbook.getWorksheet('sheet1');
* const dataValidation = worksheet.getDataValidation('ruleId');
* ```
*/
getDataValidation(ruleId: string): Nullable<FDataValidation>;
}
export class FWorksheetDataValidationMixin extends FWorksheet implements IFWorksheetDataValidationMixin {
@@ -46,6 +69,15 @@ export class FWorksheetDataValidationMixin extends FWorksheet implements IFWorks
this._worksheet.getSheetId()
);
}
override getDataValidation(ruleId: string): Nullable<FDataValidation> {
const dataValidationModel = this._injector.get(DataValidationModel);
const rule = dataValidationModel.getRuleById(this._workbook.getUnitId(), this._worksheet.getSheetId(), ruleId);
if (rule) {
return new FDataValidation(rule, this._worksheet, this._injector);
}
return null;
}
}
FWorksheet.extend(FWorksheetDataValidationMixin);
@@ -18,10 +18,12 @@ import './f-range';
import './f-univer';
import './f-workbook';
import './f-worksheet';
import './f-event';
export { FDataValidation } from './f-data-validation';
export { FDataValidationBuilder } from './f-data-validation-builder';
export type * from './f-event';
export type * from './f-range';
export type * from './f-univer';
export type * from './f-workbook';
@@ -25,7 +25,6 @@ export interface IFTextFinder {
* get all the matched range in the univer
* @returns all the matched range
* @throws if the find operation is not completed
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -41,7 +40,6 @@ export interface IFTextFinder {
* @returns the next matched range
* @throws if the find operation is not completed
* @returns null if no more match
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -57,7 +55,6 @@ export interface IFTextFinder {
* @returns the previous matched range
* @throws if the find operation is not completed
* @returns null if no more match
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -72,7 +69,6 @@ export interface IFTextFinder {
* get the current matched range in the univer
* @returns the current matched range
* @throws if the find operation is not completed
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -88,7 +84,6 @@ export interface IFTextFinder {
* set the match case option
* @param {boolean} matchCase whether to match case
* @returns text-finder instance
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -100,7 +95,6 @@ export interface IFTextFinder {
* set the match entire cell option
* @param {boolean} matchEntireCell whether to match entire cell
* @returns text-finder instance
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -112,7 +106,6 @@ export interface IFTextFinder {
* set the match formula text option
* @param {boolean} matchFormulaText whether to match formula text
* @returns text-finder instance
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -125,7 +118,6 @@ export interface IFTextFinder {
* @param {string} replaceText the text to replace
* @returns the number of replaced text
* @throws if the find operation is not completed
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -139,7 +131,6 @@ export interface IFTextFinder {
* @param {string} replaceText the text to replace
* @returns whether the replace is successful
* @throws if the find operation is not completed
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -151,7 +142,6 @@ export interface IFTextFinder {
/**
* ensure the find operation is completed
* @returns the find complete result
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('hello');
@@ -21,11 +21,8 @@ import { FTextFinder } from './f-text-finder';
export interface IFUniverFindReplaceMixin {
/**
* Create a text-finder for the current univer.
*
* @param {string} text - The text to find.
*
* @returns {Promise<FTextFinder | null>} A promise that resolves to the text-finder instance.
*
* @example
* ```typescript
* const textFinder = await univerAPI.createTextFinderAsync('Hello');
@@ -63,7 +63,7 @@ export const CancelHyperLinkCommand: ICommand<ICancelHyperLinkCommandParams> = {
const range = snapshot.body?.customRanges?.find((range) => `${range.rangeId}` === id);
if (!range) return false;
const textX = BuildTextUtils.customRange.delete(accessor, { documentDataModel: doc.documentModel, rangeId: range.rangeId });
const textX = BuildTextUtils.customRange.delete({ documentDataModel: doc.documentModel, rangeId: range.rangeId });
if (!textX) return false;
const newBody = TextX.apply(snapshot.body!, textX.serialize());
@@ -16,7 +16,7 @@
import type { IAddHyperLinkCommandParams, ICancelHyperLinkCommandParams, IUpdateHyperLinkCommandParams } from '@univerjs/sheets-hyper-link';
import { CustomRangeType, DataStreamTreeTokenType, generateRandomId } from '@univerjs/core';
import { AddHyperLinkCommand, CancelHyperLinkCommand, UpdateHyperLinkCommand } from '@univerjs/sheets-hyper-link';
import { AddHyperLinkCommand, CancelHyperLinkCommand, SheetsHyperLinkParserService, UpdateHyperLinkCommand } from '@univerjs/sheets-hyper-link';
import { FRange } from '@univerjs/sheets/facade';
export interface ICellHyperLink {
@@ -29,39 +29,34 @@ export interface ICellHyperLink {
export interface IFRangeHyperlinkMixin {
/**
* Set hyperlink in the cell in the range.
* [!important] This method is async.
* @param url url
* @param label optional, label of the url
* @returns success or not
* @deprecated use `range.setRichTextValueForCell(univerAPI.newRichText().insertLink(label, url))` instead
*/
setHyperLink(url: string, label?: string): Promise<boolean>;
/**
* Get all hyperlinks in the cell in the range.
* @returns hyperlinks
* @deprecated use `range.setRichTextValueForCell(range.getRichTextValue().getLinks())` instead
*/
getHyperLinks(): ICellHyperLink[];
/**
* Update hyperlink in the cell in the range.
* [!important] This method is async.
* @param id id of the hyperlink
* @param url url
* @param label optional, label of the url
* @returns success or not
* @deprecated use `range.setRichTextValueForCell(range.getRichTextValue().copy().updateLink(id, url))` instead
*/
updateHyperLink(id: string, url: string, label?: string): Promise<boolean>;
/**
* Cancel hyperlink in the cell in the range.
* [!important] This method is async.
* @param id id of the hyperlink
* @returns success or not
* @deprecated use `range.setRichTextValueForCell(range.getRichTextValue().copy().cancelLink(id))` instead
*/
cancelHyperLink(id: string): boolean;
/**
* Get the url of this range.
*/
getUrl(): string;
}
export class FRangeHyperlinkMixin extends FRange implements IFRangeHyperlinkMixin {
// #region hyperlink
/**
* @deprecated
*/
override setHyperLink(url: string, label?: string): Promise<boolean> {
const params: IAddHyperLinkCommandParams = {
unitId: this.getUnitId(),
@@ -79,8 +74,7 @@ export class FRangeHyperlinkMixin extends FRange implements IFRangeHyperlinkMixi
}
/**
* Get all hyperlinks in the cell in the range.
* @returns hyperlinks
* @deprecated
*/
override getHyperLinks(): ICellHyperLink[] {
const cellValue = this._worksheet.getCellRaw(this._range.startRow, this._range.startColumn);
@@ -100,12 +94,7 @@ export class FRangeHyperlinkMixin extends FRange implements IFRangeHyperlinkMixi
}
/**
* Update hyperlink in the cell in the range.
* [!important] This method is async.
* @param id id of the hyperlink
* @param url url
* @param label optional, label of the url
* @returns success or not
* @deprecated
*/
override updateHyperLink(id: string, url: string, label?: string): Promise<boolean> {
const params: IUpdateHyperLinkCommandParams = {
@@ -124,9 +113,7 @@ export class FRangeHyperlinkMixin extends FRange implements IFRangeHyperlinkMixi
}
/**
* Cancel hyperlink in the cell in the range.
* @param id id of the hyperlink
* @returns success or not
* @deprecated
*/
override cancelHyperLink(id: string): boolean {
const params: ICancelHyperLinkCommandParams = {
@@ -140,6 +127,11 @@ export class FRangeHyperlinkMixin extends FRange implements IFRangeHyperlinkMixi
return this._commandService.syncExecuteCommand(CancelHyperLinkCommand.id, params);
}
override getUrl(): string {
const parserService = this._injector.get(SheetsHyperLinkParserService);
return parserService.buildHyperLink(this.getUnitId(), this.getSheetId(), this.getRange());
}
// #endregion
}
@@ -14,23 +14,27 @@
* limitations under the License.
*/
import type { IRange } from '@univerjs/core';
import type { ISheetHyperLinkInfo } from '@univerjs/sheets-hyper-link';
import type { FRange } from '@univerjs/sheets/facade';
import { Inject, type IRange } from '@univerjs/core';
import { SheetsHyperLinkParserService } from '@univerjs/sheets-hyper-link';
import { FWorkbook } from '@univerjs/sheets/facade';
export class SheetHyperLinkBuilder {
constructor(
private _workbook: FWorkbook,
@Inject(SheetsHyperLinkParserService) private readonly _parserService: SheetsHyperLinkParserService
) {}
getRangeUrl(range: FRange): this {
this._parserService.buildHyperLink(this._workbook.getId(), range.getSheetId(), range.getRange());
return this;
}
}
export interface IFWorkbookHyperlinkMixin {
/**
* Create a hyperlink for the sheet.
* @param sheetId the sheet id to link
* @param range the range to link, or define-name id
* @returns the hyperlink string
* @example
* ```ts
* let range = univerAPI.getActiveWorkbook().getActiveSheet().getRange(5, 5);
* // return something like '#gid=sheet_Id&range=F6'
* univerAPI.getActiveWorkbook().createSheetHyperlink('sheet_Id', r.getRange());
* ```
* @deprecated use `getUrl` method in `FRange` or `FWorksheet` instead.
*/
createSheetHyperlink(this: FWorkbook, sheetId: string, range?: string | IRange): string;
@@ -0,0 +1,39 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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';
export interface IFWorksheetHyperlinkMixin {
/**
* Get the url of this sheet
*/
getUrl(): string;
}
export class FWorksheetHyperlinkMixin extends FWorksheet implements IFWorksheetHyperlinkMixin {
override getUrl(): string {
const parserService = this._injector.get(SheetsHyperLinkParserService);
return parserService.buildHyperLink(this._workbook.getUnitId(), this._worksheet.getSheetId());
}
}
FWorksheet.extend(FWorksheetHyperlinkMixin);
declare module '@univerjs/sheets/facade' {
// eslint-disable-next-line ts/naming-convention
interface FWorksheet extends IFWorksheetHyperlinkMixin {}
}
@@ -15,6 +15,7 @@
*/
import './f-workbook';
import './f-worksheet';
import './f-range';
export { FWorkbookHyperLinkMixin } from './f-workbook';
@@ -22,3 +23,4 @@ export { FWorkbookHyperLinkMixin } from './f-workbook';
// eslint-disable-next-line perfectionist/sort-exports
export type * from './f-range';
export type * from './f-workbook';
export type * from './f-worksheet';
@@ -0,0 +1,324 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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, RichTextValue } from '@univerjs/core';
import type { FWorkbook, FWorksheet } from '@univerjs/sheets/facade';
import type { FTheadCommentItem, FThreadComment } from './f-thread-comment';
import { FEventName } from '@univerjs/core';
interface ICommentEventMixin {
/**
* Event fired after comment added
* @see {@link ISheetCommentAddEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.CommentAdded, (event) => {
* const { comment, workbook, worksheet, row, col } = event;
* console.log(event);
* });
* ```
*/
readonly CommentAdded: 'CommentAdded';
/**
* Event fired before comment added
* @see {@link IBeforeSheetCommentAddEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.BeforeCommentAdd, (event) => {
* const { comment, workbook, worksheet, row, col } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeCommentAdd: 'BeforeCommentAdd';
/**
* Event fired after comment updated
* @see {@link ISheetCommentUpdateEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.CommentUpdated, (event) => {
* const { comment, workbook, worksheet, row, col } = event;
* console.log(event);
* });
* ```
*/
readonly CommentUpdated: 'CommentUpdated';
/**
* Event fired before comment update
* @see {@link IBeforeSheetCommentUpdateEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.BeforeCommentUpdate, (event) => {
* const { comment, workbook, worksheet, row, col, newContent } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeCommentUpdate: 'BeforeCommentUpdate';
/**
* Event fired after comment deleted
* @see {@link ISheetCommentDeleteEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.CommentDeleted, (event) => {
* const { commentId, workbook, worksheet } = event;
* console.log(event);
* });
* ```
*/
readonly CommentDeleted: 'CommentDeleted';
/**
* Event fired before comment delete
* @see {@link IBeforeSheetCommentDeleteEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.BeforeCommentDeleted, (event) => {
* const { commentId, workbook, worksheet } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeCommentDeleted: 'BeforeCommentDeleted';
/**
* Event fired after comment resolve
* @see {@link ISheetCommentResolveEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.CommentResolved, (event) => {
* const { comment, row, col, resolved, workbook, worksheet } = event;
* console.log(event);
* });
* ```
*/
readonly CommentResolved: 'CommentResolved';
/**
* Event fired before comment resolve
* @see {@link ISheetCommentResolveEvent}
* @example
* ```ts
* univerAPI.addEventListener(CommentEvent.BeforeCommentResolve, (event) => {
* const { comment, row, col, resolved, workbook, worksheet } = event;
* console.log(event);
* });
* ```
*/
readonly BeforeCommentResolve: 'BeforeCommentResolve';
}
const CommentEvent: ICommentEventMixin = {
CommentAdded: 'CommentAdded',
BeforeCommentAdd: 'BeforeCommentAdd',
CommentUpdated: 'CommentUpdated',
BeforeCommentUpdate: 'BeforeCommentUpdate',
CommentDeleted: 'CommentDeleted',
BeforeCommentDeleted: 'BeforeCommentDeleted',
CommentResolved: 'CommentResolved',
BeforeCommentResolve: 'BeforeCommentResolve',
} as const;
export class FCommentEvent extends FEventName {
override get CommentAdded(): 'CommentAdded' {
return CommentEvent.CommentAdded;
}
override get BeforeCommentAdd(): 'BeforeCommentAdd' {
return CommentEvent.BeforeCommentAdd;
}
override get CommentUpdated(): 'CommentUpdated' {
return CommentEvent.CommentUpdated;
}
override get BeforeCommentUpdate(): 'BeforeCommentUpdate' {
return CommentEvent.BeforeCommentUpdate;
}
override get CommentDeleted(): 'CommentDeleted' {
return CommentEvent.CommentDeleted;
}
override get BeforeCommentDeleted(): 'BeforeCommentDeleted' {
return CommentEvent.BeforeCommentDeleted;
}
override get CommentResolved(): 'CommentResolved' {
return CommentEvent.CommentResolved;
}
override get BeforeCommentResolve(): 'BeforeCommentResolve' {
return CommentEvent.BeforeCommentResolve;
}
}
/**
* Event interface triggered after a comment is added to a sheet
* @interface ISheetCommentAddEvent
* @augments {IEventBase}
*/
export interface ISheetCommentAddEvent extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet where the comment is added */
worksheet: FWorksheet;
/** Row index of the comment */
row: number;
/** Column index of the comment */
col: number;
/** The added comment object */
comment: FThreadComment;
}
/**
* Event interface triggered before a comment is added to a sheet
* @interface IBeforeSheetCommentAddEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetCommentAddEvent extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet where the comment will be added */
worksheet: FWorksheet;
/** Row index for the new comment */
row: number;
/** Column index for the new comment */
col: number;
/** The comment item to be added */
comment: FTheadCommentItem;
}
/**
* Event interface triggered after a comment is updated in a sheet
* @interface ISheetCommentUpdateEvent
* @augments {IEventBase}
*/
export interface ISheetCommentUpdateEvent extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet containing the updated comment */
worksheet: FWorksheet;
/** Row index of the comment */
row: number;
/** Column index of the comment */
col: number;
/** The updated comment object */
comment: FThreadComment;
}
/**
* Event interface triggered before a comment is updated in a sheet
* @interface IBeforeSheetCommentUpdateEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetCommentUpdateEvent extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet containing the comment */
worksheet: FWorksheet;
/** Row index of the comment */
row: number;
/** Column index of the comment */
col: number;
/** The current comment object */
comment: FThreadComment;
/** The new content to replace the existing comment */
newContent: RichTextValue;
}
/**
* Event interface triggered before a comment is deleted from a sheet
* @interface IBeforeSheetCommentDeleteEvent
* @augments {IEventBase}
*/
export interface IBeforeSheetCommentDeleteEvent extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet containing the comment */
worksheet: FWorksheet;
/** Row index of the comment */
row: number;
/** Column index of the comment */
col: number;
/** The comment to be deleted */
comment: FThreadComment;
}
/**
* Event interface triggered after a comment is deleted from a sheet
* @interface ISheetCommentDeleteEvent
* @augments {IEventBase}
*/
export interface ISheetCommentDeleteEvent extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet that contained the comment */
worksheet: FWorksheet;
/** The ID of the deleted comment */
commentId: string;
}
/**
* Event interface triggered when a comment's resolve status changes
* @interface ISheetCommentResolveEvent
* @augments {IEventBase}
*/
export interface ISheetCommentResolveEvent extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet containing the comment */
worksheet: FWorksheet;
/** Row index of the comment */
row: number;
/** Column index of the comment */
col: number;
/** The comment object */
comment: FThreadComment;
/** The new resolve status */
resolved: boolean;
}
FEventName.extend(FCommentEvent);
export interface ISheetCommentEventConfig {
BeforeCommentAdd: IBeforeSheetCommentAddEvent;
CommentAdded: ISheetCommentAddEvent;
BeforeCommentUpdate: IBeforeSheetCommentUpdateEvent;
CommentUpdated: ISheetCommentUpdateEvent;
BeforeCommentDeleted: IBeforeSheetCommentDeleteEvent;
CommentDeleted: ISheetCommentDeleteEvent;
BeforeCommentResolve: ISheetCommentResolveEvent;
CommentResolved: ISheetCommentResolveEvent;
}
declare module '@univerjs/core' {
// eslint-disable-next-line ts/naming-convention
interface FEventName extends ICommentEventMixin {
}
interface IEventParamConfig extends ISheetCommentEventConfig {
}
}
@@ -14,11 +14,12 @@
* limitations under the License.
*/
import { ICommandService, type IDocumentBody, type Nullable, Tools, UserManagerService } from '@univerjs/core';
import type { IThreadComment } from '@univerjs/thread-comment';
import { generateRandomId, ICommandService, type IDocumentBody, type Nullable, Range, Tools, UserManagerService } from '@univerjs/core';
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
import { FRange } from '@univerjs/sheets/facade';
import { AddCommentCommand, DeleteCommentTreeCommand, getDT } from '@univerjs/thread-comment';
import { FThreadComment } from './f-thread-comment';
import { FTheadCommentBuilder, FThreadComment } from './f-thread-comment';
export interface IFRangeCommentMixin {
/**
@@ -26,17 +27,30 @@ export interface IFRangeCommentMixin {
* @returns The comment of the start cell in the current range. If the cell does not have a comment, return `null`.
*/
getComment(): Nullable<FThreadComment>;
/**
* Get the comments in the current range.
* @returns {FThreadComment[]} The comments in the current range.
*/
getComments(): FThreadComment[];
/**
* Add a comment to the start cell in the current range.
* @param content The content of the comment.
* @returns Whether the comment is added successfully.
*/
addComment(this: FRange, content: IDocumentBody): Promise<boolean>;
addComment(content: IDocumentBody | FTheadCommentBuilder): Promise<boolean>;
/**
* Clear the comment of the start cell in the current range.
* @returns Whether the comment is cleared successfully.
*/
clearComment(): Promise<boolean>;
/**
* Clear all of the comments in the current range.
* @returns Whether the comments are cleared successfully.
*/
clearComments(): Promise<boolean>;
}
export class FRangeCommentMixin extends FRange implements IFRangeCommentMixin {
@@ -58,7 +72,26 @@ export class FRangeCommentMixin extends FRange implements IFRangeCommentMixin {
return null;
}
override addComment(content: IDocumentBody): Promise<boolean> {
override getComments(): FThreadComment[] {
const injector = this._injector;
const sheetsTheadCommentModel = injector.get(SheetsThreadCommentModel);
const unitId = this._workbook.getUnitId();
const sheetId = this._worksheet.getSheetId();
const comments: FThreadComment[] = [];
Range.foreach(this._range, (row, col) => {
const commentId = sheetsTheadCommentModel.getByLocation(unitId, sheetId, row, col);
if (commentId) {
const comment = sheetsTheadCommentModel.getComment(unitId, sheetId, commentId);
if (comment) {
comments.push(this._injector.createInstance(FThreadComment, comment));
}
}
});
return comments;
}
override addComment(content: IDocumentBody | FTheadCommentBuilder): Promise<boolean> {
const injector = this._injector;
const currentComment = this.getComment()?.getCommentData();
const commentService = injector.get(ICommandService);
@@ -67,21 +100,22 @@ export class FRangeCommentMixin extends FRange implements IFRangeCommentMixin {
const sheetId = this._worksheet.getSheetId();
const refStr = `${Tools.chatAtABC(this._range.startColumn)}${this._range.startRow + 1}`;
const currentUser = userService.getCurrentUser();
const commentData: Partial<IThreadComment> = content instanceof FTheadCommentBuilder ? content.build() : { text: content };
return commentService.executeCommand(AddCommentCommand.id, {
unitId,
subUnitId: sheetId,
comment: {
text: content,
text: commentData.text,
dT: commentData.dT || getDT(),
attachments: [],
dT: getDT(),
id: Tools.generateRandomId(),
ref: refStr!,
personId: currentUser.userID,
id: commentData.id || generateRandomId(),
ref: refStr,
personId: commentData.personId || currentUser.userID,
parentId: currentComment?.id,
unitId,
subUnitId: sheetId,
threadId: currentComment?.threadId,
threadId: currentComment?.threadId || generateRandomId(),
},
});
}
@@ -104,6 +138,13 @@ export class FRangeCommentMixin extends FRange implements IFRangeCommentMixin {
return Promise.resolve(true);
}
override clearComments(): Promise<boolean> {
const comments = this.getComments();
const promises = comments.map((comment) => comment.deleteAsync());
return Promise.all(promises).then(() => true);
}
}
FRange.extend(FRangeCommentMixin);
@@ -15,12 +15,234 @@
*/
import type { IDocumentBody, IRange, Workbook } from '@univerjs/core';
import type { IBaseComment, IDeleteCommentCommandParams, IResolveCommentCommandParams, IThreadComment, IUpdateCommentCommandParams } from '@univerjs/thread-comment';
import { ICommandService, Inject, Injector, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import type { IAddCommentCommandParams, IBaseComment, IDeleteCommentCommandParams, IResolveCommentCommandParams, IThreadComment, IUpdateCommentCommandParams } from '@univerjs/thread-comment';
import { generateRandomId, ICommandService, Inject, Injector, IUniverInstanceService, RichTextBuilder, RichTextValue, Tools, UniverInstanceType, UserManagerService } from '@univerjs/core';
import { deserializeRangeWithSheet } from '@univerjs/engine-formula';
import { FRange } from '@univerjs/sheets/facade';
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
import { DeleteCommentCommand, DeleteCommentTreeCommand, getDT, ResolveCommentCommand, UpdateCommentCommand } from '@univerjs/thread-comment';
import { FRange } from '@univerjs/sheets/facade';
import { AddCommentCommand, DeleteCommentCommand, DeleteCommentTreeCommand, getDT, ResolveCommentCommand, UpdateCommentCommand } from '@univerjs/thread-comment';
export class FTheadCommentItem {
protected _comment: IThreadComment = {
id: generateRandomId(),
ref: '',
threadId: '',
dT: '',
personId: '',
text: RichTextBuilder.newEmptyData().body!,
attachments: [],
unitId: '',
subUnitId: '',
};
/**
* Create a new FTheadCommentItem
* @param {IThreadComment|undefined} comment The comment
* @returns {FTheadCommentItem} A new instance of FTheadCommentItem
* @example
* ```ts
* const comment = univerAPI.newTheadComment();
* ```
*/
static create(comment?: IThreadComment): FTheadCommentItem {
return new FTheadCommentItem(comment);
}
constructor(comment?: IThreadComment) {
if (comment) {
this._comment = comment;
}
}
/**
* Get the person id of the comment
* @returns {string} The person id of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const personId = comment.personId;
* ```
*/
get personId(): string {
return this._comment.personId;
}
/**
* Get the date time of the comment
* @returns {string} The date time of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const dateTime = comment.dateTime;
* ```
*/
get dateTime(): string {
return this._comment.dT;
}
/**
* Get the content of the comment
* @returns {RichTextValue} The content of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const content = comment.content;
* ```
*/
get content(): RichTextValue {
return RichTextValue.createByBody(this._comment.text);
}
/**
* Get the id of the comment
* @returns {string} The id of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const id = comment.id;
* ```
*/
get id(): string {
return this._comment.id;
}
/**
* Get the thread id of the comment
* @returns {string} The thread id of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const threadId = comment.threadId;
* ```
*/
get threadId(): string {
return this._comment.threadId;
}
/**
* Copy the comment
* @returns {FTheadCommentBuilder} The comment builder
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const newComment = comment.copy();
* ```
*/
copy(): FTheadCommentBuilder {
return FTheadCommentBuilder.create(Tools.deepClone(this._comment));
}
}
export class FTheadCommentBuilder extends FTheadCommentItem {
static override create(comment?: IThreadComment): FTheadCommentBuilder {
return new FTheadCommentBuilder(comment);
}
/**
* Set the content of the comment
* @param {IDocumentBody | RichTextValue} content The content of the comment
* @returns {FTheadCommentBuilder} The comment builder
* @example
* ```ts
* const comment = univerAPI.newTheadComment()
* .setContent(univerAPI.newRichText().insertText('hello zhangsan'));
* ```
*/
setContent(content: IDocumentBody | RichTextValue): FTheadCommentBuilder {
if (content instanceof RichTextValue) {
this._comment.text = content.getData().body!;
} else {
this._comment.text = content;
}
return this;
}
/**
* Set the person id of the comment
* @param {string} userId The person id of the comment
* @returns {FTheadCommentBuilder} The comment builder
* @example
* ```ts
* const comment = univerAPI.newTheadComment()
* .setPersonId('123');
* ```
*/
setPersonId(userId: string): FTheadCommentBuilder {
this._comment.personId = userId;
return this;
}
/**
* Set the date time of the comment
* @param {Date} date The date time of the comment
* @returns {FTheadCommentBuilder} The comment builder
* @example
* ```ts
* const comment = univerAPI.newTheadComment()
* .setDateTime(new Date());
* ```
*/
setDateTime(date: Date): FTheadCommentBuilder {
this._comment.dT = getDT(date);
return this;
}
/**
* Set the id of the comment
* @param {string} id The id of the comment
* @returns {FTheadCommentBuilder} The comment builder
* @example
* ```ts
* const comment = univerAPI.newTheadComment()
* .setId('123');
* ```
*/
setId(id: string): FTheadCommentBuilder {
this._comment.id = id;
return this;
}
/**
* Set the thread id of the comment
* @param {string} threadId The thread id of the comment
* @returns {FTheadCommentBuilder} The comment builder
* @example
* ```ts
* const comment = univerAPI.newTheadComment()
* .setThreadId('123');
* ```
*/
setThreadId(threadId: string): FTheadCommentBuilder {
this._comment.threadId = threadId;
return this;
}
/**
* Build the comment
* @returns {IThreadComment} The comment
* @example
* ```ts
* const comment = univerAPI.newTheadComment()
* .setContent(univerAPI.newRichText().insertText('hello zhangsan'))
* .build();
* ```
*/
build(): IThreadComment {
return this._comment;
}
}
export class FThreadComment {
constructor(
@@ -29,7 +251,8 @@ export class FThreadComment {
@Inject(Injector) private readonly _injector: Injector,
@ICommandService private readonly _commandService: ICommandService,
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
@Inject(SheetsThreadCommentModel) private readonly _threadCommentModel: SheetsThreadCommentModel
@Inject(SheetsThreadCommentModel) private readonly _threadCommentModel: SheetsThreadCommentModel,
@Inject(UserManagerService) private readonly _userManagerService: UserManagerService
) {
}
@@ -43,6 +266,13 @@ export class FThreadComment {
/**
* Whether the comment is a root comment
* @returns Whether the comment is a root comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const isRoot = comment.getIsRoot();
* ```
*/
getIsRoot(): boolean {
return !this._parent;
@@ -51,6 +281,13 @@ export class FThreadComment {
/**
* Get the comment data
* @returns The comment data
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const commentData = comment.getCommentData();
* ```
*/
getCommentData(): IBaseComment {
const { children, ...comment } = this._thread;
@@ -60,6 +297,13 @@ export class FThreadComment {
/**
* Get the replies of the comment
* @returns the replies of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const replies = comment.getReplies();
* ```
*/
getReplies(): FThreadComment[] | undefined {
const range = this._getRef();
@@ -71,6 +315,13 @@ export class FThreadComment {
/**
* Get the range of the comment
* @returns The range of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const range = comment.getRange();
* ```
*/
getRange(): FRange | null {
const workbook = this._univerInstanceService.getUnit<Workbook>(this._thread.unitId, UniverInstanceType.UNIVER_SHEET);
@@ -86,18 +337,40 @@ export class FThreadComment {
}
/**
* Get the content of the comment
* @returns The content of the comment
* @deprecated use `getRichText` as instead
*/
getContent(): IDocumentBody {
return this._thread.text;
}
/**
* Delete the comment and it's replies
* @returns success or not
* Get the rich text of the comment
* @returns {RichTextValue} The rich text of the comment
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const richText = comment.getRichText();
* ```
*/
delete(): Promise<boolean> {
getRichText(): RichTextValue {
const body = this._thread.text;
return RichTextValue.create({ body, documentStyle: {}, id: 'd' });
}
/**
* Delete the comment and it's replies
* @returns {Promise<boolean>} success or not
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const success = await comment.deleteAsync();
* ```
*/
deleteAsync(): Promise<boolean> {
return this._commandService.executeCommand(
this.getIsRoot() ? DeleteCommentTreeCommand.id : DeleteCommentCommand.id,
{
@@ -108,12 +381,35 @@ export class FThreadComment {
);
}
/**
* @deprecated use `deleteAsync` as instead.
*/
delete(): Promise<boolean> {
return this.deleteAsync();
}
/**
* @param content
* @deprecated use `updateAsync` as instead
*/
async update(content: IDocumentBody): Promise<boolean> {
return this.updateAsync(content);
}
/**
* Update the comment content
* @param content The new content of the comment
* @returns success or not
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const success = await comment.updateAsync(univerAPI.newRichText().insertText('hello zhangsan'));
* ```
*/
async update(content: IDocumentBody): Promise<boolean> {
async updateAsync(content: IDocumentBody | RichTextValue): Promise<boolean> {
const body = content instanceof RichTextValue ? content.getData().body : content;
const dt = getDT();
const res = await this._commandService.executeCommand(
UpdateCommentCommand.id,
@@ -122,7 +418,7 @@ export class FThreadComment {
subUnitId: this._thread.subUnitId,
payload: {
commentId: this._thread.id,
text: content,
text: body,
updated: true,
updateT: dt,
},
@@ -133,11 +429,26 @@ export class FThreadComment {
}
/**
* Resolve the comment
* @param resolved Whether the comment is resolved
* @returns success or not
* @param resolved
* @deprecated use `resolveAsync` as instead
*/
resolve(resolved?: boolean): Promise<boolean> {
return this.resolveAsync(resolved);
}
/**
* Resolve the comment
* @param resolved Whether the comment is resolved
* @returns success or not
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
* const success = await comment.resolveAsync(true);
* ```
*/
resolveAsync(resolved?: boolean): Promise<boolean> {
return this._commandService.executeCommand(
ResolveCommentCommand.id,
{
@@ -148,4 +459,44 @@ export class FThreadComment {
} as IResolveCommentCommandParams
);
}
/**
* Reply to the comment
* @param comment The comment to reply to
* @returns success or not
* @example
* ```ts
* const comment = univerAPI.getActiveUniverSheet()
* .getSheetById(sheetId)
* .getCommentById(commentId);
*
* const reply = univerAPI.newTheadComment()
* .setContent(univerAPI.newRichText().insertText('hello zhangsan'));
*
* const success = await comment.replyAsync(reply);
* ```
*/
async replyAsync(comment: FTheadCommentBuilder): Promise<boolean> {
const commentData = comment.build();
return this._commandService.executeCommand(
AddCommentCommand.id,
{
unitId: this._thread.unitId,
subUnitId: this._thread.subUnitId,
comment: {
id: generateRandomId(),
parentId: this._thread.id,
threadId: this._thread.threadId,
ref: this._parent?.ref,
unitId: this._thread.unitId,
subUnitId: this._thread.subUnitId,
text: commentData.text,
attachments: commentData.attachments,
dT: commentData.dT || getDT(),
personId: commentData.personId || this._userManagerService.getCurrentUser().userID,
},
} as IAddCommentCommandParams
);
}
}
@@ -0,0 +1,260 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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 { ICommandInfo, IDisposable, Injector } from '@univerjs/core';
import type { IAddCommentCommandParams, IDeleteCommentCommandParams, IResolveCommentCommandParams, IThreadComment, IUpdateCommentCommandParams } from '@univerjs/thread-comment';
import type { IBeforeSheetCommentAddEvent, IBeforeSheetCommentDeleteEvent, IBeforeSheetCommentUpdateEvent, ISheetCommentAddEvent, ISheetCommentDeleteEvent, ISheetCommentResolveEvent, ISheetCommentUpdateEvent } from './f-event';
import { CanceledError, FUniver, ICommandService, RichTextValue } from '@univerjs/core';
import { AddCommentCommand, DeleteCommentCommand, ResolveCommentCommand, UpdateCommentCommand } from '@univerjs/thread-comment';
import { FTheadCommentBuilder, FTheadCommentItem } from './f-thread-comment';
export interface IFUniverCommentMixin {
/**
* @deprecated use `univerAPI.addEvent(univerAPI.event.CommentAdded, () => {})` as instead
*/
onCommentAdded(callback: (event: ISheetCommentAddEvent) => void): IDisposable;
/**
* @deprecated use `univerAPI.addEvent(univerAPI.event.CommentUpdated, () => {})` as instead
*/
onCommentUpdated(callback: (event: ISheetCommentUpdateEvent) => void): IDisposable;
/**
* @deprecated use `univerAPI.addEvent(univerAPI.event.CommentDeleted, () => {})` as instead
*/
onCommentDeleted(callback: (event: ISheetCommentDeleteEvent) => void): IDisposable;
/**
* @deprecated use `univerAPI.addEvent(univerAPI.event.CommentResolved, () => {})` as instead
*/
onCommentResolved(callback: (event: ISheetCommentResolveEvent) => void): IDisposable;
/**
* create a new thread comment
* @return {FTheadCommentBuilder} thead comment builder
* @example
* ```ts
* const comment = univerAPI.newTheadComment().setContent(univerAPI.newRichText().insertText('hello zhangsan'));
* ```
*/
newTheadComment(): FTheadCommentBuilder;
}
export class FUniverCommentMixin extends FUniver implements IFUniverCommentMixin {
// eslint-disable-next-line complexity
private _handleCommentCommand(commandInfo: ICommandInfo): void {
const params = commandInfo.params as { unitId: string; subUnitId: string; sheetId: string };
if (!params) return;
const workbook = params.unitId ? this.getUniverSheet(params.unitId) : this.getActiveWorkbook?.();
if (!workbook) {
return;
}
const worksheet = workbook.getSheetBySheetId(params.subUnitId || params.sheetId) || workbook.getActiveSheet();
if (!worksheet) {
return;
}
switch (commandInfo.id) {
case AddCommentCommand.id: {
if (!this._eventListend(this.Event.CommentAdded)) return;
const addParams = commandInfo.params as IAddCommentCommandParams;
const { comment } = addParams;
const threadComment = worksheet.getRange(comment.ref).getComment();
if (threadComment) {
this.fireEvent(this.Event.CommentAdded, {
workbook,
worksheet,
row: threadComment.getRange()?.getRow() ?? 0,
col: threadComment.getRange()?.getColumn() ?? 0,
comment: threadComment,
});
}
break;
}
case UpdateCommentCommand.id: {
if (!this._eventListend(this.Event.CommentUpdated)) return;
const updateParams = commandInfo.params as IUpdateCommentCommandParams;
const { commentId } = updateParams.payload;
const threadComment = worksheet.getCommentById(commentId);
if (threadComment) {
this.fireEvent(this.Event.CommentUpdated, {
workbook,
worksheet,
row: threadComment.getRange()?.getRow() ?? 0,
col: threadComment.getRange()?.getColumn() ?? 0,
comment: threadComment,
});
}
break;
}
case DeleteCommentCommand.id: {
if (!this._eventListend(this.Event.CommentDeleted)) return;
const deleteParams = commandInfo.params as IDeleteCommentCommandParams;
const { commentId } = deleteParams;
this.fireEvent(this.Event.CommentDeleted, {
workbook,
worksheet,
commentId,
});
break;
}
case ResolveCommentCommand.id: {
if (!this._eventListend(this.Event.CommentResolved)) return;
const resolveParams = commandInfo.params as IResolveCommentCommandParams;
const { commentId, resolved } = resolveParams;
const threadComment = worksheet.getComments().find((c) => c.getCommentData().id === commentId);
if (threadComment) {
this.fireEvent(this.Event.CommentResolved, {
workbook,
worksheet,
row: threadComment.getRange()!.getRow() ?? 0,
col: threadComment.getRange()!.getColumn() ?? 0,
comment: threadComment,
resolved,
});
}
break;
}
}
}
// eslint-disable-next-line complexity, max-lines-per-function
private _handleBeforeCommentCommand(commandInfo: ICommandInfo): void {
const params = commandInfo.params as { unitId: string; subUnitId: string; sheetId: string };
if (!params) return;
const workbook = params.unitId ? this.getUniverSheet(params.unitId) : this.getActiveWorkbook?.();
if (!workbook) {
return;
}
const worksheet = workbook.getSheetBySheetId(params.subUnitId || params.sheetId) || workbook.getActiveSheet();
if (!worksheet) {
return;
}
switch (commandInfo.id) {
case AddCommentCommand.id: {
if (!this._eventListend(this.Event.BeforeCommentAdd)) return;
const addParams = commandInfo.params as IAddCommentCommandParams;
const { comment } = addParams;
const activeRange = worksheet.getActiveRange();
if (!activeRange) return;
const eventParams: IBeforeSheetCommentAddEvent = {
workbook,
worksheet,
row: activeRange.getRow() ?? 0,
col: activeRange.getColumn() ?? 0,
comment: FTheadCommentItem.create(comment),
};
this.fireEvent(this.Event.BeforeCommentAdd, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
};
break;
}
case UpdateCommentCommand.id: {
if (!this._eventListend(this.Event.BeforeCommentUpdate)) return;
const updateParams = commandInfo.params as IUpdateCommentCommandParams;
const { commentId, text } = updateParams.payload;
const threadComment = worksheet.getCommentById(commentId);
if (threadComment) {
const eventParams: IBeforeSheetCommentUpdateEvent = {
workbook,
worksheet,
row: threadComment.getRange()?.getRow() ?? 0,
col: threadComment.getRange()?.getColumn() ?? 0,
comment: threadComment,
newContent: RichTextValue.createByBody(text),
};
this.fireEvent(this.Event.BeforeCommentUpdate, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
};
}
break;
}
case DeleteCommentCommand.id: {
if (!this._eventListend(this.Event.BeforeCommentDeleted)) return;
const deleteParams = commandInfo.params as IDeleteCommentCommandParams;
const { commentId } = deleteParams;
const threadComment = worksheet.getCommentById(commentId);
if (threadComment) {
const eventParams: IBeforeSheetCommentDeleteEvent = {
workbook,
worksheet,
row: threadComment.getRange()?.getRow() ?? 0,
col: threadComment.getRange()?.getColumn() ?? 0,
comment: threadComment,
};
this.fireEvent(this.Event.BeforeCommentDeleted, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
};
}
break;
}
case ResolveCommentCommand.id: {
if (!this._eventListend(this.Event.BeforeCommentResolve)) return;
const resolveParams = commandInfo.params as IResolveCommentCommandParams;
const { commentId, resolved } = resolveParams;
const threadComment = worksheet.getComments().find((c) => c.getCommentData().id === commentId);
if (threadComment) {
const eventParams: ISheetCommentResolveEvent = {
workbook,
worksheet,
row: threadComment.getRange()!.getRow() ?? 0,
col: threadComment.getRange()!.getColumn() ?? 0,
comment: threadComment,
resolved,
};
this.fireEvent(this.Event.BeforeCommentResolve, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
}
break;
}
}
}
override _initialize(injector: Injector): void {
const commandService = injector.get(ICommandService);
this.disposeWithMe(
commandService.onCommandExecuted((commandInfo) => {
this._handleCommentCommand(commandInfo);
})
);
this.disposeWithMe(
commandService.beforeCommandExecuted((commandInfo) => {
this._handleBeforeCommentCommand(commandInfo);
})
);
}
override newTheadComment(comment?: IThreadComment): FTheadCommentBuilder {
return new FTheadCommentBuilder(comment);
}
}
FUniver.extend(FUniverCommentMixin);
declare module '@univerjs/core' {
// eslint-disable-next-line ts/naming-convention
interface FUniver extends IFUniverCommentMixin {}
}
@@ -20,33 +20,38 @@ import { toDisposable } from '@univerjs/core';
import { FWorkbook } from '@univerjs/sheets/facade';
import { AddCommentCommand, DeleteCommentCommand, DeleteCommentTreeCommand, ThreadCommentModel, UpdateCommentCommand } from '@univerjs/thread-comment';
import { filter } from 'rxjs';
import { FThreadComment } from './f-thread-comment';
// FIXME@weird94: this plugin should not rely on docs-ui
// eslint-disable-next-line ts/no-explicit-any
type IUpdateCommandParams = any;
export interface IFWorkbookThreadCommentMixin {
/**
* The onThreadCommentChange event is fired when the thread comment of this sheet is changed.
* @param callback Callback function that will be called when the event is fired
* @returns A disposable object that can be used to unsubscribe from the event
* Get all comments in the current sheet
* @returns all comments in the current sheet
*/
getComments(): FThreadComment[];
/**
* Clear all comments in the current sheet
*/
clearComments(): Promise<boolean>;
/**
* @deprecated use `univerAPI.addEvent(univerAPI.event.CommentUpdated, () => {})` as instead
*/
onThreadCommentChange(callback: (commentUpdate: CommentUpdate) => void | false): IDisposable;
/**
* The onThreadCommentChange event is fired when the thread comment of this sheet is changed.
* @param callback Callback function that will be called when the event is fired
* @returns A disposable object that can be used to unsubscribe from the event
*/
/**
* @deprecated use `univerAPI.addEvent(univerAPI.event.BeforeCommentAdd, () => {})` as instead
*/
onBeforeAddThreadComment(
this: FWorkbook,
callback: (params: IAddCommentCommandParams, options: IExecutionOptions | undefined) => void | false
): IDisposable;
/**
* The onBeforeUpdateThreadComment event is fired before the thread comment is updated.
* @param callback Callback function that will be called when the event is fired
* @returns A disposable object that can be used to unsubscribe from the event
* @deprecated use `univerAPI.addEvent(univerAPI.event.BeforeCommentUpdate, () => {})` as instead
*/
onBeforeUpdateThreadComment(
this: FWorkbook,
@@ -54,9 +59,7 @@ export interface IFWorkbookThreadCommentMixin {
): IDisposable;
/**
* The onBeforeDeleteThreadComment event is fired before the thread comment is deleted.
* @param callback Callback function that will be called when the event is fired
* @returns A disposable object that can be used to unsubscribe from the event
* @deprecated use `univerAPI.addEvent(univerAPI.event.BeforeCommentDelete, () => {})` as instead
*/
onBeforeDeleteThreadComment(
this: FWorkbook,
@@ -75,12 +78,29 @@ export class FWorkbookThreadCommentMixin extends FWorkbook implements IFWorkbook
});
}
override getComments(): FThreadComment[] {
return this._threadCommentModel.getUnit(this._workbook.getUnitId()).map((i) => this._injector.createInstance(FThreadComment, i.root));
}
override clearComments(): Promise<boolean> {
const comments = this.getComments();
const promises = comments.map((comment) => comment.deleteAsync());
return Promise.all(promises).then(() => true);
}
/**
* @deprecated
*/
override onThreadCommentChange(callback: (commentUpdate: CommentUpdate) => void | false): IDisposable {
return toDisposable(this._threadCommentModel.commentUpdate$
.pipe(filter((change) => change.unitId === this._workbook.getUnitId()))
.subscribe(callback));
}
/**
* @deprecated
*/
override onBeforeAddThreadComment(callback: (params: IAddCommentCommandParams, options: IExecutionOptions | undefined) => void | false): IDisposable {
return toDisposable(this._commandService.beforeCommandExecuted((commandInfo, options) => {
const params = commandInfo.params as IAddCommentCommandParams;
@@ -95,6 +115,9 @@ export class FWorkbookThreadCommentMixin extends FWorkbook implements IFWorkbook
}));
}
/**
* @deprecated
*/
override onBeforeUpdateThreadComment(callback: (params: IUpdateCommandParams, options: IExecutionOptions | undefined) => void | false): IDisposable {
return toDisposable(this._commandService.beforeCommandExecuted((commandInfo, options) => {
const params = commandInfo.params as IUpdateCommandParams;
@@ -109,6 +132,9 @@ export class FWorkbookThreadCommentMixin extends FWorkbook implements IFWorkbook
}));
}
/**
* @deprecated
*/
override onBeforeDeleteThreadComment(callback: (params: IDeleteCommentCommandParams, options: IExecutionOptions | undefined) => void | false): IDisposable {
return toDisposable(this._commandService.beforeCommandExecuted((commandInfo, options) => {
const params = commandInfo.params as IDeleteCommentCommandParams;
@@ -26,18 +26,34 @@ export interface IFWorksheetCommentMixin {
/**
* Get all comments in the current sheet
* @returns all comments in the current sheet
* ```ts
* const workbook = univerAPI.getActiveUniverSheet();
* const worksheet = workbook.getSheetById(sheetId);
* const comments = worksheet.getComments();
* ```
*/
getComments(): FThreadComment[];
/**
* Subscribe to comment events.
* @param callback (cellPos: Nullable<IHoverCellPosition>) => void Callback function, param contains comment info and target cell.
* @example
* Clear all comments in the current sheet
* ```ts
* univerAPI.getActiveWorkbook().getActiveSheet().onCommented((params) => {...})
* const workbook = univerAPI.getActiveUniverSheet();
* const worksheet = workbook.getSheetById(sheetId);
* await worksheet.clearComments();
* ```
*/
onCommented(callback: (params: IAddCommentCommandParams) => void): IDisposable;
clearComments(): Promise<boolean>;
/**
* get comment by comment id
* @param {string} commentId comment id
* ```ts
* const workbook = univerAPI.getActiveUniverSheet();
* const worksheet = workbook.getSheetById(sheetId);
* const comment = worksheet.getCommentById(commentId);
* ```
*/
getCommentById(commentId: string): FThreadComment | undefined;
}
export class FWorksheetCommentMixin extends FWorksheet implements IFWorksheetCommentMixin {
@@ -47,7 +63,18 @@ export class FWorksheetCommentMixin extends FWorksheet implements IFWorksheetCom
return comments.map((comment) => this._injector.createInstance(FThreadComment, comment));
}
override onCommented(callback: (params: IAddCommentCommandParams) => void): IDisposable {
override clearComments(): Promise<boolean> {
const comments = this.getComments();
const promises = comments.map((comment) => comment.deleteAsync());
return Promise.all(promises).then(() => true);
}
/**
* Subscribe to comment events.
* @param callback Callback function, param contains comment info and target cell.
*/
onCommented(callback: (params: IAddCommentCommandParams) => void): IDisposable {
const commandService = this._injector.get(ICommandService);
return commandService.onCommandExecuted((command) => {
if (command.id === AddCommentCommand.id) {
@@ -56,6 +83,14 @@ export class FWorksheetCommentMixin extends FWorksheet implements IFWorksheetCom
}
});
}
override getCommentById(commentId: string): FThreadComment | undefined {
const sheetsTheadCommentModel = this._injector.get(SheetsThreadCommentModel);
const comment = sheetsTheadCommentModel.getComment(this._workbook.getUnitId(), this._worksheet.getSheetId(), commentId);
if (comment) {
return this._injector.createInstance(FThreadComment, comment);
}
}
}
FWorksheet.extend(FWorksheetCommentMixin);
@@ -17,10 +17,11 @@
import './f-range';
import './f-workbook';
import './f-worksheet';
import './f-event';
export { FThreadComment } from './f-thread-comment';
// eslint-disable-next-line perfectionist/sort-exports
export type * from './f-event';
export type * from './f-range';
export type * from './f-thread-comment';
export { FThreadComment } from './f-thread-comment';
export type * from './f-workbook';
export type * from './f-worksheet';
@@ -47,6 +47,14 @@ import { isRichText } from '../editor/editing.render-controller';
import { discreteRangeToRange, type IDiscreteRange, virtualizeDiscreteRanges } from '../utils/range-tools';
// if special paste need append mutations instead of replace the default, it can use this function to generate default mutations.
/**
*
* @param pasteFrom
* @param pasteTo
* @param data
* @param payload
* @param accessor
*/
export function getDefaultOnPasteCellMutations(
pasteFrom: ISheetDiscreteRangeLocation,
pasteTo: ISheetDiscreteRangeLocation,
@@ -101,6 +109,18 @@ export function getDefaultOnPasteCellMutations(
};
}
/**
*
* @param from
* @param from.unitId
* @param from.subUnitId
* @param from.range
* @param to
* @param to.unitId
* @param to.subUnitId
* @param to.range
* @param accessor
*/
export function getMoveRangeMutations(
from: {
unitId: string;
@@ -292,6 +312,13 @@ export function getMoveRangeMutations(
};
}
/**
*
* @param pasteTo
* @param pasteFrom
* @param matrix
* @param accessor
*/
export function getSetCellValueMutations(
pasteTo: ISheetDiscreteRangeLocation,
pasteFrom: Nullable<ISheetDiscreteRangeLocation>,
@@ -350,6 +377,13 @@ export function getSetCellValueMutations(
};
}
/**
*
* @param pasteTo
* @param pasteFrom
* @param matrix
* @param accessor
*/
export function getSetCellCustomMutations(
pasteTo: ISheetDiscreteRangeLocation,
pasteFrom: Nullable<ISheetDiscreteRangeLocation>,
@@ -397,6 +431,13 @@ export function getSetCellCustomMutations(
};
}
/**
*
* @param pasteTo
* @param matrix
* @param accessor
* @param withRichFormat
*/
export function getSetCellStyleMutations(
pasteTo: ISheetDiscreteRangeLocation,
matrix: ObjectMatrix<ICellDataWithSpanInfo>,
@@ -480,6 +521,12 @@ export function getSetCellStyleMutations(
};
}
/**
*
* @param pasteTo
* @param matrix
* @param accessor
*/
export function getClearCellStyleMutations(
pasteTo: ISheetDiscreteRangeLocation,
matrix: ObjectMatrix<ICellDataWithSpanInfo>,
@@ -525,6 +572,12 @@ export function getClearCellStyleMutations(
return { undos: undoMutationsInfo, redos: redoMutationsInfo };
}
/**
*
* @param pasteTo
* @param matrix
* @param accessor
*/
export function getClearCellValueMutations(
pasteTo: ISheetDiscreteRangeLocation,
matrix: ObjectMatrix<ICellDataWithSpanInfo>,
@@ -566,6 +619,12 @@ export function getClearCellValueMutations(
return { undos: undoMutationsInfo, redos: redoMutationsInfo };
}
/**
*
* @param pasteTo
* @param matrix
* @param accessor
*/
export function getClearAndSetMergeMutations(
pasteTo: ISheetDiscreteRangeLocation,
matrix: ObjectMatrix<ICellDataWithSpanInfo>,
@@ -662,6 +721,10 @@ export function getClearAndSetMergeMutations(
return { undos: undoMutationsInfo, redos: redoMutationsInfo };
}
/**
*
* @param text
*/
export function generateBody(text: string): IDocumentBody {
if (!text.includes('\r') && Tools.isLegalUrl(text)) {
const id = generateRandomId();
+199 -3
View File
@@ -14,9 +14,124 @@
* limitations under the License.
*/
import type { IEventBase } from '@univerjs/core';
import type { DeviceInputEventType } from '@univerjs/engine-render';
import type { FRange, FWorkbook, FWorksheet } from '@univerjs/sheets/facade';
import { FEventName } from '@univerjs/core';
import type { KeyCode } from '@univerjs/ui';
import { FEventName, type IEventBase, type RichTextValue } from '@univerjs/core';
/**
* Event interface triggered when cell editing starts
* @interface ISheetEditStartedEventParams
* @augments {IEventBase}
*/
export interface ISheetEditStartedEventParams extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet being edited */
worksheet: FWorksheet;
/** Row index of the editing cell */
row: number;
/** Column index of the editing cell */
column: number;
/** Type of input device event that triggered the edit */
eventType: DeviceInputEventType;
/** Optional keycode that triggered the edit */
keycode?: KeyCode;
/** Whether the edit is happening in zen editor mode */
isZenEditor: boolean;
}
/**
* Event interface triggered when cell editing ends
* @interface ISheetEditEndedEventParams
* @augments {IEventBase}
*/
export interface ISheetEditEndedEventParams extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet being edited */
worksheet: FWorksheet;
/** Row index of the edited cell */
row: number;
/** Column index of the edited cell */
column: number;
/** Type of input device event that triggered the edit end */
eventType: DeviceInputEventType;
/** Optional keycode that triggered the edit end */
keycode?: KeyCode;
/** Whether the edit happened in zen editor mode */
isZenEditor: boolean;
/** Whether the edit was confirmed or cancelled */
isConfirm: boolean;
}
/**
* Event interface triggered while cell content is being changed
* @interface ISheetEditChangingEventParams
* @augments {IEventBase}
*/
export interface ISheetEditChangingEventParams extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet being edited */
worksheet: FWorksheet;
/** Row index of the editing cell */
row: number;
/** Column index of the editing cell */
column: number;
/** Current value being edited */
value: RichTextValue;
/** Whether the edit is happening in zen editor mode */
isZenEditor: boolean;
}
/**
* Event interface triggered before cell editing starts
* @interface IBeforeSheetEditStartEventParams
* @augments {IEventBase}
*/
export interface IBeforeSheetEditStartEventParams extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet to be edited */
worksheet: FWorksheet;
/** Row index of the cell to be edited */
row: number;
/** Column index of the cell to be edited */
column: number;
/** Type of input device event triggering the edit */
eventType: DeviceInputEventType;
/** Optional keycode triggering the edit */
keycode?: KeyCode;
/** Whether the edit will happen in zen editor mode */
isZenEditor: boolean;
}
/**
* Event interface triggered before cell editing ends
* @interface IBeforeSheetEditEndEventParams
* @augments {IEventBase}
*/
export interface IBeforeSheetEditEndEventParams extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The worksheet being edited */
worksheet: FWorksheet;
/** Row index of the editing cell */
row: number;
/** Column index of the editing cell */
column: number;
/** Current value being edited */
value: RichTextValue;
/** Type of input device event triggering the edit end */
eventType: DeviceInputEventType;
/** Optional keycode triggering the edit end */
keycode?: KeyCode;
/** Whether the edit is happening in zen editor mode */
isZenEditor: boolean;
/** Whether the edit will be confirmed or cancelled */
isConfirm: boolean;
}
interface IFSheetsUIEventNameMixin {
/**
@@ -72,6 +187,62 @@ interface IFSheetsUIEventNameMixin {
* ```
*/
readonly ClipboardPasted: 'ClipboardPasted';
/**
* Event fired before a cell is edited
* @see {@link IBeforeSheetEditStartEventParams}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.Event.BeforeSheetEditStart, (params) => {
* const { worksheet, workbook, row, column, eventType, keycode, isZenEditor } = params;
* });
* ```
*/
readonly BeforeSheetEditStart: 'BeforeSheetEditStart';
/**
* Event fired after a cell is edited
* @see {@link ISheetEditEndedEventParams}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.Event.SheetEditStarted, (params) => {
* const { worksheet, workbook, row, column, eventType, keycode, isZenEditor } = params;
* });
* ```
*/
readonly SheetEditStarted: 'SheetEditStarted';
/**
* Event fired when a cell is being edited
* @see {@link ISheetEditChangingEventParams}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.Event.SheetEditChanging, (params) => {
* const { worksheet, workbook, row, column, value, isZenEditor } = params;
* });
* ```
*/
readonly SheetEditChanging: 'SheetEditChanging';
/**
* Event fired before a cell edit ends
* @see {@link IBeforeSheetEditEndEventParams}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.Event.BeforeSheetEditEnd, (params) => {
* const { worksheet, workbook, row, column, value, eventType, keycode, isZenEditor } = params;
* });
* ```
*/
readonly BeforeSheetEditEnd: 'BeforeSheetEditEnd';
/**
* Event fired after a cell edit ends
* @see {@link ISheetEditEndedEventParams}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.Event.SheetEditEnded, (params) => {
* const { worksheet, workbook, row, column, eventType, keycode, isZenEditor } = params;
* });
* ```
*/
readonly SheetEditEnded: 'SheetEditEnded';
}
export class FSheetsUIEventName extends FEventName implements IFSheetsUIEventNameMixin {
@@ -90,6 +261,26 @@ export class FSheetsUIEventName extends FEventName implements IFSheetsUIEventNam
override get ClipboardPasted(): 'ClipboardPasted' {
return 'ClipboardPasted' as const;
}
override get BeforeSheetEditStart(): 'BeforeSheetEditStart' {
return 'BeforeSheetEditStart';
}
override get SheetEditStarted(): 'SheetEditStarted' {
return 'SheetEditStarted';
}
override get SheetEditChanging(): 'SheetEditChanging' {
return 'SheetEditChanging';
}
override get BeforeSheetEditEnd(): 'BeforeSheetEditEnd' {
return 'BeforeSheetEditEnd';
}
override get SheetEditEnded(): 'SheetEditEnded' {
return 'SheetEditEnded';
}
}
export interface IBeforeClipboardChangeParam extends IEventBase {
@@ -147,6 +338,12 @@ interface IFSheetsUIEventParamConfig {
ClipboardChanged: IClipboardChangedParam;
BeforeClipboardPaste: IBeforeClipboardPasteParam;
ClipboardPasted: IClipboardPastedParam;
BeforeSheetEditStart: IBeforeSheetEditStartEventParams;
SheetEditStarted: ISheetEditStartedEventParams;
SheetEditChanging: ISheetEditChangingEventParams;
BeforeSheetEditEnd: IBeforeSheetEditEndEventParams;
SheetEditEnded: ISheetEditEndedEventParams;
}
FEventName.extend(FSheetsUIEventName);
@@ -155,4 +352,3 @@ declare module '@univerjs/core' {
interface FEventName extends IFSheetsUIEventNameMixin { }
interface IEventParamConfig extends IFSheetsUIEventParamConfig { }
}
+133 -12
View File
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import type { IDisposable, Injector, Nullable } from '@univerjs/core';
import type { DocumentDataModel, IDisposable, Injector, Nullable } from '@univerjs/core';
import type { IRichTextEditingMutationParams } from '@univerjs/docs';
import type {
IColumnsHeaderCfgParam,
IRowsHeaderCfgParam,
@@ -24,13 +25,14 @@ import type {
SpreadsheetColumnHeader,
SpreadsheetRowHeader,
} from '@univerjs/engine-render';
import type { ISheetPasteByShortKeyParams } from '@univerjs/sheets-ui';
import type { IBeforeClipboardChangeParam, IBeforeClipboardPasteParam } from './f-event';
import { FUniver, ICommandService, ILogService, toDisposable } from '@univerjs/core';
import type { IEditorBridgeServiceVisibleParam, ISheetPasteByShortKeyParams } from '@univerjs/sheets-ui';
import type { IBeforeClipboardChangeParam, IBeforeClipboardPasteParam, IBeforeSheetEditEndEventParams, IBeforeSheetEditStartEventParams, ISheetEditChangingEventParams, ISheetEditEndedEventParams, ISheetEditStartedEventParams } from './f-event';
import { CanceledError, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, FUniver, ICommandService, ILogService, IUniverInstanceService, RichTextValue, toDisposable } from '@univerjs/core';
import { RichTextEditingMutation } from '@univerjs/docs';
import { IRenderManagerService } from '@univerjs/engine-render';
import { ISheetClipboardService, SHEET_VIEW_KEY, SheetPasteShortKeyCommand } from '@univerjs/sheets-ui';
import { IEditorBridgeService, ISheetClipboardService, SetCellEditVisibleOperation, SHEET_VIEW_KEY, SheetPasteShortKeyCommand } from '@univerjs/sheets-ui';
import { FSheetHooks } from '@univerjs/sheets/facade';
import { CopyCommand, CutCommand, HTML_CLIPBOARD_MIME_TYPE, IClipboardInterfaceService, PasteCommand, PLAIN_TEXT_CLIPBOARD_MIME_TYPE, supportClipboardAPI } from '@univerjs/ui';
import { CopyCommand, CutCommand, HTML_CLIPBOARD_MIME_TYPE, IClipboardInterfaceService, KeyCode, PasteCommand, PLAIN_TEXT_CLIPBOARD_MIME_TYPE, supportClipboardAPI } from '@univerjs/ui';
export interface IFUniverSheetsUIMixin {
/**
@@ -74,18 +76,137 @@ export interface IFUniverSheetsUIMixin {
registerSheetMainExtension(unitId: string, ...extensions: SheetExtension[]): IDisposable;
/**
* Get sheet hooks.
* @returns {FSheetHooks} FSheetHooks instance.
* @example
* ``` ts
* univerAPI.getSheetHooks();
* ```
* @deprecated use `univerAPI.addEvent` as instead.
*/
getSheetHooks(): FSheetHooks;
}
export class FUniverSheetsUIMixin extends FUniver implements IFUniverSheetsUIMixin {
// eslint-disable-next-line max-lines-per-function
private _initSheetUIEvent(injector: Injector): void {
const commandService = injector.get(ICommandService);
this.disposeWithMe(commandService.beforeCommandExecuted((commandInfo) => {
if (commandInfo.id === SetCellEditVisibleOperation.id) {
if (!this._eventListend(this.Event.BeforeSheetEditStart) && !this._eventListend(this.Event.BeforeSheetEditEnd)) {
return;
}
const target = this.getCommandSheetTarget(commandInfo);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const editorBridgeService = injector.get(IEditorBridgeService);
const univerInstanceService = injector.get(IUniverInstanceService);
const params = commandInfo.params as IEditorBridgeServiceVisibleParam;
const { visible, keycode, eventType } = params;
const loc = editorBridgeService.getEditLocation()!;
if (visible) {
const eventParams: IBeforeSheetEditStartEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: false,
};
this.fireEvent(this.Event.BeforeSheetEditStart, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
} else {
const eventParams: IBeforeSheetEditEndEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: false,
value: RichTextValue.create(univerInstanceService.getUnit<DocumentDataModel>(DOCS_NORMAL_EDITOR_UNIT_ID_KEY)!.getSnapshot()),
isConfirm: keycode !== KeyCode.ESC,
};
this.fireEvent(this.Event.BeforeSheetEditEnd, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
}
}
}));
this.disposeWithMe(commandService.onCommandExecuted((commandInfo) => {
if (commandInfo.id === SetCellEditVisibleOperation.id) {
if (!this._eventListend(this.Event.SheetEditStarted) && !this._eventListend(this.Event.SheetEditEnded)) {
return;
}
const target = this.getCommandSheetTarget(commandInfo);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const editorBridgeService = injector.get(IEditorBridgeService);
const params = commandInfo.params as IEditorBridgeServiceVisibleParam;
const { visible, keycode, eventType } = params;
const loc = editorBridgeService.getEditLocation()!;
if (visible) {
const eventParams: ISheetEditStartedEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: false,
};
this.fireEvent(this.Event.SheetEditStarted, eventParams);
} else {
const eventParams: ISheetEditEndedEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: false,
isConfirm: keycode !== KeyCode.ESC,
};
this.fireEvent(this.Event.SheetEditEnded, eventParams);
}
}
if (commandInfo.id === RichTextEditingMutation.id) {
if (!this._eventListend(this.Event.SheetEditChanging)) {
return;
}
const target = this.getCommandSheetTarget(commandInfo);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const editorBridgeService = injector.get(IEditorBridgeService);
const univerInstanceService = injector.get(IUniverInstanceService);
const params = commandInfo.params as IRichTextEditingMutationParams;
if (!editorBridgeService.isVisible().visible) return;
const { unitId } = params;
if (unitId === DOCS_NORMAL_EDITOR_UNIT_ID_KEY) {
const { row, column } = editorBridgeService.getEditLocation()!;
const eventParams: ISheetEditChangingEventParams = {
workbook,
worksheet,
row,
column,
value: RichTextValue.create(univerInstanceService.getUnit<DocumentDataModel>(DOCS_NORMAL_EDITOR_UNIT_ID_KEY)!.getSnapshot()),
isZenEditor: false,
};
this.fireEvent(this.Event.SheetEditChanging, eventParams);
}
}
}));
}
override _initialize(injector: Injector): void {
this._initSheetUIEvent(injector);
const commandService = injector.get(ICommandService);
this.disposeWithMe(commandService.beforeCommandExecuted((commandInfo) => {
switch (commandInfo.id) {
+2 -2
View File
@@ -21,10 +21,10 @@ import './f-permission';
import './f-sheet-hooks';
import './f-event';
export { type IFComponentKey, transformComponentKey } from './f-range';
export type * from './f-event';
// eslint-disable-next-line perfectionist/sort-exports
export type * from './f-permission';
export { type IFComponentKey, transformComponentKey } from './f-range';
export type * from './f-sheet-hooks';
export type * from './f-univer';
export type * from './f-workbook';
@@ -22,6 +22,11 @@ import React from 'react';
import { CellAlertType } from '../../services/cell-alert-manager.service';
import styles from './index.module.less';
/**
*
* @param root0
* @param root0.popup
*/
export function CellAlert({ popup }: { popup: ICanvasPopup }) {
const alert = popup.extraProps?.alert;
+8 -1
View File
@@ -23,7 +23,8 @@
"exports": {
".": "./src/index.ts",
"./*": "./src/*",
"./locale/*": "./src/locale/*.ts"
"./locale/*": "./src/locale/*.ts",
"./facade": "./src/facade/index.ts"
},
"main": "./src/index.ts",
"types": "./lib/types/index.d.ts",
@@ -47,6 +48,11 @@
"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/*": "./lib/*"
}
},
@@ -69,6 +75,7 @@
},
"dependencies": {
"@univerjs/core": "workspace:*",
"@univerjs/docs": "workspace:*",
"@univerjs/docs-ui": "workspace:*",
"@univerjs/engine-render": "workspace:*",
"@univerjs/icons": "^0.2.10",
@@ -0,0 +1,172 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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, Injector } from '@univerjs/core';
import type { IRichTextEditingMutationParams } from '@univerjs/docs';
import type { IEditorBridgeServiceVisibleParam } from '@univerjs/sheets-ui';
import type { IBeforeSheetEditEndEventParams, IBeforeSheetEditStartEventParams, ISheetEditChangingEventParams, ISheetEditEndedEventParams, ISheetEditStartedEventParams } from '@univerjs/sheets-ui/facade';
import { CanceledError, DOCS_ZEN_EDITOR_UNIT_ID_KEY, FUniver, ICommandService, IUniverInstanceService, RichTextValue } from '@univerjs/core';
import { RichTextEditingMutation } from '@univerjs/docs';
import { IEditorBridgeService } from '@univerjs/sheets-ui';
import { CancelZenEditCommand, ConfirmZenEditCommand, OpenZenEditorCommand } from '@univerjs/sheets-zen-editor';
export interface IFUniverSheetsZenEditorMixin {}
export class FUniverSheetsZenEditorMixin extends FUniver implements IFUniverSheetsZenEditorMixin {
// eslint-disable-next-line max-lines-per-function
private _initSheetZenEditorEvent(injector: Injector): void {
const commandService = injector.get(ICommandService);
this.disposeWithMe(commandService.beforeCommandExecuted((commandInfo) => {
if (
commandInfo.id === OpenZenEditorCommand.id ||
commandInfo.id === CancelZenEditCommand.id ||
commandInfo.id === ConfirmZenEditCommand.id
) {
if (!this._eventListend(this.Event.BeforeSheetEditStart) && !this._eventListend(this.Event.BeforeSheetEditEnd)) {
return;
}
const target = this.getCommandSheetTarget(commandInfo);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const editorBridgeService = injector.get(IEditorBridgeService);
const univerInstanceService = injector.get(IUniverInstanceService);
const params = commandInfo.params as IEditorBridgeServiceVisibleParam;
const { keycode, eventType } = params;
const loc = editorBridgeService.getEditLocation()!;
if (commandInfo.id === OpenZenEditorCommand.id) {
const eventParams: IBeforeSheetEditStartEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: true,
};
this.fireEvent(this.Event.BeforeSheetEditStart, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
} else {
const eventParams: IBeforeSheetEditEndEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: true,
value: RichTextValue.create(univerInstanceService.getUnit<DocumentDataModel>(DOCS_ZEN_EDITOR_UNIT_ID_KEY)!.getSnapshot()),
isConfirm: commandInfo.id === ConfirmZenEditCommand.id,
};
this.fireEvent(this.Event.BeforeSheetEditEnd, eventParams);
if (eventParams.cancel) {
throw new CanceledError();
}
}
}
}));
this.disposeWithMe(commandService.onCommandExecuted((commandInfo) => {
if (
commandInfo.id === OpenZenEditorCommand.id ||
commandInfo.id === CancelZenEditCommand.id ||
commandInfo.id === ConfirmZenEditCommand.id
) {
if (!this._eventListend(this.Event.SheetEditStarted) && !this._eventListend(this.Event.SheetEditEnded)) {
return;
}
const target = this.getCommandSheetTarget(commandInfo);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const editorBridgeService = injector.get(IEditorBridgeService);
const params = commandInfo.params as IEditorBridgeServiceVisibleParam;
const { keycode, eventType } = params;
const loc = editorBridgeService.getEditLocation()!;
if (commandInfo.id === OpenZenEditorCommand.id) {
const eventParams: ISheetEditStartedEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: true,
};
this.fireEvent(this.Event.SheetEditStarted, eventParams);
} else {
const eventParams: ISheetEditEndedEventParams = {
row: loc.row,
column: loc.column,
eventType,
keycode,
workbook,
worksheet,
isZenEditor: true,
isConfirm: commandInfo.id === ConfirmZenEditCommand.id,
};
this.fireEvent(this.Event.SheetEditEnded, eventParams);
}
}
if (commandInfo.id === RichTextEditingMutation.id) {
if (!this._eventListend(this.Event.SheetEditChanging)) {
return;
}
const target = this.getCommandSheetTarget(commandInfo);
if (!target) {
return;
}
const { workbook, worksheet } = target;
const editorBridgeService = injector.get(IEditorBridgeService);
const univerInstanceService = injector.get(IUniverInstanceService);
const params = commandInfo.params as IRichTextEditingMutationParams;
if (!editorBridgeService.isVisible().visible) return;
const { unitId } = params;
if (unitId === DOCS_ZEN_EDITOR_UNIT_ID_KEY) {
const { row, column } = editorBridgeService.getEditLocation()!;
const eventParams: ISheetEditChangingEventParams = {
workbook,
worksheet,
row,
column,
value: RichTextValue.create(univerInstanceService.getUnit<DocumentDataModel>(DOCS_ZEN_EDITOR_UNIT_ID_KEY)!.getSnapshot()),
isZenEditor: true,
};
this.fireEvent(this.Event.SheetEditChanging, eventParams);
}
}
}));
}
override _initialize(injector: Injector): void {
this._initSheetZenEditorEvent(injector);
}
}
FUniver.extend(FUniverSheetsZenEditorMixin);
declare module '@univerjs/core' {
// eslint-disable-next-line ts/naming-convention
interface FUniver extends IFUniverSheetsZenEditorMixin { }
}
@@ -0,0 +1,65 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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 { ICommandService } from '@univerjs/core';
import { CancelZenEditCommand, ConfirmZenEditCommand, OpenZenEditorCommand } from '@univerjs/sheets-zen-editor';
import { FWorkbook } from '@univerjs/sheets/facade';
export interface IFWorkbookSheetsZenEditorMixin {
/**
* Start the zen editing process
* @returns A promise that resolves to a boolean indicating whether the zen editing process was started successfully.
* @example
* ```ts
* univerAPI.getActiveWorkbook().startZenEditingAsync();
* ```
*/
startZenEditingAsync(): Promise<boolean>;
/**
* End the zen editing process
* @async
* @param {boolean} save - Whether to save the changes, default is true
* @returns A promise that resolves to a boolean indicating whether the zen editing process was ended successfully.
* @example
* ```ts
* univerAPI.getActiveWorkbook().endZenEditingAsync(false);
* ```
*/
endZenEditingAsync(save?: boolean): Promise<boolean>;
}
export class FWorkbookSheetsZenEditorMixin extends FWorkbook implements IFWorkbookSheetsZenEditorMixin {
override startZenEditingAsync(): Promise<boolean> {
const commandService = this._injector.get(ICommandService);
return commandService.executeCommand(OpenZenEditorCommand.id);
}
override endZenEditingAsync(save = true): Promise<boolean> {
const commandService = this._injector.get(ICommandService);
return save
? commandService.executeCommand(ConfirmZenEditCommand.id)
: commandService.executeCommand(CancelZenEditCommand.id);
}
}
FWorkbook.extend(FWorkbookSheetsZenEditorMixin);
declare module '@univerjs/sheets/facade' {
// eslint-disable-next-line ts/naming-convention
interface FWorkbook extends IFWorkbookSheetsZenEditorMixin {}
}
@@ -0,0 +1,21 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './f-univer';
import './f-workbook';
export type * from './f-univer';
export type * from './f-workbook';
+16
View File
@@ -17,6 +17,12 @@
import type { IBorderData, ICellData, IDocumentData, IKeyValue, IParagraph, IStyleData, ITextRun, ITextStyle, Nullable, Styles } from '@univerjs/core';
import { normalizeTextRuns, Tools } from '@univerjs/core';
/**
*
* @param styles
* @param oldVal
* @param newVal
*/
export function handleStyle(styles: Styles, oldVal: ICellData, newVal: ICellData) {
// use null to clear style
const oldStyle = styles.getStyleByCell(oldVal);
@@ -65,6 +71,8 @@ export function handleStyle(styles: Styles, oldVal: ICellData, newVal: ICellData
/**
* Convert old style data for storage
* @param style
* @param oldStyle
* @param newStyle
*/
export function transformStyle(oldStyle: Nullable<IStyleData>, newStyle: Nullable<IStyleData>): Nullable<IStyleData> {
// If there is no newly set style, directly store the historical style
@@ -89,6 +97,8 @@ export function transformStyle(oldStyle: Nullable<IStyleData>, newStyle: Nullabl
/**
* Convert old style border for storage
* @param style
* @param oldBorders
* @param newBorders
*/
function transformBorders(oldBorders: IBorderData, newBorders: Nullable<IBorderData>): IBorderData {
// If there is no newly set border, directly store the historical border
@@ -111,6 +121,7 @@ function transformBorders(oldBorders: IBorderData, newBorders: Nullable<IBorderD
* merge new style to old style
* @param oldStyle
* @param newStyle
* @param isRichText
*/
function mergeStyle(
oldStyle: Nullable<IStyleData>,
@@ -154,6 +165,11 @@ function mergeStyle(
return backupStyle;
}
/**
*
* @param paragraphs
* @param offset
*/
function skipParagraphs(paragraphs: IParagraph[], offset: number): number {
if (paragraphs.some((p) => p.startIndex === offset)) {
return skipParagraphs(paragraphs, offset + 1);
+100 -4
View File
@@ -14,35 +14,132 @@
* limitations under the License.
*/
import type { IEventBase, IWorksheetData } from '@univerjs/core';
import type { IEventBase, IWorkbookData, IWorksheetData, UniverInstanceType } from '@univerjs/core';
import type { FWorkbook } from './f-workbook';
import type { FWorksheet } from './f-worksheet';
import { FEventName } from '@univerjs/core';
export interface IFSheetEventMixin {
/**
* Event fired after a sheet is created
* @see {@link ISheetCreatedEventParams}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.SheetCreated, (params) => {
* const { workbook, worksheet } = params;
* console.log('unit created', params);
* });
* ```
*/
get SheetCreated(): 'SheetCreated' ;
/**
* Event fired before a sheet is created
* @see {@link IBeforeSheetCreateEventParams}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.BeforeSheetCreate, (params) => {
* const { workbook, index, sheet } = params;
* console.log('unit created', params);
* });
* ```
*/
get BeforeSheetCreate(): 'BeforeSheetCreate';
/**
* Event fired after a workbook is created
* @see {@link IWorkbookCreateParam}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.WorkbookCreated, (params) => {
* const { unitId, type, workbook, unit } = params;
* console.log('unit created', params);
* });
* ```
*/
get WorkbookCreated(): 'WorkbookCreated';
/**
* Event fired after a workbook is disposed
* @see {@link IWorkbookDisposedEvent}
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.WorkbookDisposed, (params) => {
* const { unitId, unitType, snapshot } = params;
* console.log('unit disposed', params);
* });
* ```
*/
get WorkbookDisposed(): 'WorkbookDisposed';
}
export interface IWorkbookCreateParam extends IEventBase {
unitId: string;
type: UniverInstanceType.UNIVER_SHEET;
workbook: FWorkbook;
unit: FWorkbook;
}
export interface IWorkbookDisposedEvent extends IEventBase {
unitId: string;
unitType: UniverInstanceType.UNIVER_SHEET;
snapshot: IWorkbookData;
}
export class FSheetEventName extends FEventName implements IFSheetEventMixin {
override get SheetCreated(): 'SheetCreated' { return 'SheetCreated' as const; }
override get BeforeSheetCreate(): 'BeforeSheetCreate' { return 'BeforeSheetCreate' as const; }
override get SheetCreated(): 'SheetCreated' {
return 'SheetCreated' as const;
}
override get BeforeSheetCreate(): 'BeforeSheetCreate' {
return 'BeforeSheetCreate' as const;
}
override get WorkbookCreated(): 'WorkbookCreated' {
return 'WorkbookCreated' as const;
}
override get WorkbookDisposed(): 'WorkbookDisposed' {
return 'WorkbookDisposed' as const;
}
}
/**
* Event interface triggered before creating a new worksheet
* @interface IBeforeSheetCreateEventParams
* @augments {IEventBase}
*/
export interface IBeforeSheetCreateEventParams extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** Optional index where the new sheet will be inserted */
index?: number;
/** Optional initial worksheet data */
sheet?: IWorksheetData;
}
/**
* Event interface triggered after a worksheet is created
* @interface ISheetCreatedEventParams
* @augments {IEventBase}
*/
export interface ISheetCreatedEventParams extends IEventBase {
/** The workbook instance */
workbook: FWorkbook;
/** The newly created worksheet */
worksheet: FWorksheet;
}
/**
* Configuration interface for sheet-related events
* @interface ISheetEventParamConfig
*/
export interface ISheetEventParamConfig {
/** Event fired after a worksheet is created */
SheetCreated: ISheetCreatedEventParams;
/** Event fired before creating a worksheet */
BeforeSheetCreate: IBeforeSheetCreateEventParams;
/** Event fired after a workbook is created */
WorkbookCreated: IWorkbookCreateParam;
/** Event fired when a workbook is disposed */
WorkbookDisposed: IWorkbookDisposedEvent;
}
FEventName.extend(FSheetEventName);
@@ -51,4 +148,3 @@ declare module '@univerjs/core' {
interface FEventName extends IFSheetEventMixin { }
interface IEventParamConfig extends ISheetEventParamConfig { }
}
+280 -22
View File
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import type { CellValue, ICellData, IColorStyle, IObjectMatrixPrimitiveType, IRange, IStyleData, ITextDecoration, Nullable, Workbook, Worksheet } from '@univerjs/core';
import type { ISetHorizontalTextAlignCommandParams, ISetStyleCommandParams, ISetTextWrapCommandParams, ISetVerticalTextAlignCommandParams, IStyleTypeValue, SplitDelimiterEnum } from '@univerjs/sheets';
import type { CellValue, CustomData, ICellData, IColorStyle, IDocumentData, IObjectMatrixPrimitiveType, IRange, IStyleData, ITextDecoration, Nullable, Workbook, Worksheet } from '@univerjs/core';
import type { ISetHorizontalTextAlignCommandParams, ISetRangeValuesCommandParams, ISetStyleCommandParams, ISetTextWrapCommandParams, ISetVerticalTextAlignCommandParams, IStyleTypeValue, SplitDelimiterEnum } from '@univerjs/sheets';
import type { FHorizontalAlignment, FVerticalAlignment } from './utils';
import { BooleanNumber, Dimension, FBaseInitialable, ICommandService, Inject, Injector, Rectangle, WrapStrategy } from '@univerjs/core';
import { BooleanNumber, Dimension, FBaseInitialable, ICommandService, Inject, Injector, Rectangle, RichTextValue, WrapStrategy } from '@univerjs/core';
import { FormulaDataModel, serializeRange, serializeRangeWithSheet } from '@univerjs/engine-formula';
import { addMergeCellsUtil, DeleteWorksheetRangeThemeStyleCommand, getAddMergeMutationRangeByType, RemoveWorksheetMergeCommand, SetHorizontalTextAlignCommand, SetRangeValuesCommand, SetStyleCommand, SetTextWrapCommand, SetVerticalTextAlignCommand, SetWorksheetRangeThemeStyleCommand, SheetRangeThemeService, SplitTextToColumnsCommand } from '@univerjs/sheets';
import { FWorkbook } from './f-workbook';
@@ -55,6 +55,14 @@ export class FRange extends FBaseInitialable {
return this._worksheet.getName();
}
/**
* Gets the ID of the worksheet
* @returns The ID of the worksheet
*/
getSheetId(): string {
return this._worksheet.getSheetId();
}
/**
* Gets the area where the statement is applied
* @returns The area where the statement is applied
@@ -95,14 +103,6 @@ export class FRange extends FBaseInitialable {
return this._range.endRow - this._range.startRow + 1;
}
/**
* Return first cell model data in this range
* @returns The cell model data
*/
getCellData(): ICellData | null {
return this._worksheet.getCell(this._range.startRow, this._range.startColumn) ?? null;
}
/**
* Return range whether this range is merged
* @returns if true is merged
@@ -115,7 +115,7 @@ export class FRange extends FBaseInitialable {
/**
* Return first cell style data in this range
* @returns The cell style data
* @returns {IStyleData | null} The cell style data
*/
getCellStyleData(): IStyleData | null {
const cell = this.getCellData();
@@ -129,7 +129,7 @@ export class FRange extends FBaseInitialable {
/**
* Returns the value of the cell at the start of this range.
* @returns The value of the cell.
* @returns {CellValue | null} The value of the cell.
*/
getValue(): CellValue | null {
return this._worksheet.getCell(this._range.startRow, this._range.startColumn)?.v ?? null;
@@ -138,7 +138,7 @@ export class FRange extends FBaseInitialable {
/**
* Returns the rectangular grid of values for this range.
* Returns a two-dimensional array of values, indexed by row, then by column.
* @returns A two-dimensional array of values.
* @returns {Nullable<CellValue>[][]} A two-dimensional array of values.
*/
getValues(): Nullable<CellValue>[][] {
const { startRow, endRow, startColumn, endColumn } = this._range;
@@ -157,10 +157,32 @@ export class FRange extends FBaseInitialable {
}
/**
* Returns the cell data for the cells in the range.
* @returns A two-dimensional array of cell data.
* Return first cell model data in this range
* @returns {ICellData | null} The cell model data
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getCellData()
* ```
*/
getCellDataGrid(): Nullable<ICellData>[][] {
getCellData(): ICellData | null {
return this._worksheet.getCell(this._range.startRow, this._range.startColumn) ?? null;
}
/**
* Returns the cell data for the cells in the range.
* @returns {Nullable<ICellData>[][]} A two-dimensional array of cell data.
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getCellDatas()
* ```
*/
getCellDatas(): Nullable<ICellData>[][] {
const { startRow, endRow, startColumn, endColumn } = this._range;
const range: Nullable<ICellData>[][] = [];
@@ -174,9 +196,90 @@ export class FRange extends FBaseInitialable {
return range;
}
/**
* @deprecated use `getCellDatas` instead.
*/
getCellDataGrid(): Nullable<ICellData>[][] {
return this.getCellDatas();
}
/**
* Returns the rich text value for the cell at the start of this range.
* @returns {Nullable<RichTextValue>} The rich text value
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getRichTextValue()
* ```
*/
getRichTextValue(): Nullable<RichTextValue> {
const data = this.getCellData();
if (data?.p) {
return new RichTextValue(data.p);
}
return null;
}
/**
* Returns the rich text value for the cells in the range.
* @returns {Nullable<RichTextValue>[][]} A two-dimensional array of RichTextValue objects.
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getRichTextValues()
* ```
*/
getRichTextValues(): Nullable<RichTextValue>[][] {
const dataGrid = this.getCellDataGrid();
return dataGrid.map((row) => row.map((data) => data?.p ? new RichTextValue(data.p) : null));
}
/**
* Returns the value and rich text value for the cell at the start of this range.
* @returns {Nullable<CellValue | RichTextValue>} The value and rich text value
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getValueAndRichTextValue()
* ```
*/
getValueAndRichTextValue(): Nullable<CellValue | RichTextValue> {
const cell = this.getCellData();
return cell?.p ? new RichTextValue(cell.p) : cell?.v;
}
/**
* Returns the value and rich text value for the cells in the range.
* @returns {Nullable<CellValue | RichTextValue>[][]} A two-dimensional array of value and rich text value
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getValueAndRichTextValues()
* ```
*/
getValueAndRichTextValues(): Nullable<CellValue | RichTextValue>[][] {
const dataGrid = this.getCellDatas();
return dataGrid.map((row) => row.map((data) => data?.p ? new RichTextValue(data.p) : data?.v));
}
/**
* Returns the formulas (A1 notation) for the cells in the range. Entries in the 2D array are empty strings for cells with no formula.
* @returns A two-dimensional array of formulas in string format.
* @returns {string[][]} A two-dimensional array of formulas in string format.
* @example
* ```ts
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getFormulas()
* ```
*/
getFormulas(): string[][] {
const formulas: string[][] = [];
@@ -215,14 +318,81 @@ export class FRange extends FBaseInitialable {
return transformCoreVerticalAlignment(this._worksheet.getRange(this._range).getVerticalAlignment());
}
/**
* Set custom meta data for first cell in current range.
* @param {CustomData} data The custom meta data
* @returns {FRange} This range, for chaining
* ```ts
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .setCustomMetaData({ key: 'value' });
* ```
*/
setCustomMetaData(data: CustomData): FRange {
return this.setValue({
custom: data,
});
}
/**
* Set custom meta data for current range.
* @param {CustomData[][]} datas The custom meta data
* @returns {FRange} This range, for chaining
* ```ts
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .setCustomMetaDatas([[{ key: 'value' }]]);
* ```
*/
setCustomMetaDatas(datas: CustomData[][]): FRange {
return this.setValues(datas.map((row) => row.map((data) => ({ custom: data }))));
}
/**
* Returns the custom meta data for the cell at the start of this range.
* @returns {CustomData | null} The custom meta data
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getCustomMetaData()
* ```
*/
getCustomMetaData(): CustomData | null {
const cell = this.getCellData();
return cell?.custom ?? null;
}
/**
* Returns the custom meta data for the cells in the range.
* @returns {CustomData[][]} A two-dimensional array of custom meta data
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .getCustomMetaDatas()
* ```
*/
getCustomMetaDatas(): Nullable<CustomData>[][] {
const dataGrid = this.getCellDataGrid();
return dataGrid.map((row) => row.map((data) => data?.custom ?? null));
}
// #region editing
/**
* Set background color for current range.
* @param color {string}
* @example
* ```typescript
* univerAPI.getActiveWorkbook().getActiveSheet().getActiveRange().setBackgroundColor('red')
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .setBackgroundColor('red')
* ```
*/
setBackgroundColor(color: string): FRange {
@@ -254,8 +424,8 @@ export class FRange extends FBaseInitialable {
}
/**
* The value can be a number, string, boolean, or standard cell format. If it begins with `=`, it is interpreted as a formula. The value is tiled to all cells in the range.
* @param value
* Set new value for current cell, first cell in this range.
* @param {CellValue | ICellData} value The value can be a number, string, boolean, or standard cell format. If it begins with `=`, it is interpreted as a formula. The value is tiled to all cells in the range.
*/
setValue(value: CellValue | ICellData): FRange {
const realValue = covertCellValue(value);
@@ -274,6 +444,94 @@ export class FRange extends FBaseInitialable {
return this;
}
/**
* Set new value for current cell, first cell in this range.
* @param {CellValue | ICellData} value The value can be a number, string, boolean, or standard cell format. If it begins with `=`, it is interpreted as a formula. The value is tiled to all cells in the range.
* ```ts
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .setValueForCell(1);
* ```
*/
setValueForCell(value: CellValue | ICellData): FRange {
const realValue = covertCellValue(value);
if (!realValue) {
throw new Error('Invalid value');
}
this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, {
unitId: this._workbook.getUnitId(),
subUnitId: this._worksheet.getSheetId(),
range: {
startColumn: this._range.startColumn,
startRow: this._range.startRow,
endColumn: this._range.endColumn,
endRow: this._range.endRow,
},
value: realValue,
});
return this;
}
/**
* Set the rich text value for the cell at the start of this range.
* @param {RichTextValue | IDocumentData} value The rich text value
* @returns {FRange} The range
* @example
* ```
* univerAPI.getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .setRichTextValueForCell(new RichTextValue().insertText('Hello'));
* ```
*/
setRichTextValueForCell(value: RichTextValue | IDocumentData): FRange {
const p = value instanceof RichTextValue ? value.getData() : value;
const params: ISetRangeValuesCommandParams = {
unitId: this._workbook.getUnitId(),
subUnitId: this._worksheet.getSheetId(),
range: {
startColumn: this._range.startColumn,
startRow: this._range.startRow,
endColumn: this._range.endColumn,
endRow: this._range.endRow,
},
value: { p },
};
this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, params);
return this;
}
/**
* Set the rich text value for the cells in the range.
* @param {RichTextValue[][]} values The rich text value
* @returns {FRange} The range
* @example
* ```ts
* univerAPI
* .getActiveWorkbook()
* .getActiveSheet()
* .getActiveRange()
* .setRichTextValues([[new RichTextValue().insertText('Hello')]]);
* ```
*/
setRichTextValues(values: (RichTextValue | IDocumentData)[][]): FRange {
const cellDatas = values.map((row) => row.map((item) => item && { p: item instanceof RichTextValue ? item.getData() : item }));
const realValue = covertCellValues(cellDatas, this._range);
const params: ISetRangeValuesCommandParams = {
unitId: this._workbook.getUnitId(),
subUnitId: this._worksheet.getSheetId(),
range: this._range,
value: realValue,
};
this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, params);
return this;
}
/**
* Set the cell wrap of the given range.
* Cells with wrap enabled (the default) resize to display their full content. Cells with wrap disabled display as much as possible in the cell without resizing or running to multiple lines.
+102 -3
View File
@@ -14,10 +14,11 @@
* limitations under the License.
*/
import type { IDisposable, Injector, IWorkbookData, Workbook } from '@univerjs/core';
import type { ICommandInfo, IDisposable, Injector, IWorkbookData, Nullable, Workbook } from '@univerjs/core';
import type { IInsertSheetCommandParams } from '@univerjs/sheets';
import type { IBeforeSheetCreateEventParams, ISheetCreatedEventParams } from './f-event';
import { FUniver, ICommandService, IUniverInstanceService, toDisposable, UniverInstanceType } from '@univerjs/core';
import type { FWorksheet } from './f-worksheet';
import { CanceledError, FUniver, ICommandService, IUniverInstanceService, toDisposable, UniverInstanceType } from '@univerjs/core';
import { InsertSheetCommand } from '@univerjs/sheets';
import { FDefinedNameBuilder } from './f-defined-name';
import { FPermission } from './f-permission';
@@ -76,9 +77,105 @@ export interface IFUniverSheetsMixin {
* ```
*/
newDefinedName(): FDefinedNameBuilder;
/**
* Get the target of the sheet.
* @param {string} unitId - The unitId of the sheet.
* @param {string} subUnitId - The subUnitId of the sheet.
* @returns {Nullable<{ workbook: FWorkbook; worksheet: FWorksheet }>} - The target of the sheet.
* @example
* ```ts
* univerAPI.getSheetTarget('unitId', 'subUnitId');
* ```
*/
getSheetTarget(unitId: string, subUnitId: string): Nullable<{ workbook: FWorkbook; worksheet: FWorksheet }>;
/**
* Get the target of the sheet.
* @param {ICommandInfo<object>} commandInfo - The commandInfo of the command.
* @returns {Nullable<{ workbook: FWorkbook; worksheet: FWorksheet }>} - The target of the sheet.
* @example
* ```ts
* univerAPI.addEvent(univerAPI.event.CommandExecuted, (commandInfo) => {
* const target = univerAPI.getCommandSheetTarget(commandInfo);
* if (!target) return;
* const { workbook, worksheet } = target;
* });
* ```
*/
getCommandSheetTarget(commandInfo: ICommandInfo<object>): Nullable<{ workbook: FWorkbook; worksheet: FWorksheet }>;
}
export class FUniverSheetsMixin extends FUniver implements IFUniverSheetsMixin {
override getCommandSheetTarget(commandInfo: ICommandInfo<object>): Nullable<{ workbook: FWorkbook; worksheet: FWorksheet }> {
const params = commandInfo.params as { unitId: string; subUnitId: string; sheetId: string };
if (!params) return;
const workbook = params.unitId ? this.getUniverSheet(params.unitId) : this.getActiveWorkbook?.();
if (!workbook) {
return;
}
const worksheet = workbook.getSheetBySheetId(params.subUnitId || params.sheetId) || workbook.getActiveSheet();
if (!worksheet) {
return;
}
return { workbook, worksheet };
}
override getSheetTarget(unitId: string, subUnitId: string): Nullable<{ workbook: FWorkbook; worksheet: FWorksheet }> {
const workbook = this.getUniverSheet(unitId);
if (!workbook) {
return;
}
const worksheet = workbook.getSheetBySheetId(subUnitId);
if (!worksheet) {
return;
}
return { workbook, worksheet };
}
private _initWorkbookEvent(injector: Injector): void {
const univerInstanceService = injector.get(IUniverInstanceService);
this.disposeWithMe(
univerInstanceService.unitDisposed$.subscribe((unit) => {
if (!this._eventRegistry.get(this.Event.WorkbookDisposed)) return;
if (unit.type === UniverInstanceType.UNIVER_SHEET) {
this.fireEvent(this.Event.WorkbookDisposed,
{
unitId: unit.getUnitId(),
unitType: unit.type,
snapshot: unit.getSnapshot() as IWorkbookData,
}
);
}
})
);
this.disposeWithMe(
univerInstanceService.unitAdded$.subscribe((unit) => {
if (!this._eventRegistry.get(this.Event.WorkbookCreated)) return;
if (unit.type === UniverInstanceType.UNIVER_SHEET) {
const workbook = unit as Workbook;
const workbookUnit = injector.createInstance(FWorkbook, workbook);
this.fireEvent(this.Event.WorkbookCreated,
{
unitId: unit.getUnitId(),
type: unit.type,
workbook: workbookUnit,
unit: workbookUnit,
}
);
}
})
);
}
override _initialize(injector: Injector): void {
const commandService = injector.get(ICommandService);
this.disposeWithMe(
@@ -102,7 +199,7 @@ export class FUniverSheetsMixin extends FUniver implements IFUniverSheetsMixin {
);
// cancel this command
if (eventParams.cancel) {
throw new Error('Sheet create canceled by facade api.');
throw new CanceledError();
}
break;
}
@@ -143,6 +240,8 @@ export class FUniverSheetsMixin extends FUniver implements IFUniverSheetsMixin {
}
})
);
this._initWorkbookEvent(injector);
}
override createUniverSheet(data: Partial<IWorkbookData>): FWorkbook {
+32 -2
View File
@@ -14,11 +14,10 @@
* limitations under the License.
*/
import type { CommandListener, ICommandInfo, IDisposable, IRange, IWorkbookData, LocaleType, Workbook } from '@univerjs/core';
import type { CommandListener, CustomData, ICommandInfo, IDisposable, IRange, IWorkbookData, LocaleType, Workbook } from '@univerjs/core';
import type { ISetDefinedNameMutationParam } from '@univerjs/engine-formula';
import type { ISetSelectionsOperationParams, ISheetCommandSharedParams, RangeThemeStyle } from '@univerjs/sheets';
import { FBaseInitialable, ICommandService, ILogService, Inject, Injector, IPermissionService, IResourceLoaderService, IUniverInstanceService, LocaleService, mergeWorksheetSnapshotWithDefault, RedoCommand, toDisposable, UndoCommand, UniverInstanceType } from '@univerjs/core';
import { IDefinedNamesService } from '@univerjs/engine-formula';
import { CopySheetCommand, getPrimaryForRange, InsertSheetCommand, RegisterWorksheetRangeThemeStyleCommand, RemoveSheetCommand, SCOPE_WORKBOOK_VALUE_DEFINED_NAME, SetDefinedNameCommand, SetSelectionsOperation, SetWorksheetActiveOperation, SetWorksheetOrderCommand, SheetRangeThemeService, SheetsSelectionsService, UnregisterWorksheetRangeThemeStyleCommand, WorkbookEditablePermission } from '@univerjs/sheets';
import { FDefinedName, FDefinedNameBuilder } from './f-defined-name';
@@ -46,6 +45,10 @@ export class FWorkbook extends FBaseInitialable {
this.id = this._workbook.getUnitId();
}
getWorkbook(): Workbook {
return this._workbook;
}
/**
* Get the id of the workbook.
* @returns {string} The id of the workbook.
@@ -821,4 +824,31 @@ export class FWorkbook extends FBaseInitialable {
themeName,
});
}
/**
* Set custom metadata of workbook
* @param {CustomData | undefined} custom custom metadata
* @example
* ```ts
* const fWorkbook = univerAPI.getActiveWorkbook();
* fWorkbook.setCustomMetadata({ key: 'value' });
* ```
*/
setCustomMetadata(custom: CustomData | undefined): FWorkbook {
this._workbook.setCustomMetadata(custom);
return this;
}
/**
* Get custom metadata of workbook
* @returns {CustomData | undefined} custom metadata
* @example
* ```ts
* const fWorkbook = univerAPI.getActiveWorkbook();
* const custom = fWorkbook.getCustomMetadata();
* ```
*/
getCustomMetadata(): CustomData | undefined {
return this._workbook.getCustomMetadata();
}
}
+81
View File
@@ -47,6 +47,10 @@ export class FWorksheet extends FBaseInitialable {
super(_injector);
}
getSheet(): Worksheet {
return this._worksheet;
}
/**
* Returns the injector
* @returns The injector
@@ -1722,6 +1726,83 @@ export class FWorksheet extends FBaseInitialable {
return names.filter((name) => name.getLocalSheetId() === this.getSheetId());
}
/**
* Set custom metadata of worksheet
* @param {CustomData | undefined} custom custom metadata
* @example
* ```ts
* const fWorkbook = univerAPI.getActiveWorkbook();
* const fWorkSheet = fWorkbook.getActiveSheet();
* fWorkSheet.setCustomMetadata({ key: 'value' });
* ```
*/
setCustomMetadata(custom: CustomData | undefined): FWorksheet {
this._worksheet.setCustomMetadata(custom);
return this;
}
/**
* Set custom metadata of row
* @param {number} index row index
* @param {CustomData | undefined} custom custom metadata
* @example
* ```ts
* const fWorkbook = univerAPI.getActiveWorkbook();
* const fWorkSheet = fWorkbook.getActiveSheet();
* fWorkSheet.setRowCustomMetadata(0, { key: 'value' });
* ```
*/
setRowCustomMetadata(index: number, custom: CustomData | undefined): FWorksheet {
this._worksheet.getRowManager().setCustomMetadata(index, custom);
return this;
}
/**
* Set custom metadata of column
* @param {number} index column index
* @param {CustomData | undefined} custom custom metadata
* @example
* ```ts
* const fWorkbook = univerAPI.getActiveWorkbook();
* const fWorkSheet = fWorkbook.getActiveSheet();
* fWorkSheet.setColumnCustomMetadata(0, { key: 'value' });
* ```
*/
setColumnCustomMetadata(index: number, custom: CustomData | undefined): FWorksheet {
this._worksheet.getColumnManager().setCustomMetadata(index, custom);
return this;
}
/**
* Get custom metadata of row
* @param {number} index row index
* @returns {CustomData | undefined} custom metadata
* @example
* ```ts
* const fWorkbook = univerAPI.getActiveWorkbook();
* const fWorkSheet = fWorkbook.getActiveSheet();
* const custom = fWorkSheet.getRowCustomMetadata(0);
* ```
*/
getRowCustomMetadata(index: number): CustomData | undefined {
return this._worksheet.getRowManager().getCustomMetadata(index);
}
/**
* Get custom metadata of column
* @param {number} index column index
* @returns {CustomData | undefined} custom metadata
* @example
* ```ts
* const fWorkbook = univerAPI.getActiveWorkbook();
* const fWorkSheet = fWorkbook.getActiveSheet();
* const custom = fWorkSheet.getColumnCustomMetadata(0);
* ```
*/
getColumnCustomMetadata(index: number): CustomData | undefined {
return this._worksheet.getColumnManager().getCustomMetadata(index);
}
/**
* Get all merged cells in the current worksheet
* @returns {FRange[]} All the merged cells in the worksheet
+2 -2
View File
@@ -16,6 +16,6 @@
import { dayjs } from '@univerjs/core';
export function getDT() {
return dayjs().format('YYYY/MM/DD HH:mm');
export function getDT(date?: Date) {
return dayjs(date).format('YYYY/MM/DD HH:mm');
}
+6
View File
@@ -1789,6 +1789,9 @@ importers:
'@univerjs/sheets-ui':
specifier: workspace:*
version: link:../sheets-ui
'@univerjs/sheets-zen-editor':
specifier: workspace:*
version: link:../sheets-zen-editor
'@univerjs/ui':
specifier: workspace:*
version: link:../ui
@@ -3008,6 +3011,9 @@ importers:
'@univerjs/core':
specifier: workspace:*
version: link:../core
'@univerjs/docs':
specifier: workspace:*
version: link:../docs
'@univerjs/docs-ui':
specifier: workspace:*
version: link:../docs-ui