Frontent dev env (#247)
* Added frontend development files/environment * More items-categories related removals * Improvements in pages templates (inc. static pages) * Improvements in video player * Added empty home page message + cta * Updates in media, playlist and management pages * Improvements in material icons font loading * Replaced media & playlists links in frontend dev-env * frontend package version update * chnaged frontend dev url port * static files update * Changed default position of theme switcher * enabled frontend docker container
587
frontend/src/static/css/AddMediaPage.scss
Executable file
@@ -0,0 +1,587 @@
|
||||
@use "sass:math";
|
||||
@import './includes/_variables.scss';
|
||||
|
||||
dialog {
|
||||
background-color: var(--add-media-page-tmplt-dialog-bg-color);
|
||||
}
|
||||
|
||||
.media-uploader {
|
||||
background-color: var(--add-media-page-tmplt-uploader-bg-color);
|
||||
}
|
||||
|
||||
.media-dropzone {
|
||||
background-color: var(--add-media-page-tmplt-dropzone-bg-color);
|
||||
}
|
||||
|
||||
.media-drag-drop-content-inner {
|
||||
color: var(--add-media-page-tmplt-drag-drop-inner-text-color);
|
||||
}
|
||||
|
||||
.media-upload-item-spinner {
|
||||
i {
|
||||
color: var(--add-media-page-tmplt-upload-item-spiner-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-top-actions,
|
||||
.media-upload-item-bottom-actions {
|
||||
> * {
|
||||
color: var(--add-media-page-tmplt-upload-item-actions-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-upload-size {
|
||||
color: var(--add-media-page-tmplt-upload-item-actions-text-color);
|
||||
}
|
||||
|
||||
.media-drag-drop-inner,
|
||||
.media-upload-item-thumb,
|
||||
.media-upload-item-spinner,
|
||||
.media-upload-item-name .media-upload-item-filename-input,
|
||||
.media-upload-item-bottom-actions > *,
|
||||
.retry-media-upload-item,
|
||||
.media-upload-item-progress-bar-container {
|
||||
background-color: var(--sidebar-bg-color);
|
||||
}
|
||||
|
||||
@-moz-keyframes spin {
|
||||
from {
|
||||
-moz-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-moz-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.media-uploader-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 1324px;
|
||||
padding: 8px 8px;
|
||||
margin: 0 auto 1em auto;
|
||||
}
|
||||
|
||||
.pre-upload-msg {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.media-uploader {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 0.75rem;
|
||||
width: 100%;
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
pointer-events: none;
|
||||
box-shadow: inset 0px 4px 8px -3px rgba(17, 17, 17, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
.media-uploader-top-wrap {
|
||||
position: relative;
|
||||
padding: 0 0 1.5em;
|
||||
h1 {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.25em;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.media-uploader-bottom-wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.media-uploader-top-left-wrap,
|
||||
.media-uploader-top-right-wrap {
|
||||
position: relative;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.media-uploader-bottom-left-wrap,
|
||||
.media-uploader-bottom-right-wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.media-uploader-bottom-left-wrap {
|
||||
min-height: 225px;
|
||||
height: 0;
|
||||
padding-top: math.div(3, 4) * 100%;
|
||||
@media screen and (min-width: 480px) {
|
||||
padding-top: math.div(5, 8) * 100%;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
padding-top: math.div(9, 16) * 100%;
|
||||
}
|
||||
@media screen and (min-width: 1024px) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.media-uploader-bottom-right-wrap {
|
||||
float: right;
|
||||
@media screen and (min-width: 1024px) {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
dialog {
|
||||
padding: 32px 24px 16px;
|
||||
border: 0;
|
||||
box-shadow: rgba(#000, 0.14) 0px 16px 24px 2px, rgba(#000, 0.12) 0px 6px 30px 5px, rgba(#000, 0.4) 0px 8px 10px -5px;
|
||||
display: none;
|
||||
&[open] {
|
||||
display: block;
|
||||
}
|
||||
&::backdrop {
|
||||
background-color: rgba(#000, 0.5);
|
||||
}
|
||||
.qq-dialog-buttons {
|
||||
padding-top: 16px;
|
||||
text-align: center;
|
||||
button {
|
||||
font-size: 14px;
|
||||
font-stretch: 100%;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.007px;
|
||||
text-align: center;
|
||||
padding: 10px 16px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
.qq-dialog-message-selector {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.media-drag-drop-wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 0.75rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
@media screen and (min-width: 1024px) {
|
||||
position: relative;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.media-drag-drop-inner {
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.media-drag-drop-content {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.media-drag-drop-content-inner {
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding-bottom: 1rem;
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
&:nth-child(2) {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
.material-icons {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 4em;
|
||||
line-height: 1;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.browse-files-btn-wrap {
|
||||
margin-top: 0.75rem;
|
||||
font-size: 14px;
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1rem;
|
||||
color: #fff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-dropzone {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.media-upload-items-list {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0 0.75rem 0.75rem 0.75rem;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
max-height: 80vh;
|
||||
list-style: none;
|
||||
@media screen and (min-width: 1024px) {
|
||||
min-height: 320px;
|
||||
}
|
||||
li {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0.75rem 0 0;
|
||||
margin: 0 0 1.5rem;
|
||||
@media screen and (min-width: 1024px) {
|
||||
padding: 0.75rem 0.75rem 0;
|
||||
}
|
||||
&:hover {
|
||||
}
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -0.5rem * 0.75;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
height: 1px;
|
||||
@media screen and (min-width: 1024px) {
|
||||
left: 0.75rem;
|
||||
right: 0.75rem;
|
||||
}
|
||||
background-color: rgba(17, 17, 17, 0.06);
|
||||
}
|
||||
&:first-child {
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$thumbnail-height: 100px;
|
||||
$thumbnail-height-small: 80px;
|
||||
.media-upload-item-thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
width: $thumbnail-height-small;
|
||||
height: $thumbnail-height-small;
|
||||
@media screen and (min-width: 480px) {
|
||||
width: $thumbnail-height;
|
||||
height: $thumbnail-height;
|
||||
}
|
||||
overflow: hidden;
|
||||
border-radius: 1px;
|
||||
.media-upload-items-list li:hover & {
|
||||
}
|
||||
img {
|
||||
height: 100%;
|
||||
min-width: 100%;
|
||||
width: auto;
|
||||
.qq-upload-fail & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-spinner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: $thumbnail-height-small;
|
||||
height: $thumbnail-height-small;
|
||||
line-height: $thumbnail-height-small - 2;
|
||||
@media screen and (min-width: 480px) {
|
||||
width: $thumbnail-height;
|
||||
height: $thumbnail-height;
|
||||
line-height: $thumbnail-height - 2;
|
||||
}
|
||||
text-align: center;
|
||||
i {
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
font-size: 1.5em;
|
||||
|
||||
-webkit-animation-name: spin;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-timing-function: linear;
|
||||
-moz-animation-name: spin;
|
||||
-moz-animation-duration: 2s;
|
||||
-moz-animation-iteration-count: infinite;
|
||||
-moz-animation-timing-function: linear;
|
||||
-ms-animation-name: spin;
|
||||
-ms-animation-duration: 2s;
|
||||
-ms-animation-iteration-count: infinite;
|
||||
-ms-animation-timing-function: linear;
|
||||
animation-name: spin;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-details {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-height: $thumbnail-height-small;
|
||||
margin: 0 auto 0 ($thumbnail-height-small + 16);
|
||||
@media screen and (min-width: 480px) {
|
||||
min-height: $thumbnail-height;
|
||||
margin: 0 auto 0 ($thumbnail-height + 16);
|
||||
}
|
||||
}
|
||||
|
||||
$media-upload-item-name-font-size: 14px;
|
||||
$media-upload-item-name-line-height: 20px;
|
||||
.media-upload-item-name {
|
||||
$max-lines: 2;
|
||||
position: relative;
|
||||
font-size: $media-upload-item-name-font-size;
|
||||
line-height: $media-upload-item-name-line-height;
|
||||
/* Only for non-webkit */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $max-lines;
|
||||
-webkit-box-orient: vertical;
|
||||
/* Fallback for non-webkit */
|
||||
max-height: $max-lines * $media-upload-item-name-line-height;
|
||||
display: block;
|
||||
padding-right: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 8rem;
|
||||
font-weight: 500;
|
||||
&.qq-editable {
|
||||
margin-right: 10rem;
|
||||
}
|
||||
> span {
|
||||
@include multiline_texts_excerpt(
|
||||
$font-size: $media-upload-item-name-font-size,
|
||||
$line-height: $media-upload-item-name-line-height,
|
||||
$lines-to-show: $max-lines,
|
||||
$bg-color: transparent
|
||||
);
|
||||
}
|
||||
.media-upload-item-filename {
|
||||
}
|
||||
.media-upload-item-filename-input {
|
||||
width: 100%;
|
||||
height: 1.5 * $media-upload-item-name-line-height;
|
||||
line-height: 1.5 * $media-upload-item-name-line-height;
|
||||
padding: 0 0.5rem;
|
||||
display: none;
|
||||
&.qq-editing {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-uploaded-media-link {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.media-upload-item-top-actions,
|
||||
.media-upload-item-bottom-actions {
|
||||
> * {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
}
|
||||
|
||||
&.view-uploaded-media {
|
||||
}
|
||||
}
|
||||
.material-icons {
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-top-actions {
|
||||
position: absolute;
|
||||
// top:-0.25rem;
|
||||
top: 0;
|
||||
right: 0;
|
||||
> * {
|
||||
padding: 0.125rem 0.25rem;
|
||||
font-size: 13px;
|
||||
&:not(.qq-hide) ~ * {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
.material-icons {
|
||||
font-size: 15px;
|
||||
line-height: 1em;
|
||||
vertical-align: middle;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
.filename-edit {
|
||||
display: none;
|
||||
&.qq-editable {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-bottom-actions {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
> * {
|
||||
float: left;
|
||||
line-height: 2;
|
||||
padding: 0 0.5rem 0 0.25rem;
|
||||
margin-top: 0.5rem;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
border-radius: 1px;
|
||||
&:not(.qq-hide) ~ * {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.material-icons {
|
||||
font-size: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
.retry-media-upload-item {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 4rem;
|
||||
line-height: 1.75rem;
|
||||
margin-top: -0.5 * 1.75rem;
|
||||
margin-left: -2rem;
|
||||
padding: 0 0.25rem 0 0;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
border-radius: 1px;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.material-icons {
|
||||
font-size: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-main {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.media-upload-item-progress-bar-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
overflow: hidden;
|
||||
border-radius: 1px;
|
||||
.media-upload-item-progress-bar {
|
||||
position: relative;
|
||||
height: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-details-bottom {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.media-upload-item-upload-size {
|
||||
position: relative;
|
||||
width: auto;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.media-upload-item-status-text {
|
||||
position: relative;
|
||||
width: auto;
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-family: Arial, sans-serif;
|
||||
.qq-upload-fail & {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.qq-hide {
|
||||
display: none;
|
||||
}
|
||||
0
frontend/src/static/css/_extra.css
Executable file
343
frontend/src/static/css/config/_dark_theme.scss
Executable file
@@ -0,0 +1,343 @@
|
||||
body.dark_theme {
|
||||
--body-text-color: rgba(255, 255, 255, 0.88);
|
||||
--body-bg-color: #121212;
|
||||
|
||||
--hr-color: #2a2a2a;
|
||||
|
||||
--dotted-outline-color: rgba(255, 255, 255, 0.4);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--input-color: hsla(0, 0%, 100%, 0.88);
|
||||
--input-bg-color: hsla(0, 0%, 0%, 0.55);
|
||||
--input-border-color: hsl(0, 0%, 19%);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--header-bg-color: #272727;
|
||||
|
||||
--header-circle-button-color: #fff;
|
||||
|
||||
--header-popup-menu-color: #fff;
|
||||
--header-popup-menu-icon-color: rgb(144, 144, 144);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--sidebar-bg-color: #1c1c1c;
|
||||
|
||||
--sidebar-nav-border-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--sidebar-nav-item-text-color: #fff;
|
||||
--sidebar-nav-item-icon-color: rgb(144, 144, 144);
|
||||
|
||||
--sidebar-bottom-link-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--spinner-loader-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--nav-menu-active-item-bg-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--nav-menu-item-hover-bg-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--in-popup-nav-menu-item-hover-bg-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--search-field-input-text-color: rgba(255, 255, 255, 0.88);
|
||||
--search-field-input-bg-color: #121212; // darken( #272727, 8.25% )
|
||||
--search-field-input-border-color: #303030; // lighten( #272727, 3.5% )
|
||||
|
||||
--search-field-submit-text-color: rgba(255, 255, 255, 0.5); // lighten(#66b1c3, 15%)
|
||||
|
||||
--search-field-submit-bg-color: rgba(255, 255, 255, 0.08); // lighten( #272727, 6.75% )
|
||||
--search-field-submit-border-color: #2e2e2e; // lighten( #272727, 2.75% )
|
||||
|
||||
--search-field-submit-hover-bg-color: rgba(255, 255, 255, 0.08);
|
||||
--search-field-submit-hover-border-color: #2e2e2e;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--search-results-item-content-link-title-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--logged-in-user-thumb-bg-color: rgba(255, 255, 255, 0.14);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--popup-bg-color: #242424;
|
||||
|
||||
--popup-hr-bg-color: rgba(255, 255, 255, 0.08);
|
||||
|
||||
--popup-top-text-color: #fff;
|
||||
--popup-top-bg-color: rgba(136, 136, 136, 0.4);
|
||||
|
||||
--popup-msg-title-text-color: rgba(255, 255, 255, 0.88);
|
||||
--popup-msg-main-text-color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--comments-textarea-wrapper-border-color: #898989;
|
||||
--comments-textarea-wrapper-after-bg-color: #fff;
|
||||
|
||||
--comments-textarea-text-color: #fff;
|
||||
--comments-textarea-text-placeholder-color: #898989;
|
||||
|
||||
--comments-list-inner-border-color: rgba(255, 255, 255, 0.08);
|
||||
|
||||
--comment-author-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--comment-date-text-color: #888;
|
||||
--comment-date-hover-text-color: #fff;
|
||||
|
||||
--comment-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--comment-actions-material-icon-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--comment-actions-likes-num-text-color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
--comment-actions-reply-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--comment-actions-reply-button-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--comment-actions-cancel-removal-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--comment-actions-cancel-removal-button-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--item-bg-color: #121212;
|
||||
|
||||
--item-title-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--item-thumb-bg-color: var(--sidebar-bg-color);
|
||||
|
||||
--item-meta-text-color: #888;
|
||||
--item-meta-link-text-color: var(--item-text-color);
|
||||
--item-meta-link-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--profile-page-item-content-title-bg-color: #121212;
|
||||
|
||||
--playlist-item-main-view-full-link-text-color: rgb(170, 170, 170);
|
||||
--playlist-item-main-view-full-link-hover-text-color: #fff;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--item-list-load-more-text-color: #888;
|
||||
--item-list-load-more-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--media-list-row-border-color: rgba(255, 255, 255, 0.08);
|
||||
--media-list-header-title-link-text-color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-form-title-focused-bg-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--playlist-privacy-border-color: #888;
|
||||
|
||||
--playlist-form-cancel-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--playlist-form-cancel-button-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--playlist-form-field-text-color: #fff;
|
||||
--playlist-form-field-border-color: #888;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-save-popup-text-color: rgba(255, 255, 255, 0.88);
|
||||
--playlist-save-popup-border-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--playlist-save-popup-create-icon-text-color: #909090;
|
||||
--playlist-save-popup-create-focus-bg-color: rgba(255, 255, 255, 0.14);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-view-header-bg-color: #252525;
|
||||
|
||||
--playlist-view-header-toggle-text-color: #fff;
|
||||
|
||||
--playlist-view-header-toggle-bg-color: #252525;
|
||||
|
||||
--playlist-view-title-link-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--playlist-view-meta-text-color: rgb(238, 238, 238);
|
||||
--playlist-view-meta-link-color: #fff;
|
||||
--playlist-view-meta-link-hover-text-color: #fff;
|
||||
|
||||
--playlist-view-status-text-color: rgba(255, 255, 255, 0.6);
|
||||
--playlist-view-status-bg-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--playlist-view-status-icon-text-color: rgba(255, 255, 255, 0.6);
|
||||
|
||||
--playlist-view-actions-bg-color: #252525;
|
||||
|
||||
--playlist-view-media-bg-color: var(--sidebar-bg-color);
|
||||
|
||||
--playlist-view-media-order-number-color: rgb(136, 136, 136);
|
||||
|
||||
--playlist-view-item-title-text-color: #fff;
|
||||
|
||||
--playlist-view-item-author-text-color: #fff;
|
||||
|
||||
--playlist-view-item-author-bg-color: var(--sidebar-bg-color);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--profile-page-bg-color: var(--body-bg-color);
|
||||
|
||||
--profile-page-header-bg-color: #1a1a1a;
|
||||
|
||||
--profile-page-info-videos-number-text-color: #888;
|
||||
|
||||
--profile-page-nav-link-text-color: #888;
|
||||
--profile-page-nav-link-hover-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--profile-page-nav-link-active-text-color: rgba(255, 255, 255, 0.88);
|
||||
--profile-page-nav-link-active-after-bg-color: #888;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--add-media-page-tmplt-dialog-bg-color: #242424;
|
||||
--add-media-page-tmplt-uploader-bg-color: #242424;
|
||||
--add-media-page-tmplt-dropzone-bg-color: rgba(28, 28, 28, 0.5);
|
||||
--add-media-page-tmplt-drag-drop-inner-text-color: rgba(255, 255, 255, 0.5);
|
||||
--add-media-page-tmplt-upload-item-spiner-text-color: rgba(255, 255, 255, 0.4);
|
||||
--add-media-page-tmplt-upload-item-actions-text-color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--add-media-page-qq-gallery-upload-button-text-color: rgba(255, 255, 255, 0.528);
|
||||
--add-media-page-qq-gallery-upload-button-icon-text-color: rgba(255, 255, 255, 0.528);
|
||||
|
||||
--add-media-page-qq-gallery-upload-button-hover-text-color: rgba(255, 255, 255, 0.88);
|
||||
--add-media-page-qq-gallery-upload-button-hover-icon-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--add-media-page-qq-gallery-upload-button-focus-text-color: rgba(255, 255, 255, 0.704);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-page-bg-color: #1a1a1a;
|
||||
--playlist-page-details-text-color: rgb(170, 170, 170);
|
||||
--playlist-page-thumb-bg-color: #272727;
|
||||
--playlist-page-title-link-text-color: #fff;
|
||||
|
||||
--playlist-page-actions-circle-icon-text-color: #1a1a1a;
|
||||
--playlist-page-actions-circle-icon-bg-color: inherit; // TODO: Check this value.
|
||||
|
||||
--playlist-page-actions-nav-item-button-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--playlist-page-actions-popup-message-bottom-cancel-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--playlist-page-actions-popup-message-bottom-cancel-button-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
--playlist-page-actions-popup-message-bottom-cancel-button-icon-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--playlist-page-status-text-color: rgba(255, 255, 255, 0.6);
|
||||
--playlist-page-status-bg-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--playlist-page-status-icon-text-color: rgba(255, 255, 255, 0.4);
|
||||
|
||||
--playlist-page-author-border-top-color: rgba(255, 255, 255, 0.1);
|
||||
--playlist-page-author-name-link-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--playlist-page-author-edit-playlist-icon-button-text-color: rgb(170, 170, 170);
|
||||
|
||||
--playlist-page-author-edit-playlist-icon-button-bg-color: #252525;
|
||||
|
||||
--playlist-page-author-edit-playlist-icon-button-active-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--playlist-page-author-edit-playlist-form-wrap-text-color: rgba(255, 255, 255, 0.88);
|
||||
--playlist-page-author-edit-playlist-form-wrap-bg-color: #242424;
|
||||
|
||||
--playlist-page-author-edit-playlist-form-wrap-border-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--playlist-page-author-edit-playlist-form-wrap-title-circle-icon-hover-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--playlist-page-author-edit-playlist-author-thumb-text-color: #fff;
|
||||
--playlist-page-author-edit-playlist-author-thumb-bg-color: #272727;
|
||||
|
||||
--playlist-page-details-bg-color: #252525;
|
||||
|
||||
--playlist-page-video-list-bg-color: #1c1c1c;
|
||||
|
||||
--playlist-page-video-list-item-title-bg-color: #1c1c1c;
|
||||
|
||||
--playlist-page-video-list-item-hover-bg-color: #333;
|
||||
|
||||
--playlist-page-video-list-item-title-hover-bg-color: #333;
|
||||
|
||||
--playlist-page-video-list-item-after-bg-color: rgba(255, 255, 255, 0.1);
|
||||
--playlist-page-video-list-item-order-text-color: rgb(170, 170, 170);
|
||||
|
||||
--playlist-page-video-list-item-options-icon-hover-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--playlist-page-video-list-item-options-popup-cancel-removal-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--playlist-page-video-list-item-options-popup-cancel-removal-button-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--playlist-page-video-list-item-options-popup-cancel-removal-button-hover-icon-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--media-author-actions-popup-bottom-cancel-removal-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--media-author-actions-popup-bottom-cancel-removal-button-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
--media-author-actions-popup-bottom-cancel-removal-button-hover-icon-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--profile-banner-wrap-popup-bottom-cancel-removal-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--profile-banner-wrap-popup-bottom-cancel-removal-button-hover-text-color: rgba(255, 255, 255, 0.74);
|
||||
--profile-banner-wrap-popup-bottom-cancel-removal-button-hover-icon-text-color: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--media-title-banner-border-color: rgba(255, 255, 255, 0.08);
|
||||
--media-title-labels-area-text-color: rgba(255, 255, 255, 0.6);
|
||||
--media-title-labels-area-bg-color: rgba(255, 255, 255, 0.08);
|
||||
--media-title-views-text-color: rgb(136, 136, 136);
|
||||
|
||||
--media-actions-not-popup-circle-icon-focus-bg-color: rgba(255, 255, 255, 0.07);
|
||||
--media-actions-not-popup-circle-icon-active-bg-color: rgba(255, 255, 255, 0.14);
|
||||
--media-actions-like-before-border-color: rgba(255, 255, 255, 0.5);
|
||||
--media-actions-share-title-text-color: rgba(255, 255, 255, 0.88);
|
||||
--media-actions-share-options-nav-button-text-color: rgba(255, 255, 255, 0.5);
|
||||
--media-actions-share-options-link-text-color: rgba(255, 255, 255, 0.88);
|
||||
--media-actions-share-copy-field-border-color: rgb(41, 41, 41);
|
||||
--media-actions-share-copy-field-bg-color: rgb(28, 28, 28);
|
||||
--media-actions-share-copy-field-input-text-color: rgba(255, 255, 255, 0.88);
|
||||
--media-actions-more-options-popup-bg-color: #242424;
|
||||
--media-actions-more-options-popup-nav-link-text-color: rgba(255, 255, 255, 0.88);
|
||||
--media-actions-share-fullscreen-popup-main-bg-color: #242424;
|
||||
|
||||
--report-form-title-text-color: rgba(255, 255, 255, 0.88);
|
||||
--report-form-field-label-text-color: rgba(255, 255, 255, 0.88);
|
||||
--report-form-field-input-text-color: rgba(255, 255, 255, 0.88);
|
||||
--report-form-field-input-border-color: rgb(41, 41, 41);
|
||||
--report-form-field-input-bg-color: rgb(28, 28, 28);
|
||||
--report-form-help-text-color: rgb(136, 136, 136);
|
||||
|
||||
--form-actions-bottom-border-top-color: rgba(255, 255, 255, 0.08);
|
||||
|
||||
--media-author-banner-name-text-color: rgba(255, 255, 255, 0.88);
|
||||
--media-author-banner-date-text-color: rgba(255, 255, 255, 0.6);
|
||||
|
||||
--media-content-banner-border-color: rgba(255, 255, 255, 0.08);
|
||||
|
||||
--share-embed-inner-on-right-border-color: rgba(255, 255, 255, 0.08);
|
||||
--share-embed-inner-on-right-ttl-text-color: rgba(255, 255, 255, 0.88);
|
||||
--share-embed-inner-on-right-icon-text-color: rgba(255, 255, 255, 0.5);
|
||||
--share-embed-inner-textarea-text-color: rgba(255, 255, 255, 0.55);
|
||||
--share-embed-inner-textarea-border-color: rgb(41, 41, 41);
|
||||
--share-embed-inner-textarea-bg-color: rgb(28, 28, 28);
|
||||
--share-embed-inner-embed-wrap-iconn-text-color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
--media-status-info-item-text-color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
--viewer-sidebar-auto-play-border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
--viewer-sidebar-auto-play-next-label-text-color: #fff;
|
||||
--viewer-sidebar-auto-play-option-text-color: #aaa;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--user-action-form-inner-bg-color: #242424;
|
||||
--user-action-form-inner-title-border-bottom-color: var(--sidebar-nav-border-color);
|
||||
|
||||
--user-action-form-inner-input-border-color: #303030;
|
||||
--user-action-form-inner-input-text-color: rgba(255, 255, 255, 0.88);
|
||||
--user-action-form-inner-input-bg-color: #121212;
|
||||
}
|
||||
343
frontend/src/static/css/config/_light_theme.scss
Executable file
@@ -0,0 +1,343 @@
|
||||
body {
|
||||
--body-text-color: #111;
|
||||
--body-bg-color: #fafafa;
|
||||
|
||||
--hr-color: #e1e1e1;
|
||||
|
||||
--dotted-outline-color: rgba(0, 0, 0, 0.4);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--input-color: hsl(0, 0%, 7%);
|
||||
--input-bg-color: hsl(0, 0%, 100%);
|
||||
--input-border-color: hsl(0, 0%, 80%);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--header-bg-color: #fff;
|
||||
|
||||
--header-circle-button-color: #606060;
|
||||
|
||||
--header-popup-menu-color: rgb(13, 13, 13);
|
||||
--header-popup-menu-icon-color: rgb(144, 144, 144);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--sidebar-bg-color: #f5f5f5;
|
||||
|
||||
--sidebar-nav-border-color: #eee;
|
||||
|
||||
--sidebar-nav-item-text-color: rgb(13, 13, 13);
|
||||
--sidebar-nav-item-icon-color: rgb(144, 144, 144);
|
||||
|
||||
--sidebar-bottom-link-color: initial;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--spinner-loader-color: rgba(17, 17, 17, 0.8);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--nav-menu-active-item-bg-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
--nav-menu-item-hover-bg-color: rgba(0, 0, 0, 0.04);
|
||||
|
||||
--in-popup-nav-menu-item-hover-bg-color: #eee;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--search-field-input-text-color: #111;
|
||||
--search-field-input-bg-color: #fff;
|
||||
--search-field-input-border-color: #ccc;
|
||||
|
||||
--search-field-submit-text-color: #333;
|
||||
|
||||
--search-field-submit-bg-color: #f8f8f8;
|
||||
--search-field-submit-border-color: #d3d3d3;
|
||||
|
||||
--search-field-submit-hover-bg-color: #f0f0f0;
|
||||
--search-field-submit-hover-border-color: #c6c6c6;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--search-results-item-content-link-title-text-color: rgb(17, 17, 17);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--logged-in-user-thumb-bg-color: rgba(0, 0, 0, 0.07);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--popup-bg-color: #fff;
|
||||
|
||||
--popup-hr-bg-color: #eee;
|
||||
|
||||
--popup-top-text-color: rgb(13, 13, 13);
|
||||
--popup-top-bg-color: #eee;
|
||||
|
||||
--popup-msg-title-text-color: rgb(17, 17, 17);
|
||||
--popup-msg-main-text-color: rgba(17, 17, 17, 0.8);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--comments-textarea-wrapper-border-color: #eeeeee;
|
||||
--comments-textarea-wrapper-after-bg-color: #0a0a0a;
|
||||
|
||||
--comments-textarea-text-color: #0a0a0a;
|
||||
--comments-textarea-text-placeholder-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
--comments-list-inner-border-color: #eee;
|
||||
|
||||
--comment-author-text-color: #111;
|
||||
|
||||
--comment-date-text-color: #606060;
|
||||
--comment-date-hover-text-color: #0a0a0a;
|
||||
|
||||
--comment-text-color: #111;
|
||||
|
||||
--comment-actions-material-icon-text-color: rgba(17, 17, 17, 0.8);
|
||||
|
||||
--comment-actions-likes-num-text-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
--comment-actions-reply-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--comment-actions-reply-button-hover-text-color: #111;
|
||||
|
||||
--comment-actions-cancel-removal-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--comment-actions-cancel-removal-button-hover-text-color: #111;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--item-bg-color: #fafafa;
|
||||
|
||||
--item-title-text-color: #111;
|
||||
|
||||
--item-thumb-bg-color: var(--sidebar-bg-color);
|
||||
|
||||
--item-meta-text-color: rgba(17, 17, 17, 0.6);
|
||||
--item-meta-link-text-color: var(--item-text-color);
|
||||
--item-meta-link-hover-text-color: rgba(17, 17, 17, 0.8);
|
||||
|
||||
--profile-page-item-content-title-bg-color: #fff;
|
||||
|
||||
--playlist-item-main-view-full-link-text-color: rgb(96, 96, 96);
|
||||
--playlist-item-main-view-full-link-hover-text-color: rgb(13, 13, 13);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--item-list-load-more-text-color: rgba(17, 17, 17, 0.6);
|
||||
--item-list-load-more-hover-text-color: rgba(17, 17, 17, 0.8);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--media-list-row-border-color: #eee;
|
||||
--media-list-header-title-link-text-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-form-title-focused-bg-color: #111;
|
||||
|
||||
--playlist-privacy-border-color: #888;
|
||||
|
||||
--playlist-form-cancel-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--playlist-form-cancel-button-hover-text-color: #111;
|
||||
|
||||
--playlist-form-field-text-color: #000;
|
||||
--playlist-form-field-border-color: #888;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-save-popup-text-color: #111;
|
||||
--playlist-save-popup-border-color: #eee;
|
||||
|
||||
--playlist-save-popup-create-icon-text-color: #909090;
|
||||
--playlist-save-popup-create-focus-bg-color: rgba(136, 136, 136, 0.14);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-view-header-bg-color: #fafafa;
|
||||
|
||||
--playlist-view-header-toggle-text-color: rgb(96, 96, 96);
|
||||
|
||||
--playlist-view-header-toggle-bg-color: #fafafa;
|
||||
|
||||
--playlist-view-title-link-text-color: rgb(13, 13, 13);
|
||||
|
||||
--playlist-view-meta-text-color: rgba(17, 17, 17, 0.6);
|
||||
--playlist-view-meta-link-color: rgba(17, 17, 17, 0.6);
|
||||
--playlist-view-meta-link-hover-text-color: rgb(13, 13, 13);
|
||||
|
||||
--playlist-view-status-text-color: rgba(17, 17, 17, 0.6);
|
||||
--playlist-view-status-bg-color: rgba(0, 0, 0, 0.05);
|
||||
|
||||
--playlist-view-status-icon-text-color: rgba(17, 17, 17, 0.4);
|
||||
|
||||
--playlist-view-actions-bg-color: #fafafa;
|
||||
|
||||
--playlist-view-media-bg-color: var(--sidebar-bg-color);
|
||||
|
||||
--playlist-view-media-order-number-color: rgb(136, 136, 136);
|
||||
|
||||
--playlist-view-item-title-text-color: rgb(13, 13, 13);
|
||||
|
||||
--playlist-view-item-author-text-color: rgb(13, 13, 13);
|
||||
|
||||
--playlist-view-item-author-bg-color: var(--sidebar-bg-color);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--profile-page-bg-color: #fff;
|
||||
|
||||
--profile-page-header-bg-color: var(--body-bg-color);
|
||||
|
||||
--profile-page-info-videos-number-text-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
--profile-page-nav-link-text-color: rgba(17, 17, 17, 0.6);
|
||||
--profile-page-nav-link-hover-text-color: #111;
|
||||
|
||||
--profile-page-nav-link-active-text-color: #111;
|
||||
--profile-page-nav-link-active-after-bg-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--add-media-page-tmplt-dialog-bg-color: #fff;
|
||||
--add-media-page-tmplt-uploader-bg-color: #fff;
|
||||
--add-media-page-tmplt-dropzone-bg-color: rgba(255, 255, 255, 0.5);
|
||||
--add-media-page-tmplt-drag-drop-inner-text-color: rgba(17, 17, 17, 0.4);
|
||||
--add-media-page-tmplt-upload-item-spiner-text-color: rgba(17, 17, 17, 0.32);
|
||||
--add-media-page-tmplt-upload-item-actions-text-color: rgba(17, 17, 17, 0.4);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--add-media-page-qq-gallery-upload-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--add-media-page-qq-gallery-upload-button-icon-text-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
--add-media-page-qq-gallery-upload-button-hover-text-color: rgba(17, 17, 17, 1);
|
||||
--add-media-page-qq-gallery-upload-button-hover-icon-text-color: rgba(17, 17, 17, 1);
|
||||
|
||||
--add-media-page-qq-gallery-upload-button-focus-text-color: rgba(17, 17, 17, 0.4);
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--playlist-page-bg-color: rgb(250, 250, 250);
|
||||
--playlist-page-details-text-color: rgb(96, 96, 96);
|
||||
--playlist-page-thumb-bg-color: rgba(0, 0, 0, 0.07);
|
||||
--playlist-page-title-link-text-color: rgb(13, 13, 13);
|
||||
|
||||
--playlist-page-actions-circle-icon-text-color: rgb(144, 144, 144);
|
||||
--playlist-page-actions-circle-icon-bg-color: rgb(250, 250, 250);
|
||||
|
||||
--playlist-page-actions-nav-item-button-text-color: rgb(10, 10, 10);
|
||||
|
||||
--playlist-page-actions-popup-message-bottom-cancel-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--playlist-page-actions-popup-message-bottom-cancel-button-hover-text-color: #111;
|
||||
--playlist-page-actions-popup-message-bottom-cancel-button-icon-hover-text-color: #111;
|
||||
|
||||
--playlist-page-status-text-color: rgba(17, 17, 17, 0.6);
|
||||
--playlist-page-status-bg-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
--playlist-page-status-icon-text-color: rgba(17, 17, 17, 0.4);
|
||||
|
||||
--playlist-page-author-border-top-color: rgba(0, 0, 0, 0.1);
|
||||
--playlist-page-author-name-link-color: rgb(13, 13, 13);
|
||||
|
||||
--playlist-page-author-edit-playlist-icon-button-text-color: rgb(96, 96, 96);
|
||||
|
||||
--playlist-page-author-edit-playlist-icon-button-bg-color: #fafafa;
|
||||
|
||||
--playlist-page-author-edit-playlist-icon-button-active-text-color: rgb(13, 13, 13);
|
||||
|
||||
--playlist-page-author-edit-playlist-form-wrap-text-color: rgb(13, 13, 13);
|
||||
--playlist-page-author-edit-playlist-form-wrap-bg-color: #fff;
|
||||
|
||||
--playlist-page-author-edit-playlist-form-wrap-border-color: #eee;
|
||||
|
||||
--playlist-page-author-edit-playlist-form-wrap-title-circle-icon-hover-text-color: #111;
|
||||
|
||||
--playlist-page-author-edit-playlist-author-thumb-text-color: #606060;
|
||||
--playlist-page-author-edit-playlist-author-thumb-bg-color: rgba(0, 0, 0, 0.07);
|
||||
|
||||
--playlist-page-details-bg-color: #fafafa;
|
||||
|
||||
--playlist-page-video-list-bg-color: #f5f5f5;
|
||||
|
||||
--playlist-page-video-list-item-title-bg-color: #f5f5f5;
|
||||
|
||||
--playlist-page-video-list-item-hover-bg-color: #ebebeb;
|
||||
|
||||
--playlist-page-video-list-item-title-hover-bg-color: #ebebeb;
|
||||
|
||||
--playlist-page-video-list-item-after-bg-color: rgba(0, 0, 0, 0.1);
|
||||
--playlist-page-video-list-item-order-text-color: rgb(96, 96, 96);
|
||||
|
||||
--playlist-page-video-list-item-options-icon-hover-color: #111;
|
||||
|
||||
--playlist-page-video-list-item-options-popup-cancel-removal-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--playlist-page-video-list-item-options-popup-cancel-removal-button-hover-text-color: #111;
|
||||
|
||||
--playlist-page-video-list-item-options-popup-cancel-removal-button-hover-icon-text-color: #111;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--media-author-actions-popup-bottom-cancel-removal-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--media-author-actions-popup-bottom-cancel-removal-button-hover-text-color: #111;
|
||||
--media-author-actions-popup-bottom-cancel-removal-button-hover-icon-text-color: #111;
|
||||
|
||||
--profile-banner-wrap-popup-bottom-cancel-removal-button-text-color: rgba(17, 17, 17, 0.6);
|
||||
--profile-banner-wrap-popup-bottom-cancel-removal-button-hover-text-color: #111;
|
||||
--profile-banner-wrap-popup-bottom-cancel-removal-button-hover-icon-text-color: #111;
|
||||
|
||||
--media-title-banner-border-color: #eee;
|
||||
--media-title-labels-area-text-color: rgba(17, 17, 17, 0.6);
|
||||
--media-title-labels-area-bg-color: rgba(238, 238, 238, 0.6);
|
||||
--media-title-views-text-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
--media-actions-not-popup-circle-icon-focus-bg-color: rgba(0, 0, 0, 0.04);
|
||||
--media-actions-not-popup-circle-icon-active-bg-color: rgba(0, 0, 0, 0.07);
|
||||
--media-actions-like-before-border-color: rgba(17, 17, 17, 0.4);
|
||||
--media-actions-share-title-text-color: #111;
|
||||
--media-actions-share-options-nav-button-text-color: rgba(17, 17, 17, 0.4);
|
||||
--media-actions-share-options-link-text-color: rgb(17, 17, 17);
|
||||
--media-actions-share-copy-field-border-color: rgb(237, 237, 237);
|
||||
--media-actions-share-copy-field-bg-color: rgb(250, 250, 250);
|
||||
--media-actions-share-copy-field-input-text-color: rgb(17, 17, 17);
|
||||
--media-actions-more-options-popup-bg-color: #fff;
|
||||
--media-actions-more-options-popup-nav-link-text-color: rgb(10, 10, 10);
|
||||
--media-actions-share-fullscreen-popup-main-bg-color: #fff;
|
||||
|
||||
--report-form-title-text-color: #111;
|
||||
--report-form-field-label-text-color: rgba(17, 17, 17, 0.6);
|
||||
--report-form-field-input-text-color: #111;
|
||||
--report-form-field-input-border-color: rgb(237, 237, 237);
|
||||
--report-form-field-input-bg-color: rgb(250, 250, 250);
|
||||
--report-form-help-text-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
--form-actions-bottom-border-top-color: rgb(238, 238, 238);
|
||||
|
||||
--media-author-banner-name-text-color: #0a0a0a;
|
||||
--media-author-banner-date-text-color: rgba(17, 17, 17, 0.6);
|
||||
|
||||
--media-content-banner-border-color: #eee;
|
||||
|
||||
--share-embed-inner-on-right-border-color: rgb(238, 238, 238);
|
||||
--share-embed-inner-on-right-ttl-text-color: #111;
|
||||
--share-embed-inner-on-right-icon-text-color: rgba(17, 17, 17, 0.4);
|
||||
--share-embed-inner-textarea-text-color: rgba(17, 17, 17, 0.8);
|
||||
--share-embed-inner-textarea-border-color: rgb(237, 237, 237);
|
||||
--share-embed-inner-textarea-bg-color: rgb(250, 250, 250);
|
||||
--share-embed-inner-embed-wrap-iconn-text-color: rgba(17, 17, 17, 0.4);
|
||||
|
||||
--media-status-info-item-text-color: #111;
|
||||
|
||||
--viewer-sidebar-auto-play-border-bottom-color: rgba(0, 0, 0, 0.1);
|
||||
--viewer-sidebar-auto-play-next-label-text-color: #0a0a0a;
|
||||
--viewer-sidebar-auto-play-option-text-color: #606060;
|
||||
|
||||
/* ################################################## */
|
||||
|
||||
--user-action-form-inner-bg-color: #fff;
|
||||
--user-action-form-inner-title-border-bottom-color: var(--sidebar-nav-border-color);
|
||||
|
||||
--user-action-form-inner-input-border-color: #d3d3d3;
|
||||
--user-action-form-inner-input-text-color: #000;
|
||||
--user-action-form-inner-input-bg-color: #fff;
|
||||
}
|
||||
47
frontend/src/static/css/config/index.scss
Executable file
@@ -0,0 +1,47 @@
|
||||
@import './_light_theme.scss';
|
||||
@import './_dark_theme.scss';
|
||||
|
||||
body {
|
||||
--default-logo-height: 18px;
|
||||
|
||||
--default-theme-color: #009933;
|
||||
--default-brand-color: #009933;
|
||||
|
||||
--success-color: #00a28b;
|
||||
--warning-color: #e09f1f;
|
||||
--danger-color: #de623b;
|
||||
|
||||
--input-disabled-bg-color: hsla(0, 0%, 0%, 0.05);
|
||||
|
||||
--dotted-outline: 1px dotted var(--dotted-outline-color);
|
||||
|
||||
--header-height: 56px;
|
||||
|
||||
--sidebar-width: 240px;
|
||||
|
||||
--item-title-font-size: 14px;
|
||||
--item-title-max-lines: 2;
|
||||
--item-title-line-height: 18px;
|
||||
|
||||
--horizontal-item-title-line-height: 21px;
|
||||
|
||||
--playlist-item-title-line-height: 20px;
|
||||
|
||||
--large-item-title-font-size: 16px;
|
||||
--large-item-title-line-height: 22px;
|
||||
|
||||
--links-color: var(--default-theme-color);
|
||||
}
|
||||
|
||||
body {
|
||||
--default-item-width: 218px;
|
||||
--default-max-item-width: 344px;
|
||||
|
||||
--default-max-row-items: 6;
|
||||
|
||||
--default-item-margin-right-width: 4px;
|
||||
--default-item-margin-bottom-width: 24px;
|
||||
|
||||
--default-horizontal-item-margin-right-width: 12px;
|
||||
--default-horizontal-item-margin-bottom-width: 12px;
|
||||
}
|
||||
62
frontend/src/static/css/includes/_mixins.scss
Executable file
@@ -0,0 +1,62 @@
|
||||
@mixin multiline_texts_excerpt($font-size: 1em, $line-height: 1.15, $lines-to-show: 2, $bg-color: transparent) {
|
||||
line-height: $line-height;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background-color: $bg-color;
|
||||
|
||||
/* Fallback for non-webkit */
|
||||
display: block;
|
||||
max-height: $lines-to-show * $line-height;
|
||||
|
||||
/* Only for non-webkit */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $lines-to-show;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@mixin media_list_row_width($width, $vertSpace: 56) {
|
||||
width: $width;
|
||||
|
||||
@media (min-width: $vertSpace + ( 2 * $width )) {
|
||||
width: 2 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 3 * $width )) {
|
||||
width: 3 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 4 * $width )) {
|
||||
width: 4 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 5 * $width )) {
|
||||
width: 5 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 7 * $width )) {
|
||||
width: 6 * $width;
|
||||
}
|
||||
|
||||
.visible-sidebar & {
|
||||
@media (min-width: $vertSpace + ( 2 * $width )) {
|
||||
width: 1 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 3 * $width )) {
|
||||
width: 2 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 4 * $width )) {
|
||||
width: 3 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 5 * $width )) {
|
||||
width: 4 * $width;
|
||||
}
|
||||
|
||||
@media (min-width: $vertSpace + ( 7 * $width )) {
|
||||
width: 6 * $width;
|
||||
}
|
||||
}
|
||||
}
|
||||
439
frontend/src/static/css/includes/_theme_color.scss
Executable file
@@ -0,0 +1,439 @@
|
||||
@import './_variables.scss';
|
||||
|
||||
/// Convert a direction to legacy syntax
|
||||
/// @param {Keyword | Angle} $value - Value to convert
|
||||
/// @require {function} is-direction
|
||||
/// @require {function} convert-angle
|
||||
/// @throw Cannot convert `#{$value}` to legacy syntax because it doesn't seem to be a direction.;
|
||||
@function legacy-direction($value) {
|
||||
@if is-direction($value) == false {
|
||||
@error "Cannot convert `#{$value}` to legacy syntax because it doesn't seem to be a direction.";
|
||||
}
|
||||
|
||||
$conversion-map: (
|
||||
to top: bottom,
|
||||
to top right: bottom left,
|
||||
to right top: left bottom,
|
||||
to right: left,
|
||||
to bottom right: top left,
|
||||
to right bottom: left top,
|
||||
to bottom: top,
|
||||
to bottom left: top right,
|
||||
to left bottom: right top,
|
||||
to left: right,
|
||||
to left top: right bottom,
|
||||
to top left: bottom right
|
||||
);
|
||||
|
||||
@if map-has-key($conversion-map, $value) {
|
||||
@return map-get($conversion-map, $value);
|
||||
}
|
||||
|
||||
@return 90deg - $value;
|
||||
}
|
||||
|
||||
/// Test if `$value` is a valid direction
|
||||
/// @param {*} $value - Value to test
|
||||
/// @return {Bool}
|
||||
@function is-direction($value) {
|
||||
$is-keyword: index(
|
||||
(
|
||||
to top,
|
||||
to top right,
|
||||
to right top,
|
||||
to right,
|
||||
to bottom right,
|
||||
to right bottom,
|
||||
to bottom,
|
||||
to bottom left,
|
||||
to left bottom,
|
||||
to left,
|
||||
to left top,
|
||||
to top left
|
||||
),
|
||||
$value
|
||||
);
|
||||
$is-angle: type-of($value) == 'number' and index('deg' 'grad' 'turn' 'rad', unit($value));
|
||||
|
||||
@return $is-keyword or $is-angle;
|
||||
}
|
||||
|
||||
@mixin linear-gradient($direction, $color-stops...) {
|
||||
// Direction has been omitted and happens to be a color-stop
|
||||
@if is-direction($direction) == false {
|
||||
$color-stops: $direction, $color-stops;
|
||||
$direction: 180deg;
|
||||
}
|
||||
|
||||
background: nth(nth($color-stops, 1), 1);
|
||||
background: -webkit-linear-gradient(legacy-direction($direction), $color-stops);
|
||||
background: linear-gradient($direction, $color-stops);
|
||||
}
|
||||
|
||||
$bg-color_theme-color: $theme-color;
|
||||
|
||||
@mixin font_color_gradient() {
|
||||
color: $theme-color;
|
||||
/*background: -webkit-linear-gradient( $theme-color, scale-lightness( $theme-color, -15 ) );
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;*/
|
||||
}
|
||||
|
||||
@mixin font_color_gradient_important() {
|
||||
color: $theme-color !important;
|
||||
/*background: -webkit-linear-gradient( $theme-color, scale-lightness( $theme-color, -15 ) ) !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;*/
|
||||
}
|
||||
|
||||
@mixin background_color_gradient() {
|
||||
background-color: $theme-color;
|
||||
// @include linear-gradient( to bottom right, $theme-color 0%, scale-lightness( $theme-color, -10 ) 100%);
|
||||
}
|
||||
|
||||
@mixin background_gradient() {
|
||||
background: $theme-color;
|
||||
// @include linear-gradient( to bottom right, $theme-color 0%, scale-lightness( $theme-color, -10 ) 100%);
|
||||
}
|
||||
|
||||
@mixin border_color_gradient() {
|
||||
border-color: $theme-color;
|
||||
// @include linear-gradient( to bottom right, $theme-color 0%, scale-lightness( $theme-color, -10 ) 100%);
|
||||
}
|
||||
|
||||
/*
|
||||
* Typography
|
||||
*/
|
||||
|
||||
.nav-menu {
|
||||
li {
|
||||
&.link-item {
|
||||
&.active {
|
||||
.menu-item-icon {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
span {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Components
|
||||
*/
|
||||
|
||||
/* Comments */
|
||||
|
||||
.comments-form-inner {
|
||||
.form {
|
||||
.form-buttons {
|
||||
a,
|
||||
button {
|
||||
@include background_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
a {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
.remove-comment {
|
||||
> button {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
|
||||
.popup-message-bottom {
|
||||
button {
|
||||
&.proceed-comment-removal {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* NavigationMenuList */
|
||||
|
||||
.nav-menu {
|
||||
li {
|
||||
&.label-item {
|
||||
button {
|
||||
&.reported-label,
|
||||
&.reported-label * {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* PageSidebar */
|
||||
|
||||
.page-sidebar {
|
||||
.page-sidebar-bottom {
|
||||
a {
|
||||
&:hover {
|
||||
@include font_color_gradient_important();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Components_Pages
|
||||
*/
|
||||
|
||||
/* AddMediaPage */
|
||||
|
||||
.media-drag-drop-content-inner {
|
||||
.browse-files-btn-wrap {
|
||||
span {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filename-edit {
|
||||
&:hover {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-bottom-actions {
|
||||
> * {
|
||||
&:hover {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-progress-bar-container {
|
||||
.media-upload-item-progress-bar {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
/* AddMediaPageTemplate */
|
||||
|
||||
dialog {
|
||||
.qq-dialog-buttons {
|
||||
button {
|
||||
color: $theme-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-drag-drop-content-inner {
|
||||
.browse-files-btn-wrap {
|
||||
span {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-top-actions,
|
||||
.media-upload-item-bottom-actions {
|
||||
> * {
|
||||
&:hover {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-bottom-actions {
|
||||
> * {
|
||||
&:hover {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.retry-media-upload-item {
|
||||
@include font_color_gradient();
|
||||
|
||||
&:hover {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-item-progress-bar-container {
|
||||
.media-upload-item-progress-bar {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
/* MediaPage */
|
||||
|
||||
.viewer-container {
|
||||
.player-container {
|
||||
&.audio-player-container {
|
||||
.vjs-big-play-button {
|
||||
background-color: var(--brand-color, var(--default-brand-color)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-author-actions {
|
||||
> a,
|
||||
> button {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
|
||||
.popup-message-bottom {
|
||||
button {
|
||||
&.proceed-comment-removal {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-title-banner {
|
||||
.media-actions {
|
||||
> * {
|
||||
> * {
|
||||
&.share {
|
||||
.copy-field {
|
||||
button {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disliked-media {
|
||||
> * {
|
||||
> * {
|
||||
&.dislike {
|
||||
&:before {
|
||||
@include border_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-views-actions {
|
||||
&.liked-media {
|
||||
.media-actions {
|
||||
> * {
|
||||
> * {
|
||||
&.like,
|
||||
&.like button,
|
||||
&.like .circle-icon-button {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
|
||||
&.like,
|
||||
&.dislike {
|
||||
&:before {
|
||||
@include border_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disliked-media {
|
||||
.media-actions {
|
||||
> * {
|
||||
> * {
|
||||
&.dislike,
|
||||
&.dislike button,
|
||||
&.dislike .circle-icon-button {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
|
||||
&.like,
|
||||
&.dislike {
|
||||
&:before {
|
||||
@include border_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions-bottom {
|
||||
button {
|
||||
color: $theme-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.media-content-field-content {
|
||||
a {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
.share-embed .share-embed-inner {
|
||||
.on-right-bottom {
|
||||
button {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ProfilePage */
|
||||
|
||||
.profile-page-header {
|
||||
a.edit-channel,
|
||||
a.edit-profile,
|
||||
button.delete-profile {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
.profile-banner-wrap {
|
||||
.popup-message-bottom {
|
||||
> a,
|
||||
> button {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
|
||||
button {
|
||||
&.proceed-profile-removal {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* General
|
||||
*/
|
||||
|
||||
p {
|
||||
a {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
}
|
||||
|
||||
.user-action-form-inner {
|
||||
a {
|
||||
@include font_color_gradient();
|
||||
}
|
||||
|
||||
button,
|
||||
*[type='submit'],
|
||||
*[type='button'] {
|
||||
@include background_color_gradient();
|
||||
}
|
||||
}
|
||||
12
frontend/src/static/css/includes/_theme_variables.scss
Executable file
@@ -0,0 +1,12 @@
|
||||
$theme-color: var(--theme-color, var(--default-theme-color));
|
||||
|
||||
$color-error-red: rgba(red, 0.8);
|
||||
|
||||
$message-default-color: rgba(#111, 0.9);
|
||||
|
||||
$message-default-bg-color: #e6e6e6;
|
||||
$message-info-bg-color: #e6e6fa;
|
||||
$message-error-bg-color: #fae6e6;
|
||||
$message-success-bg-color: #e6f0e6;
|
||||
|
||||
$message-warning-bg-color: #fafae6;
|
||||
2
frontend/src/static/css/includes/_variables.scss
Executable file
@@ -0,0 +1,2 @@
|
||||
@import './_mixins.scss';
|
||||
@import './_theme_variables.scss';
|
||||
82
frontend/src/static/css/includes/_variables_dimensions.scss
Executable file
@@ -0,0 +1,82 @@
|
||||
@use "sass:math";
|
||||
|
||||
// #_VARIABLES
|
||||
|
||||
$_use_rem_unit: true;
|
||||
|
||||
$single-item-breakpoint: 348px;
|
||||
$single-item-breakpoint: 492px;
|
||||
|
||||
$double-item-breakpoint: 689px;
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
// #_DIMENSIONS
|
||||
|
||||
// #_HeaderHeight
|
||||
|
||||
$_header-height-value: 56;
|
||||
|
||||
$header-height: $_header-height-value * 1px;
|
||||
|
||||
@if $_use_rem_unit {
|
||||
$header-height: math.div($_header-height-value, 16) * 1rem;
|
||||
}
|
||||
|
||||
// #_HeaderPadding
|
||||
|
||||
$_header-padding-vertical-value: 0;
|
||||
$_header-padding-horizontal-value: 16;
|
||||
|
||||
$_header-padding-vertical-value-pixels: $_header-padding-vertical-value * 1px;
|
||||
$_header-padding-horizontal-value-pixels: $_header-padding-horizontal-value * 1px;
|
||||
|
||||
$_header-padding-vertical-value-rem: math.div($_header-padding-vertical-value, 16) * 1rem;
|
||||
$_header-padding-horizontal-value-rem: math.div($_header-padding-horizontal-value, 16) * 1rem;
|
||||
|
||||
$header-padding-horizontal: $_header-padding-horizontal-value-pixels;
|
||||
$header-padding: $_header-padding-vertical-value-pixels $_header-padding-horizontal-value-pixels;
|
||||
|
||||
@if $_use_rem_unit {
|
||||
$header-padding-horizontal: $_header-padding-horizontal-value-rem;
|
||||
$header-padding: $_header-padding-vertical-value-rem $_header-padding-horizontal-value-rem;
|
||||
}
|
||||
|
||||
// #_SidebarWidth
|
||||
|
||||
$_sidebar-width-value: 240;
|
||||
|
||||
$sidebar-width: $_sidebar-width-value * 1px;
|
||||
|
||||
@if $_use_rem_unit {
|
||||
$sidebar-width: math.div($_sidebar-width-value, 16) * 1rem;
|
||||
}
|
||||
|
||||
// #_SidebarPadding
|
||||
|
||||
$_sidebar-nav-horizontal-padding-pixels: 24;
|
||||
$_sidebar-nav-horizontal-padding-rem: math.div($_sidebar-nav-horizontal-padding-pixels, 16) * 1rem;
|
||||
|
||||
$sidebar-nav-padding: 0 $_sidebar-nav-horizontal-padding-pixels;
|
||||
|
||||
@if $_use_rem_unit {
|
||||
$sidebar-nav-padding: 0 $_sidebar-nav-horizontal-padding-rem;
|
||||
}
|
||||
|
||||
// #_AuthorPage
|
||||
|
||||
$_authorPage-navHeight-value: 48;
|
||||
|
||||
$_authorPage-navHeight: $_authorPage-navHeight-value * 1px;
|
||||
|
||||
@if $_use_rem_unit {
|
||||
$_authorPage-navHeight: math.div($_authorPage-navHeight-value, 16) * 1rem;
|
||||
}
|
||||
|
||||
$_authorPage-navItem-padding-horizontal-value: 32;
|
||||
|
||||
$_authorPage-navItem-padding-horizontal: $_authorPage-navItem-padding-horizontal-value * 1px;
|
||||
|
||||
@if $_use_rem_unit {
|
||||
$_authorPage-navItem-padding-horizontal: math.div($_authorPage-navItem-padding-horizontal-value, 16) * 1rem;
|
||||
}
|
||||
379
frontend/src/static/css/includes/form_controls/_buttons.scss
Executable file
@@ -0,0 +1,379 @@
|
||||
$success-color: #00a28b;
|
||||
$warning-color: #e09f1f;
|
||||
$danger-color: #de623b;
|
||||
|
||||
$brand-color: #008ede;
|
||||
|
||||
$body-text-color: #111;
|
||||
|
||||
$light-color: #fafafa;
|
||||
$dark-color: #111;
|
||||
|
||||
$light-accent-color: #98bc92;
|
||||
$dark-accent-color: #087e04;
|
||||
|
||||
$success-dark-color: shade($success-color, 10);
|
||||
$warning-dark-color: shade($warning-color, 10);
|
||||
$danger-dark-color: shade($danger-color, 10);
|
||||
$brand-dark-color: shade($brand-color, 10);
|
||||
$light-dark-color: shade($light-color, 10);
|
||||
$dark-dark-color: shade($dark-color, 10);
|
||||
$light-accent-dark-color: shade($light-accent-color, 10);
|
||||
$dark-accent-dark-color: shade($dark-accent-color, 10);
|
||||
|
||||
$success-darker-color: shade($success-dark-color, 10);
|
||||
$warning-darker-color: shade($warning-dark-color, 10);
|
||||
$danger-darker-color: shade($danger-dark-color, 10);
|
||||
$brand-darker-color: shade($brand-dark-color, 10);
|
||||
$light-darker-color: shade($light-dark-color, 10);
|
||||
$dark-darker-color: shade($dark-dark-color, 10);
|
||||
$light-accent-darker-color: shade($light-accent-dark-color, 10);
|
||||
$dark-accent-darker-color: shade($dark-accent-dark-color, 10);
|
||||
|
||||
$dark-light-color: tint($dark-color, 10);
|
||||
$dark-lighter-color: tint($dark-light-color, 10);
|
||||
|
||||
$brand-light-color: tint($brand-color, 20);
|
||||
$brand-lighter-color: tint($brand-light-color, 20);
|
||||
|
||||
$success-light-color: tint($success-color, 36);
|
||||
$success-lighter-color: tint($success-light-color, 36);
|
||||
|
||||
$warning-light-color: tint($warning-color, 36);
|
||||
$warning-lighter-color: tint($warning-light-color, 36);
|
||||
|
||||
$danger-light-color: tint($danger-color, 36);
|
||||
$danger-lighter-color: tint($danger-light-color, 36);
|
||||
|
||||
@mixin outline_button_colors($color) {
|
||||
color: $color;
|
||||
border-color: $color;
|
||||
}
|
||||
|
||||
@mixin button_colors() {
|
||||
color: $light-color;
|
||||
background-color: $default-gray-color;
|
||||
|
||||
&.brand-btn {
|
||||
background-color: $brand-color;
|
||||
}
|
||||
|
||||
&.light-btn {
|
||||
color: $dark-color;
|
||||
background-color: $light-color;
|
||||
}
|
||||
|
||||
&.dark-btn {
|
||||
background-color: $dark-color;
|
||||
}
|
||||
|
||||
&.light-accent-btn {
|
||||
background-color: $light-accent-color;
|
||||
}
|
||||
|
||||
&.dark-accent-btn {
|
||||
background-color: $dark-accent-color;
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
background-color: $success-color;
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
||||
&.danger-btn {
|
||||
background-color: $danger-color;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $default-gray-dark-color;
|
||||
|
||||
&.brand-btn {
|
||||
background-color: $brand-dark-color;
|
||||
}
|
||||
|
||||
&.light-btn {
|
||||
background-color: $light-dark-color;
|
||||
}
|
||||
|
||||
&.dark-btn {
|
||||
background-color: $dark-dark-color;
|
||||
}
|
||||
|
||||
&.light-accent-btn {
|
||||
background-color: $light-accent-dark-color;
|
||||
}
|
||||
|
||||
&.dark-accent-btn {
|
||||
background-color: $dark-accent-dark-color;
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
background-color: $success-dark-color;
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
background-color: $warning-dark-color;
|
||||
}
|
||||
|
||||
&.danger-btn {
|
||||
background-color: $danger-dark-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $default-gray-darker-color;
|
||||
|
||||
&.brand-btn {
|
||||
background-color: $brand-darker-color;
|
||||
}
|
||||
|
||||
&.light-btn {
|
||||
background-color: $light-darker-color;
|
||||
}
|
||||
|
||||
&.dark-btn {
|
||||
background-color: $dark-darker-color;
|
||||
}
|
||||
|
||||
&.light-accent-btn {
|
||||
background-color: $light-accent-darker-color;
|
||||
}
|
||||
|
||||
&.dark-accent-btn {
|
||||
background-color: $dark-accent-darker-color;
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
background-color: $success-darker-color;
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
background-color: $warning-darker-color;
|
||||
}
|
||||
|
||||
&.danger-btn {
|
||||
background-color: $danger-darker-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: $light-color;
|
||||
background-color: $border-gray-color;
|
||||
}
|
||||
|
||||
&.outline-btn {
|
||||
background-color: transparent;
|
||||
|
||||
box-shadow: inset 0 0 0 $brand-color;
|
||||
|
||||
@include outline_button_colors($default-gray-color);
|
||||
|
||||
&.brand-btn {
|
||||
@include outline_button_colors($brand-color);
|
||||
}
|
||||
|
||||
&.light-btn {
|
||||
@include outline_button_colors($light-color);
|
||||
}
|
||||
|
||||
&.dark-btn {
|
||||
@include outline_button_colors($dark-color);
|
||||
}
|
||||
|
||||
&.light-accent-btn {
|
||||
@include outline_button_colors($light-accent-color);
|
||||
}
|
||||
|
||||
&.dark-accent-btn {
|
||||
@include outline_button_colors($dark-accent-color);
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
@include outline_button_colors($success-color);
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
@include outline_button_colors($warning-color);
|
||||
}
|
||||
|
||||
&.danger-btn {
|
||||
@include outline_button_colors($danger-color);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: transparent;
|
||||
|
||||
@include outline_button_colors($default-gray-dark-color);
|
||||
|
||||
box-shadow: inset 0 0 0 $brand-dark-color;
|
||||
// box-shadow: inset 0 0 0 $default-gray-dark-color;
|
||||
|
||||
&.brand-btn {
|
||||
@include outline_button_colors($brand-dark-color);
|
||||
// box-shadow: inset 0 0 0 $brand-dark-color;
|
||||
}
|
||||
|
||||
&.light-btn {
|
||||
@include outline_button_colors($light-dark-color);
|
||||
// box-shadow: inset 0 0 0 $light-dark-color;
|
||||
}
|
||||
|
||||
&.dark-btn {
|
||||
@include outline_button_colors($dark-dark-color);
|
||||
// box-shadow: inset 0 0 0 $dark-dark-color;
|
||||
}
|
||||
|
||||
&.light-accent-btn {
|
||||
@include outline_button_colors($light-accent-dark-color);
|
||||
// box-shadow: inset 0 0 0 $light-accent-dark-color;
|
||||
}
|
||||
|
||||
&.dark-accent-btn {
|
||||
@include outline_button_colors($dark-accent-dark-color);
|
||||
// box-shadow: inset 0 0 0 $dark-accent-dark-color;
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
@include outline_button_colors($success-dark-color);
|
||||
// box-shadow: inset 0 0 0 $success-dark-color;
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
@include outline_button_colors($warning-dark-color);
|
||||
// box-shadow: inset 0 0 0 $warning-dark-color;
|
||||
}
|
||||
|
||||
&.danger-btn {
|
||||
@include outline_button_colors($danger-dark-color);
|
||||
// box-shadow: inset 0 0 0 $danger-dark-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: transparent;
|
||||
|
||||
@include outline_button_colors($default-gray-darker-color);
|
||||
|
||||
box-shadow: inset 0 0 0 $brand-darker-color;
|
||||
// box-shadow: inset 0 0 0 $default-gray-darker-color;
|
||||
|
||||
&.brand-btn {
|
||||
@include outline_button_colors($brand-darker-color);
|
||||
// box-shadow: inset 0 0 0 $brand-darker-color;
|
||||
}
|
||||
|
||||
&.light-btn {
|
||||
@include outline_button_colors($light-darker-color);
|
||||
// box-shadow: inset 0 0 0 $light-darker-color;
|
||||
}
|
||||
|
||||
&.dark-btn {
|
||||
@include outline_button_colors($dark-darker-color);
|
||||
// box-shadow: inset 0 0 0 $dark-darker-color;
|
||||
}
|
||||
|
||||
&.light-accent-btn {
|
||||
@include outline_button_colors($light-accent-darker-color);
|
||||
// box-shadow: inset 0 0 0 $light-accent-darker-color;
|
||||
}
|
||||
|
||||
&.dark-accent-btn {
|
||||
@include outline_button_colors($dark-accent-darker-color);
|
||||
// box-shadow: inset 0 0 0 $dark-accent-color;
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
@include outline_button_colors($success-darker-color);
|
||||
// box-shadow: inset 0 0 0 $success-darker-color;
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
@include outline_button_colors($warning-darker-color);
|
||||
// box-shadow: inset 0 0 0 $warning-darker-color;
|
||||
}
|
||||
|
||||
&.danger-btn {
|
||||
@include outline_button_colors($danger-darker-color);
|
||||
// box-shadow: inset 0 0 0 $danger-darker-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@include outline_button_colors($border-gray-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.unstyled-btn,
|
||||
&.unstyled-btn:hover,
|
||||
&.unstyled-btn:active,
|
||||
&.unstyled-btn:focus {
|
||||
color: $brand-color;
|
||||
background-color: transparent;
|
||||
box-shadow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin buttons() {
|
||||
.btn,
|
||||
a.btn,
|
||||
button,
|
||||
input[type='button'],
|
||||
input[type='submit'],
|
||||
input[type='reset'] {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
padding: 0.75rem 1.25rem;
|
||||
margin-right: 0.5rem;
|
||||
line-height: 0.9;
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
border-radius: 0.125rem;
|
||||
|
||||
&.small-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
&.large-btn {
|
||||
padding: 1rem 1.5rem;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-color: $brand-color;
|
||||
}
|
||||
|
||||
&.outline-btn {
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
&.unstyled-btn,
|
||||
&.unstyled-btn:hover,
|
||||
&.unstyled-btn:active,
|
||||
&.unstyled-btn:focus {
|
||||
padding: 0;
|
||||
font-weight: 400;
|
||||
text-decoration: underline;
|
||||
outline-offset: 0;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@include button_colors;
|
||||
}
|
||||
}
|
||||
3
frontend/src/static/css/includes/form_controls/_form_controls.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
@import './_mixins_form_controls.scss';
|
||||
|
||||
@include form_controls();
|
||||
619
frontend/src/static/css/includes/form_controls/_mixins_form_controls.scss
Executable file
@@ -0,0 +1,619 @@
|
||||
// Replace `$search` with `$replace` in `$string`
|
||||
// @param {String} $string - Initial string
|
||||
// @param {String} $search - Substring to replace
|
||||
// @param {String} $replace ('') - New value
|
||||
// @return {String} - Updated string
|
||||
@function str-replace($string, $search, $replace: '') {
|
||||
$index: str-index($string, $search);
|
||||
|
||||
@if $index {
|
||||
@return str-slice($string, 1, $index - 1) + $replace +
|
||||
str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
|
||||
}
|
||||
|
||||
@return $string;
|
||||
}
|
||||
|
||||
$encoding-reference: (
|
||||
//('%','%25'), // Encode "%" first, otherwise the "%" from encoded code would be encoded again (which would be bad)
|
||||
('<', '%3C'),
|
||||
('>', '%3E'),
|
||||
//('"','%22'), // Replace " with ' because that's shorter than %22 and normally working
|
||||
('"', "'"),
|
||||
('#', '%23'),
|
||||
('&', '%26') // Here are a few more characters you could encode
|
||||
//(' ','%20'),
|
||||
//('!','%21'),
|
||||
//('$','%24'),
|
||||
//(',','%27'),
|
||||
//('(','%28'),
|
||||
//(')','%29'),
|
||||
//('*','%2A'),
|
||||
//('+','%2B'),
|
||||
//('"','%2C'),
|
||||
//('/','%2F'),
|
||||
//(':','%3A'),
|
||||
//(';','%3B'),
|
||||
//('=','%3D'),
|
||||
//('?','%3F'),
|
||||
//('@','%40'),
|
||||
//('[','%5B'),
|
||||
//(']','%5D'),
|
||||
//('^','%5E'),
|
||||
//('`','%60'),
|
||||
//('{','%7B'),
|
||||
//('|','%7C'),
|
||||
//('}','%7D'),
|
||||
//('~','%7E'),
|
||||
//(',','%E2%80%9A'),
|
||||
//('\\','%5C'),
|
||||
//('_','%5F'),
|
||||
//('-','%2D'),
|
||||
//('.','%2E'),
|
||||
// ('\','%5C'),
|
||||
// (' ','%7F'),
|
||||
// ('`','%E2%82%AC'),
|
||||
//('ƒ','%C6%92'),
|
||||
//('„','%E2%80%9E'),
|
||||
//('…','%E2%80%A6'),
|
||||
//('†','%E2%80%A0'),
|
||||
//('‡','%E2%80%A1'),
|
||||
//('ˆ','%CB%86'),
|
||||
//('‰','%E2%80%B0'),
|
||||
//('Š','%C5%A0'),
|
||||
//('‹','%E2%80%B9'),
|
||||
//('Œ','%C5%92'),
|
||||
//('','%C5%8D'),
|
||||
//('Ž','%C5%BD'),
|
||||
//('','%8F'),
|
||||
//('','%C2%90'),
|
||||
//(','%'E2%80%98'),
|
||||
//(','%'E2%80%99'),
|
||||
//('“','%E2%80%9C'),
|
||||
//('”','%E2%80%9D'),
|
||||
//('•','%E2%80%A2'),
|
||||
//('–','%E2%80%93'),
|
||||
//('—','%E2%80%94'),
|
||||
//('˜','%CB%9C'),
|
||||
//('™','%E2%84'),
|
||||
//('š','%C5%A1'),
|
||||
//('›','%E2%80'),
|
||||
//('œ','%C5%93'),
|
||||
//('','%9D'),
|
||||
//('ž','%C5%BE'),
|
||||
//('Ÿ','%C5%B8'),
|
||||
//(' ','%C2%A0'),
|
||||
//('¡','%C2%A1'),
|
||||
//('¢','%C2%A2'),
|
||||
//('£','%C2%A3'),
|
||||
//('¤','%C2%A4'),
|
||||
//('¥','%C2%A5'),
|
||||
//('¦','%C2%A6'),
|
||||
//('§','%C2%A7'),
|
||||
//('¨','%C2%A8'),
|
||||
//('©','%C2%A9'),
|
||||
//('ª','%C2%AA'),
|
||||
//('«','%C2%AB'),
|
||||
//('¬','%C2%AC'),
|
||||
//(','%'C2%AD'),
|
||||
//('®','%C2%AE'),
|
||||
//('¯','%C2%AF'),
|
||||
//('°','%C2%B0'),
|
||||
//('±','%C2%B1'),
|
||||
//('²','%C2%B2'),
|
||||
//('³','%C2%B3'),
|
||||
//('´','%C2%B4'),
|
||||
//('µ','%C2%B5'),
|
||||
//('¶','%C2%B6'),
|
||||
//('·','%C2%B7'),
|
||||
//('¸','%C2%B8'),
|
||||
//('¹','%C2%B9'),
|
||||
//('º','%C2%BA'),
|
||||
//('»','%C2%BB'),
|
||||
//('¼','%C2%BC'),
|
||||
//('½','%C2%BD'),
|
||||
//('¾','%C2%BE'),
|
||||
//('¿','%C2%BF'),
|
||||
//('À','%C3%80'),
|
||||
//('Á','%C3%81'),
|
||||
//('Â','%C3%82'),
|
||||
//('Ã','%C3%83'),
|
||||
//('Ä','%C3%84'),
|
||||
//('Å','%C3%85'),
|
||||
//('Æ','%C3%86'),
|
||||
//('Ç','%C3%87'),
|
||||
//('È','%C3%88'),
|
||||
//('É','%C3%89'),
|
||||
//('Ê','%C3%8A'),
|
||||
//('Ë','%C3%8B'),
|
||||
//('Ì','%C3%8C'),
|
||||
//('Í','%C3%8D'),
|
||||
//('Î','%C3%8E'),
|
||||
//('Ï','%C3%8F'),
|
||||
//('Ð','%C3%90'),
|
||||
//('Ñ','%C3%91'),
|
||||
//('Ò','%C3%92'),
|
||||
//('Ó','%C3%93'),
|
||||
//('Ô','%C3%94'),
|
||||
//('Õ','%C3%95'),
|
||||
//('Ö','%C3%96'),
|
||||
//('×','%C3%97'),
|
||||
//('Ø','%C3%98'),
|
||||
//('Ù','%C3%99'),
|
||||
//('Ú','%C3%9A'),
|
||||
//('Û','%C3%9B'),
|
||||
//('Ü','%C3%9C'),
|
||||
//('Ý','%C3%9D'),
|
||||
//('Þ','%C3%9E'),
|
||||
//('ß','%C3%9F'),
|
||||
//('à','%C3%A0'),
|
||||
//('á','%C3%A1'),
|
||||
//('â','%C3%A2'),
|
||||
//('ã','%C3%A3'),
|
||||
//('ä','%C3%A4'),
|
||||
//('å','%C3%A5'),
|
||||
//('æ','%C3%A6'),
|
||||
//('ç','%C3%A7'),
|
||||
//('è','%C3%A8'),
|
||||
//('é','%C3%A9'),
|
||||
//('ê','%C3%AA'),
|
||||
//('ë','%C3%AB'),
|
||||
//('ì','%C3%AC'),
|
||||
//('í','%C3%AD'),
|
||||
//('î','%C3%AE'),
|
||||
//('ï','%C3%AF'),
|
||||
//('ð','%C3%B0'),
|
||||
//('ñ','%C3%B1'),
|
||||
//('ò','%C3%B2'),
|
||||
//('ó','%C3%B3'),
|
||||
//('ô','%C3%B4'),
|
||||
//('õ','%C3%B5'),
|
||||
//('ö','%C3%B6'),
|
||||
//('÷','%C3%B7'),
|
||||
//('ø','%C3%B8'),
|
||||
//('ù','%C3%B9'),
|
||||
//('ú','%C3%BA'),
|
||||
//('û','%C3%BB'),
|
||||
//('ü','%C3%BC'),
|
||||
//('ý','%C3%BD'),
|
||||
//('þ','%C3%BE'),
|
||||
//('ÿ','%C3%BF')
|
||||
);
|
||||
|
||||
@function svg-encode($svg) {
|
||||
@each $char, $encoded in $encoding-reference {
|
||||
$svg: str-replace($svg, $char, $encoded);
|
||||
}
|
||||
|
||||
@return 'data:image/svg+xml,' + $svg;
|
||||
}
|
||||
|
||||
@mixin svg-background-image($svg) {
|
||||
background-image: url(svg-encode($svg)), linear-gradient(transparent, transparent);
|
||||
}
|
||||
|
||||
@mixin range-focus {
|
||||
background-color: var(--body-bg-color);
|
||||
background-color: var(--theme-color, var(--default-theme-color));
|
||||
}
|
||||
|
||||
@mixin range-track {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 9999em;
|
||||
border: 1px solid var(--input-border-color);
|
||||
background: var(--input-bg-color);
|
||||
}
|
||||
|
||||
@mixin range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
border-radius: 999em;
|
||||
background-color: var(--input-border-color);
|
||||
}
|
||||
|
||||
@mixin range-ms-fill {
|
||||
border-radius: 999em;
|
||||
border: 1px solid var(--input-border-color);
|
||||
background: var(--input-bg-color);
|
||||
}
|
||||
|
||||
@mixin form_controls() {
|
||||
:root {
|
||||
--checkbox-width: 1.143em;
|
||||
--checkbox-height: 1.143em;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='email'],
|
||||
input[type='number'],
|
||||
input[type='password'],
|
||||
input[type='file'],
|
||||
input[type='range'],
|
||||
input[type='reset'],
|
||||
input[type='radio'],
|
||||
input[type='checkbox'],
|
||||
select,
|
||||
textarea {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='email'],
|
||||
input[type='number'],
|
||||
input[type='password'],
|
||||
input[type='file'],
|
||||
input[type='reset'],
|
||||
input[type='radio'],
|
||||
input[type='checkbox'],
|
||||
select,
|
||||
textarea {
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='email'],
|
||||
input[type='number'],
|
||||
input[type='password'],
|
||||
input[type='file'],
|
||||
input[type='reset'],
|
||||
select,
|
||||
textarea {
|
||||
&:focus {
|
||||
border-color: var(--theme-color, var(--default-theme-color));
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
padding: 0.57142875em;
|
||||
line-height: 1.3;
|
||||
color: var(--input-color);
|
||||
border-radius: 1px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--input-border-color);
|
||||
background-color: var(--input-bg-color);
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
background-color: var(--input-disabled-bg-color);
|
||||
}
|
||||
|
||||
&.input-success {
|
||||
border-color: var(--success-color);
|
||||
}
|
||||
|
||||
&.input-warning {
|
||||
border-color: var(--warning-color);
|
||||
}
|
||||
|
||||
&.input-error {
|
||||
border-color: var(--danger-color);
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
select {
|
||||
padding-right: 32px;
|
||||
background-size: 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 4px center;
|
||||
|
||||
@include svg-background-image(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="rgba(0,0,0,0.897)"><path d="M14 22l10-10 10 10z" /><path d="M14 26l10 10 10-10z" /></svg>'
|
||||
);
|
||||
|
||||
.dark_theme & {
|
||||
@include svg-background-image(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="rgba(255,255,255,0.88)"><path d="M14 22l10-10 10 10z" /><path d="M14 26l10 10 10-10z" /></svg>'
|
||||
);
|
||||
}
|
||||
|
||||
&[multiple] {
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
background-image: none;
|
||||
|
||||
option {
|
||||
padding: 0.7143em 0.57142875em;
|
||||
margin: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 2.75rem;
|
||||
height: 160px;
|
||||
|
||||
min-width: 0.5 * 18.75rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input[type='file'] {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
display: block;
|
||||
max-width: none;
|
||||
min-height: 40px;
|
||||
padding: 1em 1px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
|
||||
&:focus {
|
||||
&::-webkit-slider-thumb {
|
||||
@include range-focus;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
@include range-focus;
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
@include range-focus;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include range-track;
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
@include range-track;
|
||||
}
|
||||
|
||||
&::-ms-track {
|
||||
@include range-track;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
@include range-thumb;
|
||||
appearance: none;
|
||||
margin-top: -0.19rem;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
@include range-thumb;
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
@include range-thumb;
|
||||
}
|
||||
|
||||
&::-ms-fill-lower {
|
||||
@include range-ms-fill;
|
||||
}
|
||||
|
||||
&::-ms-fill-upper {
|
||||
@include range-ms-fill;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio'],
|
||||
input[type='checkbox'],
|
||||
*.radio-label .selectbox,
|
||||
*.checkbox-label .selectbox {
|
||||
width: var(--checkbox-width);
|
||||
height: var(--checkbox-height);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type='radio'],
|
||||
input[type='checkbox'] {
|
||||
margin: 0 0.75em;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--input-border-color);
|
||||
box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
background-size: 20px auto;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-color: var(--theme-color, var(--default-theme-color));
|
||||
border-color: var(--theme-color, var(--default-theme-color));
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
border-color: var(--input-border-color);
|
||||
background-color: var(--input-disabled-bg-color);
|
||||
}
|
||||
|
||||
&:checked:disabled {
|
||||
background-color: var(--input-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
*.radio-label,
|
||||
*.checkbox-label {
|
||||
.selectbox {
|
||||
background-color: var(--input-bg-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
}
|
||||
|
||||
input[type='radio'],
|
||||
input[type='checkbox'] {
|
||||
&:focus ~ .selectbox {
|
||||
border-color: var(--input-border-color);
|
||||
box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
&:active ~ .selectbox {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&:checked ~ .selectbox {
|
||||
background-size: 20px auto;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-color: var(--theme-color, var(--default-theme-color));
|
||||
border-color: var(--theme-color, var(--default-theme-color));
|
||||
}
|
||||
|
||||
&:disabled ~ .selectbox {
|
||||
border-color: var(--input-border-color);
|
||||
background-color: var(--input-disabled-bg-color);
|
||||
}
|
||||
|
||||
&:checked:disabled ~ .selectbox {
|
||||
background-color: var(--input-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio'],
|
||||
input[type='radio'] ~ .selectbox {
|
||||
border-radius: 99em;
|
||||
}
|
||||
|
||||
input[type='radio']:checked,
|
||||
input[type='radio']:checked ~ .selectbox {
|
||||
@include svg-background-image(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18px" height="18px" fill="white"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M2 12C2 6.48 6.48 2 12 2s10 4.48 10 10-4.48 10-10 10S2 17.52 2 12zm10 6c3.31 0 6-2.69 6-6s-2.69-6-6-6-6 2.69-6 6 2.69 6 6 6z"/></svg>'
|
||||
);
|
||||
}
|
||||
|
||||
input[type='radio']:checked:disabled,
|
||||
input[type='radio']:checked:disabled ~ .selectbox {
|
||||
@include svg-background-image(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18px" height="18px" fill="rgba(255,255,255,0.65)"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M2 12C2 6.48 6.48 2 12 2s10 4.48 10 10-4.48 10-10 10S2 17.52 2 12zm10 6c3.31 0 6-2.69 6-6s-2.69-6-6-6-6 2.69-6 6 2.69 6 6 6z"/></svg>'
|
||||
);
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked,
|
||||
input[type='checkbox']:checked ~ .selectbox {
|
||||
@include svg-background-image(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48" fill="white"><path d="M18 32.34L9.66 24l-2.83 2.83L18 38l24-24-2.83-2.83z"/></svg>'
|
||||
);
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked:disabled,
|
||||
input[type='checkbox']:checked:disabled ~ .selectbox {
|
||||
@include svg-background-image(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48" fill="rgba(255,255,255,0.65)"><path d="M18 32.34L9.66 24l-2.83 2.83L18 38l24-24-2.83-2.83z"/></svg>'
|
||||
);
|
||||
}
|
||||
|
||||
*.radio-label,
|
||||
*.checkbox-label {
|
||||
position: relative;
|
||||
line-height: 1.143;
|
||||
margin-right: 1em;
|
||||
cursor: pointer;
|
||||
|
||||
.selectbox {
|
||||
display: inline-block;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
|
||||
&.right-selectbox .selectbox {
|
||||
margin-right: 0;
|
||||
margin-left: 0.75em;
|
||||
}
|
||||
|
||||
input[type='radio'],
|
||||
input[type='checkbox'] {
|
||||
position: absolute;
|
||||
left: -999em;
|
||||
}
|
||||
}
|
||||
|
||||
label,
|
||||
.input-message {
|
||||
+ input:not([type='radio']):not([type='checkbox']),
|
||||
+ select,
|
||||
+ textarea,
|
||||
+ button {
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.input-message {
|
||||
display: inline-block;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
&.success-message {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
&.warning-message {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
&.error-message {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
}
|
||||
|
||||
label + .input-message {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.num-value-unit {
|
||||
.label {
|
||||
display: block;
|
||||
padding: 0 0 4px;
|
||||
}
|
||||
|
||||
.value-input,
|
||||
.value-unit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: auto;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
z-index: +1;
|
||||
}
|
||||
}
|
||||
|
||||
.value-input {
|
||||
margin-right: -1px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.value-unit {
|
||||
}
|
||||
}
|
||||
109
frontend/src/static/css/includes/typography/_gfonts.scss
Executable file
@@ -0,0 +1,109 @@
|
||||
// @import url('https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i&display=swap&subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext,vietnamese');
|
||||
// @import url('https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,500,500i,700,700i&display=swap&subset=greek');
|
||||
|
||||
$url_prefix: '../lib/gfonts/roboto/greek-latin/300-400-500-700/roboto-v20-greek_latin-'; // Address relative to 'css' folder.
|
||||
|
||||
/* roboto-300 - greek_latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: fallback;
|
||||
src: url($url_prefix + '300.eot'); /* IE9 Compat Modes */
|
||||
src: local('Arial'), local('Roboto Light'), local('Roboto-Light'),
|
||||
url($url_prefix + '300.eot?#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url($url_prefix + '300.woff2') format('woff2'),
|
||||
/* Super Modern Browsers */ url($url_prefix + '300.woff') format('woff'),
|
||||
/* Modern Browsers */ url($url_prefix + '300.ttf') format('truetype'),
|
||||
/* Safari, Android, iOS */ url($url_prefix + '300.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-300italic - greek_latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: fallback;
|
||||
src: url($url_prefix + '300italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Arial'), local('Roboto Light Italic'), local('Roboto-LightItalic'),
|
||||
url($url_prefix + '300italic.eot?#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url($url_prefix + '300italic.woff2') format('woff2'),
|
||||
/* Super Modern Browsers */ url($url_prefix + '300italic.woff') format('woff'),
|
||||
/* Modern Browsers */ url($url_prefix + '300italic.ttf') format('truetype'),
|
||||
/* Safari, Android, iOS */ url($url_prefix + '300italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-regular - greek_latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: fallback;
|
||||
src: url($url_prefix + 'regular.eot'); /* IE9 Compat Modes */
|
||||
src: local('Arial'), local('Roboto'), local('Roboto-Regular'),
|
||||
url($url_prefix + 'regular.eot?#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url($url_prefix + 'regular.woff2') format('woff2'),
|
||||
/* Super Modern Browsers */ url($url_prefix + 'regular.woff') format('woff'),
|
||||
/* Modern Browsers */ url($url_prefix + 'regular.ttf') format('truetype'),
|
||||
/* Safari, Android, iOS */ url($url_prefix + 'regular.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-italic - greek_latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: fallback;
|
||||
src: url($url_prefix + 'italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Arial'), local('Roboto Italic'), local('Roboto-Italic'),
|
||||
url($url_prefix + 'italic.eot?#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url($url_prefix + 'italic.woff2') format('woff2'),
|
||||
/* Super Modern Browsers */ url($url_prefix + 'italic.woff') format('woff'),
|
||||
/* Modern Browsers */ url($url_prefix + 'italic.ttf') format('truetype'),
|
||||
/* Safari, Android, iOS */ url($url_prefix + 'italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-500 - greek_latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: fallback;
|
||||
src: url($url_prefix + '500.eot'); /* IE9 Compat Modes */
|
||||
src: local('Arial'), local('Roboto Medium'), local('Roboto-Medium'),
|
||||
url($url_prefix + '500.eot?#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url($url_prefix + '500.woff2') format('woff2'),
|
||||
/* Super Modern Browsers */ url($url_prefix + '500.woff') format('woff'),
|
||||
/* Modern Browsers */ url($url_prefix + '500.ttf') format('truetype'),
|
||||
/* Safari, Android, iOS */ url($url_prefix + '500.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-500italic - greek_latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: fallback;
|
||||
src: url($url_prefix + '500italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Arial'), local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
|
||||
url($url_prefix + '500italic.eot?#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url($url_prefix + '500italic.woff2') format('woff2'),
|
||||
/* Super Modern Browsers */ url($url_prefix + '500italic.woff') format('woff'),
|
||||
/* Modern Browsers */ url($url_prefix + '500italic.ttf') format('truetype'),
|
||||
/* Safari, Android, iOS */ url($url_prefix + '500italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-700 - greek_latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: fallback;
|
||||
src: url($url_prefix + '700.eot'); /* IE9 Compat Modes */
|
||||
src: local('Arial'), local('Roboto Bold'), local('Roboto-Bold'),
|
||||
url($url_prefix + '700.eot?#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url($url_prefix + '700.woff2') format('woff2'),
|
||||
/* Super Modern Browsers */ url($url_prefix + '700.woff') format('woff'),
|
||||
/* Modern Browsers */ url($url_prefix + '700.ttf') format('truetype'),
|
||||
/* Safari, Android, iOS */ url($url_prefix + '700.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
26
frontend/src/static/css/includes/typography/_material_icons.scss
Executable file
@@ -0,0 +1,26 @@
|
||||
// @import url('https://fonts.googleapis.com/icon?family=Material+Icons');
|
||||
|
||||
$url_prefix: '../lib/material-icons/v50/'; // Address relative to 'css' folder.
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url($url_prefix + 'icons.woff2') format('woff2');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
161
frontend/src/static/css/includes/typography/_mixins_typoghraphy.scss
Executable file
@@ -0,0 +1,161 @@
|
||||
@use "sass:math";
|
||||
|
||||
$font-family-serif: 'Roboto Slab', 'Merriweather Web', 'Georgia', 'Cambria', 'Times New Roman', 'Times', serif;
|
||||
$font-family-sans-serif: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
||||
$font-family-monospace: 'Roboto Mono', 'Consolas', 'Courier', monospace;
|
||||
|
||||
$line-height-serif: 1.29;
|
||||
$line-height-sans-serif: 1.33;
|
||||
$line-height-monospace: 1.42;
|
||||
|
||||
$font-size-serif: 0.85625 * 16px;
|
||||
$font-size-sans-serif: 0.93125 * 16px;
|
||||
$font-size-monospace: 0.83125 * 16px;
|
||||
|
||||
@mixin typeset($font-size, $line-height, $font-family) {
|
||||
$font-size: math.div($font-size, 16px) * 16px;
|
||||
|
||||
font-size: $font-size;
|
||||
font-family: $font-family;
|
||||
line-height: $line-height;
|
||||
|
||||
.font-size-large {
|
||||
font-size: 2.6625em;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
font-size: 2.13125em;
|
||||
}
|
||||
|
||||
h2,
|
||||
.h2 {
|
||||
font-size: 1.4625em;
|
||||
}
|
||||
|
||||
h3,
|
||||
.h3 {
|
||||
font-size: 1.13125em;
|
||||
}
|
||||
|
||||
h4,
|
||||
.h4 {
|
||||
font-size: 1.0625em;
|
||||
}
|
||||
|
||||
h5,
|
||||
.h5 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h6,
|
||||
.h6 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.font-size-small {
|
||||
font-size: 0.93125em;
|
||||
}
|
||||
|
||||
.sub-heading,
|
||||
.font-size-x-small {
|
||||
font-size: 0.8625em;
|
||||
}
|
||||
|
||||
.section-intro {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.8625em;
|
||||
}
|
||||
|
||||
big {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin serif_typeset() {
|
||||
@include typeset($font-size-serif, $line-height-serif, $font-family-serif);
|
||||
}
|
||||
|
||||
@mixin sans_serif_typeset() {
|
||||
@include typeset($font-size-sans-serif, $line-height-sans-serif, $font-family-sans-serif);
|
||||
}
|
||||
|
||||
@mixin monospace_typeset() {
|
||||
@include typeset($font-size-monospace, $line-height-monospace, $font-family-monospace);
|
||||
}
|
||||
|
||||
@mixin mediacms_typography(
|
||||
$font-size: $font-size-sans-serif,
|
||||
$font-family: $default-font-family,
|
||||
$line-height: $line-height-sans-serif
|
||||
) {
|
||||
body {
|
||||
@include typeset($font-size, $line-height, $font-family);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: bold;
|
||||
font-weight: 500;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.sub-heading {
|
||||
display: block;
|
||||
clear: both;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0.05em;
|
||||
margin: 8px 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.section-intro {
|
||||
font-weight: 100;
|
||||
font-weight: 200;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
p,
|
||||
ul {
|
||||
font-size: 1em;
|
||||
line-height: 1.62;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
button {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: 1em 0 2em 0;
|
||||
border: 0;
|
||||
background-color: var(--hr-color);
|
||||
}
|
||||
}
|
||||
8
frontend/src/static/css/includes/typography/_typography.scss
Executable file
@@ -0,0 +1,8 @@
|
||||
@import './_mixins_typoghraphy.scss';
|
||||
|
||||
$body-font-family: 'Roboto', Arial, sans-serif;
|
||||
|
||||
$body-font-size: 14px;
|
||||
$body-line-height: 1.5;
|
||||
|
||||
@include mediacms_typography($body-font-size, $body-font-family, $body-line-height);
|
||||
600
frontend/src/static/css/styles.scss
Executable file
@@ -0,0 +1,600 @@
|
||||
// @import '~compass-mixins';
|
||||
@import './includes/_variables.scss';
|
||||
@import './includes/_variables_dimensions.scss';
|
||||
@import './config/index.scss';
|
||||
@import './includes/_theme_color.scss';
|
||||
@import '~normalize.css/normalize.css';
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body,
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
color: var(--body-text-color);
|
||||
background-color: var(--body-bg-color);
|
||||
|
||||
transition-property: overflow;
|
||||
transition-duration: 0.2s;
|
||||
transition-timing-function: linear;
|
||||
|
||||
&.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@import './includes/typography/_typography.scss';
|
||||
@import './includes/form_controls/_form_controls.scss';
|
||||
|
||||
a {
|
||||
color: var(--theme-color, var(--default-theme-color));
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cf:before,
|
||||
.cf:after {
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
.cf:after {
|
||||
clear: both;
|
||||
}
|
||||
.cf {
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.hidden-txt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-link {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
-webkit-tap-highlight-color: rgba(black, 0);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.button-link {
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 640px) {
|
||||
.visible-only-in-small {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 639px) {
|
||||
.hidden-only-in-small {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 480px) {
|
||||
.visible-only-in-extra-small {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 479px) {
|
||||
.hidden-only-in-extra-small {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.user-action-form-wrap {
|
||||
margin: 2em 1em 1em;
|
||||
|
||||
@media screen and (min-width: 1220px) {
|
||||
.sliding-sidebar & {
|
||||
transition-property: padding-right;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.visible-sidebar & {
|
||||
padding-right: $_sidebar-width-value * 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-action-form-inner {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
|
||||
@media screen and (min-width: 1220px) {
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
padding: 2em 2em;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
|
||||
background-color: var(--user-action-form-inner-bg-color);
|
||||
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 4px 8px 0 rgba(17, 17, 17, 0.06);
|
||||
|
||||
form,
|
||||
label,
|
||||
select,
|
||||
textarea,
|
||||
input[type='text'],
|
||||
input[type='email'],
|
||||
input[type='number'],
|
||||
input[type='password'] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 0 0 0.67em 0;
|
||||
margin: 0 0 0.5em;
|
||||
font-size: 1.13125em;
|
||||
font-weight: 400;
|
||||
border-width: 0 0 1px;
|
||||
border-style: solid;
|
||||
border-bottom-color: var(--user-action-form-inner-title-border-bottom-color);
|
||||
}
|
||||
|
||||
form {
|
||||
*[type='submit'],
|
||||
.primaryAction,
|
||||
.secondaryAction {
|
||||
line-height: 1.125;
|
||||
padding: 1em 2em;
|
||||
margin: 1em 0 0.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
h1 + form {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.help-block {
|
||||
line-height: 1.5;
|
||||
font-weight: lighter;
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 1.5em;
|
||||
|
||||
> .control-group {
|
||||
> *:first-child {
|
||||
&.controls {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2.5em;
|
||||
|
||||
> *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
a {
|
||||
margin: 0 0.25em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
margin: 1em 0 0 0;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
|
||||
&[for='banner_logo-clear_id'] {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='file'] {
|
||||
width: 100%;
|
||||
margin-top: 0.5em;
|
||||
|
||||
@media screen and (min-width: 711px) {
|
||||
width: auto;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
.controls {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.login,
|
||||
&.logout {
|
||||
.primaryAction {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.secondaryAction {
|
||||
float: right;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
label.checkbox {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
|
||||
+ .help-block {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
margin-top: -2px;
|
||||
margin-right: 1em;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
position: relative;
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
a {
|
||||
margin: 0 0.25em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
|
||||
+ input,
|
||||
+ select,
|
||||
+ textarea {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
+ input[type='radio'],
|
||||
+ input[type='checkbox'] {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
margin: 0.3em 0em 0em 0.75em;
|
||||
}
|
||||
|
||||
&[for='logo-clear_id'] {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='file'] {
|
||||
width: 100%;
|
||||
margin-top: 0.5em;
|
||||
|
||||
@media screen and (min-width: 711px) {
|
||||
width: auto;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button,
|
||||
*[type='submit'],
|
||||
*[type='button'],
|
||||
form.login .secondaryAction,
|
||||
form.logout .secondaryAction {
|
||||
min-width: 88px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button,
|
||||
*[type='submit'],
|
||||
*[type='button'] {
|
||||
border: 0;
|
||||
color: white;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 80px;
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.requiredField {
|
||||
.asteriskField {
|
||||
margin-left: 0.25em;
|
||||
color: $color-error-red;
|
||||
}
|
||||
}
|
||||
|
||||
.control-group {
|
||||
&.error {
|
||||
input {
|
||||
border-color: rgba($color-error-red, 0.4);
|
||||
|
||||
+ p {
|
||||
color: $color-error-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.errorlist {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
padding: 0.75rem 0.75rem 0;
|
||||
margin: 0 0 1rem;
|
||||
list-style: lower-latin;
|
||||
list-style-position: inside;
|
||||
color: $message-default-color;
|
||||
background-color: $message-error-bg-color;
|
||||
|
||||
li {
|
||||
margin: 0 0 0.75rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.player-container.player-container-error {
|
||||
.error-container {
|
||||
position: relative;
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.error-container-inner {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
padding: 1em;
|
||||
font-size: 20px;
|
||||
|
||||
.icon-wrap {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.4;
|
||||
|
||||
i {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 640px) {
|
||||
display: inline-block;
|
||||
padding-right: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
|
||||
i {
|
||||
font-size: 3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg-wrap {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 639px) {
|
||||
padding: 0.5em 0.5em 2.5em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 1.5rem 4rem 1.5rem 1.5rem;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $message-default-color;
|
||||
background-color: $message-default-bg-color;
|
||||
|
||||
&.info {
|
||||
background-color: $message-info-bg-color;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: $message-error-bg-color;
|
||||
}
|
||||
|
||||
&.warn,
|
||||
&.warning {
|
||||
background-color: $message-warning-bg-color;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: $message-success-bg-color;
|
||||
}
|
||||
|
||||
&.alert-dismissible {
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
transition-property: margin-top;
|
||||
transition-duration: 0.3s;
|
||||
|
||||
&.hiding {
|
||||
margin-top: -4rem;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 0.875rem;
|
||||
right: 0.75rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
display: block;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
color: $message-default-color;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font-family: serif;
|
||||
font-size: 32px;
|
||||
font-weight: normal;
|
||||
border-radius: 9999px;
|
||||
|
||||
&:focus {
|
||||
background-color: rgba(black, 0.07);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-page-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 1366px;
|
||||
padding: 1em 3em 1em;
|
||||
margin: 0 auto;
|
||||
display: inline-block;
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-size: 1.071428571em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
img {
|
||||
&.fl {
|
||||
margin: 0 0.75em 0.5em 0;
|
||||
}
|
||||
|
||||
&.fr {
|
||||
margin: 0 0 0.5em 0.75em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-main-inner .custom-page-wrapper {
|
||||
padding: 0 2em 1em;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
max-width: 15em;
|
||||
padding: 0.916666666667em 0.666666666667em !important;
|
||||
padding: 0.9125em 1.125em !important;
|
||||
padding: 10px 12px !important;
|
||||
font-size: 12px !important;
|
||||
line-height: 1.5 !important;
|
||||
color: rgba(#fff, 1) !important;
|
||||
background-color: #595959 !important;
|
||||
border-radius: 2px !important;
|
||||
z-index: +5 !important;
|
||||
}
|
||||
|
||||
.empty-media {
|
||||
padding: 80px 0 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1366px) {
|
||||
.empty-media {
|
||||
padding: 96px 0 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-media .welcome-title {
|
||||
display: block;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.empty-media .start-uploading {
|
||||
max-width: 360px;
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
padding: 12px 0 24px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.empty-media .button-link {
|
||||
display: inline-block;
|
||||
padding: 13px 16px 11px;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 1px;
|
||||
border-color: var(--default-brand-color);
|
||||
background-color: var(--default-brand-color);
|
||||
}
|
||||
.empty-media .button-link .material-icons {
|
||||
margin-right: 8px;
|
||||
margin-top: -1px;
|
||||
font-size: 17px;
|
||||
line-height: 1;
|
||||
opacity: 0.65;
|
||||
}
|
||||
BIN
frontend/src/static/favicons/android-chrome-192x192.png
Executable file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
frontend/src/static/favicons/android-chrome-512x512.png
Executable file
|
After Width: | Height: | Size: 25 KiB |
BIN
frontend/src/static/favicons/apple-touch-icon.png
Executable file
|
After Width: | Height: | Size: 8.2 KiB |
9
frontend/src/static/favicons/browserconfig.xml
Executable file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="static/favicons/mstile-150x150.png"/>
|
||||
<TileColor>#ffffff</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
frontend/src/static/favicons/favicon-16x16.png
Executable file
|
After Width: | Height: | Size: 961 B |
BIN
frontend/src/static/favicons/favicon-32x32.png
Executable file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
frontend/src/static/favicons/favicon.ico
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
frontend/src/static/favicons/mstile-150x150.png
Executable file
|
After Width: | Height: | Size: 5.2 KiB |
65
frontend/src/static/favicons/safari-pinned-tab.svg
Executable file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M3310 3608 l0 -252 -42 41 c-73 69 -129 91 -234 91 -219 0 -349 -181
|
||||
-343 -481 1 -55 8 -125 16 -155 31 -120 102 -218 190 -262 41 -22 61 -25 143
|
||||
-25 110 1 167 22 232 87 l38 38 1 -32 c3 -76 1 -73 52 -76 l47 -3 0 640 0 641
|
||||
-50 0 -50 0 0 -252z m-134 -236 c45 -23 110 -90 124 -128 6 -14 10 -119 10
|
||||
-233 l0 -208 -30 -41 c-39 -54 -92 -88 -158 -103 -60 -12 -140 -6 -181 15 -98
|
||||
51 -154 183 -150 356 5 190 57 300 166 351 52 24 163 19 219 -9z"/>
|
||||
<path d="M3683 3780 c-58 -67 35 -152 98 -89 24 24 20 75 -8 97 -25 18 -71 15
|
||||
-90 -8z"/>
|
||||
<path d="M410 3185 l0 -605 49 0 c30 0 51 5 53 13 5 14 0 924 -5 980 -2 20 -1
|
||||
37 3 37 4 0 26 -44 49 -97 22 -54 119 -285 215 -513 l174 -415 41 -3 c40 -3
|
||||
42 -2 57 35 8 21 19 47 24 58 5 11 26 61 46 110 20 50 42 104 50 120 7 17 76
|
||||
180 153 364 78 184 144 338 149 343 5 5 8 -34 6 -89 -3 -88 -5 -651 -4 -860
|
||||
l0 -83 50 0 50 0 0 605 0 605 -69 0 -69 0 -77 -187 c-42 -104 -83 -201 -91
|
||||
-218 -50 -114 -264 -637 -264 -647 0 -22 -17 6 -48 77 -16 39 -100 241 -187
|
||||
450 -86 209 -163 394 -170 410 -7 17 -21 49 -30 73 l-17 42 -69 0 -69 0 0
|
||||
-605z"/>
|
||||
<path d="M2071 3474 c-115 -41 -202 -131 -246 -254 -50 -143 -37 -352 31 -468
|
||||
33 -56 101 -121 151 -146 21 -10 46 -22 56 -27 32 -17 160 -22 222 -9 79 16
|
||||
122 39 179 95 40 38 46 48 35 61 -30 36 -48 37 -86 3 -63 -55 -117 -73 -213
|
||||
-73 -70 0 -93 4 -130 23 -97 51 -163 154 -174 275 l-6 56 322 0 321 0 -6 93
|
||||
c-11 194 -89 318 -232 368 -66 23 -162 24 -224 3z m201 -92 c77 -31 143 -131
|
||||
155 -233 l6 -49 -268 0 -268 0 7 39 c16 101 85 198 170 237 53 25 145 27 198
|
||||
6z"/>
|
||||
<path d="M4260 3481 c-128 -29 -218 -110 -244 -222 -5 -25 -3 -27 32 -31 50
|
||||
-6 68 0 69 23 6 71 103 138 210 145 163 11 253 -63 255 -209 1 -96 15 -87
|
||||
-135 -87 -195 0 -298 -29 -379 -106 -52 -49 -70 -95 -71 -179 -1 -82 21 -133
|
||||
78 -183 50 -44 98 -63 180 -69 112 -9 215 27 295 103 l35 33 3 -32 c1 -18 4
|
||||
-41 6 -52 3 -11 5 -23 5 -27 1 -5 26 -8 57 -8 49 0 55 2 49 18 -17 45 -22 120
|
||||
-23 370 -1 149 -4 285 -7 302 -14 80 -76 154 -160 191 -54 24 -191 35 -255 20z
|
||||
m322 -564 c0 -107 -8 -128 -66 -181 -72 -67 -210 -103 -292 -76 -181 60 -173
|
||||
274 12 333 68 22 83 24 223 23 l124 -1 -1 -98z"/>
|
||||
<path d="M3680 3030 l0 -450 50 0 50 0 0 450 0 450 -50 0 -50 0 0 -450z"/>
|
||||
<path d="M1512 2091 c-66 -23 -117 -63 -159 -125 -53 -77 -66 -134 -69 -291
|
||||
-3 -165 6 -229 46 -310 36 -75 89 -127 161 -159 78 -34 209 -32 289 5 91 43
|
||||
151 120 166 217 l7 43 -38 -3 c-36 -3 -38 -5 -50 -50 -18 -71 -56 -122 -114
|
||||
-151 -43 -21 -62 -24 -128 -22 -102 3 -149 28 -201 105 -52 78 -62 133 -59
|
||||
314 4 193 16 235 92 311 58 59 100 74 189 71 119 -4 209 -78 223 -185 6 -40 6
|
||||
-41 45 -41 l40 0 -6 41 c-13 95 -68 171 -156 215 -72 36 -196 43 -278 15z"/>
|
||||
<path d="M3383 2092 c-111 -40 -171 -107 -178 -199 -10 -135 69 -206 310 -282
|
||||
157 -50 208 -87 224 -162 16 -76 -18 -140 -95 -179 -42 -21 -62 -25 -139 -24
|
||||
-73 0 -98 5 -134 23 -60 31 -98 76 -112 133 -12 47 -14 48 -50 48 l-38 0 6
|
||||
-37 c16 -100 83 -174 197 -213 61 -21 212 -21 278 -1 49 15 117 68 141 108 26
|
||||
46 36 122 21 172 -29 99 -99 149 -295 211 -190 60 -250 115 -234 217 17 116
|
||||
205 181 338 118 63 -30 117 -109 117 -172 0 -8 15 -13 40 -13 l41 0 -7 38
|
||||
c-15 92 -74 165 -163 205 -66 29 -200 33 -268 9z"/>
|
||||
<path d="M2144 1640 l0 -450 39 0 38 0 -2 380 c-1 209 0 382 3 386 4 3 9 -8
|
||||
13 -25 4 -17 11 -33 16 -36 5 -4 9 -12 9 -19 0 -8 10 -35 22 -62 12 -27 77
|
||||
-177 143 -334 l121 -285 33 0 32 0 125 295 c69 162 143 338 165 390 25 61 39
|
||||
86 39 70 1 -14 1 -95 0 -180 -2 -150 -2 -534 -1 -566 1 -11 10 -14 38 -12 l38
|
||||
3 0 435 c1 239 -1 441 -3 448 -3 7 -23 12 -52 12 l-48 0 -47 -112 c-114 -268
|
||||
-268 -635 -277 -658 -9 -24 -10 -24 -28 20 -11 25 -73 174 -139 331 -66 157
|
||||
-132 316 -146 352 l-27 67 -52 0 -52 0 0 -450z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
19
frontend/src/static/favicons/site.webmanifest
Executable file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "MediaCMS",
|
||||
"short_name": "MediaCMS",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
BIN
frontend/src/static/images/logo_dark.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
48
frontend/src/static/images/logo_dark.svg
Executable file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="339px" height="56px" viewBox="0 0 339 56" enable-background="new 0 0 339 56" xml:space="preserve">
|
||||
<path fill="#093" d="M226.922,38.197c-0.542,4.369-1.967,7.646-4.276,9.83c-2.31,2.184-5.737,3.276-10.284,3.276
|
||||
c-4.594,0-8.24-1.772-10.938-5.317c-2.698-3.545-4.046-8.295-4.046-14.25v-6.31c0-6.168,1.384-11,4.152-14.498
|
||||
c2.768-3.498,6.485-5.247,11.149-5.247c8.411,0,13.158,4.381,14.242,13.141h4.382c-0.518-5.266-2.374-9.386-5.566-12.362
|
||||
c-3.192-2.975-7.545-4.463-13.058-4.463c-3.911,0-7.368,0.962-10.372,2.887c-3.004,1.925-5.307,4.658-6.909,8.2
|
||||
c-1.603,3.542-2.403,7.604-2.403,12.185v6.694c0.023,4.511,0.836,8.519,2.438,12.025s3.87,6.216,6.803,8.129
|
||||
s6.308,2.869,10.125,2.869c5.465,0,9.842-1.44,13.128-4.321c3.287-2.881,5.224-7.037,5.813-12.468H226.922z"/>
|
||||
<path fill="#093" d="M241.871,2.706v51.572h4.347V31.68l-0.353-22.705l19.083,45.303h3.357l19.154-45.48l-0.354,23.023v22.457
|
||||
h4.347V2.706h-5.795l-19.048,45.516L247.631,2.706H241.871z"/>
|
||||
<path fill="#093" d="M329.971,48.646c-2.427,1.771-5.643,2.657-9.647,2.657c-4.5,0-8.105-1.051-10.814-3.153
|
||||
c-2.709-2.101-4.064-4.97-4.064-8.607h-4.347c0,3.023,0.783,5.703,2.35,8.041c1.566,2.338,3.864,4.156,6.891,5.455
|
||||
c3.027,1.298,6.355,1.948,9.983,1.948c5.301,0,9.571-1.228,12.811-3.684c3.239-2.456,4.859-5.714,4.859-9.776
|
||||
c0-2.573-0.601-4.805-1.802-6.694s-2.992-3.512-5.372-4.871c-2.38-1.357-5.878-2.669-10.496-3.934
|
||||
c-4.618-1.265-7.975-2.726-10.072-4.382c-2.097-1.655-3.145-3.796-3.145-6.421c0-2.838,1.166-5.138,3.499-6.9s5.465-2.644,9.4-2.644
|
||||
c4.052,0,7.28,1.072,9.683,3.216c2.403,2.144,3.604,4.979,3.604,8.508h4.382c0-2.904-0.742-5.537-2.227-7.898
|
||||
c-1.484-2.361-3.575-4.203-6.272-5.526c-2.698-1.322-5.755-1.983-9.171-1.983c-5.042,0-9.182,1.234-12.421,3.702
|
||||
c-3.24,2.468-4.859,5.673-4.859,9.617c0,4.062,1.743,7.344,5.23,9.847c2.45,1.771,6.337,3.406,11.662,4.906
|
||||
c5.324,1.5,8.988,3.082,10.991,4.747c2.002,1.665,3.004,3.926,3.004,6.783C333.611,44.525,332.397,46.875,329.971,48.646z"/>
|
||||
<path fill="#111" d="M-0.013,2.829V54.28h4.335V31.734L3.97,9.083L23.004,54.28h3.349L45.457,8.907l-0.353,22.969V54.28h4.335
|
||||
V2.829h-5.781L24.661,48.237L5.732,2.829H-0.013z"/>
|
||||
<path fill="#111" d="M89.728,48.237l-2.644-2.014c-1.41,1.814-2.979,3.133-4.706,3.958c-1.728,0.825-3.719,1.237-5.975,1.237
|
||||
c-3.76,0-6.879-1.443-9.358-4.329c-2.479-2.885-3.718-6.496-3.718-10.831v-0.813h27.211v-2.367c0-5.489-1.357-9.818-4.071-12.987
|
||||
c-2.714-3.168-6.386-4.753-11.015-4.753c-2.961,0-5.71,0.843-8.248,2.527c-2.538,1.685-4.518,3.999-5.939,6.944
|
||||
c-1.422,2.945-2.133,6.254-2.133,9.93v1.52c0,3.557,0.728,6.767,2.186,9.629c1.457,2.862,3.495,5.095,6.115,6.696
|
||||
c2.62,1.602,5.552,2.403,8.794,2.403C82.126,54.986,86.626,52.737,89.728,48.237L89.728,48.237z M83.207,22.405
|
||||
c1.974,2.31,3.019,5.301,3.137,8.976v0.459H63.468c0.47-3.934,1.792-7.068,3.965-9.4c2.173-2.333,4.847-3.499,8.019-3.499
|
||||
C78.648,18.942,81.233,20.097,83.207,22.405z"/>
|
||||
<path fill="#111" d="M97.06,35.304c0,6.031,1.345,10.819,4.036,14.364c2.69,3.546,6.233,5.318,10.627,5.318
|
||||
c5.24,0,9.211-1.931,11.914-5.795l0.176,5.089h3.948V0.002h-4.194v21.449c-2.703-4.075-6.627-6.113-11.773-6.113
|
||||
c-4.535,0-8.125,1.762-10.768,5.283c-2.644,3.522-3.965,8.251-3.965,14.188V35.304z M104.285,23.201
|
||||
c1.974-2.792,4.735-4.188,8.283-4.188c5.146,0,8.812,2.486,10.998,7.456V44.42c-2.186,4.594-5.875,6.891-11.068,6.891
|
||||
c-3.548,0-6.298-1.408-8.248-4.223c-1.951-2.814-2.926-6.661-2.926-11.538C101.324,30.109,102.312,25.992,104.285,23.201z"/>
|
||||
<path fill="#111" d="M143.586,16.045h-4.23V54.28h4.23V16.045z M139.392,7.003c0.517,0.539,1.221,0.808,2.115,0.808
|
||||
c0.893,0,1.604-0.269,2.132-0.808c0.529-0.538,0.793-1.205,0.793-2.001s-0.265-1.469-0.793-2.019s-1.24-0.826-2.132-0.826
|
||||
c-0.894,0-1.598,0.276-2.115,0.826c-0.517,0.55-0.775,1.223-0.775,2.019S138.875,6.465,139.392,7.003z"/>
|
||||
<path fill="#111" d="M183.628,54.28v-0.424c-0.775-1.837-1.163-4.605-1.163-8.304V27.459c-0.071-3.817-1.334-6.791-3.789-8.923
|
||||
c-2.456-2.132-5.811-3.198-10.063-3.198c-4.112,0-7.578,1.113-10.398,3.34c-2.82,2.226-4.23,4.858-4.23,7.897l4.23,0.036
|
||||
c0-2.121,0.963-3.934,2.89-5.442c1.927-1.507,4.359-2.262,7.296-2.262c3.219,0,5.669,0.784,7.349,2.35
|
||||
c1.68,1.567,2.52,3.716,2.52,6.449v4.347h-7.648c-5.522,0-9.829,1.083-12.918,3.251c-3.09,2.167-4.635,5.112-4.635,8.834
|
||||
c0,3.134,1.134,5.725,3.402,7.774c2.267,2.049,5.234,3.074,8.9,3.074c2.561,0,4.987-0.524,7.279-1.572
|
||||
c2.291-1.048,4.177-2.539,5.657-4.47c0.117,2.356,0.388,4.134,0.811,5.336H183.628z M159.677,49.121
|
||||
c-1.586-1.414-2.379-3.192-2.379-5.336c0-2.615,1.186-4.67,3.56-6.167c2.373-1.496,5.663-2.267,9.869-2.314h7.543v8.41
|
||||
c-1.01,2.285-2.625,4.111-4.846,5.477c-2.221,1.367-4.741,2.05-7.561,2.05C163.325,51.241,161.263,50.534,159.677,49.121z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
BIN
frontend/src/static/images/logo_light.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
48
frontend/src/static/images/logo_light.svg
Executable file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="339px" height="56px" viewBox="0 0 339 56" enable-background="new 0 0 339 56" xml:space="preserve">
|
||||
<path fill="#093" d="M226.922,38.197c-0.542,4.369-1.967,7.646-4.276,9.83c-2.31,2.184-5.737,3.276-10.284,3.276
|
||||
c-4.594,0-8.24-1.772-10.938-5.317c-2.698-3.545-4.046-8.295-4.046-14.25v-6.31c0-6.168,1.384-11,4.152-14.498
|
||||
c2.768-3.498,6.485-5.247,11.149-5.247c8.411,0,13.158,4.381,14.242,13.141h4.382c-0.518-5.266-2.374-9.386-5.566-12.362
|
||||
c-3.192-2.975-7.545-4.463-13.058-4.463c-3.911,0-7.368,0.962-10.372,2.887c-3.004,1.925-5.307,4.658-6.909,8.2
|
||||
c-1.603,3.542-2.403,7.604-2.403,12.185v6.694c0.023,4.511,0.836,8.519,2.438,12.025s3.87,6.216,6.803,8.129
|
||||
s6.308,2.869,10.125,2.869c5.465,0,9.842-1.44,13.128-4.321c3.287-2.881,5.224-7.037,5.813-12.468H226.922z"/>
|
||||
<path fill="#093" d="M241.871,2.706v51.572h4.347V31.68l-0.353-22.705l19.083,45.303h3.357l19.154-45.48l-0.354,23.023v22.457
|
||||
h4.347V2.706h-5.795l-19.048,45.516L247.631,2.706H241.871z"/>
|
||||
<path fill="#093" d="M329.971,48.646c-2.427,1.771-5.643,2.657-9.647,2.657c-4.5,0-8.105-1.051-10.814-3.153
|
||||
c-2.709-2.101-4.064-4.97-4.064-8.607h-4.347c0,3.023,0.783,5.703,2.35,8.041c1.566,2.338,3.864,4.156,6.891,5.455
|
||||
c3.027,1.298,6.355,1.948,9.983,1.948c5.301,0,9.571-1.228,12.811-3.684c3.239-2.456,4.859-5.714,4.859-9.776
|
||||
c0-2.573-0.601-4.805-1.802-6.694s-2.992-3.512-5.372-4.871c-2.38-1.357-5.878-2.669-10.496-3.934
|
||||
c-4.618-1.265-7.975-2.726-10.072-4.382c-2.097-1.655-3.145-3.796-3.145-6.421c0-2.838,1.166-5.138,3.499-6.9s5.465-2.644,9.4-2.644
|
||||
c4.052,0,7.28,1.072,9.683,3.216c2.403,2.144,3.604,4.979,3.604,8.508h4.382c0-2.904-0.742-5.537-2.227-7.898
|
||||
c-1.484-2.361-3.575-4.203-6.272-5.526c-2.698-1.322-5.755-1.983-9.171-1.983c-5.042,0-9.182,1.234-12.421,3.702
|
||||
c-3.24,2.468-4.859,5.673-4.859,9.617c0,4.062,1.743,7.344,5.23,9.847c2.45,1.771,6.337,3.406,11.662,4.906
|
||||
c5.324,1.5,8.988,3.082,10.991,4.747c2.002,1.665,3.004,3.926,3.004,6.783C333.611,44.525,332.397,46.875,329.971,48.646z"/>
|
||||
<path fill="#fff" d="M-0.013,2.829V54.28h4.335V31.734L3.97,9.083L23.004,54.28h3.349L45.457,8.907l-0.353,22.969V54.28h4.335
|
||||
V2.829h-5.781L24.661,48.237L5.732,2.829H-0.013z"/>
|
||||
<path fill="#fff" d="M89.728,48.237l-2.644-2.014c-1.41,1.814-2.979,3.133-4.706,3.958c-1.728,0.825-3.719,1.237-5.975,1.237
|
||||
c-3.76,0-6.879-1.443-9.358-4.329c-2.479-2.885-3.718-6.496-3.718-10.831v-0.813h27.211v-2.367c0-5.489-1.357-9.818-4.071-12.987
|
||||
c-2.714-3.168-6.386-4.753-11.015-4.753c-2.961,0-5.71,0.843-8.248,2.527c-2.538,1.685-4.518,3.999-5.939,6.944
|
||||
c-1.422,2.945-2.133,6.254-2.133,9.93v1.52c0,3.557,0.728,6.767,2.186,9.629c1.457,2.862,3.495,5.095,6.115,6.696
|
||||
c2.62,1.602,5.552,2.403,8.794,2.403C82.126,54.986,86.626,52.737,89.728,48.237L89.728,48.237z M83.207,22.405
|
||||
c1.974,2.31,3.019,5.301,3.137,8.976v0.459H63.468c0.47-3.934,1.792-7.068,3.965-9.4c2.173-2.333,4.847-3.499,8.019-3.499
|
||||
C78.648,18.942,81.233,20.097,83.207,22.405z"/>
|
||||
<path fill="#fff" d="M97.06,35.304c0,6.031,1.345,10.819,4.036,14.364c2.69,3.546,6.233,5.318,10.627,5.318
|
||||
c5.24,0,9.211-1.931,11.914-5.795l0.176,5.089h3.948V0.002h-4.194v21.449c-2.703-4.075-6.627-6.113-11.773-6.113
|
||||
c-4.535,0-8.125,1.762-10.768,5.283c-2.644,3.522-3.965,8.251-3.965,14.188V35.304z M104.285,23.201
|
||||
c1.974-2.792,4.735-4.188,8.283-4.188c5.146,0,8.812,2.486,10.998,7.456V44.42c-2.186,4.594-5.875,6.891-11.068,6.891
|
||||
c-3.548,0-6.298-1.408-8.248-4.223c-1.951-2.814-2.926-6.661-2.926-11.538C101.324,30.109,102.312,25.992,104.285,23.201z"/>
|
||||
<path fill="#fff" d="M143.586,16.045h-4.23V54.28h4.23V16.045z M139.392,7.003c0.517,0.539,1.221,0.808,2.115,0.808
|
||||
c0.893,0,1.604-0.269,2.132-0.808c0.529-0.538,0.793-1.205,0.793-2.001s-0.265-1.469-0.793-2.019s-1.24-0.826-2.132-0.826
|
||||
c-0.894,0-1.598,0.276-2.115,0.826c-0.517,0.55-0.775,1.223-0.775,2.019S138.875,6.465,139.392,7.003z"/>
|
||||
<path fill="#fff" d="M183.628,54.28v-0.424c-0.775-1.837-1.163-4.605-1.163-8.304V27.459c-0.071-3.817-1.334-6.791-3.789-8.923
|
||||
c-2.456-2.132-5.811-3.198-10.063-3.198c-4.112,0-7.578,1.113-10.398,3.34c-2.82,2.226-4.23,4.858-4.23,7.897l4.23,0.036
|
||||
c0-2.121,0.963-3.934,2.89-5.442c1.927-1.507,4.359-2.262,7.296-2.262c3.219,0,5.669,0.784,7.349,2.35
|
||||
c1.68,1.567,2.52,3.716,2.52,6.449v4.347h-7.648c-5.522,0-9.829,1.083-12.918,3.251c-3.09,2.167-4.635,5.112-4.635,8.834
|
||||
c0,3.134,1.134,5.725,3.402,7.774c2.267,2.049,5.234,3.074,8.9,3.074c2.561,0,4.987-0.524,7.279-1.572
|
||||
c2.291-1.048,4.177-2.539,5.657-4.47c0.117,2.356,0.388,4.134,0.811,5.336H183.628z M159.677,49.121
|
||||
c-1.586-1.414-2.379-3.192-2.379-5.336c0-2.615,1.186-4.67,3.56-6.167c2.373-1.496,5.663-2.267,9.869-2.314h7.543v8.41
|
||||
c-1.01,2.285-2.625,4.111-4.846,5.477c-2.221,1.367-4.741,2.05-7.561,2.05C163.325,51.241,161.263,50.534,159.677,49.121z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
BIN
frontend/src/static/images/social-media-icons/fb-logo.png
Executable file
|
After Width: | Height: | Size: 378 B |
BIN
frontend/src/static/images/social-media-icons/linkedin-logo.png
Executable file
|
After Width: | Height: | Size: 525 B |
BIN
frontend/src/static/images/social-media-icons/mix-logo.png
Executable file
|
After Width: | Height: | Size: 440 B |
BIN
frontend/src/static/images/social-media-icons/pinterest-logo.png
Executable file
|
After Width: | Height: | Size: 753 B |
BIN
frontend/src/static/images/social-media-icons/reddit-logo.png
Executable file
|
After Width: | Height: | Size: 791 B |
BIN
frontend/src/static/images/social-media-icons/telegram-logo.png
Executable file
|
After Width: | Height: | Size: 777 B |
BIN
frontend/src/static/images/social-media-icons/tumblr-logo.png
Executable file
|
After Width: | Height: | Size: 445 B |
BIN
frontend/src/static/images/social-media-icons/twitter-logo.png
Executable file
|
After Width: | Height: | Size: 741 B |
BIN
frontend/src/static/images/social-media-icons/vk-logo.png
Executable file
|
After Width: | Height: | Size: 815 B |
BIN
frontend/src/static/images/social-media-icons/whatsapp-logo.png
Executable file
|
After Width: | Height: | Size: 986 B |
27
frontend/src/static/js/components/MediaListHeader.tsx
Executable file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
|
||||
interface MediaListHeaderProps {
|
||||
title?: string;
|
||||
viewAllLink?: string;
|
||||
viewAllText?: string;
|
||||
className?: string;
|
||||
style?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export const MediaListHeader: React.FC<MediaListHeaderProps> = (props) => {
|
||||
const viewAllText = props.viewAllText || 'VIEW ALL';
|
||||
return (
|
||||
<div className={(props.className ? props.className + ' ' : '') + 'media-list-header'} style={props.style}>
|
||||
<h2>{props.title}</h2>
|
||||
{props.viewAllLink ? (
|
||||
<h3>
|
||||
{' '}
|
||||
<a href={props.viewAllLink} title={viewAllText}>
|
||||
{' '}
|
||||
{viewAllText || props.viewAllLink}{' '}
|
||||
</a>{' '}
|
||||
</h3>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
21
frontend/src/static/js/components/MediaListRow.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { MediaListHeader } from './MediaListHeader';
|
||||
|
||||
interface MediaListRowProps {
|
||||
title?: string;
|
||||
viewAllLink?: string;
|
||||
viewAllText?: string;
|
||||
className?: string;
|
||||
style?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export const MediaListRow: React.FC<MediaListRowProps> = (props) => {
|
||||
return (
|
||||
<div className={(props.className ? props.className + ' ' : '') + 'media-list-row'} style={props.style}>
|
||||
{props.title ? (
|
||||
<MediaListHeader title={props.title} viewAllLink={props.viewAllLink} viewAllText={props.viewAllText} />
|
||||
) : null}
|
||||
{props.children || null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
372
frontend/src/static/js/components/MediaListWrapper.scss
Executable file
@@ -0,0 +1,372 @@
|
||||
@import '../../css/config/index.scss';
|
||||
|
||||
.media-list-wrapper {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.media-list-row {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
min-height: 136px;
|
||||
|
||||
+ .media-list-row {
|
||||
border-width: 1px 0 0;
|
||||
border-style: solid;
|
||||
border-color: var(--media-list-row-border-color);
|
||||
}
|
||||
|
||||
.spinner-loader {
|
||||
margin: 3.5rem auto 0;
|
||||
}
|
||||
}
|
||||
|
||||
.media-list-header {
|
||||
}
|
||||
|
||||
.media-list-row {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
will-change: width;
|
||||
}
|
||||
|
||||
.media-list-wrapper {
|
||||
max-width: calc(var(--item-width, var(--default-item-width)) * var(--max-row-items, var(--default-max-row-items)));
|
||||
|
||||
&.items-list-hor,
|
||||
&.items-list-ver {
|
||||
padding: 0 16px;
|
||||
|
||||
@media (min-width: 710px) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.media-list-row {
|
||||
.sliding-sidebar & {
|
||||
transition-property: width;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.items-list-hor {
|
||||
.media-list-row {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.items-list-ver {
|
||||
.media-list-row {
|
||||
max-width: var(--max-item-width, var(--default-max-item-width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 710px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver {
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
@media (min-width: 400px) {
|
||||
.media-list-wrapper.items-list-hor .media-list-row {
|
||||
max-width: calc(var(--item-width, var(--default-item-width)) * var(--max-row-items, var(--default-max-row-items)));
|
||||
}
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
$item-width: 260px;
|
||||
$item-width: 218px;
|
||||
$side-empty-space: 40px;
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 2 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 2 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(2 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 3 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(3 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
$item-width: 218px;
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 4 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(4 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 5 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(5 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 6 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(6 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 7 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(7 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 8 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(8 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 9 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(9 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 10 * $item-width ) )) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(10 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 401px) and (max-width: 599px) {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: var(--max-item-width, var(--default-max-item-width));
|
||||
}
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 3 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(2 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 4 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(3 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 5 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(4 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 6 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(5 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 7 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(6 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 8 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(7 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 9 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(8 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 10 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(9 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 11 * $item-width ) )) and (min-width: 768px) {
|
||||
.visible-sidebar .media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(10 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
.media-list-wrapper .media-filters-row {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.media-list-wrapper .media-list-header + .media-filters-row {
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
.media-filters-row + .media-list-header {
|
||||
padding-top: 0;
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.media-filters-row-inner {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
.media-type-filters,
|
||||
.media-filters-sort {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
button {
|
||||
position: relative;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--header-circle-button-color);
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: inherit;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
color: var(--header-circle-button-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-trigger {
|
||||
.filter-button-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.media-type-filters {
|
||||
margin-right: 8px;
|
||||
|
||||
.popup-trigger {
|
||||
.filter-button-label {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.media-filters-sort {
|
||||
position: relative;
|
||||
float: right;
|
||||
clear: right;
|
||||
|
||||
.popup-trigger {
|
||||
.filter-button-label {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.007px;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-filter {
|
||||
.media-filter-option-list {
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
|
||||
.media-filter-option {
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-align: initial;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
color: inherit;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: var(--in-popup-nav-menu-item-hover-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
frontend/src/static/js/components/MediaListWrapper.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { MediaListRow } from './MediaListRow';
|
||||
import './MediaListWrapper.scss';
|
||||
|
||||
interface MediaListWrapperProps {
|
||||
title?: string;
|
||||
viewAllLink?: string;
|
||||
viewAllText?: string;
|
||||
className?: string;
|
||||
style?: { [key: string]: any };
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export const MediaListWrapper: React.FC<MediaListWrapperProps> = ({
|
||||
title,
|
||||
viewAllLink,
|
||||
viewAllText,
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
}) => (
|
||||
<div className={(className ? className + ' ' : '') + 'media-list-wrapper'} style={style}>
|
||||
<MediaListRow title={title} viewAllLink={viewAllLink} viewAllText={viewAllText}>
|
||||
{children || null}
|
||||
</MediaListRow>
|
||||
</div>
|
||||
);
|
||||
14
frontend/src/static/js/components/MediaMultiListWrapper.tsx
Executable file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import './MediaListWrapper.scss';
|
||||
|
||||
interface MediaMultiListWrapperProps {
|
||||
className?: string;
|
||||
style?: { [key: string]: any };
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export const MediaMultiListWrapper: React.FC<MediaMultiListWrapperProps> = ({ className, style, children }) => (
|
||||
<div className={(className ? className + ' ' : '') + 'media-list-wrapper'} style={style}>
|
||||
{children || null}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './CircleIconButton.scss';
|
||||
|
||||
export function CircleIconButton(props) {
|
||||
const children = (
|
||||
<span>
|
||||
<span>{props.children}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
const attr = {
|
||||
tabIndex: props.tabIndex || null,
|
||||
title: props.title || null,
|
||||
className:
|
||||
'circle-icon-button' +
|
||||
(void 0 !== props.className ? ' ' + props.className : '') +
|
||||
(props.buttonShadow ? ' button-shadow' : ''),
|
||||
};
|
||||
|
||||
if (void 0 !== props['data-page-id']) {
|
||||
attr['data-page-id'] = props['data-page-id'];
|
||||
}
|
||||
|
||||
if (void 0 !== props['aria-label']) {
|
||||
attr['aria-label'] = props['aria-label'];
|
||||
}
|
||||
|
||||
if ('link' === props.type) {
|
||||
return (
|
||||
<a {...attr} href={props.href || null} rel={props.rel || null}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if ('span' === props.type) {
|
||||
return (
|
||||
<span {...attr} onClick={props.onClick || null}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button {...attr} onClick={props.onClick || null}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
CircleIconButton.propTypes = {
|
||||
type: PropTypes.oneOf(['button', 'link', 'span']),
|
||||
buttonShadow: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
CircleIconButton.defaultProps = {
|
||||
type: 'button',
|
||||
buttonShadow: false,
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
.circle-icon-button {
|
||||
--txt-color: rgba(17, 17, 17, 0.4);
|
||||
--bg-color: #fff;
|
||||
|
||||
--bg-focus-color: rgba(0, 0, 0, 0.07);
|
||||
--bg-active-color: rgba(0, 0, 0, 0.11);
|
||||
}
|
||||
|
||||
body.dark_theme .circle-icon-button {
|
||||
--txt-color: rgba(255, 255, 255, 0.5);
|
||||
--bg-color: #272727;
|
||||
|
||||
--bg-focus-color: rgba(255, 255, 255, 0.14);
|
||||
--bg-active-color: rgba(255, 255, 255, 0.34);
|
||||
}
|
||||
|
||||
.circle-icon-button {
|
||||
color: var(--txt-color);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
a.circle-icon-button,
|
||||
button.circle-icon-button {
|
||||
&:focus {
|
||||
> * {
|
||||
background-color: var(--bg-focus-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
> * {
|
||||
background-color: var(--bg-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-player .more-media {
|
||||
// In video player "More videos" section, use dark theme properties.
|
||||
|
||||
a.circle-icon-button,
|
||||
button.circle-icon-button {
|
||||
&:focus {
|
||||
> * {
|
||||
background-color: rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
> * {
|
||||
background-color: rgba(0, 0, 0, 0.11);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.circle-icon-button {
|
||||
display: block;
|
||||
padding: 0;
|
||||
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
-webkit-tap-highlight-color: rgba(#000, 0);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
outline-width: 0;
|
||||
border-width: 0;
|
||||
|
||||
border-radius: 50%;
|
||||
|
||||
> * {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
|
||||
> * {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&.button-shadow {
|
||||
box-shadow: 0 4px 4px rgba(#000, 0.3), 0 0 4px rgba(#000, 0.2);
|
||||
}
|
||||
|
||||
i {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { MaterialIcon } from '../material-icon/MaterialIcon.jsx';
|
||||
|
||||
export function FilterOptions(props) {
|
||||
return props.options.map((filter) => {
|
||||
return (
|
||||
<div key={filter.id} className={filter.id === props.selected ? 'active' : ''}>
|
||||
<button onClick={props.onSelect} filter={props.id} value={filter.id}>
|
||||
<span>{filter.title}</span>
|
||||
{filter.id === props.selected ? <MaterialIcon type="close" /> : null}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
FilterOptions.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
selected: PropTypes.string.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MaterialIcon } from '../material-icon/MaterialIcon.jsx';
|
||||
|
||||
export function FiltersToggleButton(props) {
|
||||
const [isActive, setIsActive] = useState(props.active);
|
||||
|
||||
function onClick() {
|
||||
setIsActive(!isActive);
|
||||
if (void 0 !== props.onClick) {
|
||||
props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mi-filters-toggle">
|
||||
<button className={isActive ? 'active' : ''} aria-label="Filter" onClick={onClick}>
|
||||
<MaterialIcon type="filter_list" />
|
||||
<span className="filter-button-label">
|
||||
<span className="filter-button-label-text">FILTERS</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
FiltersToggleButton.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
active: PropTypes.bool,
|
||||
};
|
||||
|
||||
FiltersToggleButton.defaultProps = {
|
||||
active: false,
|
||||
};
|
||||
11
frontend/src/static/js/components/_shared/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export * from './circle-icon-button/CircleIconButton.jsx';
|
||||
export * from './filter-options/FilterOptions.jsx';
|
||||
export * from './filters-toggle-button/FiltersToggleButton.jsx';
|
||||
export * from './material-icon/MaterialIcon.jsx';
|
||||
export * from './navigation-content-app/NavigationContentApp.jsx';
|
||||
export * from './navigation-menu-list/NavigationMenuList.jsx';
|
||||
export * from './notifications/Notifications.jsx';
|
||||
export * from './numeric-input-with-unit/NumericInputWithUnit.jsx';
|
||||
export * from './popup/Popup.jsx';
|
||||
export * from './spinner-loader/SpinnerLoader.jsx';
|
||||
export * from './user-thumbnail/UserThumbnail.jsx';
|
||||
@@ -0,0 +1,3 @@
|
||||
import React from 'react';
|
||||
import './MaterialIcon.scss';
|
||||
export const MaterialIcon = ({ type }) => (type ? <i className="material-icons" data-icon={type}></i> : null);
|
||||
@@ -0,0 +1,11 @@
|
||||
.material-icons {
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.material-icons[data-icon]::after {
|
||||
display: block;
|
||||
content: attr(data-icon);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export function NavigationContentApp(props) {
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(null);
|
||||
|
||||
let changePageElements = [];
|
||||
|
||||
function initEvents() {
|
||||
let domElem = findDOMNode(containerRef.current);
|
||||
let elems = domElem.querySelectorAll(props.pageChangeSelector);
|
||||
|
||||
let i, pageId;
|
||||
|
||||
if (elems.length) {
|
||||
i = 0;
|
||||
while (i < elems.length) {
|
||||
pageId = elems[i].getAttribute(props.pageIdSelectorAttr);
|
||||
pageId = pageId ? pageId.trim() : pageId;
|
||||
|
||||
if (pageId) {
|
||||
changePageElements[i] = {
|
||||
id: pageId,
|
||||
elem: elems[i],
|
||||
};
|
||||
|
||||
changePageElements[i].listener = (
|
||||
(index) => (event) =>
|
||||
changePageListener(index, event)
|
||||
)(i);
|
||||
changePageElements[i].elem.addEventListener('click', changePageElements[i].listener);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.focusFirstItemOnPageChange) {
|
||||
domElem.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function clearEvents() {
|
||||
let i = 0;
|
||||
while (i < changePageElements.length) {
|
||||
changePageElements[i].elem.removeEventListener('click', changePageElements[i].listener);
|
||||
i += 1;
|
||||
}
|
||||
changePageElements = [];
|
||||
}
|
||||
|
||||
function changePageListener(index, event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
changePage(changePageElements[index].id);
|
||||
}
|
||||
|
||||
function changePage(newPage) {
|
||||
if (void 0 !== props.pages[newPage]) {
|
||||
setCurrentPage(newPage);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (void 0 !== props.pages[props.initPage]) {
|
||||
setCurrentPage(props.initPage);
|
||||
} else if (Object.keys(props.pages).length) {
|
||||
setCurrentPage(Object.keys(props.pages)[0]);
|
||||
} else {
|
||||
setCurrentPage(null);
|
||||
}
|
||||
}, [props.initPage]);
|
||||
|
||||
useEffect(() => {
|
||||
clearEvents();
|
||||
|
||||
if (currentPage) {
|
||||
initEvents();
|
||||
|
||||
if ('function' === typeof props.pageChangeCallback) {
|
||||
props.pageChangeCallback(currentPage);
|
||||
}
|
||||
}
|
||||
}, [currentPage]);
|
||||
|
||||
return !currentPage ? null : <div ref={containerRef}>{React.cloneElement(props.pages[currentPage])}</div>;
|
||||
}
|
||||
|
||||
NavigationContentApp.propTypes = {
|
||||
initPage: PropTypes.string,
|
||||
pages: PropTypes.object.isRequired,
|
||||
pageChangeSelector: PropTypes.string.isRequired,
|
||||
pageIdSelectorAttr: PropTypes.string.isRequired,
|
||||
focusFirstItemOnPageChange: PropTypes.bool,
|
||||
pageChangeCallback: PropTypes.func,
|
||||
};
|
||||
|
||||
NavigationContentApp.defaultProps = {
|
||||
focusFirstItemOnPageChange: true,
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MaterialIcon } from '../material-icon/MaterialIcon.jsx';
|
||||
import './NavigationMenuList.scss';
|
||||
|
||||
// TODO: Improve components.
|
||||
|
||||
function NavigationMenuListItem(props) {
|
||||
let children = [];
|
||||
|
||||
const attr = props.itemAttr || {};
|
||||
|
||||
if (void 0 === attr.className) {
|
||||
attr.className = '';
|
||||
} else if (attr.className) {
|
||||
attr.className += ' ';
|
||||
}
|
||||
|
||||
let textPosIndex = props.text ? (!props.icon || 'right' === props.iconPos ? 0 : 1) : -1;
|
||||
let iconPosIndex = props.icon ? (props.text && 'right' === props.iconPos ? 1 : 0) : -1;
|
||||
|
||||
if (-1 < textPosIndex) {
|
||||
children[textPosIndex] = <span key="Text">{props.text}</span>;
|
||||
}
|
||||
|
||||
if (-1 < iconPosIndex) {
|
||||
children[iconPosIndex] = (
|
||||
<span key="Icon" className={'right' === props.iconPos ? 'menu-item-icon-right' : 'menu-item-icon'}>
|
||||
{<MaterialIcon type={props.icon} />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
switch (props.itemType) {
|
||||
case 'link':
|
||||
children = (
|
||||
<a {...(props.linkAttr || {})} href={props.link} title={props.text || null}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
attr.className += 'link-item' + (props.active ? ' active' : '');
|
||||
break;
|
||||
case 'button':
|
||||
case 'open-subpage':
|
||||
children = (
|
||||
<button {...(props.buttonAttr || {})} key="button">
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
break;
|
||||
case 'label':
|
||||
children = (
|
||||
<button {...(props.buttonAttr || {})} key="button">
|
||||
<span>{props.text || null}</span>
|
||||
</button>
|
||||
);
|
||||
attr.className = 'label-item';
|
||||
break;
|
||||
case 'div':
|
||||
children = (
|
||||
<div {...(props.divAttr || {})} key="div">
|
||||
{props.text || null}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if ('' !== attr.className) {
|
||||
attr.className = ' ' + attr.className;
|
||||
}
|
||||
|
||||
attr.className = attr.className.trim();
|
||||
|
||||
return <li {...attr}>{children}</li>;
|
||||
}
|
||||
|
||||
NavigationMenuListItem.propTypes = {
|
||||
itemType: PropTypes.oneOf(['link', 'open-subpage', 'button', 'label', 'div']),
|
||||
link: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
iconPos: PropTypes.oneOf(['left', 'right']),
|
||||
text: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
divAttr: PropTypes.object,
|
||||
buttonAttr: PropTypes.object,
|
||||
itemAttr: PropTypes.object,
|
||||
linkAttr: PropTypes.object,
|
||||
};
|
||||
|
||||
NavigationMenuListItem.defaultProps = {
|
||||
itemType: 'link',
|
||||
iconPos: 'left',
|
||||
active: !1,
|
||||
};
|
||||
|
||||
export function NavigationMenuList(props) {
|
||||
const menuItems = props.items.map((item, index) => <NavigationMenuListItem key={index} {...item} />);
|
||||
return menuItems.length ? (
|
||||
<div className={'nav-menu' + (props.removeVerticalPadding ? ' pv0' : '')}>
|
||||
<nav>
|
||||
<ul>{menuItems}</ul>
|
||||
</nav>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
NavigationMenuList.propTypes = {
|
||||
removeVerticalPadding: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(PropTypes.shape(NavigationMenuListItem.propTypes)).isRequired,
|
||||
};
|
||||
|
||||
NavigationMenuList.defaultProps = {
|
||||
removeVerticalPadding: false,
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
@import '../../../../css/includes/_variables_dimensions.scss';
|
||||
|
||||
.nav-menu {
|
||||
padding: 12px 0;
|
||||
|
||||
&.pv0 {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
> * {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: $sidebar-nav-padding;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.link-item {
|
||||
&.active {
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
&:hover,
|
||||
&:focus {
|
||||
.popup & {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.label-item {
|
||||
button {
|
||||
font-weight: 500;
|
||||
cursor: default;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reported-label {
|
||||
padding: 0 1rem;
|
||||
line-height: 48px;
|
||||
font-size: 13px;
|
||||
color: initial;
|
||||
color: red;
|
||||
|
||||
&:before {
|
||||
content: '\e153';
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 1rem;
|
||||
padding: 0;
|
||||
margin: 0 1.5rem 0 0;
|
||||
font-family: 'Material Icons';
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.menu-item-icon {
|
||||
margin-right: 24px;
|
||||
|
||||
color: #888;
|
||||
|
||||
.material-icons {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-icon-right {
|
||||
float: right;
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
li {
|
||||
&.link-item {
|
||||
&.active {
|
||||
background-color: var(--nav-menu-active-item-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--nav-menu-item-hover-bg-color);
|
||||
|
||||
.popup & {
|
||||
background-color: var(--in-popup-nav-menu-item-hover-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
li {
|
||||
> * {
|
||||
text-align: initial;
|
||||
text-decoration: none;
|
||||
|
||||
> * {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&.link-item {
|
||||
&.active {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-icon {
|
||||
.material-icons {
|
||||
font-size: 1.715em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import PageStore from '../../../utils/stores/PageStore.js';
|
||||
|
||||
import './Notifications.scss';
|
||||
|
||||
let visibleNotifications = [];
|
||||
|
||||
function NotificationItem(props) {
|
||||
const [isHidden, setIsHidden] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
let timeout1 = null;
|
||||
let timeout2 = null;
|
||||
|
||||
useEffect(() => {
|
||||
timeout1 = setTimeout(function () {
|
||||
timeout2 = setTimeout(function () {
|
||||
setIsVisible(false);
|
||||
timeout2 = null;
|
||||
}, 1000);
|
||||
|
||||
timeout1 = null;
|
||||
setIsHidden(true);
|
||||
props.onHide(props.id);
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
if (timeout1) {
|
||||
clearTimeout(timeout1);
|
||||
}
|
||||
|
||||
if (timeout2) {
|
||||
clearTimeout(timeout2);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !isVisible ? null : (
|
||||
<div className={'notification-item' + (isHidden ? ' hidden' : '')}>
|
||||
<div>{props.children || null}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Notifications() {
|
||||
const [notificationsLength, setNotificationsLength] = useState(visibleNotifications.length);
|
||||
|
||||
function onNotificationsUpdate() {
|
||||
setNotificationsLength(PageStore.get('notifications-size') + visibleNotifications.length);
|
||||
}
|
||||
|
||||
function onNotificationHide(id) {
|
||||
const newVisibleNotifications = [];
|
||||
visibleNotifications.map((item) => {
|
||||
if (item[0] !== id) {
|
||||
newVisibleNotifications.push(item);
|
||||
}
|
||||
});
|
||||
visibleNotifications = newVisibleNotifications;
|
||||
}
|
||||
|
||||
function notificationsContent() {
|
||||
const newItems = PageStore.get('notifications');
|
||||
|
||||
const oldNotifications = visibleNotifications.map((n) => {
|
||||
return (
|
||||
<NotificationItem key={n[0]} id={n[0]} onHide={onNotificationHide}>
|
||||
{n[1]}
|
||||
</NotificationItem>
|
||||
);
|
||||
});
|
||||
|
||||
const newNotifications = newItems.map((n) => {
|
||||
visibleNotifications.push(n);
|
||||
return (
|
||||
<NotificationItem key={n[0]} id={n[0]} onHide={onNotificationHide}>
|
||||
{n[1]}
|
||||
</NotificationItem>
|
||||
);
|
||||
});
|
||||
|
||||
return [...oldNotifications, ...newNotifications];
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onNotificationsUpdate();
|
||||
PageStore.on('added_notification', onNotificationsUpdate);
|
||||
return () => PageStore.removeListener('added_notification', onNotificationsUpdate);
|
||||
}, []);
|
||||
|
||||
return !notificationsLength ? null : (
|
||||
<div className="notifications">
|
||||
<div>{notificationsContent()}</div>{' '}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
frontend/src/static/js/components/_shared/notifications/Notifications.scss
Executable file
@@ -0,0 +1,43 @@
|
||||
@import '../../../../css/includes/_variables.scss';
|
||||
|
||||
.notifications {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
height: auto !important;
|
||||
max-width: 100%;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
z-index: +5;
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
display: table;
|
||||
width: 288px;
|
||||
max-width: 100%;
|
||||
min-height: 48px;
|
||||
margin: 12px;
|
||||
color: #f1f1f1;
|
||||
background-color: #323232;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 5px 0 rgba(#000, 0.26);
|
||||
|
||||
transition: opacity 500ms linear;
|
||||
|
||||
> * {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
line-height: 20px;
|
||||
padding: 8px 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function setValue(value, min, max) {
|
||||
if (void 0 !== value) {
|
||||
let ret = null;
|
||||
ret = void 0 !== min && min > value ? min : value;
|
||||
ret = void 0 !== max && max < ret ? max : ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (void 0 !== min) {
|
||||
return min;
|
||||
}
|
||||
|
||||
if (void 0 !== max) {
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
function setUnit(value, units) {
|
||||
if (!units || !units.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
while (i < units.length) {
|
||||
if (void 0 !== units[i].key && value === units[i].key) {
|
||||
return units[i].key;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return units[0].key;
|
||||
}
|
||||
|
||||
export function NumericInputWithUnit(props) {
|
||||
const valueInputRef = useRef(null);
|
||||
const valueUnitRef = useRef(null);
|
||||
|
||||
const [currentValue, setCurrentValue] = useState(null);
|
||||
const [currentUnit, setCurrentUnit] = useState(null);
|
||||
|
||||
function onChangeValue() {
|
||||
setCurrentValue(valueInputRef.current.value);
|
||||
|
||||
if (void 0 !== props.valueCallback) {
|
||||
props.valueCallback(valueInputRef.current.value);
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeUnit() {
|
||||
setCurrentUnit(valueUnitRef.current.value);
|
||||
|
||||
if (void 0 !== props.unitCallback) {
|
||||
props.unitCallback(valueUnitRef.current.value);
|
||||
}
|
||||
}
|
||||
|
||||
function unitOptions() {
|
||||
if (!props.units.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ret = [];
|
||||
|
||||
let i = 0;
|
||||
while (i < props.units.length) {
|
||||
if (void 0 !== props.units[i].key) {
|
||||
ret.push(
|
||||
<option key={props.units[i].key} value={props.units[i].key}>
|
||||
{void 0 !== props.units[i].label ? props.units[i].label : props.units[i].key}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentValue(setValue(0 + props.defaultValue, props.minValue, props.maxValue));
|
||||
setCurrentUnit(setUnit(props.defaultUnit, props.units));
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="num-value-unit">
|
||||
{void 0 !== props.label ? <span className="label">{props.label}</span> : null}
|
||||
<input
|
||||
ref={valueInputRef}
|
||||
className="value-input"
|
||||
type="number"
|
||||
value={null !== currentValue ? currentValue : ''}
|
||||
min={void 0 !== props.minValue ? props.minValue : null}
|
||||
max={void 0 !== props.maxValue ? props.maxValue : null}
|
||||
onChange={onChangeValue}
|
||||
/>
|
||||
<select
|
||||
ref={valueUnitRef}
|
||||
className="value-unit"
|
||||
onChange={onChangeUnit}
|
||||
value={null !== currentUnit ? currentUnit : ''}
|
||||
>
|
||||
{unitOptions()}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NumericInputWithUnit.propTypes = {
|
||||
label: PropTypes.string,
|
||||
units: PropTypes.array.isRequired,
|
||||
defaultUnit: PropTypes.string,
|
||||
defaultValue: PropTypes.number,
|
||||
minValue: PropTypes.number,
|
||||
maxValue: PropTypes.number,
|
||||
valueCallback: PropTypes.func,
|
||||
unitCallback: PropTypes.func,
|
||||
};
|
||||
29
frontend/src/static/js/components/_shared/popup/Popup.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
import './Popup.scss';
|
||||
|
||||
const Popup = React.forwardRef((props, ref) => {
|
||||
return void 0 !== props.children ? (
|
||||
<div ref={ref} className={'popup' + (void 0 !== props.className ? ' ' + props.className : '')} style={props.style}>
|
||||
{props.children}
|
||||
</div>
|
||||
) : null;
|
||||
});
|
||||
|
||||
export default Popup;
|
||||
|
||||
export function PopupTop(props) {
|
||||
return void 0 !== props.children ? (
|
||||
<div className={'popup-top' + (void 0 !== props.className ? ' ' + props.className : '')} style={props.style}>
|
||||
{props.children}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export function PopupMain(props) {
|
||||
return void 0 !== props.children ? (
|
||||
<div className={'popup-main' + (void 0 !== props.className ? ' ' + props.className : '')} style={props.style}>
|
||||
{props.children}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
135
frontend/src/static/js/components/_shared/popup/Popup.scss
Executable file
@@ -0,0 +1,135 @@
|
||||
.popup {
|
||||
background-color: var(--popup-bg-color);
|
||||
|
||||
hr {
|
||||
background-color: var(--popup-hr-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.popup-top {
|
||||
color: var(--popup-top-text-color);
|
||||
background-color: var(--popup-top-bg-color);
|
||||
|
||||
.circle-icon-button.menu-item-icon {
|
||||
color: inherit;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-message-title {
|
||||
color: var(--popup-msg-title-text-color);
|
||||
}
|
||||
|
||||
.popup-message-main {
|
||||
color: var(--popup-msg-main-text-color);
|
||||
}
|
||||
|
||||
.popup {
|
||||
z-index: +4;
|
||||
display: block;
|
||||
width: 300px;
|
||||
text-align: initial;
|
||||
cursor: default;
|
||||
box-shadow: 0 16px 24px 2px rgba(#000, 0.14), 0 6px 30px 5px rgba(#000, 0.12), 0 8px 10px -5px rgba(#000, 0.4);
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-top {
|
||||
padding: 16px * 0.25 8px * 0.5;
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
display: table;
|
||||
|
||||
> * {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
min-width: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.circle-icon-button.menu-item-icon {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-main {
|
||||
overflow: hidden;
|
||||
}
|
||||
.popup-message {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.popup-message-title {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 24px;
|
||||
padding: 0 24px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.popup-message-main {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
margin-bottom: 32px;
|
||||
margin-top: 4px;
|
||||
padding: 0 24px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.popup-message-bottom {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
float: left;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 16px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.popup-fullscreen {
|
||||
z-index: +4;
|
||||
position: fixed;
|
||||
display: table;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 24px 40px;
|
||||
padding-top: calc(var(--header-height) + 24px);
|
||||
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
|
||||
.popup-main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 1840px;
|
||||
max-height: 940px;
|
||||
margin: 0 auto;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
|
||||
text-align: center;
|
||||
|
||||
.popup-fullscreen-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #000;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import React, { useEffect, useRef, useState, useImperativeHandle, useCallback } from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import { hasClassname } from '../../../utils/helpers/dom';
|
||||
import { default as Popup } from './Popup.jsx';
|
||||
|
||||
export function PopupContent(props) {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
const [isVisible, setVisibility] = useState(false);
|
||||
|
||||
const onClickOutside = useCallback((ev) => {
|
||||
if (hasClassname(ev.target, 'popup-fullscreen-overlay')) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const domElem = findDOMNode(wrapperRef.current);
|
||||
|
||||
if (-1 === ev.path.indexOf(domElem)) {
|
||||
hide();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onKeyDown = useCallback((ev) => {
|
||||
let key = ev.keyCode || ev.charCode;
|
||||
if (27 === key) {
|
||||
onClickOutside(ev);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function enableListeners() {
|
||||
document.addEventListener('click', onClickOutside);
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
}
|
||||
|
||||
function disableListeners() {
|
||||
document.removeEventListener('click', onClickOutside);
|
||||
document.removeEventListener('keydown', onKeyDown);
|
||||
}
|
||||
|
||||
function show() {
|
||||
setVisibility(true);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
disableListeners();
|
||||
setVisibility(false);
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isVisible) {
|
||||
hide();
|
||||
} else {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
function tryToHide() {
|
||||
if (isVisible) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
function tryToShow() {
|
||||
if (!isVisible) {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
enableListeners();
|
||||
if ('function' === typeof props.showCallback) {
|
||||
props.showCallback();
|
||||
}
|
||||
} else {
|
||||
if ('function' === typeof props.hideCallback) {
|
||||
props.hideCallback();
|
||||
}
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
useImperativeHandle(props.contentRef, () => ({
|
||||
toggle,
|
||||
tryToHide,
|
||||
tryToShow,
|
||||
}));
|
||||
|
||||
return isVisible ? (
|
||||
<Popup ref={wrapperRef} className={props.className} style={props.style}>
|
||||
{props.children}
|
||||
</Popup>
|
||||
) : null;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
export function PopupTrigger(props) {
|
||||
const onClick = () => props.contentRef.current.toggle();
|
||||
return React.cloneElement(props.children, { onClick });
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './SpinnerLoader.scss';
|
||||
|
||||
export function SpinnerLoader(props) {
|
||||
let classname = 'spinner-loader';
|
||||
|
||||
switch (props.size) {
|
||||
case 'tiny':
|
||||
case 'x-small':
|
||||
case 'small':
|
||||
case 'large':
|
||||
case 'x-large':
|
||||
classname += ' ' + props.size;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classname}>
|
||||
<svg className="circular" viewBox="25 25 50 50">
|
||||
<circle className="path" cx="50" cy="50" r="20" fill="none" strokeWidth="1.5" strokeMiterlimit="10" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SpinnerLoader.propTypes = {
|
||||
size: PropTypes.oneOf(['tiny', 'x-small', 'small', 'medium', 'large', 'x-large']),
|
||||
};
|
||||
|
||||
SpinnerLoader.defaultProps = {
|
||||
size: 'medium',
|
||||
};
|
||||
87
frontend/src/static/js/components/_shared/spinner-loader/SpinnerLoader.scss
Executable file
@@ -0,0 +1,87 @@
|
||||
@import '../../../../css/includes/_variables.scss';
|
||||
|
||||
$green: #008744;
|
||||
$blue: #0057e7;
|
||||
$red: #d62d20;
|
||||
$yellow: #ffa700;
|
||||
|
||||
.spinner-loader {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
&.tiny {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.x-small {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
&.large {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
&.x-large {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
transform-origin: center center;
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
circle {
|
||||
stroke: var(--spinner-loader-color);
|
||||
stroke-dasharray: 1, 200;
|
||||
stroke-dashoffset: 0;
|
||||
stroke-linecap: round;
|
||||
animation: dash 1.5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1, 200;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -35px;
|
||||
}
|
||||
|
||||
100% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -124px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useUser } from '../../../utils/hooks/useUser';
|
||||
import { CircleIconButton } from '../circle-icon-button/CircleIconButton.jsx';
|
||||
import { MaterialIcon } from '../material-icon/MaterialIcon.jsx';
|
||||
|
||||
import './UserThumbnail.scss';
|
||||
|
||||
export function UserThumbnail(props) {
|
||||
const { thumbnail } = useUser();
|
||||
|
||||
const attr = {
|
||||
'aria-label': 'Account profile photo that opens list of options and settings pages links',
|
||||
className: 'thumbnail',
|
||||
};
|
||||
|
||||
if (props.isButton) {
|
||||
if (void 0 !== props.onClick) {
|
||||
attr.onClick = props.onClick;
|
||||
}
|
||||
} else {
|
||||
attr.type = 'span';
|
||||
}
|
||||
|
||||
switch (props.size) {
|
||||
case 'small':
|
||||
case 'large':
|
||||
attr.className += ' ' + props.size + '-thumb';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<CircleIconButton {...attr}>
|
||||
{thumbnail ? <img src={thumbnail} alt="" /> : <MaterialIcon type="person" />}
|
||||
</CircleIconButton>
|
||||
);
|
||||
}
|
||||
|
||||
UserThumbnail.propTypes = {
|
||||
isButton: PropTypes.bool,
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
UserThumbnail.defaultProps = {
|
||||
isButton: false,
|
||||
size: 'medium',
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
.thumbnail,
|
||||
.thumbnail.circle-icon-button {
|
||||
background-color: var(--logged-in-user-thumb-bg-color);
|
||||
}
|
||||
|
||||
a.thumbnail.circle-icon-button,
|
||||
button.thumbnail.circle-icon-button {
|
||||
&:focus,
|
||||
&:active {
|
||||
background-color: var(--logged-in-user-thumb-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail,
|
||||
.thumbnail.circle-icon-button,
|
||||
.thumbnail img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
&.small-thumb,
|
||||
&.small-thumb img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
&.large-thumb,
|
||||
&.large-thumb img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail.circle-icon-button {
|
||||
.material-icons {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
&.small-thumb {
|
||||
.material-icons {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
&.large-thumb {
|
||||
.material-icons {
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 0 16px 0 rgba(#000, 0.1);
|
||||
|
||||
img {
|
||||
vertical-align: inherit;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
455
frontend/src/static/js/components/comments/Comments.jsx
Normal file
@@ -0,0 +1,455 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { format } from 'timeago.js';
|
||||
import { usePopup } from '../../utils/hooks/';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||
import { LinksContext, MemberContext, SiteContext } from '../../utils/contexts/';
|
||||
import { PopupMain, UserThumbnail } from '../_shared';
|
||||
|
||||
import './Comments.scss';
|
||||
|
||||
const commentsText = {
|
||||
single: 'comment',
|
||||
uppercaseSingle: 'COMMENT',
|
||||
ucfirstSingle: 'Comment',
|
||||
ucfirstPlural: 'Comments',
|
||||
submitCommentText: 'SUBMIT',
|
||||
disabledCommentsMsg: 'Comments are disabled',
|
||||
};
|
||||
|
||||
function CommentForm(props) {
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
const [madeChanges, setMadeChanges] = useState(false);
|
||||
const [textareaFocused, setTextareaFocused] = useState(false);
|
||||
const [textareaLineHeight, setTextareaLineHeight] = useState(-1);
|
||||
|
||||
const [loginUrl] = useState(
|
||||
!MemberContext._currentValue.is.anonymous
|
||||
? null
|
||||
: LinksContext._currentValue.signin +
|
||||
'?next=/' +
|
||||
window.location.href.replace(SiteContext._currentValue.url, '').replace(/^\//g, '')
|
||||
);
|
||||
|
||||
function onFocus() {
|
||||
setTextareaFocused(true);
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
setTextareaFocused(false);
|
||||
}
|
||||
|
||||
function onCommentSubmit() {
|
||||
textareaRef.current.style.height = '';
|
||||
|
||||
const contentHeight = textareaRef.current.scrollHeight;
|
||||
const contentLineHeight =
|
||||
0 < textareaLineHeight ? textareaLineHeight : parseFloat(window.getComputedStyle(textareaRef.current).lineHeight);
|
||||
|
||||
setValue('');
|
||||
setMadeChanges(false);
|
||||
setTextareaLineHeight(contentLineHeight);
|
||||
|
||||
textareaRef.current.style.height =
|
||||
Math.max(20, textareaLineHeight * Math.ceil(contentHeight / contentLineHeight)) + 'px';
|
||||
}
|
||||
|
||||
function onCommentSubmitFail() {
|
||||
setMadeChanges(false);
|
||||
}
|
||||
|
||||
function onChange(event) {
|
||||
textareaRef.current.style.height = '';
|
||||
|
||||
const contentHeight = textareaRef.current.scrollHeight;
|
||||
const contentLineHeight =
|
||||
0 < textareaLineHeight ? textareaLineHeight : parseFloat(window.getComputedStyle(textareaRef.current).lineHeight);
|
||||
|
||||
setValue(textareaRef.current.value);
|
||||
setMadeChanges(true);
|
||||
setTextareaLineHeight(contentLineHeight);
|
||||
|
||||
textareaRef.current.style.height =
|
||||
Math.max(20, textareaLineHeight * Math.ceil(contentHeight / contentLineHeight)) + 'px';
|
||||
}
|
||||
|
||||
function submitComment() {
|
||||
if (!madeChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
const val = textareaRef.current.value.trim();
|
||||
|
||||
if ('' !== val) {
|
||||
MediaPageActions.submitComment(val);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
MediaPageStore.on('comment_submit', onCommentSubmit);
|
||||
MediaPageStore.on('comment_submit_fail', onCommentSubmitFail);
|
||||
|
||||
return () => {
|
||||
MediaPageStore.removeListener('comment_submit', onCommentSubmit);
|
||||
MediaPageStore.removeListener('comment_submit_fail', onCommentSubmitFail);
|
||||
};
|
||||
});
|
||||
|
||||
return !MemberContext._currentValue.is.anonymous ? (
|
||||
<div className="comments-form">
|
||||
<div className="comments-form-inner">
|
||||
<UserThumbnail />
|
||||
<div className="form">
|
||||
<div className={'form-textarea-wrap' + (textareaFocused ? ' focused' : '')}>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className="form-textarea"
|
||||
rows="1"
|
||||
placeholder={'Add a ' + commentsText.single + '...'}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="form-buttons">
|
||||
<button className={'' === value.trim() ? 'disabled' : ''} onClick={submitComment}>
|
||||
{commentsText.submitCommentText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="comments-form">
|
||||
<div className="comments-form-inner">
|
||||
<UserThumbnail />
|
||||
<div className="form">
|
||||
<a
|
||||
href={loginUrl}
|
||||
rel="noffolow"
|
||||
className="form-textarea-wrap"
|
||||
title={'Add a ' + commentsText.single + '...'}
|
||||
>
|
||||
<span className="form-textarea">{'Add a ' + commentsText.single + '...'}</span>
|
||||
</a>
|
||||
<div className="form-buttons">
|
||||
<a href={loginUrl} rel="noffolow" className="disabled">
|
||||
{commentsText.submitCommentText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CommentForm.propTypes = {
|
||||
comment_type: PropTypes.oneOf(['new', 'reply']),
|
||||
media_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
reply_comment_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
CommentForm.defaultProps = {
|
||||
comment_type: 'new',
|
||||
};
|
||||
|
||||
const ENABLED_COMMENTS_READ_MORE = false;
|
||||
|
||||
function CommentActions(props) {
|
||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||
|
||||
function cancelCommentRemoval() {
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
|
||||
function proceedCommentRemoval() {
|
||||
popupContentRef.current.toggle();
|
||||
MediaPageActions.deleteComment(props.comment_id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="comment-actions">
|
||||
{/*<div className="comment-action like-action"><CircleIconButton><MaterialIcon type="thumb_up" /></CircleIconButton><span className="likes-num">145</span></div>*/}
|
||||
{/*<div className="comment-action dislike-action"><CircleIconButton><MaterialIcon type="thumb_down" /></CircleIconButton><span className="dislikes-num">19</span></div>*/}
|
||||
{/*<div className="comment-action replay-comment"><button>REPLY</button></div>*/}
|
||||
|
||||
{MemberContext._currentValue.can.deleteComment ? (
|
||||
<div className="comment-action remove-comment">
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<button>DELETE {commentsText.uppercaseSingle}</button>
|
||||
</PopupTrigger>
|
||||
|
||||
<PopupContent contentRef={popupContentRef}>
|
||||
<PopupMain>
|
||||
<div className="popup-message">
|
||||
<span className="popup-message-title">{commentsText.ucfirstSingle} removal</span>
|
||||
<span className="popup-message-main">You're willing to remove {commentsText.single} permanently?</span>
|
||||
</div>
|
||||
<hr />
|
||||
<span className="popup-message-bottom">
|
||||
<button className="button-link cancel-comment-removal" onClick={cancelCommentRemoval}>
|
||||
CANCEL
|
||||
</button>
|
||||
<button className="button-link proceed-comment-removal" onClick={proceedCommentRemoval}>
|
||||
PROCEED
|
||||
</button>
|
||||
</span>
|
||||
</PopupMain>
|
||||
</PopupContent>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Comment(props) {
|
||||
const commentTextRef = useRef(null);
|
||||
const commentTextInnerRef = useRef(null);
|
||||
|
||||
const [viewMoreContent, setViewMoreContent] = useState(!ENABLED_COMMENTS_READ_MORE || false);
|
||||
const [enabledViewMoreContent, setEnabledViewMoreContent] = useState(false);
|
||||
|
||||
function onWindowResize() {
|
||||
const newval = enabledViewMoreContent || commentTextInnerRef.offsetHeight > commentTextRef.offsetHeight;
|
||||
setEnabledViewMoreContent(newval);
|
||||
setViewMoreContent(newval || false);
|
||||
}
|
||||
|
||||
function toggleMore() {
|
||||
setViewMoreContent(!viewMoreContent);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (ENABLED_COMMENTS_READ_MORE) {
|
||||
PageStore.on('window_resize', onWindowResize);
|
||||
setEnabledViewMoreContent(commentTextInnerRef.offsetHeight > commentTextRef.offsetHeight);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (ENABLED_COMMENTS_READ_MORE) {
|
||||
PageStore.removeListener('window_resize', onWindowResize);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="comment">
|
||||
<div className="comment-inner">
|
||||
<a className="comment-author-thumb" href={props.author_link} title={props.author_name}>
|
||||
<img src={props.author_thumb} alt={props.author_name} />
|
||||
</a>
|
||||
<div className="comment-content">
|
||||
<div className="comment-meta">
|
||||
<div className="comment-author">
|
||||
<a href={props.author_link} title={props.author_name}>
|
||||
{props.author_name}
|
||||
</a>
|
||||
</div>
|
||||
<div className="comment-date">{format(new Date(props.publish_date))}</div>
|
||||
</div>
|
||||
<div ref={commentTextRef} className={'comment-text' + (viewMoreContent ? ' show-all' : '')}>
|
||||
<div
|
||||
ref={commentTextInnerRef}
|
||||
className="comment-text-inner"
|
||||
dangerouslySetInnerHTML={{ __html: props.text }}
|
||||
></div>
|
||||
</div>
|
||||
{enabledViewMoreContent ? (
|
||||
<button className="toggle-more" onClick={toggleMore}>
|
||||
{viewMoreContent ? 'Show less' : 'Read more'}
|
||||
</button>
|
||||
) : null}
|
||||
{MemberContext._currentValue.can.deleteComment ? <CommentActions comment_id={props.comment_id} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Comment.propTypes = {
|
||||
comment_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
media_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
text: PropTypes.string,
|
||||
author_name: PropTypes.string,
|
||||
author_link: PropTypes.string,
|
||||
author_thumb: PropTypes.string,
|
||||
publish_date: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
likes: PropTypes.number,
|
||||
dislikes: PropTypes.number,
|
||||
};
|
||||
|
||||
Comment.defaultProps = {
|
||||
author_name: '',
|
||||
author_link: '#',
|
||||
publish_date: 0,
|
||||
likes: 0,
|
||||
dislikes: 0,
|
||||
};
|
||||
|
||||
function displayCommentsRelatedAlert() {
|
||||
// TODO: Improve this and move it into Media Page code.
|
||||
|
||||
var pageMainEl = document.querySelector('.page-main');
|
||||
var noCommentDiv = pageMainEl.querySelector('.no-comment');
|
||||
|
||||
const postUploadMessage = PageStore.get('config-contents').uploader.postUploadMessage;
|
||||
|
||||
if ('' === postUploadMessage) {
|
||||
if (noCommentDiv && 0 === comm.length) {
|
||||
noCommentDiv.parentNode.removeChild(noCommentDiv);
|
||||
}
|
||||
} else if (0 === comm.length && 'unlisted' === MediaPageStore.get('media-data').state) {
|
||||
if (-1 < LinksContext._currentValue.profile.media.indexOf(MediaPageStore.get('media-data').author_profile)) {
|
||||
if (!noCommentDiv) {
|
||||
const missingCommentariesUnlistedMsgElem = document.createElement('div');
|
||||
|
||||
missingCommentariesUnlistedMsgElem.setAttribute('role', 'alert');
|
||||
missingCommentariesUnlistedMsgElem.setAttribute('class', 'alert info alert-dismissible no-comment');
|
||||
missingCommentariesUnlistedMsgElem.innerHTML =
|
||||
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>' +
|
||||
postUploadMessage;
|
||||
|
||||
if (pageMainEl.firstChild) {
|
||||
pageMainEl.insertBefore(missingCommentariesUnlistedMsgElem, pageMainEl.firstChild);
|
||||
} else {
|
||||
pageMainEl.appendChild(missingCommentariesUnlistedMsgElem);
|
||||
}
|
||||
|
||||
missingCommentariesUnlistedMsgElem.querySelector('button.close').addEventListener('click', function (ev) {
|
||||
missingCommentariesUnlistedMsgElem.setAttribute('class', 'alert info alert-dismissible hiding');
|
||||
setTimeout(function () {
|
||||
missingCommentariesUnlistedMsgElem.parentNode.removeChild(missingCommentariesUnlistedMsgElem);
|
||||
}, 400);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (noCommentDiv && 0 < comm.length) {
|
||||
noCommentDiv.parentNode.removeChild(noCommentDiv);
|
||||
}
|
||||
}
|
||||
|
||||
const CommentsListHeader = ({ commentsLength }) => {
|
||||
return (
|
||||
<>
|
||||
{!MemberContext._currentValue.can.readComment || MediaPageStore.get('media-data').enable_comments ? null : (
|
||||
<span className="disabled-comments-msg">{commentsText.disabledCommentsMsg}</span>
|
||||
)}
|
||||
|
||||
{MemberContext._currentValue.can.readComment &&
|
||||
(MediaPageStore.get('media-data').enable_comments || MemberContext._currentValue.can.editMedia) ? (
|
||||
<h2>
|
||||
{commentsLength
|
||||
? 1 < commentsLength
|
||||
? commentsLength + ' ' + commentsText.ucfirstPlural
|
||||
: commentsLength + ' ' + commentsText.ucfirstSingle
|
||||
: MediaPageStore.get('media-data').enable_comments
|
||||
? 'No ' + commentsText.single + ' yet'
|
||||
: ''}
|
||||
</h2>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default function CommentsList(props) {
|
||||
const [mediaId, setMediaId] = useState(MediaPageStore.get('media-id'));
|
||||
|
||||
const [comments, setComments] = useState(
|
||||
MemberContext._currentValue.can.readComment ? MediaPageStore.get('media-comments') : []
|
||||
);
|
||||
|
||||
const [displayComments, setDisplayComments] = useState(false);
|
||||
|
||||
function onCommentsLoad() {
|
||||
displayCommentsRelatedAlert();
|
||||
setComments([...MediaPageStore.get('media-comments')]);
|
||||
}
|
||||
|
||||
function onCommentSubmit(commentId) {
|
||||
onCommentsLoad();
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(() => PageActions.addNotification(commentsText.ucfirstSingle + ' added', 'commentSubmit'), 100);
|
||||
}
|
||||
|
||||
function onCommentSubmitFail() {
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(
|
||||
() => PageActions.addNotification(commentsText.ucfirstSingle + ' submition failed', 'commentSubmitFail'),
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
function onCommentDelete(commentId) {
|
||||
onCommentsLoad();
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(() => PageActions.addNotification(commentsText.ucfirstSingle + ' removed', 'commentDelete'), 100);
|
||||
}
|
||||
|
||||
function onCommentDeleteFail(commentId) {
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(
|
||||
() => PageActions.addNotification(commentsText.ucfirstSingle + ' removal failed', 'commentDeleteFail'),
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayComments(
|
||||
comments.length &&
|
||||
MemberContext._currentValue.can.readComment &&
|
||||
(MediaPageStore.get('media-data').enable_comments || MemberContext._currentValue.can.editMedia)
|
||||
);
|
||||
}, [comments]);
|
||||
|
||||
useEffect(() => {
|
||||
MediaPageStore.on('comments_load', onCommentsLoad);
|
||||
MediaPageStore.on('comment_submit', onCommentSubmit);
|
||||
MediaPageStore.on('comment_submit_fail', onCommentSubmitFail);
|
||||
MediaPageStore.on('comment_delete', onCommentDelete);
|
||||
MediaPageStore.on('comment_delete_fail', onCommentDeleteFail);
|
||||
|
||||
return () => {
|
||||
MediaPageStore.removeListener('comments_load', onCommentsLoad);
|
||||
MediaPageStore.removeListener('comment_submit', onCommentSubmit);
|
||||
MediaPageStore.removeListener('comment_submit_fail', onCommentSubmitFail);
|
||||
MediaPageStore.removeListener('comment_delete', onCommentDelete);
|
||||
MediaPageStore.removeListener('comment_delete_fail', onCommentDeleteFail);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="comments-list">
|
||||
<div className="comments-list-inner">
|
||||
<CommentsListHeader commentsLength={comments.length} />
|
||||
|
||||
{MediaPageStore.get('media-data').enable_comments ? <CommentForm media_id={mediaId} /> : null}
|
||||
|
||||
{displayComments
|
||||
? comments.map((c) => {
|
||||
return (
|
||||
<Comment
|
||||
key={c.uid}
|
||||
comment_id={c.uid}
|
||||
media_id={mediaId}
|
||||
text={c.text}
|
||||
author_name={c.author_name}
|
||||
author_link={c.author_profile}
|
||||
author_thumb={SiteContext._currentValue.url + '/' + c.author_thumbnail_url.replace(/^\//g, '')}
|
||||
publish_date={c.add_date}
|
||||
likes={0}
|
||||
dislikes={0}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
511
frontend/src/static/js/components/comments/Comments.scss
Executable file
@@ -0,0 +1,511 @@
|
||||
@import '../../../css/includes/_variables.scss';
|
||||
|
||||
.comments-form-inner {
|
||||
.form {
|
||||
.form-textarea-wrap {
|
||||
border-color: var(--comments-textarea-wrapper-border-color);
|
||||
|
||||
&:after {
|
||||
background-color: var(--comments-textarea-wrapper-after-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
color: var(--comments-textarea-text-color);
|
||||
|
||||
&:placeholder {
|
||||
color: var(--comments-textarea-text-placeholder-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comments-list-inner {
|
||||
border-color: var(--comments-list-inner-border-color);
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
color: var(--comment-author-text-color);
|
||||
|
||||
a {
|
||||
color: var(--comment-author-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-date {
|
||||
color: var(--comment-date-text-color);
|
||||
text-decoration-color: var(--comment-date-text-color);
|
||||
|
||||
a {
|
||||
color: var(--comment-date-text-color);
|
||||
text-decoration-color: var(--comment-date-text-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--comment-date-hover-text-color);
|
||||
text-decoration-color: var(--comment-date-hover-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
color: var(--comment-text-color);
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
button {
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.circle-icon-button {
|
||||
background-color: var(--body-bg-color);
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--comment-actions-material-icon-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.likes-num,
|
||||
.dislikes-num {
|
||||
color: var(--comment-actions-likes-num-text-color);
|
||||
}
|
||||
|
||||
.reply-comment {
|
||||
> button {
|
||||
color: var(--comment-actions-reply-button-text-color);
|
||||
|
||||
background: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--comment-actions-reply-button-hover-text-color);
|
||||
|
||||
.material-icons {
|
||||
color: var(--comment-actions-reply-button-hover-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remove-comment {
|
||||
.popup-message-bottom {
|
||||
button {
|
||||
&.cancel-comment-removal {
|
||||
color: var(--comment-actions-cancel-removal-button-text-color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--comment-actions-cancel-removal-button-hover-text-color);
|
||||
|
||||
.material-icons {
|
||||
color: var(--comment-actions-cancel-removal-button-hover-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comments-form {
|
||||
position: relative;
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
|
||||
.comments-form-inner {
|
||||
min-height: 40px;
|
||||
|
||||
.thumbnail {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-left: 56px;
|
||||
|
||||
.form-textarea-wrap {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 0 0 0.3em;
|
||||
border-style: solid;
|
||||
border-width: 0 0 1px;
|
||||
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&.focused {
|
||||
&:after {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
position: relative;
|
||||
resize: none;
|
||||
display: block;
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
border-style: solid;
|
||||
border: 0;
|
||||
min-height: 20px;
|
||||
height: auto;
|
||||
text-decoration: none;
|
||||
overflow-y: hidden;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
vertical-align: baseline;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
background-color: transparent;
|
||||
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
|
||||
&:placeholder {
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
a.form-textarea-wrap {
|
||||
&:focus {
|
||||
outline: 1px dotted rgba(#0a0a0a, 0.5);
|
||||
}
|
||||
|
||||
text-decoration: none;
|
||||
|
||||
.form-textarea {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
|
||||
a,
|
||||
button {
|
||||
display: inline-block;
|
||||
padding: 12px 16px 10px;
|
||||
margin-left: 8px;
|
||||
line-height: 1;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
border-radius: 1px;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comments-list {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.comments-list-inner {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
|
||||
padding-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
|
||||
@media screen and (min-width: 640px) {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1008px) {
|
||||
padding-top: 24px;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 0;
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
h2 {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
margin: 0 2rem 1.5rem 0;
|
||||
}
|
||||
|
||||
.disabled-comments-msg {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ratings-container + .comments-list .comments-list-inner {
|
||||
margin-top: -16px;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.comment-replies {
|
||||
}
|
||||
|
||||
.comment-replies-inner {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.comment {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.comment-inner {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.comment-author-thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
border-radius: 9999px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
position: relative;
|
||||
width: auto;
|
||||
margin: 0 0 0 56px;
|
||||
display: inline-block;
|
||||
|
||||
.toggle-more {
|
||||
padding: 0;
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
overflow-wrap: break-word;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-meta {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
display: inline-block;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
margin: 0 4px 2px 0;
|
||||
}
|
||||
|
||||
.comment-date {
|
||||
display: inline-block;
|
||||
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
|
||||
a {
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
overflow: hidden;
|
||||
|
||||
max-height: (5 * 20px);
|
||||
|
||||
@media screen and (min-width: 1008px) {
|
||||
max-height: (4 * 20px);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1216px) {
|
||||
max-height: (3 * 20px);
|
||||
}
|
||||
|
||||
&.show-all {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
p:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comment-text-inner {
|
||||
}
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
color: #909090;
|
||||
|
||||
.circle-icon-button {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
color: #909090;
|
||||
font-size: 16px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.comment-action {
|
||||
display: inline-block;
|
||||
|
||||
~ * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.likes-num,
|
||||
.dislikes-num {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.reply-comment,
|
||||
.remove-comment {
|
||||
> button {
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
line-height: 15px;
|
||||
border: 0;
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-comment {
|
||||
> button {
|
||||
}
|
||||
}
|
||||
|
||||
.remove-comment {
|
||||
position: relative;
|
||||
width: auto;
|
||||
// float:right;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
> button {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.popup-message-bottom {
|
||||
button {
|
||||
position: relative;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
&.cancel-comment-removal,
|
||||
&.proceed-comment-removal {
|
||||
background-color: transparent;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
&.proceed-comment-removal {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.cancel-comment-removal {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-replies {
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { PageStore } from '../../utils/stores/';
|
||||
import { useLayout, useItemListInlineSlider } from '../../utils/hooks/';
|
||||
import { ItemsStaticListHandler } from './includes/itemLists/ItemsStaticListHandler';
|
||||
import { ItemList } from './ItemList';
|
||||
import { PendingItemsList } from './PendingItemsList';
|
||||
import { ListItem, listItemProps } from '../list-item/ListItem';
|
||||
|
||||
export function InlineSliderItemList(props) {
|
||||
const { visibleSidebar } = useLayout();
|
||||
|
||||
const [
|
||||
items,
|
||||
countedItems,
|
||||
listHandler,
|
||||
classname,
|
||||
setListHandler,
|
||||
onItemsCount,
|
||||
onItemsLoad,
|
||||
winResizeListener,
|
||||
sidebarVisibilityChangeListener,
|
||||
itemsListWrapperRef,
|
||||
itemsListRef,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = useItemListInlineSlider(props);
|
||||
|
||||
useEffect(() => {
|
||||
sidebarVisibilityChangeListener();
|
||||
}, [visibleSidebar]);
|
||||
|
||||
useEffect(() => {
|
||||
setListHandler(new ItemsStaticListHandler(props.items, props.pageItems, props.maxItems, onItemsCount, onItemsLoad));
|
||||
|
||||
PageStore.on('window_resize', winResizeListener);
|
||||
|
||||
return () => {
|
||||
PageStore.removeListener('window_resize', winResizeListener);
|
||||
|
||||
if (listHandler) {
|
||||
listHandler.cancelAll();
|
||||
setListHandler(null);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !countedItems ? (
|
||||
<PendingItemsList className={classname.listOuter} />
|
||||
) : !items.length ? null : (
|
||||
<div className={classname.listOuter}>
|
||||
{renderBeforeListWrap()}
|
||||
|
||||
<div ref={itemsListWrapperRef} className="items-list-wrap">
|
||||
<div ref={itemsListRef} className={classname.list}>
|
||||
{items.map((itm, index) => (
|
||||
<ListItem key={index} {...listItemProps(props, itm, index)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderAfterListWrap()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
InlineSliderItemList.propTypes = {
|
||||
...ItemList.propTypes,
|
||||
};
|
||||
|
||||
InlineSliderItemList.defaultProps = {
|
||||
...ItemList.defaultProps,
|
||||
pageItems: 12,
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { PageStore } from '../../utils/stores/';
|
||||
import { useLayout, useItemListInlineSlider } from '../../utils/hooks/';
|
||||
import { ItemListAsync } from './ItemListAsync';
|
||||
import { PendingItemsList } from './PendingItemsList';
|
||||
import { ListItem, listItemProps } from '../list-item/ListItem';
|
||||
import { ItemsListHandler } from './includes/itemLists/ItemsListHandler';
|
||||
|
||||
export function InlineSliderItemListAsync(props) {
|
||||
const { visibleSidebar } = useLayout();
|
||||
|
||||
const [
|
||||
items,
|
||||
countedItems,
|
||||
listHandler,
|
||||
classname,
|
||||
setListHandler,
|
||||
onItemsCount,
|
||||
onItemsLoad,
|
||||
winResizeListener,
|
||||
sidebarVisibilityChangeListener,
|
||||
itemsListWrapperRef,
|
||||
itemsListRef,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = useItemListInlineSlider(props);
|
||||
|
||||
useEffect(() => {
|
||||
sidebarVisibilityChangeListener();
|
||||
}, [visibleSidebar]);
|
||||
|
||||
useEffect(() => {
|
||||
setListHandler(
|
||||
new ItemsListHandler(
|
||||
props.pageItems,
|
||||
props.maxItems,
|
||||
props.firstItemRequestUrl,
|
||||
props.requestUrl,
|
||||
onItemsCount,
|
||||
onItemsLoad
|
||||
)
|
||||
);
|
||||
|
||||
PageStore.on('window_resize', winResizeListener);
|
||||
|
||||
return () => {
|
||||
PageStore.removeListener('window_resize', winResizeListener);
|
||||
|
||||
if (listHandler) {
|
||||
listHandler.cancelAll();
|
||||
setListHandler(null);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !countedItems ? (
|
||||
<PendingItemsList className={classname.listOuter} />
|
||||
) : !items.length ? null : (
|
||||
<div className={classname.listOuter}>
|
||||
{renderBeforeListWrap()}
|
||||
|
||||
<div ref={itemsListWrapperRef} className="items-list-wrap">
|
||||
<div ref={itemsListRef} className={classname.list}>
|
||||
{items.map((itm, index) => (
|
||||
<ListItem key={index} {...listItemProps(props, itm, index)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderAfterListWrap()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
InlineSliderItemListAsync.propTypes = {
|
||||
...ItemListAsync.propTypes,
|
||||
};
|
||||
|
||||
InlineSliderItemListAsync.defaultProps = {
|
||||
...ItemListAsync.defaultProps,
|
||||
pageItems: 12,
|
||||
};
|
||||
105
frontend/src/static/js/components/item-list/ItemList.jsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useItemListSync } from '../../utils/hooks/';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers';
|
||||
import { PendingItemsList } from './PendingItemsList';
|
||||
import { ListItem, listItemProps } from '../list-item/ListItem';
|
||||
import { ItemsStaticListHandler } from './includes/itemLists/ItemsStaticListHandler';
|
||||
|
||||
export function ItemList(props) {
|
||||
const [
|
||||
countedItems,
|
||||
items,
|
||||
listHandler,
|
||||
setListHandler,
|
||||
classname,
|
||||
itemsListWrapperRef,
|
||||
itemsListRef,
|
||||
onItemsCount,
|
||||
onItemsLoad,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = useItemListSync(props);
|
||||
|
||||
useEffect(() => {
|
||||
setListHandler(new ItemsStaticListHandler(props.items, props.pageItems, props.maxItems, onItemsCount, onItemsLoad));
|
||||
|
||||
return () => {
|
||||
if (listHandler) {
|
||||
listHandler.cancelAll();
|
||||
setListHandler(null);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !countedItems ? (
|
||||
<PendingItemsList className={classname.listOuter} />
|
||||
) : !items.length ? null : (
|
||||
<div className={classname.listOuter}>
|
||||
{renderBeforeListWrap()}
|
||||
|
||||
<div ref={itemsListWrapperRef} className="items-list-wrap">
|
||||
<div ref={itemsListRef} className={classname.list}>
|
||||
{items.map((itm, index) => (
|
||||
<ListItem key={index} {...listItemProps(props, itm, index)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderAfterListWrap()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ItemList.propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
className: PropTypes.string,
|
||||
hideDate: PropTypes.bool,
|
||||
hideViews: PropTypes.bool,
|
||||
hideAuthor: PropTypes.bool,
|
||||
hidePlaylistOptions: PropTypes.bool,
|
||||
hidePlaylistOrderNumber: PropTypes.bool,
|
||||
hideAllMeta: PropTypes.bool,
|
||||
preferSummary: PropTypes.bool,
|
||||
inPlaylistView: PropTypes.bool,
|
||||
inPlaylistPage: PropTypes.bool,
|
||||
playlistActiveItem: PositiveIntegerOrZero,
|
||||
playlistId: PropTypes.string,
|
||||
/* ################################################## */
|
||||
maxItems: PropTypes.number.isRequired,
|
||||
pageItems: PropTypes.number.isRequired,
|
||||
horizontalItemsOrientation: PropTypes.bool.isRequired,
|
||||
singleLinkContent: PropTypes.bool.isRequired,
|
||||
inTagsList: PropTypes.bool,
|
||||
inCategoriesList: PropTypes.bool,
|
||||
itemsCountCallback: PropTypes.func,
|
||||
itemsLoadCallback: PropTypes.func,
|
||||
firstItemViewer: PropTypes.bool,
|
||||
firstItemDescr: PropTypes.bool,
|
||||
canEdit: PropTypes.bool,
|
||||
};
|
||||
|
||||
ItemList.defaultProps = {
|
||||
hideDate: false,
|
||||
hideViews: false,
|
||||
hideAuthor: false,
|
||||
hidePlaylistOptions: true,
|
||||
hidePlaylistOrderNumber: true,
|
||||
hideAllMeta: false,
|
||||
preferSummary: false,
|
||||
inPlaylistView: false,
|
||||
inPlaylistPage: false,
|
||||
playlistActiveItem: 1,
|
||||
playlistId: void 0,
|
||||
/* ################################################## */
|
||||
maxItems: 99999,
|
||||
// pageItems: 48,
|
||||
pageItems: 24,
|
||||
horizontalItemsOrientation: false,
|
||||
singleLinkContent: false,
|
||||
inTagsList: false,
|
||||
inCategoriesList: false,
|
||||
firstItemViewer: false,
|
||||
firstItemDescr: false,
|
||||
canEdit: false,
|
||||
};
|
||||
149
frontend/src/static/js/components/item-list/ItemList.scss
Executable file
@@ -0,0 +1,149 @@
|
||||
@import '../../../css/includes/_variables.scss';
|
||||
@import '../../../css/includes/_variables_dimensions.scss';
|
||||
|
||||
@import '../../../css/config/index.scss';
|
||||
|
||||
.items-list-outer {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
&.list-inline.list-slider {
|
||||
margin: 0 8px;
|
||||
|
||||
.previous-slide,
|
||||
.next-slide {
|
||||
position: absolute;
|
||||
z-index: +1;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding-top: 28.125%;
|
||||
|
||||
.circle-icon-button {
|
||||
margin-top: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.previous-slide {
|
||||
left: -12px;
|
||||
}
|
||||
|
||||
.next-slide {
|
||||
right: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 420px) {
|
||||
&.list-inline.list-slider {
|
||||
margin: 0;
|
||||
|
||||
.previous-slide {
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
.next-slide {
|
||||
right: -20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
&.list-inline.list-slider {
|
||||
.previous-slide,
|
||||
.next-slide {
|
||||
padding-top: calc(0.28125 * calc(var(--item-width, var(--default-item-width))));
|
||||
}
|
||||
|
||||
.next-slide {
|
||||
right: calc(-20px + var(--item-margin-right-width, var(--default-item-margin-right-width)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-list-header,
|
||||
.media-list-header {
|
||||
display: block;
|
||||
padding: 12px 0;
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
display: inline-block;
|
||||
margin: 12px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
margin: 10px 16px;
|
||||
text-decoration: none;
|
||||
color: var(--media-list-header-title-link-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-list-wrap {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-height: 218px;
|
||||
|
||||
.list-inline & {
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
will-change: width, scroll-position, scroll-behavior;
|
||||
|
||||
.item {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.list-slider & {
|
||||
overflow: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
.list-slider .items-list-wrap.resizing {
|
||||
scroll-behavior: unset;
|
||||
}
|
||||
|
||||
.items-list {
|
||||
max-width: 100%;
|
||||
word-break: break-word;
|
||||
|
||||
img,
|
||||
picture {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button.load-more {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.007px;
|
||||
margin: 0 auto 24px 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
|
||||
color: var(--item-list-load-more-text-color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--item-list-load-more-hover-text-color);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@import '../list-item/Item.scss';
|
||||
@import '../list-item/ItemVertical.scss';
|
||||
@import '../list-item/ItemHorizontal.scss';
|
||||
@@ -0,0 +1,75 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useItemListSync } from '../../utils/hooks/';
|
||||
import { ItemList } from './ItemList';
|
||||
import { PendingItemsList } from './PendingItemsList';
|
||||
import { ListItem, listItemProps } from '../list-item/ListItem';
|
||||
import { ItemsListHandler } from './includes/itemLists/ItemsListHandler';
|
||||
|
||||
export function ItemListAsync(props) {
|
||||
const [
|
||||
countedItems,
|
||||
items,
|
||||
listHandler,
|
||||
setListHandler,
|
||||
classname,
|
||||
itemsListWrapperRef,
|
||||
itemsListRef,
|
||||
onItemsCount,
|
||||
onItemsLoad,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = useItemListSync(props);
|
||||
|
||||
useEffect(() => {
|
||||
setListHandler(
|
||||
new ItemsListHandler(
|
||||
props.pageItems,
|
||||
props.maxItems,
|
||||
props.firstItemRequestUrl,
|
||||
props.requestUrl,
|
||||
onItemsCount,
|
||||
onItemsLoad
|
||||
)
|
||||
);
|
||||
|
||||
return () => {
|
||||
if (listHandler) {
|
||||
listHandler.cancelAll();
|
||||
setListHandler(null);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !countedItems ? (
|
||||
<PendingItemsList className={classname.listOuter} />
|
||||
) : !items.length ? null : (
|
||||
<div className={classname.listOuter}>
|
||||
{renderBeforeListWrap()}
|
||||
|
||||
<div ref={itemsListWrapperRef} className="items-list-wrap">
|
||||
<div ref={itemsListRef} className={classname.list}>
|
||||
{items.map((itm, index) => (
|
||||
<ListItem key={index} {...listItemProps(props, itm, index)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderAfterListWrap()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ItemListAsync.propTypes = {
|
||||
...ItemList.propTypes,
|
||||
items: PropTypes.array, // Reset 'isRequired' feature.
|
||||
requestUrl: PropTypes.string.isRequired,
|
||||
firstItemRequestUrl: PropTypes.string,
|
||||
};
|
||||
|
||||
ItemListAsync.defaultProps = {
|
||||
...ItemList.defaultProps,
|
||||
requestUrl: null,
|
||||
firstItemRequestUrl: null,
|
||||
pageItems: 24,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { PageStore } from '../../utils/stores/';
|
||||
import { useItemListLazyLoad } from '../../utils/hooks/';
|
||||
import { ItemList } from './ItemList';
|
||||
import { PendingItemsList } from './PendingItemsList';
|
||||
import { ListItem, listItemProps } from '../list-item/ListItem';
|
||||
import { ItemsStaticListHandler } from './includes/itemLists/ItemsStaticListHandler';
|
||||
|
||||
export function LazyLoadItemList(props) {
|
||||
const [
|
||||
items,
|
||||
countedItems,
|
||||
listHandler,
|
||||
setListHandler,
|
||||
classname,
|
||||
onItemsCount,
|
||||
onItemsLoad,
|
||||
onWindowScroll,
|
||||
onDocumentVisibilityChange,
|
||||
itemsListWrapperRef,
|
||||
itemsListRef,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = useItemListLazyLoad(props);
|
||||
|
||||
useEffect(() => {
|
||||
setListHandler(new ItemsStaticListHandler(props.items, props.pageItems, props.maxItems, onItemsCount, onItemsLoad));
|
||||
|
||||
PageStore.on('window_scroll', onWindowScroll);
|
||||
PageStore.on('document_visibility_change', onDocumentVisibilityChange);
|
||||
|
||||
onWindowScroll();
|
||||
|
||||
return () => {
|
||||
PageStore.removeListener('window_scroll', onWindowScroll);
|
||||
PageStore.removeListener('document_visibility_change', onDocumentVisibilityChange);
|
||||
|
||||
if (listHandler) {
|
||||
listHandler.cancelAll();
|
||||
setListHandler(null);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !countedItems ? (
|
||||
<PendingItemsList className={classname.listOuter} />
|
||||
) : !items.length ? null : (
|
||||
<div className={classname.listOuter}>
|
||||
{renderBeforeListWrap()}
|
||||
|
||||
<div ref={itemsListWrapperRef} className="items-list-wrap">
|
||||
<div ref={itemsListRef} className={classname.list}>
|
||||
{items.map((itm, index) => (
|
||||
<ListItem key={index} {...listItemProps(props, itm, index)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderAfterListWrap()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LazyLoadItemList.propTypes = {
|
||||
...ItemList.propTypes,
|
||||
};
|
||||
|
||||
LazyLoadItemList.defaultProps = {
|
||||
...ItemList.defaultProps,
|
||||
pageItems: 2,
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { PageStore } from '../../utils/stores/';
|
||||
import { useItemListLazyLoad } from '../../utils/hooks/';
|
||||
import { ItemListAsync } from './ItemListAsync';
|
||||
import { PendingItemsList } from './PendingItemsList';
|
||||
import { ListItem, listItemProps } from '../list-item/ListItem';
|
||||
import { ItemsListHandler } from './includes/itemLists/ItemsListHandler';
|
||||
|
||||
export function LazyLoadItemListAsync(props) {
|
||||
const [
|
||||
items,
|
||||
countedItems,
|
||||
listHandler,
|
||||
setListHandler,
|
||||
classname,
|
||||
onItemsCount,
|
||||
onItemsLoad,
|
||||
onWindowScroll,
|
||||
onDocumentVisibilityChange,
|
||||
itemsListWrapperRef,
|
||||
itemsListRef,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = useItemListLazyLoad(props);
|
||||
|
||||
useEffect(() => {
|
||||
setListHandler(
|
||||
new ItemsListHandler(
|
||||
props.pageItems,
|
||||
props.maxItems,
|
||||
props.firstItemRequestUrl,
|
||||
props.requestUrl,
|
||||
onItemsCount,
|
||||
onItemsLoad
|
||||
)
|
||||
);
|
||||
|
||||
PageStore.on('window_scroll', onWindowScroll);
|
||||
PageStore.on('document_visibility_change', onDocumentVisibilityChange);
|
||||
|
||||
onWindowScroll();
|
||||
|
||||
return () => {
|
||||
PageStore.removeListener('window_scroll', onWindowScroll);
|
||||
PageStore.removeListener('document_visibility_change', onDocumentVisibilityChange);
|
||||
|
||||
if (listHandler) {
|
||||
listHandler.cancelAll();
|
||||
setListHandler(null);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !countedItems ? (
|
||||
<PendingItemsList className={classname.listOuter} />
|
||||
) : !items.length ? null : (
|
||||
<div className={classname.listOuter}>
|
||||
{renderBeforeListWrap()}
|
||||
|
||||
<div ref={itemsListWrapperRef} className="items-list-wrap">
|
||||
<div ref={itemsListRef} className={classname.list}>
|
||||
{items.map((itm, index) => (
|
||||
<ListItem key={index} {...listItemProps(props, itm, index)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderAfterListWrap()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LazyLoadItemListAsync.propTypes = {
|
||||
...ItemListAsync.propTypes,
|
||||
};
|
||||
|
||||
LazyLoadItemListAsync.defaultProps = {
|
||||
...ItemListAsync.defaultProps,
|
||||
pageItems: 2,
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { SpinnerLoader } from '../_shared/';
|
||||
|
||||
export function PendingItemsList(props) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<div className="items-list-wrap items-list-wrap-waiting">
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
function calcCurrentSlide(wrapperDom, itemWidth, currentSlide) {
|
||||
return wrapperDom.scrollLeft ? 1 + Math.ceil(wrapperDom.scrollLeft / itemWidth) : currentSlide;
|
||||
}
|
||||
|
||||
export default function ItemsInlineSlider(container, itemSelector) {
|
||||
if (void 0 === container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = {
|
||||
dom: {
|
||||
wrapper: container,
|
||||
firstItem: container.querySelector(itemSelector),
|
||||
},
|
||||
item: {
|
||||
// selector: itemSelector,
|
||||
width: null,
|
||||
},
|
||||
};
|
||||
|
||||
this.data.item.width = this.data.dom.firstItem.offsetWidth;
|
||||
|
||||
this.state = {
|
||||
initedAllStateValues: false,
|
||||
currentSlide: 1,
|
||||
maxSlideIndex: null,
|
||||
slideItemsFit: null,
|
||||
slideItems: null,
|
||||
totalItems: null,
|
||||
wrapper: {
|
||||
width: null,
|
||||
scrollWidth: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ItemsInlineSlider.prototype.updateDataStateOnResize = function (totalItems, itemsLoadedAll) {
|
||||
this.data.item.width = this.data.dom.firstItem.offsetWidth;
|
||||
|
||||
this.state.wrapper.width = this.data.dom.wrapper.offsetWidth;
|
||||
this.state.wrapper.scrollWidth = this.data.dom.wrapper.scrollWidth;
|
||||
this.state.slideItemsFit = Math.floor(this.state.wrapper.width / this.data.item.width);
|
||||
|
||||
this.state.slideItems = Math.max(1, this.state.slideItemsFit);
|
||||
|
||||
if (itemsLoadedAll && this.state.slideItems <= this.state.slideItemsFit) {
|
||||
this.state.itemsLengthFit = this.state.slideItems;
|
||||
}
|
||||
|
||||
this.state.totalItems = totalItems;
|
||||
|
||||
this.state.maxSlideIndex = Math.max(1 + (this.state.totalItems - this.state.slideItemsFit));
|
||||
|
||||
this.state.currentSlide = Math.min(this.state.currentSlide, this.state.maxSlideIndex || 1);
|
||||
this.state.currentSlide = 0 >= this.state.currentSlide ? 1 : this.state.currentSlide;
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.updateDataState = function (totalItems, itemsLoadedAll, forcedRefresh) {
|
||||
if (forcedRefresh || !this.state.initedAllStateValues) {
|
||||
this.state.initedAllStateValues = true;
|
||||
|
||||
this.state.wrapper.width = this.data.dom.wrapper.offsetWidth;
|
||||
this.state.wrapper.scrollWidth = this.data.dom.wrapper.scrollWidth;
|
||||
this.state.slideItemsFit = Math.floor(this.state.wrapper.width / this.data.item.width);
|
||||
|
||||
this.state.slideItems = Math.max(1, this.state.slideItemsFit);
|
||||
|
||||
if (itemsLoadedAll && this.state.slideItems <= this.state.slideItemsFit) {
|
||||
this.state.itemsLengthFit = this.state.slideItems;
|
||||
}
|
||||
}
|
||||
|
||||
this.state.totalItems = totalItems;
|
||||
|
||||
this.state.maxSlideIndex = Math.max(1, 1 + (this.state.totalItems - this.state.slideItemsFit));
|
||||
|
||||
this.state.currentSlide = Math.min(this.state.currentSlide, this.state.maxSlideIndex);
|
||||
this.state.currentSlide = 0 >= this.state.currentSlide ? 1 : this.state.currentSlide;
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.nextSlide = function () {
|
||||
this.state.currentSlide = Math.min(
|
||||
calcCurrentSlide(this.data.dom.wrapper, this.data.item.width, this.state.currentSlide) + this.state.slideItems,
|
||||
this.state.maxSlideIndex
|
||||
);
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.previousSlide = function () {
|
||||
this.state.currentSlide = Math.max(
|
||||
1,
|
||||
calcCurrentSlide(this.data.dom.wrapper, this.data.item.width, this.state.currentSlide) - this.state.slideItems
|
||||
);
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.scrollToCurrentSlide = function () {
|
||||
this.data.dom.wrapper.scrollLeft = this.data.item.width * (this.state.currentSlide - 1);
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.hasNextSlide = function () {
|
||||
return this.state.currentSlide < this.state.maxSlideIndex;
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.hasPreviousSlide = function () {
|
||||
return 1 < this.state.currentSlide;
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.currentSlide = function () {
|
||||
return this.state.currentSlide;
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.loadItemsToFit = function () {
|
||||
// Set slider minimum items length ( 2 * this.state.slideItemsFit ).
|
||||
return 2 * this.state.slideItemsFit > this.state.totalItems;
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.loadMoreItems = function () {
|
||||
return this.state.currentSlide + this.state.slideItemsFit >= this.state.maxSlideIndex;
|
||||
};
|
||||
|
||||
ItemsInlineSlider.prototype.itemsFit = function () {
|
||||
return this.state.slideItemsFit;
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
import { PageStore } from '../../../../utils/stores/';
|
||||
import { formatInnerLink, getRequest } from '../../../../utils/helpers/';
|
||||
|
||||
export function ItemsListHandler(
|
||||
itemsPerPage,
|
||||
maxItems,
|
||||
first_item_request_url,
|
||||
request_url,
|
||||
itemsCountCallback,
|
||||
loadItemsCallback
|
||||
) {
|
||||
const config = {
|
||||
maxItems: maxItems || 255,
|
||||
pageItems: itemsPerPage ? Math.min(maxItems, itemsPerPage) : 1,
|
||||
};
|
||||
|
||||
const state = {
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
nextRequestUrl: formatInnerLink(request_url, PageStore.get('config-site').url),
|
||||
};
|
||||
|
||||
const waiting = {
|
||||
pageItems: 0,
|
||||
requestResponse: false,
|
||||
};
|
||||
|
||||
let firstItemUrl = null;
|
||||
|
||||
const items = [];
|
||||
const responseItems = [];
|
||||
|
||||
const callbacks = {
|
||||
itemsCount: function () {
|
||||
if ('function' === typeof itemsCountCallback) {
|
||||
itemsCountCallback(state.totalItems);
|
||||
}
|
||||
},
|
||||
itemsLoad: function () {
|
||||
if ('function' === typeof loadItemsCallback) {
|
||||
loadItemsCallback(items);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function loadNextItems(itemsLength) {
|
||||
let itemsToLoad, needExtraRequest;
|
||||
|
||||
itemsLength = !isNaN(itemsLength) ? itemsLength : config.pageItems;
|
||||
|
||||
if (waiting.pageItems && waiting.pageItems <= responseItems.length) {
|
||||
itemsToLoad = waiting.pageItems;
|
||||
needExtraRequest = false;
|
||||
waiting.pageItems = 0;
|
||||
} else {
|
||||
itemsToLoad = Math.min(itemsLength, responseItems.length);
|
||||
needExtraRequest = itemsLength > responseItems.length && !!state.nextRequestUrl;
|
||||
waiting.pageItems = needExtraRequest ? itemsLength - responseItems.length : 0;
|
||||
}
|
||||
|
||||
if (itemsToLoad) {
|
||||
let i = 0;
|
||||
while (i < itemsToLoad) {
|
||||
items.push(responseItems.shift());
|
||||
i += 1;
|
||||
}
|
||||
callbacks.itemsLoad();
|
||||
}
|
||||
|
||||
if (needExtraRequest) {
|
||||
runRequest();
|
||||
}
|
||||
}
|
||||
|
||||
function runFirstItemRequest() {
|
||||
function fn(response) {
|
||||
if (!!!response || !!!response.data) {
|
||||
} else {
|
||||
let data = response.data;
|
||||
let results = void 0 !== data.results ? data.results : data; // NOTE: The structure of response data in case of categories differs from the others.
|
||||
|
||||
if (results.length) {
|
||||
firstItemUrl = results[0].url;
|
||||
items.push(results[0]);
|
||||
}
|
||||
}
|
||||
|
||||
runRequest(true);
|
||||
}
|
||||
|
||||
getRequest(formatInnerLink(first_item_request_url, PageStore.get('config-site').url), false, fn);
|
||||
}
|
||||
|
||||
function runRequest(initialRequest) {
|
||||
waiting.requestResponse = true;
|
||||
|
||||
function fn(response) {
|
||||
waiting.requestResponse = false;
|
||||
|
||||
if (!!!response || !!!response.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = response.data;
|
||||
let results = void 0 !== data.results ? data.results : data; // NOTE: The structure of response data in case of categories differs from the others.
|
||||
|
||||
let i = 0;
|
||||
while (i < results.length && config.maxItems > responseItems.length) {
|
||||
if (null === firstItemUrl || firstItemUrl !== results[i].url) {
|
||||
responseItems.push(results[i]);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
state.nextRequestUrl = !!data.next && config.maxItems > responseItems.length ? data.next : null;
|
||||
|
||||
if (initialRequest) {
|
||||
// In some cases, (total) 'count' field is missing, but probably doesn't need (eg. in recommended media).
|
||||
state.totalItems = !!data.count ? data.count : responseItems.length;
|
||||
state.totalItems = Math.min(config.maxItems, state.totalItems);
|
||||
|
||||
state.totalPages = Math.ceil(state.totalItems / config.pageItems);
|
||||
|
||||
callbacks.itemsCount();
|
||||
}
|
||||
|
||||
loadNextItems();
|
||||
}
|
||||
|
||||
getRequest(state.nextRequestUrl, false, fn);
|
||||
|
||||
state.nextRequestUrl = null;
|
||||
}
|
||||
|
||||
function loadItems(itemsLength) {
|
||||
if (!waiting.requestResponse && items.length < state.totalItems) {
|
||||
loadNextItems(itemsLength);
|
||||
}
|
||||
}
|
||||
|
||||
function totalPages() {
|
||||
return state.totalPages;
|
||||
}
|
||||
|
||||
function loadedAllItems() {
|
||||
return items.length === state.totalItems;
|
||||
}
|
||||
|
||||
function cancelAll() {
|
||||
itemsCountCallback = null;
|
||||
loadItemsCallback = null;
|
||||
}
|
||||
|
||||
if (void 0 !== first_item_request_url && null !== first_item_request_url) {
|
||||
runFirstItemRequest();
|
||||
} else {
|
||||
runRequest(true);
|
||||
}
|
||||
|
||||
return {
|
||||
loadItems,
|
||||
totalPages,
|
||||
loadedAllItems,
|
||||
cancelAll,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
export function ItemsStaticListHandler(itemsArray, itemsPerPage, maxItems, itemsCountCallback, loadItemsCallback) {
|
||||
const config = {
|
||||
maxItems: maxItems || 255,
|
||||
pageItems: itemsPerPage ? Math.min(maxItems, itemsPerPage) : 1,
|
||||
};
|
||||
|
||||
const state = {
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
};
|
||||
|
||||
let results = itemsArray;
|
||||
|
||||
const items = [];
|
||||
const responseItems = [];
|
||||
|
||||
const callbacks = {
|
||||
itemsCount: function () {
|
||||
if ('function' === typeof itemsCountCallback) {
|
||||
itemsCountCallback(state.totalItems);
|
||||
}
|
||||
},
|
||||
itemsLoad: function () {
|
||||
if ('function' === typeof loadItemsCallback) {
|
||||
loadItemsCallback(items);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function loadNextItems(itemsLength) {
|
||||
itemsLength = !isNaN(itemsLength) ? itemsLength : config.pageItems;
|
||||
|
||||
let itemsToLoad = Math.min(itemsLength, responseItems.length);
|
||||
|
||||
if (itemsToLoad) {
|
||||
let i = 0;
|
||||
while (i < itemsToLoad) {
|
||||
items.push(responseItems.shift());
|
||||
i += 1;
|
||||
}
|
||||
|
||||
callbacks.itemsLoad();
|
||||
}
|
||||
}
|
||||
|
||||
function loadItems(itemsLength) {
|
||||
if (items.length < state.totalItems) {
|
||||
loadNextItems(itemsLength);
|
||||
}
|
||||
}
|
||||
|
||||
function totalPages() {
|
||||
return state.totalPages;
|
||||
}
|
||||
|
||||
function loadedAllItems() {
|
||||
return items.length === state.totalItems;
|
||||
}
|
||||
|
||||
function cancelAll() {
|
||||
itemsCountCallback = null;
|
||||
loadItemsCallback = null;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
while (i < results.length && config.maxItems > responseItems.length) {
|
||||
responseItems.push(results[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
state.totalItems = Math.min(config.maxItems, results.length);
|
||||
state.totalPages = Math.ceil(state.totalItems / config.pageItems);
|
||||
|
||||
callbacks.itemsCount();
|
||||
|
||||
loadNextItems();
|
||||
|
||||
return {
|
||||
loadItems,
|
||||
totalPages,
|
||||
loadedAllItems,
|
||||
cancelAll,
|
||||
};
|
||||
}
|
||||
53
frontend/src/static/js/components/item-list/includes/itemLists/MediaItem.js
Executable file
@@ -0,0 +1,53 @@
|
||||
import MediaItemPreviewer from './MediaItemPreviewer';
|
||||
|
||||
let mediaPreviewerInstance = null;
|
||||
|
||||
var CSS_selectors = {
|
||||
mediaItemPreviewer: '.item-img-preview',
|
||||
};
|
||||
|
||||
var DataAttributes = {
|
||||
mediaPreviewSrc: 'data-src',
|
||||
mediaPreviewExt: 'data-ext',
|
||||
};
|
||||
|
||||
export default class MediaItem {
|
||||
constructor(item) {
|
||||
if (!Node.prototype.isPrototypeOf(item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.element = item;
|
||||
|
||||
this.previewer = {
|
||||
element: item.querySelector(CSS_selectors.mediaItemPreviewer),
|
||||
};
|
||||
|
||||
let tmp;
|
||||
|
||||
if (this.previewer.element) {
|
||||
tmp = this.previewer.element.getAttribute(DataAttributes.mediaPreviewSrc);
|
||||
|
||||
if (tmp) {
|
||||
this.previewer.src = tmp.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.previewer.src) {
|
||||
tmp = this.previewer.element.getAttribute(DataAttributes.mediaPreviewExt);
|
||||
|
||||
if (tmp) {
|
||||
this.previewer.extensions = tmp.trim().split(',');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.previewer.extensions) {
|
||||
mediaPreviewerInstance = mediaPreviewerInstance || new MediaItemPreviewer(this.previewer.extensions);
|
||||
mediaPreviewerInstance.elementEvents(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
element() {
|
||||
return this.element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import { PageStore } from '../../../../utils/stores/';
|
||||
import { cancelAnimationFrame, requestAnimationFrame, addClassname } from '../../../../utils/helpers/';
|
||||
|
||||
Array.isArray =
|
||||
Array.isArray ||
|
||||
function (arg) {
|
||||
'use strict';
|
||||
return Object.prototype.toString.call(arg) === '[object Array]';
|
||||
};
|
||||
|
||||
var hoverTimeoutID, requestAnimationFrameID;
|
||||
|
||||
var CSS_selectors = {
|
||||
mediaItemPreviewer: '.item-img-preview',
|
||||
};
|
||||
|
||||
var DataAttributes = {
|
||||
mediaPreviewSrc: 'data-src',
|
||||
};
|
||||
|
||||
export default class MediaItemPreviewer {
|
||||
constructor(extensions) {
|
||||
if (!Array.isArray(extensions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.extensions = {};
|
||||
|
||||
function onImageLoad(ins, evt) {
|
||||
requestAnimationFrameID = requestAnimationFrame(function () {
|
||||
if (ins.wrapperItem) {
|
||||
addClassname(ins.wrapperItem, 'on-hover-preview');
|
||||
requestAnimationFrameID = void 0;
|
||||
ins.wrapperItem = void 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fallback_ext = ['png', 'jpg', 'jpeg']; // NOTE: Keep extentions order.
|
||||
let i, x;
|
||||
|
||||
this.element = null;
|
||||
|
||||
if (-1 < extensions.indexOf('webp')) {
|
||||
x = document.createElement('source');
|
||||
x.type = 'image/webp';
|
||||
this.extensions.anim = this.extensions.anim || [];
|
||||
this.extensions.anim.push({ elem: x, type: 'webp' });
|
||||
if (1 === extensions.length) {
|
||||
this.extensions.fallback = { elem: document.createElement('img'), type: 'webp' };
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 < extensions.indexOf('gif')) {
|
||||
x = document.createElement('source');
|
||||
x.type = 'image/gif';
|
||||
this.extensions.anim = this.extensions.anim || [];
|
||||
this.extensions.anim.push({ elem: x, type: 'gif' });
|
||||
this.extensions.fallback = { elem: document.createElement('img'), type: 'gif' };
|
||||
}
|
||||
|
||||
if (-1 < extensions.indexOf('jpg')) {
|
||||
x = document.createElement('source');
|
||||
x.type = 'image/jpg';
|
||||
this.extensions.anim = this.extensions.anim || [];
|
||||
this.extensions.anim.push({ elem: x, type: 'jpg' });
|
||||
this.extensions.fallback = { elem: document.createElement('img'), type: 'jpg' };
|
||||
}
|
||||
|
||||
if (-1 < extensions.indexOf('jpeg')) {
|
||||
x = document.createElement('source');
|
||||
x.type = 'image/jpeg';
|
||||
this.extensions.anim = this.extensions.anim || [];
|
||||
this.extensions.anim.push({ elem: x, type: 'jpeg' });
|
||||
this.extensions.fallback = { elem: document.createElement('img'), type: 'jpeg' };
|
||||
}
|
||||
|
||||
if (!this.extensions.fallback.elem) {
|
||||
i = 0;
|
||||
while (i < fallback_extensions.length) {
|
||||
if (-1 < extensions.indexOf(fallback_ext[i])) {
|
||||
this.extensions.fallback = { elem: document.createElement('img'), type: fallback_ext[i] };
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.extensions.anim.length || this.extensions.fallback.elem) {
|
||||
this.element = document.createElement('picture');
|
||||
|
||||
if (this.extensions.anim.length) {
|
||||
i = 0;
|
||||
while (i < this.extensions.anim.length) {
|
||||
this.element.appendChild(this.extensions.anim[i].elem);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.extensions.fallback.elem) {
|
||||
this.element.appendChild(this.extensions.fallback.elem);
|
||||
}
|
||||
|
||||
this.image = this.element.querySelector('img');
|
||||
this.image.addEventListener('load', onImageLoad.bind(null, this));
|
||||
}
|
||||
}
|
||||
|
||||
elementEvents(el) {
|
||||
el.addEventListener('mouseenter', this.onMediaItemMouseEnter.bind(null, this));
|
||||
el.addEventListener('mouseleave', this.onMediaItemMouseLeave.bind(null, this));
|
||||
}
|
||||
|
||||
newImage(src, width, height, item) {
|
||||
let i;
|
||||
|
||||
if (void 0 !== hoverTimeoutID) {
|
||||
clearTimeout(hoverTimeoutID);
|
||||
}
|
||||
|
||||
if (void 0 !== requestAnimationFrameID) {
|
||||
cancelAnimationFrame(requestAnimationFrameID);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set source (src).
|
||||
*/
|
||||
|
||||
if (this.extensions.anim.length) {
|
||||
i = 0;
|
||||
while (i < this.extensions.anim.length) {
|
||||
this.extensions.anim[i].elem.setAttribute('srcset', src + '.' + this.extensions.anim[i].type);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if (this.extensions.fallback.elem) {
|
||||
this.extensions.fallback.elem.setAttribute('src', src + '.' + this.extensions.fallback.type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set dimensions (src).
|
||||
*/
|
||||
|
||||
if (this.extensions.fallback.elem) {
|
||||
this.extensions.fallback.elem.setAttribute('width', width + 'px');
|
||||
this.extensions.fallback.elem.setAttribute('height', height + 'px');
|
||||
}
|
||||
|
||||
/*
|
||||
* Append previewer.
|
||||
*/
|
||||
|
||||
item.querySelector(CSS_selectors.mediaItemPreviewer).appendChild(this.element);
|
||||
|
||||
/*
|
||||
* Set previewer's container element.
|
||||
*/
|
||||
|
||||
this.wrapperItem = item;
|
||||
}
|
||||
|
||||
onMediaItemMouseEnter(ins, evt) {
|
||||
var elem, src;
|
||||
|
||||
if (ins.image) {
|
||||
elem = evt.target.querySelector(CSS_selectors.mediaItemPreviewer);
|
||||
src =
|
||||
PageStore.get('config-site').url + '/' + elem.getAttribute(DataAttributes.mediaPreviewSrc).replace(/^\//g, '');
|
||||
|
||||
hoverTimeoutID = setTimeout(function () {
|
||||
ins.newImage(src, 1 + elem.offsetWidth, 1 + elem.offsetHeight, evt.target);
|
||||
}, 100); // NOTE: Avoid loading unnecessary media, when mouse is moving fast over dom items.
|
||||
}
|
||||
}
|
||||
|
||||
onMediaItemMouseLeave(ins, evt) {
|
||||
if (void 0 !== hoverTimeoutID) {
|
||||
clearTimeout(hoverTimeoutID);
|
||||
}
|
||||
|
||||
if (void 0 !== requestAnimationFrameID) {
|
||||
cancelAnimationFrame(requestAnimationFrameID);
|
||||
}
|
||||
|
||||
ins.wrapperItem = void 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import MediaItem from './MediaItem';
|
||||
import { hasClassname } from '../../../../utils/helpers/';
|
||||
|
||||
const _MediaItemsListData = {};
|
||||
|
||||
export default class MediaItemsList {
|
||||
constructor(listContainer, initialItems) {
|
||||
if (!Node.prototype.isPrototypeOf(listContainer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
_MediaItemsListData[
|
||||
Object.defineProperty(this, 'id', { value: 'MediaItemsList_' + Object.keys(_MediaItemsListData).length }).id
|
||||
] = {};
|
||||
|
||||
this.items = [];
|
||||
this.container = listContainer;
|
||||
this.horizontalItems = hasClassname(this.container, 'items-list-hor');
|
||||
|
||||
this.appendItems(initialItems);
|
||||
}
|
||||
|
||||
dataObject() {
|
||||
return _MediaItemsListData;
|
||||
}
|
||||
|
||||
appendItems(items) {
|
||||
var i;
|
||||
if (NodeList.prototype.isPrototypeOf(items)) {
|
||||
i = 0;
|
||||
while (i < items.length) {
|
||||
this.items.push(new MediaItem(items[i]));
|
||||
i += 1;
|
||||
}
|
||||
} else if (Node.prototype.isPrototypeOf(items)) {
|
||||
this.items.push(new MediaItem(items));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import MediaItemsList from './MediaItemsList';
|
||||
|
||||
var CSS_selectors = {
|
||||
mediaItems: '.item',
|
||||
};
|
||||
|
||||
var mediaItemsListInstances = [];
|
||||
|
||||
export default function (lists) {
|
||||
if (!lists.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let items,
|
||||
i = 0;
|
||||
|
||||
while (i < lists.length) {
|
||||
items = lists[i].querySelectorAll(CSS_selectors.mediaItems);
|
||||
|
||||
if (items.length) {
|
||||
mediaItemsListInstances = mediaItemsListInstances || [];
|
||||
mediaItemsListInstances.push(new MediaItemsList(lists[i], items));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return mediaItemsListInstances;
|
||||
}
|
||||
7
frontend/src/static/js/components/item-list/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './InlineSliderItemList.jsx';
|
||||
export * from './InlineSliderItemListAsync.jsx';
|
||||
export * from './ItemList.jsx';
|
||||
export * from './ItemListAsync.jsx';
|
||||
export * from './LazyLoadItemList.jsx';
|
||||
export * from './LazyLoadItemListAsync.jsx';
|
||||
export * from './PendingItemsList.jsx';
|
||||
28
frontend/src/static/js/components/list-item/Item.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
|
||||
export function Item(props) { }
|
||||
|
||||
Item.propTypes = {
|
||||
order: PositiveIntegerOrZero,
|
||||
title: PropTypes.string.isRequired,
|
||||
link: PropTypes.string.isRequired,
|
||||
singleLinkContent: PropTypes.bool.isRequired,
|
||||
description: PropTypes.string,
|
||||
meta_description: PropTypes.string,
|
||||
thumbnail: PropTypes.string,
|
||||
onMount: PropTypes.func,
|
||||
publish_date: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
editLink: PropTypes.string,
|
||||
};
|
||||
|
||||
Item.defaultProps = {
|
||||
title: '',
|
||||
link: '#',
|
||||
singleLinkContent: false,
|
||||
description: '',
|
||||
meta_description: '',
|
||||
thumbnail: '',
|
||||
publish_date: 0,
|
||||
};
|
||||
590
frontend/src/static/js/components/list-item/Item.scss
Executable file
@@ -0,0 +1,590 @@
|
||||
@import '../../../css/includes/_variables.scss';
|
||||
@import '../../../css/includes/_variables_dimensions.scss';
|
||||
|
||||
@import '../../../css/config/index.scss';
|
||||
|
||||
.item {
|
||||
vertical-align: top;
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: var(--max-item-width, var(--default-max-item-width));
|
||||
margin-bottom: var(--item-margin-bottom-width, var(--default-item-margin-bottom-width));
|
||||
}
|
||||
|
||||
.item-thumb,
|
||||
a.item-thumb {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: auto;
|
||||
padding-bottom: 56.11%;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-color: var(--item-thumb-bg-color);
|
||||
}
|
||||
|
||||
.item-thumb.no-thumb {
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: 0;
|
||||
margin-top: -1rem;
|
||||
margin-left: -1rem;
|
||||
font-size: 2rem;
|
||||
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
font-family: 'Material Icons';
|
||||
text-decoration: none;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
// @link: https://github.com/google/material-design-icons/blob/master/iconfont/codepoints
|
||||
|
||||
.item.video-item & {
|
||||
&:before {
|
||||
content: '\E02C';
|
||||
content: '\E54D';
|
||||
content: '\E04B';
|
||||
}
|
||||
}
|
||||
|
||||
.item.image-item & {
|
||||
&:before {
|
||||
content: '\E3F4';
|
||||
content: '\E412';
|
||||
}
|
||||
}
|
||||
|
||||
.item.audio-item & {
|
||||
&:before {
|
||||
content: '\E3A1';
|
||||
}
|
||||
}
|
||||
|
||||
.item.pdf-item &,
|
||||
.item.attachment-item & {
|
||||
&:before {
|
||||
content: '\e415';
|
||||
content: '\e24d';
|
||||
}
|
||||
}
|
||||
|
||||
.item.playlist-item & {
|
||||
&:before {
|
||||
content: '\e43c';
|
||||
}
|
||||
}
|
||||
|
||||
.item.category-item & {
|
||||
&:before {
|
||||
content: '\e892';
|
||||
content: 'list_alt';
|
||||
}
|
||||
}
|
||||
|
||||
.item.tag-item & {
|
||||
&:before {
|
||||
content: '\e54e';
|
||||
}
|
||||
}
|
||||
|
||||
.item.other-item & {
|
||||
&:before {
|
||||
content: '\e2bc';
|
||||
content: '\e24d';
|
||||
}
|
||||
}
|
||||
|
||||
.item.member-item & {
|
||||
&:before {
|
||||
content: 'person';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: block;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.item-img-preview {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 750ms;
|
||||
}
|
||||
|
||||
.item-duration,
|
||||
.item-type-icon {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
margin: 4px;
|
||||
padding: 2px 4px;
|
||||
color: hsl(0, 0%, 100%);
|
||||
background-color: hsl(0, 0%, 6.7%);
|
||||
border-radius: 2px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.item-duration {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 13.5px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.item-type-icon {
|
||||
> * {
|
||||
float: left;
|
||||
|
||||
&:before {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item.video-item & {
|
||||
&:before {
|
||||
content: '\E02C';
|
||||
content: '\E54D';
|
||||
content: '\E04B';
|
||||
}
|
||||
}
|
||||
|
||||
.item.audio-item & {
|
||||
&:before {
|
||||
content: '\E3A1';
|
||||
}
|
||||
}
|
||||
|
||||
.item.image-item & {
|
||||
&:before {
|
||||
content: '\E3F4';
|
||||
content: '\E412';
|
||||
}
|
||||
}
|
||||
|
||||
.item.pdf-item &,
|
||||
.item.attachment-item & {
|
||||
&:before {
|
||||
content: '\e24d';
|
||||
}
|
||||
}
|
||||
|
||||
.item.category-item & {
|
||||
&:before {
|
||||
content: '\e892';
|
||||
content: 'list_alt';
|
||||
}
|
||||
}
|
||||
|
||||
.item.tag-item & {
|
||||
&:before {
|
||||
content: '\e54e';
|
||||
}
|
||||
}
|
||||
|
||||
.item.other-item & {
|
||||
&:before {
|
||||
content: '\e001';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
clear: left;
|
||||
float: left;
|
||||
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 18px;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
color: var(--item-meta-text-color);
|
||||
|
||||
> * {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
a,
|
||||
a {
|
||||
color: var(--item-meta-link-text-color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--item-meta-link-hover-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-author {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.item-views {
|
||||
}
|
||||
|
||||
.item-views + .item-date {
|
||||
&:before {
|
||||
content: '•';
|
||||
content: '\2022';
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-description {
|
||||
$descr-font-size: 13px;
|
||||
$descr-max-lines: 2;
|
||||
$descr-line-height: 18px;
|
||||
|
||||
color: rgb(136, 136, 136);
|
||||
|
||||
font-size: $descr-font-size;
|
||||
line-height: 1em;
|
||||
width: 100%;
|
||||
float: left;
|
||||
margin: 10px 0 8px;
|
||||
// overflow: hidden;
|
||||
|
||||
/* Only for non-webkit */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $descr-max-lines;
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
/* Fallback for non-webkit */
|
||||
max-height: $descr-max-lines * $descr-line-height;
|
||||
|
||||
div {
|
||||
@include multiline_texts_excerpt(
|
||||
$font-size: $descr-font-size,
|
||||
$line-height: $descr-line-height,
|
||||
$lines-to-show: $descr-max-lines,
|
||||
$bg-color: transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.item.on-hover-preview {
|
||||
&:hover {
|
||||
.item-img-preview {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
h3 {
|
||||
display: inline-block;
|
||||
clear: right;
|
||||
width: auto;
|
||||
position: relative;
|
||||
|
||||
/* Fallback for non-webkit */
|
||||
max-height: calc(var(--item-title-max-lines) * var(--item-title-line-height));
|
||||
|
||||
/* Only for non-webkit */
|
||||
/*display: -webkit-box;
|
||||
-webkit-line-clamp: var(--item-title-max-lines);
|
||||
-webkit-box-orient: vertical;*/
|
||||
|
||||
a {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
span {
|
||||
line-height: var(--item-title-line-height);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background-color: var(--item-bg-color);
|
||||
|
||||
/* Fallback for non-webkit */
|
||||
display: block;
|
||||
max-height: calc(var(--item-title-max-lines) * var(--item-title-line-height));
|
||||
|
||||
/* Only for non-webkit */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: var(--item-title-max-lines);
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content-link {
|
||||
h3 {
|
||||
text-decoration: none;
|
||||
color: var(--item-title-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.item-main {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
line-height: var(--item-title-line-height);
|
||||
|
||||
h3 {
|
||||
font-weight: 500;
|
||||
font-size: var(--item-title-font-size);
|
||||
line-height: var(--item-title-line-height);
|
||||
margin-top: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
a.item-edit-link {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
padding: 8px 0;
|
||||
font-size: 0.928571429em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 1px 1px 0 0;
|
||||
color: #fff;
|
||||
background-color: var(--brand-color, var(--default-brand-color));
|
||||
}
|
||||
|
||||
.playlist-item {
|
||||
.playlist-count {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 92px;
|
||||
display: block;
|
||||
line-height: 1.25;
|
||||
color: rgba(#fff, 0.8);
|
||||
background-color: rgba(17, 17, 17, 0.8);
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 29px;
|
||||
margin: 1px 0 0 4px;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-hover-play-all {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(#fff, 0.8);
|
||||
background-color: rgba(17, 17, 17, 0.8);
|
||||
|
||||
letter-spacing: 0.007px;
|
||||
line-height: 1;
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-count,
|
||||
.playlist-hover-play-all {
|
||||
> * {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.playlist-hover-play-all {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.item-main {
|
||||
.item-meta {
|
||||
}
|
||||
|
||||
a.view-full-playlist {
|
||||
position: relative;
|
||||
float: left;
|
||||
clear: both;
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a.view-full-playlist {
|
||||
color: var(--playlist-item-main-view-full-link-text-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--playlist-item-main-view-full-link-hover-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hover-overlay-title {
|
||||
.item {
|
||||
.item-main,
|
||||
.item-content-link {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.item-main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.item-content-link {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
h3 {
|
||||
z-index: +1;
|
||||
color: #fff;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
transition-property: color, background-color;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
background: radial-gradient(circle, rgba(#000, 0.75) 0%, rgba(#4a4a4a, 0.75) 100%);
|
||||
}
|
||||
|
||||
&:after {
|
||||
opacity: 0;
|
||||
background: radial-gradient(circle, rgba(#fff, 0.75) 0%, rgba(#c6c6c6, 0.75) 100%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
h3 {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&:before {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
max-height: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 8px;
|
||||
|
||||
font-size: 1.5em;
|
||||
|
||||
span {
|
||||
max-height: 100% !important;
|
||||
line-height: 1.15;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.item-meta,
|
||||
.item-description {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
frontend/src/static/js/components/list-item/ItemHorizontal.scss
Executable file
@@ -0,0 +1,117 @@
|
||||
@import '../../../css/config/index.scss';
|
||||
|
||||
@media (min-width: 390px) {
|
||||
.items-list-hor .item {
|
||||
max-width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.items-list-hor .item-content {
|
||||
padding-left: calc(218px - 4px);
|
||||
}
|
||||
|
||||
.items-list-hor .item-thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(218px - 4px);
|
||||
height: calc(0.5611 * calc(218px - 4px));
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.items-list-hor .item-main {
|
||||
min-height: calc(0.5611 * calc(218px - 4px));
|
||||
padding-left: var(--horizontal-item-margin-right-width, var(--default-horizontal-item-margin-right-width));
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1em;
|
||||
max-height: initial;
|
||||
|
||||
span {
|
||||
line-height: var(--horizontal-item-title-line-height);
|
||||
max-height: calc(var(--horizontal-item-title-max-lines) * var(--default-horizontal-item-title-line-height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-list-hor .item-author {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.items-list-hor .item-views {
|
||||
&:before {
|
||||
content: '•';
|
||||
content: '\2022';
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.items-list-hor .item-description {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 390px) and (max-width: 599px) {
|
||||
.items-list-hor {
|
||||
.items-list {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
}
|
||||
|
||||
.item-content {
|
||||
padding-left: 168px;
|
||||
}
|
||||
|
||||
.item-thumb,
|
||||
a.item-thumb {
|
||||
width: 168px;
|
||||
height: calc(0.5611 * 168px);
|
||||
}
|
||||
|
||||
.item-main {
|
||||
min-height: calc(0.5611 * 168px);
|
||||
}
|
||||
|
||||
.item-main h3 {
|
||||
line-height: 20px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.item-author {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.item-views {
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.item-meta > .item-views + .item-date:before {
|
||||
content: '•';
|
||||
content: '\2022';
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.item-description {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.items-list-hor .item-date:before {
|
||||
content: '•';
|
||||
content: '\2022';
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.items-list-hor .item {
|
||||
margin-bottom: var(--horizontal-item-margin-bottom-width, var(--default-horizontal-item-margin-bottom-width));
|
||||
}
|
||||
}
|
||||
202
frontend/src/static/js/components/list-item/ItemVertical.scss
Executable file
@@ -0,0 +1,202 @@
|
||||
@import '../../../css/config/index.scss';
|
||||
|
||||
.items-list-ver .feat-first-item {
|
||||
.items-list-wrap,
|
||||
.items-list {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&.no-title {
|
||||
margin-top: var(--default-item-margin-bottom-width);
|
||||
}
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:first-child .item-player-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding-bottom: 56.11%;
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:first-child .item-player-wrapper-inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
background-color: var(--item-thumb-bg-color);
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
$item-width: 260px;
|
||||
$side-empty-space: 40px;
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 2 * $item-width ) )) {
|
||||
.items-list-ver.media-list-wrapper .media-list-row .item {
|
||||
display: inline-block;
|
||||
max-width: var(--item-width, var(--default-item-width));
|
||||
}
|
||||
|
||||
.items-list-ver.media-list-wrapper .media-list-row .item-content {
|
||||
margin-right: var(--item-margin-right-width, var(--default-item-margin-right-width));
|
||||
}
|
||||
|
||||
.items-list-ver.media-list-wrapper .media-list-row .item-main {
|
||||
h3 {
|
||||
margin: 0.5714285em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 2 * $item-width ) )) {
|
||||
.items-list-ver .feat-first-item .item:first-child {
|
||||
float: left;
|
||||
max-width: calc(3 * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
$item-width: 218px;
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 4 * $item-width ) )) {
|
||||
.items-list-ver .feat-first-item .item:nth-child(4n + 4) {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:nth-child(2) {
|
||||
min-height: 232px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 5 * $item-width ) )) {
|
||||
.items-list-ver .feat-first-item .item:nth-child(4n + 4) {
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:nth-child(5n + 6) {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:nth-child(2) {
|
||||
min-height: 0;
|
||||
margin-bottom: var(--default-item-margin-bottom-width);
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:nth-child(3) {
|
||||
min-height: 232px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 6 * $item-width ) )) {
|
||||
.items-list-ver .feat-first-item .item:nth-child(5n + 6) {
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:nth-child(6n + 8) {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:nth-child(3) {
|
||||
min-height: 0;
|
||||
margin-bottom: var(--default-item-margin-bottom-width);
|
||||
}
|
||||
|
||||
.items-list-ver .feat-first-item .item:nth-child(4) {
|
||||
min-height: 232px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* #################################################################################################### */
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 4 * $item-width ) )) {
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(4n + 4),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(4n + 4) {
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(2),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(2) {
|
||||
min-height: 0;
|
||||
margin-bottom: var(--default-item-margin-bottom-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 5 * $item-width ) )) {
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(4n + 4),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(4n + 4) {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(3),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(3) {
|
||||
min-height: 0;
|
||||
margin-bottom: var(--default-item-margin-bottom-width);
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(2),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(2) {
|
||||
min-height: 232px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 6 * $item-width ) )) {
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(4n + 4),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(4n + 4) {
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(5n + 6),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(5n + 6) {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(2),
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(4),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(2),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(4) {
|
||||
min-height: 0;
|
||||
margin-bottom: var(--default-item-margin-bottom-width);
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(3),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(3) {
|
||||
min-height: 232px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 7 * $item-width ) )) {
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(5n + 6),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(5n + 6) {
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(6n + 8),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(6n + 8) {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(3),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(3) {
|
||||
min-height: 0;
|
||||
margin-bottom: var(--default-item-margin-bottom-width);
|
||||
}
|
||||
|
||||
.sliding-sidebar .items-list-ver .feat-first-item .item:nth-child(4),
|
||||
.visible-sidebar .items-list-ver .feat-first-item .item:nth-child(4) {
|
||||
min-height: 232px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
}
|
||||
344
frontend/src/static/js/components/list-item/ListItem.jsx
Normal file
@@ -0,0 +1,344 @@
|
||||
import React from 'react';
|
||||
import { LinksContext } from '../../utils/contexts/';
|
||||
import { PageStore } from '../../utils/stores/';
|
||||
import { MediaItemAudio as AudioItem } from './MediaItemAudio';
|
||||
import { MediaItemVideo as VideoItem } from './MediaItemVideo';
|
||||
import { MediaItem as ImageItem } from './MediaItem';
|
||||
import { MediaItem as PdfItem } from './MediaItem';
|
||||
import { MediaItem as AttachmentItem } from './MediaItem';
|
||||
import { PlaylistItem } from './PlaylistItem';
|
||||
import { TaxonomyItem } from './TaxonomyItem';
|
||||
import { UserItem } from './UserItem';
|
||||
|
||||
function extractPlaylistId() {
|
||||
let playlistId = null;
|
||||
|
||||
const getParamsString = window.location.search;
|
||||
|
||||
if ('' !== getParamsString) {
|
||||
let tmp = getParamsString.split('?');
|
||||
|
||||
if (2 === tmp.length) {
|
||||
tmp = tmp[1].split('&');
|
||||
|
||||
let x;
|
||||
|
||||
let i = 0;
|
||||
while (i < tmp.length) {
|
||||
x = tmp[i].split('=');
|
||||
|
||||
if ('pl' === x[0]) {
|
||||
if (2 === x.length) {
|
||||
playlistId = x[1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playlistId;
|
||||
}
|
||||
|
||||
function itemPageLink(props, item) {
|
||||
if (props.inCategoriesList) {
|
||||
return LinksContext._currentValue.search.category + item.title.replace(' ', '%20');
|
||||
}
|
||||
|
||||
if (props.inTagsList) {
|
||||
return LinksContext._currentValue.search.tag + item.title.replace(' ', '%20');
|
||||
}
|
||||
|
||||
const playlistId = extractPlaylistId();
|
||||
|
||||
if (props.inPlaylistView && playlistId) {
|
||||
return item.url + '&pl=' + playlistId;
|
||||
}
|
||||
|
||||
if (void 0 !== props.playlistId && null !== props.playlistId) {
|
||||
return item.url + '&pl=' + props.playlistId;
|
||||
}
|
||||
|
||||
return item.url;
|
||||
}
|
||||
|
||||
export function listItemProps(props, item, index) {
|
||||
const isArchiveItem = props.inCategoriesList || props.inTagsList;
|
||||
const isUserItem = !isArchiveItem && void 0 !== item.username;
|
||||
const isPlaylistItem =
|
||||
!isArchiveItem &&
|
||||
!isUserItem &&
|
||||
('playlist' === item.media_type || (void 0 !== item.url && -1 < item.url.indexOf('playlists'))); // TODO: Improve this.
|
||||
const isMediaItem = !isArchiveItem && !isUserItem && !isPlaylistItem;
|
||||
const isSearchItem = 'search-results' === PageStore.get('current-page'); // TODO: Improve this.
|
||||
|
||||
const url = {
|
||||
view: itemPageLink(props, item),
|
||||
edit: props.canEdit ? item.url.replace('view?m=', 'edit?m=') : null,
|
||||
};
|
||||
|
||||
if (window.MediaCMS.site.devEnv && -1 < url.view.indexOf('view?')) {
|
||||
url.view = '/media.html?' + url.view.split('view?')[1];
|
||||
}
|
||||
|
||||
const thumbnail = item.thumbnail_url || '';
|
||||
const previewThumbnail = item.preview_url || '';
|
||||
|
||||
let type, title, date, description, meta_description;
|
||||
|
||||
title =
|
||||
void 0 !== item.username && 'string' === typeof item.username
|
||||
? item.username
|
||||
: void 0 !== item.title && 'string' === typeof item.title
|
||||
? item.title
|
||||
: null;
|
||||
|
||||
date =
|
||||
void 0 !== item.date_added && 'string' === typeof item.date_added
|
||||
? item.date_added
|
||||
: void 0 !== item.add_date && 'string' === typeof item.add_date
|
||||
? item.add_date
|
||||
: null;
|
||||
|
||||
// description = props.preferSummary && 'string' === typeof props.summary ? props.summary.trim() : ( 'string' === typeof item.description ? item.description.trim() : null );
|
||||
// description = null === description ? description : description.replace(/(<([^>]+)>)/ig,"");
|
||||
|
||||
if (isUserItem) {
|
||||
type = 'user';
|
||||
} else if (isPlaylistItem) {
|
||||
type = 'playlist';
|
||||
} else if (isMediaItem) {
|
||||
type = item.media_type;
|
||||
}
|
||||
|
||||
const taxonomyPage = {
|
||||
current: false,
|
||||
type: null,
|
||||
};
|
||||
|
||||
const playlistPage = {
|
||||
current: props.inPlaylistPage,
|
||||
id: props.playlistId,
|
||||
hideOptions: props.hidePlaylistOptions || false,
|
||||
hideOrderNumber: props.hidePlaylistOrderNumber || false,
|
||||
};
|
||||
|
||||
const playlistPlayback = {
|
||||
current: props.inPlaylistView,
|
||||
id: props.playlistId,
|
||||
activeItem: props.playlistActiveItem || false,
|
||||
hideOrderNumber: props.hidePlaylistOrderNumber || false,
|
||||
};
|
||||
|
||||
if (isArchiveItem) {
|
||||
if (props.inCategoriesList) {
|
||||
taxonomyPage.type = 'categories';
|
||||
} else if (props.inTagsList) {
|
||||
taxonomyPage.type = 'tags';
|
||||
}
|
||||
|
||||
if (null !== taxonomyPage.type) {
|
||||
taxonomyPage.current = true;
|
||||
}
|
||||
}
|
||||
|
||||
const author = {
|
||||
name: item.author_name || item.user,
|
||||
url: item.author_profile ? item.author_profile.replace(' ', '%20') : null,
|
||||
};
|
||||
|
||||
const stats = {
|
||||
views: item.views || null,
|
||||
};
|
||||
|
||||
const hide = {
|
||||
allMeta: props.hideAllMeta || false,
|
||||
};
|
||||
|
||||
let args = {
|
||||
order: index + 1,
|
||||
type,
|
||||
title,
|
||||
date,
|
||||
url,
|
||||
author,
|
||||
stats,
|
||||
thumbnail,
|
||||
taxonomyPage,
|
||||
playlistPage,
|
||||
playlistPlayback,
|
||||
canEdit: null !== url.edit,
|
||||
singleLinkContent: props.singleLinkContent || false,
|
||||
hasMediaViewer: 0 === index && 'video' === item.media_type && !!props.firstItemViewer,
|
||||
hasMediaViewerDescr: false,
|
||||
};
|
||||
|
||||
args.hasMediaViewerDescr = args.hasMediaViewer && !!props.firstItemDescr;
|
||||
|
||||
if (!args.hasMediaViewerDescr) {
|
||||
description =
|
||||
props.preferSummary && 'string' === typeof props.summary
|
||||
? props.summary.trim()
|
||||
: 'string' === typeof item.description
|
||||
? item.description.trim()
|
||||
: null;
|
||||
description = null === description ? description : description.replace(/(<([^>]+)>)/gi, '');
|
||||
|
||||
if (isSearchItem || props.inCategoriesList || 'user' === type) {
|
||||
args.description = description;
|
||||
} else {
|
||||
args.meta_description = description;
|
||||
}
|
||||
} else {
|
||||
if (!!props.firstItemViewer) {
|
||||
description = 'string' === typeof props.summary ? props.summary.trim() : null;
|
||||
} else {
|
||||
description = 'string' === typeof item.description ? item.description.trim() : null;
|
||||
}
|
||||
|
||||
description = null === description ? description : description.replace(/(<([^>]+)>)/gi, '');
|
||||
|
||||
args.description = description;
|
||||
|
||||
// TODO: Improve this.
|
||||
if (props.summary) {
|
||||
meta_description = props.summary.trim();
|
||||
meta_description = null === meta_description ? meta_description : meta_description.replace(/(<([^>]+)>)/gi, '');
|
||||
args.meta_description = meta_description;
|
||||
}
|
||||
}
|
||||
|
||||
if ('video' === type) {
|
||||
args.previewThumbnail = previewThumbnail;
|
||||
}
|
||||
|
||||
if ('video' === type || 'audio' === type) {
|
||||
args.duration = item.duration;
|
||||
}
|
||||
|
||||
if ((isArchiveItem || isPlaylistItem) && !isNaN(item.media_count)) {
|
||||
args.media_count = parseInt(item.media_count, 10);
|
||||
}
|
||||
|
||||
if (isMediaItem) {
|
||||
hide.date = props.hideDate || false;
|
||||
hide.views = props.hideViews || false;
|
||||
hide.author = props.hideAuthor || false;
|
||||
}
|
||||
|
||||
args = { ...args, hide };
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function ListItem(props) {
|
||||
let isMediaItem = false;
|
||||
|
||||
const args = {
|
||||
order: props.order,
|
||||
title: props.title,
|
||||
link: props.url.view,
|
||||
thumbnail: props.thumbnail,
|
||||
publish_date: props.date,
|
||||
singleLinkContent: props.singleLinkContent,
|
||||
hasMediaViewer: props.hasMediaViewer,
|
||||
hasMediaViewerDescr: props.hasMediaViewerDescr,
|
||||
};
|
||||
|
||||
switch (props.type) {
|
||||
case 'user':
|
||||
break;
|
||||
case 'playlist':
|
||||
break;
|
||||
case 'video':
|
||||
isMediaItem = true;
|
||||
args.duration = props.duration;
|
||||
args.preview_thumbnail = props.previewThumbnail;
|
||||
break;
|
||||
case 'audio':
|
||||
isMediaItem = true;
|
||||
args.duration = props.duration;
|
||||
break;
|
||||
case 'image':
|
||||
isMediaItem = true;
|
||||
break;
|
||||
case 'pdf':
|
||||
isMediaItem = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (void 0 !== props.description) {
|
||||
args.description = props.description;
|
||||
}
|
||||
|
||||
if (void 0 !== props.meta_description) {
|
||||
args.meta_description = props.meta_description;
|
||||
}
|
||||
|
||||
if ((props.taxonomyPage.current || 'playlist' === props.type) && !isNaN(props.media_count)) {
|
||||
args.media_count = props.media_count;
|
||||
}
|
||||
|
||||
args.hideAllMeta = props.hide.allMeta;
|
||||
|
||||
if (isMediaItem) {
|
||||
args.views = props.stats.views;
|
||||
|
||||
args.author_name = props.author.name;
|
||||
args.author_link = props.author.url;
|
||||
|
||||
args.hideDate = props.hide.date;
|
||||
args.hideViews = props.hide.views;
|
||||
args.hideAuthor = props.hide.author;
|
||||
}
|
||||
|
||||
if (props.playlistPage.current || props.playlistPlayback.current) {
|
||||
args.playlistOrder = props.order;
|
||||
|
||||
if (props.playlistPlayback.current) {
|
||||
args.playlist_id = props.playlistPlayback.id;
|
||||
args.playlistActiveItem = props.playlistPlayback.activeItem;
|
||||
args.hidePlaylistOrderNumber = props.playlistPlayback.hideOrderNumber;
|
||||
} else {
|
||||
args.playlist_id = props.playlistPage.id;
|
||||
args.hidePlaylistOptions = props.playlistPage.hideOptions;
|
||||
args.hidePlaylistOrderNumber = props.playlistPage.hideOrderNumber;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.canEdit) {
|
||||
args.editLink = props.url.edit;
|
||||
}
|
||||
|
||||
if (props.taxonomyPage.current) {
|
||||
switch (props.taxonomyPage.type) {
|
||||
case 'categories':
|
||||
return <TaxonomyItem {...args} type="category" />;
|
||||
case 'tags':
|
||||
return <TaxonomyItem {...args} type="tag" />;
|
||||
}
|
||||
}
|
||||
|
||||
switch (props.type) {
|
||||
case 'user':
|
||||
return <UserItem {...args} />;
|
||||
case 'playlist':
|
||||
if (window.MediaCMS.site.devEnv) {
|
||||
args.link = args.link.replace('/playlists/', 'playlist.html?pl=');
|
||||
}
|
||||
return <PlaylistItem {...args} />;
|
||||
case 'video':
|
||||
return <VideoItem {...args} />;
|
||||
case 'audio':
|
||||
return <AudioItem {...args} />;
|
||||
case 'image':
|
||||
return <ImageItem {...args} type="image" />;
|
||||
case 'pdf':
|
||||
return <PdfItem {...args} type="pdf" />;
|
||||
}
|
||||
|
||||
return <AttachmentItem {...args} type="attachment" />;
|
||||
}
|
||||
70
frontend/src/static/js/components/list-item/MediaItem.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useMediaItem } from '../../utils/hooks/';
|
||||
import { PositiveInteger, PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
import { MediaItemThumbnailLink, itemClassname } from './includes/items/';
|
||||
import { Item } from './Item';
|
||||
|
||||
export function MediaItem(props) {
|
||||
const type = props.type;
|
||||
|
||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents] =
|
||||
useMediaItem({ ...props, type });
|
||||
|
||||
function thumbnailComponent() {
|
||||
return <MediaItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />;
|
||||
}
|
||||
|
||||
const containerClassname = itemClassname(
|
||||
'item ' + type + '-item',
|
||||
props.class_name.trim(),
|
||||
props.playlistOrder === props.playlistActiveItem
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={containerClassname}>
|
||||
<div className="item-content">
|
||||
{editMediaComponent()}
|
||||
|
||||
{thumbnailComponent()}
|
||||
|
||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||
{titleComponent()}
|
||||
{metaComponents()}
|
||||
{descriptionComponent()}
|
||||
</UnderThumbWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MediaItem.propTypes = {
|
||||
...Item.propTypes,
|
||||
type: PropTypes.string.isRequired,
|
||||
class_name: PropTypes.string,
|
||||
views: PositiveIntegerOrZero,
|
||||
hideViews: PropTypes.bool,
|
||||
hideDate: PropTypes.bool,
|
||||
hideAuthor: PropTypes.bool,
|
||||
author_name: PropTypes.string,
|
||||
author_link: PropTypes.string,
|
||||
playlistOrder: PositiveInteger,
|
||||
playlistActiveItem: PositiveIntegerOrZero,
|
||||
inPlaylistView: PropTypes.bool,
|
||||
hidePlaylistOrderNumber: PropTypes.bool,
|
||||
};
|
||||
|
||||
MediaItem.defaultProps = {
|
||||
...Item.defaultProps,
|
||||
class_name: '',
|
||||
views: 0,
|
||||
hideViews: false,
|
||||
hideDate: false,
|
||||
hideAuthor: false,
|
||||
author_name: '',
|
||||
author_link: '#',
|
||||
playlistOrder: 1,
|
||||
playlistActiveItem: 1,
|
||||
inPlaylistView: false,
|
||||
hidePlaylistOrderNumber: true,
|
||||
};
|
||||
106
frontend/src/static/js/components/list-item/MediaItemAudio.jsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useMediaItem } from '../../utils/hooks/';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
import { MediaDurationInfo } from '../../utils/classes/';
|
||||
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions';
|
||||
import { MediaItemDuration, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
||||
import { MediaItem } from './MediaItem';
|
||||
|
||||
export function MediaItemAudio(props) {
|
||||
const type = props.type;
|
||||
|
||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents] =
|
||||
useMediaItem({ ...props, type });
|
||||
|
||||
const _MediaDurationInfo = new MediaDurationInfo();
|
||||
|
||||
_MediaDurationInfo.update(props.duration);
|
||||
|
||||
const duration = _MediaDurationInfo.ariaLabel();
|
||||
const durationStr = _MediaDurationInfo.toString();
|
||||
const durationISO8601 = _MediaDurationInfo.ISO8601();
|
||||
|
||||
function thumbnailComponent() {
|
||||
const attr = {
|
||||
key: 'item-thumb',
|
||||
href: props.link,
|
||||
title: props.title,
|
||||
tabIndex: '-1',
|
||||
'aria-hidden': true,
|
||||
className: 'item-thumb' + (!thumbnailUrl ? ' no-thumb' : ''),
|
||||
style: !thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" },
|
||||
};
|
||||
|
||||
return (
|
||||
<a {...attr}>
|
||||
{props.inPlaylistView ? null : (
|
||||
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function playlistOrderNumberComponent() {
|
||||
return props.hidePlaylistOrderNumber ? null : (
|
||||
<MediaItemPlaylistIndex
|
||||
index={props.playlistOrder}
|
||||
inPlayback={props.inPlaylistView}
|
||||
activeIndex={props.playlistActiveItem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function playlistOptionsComponent() {
|
||||
let mediaId = props.link.split('=')[1];
|
||||
mediaId = mediaId.split('&')[0];
|
||||
return props.hidePlaylistOptions ? null : (
|
||||
<MediaPlaylistOptions key="options" media_id={mediaId} playlist_id={props.playlist_id} />
|
||||
);
|
||||
}
|
||||
|
||||
const containerClassname = itemClassname(
|
||||
'item ' + type + '-item',
|
||||
props.class_name.trim(),
|
||||
props.playlistOrder === props.playlistActiveItem
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={containerClassname}>
|
||||
{playlistOrderNumberComponent()}
|
||||
|
||||
<div className="item-content">
|
||||
{editMediaComponent()}
|
||||
|
||||
{thumbnailComponent()}
|
||||
|
||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||
{titleComponent()}
|
||||
{metaComponents()}
|
||||
{descriptionComponent()}
|
||||
</UnderThumbWrapper>
|
||||
|
||||
{playlistOptionsComponent()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MediaItemAudio.propTypes = {
|
||||
...MediaItem.propTypes,
|
||||
type: PropTypes.string.isRequired,
|
||||
duration: PositiveIntegerOrZero,
|
||||
hidePlaylistOptions: PropTypes.bool,
|
||||
hasMediaViewer: PropTypes.bool,
|
||||
hasMediaViewerDescr: PropTypes.bool,
|
||||
playlist_id: PropTypes.string,
|
||||
};
|
||||
|
||||
MediaItemAudio.defaultProps = {
|
||||
...MediaItem.defaultProps,
|
||||
type: 'audio',
|
||||
duration: 0,
|
||||
hidePlaylistOptions: true,
|
||||
hasMediaViewer: false,
|
||||
hasMediaViewerDescr: false,
|
||||
};
|
||||
113
frontend/src/static/js/components/list-item/MediaItemVideo.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useMediaItem } from '../../utils/hooks/';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
import { MediaDurationInfo } from '../../utils/classes/';
|
||||
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions.jsx';
|
||||
import { MediaItemVideoPlayer, MediaItemDuration, MediaItemVideoPreviewer, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
||||
import { MediaItem } from './MediaItem';
|
||||
|
||||
export function MediaItemVideo(props) {
|
||||
const type = props.type;
|
||||
|
||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents] =
|
||||
useMediaItem({ ...props, type });
|
||||
|
||||
const _MediaDurationInfo = new MediaDurationInfo();
|
||||
|
||||
_MediaDurationInfo.update(props.duration);
|
||||
|
||||
const duration = _MediaDurationInfo.ariaLabel();
|
||||
const durationStr = _MediaDurationInfo.toString();
|
||||
const durationISO8601 = _MediaDurationInfo.ISO8601();
|
||||
|
||||
function videoViewerComponent() {
|
||||
return <MediaItemVideoPlayer mediaPageLink={props.link} />;
|
||||
}
|
||||
|
||||
function thumbnailComponent() {
|
||||
const attr = {
|
||||
key: 'item-thumb',
|
||||
href: props.link,
|
||||
title: props.title,
|
||||
tabIndex: '-1',
|
||||
'aria-hidden': true,
|
||||
className: 'item-thumb' + (!thumbnailUrl ? ' no-thumb' : ''),
|
||||
style: !thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" },
|
||||
};
|
||||
|
||||
return (
|
||||
<a {...attr}>
|
||||
{props.inPlaylistView ? null : (
|
||||
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
|
||||
)}
|
||||
{props.inPlaylistView || props.inPlaylistPage ? null : (
|
||||
<MediaItemVideoPreviewer url={props.preview_thumbnail} />
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function playlistOrderNumberComponent() {
|
||||
return props.hidePlaylistOrderNumber ? null : (
|
||||
<MediaItemPlaylistIndex
|
||||
index={props.playlistOrder}
|
||||
inPlayback={props.inPlaylistView}
|
||||
activeIndex={props.playlistActiveItem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function playlistOptionsComponent() {
|
||||
let mediaId = props.link.split('=')[1];
|
||||
mediaId = mediaId.split('&')[0];
|
||||
return props.hidePlaylistOptions ? null : (
|
||||
<MediaPlaylistOptions key="options" media_id={mediaId} playlist_id={props.playlist_id} />
|
||||
);
|
||||
}
|
||||
|
||||
const containerClassname = itemClassname(
|
||||
'item ' + type + '-item',
|
||||
props.class_name.trim(),
|
||||
props.playlistOrder === props.playlistActiveItem
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={containerClassname}>
|
||||
{playlistOrderNumberComponent()}
|
||||
|
||||
<div className="item-content">
|
||||
{editMediaComponent()}
|
||||
|
||||
{props.hasMediaViewer ? videoViewerComponent() : thumbnailComponent()}
|
||||
|
||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||
{titleComponent()}
|
||||
{metaComponents()}
|
||||
{descriptionComponent()}
|
||||
</UnderThumbWrapper>
|
||||
</div>
|
||||
|
||||
{playlistOptionsComponent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MediaItemVideo.propTypes = {
|
||||
...MediaItem.propTypes,
|
||||
type: PropTypes.string.isRequired,
|
||||
duration: PositiveIntegerOrZero,
|
||||
hidePlaylistOptions: PropTypes.bool,
|
||||
hasMediaViewer: PropTypes.bool,
|
||||
hasMediaViewerDescr: PropTypes.bool,
|
||||
playlist_id: PropTypes.string,
|
||||
};
|
||||
|
||||
MediaItemVideo.defaultProps = {
|
||||
...MediaItem.defaultProps,
|
||||
type: 'video',
|
||||
duration: 0,
|
||||
hidePlaylistOptions: true,
|
||||
hasMediaViewer: false,
|
||||
hasMediaViewerDescr: false,
|
||||
};
|
||||
73
frontend/src/static/js/components/list-item/PlaylistItem.jsx
Executable file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import { format } from 'timeago.js';
|
||||
import { useItem } from '../../utils/hooks/';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
import { PlaylistItemMetaDate } from './includes/items/';
|
||||
import { Item } from './Item';
|
||||
|
||||
export function PlaylistItem(props) {
|
||||
const type = 'playlist';
|
||||
|
||||
const { titleComponent, thumbnailUrl, UnderThumbWrapper } = useItem({ ...props, type });
|
||||
|
||||
function metaComponents() {
|
||||
const publishDate = format(new Date(props.publish_date));
|
||||
const publishDateTime =
|
||||
'string' === typeof props.publish_date
|
||||
? Date.parse(props.publish_date)
|
||||
: Date.parse(new Date(props.publish_date));
|
||||
|
||||
return <PlaylistItemMetaDate dateTime={publishDateTime} text={'Created ' + publishDate} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="item playlist-item">
|
||||
<div className="item-content">
|
||||
<a
|
||||
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
|
||||
href={props.link}
|
||||
title={props.title}
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
style={!thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" }}
|
||||
>
|
||||
<div className="playlist-count">
|
||||
<div>
|
||||
<div>
|
||||
<span>{props.media_count}</span>
|
||||
<i className="material-icons">playlist_play</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="playlist-hover-play-all">
|
||||
<div>
|
||||
<div>
|
||||
<i className="material-icons">play_arrow</i>
|
||||
<span>PLAY ALL</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||
{titleComponent()}
|
||||
{metaComponents()}
|
||||
<a href={props.link} title="" className="view-full-playlist">
|
||||
VIEW FULL PLAYLIST
|
||||
</a>
|
||||
</UnderThumbWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PlaylistItem.propTypes = {
|
||||
...Item.propTypes,
|
||||
media_count: PositiveIntegerOrZero,
|
||||
};
|
||||
|
||||
PlaylistItem.defaultProps = {
|
||||
...Item.defaultProps,
|
||||
media_count: 0,
|
||||
};
|
||||
60
frontend/src/static/js/components/list-item/TaxonomyItem.jsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useItem } from '../../utils/hooks/';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
import { TaxonomyItemMediaCount, itemClassname } from './includes/items/';
|
||||
import { Item } from './Item';
|
||||
|
||||
export function TaxonomyItem(props) {
|
||||
const type = props.type;
|
||||
|
||||
const { titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper } = useItem({ ...props, type });
|
||||
|
||||
function thumbnailComponent() {
|
||||
const attr = {
|
||||
key: 'item-thumb',
|
||||
href: props.link,
|
||||
title: props.title,
|
||||
tabIndex: '-1',
|
||||
'aria-hidden': true,
|
||||
className: 'item-thumb' + (!thumbnailUrl ? ' no-thumb' : ''),
|
||||
style: !thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" },
|
||||
};
|
||||
return <a {...attr}></a>;
|
||||
}
|
||||
|
||||
function metaComponents() {
|
||||
return props.hideAllMeta ? null : (
|
||||
<span className="item-meta">{<TaxonomyItemMediaCount count={props.media_count} />}</span>
|
||||
);
|
||||
}
|
||||
|
||||
const containerClassname = itemClassname('item ' + type + '-item', props.class_name.trim(), false);
|
||||
|
||||
return (
|
||||
<div className={containerClassname}>
|
||||
<div className="item-content">
|
||||
{thumbnailComponent()}
|
||||
|
||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||
{titleComponent()}
|
||||
{metaComponents()}
|
||||
{descriptionComponent()}
|
||||
</UnderThumbWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TaxonomyItem.propTypes = {
|
||||
...Item.propTypes,
|
||||
type: PropTypes.string.isRequired,
|
||||
class_name: PropTypes.string,
|
||||
media_count: PositiveIntegerOrZero,
|
||||
};
|
||||
|
||||
TaxonomyItem.defaultProps = {
|
||||
...Item.defaultProps,
|
||||
class_name: '',
|
||||
media_count: 0,
|
||||
};
|
||||
44
frontend/src/static/js/components/list-item/UserItem.jsx
Executable file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { useItem } from '../../utils/hooks/';
|
||||
import { UserItemMemberSince, UserItemThumbnailLink } from './includes/items/';
|
||||
import { Item } from './Item';
|
||||
|
||||
export function UserItem(props) {
|
||||
const type = 'user';
|
||||
|
||||
const { titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper } = useItem({ ...props, type });
|
||||
|
||||
function metaComponents() {
|
||||
return props.hideAllMeta ? null : (
|
||||
<span className="item-meta">
|
||||
<UserItemMemberSince date={props.publish_date} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function thumbnailComponent() {
|
||||
return <UserItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="item member-item">
|
||||
<div className="item-content">
|
||||
{thumbnailComponent()}
|
||||
|
||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||
{titleComponent()}
|
||||
{metaComponents()}
|
||||
{descriptionComponent()}
|
||||
</UnderThumbWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
UserItem.propTypes = {
|
||||
...Item.propTypes,
|
||||
};
|
||||
|
||||
UserItem.defaultProps = {
|
||||
...Item.defaultProps,
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
import React from 'react';
|
||||
import { format } from 'timeago.js';
|
||||
import { formatViewsNumber, imageExtension } from '../../../../utils/helpers/';
|
||||
import { VideoPlayerByPageLink } from '../../../video-player/VideoPlayerByPageLink';
|
||||
|
||||
export function ItemDescription(props) {
|
||||
return '' === props.description ? null : (
|
||||
<div className="item-description">
|
||||
<div>{props.description}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ItemMain(props) {
|
||||
return <div className="item-main">{props.children}</div>;
|
||||
}
|
||||
|
||||
export function ItemMainInLink(props) {
|
||||
return (
|
||||
<ItemMain>
|
||||
<a className="item-content-link" href={props.link} title={props.title}>
|
||||
{props.children}
|
||||
</a>
|
||||
</ItemMain>
|
||||
);
|
||||
}
|
||||
|
||||
export function ItemTitle(props) {
|
||||
return '' === props.title ? null : (
|
||||
<h3>
|
||||
<span aria-label={props.ariaLabel}>{props.title}</span>
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
export function ItemTitleLink(props) {
|
||||
return '' === props.title ? null : (
|
||||
<h3>
|
||||
<a href={props.link} title={props.title}>
|
||||
<span aria-label={props.ariaLabel}>{props.title}</span>
|
||||
</a>
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserItemMemberSince(props) {
|
||||
return <time key="member-since">Member for {format(new Date(props.date)).replace(' ago', '')}</time>;
|
||||
}
|
||||
|
||||
export function TaxonomyItemMediaCount(props) {
|
||||
return (
|
||||
<span key="item-media-count" className="item-media-count">
|
||||
{' ' + props.count} media
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlaylistItemMetaDate(props) {
|
||||
return (
|
||||
<span className="item-meta">
|
||||
<span className="playlist-date">
|
||||
<time dateTime={props.dateTime}>{props.text}</time>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemEditLink(props) {
|
||||
let link = props.link;
|
||||
|
||||
if (link && window.MediaCMS.site.devEnv) {
|
||||
link = '/edit-media.html';
|
||||
}
|
||||
|
||||
return !link ? null : (
|
||||
<a href={link} title="Edit media" className="item-edit-link">
|
||||
EDIT MEDIA
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemThumbnailLink(props) {
|
||||
const attr = {
|
||||
key: 'item-thumb',
|
||||
href: props.link,
|
||||
title: props.title,
|
||||
tabIndex: '-1',
|
||||
'aria-hidden': true,
|
||||
className: 'item-thumb' + (!props.src ? ' no-thumb' : ''),
|
||||
style: !props.src ? null : { backgroundImage: "url('" + props.src + "')" },
|
||||
};
|
||||
|
||||
return (
|
||||
<a {...attr}>
|
||||
{!props.src ? null : (
|
||||
<div key="item-type-icon" className="item-type-icon">
|
||||
<div></div>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserItemThumbnailLink(props) {
|
||||
const attr = {
|
||||
key: 'item-thumb',
|
||||
href: props.link,
|
||||
title: props.title,
|
||||
tabIndex: '-1',
|
||||
'aria-hidden': true,
|
||||
className: 'item-thumb' + (!props.src ? ' no-thumb' : ''),
|
||||
style: !props.src ? null : { backgroundImage: "url('" + props.src + "')" },
|
||||
};
|
||||
|
||||
return <a {...attr}></a>;
|
||||
}
|
||||
|
||||
export function MediaItemAuthor(props) {
|
||||
return '' === props.name ? null : (
|
||||
<span className="item-author">
|
||||
<span>{props.name}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemAuthorLink(props) {
|
||||
return '' === props.name ? null : (
|
||||
<span className="item-author">
|
||||
<a href={props.link} title={props.name}>
|
||||
<span>{props.name}</span>
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemMetaViews(props) {
|
||||
return (
|
||||
<span className="item-views">{formatViewsNumber(props.views) + ' ' + (1 >= props.views ? 'view' : 'views')}</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemMetaDate(props) {
|
||||
return (
|
||||
<span className="item-date">
|
||||
<time dateTime={props.dateTime} content={props.time}>
|
||||
{props.text}
|
||||
</time>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemDuration(props) {
|
||||
return (
|
||||
<span className="item-duration">
|
||||
<span aria-label={props.ariaLabel} content={props.time}>
|
||||
{props.text}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemVideoPreviewer(props) {
|
||||
if ('' === props.url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const src = props.url.split('.').slice(0, -1).join('.');
|
||||
const ext = imageExtension(props.url);
|
||||
|
||||
return <span className="item-img-preview" data-src={src} data-ext={ext}></span>;
|
||||
}
|
||||
|
||||
export function MediaItemVideoPlayer(props) {
|
||||
return (
|
||||
<div className="item-player-wrapper">
|
||||
<div className="item-player-wrapper-inner">
|
||||
<VideoPlayerByPageLink pageLink={props.mediaPageLink} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaItemPlaylistIndex(props) {
|
||||
return (
|
||||
<div className="item-order-number">
|
||||
<div>
|
||||
<div data-order={props.index} data-id={props.media_id}>
|
||||
{props.inPlayback && props.index === props.activeIndex ? (
|
||||
<i className="material-icons">play_arrow</i>
|
||||
) : (
|
||||
props.index
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './includes';
|
||||
export * from './itemClassname';
|
||||
@@ -0,0 +1,13 @@
|
||||
export function itemClassname(defaultClassname, inheritedClassname, isActiveInPlaylistPlayback) {
|
||||
let classname = defaultClassname;
|
||||
|
||||
if ('' !== inheritedClassname) {
|
||||
classname += ' ' + inheritedClassname;
|
||||
}
|
||||
|
||||
if (isActiveInPlaylistPlayback) {
|
||||
classname += ' pl-active-item';
|
||||
}
|
||||
|
||||
return classname;
|
||||
}
|
||||