Commit 28c85d5f by solashirai

naming changes (partial), fixes to Mustache template usage

parent 7ee2a4c5
...@@ -115,7 +115,7 @@ class CrowdsourceHinter(XBlock): ...@@ -115,7 +115,7 @@ class CrowdsourceHinter(XBlock):
data['submittedanswer']: The string of text that the student submits for a problem. data['submittedanswer']: The string of text that the student submits for a problem.
returns: returns:
'Hints': the highest rated hint for an incorrect answer 'bestHint': the highest rated hint for an incorrect answer
or another random hint for an incorrect answer or another random hint for an incorrect answer
or 'Sorry, there are no more hints for this answer.' if no more hints exist or 'Sorry, there are no more hints for this answer.' if no more hints exist
""" """
...@@ -146,12 +146,12 @@ class CrowdsourceHinter(XBlock): ...@@ -146,12 +146,12 @@ class CrowdsourceHinter(XBlock):
# currently set by default to True # currently set by default to True
if best_hint not in self.Reported.keys(): if best_hint not in self.Reported.keys():
self.Used.append(best_hint) self.Used.append(best_hint)
return {'Hints': best_hint, "StudentAnswer": answer} return {'BestHint': best_hint, "StudentAnswer": answer}
if best_hint not in self.Used: if best_hint not in self.Used:
# choose highest rated hint for the incorrect answer # choose highest rated hint for the incorrect answer
if best_hint not in self.Reported.keys(): if best_hint not in self.Reported.keys():
self.Used.append(best_hint) self.Used.append(best_hint)
return {'Hints': best_hint, "StudentAnswer": answer} return {'BestHint': best_hint, "StudentAnswer": answer}
# choose another random hint for the answer. # choose another random hint for the answer.
temporary_hints_list = [] temporary_hints_list = []
for hint_keys in self.hint_database[str(answer)]: for hint_keys in self.hint_database[str(answer)]:
...@@ -160,12 +160,12 @@ class CrowdsourceHinter(XBlock): ...@@ -160,12 +160,12 @@ class CrowdsourceHinter(XBlock):
temporary_hints_list.append(str(hint_keys)) temporary_hints_list.append(str(hint_keys))
not_used = random.choice(temporary_hints_list) not_used = random.choice(temporary_hints_list)
self.Used.append(not_used) self.Used.append(not_used)
return {'Hints': not_used, "StudentAnswer": answer} return {'BestHint': not_used, "StudentAnswer": answer}
# find generic hints for the student if no specific hints exist # find generic hints for the student if no specific hints exist
if len(self.generic_hints) != 0: if len(self.generic_hints) != 0:
not_used = random.choice(self.generic_hints) not_used = random.choice(self.generic_hints)
self.Used.append(not_used) self.Used.append(not_used)
return {'Hints': not_used, "StudentAnswer": answer} return {'BestHint': not_used, "StudentAnswer": answer}
else: else:
# if there are no more hints left in either the database or defaults # if there are no more hints left in either the database or defaults
self.Used.append(str("There are no hints for" + " " + answer)) self.Used.append(str("There are no hints for" + " " + answer))
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
flex-direction: column; flex-direction: column;
} }
.csh_HintQuickFeedback{ .csh_hint_rating_on_incorrect{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
......
<script type='x-tmpl/mustache' id='show_hint_feedback'> <script type='x-tmpl/mustache' id='show_hint_contribution'>
<div class='csh_hint_value' value="{{hintvalue}}"> <div class='csh_hint_value' value="{{hintText}}">
<div class='csh_hint_data'> <div class='csh_hint_data'>
<div class="csh_hint"><b>{{hint}}</b></div> <div class="csh_hint"><b>{{hintText}}</b></div>
</div> </div>
<div class='csh_rating_data'> <div class='csh_rating_data'>
<div role="button" class="csh_rate_hint" data-rate="upvote"> <div role="button" class="csh_rate_hint" data-rate="upvote">
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
</div> </div>
</script> </script>
<script type="x-tmpl/mustache" id="show_reported_feedback"> <script type="x-tmpl/mustache" id="show_reported_moderation">
<div class="csh_hint_value" value ="{{hint}}"> <div class="csh_hint_value" value ="{{reportedHintText}}">
<div class="csh_hint">{{hint}}</div> <div class="csh_hint">{{reportedHintText}}</div>
<div role="button" class="csh_staff_rate" data-rate="unreport" aria-label="unreport"> <div role="button" class="csh_staff_rate" data-rate="unreport" aria-label="unreport">
<u><b>Return hint for use in the hinter</b></u> <u><b>Return hint for use in the hinter</b></u>
</div> </div>
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
</div> </div>
</script> </script>
<script type="x-tmpl/mustache" id="student_hint_creation"> <script type="x-tmpl/mustache" id="hint_text_input">
<p> <p>
<input type="text" name="studentinput" class="csh_student_text_input"> <input type="text" name="studentinput" class="csh_student_text_input">
</p> </p>
...@@ -44,10 +44,10 @@ ...@@ -44,10 +44,10 @@
</div> </div>
</script> </script>
<script type="x-tmpl/mustache" id="show_answer_feedback"> <script type="x-tmpl/mustache" id="show_student_submission">
<div class="csh_student_answer"> <div class="csh_student_answer">
<h class="csh_answer_text" answer={{answer}}> <h class="csh_answer_text" answer={{answer}}>
<i> Improve hints for this question by leaving feedback on this hint or contributing your own! </i> <br> <i> Improve hints for this question by rating hints as helpful/unhelpful or contributing your own! </i> <br>
<i> Your original answer was: {{answer}} </i></h> <i> Your original answer was: {{answer}} </i></h>
</div> </div>
</script> </script>
...@@ -62,9 +62,9 @@ ...@@ -62,9 +62,9 @@
<div class="crowdsourcehinter_block"> <div class="crowdsourcehinter_block">
<div class='csh_hint_reveal'> <div class='csh_hint_reveal'>
<div class='csh_Hints' student_answer = '' hint_received=''> <div class='csh_hint_text' student_answer = '' hint_received=''>
</div> </div>
<div class='csh_HintQuickFeedback'> <div class='csh_hint_rating_on_incorrect'>
<div role="button" class="csh_rate_hint" data-rate="upvote" title="This hint was helpful!"> <div role="button" class="csh_rate_hint" data-rate="upvote" title="This hint was helpful!">
<b>+</b> <b>+</b>
</div> </div>
...@@ -81,10 +81,7 @@ ...@@ -81,10 +81,7 @@
<div class="crowdsourcehinter_block"> <div class="crowdsourcehinter_block">
<section class="csh_correct"></section> <section class="csh_correct"></section>
<p> <div class="csh_student_submission">
<span class='Thankyou'></span>
</p>
<div class="csh_feedback">
<div class="csh_reported_hints"> <div class="csh_reported_hints">
<span>moderate reported hints</span> <span>moderate reported hints</span>
</div> </div>
......
...@@ -3,7 +3,6 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -3,7 +3,6 @@ function CrowdsourceHinter(runtime, element, data){
var executeHinter = true; var executeHinter = true;
var isShowingHintFeedback = false; var isShowingHintFeedback = false;
var voted = false; var voted = false;
var correctSubmission = false;
$(".crowdsourcehinter_block", element).hide(); $(".crowdsourcehinter_block", element).hide();
...@@ -25,24 +24,27 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -25,24 +24,27 @@ function CrowdsourceHinter(runtime, element, data){
Logger.listen('seq_goto', null, stopScript); Logger.listen('seq_goto', null, stopScript);
/** /**
* Get a hint to show to the student after incorrectly answering a question. * Get a hint from the server to show to the student after incorrectly answering a
* question. On success, continue to showHint.
* @param data is data generated by the problem_graded event * @param data is data generated by the problem_graded event
*/ */
function get_hint(data){ function getHint(problemGradedEvent){
$(".crowdsourcehinter_block", element).show(); $(".crowdsourcehinter_block", element).show();
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: runtime.handlerUrl(element, 'get_hint'), url: runtime.handlerUrl(element, 'get_hint'),
data: JSON.stringify({"submittedanswer": unescape(data[0])}), data: JSON.stringify({"submittedanswer": unescape(problemGradedEvent[0])}),
success: showHint success: showHint
}); });
} }
/** /**
* Start student hint feedback. This function is called after the student answers * Start student hint contribution. This will allow students to contribute new hints
* to the hinter as well as vote on the helpfulness of the first hint they received
* for the current problem. This function is called after the student answers
* the question correctly. * the question correctly.
*/ */
function start_feedback(){ function startHintContribution(){
$('.csh_correct', element).show(); $('.csh_correct', element).show();
$(".csh_hint_reveal", element).hide(); $(".csh_hint_reveal", element).hide();
//send empty data for ajax call because not having a data field causes error //send empty data for ajax call because not having a data field causes error
...@@ -50,11 +52,25 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -50,11 +52,25 @@ function CrowdsourceHinter(runtime, element, data){
type: "POST", type: "POST",
url: runtime.handlerUrl(element, 'get_feedback'), url: runtime.handlerUrl(element, 'get_feedback'),
data: JSON.stringify({}), data: JSON.stringify({}),
success: showStudentContribution success: setStudentContribution
}); });
} }
/** /**
* Check whether or not the question was correctly answered by the student.
* The current method of checking the correctness of the answer is very brittle
* since we simply look for a string within the problemGradedEventData.
* @param problemGradedEventData is the data from problem_graded event.
*/
function checkIsAnswerCorrect(problemGradedEventData){
if (problemGradedEventData[1].search(/class="correct/) === -1){
return false;
} else {
return true;
}
}
/**
* Check whether student answered the question correctly and call the appropriate * Check whether student answered the question correctly and call the appropriate
* function afterwards. Current method for determining correctness if very brittle. * function afterwards. Current method for determining correctness if very brittle.
* @param event_type, element are both unused but automatically passed * @param event_type, element are both unused but automatically passed
...@@ -63,82 +79,59 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -63,82 +79,59 @@ function CrowdsourceHinter(runtime, element, data){
function onStudentSubmission(){ return function(event_type, data, element){ function onStudentSubmission(){ return function(event_type, data, element){
//search method of correctness of problem is brittle due to checking for a class within //search method of correctness of problem is brittle due to checking for a class within
//the problem block. //the problem block.
if (data[1].search(/class="correct/) === -1){ if (checkIsAnswerCorrect(data)){
get_hint(data); startHintContribution();
} else { //if the correct answer is submitted } else { //if the submitted answer is incorrect
start_feedback(); getHint(data);
} }
}} }}
Logger.listen('problem_graded', data.hinting_element, onStudentSubmission()); Logger.listen('problem_graded', data.hinting_element, onStudentSubmission());
/** /**
* Modify csh_Hints attributes to show hint to the student. * Modify csh_hint_text attributes to show hint to the student.
*/ */
function showHint(result){ function showHint(result){
$('.csh_Hints', element).attr('student_answer', result.StudentAnswer); $('.csh_hint_text', element).attr('student_answer', result.StudentAnswer);
$('.csh_Hints', element).attr('hint_received', result.Hints); $('.csh_hint_text', element).attr('hint_received', result.BestHint);
$('.csh_Hints', element).text("Hint: " + result.Hints); $('.csh_hint_text', element).text("Hint: " + result.BestHint);
Logger.log('crowd_hinter.showHint', {"student_answer": result.StudentAnswer, "hint_received": result.Hints}); Logger.log('crowd_hinter.showHint', {"student_answer": result.StudentAnswer, "hint_received": result.Hints});
} }
/** /**
* Called by showStudentContribution to append hints into divs created by * Called by setStudentContribution to append hints into divs created by
* showStudentSubmissoinHistory, after the student answered the question correctly. * showStudentSubmissoinHistory, after the student answered the question correctly.
* Feedback on hints at this stage consists of upvote/downvote/report buttons. * Feedback on hints at this stage consists of upvote/downvote/report buttons.
* @param hint is the first hint that was shown to the student * @param hint is the first hint that was shown to the student
* @param student_answer is the first incorrect answer submitted by the student * @param student_answer is the first incorrect answer submitted by the student
*/ */
function showHintFeedback(hint, student_answer){ function showStudentHintContribution(hint, student_answer){
$(".csh_student_answer", element).each(function(){ var hintContributionTemplate = $(Mustache.render($('#show_hint_contribution').html(), {hintText: hint}));
if ($(this).find('.csh_answer_text').attr('answer') == student_answer){ $('.csh_answer_text', element).append(hintContributionTemplate);
var html = ""; var hintCreationTemplate = $(Mustache.render($('#add_hint_creation').html(), {}));
$(function(){ $('.csh_answer_text', element).append(hintCreationTemplate);
var data = {
hint: hint
};
html = Mustache.render($("#show_hint_feedback").html(), data);
});
$(this).append(html);
var html = "";
var template = $('#add_hint_creation').html();
var data = {};
html = Mustache.render(template, data);
$(this).append(html);
}
});
} }
/** /**
* Show options to remove or return reported hints from/to the hint pool. Called after * Show options to remove or return reported hints from/to the hint pool. Called after
* correctly answering the question, only visible to staff. * correctly answering the question, only visible to staff.
* @param result is the reported hint text * A better method of moderating hints should probably be implemented in the future. Hints
* only can be moderated after being reported, so unreported hints will stay in the system.
* @param reportedHint is the reported hint text
*/ */
function showReportedFeedback(result){ function showReportedModeration(reportedHint){
var html = ""; var reportedModerationTemplate = $(Mustache.render($('#show_reported_moderation').html(), {reportedHintText: reportedHint}));
$(function(){ $('.csh_reported_hints', element).append(reportedModerationTemplate);
var template = $('#show_reported_feedback').html();
var data = {
hint: result
};
html = Mustache.render(template, data);
});
$(".csh_reported_hints", element).append(html);
} }
/** /**
* Append new divisions into html for each answer the student submitted before correctly * Append new divisions into html for each answer the student submitted before correctly
* answering the question. showHintFeedback appends new hints into these divs. * answering the question. showStudentHintContribution appends new hints into these divs.
* When the hinter is set to show best, only one div will be created. * When the hinter is set to show best, only one div will be created.
* @param student_answers is the text of the student's incorrect answer * @param student_answers is the text of the student's incorrect answer
*/ */
function showStudentSubmissionHistory(student_answers){ function showStudentSubmissionHistory(student_answer){
var html = ""; var showStudentSubmissionTemplate = $(Mustache.render($('#show_student_submission').html(), {answer: student_answer}));
var template = $('#show_answer_feedback').html(); $('.csh_student_submission', element).append(showStudentSubmissionTemplate);
var data = {
answer: student_answers
};
html = Mustache.render(template, data);
$(".csh_feedback", element).append(html);
} }
/** /**
...@@ -149,7 +142,7 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -149,7 +142,7 @@ function CrowdsourceHinter(runtime, element, data){
* @param result is a dictionary of incorrect answers and hints, with the index being the hint and the value * @param result is a dictionary of incorrect answers and hints, with the index being the hint and the value
* being the incorrect answer * being the incorrect answer
*/ */
function showStudentContribution(result){ function setStudentContribution(result){
//Set up the student feedback stage. Each student answer and all answer-specific hints for that answer are shown //Set up the student feedback stage. Each student answer and all answer-specific hints for that answer are shown
//to the student, as well as an option to create a new hint for an answer. //to the student, as well as an option to create a new hint for an answer.
if(data.isStaff){ if(data.isStaff){
...@@ -157,7 +150,7 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -157,7 +150,7 @@ function CrowdsourceHinter(runtime, element, data){
$.each(result, function(index, value) { $.each(result, function(index, value) {
if(value == "Reported") { if(value == "Reported") {
//index represents the reported hint's text //index represents the reported hint's text
showReportedFeedback(index); showReportedModeration(index);
} }
}); });
} }
...@@ -169,24 +162,14 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -169,24 +162,14 @@ function CrowdsourceHinter(runtime, element, data){
hint = index; hint = index;
//hints return null if no answer-specific hints exist //hints return null if no answer-specific hints exist
if(hint === "null"){ if(hint === "null"){
$(".csh_student_answer", element).each(function(){ var noHintsTemplate = $(Mustache.render($('#show_no_hints').html(), {}));
if ($(this).find('.csh_answer_text').attr('answer') == student_answer){ $('.csh_student_answer', element).append(noHintsTemplate);
var html = ""; var hintCreationTemplate = $(Mustache.render($('#add_hint_creation').html(), {}));
var template = $('#show_no_hints').html(); $('.csh_student_answer', element).append(hintCreationTemplate);
var data = {};
html = Mustache.render(template, data);
$(this).append(html);
var html = "";
var template = $('#add_hint_creation').html();
var data = {};
html = Mustache.render(template, data);
$(this).append(html);
}
});
} }
//reported hints have their corresponding answer set to "Reported" //reported hints have their corresponding answer set to "Reported"
else { else {
showHintFeedback(hint, student_answer); showStudentHintContribution(hint, student_answer);
} }
} }
}); });
...@@ -197,27 +180,19 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -197,27 +180,19 @@ function CrowdsourceHinter(runtime, element, data){
/** /**
* Create a text input area for the student to create a new hint. This function * Create a text input area for the student to create a new hint. This function
* is triggered by clicking the "contribute a new hint" button. * is triggered by clicking the "contribute a new hint" button.
* @param clicked is the "contribute a new hint" button that was clicked * @param createTextInputButtonHTML is the "contribute a new hint" button that was clicked
*/ */
function create_text_input(){ return function(clicked){ function create_text_input(){ return function(createTextInputButtonHTML){
$('.csh_student_hint_creation', element).each(function(){ $('.csh_student_hint_creation', element).each(function(){
$(clicked.currentTarget).show(); $(createTextInputButtonHTML.currentTarget).show();
}); });
$('.csh_student_text_input', element).remove(); $('.csh_student_text_input', element).remove();
$('.csh_submit_new', element).remove(); $('.csh_submit_new', element).remove();
$(clicked.currentTarget).hide(); $(createTextInputButtonHTML.currentTarget).hide();
student_answer = $(clicked.currentTarget).parent().parent().find('.csh_answer_text').attr('answer'); student_answer = $('.csh_answer_text', element).attr('answer');
$(".csh_student_answer", element).each(function(){ console.log(student_answer);
if ($('.csh_answer_text', element).attr('answer') == student_answer){ var hintTextInputTemplate = $(Mustache.render($('#hint_text_input').html(), {student_answer: student_answer}));
var html = ""; $('.csh_answer_text', element).append(hintTextInputTemplate);
$(function(){
var template = $('#student_hint_creation').html();
var data = {student_answer: student_answer};
html = Mustache.render(template, data);
});
$(this).append(html);
}
});
}} }}
$(element).on('click', '.csh_student_hint_creation', create_text_input($(this))); $(element).on('click', '.csh_student_hint_creation', create_text_input($(this)));
...@@ -225,22 +200,22 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -225,22 +200,22 @@ function CrowdsourceHinter(runtime, element, data){
* Submit a new hint created by the student to the hint pool. Hint text is in * Submit a new hint created by the student to the hint pool. Hint text is in
* the text input area created by create_text_input. Contributed hints are specific to * the text input area created by create_text_input. Contributed hints are specific to
* incorrect answers. Triggered by clicking the "submit hint" button. * incorrect answers. Triggered by clicking the "submit hint" button.
* @param clicked is the "submit hint" button clicked * @param submitHintButtonHTML is the "submit hint" button clicked
*/ */
function submit_new_hint(){ return function(clicked){ function submit_new_hint(){ return function(submitHintButtonHTML){
//add the newly created hint to the hinter's pool of hints //add the newly created hint to the hinter's pool of hints
if($('.csh_student_text_input', element).val().length > 0){ if($('.csh_student_text_input', element).val().length > 0){
var answerdata = unescape(clicked.currentTarget.attributes['answer'].value); var studentAnswer = unescape(submitHintButtonHTML.currentTarget.attributes['answer'].value);
var newhint = unescape($('.csh_student_text_input').val()); var newHint = unescape($('.csh_student_text_input').val());
$('.csh_submitbutton', element).show(); $('.csh_submitbutton', element).show();
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: runtime.handlerUrl(element, 'add_new_hint'), url: runtime.handlerUrl(element, 'add_new_hint'),
data: JSON.stringify({"submission": newhint, "answer": answerdata}), data: JSON.stringify({"submission": newHint, "answer": studentAnswer}),
success: Logger.log('crowd_hinter.submit_new.click.event', {"student_answer": answerdata, "new_hint_submission": newhint}) success: Logger.log('crowd_hinter.submit_new.click.event', {"student_answer": studentAnswer, "new_hint_submission": newHint})
}); });
$('.csh_student_text_input', element).remove(); $('.csh_student_text_input', element).remove();
$(clicked.currentTarget).remove(); $(submitHintButtonHTML.currentTarget).remove();
} }
}} }}
$(element).on('click', '.csh_submit_new', submit_new_hint($(this))); $(element).on('click', '.csh_submit_new', submit_new_hint($(this)));
...@@ -249,16 +224,16 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -249,16 +224,16 @@ function CrowdsourceHinter(runtime, element, data){
* Send vote data to modify a hint's rating (or mark it as reported). Triggered by * Send vote data to modify a hint's rating (or mark it as reported). Triggered by
* clicking a button to upvote, downvote, or report the hint (both before and after * clicking a button to upvote, downvote, or report the hint (both before and after
* the student correctly submits an answer). * the student correctly submits an answer).
* @param clicked is the rate_hint button clicked (upvote/downvote/report) * @param rateHintButtonHTML is the rate_hint button clicked (upvote/downvote/report)
*/ */
function rate_hint(){ return function(clicked){ function rate_hint(){ return function(rateHintButtonHTML){
rating = clicked.currentTarget.attributes['data-rate'].value; rating = rateHintButtonHTML.currentTarget.attributes['data-rate'].value;
if(!voted || rating=="report"){ if(!voted || rating=="report"){
if (rating == "report"){ if (rating == "report"){
alert("This hint has been reported for review."); alert("This hint has been reported for review.");
} }
hint = $('.csh_Hints', element).attr('hint_received'); hint = $('.csh_hint_text', element).attr('hint_received');
student_answer = $('.csh_Hints', element).attr('student_answer'); student_answer = $('.csh_hint_text', element).attr('student_answer');
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: runtime.handlerUrl(element, 'rate_hint'), url: runtime.handlerUrl(element, 'rate_hint'),
...@@ -286,11 +261,11 @@ function CrowdsourceHinter(runtime, element, data){ ...@@ -286,11 +261,11 @@ function CrowdsourceHinter(runtime, element, data){
/** /**
* Send staff rating data to determine whether or not a reported hint will be removed from the * Send staff rating data to determine whether or not a reported hint will be removed from the
* hint pool or not. Triggered by clicking a staff_rate button. * hint pool or not. Triggered by clicking a staff_rate button.
* @param clicked is the csh_staff_rate button that was clicked * @param staffRateHintButtonHTML is the csh_staff_rate button that was clicked
*/ */
function staff_rate_hint(){ return function(clicked){ function staff_rate_hint(){ return function(staffRateHintButtonHTML){
hint = $(clicked.currentTarget).parent().find(".csh_hint").text(); hint = $(staffRateHintButtonHTML.currentTarget).parent().find(".csh_hint").text();
rating = clicked.currentTarget.attributes['data-rate'].value rating = staffRateHintButtonHTML.currentTarget.attributes['data-rate'].value
student_answer = "Reported"; student_answer = "Reported";
Logger.log('crowd_hinter.staff_rate_hint.click.event', {"hint": hint, "student_answer": student_answer, "rating": rating}); Logger.log('crowd_hinter.staff_rate_hint.click.event', {"hint": hint, "student_answer": student_answer, "rating": rating});
$.ajax({ $.ajax({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment