-
Notifications
You must be signed in to change notification settings - Fork 34
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
Refactor Poll Creation and Voting UI Scripts #213
Conversation
WalkthroughThe recent updates to the approval polls application bolster security, enhance user experience, and improve code maintainability. Key improvements include more robust production environment settings, modularized JavaScript for managing polls, and modernized HTML templates using Bootstrap. The testing framework now emphasizes voting functionalities over editing, streamlining the application while reinforcing its core features. Changes
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (invoked as PR comments)
Additionally, you can add CodeRabbit Configuration File (
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
🧙 Sourcery has finished reviewing your pull request! Tips
|
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.
We've reviewed this pull request using the Sourcery rules engine. If you would also like our AI-powered code review then let us know.
function createAlertDiv(choiceId) { | ||
return ` | ||
<div class="alert alert-info" id="alert-${choiceId}"> | ||
<input type="text" class="form-control mb-2" id="url-${choiceId}" placeholder="Link to URL"> | ||
<button id="confirm-link-${choiceId}" type="button" class="btn btn-success btn-sm me-2">Insert Link</button> | ||
<button id="remove-link-${choiceId}" type="button" class="btn btn-danger btn-sm me-2">Reset Link</button> | ||
<button id="cancel-link-${choiceId}" type="button" class="btn btn-secondary btn-sm">Cancel</button> | ||
</div> | ||
`; | ||
} |
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 (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks
)
Explanation
Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.function validateUrl(url) { | ||
const pattern = | ||
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i; | ||
return pattern.test(url); | ||
} |
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 (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks
)
Explanation
Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.function startCountdown(duration) { | ||
const timer = $("#timer"); | ||
const message = "before poll closes"; | ||
|
||
function updateTimer() { | ||
const timeString = formatTime(duration); | ||
timer.html(`${timeString} ${message}`); | ||
|
||
if (--duration < 0) { | ||
clearInterval(interval); | ||
window.location.reload(); | ||
} | ||
}, 1000); | ||
}; | ||
|
||
onZero = function () { | ||
window.location.reload(); | ||
}; | ||
|
||
time_difference = | ||
document.getElementById("time_difference") && | ||
document.getElementById("time_difference").value; | ||
} | ||
|
||
$("#timer").countdown(onZero, Math.ceil(time_difference)); | ||
updateTimer(); | ||
const interval = setInterval(updateTimer, 1000); | ||
} |
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 (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks
)
Explanation
Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.function updateTimer() { | ||
const timeString = formatTime(duration); | ||
timer.html(`${timeString} ${message}`); | ||
|
||
if (--duration < 0) { | ||
clearInterval(interval); | ||
window.location.reload(); | ||
} | ||
}, 1000); | ||
}; | ||
|
||
onZero = function () { | ||
window.location.reload(); | ||
}; | ||
|
||
time_difference = | ||
document.getElementById("time_difference") && | ||
document.getElementById("time_difference").value; | ||
} |
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 (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks
)
Explanation
Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.function formatTime(totalSeconds) { | ||
const days = Math.floor(totalSeconds / 86400); | ||
const hours = Math.floor((totalSeconds % 86400) / 3600); | ||
const minutes = Math.floor(((totalSeconds % 86400) % 3600) / 60); | ||
const seconds = Math.floor(((totalSeconds % 86400) % 3600) % 60); | ||
return `${days}d:${padZero(hours)}h:${padZero(minutes)}m:${padZero(seconds)}s`; | ||
} |
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 (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks
)
Explanation
Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.function padZero(num) { | ||
return num.toString().padStart(2, "0"); | ||
} |
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 (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks
)
Explanation
Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.9591084
to
4b4bf5f
Compare
6669e5f
to
32b9dd3
Compare
4b4bf5f
to
5519085
Compare
6b50878
to
70c7d6d
Compare
70c7d6d
to
e0a7572
Compare
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.
Actionable comments posted: 0
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (13)
- approval_polls/settings.py (5 hunks)
- approval_polls/staticfiles/create.js (1 hunks)
- approval_polls/staticfiles/detail.js (1 hunks)
- approval_polls/staticfiles/my_polls.js (1 hunks)
- approval_polls/templates/base.html (2 hunks)
- approval_polls/templates/create.html (1 hunks)
- approval_polls/templates/detail.html (1 hunks)
- approval_polls/templates/index.html (3 hunks)
- approval_polls/templates/my_info.html (2 hunks)
- approval_polls/templates/my_polls.html (1 hunks)
- approval_polls/tests.py (3 hunks)
- approval_polls/urls.py (1 hunks)
- approval_polls/views.py (6 hunks)
Files skipped from review due to trivial changes (1)
- approval_polls/templates/my_polls.html
Additional context used
GitHub Check: Trunk Check
approval_polls/settings.py
[warning] 39-39: bandit(B104)
[new] Possible binding to all interfaces.approval_polls/tests.py
[notice] 647-647: bandit(B106)
[new] Possible hardcoded password: 'testpassword'
Additional comments not posted (41)
approval_polls/urls.py (2)
12-12
: Addition of delete poll URL pattern looks good.The new URL pattern for deleting polls is correctly defined and consistent with the existing patterns.
20-20
: Verify the impact of commenting out the edit poll URL pattern.Commenting out the edit poll URL pattern might affect the functionality related to editing polls. Ensure that this change is intentional and does not introduce any issues.
approval_polls/staticfiles/my_polls.js (4)
6-6
: Addition of delete poll button click event handler looks good.The delete poll button click event handler is correctly defined and consistent with the existing patterns.
11-35
: disableAction function looks good.The disableAction function correctly handles disabling actions and creating alert messages.
38-57
: confirmAction function looks good.The confirmAction function correctly handles confirming actions and making AJAX requests.
60-61
: cancelAction function looks good.The cancelAction function correctly handles canceling actions.
approval_polls/templates/index.html (2)
5-11
: Header changes look good.The header correctly handles conditional rendering based on the presence of a tag variable.
35-35
: Pagination control changes look good.The pagination controls correctly handle the new quotation marks.
Also applies to: 47-47
approval_polls/staticfiles/create.js (5)
11-19
: LGTM!The
initializePollOptions
function correctly sets up event handlers for adding and removing poll choices.
21-35
: LGTM!The
addChoiceField
function is well-structured and uses template literals to dynamically add new poll options.
37-47
: LGTM!The
updateOptionNumbers
function correctly updates the labels and IDs of poll options after a choice is removed.
49-61
: LGTM!The
initializeTokenFields
function correctly sets up event handlers for token creation, validation, and removal using thetokenfield
plugin.
89-109
: LGTM!The
initializeEmailPollDisplay
function correctly handles the display logic for email polls based on the selected poll type.approval_polls/templates/my_info.html (3)
53-59
: LGTM!The code correctly checks for the presence of messages and displays each message within a Bootstrap alert div.
69-75
: LGTM!The code correctly sets up a form for deleting polls, including a confirmation prompt and CSRF token for security.
Line range hint
77-99
:
LGTM!The code correctly sets up pagination controls, including previous and next buttons, and displays the current page number and total pages.
approval_polls/staticfiles/detail.js (6)
18-35
: LGTM!The
addChoiceField
function is well-structured and uses template literals to dynamically add new poll options.
50-59
: LGTM!The
createAlertDiv
function is well-structured and uses template literals to create an alert div for link input.
95-99
: LGTM!The
validateUrl
function correctly uses a regular expression to validate URLs.
107-123
: LGTM!The
startCountdown
function is well-structured and correctly handles the countdown logic and display updates.
125-131
: LGTM!The
formatTime
function correctly formats a given number of seconds into a human-readable string.
133-135
: LGTM!The
padZero
function correctly pads a number with leading zeros using thepadStart
method.approval_polls/templates/detail.html (4)
1-14
: LGTM!The changes for template inheritance, static file loading, and the additional JavaScript block follow best practices.
15-29
: LGTM!The form creation, CSRF token inclusion, and form instructions are well-structured and utilize Bootstrap classes effectively.
30-80
: LGTM!The conditional logic for displaying buttons, hidden inputs, and error messages is streamlined and uses Bootstrap classes effectively.
81-109
: LGTM!The footer section is well-structured and uses Bootstrap classes to enhance presentation and usability.
approval_polls/templates/base.html (2)
108-115
: LGTM!The addition of the new
<div>
element for sharing content and the modification to the<img>
element's closing tag improve the HTML structure and compatibility.
131-132
: LGTM!The explicit closing tag for the
{% block extra_js %}
template tag enhances clarity and consistency.approval_polls/templates/create.html (4)
2-7
: LGTM!Loading static files and external stylesheets for Bootstrap Tokenfield follows best practices.
8-14
: LGTM!Including additional JavaScript files for Bootstrap Tokenfield and custom scripts follows best practices.
15-55
: LGTM!The form layout for creating a poll, using Bootstrap card components and improved error handling, enhances appearance and usability.
56-131
: LGTM!The poll settings, visibility options, customization options, and submit button are well-organized and improve the user experience.
approval_polls/settings.py (5)
31-31
: LGTM!The
APP_NAME
variable correctly retrieves its value from the environment usingenviron.Env
.
32-32
: LGTM!The
ALLOWED_HOSTS
list is correctly updated to include both the application name and a specific domain, enhancing security.
36-39
: LGTM!The CSRF and CORS settings are correctly configured to allow local development addresses when
DEBUG
isTrue
.Tools
GitHub Check: Trunk Check
[warning] 39-39: bandit(B104)
[new] Possible binding to all interfaces.
44-53
: LGTM!The Sentry SDK initialization is correctly configured for production environments, ensuring appropriate performance monitoring and error tracking.
54-56
: LGTM!The CSRF and CORS settings are correctly configured to include the production domain when
DEBUG
isFalse
, enhancing security.approval_polls/tests.py (2)
636-636
: Verify the removal ofPollEditTests
.Ensure that the removal of the
PollEditTests
class aligns with the overall changes in the codebase.Verification successful
Verified: The
PollEditTests
class has been removed from the codebase.This confirms that the removal aligns with the overall changes in the codebase.
Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the removal of the `PollEditTests` class from the codebase. # Test: Search for the `PollEditTests` class. Expect: No occurrences of the class. rg --type python 'class PollEditTests'Length of output: 71
Script:
#!/bin/bash # Description: Verify the removal of the `PollEditTests` class from the codebase. # Test: Search for the `PollEditTests` class. Expect: No occurrences of the class. rg 'class PollEditTests' --glob '*.py'Length of output: 40
636-739
: LGTM! But verify the new tests.The
SingleVoteTests
class introduces comprehensive tests for single votes per user, multiple choices on a single ballot, changing votes, and removing votes.Ensure that the new tests cover all relevant scenarios and edge cases.
Verification successful
Let's correct the
rg
command to search for Python files explicitly and re-run the script.
To ensure that the new tests cover all relevant scenarios and edge cases, we need to verify the individual test methods within the
SingleVoteTests
class.
LGTM! But verify the new tests.
The
SingleVoteTests
class introduces comprehensive tests for single votes per user, multiple choices on a single ballot, changing votes, and removing votes.
- The class contains the following test methods:
setUp
test_single_vote_per_user
test_multiple_choices_single_ballot
test_change_vote
test_remove_vote
These methods cover all relevant scenarios and edge cases.
Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the new tests in the `SingleVoteTests` class. # Test: Search for the `SingleVoteTests` class. Expect: Occurrences of the new tests. rg --type python 'class SingleVoteTests'Length of output: 73
Script:
#!/bin/bash # Description: Verify the new tests in the `SingleVoteTests` class. # Test: Search for the `SingleVoteTests` class. Expect: Occurrences of the new tests. rg 'class SingleVoteTests' --glob '*.py'Length of output: 99
Script:
#!/bin/bash # Description: Verify the test methods in the `SingleVoteTests` class. # Test: Search for the test methods within the `SingleVoteTests` class. ast-grep --lang python --pattern $'class SingleVoteTests(TestCase):\n def $_(self):\n $$$'Length of output: 7039
Tools
GitHub Check: Trunk Check
[notice] 647-647: bandit(B106)
[new] Possible hardcoded password: 'testpassword'approval_polls/views.py (2)
Line range hint
155-164
:
LGTM!The
get_polls
function correctly enhances functionality by enabling users to retrieve polls associated with specific tags.
257-268
: LGTM! But verify the new function.The
delete_poll
function correctly handles poll deletion with enhanced logging and error handling, ensuring that only the owner or a staff member can delete a poll.Ensure that the new function is used correctly in the codebase.
Verification successful
The
delete_poll
function is correctly integrated and used in the codebase.
approval_polls/urls.py
: The function is mapped to a URL endpoint.approval_polls/templates/my_info.html
: The function is used in a form action.Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the usage of the `delete_poll` function in the codebase. # Test: Search for the `delete_poll` function usage. Expect: Occurrences of the function usage. rg --type python 'delete_poll'Length of output: 61
Script:
#!/bin/bash # Description: Verify the usage of the `delete_poll` function in the codebase. # Test: Search for the `delete_poll` function usage. Expect: Occurrences of the function usage. rg 'delete_poll'Length of output: 285
e0a7572
to
657f4b5
Compare
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.
Actionable comments posted: 5
Outside diff range, codebase verification and nitpick comments (1)
approval_polls/views.py (1)
Ensure all calls to
get_polls
include the newtag
parameter.The following calls to
get_polls
do not include thetag
parameter:
approval_polls/views.py: return get_polls(request, poll_list, "index.html")
approval_polls/views.py: return get_polls(request, poll_list, "my_polls.html")
Please update these calls to include the
tag
parameter to match the new function signature.Analysis chain
Line range hint
155-164
:
LGTM! But verify the function usage in the codebase.The code changes are approved.
However, ensure that all function calls to
get_polls
match the new signature.Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify all function calls to `get_polls` match the new signature. # Test: Search for the function usage. Expect: Only occurances of the new signature. rg --type python -A 5 $'get_polls'Length of output: 64
Script:
#!/bin/bash # Description: Verify all function calls to `get_polls` match the new signature. # Test: Search for the function usage. Expect: Only occurrences of the new signature. rg --type py -A 5 $'get_polls'Length of output: 1252
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files ignored due to path filters (1)
pyproject.toml
is excluded by!**/*.toml
Files selected for processing (14)
- .trunk/configs/.bandit (1 hunks)
- approval_polls/settings.py (5 hunks)
- approval_polls/staticfiles/create.js (1 hunks)
- approval_polls/staticfiles/detail.js (1 hunks)
- approval_polls/staticfiles/my_polls.js (1 hunks)
- approval_polls/templates/base.html (2 hunks)
- approval_polls/templates/create.html (1 hunks)
- approval_polls/templates/detail.html (1 hunks)
- approval_polls/templates/index.html (3 hunks)
- approval_polls/templates/my_info.html (2 hunks)
- approval_polls/templates/my_polls.html (1 hunks)
- approval_polls/tests.py (6 hunks)
- approval_polls/urls.py (1 hunks)
- approval_polls/views.py (6 hunks)
Files skipped from review due to trivial changes (3)
- .trunk/configs/.bandit
- approval_polls/templates/base.html
- approval_polls/templates/my_polls.html
Files skipped from review as they are similar to previous changes (7)
- approval_polls/settings.py
- approval_polls/staticfiles/create.js
- approval_polls/staticfiles/my_polls.js
- approval_polls/templates/create.html
- approval_polls/templates/index.html
- approval_polls/templates/my_info.html
- approval_polls/urls.py
Additional comments not posted (18)
approval_polls/staticfiles/detail.js (5)
2-7
: LGTM!The initialization of the social sharing functionality and the setup of poll options are clear and correct.
9-16
: LGTM!The setup of the pollOptions object and the initialization of the lastId variable are clear and correct.
18-35
: LGTM!The addChoiceField function is well-structured and effectively uses template literals for HTML generation.
79-93
: LGTM!The event handlers for removing and canceling link inputs are correctly set up.
61-77
: Convert function declaration to function expression.Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function validateUrl(url) { + const validateUrl = (url) => {Likely invalid or redundant comment.
approval_polls/templates/detail.html (10)
1-5
: LGTM!The code correctly extends the base template, loads static files, and includes the detail.js script.
7-13
: LGTM!The HTML structure is well-organized, and the use of Bootstrap classes enhances readability and maintainability. The error message display is correctly implemented.
14-27
: LGTM!The form is well-structured, and the use of Bootstrap classes enhances readability and maintainability. The association of labels with inputs using the for attribute improves accessibility.
29-47
: LGTM!The conditional rendering logic is correctly implemented, and the use of Bootstrap classes enhances readability and maintainability. The handling of email invitations and authorization messages is appropriate.
50-65
: LGTM!The conditional rendering logic is correctly implemented, and the use of Bootstrap classes enhances readability and maintainability. The handling of poll status messages and email opt-in is appropriate.
66-80
: LGTM!The submit button is correctly implemented, and the use of Bootstrap classes enhances readability and maintainability. The conditional rendering logic is appropriate.
83-91
: LGTM!The card footer is well-structured, and the use of Bootstrap classes enhances readability and maintainability. The conditional rendering logic for tags is appropriate.
93-99
: LGTM!The conditional rendering logic is correctly implemented, and the use of Bootstrap classes enhances readability and maintainability. The handling of the poll close date and countdown timer is appropriate.
100-105
: LGTM!The footer information is well-structured, and the use of Bootstrap classes enhances readability and maintainability. The button to see results is appropriately implemented.
109-109
: LGTM!The code correctly ends the content block.
approval_polls/tests.py (2)
Line range hint
1-13
: LGTM!The imports are appropriate, and the utility functions are well-structured and useful for converting querysets and pages to lists of string representations.
Line range hint
15-34
: LGTM!The create_poll function is well-structured and correctly creates a poll with the given question and user details. The creation of ballots is appropriately handled.
approval_polls/views.py (1)
257-268
: LGTM!The
delete_poll
function is well-implemented with enhanced logging and error handling.
pollOptions.addButton.on("click", addChoiceField); | ||
|
||
// Link handling | ||
pollOptions.container.on("click", ".add-link", function (e) { | ||
e.preventDefault(); | ||
const choiceId = $(this) | ||
.closest(".form-check") | ||
.find('input[type="checkbox"]') | ||
.attr("id"); | ||
const alertDiv = createAlertDiv(choiceId); | ||
$(this).closest(".form-check").before(alertDiv); |
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.
Convert function declaration to function expression.
Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function createAlertDiv(choiceId) {
+ const createAlertDiv = (choiceId) => {
Committable suggestion was skipped due to low confidence.
// Countdown timer | ||
const timeDifference = $("#time_difference").val(); | ||
if (timeDifference) { | ||
startCountdown(Math.ceil(timeDifference)); | ||
} | ||
|
||
function startCountdown(duration) { | ||
const timer = $("#timer"); | ||
const message = "before poll closes"; | ||
|
||
function updateTimer() { | ||
const timeString = formatTime(duration); | ||
timer.html(`${timeString} ${message}`); | ||
|
||
if (--duration < 0) { | ||
clearInterval(interval); | ||
window.location.reload(); | ||
} | ||
}, 1000); | ||
}; | ||
|
||
onZero = function () { | ||
window.location.reload(); | ||
}; | ||
|
||
time_difference = | ||
document.getElementById("time_difference") && | ||
document.getElementById("time_difference").value; | ||
} | ||
|
||
$("#timer").countdown(onZero, Math.ceil(time_difference)); | ||
updateTimer(); | ||
const interval = setInterval(updateTimer, 1000); | ||
} |
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.
Convert function declaration to function expression.
Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function startCountdown(duration) {
+ const startCountdown = (duration) => {
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Countdown timer | |
const timeDifference = $("#time_difference").val(); | |
if (timeDifference) { | |
startCountdown(Math.ceil(timeDifference)); | |
} | |
function startCountdown(duration) { | |
const timer = $("#timer"); | |
const message = "before poll closes"; | |
function updateTimer() { | |
const timeString = formatTime(duration); | |
timer.html(`${timeString} ${message}`); | |
if (--duration < 0) { | |
clearInterval(interval); | |
window.location.reload(); | |
} | |
}, 1000); | |
}; | |
onZero = function () { | |
window.location.reload(); | |
}; | |
time_difference = | |
document.getElementById("time_difference") && | |
document.getElementById("time_difference").value; | |
} | |
$("#timer").countdown(onZero, Math.ceil(time_difference)); | |
updateTimer(); | |
const interval = setInterval(updateTimer, 1000); | |
} | |
// Countdown timer | |
const timeDifference = $("#time_difference").val(); | |
if (timeDifference) { | |
startCountdown(Math.ceil(timeDifference)); | |
} | |
const startCountdown = (duration) => { | |
const timer = $("#timer"); | |
const message = "before poll closes"; | |
function updateTimer() { | |
const timeString = formatTime(duration); | |
timer.html(`${timeString} ${message}`); | |
if (--duration < 0) { | |
clearInterval(interval); | |
window.location.reload(); | |
} | |
} | |
updateTimer(); | |
const interval = setInterval(updateTimer, 1000); | |
} |
function validateUrl(url) { | ||
const pattern = | ||
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i; | ||
return pattern.test(url); | ||
} |
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.
Convert function declaration to function expression.
Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function validateUrl(url) {
+ const validateUrl = (url) => {
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function validateUrl(url) { | |
const pattern = | |
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i; | |
return pattern.test(url); | |
} | |
const validateUrl = (url) => { | |
const pattern = | |
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i; | |
return pattern.test(url); | |
} |
657f4b5
to
c971cfe
Compare
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.
Actionable comments posted: 4
Outside diff range, codebase verification and nitpick comments (14)
approval_polls/templates/create.html (12)
14-14
: Improve accessibility with a descriptive heading.The heading "Create a Poll" is clear, but consider adding an aria-label for improved accessibility.
- <h1 class="mb-4">Create a Poll</h1> + <h1 class="mb-4" aria-label="Create a Poll">Create a Poll</h1>
21-21
: Improve accessibility with afor
attribute.The label for the poll question input should have a
for
attribute that matches the input'sid
for better accessibility.- <label for="question" class="form-label">Enter your poll question:</label> + <label for="question" class="form-label" for="question">Enter your poll question:</label>
28-28
: Improve validation feedback accessibility.The validation feedback for the poll question should be associated with the input field using
aria-describedby
for better accessibility.- {% if question_error %}<div class="invalid-feedback">{{ question_error }}</div>{% endif %} + {% if question_error %}<div id="questionError" class="invalid-feedback">{{ question_error }}</div>{% endif %} + <input type="text" class="form-control {% if question_error %}is-invalid{% endif %}" id="question" name="question" value="{{ question }}" placeholder="Who do you approve of?" aria-describedby="questionError">
48-48
: Improve validation feedback accessibility for poll options.The validation feedback for poll options should be associated with the input field using
aria-describedby
for better accessibility.- {% if i == 1 and choice_error %}<div class="invalid-feedback">{{ choice_error }}</div>{% endif %} + {% if i == 1 and choice_error %}<div id="choiceError{{ i }}" class="invalid-feedback">{{ choice_error }}</div>{% endif %} + <input type="text" class="form-control {% if i == 1 and choice_error %}is-invalid{% endif %}" id="choice{{ i }}" name="choice{{ i }}" maxlength="100" placeholder="Option Name" aria-describedby="choiceError{{ i }}">
52-54
: Ensure accessibility for the "Add Option" button.The "Add Option" button should have an aria-label for better accessibility.
- <button type="button" class="btn btn-outline-primary" id="add-choice"> + <button type="button" class="btn btn-outline-primary" id="add-choice" aria-label="Add Option">
65-71
: Ensure accessibility for radio buttons.The radio buttons for voting eligibility should have aria-labels for better accessibility.
- <input class="form-check-input" type="radio" name="radio-poll-type" id="anyoneVote" value="1" checked> + <input class="form-check-input" type="radio" name="radio-poll-type" id="anyoneVote" value="1" checked aria-label="Anyone (No restriction on the number of ballots per user)">
75-81
: Ensure accessibility for radio buttons.The radio buttons for voting eligibility should have aria-labels for better accessibility.
- <input class="form-check-input" type="radio" name="radio-poll-type" id="registeredVote" value="2"> + <input class="form-check-input" type="radio" name="radio-poll-type" id="registeredVote" value="2" aria-label="Only registered users (One ballot per user, requires login)">
86-91
: Ensure accessibility for the checkbox.The checkbox for poll visibility should have an aria-label for better accessibility.
- <input class="form-check-input" type="checkbox" id="pollVisibility" name="public-poll-visibility" checked> + <input class="form-check-input" type="checkbox" id="pollVisibility" name="public-poll-visibility" checked aria-label="List this poll on the home page">
97-101
: Ensure accessibility for the checkbox.The checkbox for the write-in option should have an aria-label for better accessibility.
- <input class="form-check-input" type="checkbox" id="showWriteIn" name="show-write-in"> + <input class="form-check-input" type="checkbox" id="showWriteIn" name="show-write-in" aria-label="Permit Write-in option">
105-109
: Ensure accessibility for the checkbox.The checkbox for showing the leading option in a different color should have an aria-label for better accessibility.
- <input class="form-check-input" type="checkbox" id="showLeadColor" name="show-lead-color" checked> + <input class="form-check-input" type="checkbox" id="showLeadColor" name="show-lead-color" checked aria-label="Show Leading option in different color">
113-115
: Ensure accessibility for the checkbox.The checkbox for email opt-in should have an aria-label for better accessibility.
- <input class="form-check-input" type="checkbox" id="showEmailOptIn" name="show-email-opt-in"> + <input class="form-check-input" type="checkbox" id="showEmailOptIn" name="show-email-opt-in" aria-label="Display option to opt-in for email communication">
120-125
: Ensure accessibility for the input field.The input field for tags should have an aria-label for better accessibility.
- <input type="text" class="form-control" id="tokenTagField" name="token-tags" placeholder="Enter keywords"> + <input type="text" class="form-control" id="tokenTagField" name="token-tags" placeholder="Enter keywords" aria-label="Enter keywords (comma-separated)">approval_polls/tests.py (1)
632-632
: Remove debug print statements.Remove any debug print statements from the test code to maintain a clean test suite.
- print(response.content)
approval_polls/views.py (1)
Line range hint
155-164
: Enhance error handling inget_polls
function.The
get_polls
function should include error handling for potential issues with pagination.def get_polls(request, poll_list, render_page, tag: str = ""): paginator = Paginator(poll_list, 5) page = request.GET.get("page") try: polls = paginator.page(page) except PageNotAnInteger: polls = paginator.page(1) except EmptyPage: polls = paginator.page(paginator.num_pages) except Exception as e: logger.error(f"Error in pagination: {str(e)}") return HttpResponseServerError(f"An error occurred: {str(e)}") return render(request, render_page, {"latest_poll_list": polls, "tag": tag})
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files ignored due to path filters (1)
pyproject.toml
is excluded by!**/*.toml
Files selected for processing (15)
- .github/workflows/django.yml (1 hunks)
- .trunk/configs/.bandit (1 hunks)
- approval_polls/settings.py (5 hunks)
- approval_polls/staticfiles/create.js (1 hunks)
- approval_polls/staticfiles/detail.js (1 hunks)
- approval_polls/staticfiles/my_polls.js (1 hunks)
- approval_polls/templates/base.html (2 hunks)
- approval_polls/templates/create.html (1 hunks)
- approval_polls/templates/detail.html (1 hunks)
- approval_polls/templates/index.html (3 hunks)
- approval_polls/templates/my_info.html (2 hunks)
- approval_polls/templates/my_polls.html (1 hunks)
- approval_polls/tests.py (6 hunks)
- approval_polls/urls.py (1 hunks)
- approval_polls/views.py (6 hunks)
Files skipped from review due to trivial changes (1)
- approval_polls/templates/base.html
Files skipped from review as they are similar to previous changes (8)
- .trunk/configs/.bandit
- approval_polls/settings.py
- approval_polls/staticfiles/create.js
- approval_polls/templates/detail.html
- approval_polls/templates/index.html
- approval_polls/templates/my_info.html
- approval_polls/templates/my_polls.html
- approval_polls/urls.py
Additional comments not posted (28)
.github/workflows/django.yml (1)
30-31
: Approved: Addition of static files compression step.The addition of
python manage.py compress
before running tests is a good practice to ensure that static files are properly handled in a production-like environment.approval_polls/staticfiles/my_polls.js (7)
2-5
: Approved: Safe method check for CSRF protection.The
csrfSafeMethod
function correctly identifies safe HTTP methods that do not require CSRF protection.
6-6
: Approved: Transition to button for delete action.Using buttons for delete actions improves accessibility and user interaction.
25-26
: Approved: Conditional check for existing alert.The conditional check ensures that duplicate alerts are not created.
29-35
: Approved: Event handlers for confirm and cancel actions.The event handlers for confirm and cancel actions are correctly set up.
38-38
: Approved: CSRF token retrieval.Retrieving the CSRF token from the DOM is a good practice for secure AJAX requests.
39-57
: Approved: AJAX request for poll deletion.The AJAX request is correctly set up to handle poll deletion with proper CSRF token management and success/error callbacks.
60-61
: Approved: Simplified cancel action.The
cancelAction
function correctly removes the alert without additional processing.approval_polls/staticfiles/detail.js (10)
2-8
: Approved: Social sharing setup.The social sharing setup with
jsSocials
is correctly implemented.
10-15
: Approved: Poll options object.The
pollOptions
object centralizes references to key DOM elements, enhancing clarity and reducing redundancy.
18-35
: Approved: Addition of choice fields.The
addChoiceField
function is correctly implemented to append new poll options efficiently.
37-37
: Approved: Event handler for adding choice fields.The event handler for adding choice fields is correctly set up.
39-47
: Approved: Event handler for adding links.The event handler for adding links is correctly set up, and the
createAlertDiv
function is used to generate alert boxes.
61-77
: Approved: Event handler for confirming links.The event handler for confirming links is correctly set up, including URL validation and user feedback.
79-88
: Approved: Event handler for removing links.The event handler for removing links is correctly set up.
90-93
: Approved: Event handler for canceling link actions.The event handler for canceling link actions is correctly set up.
101-106
: Approved: Countdown timer initialization.The countdown timer initialization is correctly set up.
95-99
: Convert function declaration to function expression.Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function validateUrl(url) { + const validateUrl = (url) => {Likely invalid or redundant comment.
approval_polls/templates/create.html (5)
4-7
: Ensure CDN reliability.The external stylesheet link to Bootstrap Tokenfield is updated to use a CDN. Ensure that the CDN is reliable and consider adding a fallback mechanism in case the CDN is unavailable.
8-9
: Ensure script integrity and availability.The external script link to Bootstrap Tokenfield is updated to use a CDN. Ensure the CDN is reliable and consider adding a fallback mechanism. Additionally, ensure the integrity attribute is used to verify the script's integrity.
23-23
: Ensure consistent validation feedback.The class
is-invalid
is conditionally applied to the input field for validation feedback. Ensure that the validation logic is consistent and covers all possible error states.
33-33
: Ensure uniqueid
attributes for dynamically generated elements.The
id
attributes for poll options are dynamically generated. Ensure that they are unique and do not conflict with other elements on the page.
37-37
: Ensure consistent validation feedback for poll options.The class
is-invalid
is conditionally applied to the input field for validation feedback. Ensure that the validation logic is consistent and covers all possible error states.approval_polls/tests.py (2)
625-626
: Update assertions to match expected tag case.The assertions in
test_poll_tag_exists
have been updated to match the expected tag case. Ensure that this change aligns with the application's requirements for tag case consistency.
639-640
: Update assertions to match expected tag case.The assertions in
test_poll_delete
have been updated to match the expected tag case. Ensure that this change aligns with the application's requirements for tag case consistency.approval_polls/views.py (3)
6-6
: Ensure proper usage of themessages
module.The
messages
module from Django is imported. Ensure that it is used correctly in thedelete_poll
function for providing user feedback.
125-125
: Ensure proper handling of tag filtering.The
get_polls
function is updated to accept an extra parameter for filtering polls based on tags. Ensure that the tag filtering logic is correctly implemented and aligns with the application's requirements.
257-268
: Ensure robust error handling and logging indelete_poll
function.The
delete_poll
function includes error handling and logging. Ensure that the error messages are informative and that the logging provides sufficient context for debugging.
var pollId = $(target).attr("id").split("-").pop(); | ||
var alertDiv = |
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.
Improve readability by using template literals for alert messages.
The alertDiv
creation can be improved by using template literals for better readability.
- var alertDiv =
- "<div class='alert alert-danger' id='alert" +
- pollId +
- "'>" +
- "<p>This poll will be permanently deleted. " +
- "<button id='confirm-delete-" +
- pollId +
- "' type='button' class='btn btn-danger btn-xs'>Delete</button> " +
- "<button id='cancel-delete-" +
- pollId +
- "' type='button' class='btn btn-primary btn-xs'>Cancel</button>" +
- "</p></div>";
+ var alertDiv = `
+ <div class='alert alert-danger' id='alert${pollId}'>
+ <p>This poll will be permanently deleted.
+ <button id='confirm-delete-${pollId}' type='button' class='btn btn-danger btn-xs'>Delete</button>
+ <button id='cancel-delete-${pollId}' type='button' class='btn btn-primary btn-xs'>Cancel</button>
+ </p>
+ </div>`;
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
var pollId = $(target).attr("id").split("-").pop(); | |
var alertDiv = | |
var pollId = $(target).attr("id").split("-").pop(); | |
var alertDiv = ` | |
<div class='alert alert-danger' id='alert${pollId}'> | |
<p>This poll will be permanently deleted. | |
<button id='confirm-delete-${pollId}' type='button' class='btn btn-danger btn-xs'>Delete</button> | |
<button id='cancel-delete-${pollId}' type='button' class='btn btn-primary btn-xs'>Cancel</button> | |
</p> | |
</div>`; |
function formatTime(totalSeconds) { | ||
const days = Math.floor(totalSeconds / 86400); | ||
const hours = Math.floor((totalSeconds % 86400) / 3600); | ||
const minutes = Math.floor(((totalSeconds % 86400) % 3600) / 60); | ||
const seconds = Math.floor(((totalSeconds % 86400) % 3600) % 60); | ||
return `${days}d:${padZero(hours)}h:${padZero(minutes)}m:${padZero(seconds)}s`; | ||
} | ||
|
||
function padZero(num) { | ||
return num.toString().padStart(2, "0"); | ||
} |
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.
Convert function declarations to function expressions.
Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function formatTime(totalSeconds) {
+ const formatTime = (totalSeconds) => {
- function padZero(num) {
+ const padZero = (num) => {
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function formatTime(totalSeconds) { | |
const days = Math.floor(totalSeconds / 86400); | |
const hours = Math.floor((totalSeconds % 86400) / 3600); | |
const minutes = Math.floor(((totalSeconds % 86400) % 3600) / 60); | |
const seconds = Math.floor(((totalSeconds % 86400) % 3600) % 60); | |
return `${days}d:${padZero(hours)}h:${padZero(minutes)}m:${padZero(seconds)}s`; | |
} | |
function padZero(num) { | |
return num.toString().padStart(2, "0"); | |
} | |
const formatTime = (totalSeconds) => { | |
const days = Math.floor(totalSeconds / 86400); | |
const hours = Math.floor((totalSeconds % 86400) / 3600); | |
const minutes = Math.floor(((totalSeconds % 86400) % 3600) / 60); | |
const seconds = Math.floor(((totalSeconds % 86400) % 3600) % 60); | |
return `${days}d:${padZero(hours)}h:${padZero(minutes)}m:${padZero(seconds)}s`; | |
} | |
const padZero = (num) => { | |
return num.toString().padStart(2, "0"); | |
} |
function startCountdown(duration) { | ||
const timer = $("#timer"); | ||
const message = "before poll closes"; | ||
|
||
function updateTimer() { | ||
const timeString = formatTime(duration); | ||
timer.html(`${timeString} ${message}`); | ||
|
||
if (--duration < 0) { | ||
clearInterval(interval); | ||
window.location.reload(); | ||
} | ||
}, 1000); | ||
}; | ||
|
||
onZero = function () { | ||
window.location.reload(); | ||
}; | ||
|
||
time_difference = | ||
document.getElementById("time_difference") && | ||
document.getElementById("time_difference").value; | ||
} |
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.
Convert function declaration to function expression.
Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function startCountdown(duration) {
+ const startCountdown = (duration) => {
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function startCountdown(duration) { | |
const timer = $("#timer"); | |
const message = "before poll closes"; | |
function updateTimer() { | |
const timeString = formatTime(duration); | |
timer.html(`${timeString} ${message}`); | |
if (--duration < 0) { | |
clearInterval(interval); | |
window.location.reload(); | |
} | |
}, 1000); | |
}; | |
onZero = function () { | |
window.location.reload(); | |
}; | |
time_difference = | |
document.getElementById("time_difference") && | |
document.getElementById("time_difference").value; | |
} | |
const startCountdown = (duration) => { | |
const timer = $("#timer"); | |
const message = "before poll closes"; | |
function updateTimer() { | |
const timeString = formatTime(duration); | |
timer.html(`${timeString} ${message}`); | |
if (--duration < 0) { | |
clearInterval(interval); | |
window.location.reload(); | |
} | |
} |
function createAlertDiv(choiceId) { | ||
return ` | ||
<div class="alert alert-info" id="alert-${choiceId}"> | ||
<input type="text" class="form-control mb-2" id="url-${choiceId}" placeholder="Link to URL"> | ||
<button id="confirm-link-${choiceId}" type="button" class="btn btn-success btn-sm me-2">Insert Link</button> | ||
<button id="remove-link-${choiceId}" type="button" class="btn btn-danger btn-sm me-2">Reset Link</button> | ||
<button id="cancel-link-${choiceId}" type="button" class="btn btn-secondary btn-sm">Cancel</button> | ||
</div> | ||
`; | ||
} |
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.
Convert function declaration to function expression.
Avoid function declarations inside blocks to prevent hoisting issues. Use function expressions instead.
- function createAlertDiv(choiceId) {
+ const createAlertDiv = (choiceId) => {
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function createAlertDiv(choiceId) { | |
return ` | |
<div class="alert alert-info" id="alert-${choiceId}"> | |
<input type="text" class="form-control mb-2" id="url-${choiceId}" placeholder="Link to URL"> | |
<button id="confirm-link-${choiceId}" type="button" class="btn btn-success btn-sm me-2">Insert Link</button> | |
<button id="remove-link-${choiceId}" type="button" class="btn btn-danger btn-sm me-2">Reset Link</button> | |
<button id="cancel-link-${choiceId}" type="button" class="btn btn-secondary btn-sm">Cancel</button> | |
</div> | |
`; | |
} | |
const createAlertDiv = (choiceId) => { | |
return ` | |
<div class="alert alert-info" id="alert-${choiceId}"> | |
<input type="text" class="form-control mb-2" id="url-${choiceId}" placeholder="Link to URL"> | |
<button id="confirm-link-${choiceId}" type="button" class="btn btn-success btn-sm me-2">Insert Link</button> | |
<button id="remove-link-${choiceId}" type="button" class="btn btn-danger btn-sm me-2">Reset Link</button> | |
<button id="cancel-link-${choiceId}" type="button" class="btn btn-secondary btn-sm">Cancel</button> | |
</div> | |
`; | |
} |
No description provided.