Change the settings to avoid using Session authentication for rest framework as it raise exceptions in case client and backend are on the same domain
On the filter, adapt to take into account new version of django_filters
import React from 'react';
import ToolbarButtons from './ToolbarButtons';
import MarkButton from './MarkButton';
import CategoryButton from './CategoryButton';
import BlockButton from './BlockButton';
/**
* Define the default node type.
*/
const DEFAULT_NODE = 'paragraph'
/**
* Render the toolbar.
*
* @return {Element}
*/
export default class Toolbar extends React.Component {
/**
* Deserialize the initial editor state.
*
* @type {Object}
*/
constructor(props) {
super(props);
this.editorRef = React.createRef();
}
/**
* Check if the current selection has a mark with `type` in it.
*
* @param {String} type
* @return {Boolean}
*/
hasMark = type => {
const { value } = this.props;
return value.activeMarks.some(mark => mark.type === type);
}
/**
* Check if the any of the currently selected blocks are of `type`.
*
* @param {String} type
* @return {Boolean}
*/
hasBlock = type => {
const { value } = this.props;
return value.blocks.some(node => node.type === type)
}
/**
* When a mark button is clicked, toggle the current mark.
*
* @param {Event} e
* @param {String} type
*/
onClickMark = (e, type) => {
this.props.editor.toggleMark(type)
}
isBlockActive = (type) => {
let isActive = this.hasBlock(type)
if (['numbered-list', 'bulleted-list'].includes(type)) {
const { value } = this.props;
const firstBlock = value.blocks.first();
if(firstBlock) {
const parent = value.document.getParent(firstBlock.key);
isActive = this.hasBlock('list-item') && parent && parent.type === type;
}
}
return isActive;
}
onClickCategoryButton = (openPortal, closePortal, isOpen, e) => {
e.preventDefault();
const { value, editor } = this.props;
// Can't use toggleMark here, because it expects the same object
// @see https://github.com/ianstormtaylor/slate/issues/873
if (this.hasMark('category')) {
value.activeMarks.filter(mark => mark.type === 'category')
.forEach(mark => editor.removeMark(mark));
closePortal();
} else {
openPortal();
}
}
getSelectionParams = (value) => {
const { selection } = value
const { start, end} = selection
if (selection.isCollapsed) {
return {};
}
const nodes = [];
let hasStarted = false;
let hasEnded = false;
// Keep only the relevant nodes,
// i.e. nodes which are contained within selection
value.document.nodes.forEach((node) => {
if (start.isInNode(node)) {
hasStarted = true;
}
if (hasStarted && !hasEnded) {
nodes.push(node);
}
if (end.isAtEndOfNode(node)) {
hasEnded = true;
}
});
// Concatenate the nodes text
const text = nodes.map((node) => {
let textStart = start.isInNode(node) ? start.offset : 0;
let textEnd = end.isInNode(node) ? end.offset : node.text.length;
return node.text.substring(textStart,textEnd);
}).join('\n');
return {
currentSelectionText: text,
currentSelectionStart: start.offset,
currentSelectionEnd: end.offset
};
}
onCategoryClick = (closePortal, category) => {
const { value, editor } = this.props;
const { currentSelectionText, currentSelectionStart, currentSelectionEnd } = this.getSelectionParams(value);
if(!currentSelectionText) {
closePortal();
return;
}
const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category')
categoryMarks.forEach(mark => this.editor.removeMark(mark));
editor.addMark({
type: 'category',
data: {
text: currentSelectionText,
selection: {
start: currentSelectionStart,
end: currentSelectionEnd,
},
color: category.color,
key: category.key,
name: category.name,
comment: category.comment
}
})
closePortal();
}
/**
* When a block button is clicked, toggle the block type.
*
* @param {Event} e
* @param {String} type
*/
onClickBlock = (e, type) => {
e.preventDefault();
const { editor, value } = this.props;
// Handle everything but list buttons.
if (type !== 'bulleted-list' && type !== 'numbered-list') {
const isActive = this.hasBlock(type)
const isList = this.hasBlock('list-item')
if (isList) {
editor
.setBlocks(isActive ? DEFAULT_NODE : type)
.unwrapBlock('bulleted-list')
.unwrapBlock('numbered-list')
}
else {
editor
.setBlocks(isActive ? DEFAULT_NODE : type)
}
}
// Handle the extra wrapping required for list buttons.
else {
const isList = this.hasBlock('list-item')
const isType = value.blocks.some((block) => {
return !!document.getClosest(block.key, parent => parent.type === type)
})
if (isList && isType) {
editor
.setBlocks(DEFAULT_NODE)
.unwrapBlock('bulleted-list')
.unwrapBlock('numbered-list')
} else if (isList) {
editor
.unwrapBlock(type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list')
.wrapBlock(type)
} else {
editor
.setBlocks('list-item')
.wrapBlock(type)
}
}
}
render = () => {
return (
<div className="menu toolbar-menu d-flex sticky-top bg-secondary">
<MarkButton icon='format_bold' isActive={this.hasMark('bold')} onMouseDown={(e) => this.onClickMark(e, 'bold')} />
<MarkButton icon='format_italic' isActive={this.hasMark('italic')} onMouseDown={(e) => this.onClickMark(e, 'italic')} />
<MarkButton icon='format_underlined' isActive={this.hasMark('underlined')} onMouseDown={(e) => this.onClickMark(e, 'underlined')} />
<CategoryButton
isActive={this.hasMark('category')}
onClickCategoryButton={this.onClickCategoryButton}
onCategoryClick={this.onCategoryClick}
annotationCategories={this.props.annotationCategories}
/>
<BlockButton icon='format_list_numbered' isActive={this.isBlockActive('numbered-list')} onMouseDown={e => this.onClickBlock(e, 'numbered-list')} />
<BlockButton icon='format_list_bulleted' isActive={this.isBlockActive('bulleted-list')} onMouseDown={e => this.onClickBlock(e, 'bulleted-list')} />
<ToolbarButtons
hasNote={!!this.props.note}
isButtonDisabled={this.props.isButtonDisabled}
submitNote={this.props.submitNote}
/>
</div>
)
}
}