Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .scratch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
We want to have comments
Attached to RTF
Anchored to spec indices
Threading ?
We don't have persisting comment on changes

## Thoughts
Comment collection?
- we store the id of the content
- start and end index of anchor
- content of comment
- name of the field (this wouldn't be scalable)

Comments as linked content
- defining comment as separate content, and link to article or page
- links to list of comments

Comments part of an existing content
- defining comment data model within content (ie article or page)
- defining a wrapper to contain a list of comments.

- simliar to workflow, allow enabling comments for a content type
- commenting will be auto-attached for any RTF within the content
68 changes: 68 additions & 0 deletions pal-demo/collections/Comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { CollectionConfig } from '../../src/collections/config/types';

const Comments: CollectionConfig = {
slug: 'comments',
fields: [
{
name: 'content-id',
type: 'text',
index: true,
},
{
name: 'field',
type: 'text',
},
{
name: 'range',
type: 'group',
fields: [
{
name: 'anchor',
type: 'group',
fields: [
{
name: 'path',
type: 'array',
fields: [
{
name: 'index',
type: 'number',
},
],
},
{
name: 'offset',
type: 'number',
},
],
},
{
name: 'focus',
type: 'group',
fields: [
{
name: 'path',
type: 'array',
fields: [
{
name: 'index',
type: 'number',
},
],
},
{
name: 'offset',
type: 'number',
},
],
},
],
},
{
name: 'comment-content',
type: 'text',
},
],
};

export default Comments;
6 changes: 4 additions & 2 deletions pal-demo/payload.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Workflows from './globals/Workflows';

import { Logo } from './components/Logo';
import { Icon } from './components/Icon';
import Comments from './collections/Comments';

export default buildConfig({
// By default, Payload will boot up normally
Expand All @@ -15,6 +16,7 @@ export default buildConfig({
collections: [
Admin,
PublicUser,
Comments,
{
slug: 'pages',
fields: [
Expand All @@ -30,7 +32,7 @@ export default buildConfig({
workflow: true,
},
{
slug: 'articles',
slug: 'posts',
fields: [
{
name: 'title',
Expand All @@ -39,7 +41,7 @@ export default buildConfig({
},
{
name: 'body',
type: 'text',
type: 'richText',
required: true,
},
],
Expand Down
4 changes: 4 additions & 0 deletions src/admin/components/forms/RenderFields/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const RenderFields: React.FC<Props> = (props) => {
permissions,
readOnly: readOnlyOverride,
className,
addComment,
setIsEditingComment,
} = props;

const [hasRendered, setHasRendered] = useState(false);
Expand All @@ -30,6 +32,7 @@ const RenderFields: React.FC<Props> = (props) => {
const shouldRender = isIntersecting || isAboveViewport;



useEffect(() => {
if (shouldRender && !hasRendered) {
setHasRendered(true);
Expand Down Expand Up @@ -95,6 +98,7 @@ const RenderFields: React.FC<Props> = (props) => {
readOnly,
},
permissions: fieldPermissions,
addComment
}}
/>
);
Expand Down
26 changes: 25 additions & 1 deletion src/admin/components/forms/field-types/RichText/RichText.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
import isHotkey from 'is-hotkey';
import { createEditor, Transforms, Node, Element as SlateElement, Text, BaseEditor } from 'slate';
import { createEditor, Transforms, Node, Element as SlateElement, Text, BaseEditor, Range } from 'slate';
import { ReactEditor, Editable, withReact, Slate } from 'slate-react';
import { HistoryEditor, withHistory } from 'slate-history';
import { richText } from '../../../../../fields/validations';
Expand All @@ -24,6 +24,7 @@ import mergeCustomFunctions from './mergeCustomFunctions';
import withEnterBreakOut from './plugins/withEnterBreakOut';

import './index.scss';
import { useCommentsContext } from '../../../views/Comments/context';

const defaultElements: RichTextElement[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'indent', 'link', 'relationship', 'upload'];
const defaultLeaves: RichTextLeaf[] = ['bold', 'italic', 'underline', 'strikethrough', 'code'];
Expand Down Expand Up @@ -93,6 +94,16 @@ const RichText: React.FC<Props> = (props) => {
return <div {...attributes}>{children}</div>;
}, [enabledElements, path, props]);


const { setFieldName, setRange, setIsEditing: setIsEditingComment } = useCommentsContext();

const addComment = (fieldName: string) => (e) => {
e.preventDefault();
setFieldName(fieldName);
setIsEditingComment(true);
};


const renderLeaf = useCallback(({ attributes, children, leaf }) => {
const matchedLeafName = Object.keys(enabledLeaves).find((leafName) => leaf[leafName]);

Expand Down Expand Up @@ -329,6 +340,13 @@ const RichText: React.FC<Props> = (props) => {
}
});
}}
onSelect={() => {
const range = editor.selection;
if (!Range.isCollapsed(range)) {
setRange(range);
console.log(range);
}
}}
/>
</div>
</div>
Expand All @@ -337,6 +355,12 @@ const RichText: React.FC<Props> = (props) => {
value={value}
description={description}
/>
<button
type="button"
onClick={addComment(name)}
>
+ Comments
</button>
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/admin/components/forms/field-types/RichText/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RichTextField } from '../../../../../fields/config/types';

export type Props = Omit<RichTextField, 'type'> & {
path?: string
addComment: (name: string) => (evt: React.MouseEvent<HTMLButtonElement>) => void
}

export interface BlurSelectionEditor extends BaseEditor {
Expand Down
1 change: 1 addition & 0 deletions src/admin/components/views/Account/Default.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const DefaultAccount: React.FC<Props> = (props) => {
fieldSchema={fields}
/>
</div>
<h1>Comment</h1>
<ul className={`${baseClass}__meta`}>
<li className={`${baseClass}__api-url`}>
<span className={`${baseClass}__label`}>
Expand Down
87 changes: 87 additions & 0 deletions src/admin/components/views/Comments/context/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { useRouteMatch } from 'react-router-dom';
import queryString from 'qs';
import { Range } from 'slate';
import { requests } from '../../../../api';
import { useConfig } from '../../../utilities/Config';
import { Comment } from '../types';


type UpdateFn<T> = (t: T) => void

interface Context {
comments: Comment[]
range: Range | null
setRange: UpdateFn<Range | null>
isEditing: boolean
setIsEditing: UpdateFn<boolean>
fieldName: string
setFieldName: UpdateFn<string>
reloadComments: () => void
}

const CommentsContext = createContext({} as Context);

export const useCommentsContext = () => useContext(CommentsContext);

export const CommentsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isEditing, setIsEditing] = useState(false);
const [fieldName, setFieldName] = useState('');
const [comments, setComments] = useState<Comment[]>([]);
const [range, setRange] = useState<Range | null>(null);
const [isError, setIsError] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const { serverURL, routes: { api } } = useConfig();
const { params: { id } = {} } = useRouteMatch<Record<string, string>>();


const reloadComments = useCallback(async () => {
const commentQuery = {
'content-id': {
equals: id,
},
};
const url = `${serverURL}${api}/comments`;
const search = queryString.stringify({
'fallback-locale': 'null', depth: 0, draft: 'true', where: commentQuery,
});
try {
const response = await requests.get(`${url}?${search}`);

if (response.status > 201) {
setIsError(true);
}

const json = await response.json();
setComments(json.docs ?? []);

console.log(json);
setIsLoading(false);
} catch (error) {
console.log(error);
setIsError(true);
setIsLoading(false);
}

console.log(api, serverURL, commentQuery);
}, [api, serverURL, id]);

console.log(comments);

return (
<CommentsContext.Provider value={{
comments,
range,
setRange,
isEditing,
setIsEditing,
fieldName,
setFieldName,
reloadComments,
}}
>
{children}
</CommentsContext.Provider>
);
};
Loading