Skip to content

refactor: update ui and logic for comments#166

Open
fwy13 wants to merge 1 commit into
anime-vsub:mainfrom
fwy13:comments-new
Open

refactor: update ui and logic for comments#166
fwy13 wants to merge 1 commit into
anime-vsub:mainfrom
fwy13:comments-new

Conversation

@fwy13

@fwy13 fwy13 commented Apr 12, 2026

Copy link
Copy Markdown

Feature

  • Added a basic comment section to the main website.

To-Do

  • Like / Dislike action.
  • User authentication (Login required to comment).

@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@codesandbox

codesandbox Bot commented Apr 12, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Implement custom comments system with replies and spoiler support

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implemented comprehensive comments component with nested replies support
• Added spoiler reveal functionality and content rendering with mentions/links
• Integrated like/dislike voting system and pinned comment indicators
• Added internationalization support for English and Vietnamese languages
• Replaced Facebook comments embed with custom comments component
Diagram
flowchart LR
  A["Comments Component"] --> B["Display Comments List"]
  A --> C["Load More Comments"]
  B --> D["Render Replies"]
  B --> E["Spoiler Reveal"]
  B --> F["Like/Dislike Votes"]
  D --> G["Load More Replies"]
  A --> H["i18n Translations"]
  I["Season Page"] --> J["Replace FB Comments"]
  J --> A
Loading

Grey Divider

File Changes

1. src/components/comments/index.vue ✨ Enhancement +524/-0

New comments component with replies and spoiler support

• Created new comments component with full UI for displaying comments and nested replies
• Implemented pagination for comments and replies with lazy loading
• Added spoiler content reveal functionality with blur effect
• Integrated like/dislike voting buttons and user badges display
• Added content rendering with support for mentions, URLs, and stickers
• Implemented avatar fallback handling and timestamp formatting

src/components/comments/index.vue


2. src/pages/phim/_season.vue ✨ Enhancement +4/-2

Integrate custom comments component into season page

• Replaced Facebook comments embed with custom Comments component
• Passed seasonId as film-id prop to new Comments component
• Commented out legacy FbComments component integration
• Imported new Comments component from components/comments

src/pages/phim/_season.vue


3. src/i18n/messages/en-US.json 📝 Documentation +11/-1

Add English translations for comments feature

• Added 11 new translation keys for comments feature
• Includes keys for post comment, pinned, edited, spoiler reveal, likes, replies
• Added loading and error state translations

src/i18n/messages/en-US.json


View more (1)
4. src/i18n/messages/vi-VN.json 📝 Documentation +11/-1

Add Vietnamese translations for comments feature

• Added 11 new Vietnamese translation keys for comments feature
• Mirrors English translations with Vietnamese language support
• Includes all UI text for comments, replies, spoilers, and loading states

src/i18n/messages/vi-VN.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 12, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3)   📘 Rule violations (0)   📎 Requirement gaps (0)   🖥 UI issues (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (1) ⛨ Security (1) ⚙ Maintainability (1)

Grey Divider


Action required

1. XSS via v-html 🐞
Description
The component renders comment/reply content with v-html using renderContent() that returns
unsanitized HTML built from user data. A crafted comment (or sticker/url) can inject arbitrary
HTML/JS by breaking out of attributes in the generated <a href="..."> or <img src="...">.
Code

src/components/comments/index.vue[R104-112]

+          <div class="relative">
+            <div
+              class="text-sm text-gray-300 leading-relaxed mb-2 whitespace-pre-wrap break-words transition-all duration-300"
+              :class="{
+                'filter blur-md select-none pointer-events-none opacity-50':
+                  comment.is_spoiler == 1 && !comment.isRevealed
+              }"
+              v-html="renderContent(comment.content)"
+            ></div>
Evidence
v-html directly injects the string returned by renderContent() into the DOM for both comments
and replies; renderContent() interpolates raw user-controlled substrings (content, captured URLs,
sticker URLs) into HTML attributes without escaping or sanitization, enabling XSS.

src/components/comments/index.vue[104-112]
src/components/comments/index.vue[235-243]
src/components/comments/index.vue[493-520]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`renderContent()` returns HTML strings derived from user-controlled comment text and then injects them into the DOM via `v-html`. Because the code interpolates captured URLs and sticker URLs into HTML attributes without escaping/sanitization, an attacker can inject scripts/HTML.

### Issue Context
You need linkification/mentions/stickers, but it must be done without allowing arbitrary HTML execution.

### Fix Focus Areas
- src/components/comments/index.vue[104-112]
- src/components/comments/index.vue[235-243]
- src/components/comments/index.vue[493-520]

### Implementation guidance
- Escape user text first (e.g., `escapeHtml(content)`) before doing any replacements.
- Avoid building HTML with string concatenation for attributes. Prefer creating DOM nodes (or Vue VNodes) and setting `href/src/textContent` properties.
- If you must use `v-html`, sanitize the final HTML with a vetted sanitizer (e.g., DOMPurify) and explicitly restrict allowed tags/attributes.
- Validate URLs before rendering:
 - allow only `http:`/`https:` schemes
 - reject/strip quotes and control characters
 - consider normalizing and rendering unsafe URLs as plain text.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Stale comments on filmId change 🐞
Description
When filmId changes, the watcher clears state but does not cancel/ignore in-flight requests, and
responses are applied without verifying they match the current filmId. This can append comments
from the previous film into the new film’s list if the earlier request resolves after the reset.
Code

src/components/comments/index.vue[R394-429]

+watch(
+  () => props.filmId,
+  () => {
+    commentsList.value = []
+    currentOffset.value = 0
+    hasMoreComments.value = true
+    totalComments.value = 0
+    loadMoreComments()
+  }
+)
+
+const loadMoreComments = async () => {
+  if (isLoadingComments.value || !hasMoreComments.value) return
+  isLoadingComments.value = true
+
+  try {
+    const response = await get({
+      url: `${C_URL}/ajax/comment?action=get&film_id=${props.filmId}&sort=newest&offset=${currentOffset.value}`,
+      responseType: "json"
+    })
+
+    const data = response.data as any
+    if (data.success) {
+      const newComments = data.comments.map((c: any) => ({
+        ...c,
+        replies: c.replies || [],
+        replies_offset: 0,
+        isLoadingReplies: false,
+        isRevealed: false
+      }))
+
+      commentsList.value.push(...newComments)
+      currentOffset.value = data.offset
+      hasMoreComments.value = data.has_more
+      totalComments.value = data.total
+    }
Evidence
The watcher resets commentsList/currentOffset/hasMoreComments/totalComments then calls
loadMoreComments(), but loadMoreComments() gates on a shared isLoadingComments flag and pushes
results unconditionally; there is no request identity check (e.g., captured filmId/token) before
mutating the current list.

src/components/comments/index.vue[394-403]
src/components/comments/index.vue[405-429]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Changing `filmId` can cause older in-flight requests to resolve after state reset and still mutate the current `commentsList`, showing comments for the wrong film.

### Issue Context
The component uses a shared `isLoadingComments` flag and does not cancel or validate responses against the `filmId` used to start the request.

### Fix Focus Areas
- src/components/comments/index.vue[394-403]
- src/components/comments/index.vue[405-429]

### Implementation guidance
Choose one (or combine):
- **Request token approach (no AbortController needed):**
 - Maintain `let requestSeq = 0` in module scope.
 - In `loadMoreComments`, capture `const seq = ++requestSeq` and `const filmIdAtStart = props.filmId`.
 - After `await get(...)`, before applying results, check `if (seq !== requestSeq || props.filmId !== filmIdAtStart) return`.
 - In the `watch` handler, increment `requestSeq` to invalidate prior requests.
- **Abort approach (if supported by your Http.get wrapper):**
 - Keep an `AbortController` per filmId and abort it on `filmId` change.

Also consider guarding the watcher so it doesn’t call `loadMoreComments()` when `filmId` is falsy.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Unused FbComments import 🐞
Description
FbComments is still imported in _season.vue even though its template usage was commented out,
leaving dead code (and potentially failing lint if @typescript-eslint/no-unused-vars is enforced).
Remove the unused import or re-enable its usage.
Code

src/pages/phim/_season.vue[R393-401]

+      <Comments v-if="seasonId" :film-id="seasonId" />
+      <!-- <FbComments
        v-if="semverGt(Http.version, '1.0.29')"
        :href="`http://animevietsub.tv/phim/-${seasonId}/`"
        :lang="locale?.replace('-', '_')"
-      />
+      />  -->
      <template v-else>
        <div class="mt-5 flex items-center justify-between flex-nowrap">
          <span class="text-subtitle1 text-[#eee]">{{ t("binh-luan") }}</span>
Evidence
FbComments is only present in a commented template block and is not otherwise referenced, but its
import remains. The repo ESLint config extends plugin:@typescript-eslint/recommended, which
typically flags unused imports/vars unless explicitly disabled.

src/pages/phim/_season.vue[392-399]
src/pages/phim/_season.vue[486-501]
.eslintrc.js[25-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`FbComments` is imported but not used (its template usage is commented out). This is dead code and can trigger lint failures.

### Issue Context
The file now uses the new `<Comments />` component; the old `<FbComments />` block is commented.

### Fix Focus Areas
- src/pages/phim/_season.vue[392-399]
- src/pages/phim/_season.vue[486-501]

### Implementation guidance
- Remove `import FbComments ...` if it’s no longer needed.
- Optionally delete the commented-out `<FbComments ... />` block to avoid confusion.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

Comment on lines +104 to +112
<div class="relative">
<div
class="text-sm text-gray-300 leading-relaxed mb-2 whitespace-pre-wrap break-words transition-all duration-300"
:class="{
'filter blur-md select-none pointer-events-none opacity-50':
comment.is_spoiler == 1 && !comment.isRevealed
}"
v-html="renderContent(comment.content)"
></div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Xss via v-html 🐞 Bug ⛨ Security

The component renders comment/reply content with v-html using renderContent() that returns
unsanitized HTML built from user data. A crafted comment (or sticker/url) can inject arbitrary
HTML/JS by breaking out of attributes in the generated <a href="..."> or <img src="...">.
Agent Prompt
### Issue description
`renderContent()` returns HTML strings derived from user-controlled comment text and then injects them into the DOM via `v-html`. Because the code interpolates captured URLs and sticker URLs into HTML attributes without escaping/sanitization, an attacker can inject scripts/HTML.

### Issue Context
You need linkification/mentions/stickers, but it must be done without allowing arbitrary HTML execution.

### Fix Focus Areas
- src/components/comments/index.vue[104-112]
- src/components/comments/index.vue[235-243]
- src/components/comments/index.vue[493-520]

### Implementation guidance
- Escape user text first (e.g., `escapeHtml(content)`) before doing any replacements.
- Avoid building HTML with string concatenation for attributes. Prefer creating DOM nodes (or Vue VNodes) and setting `href/src/textContent` properties.
- If you must use `v-html`, sanitize the final HTML with a vetted sanitizer (e.g., DOMPurify) and explicitly restrict allowed tags/attributes.
- Validate URLs before rendering:
  - allow only `http:`/`https:` schemes
  - reject/strip quotes and control characters
  - consider normalizing and rendering unsafe URLs as plain text.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +394 to +429
watch(
() => props.filmId,
() => {
commentsList.value = []
currentOffset.value = 0
hasMoreComments.value = true
totalComments.value = 0
loadMoreComments()
}
)

const loadMoreComments = async () => {
if (isLoadingComments.value || !hasMoreComments.value) return
isLoadingComments.value = true

try {
const response = await get({
url: `${C_URL}/ajax/comment?action=get&film_id=${props.filmId}&sort=newest&offset=${currentOffset.value}`,
responseType: "json"
})

const data = response.data as any
if (data.success) {
const newComments = data.comments.map((c: any) => ({
...c,
replies: c.replies || [],
replies_offset: 0,
isLoadingReplies: false,
isRevealed: false
}))

commentsList.value.push(...newComments)
currentOffset.value = data.offset
hasMoreComments.value = data.has_more
totalComments.value = data.total
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Stale comments on filmid change 🐞 Bug ≡ Correctness

When filmId changes, the watcher clears state but does not cancel/ignore in-flight requests, and
responses are applied without verifying they match the current filmId. This can append comments
from the previous film into the new film’s list if the earlier request resolves after the reset.
Agent Prompt
### Issue description
Changing `filmId` can cause older in-flight requests to resolve after state reset and still mutate the current `commentsList`, showing comments for the wrong film.

### Issue Context
The component uses a shared `isLoadingComments` flag and does not cancel or validate responses against the `filmId` used to start the request.

### Fix Focus Areas
- src/components/comments/index.vue[394-403]
- src/components/comments/index.vue[405-429]

### Implementation guidance
Choose one (or combine):
- **Request token approach (no AbortController needed):**
  - Maintain `let requestSeq = 0` in module scope.
  - In `loadMoreComments`, capture `const seq = ++requestSeq` and `const filmIdAtStart = props.filmId`.
  - After `await get(...)`, before applying results, check `if (seq !== requestSeq || props.filmId !== filmIdAtStart) return`.
  - In the `watch` handler, increment `requestSeq` to invalidate prior requests.
- **Abort approach (if supported by your Http.get wrapper):**
  - Keep an `AbortController` per filmId and abort it on `filmId` change.

Also consider guarding the watcher so it doesn’t call `loadMoreComments()` when `filmId` is falsy.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant