mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-12-10 14:02:31 -05:00
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
This commit is contained in:
28
frontend/src/static/js/components/list-item/Item.jsx
Normal file
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
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
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
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
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
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
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
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
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
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
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;
|
||||
}
|
||||
8
frontend/src/static/js/components/list-item/index.js
Normal file
8
frontend/src/static/js/components/list-item/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './Item.jsx';
|
||||
export * from './ListItem.jsx';
|
||||
export * from './MediaItem.jsx';
|
||||
export * from './MediaItemAudio.jsx';
|
||||
export * from './MediaItemVideo.jsx';
|
||||
export * from './PlaylistItem.jsx';
|
||||
export * from './TaxonomyItem.jsx';
|
||||
export * from './UserItem.jsx';
|
||||
Reference in New Issue
Block a user