BS4 by default, BS5 optional

Bootstrap 4 w/ jQuery is used by default unless bs5 is enabled in
settings/ui. bs4 also now has a jellyfin-style look.
This commit is contained in:
Harvey Tindall 2020-07-05 14:38:07 +01:00
parent ade935da4e
commit adef32ef89
26 changed files with 19315 additions and 39 deletions

View File

@ -1,13 +1,11 @@
# ![jellyfin-accounts](https://raw.githubusercontent.com/hrfee/jellyfin-accounts/master/images/jellyfin-accounts-banner-wide.svg)
**This branch uses the bootstrap 5 alpha, which works well enough for the most part, but can sometimes be a bit glitchy. Also no more jquery.**
A basic account management system for [Jellyfin](https://github.com/jellyfin/jellyfin).
* Provides a web interface for creating invite codes, and a simple account creation form
* Sends out emails when a user requests a password reset
* Uses a basic python jellyfin API client for communication with the server.
* Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress)
* Frontend uses [Bootstrap 5](https://v5.getbootstrap.com)
* Frontend uses [Bootstrap](https://v5.getbootstrap.com)
* Password resets are handled using smtplib, requests, and [jinja](https://github.com/pallets/jinja)
## Interface
<p align="center">

View File

@ -9,9 +9,9 @@ server = http://jellyfin.local:8096
public_server = https://jellyf.in:443
; this and below settings will show on the jellyfin dashboard when the program connects. you may as well leave them alone.
client = jf-accounts
version = 0.2.5
version = 0.3.0
device = jf-accounts
device_id = jf-accounts-0.2.5
device_id = jf-accounts-0.3.0
[ui]
; settings related to the ui and program functionality.
@ -33,6 +33,8 @@ contact_message = Need help? contact me.
help_message = Enter your details to create an account.
; displayed when a user creates an account
success_message = Your account has been created. Click below to continue to Jellyfin.
; use bootstrap 5 (currently in alpha). this also removes the need for jquery, so the page should load faster.
bs5 = false
[password_validation]
; password validation (minimum length, etc.)

View File

@ -142,6 +142,11 @@ def load_config(config_path, data_dir):
or config["jellyfin"]["public_server"] == ""
):
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
if (
"bs5" not in config["ui"]
or config["ui"]["bs5"] == ""
):
config["ui"]["bs5"] = "false"
return config
@ -185,8 +190,12 @@ data_store = JSONStorage(
config["files"]["user_configuration"],
)
if config.getboolean("ui", "bs5"):
css_file = "bs5-jf.css"
log.debug('Using Bootstrap 5')
else:
css_file = "bs4-jf.css"
css_file = "bs5-jf.css"
if "custom_css" in config["files"]:
if config["files"]["custom_css"] != "":
try:

View File

@ -150,6 +150,14 @@
"type": "text",
"value": "Your account has been created. Click below to continue to Jellyfin.",
"description": "Displayed when a user creates an account"
},
"bs5": {
"name": "Use Bootstrap 5",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Use Bootstrap 5 (currently in alpha). This also removes the need for jQuery, so the page should load faster."
}
},
"password_validation": {

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

@ -14,8 +14,15 @@
<title>404</title>
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
{% if not bs5 %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
{% if bs5 %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
{% else %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
{% endif %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
.messageBox {

View File

@ -12,11 +12,18 @@
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#603cba">
<meta name="theme-color" content="#ffffff">
<!-- Bootstrap CSS -->
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
{% if not bs5 %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
{% if bs5 %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
{% else %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
{% endif %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
.pageContainer {

View File

@ -1,8 +1,64 @@
var loginModal = new bootstrap.Modal(document.getElementById('login'));
var settingsModal = new bootstrap.Modal(document.getElementById('settingsMenu'));
var userDefaultsModal = new bootstrap.Modal(document.getElementById('userDefaults'));
var usersModal = new bootstrap.Modal(document.getElementById('users'));
var restartModal = new bootstrap.Modal(document.getElementById('restartModal'));
var bsVersion = {{ bsVersion }};
if (bsVersion == 5) {
function createModal(id, find = false) {
if (find) {
return bootstrap.Modal.getInstance(document.getElementById(modalId));
};
return new bootstrap.Modal(document.getElementById(id));
};
function triggerTooltips() {
document.getElementById('settingsMenu').addEventListener('shown.bs.modal', function() {
// Hack to ensure anything dependent on checkboxes are disabled if necessary
var checkboxes = document.getElementById('settingsMenu').querySelectorAll('input[type="checkbox"]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].click();
checkboxes[i].click();
};
// Initialize tooltips
var to_trigger = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]'));
var tooltips = to_trigger.map(function(el) {
return new bootstrap.Tooltip(el);
});
});
};
// var loginModal = new bootstrap.Modal(document.getElementById('login'));
// var settingsModal = new bootstrap.Modal(document.getElementById('settingsMenu'));
// var userDefaultsModal = new bootstrap.Modal(document.getElementById('userDefaults'));
// var usersModal = new bootstrap.Modal(document.getElementById('users'));
// var restartModal = new bootstrap.Modal(document.getElementById('restartModal'));
} else if (bsVersion == 4) {
document.getElementById('send_to_address_enabled').classList.remove('form-check-input');
function createModal(id, find = false) {
return {
show : function() {
return $('#' + id).modal('show');
},
hide : function() {
return $('#' + id).modal('hide');
}
};
};
function triggerTooltips() {
$('#settingsMenu').on('shown.bs.modal', function() {
var checkboxes = document.getElementById('settingsMenu').querySelectorAll('input[type="checkbox"]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].click();
checkboxes[i].click();
};
$("a[data-toggle='tooltip']").each(function (i, obj) {
$(obj).tooltip();
});
});
};
};
var loginModal = createModal('login');
var settingsModal = createModal('settingsMenu');
var userDefaultsModal = createModal('userDefaults');
var usersModal = createModal('users');
var restartModal = createModal('restartModal');
function parseInvite(invite, empty = false) {
if (empty === true) {
@ -612,19 +668,7 @@ document.getElementById('openSettings').onclick = function () {
settingsModal.show();
};
document.getElementById('settingsMenu').addEventListener('shown.bs.modal', function() {
// Hack to ensure anything dependent on checkboxes are disabled if necessary
var checkboxes = document.getElementById('settingsMenu').querySelectorAll('input[type="checkbox"]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].click();
checkboxes[i].click();
};
// Initialize tooltips
var to_trigger = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]'));
var tooltips = to_trigger.map(function(el) {
return new bootstrap.Tooltip(el);
});
});
triggerTooltips();
//
// $('#settingsMenu').on('shown.bs.modal', function() {
// $("a[data-toggle='tooltip']").each(function (i, obj) {
@ -642,7 +686,7 @@ function sendConfig(modalId) {
req.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200 || this.status == 204) {
bootstrap.Modal.getInstance(document.getElementById(modalId)).hide();
createModal(modalId, true).hide();
if (modalId != 'settingsMenu') {
settingsModal.hide();
};
@ -699,4 +743,3 @@ document.getElementById('settingsSave').onclick = function() {
settingsModal.hide();
};
};

View File

@ -14,8 +14,15 @@
<!-- Bootstrap CSS -->
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
{% if not bs5 %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
{% if bs5 %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
{% else %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
{% endif %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
.pageContainer {
@ -109,7 +116,23 @@
</div>
<script src="serialize.js"></script>
<script>
{% if bs5 %}
var bsVersion = 5;
{% else %}
var bsVersion = 4;
{% endif %}
if (bsVersion == 5) {
var successBox = new bootstrap.Modal(document.getElementById('successBox'));
} else if (bsVersion == 4) {
var successBox = {
show : function() {
return $('#successBox').modal('show');
},
hide : function() {
return $('#successBox').modal('hide');
}
};
};
var code = window.location.href.split('/').pop();
function toggleSpinner () {
var submitButton = document.getElementById('submitButton');

View File

@ -6,8 +6,15 @@
<title>Invalid Code</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
{% if not bs5 %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
{% if bs5 %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
{% else %}
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
{% endif %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
.messageBox {

View File

@ -11,6 +11,7 @@ def page_not_found(e):
return (
render_template(
"404.html",
bs5=config.getboolean('ui', 'bs5'),
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
),
@ -23,6 +24,7 @@ def admin():
# return app.send_static_file('admin.html')
return render_template(
"admin.html",
bs5=config.getboolean('ui', 'bs5'),
css_file=css_file,
contactMessage="",
email_enabled=config.getboolean("invite_emails", "enabled"),
@ -32,10 +34,18 @@ def admin():
@app.route("/<path:path>")
def static_proxy(path):
if "html" not in path:
if "admin.js" in path:
if config.getboolean('ui', 'bs5'):
bsVersion = 5
else:
bsVersion = 4
return render_template("admin.js",
bsVersion=bsVersion)
return app.send_static_file(path)
return (
render_template(
"404.html",
bs5=config.getboolean('ui', 'bs5'),
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
),
@ -53,6 +63,7 @@ def inviteProxy(path):
email = ""
return render_template(
"form.html",
bs5=config.getboolean('ui', 'bs5'),
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
helpMessage=config["ui"]["help_message"],
@ -69,6 +80,7 @@ def inviteProxy(path):
log.debug("Attempted use of invalid invite")
return render_template(
"invalidCode.html",
bs5=config.getboolean('ui', 'bs5'),
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
)

5
package-lock.json generated
View File

@ -191,6 +191,11 @@
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0-alpha1.tgz",
"integrity": "sha512-iwKneP2pLXl8lN0YpnOuOARiNPTzmh/4cw+Un86u4OqrMLuQpyMC7nO07hvivvcg0B/ektJPjuPnS1s+YmRK9A=="
},
"bootstrap4": {
"version": "npm:bootstrap@4.5.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.0.tgz",
"integrity": "sha512-Z93QoXvodoVslA+PWNdk23Hze4RBYIkpb5h8I2HY2Tu2h7A0LpAgLcyrhrSUyo2/Oxm2l1fRZPs1e5hnxnliXA=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",

View File

@ -1,15 +1,16 @@
## SCSS
* `bs5-jf.scss` contains the source for the customizations to bootstrap. To customize the UI, you can make modifications to this file and then compile it.
* `bs<4/5>-jf.scss` contains the source for the customizations to bootstrap. To customize the UI, you can make modifications to this file and then compile it.
Note: assumes that bootstrap is installed in `../node_modules/bootstrap` relative to itself.
Note: For BS5, it assumes that bootstrap is installed in `../../node_modules/bootstrap` relative to itself.
For BS4, it assumes that bootstrap is installed in `../../node_modules/bootstrap4` relative to itself. (`npm install bootstrap4@npm:bootstrap`)
* Compilation requires a sass compiler of your choice, and `postcss-cli`, `autoprefixer` + `clean-css-cli` from npm.
* If you're using `sassc`, run `./compile.sh bs5-jf.scss` in this directory. This will create a .css file, and minified .css file.
* If you're using `sassc`, run `./compile.sh bs<4/5>-jf.scss` in this directory. This will create a .css file, and minified .css file.
* For `node-sass`, replace the `sassc` line in `compile.sh` with
```
node-sass --output-style expanded --precision 6 $1 $css_file
```
and run as above.
* If you're building from source, copy the minified css to `<jf-accounts git directory>/jellyfin_accounts/data/static/bs5-jf.css`.
* If you're building from source, copy the minified css to `<jf-accounts git directory>/jellyfin_accounts/data/static/bs<4/5>-jf.css`.
* If you're just customizing your install, set `custom_css` in your config as the path to your minified css.

9548
scss/bs4/bs4-jf.css Normal file

File diff suppressed because one or more lines are too long

7
scss/bs4/bs4-jf.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

136
scss/bs4/bs4-jf.scss Normal file
View File

@ -0,0 +1,136 @@
$jf-blue: rgb(0, 164, 220);
$jf-blue-hover: rgba(0, 164, 220, 0.2);
$jf-blue-focus: rgb(12, 176, 232);
$jf-blue-light: #4bb3dd;
$jf-red: rgb(204, 0, 0);
$jf-red-light: #e12026;
$jf-yellower: #ffc107;
$jf-yellow: #e1b222;
$jf-orange: #ff870f;
$jf-green: #6fbd45;
$jf-green-dark: #008040;
$jf-black: #101010; // 16 16 16
$jf-gray-90: #202020; // 32 32 32
$jf-gray-80: #242424; // jf-card 36 36 36
$jf-gray-70: #292929; // jf-input 41 41 41
$jf-gray-60: #303030; // jf-button 48 48 48
$jf-gray-50: #383838; // jf-button-focus 56 56 56
$jf-text-bold: rgba(255, 255, 255, 0.87);
$jf-text-primary: rgba(255, 255, 255, 0.8);
$jf-text-secondary: rgb(153, 153, 153);
$primary: $jf-blue;
$secondary: $jf-gray-50;
$success: $jf-green-dark;
$danger: $jf-red-light;
$light: $jf-text-primary;
$dark: $jf-gray-90;
$info: $jf-yellow;
$warning: $jf-yellower;
$enable-gradients: false;
$enable-shadows: false;
$enable-rounded: false;
$body-bg: $jf-black;
$body-color: $jf-text-primary;
$border-color: $jf-gray-60;
$component-active-color: $jf-text-bold;
$component-active-bg: $jf-blue-focus;
$text-muted: $jf-text-secondary;
$link-color: $jf-blue-focus;
$btn-link-disabled-color: $jf-text-secondary;
$input-bg: $jf-gray-90;
$input-color: $jf-text-primary;
$input-focus-bg: $jf-gray-60;
$input-focus-border-color: $jf-blue-focus;
$input-disabled-bg: $jf-gray-70;
input:disabled {
color: $text-muted;
}
$input-border-color: $jf-gray-60;
$input-placeholder-color: $text-muted;
$form-check-input-bg: $jf-gray-60;
$form-check-input-border: $jf-gray-50;
$form-check-input-checked-color: $jf-blue-focus;
$form-check-input-checked-bg-color: $jf-blue-hover;
$input-group-addon-bg: $input-bg;
$form-select-disabled-color: $jf-text-secondary;
$form-select-disabled-bg: $input-disabled-bg;
$form-select-indicator-color: $jf-gray-50;
$card-bg: $jf-gray-80;
$card-border-color: null;
$tooltip-color: $jf-text-bold;
$tooltip-bg: $jf-gray-50;
$modal-content-bg: $jf-gray-80;
$modal-content-border-color: $jf-gray-50;
$modal-header-border-color: null;
$modal-footer-border-color: null;
$list-group-bg: $card-bg;
$list-group-border-color: $jf-gray-50;
$list-group-hover-bg: $jf-blue-hover;
$list-group-active-bg: $jf-blue-focus;
$list-group-action-color: $jf-text-primary;
$list-group-action-hover-color: $jf-text-bold;
$list-group-action-active-color: $jf-text-bold;
$list-group-action-active-bg: $jf-blue-focus;
// idk why but i had to put these above and below the import
.list-group-item-danger {
color: $jf-text-bold;
background-color: $danger;
}
.list-group-item-success {
color: $jf-text-bold;
background-color: $success;
}
@import "../../node_modules/bootstrap4/scss/bootstrap";
.btn-primary, .btn-outline-primary:hover, .btn-outline-primary:active {
color: $jf-text-bold;
}
.close {
color: $jf-text-secondary;
}
.close:hover, .close:active {
color: $jf-text-primary;
}
.icon-button {
color: $text-muted;
}
.icon-button:hover {
color: $jf-text-bold;
}
.text-bright {
color: $jf-text-bold;
}
.list-group-item-danger {
color: $jf-text-bold;
background-color: $danger;
}
.list-group-item-success {
color: $jf-text-bold;
background-color: $success;
}

View File

@ -98,7 +98,7 @@ $list-group-action-active-bg: $jf-blue-focus;
background-color: $success;
}
@import "../node_modules/bootstrap/scss/bootstrap";
@import "../../node_modules/bootstrap/scss/bootstrap";
.btn-primary, .btn-outline-primary:hover, .btn-outline-primary:active {
color: $jf-text-bold;

10
scss/bs5/compile.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
css_file=$(echo $1 | sed 's/scss/css/g')
min_file=$(echo $1 | sed 's/scss/min.css/g')
sassc -t expanded -p 6 $1 $css_file
echo "Compiled."
postcss $css_file --replace --use autoprefixer
echo "Prefixed."
echo "Written to $css_file."
cleancss --level 1 --format breakWith=lf --source-map --source-map-inline-sources --output $min_file $css_file
echo "Minified version written to $min_file."