1329 theme settings (#1485)

* Adds `window.init.theme_settings` which is a JSON blob that's passed by CTFd and configurable from the Admin Panel
* Adds `config.html` which should be a form which can be loaded into the Admin Panel and able to emit a JSON blob which can be used as `window.init.theme_settings`.
* Closes #1329
This commit is contained in:
Kevin Chung
2020-06-12 00:09:56 -04:00
committed by GitHub
parent 3095032536
commit bd5e6d4552
15 changed files with 180 additions and 56 deletions

View File

@@ -1,3 +1,5 @@
import json
from CTFd.utils import get_config
from CTFd.utils.helpers import markup
@@ -18,5 +20,9 @@ class _ConfigsWrapper:
def theme_footer(self):
return markup(get_config("theme_footer", default=""))
@property
def theme_settings(self):
return json.loads(get_config("theme_settings", default="null"))
Configs = _ConfigsWrapper()

View File

@@ -1,4 +1,5 @@
@import "~codemirror/lib/codemirror.css";
.CodeMirror {
font-size: 12px;
border: 1px solid lightgray;
}

View File

@@ -251,6 +251,52 @@ $(() => {
}
);
const theme_settings_editor = CodeMirror.fromTextArea(
document.getElementById("theme-settings"),
{
lineNumbers: true,
lineWrapping: true,
mode: { name: "javascript", json: true }
}
);
// Handle refreshing codemirror when switching tabs.
// Better than the autorefresh approach b/c there's no flicker
$("a[href='#theme']").on("shown.bs.tab", function(e) {
theme_header_editor.refresh();
theme_footer_editor.refresh();
theme_settings_editor.refresh();
});
$("#theme-settings-modal form").submit(function(e) {
e.preventDefault();
theme_settings_editor
.getDoc()
.setValue(JSON.stringify($(this).serializeJSON(), null, 2));
$("#theme-settings-modal").modal("hide");
});
$("#theme-settings-button").click(function() {
let form = $("#theme-settings-modal form");
let data = JSON.parse(theme_settings_editor.getValue());
$.each(data, function(key, value) {
var ctrl = form.find(`[name='${key}']`);
switch (ctrl.prop("type")) {
case "radio":
case "checkbox":
ctrl.each(function() {
if ($(this).attr("value") == value) {
$(this).attr("checked", value);
}
});
break;
default:
ctrl.val(value);
}
});
$("#theme-settings-modal").modal();
});
insertTimezones($("#start-timezone"));
insertTimezones($("#end-timezone"));
insertTimezones($("#freeze-timezone"));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -17,6 +17,9 @@
<li class="nav-item">
<a class="nav-link rounded-0 active" href="#appearance" role="tab" data-toggle="tab">Appearance</a>
</li>
<li class="nav-item">
<a class="nav-link rounded-0" href="#theme" role="tab" data-toggle="tab">Theme</a>
</li>
<li class="nav-item">
<a class="nav-link rounded-0" href="#accounts" role="tab" data-toggle="tab">Accounts</a>
</li>
@@ -54,6 +57,8 @@
<div class="tab-content">
{% include "admin/configs/appearance.html" %}
{% include "admin/configs/theme.html" %}
{% include "admin/configs/accounts.html" %}
{% include "admin/configs/mlc.html" %}

View File

@@ -76,56 +76,6 @@
</div>
</div>
<div class="form-group">
<label for="ctf_theme">
Theme
<small class="form-text text-muted">
Switch themes to change CTFd's aesthetics
</small>
</label>
<select class="form-control custom-select" id="ctf_theme" name="ctf_theme">
<option>{{ ctf_theme }}</option>
{% for theme in themes %}
<option>{{ theme }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>
Theme Color
<small class="form-text text-muted">
Color used by theme to control aesthetics. Requires theme support.
</small>
</label>
<div class="d-inline-block">
<input type="color" id="config-color-picker">
<button type="button" class="btn-sm btn-primary" id="config-color-update">Update CSS</button>
</div>
</div>
<div class="form-group">
<label>
Theme Header
<small class="form-text text-muted">
Theme headers are inserted just before the <code>&lt;/head&gt;</code> tag on all public facing pages.
Requires theme support.
</small>
</label>
<textarea class="form-control" id="theme-header" name="theme_header" rows="7">{{ theme_header or "" }}</textarea>
</div>
<div class="form-group">
<label>
Theme Footer
<small class="form-text text-muted">
Theme footers are inserted just before the <code>&lt;/body&gt;</code> tag on all public facing pages.
Requires theme support.
</small>
</label>
<textarea class="form-control" id="theme-footer" name="theme_footer" rows="7">{{ theme_footer or "" }}</textarea>
</div>
<button type="submit" class="btn btn-md btn-primary float-right">Update</button>
</form>
</div>

View File

@@ -0,0 +1,83 @@
<div role="tabpanel" class="tab-pane config-section" id="theme">
<div class="modal fade" role="document" id="theme-settings-modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Theme Settings</h5>
</div>
<div class="modal-body">
{% include "config.html" ignore missing %}
</div>
</div>
</div>
</div>
<form method="POST" autocomplete="off" class="w-100">
<div class="form-group">
<label for="ctf_theme">
Theme
<small class="form-text text-muted">
Switch themes to change CTFd's aesthetics
</small>
</label>
<select class="form-control custom-select" id="ctf_theme" name="ctf_theme">
<option>{{ ctf_theme }}</option>
{% for theme in themes %}
<option>{{ theme }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>
Theme Color
<small class="form-text text-muted">
Color used by theme to control aesthetics. Requires theme support.
</small>
</label>
<div class="d-inline-block">
<input type="color" id="config-color-picker">
<button type="button" class="btn-sm btn-primary" id="config-color-update">Update CSS</button>
</div>
</div>
<div class="form-group">
<label>
Theme Header
<small class="form-text text-muted">
Theme headers are inserted just before the <code>&lt;/head&gt;</code> tag on all public facing pages.
Requires theme support.
</small>
</label>
<textarea class="form-control" id="theme-header" name="theme_header" rows="7">{{ theme_header or "" }}</textarea>
</div>
<div class="form-group">
<label>
Theme Footer
<small class="form-text text-muted">
Theme footers are inserted just before the <code>&lt;/body&gt;</code> tag on all public facing pages.
Requires theme support.
</small>
</label>
<textarea class="form-control" id="theme-footer" name="theme_footer" rows="7">{{ theme_footer or "" }}</textarea>
</div>
<div class="form-group">
<label for="ctf_theme">
Theme Settings
</label>
<div class="d-block pb-2">
<button type="button" class="btn-sm btn-primary" id="theme-settings-button">
Settings Editor
</button>
</div>
<textarea class="form-control" id="theme-settings" name="theme_settings" rows="7">{{ theme_settings or "" }}</textarea>
<small class="form-text text-muted">
Settings specific to the theme
</small>
</div>
<button type="submit" class="btn btn-md btn-primary float-right">Update</button>
</form>
</div>

View File

@@ -62,6 +62,26 @@ const displayChal = chal => {
$("#challenge-window").append(template.render(challenge_data));
let modal = $("#challenge-window").find(".modal-dialog");
if (
window.init.theme_settings &&
window.init.theme_settings.challenge_window_size
) {
switch (window.init.theme_settings.challenge_window_size) {
case "sm":
modal.addClass("modal-sm");
break;
case "lg":
modal.addClass("modal-lg");
break;
case "xl":
modal.addClass("modal-xl");
break;
default:
break;
}
}
$(".challenge-solves").click(function(event) {
getSolves($("#challenge-id").val());
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,6 +20,7 @@
'userId': {{ Session.id }},
'start': {{ Configs.start | tojson }},
'end': {{ Configs.end | tojson }},
'theme_settings': {{ Configs.theme_settings | tojson }}
}
</script>
{{ Configs.theme_header }}

View File

@@ -0,0 +1,12 @@
<form>
<div class="form-group">
<label>Challenge Window Size</label>
<select class="form-control custom-select" name="challenge_window_size">
<option value="sm">Small</option>
<option value="norm">Normal</option>
<option value="lg">Large</option>
<option value="xl">Extra Large</option>
</select>
</div>
<button type="submit" class="btn btn-primary float-right">Update</button>
</form>