Skip to content

Commit

Permalink
feat(docs-link): support acrossing-paragraph add link (#4104)
Browse files Browse the repository at this point in the history
  • Loading branch information
weird94 authored Nov 19, 2024
1 parent 545dc82 commit cea9cb8
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 120 deletions.
112 changes: 61 additions & 51 deletions packages/core/src/docs/data-model/text-x/build-utils/text-x-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function deleteCustomRangeTextX(accessor: IAccessor, params: IDeleteCusto
}

export interface IAddCustomRangeTextXParam {
range: ITextRange;
ranges: ITextRange[];
segmentId?: string;
rangeId: string;
rangeType: CustomRangeType;
Expand All @@ -89,62 +89,72 @@ export interface IAddCustomRangeTextXParam {
}

export function addCustomRangeTextX(param: IAddCustomRangeTextXParam) {
const { range, rangeId, rangeType, wholeEntity, properties, body } = param;
const actualRange = getSelectionForAddCustomRange(range, body);
if (!actualRange) {
return false;
}
const { ranges, rangeId, rangeType, wholeEntity, properties, body } = param;
let cursor = 0;
const textX: TextX & { selections?: ITextRange[] } = new TextX();

if (!body) {
return false;
}
let changed = false;
ranges.forEach((range) => {
const actualRange = getSelectionForAddCustomRange(range, body);
if (!actualRange) {
return false;
}

const { startOffset, endOffset } = actualRange;
if (!body) {
return false;
}

const customRanges = body.customRanges ?? [];
let cursor = 0;
const textX: TextX & { selections?: ITextRange[] } = new TextX();
const { startOffset, endOffset } = actualRange;

const customRanges = body.customRanges ?? [];

const addCustomRange = (startIndex: number, endIndex: number, index: number) => {
const relativeCustomRanges = getIntersectingCustomRanges(startIndex, endIndex, customRanges, rangeType);
const rangeStartIndex = Math.min((relativeCustomRanges[0]?.startIndex ?? Infinity), startIndex);
const rangeEndIndex = Math.max(relativeCustomRanges[relativeCustomRanges.length - 1]?.endIndex ?? -Infinity, endIndex);

const customRange = {
rangeId: index ? `${rangeId}$${index}` : rangeId,
rangeType,
startIndex: 0,
endIndex: rangeEndIndex - rangeStartIndex,
wholeEntity,
properties: {
...properties,
},
};

textX.push({
t: TextXActionType.RETAIN,
len: rangeStartIndex - cursor,
});

const addCustomRange = (startIndex: number, endIndex: number, index: number) => {
const relativeCustomRanges = getIntersectingCustomRanges(startIndex, endIndex, customRanges, rangeType);
const rangeStartIndex = Math.min((relativeCustomRanges[0]?.startIndex ?? Infinity), startIndex);
const rangeEndIndex = Math.max(relativeCustomRanges[relativeCustomRanges.length - 1]?.endIndex ?? -Infinity, endIndex);

const customRange = {
rangeId: index ? `${rangeId}-${index}` : rangeId,
rangeType,
startIndex: 0,
endIndex: rangeEndIndex - rangeStartIndex,
wholeEntity,
properties: {
...properties,
},
textX.push({
t: TextXActionType.RETAIN,
len: rangeEndIndex - rangeStartIndex + 1,
body: {
dataStream: '',
customRanges: [customRange],
},
coverType: UpdateDocsAttributeType.COVER,
});
cursor = rangeEndIndex + 1;
};
textX.push({
t: TextXActionType.RETAIN,
len: rangeStartIndex - cursor,
});
textX.push({
t: TextXActionType.RETAIN,
len: rangeEndIndex - rangeStartIndex + 1,
body: {
dataStream: '',
customRanges: [customRange],
},
coverType: UpdateDocsAttributeType.COVER,
});
cursor = rangeEndIndex + 1;
};
const relativeParagraphs = (body.paragraphs ?? []).filter((p) => p.startIndex < endOffset && p.startIndex > startOffset);
const newRanges = excludePointsFromRange([startOffset, endOffset - 1], relativeParagraphs.map((p) => p.startIndex));
newRanges.forEach(([start, end], i) => addCustomRange(start, end, i));

textX.selections = [{
startOffset: actualRange.endOffset,
endOffset: actualRange.endOffset,
collapsed: true,
}];
return textX;
const relativeParagraphs = (body.paragraphs ?? []).filter((p) => p.startIndex < endOffset && p.startIndex > startOffset);
const customBlocks = (body.customBlocks ?? []).filter((block) => block.startIndex < endOffset && block.startIndex > startOffset);
const newRanges = excludePointsFromRange([startOffset, endOffset - 1], [...relativeParagraphs.map((p) => p.startIndex), ...customBlocks.map((b) => b.startIndex)]);
newRanges.forEach(([start, end], i) => addCustomRange(start, end, i));

changed = true;
textX.selections = [{
startOffset: actualRange.endOffset,
endOffset: actualRange.endOffset,
collapsed: true,
}];
});

return changed ? textX : false;
}

// If the selection contains line breaks,
Expand Down
12 changes: 7 additions & 5 deletions packages/core/src/sheets/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,13 @@ export function addLinkToDocumentModel(documentModel: DocumentDataModel, linkUrl
}

const textX = BuildTextUtils.customRange.add({
range: {
startOffset: 0,
endOffset: body.dataStream.length - 1,
collapsed: false,
},
ranges: [
{
startOffset: 0,
endOffset: body.dataStream.length - 1,
collapsed: false,
},
],
rangeId: linkId,
rangeType: CustomRangeType.HYPERLINK,
body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { addCustomRangeBySelectionFactory } from '@univerjs/docs';
export interface IAddDocHyperLinkCommandParams {
payload: string;
unitId: string;
selection?: ITextRangeParam;
selections?: ITextRangeParam[];
}

export const AddDocHyperLinkCommand: ICommand<IAddDocHyperLinkCommandParams> = {
Expand All @@ -32,7 +32,7 @@ export const AddDocHyperLinkCommand: ICommand<IAddDocHyperLinkCommandParams> = {
return false;
}

const { payload, unitId, selection } = params;
const { payload, unitId, selections } = params;
const commandService = accessor.get(ICommandService);
const id = generateRandomId();
const doMutation = addCustomRangeBySelectionFactory(
Expand All @@ -44,9 +44,10 @@ export const AddDocHyperLinkCommand: ICommand<IAddDocHyperLinkCommandParams> = {
url: payload,
},
unitId,
selection,
selections,
}
);

if (doMutation) {
return commandService.syncExecuteCommand(doMutation.id, doMutation.params);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
* limitations under the License.
*/

import type { DocumentDataModel, IAccessor, ICommand, ITextRange } from '@univerjs/core';
import { BuildTextUtils, CommandType, CustomRangeType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import type { DocumentDataModel, IAccessor, ICommand } from '@univerjs/core';
import { CommandType, CustomRangeType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { DocSelectionManagerService } from '@univerjs/docs';
import { DocHyperLinkPopupService } from '../../services/hyper-link-popup.service';

export const shouldDisableAddLink = (accessor: IAccessor) => {
const textSelectionService = accessor.get(DocSelectionManagerService);
const univerInstanceService = accessor.get(IUniverInstanceService);
const textRanges = textSelectionService.getDocRanges();
if (!textRanges.length || textRanges.length > 1) {
const textRanges = textSelectionService.getTextRanges();
if (!textRanges?.length) {
return true;
}

Expand All @@ -33,26 +33,7 @@ export const shouldDisableAddLink = (accessor: IAccessor) => {
return true;
}

const body = doc.getSelfOrHeaderFooterModel(activeRange.segmentId).getBody();
const paragraphs = body?.paragraphs;
if (!paragraphs) {
return true;
}

for (let i = 0, len = paragraphs.length; i < len; i++) {
const p = paragraphs[i];
if (activeRange.startOffset! <= p.startIndex && activeRange.endOffset! > p.startIndex) {
return true;
}

if (p.startIndex > activeRange.endOffset!) {
break;
}
}

const insertCustomRanges = BuildTextUtils.customRange.getCustomRangesInterestsWithSelection(activeRange as ITextRange, body.customRanges ?? []);
// can't insert hyperlink in range contains other custom ranges
return !insertCustomRanges.every((range) => range.rangeType === CustomRangeType.HYPERLINK);
return false;
};

export interface IShowDocHyperLinkEditPopupOperationParams {
Expand Down
4 changes: 2 additions & 2 deletions packages/docs-hyper-link-ui/src/controllers/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
* limitations under the License.
*/

import type { IAccessor } from '@univerjs/core';
import type { IMenuButtonItem, IShortcutItem } from '@univerjs/ui';
import { UniverInstanceType } from '@univerjs/core';
import { DocSelectionManagerService } from '@univerjs/docs';
import { whenDocAndEditorFocused } from '@univerjs/docs-ui';
import { getMenuHiddenObservable, KeyCode, MenuItemType, MetaKeys } from '@univerjs/ui';
import { debounceTime, Observable } from 'rxjs';
import type { IAccessor } from '@univerjs/core';
import type { IMenuButtonItem, IShortcutItem } from '@univerjs/ui';
import { shouldDisableAddLink, ShowDocHyperLinkEditPopupOperation } from '../commands/operations/popup.operation';

export const DOC_LINK_ICON = 'doc-hyper-link-icon';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import type { DocumentDataModel, IDisposable, ITextRangeParam, Nullable } from '@univerjs/core';
import type { DocumentDataModel, IDisposable, Nullable } from '@univerjs/core';
import { Disposable, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { DocSelectionManagerService } from '@univerjs/docs';
import { DocCanvasPopManagerService } from '@univerjs/docs-ui';
Expand Down Expand Up @@ -66,7 +66,9 @@ export class DocHyperLinkPopupService extends Disposable {
this._editPopup.dispose();
}
this._editingLink$.next(linkInfo);
let activeRange: Nullable<ITextRangeParam> = this._textSelectionManagerService.getActiveTextRange();
const textRanges = this._textSelectionManagerService.getTextRanges({ unitId, subUnitId: unitId });
let activeRange = textRanges?.[textRanges.length - 1];

if (linkInfo) {
const { segmentId, segmentPage, startIndex, endIndex } = linkInfo;
activeRange = {
Expand Down
18 changes: 9 additions & 9 deletions packages/docs/src/utils/custom-range-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import type { CustomRangeType, DocumentDataModel, IAccessor, IAddCustomRangeTextXParam, IDocumentBody, IMutationInfo, ITextRangeParam, Nullable, TextX } from '@univerjs/core';
import type { CustomRangeType, DocumentDataModel, IAccessor, IAddCustomRangeTextXParam, IDocumentBody, IMutationInfo, ITextRange, ITextRangeParam, Nullable, TextX } from '@univerjs/core';
import type { IRichTextEditingMutationParams } from '../commands/mutations/core-editing.mutation';
import { BuildTextUtils, IUniverInstanceService, JSONX, UniverInstanceType } from '@univerjs/core';
import { RichTextEditingMutation } from '../commands/mutations/core-editing.mutation';
Expand Down Expand Up @@ -80,31 +80,30 @@ interface IAddCustomRangeFactoryParam {
wholeEntity?: boolean;
properties?: Record<string, any>;
unitId: string;
selection?: ITextRangeParam;
selections?: ITextRangeParam[];
}

export function addCustomRangeBySelectionFactory(accessor: IAccessor, param: IAddCustomRangeFactoryParam) {
const { rangeId, rangeType, wholeEntity, properties, unitId, selection: propSelection } = param;
const { rangeId, rangeType, wholeEntity, properties, unitId, selections: propSelection } = param;
const docSelectionManagerService = accessor.get(DocSelectionManagerService);
const univerInstanceService = accessor.get(IUniverInstanceService);
const selection = propSelection ?? docSelectionManagerService.getTextRanges({ unitId, subUnitId: unitId })?.[0];
const segmentId = selection?.segmentId;
if (!selection) {
const selections = propSelection ?? docSelectionManagerService.getTextRanges({ unitId, subUnitId: unitId });
const segmentId = selections?.[0]?.segmentId;
if (!selections?.length) {
return false;
}

const documentDataModel = univerInstanceService.getUnit<DocumentDataModel>(unitId, UniverInstanceType.UNIVER_DOC);
if (!documentDataModel) {
return false;
}
const body = documentDataModel.getSelfOrHeaderFooterModel(selection.segmentId).getBody();
const body = documentDataModel.getSelfOrHeaderFooterModel(segmentId).getBody();
if (!body) {
return false;
}
const { startOffset, endOffset } = BuildTextUtils.selection.normalizeSelection(selection);

const textX = BuildTextUtils.customRange.add({
range: { startOffset, endOffset, collapsed: startOffset === endOffset },
ranges: selections as ITextRange[],
rangeId,
rangeType,
segmentId,
Expand All @@ -123,6 +122,7 @@ export function addCustomRangeBySelectionFactory(accessor: IAccessor, param: IAd
unitId,
actions: [],
textRanges: textX.selections,
segmentId,
},
textX,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ export class SheetsHyperLinkPopupController extends Disposable {
}
const cell = worksheet.getCellStyleOnly(targetRow, targetCol);
const style = workbook!.getStyles().getStyleByCell(cell);
const tr = style?.tr;
const tr = style?.tr?.a;

if (!tr && !currentCell.customRange) {
this._sheetsHyperLinkPopupService.hideCurrentPopup();
return;
Expand Down
29 changes: 7 additions & 22 deletions packages/sheets-hyper-link-ui/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* limitations under the License.
*/

import type { DocumentDataModel, IAccessor, ITextRange, Workbook, Worksheet } from '@univerjs/core';
import { BuildTextUtils, CustomRangeType, DataValidationType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import type { DocumentDataModel, IAccessor, Workbook, Worksheet } from '@univerjs/core';
import { DataValidationType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { DocSelectionManagerService } from '@univerjs/docs';
import { SheetsSelectionsService } from '@univerjs/sheets';

Expand Down Expand Up @@ -55,35 +55,20 @@ export const getShouldDisableCurrentCellLink = (accessor: IAccessor) => {
export const shouldDisableAddLink = (accessor: IAccessor) => {
const textSelectionService = accessor.get(DocSelectionManagerService);
const univerInstanceService = accessor.get(IUniverInstanceService);
const textRanges = textSelectionService.getDocRanges();
if (!textRanges.length || textRanges.length > 1) {
const textRanges = textSelectionService.getTextRanges();
if (!textRanges?.length) {
return true;
}

const activeRange = textRanges[0];
const doc = univerInstanceService.getCurrentUnitForType<DocumentDataModel>(UniverInstanceType.UNIVER_DOC);
if (!doc || !activeRange || activeRange.collapsed) {
if (!doc || textRanges.every((range) => range.collapsed)) {
return true;
}

const body = doc.getSelfOrHeaderFooterModel(activeRange.segmentId).getBody();
const body = doc.getSelfOrHeaderFooterModel(textRanges[0].segmentId).getBody();
if (!body) {
return true;
}
const paragraphs = body?.paragraphs ?? [];

for (let i = 0, len = paragraphs.length; i < len; i++) {
const p = paragraphs[i];
if (activeRange.startOffset! <= p.startIndex && activeRange.endOffset! > p.startIndex) {
return true;
}

if (p.startIndex > activeRange.endOffset!) {
break;
}
}

const insertCustomRanges = BuildTextUtils.customRange.getCustomRangesInterestsWithSelection(activeRange as ITextRange, body.customRanges ?? []);
// can't insert hyperlink in range contains other custom ranges
return !insertCustomRanges.every((range) => range.rangeType === CustomRangeType.HYPERLINK);
return false;
};
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const AddHyperLinkCommand: ICommand<IAddHyperLinkCommandParams> = {
} else {
textX = BuildTextUtils.customRange.add({
body,
range: { startOffset: 0, endOffset: body.dataStream.length - 2, collapsed: false },
ranges: [{ startOffset: 0, endOffset: body.dataStream.length - 2, collapsed: false }],
rangeId: id,
rangeType: CustomRangeType.HYPERLINK,
properties: {
Expand Down

0 comments on commit cea9cb8

Please sign in to comment.