Skip to content

Commit

Permalink
Merge branch 'f24' into f24
Browse files Browse the repository at this point in the history
  • Loading branch information
Tasnim1147 authored Oct 25, 2024
2 parents 7fa7945 + d523bea commit d5b0149
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 19 deletions.
75 changes: 75 additions & 0 deletions UserGuide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# User Guide for additional NodeBB Features

## Introduction
This document provides an outline of the new features implemented in the NodeBB platform. The following functionalities have been added to enhance user experience for both instructors and students:

1. Marking answers as "Instructor-Approved."
2. Sharing posts anonymously.
3. Displaying estimated reading time for each post.

## Feature 1: Marking Answers as "Instructor-Approved"

### Description
Instructors can now mark specific answers as "Instructor-Approved." This feature helps students easily identify which responses are accurate and verified by their instructors.

### How to Use
1. **For Instructors:**
Log in using the following:
- Username: Instructor
- Password: password
- Navigate to the post containing the answers.
- Hover over the answer you want to approve.
- toggle on the **"Mark as Approved"** button that appears.
- The answer will now be highlighted, indicating that it has been approved by an instructor.

2. **For Students:**
Log in using the following:
- Username: Student
- Password: password
- Look for answers marked with the "Instructor-Approved" label.
- Engage with these answers confidently, knowing they are verified.

### Testing
Automated tests for this feature are located in the `test/newImplementedFeatures/instructorApproved.js` file. The tests cover:
- Approval of an answer by an instructor.
- Verification that approved answers are highlighted appropriately.

---

## Feature 2: Sharing Posts Anonymously

### Description
Students can now share posts anonymously, allowing them to ask questions or share information without revealing their identities. This feature promotes a more open and peaceful discussion environment.

### How to Use
1. **For Students:**
- When creating a new post, check the **"Post Anonymously"** option. you can choose to be anonymous to all, none or your students.
- Submit your post as usual. Your identity will remain hidden from other users.

### Testing
Automated tests for this feature can be found in the `test/newImplementedFeatures/anonymousFeature.js` file. The tests validate:
- Successful submission of an anonymous post.
- Proper display of anonymous posts without revealing the user’s identity.

---

## Feature 3: Estimated Reading Time for Posts

### Description
Each post now includes an estimated reading time, helping students decide whether they have sufficient time to read the content before engaging.

### How to Use
1. **For Students:**
- When viewing a post, look for the estimated reading time displayed at the top right of the post.
- Use this information to gauge if you can read the content within your available time.
- This implementation can be seen when you click on discussion and then click on the post. Within the post, you can see the estimated reading time for each of the posts.

### Testing
Automated tests for this feature are located in the `test/newImplementedFeatures/EstimatedTimeForReadingPost.js` file. The tests ensure:
- Accurate calculation of estimated reading time based on the post's word count.
- Proper display of reading time in the post view.

---

## Conclusion
These new features aim to enhance the overall experience for instructors and students on the NodeBB platform. By providing verified answers, allowing anonymous discussions, and indicating reading times, we hope to foster a more effective and supportive learning environment.
84 changes: 67 additions & 17 deletions node_modules/nodebb-theme-harmony/templates/partials/topic/post.tpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/controllers/write/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ Posts.getReplies = async (req, res) => {
helpers.formatApiResponse(200, res, { replies });
};


