Skip to content

Commit

Permalink
Merge branch 'next' into pr/2396
Browse files Browse the repository at this point in the history
  • Loading branch information
neSpecc committed Jul 6, 2023
2 parents adc7aee + b24bebb commit c7ed69d
Show file tree
Hide file tree
Showing 16 changed files with 1,155 additions and 166 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"textareas",
"twitterwidget",
"typeof",
"Unmergeable",
"viewports"
]
}
7 changes: 7 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

### 2.28.0

- `New` - Block ids now displayed in DOM via a data-id attribute. Could be useful for plugins that want access a Block's element by id.
- `Improvement` - The Delete keydown at the end of the Block will now work opposite a Backspace at the start. Next Block will be removed (if empty) or merged with the current one.
- `Improvement` - The Delete keydown will work like a Backspace when several Blocks are selected.
- `Improvement` - If we have two empty Blocks, and press Backspace at the start of the second one, the previous will be removed instead of current.

### 2.27.2

- `Fix` - `onChange` won't be called when element with data-mutation-free changes some attribute
Expand Down
2 changes: 1 addition & 1 deletion example/tools/table
Submodule table updated 2 files
+1 −1 package.json
+2 −0 src/toolbox.js
6 changes: 6 additions & 0 deletions src/components/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,12 @@ export default class Block extends EventsDispatcher<BlockEvents> {
contentNode = $.make('div', Block.CSS.content),
pluginsContent = this.toolInstance.render();

/**
* Export id to the DOM three
* Useful for standalone modules development. For example, allows to identify Block by some child node. Or scroll to a particular Block by id.
*/
wrapper.dataset.id = this.id;

/**
* Saving a reference to plugin's content element for guaranteed accessing it later
*/
Expand Down
6 changes: 4 additions & 2 deletions src/components/modules/api/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,11 @@ export default class BlocksAPI extends Module {
*
* @param {number} blockIndex - index of Block to delete
*/
public delete(blockIndex?: number): void {
public delete(blockIndex: number = this.Editor.BlockManager.currentBlockIndex): void {
try {
this.Editor.BlockManager.removeBlock(blockIndex);
const block = this.Editor.BlockManager.getBlockByIndex(blockIndex);

this.Editor.BlockManager.removeBlock(block);
} catch (e) {
_.logLabeled(e, 'warn');

Expand Down
208 changes: 143 additions & 65 deletions src/components/modules/blockEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Module from '../__module';
import * as _ from '../utils';
import SelectionUtils from '../selection';
import Flipper from '../flipper';
import type Block from '../block';
import { areBlocksMergeable } from '../utils/blocks';

/**
*
Expand All @@ -29,6 +31,10 @@ export default class BlockEvents extends Module {
this.backspace(event);
break;

case _.keyCodes.DELETE:
this.delete(event);
break;

case _.keyCodes.ENTER:
this.enter(event);
break;
Expand Down Expand Up @@ -112,7 +118,7 @@ export default class BlockEvents extends Module {
*
* @param {KeyboardEvent} event - tab keydown event
*/
public tabPressed(event): void {
public tabPressed(event: KeyboardEvent): void {
/**
* Clear blocks selection by tab
*/
Expand Down Expand Up @@ -277,104 +283,176 @@ export default class BlockEvents extends Module {
* @param {KeyboardEvent} event - keydown
*/
private backspace(event: KeyboardEvent): void {
const { BlockManager, BlockSelection, Caret } = this.Editor;
const currentBlock = BlockManager.currentBlock;
const tool = currentBlock.tool;
const { BlockManager, Caret } = this.Editor;
const { currentBlock, previousBlock } = BlockManager;

/**
* Check if Block should be removed by current Backspace keydown
* If some fragment is selected, leave native behaviour
*/
if (currentBlock.selected || (currentBlock.isEmpty && currentBlock.currentInput === currentBlock.firstInput)) {
event.preventDefault();

const index = BlockManager.currentBlockIndex;
if (!SelectionUtils.isCollapsed) {
return;
}

if (BlockManager.previousBlock && BlockManager.previousBlock.inputs.length === 0) {
/** If previous block doesn't contain inputs, remove it */
BlockManager.removeBlock(index - 1);
} else {
/** If block is empty, just remove it */
BlockManager.removeBlock();
}
/**
* If caret is not at the start, leave native behaviour
*/
if (!Caret.isAtStart) {
return;
}
/**
* All the cases below have custom behaviour, so we don't need a native one
*/
event.preventDefault();
this.Editor.Toolbar.close();

Caret.setToBlock(
BlockManager.currentBlock,
index ? Caret.positions.END : Caret.positions.START
);
const isFirstInputFocused = currentBlock.currentInput === currentBlock.firstInput;

/** Close Toolbar */
this.Editor.Toolbar.close();
/**
* For example, caret at the start of the Quote second input (caption) — just navigate previous input
*/
if (!isFirstInputFocused) {
Caret.navigatePrevious();

/** Clear selection */
BlockSelection.clearSelection(event);
return;
}

/**
* Backspace at the start of the first Block should do nothing
*/
if (previousBlock === null) {
return;
}

/**
* Don't handle Backspaces when Tool sets enableLineBreaks to true.
* Uses for Tools like <code> where line breaks should be handled by default behaviour.
*
* But if caret is at start of the block, we allow to remove it by backspaces
* If prev Block is empty, it should be removed just like a character
*/
if (tool.isLineBreaksEnabled && !Caret.isAtStart) {
if (previousBlock.isEmpty) {
BlockManager.removeBlock(previousBlock);

return;
}

const isFirstBlock = BlockManager.currentBlockIndex === 0;
const canMergeBlocks = Caret.isAtStart &&
SelectionUtils.isCollapsed &&
currentBlock.currentInput === currentBlock.firstInput &&
!isFirstBlock;
/**
* If current Block is empty, just remove it and set cursor to the previous Block (like we're removing line break char)
*/
if (currentBlock.isEmpty) {
BlockManager.removeBlock(currentBlock);

if (canMergeBlocks) {
/**
* preventing browser default behaviour
*/
event.preventDefault();
const newCurrentBlock = BlockManager.currentBlock;

/**
* Merge Blocks
*/
this.mergeBlocks();
Caret.setToBlock(newCurrentBlock, Caret.positions.END);

return;
}

const bothBlocksMergeable = areBlocksMergeable(currentBlock, previousBlock);

/**
* If Blocks could be merged, do it
* Otherwise, just navigate previous block
*/
if (bothBlocksMergeable) {
this.mergeBlocks(previousBlock, currentBlock);
} else {
Caret.setToBlock(previousBlock, Caret.positions.END);
}
}

/**
* Merge current and previous Blocks if they have the same type
* Handles delete keydown on Block
* Removes char after the caret.
* If caret is at the end of the block, merge next block with current
*
* @param {KeyboardEvent} event - keydown
*/
private mergeBlocks(): void {
const { BlockManager, Caret, Toolbar } = this.Editor;
const targetBlock = BlockManager.previousBlock;
const blockToMerge = BlockManager.currentBlock;
private delete(event: KeyboardEvent): void {
const { BlockManager, Caret } = this.Editor;
const { currentBlock, nextBlock } = BlockManager;

/**
* If some fragment is selected, leave native behaviour
*/
if (!SelectionUtils.isCollapsed) {
return;
}

/**
* Blocks that can be merged:
* 1) with the same Name
* 2) Tool has 'merge' method
*
* other case will handle as usual ARROW LEFT behaviour
* If caret is not at the end, leave native behaviour
*/
if (blockToMerge.name !== targetBlock.name || !targetBlock.mergeable) {
/** If target Block doesn't contain inputs or empty, remove it */
if (targetBlock.inputs.length === 0 || targetBlock.isEmpty) {
BlockManager.removeBlock(BlockManager.currentBlockIndex - 1);
if (!Caret.isAtEnd) {
return;
}

Caret.setToBlock(BlockManager.currentBlock);
Toolbar.close();
/**
* All the cases below have custom behaviour, so we don't need a native one
*/
event.preventDefault();
this.Editor.Toolbar.close();

return;
}
const isLastInputFocused = currentBlock.currentInput === currentBlock.lastInput;

if (Caret.navigatePrevious()) {
Toolbar.close();
}
/**
* For example, caret at the end of the Quote first input (quote text) — just navigate next input (caption)
*/
if (!isLastInputFocused) {
Caret.navigateNext();

return;
}

/**
* Delete at the end of the last Block should do nothing
*/
if (nextBlock === null) {
return;
}

/**
* If next Block is empty, it should be removed just like a character
*/
if (nextBlock.isEmpty) {
BlockManager.removeBlock(nextBlock);

return;
}

/**
* If current Block is empty, just remove it and set cursor to the next Block (like we're removing line break char)
*/
if (currentBlock.isEmpty) {
BlockManager.removeBlock(currentBlock);

Caret.setToBlock(nextBlock, Caret.positions.START);

return;
}

const bothBlocksMergeable = areBlocksMergeable(currentBlock, nextBlock);

/**
* If Blocks could be merged, do it
* Otherwise, just navigate to the next block
*/
if (bothBlocksMergeable) {
this.mergeBlocks(currentBlock, nextBlock);
} else {
Caret.setToBlock(nextBlock, Caret.positions.START);
}
}

/**
* Merge passed Blocks
*
* @param targetBlock - to which Block we want to merge
* @param blockToMerge - what Block we want to merge
*/
private mergeBlocks(targetBlock: Block, blockToMerge: Block): void {
const { BlockManager, Caret, Toolbar } = this.Editor;

Caret.createShadow(targetBlock.pluginsContent);
BlockManager.mergeBlocks(targetBlock, blockToMerge)

BlockManager
.mergeBlocks(targetBlock, blockToMerge)
.then(() => {
/** Restore caret position after merge */
Caret.restoreCaret(targetBlock.pluginsContent as HTMLElement);
Expand Down
Loading

0 comments on commit c7ed69d

Please sign in to comment.