mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-10-08 05:25:34 +00:00
Bootstrap 5 & remove jQuery (#661)
* build: move frontend assets to Bootstrap 5.3 and Tom Select * feat: migrate templates and scripts to Bootstrap 5, dropping jQuery Templates - Rename Bootstrap 4 classes and data attributes (data-bs-*, ms-/me-/ps-/pe-, text-start/end, form-group -> mb-3, input-group wrappers removed, badges, custom controls -> form-check/form-select, media -> d-flex, btn-close, ...) - Dark mode now uses Bootstrap 5.3 colour modes (data-bs-theme on <html>), set from <head> to avoid a flash of the light theme - Remove the Bootstrap 4 /bootstrap/ demo page - Contextual table classes (table-success etc.) still work as plain backgrounds for the rigboard key JavaScript - Remove jQuery: src.js/interaction.js and every inline template script are rewritten in plain JS on top of small helpers (helpers.js, bundled with Bootstrap into jpop.js so inline scripts can use them) - Replace bootstrap-select/ajax-bootstrap-select with Tom Select. The remote search now sends the `q` parameter and reads `text`, which is what the API actually uses (the old picker sent `term` and read `label`) - HTML fragments are fetched with an X-Requested-With header so views keep rendering their modal variants, and fetched scripts are re-executed - Remove the Bootstrap 4 plugin script includes * fix: stop rigboard header buttons stretching to full width under Bootstrap 5 * fix: restore dark-mode brand colours and contextual table colours under Bootstrap 5 * fix: brighten dark-mode links and make coloured card outlines visible * fix: calendar layout and grid borders, navbar search button width, guard missing modal links * fix: readable text in dark-mode Tom Select inputs and placeholders, restore form-control column widths * build: split the Dockerfile into cached stages with prod and dev targets * deploy: make compose.yml development-only with live reload, and document it * fix: keep badge text colour for links inside badges * fix: restore table-bordered grid lines in dark mode * fix: include Bootstrap's progress styles and keep progress labels readable
This commit is contained in:
@@ -1,101 +1,138 @@
|
||||
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
|
||||
//console.log('Changing selected value');
|
||||
obj.find('option').remove(); //Remove all the available options
|
||||
obj[0].add(new Option(text, pk, true, true)); // Add new option
|
||||
//obj.selectpicker('val', pk); //Set the new value to be selected
|
||||
obj.selectpicker('refresh');
|
||||
obj.change(); //Trigger the change function manually
|
||||
//console.log(obj);
|
||||
// Select widgets, built on Tom Select.
|
||||
// * <select class="ts-select" data-sourceurl="..."> loads its options remotely as the user types
|
||||
// * <select class="ts-select"> without a source URL is a plain (optionally multiple) select
|
||||
|
||||
var clearSelectionLabel = '(no selection)';
|
||||
|
||||
function changeSelectedValue(select, pk, text, update_url) { // Pass in the <select> and the new option's parameters
|
||||
var ts = select.tomselect;
|
||||
if (!ts) {
|
||||
return;
|
||||
}
|
||||
ts.clear(true);
|
||||
ts.clearOptions();
|
||||
ts.addOption({value: pk, text: text, update_url: update_url || ''});
|
||||
ts.setValue(pk, true);
|
||||
select.dispatchEvent(new Event('change', {bubbles: true})); // Trigger the change function manually
|
||||
}
|
||||
|
||||
function refreshUpdateHref(obj) {
|
||||
//console.log('Refreshing Update URL');
|
||||
targetObject = $('#'+obj.attr('id')+'-update');
|
||||
update_url = $('option:selected', obj).data('update_url');
|
||||
function selectedUpdateUrl(select) {
|
||||
var ts = select.tomselect;
|
||||
var value = ts.getValue();
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
var option = ts.options[value];
|
||||
if (option && option.update_url !== undefined) {
|
||||
return option.update_url;
|
||||
}
|
||||
var domOption = select.querySelector('option[value="' + CSS.escape(value) + '"]');
|
||||
return domOption && domOption.dataset.update_url ? domOption.dataset.update_url : null;
|
||||
}
|
||||
|
||||
if (update_url=="") { //Probably "clear selection" has been chosen
|
||||
//console.log('Trying to disable');
|
||||
targetObject.removeAttr('href');
|
||||
targetObject.addClass('disabled');
|
||||
} else {
|
||||
targetObject.prop('href', update_url);
|
||||
targetObject.removeClass('disabled');
|
||||
function refreshUpdateHref(select) {
|
||||
var target = document.getElementById(select.id + '-update');
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
var update_url = selectedUpdateUrl(select);
|
||||
|
||||
if (update_url === '') { // Nothing is selected
|
||||
target.removeAttribute('href');
|
||||
target.classList.add('disabled');
|
||||
} else if (update_url !== null) {
|
||||
target.setAttribute('href', update_url);
|
||||
target.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function initPicker(obj) {
|
||||
var options = {
|
||||
ajax: {
|
||||
url: obj.data('sourceurl'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
|
||||
// automatically replace it with the value of the search query.
|
||||
data: {
|
||||
term: '{{{q}}}'
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: ''
|
||||
},
|
||||
clearOnEmpty:false,
|
||||
//log: 3,
|
||||
preprocessData: function (data) {
|
||||
var i, l = data.length, array = [];
|
||||
if (!obj.data('noclear')) {
|
||||
array.push({
|
||||
text: clearSelectionLabel,
|
||||
value: '',
|
||||
data:{
|
||||
update_url: '',
|
||||
subtext:''
|
||||
}
|
||||
});
|
||||
}
|
||||
function initPicker(select) {
|
||||
var noClear = 'noclear' in select.dataset;
|
||||
var multiple = select.multiple;
|
||||
var url = select.dataset.sourceurl;
|
||||
var plugins = [];
|
||||
if (multiple) {
|
||||
plugins.push('remove_button');
|
||||
} else if (!noClear) {
|
||||
plugins.push('clear_button');
|
||||
}
|
||||
|
||||
if (l) {
|
||||
for(i = 0; i < l; i++){
|
||||
array.push($.extend(true, data[i], {
|
||||
text: data[i]['label'],
|
||||
value: data[i]['pk'],
|
||||
data:{
|
||||
update_url: data[i]['update'],
|
||||
subtext:''
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
var settings = {
|
||||
plugins: plugins,
|
||||
valueField: 'value',
|
||||
labelField: 'text',
|
||||
searchField: ['text'],
|
||||
allowEmptyOption: false,
|
||||
placeholder: select.dataset.noneSelectedText || '',
|
||||
maxOptions: null,
|
||||
hideSelected: false,
|
||||
closeAfterSelect: !multiple
|
||||
};
|
||||
//console.log(obj.data);
|
||||
if (!obj.data('noclear')) {
|
||||
obj.prepend($("<option></option>")
|
||||
.attr("value",'')
|
||||
.text(clearSelectionLabel)
|
||||
.data('update_url','')); //Add "clear selection" option
|
||||
|
||||
if (url) {
|
||||
Object.assign(settings, {
|
||||
// Results come from the server so don't filter them locally
|
||||
score: function () { return function () { return 1; }; },
|
||||
shouldLoad: function (query) { return query.length > 0; },
|
||||
load: function (query, callback) {
|
||||
var separator = url.indexOf('?') === -1 ? '?' : '&';
|
||||
fetch(url + separator + 'q=' + encodeURIComponent(query), {credentials: 'same-origin'})
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (data) {
|
||||
callback(data.map(function (item) {
|
||||
return {value: item.pk, text: item.text, update_url: item.update};
|
||||
}));
|
||||
})
|
||||
.catch(function () { callback(); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var update_urls = {};
|
||||
select.querySelectorAll('option[data-update_url]').forEach(function (option) {
|
||||
update_urls[option.value] = option.dataset.update_url;
|
||||
});
|
||||
|
||||
obj.selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
|
||||
obj.change(function(){ //on change, update the edit button href
|
||||
//console.log('Selectbox Changed');
|
||||
refreshUpdateHref(obj);
|
||||
});
|
||||
|
||||
refreshUpdateHref(obj); //Ensure href is correct at the beginning
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
clearSelectionLabel = '(no selection)';
|
||||
|
||||
$(".selectpicker").each(function(){initPicker($(this))});
|
||||
|
||||
//When update/edit modal box submitted
|
||||
$('#modal').on('hide.bs.modal', function (e) {
|
||||
if (modaltarget != undefined && modalobject != "") {
|
||||
//Update the selector with new values
|
||||
changeSelectedValue($(modaltarget),modalobject[0]['pk'],modalobject[0]['fields']['name'],modalobject[0]['update_url']);
|
||||
var ts = new TomSelect(select, settings);
|
||||
Object.keys(update_urls).forEach(function (value) {
|
||||
if (ts.options[value]) {
|
||||
ts.options[value].update_url = update_urls[value];
|
||||
}
|
||||
});
|
||||
|
||||
if (select.id && document.getElementById(select.id + '-update')) {
|
||||
select.addEventListener('change', function () { // on change, update the edit button href
|
||||
refreshUpdateHref(select);
|
||||
});
|
||||
refreshUpdateHref(select); // Ensure href is correct at the beginning
|
||||
}
|
||||
}
|
||||
|
||||
function initSelects(root) {
|
||||
if (typeof TomSelect === 'undefined') {
|
||||
return;
|
||||
}
|
||||
(root || document).querySelectorAll('select.ts-select').forEach(function (select) {
|
||||
if (!select.tomselect) {
|
||||
initPicker(select);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
initSelects();
|
||||
|
||||
//When update/edit modal box submitted
|
||||
var modal = document.getElementById('modal');
|
||||
if (modal) {
|
||||
modal.addEventListener('hide.bs.modal', function () {
|
||||
if (typeof modaltarget !== 'undefined' && modaltarget && typeof modalobject !== 'undefined' && modalobject !== "") {
|
||||
//Update the selector with new values
|
||||
var select = document.querySelector(modaltarget);
|
||||
if (select) {
|
||||
changeSelectedValue(select, modalobject[0]['pk'], modalobject[0]['fields']['name'], modalobject[0]['update_url']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
138
pipeline/source_assets/js/helpers.js
Normal file
138
pipeline/source_assets/js/helpers.js
Normal file
@@ -0,0 +1,138 @@
|
||||
// Helpers used by inline template scripts, so this is loaded in <head> (bundled with Bootstrap as jpop.js)
|
||||
// Run fn once the DOM is ready
|
||||
function ready(fn) {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
// Delegated event handler: on(document, 'click', '.selector', function (e) { this === matched element })
|
||||
function on(root, type, selector, handler) {
|
||||
root.addEventListener(type, function (e) {
|
||||
var target = e.target instanceof Element ? e.target.closest(selector) : null;
|
||||
if (target && root.contains(target)) {
|
||||
handler.call(target, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// fetch() for endpoints that return HTML fragments: the X-Requested-With header makes the server
|
||||
// render the ajax (modal) variant of a page, as jQuery's .load()/.post() used to
|
||||
function ajaxFetch(url, options) {
|
||||
options = Object.assign({credentials: 'same-origin'}, options);
|
||||
options.headers = Object.assign({'X-Requested-With': 'XMLHttpRequest'}, options.headers);
|
||||
return fetch(url, options);
|
||||
}
|
||||
|
||||
// Replace the contents of an element with an HTML string, executing any <script>s it contains
|
||||
function setHtml(element, html) {
|
||||
element.innerHTML = html;
|
||||
element.querySelectorAll('script').forEach(function (old) {
|
||||
var script = document.createElement('script');
|
||||
Array.from(old.attributes).forEach(function (attr) { script.setAttribute(attr.name, attr.value); });
|
||||
script.text = old.text;
|
||||
old.replaceWith(script);
|
||||
});
|
||||
initTooltips(element);
|
||||
initPopovers(element);
|
||||
if (typeof initSelects === 'function') {
|
||||
initSelects(element);
|
||||
}
|
||||
}
|
||||
|
||||
function initTooltips(root) {
|
||||
(root || document).querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) {
|
||||
bootstrap.Tooltip.getOrCreateInstance(el);
|
||||
});
|
||||
}
|
||||
|
||||
// Popovers may contain <ins>/<del> (used by the activity feed's diffs)
|
||||
function popoverOptions() {
|
||||
var allowList = bootstrap.Tooltip.Default.allowList;
|
||||
allowList.ins = [];
|
||||
allowList.del = [];
|
||||
return {allowList: allowList};
|
||||
}
|
||||
|
||||
function initPopovers(root) {
|
||||
(root || document).querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
|
||||
bootstrap.Popover.getOrCreateInstance(el, popoverOptions());
|
||||
});
|
||||
}
|
||||
|
||||
// Load a URL into the shared modal and show it
|
||||
function loadModal(url) {
|
||||
var modal = document.getElementById('modal');
|
||||
return ajaxFetch(url)
|
||||
.then(function (response) { return response.text(); })
|
||||
.then(function (html) {
|
||||
setHtml(modal, html);
|
||||
showModal(modal);
|
||||
});
|
||||
}
|
||||
|
||||
function showModal(selectorOrElement) {
|
||||
var el = typeof selectorOrElement === 'string' ? document.querySelector(selectorOrElement) : selectorOrElement;
|
||||
bootstrap.Modal.getOrCreateInstance(el).show();
|
||||
}
|
||||
|
||||
function hideModal(selectorOrElement) {
|
||||
var el = typeof selectorOrElement === 'string' ? document.querySelector(selectorOrElement) : selectorOrElement;
|
||||
var modal = bootstrap.Modal.getInstance(el);
|
||||
if (modal) {
|
||||
modal.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Animated show/hide for form sections (replacing jQuery's slideDown/slideUp)
|
||||
function resetSlide(el) {
|
||||
window.clearTimeout(el._slideTimer);
|
||||
['height', 'overflow', 'transition', 'opacity'].forEach(function (p) { el.style.removeProperty(p); });
|
||||
}
|
||||
|
||||
function slideUp(el, duration) {
|
||||
resetSlide(el);
|
||||
if (duration === 0 || isHidden(el)) {
|
||||
el.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
el.style.overflow = 'hidden';
|
||||
el.style.height = el.offsetHeight + 'px';
|
||||
el.offsetHeight; // force reflow
|
||||
el.style.transition = 'height ' + duration + 'ms ease, opacity ' + duration + 'ms ease';
|
||||
el.style.height = '0';
|
||||
el.style.opacity = '0';
|
||||
el._slideTimer = window.setTimeout(function () {
|
||||
resetSlide(el);
|
||||
el.style.display = 'none';
|
||||
}, duration);
|
||||
}
|
||||
|
||||
function slideDown(el, duration) {
|
||||
resetSlide(el);
|
||||
el.hidden = false;
|
||||
el.style.removeProperty('display');
|
||||
if (isHidden(el)) {
|
||||
el.style.display = 'block'; // not hidden by an inline style, so fall back to a block element
|
||||
}
|
||||
if (duration === 0) {
|
||||
return;
|
||||
}
|
||||
var height = el.scrollHeight;
|
||||
el.style.overflow = 'hidden';
|
||||
el.style.height = '0';
|
||||
el.style.opacity = '0';
|
||||
el.offsetHeight; // force reflow
|
||||
el.style.transition = 'height ' + duration + 'ms ease, opacity ' + duration + 'ms ease';
|
||||
el.style.height = height + 'px';
|
||||
el.style.opacity = '1';
|
||||
el._slideTimer = window.setTimeout(function () {
|
||||
resetSlide(el);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
function isHidden(el) {
|
||||
return getComputedStyle(el).display === 'none';
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
function setupItemTable(items_json) {
|
||||
objectitems = JSON.parse(items_json)
|
||||
$.each(objectitems, function (key, val) {
|
||||
objectitems[key] = JSON.parse(val);
|
||||
})
|
||||
Object.keys(objectitems).forEach(function (key) {
|
||||
objectitems[key] = JSON.parse(objectitems[key]);
|
||||
});
|
||||
newitem = -1;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return $('<div/>').text(str).html();
|
||||
var div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function updatePrices() {
|
||||
@@ -16,116 +18,144 @@ function updatePrices() {
|
||||
for (var pk in objectitems) {
|
||||
var fields = objectitems[pk].fields;
|
||||
var sub = fields.cost * fields.quantity;
|
||||
$('#item-' + pk + ' .sub-total').html(parseFloat(sub).toFixed(2)).data('subtotal', sub);
|
||||
var subTotal = document.querySelector('#item-' + pk + ' .sub-total');
|
||||
if (subTotal) {
|
||||
subTotal.textContent = parseFloat(sub).toFixed(2);
|
||||
subTotal.dataset.subtotal = sub;
|
||||
}
|
||||
|
||||
sum += Number(sub);
|
||||
}
|
||||
|
||||
$('#sumtotal').text(parseFloat(sum).toFixed(2));
|
||||
var vat = sum * Number($('#vat-rate').data('rate'));
|
||||
$('#vat').text(parseFloat(vat).toFixed(2));
|
||||
$('#total').text(parseFloat(sum + vat).toFixed(2));
|
||||
document.getElementById('sumtotal').textContent = parseFloat(sum).toFixed(2);
|
||||
var vat = sum * Number(document.getElementById('vat-rate').dataset.rate);
|
||||
document.getElementById('vat').textContent = parseFloat(vat).toFixed(2);
|
||||
document.getElementById('total').textContent = parseFloat(sum + vat).toFixed(2);
|
||||
}
|
||||
|
||||
function setupMDE(selector) {
|
||||
var element = document.querySelector(selector);
|
||||
editor = new EasyMDE({
|
||||
autoDownloadFontAwesome: false,
|
||||
element: $(selector)[0],
|
||||
element: element,
|
||||
forceSync: true,
|
||||
toolbar: ["bold", "italic", "strikethrough", "|", "unordered-list", "ordered-list", "|", "link", "|", "preview", "guide"],
|
||||
status: true,
|
||||
});
|
||||
$(selector).data('mde_editor',editor);
|
||||
element.mde_editor = editor;
|
||||
}
|
||||
|
||||
$('#item-table').on('click', '.item-delete', function () {
|
||||
delete objectitems[$(this).data('pk')]
|
||||
$('#item-' + $(this).data('pk')).remove();
|
||||
updatePrices();
|
||||
});
|
||||
(function () {
|
||||
var itemTable = document.getElementById('item-table');
|
||||
var itemForm = document.getElementById('item-form');
|
||||
|
||||
$('#item-table').on('click', '.item-add', function () {
|
||||
$('#item-form').data('pk', newitem);
|
||||
function field(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
// Set the form values
|
||||
$('#item_name').val('');
|
||||
$('#item_description').val('');
|
||||
$('#item_quantity').val('');
|
||||
$('#item_cost').val('');
|
||||
if (itemTable) {
|
||||
on(itemTable, 'click', '.item-delete', function () {
|
||||
delete objectitems[this.dataset.pk];
|
||||
var row = document.getElementById('item-' + this.dataset.pk);
|
||||
if (row) {
|
||||
row.remove();
|
||||
}
|
||||
updatePrices();
|
||||
});
|
||||
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
on(itemTable, 'click', '.item-add', function () {
|
||||
itemForm.dataset.pk = newitem;
|
||||
|
||||
$('#item-table').on('click', '.item-edit', function () {
|
||||
// set the pk as we will need this later
|
||||
var pk = $(this).data('pk');
|
||||
$('#item-form').data('pk', pk);
|
||||
// Set the form values
|
||||
field('item_name').value = '';
|
||||
field('item_description').value = '';
|
||||
field('item_quantity').value = '';
|
||||
field('item_cost').value = '';
|
||||
|
||||
// Set the form values
|
||||
var fields = objectitems[pk].fields;
|
||||
$('#item_name').val(fields.name);
|
||||
$('#item_description').val(fields.description);
|
||||
$('#item_quantity').val(fields.quantity);
|
||||
$('#item_cost').val(fields.cost);
|
||||
showModal(this.dataset.modalTarget);
|
||||
});
|
||||
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
on(itemTable, 'click', '.item-edit', function () {
|
||||
// set the pk as we will need this later
|
||||
var pk = this.dataset.pk;
|
||||
itemForm.dataset.pk = pk;
|
||||
|
||||
$('body').on('submit', '#item-form', function (e) {
|
||||
e.preventDefault();
|
||||
var pk = $(this).data('pk');
|
||||
$('#itemModal').modal('hide');
|
||||
// Set the form values
|
||||
var fields = objectitems[pk].fields;
|
||||
field('item_name').value = fields.name;
|
||||
field('item_description').value = fields.description;
|
||||
field('item_quantity').value = fields.quantity;
|
||||
field('item_cost').value = fields.cost;
|
||||
|
||||
var fields;
|
||||
if (pk == newitem--) {
|
||||
// Create the new data structure and add it on.
|
||||
fields = new Object();
|
||||
fields['name'] = $('#item_name').val()
|
||||
fields['description'] = $('#item_description').val();
|
||||
fields['cost'] = $('#item_cost').val();
|
||||
fields['quantity'] = $('#item_quantity').val();
|
||||
showModal(this.dataset.modalTarget);
|
||||
});
|
||||
}
|
||||
|
||||
var order = 0;
|
||||
for (item in objectitems) {
|
||||
order++;
|
||||
on(document, 'submit', '#item-form', function (e) {
|
||||
e.preventDefault();
|
||||
var pk = this.dataset.pk;
|
||||
hideModal('#itemModal');
|
||||
|
||||
var fields;
|
||||
if (pk == newitem--) {
|
||||
// Create the new data structure and add it on.
|
||||
fields = new Object();
|
||||
fields['name'] = field('item_name').value;
|
||||
fields['description'] = field('item_description').value;
|
||||
fields['cost'] = field('item_cost').value;
|
||||
fields['quantity'] = field('item_quantity').value;
|
||||
|
||||
var order = 0;
|
||||
for (var item in objectitems) {
|
||||
order++;
|
||||
}
|
||||
|
||||
fields['order'] = order;
|
||||
|
||||
objectitems[pk] = new Object();
|
||||
objectitems[pk]['fields'] = fields;
|
||||
|
||||
// Add the new table row
|
||||
var row = field('new-item-row').cloneNode(true);
|
||||
row.id = 'item-' + pk;
|
||||
row.dataset.pk = pk;
|
||||
field('item-table-body').appendChild(row);
|
||||
row.querySelectorAll('.item-delete, .item-edit').forEach(function (button) {
|
||||
button.dataset.pk = pk;
|
||||
});
|
||||
} else {
|
||||
// Existing item
|
||||
// update data structure
|
||||
fields = objectitems[pk].fields;
|
||||
fields.name = field('item_name').value;
|
||||
fields.description = field('item_description').value;
|
||||
fields.cost = field('item_cost').value;
|
||||
fields.quantity = field('item_quantity').value;
|
||||
objectitems[pk].fields = fields;
|
||||
}
|
||||
// update the table
|
||||
var tableRow = field('item-' + pk);
|
||||
tableRow.querySelector('.name').innerHTML = escapeHtml(fields.name);
|
||||
tableRow.querySelector('.description').innerHTML = fields.description;
|
||||
tableRow.querySelector('.cost').innerHTML = parseFloat(fields.cost).toFixed(2);
|
||||
tableRow.querySelector('.quantity').innerHTML = fields.quantity;
|
||||
|
||||
fields['order'] = order;
|
||||
updatePrices();
|
||||
});
|
||||
|
||||
objectitems[pk] = new Object();
|
||||
objectitems[pk]['fields'] = fields;
|
||||
|
||||
// Add the new table
|
||||
$('#new-item-row').clone().attr('id', 'item-' + pk).data('pk', pk).appendTo('#item-table-body');
|
||||
$('#item-'+pk+' .item-delete, #item-'+pk+' .item-edit').data('pk', pk)
|
||||
} else {
|
||||
// Existing item
|
||||
// update data structure
|
||||
fields = objectitems[pk].fields;
|
||||
fields.name = $('#item_name').val()
|
||||
fields.description = $('#item_description').val();
|
||||
fields.cost = $('#item_cost').val();
|
||||
fields.quantity = $('#item_quantity').val();
|
||||
objectitems[pk].fields = fields;
|
||||
on(document, 'submit', '.itemised_form', function () {
|
||||
field('id_items_json').value = JSON.stringify(objectitems);
|
||||
});
|
||||
|
||||
if (itemTable && typeof sortable === 'function') {
|
||||
var sortables = sortable("#item-table tbody");
|
||||
if (sortables.length) {
|
||||
sortables[0].addEventListener('sortupdate', function (e) {
|
||||
var items = e.detail.destination.items;
|
||||
for (var i in items) {
|
||||
objectitems[items[i].dataset.pk].fields.order = i;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// update the table
|
||||
$row = $('#item-' + pk);
|
||||
$row.find('.name').html(escapeHtml(fields.name));
|
||||
$row.find('.description').html(fields.description);
|
||||
$row.find('.cost').html(parseFloat(fields.cost).toFixed(2));
|
||||
$row.find('.quantity').html(fields.quantity);
|
||||
|
||||
updatePrices();
|
||||
});
|
||||
|
||||
$('body').on('submit', '.itemised_form', function (e) {
|
||||
$('#id_items_json').val(JSON.stringify(objectitems));
|
||||
});
|
||||
|
||||
sortable("#item-table tbody")[0].addEventListener('sortupdate', function (e) {
|
||||
var items = e.detail.destination.items;
|
||||
for(var i in items) {
|
||||
objectitems[items[i].dataset.pk].fields.order = i;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -4,19 +4,30 @@ Date.prototype.getISOString = function () {
|
||||
var dd = this.getDate().toString();
|
||||
return yyyy + '-' + (mm[1] ? mm : "0" + mm[0]) + '-' + (dd[1] ? dd : "0" + dd[0]); // padding
|
||||
};
|
||||
jQuery(document).ready(function () {
|
||||
jQuery(document).on('click', '.modal-href', function (e) {
|
||||
$link = jQuery(this);
|
||||
|
||||
ready(function () {
|
||||
// Links that load their target into the shared modal
|
||||
on(document, 'click', '.modal-href', function (e) {
|
||||
// Anti modal inception
|
||||
if ($link.parents('#modal').length == 0) {
|
||||
if (!this.closest('#modal')) {
|
||||
e.preventDefault();
|
||||
modaltarget = $link.data('target');
|
||||
modaltarget = this.dataset.selectTarget;
|
||||
modalobject = "";
|
||||
jQuery('#modal').load($link.attr('href'), function (e) {
|
||||
jQuery('#modal').modal();
|
||||
});
|
||||
loadModal(this.getAttribute('href'));
|
||||
}
|
||||
});
|
||||
|
||||
// Forms inside the modal are submitted over AJAX and the response replaces the modal contents
|
||||
on(document, 'submit', '#modal form', function (e) {
|
||||
e.preventDefault();
|
||||
var form = this;
|
||||
ajaxFetch(form.getAttribute('action') || window.location.href, {
|
||||
method: 'POST',
|
||||
body: new FormData(form)
|
||||
}).then(function (response) { return response.text(); })
|
||||
.then(function (html) { setHtml(document.getElementById('modal'), html); });
|
||||
});
|
||||
|
||||
var easter_egg = new Konami(function () {
|
||||
var s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
@@ -24,7 +35,12 @@ jQuery(document).ready(function () {
|
||||
s.src = '/static/js/asteroids.min.js';
|
||||
});
|
||||
easter_egg.load();
|
||||
|
||||
initTooltips();
|
||||
initPopovers();
|
||||
document.querySelectorAll('.navbar-collapse').forEach(function (el) { el.classList.add('collapse'); });
|
||||
});
|
||||
|
||||
//CTRL-Enter form submission
|
||||
document.body.addEventListener('keydown', function(e) {
|
||||
if(e.keyCode == 13 && (e.metaKey || e.ctrlKey)) {
|
||||
@@ -34,4 +50,3 @@ document.body.addEventListener('keydown', function(e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
$('.navbar-collapse').addClass('collapse');
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
$font-family-sans-serif: "Open Sans", sans-serif;
|
||||
$font-family-sans-serif: "Open Sans", sans-serif;
|
||||
//Make it look less primary school
|
||||
$font-size-base: 0.85rem;
|
||||
$theme-colors: (
|
||||
"success": #5cb85c,
|
||||
"warning": #f0ad4e,
|
||||
"danger": #d9534f,
|
||||
"info": #5bc0de,
|
||||
"primary": #3A52A2
|
||||
) !default;
|
||||
$enable-shadows: true;
|
||||
$alert-color-level: 10;
|
||||
$enable-rounded: false;
|
||||
|
||||
$primary: #3A52A2;
|
||||
$success: #5cb85c;
|
||||
$warning: #f0ad4e;
|
||||
$danger: #d9534f;
|
||||
$info: #5bc0de;
|
||||
|
||||
// Pre-computed: Sass serialises the derived (tinted) colour as rgb(...%), which breaks the url-encoded SVG
|
||||
$form-switch-focus-color: #9da9d1;
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
[data-theme='dark'] {
|
||||
$theme-colors: (
|
||||
"success": #3AB54A,
|
||||
"warning": #FFE89B,
|
||||
"danger": #BF1E2E,
|
||||
"info": #25AAE2,
|
||||
"primary": #3A52A2
|
||||
);
|
||||
@import "custom-variables";
|
||||
@import 'node_modules/@forevolve/bootstrap-dark/scss/bootstrap-dark.scss';
|
||||
background: #222;
|
||||
color: $gray-100;
|
||||
$darktheme: #121416;
|
||||
.navbar {
|
||||
background-color: #111111 !important;
|
||||
}
|
||||
.dropdown-menu {
|
||||
box-shadow: 0 0.5rem 1rem black;
|
||||
color: $gray-100;
|
||||
background: $darktheme;
|
||||
}
|
||||
.form-control:not(.btn) {
|
||||
background-color: $darktheme;
|
||||
color: $gray-100;
|
||||
}
|
||||
.btn-light, .popover, .popover-header, .popover-body, .status {
|
||||
background-color: $darktheme !important;
|
||||
color: $gray-100 !important;
|
||||
border-color: $darktheme;
|
||||
}
|
||||
.btn-link {
|
||||
color: white;
|
||||
}
|
||||
.bs-popover-right > .arrow::after {
|
||||
border-right-color: $darktheme;
|
||||
}
|
||||
.bs-popover-left > .arrow::after {
|
||||
border-left-color: $darktheme;
|
||||
}
|
||||
.bs-popover-top > .arrow::after {
|
||||
border-top-color: $darktheme;
|
||||
}
|
||||
.bs-popover-bottom > .arrow::after {
|
||||
border-bottom-color: $darktheme;
|
||||
}
|
||||
a {
|
||||
color: $blue;
|
||||
}
|
||||
.badge, .btn-success, .bg-warning {
|
||||
color: black;
|
||||
}
|
||||
.badge-dark, .badge-secondary, .btn-primary {
|
||||
color: $gray-100;
|
||||
}
|
||||
.input-group-text {
|
||||
border-color: $darktheme;
|
||||
}
|
||||
.btn-secondary, .btn-info {
|
||||
color: white;
|
||||
}
|
||||
.page-item.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bg-light {
|
||||
color: black;
|
||||
background-color: $gray-200 !important;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
.fc-day-today {
|
||||
background-color: transparent !important;
|
||||
border: 0.2em solid $info !important;
|
||||
}
|
||||
.fc-daygrid-dot-event {
|
||||
color: white !important;
|
||||
}
|
||||
.table {
|
||||
border-collapse: separate !important;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@each $color, $value in $theme-colors {
|
||||
table.table-#{$color} {
|
||||
> td,th {
|
||||
border: 0.3em solid theme-color-level($color, -6) !important;
|
||||
}
|
||||
> * {
|
||||
color: white !important;
|
||||
background-color: #222 !important;
|
||||
}
|
||||
}
|
||||
#event_row.table-#{$color} {
|
||||
border: 0.3em solid theme-color-level($color, -6) !important;
|
||||
background-color: #222 !important;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
del {
|
||||
color: black;
|
||||
background-color: $danger;
|
||||
border-radius: 3px;
|
||||
}
|
||||
ins {
|
||||
color: black;
|
||||
background-color: $success;
|
||||
border-radius: 3px;
|
||||
}
|
||||
pre {
|
||||
background: $gray-600;
|
||||
color: white;
|
||||
}
|
||||
.custom-control-input:focus ~ .custom-control-label::before {
|
||||
box-shadow: 0 0 0 $input-focus-width rgba($primary, 0.7) !important;
|
||||
}
|
||||
.source {
|
||||
color: white !important;
|
||||
}
|
||||
.embed_container {
|
||||
border-color: #3853a4 !important;
|
||||
background: #222;
|
||||
color: $gray-100;
|
||||
}
|
||||
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 {
|
||||
border: 1px solid $info;
|
||||
-webkit-text-fill-color: white;
|
||||
-webkit-box-shadow: 0 0 0px 1000px rgba($info, .3) inset;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
.editor-toolbar > a {
|
||||
color: white !important;
|
||||
}
|
||||
.editor-toolbar > a:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
.editor-toolbar > a.active {
|
||||
background: $info !important;
|
||||
}
|
||||
.cm-s-easymde {
|
||||
color: white;
|
||||
background-color: $darktheme;
|
||||
border-color: #bbb;
|
||||
}
|
||||
.CodeMirror-cursor {
|
||||
border-color: white !important;
|
||||
}
|
||||
.modal {
|
||||
overflow-y: auto !important; //Bootstrap Dark Theme overrides this to none for some insane reason so we need to change it back
|
||||
}
|
||||
.text-muted {
|
||||
color: #c9c9c9 !important;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,40 @@
|
||||
// Bootstrap 5 is consumed via @import (the Sass module system is not yet supported by Bootstrap's own build)
|
||||
@import "custom-variables";
|
||||
//Required
|
||||
@import "node_modules/bootstrap/scss/bootstrap-reboot";
|
||||
@import "node_modules/bootstrap/scss/bootstrap-grid";
|
||||
@import "node_modules/bootstrap/scss/functions";
|
||||
@import "node_modules/bootstrap/scss/variables";
|
||||
@import "node_modules/bootstrap/scss/variables-dark";
|
||||
@import "node_modules/bootstrap/scss/maps";
|
||||
@import "node_modules/bootstrap/scss/mixins";
|
||||
@import "node_modules/bootstrap/scss/utilities";
|
||||
//Optional
|
||||
@import "node_modules/bootstrap/scss/root";
|
||||
@import "node_modules/bootstrap/scss/reboot";
|
||||
@import "node_modules/bootstrap/scss/type";
|
||||
@import "node_modules/bootstrap/scss/images";
|
||||
@import "node_modules/bootstrap/scss/containers";
|
||||
@import "node_modules/bootstrap/scss/grid";
|
||||
@import "node_modules/bootstrap/scss/tables";
|
||||
@import "node_modules/bootstrap/scss/forms";
|
||||
@import "node_modules/bootstrap/scss/buttons";
|
||||
@import "node_modules/bootstrap/scss/transitions";
|
||||
@import "node_modules/bootstrap/scss/dropdown";
|
||||
@import "node_modules/bootstrap/scss/button-group";
|
||||
@import "node_modules/bootstrap/scss/input-group";
|
||||
@import "node_modules/bootstrap/scss/custom-forms";
|
||||
@import "node_modules/bootstrap/scss/nav";
|
||||
@import "node_modules/bootstrap/scss/navbar";
|
||||
@import "node_modules/bootstrap/scss/card";
|
||||
@import "node_modules/bootstrap/scss/pagination";
|
||||
@import "node_modules/bootstrap/scss/badge";
|
||||
@import "node_modules/bootstrap/scss/alert";
|
||||
@import "node_modules/bootstrap/scss/media";
|
||||
@import "node_modules/bootstrap/scss/list-group";
|
||||
@import "node_modules/bootstrap/scss/close";
|
||||
@import "node_modules/bootstrap/scss/modal";
|
||||
@import "node_modules/bootstrap/scss/tooltip";
|
||||
@import "node_modules/bootstrap/scss/popover";
|
||||
@import "node_modules/bootstrap/scss/progress";
|
||||
@import "node_modules/bootstrap/scss/spinners";
|
||||
@import "node_modules/bootstrap/scss/utilities";
|
||||
@import "node_modules/bootstrap/scss/helpers";
|
||||
@import "node_modules/bootstrap/scss/utilities/api";
|
||||
//FontAwesome
|
||||
$fa-font-path: '/static/fonts';
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/fontawesome";
|
||||
@@ -96,6 +103,38 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap 4's contextual table classes also worked as background colours on non-table elements
|
||||
@each $color, $value in $table-variants {
|
||||
.table-#{$color}:not(table):not(tr):not(td):not(th) {
|
||||
background-color: var(--bs-table-bg);
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap 4 columns used max-width, so `.form-control.col-sm-6` stayed narrow; Bootstrap 5's
|
||||
// `.form-control { width: 100% }` now wins over the column width
|
||||
@each $breakpoint, $_min in $grid-breakpoints {
|
||||
@include media-breakpoint-up($breakpoint) {
|
||||
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
|
||||
@for $i from 1 through $grid-columns {
|
||||
.form-control.col#{$infix}-#{$i},
|
||||
.form-select.col#{$infix}-#{$i} {
|
||||
width: calc(#{$i} / #{$grid-columns} * 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Links inside badges (e.g. "Reviewed by ...") sit on a coloured background, so keep the badge's text colour
|
||||
.badge a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
// Bootstrap 4 gave labels a bottom margin, Bootstrap 5 expects .form-label
|
||||
label {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.table tbody > tr > td.vert-align {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -152,7 +191,7 @@ ins {
|
||||
}
|
||||
|
||||
.btn-light {
|
||||
background-color: $gray-200 !important;
|
||||
--bs-btn-bg: #{$gray-200};
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
@@ -272,14 +311,12 @@ html.embedded {
|
||||
}
|
||||
}
|
||||
|
||||
.card, .card-header, .btn, input, select, .CodeMirror, .editor-toolbar, .card-img-top {
|
||||
.CodeMirror, .editor-toolbar {
|
||||
border-radius: 0 !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.bootstrap-select, button.btn.dropdown-toggle.bs-placeholder.btn-light {
|
||||
padding-right: 1rem !important;
|
||||
.ts-wrapper.form-select, .ts-wrapper.form-control {
|
||||
padding: 0;
|
||||
}
|
||||
// New implementation of class dropped in Bootstrap 3
|
||||
.dl-horizontal {
|
||||
@@ -290,3 +327,221 @@ html.embedded {
|
||||
.dl-horizontal > dd, .dl-horizontal .markdown > p {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
// Dark mode: Bootstrap 5.3 colour modes (set via data-bs-theme on <html>), with a few project-specific tweaks
|
||||
$darktheme: #121416;
|
||||
|
||||
// Brighter brand colours for dark mode (the light-mode ones are pastel tints on a dark background)
|
||||
$dark-theme-colors: (
|
||||
"success": #3AB54A,
|
||||
"warning": #FFE89B,
|
||||
"danger": #BF1E2E,
|
||||
"info": #25AAE2,
|
||||
"primary": #3A52A2,
|
||||
"secondary": $secondary,
|
||||
"light": $light,
|
||||
"dark": $dark
|
||||
);
|
||||
|
||||
[data-bs-theme="dark"] {
|
||||
@each $name, $value in $dark-theme-colors {
|
||||
--bs-#{$name}: #{$value};
|
||||
--bs-#{$name}-rgb: #{to-rgb($value)};
|
||||
|
||||
.btn-#{$name} {
|
||||
@include button-variant($value, $value);
|
||||
}
|
||||
.btn-outline-#{$name} {
|
||||
@include button-outline-variant($value);
|
||||
}
|
||||
.text-bg-#{$name} {
|
||||
color: color-contrast($value) !important;
|
||||
background-color: RGBA(to-rgb($value), var(--bs-bg-opacity, 1)) !important;
|
||||
}
|
||||
// Contextual table classes (tables, rows, cells and the plain-element variant used for the rigboard key)
|
||||
.table-#{$name} {
|
||||
--bs-table-color: #{color-contrast(shade-color($value, 55%))};
|
||||
--bs-table-bg: #{shade-color($value, 55%)};
|
||||
--bs-table-border-color: #{shade-color($value, 35%)};
|
||||
--bs-table-striped-bg: #{shade-color($value, 50%)};
|
||||
--bs-table-hover-bg: #{shade-color($value, 45%)};
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
}
|
||||
}
|
||||
--bs-body-bg: #222;
|
||||
--bs-body-color: #{$gray-100};
|
||||
// Bootstrap derives these from $primary, which is too dim on the dark background
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-color-rgb: #{to-rgb(#6ea8fe)};
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-hover-color-rgb: #{to-rgb(#8bb9fe)};
|
||||
--bs-secondary-color: #c9c9c9;
|
||||
--bs-tertiary-bg: #{$darktheme};
|
||||
|
||||
.navbar {
|
||||
background-color: #111111 !important;
|
||||
}
|
||||
// Coloured card outlines (training levels): make them visible, including the "dark" and "light" ones
|
||||
.card[class*="border-"] {
|
||||
--bs-card-border-width: 2px;
|
||||
}
|
||||
.border-dark {
|
||||
border-color: $gray-500 !important;
|
||||
}
|
||||
.dropdown-menu, .popover {
|
||||
box-shadow: 0 0.5rem 1rem black;
|
||||
--bs-dropdown-bg: #{$darktheme};
|
||||
--bs-popover-bg: #{$darktheme};
|
||||
}
|
||||
.form-control:not(.btn), .form-select, .ts-control, .ts-dropdown {
|
||||
background-color: $darktheme;
|
||||
color: $gray-100;
|
||||
}
|
||||
// Tom Select hard-codes dark text colours
|
||||
.ts-wrapper .ts-control,
|
||||
.ts-wrapper .ts-control input,
|
||||
.ts-dropdown,
|
||||
.ts-dropdown .option,
|
||||
.ts-dropdown .optgroup-header,
|
||||
.ts-dropdown .no-results,
|
||||
.ts-dropdown .create {
|
||||
color: $gray-100;
|
||||
}
|
||||
.ts-wrapper .ts-control input::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
.ts-wrapper.multi .ts-control > div {
|
||||
background: $gray-700;
|
||||
color: $gray-100;
|
||||
border-color: $gray-600;
|
||||
}
|
||||
.ts-wrapper.multi .ts-control > div.active {
|
||||
background: $primary;
|
||||
color: #fff;
|
||||
}
|
||||
.ts-dropdown .active {
|
||||
background-color: $gray-800;
|
||||
color: $gray-100;
|
||||
}
|
||||
.ts-dropdown .active.create {
|
||||
color: $gray-100;
|
||||
}
|
||||
.ts-dropdown .optgroup-header {
|
||||
background: $darktheme;
|
||||
}
|
||||
::placeholder {
|
||||
color: $gray-500;
|
||||
opacity: 1;
|
||||
}
|
||||
.btn-light, .status {
|
||||
--bs-btn-bg: #{$darktheme};
|
||||
--bs-btn-color: #{$gray-100};
|
||||
--bs-btn-border-color: #{$darktheme};
|
||||
--bs-btn-hover-bg: #{tint-color($darktheme, 8%)};
|
||||
--bs-btn-hover-color: #{$gray-100};
|
||||
--bs-btn-hover-border-color: #{$darktheme};
|
||||
background-color: $darktheme !important;
|
||||
color: $gray-100 !important;
|
||||
border-color: $darktheme;
|
||||
}
|
||||
.btn-link {
|
||||
color: white;
|
||||
}
|
||||
.input-group-text {
|
||||
border-color: $darktheme;
|
||||
}
|
||||
.page-item.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bg-light {
|
||||
color: black;
|
||||
background-color: $gray-200 !important;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
.fc-day-today {
|
||||
background-color: transparent !important;
|
||||
border: 0.2em solid $info !important;
|
||||
}
|
||||
.fc-daygrid-dot-event {
|
||||
color: white !important;
|
||||
}
|
||||
@each $color, $value in $dark-theme-colors {
|
||||
#event_row.table-#{$color} {
|
||||
border: 0.3em solid shade-color($value, 60%) !important;
|
||||
background-color: #222 !important;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
del {
|
||||
color: black;
|
||||
background-color: $danger;
|
||||
border-radius: 3px;
|
||||
}
|
||||
ins {
|
||||
color: black;
|
||||
background-color: $success;
|
||||
border-radius: 3px;
|
||||
}
|
||||
pre {
|
||||
background: $gray-600;
|
||||
color: white;
|
||||
}
|
||||
.source {
|
||||
color: white !important;
|
||||
}
|
||||
.embed_container {
|
||||
border-color: #3853a4 !important;
|
||||
background: #222;
|
||||
color: $gray-100;
|
||||
}
|
||||
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 {
|
||||
border: 1px solid $info;
|
||||
-webkit-text-fill-color: white;
|
||||
-webkit-box-shadow: 0 0 0px 1000px rgba($info, .3) inset;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
.editor-toolbar > a {
|
||||
color: white !important;
|
||||
}
|
||||
.editor-toolbar > a:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
.editor-toolbar > a.active {
|
||||
background: $info !important;
|
||||
}
|
||||
.cm-s-easymde {
|
||||
color: white;
|
||||
background-color: $darktheme;
|
||||
border-color: #bbb;
|
||||
}
|
||||
.CodeMirror-cursor {
|
||||
border-color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ts-wrapper.has-items .ts-control > input::placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
// FullCalendar (standard theme) colours, matching the site's light/dark modes
|
||||
.fc {
|
||||
--fc-border-color: var(--bs-border-color);
|
||||
--fc-page-bg-color: var(--bs-body-bg);
|
||||
--fc-neutral-bg-color: var(--bs-tertiary-bg);
|
||||
--fc-today-bg-color: rgba(var(--bs-warning-rgb), 0.15);
|
||||
--fc-list-event-hover-bg-color: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
.fc a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user