fix(editor): at menu position in split view (#9500)
This commit is contained in:
@@ -401,7 +401,7 @@ export class HeaderAreaTextCellEditing extends BaseTextCell {
|
|||||||
? getViewportElement(this.topContenteditableElement.host)
|
? getViewportElement(this.topContenteditableElement.host)
|
||||||
: null}"
|
: null}"
|
||||||
data-parent-flavour="affine:database"
|
data-parent-flavour="affine:database"
|
||||||
class="data-view-header-area-rich-text can-link-doc"
|
class="data-view-header-area-rich-text"
|
||||||
></rich-text>`;
|
></rich-text>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export {
|
|||||||
export {
|
export {
|
||||||
getBlockSelectionsCommand,
|
getBlockSelectionsCommand,
|
||||||
getImageSelectionsCommand,
|
getImageSelectionsCommand,
|
||||||
|
getRangeRects,
|
||||||
getSelectionRectsCommand,
|
getSelectionRectsCommand,
|
||||||
getTextSelectionCommand,
|
getTextSelectionCommand,
|
||||||
type SelectionRect,
|
type SelectionRect,
|
||||||
|
|||||||
@@ -54,22 +54,8 @@ export const getSelectionRectsCommand: Command<
|
|||||||
const range = std.range.textSelectionToRange(textSelection);
|
const range = std.range.textSelectionToRange(textSelection);
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
const nativeRects = Array.from(range.getClientRects());
|
|
||||||
const rectsWithoutFiltered = nativeRects
|
|
||||||
.map(rect => ({
|
|
||||||
width: rect.right - rect.left,
|
|
||||||
height: rect.bottom - rect.top,
|
|
||||||
top:
|
|
||||||
rect.top - (containerRect?.top ?? 0) + (container?.scrollTop ?? 0),
|
|
||||||
left:
|
|
||||||
rect.left -
|
|
||||||
(containerRect?.left ?? 0) +
|
|
||||||
(container?.scrollLeft ?? 0),
|
|
||||||
}))
|
|
||||||
.filter(rect => rect.width > 0 && rect.height > 0);
|
|
||||||
|
|
||||||
return next({
|
return next({
|
||||||
selectionRects: filterCoveringRects(rectsWithoutFiltered),
|
selectionRects: getRangeRects(range, container),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (blockSelections && blockSelections.length > 0) {
|
} else if (blockSelections && blockSelections.length > 0) {
|
||||||
@@ -198,3 +184,22 @@ export function filterCoveringRects(rects: SelectionRect[]): SelectionRect[] {
|
|||||||
|
|
||||||
return mergedRects;
|
return mergedRects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRangeRects(
|
||||||
|
range: Range,
|
||||||
|
container: HTMLElement | null
|
||||||
|
): SelectionRect[] {
|
||||||
|
const nativeRects = Array.from(range.getClientRects());
|
||||||
|
const containerRect = container?.getBoundingClientRect();
|
||||||
|
const rectsWithoutFiltered = nativeRects
|
||||||
|
.map(rect => ({
|
||||||
|
width: rect.right - rect.left,
|
||||||
|
height: rect.bottom - rect.top,
|
||||||
|
top: rect.top - (containerRect?.top ?? 0) + (container?.scrollTop ?? 0),
|
||||||
|
left:
|
||||||
|
rect.left - (containerRect?.left ?? 0) + (container?.scrollLeft ?? 0),
|
||||||
|
}))
|
||||||
|
.filter(rect => rect.width > 0 && rect.height > 0);
|
||||||
|
|
||||||
|
return filterCoveringRects(rectsWithoutFiltered);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export { getBlockSelectionsCommand } from './get-block-selections.js';
|
export { getBlockSelectionsCommand } from './get-block-selections.js';
|
||||||
export { getImageSelectionsCommand } from './get-image-selections.js';
|
export { getImageSelectionsCommand } from './get-image-selections.js';
|
||||||
export {
|
export {
|
||||||
|
getRangeRects,
|
||||||
getSelectionRectsCommand,
|
getSelectionRectsCommand,
|
||||||
type SelectionRect,
|
type SelectionRect,
|
||||||
} from './get-selection-rects.js';
|
} from './get-selection-rects.js';
|
||||||
|
|||||||
@@ -324,7 +324,10 @@ const pageToolGroup: KeyboardToolPanelGroup = {
|
|||||||
const inlineEditor = getInlineEditorByModel(std.host, currentModel);
|
const inlineEditor = getInlineEditorByModel(std.host, currentModel);
|
||||||
// Wait for range to be updated
|
// Wait for range to be updated
|
||||||
inlineEditor?.slots.inlineRangeSync.once(() => {
|
inlineEditor?.slots.inlineRangeSync.once(() => {
|
||||||
linkedDocWidget.show('mobile');
|
linkedDocWidget.show({
|
||||||
|
mode: 'mobile',
|
||||||
|
addTriggerKey: true,
|
||||||
|
});
|
||||||
closeToolPanel();
|
closeToolPanel();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export interface LinkedWidgetConfig {
|
|||||||
*/
|
*/
|
||||||
convertTriggerKey: boolean;
|
convertTriggerKey: boolean;
|
||||||
ignoreBlockTypes: (keyof BlockSuite.BlockModels)[];
|
ignoreBlockTypes: (keyof BlockSuite.BlockModels)[];
|
||||||
|
ignoreSelector: string;
|
||||||
getMenus: (
|
getMenus: (
|
||||||
query: string,
|
query: string,
|
||||||
abort: () => void,
|
abort: () => void,
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import type { AffineInlineEditor } from '@blocksuite/affine-components/rich-text';
|
|
||||||
import { getInlineEditorByModel } from '@blocksuite/affine-components/rich-text';
|
|
||||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||||
import type { SelectionRect } from '@blocksuite/affine-shared/commands';
|
|
||||||
import {
|
import {
|
||||||
getViewportElement,
|
getRangeRects,
|
||||||
matchFlavours,
|
type SelectionRect,
|
||||||
} from '@blocksuite/affine-shared/utils';
|
} from '@blocksuite/affine-shared/commands';
|
||||||
import type { UIEventStateContext } from '@blocksuite/block-std';
|
import { getViewportElement } from '@blocksuite/affine-shared/utils';
|
||||||
import { WidgetComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
|
import { BLOCK_ID_ATTR, WidgetComponent } from '@blocksuite/block-std';
|
||||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||||
import type { Disposable } from '@blocksuite/global/utils';
|
import {
|
||||||
import { InlineEditor, type InlineRange } from '@blocksuite/inline';
|
INLINE_ROOT_ATTR,
|
||||||
|
type InlineEditor,
|
||||||
|
type InlineRootElement,
|
||||||
|
} from '@blocksuite/inline';
|
||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
import { html, nothing } from 'lit';
|
import { html, nothing } from 'lit';
|
||||||
import { state } from 'lit/decorators.js';
|
import { state } from 'lit/decorators.js';
|
||||||
@@ -35,122 +36,6 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
> {
|
> {
|
||||||
static override styles = linkedDocWidgetStyles;
|
static override styles = linkedDocWidgetStyles;
|
||||||
|
|
||||||
private _disposeObserveInputRects: Disposable | null = null;
|
|
||||||
|
|
||||||
private readonly _getInlineEditor = (
|
|
||||||
evt?: KeyboardEvent | CompositionEvent
|
|
||||||
) => {
|
|
||||||
if (evt && evt.target instanceof HTMLElement) {
|
|
||||||
const editor = (
|
|
||||||
evt.target.closest('.can-link-doc > .inline-editor') as {
|
|
||||||
inlineEditor?: AffineInlineEditor;
|
|
||||||
}
|
|
||||||
)?.inlineEditor;
|
|
||||||
if (editor instanceof InlineEditor) {
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = this.host.selection.value.find(selection =>
|
|
||||||
selection.is('text')
|
|
||||||
);
|
|
||||||
if (!text) return null;
|
|
||||||
|
|
||||||
const model = this.host.doc.getBlockById(text.blockId);
|
|
||||||
if (!model) return null;
|
|
||||||
|
|
||||||
if (matchFlavours(model, this.config.ignoreBlockTypes)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getInlineEditorByModel(this.host, model);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _inlineEditor: AffineInlineEditor | null = null;
|
|
||||||
|
|
||||||
private readonly _observeInputRects = () => {
|
|
||||||
if (!this._inlineEditor) return;
|
|
||||||
|
|
||||||
const updateInputRects = () => {
|
|
||||||
const blockId =
|
|
||||||
this.std.command.exec('getSelectedModels').selectedModels?.[0]?.id;
|
|
||||||
if (!blockId) return;
|
|
||||||
|
|
||||||
if (!this._startRange) return;
|
|
||||||
const index = this._startRange.index - this._triggerKey.length;
|
|
||||||
if (index < 0) return;
|
|
||||||
|
|
||||||
const currentRange = this._inlineEditor?.getInlineRange();
|
|
||||||
if (!currentRange) return;
|
|
||||||
const length = currentRange.index + currentRange.length - index;
|
|
||||||
|
|
||||||
const textSelection = this.std.selection.create('text', {
|
|
||||||
from: { blockId, index, length },
|
|
||||||
to: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { selectionRects } = this.std.command.exec('getSelectionRects', {
|
|
||||||
textSelection,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!selectionRects) return;
|
|
||||||
|
|
||||||
this._inputRects = selectionRects;
|
|
||||||
};
|
|
||||||
|
|
||||||
updateInputRects();
|
|
||||||
this._disposeObserveInputRects =
|
|
||||||
this._inlineEditor.slots.renderComplete.on(updateInputRects);
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _onCompositionEnd = (ctx: UIEventStateContext) => {
|
|
||||||
const event = ctx.get('defaultState').event as CompositionEvent;
|
|
||||||
|
|
||||||
const key = event.data;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!key ||
|
|
||||||
!this.config.triggerKeys.some(triggerKey => triggerKey.includes(key))
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._inlineEditor = this._getInlineEditor(event);
|
|
||||||
if (!this._inlineEditor) return;
|
|
||||||
|
|
||||||
this._handleInput(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _onKeyDown = (ctx: UIEventStateContext) => {
|
|
||||||
const eventState = ctx.get('keyboardState');
|
|
||||||
const event = eventState.raw;
|
|
||||||
|
|
||||||
const key = event.key;
|
|
||||||
if (
|
|
||||||
key === undefined || // in mac os, the key may be undefined
|
|
||||||
key === 'Process' ||
|
|
||||||
event.isComposing
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!this.config.triggerKeys.some(triggerKey => triggerKey.includes(key)))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._inlineEditor = this._getInlineEditor(event);
|
|
||||||
if (!this._inlineEditor) return;
|
|
||||||
|
|
||||||
const inlineRange = this._inlineEditor.getInlineRange();
|
|
||||||
if (!inlineRange) return;
|
|
||||||
|
|
||||||
if (inlineRange.length > 0) {
|
|
||||||
// When select text and press `[[` should not trigger transform,
|
|
||||||
// since it will break the bracket complete.
|
|
||||||
// Expected `[[selected text]]` instead of `@selected text]]`
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handleInput(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _renderLinkedDocMenu = () => {
|
private readonly _renderLinkedDocMenu = () => {
|
||||||
if (!this.block.rootComponent) return nothing;
|
if (!this.block.rootComponent) return nothing;
|
||||||
|
|
||||||
@@ -166,123 +51,8 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
></affine-linked-doc-popover>`;
|
></affine-linked-doc-popover>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _show$ = signal<'desktop' | 'mobile' | 'none'>('none');
|
@state()
|
||||||
|
private accessor _inputRects: SelectionRect[] = [];
|
||||||
private _startRange: InlineRange | null = null;
|
|
||||||
|
|
||||||
close = () => {
|
|
||||||
this._disposeObserveInputRects?.dispose();
|
|
||||||
this._disposeObserveInputRects = null;
|
|
||||||
this._inlineEditor = null;
|
|
||||||
this._triggerKey = '';
|
|
||||||
this._show$.value = 'none';
|
|
||||||
this._startRange = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
show = (mode: 'desktop' | 'mobile' = 'desktop') => {
|
|
||||||
if (this._inlineEditor === null) {
|
|
||||||
this._inlineEditor = this._getInlineEditor();
|
|
||||||
}
|
|
||||||
if (this._triggerKey === '') {
|
|
||||||
this._triggerKey = this.config.triggerKeys[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._startRange = this._inlineEditor?.getInlineRange() ?? null;
|
|
||||||
|
|
||||||
const enableMobile = this.doc.awarenessStore.getFlag(
|
|
||||||
'enable_mobile_linked_doc_menu'
|
|
||||||
);
|
|
||||||
|
|
||||||
this._observeInputRects();
|
|
||||||
|
|
||||||
this._show$.value = enableMobile ? mode : 'desktop';
|
|
||||||
};
|
|
||||||
|
|
||||||
private get _context(): LinkedDocContext {
|
|
||||||
return {
|
|
||||||
std: this.std,
|
|
||||||
inlineEditor: this._inlineEditor!,
|
|
||||||
startRange: this._startRange!,
|
|
||||||
triggerKey: this._triggerKey,
|
|
||||||
config: this.config,
|
|
||||||
close: this.close,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get config(): LinkedWidgetConfig {
|
|
||||||
return {
|
|
||||||
triggerKeys: ['@', '[[', '【【'],
|
|
||||||
ignoreBlockTypes: ['affine:code'],
|
|
||||||
convertTriggerKey: true,
|
|
||||||
getMenus,
|
|
||||||
mobile: {
|
|
||||||
useScreenHeight: false,
|
|
||||||
scrollContainer: getViewportElement(this.std.host) ?? window,
|
|
||||||
scrollTopOffset: 46,
|
|
||||||
},
|
|
||||||
...this.std.getConfig('affine:page')?.linkedWidget,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleInput(isCompositionEnd: boolean) {
|
|
||||||
const primaryTriggerKey = this.config.triggerKeys[0];
|
|
||||||
|
|
||||||
const inlineEditor = this._inlineEditor;
|
|
||||||
if (!inlineEditor) return;
|
|
||||||
|
|
||||||
const inlineRangeApplyCallback = (callback: () => void) => {
|
|
||||||
// the inline ranged updated in compositionEnd event before this event callback
|
|
||||||
if (isCompositionEnd) callback();
|
|
||||||
else inlineEditor.slots.inlineRangeSync.once(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
inlineRangeApplyCallback(() => {
|
|
||||||
const inlineRange = inlineEditor.getInlineRange();
|
|
||||||
if (!inlineRange) return;
|
|
||||||
const textPoint = inlineEditor.getTextPoint(inlineRange.index);
|
|
||||||
if (!textPoint) return;
|
|
||||||
const [leafStart, offsetStart] = textPoint;
|
|
||||||
|
|
||||||
const text = leafStart.textContent
|
|
||||||
? leafStart.textContent.slice(0, offsetStart)
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const matchedKey = this.config.triggerKeys.find(triggerKey =>
|
|
||||||
text.endsWith(triggerKey)
|
|
||||||
);
|
|
||||||
if (!matchedKey) return;
|
|
||||||
|
|
||||||
if (this.config.convertTriggerKey && primaryTriggerKey !== matchedKey) {
|
|
||||||
const inlineRange = inlineEditor.getInlineRange();
|
|
||||||
if (!inlineRange) return;
|
|
||||||
|
|
||||||
// Convert to the primary trigger key
|
|
||||||
// e.g. [[ -> @
|
|
||||||
this._triggerKey = primaryTriggerKey;
|
|
||||||
const startIdxBeforeMatchKey = inlineRange.index - matchedKey.length;
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIdxBeforeMatchKey,
|
|
||||||
length: matchedKey.length,
|
|
||||||
});
|
|
||||||
inlineEditor.insertText(
|
|
||||||
{ index: startIdxBeforeMatchKey, length: 0 },
|
|
||||||
primaryTriggerKey
|
|
||||||
);
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIdxBeforeMatchKey + primaryTriggerKey.length,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
inlineEditor.slots.inlineRangeSync.once(() => {
|
|
||||||
this.show(IS_MOBILE ? 'mobile' : 'desktop');
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this._triggerKey = matchedKey;
|
|
||||||
this.show(IS_MOBILE ? 'mobile' : 'desktop');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderInputMask() {
|
private _renderInputMask() {
|
||||||
return html`${repeat(
|
return html`${repeat(
|
||||||
this._inputRects,
|
this._inputRects,
|
||||||
@@ -302,20 +72,204 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly _mode$ = signal<'desktop' | 'mobile' | 'none'>('none');
|
||||||
|
|
||||||
|
get config(): LinkedWidgetConfig {
|
||||||
|
return {
|
||||||
|
triggerKeys: ['@', '[[', '【【'],
|
||||||
|
ignoreBlockTypes: ['affine:code'],
|
||||||
|
ignoreSelector:
|
||||||
|
'edgeless-text-editor, edgeless-shape-text-editor, edgeless-group-title-editor, edgeless-frame-title-editor, edgeless-connector-label-editor',
|
||||||
|
convertTriggerKey: true,
|
||||||
|
getMenus,
|
||||||
|
mobile: {
|
||||||
|
useScreenHeight: false,
|
||||||
|
scrollContainer: getViewportElement(this.std.host) ?? window,
|
||||||
|
scrollTopOffset: 46,
|
||||||
|
},
|
||||||
|
...this.std.getConfig('affine:page')?.linkedWidget,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _context: LinkedDocContext | null = null;
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.handleEvent('keyDown', this._onKeyDown);
|
|
||||||
this.handleEvent('compositionEnd', this._onCompositionEnd);
|
this.handleEvent('beforeInput', ctx => {
|
||||||
|
if (this._mode$.peek() !== 'none') return;
|
||||||
|
|
||||||
|
const event = ctx.get('defaultState').event;
|
||||||
|
if (!(event instanceof InputEvent)) return;
|
||||||
|
|
||||||
|
if (event.data === null) return;
|
||||||
|
|
||||||
|
const host = this.std.host;
|
||||||
|
|
||||||
|
const range = host.range.value;
|
||||||
|
if (!range || !range.collapsed) return;
|
||||||
|
|
||||||
|
const containerElement =
|
||||||
|
range.commonAncestorContainer instanceof Element
|
||||||
|
? range.commonAncestorContainer
|
||||||
|
: range.commonAncestorContainer.parentElement;
|
||||||
|
if (!containerElement) return;
|
||||||
|
|
||||||
|
if (containerElement.closest(this.config.ignoreSelector)) return;
|
||||||
|
|
||||||
|
const block = containerElement.closest<BlockComponent>(
|
||||||
|
`[${BLOCK_ID_ATTR}]`
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!block ||
|
||||||
|
this.config.ignoreBlockTypes.includes(
|
||||||
|
block.flavour as keyof BlockSuite.BlockModels
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const inlineRoot = containerElement.closest<InlineRootElement>(
|
||||||
|
`[${INLINE_ROOT_ATTR}]`
|
||||||
|
);
|
||||||
|
if (!inlineRoot) return;
|
||||||
|
|
||||||
|
const inlineEditor = inlineRoot.inlineEditor;
|
||||||
|
const inlineRange = inlineEditor.getInlineRange();
|
||||||
|
if (!inlineRange) return;
|
||||||
|
|
||||||
|
const triggerKeys = this.config.triggerKeys;
|
||||||
|
const primaryTriggerKey = triggerKeys[0];
|
||||||
|
const convertTriggerKey = this.config.convertTriggerKey;
|
||||||
|
if (primaryTriggerKey.length > inlineRange.index) return;
|
||||||
|
const matchedText = inlineEditor.yTextString.slice(
|
||||||
|
inlineRange.index - primaryTriggerKey.length,
|
||||||
|
inlineRange.index
|
||||||
|
);
|
||||||
|
|
||||||
|
let converted = false;
|
||||||
|
if (matchedText !== primaryTriggerKey && convertTriggerKey) {
|
||||||
|
for (const key of triggerKeys.slice(1)) {
|
||||||
|
if (key.length > inlineRange.index) continue;
|
||||||
|
const matchedText = inlineEditor.yTextString.slice(
|
||||||
|
inlineRange.index - key.length,
|
||||||
|
inlineRange.index
|
||||||
|
);
|
||||||
|
if (matchedText === key) {
|
||||||
|
const startIdxBeforeMatchKey = inlineRange.index - key.length;
|
||||||
|
inlineEditor.deleteText({
|
||||||
|
index: startIdxBeforeMatchKey,
|
||||||
|
length: key.length,
|
||||||
|
});
|
||||||
|
inlineEditor.insertText(
|
||||||
|
{ index: startIdxBeforeMatchKey, length: 0 },
|
||||||
|
primaryTriggerKey
|
||||||
|
);
|
||||||
|
inlineEditor.setInlineRange({
|
||||||
|
index: startIdxBeforeMatchKey + primaryTriggerKey.length,
|
||||||
|
length: 0,
|
||||||
|
});
|
||||||
|
converted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedText !== primaryTriggerKey && !converted) return;
|
||||||
|
|
||||||
|
inlineEditor
|
||||||
|
.waitForUpdate()
|
||||||
|
.then(() => {
|
||||||
|
this.show({
|
||||||
|
inlineEditor,
|
||||||
|
primaryTriggerKey,
|
||||||
|
mode: IS_MOBILE ? 'mobile' : 'desktop',
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
show(props?: {
|
||||||
|
inlineEditor?: InlineEditor;
|
||||||
|
primaryTriggerKey?: string;
|
||||||
|
mode?: 'desktop' | 'mobile';
|
||||||
|
addTriggerKey?: boolean;
|
||||||
|
}) {
|
||||||
|
const host = this.host;
|
||||||
|
const {
|
||||||
|
primaryTriggerKey = '@',
|
||||||
|
mode = 'desktop',
|
||||||
|
addTriggerKey = false,
|
||||||
|
} = props ?? {};
|
||||||
|
let inlineEditor: InlineEditor;
|
||||||
|
if (!props?.inlineEditor) {
|
||||||
|
const range = host.range.value;
|
||||||
|
if (!range || !range.collapsed) return;
|
||||||
|
const containerElement =
|
||||||
|
range.commonAncestorContainer instanceof Element
|
||||||
|
? range.commonAncestorContainer
|
||||||
|
: range.commonAncestorContainer.parentElement;
|
||||||
|
if (!containerElement) return;
|
||||||
|
const inlineRoot = containerElement.closest<InlineRootElement>(
|
||||||
|
`[${INLINE_ROOT_ATTR}]`
|
||||||
|
);
|
||||||
|
if (!inlineRoot) return;
|
||||||
|
inlineEditor = inlineRoot.inlineEditor;
|
||||||
|
} else {
|
||||||
|
inlineEditor = props.inlineEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inlineRange = inlineEditor.getInlineRange();
|
||||||
|
if (!inlineRange) return;
|
||||||
|
|
||||||
|
if (addTriggerKey) {
|
||||||
|
inlineEditor.insertText(
|
||||||
|
{ index: inlineRange.index, length: 0 },
|
||||||
|
primaryTriggerKey
|
||||||
|
);
|
||||||
|
inlineEditor.setInlineRange({
|
||||||
|
index: inlineRange.index + primaryTriggerKey.length,
|
||||||
|
length: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const disposable = inlineEditor.slots.renderComplete.on(() => {
|
||||||
|
const currentInlineRange = inlineEditor.getInlineRange();
|
||||||
|
if (!currentInlineRange) return;
|
||||||
|
const range = inlineEditor.toDomRange({
|
||||||
|
index: inlineRange.index,
|
||||||
|
length: currentInlineRange.index - inlineRange.index,
|
||||||
|
});
|
||||||
|
if (!range) return;
|
||||||
|
this._inputRects = getRangeRects(range, getViewportElement(host));
|
||||||
|
});
|
||||||
|
this._context = {
|
||||||
|
std: this.std,
|
||||||
|
inlineEditor,
|
||||||
|
startRange: inlineRange,
|
||||||
|
triggerKey: primaryTriggerKey,
|
||||||
|
config: this.config,
|
||||||
|
close: () => {
|
||||||
|
disposable.dispose();
|
||||||
|
this._inputRects = [];
|
||||||
|
this._mode$.value = 'none';
|
||||||
|
this._context = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableMobile = this.doc.awarenessStore.getFlag(
|
||||||
|
'enable_mobile_linked_doc_menu'
|
||||||
|
);
|
||||||
|
this._mode$.value = enableMobile ? mode : 'desktop';
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
if (this._show$.value === 'none') return nothing;
|
if (this._mode$.value === 'none') return nothing;
|
||||||
|
|
||||||
return html`${this._renderInputMask()}
|
return html`${this._renderInputMask()}
|
||||||
<blocksuite-portal
|
<blocksuite-portal
|
||||||
.shadowDom=${false}
|
.shadowDom=${false}
|
||||||
.template=${choose(
|
.template=${choose(
|
||||||
this._show$.value,
|
this._mode$.value,
|
||||||
[
|
[
|
||||||
['desktop', this._renderLinkedDocPopover],
|
['desktop', this._renderLinkedDocPopover],
|
||||||
['mobile', this._renderLinkedDocMenu],
|
['mobile', this._renderLinkedDocMenu],
|
||||||
@@ -324,12 +278,6 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
)}
|
)}
|
||||||
></blocksuite-portal>`;
|
></blocksuite-portal>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@state()
|
|
||||||
private accessor _inputRects: SelectionRect[] = [];
|
|
||||||
|
|
||||||
@state()
|
|
||||||
private accessor _triggerKey = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ export const defaultSlashMenuConfig: SlashMenuConfig = {
|
|||||||
const inlineEditor = getInlineEditorByModel(rootComponent.host, model);
|
const inlineEditor = getInlineEditorByModel(rootComponent.host, model);
|
||||||
// Wait for range to be updated
|
// Wait for range to be updated
|
||||||
inlineEditor?.slots.inlineRangeSync.once(() => {
|
inlineEditor?.slots.inlineRangeSync.once(() => {
|
||||||
linkedDocWidget.show();
|
linkedDocWidget.show({ addTriggerKey: true });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user