-
-
Notifications
You must be signed in to change notification settings - Fork 362
doc(Vote): add gitee vote toast #7009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Reviewer's GuideThis PR refactors BaseLayout to use async JS interop lifecycle methods and adds a delayed Gitee vote toast, leveraging localStorage and event handling for user interactions. Sequence diagram for Gitee vote toast display and user interactionsequenceDiagram
participant Browser
participant JSModule
participant BaseLayout
actor User
Browser->>JSModule: doTask(DotNetObjectReference)
JSModule->>Browser: initTheme()
JSModule->>Browser: Check localStorage 'bb-gitee-vote'
alt No recent vote
JSModule->>Browser: setTimeout (10s)
Browser->>BaseLayout: ShowVoteToast() (via JSInvokable)
BaseLayout->>Browser: Show toast with vote options
end
User->>Browser: Click #bb-gitee-vote
Browser->>JSModule: EventHandler.on('click', '#bb-gitee-vote')
JSModule->>Browser: Hide toast
JSModule->>Browser: localStorage.setItem('bb-gitee-vote', timestamp)
Class diagram for updated BaseLayout and related typesclassDiagram
class BaseLayout {
-JSRuntime : IJSRuntime
-WebsiteOption : IOptions<WebsiteOption>
-Toast : ToastService
-CommitDispatchService : DispatchService<GiteePostBody>
-RebootDispatchService : DispatchService<bool>
-_option : ToastOption
-_init : bool
-_module : JSModule
-_interop : DotNetObjectReference_BaseLayout
+ShowVoteToast() : Task
+DisposeAsync() : ValueTask
+RenderVote : RenderFragment
}
class ToastOption {
Category : ToastCategory
Title : string
IsAutoHide : bool
ChildContent : RenderFragment
PreventDuplicates : bool
}
class DotNetObjectReference_BaseLayout
BaseLayout --> ToastOption
BaseLayout ..> JSModule
BaseLayout ..> DotNetObjectReference_BaseLayout
BaseLayout ..> ToastService
BaseLayout ..> DispatchService_GiteePostBody
BaseLayout ..> DispatchService_bool
class DispatchService_GiteePostBody
class DispatchService_bool
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a Gitee voting activity toast notification feature that prompts users (excluding English locale users) to participate in Gitee's 2025 open source software voting. The toast appears 10 seconds after page load and remembers user interaction for 24 hours via localStorage.
Key Changes:
- Implemented a delayed toast notification system with localStorage-based suppression logic
- Modified the BaseLayout component lifecycle to support async disposal and JavaScript interop
- Added event handling for vote toast interactions with automatic hiding
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| BaseLayout.razor.js | Added doTask function with timer logic, localStorage checks, and event handlers for vote toast interactions |
| BaseLayout.razor.cs | Changed from IDisposable to IAsyncDisposable, added JS module lifecycle management and ShowVoteToast method with culture check |
| BaseLayout.razor | Added RenderVote fragment containing the voting activity UI with links to Gitee |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| private bool _init = false; | ||
| private JSModule? _module; | ||
| private DotNetObjectReference<BaseLayout>? _interop; |
Copilot
AI
Oct 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _interop DotNetObjectReference is created but never disposed. Add _interop?.Dispose(); in the DisposeAsync method to prevent memory leaks.
| if (v) { | ||
| try { | ||
| const differ = new Date().getTime() - v; | ||
| if (differ < 86400000) { |
Copilot
AI
Oct 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic number 86400000 (milliseconds in 24 hours) should be extracted as a named constant for better readability. Consider: const ONE_DAY_MS = 86400000;
| const handler = setTimeout(async () => { | ||
| clearTimeout(handler); | ||
| await invoke.invokeMethodAsync("ShowVoteToast"); | ||
| }, 10000); |
Copilot
AI
Oct 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic number 10000 (10 seconds delay) should be extracted as a named constant for better readability. Consider: const VOTE_TOAST_DELAY_MS = 10000;
| @code { | ||
| RenderFragment RenderVote => | ||
| @<div> | ||
| <p style="font-weight: bold;">我正在参加 Gitee 2025 最受欢迎的开源软件投票活动,快来给我投票吧!</p> |
Copilot
AI
Oct 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The text '我正在参加' (I am participating) should be '我们正在参加' (We are participating) to correctly represent the project rather than an individual.
| <p style="font-weight: bold;">我正在参加 Gitee 2025 最受欢迎的开源软件投票活动,快来给我投票吧!</p> | |
| <p style="font-weight: bold;">我们正在参加 Gitee 2025 最受欢迎的开源软件投票活动,快来给我投票吧!</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `src/BootstrapBlazor.Server/Components/Layout/BaseLayout.razor.cs:160-169` </location>
<code_context>
/// </summary>
/// <param name="disposing"></param>
- private void Dispose(bool disposing)
+ private async ValueTask DisposeAsync(bool disposing)
{
if (disposing)
{
CommitDispatchService.UnSubscribe(NotifyCommit);
RebootDispatchService.UnSubscribe(NotifyReboot);
+
+ if (_module != null)
+ {
+ await _module.InvokeVoidAsync("dispose");
+ await _module.DisposeAsync();
+ }
}
</code_context>
<issue_to_address>
**issue (bug_risk):** DisposeAsync should also dispose _interop to avoid memory leaks.
Dispose _interop in DisposeAsync if it's not null to ensure unmanaged resources are released.
</issue_to_address>
### Comment 2
<location> `src/BootstrapBlazor.Server/Components/Layout/BaseLayout.razor.js:24-26` </location>
<code_context>
+ localStorage.removeItem('bb-gitee-vote');
+ }
+ }
+ const handler = setTimeout(async () => {
+ clearTimeout(handler);
+ await invoke.invokeMethodAsync("ShowVoteToast");
+ }, 10000);
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Using setTimeout with async function may cause unhandled promise rejections.
Since errors in the async callback won't be caught, wrap the function body in try/catch or handle the promise to prevent unhandled rejections.
</issue_to_address>
### Comment 3
<location> `src/BootstrapBlazor.Server/Components/Layout/BaseLayout.razor.js:2` </location>
<code_context>
import { getTheme, setTheme } from "../../_content/BootstrapBlazor/modules/utility.js"
+import EventHandler from "../../_content/BootstrapBlazor/modules/event-handler.js"
-export function initTheme() {
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the EventHandler abstraction with direct event listeners and simplifying localStorage handling for clarity and efficiency.
```suggestion
// 1. Drop the custom EventHandler import and use direct add/removeEventListener
// 2. Simplify localStorage parsing & drop try/catch
// 3. Remove clearTimeout (one‐off timers auto‐clear)
//
// At top of your module:
let voteClickHandler = null;
export function doTask(invoke) {
initTheme();
// 2. parseInt handles invalid or missing entries without try/catch
const lastVote = parseInt(localStorage.getItem('bb-gitee-vote'), 10);
if (lastVote && Date.now() - lastVote < 86_400_000) {
return;
}
// 3. no need to clearTimeout after one shot
setTimeout(() => {
invoke.invokeMethodAsync("ShowVoteToast");
}, 10_000);
// 1. direct listener with selector check
voteClickHandler = (e) => {
if (!e.target.matches('#bb-gitee-vote')) return;
const toast = e.target.closest('.toast');
if (toast) {
toast.classList.remove('show');
localStorage.setItem('bb-gitee-vote', Date.now());
}
};
document.addEventListener('click', voteClickHandler);
}
export function dispose() {
if (voteClickHandler) {
document.removeEventListener('click', voteClickHandler);
voteClickHandler = null;
}
}
```
This removes the `EventHandler` abstraction, streamlines storage parsing, and keeps identical behavior.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const handler = setTimeout(async () => { | ||
| clearTimeout(handler); | ||
| await invoke.invokeMethodAsync("ShowVoteToast"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Using setTimeout with async function may cause unhandled promise rejections.
Since errors in the async callback won't be caught, wrap the function body in try/catch or handle the promise to prevent unhandled rejections.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7009 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 741 741
Lines 32397 32397
Branches 4485 4485
=========================================
Hits 32397 32397
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Link issues
fixes #7008
Summary By Copilot
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Add a Gitee vote prompt to the base layout that appears on first render for non-English users and can be dismissed for 24 hours.
New Features:
Enhancements: