kopia lustrzana https://github.com/wagtail/wagtail
Update comment list positioning so that they slide in/out with the sidebar (#8345)
rodzic
167471b1f7
commit
7f297cd019
|
@ -41,6 +41,7 @@ Changelog
|
|||
* Revise alignment and spacing of form fields and sections (Thibaud Colas)
|
||||
* Update Wagtail’s type scale so StreamField block labels and field labels are the same size (Thibaud Colas)
|
||||
* Allow customising the `search_fields` and search backend via SnippetViewSet (Sage Abdullah)
|
||||
* Style comments as per page editor design, in side panel (Karl Hobley, Thibaud Colas)
|
||||
* Fix: Ensure `label_format` on StructBlock gracefully handles missing variables (Aadi jindal)
|
||||
* Fix: Adopt a no-JavaScript and more accessible solution for the 'Reset to default' switch to Gravatar when editing user profile (Loveth Omokaro)
|
||||
* Fix: Ensure `Site.get_site_root_paths` works on cache backends that do not preserve Python objects (Jaap Roes)
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
|
||||
&__close-button {
|
||||
@apply w-text-primary w-absolute w-left-3 w-top-3 hover:w-text-primary-200 w-bg-white w-p-3 w-hidden w-transition;
|
||||
@apply w-text-primary w-absolute w-left-3 w-top-3 hover:w-text-primary-200 w-bg-white w-p-3 w-transition;
|
||||
|
||||
.icon {
|
||||
@apply w-w-4 w-h-4;
|
||||
|
@ -93,10 +93,6 @@
|
|||
@apply w-w-0 w-h-0 w-opacity-0 w-absolute w-pointer-events-none;
|
||||
}
|
||||
|
||||
&--open .form-side__close-button {
|
||||
@apply w-block;
|
||||
}
|
||||
|
||||
&__panel {
|
||||
@apply w-px-5 xl:w-px-10 w-py-4 w-w-full w-h-full w-overflow-y-auto w-scrollbar-thin;
|
||||
}
|
||||
|
|
|
@ -411,7 +411,7 @@ export default class CommentComponent extends React.Component<CommentProps> {
|
|||
{gettext('Are you sure?')}
|
||||
<button
|
||||
type="button"
|
||||
className="comment__button"
|
||||
className="comment__button button button-small"
|
||||
onClick={onClickCancel}
|
||||
>
|
||||
{gettext('Cancel')}
|
||||
|
|
|
@ -1,43 +1,23 @@
|
|||
.comment {
|
||||
@include box;
|
||||
|
||||
width: calc(100vw - 40px);
|
||||
max-width: calc(100vw - 19%);
|
||||
width: 300px;
|
||||
display: block;
|
||||
transition: top 0.5s ease 0s, inset-inline-end 0.5s ease 0s,
|
||||
height 0.5s ease 0s;
|
||||
pointer-events: auto;
|
||||
padding-bottom: 0;
|
||||
inset-inline-end: -2000px;
|
||||
background-color: theme('colors.white.DEFAULT');
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
width: calc(100vw - 40px);
|
||||
max-width: 400px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
max-width: 200px;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
max-width: 275px;
|
||||
}
|
||||
inset-inline-end: 0;
|
||||
|
||||
&--focused {
|
||||
inset-inline-end: 35px;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
inset-inline-end: 50px;
|
||||
}
|
||||
inset-inline-end: 30px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: $color-box-text;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
margin-bottom: 0;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
@ -53,11 +33,11 @@
|
|||
|
||||
&--mode-creating form {
|
||||
border-top: 0;
|
||||
margin-top: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&--mode-editing form {
|
||||
margin-top: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&--mode-deleting &__text {
|
||||
|
@ -76,13 +56,13 @@
|
|||
|
||||
&__actions,
|
||||
&__reply-actions {
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
&__actions &__button,
|
||||
&__reply-actions &__button {
|
||||
margin-inline-end: 10px;
|
||||
margin-top: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&__confirm-delete &__button {
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
); // Leave room for actions to the right and avatar to the left
|
||||
margin: 0;
|
||||
margin-inline-start: 45px;
|
||||
font-size: 11px;
|
||||
line-height: 15px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 130%;
|
||||
color: theme('colors.primary.DEFAULT');
|
||||
}
|
||||
|
||||
&__date {
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
&__text {
|
||||
color: $color-box-text;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
margin-bottom: 0;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
@import '../../../scss/settings/variables';
|
||||
|
||||
$color-comment-separator: theme('colors.grey.100');
|
||||
$color-comment-separator: theme('colors.grey.200');
|
||||
|
||||
$color-box-background: $color-white;
|
||||
$color-box-border: $color-grey-3;
|
||||
$color-box-border-focused: $color-grey-2;
|
||||
$color-box-background-focused: theme('colors.grey.50');
|
||||
$color-box-border: theme('colors.grey.150');
|
||||
$color-box-border-focused: theme('colors.grey.200');
|
||||
$color-box-text: $color-black;
|
||||
$color-textarea-background: theme('colors.grey.50');
|
||||
$color-textarea-border: $color-input-border;
|
||||
$color-textarea-placeholder-text: $color-grey-2;
|
||||
$box-border-radius: 5px;
|
||||
$box-padding: 10px;
|
||||
$box-padding: 20px;
|
||||
|
||||
@mixin focus-outline {
|
||||
outline: $color-focus-outline solid 3px;
|
||||
|
@ -18,28 +16,28 @@ $box-padding: 10px;
|
|||
|
||||
@mixin box {
|
||||
background-color: $color-box-background;
|
||||
border: 1px solid $color-box-border;
|
||||
padding: $box-padding;
|
||||
font-size: 11px;
|
||||
border-radius: $box-border-radius;
|
||||
color: $color-box-text;
|
||||
border: 1px solid $color-box-border;
|
||||
|
||||
&--focused {
|
||||
background-color: $color-box-background-focused;
|
||||
border: 1px solid $color-box-border-focused;
|
||||
box-shadow: 3px 2px 3px -1px theme('colors.black-10');
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: $font-sans;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
background-color: $color-textarea-background;
|
||||
border: 1px solid $color-textarea-border;
|
||||
background-color: $color-white;
|
||||
border: 1px solid $color-input-border;
|
||||
border-radius: 5px;
|
||||
color: $color-box-text;
|
||||
|
||||
&::placeholder {
|
||||
color: $color-textarea-placeholder-text;
|
||||
color: theme('colors.grey.400');
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
@ -89,12 +87,10 @@ $box-padding: 10px;
|
|||
border-radius: 3px;
|
||||
color: $color-teal;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
height: 25px;
|
||||
padding-inline-start: 5px;
|
||||
padding-inline-end: 5px;
|
||||
font-weight: 700;
|
||||
height: 30px;
|
||||
padding-inline-start: 10px;
|
||||
padding-inline-end: 10px;
|
||||
|
||||
&--primary {
|
||||
color: $color-white;
|
||||
|
@ -119,13 +115,10 @@ $box-padding: 10px;
|
|||
}
|
||||
|
||||
.comments-list {
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
inset-inline-end: 35px;
|
||||
z-index: 95;
|
||||
font-family: $font-sans;
|
||||
pointer-events: none;
|
||||
top: 20px;
|
||||
inset-inline-end: 20px;
|
||||
z-index: calc(theme('zIndex.header') + 5);
|
||||
}
|
||||
|
||||
// stylelint-disable no-invalid-position-at-import-rule
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createStore } from 'redux';
|
||||
|
||||
|
@ -21,7 +21,6 @@ import {
|
|||
selectComments,
|
||||
selectCommentsForContentPathFactory,
|
||||
selectCommentFactory,
|
||||
selectEnabled,
|
||||
selectFocused,
|
||||
selectIsDirty,
|
||||
selectCommentCount,
|
||||
|
@ -70,17 +69,59 @@ const getAuthor = (
|
|||
};
|
||||
};
|
||||
|
||||
function renderCommentsUi(
|
||||
store: Store,
|
||||
layout: LayoutController,
|
||||
comments: Comment[],
|
||||
): React.ReactElement {
|
||||
interface CommentListingProps {
|
||||
store: Store;
|
||||
layout: LayoutController;
|
||||
comments: Comment[];
|
||||
}
|
||||
|
||||
function CommentListing({
|
||||
store,
|
||||
layout,
|
||||
comments,
|
||||
}: CommentListingProps): React.ReactElement {
|
||||
const state = store.getState();
|
||||
const { commentsEnabled, user, currentTab } = state.settings;
|
||||
const { user, currentTab } = state.settings;
|
||||
const { focusedComment, forceFocus } = state.comments;
|
||||
const commentsListRef = React.useRef<HTMLOListElement | null>(null);
|
||||
// Update the position of the comments listing as the window scrolls to keep the comments in line with the content
|
||||
const updateScroll = useCallback(
|
||||
(e: Event) => {
|
||||
if (!commentsListRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
e.type === 'scroll' &&
|
||||
!document.querySelector('.form-side--comments')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollContainer = document.querySelector('.content');
|
||||
const top = scrollContainer?.getBoundingClientRect().top;
|
||||
commentsListRef.current.style.top = `${top}px`;
|
||||
},
|
||||
[commentsListRef],
|
||||
);
|
||||
let commentsToRender = comments;
|
||||
|
||||
if (!commentsEnabled || !user) {
|
||||
React.useEffect(() => {
|
||||
const root = document.querySelector('#main');
|
||||
const commentSidePanel = document.querySelector(
|
||||
'[data-side-panel="comments"]',
|
||||
);
|
||||
|
||||
root?.addEventListener('scroll', updateScroll);
|
||||
commentSidePanel?.addEventListener('show', updateScroll);
|
||||
|
||||
return () => {
|
||||
root?.removeEventListener('scroll', updateScroll);
|
||||
commentSidePanel?.removeEventListener('show', updateScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!user) {
|
||||
commentsToRender = [];
|
||||
}
|
||||
// Hide all resolved/deleted comments
|
||||
|
@ -99,7 +140,11 @@ function renderCommentsUi(
|
|||
isVisible={layout.getCommentVisible(currentTab, comment.localId)}
|
||||
/>
|
||||
));
|
||||
return <ol className="comments-list">{commentsRendered}</ol>;
|
||||
return (
|
||||
<ol ref={commentsListRef} className="comments-list">
|
||||
{commentsRendered}
|
||||
</ol>
|
||||
);
|
||||
/* eslint-enable react/no-danger */
|
||||
}
|
||||
|
||||
|
@ -115,13 +160,13 @@ export class CommentApp {
|
|||
|
||||
selectors = {
|
||||
selectComments,
|
||||
selectEnabled,
|
||||
selectFocused,
|
||||
selectIsDirty,
|
||||
selectCommentCount,
|
||||
};
|
||||
|
||||
actions = commentActionFunctions;
|
||||
activationHandlers: (() => void)[] = [];
|
||||
|
||||
constructor() {
|
||||
this.store = createStore(reducer, {
|
||||
|
@ -189,12 +234,12 @@ export class CommentApp {
|
|||
return commentId;
|
||||
}
|
||||
|
||||
setVisible(visible: boolean) {
|
||||
this.store.dispatch(
|
||||
updateGlobalSettings({
|
||||
commentsEnabled: visible,
|
||||
}),
|
||||
);
|
||||
activate() {
|
||||
this.activationHandlers.forEach((handler) => handler());
|
||||
}
|
||||
|
||||
onActivate(handler: () => void) {
|
||||
this.activationHandlers.push(handler);
|
||||
}
|
||||
|
||||
invalidateContentPath(contentPath: string) {
|
||||
|
@ -255,7 +300,11 @@ export class CommentApp {
|
|||
}
|
||||
|
||||
ReactDOM.render(
|
||||
renderCommentsUi(this.store, this.layout, commentList),
|
||||
<CommentListing
|
||||
store={this.store}
|
||||
layout={this.layout}
|
||||
comments={commentList}
|
||||
/>,
|
||||
element,
|
||||
() => {
|
||||
// Render again if layout has changed (eg, a comment was added, deleted or resized)
|
||||
|
@ -263,7 +312,11 @@ export class CommentApp {
|
|||
this.layout.refreshDesiredPositions(state.settings.currentTab);
|
||||
if (this.layout.refreshLayout()) {
|
||||
ReactDOM.render(
|
||||
renderCommentsUi(this.store, this.layout, commentList),
|
||||
<CommentListing
|
||||
store={this.store}
|
||||
layout={this.layout}
|
||||
comments={commentList}
|
||||
/>,
|
||||
element,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ export function selectCommentFactory(localId: number) {
|
|||
});
|
||||
}
|
||||
|
||||
export const selectEnabled = (state: State) => state.settings.commentsEnabled;
|
||||
|
||||
export const selectIsDirty = createSelector(
|
||||
selectComments,
|
||||
selectRemoteCommentCount,
|
||||
|
|
|
@ -5,7 +5,6 @@ import { update } from './utils';
|
|||
|
||||
export interface SettingsState {
|
||||
user: Author | null;
|
||||
commentsEnabled: boolean;
|
||||
currentTab: string | null;
|
||||
}
|
||||
|
||||
|
@ -14,7 +13,6 @@ export type SettingsStateUpdate = Partial<SettingsState>;
|
|||
// Reducer with initial state
|
||||
export const INITIAL_STATE: SettingsState = {
|
||||
user: null,
|
||||
commentsEnabled: false,
|
||||
currentTab: null,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
.Draftail-CommentControl {
|
||||
display: none;
|
||||
|
||||
.tab-content--comments-enabled & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.Draftail-CommentControl .Draftail-ToolbarButton {
|
||||
color: theme('colors.secondary.100');
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { createEditorStateFromRaw } from 'draftail';
|
|||
import { DraftInlineStyleType, EditorState, SelectionState } from 'draft-js';
|
||||
|
||||
import { CommentApp } from '../../CommentApp/main';
|
||||
import { updateGlobalSettings } from '../../CommentApp/actions/settings';
|
||||
import { newComment } from '../../CommentApp/state/comments';
|
||||
import { noop } from '../../../utils/noop';
|
||||
|
||||
|
@ -137,20 +136,12 @@ describe('CommentableEditor', () => {
|
|||
commentApp = new CommentApp();
|
||||
});
|
||||
it('has control', () => {
|
||||
commentApp.setVisible(true);
|
||||
const editor = mount(getEditorComponent(commentApp));
|
||||
const controls = editor.find('DraftailEditor').prop('controls');
|
||||
expect(controls).toHaveLength(1);
|
||||
expect(controls[0].inline).toBeTruthy();
|
||||
editor.unmount();
|
||||
});
|
||||
it('has no control when comments disabled', () => {
|
||||
commentApp.store.dispatch(updateGlobalSettings({ commentsEnabled: false }));
|
||||
const editor = mount(getEditorComponent(commentApp));
|
||||
const controls = editor.find('DraftailEditor').prop('controls');
|
||||
expect(controls).toHaveLength(0);
|
||||
editor.unmount();
|
||||
});
|
||||
it('can update comment positions', () => {
|
||||
commentApp.store.dispatch(
|
||||
commentApp.actions.addComment(
|
||||
|
|
|
@ -335,6 +335,9 @@ function getCommentControl(
|
|||
</>
|
||||
}
|
||||
onClick={() => {
|
||||
// Open the comments side panel
|
||||
commentApp.activate();
|
||||
|
||||
onChange(
|
||||
addNewComment(getEditorState(), fieldNode, commentApp, contentPath),
|
||||
);
|
||||
|
@ -467,7 +470,6 @@ function getCommentDecorator(commentApp: CommentApp) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const enabled = useSelector(commentApp.selectors.selectEnabled);
|
||||
const blockKey: BlockKey = children[0].props.block.getKey();
|
||||
const start: number = children[0].props.start;
|
||||
|
||||
|
@ -490,10 +492,6 @@ function getCommentDecorator(commentApp: CommentApp) {
|
|||
return undefined; // eslint demands an explicit return here
|
||||
}, [commentId, annotationNode, blockKey]);
|
||||
|
||||
if (!enabled) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const onClick = () => {
|
||||
// Ensure the comment will appear alongside the current block
|
||||
if (!commentId) {
|
||||
|
@ -668,7 +666,6 @@ function CommentableEditor({
|
|||
[commentApp],
|
||||
);
|
||||
const comments = useSelector(commentsSelector, shallowEqual);
|
||||
const enabled = useSelector(commentApp.selectors.selectEnabled);
|
||||
const focusedId = useSelector(commentApp.selectors.selectFocused);
|
||||
|
||||
const ids = useMemo(
|
||||
|
@ -688,7 +685,6 @@ function CommentableEditor({
|
|||
|
||||
const previousFocused = usePrevious(focusedId);
|
||||
const previousIds = usePrevious(ids);
|
||||
const previousEnabled = usePrevious(enabled);
|
||||
useEffect(() => {
|
||||
// Only trigger a focus-related rerender if the current focused comment is inside the field, or the previous one was
|
||||
const validFocusChange =
|
||||
|
@ -700,7 +696,6 @@ function CommentableEditor({
|
|||
|
||||
if (
|
||||
!validFocusChange &&
|
||||
previousEnabled === enabled &&
|
||||
(previousIds === ids ||
|
||||
(previousIds.length === ids.length &&
|
||||
previousIds.every((value, index) => value === ids[index])))
|
||||
|
@ -731,7 +726,7 @@ function CommentableEditor({
|
|||
),
|
||||
);
|
||||
setUniqueStyleId((id) => (id + 1) % 200);
|
||||
}, [focusedId, enabled, inlineStyles, ids, editorState]);
|
||||
}, [focusedId, inlineStyles, ids, editorState]);
|
||||
|
||||
useEffect(() => {
|
||||
// if there are any comments without annotations, we need to add them to the EditorState
|
||||
|
@ -806,9 +801,7 @@ function CommentableEditor({
|
|||
setEditorState(newEditorState);
|
||||
}}
|
||||
editorState={editorState}
|
||||
controls={
|
||||
enabled ? controls.concat([{ inline: CommentControl }]) : controls
|
||||
}
|
||||
controls={controls.concat([{ inline: CommentControl }])}
|
||||
inlineStyles={inlineStyles.concat(commentStyles)}
|
||||
plugins={plugins.concat([
|
||||
{
|
||||
|
@ -840,7 +833,10 @@ function CommentableEditor({
|
|||
handleArrowAtContentEnd(getEditorState(), setEditorState, 'RTL');
|
||||
},
|
||||
handleKeyCommand: (command: string, state: EditorState) => {
|
||||
if (enabled && command === 'comment') {
|
||||
if (command === 'comment') {
|
||||
// Open the comments side panel
|
||||
commentApp.activate();
|
||||
|
||||
const selection = state.getSelection();
|
||||
const content = state.getCurrentContent();
|
||||
if (selection.isCollapsed()) {
|
||||
|
@ -869,9 +865,6 @@ function CommentableEditor({
|
|||
return 'not-handled';
|
||||
},
|
||||
customStyleFn: (styleSet: DraftInlineStyle) => {
|
||||
if (!enabled) {
|
||||
return undefined;
|
||||
}
|
||||
// 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,
|
||||
// this casting should be removed
|
||||
|
|
|
@ -56,18 +56,15 @@ window.comments = (() => {
|
|||
* @param {number} localId - the localId of the comment to subscribe to
|
||||
*/
|
||||
subscribeToUpdates(localId) {
|
||||
const { selectFocused, selectEnabled } = commentApp.selectors;
|
||||
const { selectFocused } = commentApp.selectors;
|
||||
const selectComment = commentApp.utils.selectCommentFactory(localId);
|
||||
const store = commentApp.store;
|
||||
const initialState = store.getState();
|
||||
let focused = selectFocused(initialState) === localId;
|
||||
let shown = selectEnabled(initialState);
|
||||
if (focused) {
|
||||
this.onFocus();
|
||||
}
|
||||
if (shown) {
|
||||
this.show();
|
||||
}
|
||||
this.show();
|
||||
this.unsubscribe = store.subscribe(() => {
|
||||
const state = store.getState();
|
||||
const comment = selectComment(state);
|
||||
|
@ -83,14 +80,6 @@ window.comments = (() => {
|
|||
}
|
||||
focused = nowFocused;
|
||||
}
|
||||
if (shown !== selectEnabled(state)) {
|
||||
if (shown) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
shown = selectEnabled(state);
|
||||
}
|
||||
});
|
||||
this.setOnClickHandler(localId);
|
||||
}
|
||||
|
@ -125,6 +114,9 @@ window.comments = (() => {
|
|||
|
||||
setOnClickHandler(localId) {
|
||||
this.node.addEventListener('click', () => {
|
||||
// Open the comments side panel
|
||||
commentApp.activate();
|
||||
|
||||
commentApp.store.dispatch(
|
||||
commentApp.actions.setFocusedComment(localId, {
|
||||
updatePinnedComment: true,
|
||||
|
@ -163,7 +155,6 @@ window.comments = (() => {
|
|||
throw new MissingElementError(annotationTemplateNode);
|
||||
}
|
||||
this.annotationTemplateNode = annotationTemplateNode;
|
||||
this.updateVisibility(false);
|
||||
}
|
||||
|
||||
register() {
|
||||
|
@ -171,19 +162,14 @@ window.comments = (() => {
|
|||
// The widget has no valid contentpath, skip subscriptions
|
||||
return undefined;
|
||||
}
|
||||
const { selectEnabled } = commentApp.selectors;
|
||||
const initialState = commentApp.store.getState();
|
||||
let currentlyEnabled = selectEnabled(initialState);
|
||||
const selectCommentsForContentPath =
|
||||
commentApp.utils.selectCommentsForContentPathFactory(this.contentpath);
|
||||
let currentComments = selectCommentsForContentPath(initialState);
|
||||
this.updateVisibility(currentComments.length === 0 && currentlyEnabled);
|
||||
const unsubscribeWidget = commentApp.store.subscribe(() => {
|
||||
const state = commentApp.store.getState();
|
||||
const newComments = selectCommentsForContentPath(state);
|
||||
const newEnabled = selectEnabled(state);
|
||||
const commentsChanged = currentComments !== newComments;
|
||||
const enabledChanged = currentlyEnabled !== newEnabled;
|
||||
if (commentsChanged) {
|
||||
// Add annotations for any new comments
|
||||
currentComments = newComments;
|
||||
|
@ -195,14 +181,6 @@ window.comments = (() => {
|
|||
annotation.subscribeToUpdates(comment.localId);
|
||||
});
|
||||
}
|
||||
if (enabledChanged || commentsChanged) {
|
||||
// If comments have been enabled or disabled, or the comments have changed
|
||||
// check whether to show the widget (if comments are enabled and there are no existing comments)
|
||||
currentlyEnabled = newEnabled;
|
||||
this.updateVisibility(
|
||||
currentComments.length === 0 && currentlyEnabled,
|
||||
);
|
||||
}
|
||||
});
|
||||
initialState.comments.comments.forEach((comment) => {
|
||||
// Add annotations for any comments already in the store
|
||||
|
@ -218,11 +196,14 @@ window.comments = (() => {
|
|||
annotation.subscribeToUpdates(localId);
|
||||
};
|
||||
this.commentAdditionNode.addEventListener('click', () => {
|
||||
// Open the comments side panel
|
||||
commentApp.activate();
|
||||
|
||||
// Make the widget button clickable to add a comment
|
||||
addComment();
|
||||
});
|
||||
this.fieldNode.addEventListener('keyup', (e) => {
|
||||
if (currentlyEnabled && isCommentShortcut(e)) {
|
||||
if (isCommentShortcut(e)) {
|
||||
if (currentComments.length === 0) {
|
||||
addComment();
|
||||
} else {
|
||||
|
@ -239,19 +220,6 @@ window.comments = (() => {
|
|||
return unsubscribeWidget; // TODO: listen for widget deletion and use this
|
||||
}
|
||||
|
||||
updateVisibility(newShown) {
|
||||
if (newShown === this.shown) {
|
||||
return;
|
||||
}
|
||||
this.shown = newShown;
|
||||
|
||||
if (!this.shown) {
|
||||
this.commentAdditionNode.classList.add('u-hidden');
|
||||
} else {
|
||||
this.commentAdditionNode.classList.remove('u-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
getAnnotationForComment() {
|
||||
const annotationNode = this.annotationTemplateNode.cloneNode(true);
|
||||
annotationNode.id = '';
|
||||
|
@ -276,38 +244,13 @@ window.comments = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
function onNextEnable(fn) {
|
||||
// Run a function once, when comments are enabled
|
||||
const { selectEnabled } = commentApp.selectors;
|
||||
const getEnabled = () => selectEnabled(commentApp.store.getState());
|
||||
let enabled = getEnabled();
|
||||
if (enabled) {
|
||||
// If we're starting off enabled, run the function immediately
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
const unsubscribe = commentApp.store.subscribe(() => {
|
||||
// Otherwise, subscribe to updates and run the function when comments change to enabled
|
||||
const newEnabled = getEnabled();
|
||||
if (newEnabled && !enabled) {
|
||||
enabled = newEnabled;
|
||||
unsubscribe();
|
||||
fn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initAddCommentButton(buttonElement) {
|
||||
const initWidget = () => {
|
||||
const widget = new FieldLevelCommentWidget({
|
||||
fieldNode: buttonElement.closest('[data-contentpath]'),
|
||||
commentAdditionNode: buttonElement,
|
||||
annotationTemplateNode: document.querySelector('#comment-icon'),
|
||||
});
|
||||
widget.register();
|
||||
};
|
||||
// Our template node may not exist yet - let's hold off until comments are loaded and enabled
|
||||
onNextEnable(initWidget);
|
||||
const widget = new FieldLevelCommentWidget({
|
||||
fieldNode: buttonElement.closest('[data-contentpath]'),
|
||||
commentAdditionNode: buttonElement,
|
||||
annotationTemplateNode: document.querySelector('#comment-icon'),
|
||||
});
|
||||
widget.register();
|
||||
}
|
||||
|
||||
function initCommentsInterface(formElement) {
|
||||
|
@ -348,42 +291,21 @@ window.comments = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
// Show/hide comments when the side panel is opened/closed
|
||||
const commentsSidePanel = document.querySelector(
|
||||
'[data-side-panel="comments"]',
|
||||
);
|
||||
// Show comments app
|
||||
const commentNotifications = formElement.querySelector(
|
||||
'[data-comment-notifications]',
|
||||
);
|
||||
commentNotifications.hidden = false;
|
||||
const tabContentElement = formElement.querySelector('.tab-content');
|
||||
tabContentElement.classList.add('tab-content--comments-enabled');
|
||||
|
||||
const updateCommentVisibility = (visible) => {
|
||||
// Show/hide comments
|
||||
commentApp.setVisible(visible);
|
||||
|
||||
// Add/Remove tab-nav--comments-enabled class. This changes the size of streamfields
|
||||
if (visible) {
|
||||
tabContentElement.classList.add('tab-content--comments-enabled');
|
||||
if (commentNotifications) {
|
||||
commentNotifications.hidden = false;
|
||||
}
|
||||
} else {
|
||||
tabContentElement.classList.remove('tab-content--comments-enabled');
|
||||
if (commentNotifications) {
|
||||
commentNotifications.hidden = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (commentsSidePanel) {
|
||||
commentsSidePanel.addEventListener('show', () => {
|
||||
updateCommentVisibility(true);
|
||||
});
|
||||
|
||||
commentsSidePanel.addEventListener('hide', () => {
|
||||
updateCommentVisibility(false);
|
||||
});
|
||||
}
|
||||
// Open the comments panel whenever the comment app is activated by a user clicking on an "Add comment" widget on the form.
|
||||
const commentSidePanel = document.querySelector(
|
||||
'[data-side-panel="comments"]',
|
||||
);
|
||||
commentApp.onActivate(() => {
|
||||
commentSidePanel.dispatchEvent(new Event('open'));
|
||||
});
|
||||
|
||||
// Keep number of comments up to date with comment app
|
||||
const commentToggle = document.querySelector(
|
||||
|
|
|
@ -43,14 +43,7 @@ export default function initSidePanel() {
|
|||
if (panelName && !selectedPanel) return;
|
||||
|
||||
// Open / close side panel
|
||||
|
||||
// HACK: For now, the comments will show without the side-panel opening.
|
||||
// They will later be updated so that they render inside the side panel.
|
||||
// We couldn't implement this for Wagtail 3.0 as the existing field styling
|
||||
// renders the "Add comment" button on the right hand side, and this gets
|
||||
// covered up by the side panel.
|
||||
|
||||
if (panelName === '' || panelName === 'comments') {
|
||||
if (panelName === '') {
|
||||
sidePanelWrapper.classList.remove('form-side--open');
|
||||
sidePanelWrapper.removeAttribute('aria-labelledby');
|
||||
} else {
|
||||
|
@ -129,6 +122,14 @@ export default function initSidePanel() {
|
|||
}
|
||||
};
|
||||
|
||||
// Open the side panel if the 'open' custom event is triggered on the side panel
|
||||
// This is allows panels to be opened with JavaScript without hacking the toggle
|
||||
document.querySelectorAll('[data-side-panel]').forEach((panel) => {
|
||||
panel.addEventListener('open', () => {
|
||||
setPanel(panel.dataset.sidePanel);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-side-panel-toggle]').forEach((toggle) => {
|
||||
toggle.addEventListener('click', () => {
|
||||
togglePanel(toggle.dataset.sidePanelToggle);
|
||||
|
|
|
@ -80,6 +80,7 @@ Those improvements were implemented by Albina Starykova as part of an [Outreachy
|
|||
* Revise alignment and spacing of form fields and sections (Thibaud Colas)
|
||||
* Update Wagtail’s type scale so StreamField block labels and field labels are the same size (Thibaud Colas)
|
||||
* Allow customising the `search_fields` and search backend via SnippetViewSet (Sage Abdullah)
|
||||
* Style comments as per page editor design, in side panel (Karl Hobley, Thibaud Colas)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
{% block titletag %}{% blocktrans trimmed with page_type=content_type.model_class.get_verbose_name %}New {{ page_type }}{% endblocktrans %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="comments"></div>
|
||||
|
||||
<div class="w-sticky w-top-0 w-z-header">
|
||||
{% include 'wagtailadmin/shared/headers/page_create_header.html' %}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
{% block bodyclass %}{% if page.live %}page-is-live{% endif %} {% if page_locked %}content-locked{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="comments"></div>
|
||||
{% page_permissions page as page_perms %}
|
||||
|
||||
<div class="w-sticky w-top-0 w-z-header">
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% if show_add_comment_button %}
|
||||
<button class="w-field__comment-button w-field__comment-button--add u-hidden" type="button" data-component="add-comment-button" data-comment-add aria-label="{% trans 'Add comment' %}" {% if label_for %}aria-describedby="{{ label_id }}"{% endif %}>
|
||||
<button class="w-field__comment-button w-field__comment-button--add" type="button" data-component="add-comment-button" data-comment-add aria-label="{% trans 'Add comment' %}" {% if label_for %}aria-describedby="{{ label_id }}"{% endif %}>
|
||||
{% icon name="comment-add" %}
|
||||
{% icon name="comment-add-reversed" %}
|
||||
</button>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<div id="comments"></div>
|
Ładowanie…
Reference in New Issue