Compare commits
10 Commits
v0.2.1
...
3025e83b66
Author | SHA1 | Date | |
---|---|---|---|
3025e83b66 | |||
de6910b4bf | |||
500beed4cc | |||
fac3839ea3 | |||
a050a1eccf | |||
43cc0a5652 | |||
1f60054d46 | |||
418dfe7a70 | |||
e1e279e939 | |||
7fe1afb348 |
@ -2,12 +2,7 @@
|
||||
|
||||
# Source https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71
|
||||
|
||||
if ! [ -x "$(command -v docker)" ]; then
|
||||
echo 'Error: docker is not installed.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v compose)" ]; then
|
||||
if ! [ -x "$(command -v docker compose)" ]; then
|
||||
echo 'Error: docker compose is not installed.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
@ -1,4 +1,4 @@
|
||||
from .config import Production as Config
|
||||
from .config import Development as Config
|
||||
from .install import install_app
|
||||
from .models import User
|
||||
from .extensions import bootstrap, csrf, db, login_manager, mail
|
||||
@ -31,7 +31,7 @@ def create_app():
|
||||
def _check_cookie_consent():
|
||||
if request.cookies.get('cookie_consent'):
|
||||
return
|
||||
if any([ request.path.startswith(x) for x in [ '/admin/static/', '/root/', '/quiz/static', '/cookies/' ] ]):
|
||||
if any([ request.path.startswith(x) for x in [ '/admin/static/', '/root/', '/quiz/static', '/cookies/', '/admin/editor/static' ] ]):
|
||||
return
|
||||
flash(f'<strong>Cookie Consent</strong>: This web site only stores minimal, functional cookies. It does not store any tracking information. By using this site, you consent to this use of cookies. For more information, see our <a href="{url_for("views._privacy")}">privacy policy</a>.', 'cookie_alert')
|
||||
|
||||
@ -46,11 +46,13 @@ def create_app():
|
||||
from .api.views import api
|
||||
from .quiz.views import quiz
|
||||
from .views import views
|
||||
from .editor.views import editor
|
||||
|
||||
app.register_blueprint(admin, url_prefix='/admin')
|
||||
app.register_blueprint(api, url_prefix='/api')
|
||||
app.register_blueprint(views)
|
||||
app.register_blueprint(quiz)
|
||||
app.register_blueprint(editor, url_prefix='/admin/editor')
|
||||
|
||||
install_app(app)
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
if not os.getenv('DATA'):
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv('../.env')
|
||||
|
||||
|
0
ref-test/app/editor/__init__.py
Normal file
0
ref-test/app/editor/__init__.py
Normal file
46
ref-test/app/editor/static/css/editor.css
Normal file
46
ref-test/app/editor/static/css/editor.css
Normal file
@ -0,0 +1,46 @@
|
||||
.accordion-button {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
display: block;
|
||||
border: 1px solid rgb(0 0 0 / .3);
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.editor-controls {
|
||||
width: fit-content;
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.editor-controls a {
|
||||
margin: 0 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.accordion-button div {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
.accordion-button a {
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
right: 0%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.accordion-button::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.accordion-error {
|
||||
background-color: #bb2d3b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.accordion-error:not(.collapsed) {
|
||||
background-color: #bb2d3b;
|
||||
color: white;
|
||||
}
|
260
ref-test/app/editor/static/css/style.css
Normal file
260
ref-test/app/editor/static/css/style.css
Normal file
@ -0,0 +1,260 @@
|
||||
body {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
background-color: lightgray;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.site-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.form-display {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-heading {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-label-group {
|
||||
position: relative;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-label-group input,
|
||||
.form-label-group label {
|
||||
padding: var(--input-padding-y) var(--input-padding-x);
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
.form-label-group label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0; /* Override default `<label>` margin */
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
cursor: text; /* Match the input under the label */
|
||||
border: 1px solid transparent;
|
||||
border-radius: .25rem;
|
||||
transition: all .1s ease-in-out;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.form-label-group input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 0%;
|
||||
border-bottom: 2px solid #585858;
|
||||
}
|
||||
|
||||
.form-label-group input:active, .form-label-group input:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.form-label-group input::-webkit-input-placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.form-label-group input:-ms-input-placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.form-label-group input::-ms-input-placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.form-label-group input::-moz-placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.form-label-group input::placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.form-label-group input:not(:placeholder-shown) {
|
||||
padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3));
|
||||
padding-bottom: calc(var(--input-padding-y) / 3);
|
||||
}
|
||||
|
||||
.form-label-group input:not(:placeholder-shown) ~ label {
|
||||
padding-top: calc(var(--input-padding-y) / 3);
|
||||
padding-bottom: calc(var(--input-padding-y) / 3);
|
||||
font-size: 12px;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.form-check {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.checkbox input {
|
||||
transform: scale(1.5);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.signin-forgot-password {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
.form-submission-button {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-submission-button button, .form-submission-button a {
|
||||
margin: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.form-submission-button button span, .form-submission-button button svg, .form-submission-button a span, .form-submission-button a svg {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
table.dataTable {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.row-actions {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dt-buttons {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
float:none;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.row-actions button, .row-actions a {
|
||||
margin: 0px 5px;
|
||||
}
|
||||
|
||||
#cookie-alert {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
#dismiss-cookie-alert {
|
||||
margin-top: 16px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.alert-db-empty {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
font-size: 14pt;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.form-date-input, .form-select-input {
|
||||
position: relative;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.form-date-input input,
|
||||
.form-date-input label, .form-select-input select, .form-select-input label {
|
||||
padding: var(--input-padding-y) var(--input-padding-x);
|
||||
font-size: 16pt;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #585858;
|
||||
}
|
||||
|
||||
.datepicker::-webkit-calendar-picker-indicator {
|
||||
border: 1px;
|
||||
border-color: gray;
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
.form-date-input label, .form-select-input label {
|
||||
/* position: absolute; */
|
||||
/* top: 0;
|
||||
left: 0; */
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0; /* Override default `<label>` margin */
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
cursor: text; /* Match the input under the label */
|
||||
border: 1px solid transparent;
|
||||
border-radius: .25rem;
|
||||
transition: all .1s ease-in-out;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.form-upload {
|
||||
margin: 2rem 0;
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
.result-action-buttons, .test-action {
|
||||
margin: 5px auto;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.accordion-item {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
/* Change Autocomplete styles in Chrome*/
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
textarea:-webkit-autofill,
|
||||
textarea:-webkit-autofill:hover,
|
||||
textarea:-webkit-autofill:focus,
|
||||
select:-webkit-autofill,
|
||||
select:-webkit-autofill:hover,
|
||||
select:-webkit-autofill:focus {
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
|
||||
/* Fallback for Edge
|
||||
-------------------------------------------------- */
|
||||
@supports (-ms-ime-align: auto) {
|
||||
.form-label-group label {
|
||||
display: none;
|
||||
}
|
||||
.form-label-group input::-ms-input-placeholder {
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback for IE
|
||||
-------------------------------------------------- */
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
.form-label-group label {
|
||||
display: none;
|
||||
}
|
||||
.form-label-group input:-ms-input-placeholder {
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
|
BIN
ref-test/app/editor/static/favicon.ico
Normal file
BIN
ref-test/app/editor/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
ref-test/app/editor/static/favicon.png
Normal file
BIN
ref-test/app/editor/static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
68
ref-test/app/editor/static/js/editor.js
Normal file
68
ref-test/app/editor/static/js/editor.js
Normal file
@ -0,0 +1,68 @@
|
||||
const root = $('#editor-root')
|
||||
|
||||
var data = [
|
||||
{
|
||||
"type": "question",
|
||||
"q_no": 3,
|
||||
"text": "The ball is gathered by the defensive division and a player throws it forward, hitting the korf in the other division.",
|
||||
"options": [
|
||||
"Play on",
|
||||
"Opposite team restart under their defensive post",
|
||||
"Opposite team restart where the ball was thrown from"
|
||||
],
|
||||
"correct": 0,
|
||||
"q_type": "Multiple Choice",
|
||||
"tags": [
|
||||
"scoring from the defence zone"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "question",
|
||||
"q_no": 4,
|
||||
"text": "At the end of the game, after the final whistle, a coach loudly confronts the referee. What can the referee do?",
|
||||
"options": [
|
||||
"Ignore the coach as the match has ended",
|
||||
"Ignore the coach or inform the coach they will be reported",
|
||||
"Ignore the coach, inform the coach they will be reported and/or show the coach a card"
|
||||
],
|
||||
"correct": 2,
|
||||
"q_type": "List",
|
||||
"tags": [
|
||||
"discipline"
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (data[i]['type'] == 'question') {
|
||||
var obj = `
|
||||
<div class="accordion-item" id="i${i}">
|
||||
<h2 class="accordion-header" id="h${i}">
|
||||
<div class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c${i}" aria-expanded="true" aria-controls="c${i}">
|
||||
<div class="float-start">Question ${i+1}</div>
|
||||
<a href="javascript:void(0)" class="btn btn-success panel-control" data-id="${i}" data-action="remove">X</a>
|
||||
</div>
|
||||
</h2>
|
||||
<div id="c${i}" class="accordion-collapse collapse" aria-labelledby="h${i}" data-bs-parent="#editor-root">
|
||||
<div class="accordion-body">
|
||||
<div class="question-text">
|
||||
${data[i]['text']}
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
${data[i]['options'].join("</li><li>")}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
root.append(obj)
|
||||
}
|
||||
}
|
||||
|
||||
$('.panel-control').click(function(event) {
|
||||
console.log($(this).data('id'))
|
||||
var id = $(this).data('id')
|
||||
$(`#i${id}`).remove()
|
||||
})
|
2
ref-test/app/editor/static/js/jquery-3.6.0.min.js
vendored
Normal file
2
ref-test/app/editor/static/js/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
233
ref-test/app/editor/static/js/script.js
Normal file
233
ref-test/app/editor/static/js/script.js
Normal file
@ -0,0 +1,233 @@
|
||||
// Menu Highlight Scripts
|
||||
const menuItems = document.getElementsByClassName('nav-link');
|
||||
for(let i = 0; i < menuItems.length; i++) {
|
||||
if(menuItems[i].pathname == window.location.pathname) {
|
||||
menuItems[i].classList.add('active');
|
||||
}
|
||||
}
|
||||
const dropdownItems = document.getElementsByClassName('dropdown-item');
|
||||
for(let i = 0; i< dropdownItems.length; i++) {
|
||||
if(dropdownItems[i].pathname == window.location.pathname) {
|
||||
dropdownItems[i].classList.add('active');
|
||||
$( "#" + dropdownItems[i].id ).closest( '.dropdown' ).find('.dropdown-toggle').addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
// General Post Method Form Processing Script
|
||||
$('form.form-post').submit(function(event) {
|
||||
|
||||
var $form = $(this);
|
||||
var data = $form.serialize();
|
||||
var url = $(this).prop('action');
|
||||
var rel_success = $(this).data('rel-success');
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.redirect_to) {
|
||||
window.location.href = response.redirect_to;
|
||||
}
|
||||
else {
|
||||
window.location.href = rel_success;
|
||||
}
|
||||
},
|
||||
error: function(response) {
|
||||
error_response(response);
|
||||
}
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Form Upload Questions - Special case, needs to handle files.
|
||||
$('form[name=form-upload-questions]').submit(function(event) {
|
||||
|
||||
var $form = $(this);
|
||||
var data = new FormData($form[0]);
|
||||
var file = $('input[name=data_file]')[0].files[0]
|
||||
data.append('file', file)
|
||||
|
||||
$.ajax({
|
||||
url: window.location.pathname,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
window.location.reload();
|
||||
},
|
||||
error: function(response) {
|
||||
error_response(response);
|
||||
}
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Edit and Delete Test Button Handlers
|
||||
$('.test-action').click(function(event) {
|
||||
|
||||
let id = $(this).data('id');
|
||||
let action = $(this).data('action');
|
||||
|
||||
if (action == 'delete' || action == 'start' || action == 'end') {
|
||||
$.ajax({
|
||||
url: `/admin/tests/edit/`,
|
||||
type: 'POST',
|
||||
data: JSON.stringify({'id': id, 'action': action}), // TODO Change how CRUD operations work
|
||||
contentType: 'application/json',
|
||||
success: function(response) {
|
||||
window.location.href = '/admin/tests/';
|
||||
},
|
||||
error: function(response){
|
||||
error_response(response);
|
||||
},
|
||||
});
|
||||
} else if (action == 'edit') {
|
||||
window.location.href = `/admin/test/${id}/`
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Edit Dataset Button Handlers
|
||||
$('.edit-question-dataset').click(function(event) {
|
||||
|
||||
var filename = $(this).data('filename');
|
||||
var action = $(this).data('action');
|
||||
var disabled = $(this).hasClass('disabled');
|
||||
|
||||
if ( !disabled ) {
|
||||
$.ajax({
|
||||
url: `/admin/settings/questions/${action}/`,
|
||||
type: 'POST',
|
||||
data: JSON.stringify({'filename': filename}),
|
||||
contentType: 'application/json',
|
||||
success: function(response) {
|
||||
window.location.reload();
|
||||
},
|
||||
error: function(response){
|
||||
error_response(response);
|
||||
},
|
||||
});
|
||||
};
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
function error_response(response) {
|
||||
|
||||
const $alert = $("#alert-box");
|
||||
$alert.html('');
|
||||
|
||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||
$alert.html(`
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||
${response.responseJSON.error}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
`);
|
||||
} else if (response.responseJSON.error instanceof Array) {
|
||||
var output = ''
|
||||
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
||||
output += `
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||
${response.responseJSON.error[i]}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
`;
|
||||
$alert.html(output);
|
||||
}
|
||||
}
|
||||
|
||||
$alert.focus()
|
||||
}
|
||||
|
||||
// Dismiss Cookie Alert
|
||||
$('#dismiss-cookie-alert').click(function(event){
|
||||
|
||||
$.ajax({
|
||||
url: '/cookies/',
|
||||
type: 'POST',
|
||||
data: {
|
||||
time: Date.now()
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response){
|
||||
console.log(response);
|
||||
},
|
||||
error: function(response){
|
||||
console.log(response);
|
||||
}
|
||||
})
|
||||
|
||||
event.preventDefault();
|
||||
})
|
||||
|
||||
// Script for Result Actions
|
||||
$('.result-action-buttons').click(function(event){
|
||||
|
||||
var id = $(this).data('id');
|
||||
|
||||
if ($(this).data('result-action') == 'generate') {
|
||||
$.ajax({
|
||||
url: '/admin/certificate/',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({'id': id}),
|
||||
contentType: 'application/json',
|
||||
dataType: 'html',
|
||||
success: function(response) {
|
||||
var display_window = window.open();
|
||||
display_window.document.write(response);
|
||||
},
|
||||
error: function(response){
|
||||
error_response(response);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
var action = $(this).data('result-action')
|
||||
$.ajax({
|
||||
url: window.location.href,
|
||||
type: 'POST',
|
||||
data: JSON.stringify({'id': id, 'action': action}),
|
||||
contentType: 'application/json',
|
||||
success: function(response) {
|
||||
if (action == 'delete') {
|
||||
window.location.href = '/admin/results/';
|
||||
} else window.location.reload();
|
||||
},
|
||||
error: function(response){
|
||||
error_response(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Script for Deleting Time Adjustment
|
||||
$('.adjustment-delete').click(function(event){
|
||||
|
||||
var user_code = $(this).data('user_code');
|
||||
var location = window.location.href;
|
||||
location = location.replace('#', '')
|
||||
|
||||
$.ajax({
|
||||
url: location + 'delete-adjustment/',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({'user_code': user_code}),
|
||||
contentType: 'application/json',
|
||||
success: function(response) {
|
||||
window.location.reload();
|
||||
},
|
||||
error: function(response){
|
||||
error_response(response);
|
||||
},
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
});
|
80
ref-test/app/editor/templates/editor/components/base.html
Normal file
80
ref-test/app/editor/templates/editor/components/base.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
|
||||
crossorigin="anonymous">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('.static', filename='css/style.css') }}"
|
||||
/>
|
||||
{% block style %}
|
||||
{% endblock %}
|
||||
<title>{% block title %} SKA Referee Test | Admin Console {% endblock %}</title>
|
||||
{% include "editor/components/og-meta.html" %}
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
||||
{% block navbar %}
|
||||
{% include "editor/components/navbar.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="container">
|
||||
{% block top_alerts %}
|
||||
{% include "editor/components/server-alerts.html" %}
|
||||
{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<footer class="container site-footer mt-5">
|
||||
{% block footer %}
|
||||
{% include "editor/components/footer.html" %}
|
||||
{% endblock %}
|
||||
</footer>
|
||||
|
||||
<!-- JQuery, Popper, and Bootstrap js dependencies -->
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
<script>
|
||||
window.jQuery || document.write(`<script src="{{ url_for('.static', filename='js/jquery-3.6.0.min.js') }}"><\/script>`)
|
||||
</script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"
|
||||
integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
|
||||
integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<!-- Custom js -->
|
||||
<script type="text/javascript">
|
||||
var csrf_token = "{{ csrf_token() }}";
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{ url_for('.static', filename='js/script.js') }}"
|
||||
></script>
|
||||
{% block script %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1 @@
|
||||
<div id="alert-box" tabindex="-1"></div>
|
@ -0,0 +1,28 @@
|
||||
{% extends "editor/components/base.html" %}
|
||||
{% block datatable_css %}
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/dataTables.bootstrap5.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/2.0.1/css/buttons.bootstrap5.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/colreorder/1.5.5/css/colReorder.bootstrap5.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedheader/3.2.0/css/fixedHeader.bootstrap5.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.6.4/css/keyTable.bootstrap5.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.bootstrap5.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/searchbuilder/1.3.0/css/searchBuilder.dataTables.min.css"/>
|
||||
{% endblock %}
|
||||
{% block datatable_scripts %}
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/dataTables.bootstrap5.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/dataTables.buttons.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.bootstrap5.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.colVis.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.html5.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.print.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/colreorder/1.5.5/js/dataTables.colReorder.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/fixedheader/3.2.0/js/dataTables.fixedHeader.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/keytable/2.6.4/js/dataTables.keyTable.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/responsive.bootstrap5.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/searchbuilder/1.3.0/js/dataTables.searchBuilder.min.js"></script>
|
||||
{% endblock %}
|
@ -0,0 +1,2 @@
|
||||
<p>This web app was developed by Vivek Santayana. The source code for the web app, excluding any data pertaining to the questions in the quiz, is freely available at <a href="https://git.vsnt.uk/viveksantayana/ska-referee-test">Vivek’s personal GIT repository</a> under an MIT License.</p>
|
||||
<p>All questions in the test are © The Scottish Korfball Association {{ now.year }}. All rights are reserved.</p>
|
@ -0,0 +1,4 @@
|
||||
{% extends "admin/components/base.html" %}
|
||||
{% import "bootstrap/wtf.html" as wtf %}
|
||||
{% block top_alerts %}
|
||||
{% endblock %}
|
111
ref-test/app/editor/templates/editor/components/navbar.html
Normal file
111
ref-test/app/editor/templates/editor/components/navbar.html
Normal file
@ -0,0 +1,111 @@
|
||||
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbar"
|
||||
aria-controls="navbar"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle Navigation"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse justify-content-end" id="navbar">
|
||||
<ul class="navbar-nav">
|
||||
{% if not current_user.is_authenticated %}
|
||||
<li class="nav-item" id="nav-login">
|
||||
<a href="{{ url_for('admin._login') }}" id="link-login" class="nav-link">Log In</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-item" id="nav-results">
|
||||
<a href="{{ url_for('admin._view_entries') }}" id="link-results" class="nav-link">View Results</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="nav-tests">
|
||||
<a
|
||||
class="nav-link dropdown-toggle"
|
||||
id="dropdown-tests"
|
||||
role="button"
|
||||
href="{{ url_for('admin._tests') }}"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Exams
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
aria-labelledby="dropdown-settings"
|
||||
>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._tests', filter='active') }}" id="link-active" class="dropdown-item">Active</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._tests', filter='scheduled') }}" id="link-scheduled" class="dropdown-item">Scheduled</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._tests', filter='expired') }}" id="link-expired" class="dropdown-item">Expired</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._tests', filter='all') }}" id="link-all" class="dropdown-item">All</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._tests', filter='create') }}" id="link-create" class="dropdown-item">Create</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="nav-settings">
|
||||
<a
|
||||
class="nav-link dropdown-toggle"
|
||||
id="dropdown-account"
|
||||
role="button"
|
||||
href="{{ url_for('admin._settings') }}"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Settings
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
aria-labelledby="dropdown-settings"
|
||||
>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._settings') }}" id="link-settings" class="dropdown-item">View Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Question Datasets</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="nav-account">
|
||||
<a
|
||||
class="nav-link dropdown-toggle"
|
||||
id="dropdown-account"
|
||||
role="button"
|
||||
href="{{ url_for('admin._update_user', id=current_user.id) }}"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Account
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
aria-labelledby="dropdown-account"
|
||||
>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._update_user', id=current_user.id) }}" id="link-account" class="dropdown-item">Account Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('admin._logout') }}" id="link-logout" class="dropdown-item">Log Out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
18
ref-test/app/editor/templates/editor/components/og-meta.html
Normal file
18
ref-test/app/editor/templates/editor/components/og-meta.html
Normal file
@ -0,0 +1,18 @@
|
||||
<meta name="description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||
<meta property="og:locale" content="en_UK" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||
<meta property="og:url" content="{{ url_for(request.endpoint, _external = True, **(request.view_args or {})) }}" />
|
||||
<meta property="og:site_name" content="Scottish Korfball Association Referee Theory Exam" />
|
||||
<meta property="og:image" content="{{ url_for('.static', filename='favicon.png', _external = True) }}" />
|
||||
<meta property="og:image:alt" content="Logo of the SKA Refereeing Exam App" />
|
||||
<meta property="og:image:width" content="512" />
|
||||
<meta property="og:image:height" content="512" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||
<meta name="twitter:image" content="{{ url_for('.static', filename='favicon.png', _external = True) }}" />
|
||||
<meta name="twitter:image:alt" content="Logo of the SKA Refereeing Exam App" />
|
||||
<meta name="twitter:creator" content="@viveksantayana" />
|
||||
<meta name="twitter:site" content="@viveksantayana" />
|
||||
<meta name="theme-color" content="#343a40" />
|
||||
<link rel="shortcut icon" href="{{ url_for('.static', filename='favicon.ico') }}">
|
@ -0,0 +1,23 @@
|
||||
<div class="navbar navbar-expand-sm navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<div class="expand navbar-expand justify-content-center" id="navbar_secondary">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin._tests', filter='active') }}">Active</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin._tests', filter='scheduled') }}">Scheduled</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin._tests', filter='expired') }}">Expired</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin._tests', filter='all') }}">All</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin._tests', filter='create') }}">Create</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,43 @@
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% set cookie_flash_flag = namespace(value=False) %}
|
||||
{% for category, message in messages %}
|
||||
{% if category == "error" %}
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill" title="Error" aria-title="Error"></i>
|
||||
{{ message|safe }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif category == "success" %}
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-check2-circle" title="Success" aria-title="Success"></i>
|
||||
{{ message|safe }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif category == "warning" %}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-info-circle-fill" aria-title="Warning" title="Warning"></i>
|
||||
{{ message|safe }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif category == "cookie_alert" %}
|
||||
{% if not cookie_flash_flag.value %}
|
||||
<div class="alert alert-primary alert-dismissible fade show" id="cookie-alert" role="alert">
|
||||
<i class="bi bi-info-circle-fill" title="Cookie Alert" aria-title="Cookie Alert"></i>
|
||||
{{ message|safe }}
|
||||
<div class="d-flex justify-content-center w-100">
|
||||
<button type="button" id="dismiss-cookie-alert" class="btn btn-success" data-bs-dismiss="alert" aria-label="Close">Accept</button>
|
||||
</div>
|
||||
</div>
|
||||
{% set cookie_flash_flag.value = True %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-info-circle-fill" title="Alert"></i>
|
||||
{{ message|safe }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
140
ref-test/app/editor/templates/editor/index.html
Normal file
140
ref-test/app/editor/templates/editor/index.html
Normal file
@ -0,0 +1,140 @@
|
||||
{% extends "editor/components/base.html" %}
|
||||
|
||||
{% block style %}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('.static', filename='css/editor.css') }}"
|
||||
/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Editor</h1>
|
||||
<div class="container editor-panel" id="editor" tabindex="-1">
|
||||
<div class="accordion" id="editor-root">
|
||||
<div class="accordion-item" id="i0">
|
||||
<h2 class="accordion-header" id="h0">
|
||||
<div class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c0" aria-expanded="true" aria-controls="c0">
|
||||
<div class="float-start">Question 1</div>
|
||||
<a href="javascript:void(0)" class="btn btn-success panel-control" data-id="0" data-action="remove">X</a>
|
||||
</div>
|
||||
</h2>
|
||||
<div id="c0" class="accordion-collapse collapse" aria-labelledby="h0" data-bs-parent="#editor-root">
|
||||
<div class="accordion-body">
|
||||
<div class="question-text">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">Question 1</span>
|
||||
<textarea type="text" class="form-control" id="q0-text" aria-describedby="q0-text-caption">Placeholder for Question 1</textarea>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">Question Type</span>
|
||||
<select id="q0-type" class="form-select">
|
||||
<option value ="Multiple Choice">Multiple Choice</option>
|
||||
<option value="Yes/No">Yes/No</option>
|
||||
<option value="List">Ordered List</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="q0-options" class="form-label">Options</label>
|
||||
<ul class="options" id="q0-options">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-options-0-caption">0</span>
|
||||
<input type="text" class="form-control" value="Text for Option 1" id="q0-options-0" aria-describedby="q0-options-0-caption">
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-options-1-caption">1</span>
|
||||
<input type="text" class="form-control" value="Text for Option 2" id="q0-options-1" aria-describedby="q-options-1-caption">
|
||||
</div>
|
||||
</ul>
|
||||
<div class="editor-controls">
|
||||
<a href="" class="btn btn-danger" data-action="Cancel">Cancel</a>
|
||||
<a href="" class="btn btn-success" data-action="Done">Done</a>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-correct-caption">Correct</span>
|
||||
<input type="text" class="form-control" value="0" id="q0-correct" aria-describedby="q0-correct-caption">
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-tags-caption">Tags</span>
|
||||
<textarea type="text" class="form-control" value="Foo" id="q0-tags" aria-describedby="q0-tags-caption">List of tags
List of tags</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item" id="i1">
|
||||
<h2 class="accordion-header" id="h1">
|
||||
<div class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c1" aria-expanded="true" aria-controls="c1">
|
||||
<div class="float-start">Block 1</div>
|
||||
<a href="javascript:void(0)" class="btn btn-success panel-control" data-id="0" data-action="remove">X</a>
|
||||
</div>
|
||||
</h2>
|
||||
<div id="c1" class="accordion-collapse collapse" aria-labelledby="h1" data-bs-parent="#editor-root">
|
||||
<div class="accordion-body">
|
||||
<div class="accordion" id="b1">
|
||||
<div class="accordion-item" id="b1-item">
|
||||
<h2 class="accordion-header" id="h2">
|
||||
<div class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#c2" aria-expanded="true" aria-controls="c2">
|
||||
<div class="float-start">Question 1</div>
|
||||
<a href="javascript:void(0)" class="btn btn-success panel-control" data-id="0" data-action="remove">X</a>
|
||||
</div>
|
||||
</h2>
|
||||
<div id="c2" class="accordion-collapse collapse" aria-labelledby="h2" data-bs-parent="#b1">
|
||||
<div class="accordion-body">
|
||||
<div class="question-text">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">Question 1</span>
|
||||
<textarea type="text" class="form-control" id="q0-text" aria-describedby="q0-text-caption">Placeholder for Question 1</textarea>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">Question Type</span>
|
||||
<select id="q0-type" class="form-select">
|
||||
<option value ="Multiple Choice">Multiple Choice</option>
|
||||
<option value="Yes/No">Yes/No</option>
|
||||
<option value="List">Ordered List</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="q1-options" class="form-label">Options</label>
|
||||
<ul class="options" id="q1-options">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-options-0-caption">0</span>
|
||||
<input type="text" class="form-control" value="Text for Option 1" id="q0-options-0" aria-describedby="q0-options-0-caption">
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-options-1-caption">1</span>
|
||||
<input type="text" class="form-control" value="Text for Option 2" id="q0-options-1" aria-describedby="q-options-1-caption">
|
||||
</div>
|
||||
</ul>
|
||||
<div class="editor-controls">
|
||||
<a href="" class="btn btn-danger" data-action="Cancel">Cancel</a>
|
||||
<a href="" class="btn btn-success" data-action="Done">Done</a>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-correct-caption">Correct</span>
|
||||
<input type="text" class="form-control" value="0" id="q0-correct" aria-describedby="q0-correct-caption">
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="q0-tags-caption">Tags</span>
|
||||
<textarea type="text" class="form-control" value="Foo" id="q0-tags" aria-describedby="q0-tags-caption">List of tags
List of tags</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "editor/components/client-alerts.html" %}
|
||||
<div class="editor-controls">
|
||||
<a href="" class="btn btn-danger" data-action="Cancel">Cancel</a>
|
||||
<a href="" class="btn btn-success" data-action="Done">Done</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{ url_for('.static', filename='js/editor.js') }}"
|
||||
></script>
|
||||
{% endblock %}
|
12
ref-test/app/editor/views.py
Normal file
12
ref-test/app/editor/views.py
Normal file
@ -0,0 +1,12 @@
|
||||
from flask import Blueprint, render_template
|
||||
|
||||
editor = Blueprint(
|
||||
name='editor',
|
||||
import_name=__name__,
|
||||
template_folder='templates',
|
||||
static_folder='static'
|
||||
)
|
||||
|
||||
@editor.route('/')
|
||||
def _editor():
|
||||
return render_template('/editor/index.html')
|
Reference in New Issue
Block a user