kopia lustrzana https://github.com/wagtail/wagtail
Feature/commenting a11y pass 1 (#7016)
* Add a keyboard shortcut to Draftail for adding/focusing comments * Increase the timeout for unfocusing comments to reflect doing it on mousedown * Update react-focus-trap and add focus trap to comments * Remove extra focusing logic and replace with focusTrap initialFocus argument * Add forceFocus to tests * Remove todo * Update Draftail to 1.4.1 to allow plugin keyBindingFns to be called * Remove now unneeded icon hiding css due to Draftail update * Add data-comment-add class to buttons to prevent comment unfocus * Prevent comment button showing on streamfield root, and attach contentpath to field parent for single field * Add keyboard shortcut for field level comments * Consolidate comments keyboard shortcut check in case we change, and use keyCode instead of key * Formatting and eslint fixes * Update testspull/7050/head
rodzic
dd90405aac
commit
d727b2b922
|
@ -65,6 +65,7 @@ const localComment: Comment = {
|
|||
|
||||
export const basicCommentsState: CommentsState = {
|
||||
focusedComment: 1,
|
||||
forceFocus: false,
|
||||
pinnedComment: 1,
|
||||
remoteCommentCount: 1,
|
||||
comments: new Map([[remoteComment.localId, remoteComment], [localComment.localId, localComment]]),
|
||||
|
|
|
@ -43,6 +43,7 @@ export interface SetFocusedCommentAction {
|
|||
type: typeof SET_FOCUSED_COMMENT;
|
||||
commentId: number | null;
|
||||
updatePinnedComment: boolean;
|
||||
forceFocus: boolean;
|
||||
}
|
||||
|
||||
export interface AddReplyAction {
|
||||
|
@ -115,12 +116,13 @@ export function resolveComment(commentId: number): ResolveCommentAction {
|
|||
|
||||
export function setFocusedComment(
|
||||
commentId: number | null,
|
||||
{ updatePinnedComment } = { updatePinnedComment: false }
|
||||
{ updatePinnedComment, forceFocus } = { updatePinnedComment: false, forceFocus: false }
|
||||
): SetFocusedCommentAction {
|
||||
return {
|
||||
type: SET_FOCUSED_COMMENT,
|
||||
commentId,
|
||||
updatePinnedComment
|
||||
updatePinnedComment,
|
||||
forceFocus
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import React, { MutableRefObject } from 'react';
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
|
||||
import type { Store } from '../../state';
|
||||
import { Author, Comment, newCommentReply } from '../../state/comments';
|
||||
|
@ -75,6 +78,7 @@ export interface CommentProps {
|
|||
store: Store;
|
||||
comment: Comment;
|
||||
isFocused: boolean;
|
||||
forceFocus: boolean;
|
||||
isVisible: boolean;
|
||||
layout: LayoutController;
|
||||
user: Author | null;
|
||||
|
@ -82,13 +86,6 @@ export interface CommentProps {
|
|||
}
|
||||
|
||||
export default class CommentComponent extends React.Component<CommentProps> {
|
||||
focusTargetRef: MutableRefObject<HTMLTextAreaElement | null>
|
||||
focusTimeoutId: number | undefined
|
||||
|
||||
constructor(props: CommentProps) {
|
||||
super(props);
|
||||
this.focusTargetRef = React.createRef();
|
||||
}
|
||||
renderReplies({ hideNewReply = false } = {}): React.ReactFragment {
|
||||
const { comment, isFocused, store, user, strings } = this.props;
|
||||
|
||||
|
@ -195,7 +192,7 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
}
|
||||
|
||||
renderCreating(): React.ReactFragment {
|
||||
const { comment, store, strings } = this.props;
|
||||
const { comment, store, strings, isFocused } = this.props;
|
||||
|
||||
const onChangeText = (value: string) => {
|
||||
store.dispatch(
|
||||
|
@ -221,7 +218,7 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
<CommentHeader commentReply={comment} store={store} strings={strings} />
|
||||
<form onSubmit={onSave}>
|
||||
<TextArea
|
||||
ref={this.focusTargetRef}
|
||||
focusTarget={isFocused}
|
||||
className="comment__input"
|
||||
value={comment.newText}
|
||||
onChange={onChangeText}
|
||||
|
@ -248,7 +245,7 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
}
|
||||
|
||||
renderEditing(): React.ReactFragment {
|
||||
const { comment, store, strings } = this.props;
|
||||
const { comment, store, strings, isFocused } = this.props;
|
||||
|
||||
const onChangeText = (value: string) => {
|
||||
store.dispatch(
|
||||
|
@ -280,7 +277,7 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
<CommentHeader commentReply={comment} store={store} strings={strings} />
|
||||
<form onSubmit={onSave}>
|
||||
<TextArea
|
||||
ref={this.focusTargetRef}
|
||||
focusTarget={isFocused}
|
||||
className="comment__input"
|
||||
value={comment.newText}
|
||||
onChange={onChangeText}
|
||||
|
@ -529,11 +526,15 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
}
|
||||
|
||||
const onClick = () => {
|
||||
this.props.store.dispatch(setFocusedComment(this.props.comment.localId));
|
||||
this.props.store.dispatch(
|
||||
setFocusedComment(this.props.comment.localId, { updatePinnedComment: false, forceFocus: true })
|
||||
);
|
||||
};
|
||||
|
||||
const onDoubleClick = () => {
|
||||
this.props.store.dispatch(setFocusedComment(this.props.comment.localId, { updatePinnedComment: true }));
|
||||
this.props.store.dispatch(
|
||||
setFocusedComment(this.props.comment.localId, { updatePinnedComment: true, forceFocus: true })
|
||||
);
|
||||
};
|
||||
|
||||
const top = this.props.layout.getCommentPosition(
|
||||
|
@ -541,21 +542,39 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
);
|
||||
const right = this.props.isFocused ? 50 : 0;
|
||||
return (
|
||||
<li
|
||||
key={this.props.comment.localId}
|
||||
className={`comment comment--mode-${this.props.comment.mode} ${this.props.isFocused ? 'comment--focused' : ''}`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: `${top}px`,
|
||||
right: `${right}px`,
|
||||
display: this.props.isVisible ? 'block' : 'none',
|
||||
}}
|
||||
data-comment-id={this.props.comment.localId}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
preventScroll: true,
|
||||
clickOutsideDeactivates: true,
|
||||
onDeactivate: () => {
|
||||
this.props.store.dispatch(
|
||||
setFocusedComment(null, { updatePinnedComment: true, forceFocus: false })
|
||||
);
|
||||
},
|
||||
initialFocus: '[data-focus-target="true"]',
|
||||
} as any} // For some reason, the types for FocusTrap props don't yet include preventScroll.
|
||||
active={this.props.isFocused && this.props.forceFocus}
|
||||
>
|
||||
{inner}
|
||||
</li>
|
||||
<li
|
||||
tabIndex={-1}
|
||||
data-focus-target={this.props.isFocused && !['creating', 'editing'].includes(this.props.comment.mode)}
|
||||
key={this.props.comment.localId}
|
||||
className={
|
||||
`comment comment--mode-${this.props.comment.mode} ${this.props.isFocused ? 'comment--focused' : ''}`
|
||||
}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: `${top}px`,
|
||||
right: `${right}px`,
|
||||
display: this.props.isVisible ? 'block' : 'none',
|
||||
}}
|
||||
data-comment-id={this.props.comment.localId}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
>
|
||||
{inner}
|
||||
</li>
|
||||
</FocusTrap>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -563,18 +582,6 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
const element = ReactDOM.findDOMNode(this);
|
||||
|
||||
if (element instanceof HTMLElement) {
|
||||
// If this is a new comment, focus in the edit box
|
||||
if (this.props.comment.mode === 'creating') {
|
||||
clearTimeout(this.focusTimeoutId);
|
||||
this.focusTimeoutId = setTimeout(
|
||||
() => {
|
||||
if (this.focusTargetRef.current) {
|
||||
this.focusTargetRef.current.focus();
|
||||
}
|
||||
}
|
||||
, 10);
|
||||
}
|
||||
|
||||
this.props.layout.setCommentElement(this.props.comment.localId, element);
|
||||
|
||||
if (this.props.isVisible) {
|
||||
|
@ -587,7 +594,6 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.focusTimeoutId);
|
||||
this.props.layout.setCommentElement(this.props.comment.localId, null);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ export interface TextAreaProps {
|
|||
placeholder?: string;
|
||||
onChange?(newValue: string): void;
|
||||
focusOnMount?: boolean;
|
||||
focusTarget?: boolean;
|
||||
}
|
||||
|
||||
const TextArea = React.forwardRef<HTMLTextAreaElement | null, TextAreaProps>(({
|
||||
|
@ -13,7 +14,8 @@ const TextArea = React.forwardRef<HTMLTextAreaElement | null, TextAreaProps>(({
|
|||
className,
|
||||
placeholder,
|
||||
onChange,
|
||||
focusOnMount
|
||||
focusOnMount,
|
||||
focusTarget = false
|
||||
}, ref) => {
|
||||
const onChangeValue = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
if (onChange) {
|
||||
|
@ -42,6 +44,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement | null, TextAreaProps>(({
|
|||
|
||||
return (
|
||||
<textarea
|
||||
data-focus-target={focusTarget}
|
||||
rows={1}
|
||||
style={{ resize: 'none', overflowY: 'hidden' }}
|
||||
className={className}
|
||||
|
|
|
@ -110,7 +110,7 @@ function renderCommentsUi(
|
|||
): React.ReactElement {
|
||||
const state = store.getState();
|
||||
const { commentsEnabled, user, currentTab } = state.settings;
|
||||
const focusedComment = state.comments.focusedComment;
|
||||
const { focusedComment, forceFocus } = state.comments;
|
||||
let commentsToRender = comments;
|
||||
|
||||
if (!commentsEnabled || !user) {
|
||||
|
@ -126,6 +126,7 @@ function renderCommentsUi(
|
|||
user={user}
|
||||
comment={comment}
|
||||
isFocused={comment.localId === focusedComment}
|
||||
forceFocus={forceFocus}
|
||||
isVisible={layout.getCommentVisible(currentTab, comment.localId)}
|
||||
strings={strings}
|
||||
/>
|
||||
|
@ -213,7 +214,7 @@ export class CommentApp {
|
|||
);
|
||||
|
||||
// Focus and pin the comment
|
||||
this.store.dispatch(setFocusedComment(commentId, { updatePinnedComment: true }));
|
||||
this.store.dispatch(setFocusedComment(commentId, { updatePinnedComment: true, forceFocus: true }));
|
||||
return commentId;
|
||||
}
|
||||
setVisible(visible: boolean) {
|
||||
|
@ -333,7 +334,7 @@ export class CommentApp {
|
|||
// If this is the initial focused comment. Focus and pin it
|
||||
// TODO: Scroll to this comment
|
||||
if (initialFocusedCommentId && comment.pk === initialFocusedCommentId) {
|
||||
this.store.dispatch(setFocusedComment(commentId, { updatePinnedComment: true }));
|
||||
this.store.dispatch(setFocusedComment(commentId, { updatePinnedComment: true, forceFocus: true }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,8 +349,8 @@ export class CommentApp {
|
|||
if (!e.target.closest('#comments, [data-annotation], [data-comment-add]')) {
|
||||
// Running store.dispatch directly here seems to prevent the event from being handled anywhere else
|
||||
setTimeout(() => {
|
||||
this.store.dispatch(setFocusedComment(null, { updatePinnedComment: true }));
|
||||
}, 1);
|
||||
this.store.dispatch(setFocusedComment(null, { updatePinnedComment: true, forceFocus: false }));
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -132,16 +132,19 @@ test('Remote comment resolved', () => {
|
|||
});
|
||||
|
||||
test('Comment focused', () => {
|
||||
const focusAction = actions.setFocusedComment(4);
|
||||
const focusAction = actions.setFocusedComment(4, { updatePinnedComment: true, forceFocus: true });
|
||||
const newState = reducer(basicCommentsState, focusAction);
|
||||
expect(newState.focusedComment).toBe(4);
|
||||
expect(newState.pinnedComment).toBe(4);
|
||||
expect(newState.forceFocus).toBe(true);
|
||||
});
|
||||
|
||||
test('Invalid comment not focused', () => {
|
||||
const focusAction = actions.setFocusedComment(9000, { updatePinnedComment: true });
|
||||
const focusAction = actions.setFocusedComment(9000, { updatePinnedComment: true, forceFocus: true });
|
||||
const newState = reducer(basicCommentsState, focusAction);
|
||||
expect(newState.focusedComment).toBe(basicCommentsState.focusedComment);
|
||||
expect(newState.pinnedComment).toBe(basicCommentsState.pinnedComment);
|
||||
expect(newState.forceFocus).toBe(false);
|
||||
});
|
||||
|
||||
test('Reply added', () => {
|
||||
|
|
|
@ -159,6 +159,7 @@ export type CommentUpdate = Partial<Omit<Comment, 'originalText'>>;
|
|||
|
||||
export interface CommentsState {
|
||||
comments: Map<number, Comment>;
|
||||
forceFocus: boolean;
|
||||
focusedComment: number | null;
|
||||
pinnedComment: number | null;
|
||||
// This is redundant, but stored for efficiency as it will change only as the app adds its loaded comments
|
||||
|
@ -167,6 +168,7 @@ export interface CommentsState {
|
|||
|
||||
export const INITIAL_STATE: CommentsState = {
|
||||
comments: new Map(),
|
||||
forceFocus: false,
|
||||
focusedComment: null,
|
||||
pinnedComment: null,
|
||||
remoteCommentCount: 0,
|
||||
|
@ -185,6 +187,7 @@ export const reducer = produce((draft: CommentsState, action: actions.Action) =>
|
|||
// Unset focusedComment if the focused comment is the one being deleted
|
||||
if (draft.focusedComment === comment.localId) {
|
||||
draft.focusedComment = null;
|
||||
draft.forceFocus = false;
|
||||
}
|
||||
if (draft.pinnedComment === comment.localId) {
|
||||
draft.pinnedComment = null;
|
||||
|
@ -246,6 +249,7 @@ export const reducer = produce((draft: CommentsState, action: actions.Action) =>
|
|||
if (action.updatePinnedComment) {
|
||||
draft.pinnedComment = action.commentId;
|
||||
}
|
||||
draft.forceFocus = action.forceFocus;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,6 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.Draftail-ToolbarButton[name^='COMMENT-'] {
|
||||
// This is necessary because all inline styles added to Draftail currently
|
||||
// get a toolbar icon, even if empty. This causes the programmatically
|
||||
// added comment styles to get empty icons. This should be fixed in Draftail
|
||||
// soon - when it is, this can be removed
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Draftail-ToolbarGroup:last-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@ describe('CommentableEditor', () => {
|
|||
overlappingHighlight: '#00FF00',
|
||||
focusedHighlight: '#000000',
|
||||
}}
|
||||
isCommentShortcut={() => false}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
ContentState,
|
||||
DraftInlineStyle,
|
||||
EditorState,
|
||||
KeyBindingUtil,
|
||||
Modifier,
|
||||
RawDraftContentState,
|
||||
RichUtils,
|
||||
|
@ -26,8 +27,14 @@ import { useSelector, shallowEqual } from 'react-redux';
|
|||
import { STRINGS } from '../../../config/wagtailConfig';
|
||||
import Icon from '../../Icon/Icon';
|
||||
|
||||
const { isOptionKeyCommand } = KeyBindingUtil;
|
||||
|
||||
const COMMENT_STYLE_IDENTIFIER = 'COMMENT-';
|
||||
|
||||
// Hack taken from https://github.com/springload/draftail/blob/main/lib/api/behavior.js#L30
|
||||
// Can be replaced with usesMacOSHeuristics once we upgrade draft-js
|
||||
const IS_MAC_OS = isOptionKeyCommand({ altKey: true } as any) === true;
|
||||
|
||||
function usePrevious<Type>(value: Type) {
|
||||
const ref = useRef(value);
|
||||
useEffect(() => {
|
||||
|
@ -170,6 +177,20 @@ function getFullSelectionState(contentState: ContentState) {
|
|||
});
|
||||
}
|
||||
|
||||
function addNewComment(editorState: EditorState, fieldNode: Element, commentApp: CommentApp, contentPath: string) {
|
||||
const annotation = new DraftailInlineAnnotation(fieldNode);
|
||||
const commentId = commentApp.makeComment(annotation, contentPath, '[]');
|
||||
return (
|
||||
EditorState.acceptSelection(
|
||||
RichUtils.toggleInlineStyle(
|
||||
editorState,
|
||||
`${COMMENT_STYLE_IDENTIFIER}${commentId}`
|
||||
),
|
||||
editorState.getSelection()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface ControlProps {
|
||||
getEditorState: () => EditorState,
|
||||
onChange: (editorState: EditorState) => void
|
||||
|
@ -181,20 +202,11 @@ function getCommentControl(commentApp: CommentApp, contentPath: string, fieldNod
|
|||
<ToolbarButton
|
||||
name="comment"
|
||||
active={false}
|
||||
title={STRINGS.ADD_A_COMMENT}
|
||||
title={`${STRINGS.ADD_A_COMMENT}\n${IS_MAC_OS ? '⌘ + Alt + M' : 'Ctrl + Alt + M'}`}
|
||||
icon={<Icon name="comment" />}
|
||||
onClick={() => {
|
||||
const annotation = new DraftailInlineAnnotation(fieldNode);
|
||||
const commentId = commentApp.makeComment(annotation, contentPath, '[]');
|
||||
const editorState = getEditorState();
|
||||
onChange(
|
||||
EditorState.acceptSelection(
|
||||
RichUtils.toggleInlineStyle(
|
||||
editorState,
|
||||
`${COMMENT_STYLE_IDENTIFIER}${commentId}`
|
||||
),
|
||||
editorState.getSelection()
|
||||
)
|
||||
addNewComment(getEditorState(), fieldNode, commentApp, contentPath)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -291,10 +303,17 @@ export function updateCommentPositions({ editorState, comments, commentApp }:
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a contentBlock and offset within it, find the id of the comment at that offset which
|
||||
* has the fewest style ranges within the block, or null if no comment exists at the offset
|
||||
*/
|
||||
export function findLeastCommonCommentId(block: ContentBlock, offset: number) {
|
||||
const styles = block.getInlineStyleAt(offset).filter(styleIsComment) as Immutable.OrderedSet<string>;
|
||||
let styleToUse: string;
|
||||
if (styles.count() > 1) {
|
||||
const styleCount = styles.count();
|
||||
if (styleCount === 0) {
|
||||
return null;
|
||||
} else if (styleCount > 1) {
|
||||
// We're dealing with overlapping comments.
|
||||
// Find the least frequently occurring style and use that - this isn't foolproof, but in
|
||||
// most cases should ensure that all comments have at least one clickable section. This
|
||||
|
@ -349,6 +368,9 @@ function getCommentDecorator(commentApp: CommentApp) {
|
|||
useEffect(() => {
|
||||
// Add a ref to the annotation, allowing the comment to float alongside the attached text.
|
||||
// This adds rather than sets the ref, so that a comment may be attached across paragraphs or around entities
|
||||
if (!commentId) {
|
||||
return undefined;
|
||||
}
|
||||
const annotation = commentApp.layout.commentAnnotations.get(commentId);
|
||||
if (annotation && annotation instanceof DraftailInlineAnnotation) {
|
||||
annotation.addDecoratorRef(annotationNode, blockKey);
|
||||
|
@ -358,6 +380,9 @@ function getCommentDecorator(commentApp: CommentApp) {
|
|||
}, [commentId, annotationNode, blockKey]);
|
||||
const onClick = () => {
|
||||
// Ensure the comment will appear alongside the current block
|
||||
if (!commentId) {
|
||||
return;
|
||||
}
|
||||
const annotation = commentApp.layout.commentAnnotations.get(commentId);
|
||||
if (annotation && annotation instanceof DraftailInlineAnnotation && annotationNode) {
|
||||
annotation.setFocusedBlockKey(blockKey);
|
||||
|
@ -367,10 +392,10 @@ function getCommentDecorator(commentApp: CommentApp) {
|
|||
commentApp.store.dispatch(
|
||||
commentApp.actions.setFocusedComment(commentId, {
|
||||
updatePinnedComment: true,
|
||||
forceFocus: false
|
||||
})
|
||||
);
|
||||
};
|
||||
// TODO: determine the correct way to make this accessible, allowing both editing and focus jumps
|
||||
return (
|
||||
<span
|
||||
role="button"
|
||||
|
@ -420,8 +445,10 @@ export function addCommentsToEditor(
|
|||
});
|
||||
});
|
||||
} catch (err) {
|
||||
/* eslint-disable no-console */
|
||||
console.error(`Error loading comment position for comment ${comment.localId}`);
|
||||
console.error(err);
|
||||
/* esline-enable no-console */
|
||||
}
|
||||
});
|
||||
return newContentState;
|
||||
|
@ -450,6 +477,7 @@ interface CommentableEditorProps {
|
|||
inlineStyles: Array<InlineStyle>,
|
||||
editorRef: (editor: ReactNode) => void
|
||||
colorConfig: ColorConfigProp
|
||||
isCommentShortcut: (e: React.KeyboardEvent) => boolean
|
||||
}
|
||||
|
||||
function CommentableEditor({
|
||||
|
@ -461,6 +489,7 @@ function CommentableEditor({
|
|||
inlineStyles,
|
||||
editorRef,
|
||||
colorConfig: { standardHighlight, overlappingHighlight, focusedHighlight },
|
||||
isCommentShortcut,
|
||||
...options
|
||||
}: CommentableEditorProps) {
|
||||
const [editorState, setEditorState] = useState(() =>
|
||||
|
@ -541,7 +570,9 @@ function CommentableEditor({
|
|||
useEffect(() => {
|
||||
// if there are any comments without annotations, we need to add them to the EditorState
|
||||
const contentState = editorState.getCurrentContent();
|
||||
const newContentState = addCommentsToEditor(contentState, comments, commentApp, () => new DraftailInlineAnnotation(fieldNode));
|
||||
const newContentState = addCommentsToEditor(
|
||||
contentState, comments, commentApp, () => new DraftailInlineAnnotation(fieldNode)
|
||||
);
|
||||
if (contentState !== newContentState) {
|
||||
setEditorState(forceResetEditorState(editorState, newContentState));
|
||||
}
|
||||
|
@ -606,6 +637,36 @@ function CommentableEditor({
|
|||
}
|
||||
inlineStyles={inlineStyles.concat(commentStyles)}
|
||||
plugins={enabled ? [{
|
||||
keyBindingFn: (e: React.KeyboardEvent) => {
|
||||
if (isCommentShortcut(e)) {
|
||||
return 'comment';
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
handleKeyCommand: (command: string, state: EditorState) => {
|
||||
if (enabled && command === 'comment') {
|
||||
const selection = state.getSelection();
|
||||
const content = state.getCurrentContent();
|
||||
if (selection.isCollapsed()) {
|
||||
// We might be trying to focus an existing comment - check if we're in a comment range
|
||||
const id = findLeastCommonCommentId(
|
||||
content.getBlockForKey(selection.getAnchorKey()),
|
||||
selection.getAnchorOffset()
|
||||
);
|
||||
if (id) {
|
||||
// Focus the comment
|
||||
commentApp.store.dispatch(
|
||||
commentApp.actions.setFocusedComment(id, { updatePinnedComment: true, forceFocus: true })
|
||||
);
|
||||
return 'handled';
|
||||
}
|
||||
}
|
||||
// Otherwise, add a new comment
|
||||
setEditorState(addNewComment(state, fieldNode, commentApp, contentPath));
|
||||
return 'handled';
|
||||
}
|
||||
return 'not-handled';
|
||||
},
|
||||
customStyleFn: (styleSet: DraftInlineStyle) => {
|
||||
// Use of casting in this function is due to issue #1563 in immutable-js, which causes operations like
|
||||
// map and filter to lose type information on the results. It should be fixed in v4: when we upgrade,
|
||||
|
|
|
@ -133,6 +133,7 @@ const initEditor = (selector, options, currentScript) => {
|
|||
fieldNode={field.parentNode}
|
||||
contentPath={contentPath}
|
||||
colorConfig={colors}
|
||||
isCommentShortcut={window.comments.isCommentShortcut}
|
||||
{...sharedProps}
|
||||
/>
|
||||
</Provider>
|
||||
|
|
|
@ -157,34 +157,36 @@ class ExplorerPanel extends React.Component<ExplorerPanelProps, ExplorerPanelSta
|
|||
|
||||
return (
|
||||
<FocusTrap
|
||||
tag="div"
|
||||
role="dialog"
|
||||
className="explorer"
|
||||
paused={paused || !page || page.isFetchingChildren || page.isFetchingTranslations}
|
||||
focusTrapOptions={{
|
||||
initialFocus: '.c-explorer__header__title',
|
||||
onDeactivate: onClose,
|
||||
}}
|
||||
>
|
||||
<Button className="c-explorer__close">
|
||||
{STRINGS.CLOSE_EXPLORER}
|
||||
</Button>
|
||||
<Transition name={transition} className="c-explorer" component="nav" label={STRINGS.PAGE_EXPLORER}>
|
||||
<div key={depth} className="c-transition-group">
|
||||
<ExplorerHeader
|
||||
depth={depth}
|
||||
page={page}
|
||||
onClick={this.onHeaderClick}
|
||||
gotoPage={gotoPage}
|
||||
/>
|
||||
<div
|
||||
role="dialog"
|
||||
className="explorer"
|
||||
>
|
||||
<Button className="c-explorer__close">
|
||||
{STRINGS.CLOSE_EXPLORER}
|
||||
</Button>
|
||||
<Transition name={transition} className="c-explorer" component="nav" label={STRINGS.PAGE_EXPLORER}>
|
||||
<div key={depth} className="c-transition-group">
|
||||
<ExplorerHeader
|
||||
depth={depth}
|
||||
page={page}
|
||||
onClick={this.onHeaderClick}
|
||||
gotoPage={gotoPage}
|
||||
/>
|
||||
|
||||
{this.renderChildren()}
|
||||
{this.renderChildren()}
|
||||
|
||||
{page.isError || page.children.items && page.children.count > MAX_EXPLORER_PAGES ? (
|
||||
<PageCount page={page} />
|
||||
) : null}
|
||||
</div>
|
||||
</Transition>
|
||||
{page.isError || page.children.items && page.children.count > MAX_EXPLORER_PAGES ? (
|
||||
<PageCount page={page} />
|
||||
) : null}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ exports[`ExplorerPanel general rendering #isError 1`] = `
|
|||
<FocusTrap
|
||||
_createFocusTrap={[Function]}
|
||||
active={true}
|
||||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__header__title",
|
||||
|
@ -12,76 +11,79 @@ exports[`ExplorerPanel general rendering #isError 1`] = `
|
|||
}
|
||||
}
|
||||
paused={false}
|
||||
role="dialog"
|
||||
tag="div"
|
||||
>
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
<div
|
||||
className="explorer"
|
||||
role="dialog"
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<div
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": true,
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__drawer"
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
>
|
||||
<div
|
||||
key="children"
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": true,
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__placeholder"
|
||||
key="error"
|
||||
className="c-explorer__drawer"
|
||||
>
|
||||
Server Error
|
||||
<div
|
||||
key="children"
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__placeholder"
|
||||
key="error"
|
||||
>
|
||||
Server Error
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PageCount
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": true,
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
<PageCount
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": true,
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
`;
|
||||
|
||||
|
@ -89,7 +91,6 @@ exports[`ExplorerPanel general rendering #isFetching 1`] = `
|
|||
<FocusTrap
|
||||
_createFocusTrap={[Function]}
|
||||
active={true}
|
||||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__header__title",
|
||||
|
@ -97,57 +98,60 @@ exports[`ExplorerPanel general rendering #isFetching 1`] = `
|
|||
}
|
||||
}
|
||||
paused={false}
|
||||
role="dialog"
|
||||
tag="div"
|
||||
>
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
<div
|
||||
className="explorer"
|
||||
role="dialog"
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<div
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"isFetching": true,
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__drawer"
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
>
|
||||
<div
|
||||
key="children"
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"isFetching": true,
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__drawer"
|
||||
>
|
||||
<div
|
||||
key="children"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
`;
|
||||
|
||||
|
@ -155,7 +159,6 @@ exports[`ExplorerPanel general rendering #items 1`] = `
|
|||
<FocusTrap
|
||||
_createFocusTrap={[Function]}
|
||||
active={true}
|
||||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__header__title",
|
||||
|
@ -163,85 +166,88 @@ exports[`ExplorerPanel general rendering #items 1`] = `
|
|||
}
|
||||
}
|
||||
paused={false}
|
||||
role="dialog"
|
||||
tag="div"
|
||||
>
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
<div
|
||||
className="explorer"
|
||||
role="dialog"
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<div
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [
|
||||
1,
|
||||
2,
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__drawer"
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
>
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [
|
||||
1,
|
||||
2,
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
key="children"
|
||||
className="c-explorer__drawer"
|
||||
>
|
||||
<ExplorerItem
|
||||
item={
|
||||
Object {
|
||||
"admin_display_title": "Test",
|
||||
"id": 1,
|
||||
"meta": Object {
|
||||
"status": Object {},
|
||||
"type": "test",
|
||||
},
|
||||
<div
|
||||
key="children"
|
||||
>
|
||||
<ExplorerItem
|
||||
item={
|
||||
Object {
|
||||
"admin_display_title": "Test",
|
||||
"id": 1,
|
||||
"meta": Object {
|
||||
"status": Object {},
|
||||
"type": "test",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
key="1"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<ExplorerItem
|
||||
item={
|
||||
Object {
|
||||
"admin_display_title": "Foo",
|
||||
"id": 2,
|
||||
"meta": Object {
|
||||
"status": Object {},
|
||||
"type": "foo",
|
||||
},
|
||||
key="1"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<ExplorerItem
|
||||
item={
|
||||
Object {
|
||||
"admin_display_title": "Foo",
|
||||
"id": 2,
|
||||
"meta": Object {
|
||||
"status": Object {},
|
||||
"type": "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
key="2"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
key="2"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
`;
|
||||
|
||||
|
@ -249,7 +255,6 @@ exports[`ExplorerPanel general rendering no children 1`] = `
|
|||
<FocusTrap
|
||||
_createFocusTrap={[Function]}
|
||||
active={true}
|
||||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__header__title",
|
||||
|
@ -257,54 +262,57 @@ exports[`ExplorerPanel general rendering no children 1`] = `
|
|||
}
|
||||
}
|
||||
paused={false}
|
||||
role="dialog"
|
||||
tag="div"
|
||||
>
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
<div
|
||||
className="explorer"
|
||||
role="dialog"
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<div
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__drawer"
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
>
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__placeholder"
|
||||
key="empty"
|
||||
className="c-explorer__drawer"
|
||||
>
|
||||
No results
|
||||
<div
|
||||
className="c-explorer__placeholder"
|
||||
key="empty"
|
||||
>
|
||||
No results
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
`;
|
||||
|
||||
|
@ -312,7 +320,6 @@ exports[`ExplorerPanel general rendering renders 1`] = `
|
|||
<FocusTrap
|
||||
_createFocusTrap={[Function]}
|
||||
active={true}
|
||||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__header__title",
|
||||
|
@ -320,55 +327,58 @@ exports[`ExplorerPanel general rendering renders 1`] = `
|
|||
}
|
||||
}
|
||||
paused={false}
|
||||
role="dialog"
|
||||
tag="div"
|
||||
>
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
<div
|
||||
className="explorer"
|
||||
role="dialog"
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<div
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
<Button
|
||||
accessibleLabel={null}
|
||||
className="c-explorer__close"
|
||||
dialogTrigger={false}
|
||||
href="#"
|
||||
isLoading={false}
|
||||
onClick={null}
|
||||
preventDefault={true}
|
||||
target={null}
|
||||
>
|
||||
Close explorer
|
||||
</Button>
|
||||
<Transition
|
||||
className="c-explorer"
|
||||
component="nav"
|
||||
duration={210}
|
||||
label="Page explorer"
|
||||
name="push"
|
||||
>
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__drawer"
|
||||
className="c-transition-group"
|
||||
key="1"
|
||||
>
|
||||
<div
|
||||
key="children"
|
||||
<ExplorerHeader
|
||||
depth={1}
|
||||
gotoPage={[MockFunction]}
|
||||
onClick={[Function]}
|
||||
page={
|
||||
Object {
|
||||
"children": Object {
|
||||
"items": Array [],
|
||||
},
|
||||
"meta": Object {
|
||||
"parent": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="c-explorer__drawer"
|
||||
>
|
||||
<div
|
||||
key="children"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
`;
|
||||
|
|
|
@ -45,6 +45,7 @@ export class FieldBlock {
|
|||
const addCommentButtonElement = document.createElement('button');
|
||||
addCommentButtonElement.type = 'button';
|
||||
addCommentButtonElement.setAttribute('aria-label', blockDef.meta.strings.ADD_COMMENT);
|
||||
addCommentButtonElement.setAttribute('data-comment-add', '');
|
||||
addCommentButtonElement.classList.add('button');
|
||||
addCommentButtonElement.classList.add('button-secondary');
|
||||
addCommentButtonElement.classList.add('button-small');
|
||||
|
|
|
@ -29,6 +29,6 @@ exports[`telepath: wagtail.blocks.FieldBlock with comments enabled it renders co
|
|||
<p name=\\"the-prefix\\" id=\\"the-prefix\\">The widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
<p class=\\"help\\">drink <em>more</em> water</p><button type=\\"button\\" aria-label=\\"Add Comment\\" class=\\"button button-secondary button-small u-hidden\\"><svg class=\\"icon icon-comment initial\\" aria-hidden=\\"true\\" focusable=\\"false\\"><use href=\\"#icon-comment\\"></use></svg></button></div>
|
||||
<p class=\\"help\\">drink <em>more</em> water</p><button type=\\"button\\" aria-label=\\"Add Comment\\" data-comment-add=\\"\\" class=\\"button button-secondary button-small u-hidden\\"><svg class=\\"icon icon-comment initial\\" aria-hidden=\\"true\\" focusable=\\"false\\"><use href=\\"#icon-comment\\"></use></svg></button></div>
|
||||
</div>"
|
||||
`;
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import { initCommentApp } from '../../components/CommentApp/main';
|
||||
import { STRINGS } from '../../config/wagtailConfig';
|
||||
|
||||
const KEYCODE_M = 77;
|
||||
|
||||
/**
|
||||
* Entry point loaded when the comments system is in use.
|
||||
*/
|
||||
window.comments = (() => {
|
||||
const commentApp = initCommentApp();
|
||||
|
||||
/**
|
||||
* Returns true if the provided keyboard event is using the 'add/focus comment' keyboard
|
||||
* shortcut
|
||||
*/
|
||||
function isCommentShortcut(e) {
|
||||
return (e.ctrlKey || e.metaKey) && e.altKey && e.keyCode === KEYCODE_M;
|
||||
}
|
||||
|
||||
function getContentPath(fieldNode) {
|
||||
// Return the total contentpath for an element as a string, in the form field.streamfield_uid.block...
|
||||
if (fieldNode.closest('data-contentpath-disabled')) {
|
||||
|
@ -109,7 +119,7 @@ window.comments = (() => {
|
|||
setOnClickHandler(localId) {
|
||||
this.node.addEventListener('click', () => {
|
||||
commentApp.store.dispatch(
|
||||
commentApp.actions.setFocusedComment(localId, { updatePinnedComment: true })
|
||||
commentApp.actions.setFocusedComment(localId, { updatePinnedComment: true, forceFocus: true })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -178,11 +188,28 @@ window.comments = (() => {
|
|||
annotation.subscribeToUpdates(comment.localId);
|
||||
}
|
||||
});
|
||||
this.commentAdditionNode.addEventListener('click', () => {
|
||||
// Make the widget button clickable to add a comment
|
||||
const addComment = () => {
|
||||
const annotation = this.getAnnotationForComment();
|
||||
const localId = commentApp.makeComment(annotation, this.contentpath);
|
||||
annotation.subscribeToUpdates(localId);
|
||||
};
|
||||
this.commentAdditionNode.addEventListener('click', () => {
|
||||
// Make the widget button clickable to add a comment
|
||||
addComment();
|
||||
});
|
||||
this.fieldNode.addEventListener('keyup', (e) => {
|
||||
if (currentlyEnabled && isCommentShortcut(e)) {
|
||||
if (currentComments.length === 0) {
|
||||
addComment();
|
||||
} else {
|
||||
commentApp.store.dispatch(
|
||||
commentApp.actions.setFocusedComment(
|
||||
currentComments[0].localId,
|
||||
{ updatePinnedComment: true, forceFocus: true }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
return unsubscribeWidget; // TODO: listen for widget deletion and use this
|
||||
}
|
||||
|
@ -207,7 +234,7 @@ window.comments = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
function initAddCommentButton(buttonElement, skipDoubleInitialisedCheck = false) {
|
||||
function initAddCommentButton(buttonElement) {
|
||||
const widget = new FieldLevelCommentWidget({
|
||||
fieldNode: buttonElement.closest('[data-contentpath]'),
|
||||
commentAdditionNode: buttonElement,
|
||||
|
@ -279,6 +306,7 @@ window.comments = (() => {
|
|||
return {
|
||||
commentApp,
|
||||
getContentPath,
|
||||
isCommentShortcut,
|
||||
initAddCommentButton,
|
||||
initCommentsInterface,
|
||||
};
|
||||
|
|
|
@ -2431,6 +2431,8 @@
|
|||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
|
@ -2894,6 +2896,13 @@
|
|||
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
|
@ -4083,9 +4092,9 @@
|
|||
}
|
||||
},
|
||||
"draftail": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/draftail/-/draftail-1.3.0.tgz",
|
||||
"integrity": "sha512-LOG4YgrJX4BMYJX5XrKGBegH9f/M21I8nFt+7MrPAB0ABXpURZhUVzVfWVa6hEdw6yOl2Z1T4H2G4OFTouK6Bg==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/draftail/-/draftail-1.4.1.tgz",
|
||||
"integrity": "sha512-JaVnNngJmoon31Mb/c/RLHaR00WFjIb80tRx+7a/l0LEtYK6zRbtXUNOGKEPqysTSnXLVb0VmWPkaC/DUPk4ww==",
|
||||
"requires": {
|
||||
"decorate-component-with-props": "^1.0.2",
|
||||
"draft-js-plugins-editor": "^2.1.1",
|
||||
|
@ -4213,23 +4222,23 @@
|
|||
"integrity": "sha1-cqdAoQdFM4LijfnOXbtajfD5Zuw="
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4256,16 +4265,6 @@
|
|||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"requires": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
|
@ -5537,56 +5536,6 @@
|
|||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
},
|
||||
"extglob": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
|
||||
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
|
||||
"requires": {
|
||||
"array-unique": "^0.3.2",
|
||||
"define-property": "^1.0.0",
|
||||
"expand-brackets": "^2.1.4",
|
||||
"extend-shallow": "^2.0.1",
|
||||
"fragment-cache": "^0.2.1",
|
||||
"regex-not": "^1.0.0",
|
||||
"snapdragon": "^0.8.1",
|
||||
"to-regex": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
||||
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY="
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
||||
"requires": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"repeat-string": "^1.6.1",
|
||||
"to-regex-range": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5615,7 +5564,9 @@
|
|||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
|
@ -5943,19 +5894,19 @@
|
|||
}
|
||||
},
|
||||
"focus-trap": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-2.4.6.tgz",
|
||||
"integrity": "sha512-vWZTPtBU6pBoyWZDRZJHkXsyP2ZCZBHE3DRVXnSVdQKH/mcDtu9S5Kz8CUDyIqpfZfLEyI9rjKJLnc4Y40BRBg==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.3.0.tgz",
|
||||
"integrity": "sha512-BBzvFfkPg5PqrVVCdQ1YOIVNKGvqG9YNVkiAUQFuDM66N8J9uADhs6mlYKrd30ofDJIzEniBnBKM7GO45iCzKQ==",
|
||||
"requires": {
|
||||
"tabbable": "^1.0.3"
|
||||
"tabbable": "^5.1.5"
|
||||
}
|
||||
},
|
||||
"focus-trap-react": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-3.1.4.tgz",
|
||||
"integrity": "sha512-uqMKMg9Xlny0LKHW0HqA7ncLafW57SxgeedjE7/Xt+NB7sdOBUG4eD/9sMsq1O0+7zD3palpniTs2n0PDLc3uA==",
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-8.4.2.tgz",
|
||||
"integrity": "sha512-yuItOIwgriOBMrbHDqbWMpQjGVs9SbtugYrT0vs0yPjHiPKja3NZ9dBMxDQrV1JhyojGK5d6j7ayqBS7Kcm9xQ==",
|
||||
"requires": {
|
||||
"focus-trap": "^2.0.1"
|
||||
"focus-trap": "^6.3.0"
|
||||
}
|
||||
},
|
||||
"for-in": {
|
||||
|
@ -6016,10 +5967,15 @@
|
|||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"optional": true
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
|
@ -6363,17 +6319,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||
|
@ -6583,15 +6528,6 @@
|
|||
"ansi-wrap": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||
|
@ -7305,6 +7241,14 @@
|
|||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
|
||||
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
|
@ -7426,9 +7370,9 @@
|
|||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"dev": true
|
||||
},
|
||||
"interpret": {
|
||||
|
@ -8579,6 +8523,14 @@
|
|||
"micromatch": "^4.0.2",
|
||||
"sane": "^4.0.3",
|
||||
"walker": "^1.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-jasmine2": {
|
||||
|
@ -10657,9 +10609,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
|
@ -14822,9 +14776,9 @@
|
|||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
|
||||
},
|
||||
"tabbable": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-1.1.3.tgz",
|
||||
"integrity": "sha512-nOWwx35/JuDI4ONuF0ZTo6lYvI0fY0tZCH1ErzY2EXfu4az50ZyiUX8X073FLiZtmWUVlkRnuXsehjJgCw9tYg=="
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.2.0.tgz",
|
||||
"integrity": "sha512-0uyt8wbP0P3T4rrsfYg/5Rg3cIJ8Shl1RJ54QMqYxm1TLdWqJD1u6+RQjr2Lor3wmfT7JRHkirIwy99ydBsyPg=="
|
||||
},
|
||||
"table": {
|
||||
"version": "6.0.7",
|
||||
|
@ -15397,9 +15351,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.22",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
||||
"version": "0.7.28",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz",
|
||||
"integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g=="
|
||||
},
|
||||
"unc-path-regex": {
|
||||
"version": "0.1.2",
|
||||
|
|
|
@ -90,13 +90,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"core-js": "^2.5.3",
|
||||
"draft-js": "0.10.5",
|
||||
"draftail": "^1.3.0",
|
||||
"draft-js": "^0.10.5",
|
||||
"draftail": "^1.4.1",
|
||||
"draftjs-filters": "^2.5.0",
|
||||
"element-closest": "^2.0.2",
|
||||
"focus-trap-react": "^3.1.0",
|
||||
"immer": "^9.0.1",
|
||||
"focus-trap-react": "^8.4.2",
|
||||
"formdata-polyfill": "^3.0.20",
|
||||
"immer": "^9.0.1",
|
||||
"postcss-calc": "^7.0.5",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.14.0",
|
||||
|
|
|
@ -431,9 +431,9 @@ class FieldPanel(EditHandler):
|
|||
widget = kwargs.pop('widget', None)
|
||||
if widget is not None:
|
||||
self.widget = widget
|
||||
self.comments_enabled = not kwargs.pop('disable_comments', False)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.field_name = field_name
|
||||
self.comments_enabled = not kwargs.pop('disable_comments', False)
|
||||
|
||||
def clone_kwargs(self):
|
||||
kwargs = super().clone_kwargs()
|
||||
|
@ -936,6 +936,10 @@ Page.get_edit_handler = get_edit_handler
|
|||
|
||||
|
||||
class StreamFieldPanel(FieldPanel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
disable_comments = kwargs.pop('disable_comments', True)
|
||||
super().__init__(*args, **kwargs, disable_comments=disable_comments)
|
||||
|
||||
def classes(self):
|
||||
classes = super().classes()
|
||||
classes.append("stream-field")
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
{% load i18n %}
|
||||
|
||||
<fieldset>
|
||||
<legend>{{ self.heading }}</legend>
|
||||
<ul class="fields">
|
||||
<li>
|
||||
{% include "wagtailadmin/shared/field.html" with show_label=False show_help_text=False show_add_comment_button=False %}
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<div data-contentpath="{{ self.field_name }}">
|
||||
<fieldset>
|
||||
<legend>{{ self.heading }}</legend>
|
||||
<ul class="fields">
|
||||
<li>
|
||||
{% include "wagtailadmin/shared/field.html" with show_label=False show_help_text=False show_add_comment_button=False include_contentpath=False %}
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
||||
{% if show_add_comment_button %}
|
||||
<div class="field-comment-control" data-contentpath="{{ self.field_name }}">
|
||||
<button type="button" data-component="add-comment-button" class="button button-secondary button-small u-hidden" aria-label="{% trans 'Add comment' %}">
|
||||
<svg class="icon icon-comment-add initial" aria-hidden="true" focusable="false"><use href="#icon-comment-add"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if show_add_comment_button %}
|
||||
<div class="field-comment-control">
|
||||
<button type="button" data-component="add-comment-button" data-comment-add class="button button-secondary button-small u-hidden" aria-label="{% trans 'Add comment' %}">
|
||||
<svg class="icon icon-comment-add initial" aria-hidden="true" focusable="false"><use href="#icon-comment-add"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% load wagtailadmin_tags i18n %}
|
||||
<div class="field {{ field|fieldtype }} {{ field|widgettype }} {{ field_classes }}" data-contentpath="{{ field.name }}">
|
||||
<div class="field {{ field|fieldtype }} {{ field|widgettype }} {{ field_classes }}" {% if include_contentpath|default_if_none:True %}data-contentpath="{{ field.name }}"{% endif %}>
|
||||
{% if show_label|default_if_none:True %}{{ field.label_tag }}{% endif %}
|
||||
<div class="field-content">
|
||||
<div class="input {{ input_classes }} ">
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
{% if show_add_comment_button %}
|
||||
<div class="field-comment-control">
|
||||
<button type="button" data-component="add-comment-button" class="button button-secondary button-small u-hidden" aria-label="{% trans 'Add comment' %}">
|
||||
<button type="button" data-component="add-comment-button" data-comment-add class="button button-secondary button-small u-hidden" aria-label="{% trans 'Add comment' %}">
|
||||
<svg class="icon icon-comment-add initial" aria-hidden="true" focusable="false"><use href="#icon-comment-add"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
Ładowanie…
Reference in New Issue