kopia lustrzana https://github.com/wagtail/wagtail
pull/4410/head
rodzic
e64c4daca6
commit
cab90e5d1b
client/src/components/Draftail
docs/releases
|
@ -23,6 +23,7 @@ Changelog
|
|||
* Fix: Correct dropdown arrow styling in Firefox, IE11 (Janneke Janssen, Alexs Mathilda)
|
||||
* Fix: Password reset no indicates specific validation errors on certain password restrictions (Lucas Moeskops)
|
||||
* Fix: Confirmation page on page deletion now respects custom `get_admin_display_title` methods (Kim Chee Leong)
|
||||
* Fix: Adding external link with selected text now includes text in link chooser (Tony Yates, Thibaud Colas, Alexs Mathilda)
|
||||
|
||||
|
||||
2.0.1 (xx.xx.xxxx) - IN DEVELOPMENT
|
||||
|
|
|
@ -284,6 +284,8 @@ Contributors
|
|||
* Kevin Chung
|
||||
* Kim Chee Leong
|
||||
* Dan Swain
|
||||
* Alexs Mathilda
|
||||
* Tony Yates
|
||||
|
||||
Translators
|
||||
===========
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
/**
|
||||
* Returns collection of currently selected blocks.
|
||||
* See https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/block.js#L19.
|
||||
*/
|
||||
const getSelectedBlocksList = (editorState) => {
|
||||
const selectionState = editorState.getSelection();
|
||||
const content = editorState.getCurrentContent();
|
||||
const startKey = selectionState.getStartKey();
|
||||
const endKey = selectionState.getEndKey();
|
||||
const blockMap = content.getBlockMap();
|
||||
const blocks = blockMap
|
||||
.toSeq()
|
||||
.skipUntil((_, k) => k === startKey)
|
||||
.takeUntil((_, k) => k === endKey)
|
||||
.concat([[endKey, blockMap.get(endKey)]]);
|
||||
return blocks.toList();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the currently selected text in the editor.
|
||||
* See https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/block.js#L106.
|
||||
*/
|
||||
export const getSelectionText = (editorState) => {
|
||||
const selection = editorState.getSelection();
|
||||
let start = selection.getAnchorOffset();
|
||||
let end = selection.getFocusOffset();
|
||||
const selectedBlocks = getSelectedBlocksList(editorState);
|
||||
|
||||
if (selection.getIsBackward()) {
|
||||
const temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
}
|
||||
|
||||
let selectedText = '';
|
||||
for (let i = 0; i < selectedBlocks.size; i += 1) {
|
||||
const blockStart = i === 0 ? start : 0;
|
||||
const blockEnd = i === (selectedBlocks.size - 1) ? end : selectedBlocks.get(i).getText().length;
|
||||
selectedText += selectedBlocks.get(i).getText().slice(blockStart, blockEnd);
|
||||
}
|
||||
|
||||
return selectedText;
|
||||
};
|
|
@ -0,0 +1,121 @@
|
|||
import {
|
||||
EditorState,
|
||||
convertFromRaw,
|
||||
} from 'draft-js';
|
||||
|
||||
import { getSelectionText } from './DraftUtils';
|
||||
|
||||
describe('DraftUtils', () => {
|
||||
describe('#getSelectionText', () => {
|
||||
it('works', () => {
|
||||
const content = convertFromRaw({
|
||||
entityMap: {},
|
||||
blocks: [
|
||||
{
|
||||
key: 'a',
|
||||
text: 'test1234',
|
||||
},
|
||||
],
|
||||
});
|
||||
let editorState = EditorState.createWithContent(content);
|
||||
|
||||
let selection = editorState.getSelection();
|
||||
selection = selection.merge({
|
||||
anchorOffset: 0,
|
||||
focusOffset: 4,
|
||||
});
|
||||
|
||||
editorState = EditorState.acceptSelection(editorState, selection);
|
||||
|
||||
expect(getSelectionText(editorState)).toBe('test');
|
||||
});
|
||||
|
||||
it('empty', () => {
|
||||
expect(getSelectionText(EditorState.createEmpty())).toBe('');
|
||||
});
|
||||
|
||||
it('backwards', () => {
|
||||
const content = convertFromRaw({
|
||||
entityMap: {},
|
||||
blocks: [
|
||||
{
|
||||
key: 'a',
|
||||
text: 'test1234',
|
||||
},
|
||||
],
|
||||
});
|
||||
let editorState = EditorState.createWithContent(content);
|
||||
|
||||
let selection = editorState.getSelection();
|
||||
selection = selection.merge({
|
||||
anchorOffset: 8,
|
||||
focusOffset: 4,
|
||||
isBackward: true,
|
||||
});
|
||||
|
||||
editorState = EditorState.acceptSelection(editorState, selection);
|
||||
|
||||
expect(getSelectionText(editorState)).toBe('1234');
|
||||
});
|
||||
|
||||
it('multiblock', () => {
|
||||
const content = convertFromRaw({
|
||||
entityMap: {},
|
||||
blocks: [
|
||||
{
|
||||
key: 'a',
|
||||
text: 'test1234',
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
text: 'multiblock',
|
||||
}
|
||||
],
|
||||
});
|
||||
let editorState = EditorState.createWithContent(content);
|
||||
|
||||
let selection = editorState.getSelection();
|
||||
selection = selection.merge({
|
||||
anchorKey: 'a',
|
||||
focusKey: 'b',
|
||||
anchorOffset: 4,
|
||||
focusOffset: 5,
|
||||
isBackward: false,
|
||||
});
|
||||
|
||||
editorState = EditorState.acceptSelection(editorState, selection);
|
||||
|
||||
expect(getSelectionText(editorState)).toBe('1234multi');
|
||||
});
|
||||
|
||||
it('multiblock-backwards', () => {
|
||||
const content = convertFromRaw({
|
||||
entityMap: {},
|
||||
blocks: [
|
||||
{
|
||||
key: 'a',
|
||||
text: 'test1234',
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
text: 'multiblock',
|
||||
}
|
||||
],
|
||||
});
|
||||
let editorState = EditorState.createWithContent(content);
|
||||
|
||||
let selection = editorState.getSelection();
|
||||
selection = selection.merge({
|
||||
focusKey: 'a',
|
||||
anchorKey: 'b',
|
||||
anchorOffset: 5,
|
||||
focusOffset: 4,
|
||||
isBackward: true,
|
||||
});
|
||||
|
||||
editorState = EditorState.acceptSelection(editorState, selection);
|
||||
|
||||
expect(getSelectionText(editorState)).toBe('1234multi');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,6 +4,7 @@ import { AtomicBlockUtils, Modifier, RichUtils, EditorState } from 'draft-js';
|
|||
import { ENTITY_TYPE } from 'draftail';
|
||||
|
||||
import { STRINGS } from '../../../config/wagtailConfig';
|
||||
import { getSelectionText } from '../DraftUtils';
|
||||
|
||||
const $ = global.jQuery;
|
||||
|
||||
|
@ -16,7 +17,7 @@ MUTABILITY[DOCUMENT] = 'MUTABLE';
|
|||
MUTABILITY[ENTITY_TYPE.IMAGE] = 'IMMUTABLE';
|
||||
MUTABILITY[EMBED] = 'IMMUTABLE';
|
||||
|
||||
export const getChooserConfig = (entityType, entity) => {
|
||||
export const getChooserConfig = (entityType, entity, selectedText) => {
|
||||
const chooserURL = {};
|
||||
chooserURL[ENTITY_TYPE.IMAGE] = `${global.chooserUrls.imageChooser}?select_format=true`;
|
||||
chooserURL[EMBED] = global.chooserUrls.embedsChooser;
|
||||
|
@ -32,10 +33,7 @@ export const getChooserConfig = (entityType, entity) => {
|
|||
allow_external_link: true,
|
||||
allow_email_link: true,
|
||||
can_choose_root: 'false',
|
||||
// This does not initialise the modal with the currently selected text.
|
||||
// This will need to be implemented in the future.
|
||||
// See https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/block.js#L106.
|
||||
link_text: '',
|
||||
link_text: selectedText,
|
||||
};
|
||||
|
||||
if (entity) {
|
||||
|
@ -113,8 +111,9 @@ class ModalWorkflowSource extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { onClose, entityType, entity } = this.props;
|
||||
const { url, urlParams } = getChooserConfig(entityType, entity);
|
||||
const { onClose, entityType, entity, editorState } = this.props;
|
||||
const selectedText = getSelectionText(editorState);
|
||||
const { url, urlParams } = getChooserConfig(entityType, entity, selectedText);
|
||||
|
||||
$(document.body).on('hidden.bs.modal', this.onClose);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { shallow } from 'enzyme';
|
||||
|
||||
import ModalWorkflowSource, { getChooserConfig, filterEntityData } from './ModalWorkflowSource';
|
||||
import * as DraftUtils from '../DraftUtils';
|
||||
import { EditorState, convertFromRaw, AtomicBlockUtils, RichUtils, Modifier } from 'draft-js';
|
||||
|
||||
global.ModalWorkflow = () => {};
|
||||
|
@ -9,6 +10,7 @@ global.ModalWorkflow = () => {};
|
|||
describe('ModalWorkflowSource', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(global, 'ModalWorkflow');
|
||||
jest.spyOn(DraftUtils, 'getSelectionText').mockImplementation(() => '');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -29,21 +31,21 @@ describe('ModalWorkflowSource', () => {
|
|||
|
||||
describe('#getChooserConfig', () => {
|
||||
it('IMAGE', () => {
|
||||
expect(getChooserConfig({ type: 'IMAGE' })).toEqual({
|
||||
expect(getChooserConfig({ type: 'IMAGE' }, null, '')).toEqual({
|
||||
url: '/admin/images/chooser/?select_format=true',
|
||||
urlParams: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('EMBED', () => {
|
||||
expect(getChooserConfig({ type: 'EMBED' })).toEqual({
|
||||
expect(getChooserConfig({ type: 'EMBED' }, null, '')).toEqual({
|
||||
url: '/admin/embeds/chooser/',
|
||||
urlParams: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('DOCUMENT', () => {
|
||||
expect(getChooserConfig({ type: 'DOCUMENT' })).toEqual({
|
||||
expect(getChooserConfig({ type: 'DOCUMENT' }, null, '')).toEqual({
|
||||
url: '/admin/documents/chooser/',
|
||||
urlParams: {},
|
||||
});
|
||||
|
@ -51,25 +53,25 @@ describe('ModalWorkflowSource', () => {
|
|||
|
||||
describe('LINK', () => {
|
||||
it('no entity', () => {
|
||||
expect(getChooserConfig({ type: 'LINK' })).toMatchSnapshot();
|
||||
expect(getChooserConfig({ type: 'LINK' }, null, '')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('page', () => {
|
||||
expect(getChooserConfig({ type: 'LINK' }, {
|
||||
getData: () => ({ id: 1, parentId: 0 })
|
||||
})).toMatchSnapshot();
|
||||
}, '')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('mail', () => {
|
||||
expect(getChooserConfig({ type: 'LINK' }, {
|
||||
getData: () => ({ url: 'mailto:test@example.com' })
|
||||
})).toMatchSnapshot();
|
||||
}, '')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('external', () => {
|
||||
expect(getChooserConfig({ type: 'LINK' }, {
|
||||
getData: () => ({ url: 'https://www.example.com/' })
|
||||
})).toMatchSnapshot();
|
||||
}, '')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -146,7 +148,7 @@ describe('ModalWorkflowSource', () => {
|
|||
it('#componentDidMount', () => {
|
||||
const wrapper = shallow((
|
||||
<ModalWorkflowSource
|
||||
editorState={{}}
|
||||
editorState={EditorState.createEmpty()}
|
||||
entityType={{}}
|
||||
entity={{}}
|
||||
onComplete={() => {}}
|
||||
|
@ -171,7 +173,7 @@ describe('ModalWorkflowSource', () => {
|
|||
|
||||
const wrapper = shallow((
|
||||
<ModalWorkflowSource
|
||||
editorState={{}}
|
||||
editorState={EditorState.createEmpty()}
|
||||
entityType={{}}
|
||||
entity={{}}
|
||||
onComplete={() => {}}
|
||||
|
@ -192,7 +194,7 @@ describe('ModalWorkflowSource', () => {
|
|||
it('#componentWillUnmount', () => {
|
||||
const wrapper = shallow((
|
||||
<ModalWorkflowSource
|
||||
editorState={{}}
|
||||
editorState={EditorState.createEmpty()}
|
||||
entityType={{}}
|
||||
entity={{}}
|
||||
onComplete={() => {}}
|
||||
|
@ -335,7 +337,7 @@ describe('ModalWorkflowSource', () => {
|
|||
const onClose = jest.fn();
|
||||
const wrapper = shallow((
|
||||
<ModalWorkflowSource
|
||||
editorState={{}}
|
||||
editorState={EditorState.createEmpty()}
|
||||
entityType={{}}
|
||||
entity={{}}
|
||||
onComplete={() => {}}
|
||||
|
|
|
@ -37,6 +37,7 @@ Bug fixes
|
|||
* Correct dropdown arrow styling in Firefox, IE11 (Janneke Janssen, Alexs Mathilda)
|
||||
* Password reset no indicates specific validation errors on certain password restrictions (Lucas Moeskops)
|
||||
* Confirmation page on page deletion now respects custom ``get_admin_display_title`` methods (Kim Chee Leong)
|
||||
* Adding external link with selected text now includes text in link chooser (Tony Yates, Thibaud Colas, Alexs Mathilda)
|
||||
|
||||
|
||||
Upgrade considerations
|
||||
|
|
Ładowanie…
Reference in New Issue