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 tests
pull/7050/head
Jacob Topp-Mugglestone 2021-04-16 18:11:56 +01:00 zatwierdzone przez Matt Westcott
rodzic dd90405aac
commit d727b2b922
21 zmienionych plików z 575 dodań i 499 usunięć

Wyświetl plik

@ -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]]),

Wyświetl plik

@ -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
};
}

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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}

Wyświetl plik

@ -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);
}
}
});

Wyświetl plik

@ -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', () => {

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -101,6 +101,7 @@ describe('CommentableEditor', () => {
overlappingHighlight: '#00FF00',
focusedHighlight: '#000000',
}}
isCommentShortcut={() => false}
/>
</Provider>
);

Wyświetl plik

@ -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,

Wyświetl plik

@ -133,6 +133,7 @@ const initEditor = (selector, options, currentScript) => {
fieldNode={field.parentNode}
contentPath={contentPath}
colorConfig={colors}
isCommentShortcut={window.comments.isCommentShortcut}
{...sharedProps}
/>
</Provider>

Wyświetl plik

@ -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>
);
}

Wyświetl plik

@ -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>
`;

Wyświetl plik

@ -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');

Wyświetl plik

@ -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>"
`;

Wyświetl plik

@ -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,
};

194
package-lock.json wygenerowano
Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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")

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>