Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 53 additions & 9 deletions packages/quill/src/blots/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import Delta from 'quill-delta';
import Break from './break.js';
import Inline from './inline.js';
import TextBlot from './text.js';
import SoftBreak, { SOFT_BREAK_CHARACTER } from './soft-break.js';

const NEWLINE_LENGTH = 1;
const softBreakRegex = new RegExp(`(${SOFT_BREAK_CHARACTER})`, 'g');

class Block extends BlockBlot {
cache: { delta?: Delta | null; length?: number } = {};
Expand All @@ -25,6 +27,7 @@ class Block extends BlockBlot {

deleteAt(index: number, length: number) {
super.deleteAt(index, length);
this.optimizeChildren();
this.cache = {};
}

Expand All @@ -42,6 +45,7 @@ class Block extends BlockBlot {
value,
);
}
this.optimizeChildren();
this.cache = {};
}

Expand All @@ -55,11 +59,35 @@ class Block extends BlockBlot {
const lines = value.split('\n');
const text = lines.shift() as string;
if (text.length > 0) {
if (index < this.length() - 1 || this.children.tail == null) {
super.insertAt(Math.min(index, this.length() - 1), text);
} else {
this.children.tail.insertAt(this.children.tail.length(), text);
}
const softLines = text.split(softBreakRegex);
let i = index;
softLines.forEach((str) => {
if (i < this.length() - 1 || this.children.tail == null) {
const insertIndex = Math.min(i, this.length() - 1);
if (str === SOFT_BREAK_CHARACTER) {
super.insertAt(
insertIndex,
SoftBreak.blotName,
SOFT_BREAK_CHARACTER,
);
} else {
super.insertAt(insertIndex, str);
}
} else {
const insertIndex = this.children.tail.length();
if (str === SOFT_BREAK_CHARACTER) {
this.children.tail.insertAt(
insertIndex,
SoftBreak.blotName,
SOFT_BREAK_CHARACTER,
);
} else {
this.children.tail.insertAt(insertIndex, str);
}
}
i += str.length;
});

this.cache = {};
}
// TODO: Fix this next time the file is edited.
Expand All @@ -74,11 +102,8 @@ class Block extends BlockBlot {
}

insertBefore(blot: Blot, ref?: Blot | null) {
const { head } = this.children;
super.insertBefore(blot, ref);
if (head instanceof Break) {
head.remove();
}
this.optimizeChildren();
this.cache = {};
}

Expand All @@ -96,6 +121,17 @@ class Block extends BlockBlot {

optimize(context: { [key: string]: any }) {
super.optimize(context);
const lastLeafInBlock = this.descendants(LeafBlot).at(-1);

// in order for an end-of-block soft break to be rendered properly by the browser, we need a trailing break
if (
lastLeafInBlock != null &&
lastLeafInBlock.statics.blotName === SoftBreak.blotName &&
this.children.tail?.statics.blotName !== Break.blotName
) {
const breakBlot = this.scroll.create(Break.blotName);
super.insertBefore(breakBlot, null);
}
this.cache = {};
}

Expand All @@ -122,6 +158,14 @@ class Block extends BlockBlot {
this.cache = {};
return next;
}

private optimizeChildren() {
this.children.forEach((child) => {
if (child instanceof Break) {
child.optimize();
}
});
}
}
Block.blotName = 'block';
Block.tagName = 'P';
Expand Down
19 changes: 16 additions & 3 deletions packages/quill/src/blots/break.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { EmbedBlot } from 'parchment';
import { EmbedBlot, LeafBlot, ParentBlot } from 'parchment';
import SoftBreak from './soft-break.js';

class Break extends EmbedBlot {
static value() {
return undefined;
}

optimize() {
if (this.prev || this.next) {
optimize(): void {
const thisIsLastBlotInParent = this.next == null;
const thisIsFirstBlotInParent = this.prev == null;
const thisIsOnlyBlotInParent =
thisIsLastBlotInParent && thisIsFirstBlotInParent;
const prevLeaf =
this.prev instanceof ParentBlot
? this.prev.descendants(LeafBlot).at(-1)
: this.prev;
const prevLeafIsSoftBreak =
prevLeaf != null && prevLeaf.statics.blotName == SoftBreak.blotName;
const shouldRender =
thisIsOnlyBlotInParent || (thisIsLastBlotInParent && prevLeafIsSoftBreak);
if (!shouldRender) {
this.remove();
}
}
Expand Down
21 changes: 21 additions & 0 deletions packages/quill/src/blots/soft-break.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { EmbedBlot } from 'parchment';

export const SOFT_BREAK_CHARACTER = '\u2028';

export default class SoftBreak extends EmbedBlot {
static tagName = 'BR';
static blotName: string = 'soft-break';
static className: string = 'soft-break';

length(): number {
return 1;
}

value(): string {
return SOFT_BREAK_CHARACTER;
}

optimize(): void {
return;
}
}
2 changes: 2 additions & 0 deletions packages/quill/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Embed from './blots/embed.js';
import Inline from './blots/inline.js';
import Scroll from './blots/scroll.js';
import TextBlot from './blots/text.js';
import SoftBreak from './blots/soft-break.js';

import Clipboard from './modules/clipboard.js';
import History from './modules/history.js';
Expand All @@ -38,6 +39,7 @@ Quill.register({
'blots/block': Block,
'blots/block/embed': BlockEmbed,
'blots/break': Break,
'blots/soft-break': SoftBreak,
'blots/container': Container,
'blots/cursor': Cursor,
'blots/embed': Embed,
Expand Down
1 change: 1 addition & 0 deletions packages/quill/src/core/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ class Editor {
const normalizedDelta = normalizeDelta(contents);
const change = new Delta().retain(index).concat(normalizedDelta);
this.scroll.insertContents(index, normalizedDelta);
this.scroll.optimize();
return this.update(change);
}

Expand Down
44 changes: 40 additions & 4 deletions packages/quill/src/modules/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { FontStyle } from '../formats/font.js';
import { SizeStyle } from '../formats/size.js';
import { deleteRange } from './keyboard.js';
import normalizeExternalHTML from './normalizeExternalHTML/index.js';
import { SOFT_BREAK_CHARACTER } from '../blots/soft-break.js';

const debug = logger('quill:clipboard');

Expand Down Expand Up @@ -489,11 +490,46 @@ function matchBlot(node: Node, delta: Delta, scroll: ScrollBlot) {
return delta;
}

function matchBreak(node: Node, delta: Delta) {
if (!deltaEndsWith(delta, '\n')) {
delta.insert('\n');
function matchBreak(node: Node, delta: Delta, scroll: ScrollBlot) {
const parentLineElement = getParentLine(node, scroll);
if (parentLineElement == null) {
// <br> tags pasted without a parent will be treated as soft breaks
return new Delta().insert(SOFT_BREAK_CHARACTER);
}
return delta;
if (isPre(parentLineElement)) {
// code blocks don't allow soft breaks
return new Delta().insert('\n');
}
if (isInLastPositionOfParentLine(node, parentLineElement)) {
// ignore trailing breaks
return delta;
}
return new Delta().insert(SOFT_BREAK_CHARACTER);
}

function getParentLine(node: Node, scroll: ScrollBlot): HTMLElement | null {
let current: Node = node;
while (current.parentElement != null) {
if (isLine(current.parentElement, scroll)) {
return current.parentElement;
}
current = current.parentElement;
}
return null;
}

function isInLastPositionOfParentLine(
node: Node,
parentLineElement: HTMLElement,
): boolean {
let current: Node = node;
while (current.nextSibling == null && current.parentElement != null) {
if (current.parentElement === parentLineElement) {
return true;
}
current = current.parentElement;
}
return false;
}

function matchCodeBlock(node: Node, delta: Delta, scroll: ScrollBlot) {
Expand Down
17 changes: 17 additions & 0 deletions packages/quill/src/modules/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import logger from '../core/logger.js';
import Module from '../core/module.js';
import type { BlockEmbed } from '../blots/block.js';
import type { Range } from '../core/selection.js';
import { SOFT_BREAK_CHARACTER } from '../blots/soft-break.js';

const debug = logger('quill:keyboard');

Expand Down Expand Up @@ -84,6 +85,13 @@ class Keyboard extends Module<KeyboardOptions> {
this.addBinding(this.options.bindings[name]);
}
});
this.addBinding(
{
key: 'Enter',
shiftKey: true,
},
this.handleShiftEnter,
);
this.addBinding({ key: 'Enter', shiftKey: null }, this.handleEnter);
this.addBinding(
{ key: 'Enter', metaKey: null, ctrlKey: null, altKey: null },
Expand Down Expand Up @@ -352,6 +360,15 @@ class Keyboard extends Module<KeyboardOptions> {
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
this.quill.focus();
}

handleShiftEnter(range: Range) {
this.quill.insertText(
range.index,
SOFT_BREAK_CHARACTER,
Quill.sources.USER,
);
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
}
}

const defaultOptions: KeyboardOptions = {
Expand Down
Loading