Messages play an important role in social interaction. The message function realized here refers to the way of wechat circle of friends:

Users send a TOPIC, and readers can comment on the TOPIC or the message under the TOPIC. But only two-tier tree comments are always shown.

Of course, it is also possible to show the structure of nested multi-level trees like digging gold. I think the nesting is too deep

The actual results are as follows:

Experience the site at jimmyarea.com.

The front-end implementation

The use of the technical

  • react

  • ant design

  • typescript

In the screenshot above, it is clear that there is a form design, plus a list presentation.

The form is designed using the ant Design framework’s form component:

<Form {... layout} form={form} name="basic"
  onFinish={onFinish}
  onFinishFailed={onFinishFailed}
>
  <Form.Item
    label="Theme"
    name="subject"
    rules={[
      { required: true.message:'Please enter your topic'}, {whitespace: true.message:'Input cannot be null'}, {min: 6.message:'The theme cannot be less than6'}, {max: 30.message:'The theme cannot be greater than30Characters'},]} >
    <Input maxLength={30} placeholder="Please enter your topic (minimum 6 characters, maximum 30 characters)" />
  </Form.Item>

  <Form.Item
    label="Content"
    name="content"
    rules={[
      { required: true.message:'Please enter your content'}, {whitespace: true.message:'Input cannot be null'}, {min: 30.message:'The content cannot be less than30Characters'},]} >
    <Input.TextArea
      placeholder="Please enter your content (min. 30 characters)"
      autoSize={{
        minRows: 6.maxRows: 12,}}showCount
      maxLength={300}
    />
  </Form.Item>
  <Form.Item {. tailLayout} >
    <Button
      type="primary"
      htmlType="submit"
      style={{ width: '100%' }}
      loading={loading}
      disabled={loading}
    >
      <CloudUploadOutlined />
      &nbsp;Submit
    </Button>
  </Form.Item>
</Form>
Copy the code

This limits the length of the entered topic name to 6-30; The content is 30-300 characters

For the display of comments, the List and Comment components of Ant Design are used here:

<List
  loading={loadingMsg}
  itemLayout="horizontal"
  pagination={{
    size: 'small'.total: count,
    showTotal: () = > ` altogether${count}Article `,
    pageSize,
    current: activePage,
    onChange: changePage,
  }}
  dataSource={list}
  renderItem={(item: any, index: any) = > (
    <List.Item actions={} [] key={index}>
      <List.Item.Meta
        avatar={
          <Avatar style={{ backgroundColor: '#1890ff' }}>{item.userId? .username? .slice(0, 1)? .toUpperCase()}</Avatar>
        }
        title={<b>{item.subject}</b>}
        description={
          <>{item.content} {/* Submessage */}<div
              style={{
                fontSize: '12px',
                marginTop: '8px',
                marginBottom: '16px',
                alignItems: 'center',
                display: 'flex',
                flexWrap: 'wrap',
                justifyContent: 'space-between'}} >
              <span>The user&nbsp;{item.userId? .username}&nbsp;&nbsp;Published in&nbsp;{moment(item.meta? .createAt).format('YYYY-MM-DD HH:mm:ss')}</span>
              <span>
                {item.canDel ? (
                  <a
                    style={{ color: 'red', fontSize: '12px', marginRight: '12px'}}onClick={()= > removeMsg(item)}
                  >
                    <DeleteOutlined />
                    &nbsp; Delete
                  </a>
                ) : null}
                <a
                  style={{ fontSize: '12px', marginRight: '12px'}}onClick={()= > replyMsg(item)}
                >
                  <MessageOutlined />
                  &nbsp; Reply
                </a>
              </span>
            </div>} {item.children && item.children. Length? (<>
                {item.children.map((innerItem: any, innerIndex: any) => (
                  <Comment
                    key={innerIndex}
                    author={<span>{innerItem.subject}</span>}
                    avatar={
                      <Avatar style={{ backgroundColor: '#1890ff' }}>{innerItem.userId? .username? .slice(0, 1)? .toUpperCase()}</Avatar>
                    }
                    content={<p>{innerItem.content}</p>}
                    datetime={
                      <Tooltip
                        title={moment(innerItem.meta? .createAt).format(
                          'YYYY-MM-DD HH:mm:ss')} >
                        <span>{moment(innerItem.meta? .createAt).fromNow()}</span>
                      </Tooltip>
                    }
                    actions={[
                      <>
                        {innerItem.canDel ? (
                          <a
                            style={{
                              color: 'red',
                              fontSize: '12px',
                              marginRight: '12px'}}onClick={()= > removeMsg(innerItem)}
                          >
                            <DeleteOutlined />
                            &nbsp; Delete
                          </a>
                        ) : null}
                      </>.<a
                        style={{ fontSize: '12px', marginRight: '12px'}}onClick={()= > replyMsg(innerItem)}
                      >
                        <MessageOutlined />
                        &nbsp; Reply
                      </a>,]}} / >))</>
            ) : null}

            {/* The reply form */}
            {replyObj._id === item._id || replyObj.pid === item._id ? (
              <div style={{ marginTop: '12px'}}ref={replyArea}>
                <Form
                  form={replyForm}
                  name="reply"
                  onFinish={onFinishReply}
                  onFinishFailed={onFinishFailed}
                >
                  <Form.Item
                    name="reply"
                    rules={[
                      { required: true.message:'Please enter your content'}, {whitespace: true.message:'Input cannot be null'}, {min: 2.message:'The content cannot be less than2Characters'},]} >
                    <Input.TextArea
                      placeholder={replyPlaceholder}
                      autoSize={{
                        minRows: 6.maxRows: 12,}}showCount
                      maxLength={300}
                    />
                  </Form.Item>

                  <Form.Item>
                    <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                      <Button
                        style={{ marginRight: '12px'}}onClick={()= > cancelReply()}
                      >
                        Dismiss
                      </Button>
                      <Button
                        type="primary"
                        htmlType="submit"
                        loading={innerLoading}
                        disabled={innerLoading}
                      >
                        Submit
                      </Button>
                    </div>
                  </Form.Item>
                </Form>
              </div>
            ) : null}
          </>
        }
      />
    </List.Item>
  )}
/>
Copy the code

Of course, if there are multiple levels of nested tree structures, you can just use the Comment component to make recursive calls

A list is a display of topics, messages, and sub-messages posted by the user. If you look through the code snippet above, you’ll see that there is a Form in it.

Yes, the Form is for messages, and its structure is just to remove the subject field input field from the subject message, but I will still use it in the actual parameter passing.

The complete front-end code can be viewed at the JimmyArea message (front end).

The back-end

Techniques used:

  • Mongodb database, here I use its ODM Mongoose

  • Koa2 A Node framework

  • Pm2 process guard

  • Apidoc is used to generate interface documentation (if you notice the experience site, there is a link to “Documentation” in the upper right corner, which is the content of the generated documentation)

The construction here will not be introduced, you can refer to koA2 official website with Baidu to solve ~

In fact, essentially or add delete change check operation.

First, we define the data structure schema we want to store:

const mongoose = require('mongoose')
const Schema = mongoose.Schema

// Define the message field
let MessageSchema = new Schema({
  // Associated field -- user ID
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  type: Number.// 1 is a message, 2 is a reply
  subject: String.// Message subject
  content: String.// Message content
  pid: { / / the parent id
    type: String.default: '1'
  },
  replyTargetId: { Return the target record ID, which is different from the parent PID
    type: String.default: '1'
  },
  meta: {
    createAt: {
      type: Date.default: Date.now()
    },
    updateAt: {
      type: Date.default: Date.now()
    }
  }
})

mongoose.model('Message', MessageSchema)
Copy the code

One interesting point here is the userId field, where I directly associate the registered user.

Now that you’ve set the fields, you can add, delete, change and check them.

Detailed CRUD code can be viewed at the JimmyArea message (backend).

The focus of this article is how to convert the topic and comments into a two-tier tree structure.

This is related to the PID field, that is, the ID of the parent node: the PID of the topic is -1, and the PID of the message under the topic is the record value of the topic. The following code:

let count = await Message.count({pid: '1'})
let data = await Message.find({pid: '1'})
                      .skip((current-1) * pageSize)
                      .limit(pageSize)
                      .sort({ 'meta.createAt': -1})
                      .populate({
                        path: 'userId'.select: 'username _id' // select: 'username -_id' -_id is to exclude the _id
                      })
                      .lean(true) // Add lean to JS JSON string

const pids = Array.isArray(data) ? data.map(i= > i._id) : [];
let resReply = []
if(pids.length) {
resReply = await Message.find({pid: {$in: pids}})
                               .sort({ 'meta.createAt': 1})
                               .populate({
                                path: 'userId'.select: 'username _id' // select: 'username -_id' -_id is to exclude the _id})}const list = data.map(item= > {
const children = JSON.parse(JSON.stringify(resReply.filter(i= > i.pid === item._id.toString()))) // Reference problems
const tranformChildren = children.map(innerItem= > ({
  ...innerItem,
  canDel: innerItem.userId && innerItem.userId._id.toString() === (user._id&&user._id.toString()) ? 1 : 0
}))
return {
  ...item,
  children: tranformChildren,
  canDel: item.userId && item.userId._id.toString() === (user._id&&user._id.toString()) ? 1 : 0}})if(list) {
  ctx.body = {
    results: list,
    current: 1,
    count
  }
  return
}
ctx.body = {
  code: 10002.msg: 'Failed to get message! '
}
Copy the code

At this point, you can leave a message with pleasure

The latter

  • More content can be found at Jimmy Github

  • Key codes for messages can be found in the Jimmy message function

  • To experience the message, go to jimmyarea.com