async function markPostAsApproved(pid, status) {
try {

Check failure on line 185 in src/controllers/write/posts.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 1 tab but found 4 spaces
// 1. Fetch the post by pid

Check failure on line 186 in src/controllers/write/posts.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 2 tabs but found 8 spaces
Expand Down Expand Up @@ -216,5 +217,6 @@ Posts.approve = async (req, res) => {
}
} catch (error) {
res.status(500).json({ error: '[Alert] An error occurred while approving the post' });

}
};
2 changes: 2 additions & 0 deletions src/routes/write/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ module.exports = function () {
setupApiRoute(router, 'delete', '/:pid/diffs/:timestamp', middlewares, controllers.write.posts.deleteDiff);

setupApiRoute(router, 'get', '/:pid/replies', [middleware.assert.post], controllers.write.posts.getReplies);
// Add the new route for marking a post as "instructor-approved"
setupApiRoute(router, 'put', '/:pid/approve', [...middlewares, middleware.admin.checkPrivileges], controllers.write.posts.approve);

// Shorthand route to access post routes by topic index
router.all('/+byIndex/:index*?', [middleware.checkRequired.bind(null, ['tid'])], controllers.write.posts.redirectByIndex);
Expand Down
6 changes: 4 additions & 2 deletions src/views/post-queue.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
{{{ end }}}
<strong>{{{ if posts.data.tid }}}[[post-queue:reply]]{{{ else }}}[[post-queue:topic]]{{{ end }}}</strong>
<span class="timeago float-end" title={posts.data.timestampISO}></span>
<!-- Add the reading time -->
<span id="reading-time-{posts.data.id}" class="text-muted"></span>
</div>
<div class="card-body">
<div class="row">
Expand Down Expand Up @@ -129,11 +131,11 @@
<button class="btn btn-danger btn-sm" data-action="reject"><i class="fa fa-fw fa-times"></i> [[post-queue:remove]]</button>
{{{ end }}}
</div>
</div>
</div>[email protected]
</div>
{{{ end }}}
</div>
<!-- IMPORT partials/paginator.tpl -->
</div>
</div>
</div>
97 changes: 97 additions & 0 deletions test/newImplementedFeatures/annonymousFeature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict';

const { JSDOM } = require('jsdom');
const { expect } = require('chai');
const request = require('supertest');
const app = require('../../app');
const user = require('../../src/user');
const topics = require('../../src/topics');
const posts = require('../../src/posts');

describe('Anonymous Feature - Share Post Anonymously', () => {
let studentToken;
let postId;
let topicId;

before(async () => {
// Create a student user
const student = await user.create({ username: 'student', password: 'password' });

// Log in as student to get token
studentToken = await loginUser('student', 'password');

// Create a topic and a post
const topic = await topics.post({
title: 'Test Topic',
content: 'Test Content',
uid: student.uid,
cid: 1,
});
topicId = topic.tid;
const post = await posts.create({
content: 'Test Post',
uid: student.uid,
tid: topic.tid,
annonymousType: 'student',
});
postId = post.pid;
});

async function loginUser(username, password) {
const res = await request(app)
.post('/api/v1/login')
.send({ username, password });
return res.body.token;
}

describe('UI Elements', () => {
it('should display the anonymous type dropdown', () => {
const dom = new JSDOM(`
<div class="form-group">
<label for="anonymousDropdown">[[topic:composer.select-anonymous-type]]</label>
<select class="form-control" id="anonymousDropdown" name="anonymousType">
<option value="none">[[topic:composer-anonymous-none]]</option>
<option value="student">[[topic:composer-anonymous-student]]</option>
<option value="all">[[topic:composer-anonymous-all]]</option>
</select>
</div>
`);
const { document } = dom.window;
const dropdown = document.getElementById('anonymousDropdown');
expect(dropdown).to.not.be.null;
expect(dropdown.options.length).to.equal(3);
});
});

describe('Form Submission', () => {
it('should include the anonymous type in the form submission', async () => {
const res = await request(app)
.post('/api/v1/topics')
.set('Authorization', `Bearer ${studentToken}`)
.send({
title: 'Anonymous Post',
content: 'This is an anonymous post.',
cid: 1,
annonymousType: 'student',
})
.expect(200);

expect(res.body.topic.annonymousType).to.equal('student');
});
});

describe('Backend Handling', () => {
it('should display the post anonymously based on the selected type', async () => {
const res = await request(app)
.get(`/api/v1/posts/${postId}`)
.expect(200);

const { post } = res.body;
if (post.annonymousType === 'none') {
expect(post.user.username).to.not.be.null;
} else {
expect(post.user.username).to.be.null;
}
});
});
});
45 changes: 45 additions & 0 deletions test/newImplementedFeatures/estimatedTimeForReadingPost.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';

const { JSDOM } = require('jsdom');
const { expect } = require('chai');

describe('Estimated Time for Reading Post', () => {
let document;

beforeEach(() => {
const dom = new JSDOM(`
<div id="post-content-1">This is a test post content with a few words.</div>
<span id="reading-time-1" class="text-muted me-3"></span>
`);
document = dom.window.document;
});

it('should calculate the word count correctly', () => {
const postContentElement = document.getElementById('post-content-1');
const postContent = postContentElement.textContent;
const wordCount = postContent.trim().split(/\s+/).length;

expect(wordCount).to.equal(9); // Adjust the expected word count based on the content
});

it('should estimate the reading time correctly', () => {
const wordCount = 9; // Use the word count from the previous test
const wordsPerMinute = 200;
const readingTime = Math.ceil(wordCount / wordsPerMinute);

expect(readingTime).to.equal(1); // Adjust the expected reading time based on the word count
});

it('should display the estimated reading time in the DOM', () => {
const postContentElement = document.getElementById('post-content-1');
const readingTimeElement = document.getElementById('reading-time-1');

const postContent = postContentElement.textContent;
const wordCount = postContent.trim().split(/\s+/).length;
const readingTime = Math.ceil(wordCount / 200);

readingTimeElement.textContent = `Estimated reading time: ${readingTime} min`;

expect(readingTimeElement.textContent).to.equal('Estimated reading time: 1 min'); // Adjust the expected text based on the reading time
});
});
Loading

0 comments on commit d5b0149

Please sign in to comment.