mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-27 23:13:01 -04:00
Merge branch 'opinions'
This commit is contained in:
commit
c3175be874
616
assets/dark.scss
616
assets/dark.scss
@ -3,333 +3,339 @@
|
||||
@import "variables";
|
||||
|
||||
*:hover > .hover-invertible {
|
||||
filter: invert(1);
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
body[data-theme="dark"] {
|
||||
$primary-dark: #ff95bb;
|
||||
|
||||
background: initial !important;
|
||||
background-color: $dark !important;
|
||||
color: $light;
|
||||
|
||||
a { color: $primary-dark; }
|
||||
a:hover { color: lighten($primary-dark, 10%); }
|
||||
.text-dark { color: $light !important; }
|
||||
.btn-primary { color: $light; }
|
||||
.btn-outline-primary { color: $light; &:hover {color: $light;}}
|
||||
.btn-outline-success { color: lighten($success, 20%); &:hover {color: $light;} }
|
||||
.alert-info { background-color: darken($info, 35%); border-color: darken($info, 25%); color: $light; }
|
||||
|
||||
/* BUTTONS */
|
||||
|
||||
.btn-link { color: #fff; }
|
||||
.btn-close { filter: invert(1) grayscale(100%) brightness(200%); }
|
||||
.btn-dark { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-dark:hover { color: #000; background-color: #f9fafb; border-color: #f9fafb; }
|
||||
.btn-check:focus + .btn-dark,
|
||||
.btn-dark:focus { color: #000; background-color: #f9fafb; border-color: #f9fafb; box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5); }
|
||||
.btn-check:checked + .btn-dark,
|
||||
.btn-check:active + .btn-dark,
|
||||
.btn-dark:active,
|
||||
.btn-dark.active,
|
||||
.show > .btn-dark.dropdown-toggle { color: #000; background-color: #f9fafb; border-color: #f9fafb; }
|
||||
.btn-check:checked + .btn-dark:focus,
|
||||
.btn-check:active + .btn-dark:focus,
|
||||
.btn-dark:active:focus,
|
||||
.btn-dark.active:focus,
|
||||
.show > .btn-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5); }
|
||||
.btn-dark:disabled,
|
||||
.btn-dark.disabled { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-light { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-light:hover { color: #fff; background-color: #1c1f23; border-color: #1a1e21; }
|
||||
.btn-check:focus + .btn-light,
|
||||
.btn-light:focus { color: #fff; background-color: #1c1f23; border-color: #1a1e21; box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5); }
|
||||
.btn-check:checked + .btn-light,
|
||||
.btn-check:active + .btn-light,
|
||||
.btn-light:active,
|
||||
.btn-light.active,
|
||||
.show > .btn-light.dropdown-toggle { color: #fff; background-color: #1a1e21; border-color: #191c1f; }
|
||||
.btn-check:checked + .btn-light:focus,
|
||||
.btn-check:active + .btn-light:focus,
|
||||
.btn-light:active:focus,
|
||||
.btn-light.active:focus,
|
||||
.show > .btn-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5); }
|
||||
.btn-light:disabled,
|
||||
.btn-light.disabled { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-outline-dark { color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-outline-dark:hover { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-check:focus + .btn-outline-dark,
|
||||
.btn-outline-dark:focus { box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5); }
|
||||
.btn-check:checked + .btn-outline-dark,
|
||||
.btn-check:active + .btn-outline-dark,
|
||||
.btn-outline-dark:active,
|
||||
.btn-outline-dark.active,
|
||||
.btn-outline-dark.dropdown-toggle.show { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-check:checked + .btn-outline-dark:focus,
|
||||
.btn-check:active + .btn-outline-dark:focus,
|
||||
.btn-outline-dark:active:focus,
|
||||
.btn-outline-dark.active:focus,
|
||||
.btn-outline-dark.dropdown-toggle.show:focus { box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5); }
|
||||
.btn-outline-dark:disabled,
|
||||
.btn-outline-dark.disabled { color: #f8f9fa; background-color: transparent; }
|
||||
.btn-outline-light { color: #212529; border-color: #212529; }
|
||||
.btn-outline-light:hover { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-check:focus + .btn-outline-light,
|
||||
.btn-outline-light:focus { box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5); }
|
||||
.btn-check:checked + .btn-outline-light,
|
||||
.btn-check:active + .btn-outline-light,
|
||||
.btn-outline-light:active,
|
||||
.btn-outline-light.active,
|
||||
.btn-outline-light.dropdown-toggle.show { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-check:checked + .btn-outline-light:focus,
|
||||
.btn-check:active + .btn-outline-light:focus,
|
||||
.btn-outline-light:active:focus,
|
||||
.btn-outline-light.active:focus,
|
||||
.btn-outline-light.dropdown-toggle.show:focus { box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5); }
|
||||
.btn-outline-light:disabled,
|
||||
.btn-outline-light.disabled { color: #212529; background-color: transparent; }
|
||||
|
||||
/* DROPDOWNS */
|
||||
|
||||
.dropdown-menu { color: #dee2e6; background-color: #343a40; border-color: rgba(0, 0, 0, 0.15); }
|
||||
.dropdown-menu .dropdown-item { color: #dee2e6; }
|
||||
.dropdown-menu .dropdown-item:hover,
|
||||
.dropdown-menu .dropdown-item:focus { color: #fff; background-color: rgba(255, 255, 255, 0.15); }
|
||||
.dropdown-menu .dropdown-item.active,
|
||||
.dropdown-menu .dropdown-item:active { color: #fff; background-color: #0d6efd; }
|
||||
.dropdown-menu .dropdown-item.disabled,
|
||||
.dropdown-menu .dropdown-item:disabled { color: #adb5bd; }
|
||||
.dropdown-menu .dropdown-divider { border-color: rgba(0, 0, 0, 0.15); }
|
||||
.dropdown-menu .dropdown-item-text { color: #dee2e6; }
|
||||
.dropdown-menu .dropdown-header { color: #adb5bd; }
|
||||
|
||||
/* LIST GROUPS */
|
||||
|
||||
.list-group-item-action { color: #fff; }
|
||||
.list-group-item-action:hover,
|
||||
.list-group-item-action:focus { color: #fff; background-color: #333; }
|
||||
.list-group-item-action:active { color: #212529; background-color: #e9ecef; }
|
||||
.list-group-item { color: #eee; background-color: #111; border: 1px solid #333; }
|
||||
.list-group-item:not(:first-child) { border-top: none; }
|
||||
.list-group-item.disabled,
|
||||
.list-group-item:disabled { color: #fff; background-color: #000; }
|
||||
.list-group-item.active { color: #fff; background-color: #0d6efd; border-color: #0d6efd; }
|
||||
|
||||
/* TABS */
|
||||
|
||||
.nav-tabs { border-bottom: 1px solid #000; }
|
||||
.nav-tabs .nav-link:hover,
|
||||
[data-theme="dark"].nav-tabs .nav-link:focus { border-color: #212529 #212529 #000; }
|
||||
.nav-tabs .nav-link.disabled { color: #333; }
|
||||
.nav-tabs .nav-link.active,
|
||||
.nav-tabs .nav-item.show .nav-link { color: #fff; background-color: #000; border-color: #212529 #212529 #000; }
|
||||
|
||||
/* PAGINATION */
|
||||
|
||||
.page-link { color: #fff; background-color: #111; border: 1px solid #333; }
|
||||
.page-link:hover { color: #fff; background-color: #333; border-color: #333; }
|
||||
.page-link:focus { color: #fff; background-color: #000; }
|
||||
.page-item.active .page-link { color: #fff; background-color: #0d6efd; border-color: #0d6efd; }
|
||||
.page-item.disabled .page-link { color: #fff; background-color: #000; border-color: #333; }
|
||||
|
||||
/* BACKGROUNDS */
|
||||
|
||||
.bg-light { background-color: #212529 !important; }
|
||||
.bg-dark { background-color: #f8f9fa !important; }
|
||||
.bg-white { background-color: #000 !important; }
|
||||
.bg-white.text-white,
|
||||
.bg-dark.text-white,
|
||||
.bg-warning.text-dark,
|
||||
.bg-info.text-dark { color: #212529 !important; }
|
||||
|
||||
/* BORDERS */
|
||||
|
||||
.border,
|
||||
.border-top,
|
||||
.border-end,
|
||||
.border-bottom,
|
||||
.border-start { border-color: rgba(66, 70, 73, 0.5) !important; }
|
||||
|
||||
/* BREADCRUMB */
|
||||
|
||||
.breadcrumb.border { border-color: #333 !important; }
|
||||
|
||||
/* SHADOWS */
|
||||
|
||||
.shadow { box-shadow: 0 0.5rem 1rem rgba(255, 255, 255, 0.15) !important; }
|
||||
.shadow-sm { box-shadow: 0 0.125rem 0.25rem rgba(255, 255, 255, 0.075) !important; }
|
||||
.shadow-lg { box-shadow: 0 1rem 3rem rgba(255, 255, 255, 0.175) !important; }
|
||||
|
||||
.cookie-consent { box-shadow: 0 -0.5rem 1rem rgba(255, 255, 255, 0.15) !important; }
|
||||
|
||||
/* CARDS */
|
||||
|
||||
.card { background-color: #000; border: 1px solid rgba(255, 255, 255, 0.125); }
|
||||
|
||||
/* MODALS */
|
||||
|
||||
.modal-content { background-color: #000; border: 1px solid rgba(255, 255, 255, 0.2); }
|
||||
.modal-header { border-bottom: 1px solid #212529; }
|
||||
.modal-footer { border-top: 1px solid #212529; }
|
||||
.modal-backdrop.show { opacity: 0.75; }
|
||||
|
||||
/* ACCORDIONS */
|
||||
|
||||
.accordion:not(.accordion-flush) { border-top: 1px solid #333; border-top-left-radius: 0.25rem !important; border-top-right-radius: 0.25rem !important; }
|
||||
.accordion-button { color: #fff; background-color: #000; border: 1px solid #333; border-top: none; }
|
||||
.accordion-button:not(.collapsed) { color: #fff; background-color: #111; border-bottom: none; }
|
||||
.accordion-button::after { filter: invert(1) grayscale(100%) brightness(200%); }
|
||||
.accordion-collapse { border: 1px solid #333; }
|
||||
.accordion-flush .accordion-button { border-right: 0; border-left: 0; border-radius: 0; }
|
||||
.accordion-flush .accordion-collapse { border-width: 0; }
|
||||
.accordion-flush .accordion-item:first-of-type .accordion-button { border-top-width: 0; border-top-left-radius: 0; border-top-right-radius: 0; }
|
||||
.accordion-flush .accordion-item:last-of-type .accordion-button.collapsed { border-bottom-width: 0; border-bottom-right-radius: 0; border-bottom-left-radius: 0; }
|
||||
|
||||
/* PROGRESS BARS */
|
||||
|
||||
.progress { background-color: #333; }
|
||||
|
||||
/* FORMS */
|
||||
|
||||
.form-control::-webkit-input-placeholder,
|
||||
.form-control::-moz-placeholder,
|
||||
.form-control::placeholder { color: $light; }
|
||||
.form-control-plaintext { color: $light; }
|
||||
.form-control { color: #fff; background-color: #333; border: 1px solid #111; }
|
||||
.form-select { color: #fff; border-color: #111 !important; background: #333 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23ffffff' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; border: 1px solid #111; }
|
||||
.form-select:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem #dc3545; background-color: rgba(255,0,0,0.1); }
|
||||
.form-select:focus::-ms-value { color: #495057; background-color: #fff; }
|
||||
.form-select option { color: #000; }
|
||||
.form-select:disabled { color: #6c757d; background-color: #e9ecef; }
|
||||
.form-select:-moz-focusring { text-shadow: 0 0 0 #495057; }
|
||||
|
||||
/* CUSTOM */
|
||||
.separator {
|
||||
> .mask {
|
||||
&:after {
|
||||
box-shadow: 0 0 10px $light;
|
||||
}
|
||||
}
|
||||
> span {
|
||||
box-shadow:0 2px 4px $light;
|
||||
background: tint-color($light, 80%);
|
||||
color: $dark;
|
||||
}
|
||||
}
|
||||
|
||||
.invertible {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #848171;
|
||||
background: initial !important;
|
||||
background-color: $dark !important;
|
||||
color: $light;
|
||||
}
|
||||
|
||||
.table, .table-striped {
|
||||
> tbody > tr, > thead > tr, > tfoot > tr {
|
||||
td, th {
|
||||
a { color: $primary-dark; }
|
||||
a:hover { color: lighten($primary-dark, 10%); }
|
||||
.text-dark { color: $light !important; }
|
||||
.btn-primary { color: $light; }
|
||||
.btn-outline-primary { color: $light; &:hover {color: $light;}}
|
||||
.btn-outline-success { color: lighten($success, 20%); &:hover {color: $light;} }
|
||||
.alert-info { background-color: darken($info, 35%); border-color: darken($info, 25%); color: $light; }
|
||||
|
||||
/* BUTTONS */
|
||||
|
||||
.btn-link { color: #fff; }
|
||||
.btn-close { filter: invert(1) grayscale(100%) brightness(200%); }
|
||||
.btn-dark { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-dark:hover { color: #000; background-color: #f9fafb; border-color: #f9fafb; }
|
||||
.btn-check:focus + .btn-dark,
|
||||
.btn-dark:focus { color: #000; background-color: #f9fafb; border-color: #f9fafb; box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5); }
|
||||
.btn-check:checked + .btn-dark,
|
||||
.btn-check:active + .btn-dark,
|
||||
.btn-dark:active,
|
||||
.btn-dark.active,
|
||||
.show > .btn-dark.dropdown-toggle { color: #000; background-color: #f9fafb; border-color: #f9fafb; }
|
||||
.btn-check:checked + .btn-dark:focus,
|
||||
.btn-check:active + .btn-dark:focus,
|
||||
.btn-dark:active:focus,
|
||||
.btn-dark.active:focus,
|
||||
.show > .btn-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5); }
|
||||
.btn-dark:disabled,
|
||||
.btn-dark.disabled { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-light { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-light:hover { color: #fff; background-color: #1c1f23; border-color: #1a1e21; }
|
||||
.btn-check:focus + .btn-light,
|
||||
.btn-light:focus { color: #fff; background-color: #1c1f23; border-color: #1a1e21; box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5); }
|
||||
.btn-check:checked + .btn-light,
|
||||
.btn-check:active + .btn-light,
|
||||
.btn-light:active,
|
||||
.btn-light.active,
|
||||
.show > .btn-light.dropdown-toggle { color: #fff; background-color: #1a1e21; border-color: #191c1f; }
|
||||
.btn-check:checked + .btn-light:focus,
|
||||
.btn-check:active + .btn-light:focus,
|
||||
.btn-light:active:focus,
|
||||
.btn-light.active:focus,
|
||||
.show > .btn-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5); }
|
||||
.btn-light:disabled,
|
||||
.btn-light.disabled { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-outline-dark { color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-outline-dark:hover { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-check:focus + .btn-outline-dark,
|
||||
.btn-outline-dark:focus { box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5); }
|
||||
.btn-check:checked + .btn-outline-dark,
|
||||
.btn-check:active + .btn-outline-dark,
|
||||
.btn-outline-dark:active,
|
||||
.btn-outline-dark.active,
|
||||
.btn-outline-dark.dropdown-toggle.show { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-check:checked + .btn-outline-dark:focus,
|
||||
.btn-check:active + .btn-outline-dark:focus,
|
||||
.btn-outline-dark:active:focus,
|
||||
.btn-outline-dark.active:focus,
|
||||
.btn-outline-dark.dropdown-toggle.show:focus { box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5); }
|
||||
.btn-outline-dark:disabled,
|
||||
.btn-outline-dark.disabled { color: #f8f9fa; background-color: transparent; }
|
||||
.btn-outline-light { color: #212529; border-color: #212529; }
|
||||
.btn-outline-light:hover { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-check:focus + .btn-outline-light,
|
||||
.btn-outline-light:focus { box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5); }
|
||||
.btn-check:checked + .btn-outline-light,
|
||||
.btn-check:active + .btn-outline-light,
|
||||
.btn-outline-light:active,
|
||||
.btn-outline-light.active,
|
||||
.btn-outline-light.dropdown-toggle.show { color: #fff; background-color: #212529; border-color: #212529; }
|
||||
.btn-check:checked + .btn-outline-light:focus,
|
||||
.btn-check:active + .btn-outline-light:focus,
|
||||
.btn-outline-light:active:focus,
|
||||
.btn-outline-light.active:focus,
|
||||
.btn-outline-light.dropdown-toggle.show:focus { box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5); }
|
||||
.btn-outline-light:disabled,
|
||||
.btn-outline-light.disabled { color: #212529; background-color: transparent; }
|
||||
|
||||
/* DROPDOWNS */
|
||||
|
||||
.dropdown-menu { color: #dee2e6; background-color: #343a40; border-color: rgba(0, 0, 0, 0.15); }
|
||||
.dropdown-menu .dropdown-item { color: #dee2e6; }
|
||||
.dropdown-menu .dropdown-item:hover,
|
||||
.dropdown-menu .dropdown-item:focus { color: #fff; background-color: rgba(255, 255, 255, 0.15); }
|
||||
.dropdown-menu .dropdown-item.active,
|
||||
.dropdown-menu .dropdown-item:active { color: #fff; background-color: #0d6efd; }
|
||||
.dropdown-menu .dropdown-item.disabled,
|
||||
.dropdown-menu .dropdown-item:disabled { color: #adb5bd; }
|
||||
.dropdown-menu .dropdown-divider { border-color: rgba(0, 0, 0, 0.15); }
|
||||
.dropdown-menu .dropdown-item-text { color: #dee2e6; }
|
||||
.dropdown-menu .dropdown-header { color: #adb5bd; }
|
||||
|
||||
/* LIST GROUPS */
|
||||
|
||||
.list-group-item-action { color: #fff; }
|
||||
.list-group-item-action:hover,
|
||||
.list-group-item-action:focus { color: #fff; background-color: #333; }
|
||||
.list-group-item-action:active { color: #212529; background-color: #e9ecef; }
|
||||
.list-group-item { color: #eee; background-color: #111; border: 1px solid #333; }
|
||||
.list-group-item:not(:first-child) { border-top: none; }
|
||||
.list-group-item.disabled,
|
||||
.list-group-item:disabled { color: #fff; background-color: #000; }
|
||||
.list-group-item.active { color: #fff; background-color: #0d6efd; border-color: #0d6efd; }
|
||||
|
||||
/* TABS */
|
||||
|
||||
.nav-tabs { border-bottom: 1px solid #000; }
|
||||
.nav-tabs .nav-link:hover,
|
||||
[data-theme="dark"].nav-tabs .nav-link:focus { border-color: #212529 #212529 #000; }
|
||||
.nav-tabs .nav-link.disabled { color: #333; }
|
||||
.nav-tabs .nav-link.active,
|
||||
.nav-tabs .nav-item.show .nav-link { color: #fff; background-color: #000; border-color: #212529 #212529 #000; }
|
||||
|
||||
/* PAGINATION */
|
||||
|
||||
.page-link { color: #fff; background-color: #111; border: 1px solid #333; }
|
||||
.page-link:hover { color: #fff; background-color: #333; border-color: #333; }
|
||||
.page-link:focus { color: #fff; background-color: #000; }
|
||||
.page-item.active .page-link { color: #fff; background-color: #0d6efd; border-color: #0d6efd; }
|
||||
.page-item.disabled .page-link { color: #fff; background-color: #000; border-color: #333; }
|
||||
|
||||
/* BACKGROUNDS */
|
||||
|
||||
.bg-light { background-color: #212529 !important; }
|
||||
.bg-dark { background-color: #f8f9fa !important; }
|
||||
.bg-white { background-color: #000 !important; }
|
||||
.bg-white.text-white,
|
||||
.bg-dark.text-white,
|
||||
.bg-warning.text-dark,
|
||||
.bg-info.text-dark { color: #212529 !important; }
|
||||
|
||||
/* BORDERS */
|
||||
|
||||
.border,
|
||||
.border-top,
|
||||
.border-end,
|
||||
.border-bottom,
|
||||
.border-start { border-color: rgba(66, 70, 73, 0.5) !important; }
|
||||
|
||||
/* BREADCRUMB */
|
||||
|
||||
.breadcrumb.border { border-color: #333 !important; }
|
||||
|
||||
/* SHADOWS */
|
||||
|
||||
.shadow { box-shadow: 0 0.5rem 1rem rgba(255, 255, 255, 0.15) !important; }
|
||||
.shadow-sm { box-shadow: 0 0.125rem 0.25rem rgba(255, 255, 255, 0.075) !important; }
|
||||
.shadow-lg { box-shadow: 0 1rem 3rem rgba(255, 255, 255, 0.175) !important; }
|
||||
|
||||
.cookie-consent { box-shadow: 0 -0.5rem 1rem rgba(255, 255, 255, 0.15) !important; }
|
||||
|
||||
/* CARDS */
|
||||
|
||||
.card { background-color: #000; border: 1px solid rgba(255, 255, 255, 0.125); }
|
||||
|
||||
/* MODALS */
|
||||
|
||||
.modal-content { background-color: #000; border: 1px solid rgba(255, 255, 255, 0.2); }
|
||||
.modal-header { border-bottom: 1px solid #212529; }
|
||||
.modal-footer { border-top: 1px solid #212529; }
|
||||
.modal-backdrop.show { opacity: 0.75; }
|
||||
|
||||
/* ACCORDIONS */
|
||||
|
||||
.accordion:not(.accordion-flush) { border-top: 1px solid #333; border-top-left-radius: 0.25rem !important; border-top-right-radius: 0.25rem !important; }
|
||||
.accordion-button { color: #fff; background-color: #000; border: 1px solid #333; border-top: none; }
|
||||
.accordion-button:not(.collapsed) { color: #fff; background-color: #111; border-bottom: none; }
|
||||
.accordion-button::after { filter: invert(1) grayscale(100%) brightness(200%); }
|
||||
.accordion-collapse { border: 1px solid #333; }
|
||||
.accordion-flush .accordion-button { border-right: 0; border-left: 0; border-radius: 0; }
|
||||
.accordion-flush .accordion-collapse { border-width: 0; }
|
||||
.accordion-flush .accordion-item:first-of-type .accordion-button { border-top-width: 0; border-top-left-radius: 0; border-top-right-radius: 0; }
|
||||
.accordion-flush .accordion-item:last-of-type .accordion-button.collapsed { border-bottom-width: 0; border-bottom-right-radius: 0; border-bottom-left-radius: 0; }
|
||||
|
||||
/* PROGRESS BARS */
|
||||
|
||||
.progress { background-color: #333; }
|
||||
|
||||
/* FORMS */
|
||||
|
||||
.form-control::-webkit-input-placeholder,
|
||||
.form-control::-moz-placeholder,
|
||||
.form-control::placeholder { color: $light; }
|
||||
.form-control-plaintext { color: $light; }
|
||||
.form-control { color: #fff; background-color: #333; border: 1px solid #111; }
|
||||
.form-select { color: #fff; border-color: #111 !important; background: #333 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23ffffff' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; border: 1px solid #111; }
|
||||
.form-select:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem #dc3545; background-color: rgba(255,0,0,0.1); }
|
||||
.form-select:focus::-ms-value { color: #495057; background-color: #fff; }
|
||||
.form-select option { color: #000; }
|
||||
.form-select:disabled { color: #6c757d; background-color: #e9ecef; }
|
||||
.form-select:-moz-focusring { text-shadow: 0 0 0 #495057; }
|
||||
|
||||
/* CUSTOM */
|
||||
.separator {
|
||||
> .mask {
|
||||
&:after {
|
||||
box-shadow: 0 0 10px $light;
|
||||
}
|
||||
}
|
||||
> span {
|
||||
box-shadow:0 2px 4px $light;
|
||||
background: tint-color($light, 80%);
|
||||
color: $dark;
|
||||
}
|
||||
}
|
||||
|
||||
.invertible {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #848171;
|
||||
color: $light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
.table, .table-striped {
|
||||
> tbody > tr, > thead > tr, > tfoot > tr {
|
||||
td, th {
|
||||
color: $dark;
|
||||
> tbody > tr, > thead > tr, > tfoot > tr {
|
||||
td, th {
|
||||
color: $light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
.nav-item {
|
||||
color: $light;
|
||||
&.active, &:hover {
|
||||
color: $primary-dark !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down('lg', $grid-breakpoints) {
|
||||
.nav-custom {
|
||||
.btn {
|
||||
&:hover, &:focus, &.active {
|
||||
border-inline-start: 3px solid $primary-dark !important;
|
||||
.alert {
|
||||
.table, .table-striped {
|
||||
> tbody > tr, > thead > tr, > tfoot > tr {
|
||||
td, th {
|
||||
color: $dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-custom-start {
|
||||
.btn {
|
||||
&:hover, &:focus, &.active {
|
||||
border-inline-start: 3px solid $primary-dark !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('lg', $grid-breakpoints) {
|
||||
.nav-custom:not(.nav-custom-start) {
|
||||
.nav-item {
|
||||
&.btn {
|
||||
&:hover, &:focus, &.active {
|
||||
border-bottom: 3px solid $primary-dark !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('lg', $grid-breakpoints) {
|
||||
header {
|
||||
@supports not (backdrop-filter: blur(12px)) {
|
||||
.nav-item {
|
||||
color: $light;
|
||||
&.active, &:hover {
|
||||
color: $primary-dark !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down('lg', $grid-breakpoints) {
|
||||
.nav-custom {
|
||||
.btn {
|
||||
&:hover, &:focus, &.active {
|
||||
border-inline-start: 3px solid $primary-dark !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-custom-start {
|
||||
.btn {
|
||||
&:hover, &:focus, &.active {
|
||||
border-inline-start: 3px solid $primary-dark !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('lg', $grid-breakpoints) {
|
||||
.nav-custom:not(.nav-custom-start) {
|
||||
.nav-item {
|
||||
&.btn {
|
||||
&:hover, &:focus, &.active {
|
||||
border-bottom: 3px solid $primary-dark !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('lg', $grid-breakpoints) {
|
||||
header {
|
||||
@supports not (backdrop-filter: blur(12px)) {
|
||||
background-color: $dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alert-primary, .alert-success, .alert-warning, .alert-danger {
|
||||
a {
|
||||
color: $primary-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-warning a.btn-primary {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.alert-light {
|
||||
background-color: $gray-800;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.badge.bg-dark {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar {
|
||||
background-color: $dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alert-primary, .alert-success, .alert-warning, .alert-danger {
|
||||
a {
|
||||
color: $primary-dark;
|
||||
.day-event-0 {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-warning a.btn-primary {
|
||||
color: $white;
|
||||
}
|
||||
.profile-current {
|
||||
border-inline-start: 3px solid $primary-dark !important;
|
||||
}
|
||||
|
||||
.alert-light {
|
||||
background-color: $gray-800;
|
||||
color: $white;
|
||||
}
|
||||
.list-group-flare > :first-child {
|
||||
border-top: 3px solid $primary-dark !important;
|
||||
}
|
||||
|
||||
.badge.bg-dark {
|
||||
color: $black;
|
||||
}
|
||||
code {
|
||||
color: lighten($code-color, 45%);
|
||||
background-color: darken($code-color, 30%);
|
||||
border: 1px solid lighten($code-color, 30%);
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar {
|
||||
background-color: $dark;
|
||||
}
|
||||
|
||||
.day-event-0 {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.profile-current {
|
||||
border-inline-start: 3px solid $primary-dark !important;
|
||||
}
|
||||
|
||||
.list-group-flare > :first-child {
|
||||
border-top: 3px solid $primary-dark !important;
|
||||
}
|
||||
|
||||
code {
|
||||
color: lighten($code-color, 45%);
|
||||
background-color: darken($code-color, 30%);
|
||||
border: 1px solid lighten($code-color, 30%);
|
||||
}
|
||||
&:not(.reduced-colours) {
|
||||
@each $name, $value in $colours {
|
||||
.colour-#{$name} {
|
||||
color: map-get($value, 'dark') !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
@use "sass:list";
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Emoji&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Emoji:wght@700&display=swap');
|
||||
|
||||
@if list.index($fonts, 'Quicksand') {
|
||||
/* quicksand-regular - latin-ext_latin */
|
||||
|
@ -246,3 +246,18 @@ form[disabled] {
|
||||
border-inline-start: 3px solid $primary;
|
||||
padding-inline-start: calc(#{$list-group-item-padding-x} - 2px);
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.italics {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
body:not(.reduced-colours) {
|
||||
@each $name, $value in $colours {
|
||||
.colour-#{$name} {
|
||||
color: map-get($value, 'light') !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,3 +33,14 @@ $square-button-size: 2.2rem;
|
||||
@import "~bootstrap/scss/utilities";
|
||||
|
||||
@import '~@fortawesome/fontawesome-pro/scss/variables';
|
||||
|
||||
$primary-dark: #ff95bb;
|
||||
|
||||
$colours: (
|
||||
'pink': ('light': $primary, 'dark': $primary-dark),
|
||||
'red': ('light': $red, 'dark': $red-200),
|
||||
'orange': ('light': $orange-600, 'dark': $orange-300),
|
||||
'green': ('light': $green, 'dark': $green-300),
|
||||
'blue': ('light': $blue-700, 'dark': $blue-200),
|
||||
'grey': ('light': $gray-600, 'dark': $gray-300),
|
||||
);
|
||||
|
@ -54,7 +54,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
enabled() {
|
||||
return this.config.ads?.enabled;
|
||||
return this.config.ads?.enabled && process.env.NODE_ENV !== 'development';
|
||||
},
|
||||
visible() {
|
||||
return this.enabled && this.consent === undefined;
|
||||
|
63
components/IconSelector.vue
Normal file
63
components/IconSelector.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="bg-light border rounded">
|
||||
<input class="form-control mb-1" v-model="filter" :placeholder="$t('crud.search')" ref="filter"/>
|
||||
<ul class="list-unstyled icons-list p-2 text-center">
|
||||
<li v-for="icon in visibleIcons"
|
||||
class="list-inline-item">
|
||||
<button class="btn btn-outline-dark border-0 my-2" @click.prevent="$emit('change', icon.name)">
|
||||
<Icon :v="icon.name"/>
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="!showAll && visibleIcons.length >= displayLimit" class="list-inline-item">
|
||||
<button class="btn btn-outline-dark border-0 my-2" @click.prevent="showAll = true">
|
||||
<T>crud.loadAll</T>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import icons from '../src/icons';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
styles: { 'default': () => ['light'] },
|
||||
skipIcons: { 'default': () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icons,
|
||||
filter: '',
|
||||
showAll: false,
|
||||
displayLimit: 27,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.filter.focus();
|
||||
},
|
||||
computed: {
|
||||
visibleIcons() {
|
||||
return this.icons.filter(this.matches).slice(0, this.showAll ? undefined : this.displayLimit);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
matches(icon) {
|
||||
return !this.skipIcons.includes(icon.name)
|
||||
&& icon.styles.filter(v => this.styles.includes(v)).length > 0
|
||||
&& (
|
||||
this.filter === ''
|
||||
|| icon.searchTerms.filter(t => t.includes(this.filter.toLowerCase())).length > 0
|
||||
)
|
||||
;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icons-list {
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
108
components/LegendOpinionListInput.vue
Normal file
108
components/LegendOpinionListInput.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<ListInput v-model="v" :prototype="prototype()" :group="group" :readonly="readonly" :maxlength="maxlength">
|
||||
<template v-slot="s">
|
||||
<button type="button" :class="['btn', readonly ? 'btn-light border' : 'btn-outline-secondary', showIconSelector === s.i ? 'btn-secondary text-white border' : '']" :disabled="readonly"
|
||||
@click="showIconSelector = showIconSelector === s.i ? false : s.i">
|
||||
<Icon :v="s.val.icon"/>
|
||||
</button>
|
||||
<input v-model="s.val.description" class="form-control" :readonly="readonly"
|
||||
@keyup="s.update(s.val)" @paste="$nextTick(() => s.update(s.val))" @change="s.update(s.val)"
|
||||
required maxlength="24"
|
||||
:placeholder="$t('profile.opinions.description')"
|
||||
/>
|
||||
<select :class="['form-control', s.val.colour ? 'colour-' + s.val.colour : 'text-muted']" v-model="s.val.colour" @change="s.update(s.val)" :disabled="readonly">
|
||||
<option v-for="colour in colours" :value="colour">{{$t(`profile.opinions.colours.${colour || '_'}`)}}</option>
|
||||
</select>
|
||||
<select :class="['form-control', s.val.style || 'text-muted']" v-model="s.val.style" @change="s.update(s.val)" :disabled="readonly">
|
||||
<option v-for="st in styles" :value="st">{{$t(`profile.opinions.styles.${st || '_'}`)}}</option>
|
||||
</select>
|
||||
|
||||
<IconSelector v-if="showIconSelector === s.i" class="hanging shadow shadow-lg border"
|
||||
:skipIcons="skipIcons"
|
||||
@change="s.update({...s.val, icon: $event}); showIconSelector = false"/>
|
||||
</template>
|
||||
<template v-slot:validation="s">
|
||||
<p v-if="validation(s.val)" class="small text-danger">
|
||||
<Icon v="exclamation-triangle"/>
|
||||
<span class="ml-1">{{$t(validation(s.val))}}</span>
|
||||
</p>
|
||||
</template>
|
||||
</ListInput>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { colours, styles } from '../src/styling';
|
||||
import opinions from '../src/opinions';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {},
|
||||
group: {},
|
||||
readonly: { type: Boolean },
|
||||
maxlength: { 'default': null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v: this.value,
|
||||
showIconSelector: false,
|
||||
colours,
|
||||
styles,
|
||||
skipIcons: [...Object.values(opinions).map(op => op.icon), 'ad', 'helicopter'],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
v() { this.$emit('input', this.v); },
|
||||
value(v) { this.v = v; }
|
||||
},
|
||||
methods: {
|
||||
prototype() {
|
||||
return {icon: '', description: '', colour: '', style: ''};
|
||||
},
|
||||
setIcon(icon) {
|
||||
this.v.icon = icon;
|
||||
this.showIconSelector = false;
|
||||
this.$emit('input', this.v);
|
||||
},
|
||||
validation(v) {
|
||||
if (JSON.stringify(v) === JSON.stringify(this.prototype())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!v.icon) {
|
||||
return 'profile.opinions.validation.missingIcon';
|
||||
}
|
||||
if (!v.description) {
|
||||
return 'profile.opinions.validation.missingDescription';
|
||||
}
|
||||
if (this.v.filter(el => el.icon === v.icon).length > 1) {
|
||||
return 'profile.opinions.validation.duplicateIcon';
|
||||
}
|
||||
if (this.v.filter(el => el.description === v.description).length > 1) {
|
||||
return 'profile.opinions.validation.duplicateDescription';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "assets/variables";
|
||||
|
||||
// TODO
|
||||
select > option.small {
|
||||
font-size: $small-font-size !important;
|
||||
}
|
||||
|
||||
.hanging {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
z-index: 5000;
|
||||
}
|
||||
|
||||
</style>
|
@ -3,22 +3,24 @@
|
||||
<li v-for="(v, i) in iVal" ref="items">
|
||||
<div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<button class="btn btn-light border handle" type="button" :aria-label="$t('table.sort')">
|
||||
<button :class="['btn', 'btn-light border', readonly ? '' : 'handle']" type="button" :aria-label="$t('table.sort')" :disabled="readonly">
|
||||
<Icon v="bars"/>
|
||||
</button>
|
||||
<slot v-bind:val="iVal[i]" v-bind:update="curry(update)(i)">
|
||||
<input v-model="iVal[i]" type="text" class="form-control" required/>
|
||||
<slot v-bind:val="iVal[i]" v-bind:update="curry(update)(i)" v-bind:i="i">
|
||||
<input v-model="iVal[i]" type="text" class="form-control" required :readonly="readonly"/>
|
||||
</slot>
|
||||
<button class="btn btn-outline-danger" type="button" @click.prevent="remove(i)" :aria-label="$t('crud.remove')">
|
||||
<button :class="['btn', readonly ? 'btn-light border' : 'btn-outline-danger']" type="button" @click.prevent="remove(i)" :aria-label="$t('crud.remove')" :disabled="readonly">
|
||||
<Icon v="times"/>
|
||||
</button>
|
||||
</div>
|
||||
<slot name="validation" v-bind:val="iVal[i]"></slot>
|
||||
<slot name="validation" v-bind:val="iVal[i]" v-bind:i="i"></slot>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li slot="footer">
|
||||
<button class="btn btn-outline-success w-100 btn-sm" type="button" @click.prevent="add" :aria-label="$t('crud.add')">
|
||||
<button v-if="!readonly && (maxlength === null || iVal.length < maxlength)"
|
||||
class="btn btn-outline-success w-100 btn-sm" type="button"
|
||||
@click.prevent="add" :aria-label="$t('crud.add')">
|
||||
<Icon v="plus"/>
|
||||
</button>
|
||||
</li>
|
||||
@ -37,6 +39,8 @@
|
||||
value: {},
|
||||
prototype: { 'default': '' },
|
||||
group: {},
|
||||
readonly: { type: Boolean },
|
||||
maxlength: { 'default': null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -1,52 +1,48 @@
|
||||
<template>
|
||||
<Twemoji>
|
||||
<span>
|
||||
<strong v-if="opinion === 'yes'">
|
||||
<Tooltip :text="$t('profile.opinion.yes')">
|
||||
<Icon v="heart" set="s"/>
|
||||
</Tooltip>
|
||||
<nuxt-link v-if="link" :to="link"><Spelling :escape="escape" :text="word"/></nuxt-link>
|
||||
<span v-if="op" :class="[ op.style, `colour-${op.colour}`]">
|
||||
<Tooltip :text="op.description">
|
||||
<Icon :v="op.icon"/>
|
||||
</Tooltip>
|
||||
<Twemoji>
|
||||
<nuxt-link v-if="link" :to="link" :class="`colour-${op.colour}`"><Spelling :escape="escape" :text="word"/></nuxt-link>
|
||||
<span v-else><Spelling :escape="escape" :text="word"/></span>
|
||||
</strong>
|
||||
<span v-else-if="opinion === 'jokingly'">
|
||||
<Tooltip :text="$t('profile.opinion.jokingly')">
|
||||
<Icon v="grin-tongue"/>
|
||||
</Tooltip>
|
||||
<nuxt-link v-if="link" :to="link"><Spelling :escape="escape" :text="word"/></nuxt-link>
|
||||
<span v-else><Spelling :escape="escape" :text="word"/></span>
|
||||
</span>
|
||||
<span v-else-if="opinion === 'close'">
|
||||
<Tooltip :text="$t('profile.opinion.close')">
|
||||
<Icon v="user-friends"/>
|
||||
</Tooltip>
|
||||
<nuxt-link v-if="link" :to="link"><Spelling :escape="escape" :text="word"/></nuxt-link>
|
||||
<span v-else><Spelling :escape="escape" :text="word"/></span>
|
||||
</span>
|
||||
<span v-else-if="opinion === 'meh'">
|
||||
<Tooltip :text="$t('profile.opinion.meh')">
|
||||
<Icon v="thumbs-up"/>
|
||||
</Tooltip>
|
||||
<nuxt-link v-if="link" :to="link"><Spelling :escape="escape" :text="word"/></nuxt-link>
|
||||
<span v-else><Spelling :escape="escape" :text="word"/></span>
|
||||
</span>
|
||||
<span v-else-if="opinion === 'no'" class="text-muted small">
|
||||
<Tooltip :text="$t('profile.opinion.no')">
|
||||
<Icon v="thumbs-down"/>
|
||||
</Tooltip>
|
||||
<nuxt-link v-if="link" :to="link"><Spelling :escape="escape" :text="word"/></nuxt-link>
|
||||
<span v-else><Spelling :escape="escape" :text="word"/></span>
|
||||
</span>
|
||||
</Twemoji>
|
||||
</span>
|
||||
</Twemoji>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import opinions from '../src/opinions';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
word: { required: true },
|
||||
opinion: { required: true },
|
||||
link: {},
|
||||
escape: { type: Boolean, 'default': () => true },
|
||||
customOpinions: { 'default': () => { return {} }},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
op: this.findOpinion(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
findOpinion() {
|
||||
if (opinions.hasOwnProperty(this.opinion)) {
|
||||
return {
|
||||
...opinions[this.opinion],
|
||||
description: this.$t(`profile.opinion.${this.opinion}`),
|
||||
};
|
||||
}
|
||||
|
||||
for (let op of Object.values(this.customOpinions)) {
|
||||
if (op.icon === this.opinion) {
|
||||
return op;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,29 +1,36 @@
|
||||
<template>
|
||||
<ul class="list-inline small text-muted text-center mx-4">
|
||||
<li class="list-inline-item">
|
||||
<Icon v="heart"/>
|
||||
=
|
||||
<T>profile.opinion.yes</T>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<Icon v="grin-tongue"/>
|
||||
=
|
||||
<T>profile.opinion.jokingly</T>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<Icon v="user-friends"/>
|
||||
=
|
||||
<T>profile.opinion.close</T>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<Icon v="thumbs-up"/>
|
||||
=
|
||||
<T>profile.opinion.meh</T>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<Icon v="thumbs-down"/>
|
||||
=
|
||||
<T>profile.opinion.no</T>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<ul class="list-inline small text-muted text-center mx-4">
|
||||
<li v-for="(opinion, key) in opinions" class="list-inline-item">
|
||||
<Icon :v="opinion.icon"/>
|
||||
=
|
||||
<T>profile.opinion.{{key}}</T>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-if="Object.keys(custom).length > 0" class="list-inline small text-muted text-center mx-4">
|
||||
<li class="list-inline-item">
|
||||
<T>profile.opinions.custom</T>
|
||||
</li>
|
||||
<li v-for="(opinion, key) in custom" class="list-inline-item">
|
||||
<Icon :v="opinion.icon"/>
|
||||
=
|
||||
{{opinion.description}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import opinions from '../src/opinions';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
custom: { 'default': () => { return {} }},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
opinions: opinions,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,62 +1,55 @@
|
||||
<template>
|
||||
<ListInput v-model="v" :prototype="{value: '', opinion: 'meh'}" :group="group">
|
||||
<template v-slot="s">
|
||||
<button type="button" :class="['btn', s.val.opinion === 'yes' ? 'btn-primary' : 'btn-outline-secondary', 'btn-thin']"
|
||||
:aria-label="$t('profile.opinion.yes')"
|
||||
@click="s.update({...s.val, value: s.val.value, opinion: 'yes'})">
|
||||
<Tooltip :text="$t('profile.opinion.yes')">
|
||||
<Icon v="heart"/>
|
||||
</Tooltip>
|
||||
<button type="button" :class="['btn', 'btn-outline-secondary', showOpinionSelector === s.i ? 'btn-secondary text-white border' : (validate(s.val) ? 'btn-outline-danger' : '')]"
|
||||
@click="showOpinionSelector = showOpinionSelector === s.i ? false : s.i">
|
||||
<Icon :v="getIcon(s.val.opinion)"/>
|
||||
</button>
|
||||
<button type="button" :class="['btn', s.val.opinion === 'jokingly' ? 'btn-primary' : 'btn-outline-secondary', 'btn-thin']"
|
||||
:aria-label="$t('profile.opinion.jokingly')"
|
||||
@click="s.update({...s.val, value: s.val.value, opinion: 'jokingly'})">
|
||||
<Tooltip :text="$t('profile.opinion.jokingly')">
|
||||
<Icon v="grin-tongue"/>
|
||||
</Tooltip>
|
||||
</button>
|
||||
<button type="button" :class="['btn', s.val.opinion === 'close' ? 'btn-primary' : 'btn-outline-secondary', 'btn-thin']"
|
||||
:aria-label="$t('profile.opinion.close')"
|
||||
@click="s.update({...s.val, value: s.val.value, opinion: 'close'})">
|
||||
<Tooltip :text="$t('profile.opinion.close')">
|
||||
<Icon v="user-friends"/>
|
||||
</Tooltip>
|
||||
</button>
|
||||
<button type="button" :class="['btn', s.val.opinion === 'meh' ? 'btn-primary' : 'btn-outline-secondary', 'btn-thin']"
|
||||
:aria-label="$t('profile.opinion.meh')"
|
||||
@click="s.update({...s.val, value: s.val.value, opinion: 'meh'})">
|
||||
<Tooltip :text="$t('profile.opinion.meh')">
|
||||
<Icon v="thumbs-up"/>
|
||||
</Tooltip>
|
||||
</button>
|
||||
<button type="button" :class="['btn', s.val.opinion === 'no' ? 'btn-primary' : 'btn-outline-secondary', 'btn-thin']"
|
||||
:aria-label="$t('profile.opinion.no')"
|
||||
@click="s.update({...s.val, value: s.val.value, opinion: 'no'})">
|
||||
<Tooltip :text="$t('profile.opinion.no')">
|
||||
<Icon v="thumbs-down"/>
|
||||
</Tooltip>
|
||||
</button>
|
||||
<input v-model="s.val.value" :class="['form-control', 'mw-input', invalid(s.val) ? 'border-danger' : '']" @keyup="s.update(s.val)" required/>
|
||||
<input v-model="s.val.value" :class="['form-control', 'mw-input', validate(s.val) ? 'border-danger' : '']" @keyup="s.update(s.val)" required/>
|
||||
|
||||
<div v-if="showOpinionSelector === s.i" class="bg-light border rounded hanging shadow shadow-lg">
|
||||
<ul class="list-unstyled icons-list p-1 text-center mb-0">
|
||||
<li v-for="(opinion, key) in opinions"
|
||||
class="list-inline-item">
|
||||
<button :class="['btn', key === s.val.opinion ? 'btn-dark' : 'btn-outline-dark', 'border-0 my-2']" @click.prevent="s.val.opinion = key; showOpinionSelector = false">
|
||||
<Icon :v="opinion.icon"/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-if="customOpinions.length" class="list-unstyled icons-list p-1 text-center mb-0">
|
||||
<li v-for="opinion in customOpinions"
|
||||
class="list-inline-item">
|
||||
<button :class="['btn', opinion.icon === s.val.opinion ? 'btn-dark' : 'btn-outline-dark', 'border-0 my-2']" @click.prevent="s.val.opinion = opinion.icon; showOpinionSelector = false">
|
||||
<Icon :v="opinion.icon"/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:validation="s">
|
||||
<p v-if="invalid(s.val)" class="small text-danger">
|
||||
<p v-if="validate(s.val)" class="small text-danger">
|
||||
<Icon v="exclamation-triangle"/>
|
||||
<span class="ml-1">{{$t(validation(s.val.value))}}</span>
|
||||
<span class="ml-1">{{$t(validate(s.val))}}</span>
|
||||
</p>
|
||||
</template>
|
||||
</ListInput>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import opinions from '../src/opinions';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {},
|
||||
group: {},
|
||||
validation: {},
|
||||
customOpinions: { 'default': () => { return [] }},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v: this.value,
|
||||
showOpinionSelector: false,
|
||||
opinions,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -64,20 +57,40 @@
|
||||
value(v) { this.v = v; }
|
||||
},
|
||||
methods: {
|
||||
invalid(val) {
|
||||
return this.validation && val.value && this.validation(val.value);
|
||||
validate(val) {
|
||||
if (!this.getIcon(val.opinion)) {
|
||||
return 'profile.opinions.validation.invalidOpinion';
|
||||
}
|
||||
|
||||
if (!val.value) { return null; }
|
||||
|
||||
return this.validation && this.validation(val.value);
|
||||
},
|
||||
getIcon(opinion) {
|
||||
if (opinions.hasOwnProperty(opinion)) {
|
||||
return opinions[opinion].icon;
|
||||
}
|
||||
for (let op of this.customOpinions) {
|
||||
if (op.icon === opinion) {
|
||||
return opinion;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@import "assets/variables";
|
||||
|
||||
@include media-breakpoint-down('sm', $grid-breakpoints) {
|
||||
.btn-thin {
|
||||
padding-left: map-get($spacers, 1) !important;
|
||||
padding-right: map-get($spacers, 1) !important;
|
||||
}
|
||||
.hanging {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
z-index: 5000;
|
||||
}
|
||||
</style>
|
||||
|
@ -64,7 +64,7 @@
|
||||
</h3>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="{value: name, opinion} in profile.names"><Opinion :word="convertName(name)" :opinion="opinion" :escape="false"/></li>
|
||||
<li v-for="{value: name, opinion} in profile.names"><Opinion :word="convertName(name)" :opinion="opinion" :escape="false" :customOpinions="profile.opinions"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="profile.pronouns.length" :class="['col-6', mainRowCount === 3 ? 'col-lg-4' : 'col-lg-6']">
|
||||
@ -75,7 +75,7 @@
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="{link, pronoun, opinion} in pronounOpinions">
|
||||
<Opinion :word="typeof pronoun === 'string' ? pronoun : pronoun.name(glue)" :opinion="opinion" :link="`/${link}`"/>
|
||||
<Opinion :word="typeof pronoun === 'string' ? pronoun : pronoun.name(glue)" :opinion="opinion" :link="`/${link}`" :customOpinions="profile.opinions"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -103,14 +103,14 @@
|
||||
<div v-for="column in profile.words" v-if="column.values.length" class="col-6 col-lg-3">
|
||||
<h4 v-if="column.header" class="h6">{{ column.header }}</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="{value: word, opinion} in column.values"><Opinion :word="word" :opinion="opinion"/></li>
|
||||
<li v-for="{value: word, opinion} in column.values"><Opinion :word="word" :opinion="opinion" :customOpinions="profile.opinions"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<OpinionLegend/>
|
||||
<OpinionLegend :custom="profile.opinions"/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
27
components/ReducedColoursSwitch.vue
Normal file
27
components/ReducedColoursSwitch.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<label class="form-check form-switch d-inline-block">
|
||||
<input class="form-check-input" type="checkbox" role="switch" v-model="reducedColours">
|
||||
<T>mode.reducedColours</T>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
reducedColours: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!process.client) { return; }
|
||||
|
||||
this.reducedColours = localStorage.getItem('reducedColours') === 'true';
|
||||
},
|
||||
watch: {
|
||||
reducedColours(v) {
|
||||
document.body.classList.toggle('reduced-colours', v);
|
||||
localStorage.setItem('reducedColours', v);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
@ -24,6 +24,7 @@
|
||||
top: -2.2rem;
|
||||
left: -50%;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: .85rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -609,6 +609,29 @@ profile:
|
||||
or (under construction) by a <code>rel="me"</code> tag pointing back to the card.
|
||||
Our links also include a <code>rel="me"</code> tag, so that external websites can verify your card the other way round too.
|
||||
column: 'Column'
|
||||
opinions:
|
||||
header: 'Legend/opinions'
|
||||
description: 'Description…'
|
||||
colours:
|
||||
_: '(Font colour…)'
|
||||
pink: 'Pink'
|
||||
red: 'Red'
|
||||
orange: 'Orange'
|
||||
green: 'Green'
|
||||
blue: 'Blue'
|
||||
grey: 'Grey'
|
||||
styles:
|
||||
_: '(Style…)'
|
||||
bold: 'Bold'
|
||||
italics: 'Italics'
|
||||
small: 'Small'
|
||||
validation:
|
||||
missingIcon: 'Icon is required'
|
||||
missingDescription: 'Description is required'
|
||||
duplicateIcon: 'Icon must be unique'
|
||||
duplicateDescription: 'Description must be unique'
|
||||
invalidOpinion: 'Selected icon was not found in the legend above'
|
||||
custom: 'custom, added by the user:'
|
||||
|
||||
header: 'Cards'
|
||||
list: 'Your cards'
|
||||
@ -660,6 +683,7 @@ crud:
|
||||
loginRequired: '{/account=Log in} to submit an entry'
|
||||
copy: 'Copy link'
|
||||
download: 'Download'
|
||||
loadAll: 'Load all…'
|
||||
|
||||
footer:
|
||||
license: >
|
||||
@ -895,6 +919,7 @@ mode:
|
||||
light: 'Light mode'
|
||||
automatic: 'Automatic'
|
||||
dark: 'Dark mode'
|
||||
reducedColours: 'Reduced colours'
|
||||
|
||||
ban:
|
||||
reason: 'Ban reason'
|
||||
|
@ -702,6 +702,29 @@ profile:
|
||||
or (under construction) by a <code>rel="me"</code> tag pointing back to the card.
|
||||
Our links also include a <code>rel="me"</code> tag, so that external websites can verify your card the other way round too.
|
||||
column: 'Column'
|
||||
opinions:
|
||||
header: 'Legend/opinions'
|
||||
description: 'Description…'
|
||||
colours:
|
||||
_: '(Font colour…)'
|
||||
pink: 'Pink'
|
||||
red: 'Red'
|
||||
orange: 'Orange'
|
||||
green: 'Green'
|
||||
blue: 'Blue'
|
||||
grey: 'Grey'
|
||||
styles:
|
||||
_: '(Style…)'
|
||||
bold: 'Bold'
|
||||
italics: 'Italics'
|
||||
small: 'Small'
|
||||
validation:
|
||||
missingIcon: 'Icon is required'
|
||||
missingDescription: 'Description is required'
|
||||
duplicateIcon: 'Icon must be unique'
|
||||
duplicateDescription: 'Description must be unique'
|
||||
invalidOpinion: 'Selected icon was not found in the legend above'
|
||||
custom: 'custom, added by the user:'
|
||||
|
||||
header: 'Cards'
|
||||
list: 'Your cards'
|
||||
@ -756,6 +779,7 @@ crud:
|
||||
loginRequired: '{/account=Log in} to submit an entry'
|
||||
copy: 'Copy link'
|
||||
download: 'Download'
|
||||
loadAll: 'Load all…'
|
||||
|
||||
footer:
|
||||
license: >
|
||||
@ -1007,6 +1031,7 @@ mode:
|
||||
light: 'Light mode'
|
||||
automatic: 'Automatic'
|
||||
dark: 'Dark mode'
|
||||
reducedColours: 'Reduced colours'
|
||||
|
||||
ban:
|
||||
reason: 'Ban reason'
|
||||
|
@ -52,4 +52,25 @@ export default [
|
||||
'user.qr.download',
|
||||
'footer.stats.month',
|
||||
'profile.wordsColumnHeader',
|
||||
'profile.opinions.header',
|
||||
'profile.opinions.description',
|
||||
'profile.opinions.colours._',
|
||||
'profile.opinions.colours.pink',
|
||||
'profile.opinions.colours.red',
|
||||
'profile.opinions.colours.orange',
|
||||
'profile.opinions.colours.green',
|
||||
'profile.opinions.colours.blue',
|
||||
'profile.opinions.colours.grey',
|
||||
'profile.opinions.styles._',
|
||||
'profile.opinions.styles.bold',
|
||||
'profile.opinions.styles.italics',
|
||||
'profile.opinions.styles.small',
|
||||
'profile.opinions.validation.missingIcon',
|
||||
'profile.opinions.validation.missingDescription',
|
||||
'profile.opinions.validation.duplicateIcon',
|
||||
'profile.opinions.validation.duplicateDescription',
|
||||
'profile.opinions.validation.invalidOpinion',
|
||||
'profile.opinions.custom',
|
||||
'mode.reducedColours',
|
||||
'crud.loadAll',
|
||||
];
|
||||
|
@ -1343,6 +1343,29 @@ profile:
|
||||
albo (ficzer w budowie) poprzez umieszczenie tagu <code>rel="me"</code> wskazującego z powrotem na wizytówkę.
|
||||
Nasze linki również umieszczają <code>rel="me"</code>, aby zewnętrzne strony mogły potwierdzić wizytówkę również w odwrotną stronę.
|
||||
column: 'Kolumna'
|
||||
opinions:
|
||||
header: 'Legenda/opinie'
|
||||
description: 'Opis…'
|
||||
colours:
|
||||
_: '(Kolor fontu…)'
|
||||
pink: 'Różowy'
|
||||
red: 'Czerwony'
|
||||
orange: 'Pomarańczowy'
|
||||
green: 'Zielony'
|
||||
blue: 'Niebieski'
|
||||
grey: 'Szary'
|
||||
styles:
|
||||
_: '(Styl tekstu…)'
|
||||
bold: 'Pogrubiony'
|
||||
italics: 'Kursywa'
|
||||
small: 'Mały'
|
||||
validation:
|
||||
missingIcon: 'Ikona jest wymagana'
|
||||
missingDescription: 'Opis jest wymagany'
|
||||
duplicateIcon: 'Ikony muszą być unikalne'
|
||||
duplicateDescription: 'Opishy muszą być unikalne'
|
||||
invalidOpinion: 'Wybrana ikona nie jest dostępna w legendzie powyżej'
|
||||
custom: 'dodane ręcznie:'
|
||||
|
||||
header: 'Wizytówki'
|
||||
list: 'Twoje wizytówki'
|
||||
@ -1463,6 +1486,7 @@ crud:
|
||||
loginRequired: '{/konto=Zaloguj się}, aby zgłosić wpis'
|
||||
copy: 'Skopiuj link'
|
||||
download: 'Ściągnij'
|
||||
loadAll: 'Załaduj wszystko…'
|
||||
|
||||
footer:
|
||||
license: >
|
||||
@ -1679,6 +1703,7 @@ mode:
|
||||
light: 'Tryb dzienny'
|
||||
automatic: 'Auto'
|
||||
dark: 'Tryb nocny'
|
||||
reducedColours: 'Zredukowane kolory'
|
||||
|
||||
ban:
|
||||
reason: 'Powód blokady'
|
||||
|
6
migrations/061-profile-opinions.sql
Normal file
6
migrations/061-profile-opinions.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- Up
|
||||
|
||||
ALTER TABLE profiles ADD COLUMN opinions TEXT NOT NULL DEFAULT '{}';
|
||||
|
||||
-- Down
|
||||
|
@ -202,10 +202,15 @@ export default {
|
||||
config.module.rules.push({
|
||||
test: /\.md$/,
|
||||
use: ['html-loader', 'markdown-loader']
|
||||
});
|
||||
config.module.rules.push({
|
||||
test: /\.ya?ml$/,
|
||||
use: 'yaml-loader',
|
||||
})
|
||||
},
|
||||
},
|
||||
env: {
|
||||
ENV: process.env.ENV,
|
||||
BASE_URL: process.env.BASE_URL,
|
||||
HOME_URL: process.env.HOME_URL || 'https://pronouns.page',
|
||||
TITLE: title,
|
||||
|
@ -65,6 +65,7 @@
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuejs-datepicker": "^1.6.2",
|
||||
"webpack": "^5.0",
|
||||
"yaml-loader": "^0.8.0",
|
||||
"zh_cn_zh_tw": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -135,6 +135,9 @@
|
||||
|
||||
<Separator icon="heart"/>
|
||||
<Support/>
|
||||
<div class="text-center my-4 small">
|
||||
<ReducedColoursSwitch/>
|
||||
</div>
|
||||
</template>
|
||||
</Page>
|
||||
<Page v-else-if="user.username">
|
||||
|
@ -92,7 +92,12 @@
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<OpinionLegend/>
|
||||
<h3 class="h4">
|
||||
<Icon v="comment-smile"/>
|
||||
<T>profile.opinions.header</T>
|
||||
</h3>
|
||||
<LegendOpinionListInput v-model="defaultOpinions" readonly class="mb-0"/>
|
||||
<LegendOpinionListInput v-model="opinions" :maxlength="5"/>
|
||||
</section>
|
||||
|
||||
<section class="form-group">
|
||||
@ -103,7 +108,7 @@
|
||||
<p v-if="$te('profile.namesInfo')" class="small text-muted">
|
||||
<T>profile.namesInfo</T>
|
||||
</p>
|
||||
<OpinionListInput v-model="names"/>
|
||||
<OpinionListInput v-model="names" :customOpinions="opinions"/>
|
||||
<PropagateCheckbox field="names" :before="beforeChanges.names" :after="names" v-if="otherProfiles > 0" @change="propagateChanged"/>
|
||||
</section>
|
||||
|
||||
@ -120,7 +125,7 @@
|
||||
<T>profile.pronounsInfo</T>
|
||||
</p>
|
||||
</div>
|
||||
<OpinionListInput v-model="pronouns" :validation="validatePronoun"/>
|
||||
<OpinionListInput v-model="pronouns" :validation="validatePronoun" :customOpinions="opinions"/>
|
||||
</section>
|
||||
|
||||
<AdPlaceholder phkey="main-1"/>
|
||||
@ -229,7 +234,7 @@
|
||||
<T>profile.column</T> {{i + 1}}
|
||||
</h4>
|
||||
<input v-model="words[i].header" class="form-control form-control-sm mb-2" :placeholder="$t('profile.wordsColumnHeader')" maxlength="36"/>
|
||||
<OpinionListInput v-model="words[i].values" group="words"/>
|
||||
<OpinionListInput v-model="words[i].values" group="words" :customOpinions="opinions"/>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
@ -254,6 +259,7 @@
|
||||
import link from '../plugins/link';
|
||||
import {minBirthdate, maxBirthdate, formatDate} from '../src/birthdate';
|
||||
import opinions from '../src/opinions';
|
||||
import t from '../src/translator';``
|
||||
|
||||
const defaultWords = config.profile.defaultWords.map(({header, values}) => {
|
||||
return {
|
||||
@ -276,6 +282,18 @@
|
||||
return Array.isArray(arrayObject) ? arrayObject : Object.values(arrayObject);
|
||||
}
|
||||
|
||||
const opinionsToForm = (opinions) => buildList(function*() {
|
||||
for (let [key, options] of Object.entries(opinions)) {
|
||||
yield {
|
||||
key,
|
||||
icon: options.icon,
|
||||
description: options.description || t.get(`profile.opinion.${key}`),
|
||||
colour: options.colour || '',
|
||||
style: options.style || '',
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
const buildProfile = (profiles, currentLocale) => {
|
||||
for (let locale in profiles) {
|
||||
if (!profiles.hasOwnProperty(locale)) {
|
||||
@ -298,6 +316,7 @@
|
||||
credentials: profile.credentials,
|
||||
credentialsLevel: profile.credentialsLevel,
|
||||
credentialsName: profile.credentialsName,
|
||||
opinions: opinionsToForm(profile.opinions || {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -322,6 +341,7 @@
|
||||
credentials: [],
|
||||
credentialsLevel: null,
|
||||
credentialsName: null,
|
||||
opinions: opinionsToForm(profile.opinions || {}),
|
||||
};
|
||||
}
|
||||
|
||||
@ -340,6 +360,7 @@
|
||||
credentials: [],
|
||||
credentialsLevel: null,
|
||||
credentialsName: null,
|
||||
opinions: [],
|
||||
};
|
||||
};
|
||||
|
||||
@ -354,6 +375,7 @@
|
||||
},
|
||||
propagate: [],
|
||||
flagsAsterisk: process.env.FLAGS_ASTERISK,
|
||||
defaultOpinions: opinionsToForm(opinions),
|
||||
};
|
||||
},
|
||||
async asyncData({ app, store }) {
|
||||
@ -384,6 +406,7 @@
|
||||
this.saving = true;
|
||||
try {
|
||||
await this.$post(`/profile/save`, {
|
||||
opinions: this.opinions,
|
||||
names: this.names,
|
||||
pronouns: this.pronouns,
|
||||
description: this.description,
|
||||
|
@ -9,6 +9,7 @@ import fs from 'fs';
|
||||
import { minBirthdate, maxBirthdate, formatDate, parseDate } from '../../src/birthdate';
|
||||
import {socialProviders} from "../../src/socialProviders";
|
||||
import {downgradeToV1, upgradeToV2} from "../profileV2";
|
||||
import { colours, styles } from '../../src/styling';
|
||||
|
||||
const normalise = s => s.trim().toLowerCase();
|
||||
|
||||
@ -66,6 +67,7 @@ const fetchProfiles = async (db, username, self) => {
|
||||
for (let profile of profiles) {
|
||||
const links = JSON.parse(profile.links);
|
||||
p[profile.locale] = {
|
||||
opinions: JSON.parse(profile.opinions),
|
||||
names: JSON.parse(profile.names),
|
||||
pronouns: JSON.parse(profile.pronouns),
|
||||
description: profile.description,
|
||||
@ -102,6 +104,7 @@ function* isSuspicious(profile) {
|
||||
JSON.stringify(profile.pronouns),
|
||||
JSON.stringify(profile.names),
|
||||
JSON.stringify(profile.words),
|
||||
JSON.stringify(profile.opinions),
|
||||
]) {
|
||||
s = s.toLowerCase().replace(/\s+/g, ' ');
|
||||
for (let sus of susRegexes) {
|
||||
@ -173,6 +176,22 @@ const sanitiseBirthday = (bd) => {
|
||||
return formatDate(bd);
|
||||
}
|
||||
|
||||
const cleanupOpinions = (opinions) => {
|
||||
const cleanOpinions = {}
|
||||
let i = 0;
|
||||
for (let opinion of opinions) {
|
||||
if (!opinion.icon || !opinion.description || i >= 5) { continue; }
|
||||
cleanOpinions[opinion.icon] = {
|
||||
icon: opinion.icon,
|
||||
description: opinion.description.substring(0, 24),
|
||||
colour: opinion.colour && colours.includes(opinion.colour) ? opinion.colour : undefined,
|
||||
style: opinion.style && styles.includes(opinion.style) ? opinion.style : undefined,
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return cleanOpinions;
|
||||
}
|
||||
|
||||
router.post('/profile/save', handleErrorAsync(async (req, res) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
@ -188,11 +207,14 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
|
||||
req.body.customFlags = Object.values(req.body.customFlags);
|
||||
}
|
||||
|
||||
const opinions = cleanupOpinions(req.body.opinions);
|
||||
|
||||
// TODO just make it a transaction...
|
||||
const ids = (await req.db.all(SQL`SELECT * FROM profiles WHERE userId = ${req.user.id} AND locale = ${global.config.locale}`)).map(row => row.id);
|
||||
if (ids.length) {
|
||||
await req.db.get(SQL`UPDATE profiles
|
||||
SET
|
||||
opinions = ${JSON.stringify(opinions)},
|
||||
names = ${JSON.stringify(req.body.names)},
|
||||
pronouns = ${JSON.stringify(req.body.pronouns)},
|
||||
description = ${req.body.description},
|
||||
@ -212,8 +234,8 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
|
||||
WHERE id = ${ids[0]}
|
||||
`);
|
||||
} else {
|
||||
await req.db.get(SQL`INSERT INTO profiles (id, userId, locale, names, pronouns, description, birthday, links, flags, customFlags, words, active, teamName, footerName, footerAreas)
|
||||
VALUES (${ulid()}, ${req.user.id}, ${global.config.locale}, ${JSON.stringify(req.body.names)}, ${JSON.stringify(req.body.pronouns)},
|
||||
await req.db.get(SQL`INSERT INTO profiles (id, userId, locale, opinions, names, pronouns, description, birthday, links, flags, customFlags, words, active, teamName, footerName, footerAreas)
|
||||
VALUES (${ulid()}, ${req.user.id}, ${global.config.locale}, ${JSON.stringify(opinions)}, ${JSON.stringify(req.body.names)}, ${JSON.stringify(req.body.pronouns)},
|
||||
${req.body.description}, ${sanitiseBirthday(req.body.birthday || null)}, ${JSON.stringify(req.body.links.filter(x => !!x))}, ${JSON.stringify(req.body.flags)}, ${JSON.stringify(req.body.customFlags)},
|
||||
${JSON.stringify(req.body.words)}, 1,
|
||||
${req.isGranted() ? req.body.teamName || null : ''},
|
||||
|
15
src/icons.js
Normal file
15
src/icons.js
Normal file
@ -0,0 +1,15 @@
|
||||
import iconsMetadata from '@fortawesome/fontawesome-pro/metadata/icons.yml';
|
||||
|
||||
const icons = [];
|
||||
for (let [iconName, iconMetadata] of Object.entries(iconsMetadata)) {
|
||||
icons.push({
|
||||
name: iconName,
|
||||
styles: iconMetadata.styles,
|
||||
searchTerms: [
|
||||
...iconMetadata.search.terms.map(t => (t + '').toLowerCase()),
|
||||
iconName.toLowerCase(),
|
||||
iconMetadata.label.toLowerCase(),
|
||||
],
|
||||
});
|
||||
}
|
||||
export default icons;
|
@ -1,7 +1,7 @@
|
||||
export default {
|
||||
yes: { value: 3, bold: true },
|
||||
jokingly: { value: 1 },
|
||||
close: { value: 1 },
|
||||
meh: { value: 0 },
|
||||
no: { value: -3, small: true, color: 'muted' },
|
||||
yes: { value: 3, icon: 's:heart', emoji: '❤️', colour: 'pink', style: 'bold' },
|
||||
jokingly: { value: 1, icon: 'grin-tongue', emoji: '😜', colour: 'orange' },
|
||||
close: { value: 1, icon: 'user-friends', emoji: '🫂', colour: 'red' },
|
||||
meh: { value: 0, icon: 'thumbs-up', emoji: '👍' },
|
||||
no: { value: -3, icon: 'thumbs-down', emoji: '👎', colour: 'grey', style: 'small' },
|
||||
};
|
||||
|
2
src/styling.js
Normal file
2
src/styling.js
Normal file
@ -0,0 +1,2 @@
|
||||
export const colours = ['', 'pink', 'red', 'orange', 'green', 'blue', 'grey'];
|
||||
export const styles = ['', 'bold', 'italics', 'small'];
|
19
yarn.lock
19
yarn.lock
@ -5714,6 +5714,11 @@ isstream@~0.1.2:
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
||||
javascript-stringify@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79"
|
||||
integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==
|
||||
|
||||
jest-worker@^26.5.0, jest-worker@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
|
||||
@ -10870,11 +10875,25 @@ yallist@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml-loader@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml-loader/-/yaml-loader-0.8.0.tgz#c839325e3fdee082b3768b2a21fe34fde5d96f61"
|
||||
integrity sha512-LjeKnTzVBKWiQBeE2L9ssl6WprqaUIxCSNs5tle8PaDydgu3wVFXTbMfsvF2MSErpy9TDVa092n4q6adYwJaWg==
|
||||
dependencies:
|
||||
javascript-stringify "^2.0.1"
|
||||
loader-utils "^2.0.0"
|
||||
yaml "^2.0.0"
|
||||
|
||||
yaml@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
||||
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
|
||||
|
||||
yaml@^2.0.0:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.3.tgz#9b3a4c8aff9821b696275c79a8bee8399d945207"
|
||||
integrity sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==
|
||||
|
||||
yargs-parser@^11.1.1:
|
||||
version "11.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
|
||||
|
Loading…
x
Reference in New Issue
Block a user