Formatting: Expand exceptions to multi-newline ops, multiple ops
This commit is contained in:
parent
3ac2fb4ce4
commit
6e1916030d
3 changed files with 54 additions and 8 deletions
|
@ -3,11 +3,13 @@
|
||||||
|
|
||||||
import type Quill from 'quill';
|
import type Quill from 'quill';
|
||||||
import type { KeyboardContext } from 'quill';
|
import type { KeyboardContext } from 'quill';
|
||||||
|
import type Op from 'quill-delta/dist/Op';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Popper } from 'react-popper';
|
import { Popper } from 'react-popper';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import type { VirtualElement } from '@popperjs/core';
|
import type { VirtualElement } from '@popperjs/core';
|
||||||
|
import { isString } from 'lodash';
|
||||||
|
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import * as Errors from '../../types/errors';
|
import * as Errors from '../../types/errors';
|
||||||
|
@ -54,6 +56,14 @@ function getMetaKey(platform: string, i18n: LocalizerType) {
|
||||||
return i18n('icu:Keyboard--Key--ctrl');
|
return i18n('icu:Keyboard--Key--ctrl');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAllNewlines(ops: Array<Op>): boolean {
|
||||||
|
return ops.every(isNewlineOnlyOp);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNewlineOnlyOp(op: Op): boolean {
|
||||||
|
return isString(op.insert) && /^\n+$/gm.test(op.insert);
|
||||||
|
}
|
||||||
|
|
||||||
export class FormattingMenu {
|
export class FormattingMenu {
|
||||||
// Cache the results of our virtual elements's last rect calculation
|
// Cache the results of our virtual elements's last rect calculation
|
||||||
lastRect: DOMRect | undefined;
|
lastRect: DOMRect | undefined;
|
||||||
|
@ -181,12 +191,12 @@ export class FormattingMenu {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we special-case single \n ops because Quill doesn't apply formatting to them
|
// Note: we special-case all-newline ops because Quill doesn't apply styles to them
|
||||||
const contents = this.quill.getContents(
|
const contents = this.quill.getContents(
|
||||||
quillSelection.index,
|
quillSelection.index,
|
||||||
quillSelection.length
|
quillSelection.length
|
||||||
);
|
);
|
||||||
if (contents.length() === 1 && contents.ops[0].insert === '\n') {
|
if (isAllNewlines(contents.ops)) {
|
||||||
this.scheduleRemoval();
|
this.scheduleRemoval();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -261,13 +271,12 @@ export class FormattingMenu {
|
||||||
const contents = this.quill.getContents(selection.index, selection.length);
|
const contents = this.quill.getContents(selection.index, selection.length);
|
||||||
|
|
||||||
// Note: we special-case single \n ops because Quill doesn't apply formatting to them
|
// Note: we special-case single \n ops because Quill doesn't apply formatting to them
|
||||||
if (contents.length() === 1 && contents.ops[0].insert === '\n') {
|
if (isAllNewlines(contents.ops)) {
|
||||||
this.scheduleRemoval();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return contents.ops.every(
|
return contents.ops.every(
|
||||||
op => op.attributes?.[style] || op.insert === '\n'
|
op => op.attributes?.[style] || isNewlineOnlyOp(op)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
||||||
} from '../types/BodyRange';
|
} from '../types/BodyRange';
|
||||||
import { BodyRange } from '../types/BodyRange';
|
import { BodyRange } from '../types/BodyRange';
|
||||||
import type { MentionBlot } from './mentions/blot';
|
import type { MentionBlot } from './mentions/blot';
|
||||||
import { QuillFormattingStyle } from './formatting/menu';
|
import { isNewlineOnlyOp, QuillFormattingStyle } from './formatting/menu';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
|
|
||||||
export type MentionBlotValue = {
|
export type MentionBlotValue = {
|
||||||
|
@ -165,8 +165,8 @@ export const getTextAndRangesFromOps = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const preTrimText = ops.reduce((acc, op) => {
|
const preTrimText = ops.reduce((acc, op) => {
|
||||||
// We special-case single-newline ops because Quill doesn't apply styles to them
|
// We special-case all-newline ops because Quill doesn't apply styles to them
|
||||||
if (op.insert === '\n') {
|
if (isNewlineOnlyOp(op)) {
|
||||||
return acc + op.insert;
|
return acc + op.insert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,43 @@ describe('getTextAndRangesFromOps', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles trimming with no-formatting single- and multi-newline ops', () => {
|
||||||
|
const ops = [
|
||||||
|
{
|
||||||
|
insert: 'line1',
|
||||||
|
attributes: { bold: true },
|
||||||
|
},
|
||||||
|
// quill doesn't put formatting on all-newline ops
|
||||||
|
{
|
||||||
|
insert: '\n',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'line2',
|
||||||
|
attributes: { bold: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: '\n\n',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'line4',
|
||||||
|
attributes: { bold: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: '\n\n',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const { text, bodyRanges } = getTextAndRangesFromOps(ops);
|
||||||
|
assert.equal(text, 'line1\nline2\n\nline4');
|
||||||
|
assert.equal(bodyRanges.length, 1);
|
||||||
|
assert.deepEqual(bodyRanges, [
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
length: 18,
|
||||||
|
style: BodyRange.Style.BOLD,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('handles trimming at the end of the message', () => {
|
it('handles trimming at the end of the message', () => {
|
||||||
const ops = [
|
const ops = [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue