mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
fix(editor): callout delete merge and slash menu (#13597)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Press Enter inside a callout splits the paragraph at the cursor into a new focused paragraph. - Clicking an empty callout inserts and focuses a new paragraph; emoji menu behavior unchanged. - New command to convert a callout paragraph to callout/selection flow for Backspace handling. - New native API: ShareableContent.isUsingMicrophone(processId). - Bug Fixes - Backspace inside callout paragraphs now merges or deletes text predictably and selects the callout when appropriate. - Style - Callout layout refined: top-aligned content and adjusted emoji spacing. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2,6 +2,7 @@ import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
|
|||||||
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
||||||
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
|
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
|
||||||
import { type CalloutBlockModel } from '@blocksuite/affine-model';
|
import { type CalloutBlockModel } from '@blocksuite/affine-model';
|
||||||
|
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||||
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
|
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
|
||||||
import {
|
import {
|
||||||
DocModeProvider,
|
DocModeProvider,
|
||||||
@@ -22,14 +23,13 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
|||||||
|
|
||||||
.affine-callout-block-container {
|
.affine-callout-block-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: ${unsafeCSSVarV2('block/callout/background/grey')};
|
background-color: ${unsafeCSSVarV2('block/callout/background/grey')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.affine-callout-emoji-container {
|
.affine-callout-emoji-container {
|
||||||
margin-right: 10px;
|
|
||||||
margin-top: 14px;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
@@ -37,6 +37,9 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.affine-callout-emoji:hover {
|
.affine-callout-emoji:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -62,7 +65,7 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
|||||||
createLitPortal({
|
createLitPortal({
|
||||||
template: html`<affine-emoji-menu
|
template: html`<affine-emoji-menu
|
||||||
.theme=${theme}
|
.theme=${theme}
|
||||||
.onEmojiSelect=${(data: any) => {
|
.onEmojiSelect=${(data: { native: string }) => {
|
||||||
this.model.props.emoji = data.native;
|
this.model.props.emoji = data.native;
|
||||||
}}
|
}}
|
||||||
></affine-emoji-menu>`,
|
></affine-emoji-menu>`,
|
||||||
@@ -81,6 +84,31 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly _handleBlockClick = (event: MouseEvent) => {
|
||||||
|
// Check if the click target is emoji related element
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
if (
|
||||||
|
target.closest('.affine-callout-emoji-container') ||
|
||||||
|
target.classList.contains('affine-callout-emoji')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle clicks when there are no children
|
||||||
|
if (this.model.children.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent event bubbling
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
// Create a new paragraph block
|
||||||
|
const paragraphId = this.store.addBlock('affine:paragraph', {}, this.model);
|
||||||
|
|
||||||
|
// Focus the new paragraph
|
||||||
|
focusTextModel(this.std, paragraphId);
|
||||||
|
};
|
||||||
|
|
||||||
get attributeRenderer() {
|
get attributeRenderer() {
|
||||||
return this.inlineManager.getRenderer();
|
return this.inlineManager.getRenderer();
|
||||||
}
|
}
|
||||||
@@ -112,7 +140,10 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
|||||||
override renderBlock() {
|
override renderBlock() {
|
||||||
const emoji = this.model.props.emoji$.value;
|
const emoji = this.model.props.emoji$.value;
|
||||||
return html`
|
return html`
|
||||||
<div class="affine-callout-block-container">
|
<div
|
||||||
|
class="affine-callout-block-container"
|
||||||
|
@click=${this._handleBlockClick}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
@click=${this._toggleEmojiMenu}
|
@click=${this._toggleEmojiMenu}
|
||||||
contenteditable="false"
|
contenteditable="false"
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
import {
|
||||||
|
CalloutBlockModel,
|
||||||
|
ParagraphBlockModel,
|
||||||
|
} from '@blocksuite/affine-model';
|
||||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||||
import {
|
import {
|
||||||
BlockSelection,
|
BlockSelection,
|
||||||
@@ -6,13 +9,46 @@ import {
|
|||||||
TextSelection,
|
TextSelection,
|
||||||
} from '@blocksuite/std';
|
} from '@blocksuite/std';
|
||||||
|
|
||||||
|
import { calloutToParagraphCommand } from './commands/callout-to-paragraph.js';
|
||||||
|
import { splitCalloutCommand } from './commands/split-callout.js';
|
||||||
|
|
||||||
export const CalloutKeymapExtension = KeymapExtension(std => {
|
export const CalloutKeymapExtension = KeymapExtension(std => {
|
||||||
return {
|
return {
|
||||||
|
Enter: ctx => {
|
||||||
|
const text = std.selection.find(TextSelection);
|
||||||
|
if (!text) return false;
|
||||||
|
|
||||||
|
const currentBlock = std.store.getBlock(text.from.blockId);
|
||||||
|
if (!currentBlock) return false;
|
||||||
|
|
||||||
|
// Check if current block is a callout block
|
||||||
|
let calloutBlock = currentBlock;
|
||||||
|
if (!matchModels(currentBlock.model, [CalloutBlockModel])) {
|
||||||
|
// If not, check if the parent is a callout block
|
||||||
|
const parent = std.store.getParent(currentBlock.model);
|
||||||
|
if (!parent || !matchModels(parent, [CalloutBlockModel])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const parentBlock = std.store.getBlock(parent.id);
|
||||||
|
if (!parentBlock) return false;
|
||||||
|
calloutBlock = parentBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.get('keyboardState').raw.preventDefault();
|
||||||
|
std.command
|
||||||
|
.chain()
|
||||||
|
.pipe(splitCalloutCommand, {
|
||||||
|
blockId: calloutBlock.model.id,
|
||||||
|
inlineIndex: text.from.index,
|
||||||
|
currentBlockId: text.from.blockId,
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
Backspace: ctx => {
|
Backspace: ctx => {
|
||||||
const text = std.selection.find(TextSelection);
|
const text = std.selection.find(TextSelection);
|
||||||
if (text && text.isCollapsed() && text.from.index === 0) {
|
if (text && text.isCollapsed() && text.from.index === 0) {
|
||||||
const event = ctx.get('defaultState').event;
|
const event = ctx.get('defaultState').event;
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const block = std.store.getBlock(text.from.blockId);
|
const block = std.store.getBlock(text.from.blockId);
|
||||||
if (!block) return false;
|
if (!block) return false;
|
||||||
@@ -20,6 +56,22 @@ export const CalloutKeymapExtension = KeymapExtension(std => {
|
|||||||
if (!parent) return false;
|
if (!parent) return false;
|
||||||
if (!matchModels(parent, [CalloutBlockModel])) return false;
|
if (!matchModels(parent, [CalloutBlockModel])) return false;
|
||||||
|
|
||||||
|
// Check if current block is a paragraph inside callout
|
||||||
|
if (matchModels(block.model, [ParagraphBlockModel])) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
std.command
|
||||||
|
.chain()
|
||||||
|
.pipe(calloutToParagraphCommand, {
|
||||||
|
id: block.model.id,
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to selecting the callout block
|
||||||
|
event.preventDefault();
|
||||||
std.selection.setGroup('note', [
|
std.selection.setGroup('note', [
|
||||||
std.selection.create(BlockSelection, {
|
std.selection.create(BlockSelection, {
|
||||||
blockId: parent.id,
|
blockId: parent.id,
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import {
|
||||||
|
CalloutBlockModel,
|
||||||
|
ParagraphBlockModel,
|
||||||
|
} from '@blocksuite/affine-model';
|
||||||
|
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||||
|
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||||
|
import type { Command } from '@blocksuite/std';
|
||||||
|
import { BlockSelection } from '@blocksuite/std';
|
||||||
|
import { Text } from '@blocksuite/store';
|
||||||
|
|
||||||
|
export const calloutToParagraphCommand: Command<
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
stopCapturing?: boolean;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
> = (ctx, next) => {
|
||||||
|
const { id, stopCapturing = true } = ctx;
|
||||||
|
const std = ctx.std;
|
||||||
|
const doc = std.store;
|
||||||
|
const model = doc.getBlock(id)?.model;
|
||||||
|
|
||||||
|
if (!model || !matchModels(model, [ParagraphBlockModel])) return false;
|
||||||
|
|
||||||
|
const parent = doc.getParent(model);
|
||||||
|
if (!parent || !matchModels(parent, [CalloutBlockModel])) return false;
|
||||||
|
|
||||||
|
if (stopCapturing) std.store.captureSync();
|
||||||
|
|
||||||
|
// Get current block index in callout
|
||||||
|
const currentIndex = parent.children.indexOf(model);
|
||||||
|
const hasText = model.text && model.text.length > 0;
|
||||||
|
|
||||||
|
// Find previous paragraph block in callout
|
||||||
|
let previousBlock = null;
|
||||||
|
for (let i = currentIndex - 1; i >= 0; i--) {
|
||||||
|
const sibling = parent.children[i];
|
||||||
|
if (matchModels(sibling, [ParagraphBlockModel])) {
|
||||||
|
previousBlock = sibling;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousBlock && hasText) {
|
||||||
|
// Clone current text content before any operations to prevent data loss
|
||||||
|
const currentText = model.text || new Text();
|
||||||
|
|
||||||
|
// Get previous block text and merge index
|
||||||
|
const previousText = previousBlock.text || new Text();
|
||||||
|
const mergeIndex = previousText.length;
|
||||||
|
|
||||||
|
// Apply each delta from cloned current text to previous block to preserve formatting
|
||||||
|
previousText.join(currentText);
|
||||||
|
|
||||||
|
// Remove current block after text has been merged
|
||||||
|
doc.deleteBlock(model, {
|
||||||
|
deleteChildren: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus at merge point in previous block
|
||||||
|
focusTextModel(std, previousBlock.id, mergeIndex);
|
||||||
|
} else if (previousBlock && !hasText) {
|
||||||
|
// Move cursor to end of previous block
|
||||||
|
doc.deleteBlock(model, {
|
||||||
|
deleteChildren: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const previousText = previousBlock.text || new Text();
|
||||||
|
focusTextModel(std, previousBlock.id, previousText.length);
|
||||||
|
} else {
|
||||||
|
// No previous block, select the entire callout
|
||||||
|
doc.deleteBlock(model, {
|
||||||
|
deleteChildren: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
std.selection.setGroup('note', [
|
||||||
|
std.selection.create(BlockSelection, {
|
||||||
|
blockId: parent.id,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({ success: true });
|
||||||
|
};
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
CalloutBlockModel,
|
||||||
|
ParagraphBlockModel,
|
||||||
|
} from '@blocksuite/affine-model';
|
||||||
|
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||||
|
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||||
|
import type { Command, EditorHost } from '@blocksuite/std';
|
||||||
|
|
||||||
|
export const splitCalloutCommand: Command<{
|
||||||
|
blockId: string;
|
||||||
|
inlineIndex: number;
|
||||||
|
currentBlockId: string;
|
||||||
|
}> = (ctx, next) => {
|
||||||
|
const { blockId, inlineIndex, currentBlockId, std } = ctx;
|
||||||
|
const host = std.host as EditorHost;
|
||||||
|
const doc = host.store;
|
||||||
|
|
||||||
|
const calloutModel = doc.getBlock(blockId)?.model;
|
||||||
|
if (!calloutModel || !matchModels(calloutModel, [CalloutBlockModel])) {
|
||||||
|
console.error(`block ${blockId} is not a callout block`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentModel = doc.getBlock(currentBlockId)?.model;
|
||||||
|
if (!currentModel) {
|
||||||
|
console.error(`current block ${currentBlockId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.captureSync();
|
||||||
|
|
||||||
|
if (matchModels(currentModel, [ParagraphBlockModel])) {
|
||||||
|
// User is in a paragraph within the callout's children
|
||||||
|
const afterText = currentModel.props.text.split(inlineIndex);
|
||||||
|
|
||||||
|
// Update the current paragraph's text to keep only the part before cursor
|
||||||
|
doc.transact(() => {
|
||||||
|
currentModel.props.text.delete(
|
||||||
|
inlineIndex,
|
||||||
|
currentModel.props.text.length - inlineIndex
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a new paragraph block after the current one
|
||||||
|
const parent = doc.getParent(currentModel);
|
||||||
|
if (parent) {
|
||||||
|
const currentIndex = parent.children.indexOf(currentModel);
|
||||||
|
const newParagraphId = doc.addBlock(
|
||||||
|
'affine:paragraph',
|
||||||
|
{
|
||||||
|
text: afterText,
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
currentIndex + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newParagraphId) {
|
||||||
|
host.updateComplete
|
||||||
|
.then(() => {
|
||||||
|
focusTextModel(std, newParagraphId);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If current block is not a paragraph, create a new paragraph in callout
|
||||||
|
const newParagraphId = doc.addBlock(
|
||||||
|
'affine:paragraph',
|
||||||
|
{
|
||||||
|
text: new Text(),
|
||||||
|
},
|
||||||
|
calloutModel
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newParagraphId) {
|
||||||
|
host.updateComplete
|
||||||
|
.then(() => {
|
||||||
|
focusTextModel(std, newParagraphId);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
@@ -1,24 +1,12 @@
|
|||||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
|
||||||
import { focusBlockEnd } from '@blocksuite/affine-shared/commands';
|
import { focusBlockEnd } from '@blocksuite/affine-shared/commands';
|
||||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
|
||||||
findAncestorModel,
|
|
||||||
isInsideBlockByFlavour,
|
|
||||||
matchModels,
|
|
||||||
} from '@blocksuite/affine-shared/utils';
|
|
||||||
import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu';
|
import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu';
|
||||||
import { FontIcon } from '@blocksuite/icons/lit';
|
import { FontIcon } from '@blocksuite/icons/lit';
|
||||||
|
|
||||||
import { calloutTooltip } from './tooltips';
|
import { calloutTooltip } from './tooltips';
|
||||||
|
|
||||||
export const calloutSlashMenuConfig: SlashMenuConfig = {
|
export const calloutSlashMenuConfig: SlashMenuConfig = {
|
||||||
disableWhen: ({ model }) => {
|
|
||||||
return (
|
|
||||||
findAncestorModel(model, ancestor =>
|
|
||||||
matchModels(ancestor, [CalloutBlockModel])
|
|
||||||
) !== null
|
|
||||||
);
|
|
||||||
},
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Callout',
|
name: 'Callout',
|
||||||
|
|||||||
4
packages/frontend/native/index.d.ts
vendored
4
packages/frontend/native/index.d.ts
vendored
@@ -19,10 +19,10 @@ export declare class ApplicationStateChangedSubscriber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export declare class AudioCaptureSession {
|
export declare class AudioCaptureSession {
|
||||||
|
stop(): void
|
||||||
get sampleRate(): number
|
get sampleRate(): number
|
||||||
get channels(): number
|
get channels(): number
|
||||||
get actualSampleRate(): number
|
get actualSampleRate(): number
|
||||||
stop(): void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class ShareableContent {
|
export declare class ShareableContent {
|
||||||
@@ -31,9 +31,9 @@ export declare class ShareableContent {
|
|||||||
constructor()
|
constructor()
|
||||||
static applications(): Array<ApplicationInfo>
|
static applications(): Array<ApplicationInfo>
|
||||||
static applicationWithProcessId(processId: number): ApplicationInfo | null
|
static applicationWithProcessId(processId: number): ApplicationInfo | null
|
||||||
|
static isUsingMicrophone(processId: number): boolean
|
||||||
static tapAudio(processId: number, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioCaptureSession
|
static tapAudio(processId: number, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioCaptureSession
|
||||||
static tapGlobalAudio(excludedProcesses: Array<ApplicationInfo> | undefined | null, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioCaptureSession
|
static tapGlobalAudio(excludedProcesses: Array<ApplicationInfo> | undefined | null, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioCaptureSession
|
||||||
static isUsingMicrophone(processId: number): boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function decodeAudio(buf: Uint8Array, destSampleRate?: number | undefined | null, filename?: string | undefined | null, signal?: AbortSignal | undefined | null): Promise<Float32Array>
|
export declare function decodeAudio(buf: Uint8Array, destSampleRate?: number | undefined | null, filename?: string | undefined | null, signal?: AbortSignal | undefined | null): Promise<Float32Array>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
pressArrowUp,
|
pressArrowUp,
|
||||||
pressBackspace,
|
pressBackspace,
|
||||||
pressEnter,
|
pressEnter,
|
||||||
undoByKeyboard,
|
|
||||||
} from '@affine-test/kit/utils/keyboard';
|
} from '@affine-test/kit/utils/keyboard';
|
||||||
import { openHomePage } from '@affine-test/kit/utils/load-page';
|
import { openHomePage } from '@affine-test/kit/utils/load-page';
|
||||||
import {
|
import {
|
||||||
@@ -30,7 +29,7 @@ test('add callout block using slash menu and change emoji', async ({
|
|||||||
await expect(emoji).toContainText('😀');
|
await expect(emoji).toContainText('😀');
|
||||||
|
|
||||||
const paragraph = page.locator('affine-callout affine-paragraph');
|
const paragraph = page.locator('affine-callout affine-paragraph');
|
||||||
await expect(paragraph).toHaveCount(1);
|
await expect(paragraph).toHaveCount(2);
|
||||||
|
|
||||||
const vLine = page.locator('affine-callout v-line');
|
const vLine = page.locator('affine-callout v-line');
|
||||||
await expect(vLine).toHaveCount(2);
|
await expect(vLine).toHaveCount(2);
|
||||||
@@ -50,22 +49,6 @@ test('add callout block using slash menu and change emoji', async ({
|
|||||||
await expect(emoji).toContainText('😆');
|
await expect(emoji).toContainText('😆');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('disable slash menu in callout block', async ({ page }) => {
|
|
||||||
await type(page, '/callout\n');
|
|
||||||
const callout = page.locator('affine-callout');
|
|
||||||
const emoji = page.locator('affine-callout .affine-callout-emoji');
|
|
||||||
await expect(callout).toBeVisible();
|
|
||||||
await expect(emoji).toContainText('😀');
|
|
||||||
|
|
||||||
await type(page, '/');
|
|
||||||
const slashMenu = page.locator('.slash-menu');
|
|
||||||
await expect(slashMenu).not.toBeVisible();
|
|
||||||
await undoByKeyboard(page);
|
|
||||||
await undoByKeyboard(page);
|
|
||||||
await type(page, '/');
|
|
||||||
await expect(slashMenu).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('press backspace after callout block', async ({ page }) => {
|
test('press backspace after callout block', async ({ page }) => {
|
||||||
await pressEnter(page);
|
await pressEnter(page);
|
||||||
await pressArrowUp(page);
|
await pressArrowUp(page);
|
||||||
@@ -96,7 +79,7 @@ test('press backspace in callout block', async ({ page }) => {
|
|||||||
expect(await callout.count()).toBe(1);
|
expect(await callout.count()).toBe(1);
|
||||||
|
|
||||||
await pressBackspace(page);
|
await pressBackspace(page);
|
||||||
await expect(paragraph).toHaveCount(2);
|
await expect(paragraph).toHaveCount(1);
|
||||||
await expect(callout).toHaveCount(1);
|
await expect(callout).toHaveCount(1);
|
||||||
|
|
||||||
await pressBackspace(page);
|
await pressBackspace(page);
|
||||||
|
|||||||
Reference in New Issue
Block a user