From 3965061bdedae4d05a03e3e2b731ba666ec0bc83 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 27 Aug 2018 17:01:08 +0200 Subject: [PATCH 001/148] CSS: Split into modules Don't place pure styling on top-level files (those that don't begin with underscore). Instead, import them as individual files. --- src/styles/base.sass | 1241 +------------------------ src/styles/blog.sass | 17 + src/styles/components/_alerts.sass | 72 ++ src/styles/components/_base.sass | 32 + src/styles/components/_buttons.sass | 14 + src/styles/components/_card.sass | 23 + src/styles/components/_checkbox.sass | 8 + src/styles/components/_flyout.sass | 25 + src/styles/components/_footer.sass | 108 +++ src/styles/components/_forms.sass | 132 +++ src/styles/components/_inputs.sass | 54 ++ src/styles/components/_jumbotron.sass | 25 + src/styles/components/_navbar.sass | 364 ++++++++ src/styles/components/_overlay.sass | 75 ++ src/styles/components/_popover.sass | 26 + src/styles/components/_search.sass | 92 ++ src/styles/components/_shortcode.sass | 6 + src/styles/components/_statusbar.sass | 21 + src/styles/components/_tooltip.sass | 20 + 19 files changed, 1135 insertions(+), 1220 deletions(-) create mode 100644 src/styles/components/_alerts.sass create mode 100644 src/styles/components/_base.sass create mode 100644 src/styles/components/_buttons.sass create mode 100644 src/styles/components/_card.sass create mode 100644 src/styles/components/_checkbox.sass create mode 100644 src/styles/components/_flyout.sass create mode 100644 src/styles/components/_footer.sass create mode 100644 src/styles/components/_forms.sass create mode 100644 src/styles/components/_inputs.sass create mode 100644 src/styles/components/_jumbotron.sass create mode 100644 src/styles/components/_navbar.sass create mode 100644 src/styles/components/_overlay.sass create mode 100644 src/styles/components/_popover.sass create mode 100644 src/styles/components/_search.sass create mode 100644 src/styles/components/_shortcode.sass create mode 100644 src/styles/components/_statusbar.sass create mode 100644 src/styles/components/_tooltip.sass diff --git a/src/styles/base.sass b/src/styles/base.sass index 64f93ec9..9eeaeeb5 100644 --- a/src/styles/base.sass +++ b/src/styles/base.sass @@ -7,1223 +7,24 @@ @import _comments @import _error - -html - height: 100% - -webkit-font-smoothing: antialiased - -moz-osx-font-smoothing: grayscale - -body - height: 100% - font: - family: $font-body, sans-serif - size: $font-size - color: $color-text-dark - background-color: $color-background - - +media-sm - width: 100% - max-width: 100% - min-width: auto - - +media-xs - width: 100% - max-width: 100% - min-width: auto - -h1, h2, h3, h4, h5 - font: - family: $font-body - weight: 300 - -a - color: $color-primary-dark - &:hover - color: $color-primary - -.container - +media-xs - max-width: 100% - min-width: auto - padding: - left: 0 - right: 0 - - &.box - +container-box - -.container-page - background-color: $color-background - -.page-content - position: relative - flex: 1 - .container-box - +container-box - - -.navbar+.page-content - padding-top: $navbar-height - -.alert+.navbar+.page-content - padding-top: 0 - -.alert - display: block - position: relative - z-index: 16 - width: 100% - margin-bottom: 0 - text-align: center - padding: 15px 20px - border-radius: 0 - border-bottom: thin solid rgba(black, .1) - - // overriden by alert types - color: $color-text-dark - background-color: $color-background - - +clearfix - - &.alert-danger, - &.alert-error - background-color: lighten($color-danger, 35%) - color: $color-danger - .alert-icon, .close - color: $color-danger - - &.alert-warning - background-color: lighten($color-warning, 20%) - color: darken($color-warning, 20%) - .alert-icon, .close - color: darken($color-warning, 20%) - - &.alert-success - background-color: lighten($color-success, 45%) - color: $color-success - - .alert-icon, .close - color: $color-success - - &.alert-info - background-color: lighten($color-info, 30%) - color: darken($color-info, 10%) - .alert-icon, .close - color: darken($color-info, 10%) - - button.close - background: none - border: none - opacity: .5 - position: absolute - top: 5px - right: 10px - padding: 10px - - &:hover - opacity: 1 - - i - font-size: .8em - - i.alert-icon - &:before - font-family: "pillar-font" - padding-right: 10px - - &.success:before - content: '\e801' - &.info:before - content: "\e80c" - &.warning:before - content: "\e80b" - &.danger:before, - &.error:before - content: "\e83d" - - -/* When there's an alert, disable the fixed top */ -.alert+.navbar-fixed-top - position: relative - margin-bottom: 0 - - &+.container - padding-top: 0 - - -/* Navigation */ -header.navbar-header - position: relative - margin: 0 - height: 100% - z-index: 1 // to be on top of the overlay - - .navbar-toggle - color: white - float: none - margin: 5px 0 0 10px - - i - margin: 0 - -.navbar-overlay - bottom: 0 - display: none - left: 0 - height: 100% - position: absolute - right: 0 - top: 0 - transition: background-color 350ms ease-in-out - width: 100% - z-index: 0 - - &+.navbar-container - .search-input - +media-lg - border: thin solid rgba(white, .2) - - #cloud-search, .tt-hint - &::placeholder - color: rgba(white, .8) - - border: thin solid transparent - border-radius: 3px - margin: 8px 0 - - &.is-active - background-color: $color-background-nav - text-shadow: none - - &+.navbar-container - .search-input - +media-sm - background-color: darken($color-background-nav, 6%) - +media-md - background-color: darken($color-background-nav, 6%) - +media-lg - border: thin solid transparent - background-color: darken($color-background-nav, 6%) - - #cloud-search, .tt-hint - &::placeholder - color: rgba(white, .5) - - +media-lg - display: block - - -.navbar-container - align-items: center - display: flex - width: 100% - - .navbar-collapse - +media-xs - padding: 0 - width: 100% - max-height: initial // overrides bs, we don't have many items - - &.show-notifications - display: block - - li - display: none - &.nav-notifications - display: block - position: absolute - top: 7px - left: 0 - width: 100% - z-index: $z-index-base - - #notifications - padding: 0 10px - - -nav - &.navbar - border-radius: 0 - left: 0 - position: fixed - right: 0 - top: 0 - - -nav.navbar, -nav.sidebar - z-index: $z-index-base + 5 /* Flowplayer seems to take up to 11, project container is 12 */ - border: none - display: flex - align-items: center - background-color: $color-background-nav - - nav - margin-left: auto - margin-right: 0 - - .navbar-nav - margin-right: 0 - +media-xs - margin: 0 - width: 100% - - li - user-select: none - - a.navbar-item - align-items: center - color: $color-text-light-secondary - display: flex - height: $navbar-height - transition: color 150ms ease-in-out, box-shadow 100ms ease-in-out - user-select: none - - +media-sm - padding-left: 10px - padding-right: 10px - - &:hover, &:focus - color: $color-text-light - background-color: initial - box-shadow: inset 0 -3px 0 $color-primary - - &:focus - box-shadow: inset 0 -3px 0 $color-primary - - &.active - color: $color-text-light - box-shadow: inset 0 -3px 0 lighten($color-secondary, 10%) - - &:hover - box-shadow: inset 0 -3px 0 lighten($color-secondary, 20%) - - .pi-angle-down - position: relative - left: 5px - - &.nav-item-sign-in - i - padding-right: 6px - font-size: 1.1em - - img.gravatar - border-radius: 999em - height: 32px - width: 32px - box-shadow: 1px 1px 0 rgba(black, .2) - position: relative - - - .special - width: 18px - height: 18px - border-radius: 999em - position: absolute - background-color: white - z-index: 2 - display: inline-block - top: 10px - left: 38px - font-size: 1.2em - box-shadow: 1px 1px 1px rgba(black, .2) - - &.subscriber - background-color: $color-success - color: white - font-size: .6em - - &.demo - background-color: $color-info - color: white - font-size: .6em - - &.none - color: $color-danger - - i - +position-center-translate - - .navbar-brand - +media-xs - padding-left: 10px - align-items: center - display: flex - color: white - padding: - top: 8px - left: 5px - right: 20px // overrides bootstrap - - &:hover - span.app-logo - transform: scale(1.1) - - span.app-logo - font-size: 1.2em - position: relative - top: 3px - transition: transform 150ms ease-in-out - - .dropdown - position: relative - min-width: 60px - - &:hover - background-color: rgba($color-background-nav, .4) - - &.open a - background-color: $color-background-nav - - span.fa-stack - position: absolute - top: 50% - left: 50% - transform: translate(-50%, -50%) - - ul.dropdown-menu - padding: 0 - - li - a - background-color: rgba(white, .3) - text-shadow: none - height: initial - font: - family: $font-body - size: .9em - padding: 5px 15px 5px 5px - color: rgba($color-text, .8) - - &:hover - color: $color-primary - box-shadow: none - - &.subitem - font-size: .8em - padding-top: 0 - text-transform: initial - - i - width: 35px - text-align: center - - &.subscription-status - padding: - top: 10px - bottom: 10px - - transition: opacity 150ms ease-in-out - opacity: 1 - - &:hover - opacity: .8 - - a - text-transform: initial - - &.none a - color: $color-danger - &.subscriber a - color: $color-success - - &.demo a - color: $color-info - - span.info - display: block - - span.renew - display: block - color: $color-text-dark-primary - font-size: .9em - - - &.libraries - ul.dropdown-menu - background-color: lighten($color-background-nav, 5%) - color: white - left: 0 - right: auto - - small - padding-left: 5px - li - a - background-color: transparent - color: $color-text-light-primary - - &:hover - background-color: lighten($color-background-nav, 15%) - - &:last-child a - border-bottom-left-radius: 3px - border-bottom-right-radius: 3px - - &.disabled a - color: $color-text-dark-primary - cursor: default - - -nav.sidebar - ul li.nav-item-sign-in a.navbar-item - font-size: .8em - - -.navbar-backdrop-container - width: 100% - height: 100% - position: absolute - top: 0 - left: 0 - right: 0 - bottom: 0 - - img - display: none - position: fixed - width: 100% - align-self: flex-start - +media-md - display: block - +media-lg - display: block - - -.navbar-backdrop - z-index: 0 - position: absolute - top: 0 - left: 0 - right: 0 - bottom: 0 - - width: 100% - height: 100% - background: - size: 100% - position: 0 0 - repeat: no-repeat - color: transparent - image: url(https://cloud.blender.org/static/assets/img/backgrounds/pattern_02_blur.jpg) - - filter: none - - &.project - display: none - +media-lg - display: block - left: -10px - width: 105% - &.blog - display: none - +media-lg - display: block - left: -10px - width: 105% - +media-md - display: block - left: -10px - width: 105% - - -.navbar-backdrop-overlay - position: absolute - top: 0 - left: 0 - width: 100% - height: $navbar-backdrop-height - background: linear-gradient(to bottom, rgba($color-background, 0) 25%, rgba($color-background,0.5) 50%, rgba($color-background,1) 100%) - - -/* Global, we want all menus to look like this */ -ul.dropdown-menu - background-color: $color-background-light - border: none - box-shadow: 0 10px 25px rgba(black, .1) - user-select: none - - .divider - background-color: rgba(black, .1) - -nav .dropdown:hover ul.dropdown-menu - display: block - -.nav-tabs .dropdown-menu, .nav-pills .dropdown-menu - margin-top: 0 - - -/* FOOTER */ -#footer-container - position: relative - background-color: $color-background - - .row - +media-xs - margin: 0 - -/* Footer Navigation */ -footer - font-size: .75em - padding: 0 0 10px 0 - a - color: $color-text-dark-primary - - &:hover - color: $color-primary - - ul.links - float: left - padding: 0 - margin: 0 - list-style-type: none - - li - padding: 0 15px 0 0 - margin: 0 - float: left - - #hop - display: flex - align-items: center - justify-content: center - visibility: hidden - position: fixed - right: 25px - bottom: 25px - z-index: 999 - cursor: pointer - opacity: 0 - background: $color-background-light - width: 32px - height: 32px - border-radius: 50% - color: $color-text-dark-secondary - font-size: 2em - box-shadow: 0 0 15px rgba(black, .2) - transform: scale(0.5) - transition: all 150ms ease-in-out - - &:hover - transform: scale(1.2) - background-color: $color-background-nav - - &.active - visibility: visible - opacity: 1 - transform: scale(1) - - -#footer-navigation - font-size: .85em - margin-bottom: 5px - color: lighten($color-text, 30%) - border-top: thick solid lighten($color-text, 60%) - padding: - top: 15px - bottom: 15px - - h4 - color: lighten($color-text, 8%) - margin-bottom: 10px - - a - color: $color-text-dark-primary - - ul - position: relative - margin: 0 - padding: 0 0 10px 0 - list-style-type: none - - li - position: relative - padding: 0 - margin: 0 - - a - color: lighten($color-text, 35%) - - &:hover - color: $color-primary - - .footer-links - i - font-size: 80% - position: absolute - left: -14px - top: 20% - - .special - padding: - top: 10px - bottom: 15px - font-size: .9em - border-left: thin solid darken($color-background, 20%) - - - img - max-width: 100% - opacity: .6 - - ul.footer-social - width: 100% - text-align:center - margin: 0 auto - display: flex - align-items: center - justify-content: space-around - - li - display: inline-block - padding: 30px 0 - - i - font-size: 3em - - -.btn-outline - background-color: transparent - border-width: 1px - transition: background-color .1s - - &:focus, &:active - box-shadow: none - -.btn-empty - background-color: transparent - border-color: transparent - - &:focus, &:active - box-shadow: none - - -.tooltip - transition: opacity 50ms ease-in-out - &.in - opacity: 1 - - .tooltip-inner - max-width: auto - white-space: nowrap - background-color: $color-background-nav-light - color: $color-text-light-primary - border-radius: 3px - - &.top .tooltip-arrow - border-top-color: $color-background-nav-light - &.bottom .tooltip-arrow - border-bottom-color: $color-background-nav-light - &.left .tooltip-arrow - border-left-color: $color-background-nav-light - &.right .tooltip-arrow - border-right-color: $color-background-nav-light - -.popover - background-color: lighten($color-background-nav, 5%) - border-radius: 3px - box-shadow: 1px 1px 2px rgba(black, .2) - border: thin solid lighten($color-background-nav, 10%) - - &.in - opacity: 1 - - .popover-title - background-color: lighten($color-background-nav, 10%) - border-bottom: thin solid lighten($color-background-nav, 3%) - color: $color-text-light-primary - font-family: $font-body - - .popover-content - color: $color-text-light - font-size: .9em - - &.top .arrow:after - border-top-color: lighten($color-background-nav, 5%) - &.bottom .arrow:after - border-bottom-color: lighten($color-background-nav, 5%) - &.left .arrow:after - border-left-color: lighten($color-background-nav, 5%) - &.right .arrow:after - border-right-color: lighten($color-background-nav, 5%) - -/* Inputs */ -input, input.form-control, -textarea, textarea.form-control, -select, select.form-control - +input-generic - -label, label.control-label - +label-generic - -select, select.form-control - border-top-left-radius: 3px - border-top-right-radius: 3px - background-color: $color-background-light - - option - background-color: white - -input.fileupload - background-color: transparent - display: block - margin-top: 10px - -textarea - resize: vertical - -button, .btn - +button($color-text-dark-secondary, $color-background, false) - - &-success - +button($color-success, $color-success, false) - &-warning - +button($color-warning, $color-warning, false) - &-danger - +button($color-danger, $color-danger, false) - &-info - +button($color-info, $color-info, false) - - - &.disabled - +button($color-background-dark, $color-background, false) - background-color: $color-background-light !important - border-color: $color-background-dark !important - color: $color-text !important - opacity: .5 !important - pointer-events: none !important - text-shadow: none !important - user-select: none !important - -.input-group-flex - display: flex - -.input-group-separator - margin: 10px 0 - border-top: thin solid $color-background - -/* File Upload forms */ -.fieldlist - list-style: none - padding: 0 - margin: 10px 0 0 0 - - li.fieldlist-item - background-color: $color-background-light - border: thin solid $color-background - border-left: 3px solid $color-primary - border-top-right-radius: 3px - border-bottom-right-radius: 3px - - margin-bottom: 10px - padding: 10px - +clearfix - - .form-group - margin-bottom: 0 !important // override bs - width: 100% - - input.form-control - background-color: white !important - padding: 0 10px !important - border: thin solid $color-background-dark !important - - div[class$="slug"] - width: 50% - float: left - display: flex - align-items: center - - label - margin-right: 10px - - .fieldlist-action-button - +button($color-success, 3px) - margin: 0 0 0 10px - padding: 5px 10px - text-transform: initial - -.form-upload-file - margin-bottom: 10px - display: flex - flex-direction: column - - .form-upload-progress - margin-top: 10px - - .form-upload-progress-bar - margin-top: 5px - background-color: $color-success - height: 5px - min-width: 0 - border-radius: 3px - - &.progress-uploading - background-color: hsl(hue($color-success), 80%, 65%) !important - - &.progress-processing - +stripes($color-success, lighten($color-success, 15%), -45deg, 25px) - +stripes-animate - animation-duration: 1s - - &.progress-error - background-color: $color-danger !important - - .preview-thumbnail - width: 50px - height: 50px - min-width: 50px - min-height: 50px - margin-right: 10px - margin-top: 5px - border-radius: 3px - background-color: $color-background - - .form-upload-file-meta-container - display: flex - - .form-upload-file-meta - list-style: none - padding: 0 - margin: 0 - width: 100% - display: flex - flex-wrap: wrap - flex: 1 - - li - display: inline-block - padding: 5px 10px - - &:first-child - padding-left: 0 - - &.dimensions, &.size - color: $color-text-dark-secondary - - &.delete - margin-left: auto - - &.name - +text-overflow-ellipsis - - .file_delete - color: $color-danger - - .form-upload-file-actions - list-style: none - padding: 0 - margin: 0 - display: flex - flex-wrap: wrap - - li - display: inline-block - padding: 5px 10px - - .file_delete - color: $color-danger - -.form-group - &.error - .form-control, input - border-color: $color-danger !important - - ul.error - padding: 5px 0 0 0 - margin: 0 - color: $color-danger - list-style-type: none - -.checkbox label label - padding-left: 0 - -.checkbox label input[type=checkbox] + label - transition: color 100ms ease-in-out - -.checkbox label input[type=checkbox]:checked + label - color: $color-success !important - - - -/* Flyouts (only used on notifications for now) */ -.flyout - background-color: $color-background - border-radius: 3px - border: thin solid darken($color-background, 3%) - box-shadow: 1px 2px 2px rgba(black, .2) - display: block - font-size: .9em - - & .flyout-title - cursor: default - display: block - float: left - font-size: 1.1em - font-weight: 600 - padding: 8px 10px 5px 10px - - &.notifications - max-height: 1000% - overflow-x: hidden - position: absolute - right: 0 - top: 60px - width: 420px - z-index: 9999 - - -/* Font aliases */ -.pi /* blank item with the right spacing */ - &:after - content: '' - font-family: "pillar-font" - font-style: normal - font-weight: normal - speak: none - display: inline-block - text-decoration: inherit - width: 1em - margin-right: .2em - text-align: center - font-variant: normal - text-transform: none - line-height: 1em - margin-left: .2em - -webkit-font-smoothing: antialiased - -moz-osx-font-smoothing: grayscale - position: relative - - &:before - position: relative - -.pi-license-cc-zero:before - content: '\e85a' -.pi-license-cc-sa:before - content: '\e858' -.pi-license-cc-nd:before - content: '\e859' -.pi-license-cc-nc:before - content: '\e857' - -.pi-license-cc-0 - @extend .pi-license-cc-zero - position: relative - top: 1px -.pi-license-cc-by-sa - @extend .pi-license-cc-sa -.pi-license-cc-by-nd - @extend .pi-license-cc-nd -.pi-license-cc-by-nc - @extend .pi-license-cc-nc - -.pi-license-cc-by-sa, -.pi-license-cc-by-nd, -.pi-license-cc-by-nc - @extend .pi - - &:after - content: '\e807' - left: -27px - - &:before - left: 27px - -#search-overlay - position: absolute - top: 0 - left: 0 - right: 0 - bottom: 0 - width: 100% - height: 100% - pointer-events: none - visibility: hidden - opacity: 0 - z-index: $z-index-base + 4 - transition: opacity 150ms ease-in-out - - &.active - opacity: 1 - visibility: visible - background-color: rgba($color-background-nav, .7) - -.search-input - +media-lg - max-width: 350px - +media-md - max-width: 350px - +media-sm - max-width: 120px - +media-xs - display: block - margin: 0 10px - position: absolute - z-index: $z-index-base - right: 5px - position: relative - float: left - padding: 0 - margin: 0 - - - .search-icon - position: absolute - color: white - top: 4px - left: 10px - cursor: pointer - - &:after - opacity: 0 - content: 'Use advanced search...' - font-style: normal - background: darken($color-background-nav, 5%) - color: $color-text-light-primary - box-shadow: 1px 1px 3px rgba(black, .4) - padding: 3px 10px - font-size: .85em - border-radius: 3px - top: 30px - left: -10px - width: 150px - position: absolute - transition: top 150ms ease-in-out, opacity 150ms ease-in-out - pointer-events: none - - &:hover - &:after - opacity: 1 - top: 35px - - - #cloud-search, .tt-hint - +text-overflow-ellipsis - border: thin solid $color-background-nav-light - border-radius: 3px - color: white - font: - size: 1em - weight: 400 - family: $font-body - margin: 0 - min-height: 32px - outline: none - padding: 0 20px 0 40px - transition: border 100ms ease-in-out - - &:focus - background-color: $color-background-nav-light - outline: none - - &::placeholder - color: rgba(white, .5) - transition: color 150ms ease-in-out - - &:hover - &::placeholder - color: rgba(white, .6) - -#page-overlay - background-color: rgba(black, .8) - position: fixed - top: 0 - bottom: 0 - right: 0 - left: 0 - z-index: $z-index-base + 15 - visibility: hidden - opacity: 0 - transition: opacity 150ms ease-in-out - - display: flex - align-items: center - justify-content: center - - img - user-select: none - display: block - max-height: 96% - max-width: 96% - z-index: 0 - - box-shadow: 0 0 15px rgba(black, .2), 0 0 100px rgba(black, .5) - p.caption - position: absolute - bottom: 1% - - &.active - visibility: visible - opacity: 1 - - .no-preview - user-select: none - z-index: 0 - color: $color-text-light-secondary - - .nav-prev, .nav-next - display: block - font: - family: 'pillar-font' - size: 2em - height: 80% - width: 50px - cursor: pointer - color: $color-text-light-secondary - z-index: 1 - +position-center-translate - - &:hover - color: $color-text-light - - &:before, &:after - +position-center-translate - - .nav-prev - left: 50px - &:before - content: '\e839' - - .nav-next - left: initial - right: 0 - &:before - content: '\e83a' - - - &.video - .video-embed - +position-center-translate - position: fixed - - iframe - width: 853px - height: 480px - -#status-bar - opacity: 0 - transition: all 250ms ease-in-out - - i - margin-right: 5px - - &.info - color: $color-info - &.error - color: $color-danger - &.warning - color: $color-warning - &.success - color: $color-success - &.default - color: $color-text-light - - &.active - opacity: 1 - -p.shortcode.nocap - padding: 0.6em 3em - font-size: .8em - color: $color-text-dark-primary - background-color: $color-background-dark - border-radius: 3px +@import components/base +@import components/alerts +@import components/navbar +@import components/footer +@import components/shortcode +@import components/statusbar +@import components/search +@import components/flyout +@import components/forms +@import components/inputs +@import components/buttons +@import components/popover +@import components/tooltip +@import components/checkbox +@import components/overlay + +/* Top level, standalone stylesheets (not starting with _ so not meant for importing) + * should not have pure styling here. + * They should be imported as components/modules. + * e.g: @import _notifications + */ diff --git a/src/styles/blog.sass b/src/styles/blog.sass index 42970519..ff84c52e 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -2,10 +2,27 @@ @import _config @import _utils +@import _apps_base @import _comments @import _error @import _search +@import components/base +@import components/alerts +@import components/navbar +@import components/footer +@import components/shortcode +@import components/statusbar +@import components/search +@import components/flyout +@import components/forms +@import components/inputs +@import components/buttons +@import components/popover +@import components/tooltip +@import components/checkbox +@import components/overlay + .container-fluid.blog padding: 0 diff --git a/src/styles/components/_alerts.sass b/src/styles/components/_alerts.sass new file mode 100644 index 00000000..2877b4e9 --- /dev/null +++ b/src/styles/components/_alerts.sass @@ -0,0 +1,72 @@ +.alert + margin-bottom: 0 + text-align: center + padding: 10px 20px + z-index: 16 + + // overriden by alert types + color: $color-text-dark + background-color: $color-background + + &.alert-danger, + &.alert-error + background-color: lighten($color-danger, 35%) + color: $color-danger + .alert-icon, .close + color: $color-danger + + &.alert-warning + background-color: lighten($color-warning, 20%) + color: darken($color-warning, 20%) + .alert-icon, .close + color: darken($color-warning, 20%) + + &.alert-success + background-color: lighten($color-success, 45%) + color: $color-success + + .alert-icon, .close + color: $color-success + + &.alert-info + background-color: lighten($color-info, 30%) + color: darken($color-info, 10%) + .alert-icon, .close + color: darken($color-info, 10%) + + button.close + position: absolute + right: 10px + + i + font-size: .8em + + i.alert-icon + &:before + font-family: "pillar-font" + padding-right: 10px + + &.success:before + content: '\e801' + &.info:before + content: "\e80c" + &.warning:before + content: "\e80b" + &.danger:before, + &.error:before + content: "\e83d" + + +/* When there's an alert, disable the fixed top */ +.alert+.navbar-fixed-top + position: relative + margin-bottom: 0 + + &+.container + padding-top: 0 + +.alert+.navbar + position: relative + +.alert+.navbar+.page-content + padding-top: 0 diff --git a/src/styles/components/_base.sass b/src/styles/components/_base.sass new file mode 100644 index 00000000..027f1b92 --- /dev/null +++ b/src/styles/components/_base.sass @@ -0,0 +1,32 @@ +body + height: 100% + + +media-sm + width: 100% + max-width: 100% + min-width: auto + + +media-xs + width: 100% + max-width: 100% + min-width: auto + +.container + +media-xs + max-width: 100% + min-width: auto + padding: + left: 0 + right: 0 + + &.box + +container-box + +.container-page + background-color: white + +.page-content + position: relative + flex: 1 + .container-box + +container-box diff --git a/src/styles/components/_buttons.sass b/src/styles/components/_buttons.sass new file mode 100644 index 00000000..ff0e0163 --- /dev/null +++ b/src/styles/components/_buttons.sass @@ -0,0 +1,14 @@ +.btn-outline + background-color: transparent + border-width: 1px + transition: background-color .1s + + &:focus, &:active + box-shadow: none + +.btn-empty + background-color: transparent + border-color: transparent + + &:focus, &:active + box-shadow: none diff --git a/src/styles/components/_card.sass b/src/styles/components/_card.sass new file mode 100644 index 00000000..edb993fc --- /dev/null +++ b/src/styles/components/_card.sass @@ -0,0 +1,23 @@ +.card-deck + // Custom, as of bootstrap 4.1.3 there is no way to do this. + &.card-3-columns + .card + min-width: 30% + max-width: 30% + + +.card-padless + .card + border: none + + .card-body + padding: 20px 0 + +.card-fade + img + opacity: .8 + transition: opacity ease-in-out 150ms + + &:hover + img + opacity: 1 diff --git a/src/styles/components/_checkbox.sass b/src/styles/components/_checkbox.sass new file mode 100644 index 00000000..13d66285 --- /dev/null +++ b/src/styles/components/_checkbox.sass @@ -0,0 +1,8 @@ +.checkbox label label + padding-left: 0 + +.checkbox label input[type=checkbox] + label + transition: color 100ms ease-in-out + +.checkbox label input[type=checkbox]:checked + label + color: $color-success !important diff --git a/src/styles/components/_flyout.sass b/src/styles/components/_flyout.sass new file mode 100644 index 00000000..4e527916 --- /dev/null +++ b/src/styles/components/_flyout.sass @@ -0,0 +1,25 @@ +/* Flyouts (only used on notifications for now) */ +.flyout + background-color: $color-background + border-radius: 3px + border: thin solid darken($color-background, 3%) + box-shadow: 1px 2px 2px rgba(black, .2) + display: block + font-size: .9em + + & .flyout-title + cursor: default + display: block + float: left + font-size: 1.1em + font-weight: 600 + padding: 8px 10px 5px 10px + + &.notifications + max-height: 1000% + overflow-x: hidden + position: absolute + right: 0 + top: 60px + width: 420px + z-index: 9999 diff --git a/src/styles/components/_footer.sass b/src/styles/components/_footer.sass new file mode 100644 index 00000000..9af3ba97 --- /dev/null +++ b/src/styles/components/_footer.sass @@ -0,0 +1,108 @@ +/* FOOTER */ +#footer-container + position: relative + + .row + +media-xs + margin: 0 + +/* Footer Navigation */ +footer + font-size: .75em + padding: 0 0 10px 0 + a + color: $color-text-dark-primary + + &:hover + color: $color-primary + + ul.links + float: left + padding: 0 + margin: 0 + list-style-type: none + + li + padding: 0 15px 0 0 + margin: 0 + float: left + +#hop + display: flex + align-items: center + justify-content: center + visibility: hidden + position: fixed + right: 25px + bottom: 25px + z-index: 999 + cursor: pointer + opacity: 0 + background: $color-background-light + width: 32px + height: 32px + border-radius: 50% + color: $color-text-dark-secondary + font-size: 2em + box-shadow: 0 0 15px rgba(black, .2) + transform: scale(0.5) + transition: all 150ms ease-in-out + + &:hover + transform: scale(1.2) + background-color: $color-background-nav + + &.active + visibility: visible + opacity: 1 + transform: scale(1) + + +#footer-navigation + font-size: .85em + margin-bottom: 5px + color: lighten($color-text, 30%) + border-top: thick solid lighten($color-text, 60%) + padding: + top: 15px + bottom: 15px + + a + color: lighten($color-text, 35%) + + &:hover + color: $color-primary + + .footer-links + i + font-size: 80% + position: absolute + left: -14px + top: 20% + + .special + padding: + top: 10px + bottom: 15px + font-size: .9em + border-left: thin solid darken($color-background, 20%) + + + img + max-width: 100% + opacity: .6 + + ul.footer-social + width: 100% + text-align:center + margin: 0 auto + display: flex + align-items: center + justify-content: space-around + + li + display: inline-block + padding: 30px 0 + + i + font-size: 3em diff --git a/src/styles/components/_forms.sass b/src/styles/components/_forms.sass new file mode 100644 index 00000000..60b4b059 --- /dev/null +++ b/src/styles/components/_forms.sass @@ -0,0 +1,132 @@ +/* File Upload forms */ +.fieldlist + list-style: none + padding: 0 + margin: 10px 0 0 0 + + li.fieldlist-item + background-color: $color-background-light + border: thin solid $color-background + border-left: 3px solid $color-primary + border-top-right-radius: 3px + border-bottom-right-radius: 3px + + margin-bottom: 10px + padding: 10px + +clearfix + + .form-group + margin-bottom: 0 !important // override bs + width: 100% + + input.form-control + background-color: white !important + padding: 0 10px !important + border: thin solid $color-background-dark !important + + div[class$="slug"] + width: 50% + float: left + display: flex + align-items: center + + label + margin-right: 10px + + .fieldlist-action-button + +button($color-success, 3px) + margin: 0 0 0 10px + padding: 5px 10px + text-transform: initial + +.form-upload-file + margin-bottom: 10px + display: flex + flex-direction: column + + .form-upload-progress + margin-top: 10px + + .form-upload-progress-bar + margin-top: 5px + background-color: $color-success + height: 5px + min-width: 0 + border-radius: 3px + + &.progress-uploading + background-color: hsl(hue($color-success), 80%, 65%) !important + + &.progress-processing + +stripes($color-success, lighten($color-success, 15%), -45deg, 25px) + +stripes-animate + animation-duration: 1s + + &.progress-error + background-color: $color-danger !important + + .preview-thumbnail + width: 50px + height: 50px + min-width: 50px + min-height: 50px + margin-right: 10px + margin-top: 5px + border-radius: 3px + background-color: $color-background + + .form-upload-file-meta-container + display: flex + + .form-upload-file-meta + list-style: none + padding: 0 + margin: 0 + width: 100% + display: flex + flex-wrap: wrap + flex: 1 + + li + display: inline-block + padding: 5px 10px + + &:first-child + padding-left: 0 + + &.dimensions, &.size + color: $color-text-dark-secondary + + &.delete + margin-left: auto + + &.name + +text-overflow-ellipsis + + .file_delete + color: $color-danger + + .form-upload-file-actions + list-style: none + padding: 0 + margin: 0 + display: flex + flex-wrap: wrap + + li + display: inline-block + padding: 5px 10px + + .file_delete + color: $color-danger + +.form-group + &.error + .form-control, input + border-color: $color-danger !important + + ul.error + padding: 5px 0 0 0 + margin: 0 + color: $color-danger + list-style-type: none diff --git a/src/styles/components/_inputs.sass b/src/styles/components/_inputs.sass new file mode 100644 index 00000000..11dcd2f7 --- /dev/null +++ b/src/styles/components/_inputs.sass @@ -0,0 +1,54 @@ +/* Inputs */ +input, input.form-control, +textarea, textarea.form-control, +select, select.form-control + +input-generic + +label, label.control-label + +label-generic + +select, select.form-control + border-top-left-radius: 3px + border-top-right-radius: 3px + background-color: $color-background-light + + option + background-color: white + +input.fileupload + background-color: transparent + display: block + margin-top: 10px + +textarea + resize: vertical + +button, .btn + +button($color-text-dark-secondary, $color-background, false) + + &-success + +button($color-success, $color-success, false) + &-warning + +button($color-warning, $color-warning, false) + &-danger + +button($color-danger, $color-danger, false) + &-info + +button($color-info, $color-info, false) + + + &.disabled + +button($color-background-dark, $color-background, false) + background-color: $color-background-light !important + border-color: $color-background-dark !important + color: $color-text !important + opacity: .5 !important + pointer-events: none !important + text-shadow: none !important + user-select: none !important + +.input-group-flex + display: flex + +.input-group-separator + margin: 10px 0 + border-top: thin solid $color-background diff --git a/src/styles/components/_jumbotron.sass b/src/styles/components/_jumbotron.sass new file mode 100644 index 00000000..985ed80d --- /dev/null +++ b/src/styles/components/_jumbotron.sass @@ -0,0 +1,25 @@ +// Mainly overrides bootstrap jumbotron settings +.jumbotron + background-size: cover + border-radius: 0 + padding-top: 12em + padding-bottom: 12em + + // Black-transparent gradient from left to right to better read the overlay text. + &.jumbotron-overlay + position: relative + + &:after + background-image: linear-gradient(45deg, rgba(black, .5) 25%, transparent 50%) + bottom: 0 + content: '' + left: 0 + position: absolute + right: 0 + top: 0 + + * + z-index: 1 + + h2, p + text-shadow: 1px 1px rgba(black, .2), 1px 1px 25px rgba(black, .5) diff --git a/src/styles/components/_navbar.sass b/src/styles/components/_navbar.sass new file mode 100644 index 00000000..a6c846cc --- /dev/null +++ b/src/styles/components/_navbar.sass @@ -0,0 +1,364 @@ +/* Navigation */ +.navbar-overlay + bottom: 0 + display: none + left: 0 + height: 100% + position: absolute + right: 0 + top: 0 + transition: background-color 350ms ease-in-out + width: 100% + z-index: 0 + + &+.navbar-container + .search-input + +media-lg + border: thin solid rgba(white, .2) + + #cloud-search, .tt-hint + &::placeholder + color: rgba(white, .8) + + border: thin solid transparent + border-radius: 3px + margin: 8px 0 + + &.is-active + background-color: $color-background-nav + text-shadow: none + + &+.navbar-container + .search-input + +media-sm + background-color: darken($color-background-nav, 6%) + +media-md + background-color: darken($color-background-nav, 6%) + +media-lg + border: thin solid transparent + background-color: darken($color-background-nav, 6%) + + #cloud-search, .tt-hint + &::placeholder + color: rgba(white, .5) + + +media-lg + display: block + + +.navbar-container + align-items: center + display: flex + width: 100% + + .navbar-collapse + +media-xs + padding: 0 + width: 100% + max-height: initial // overrides bs, we don't have many items + + &.show-notifications + display: block + + li + display: none + &.nav-notifications + display: block + position: absolute + top: 7px + left: 0 + width: 100% + z-index: $z-index-base + + #notifications + padding: 0 10px + + +nav + &.navbar + border-radius: 0 + left: 0 + position: fixed + right: 0 + top: 0 + +.navbar-brand + padding-left: 10px + color: $color-text + + width: 120px // Blender Cloud logo width + + &:hover + color: $color-text-dark-primary + +nav.navbar, +nav.sidebar + align-items: center + background-color: $color-background-nav + border: none + display: flex + padding: 0 + z-index: $z-index-base + 5 /* Flowplayer seems to take up to 11, project container is 12 */ + + nav + margin-left: auto + margin-right: 0 + + .navbar-nav + margin-right: 0 + +media-xs + margin: 0 + width: 100% + + li + user-select: none + position: relative + + a.navbar-item + align-items: center + color: $color-text + display: flex + user-select: none + padding: $navbar-brand-padding-y + height: 100% + transition: color 150ms ease-in-out, box-shadow 100ms ease-in-out + + +media-sm + padding-left: 10px + padding-right: 10px + + &:hover, &:focus + color: $color-primary + background-color: transparent + box-shadow: inset 0 -3px 0 $color-primary + text-decoration: none + + &:focus + box-shadow: inset 0 -3px 0 $color-primary + + &.active + color: $color-primary + box-shadow: inset 0 -3px 0 lighten($color-secondary, 10%) + + &:hover + box-shadow: inset 0 -3px 0 lighten($color-secondary, 20%) + + .pi-angle-down + position: relative + left: 5px + + &.nav-item-sign-in + i + padding-right: 6px + font-size: 1.1em + + img.gravatar + border-radius: 999em + height: 32px + width: 32px + box-shadow: 1px 1px 0 rgba(black, .2) + position: relative + + + .special + width: 18px + height: 18px + border-radius: 999em + position: absolute + background-color: white + z-index: 2 + display: inline-block + top: 10px + left: 38px + font-size: 1.2em + box-shadow: 1px 1px 1px rgba(black, .2) + + &.subscriber + background-color: $color-success + color: white + font-size: .6em + + &.demo + background-color: $color-info + color: white + font-size: .6em + + &.none + color: $color-danger + + i + +position-center-translate + + .dropdown + position: relative + min-width: 60px + + // Removes angle-down icon from bootstrap, + // since we use a nicer chevron. + a:after + display: none + + span.fa-stack + position: absolute + top: 50% + left: 50% + transform: translate(-50%, -50%) + + ul.dropdown-menu + padding: 0 + + li + a + padding: 5px 15px 5px 5px + + &:hover + color: $color-primary + box-shadow: none + + &.subitem + font-size: .8em + padding-top: 0 + text-transform: initial + + i + width: 35px + text-align: center + + &.subscription-status + padding: + top: 10px + bottom: 10px + + transition: opacity 150ms ease-in-out + opacity: 1 + + &:hover + opacity: .8 + + a + text-transform: initial + + &.none a + color: $color-danger + &.subscriber a + color: $color-success + + &.demo a + color: $color-info + + span.info + display: block + + span.renew + display: block + color: $color-text-dark-primary + font-size: .9em + + +nav.sidebar + ul li.nav-item-sign-in a.navbar-item + font-size: .8em + + +.navbar-backdrop-container + width: 100% + height: 100% + position: absolute + top: 0 + left: 0 + right: 0 + bottom: 0 + + img + display: none + position: fixed + width: 100% + align-self: flex-start + +media-md + display: block + +media-lg + display: block + + +.navbar-backdrop + z-index: 0 + position: absolute + top: 0 + left: 0 + right: 0 + bottom: 0 + + width: 100% + height: 100% + background: + size: 100% + position: 0 0 + repeat: no-repeat + color: transparent + image: url(https://cloud.blender.org/static/assets/img/backgrounds/pattern_02_blur.jpg) + + filter: none + + &.project + display: none + +media-lg + display: block + left: -10px + width: 105% + &.blog + display: none + +media-lg + display: block + left: -10px + width: 105% + +media-md + display: block + left: -10px + width: 105% + + +.navbar-backdrop-overlay + position: absolute + top: 0 + left: 0 + width: 100% + height: $navbar-backdrop-height + background: linear-gradient(to bottom, rgba($color-background, 0) 25%, rgba($color-background,0.5) 50%, rgba($color-background,1) 100%) + + +/* Global, we want all menus to look like this */ +ul.dropdown-menu + background-color: $color-background-light + border: none + box-shadow: 0 10px 25px rgba(black, .1) + user-select: none + + .divider + background-color: rgba(black, .1) + +nav .dropdown:hover ul.dropdown-menu + display: block + +.nav-tabs .dropdown-menu, .nav-pills .dropdown-menu + margin-top: 0 + +.navbar+.page-content + padding-top: $navbar-height + + +// Secondary navigation for +.nav-secondary + align-items: center + box-shadow: 0 2px 0 0 $color-background + + .nav-link + color: $color-text + transition: box-shadow 150ms ease-in-out + + &:hover, + &.active + box-shadow: 0 2px 0 0 $color-primary + + .nav-title + font-weight: bold + padding-right: 20px diff --git a/src/styles/components/_overlay.sass b/src/styles/components/_overlay.sass new file mode 100644 index 00000000..32d9e29d --- /dev/null +++ b/src/styles/components/_overlay.sass @@ -0,0 +1,75 @@ +#page-overlay + background-color: rgba(black, .8) + position: fixed + top: 0 + bottom: 0 + right: 0 + left: 0 + z-index: $z-index-base + 15 + visibility: hidden + opacity: 0 + transition: opacity 150ms ease-in-out + + display: flex + align-items: center + justify-content: center + + img + user-select: none + display: block + max-height: 96% + max-width: 96% + z-index: 0 + + box-shadow: 0 0 15px rgba(black, .2), 0 0 100px rgba(black, .5) + p.caption + position: absolute + bottom: 1% + + &.active + visibility: visible + opacity: 1 + + .no-preview + user-select: none + z-index: 0 + color: $color-text-light-secondary + + .nav-prev, .nav-next + display: block + font: + family: 'pillar-font' + size: 2em + height: 80% + width: 50px + cursor: pointer + color: $color-text-light-secondary + z-index: 1 + +position-center-translate + + &:hover + color: $color-text-light + + &:before, &:after + +position-center-translate + + .nav-prev + left: 50px + &:before + content: '\e839' + + .nav-next + left: initial + right: 0 + &:before + content: '\e83a' + + + &.video + .video-embed + +position-center-translate + position: fixed + + iframe + width: 853px + height: 480px diff --git a/src/styles/components/_popover.sass b/src/styles/components/_popover.sass new file mode 100644 index 00000000..5e6ab300 --- /dev/null +++ b/src/styles/components/_popover.sass @@ -0,0 +1,26 @@ +.popover + background-color: lighten($color-background-nav, 5%) + border-radius: 3px + box-shadow: 1px 1px 2px rgba(black, .2) + border: thin solid lighten($color-background-nav, 10%) + + &.in + opacity: 1 + + .popover-title + background-color: lighten($color-background-nav, 10%) + border-bottom: thin solid lighten($color-background-nav, 3%) + color: $color-text-light-primary + + .popover-content + color: $color-text-light + font-size: .9em + + &.top .arrow:after + border-top-color: lighten($color-background-nav, 5%) + &.bottom .arrow:after + border-bottom-color: lighten($color-background-nav, 5%) + &.left .arrow:after + border-left-color: lighten($color-background-nav, 5%) + &.right .arrow:after + border-right-color: lighten($color-background-nav, 5%) diff --git a/src/styles/components/_search.sass b/src/styles/components/_search.sass new file mode 100644 index 00000000..929ecb8d --- /dev/null +++ b/src/styles/components/_search.sass @@ -0,0 +1,92 @@ +#search-overlay + position: absolute + top: 0 + left: 0 + right: 0 + bottom: 0 + width: 100% + height: 100% + pointer-events: none + visibility: hidden + opacity: 0 + z-index: $z-index-base + 4 + transition: opacity 150ms ease-in-out + + &.active + opacity: 1 + visibility: visible + background-color: rgba($color-background-nav, .7) + +.search-input + +media-lg + max-width: 350px + +media-md + max-width: 350px + +media-sm + max-width: 120px + +media-xs + display: block + margin: 0 10px + position: absolute + z-index: $z-index-base + right: 5px + position: relative + float: left + padding: 0 + margin: 0 + + + .search-icon + position: absolute + color: white + top: 4px + left: 10px + cursor: pointer + + &:after + opacity: 0 + content: 'Use advanced search...' + font-style: normal + background: darken($color-background-nav, 5%) + color: $color-text-light-primary + box-shadow: 1px 1px 3px rgba(black, .4) + padding: 3px 10px + font-size: .85em + border-radius: 3px + top: 30px + left: -10px + width: 150px + position: absolute + transition: top 150ms ease-in-out, opacity 150ms ease-in-out + pointer-events: none + + &:hover + &:after + opacity: 1 + top: 35px + + + #cloud-search, .tt-hint + +text-overflow-ellipsis + border: thin solid $color-background + border-radius: 3px + font: + size: 1em + weight: 400 + margin: 0 + min-height: 32px + outline: none + padding: 0 20px 0 40px + transition: border 100ms ease-in-out + + &:focus + box-shadow: none + border: none + + &::placeholder + color: rgba($color-text, .5) + transition: color 150ms ease-in-out + + &:hover + &::placeholder + color: rgba($color-text, .6) diff --git a/src/styles/components/_shortcode.sass b/src/styles/components/_shortcode.sass new file mode 100644 index 00000000..aa807f24 --- /dev/null +++ b/src/styles/components/_shortcode.sass @@ -0,0 +1,6 @@ +p.shortcode.nocap + padding: 0.6em 3em + font-size: .8em + color: $color-text-dark-primary + background-color: $color-background-dark + border-radius: 3px diff --git a/src/styles/components/_statusbar.sass b/src/styles/components/_statusbar.sass new file mode 100644 index 00000000..02d872d9 --- /dev/null +++ b/src/styles/components/_statusbar.sass @@ -0,0 +1,21 @@ +#status-bar + opacity: 0 + transition: all 250ms ease-in-out + + i + margin-right: 5px + + &.info + color: $color-info + &.error + color: $color-danger + &.warning + color: $color-warning + &.success + color: $color-success + &.default + color: $color-text-light + + &.active + opacity: 1 + diff --git a/src/styles/components/_tooltip.sass b/src/styles/components/_tooltip.sass new file mode 100644 index 00000000..3a317919 --- /dev/null +++ b/src/styles/components/_tooltip.sass @@ -0,0 +1,20 @@ +.tooltip + transition: opacity 50ms ease-in-out + &.in + opacity: 1 + + .tooltip-inner + max-width: auto + white-space: nowrap + background-color: $color-background-nav-light + color: $color-text-light-primary + border-radius: 3px + + &.top .tooltip-arrow + border-top-color: $color-background-nav-light + &.bottom .tooltip-arrow + border-bottom-color: $color-background-nav-light + &.left .tooltip-arrow + border-left-color: $color-background-nav-light + &.right .tooltip-arrow + border-right-color: $color-background-nav-light From 2ded541955a0f5ae36646fb64fd229aa94b91521 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 27 Aug 2018 17:01:43 +0200 Subject: [PATCH 002/148] CSS Cleanup: remove font-body specifics --- src/styles/_notifications.sass | 1 - src/styles/_project-dashboard.sass | 3 +-- src/styles/_project-sharing.sass | 4 ---- src/styles/_project.sass | 10 +--------- src/styles/_search.sass | 5 +---- src/styles/_utils.sass | 6 +++--- src/styles/blog.sass | 6 +----- 7 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/styles/_notifications.sass b/src/styles/_notifications.sass index 7dbf18a0..869d3cae 100644 --- a/src/styles/_notifications.sass +++ b/src/styles/_notifications.sass @@ -9,7 +9,6 @@ color: $color-primary cursor: pointer float: right - font-family: $font-body height: initial margin: 0 padding: 8px 10px 0 10px diff --git a/src/styles/_project-dashboard.sass b/src/styles/_project-dashboard.sass index c07c0dca..f63b1b50 100644 --- a/src/styles/_project-dashboard.sass +++ b/src/styles/_project-dashboard.sass @@ -287,9 +287,8 @@ flex-direction: column .title - font-size: 1.2em - padding-bottom: 2px color: $color-text-dark-primary + padding-bottom: 2px ul.meta font-size: .9em diff --git a/src/styles/_project-sharing.sass b/src/styles/_project-sharing.sass index 40a2e7be..419e767b 100644 --- a/src/styles/_project-sharing.sass +++ b/src/styles/_project-sharing.sass @@ -92,10 +92,6 @@ ul.sharing-users-list &:hover color: lighten($color-danger, 10%) -.sharing-users-intro, -.sharing-users-info - h4 - font-family: $font-body .sharing-users-info padding-left: 15px diff --git a/src/styles/_project.sass b/src/styles/_project.sass index 32a74288..2a71724c 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -607,9 +607,6 @@ ul.project_nav-edit-list li background-color: $color-background border-bottom: 1px solid $color-background-dark - font: - weight: 400 - family: $font-body color: $color-text-dark position: relative @@ -713,9 +710,7 @@ ul.project_nav-edit-list /* Node Context */ =project-node-title - font: - family: $font-body - size: 1.5em + font-size: 1.5em color: $color-text-dark-primary $node-preview-max-height-sm: 300px @@ -897,7 +892,6 @@ $node-preview-max-height-lg: 700px .node-title color: $color-text-dark-primary font: - family: $font-body weight: 500 size: 1.5em +text-overflow-ellipsis @@ -1911,7 +1905,6 @@ section.node-children .node_index-collection-name font: - family: $font-body size: 4em weight: 600 margin-bottom: -5px @@ -2034,7 +2027,6 @@ section.node-children color: $color-text-dark font: size: 1.5em - family: $font-body weight: 500 text-decoration: none diff --git a/src/styles/_search.sass b/src/styles/_search.sass index 7637ad7b..28431e93 100644 --- a/src/styles/_search.sass +++ b/src/styles/_search.sass @@ -29,7 +29,6 @@ $search-hit-width_grid: 100px font: size: .9em weight: 400 - family: $font-body style: initial width: 100% +text-overflow-ellipsis @@ -416,9 +415,7 @@ $search-hit-width_grid: 100px &.texture .texture-title - font: - size: 2em - family: $font-body + font-size: 2em padding: 15px 10px 10px 15px .node-row background: white diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index de0ba021..1f06804c 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -26,7 +26,6 @@ display: inline-flex align-items: center justify-content: center - font-family: $font-body padding: 5px 12px border-radius: $roundness @@ -125,7 +124,6 @@ padding: 5px 5px 5px 0 color: $color-text-dark box-shadow: none - font-family: $font-body border: thin solid transparent border-radius: 0 border-bottom-color: $color-background-dark @@ -354,7 +352,6 @@ +clearfix color: darken($color-text-dark, 5%) font: - family: $font-body weight: 300 size: 1.2em @@ -660,3 +657,6 @@ transition: all 1s ease-out img transition: all 1s ease-out + +.cursor-pointer + cursor: pointer diff --git a/src/styles/blog.sass b/src/styles/blog.sass index ff84c52e..fe1fdcbc 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -43,7 +43,6 @@ .form-group position: relative margin: 0 auto 30px auto - font-family: $font-body input, textarea, select +input-generic @@ -257,9 +256,7 @@ .item-title color: $color-text-dark display: block - font: - family: $font-body - size: 1.8em + font-size: 1.8em padding: 10px 25px 10px @@ -541,7 +538,6 @@ font: size: 1.6em weight: 400 - family: $font-body .item-info color: $color-text-dark-secondary From b5535a8773d3ccffa15007b0b8c8051a5619d4c2 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 27 Aug 2018 17:02:07 +0200 Subject: [PATCH 003/148] CSS: New primary color and navbar height --- src/styles/_config.sass | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/_config.sass b/src/styles/_config.sass index c69bc5a4..2cea30a5 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -25,7 +25,7 @@ $color-text-light-primary: rgba($color-text-light, .87) !default $color-text-light-secondary: rgba($color-text-light, .54) !default $color-text-light-hint: rgba($color-text-light, .38) !default -$color-primary: #68B3C8 !default +$color-primary: #065687 !default $color-primary-light: hsl(hue($color-primary), 30%, 90%) !default $color-primary-dark: hsl(hue($color-primary), 80%, 30%) !default $color-primary-accent: hsl(hue($color-primary), 100%, 50%) !default @@ -104,7 +104,7 @@ $project-sidebar-width: 50px !default $project_header-height: 50px !default $project_footer-height: 30px !default -$navbar-height: 50px !default +$navbar-height: 42px !default $navbar-backdrop-height: 600px !default $node-type-asset_image: #e87d86 !default From 6ff4ee8fa17036b665f2fe4c7bf050c807600dca Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 27 Aug 2018 17:02:36 +0200 Subject: [PATCH 004/148] Minor Dashboard style tweaks --- src/templates/projects/index_dashboard.pug | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/templates/projects/index_dashboard.pug b/src/templates/projects/index_dashboard.pug index 48c9008c..bb198864 100644 --- a/src/templates/projects/index_dashboard.pug +++ b/src/templates/projects/index_dashboard.pug @@ -213,7 +213,7 @@ style. img.header( src="{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}") .text - .title Projects + h5 Projects .lead span. Create and manage your own personal projects. @@ -223,13 +223,14 @@ style. href="https://cloud.blender.org/blog/introducing-private-projects") | Learn More - section.announcement + section.announcement.mt-3 a(href="https://cloud.blender.org/blog/introducing-blender-sync") img.header( src="{{ url_for('static', filename='assets/img/blender_sync_header.jpg') }}") .text - .title - a(href="https://cloud.blender.org/blog/introducing-blender-sync") Textures Browser & Settings Sync + h5 + a.text-muted(href="https://cloud.blender.org/blog/introducing-blender-sync") + | Textures Browser & Settings Sync .lead span. From d0ff519980c60d5708f92c2e8c4d876e86199e7c Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 27 Aug 2018 17:03:13 +0200 Subject: [PATCH 005/148] Font Pillar: Aliases for CC license icons Also comments about updating the font from fontello.com --- src/styles/font-pillar.sass | 67 ++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/styles/font-pillar.sass b/src/styles/font-pillar.sass index 905d5410..e17a8309 100644 --- a/src/styles/font-pillar.sass +++ b/src/styles/font-pillar.sass @@ -1,6 +1,71 @@ -/* So it's possible to override the path before importing font-pillar.sass */ +/* SCROLL TO READ ABOUT UPDATING THIS FILE FROM FONTELLO */ + +/* Makes it possible to override the path before importing font-pillar.sass */ $pillar-font-path: "../font" !default +/* Font aliases */ +.pi /* blank item with the right spacing */ + &:after + content: '' + font-family: "pillar-font" + font-style: normal + font-weight: normal + speak: none + display: inline-block + text-decoration: inherit + width: 1em + margin-right: .2em + text-align: center + font-variant: normal + text-transform: none + line-height: 1em + margin-left: .2em + -webkit-font-smoothing: antialiased + -moz-osx-font-smoothing: grayscale + position: relative + + &:before + position: relative + +.pi-license-cc-zero:before + content: '\e85a' +.pi-license-cc-sa:before + content: '\e858' +.pi-license-cc-nd:before + content: '\e859' +.pi-license-cc-nc:before + content: '\e857' + +.pi-license-cc-0 + @extend .pi-license-cc-zero + position: relative + top: 1px +.pi-license-cc-by-sa + @extend .pi-license-cc-sa +.pi-license-cc-by-nd + @extend .pi-license-cc-nd +.pi-license-cc-by-nc + @extend .pi-license-cc-nc + +.pi-license-cc-by-sa, +.pi-license-cc-by-nd, +.pi-license-cc-by-nc + @extend .pi + + &:after + content: '\e807' + left: -27px + + &:before + left: 27px + +/* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Here begins the CSS code generated by fontello.com, converted to * + * Sass and replaced the path with our variable $pillar-font-path. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + @font-face font-family: 'pillar-font' src: url('#{$pillar-font-path}/pillar-font.woff?55726379') format("woff"), url('#{$pillar-font-path}/pillar-font.woff2?55726379') format("woff2") From c35fb6202bd0be91179218f1338abae9638679fb Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 28 Aug 2018 15:51:56 +0200 Subject: [PATCH 006/148] render_secondary_navigation: Bootstrap 4 tweaks --- src/templates/projects/_macros.pug | 77 +++++++++++++++--------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/src/templates/projects/_macros.pug b/src/templates/projects/_macros.pug index 308ebf70..b95b7c69 100644 --- a/src/templates/projects/_macros.pug +++ b/src/templates/projects/_macros.pug @@ -1,42 +1,41 @@ | {% macro render_secondary_navigation(project, pages=None) %} nav.navbar-secondary - .navbar-container - nav.collapse.navbar-collapse - ul.nav.navbar-nav.navbar-right - li - a.navbar-item( - href="{{ url_for('projects.view', project_url=project.url) }}", - title="{{ project.name }} Homepage") - span - b {{ project.name }} - li - a.navbar-item( - href="{{ url_for('main.project_blog', project_url=project.url) }}", - title="Project Blog", - class="{% if category == 'blog' %}active{% endif %}") - span Blog - | {% if pages %} - | {% for p in pages %} - li - a.navbar-item( - href="{{ url_for('projects.view_node', project_url=project.url, node_id=p._id) }}", - title="{{ p.name }}", - class="{% if category == 'page' %}active{% endif %}") - span {{ p.name }} - | {% endfor %} - | {% endif %} - | {% if project.nodes_featured %} - | {# In some cases featured_nodes might might be embedded #} - | {% if '_id' in project.nodes_featured[0] %} - | {% set featured_node_id=project.nodes_featured[0]._id %} - | {% else %} - | {% set featured_node_id=project.nodes_featured[0] %} - | {% endif %} - li - a.navbar-item( - href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}", - title="Explore {{ project.name }}", - class="{% if category == 'blog' %}active{% endif %}") - span Explore - | {% endif %} + nav.collapse.navbar-collapse + ul.navbar-nav.navbar-right + li + a.navbar-item( + href="{{ url_for('projects.view', project_url=project.url) }}", + title="{{ project.name }} Homepage") + span + b {{ project.name }} + li + a.navbar-item( + href="{{ url_for('main.project_blog', project_url=project.url) }}", + title="Project Blog", + class="{% if category == 'blog' %}active{% endif %}") + span Blog + | {% if pages %} + | {% for p in pages %} + li + a.navbar-item( + href="{{ url_for('projects.view_node', project_url=project.url, node_id=p._id) }}", + title="{{ p.name }}", + class="{% if category == 'page' %}active{% endif %}") + span {{ p.name }} + | {% endfor %} + | {% endif %} + | {% if project.nodes_featured %} + | {# In some cases featured_nodes might might be embedded #} + | {% if '_id' in project.nodes_featured[0] %} + | {% set featured_node_id=project.nodes_featured[0]._id %} + | {% else %} + | {% set featured_node_id=project.nodes_featured[0] %} + | {% endif %} + li + a.navbar-item( + href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}", + title="Explore {{ project.name }}", + class="{% if category == 'blog' %}active{% endif %}") + span Explore + | {% endif %} | {% endmacro %} From b5698293436e43b7406c575dbe4a9339bfd780fb Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 28 Aug 2018 15:52:50 +0200 Subject: [PATCH 007/148] General cleanup --- src/styles/_error.sass | 7 +++---- src/styles/_pages.sass | 4 ---- src/styles/components/_base.sass | 5 +++-- src/styles/components/_card.sass | 2 +- src/styles/theatre.sass | 1 + src/templates/menus/user_base.pug | 18 +++++++----------- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/styles/_error.sass b/src/styles/_error.sass index 02c20aa7..5ad02dc4 100644 --- a/src/styles/_error.sass +++ b/src/styles/_error.sass @@ -60,14 +60,13 @@ #node-overlay #error-container - position: fixed - top: $navbar-height align-items: flex-start + position: fixed + top: $nav-link-height #error-box - box-shadow: 0 0 25px rgba(black, .1), 0 0 50px rgba(black, .1) - width: auto border-top-left-radius: 0 border-top-right-radius: 0 + box-shadow: 0 0 25px rgba(black, .1), 0 0 50px rgba(black, .1) position: relative width: 100% diff --git a/src/styles/_pages.sass b/src/styles/_pages.sass index ee0e37a0..b08417b8 100644 --- a/src/styles/_pages.sass +++ b/src/styles/_pages.sass @@ -409,7 +409,6 @@ a.page-card-cta display: block +position-center-translate - +media-xs display: none +media-sm @@ -419,9 +418,6 @@ a.page-card-cta +media-lg width: 100% -.services.navbar-backdrop-overlay - background: rgba(black, .5) - .services .page-card-side max-width: 500px diff --git a/src/styles/components/_base.sass b/src/styles/components/_base.sass index 027f1b92..51aa3ef4 100644 --- a/src/styles/components/_base.sass +++ b/src/styles/components/_base.sass @@ -28,5 +28,6 @@ body .page-content position: relative flex: 1 - .container-box - +container-box + +.container-box + +container-box diff --git a/src/styles/components/_card.sass b/src/styles/components/_card.sass index edb993fc..d13003b7 100644 --- a/src/styles/components/_card.sass +++ b/src/styles/components/_card.sass @@ -11,7 +11,7 @@ border: none .card-body - padding: 20px 0 + padding: 15px 0 .card-fade img diff --git a/src/styles/theatre.sass b/src/styles/theatre.sass index 10b95e22..abb97a3a 100644 --- a/src/styles/theatre.sass +++ b/src/styles/theatre.sass @@ -26,6 +26,7 @@ body.theatre .container-page display: flex align-items: center justify-content: center + .page-body height: 100% width: 100% diff --git a/src/templates/menus/user_base.pug b/src/templates/menus/user_base.pug index 079fc589..a3fa6520 100644 --- a/src/templates/menus/user_base.pug +++ b/src/templates/menus/user_base.pug @@ -1,7 +1,7 @@ | {% block menu_body %} | {% if current_user.is_authenticated %} -li(class="dropdown") +li.dropdown | {% block menu_avatar %} a.navbar-item.dropdown-toggle(href="#", data-toggle="dropdown", title="{{ current_user.email }}") img.gravatar( @@ -9,42 +9,38 @@ li(class="dropdown") alt="Avatar") | {% endblock menu_avatar %} - ul.dropdown-menu + ul.dropdown-menu.dropdown-menu-right | {% if not current_user.has_role('protected') %} | {% block menu_list %} li a.navbar-item( href="{{ url_for('projects.home_project') }}" title="Home") - i.pi-home - | Home + #[i.pi-home] Home li a.navbar-item( href="{{ url_for('projects.index') }}" title="My Projects") - i.pi-star - | My Projects + #[i.pi-star] My Projects | {% if current_user.has_organizations() %} li a.navbar-item( href="{{ url_for('pillar.web.organizations.index') }}" title="My Organizations") - i.pi-users - | My Organizations + #[i.pi-users] My Organizations | {% endif %} li a.navbar-item( href="{{ url_for('settings.profile') }}" title="Settings") - i.pi-cog - | Settings + #[i.pi-cog] Settings | {% endblock menu_list %} - li.divider(role="separator") + li.dropdown-divider(role="separator") | {% endif %} li From 819300f95459b712d4c49c91e0f5b30a0a32ccde Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 28 Aug 2018 15:52:56 +0200 Subject: [PATCH 008/148] Navbar cleanup --- src/styles/components/_navbar.sass | 237 ++++++----------------------- 1 file changed, 49 insertions(+), 188 deletions(-) diff --git a/src/styles/components/_navbar.sass b/src/styles/components/_navbar.sass index a6c846cc..f801ece6 100644 --- a/src/styles/components/_navbar.sass +++ b/src/styles/components/_navbar.sass @@ -1,5 +1,7 @@ -/* Navigation */ +// Navigation. .navbar-overlay + +media-lg + display: block bottom: 0 display: none left: 0 @@ -11,87 +13,26 @@ width: 100% z-index: 0 - &+.navbar-container - .search-input - +media-lg - border: thin solid rgba(white, .2) - - #cloud-search, .tt-hint - &::placeholder - color: rgba(white, .8) - - border: thin solid transparent - border-radius: 3px - margin: 8px 0 - &.is-active background-color: $color-background-nav text-shadow: none - &+.navbar-container - .search-input - +media-sm - background-color: darken($color-background-nav, 6%) - +media-md - background-color: darken($color-background-nav, 6%) - +media-lg - border: thin solid transparent - background-color: darken($color-background-nav, 6%) - - #cloud-search, .tt-hint - &::placeholder - color: rgba(white, .5) - - +media-lg - display: block - - -.navbar-container - align-items: center - display: flex - width: 100% - - .navbar-collapse - +media-xs - padding: 0 - width: 100% - max-height: initial // overrides bs, we don't have many items - - &.show-notifications - display: block - - li - display: none - &.nav-notifications - display: block - position: absolute - top: 7px - left: 0 - width: 100% - z-index: $z-index-base - - #notifications - padding: 0 10px - - -nav - &.navbar - border-radius: 0 - left: 0 - position: fixed - right: 0 - top: 0 - .navbar-brand padding-left: 10px color: $color-text - width: 120px // Blender Cloud logo width &:hover color: $color-text-dark-primary -nav.navbar, + +nav.navbar + .navbar-collapse + > ul > li > .navbar-item + padding: $navbar-nav-link-padding-x + height: $nav-link-height + +.navbar, nav.sidebar align-items: center background-color: $color-background-nav @@ -110,43 +51,41 @@ nav.sidebar margin: 0 width: 100% + .navbar-item + align-items: center + color: $color-text + display: flex + user-select: none + transition: color 150ms ease-in-out, box-shadow 100ms ease-in-out + + +media-sm + padding-left: 10px + padding-right: 10px + + &:hover, &:focus + color: $color-primary + background-color: transparent + box-shadow: inset 0 -3px 0 $color-primary + text-decoration: none + + &:focus + box-shadow: inset 0 -3px 0 $color-primary + + &.active + color: $color-primary + box-shadow: inset 0 -3px 0 lighten($color-secondary, 10%) + + &:hover + box-shadow: inset 0 -3px 0 lighten($color-secondary, 20%) + + .pi-angle-down + position: relative + left: 5px + li user-select: none position: relative - a.navbar-item - align-items: center - color: $color-text - display: flex - user-select: none - padding: $navbar-brand-padding-y - height: 100% - transition: color 150ms ease-in-out, box-shadow 100ms ease-in-out - - +media-sm - padding-left: 10px - padding-right: 10px - - &:hover, &:focus - color: $color-primary - background-color: transparent - box-shadow: inset 0 -3px 0 $color-primary - text-decoration: none - - &:focus - box-shadow: inset 0 -3px 0 $color-primary - - &.active - color: $color-primary - box-shadow: inset 0 -3px 0 lighten($color-secondary, 10%) - - &:hover - box-shadow: inset 0 -3px 0 lighten($color-secondary, 20%) - - .pi-angle-down - position: relative - left: 5px - &.nav-item-sign-in i padding-right: 6px @@ -159,7 +98,6 @@ nav.sidebar box-shadow: 1px 1px 0 rgba(black, .2) position: relative - .special width: 18px height: 18px @@ -190,8 +128,7 @@ nav.sidebar +position-center-translate .dropdown - position: relative - min-width: 60px + min-width: 60px // navbar avatar size // Removes angle-down icon from bootstrap, // since we use a nicer chevron. @@ -205,41 +142,25 @@ nav.sidebar transform: translate(-50%, -50%) ul.dropdown-menu - padding: 0 - li a - padding: 5px 15px 5px 5px + white-space: nowrap &:hover - color: $color-primary - box-shadow: none + box-shadow: none // removes underline - &.subitem + &.subitem // e.g. "Not Sintel? Log out" font-size: .8em padding-top: 0 text-transform: initial i - width: 35px - text-align: center + width: 30px &.subscription-status - padding: - top: 10px - bottom: 10px - - transition: opacity 150ms ease-in-out - opacity: 1 - - &:hover - opacity: .8 - - a - text-transform: initial - &.none a color: $color-danger + &.subscriber a color: $color-success @@ -280,71 +201,11 @@ nav.sidebar display: block -.navbar-backdrop - z-index: 0 - position: absolute - top: 0 - left: 0 - right: 0 - bottom: 0 - - width: 100% - height: 100% - background: - size: 100% - position: 0 0 - repeat: no-repeat - color: transparent - image: url(https://cloud.blender.org/static/assets/img/backgrounds/pattern_02_blur.jpg) - - filter: none - - &.project - display: none - +media-lg - display: block - left: -10px - width: 105% - &.blog - display: none - +media-lg - display: block - left: -10px - width: 105% - +media-md - display: block - left: -10px - width: 105% - - -.navbar-backdrop-overlay - position: absolute - top: 0 - left: 0 - width: 100% - height: $navbar-backdrop-height - background: linear-gradient(to bottom, rgba($color-background, 0) 25%, rgba($color-background,0.5) 50%, rgba($color-background,1) 100%) - - -/* Global, we want all menus to look like this */ -ul.dropdown-menu - background-color: $color-background-light - border: none - box-shadow: 0 10px 25px rgba(black, .1) - user-select: none - - .divider - background-color: rgba(black, .1) - -nav .dropdown:hover ul.dropdown-menu - display: block - .nav-tabs .dropdown-menu, .nav-pills .dropdown-menu margin-top: 0 .navbar+.page-content - padding-top: $navbar-height - + padding-top: $nav-link-height // Secondary navigation for .nav-secondary From 84608500b96ca3dd234f5df3a74d440e4bb949a7 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 28 Aug 2018 15:53:47 +0200 Subject: [PATCH 009/148] CSS: Split dropdown styling --- src/styles/base.sass | 1 + src/styles/components/_dropdown.sass | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/styles/components/_dropdown.sass diff --git a/src/styles/base.sass b/src/styles/base.sass index 9eeaeeb5..eb180f24 100644 --- a/src/styles/base.sass +++ b/src/styles/base.sass @@ -10,6 +10,7 @@ @import components/base @import components/alerts @import components/navbar +@import components/dropdown @import components/footer @import components/shortcode @import components/statusbar diff --git a/src/styles/components/_dropdown.sass b/src/styles/components/_dropdown.sass new file mode 100644 index 00000000..dc322071 --- /dev/null +++ b/src/styles/components/_dropdown.sass @@ -0,0 +1,12 @@ +// Global, we want all menus to look like this. +.dropdown-menu + box-shadow: $dropdown-box-shadow + + > li + > a + padding: $dropdown-item-padding-y + +// Open dropdown on mouse hover dropdowns in the navbar. +nav .dropdown:hover + ul.dropdown-menu + display: block From 40f79af49dce0b53f7f04e24c3fe9902cbac5f84 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 28 Aug 2018 15:54:14 +0200 Subject: [PATCH 010/148] Tooltips: Cleanup --- src/styles/_config.sass | 7 ++++++- src/styles/_utils.sass | 3 +++ src/styles/components/_tooltip.sass | 17 +---------------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/styles/_config.sass b/src/styles/_config.sass index 2cea30a5..cf088754 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -104,7 +104,6 @@ $project-sidebar-width: 50px !default $project_header-height: 50px !default $project_footer-height: 30px !default -$navbar-height: 42px !default $navbar-backdrop-height: 600px !default $node-type-asset_image: #e87d86 !default @@ -125,3 +124,9 @@ $z-index-base: 13 !default @media (min-width: $screen-lg-min) width: 1270px + + +// Tooltips. +$tooltip-font-size: 0.83rem +$tooltip-max-width: auto +$tooltip-opacity: 1 diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index 1f06804c..f68216a0 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -660,3 +660,6 @@ .cursor-pointer cursor: pointer + +.user-select-none + user-select: none diff --git a/src/styles/components/_tooltip.sass b/src/styles/components/_tooltip.sass index 3a317919..8a501463 100644 --- a/src/styles/components/_tooltip.sass +++ b/src/styles/components/_tooltip.sass @@ -1,20 +1,5 @@ .tooltip - transition: opacity 50ms ease-in-out - &.in - opacity: 1 + transition: none .tooltip-inner - max-width: auto white-space: nowrap - background-color: $color-background-nav-light - color: $color-text-light-primary - border-radius: 3px - - &.top .tooltip-arrow - border-top-color: $color-background-nav-light - &.bottom .tooltip-arrow - border-bottom-color: $color-background-nav-light - &.left .tooltip-arrow - border-left-color: $color-background-nav-light - &.right .tooltip-arrow - border-right-color: $color-background-nav-light From 46b0d6d6639636e5e95d632b70efea1b733bfea5 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Wed, 29 Aug 2018 16:30:17 +0200 Subject: [PATCH 011/148] Upgrade npm dependencies Change gulp-uglify for gulp-uglify-es which has support for ES6. New dependencies: * boostrap * jquery * popper.js (required by bootstrap) --- gulpfile.js | 2 +- package.json | 35 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 79b9d178..149089f9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -12,7 +12,7 @@ var pug = require('gulp-pug'); var rename = require('gulp-rename'); var sass = require('gulp-sass'); var sourcemaps = require('gulp-sourcemaps'); -var uglify = require('gulp-uglify'); +var uglify = require('gulp-uglify-es').default; var enabled = { uglify: argv.production, diff --git a/package.json b/package.json index d1930d4c..d8da25f9 100644 --- a/package.json +++ b/package.json @@ -4,23 +4,28 @@ "author": "Blender Institute", "repository": { "type": "git", - "url": "https://github.com/armadillica/pillar.git" + "url": "git://git.blender.org/pillar.git" }, "devDependencies": { - "gulp": "~3.9.1", - "gulp-autoprefixer": "~2.3.1", - "gulp-cached": "~1.1.0", - "gulp-chmod": "~1.3.0", - "gulp-concat": "~2.6.0", - "gulp-if": "^2.0.1", - "gulp-git": "~2.4.2", - "gulp-livereload": "~3.8.1", - "gulp-plumber": "~1.1.0", - "gulp-pug": "~3.2.0", - "gulp-rename": "~1.2.2", - "gulp-sass": "~2.3.1", - "gulp-sourcemaps": "~1.6.0", - "gulp-uglify": "~1.5.3", + "gulp": "^3.9.1", + "gulp-autoprefixer": "^6.0.0", + "gulp-cached": "^1.1.1", + "gulp-chmod": "^2.0.0", + "gulp-concat": "^2.6.1", + "gulp-if": "^2.0.2", + "gulp-git": "^2.8.0", + "gulp-livereload": "^4.0.0", + "gulp-plumber": "^1.2.0", + "gulp-pug": "^4.0.1", + "gulp-rename": "^1.4.0", + "gulp-sass": "^4.0.1", + "gulp-sourcemaps": "^2.6.4", + "gulp-uglify-es": "^1.0.4", "minimist": "^1.2.0" + }, + "dependencies": { + "bootstrap": "^4.1.3", + "jquery": "^3.3.1", + "popper.js": "^1.14.4" } } From 791906521f94dcd2545048c1abfb34c47dc6dc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 30 Aug 2018 18:27:55 +0200 Subject: [PATCH 012/148] Added a test context manager to log in when doing Flask test client requests --- pillar/auth/__init__.py | 5 +++++ pillar/tests/__init__.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pillar/auth/__init__.py b/pillar/auth/__init__.py index aef00a70..68b8db78 100644 --- a/pillar/auth/__init__.py +++ b/pillar/auth/__init__.py @@ -210,6 +210,11 @@ def login_user(oauth_token: str, *, load_from_db=False): user = _load_user(oauth_token) else: user = UserClass(oauth_token) + login_user_object(user) + + +def login_user_object(user: UserClass): + """Log in the given user.""" flask_login.login_user(user, remember=True) g.current_user = user user_authenticated.send(None) diff --git a/pillar/tests/__init__.py b/pillar/tests/__init__.py index 9e55dac4..b7f2c196 100644 --- a/pillar/tests/__init__.py +++ b/pillar/tests/__init__.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- import base64 +import contextlib import copy import datetime import json @@ -327,6 +328,31 @@ class AbstractPillarTest(TestMinimal): return user + @contextlib.contextmanager + def login_as(self, user_id: typing.Union[str, ObjectId]): + """Context manager, within the context the app context is active and the user logged in. + + The logging-in happens when a request starts, so it's only active when + e.g. self.get() or self.post() or somesuch request is used. + """ + from pillar.auth import UserClass, login_user_object + + if isinstance(user_id, str): + user_oid = ObjectId(user_id) + elif isinstance(user_id, ObjectId): + user_oid = user_id + else: + raise TypeError(f'invalid type {type(user_id)} for parameter user_id') + user_doc = self.fetch_user_from_db(user_oid) + + def signal_handler(sender, **kwargs): + login_user_object(user) + + with self.app.app_context(): + user = UserClass.construct('', user_doc) + with flask.request_started.connected_to(signal_handler, self.app): + yield + def create_valid_auth_token(self, user_id, token='token'): from pillar.api.utils import utcnow From a67527d6af94a2e806e180be7a289aaa3ff60894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 30 Aug 2018 18:28:17 +0200 Subject: [PATCH 013/148] Use app_context() instead of test_request_context() There is no request context needed here. --- pillar/tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pillar/tests/__init__.py b/pillar/tests/__init__.py index b7f2c196..1baae624 100644 --- a/pillar/tests/__init__.py +++ b/pillar/tests/__init__.py @@ -187,7 +187,7 @@ class AbstractPillarTest(TestMinimal): else: self.ensure_project_exists() - with self.app.test_request_context(): + with self.app.app_context(): files_collection = self.app.data.driver.db['files'] assert isinstance(files_collection, pymongo.collection.Collection) From 40c19a3cb0486f9ef456ef77c074337241758def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 31 Aug 2018 11:25:55 +0200 Subject: [PATCH 014/148] pillar.api.utils.utcnow() now truncates microseconds to milliseconds MongoDB stores datetimes in millisecond precision, to keep datetimes the same when roundtripping via MongoDB we now truncate the microseconds. --- pillar/api/utils/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pillar/api/utils/__init__.py b/pillar/api/utils/__init__.py index 4ff46af0..cdc85fb1 100644 --- a/pillar/api/utils/__init__.py +++ b/pillar/api/utils/__init__.py @@ -245,4 +245,10 @@ def random_etag() -> str: def utcnow() -> datetime.datetime: - return datetime.datetime.now(tz=bson.tz_util.utc) + """Construct timezone-aware 'now' in UTC with millisecond precision.""" + now = datetime.datetime.now(tz=bson.tz_util.utc) + + # MongoDB stores in millisecond precision, so truncate the microseconds. + # This way the returned datetime can be round-tripped via MongoDB and stay the same. + trunc_now = now.replace(microsecond=now.microsecond - (now.microsecond % 1000)) + return trunc_now From 814275fc95643937b78b553db436bf802dd9c5d8 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 14:17:39 +0200 Subject: [PATCH 015/148] Gulp: only chmod when running --production --- gulpfile.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 149089f9..1e54113d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,6 +21,7 @@ var enabled = { prettyPug: !argv.production, cachify: !argv.production, cleanup: argv.production, + chmod: argv.production, }; var destination = { @@ -67,7 +68,7 @@ gulp.task('scripts', function() { .pipe(gulpif(enabled.uglify, uglify())) .pipe(rename({suffix: '.min'})) .pipe(gulpif(enabled.maps, sourcemaps.write("."))) - .pipe(chmod(644)) + .pipe(gulpif(enabled.chmod, chmod(644))) .pipe(gulp.dest(destination.js)) .pipe(gulpif(argv.livereload, livereload())); }); @@ -82,7 +83,7 @@ gulp.task('scripts_concat_tutti', function() { .pipe(concat("tutti.min.js")) .pipe(gulpif(enabled.uglify, uglify())) .pipe(gulpif(enabled.maps, sourcemaps.write("."))) - .pipe(chmod(644)) + .pipe(gulpif(enabled.chmod, chmod(644))) .pipe(gulp.dest(destination.js)) .pipe(gulpif(argv.livereload, livereload())); }); @@ -94,7 +95,7 @@ gulp.task('scripts_concat_markdown', function() { .pipe(concat("markdown.min.js")) .pipe(gulpif(enabled.uglify, uglify())) .pipe(gulpif(enabled.maps, sourcemaps.write("."))) - .pipe(chmod(644)) + .pipe(gulpif(enabled.chmod, chmod(644))) .pipe(gulp.dest(destination.js)) .pipe(gulpif(argv.livereload, livereload())); }); From ac3a599bb6f9366b017fafc508f9c2a38e8e6ba8 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 14:19:09 +0200 Subject: [PATCH 016/148] Gulp: build our own bootstrap js only using the needed modules. At this point we only use tooltip and dropdown code, but we could use tabs or carousels in the future. Just add them to the toUglify list. --- gulpfile.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/gulpfile.js b/gulpfile.js index 1e54113d..aaf4caec 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -30,6 +30,10 @@ var destination = { js: 'pillar/web/static/assets/js', } +var source = { + bootstrap: 'node_modules/bootstrap/', + popper: 'node_modules/popper.js/' +} /* CSS */ gulp.task('styles', function() { @@ -101,6 +105,29 @@ gulp.task('scripts_concat_markdown', function() { }); +// Combine all needed Bootstrap JavaScript into a single file. +gulp.task('scripts_concat_bootstrap', function() { + + toUglify = [ + source.popper + 'dist/umd/popper.min.js', + source.bootstrap + 'js/dist/index.js', + source.bootstrap + 'js/dist/util.js', + source.bootstrap + 'js/dist/tooltip.js', + source.bootstrap + 'js/dist/dropdown.js', + ]; + + gulp.src(toUglify) + .pipe(gulpif(enabled.failCheck, plumber())) + .pipe(gulpif(enabled.maps, sourcemaps.init())) + .pipe(concat("bootstrap.min.js")) + .pipe(gulpif(enabled.uglify, uglify())) + .pipe(gulpif(enabled.maps, sourcemaps.write("."))) + .pipe(gulpif(enabled.chmod, chmod(644))) + .pipe(gulp.dest(destination.js)) + .pipe(gulpif(argv.livereload, livereload())); +}); + + // While developing, run 'gulp watch' gulp.task('watch',function() { // Only listen for live reloads if ran with --livereload @@ -138,4 +165,5 @@ gulp.task('default', tasks.concat([ 'scripts', 'scripts_concat_tutti', 'scripts_concat_markdown', + 'scripts_concat_bootstrap', ])); From 2332bc0960477b0bea2a3a0a5b4ba8641a1f1808 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 14:20:59 +0200 Subject: [PATCH 017/148] jQuery: Small utility to set CSS display type Showing elements with jQuery's native .show() sets display as 'inline', but sometimes we need to set 'flex' or 'inline-block'. --- src/scripts/tutti/00_utils.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/scripts/tutti/00_utils.js b/src/scripts/tutti/00_utils.js index 99f853c4..47796812 100644 --- a/src/scripts/tutti/00_utils.js +++ b/src/scripts/tutti/00_utils.js @@ -64,4 +64,13 @@ return this; }; + // jQuery's show() sets display as 'inline', this utility sets it to whatever we want. + // Useful for buttons or links that need 'inline-block' or flex for correct padding and alignment. + $.fn.displayAs = function(display_type) { + if (typeof(display_type) === 'undefined') { + display_type = 'block'; + } + + this.css('display', display_type); + } }(jQuery)); From 7405e198eb60696600e3c4a9b213c3215f04e40c Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 14:23:23 +0200 Subject: [PATCH 018/148] Use .displayAs() instead of .show() Needed for CSS display to be set as inline-block instead of show()'s inline. --- src/templates/nodes/custom/_scripts.pug | 2 +- src/templates/projects/edit.pug | 3 ++- src/templates/projects/view.pug | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/templates/nodes/custom/_scripts.pug b/src/templates/nodes/custom/_scripts.pug index 05fbbc76..7be01148 100644 --- a/src/templates/nodes/custom/_scripts.pug +++ b/src/templates/nodes/custom/_scripts.pug @@ -61,7 +61,7 @@ script(type="text/javascript"). } {% if node.has_method('PUT') %} - $('.project-mode-view').show(); + $('.project-mode-view').displayAs('inline-block'); {% else %} $('.project-mode-view').hide(); {% endif %} diff --git a/src/templates/projects/edit.pug b/src/templates/projects/edit.pug index e24883f0..77d40c6d 100644 --- a/src/templates/projects/edit.pug +++ b/src/templates/projects/edit.pug @@ -105,7 +105,8 @@ script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets script(type="text/javascript"). - $('.project-mode-edit').show(); + // Show view mode buttons. + $('.project-mode-view').displayAs('inline-block'); ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''}); var convert = new Markdown.getSanitizingConverter().makeHtml; diff --git a/src/templates/projects/view.pug b/src/templates/projects/view.pug index 6626636a..f4669381 100644 --- a/src/templates/projects/view.pug +++ b/src/templates/projects/view.pug @@ -303,7 +303,7 @@ script. function updateUi(nodeId, mode) { if (mode === 'view') { - $('.project-mode-view').show(); + $('.project-mode-view').displayAs('inline-block'); $('.project-mode-edit').hide(); $("#node-edit-form").unbind("submit"); @@ -311,12 +311,12 @@ script. $("#item_cancel").unbind("click"); } else if (mode === 'edit') { $('.project-mode-view').hide(); - $('.project-mode-edit').show(); + $('.project-mode-edit').displayAs('inline-block'); } else { if (console) console.log('Invalid mode:', mode); } - // Prevent flicker by scrolling to top + // Prevent flicker by scrolling to top. $("#project_context-container").scrollTop(0); // Enable specific items under the Add New dropdown @@ -497,7 +497,7 @@ script. // Handle old-style /p/{url}/#node-ID links, and redirect them to the correct spot. redirectToNode(location.hash.substr(1)); } - $('.project-mode-view').show(); + $('.project-mode-view').displayAs('inline-block'); $('.project-mode-edit').hide(); } else { displayNode(nodeId, false); From 76338b4568aa9d5cc3d6edc852142c2e42c6d86e Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 14:24:25 +0200 Subject: [PATCH 019/148] Sass config: Bootstrap overrides --- src/styles/_config.sass | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/styles/_config.sass b/src/styles/_config.sass index cf088754..04c0831e 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -25,7 +25,7 @@ $color-text-light-primary: rgba($color-text-light, .87) !default $color-text-light-secondary: rgba($color-text-light, .54) !default $color-text-light-hint: rgba($color-text-light, .38) !default -$color-primary: #065687 !default +$color-primary: #009eff !default $color-primary-light: hsl(hue($color-primary), 30%, 90%) !default $color-primary-dark: hsl(hue($color-primary), 80%, 30%) !default $color-primary-accent: hsl(hue($color-primary), 100%, 50%) !default @@ -101,11 +101,9 @@ $sidebar-width: 50px !default /* Project specifics */ $project_nav-width: 250px !default $project-sidebar-width: 50px !default -$project_header-height: 50px !default +$project_header-height: 37px !default $project_footer-height: 30px !default -$navbar-backdrop-height: 600px !default - $node-type-asset_image: #e87d86 !default $node-type-asset_file: #CC91C7 !default $node-type-asset_video: #7dc5e8 !default @@ -126,6 +124,26 @@ $z-index-base: 13 !default width: 1270px +// Bootstrap overrides. +$enable-caret: false + +$border-radius: .2rem +$btn-border-radius: $border-radius + +$primary: $color-primary + +$body-bg: $color-background +$body-color: $color-text + +$color-background-nav: #fff +$link-color: $primary + +$font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" +$font-size-base: .9rem + +$dropdown-border-width: 0 +$dropdown-box-shadow: 0 10px 25px rgba($black, .1) + // Tooltips. $tooltip-font-size: 0.83rem $tooltip-max-width: auto From 33bd2c5880c6d60597976a3ef63d11c58b434e67 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 14:26:42 +0200 Subject: [PATCH 020/148] Sass: Import modules on top level --- src/styles/base.sass | 48 +++++++++++++++++++++++- src/styles/blog.sass | 68 ++++++++++++++++++++++++++++++++- src/styles/main.sass | 73 +++++++++++++++++++++++++++++++++++- src/styles/project-main.sass | 70 +++++++++++++++++++++++++++++++++- 4 files changed, 253 insertions(+), 6 deletions(-) diff --git a/src/styles/base.sass b/src/styles/base.sass index eb180f24..6a5ac813 100644 --- a/src/styles/base.sass +++ b/src/styles/base.sass @@ -1,7 +1,53 @@ -@import _normalize +// First, Bootstrap variables and utilities. +@import "../../node_modules/bootstrap/scss/functions" +@import "../../node_modules/bootstrap/scss/variables" +@import "../../node_modules/bootstrap/scss/mixins" + +// Then Pillar variables and utilities. @import _config @import _utils +// Now Bootstrap components. +@import "../../node_modules/bootstrap/scss/root" +@import "../../node_modules/bootstrap/scss/reboot" + +@import "../../node_modules/bootstrap/scss/type" +@import "../../node_modules/bootstrap/scss/images" + +@import "../../node_modules/bootstrap/scss/code" +@import "../../node_modules/bootstrap/scss/grid" +@import "../../node_modules/bootstrap/scss/tables" +@import "../../node_modules/bootstrap/scss/forms" +@import "../../node_modules/bootstrap/scss/buttons" +@import "../../node_modules/bootstrap/scss/transitions" +@import "../../node_modules/bootstrap/scss/dropdown" +@import "../../node_modules/bootstrap/scss/button-group" +@import "../../node_modules/bootstrap/scss/input-group" +@import "../../node_modules/bootstrap/scss/custom-forms" + +@import "../../node_modules/bootstrap/scss/nav" +@import "../../node_modules/bootstrap/scss/navbar" + +@import "../../node_modules/bootstrap/scss/card" +@import "../../node_modules/bootstrap/scss/breadcrumb" +@import "../../node_modules/bootstrap/scss/pagination" +@import "../../node_modules/bootstrap/scss/badge" +@import "../../node_modules/bootstrap/scss/jumbotron" +@import "../../node_modules/bootstrap/scss/alert" +@import "../../node_modules/bootstrap/scss/progress" +@import "../../node_modules/bootstrap/scss/media" +@import "../../node_modules/bootstrap/scss/list-group" +@import "../../node_modules/bootstrap/scss/close" +@import "../../node_modules/bootstrap/scss/modal" +@import "../../node_modules/bootstrap/scss/tooltip" +@import "../../node_modules/bootstrap/scss/popover" +@import "../../node_modules/bootstrap/scss/carousel" + +@import "../../node_modules/bootstrap/scss/utilities" +@import "../../node_modules/bootstrap/scss/print" + + +// Pillar components. @import _apps_base @import _notifications @import _comments diff --git a/src/styles/blog.sass b/src/styles/blog.sass index fe1fdcbc..e7460a5c 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -1,8 +1,72 @@ -@import _normalize +// Bootstrap variables and utilities. +@import "../../node_modules/bootstrap/scss/functions" +@import "../../node_modules/bootstrap/scss/variables" +@import "../../node_modules/bootstrap/scss/mixins" + @import _config @import _utils -@import _apps_base +// Bootstrap components. +@import "../../node_modules/bootstrap/scss/root" +@import "../../node_modules/bootstrap/scss/reboot" + +@import "../../node_modules/bootstrap/scss/type" +@import "../../node_modules/bootstrap/scss/images" + +@import "../../node_modules/bootstrap/scss/code" +@import "../../node_modules/bootstrap/scss/grid" +@import "../../node_modules/bootstrap/scss/tables" +@import "../../node_modules/bootstrap/scss/forms" +@import "../../node_modules/bootstrap/scss/buttons" +@import "../../node_modules/bootstrap/scss/transitions" +@import "../../node_modules/bootstrap/scss/dropdown" +@import "../../node_modules/bootstrap/scss/button-group" +@import "../../node_modules/bootstrap/scss/input-group" +@import "../../node_modules/bootstrap/scss/custom-forms" + +@import "../../node_modules/bootstrap/scss/nav" +@import "../../node_modules/bootstrap/scss/navbar" + +@import "../../node_modules/bootstrap/scss/card" +@import "../../node_modules/bootstrap/scss/breadcrumb" +@import "../../node_modules/bootstrap/scss/pagination" +@import "../../node_modules/bootstrap/scss/badge" +@import "../../node_modules/bootstrap/scss/jumbotron" +@import "../../node_modules/bootstrap/scss/alert" +@import "../../node_modules/bootstrap/scss/progress" +@import "../../node_modules/bootstrap/scss/media" +@import "../../node_modules/bootstrap/scss/list-group" +@import "../../node_modules/bootstrap/scss/close" +@import "../../node_modules/bootstrap/scss/modal" +@import "../../node_modules/bootstrap/scss/tooltip" +@import "../../node_modules/bootstrap/scss/popover" +@import "../../node_modules/bootstrap/scss/carousel" + +@import "../../node_modules/bootstrap/scss/utilities" +@import "../../node_modules/bootstrap/scss/print" + +// Pillar components. +@import "apps_base" +@import "components/base" + +@import "components/jumbotron" +@import "components/alerts" +@import "components/navbar" +@import "components/dropdown" +@import "components/footer" +@import "components/shortcode" +@import "components/statusbar" +@import "components/search" +@import "components/flyout" +@import "components/forms" +@import "components/inputs" +@import "components/buttons" +@import "components/popover" +@import "components/tooltip" +@import "components/checkbox" +@import "components/overlay" +@import "components/card" + @import _comments @import _error @import _search diff --git a/src/styles/main.sass b/src/styles/main.sass index 060b8dab..aa02c044 100644 --- a/src/styles/main.sass +++ b/src/styles/main.sass @@ -1,15 +1,84 @@ -@import _normalize +// Bootstrap variables and utilities. +@import "../../node_modules/bootstrap/scss/functions" +@import "../../node_modules/bootstrap/scss/variables" +@import "../../node_modules/bootstrap/scss/mixins" + @import _config @import _utils +// Bootstrap components. +@import "../../node_modules/bootstrap/scss/root" +@import "../../node_modules/bootstrap/scss/reboot" + +@import "../../node_modules/bootstrap/scss/type" +@import "../../node_modules/bootstrap/scss/images" + +@import "../../node_modules/bootstrap/scss/code" +@import "../../node_modules/bootstrap/scss/grid" +@import "../../node_modules/bootstrap/scss/tables" +@import "../../node_modules/bootstrap/scss/forms" +@import "../../node_modules/bootstrap/scss/buttons" +@import "../../node_modules/bootstrap/scss/transitions" +@import "../../node_modules/bootstrap/scss/dropdown" +@import "../../node_modules/bootstrap/scss/button-group" +@import "../../node_modules/bootstrap/scss/input-group" +@import "../../node_modules/bootstrap/scss/custom-forms" + +@import "../../node_modules/bootstrap/scss/nav" +@import "../../node_modules/bootstrap/scss/navbar" + +@import "../../node_modules/bootstrap/scss/card" +@import "../../node_modules/bootstrap/scss/breadcrumb" +@import "../../node_modules/bootstrap/scss/pagination" +@import "../../node_modules/bootstrap/scss/badge" +@import "../../node_modules/bootstrap/scss/jumbotron" +@import "../../node_modules/bootstrap/scss/alert" +@import "../../node_modules/bootstrap/scss/progress" +@import "../../node_modules/bootstrap/scss/media" +@import "../../node_modules/bootstrap/scss/list-group" +@import "../../node_modules/bootstrap/scss/close" +@import "../../node_modules/bootstrap/scss/modal" +@import "../../node_modules/bootstrap/scss/tooltip" +@import "../../node_modules/bootstrap/scss/popover" +@import "../../node_modules/bootstrap/scss/carousel" + +@import "../../node_modules/bootstrap/scss/utilities" +@import "../../node_modules/bootstrap/scss/print" + +// Pillar components. +@import "apps_base" +@import "components/base" + +@import "components/jumbotron" +@import "components/alerts" +@import "components/navbar" +@import "components/dropdown" +@import "components/footer" +@import "components/shortcode" +@import "components/statusbar" +@import "components/search" + +@import "components/flyout" +@import "components/forms" +@import "components/inputs" +@import "components/buttons" +@import "components/popover" +@import "components/tooltip" +@import "components/checkbox" +@import "components/overlay" +@import "components/card" + + /* Generic styles (comments, notifications, etc) come from base.css */ +@import _notifications +@import _comments @import _project @import _project-sharing @import _project-dashboard @import _user -@import _search @import _organizations +@import _search /* services, about, etc */ @import _pages diff --git a/src/styles/project-main.sass b/src/styles/project-main.sass index 7854143b..8cacdd06 100644 --- a/src/styles/project-main.sass +++ b/src/styles/project-main.sass @@ -1,8 +1,76 @@ -@import _normalize +// Bootstrap variables and utilities. +@import "../../node_modules/bootstrap/scss/functions" +@import "../../node_modules/bootstrap/scss/variables" +@import "../../node_modules/bootstrap/scss/mixins" + @import _config @import _utils +// Bootstrap components. +@import "../../node_modules/bootstrap/scss/root" +@import "../../node_modules/bootstrap/scss/reboot" + +@import "../../node_modules/bootstrap/scss/type" +@import "../../node_modules/bootstrap/scss/images" + +@import "../../node_modules/bootstrap/scss/code" +@import "../../node_modules/bootstrap/scss/grid" +@import "../../node_modules/bootstrap/scss/tables" +@import "../../node_modules/bootstrap/scss/forms" +@import "../../node_modules/bootstrap/scss/buttons" +@import "../../node_modules/bootstrap/scss/transitions" +@import "../../node_modules/bootstrap/scss/dropdown" +@import "../../node_modules/bootstrap/scss/button-group" +@import "../../node_modules/bootstrap/scss/input-group" +@import "../../node_modules/bootstrap/scss/custom-forms" + +@import "../../node_modules/bootstrap/scss/nav" +@import "../../node_modules/bootstrap/scss/navbar" + +@import "../../node_modules/bootstrap/scss/card" +@import "../../node_modules/bootstrap/scss/breadcrumb" +@import "../../node_modules/bootstrap/scss/pagination" +@import "../../node_modules/bootstrap/scss/badge" +@import "../../node_modules/bootstrap/scss/jumbotron" +@import "../../node_modules/bootstrap/scss/alert" +@import "../../node_modules/bootstrap/scss/progress" +@import "../../node_modules/bootstrap/scss/media" +@import "../../node_modules/bootstrap/scss/list-group" +@import "../../node_modules/bootstrap/scss/close" +@import "../../node_modules/bootstrap/scss/modal" +@import "../../node_modules/bootstrap/scss/tooltip" +@import "../../node_modules/bootstrap/scss/popover" +@import "../../node_modules/bootstrap/scss/carousel" + +@import "../../node_modules/bootstrap/scss/utilities" +@import "../../node_modules/bootstrap/scss/print" + +// Pillar components. +@import "apps_base" +@import "components/base" + +@import "components/jumbotron" +@import "components/alerts" +@import "components/navbar" +@import "components/dropdown" +@import "components/footer" +@import "components/shortcode" +@import "components/statusbar" +@import "components/search" + +@import "components/flyout" +@import "components/forms" +@import "components/inputs" +@import "components/buttons" +@import "components/popover" +@import "components/tooltip" +@import "components/checkbox" +@import "components/overlay" +@import "components/card" + +@import _notifications @import _comments + @import _project @import _project-sharing @import _project-dashboard From 05c488c484b60da647c57121d0d2cd00e9f00e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 31 Aug 2018 15:49:04 +0200 Subject: [PATCH 021/148] Authentication: also accept user from session on API calls When loading the user from the session, a CSRF check is performed. --- pillar/api/utils/authentication.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pillar/api/utils/authentication.py b/pillar/api/utils/authentication.py index 6af98fa3..8ce39240 100644 --- a/pillar/api/utils/authentication.py +++ b/pillar/api/utils/authentication.py @@ -13,7 +13,7 @@ import logging import typing import bson -from flask import g, current_app +from flask import g, current_app, session from flask import request from werkzeug import exceptions as wz_exceptions @@ -103,7 +103,7 @@ def find_user_in_db(user_info: dict, provider='blender-id') -> dict: return db_user -def validate_token(*, force=False): +def validate_token(*, force=False) -> bool: """Validate the token provided in the request and populate the current_user flask.g object, so that permissions and access to a resource can be defined from it. @@ -115,7 +115,7 @@ def validate_token(*, force=False): :returns: True iff the user is logged in with a valid Blender ID token. """ - from pillar.auth import AnonymousUser + import pillar.auth # Trust a pre-existing g.current_user if not force: @@ -133,16 +133,22 @@ def validate_token(*, force=False): oauth_subclient = '' else: # Check the session, the user might be logged in through Flask-Login. - from pillar import auth - token = auth.get_blender_id_oauth_token() + # The user has a logged-in session; trust only if this request passes a CSRF check. + # FIXME(Sybren): we should stop saving the token as 'user_id' in the sesion. + token = session.get('user_id') + if token: + log.debug('skipping token check because current user already has a session') + current_app.csrf.protect() + else: + token = pillar.auth.get_blender_id_oauth_token() oauth_subclient = None if not token: # If no authorization headers are provided, we are getting a request # from a non logged in user. Proceed accordingly. log.debug('No authentication headers, so not logged in.') - g.current_user = AnonymousUser() + g.current_user = pillar.auth.AnonymousUser() return False return validate_this_token(token, oauth_subclient) is not None From 3f8e0396cf51b7987f5cca9c2fa82a30dbdec06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 31 Aug 2018 15:52:18 +0200 Subject: [PATCH 022/148] VideoJS: don't use `videojs.registerPlugin()` to start Google Analytics The `registerPlugin()` call should only be done once, and not for every video shown. This removes the warning about the 'analytics' plugin already being registered, which you see when navigating from one video to another via the JSTree. --- src/templates/nodes/custom/asset/video/view_embed.pug | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/templates/nodes/custom/asset/video/view_embed.pug b/src/templates/nodes/custom/asset/video/view_embed.pug index 72cd5a73..a6ab2860 100644 --- a/src/templates/nodes/custom/asset/video/view_embed.pug +++ b/src/templates/nodes/custom/asset/video/view_embed.pug @@ -59,16 +59,14 @@ script(type="text/javascript"). } }; - videojs.registerPlugin('analytics', function() { + videojs(videoPlayer, options).ready(function() { this.ga({ 'eventLabel' : '{{ node._id }} - {{ node.name }}', 'eventCategory' : '{{ node.project }}', 'eventsToTrack' : ['start', 'error', 'percentsPlayed'] }); - }); - - videojs(videoPlayer, options).ready(function() { this.hotkeys(); + }); function addVideoPlayerButton(data) { From b4acfb89fa3c6082f977e6e4ea86c46e77bb10d2 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 19:31:36 +0200 Subject: [PATCH 023/148] Layout: use bootstrap classes --- src/templates/_macros/_add_new_menu.pug | 15 +++-- src/templates/_macros/_file_uploader_form.pug | 10 +-- .../_macros/_file_uploader_javascript.pug | 6 +- src/templates/_modal.pug | 2 +- src/templates/layout.pug | 7 +- src/templates/menus/user_base.pug | 15 +++-- .../nodes/custom/_node_preview_forbidden.pug | 8 +-- .../nodes/custom/asset/video/view_embed.pug | 2 +- src/templates/nodes/custom/blog/_macros.pug | 12 ++-- .../nodes/custom/comment/list_embed_base.pug | 1 - .../nodes/custom/hdri/view_embed.pug | 2 +- src/templates/nodes/custom/post/create.pug | 4 +- .../nodes/custom/post/view_embed.pug | 2 +- .../nodes/custom/texture/view_embed.pug | 4 +- src/templates/nodes/edit_embed.pug | 14 ++-- src/templates/organizations/index.pug | 2 +- src/templates/organizations/view_embed.pug | 2 +- src/templates/projects/edit.pug | 23 ++++--- src/templates/projects/edit_layout.pug | 15 ++--- .../projects/edit_node_type_embed.pug | 4 +- src/templates/projects/home_images.pug | 2 +- src/templates/projects/home_layout.pug | 34 ++++++---- src/templates/projects/index_dashboard.pug | 48 ++++++++------ src/templates/projects/view.pug | 64 +++++++++---------- src/templates/projects/view_embed.pug | 5 +- src/templates/stats.pug | 2 +- src/templates/users/edit_embed_base.pug | 4 +- src/templates/users/login.pug | 4 +- src/templates/users/settings/base.pug | 2 +- src/templates/users/settings/profile.pug | 2 +- src/templates/users/tasks.pug | 2 +- 31 files changed, 170 insertions(+), 149 deletions(-) diff --git a/src/templates/_macros/_add_new_menu.pug b/src/templates/_macros/_add_new_menu.pug index 3e02de91..96d3bb6e 100644 --- a/src/templates/_macros/_add_new_menu.pug +++ b/src/templates/_macros/_add_new_menu.pug @@ -6,20 +6,21 @@ | {% if node_type_name == 'group' %} | {% set node_type_name = 'folder' %} | {% endif %} -li(class="button-{{ node_type['name'] }}") +li.dropdown-item(class="button-{{ node_type['name'] }}") a.item_add_node( - href="#", - title="{{ node_type['description'] }}", - data-node-type-name="{{ node_type['name'] }}", - data-toggle="tooltip", - data-placement="left") + href="#", + title="{{ node_type['description'] }}", + data-node-type-name="{{ node_type['name'] }}", + data-toggle="tooltip", + data-placement="left") i.pi(class="icon-{{ node_type['name'] }}") | {% if node_type_name == 'group_texture' %} | Texture Folder | {% elif node_type_name == 'group_hdri' %} | HDRi Folder | {% else %} - | {{ node_type_name }} + span.text-capitalize + |{{ node_type_name }} | {% endif %} | {% endif %} | {% endfor %} diff --git a/src/templates/_macros/_file_uploader_form.pug b/src/templates/_macros/_file_uploader_form.pug index d31f6b61..ad92c518 100644 --- a/src/templates/_macros/_file_uploader_form.pug +++ b/src/templates/_macros/_file_uploader_form.pug @@ -28,15 +28,15 @@ span Add files... input(type='file', name='file', multiple='') - button.btn.btn-primary.start(type='submit') + button.btn.btn-outline-primary.start(type='submit') i.pi-upload - span Start upload + span Start Upload - button.btn.btn-warning.cancel(type='reset') + button.btn.btn-outline-warning.cancel(type='reset') i.pi-cancel - span Cancel upload + span Cancel Upload - button.btn.btn-danger.delete(type='button') + button.btn.btn-outline-danger.delete(type='button') i.pi-trash span Delete diff --git a/src/templates/_macros/_file_uploader_javascript.pug b/src/templates/_macros/_file_uploader_javascript.pug index 53418c59..81e25f7f 100644 --- a/src/templates/_macros/_file_uploader_javascript.pug +++ b/src/templates/_macros/_file_uploader_javascript.pug @@ -23,7 +23,7 @@ script#template-upload(type="text/x-tmpl"). {% } %} {% if (!i) { %} - @@ -61,7 +61,7 @@ script#template-download(type="text/x-tmpl"). {% if (file.deleteUrl) { %} - @@ -71,7 +71,7 @@ script#template-download(type="text/x-tmpl"). Create {% } else { %} - diff --git a/src/templates/_modal.pug b/src/templates/_modal.pug index dd46e675..e66c1ccd 100644 --- a/src/templates/_modal.pug +++ b/src/templates/_modal.pug @@ -9,5 +9,5 @@ .modal-body | ... .modal-footer - button.btn.btn-default(type='button', data-dismiss='modal') Close + button.btn.btn-outline-secondary(type='button', data-dismiss='modal') Close button.btn.btn-primary(type='button') Save changes diff --git a/src/templates/layout.pug b/src/templates/layout.pug index 2c9e727c..fdfd8d52 100644 --- a/src/templates/layout.pug +++ b/src/templates/layout.pug @@ -66,10 +66,9 @@ html(lang="en") | {% if not title %}{% set title="default" %}{% endif %} body(class="{{ title }}") - .container-page - .page-content - .page-body - | {% block body %}{% endblock %} + .page-content + .page-body + | {% block body %}{% endblock %} | {% block footer_container %} #footer-container diff --git a/src/templates/menus/user_base.pug b/src/templates/menus/user_base.pug index a3fa6520..3cbebd77 100644 --- a/src/templates/menus/user_base.pug +++ b/src/templates/menus/user_base.pug @@ -16,27 +16,27 @@ li.dropdown a.navbar-item( href="{{ url_for('projects.home_project') }}" title="Home") - #[i.pi-home] Home + | #[i.pi-home] Home li a.navbar-item( href="{{ url_for('projects.index') }}" title="My Projects") - #[i.pi-star] My Projects + | #[i.pi-star] My Projects | {% if current_user.has_organizations() %} li a.navbar-item( href="{{ url_for('pillar.web.organizations.index') }}" title="My Organizations") - #[i.pi-users] My Organizations + | #[i.pi-users] My Organizations | {% endif %} li a.navbar-item( href="{{ url_for('settings.profile') }}" title="Settings") - #[i.pi-cog] Settings + | #[i.pi-cog] Settings | {% endblock menu_list %} @@ -55,8 +55,9 @@ li.dropdown | {% else %} -li.nav-item-sign-in - a.navbar-item(href="{{ url_for('users.login') }}") - | Log in +li.pt-1.pr-1 + a.btn.btn-sm.btn-outline-primary.px-3( + href="{{ url_for('users.login') }}") + | Log In | {% endif %} | {% endblock menu_body %} diff --git a/src/templates/nodes/custom/_node_preview_forbidden.pug b/src/templates/nodes/custom/_node_preview_forbidden.pug index 6dc22129..676a5b29 100644 --- a/src/templates/nodes/custom/_node_preview_forbidden.pug +++ b/src/templates/nodes/custom/_node_preview_forbidden.pug @@ -5,21 +5,21 @@ section.node-preview-forbidden div p Available to Blender Cloud subscribers - hr + hr.bg-white | {% if current_user.has_cap('can-renew-subscription') %} p small You have a subscription, it just needs to be renewed. - a.btn(href="/renew") + a.btn.btn-light(href="/renew") | #[i.pi-heart] Renew Subscription | {% else %} p small Support Blender and get awesome stuff! - a.btn(href="{{ url_for('cloud.join') }}") + a.btn.btn-light(href="{{ url_for('cloud.join') }}") | #[i.pi-heart] Get a Subscription | {% endif %} | {% if current_user.is_anonymous %} p(style="margin-top: 15px") small - a(href="{{ url_for('users.login') }}") Already a subscriber? Log in + a.text-white(href="{{ url_for('users.login') }}") Already a subscriber? Log in | {% endif %} diff --git a/src/templates/nodes/custom/asset/video/view_embed.pug b/src/templates/nodes/custom/asset/video/view_embed.pug index 72cd5a73..ea587f74 100644 --- a/src/templates/nodes/custom/asset/video/view_embed.pug +++ b/src/templates/nodes/custom/asset/video/view_embed.pug @@ -23,7 +23,7 @@ section.node-preview.video | {% block node_download %} | {% if node.file_variations %} -button.btn.btn-default.dropdown-toggle( +button.btn.btn-outline-secondary.dropdown-toggle( type="button", data-toggle="dropdown", aria-haspopup="true", diff --git a/src/templates/nodes/custom/blog/_macros.pug b/src/templates/nodes/custom/blog/_macros.pug index 1fc29fe3..8d9791f9 100644 --- a/src/templates/nodes/custom/blog/_macros.pug +++ b/src/templates/nodes/custom/blog/_macros.pug @@ -8,10 +8,10 @@ a.blog_index-header(href="{{ node.url }}") | {% if project and project._id != config.MAIN_PROJECT_ID %} | {{ projectmacros.render_secondary_navigation(project, pages=pages) }} | {% endif %} -.blog_index-item - a.item-title( - href="{{ node.url }}") - | {{ node.name }} +.blog_index-item.mx-auto + h2.pt-4.px-4 + a.text-muted(href="{{ node.url }}") + | {{ node.name }} ul.meta | {% if node.project.name %} li {{ node.project.name }} @@ -46,7 +46,7 @@ a.blog_index-header(href="{{ node.url }}") i.pi-document-text | {% endif %} - a.item-title( + a.item-title.text-muted( href="{{ node.url }}") | {{node.name}} @@ -66,7 +66,7 @@ a.blog_index-header(href="{{ node.url }}") | {% macro render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %} | {% if can_create_blog_posts %} .blog-action - a.btn.btn-default.button-create(href="{{url_for('nodes.posts_create', project_id=project._id)}}") + a.btn.btn-outline-primary.button-create(href="{{url_for('nodes.posts_create', project_id=project._id)}}") i.pi-plus | Create New Post | {% endif %} diff --git a/src/templates/nodes/custom/comment/list_embed_base.pug b/src/templates/nodes/custom/comment/list_embed_base.pug index 143faa7a..90ddf312 100644 --- a/src/templates/nodes/custom/comment/list_embed_base.pug +++ b/src/templates/nodes/custom/comment/list_embed_base.pug @@ -32,7 +32,6 @@ .comment-reply-preview-md .comment-reply-info .comment-action-cancel( - type="button", title="{{ _('cancel') }}") span {{ _('cancel') }} diff --git a/src/templates/nodes/custom/hdri/view_embed.pug b/src/templates/nodes/custom/hdri/view_embed.pug index 2fc00288..5c3a856a 100644 --- a/src/templates/nodes/custom/hdri/view_embed.pug +++ b/src/templates/nodes/custom/hdri/view_embed.pug @@ -28,7 +28,7 @@ li.node-details-meta-list-item | {% block node_download %} | {% if node.properties.files %} -button.btn.btn-default.dropdown-toggle( +button.btn.btn-outline-secondary.dropdown-toggle( title="Download HDRI", type="button", data-toggle="dropdown", diff --git a/src/templates/nodes/custom/post/create.pug b/src/templates/nodes/custom/post/create.pug index 1e17dd61..10414e29 100644 --- a/src/templates/nodes/custom/post/create.pug +++ b/src/templates/nodes/custom/post/create.pug @@ -68,9 +68,9 @@ | {% endif %} | {% endfor %} - input.btn.btn-default.button-create(type='submit', value='Create {{ node_type.name }}') + input.btn.btn-outline-secondary.button-create(type='submit', value='Create {{ node_type.name }}') - a.btn.btn-default.button-back(href="{{ url_for('projects.view', project_url=project.url) }}blog") + a.btn.btn-link.button-back(href="{{ url_for('projects.view', project_url=project.url) }}blog") | Back to Blog #blog_post-create-container diff --git a/src/templates/nodes/custom/post/view_embed.pug b/src/templates/nodes/custom/post/view_embed.pug index 3922fab9..4924162e 100644 --- a/src/templates/nodes/custom/post/view_embed.pug +++ b/src/templates/nodes/custom/post/view_embed.pug @@ -5,7 +5,7 @@ #blog_index-container.expand-image-links .blog-action | {% if node.has_method('PUT') %} - a.btn.btn-default.button-edit(href="{{url_for('nodes.edit', node_id=node._id)}}") + a.btn.btn-outline-secondary.button-edit(href="{{url_for('nodes.edit', node_id=node._id)}}") i.pi-edit | Edit Post | {% endif %} diff --git a/src/templates/nodes/custom/texture/view_embed.pug b/src/templates/nodes/custom/texture/view_embed.pug index e6d90184..654cc5db 100644 --- a/src/templates/nodes/custom/texture/view_embed.pug +++ b/src/templates/nodes/custom/texture/view_embed.pug @@ -91,11 +91,11 @@ a(href="{{ f.file.link }}",, title="Download texture", download="{{ f.file.filename }}") - button.btn.btn-default(type="button") + button.btn.btn-outline-secondary(type="button") i.pi-download | Download | {% else %} - button.btn.btn-default.disabled.sorry(type="button") + button.btn.btn-outline-secondary.disabled.sorry(type="button") i.pi-lock | Download | {% endif %} diff --git a/src/templates/nodes/edit_embed.pug b/src/templates/nodes/edit_embed.pug index cfe935b3..bd78fdf8 100644 --- a/src/templates/nodes/edit_embed.pug +++ b/src/templates/nodes/edit_embed.pug @@ -38,18 +38,21 @@ | {% elif field.type == 'HiddenField' %} | {{ field }} + | {% elif field.name == 'attachments' %} + hr #attachments-actions .btn.btn-info#attachments-action-add i.pi-plus | Add New Attachment - p.text-muted + p.text-muted.mt-3 | Attachments can be included in any MarkDown field by using the #[code {attachment slug}] shortcode | (#[a(href='https://pillarframework.org/shortcodes/#attachments', target='_blank') help]). | This shortcode is placed on your copy-paste buffer by clicking "Copy to clipboard". | {{ render_field(field, field.name) }} + hr | {% elif field.name == 'files' %} #files-actions @@ -66,20 +69,23 @@ | {% endfor %} - ul.project-edit-tools.bottom + hr + + ul.project-edit-tools.justify-content-end.h-auto li.button-cancel - a#item_cancel.item-cancel.project-mode-edit( + a#item_cancel.item-cancel.project-mode-edit.btn.btn-outline-secondary( href="javascript:void(0);", title="Cancel changes") i.button-cancel-icon.pi-cancel | Cancel li.button-save - a#item_save.item-save.project-mode-edit( + a#item_save.item-save.project-mode-edit.btn.btn-outline-success.ml-2( href="javascript:void(0);", title="Save changes") i.button-save-icon.pi-check | Save Changes + script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.ui.widget.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.iframe-transport.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.fileupload.min.js') }}") diff --git a/src/templates/organizations/index.pug b/src/templates/organizations/index.pug index 511aa935..53c222c4 100644 --- a/src/templates/organizations/index.pug +++ b/src/templates/organizations/index.pug @@ -27,7 +27,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba ul#sub-nav-tabs__list li.result#create_organization_result_panel li.create - button.btn.btn-success(onclick='createNewOrganization(this)') + button.btn.btn-outline-success(onclick='createNewOrganization(this)') i.pi-plus | Create Organization | {% endif %} diff --git a/src/templates/organizations/view_embed.pug b/src/templates/organizations/view_embed.pug index a9ab4564..9bc2a31b 100644 --- a/src/templates/organizations/view_embed.pug +++ b/src/templates/organizations/view_embed.pug @@ -75,7 +75,7 @@ script $('#admin-picker').hide(); .input-group - button#item-save.btn.btn-success.btn-block(type='submit') + button#item-save.btn.btn-outline-success.btn-block(type='submit') i.pi-check | Save Changes | {% else %} diff --git a/src/templates/projects/edit.pug b/src/templates/projects/edit.pug index 77d40c6d..9132251e 100644 --- a/src/templates/projects/edit.pug +++ b/src/templates/projects/edit.pug @@ -7,17 +7,16 @@ span#project-edit-title | Edit Project ul.project-edit-tools - // Edit Mode li.button-cancel - a#item_cancel.project-mode-edit( + a#item_cancel.project-mode-edit.btn.btn-sm.btn-link( href="{{url_for('projects.view', project_url=project.url, _external=True)}}", title="Cancel changes") - i.button-cancel-icon.pi-back + i.button-cancel-icon.pi-angle-left | Go to Project li.button-save - a#item_save.project-mode-edit( + a#item_save.project-mode-edit.btn.btn-sm.btn-outline-success.mx-2( href="#", title="Save changes") i.button-save-icon.pi-check @@ -80,16 +79,18 @@ ul.project-edit-tools | {% endfor %} - ul.project-edit-tools.bottom + hr + + ul.project-edit-tools.justify-content-end.h-auto li.button-cancel - a#item_cancel.project-mode-edit( + a#item_cancel.project-mode-edit.btn.btn-link( href="{{url_for('projects.view', project_url=project.url, _external=True)}}", title="Cancel changes") - i.button-cancel-icon.pi-back + i.button-cancel-icon.pi-angle-left | Go to Project li.button-save - a#item_save.project-mode-edit( + a#item_save.project-mode-edit.btn.btn-outline-success.ml-2( href="#", title="Save changes") i.button-save-icon.pi-check @@ -97,6 +98,8 @@ ul.project-edit-tools | {% endblock %} +| {% block footer_container %}{% endblock %} + | {% block footer_scripts %} script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.ui.widget.min.js') }}") script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.iframe-transport.min.js') }}") @@ -105,8 +108,8 @@ script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets script(type="text/javascript"). - // Show view mode buttons. - $('.project-mode-view').displayAs('inline-block'); + // Show edit mode buttons (save, cancel). + $('.project-mode-edit').displayAs('inline-block'); ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''}); var convert = new Markdown.getSanitizingConverter().makeHtml; diff --git a/src/templates/projects/edit_layout.pug b/src/templates/projects/edit_layout.pug index 4c0bd38a..1809f4bc 100644 --- a/src/templates/projects/edit_layout.pug +++ b/src/templates/projects/edit_layout.pug @@ -5,7 +5,7 @@ #project-container #project-side-container #project_sidebar - ul.project-tabs + ul.project-tabs.p-0 li.tabs-thumbnail( title="About", data-toggle="tooltip", @@ -51,13 +51,13 @@ #project_nav #project_nav-container - #project_nav-header - .project-title - a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") - | {{ project.name }} + #project_nav-header.bg-white + a.project-title.p-2.font-weight-bold.text-dark( + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | {{ project.name }} // TODO - make list a macro - #project_tree.edit + #project_tree.edit.bg-white ul.project_nav-edit-list li(class="{% if title == 'edit' %}active{% endif %}") a(href="{{ url_for('projects.edit', project_url=project.url) }}") @@ -77,10 +77,9 @@ i(class="pi-{{ext.icon}}") | {{ext.name | title}} | {% endfor %} - .project_split(title="Toggle Navigation [T]") #project_context-container - #project_context-header + #project_context-header.bg-white span#status-bar | {% block project_context_header %} | {% endblock %} diff --git a/src/templates/projects/edit_node_type_embed.pug b/src/templates/projects/edit_node_type_embed.pug index ca1fd4d7..aff9e8b4 100644 --- a/src/templates/projects/edit_node_type_embed.pug +++ b/src/templates/projects/edit_node_type_embed.pug @@ -19,7 +19,7 @@ form( .col-md-9 h3 Editing: {{ node_type['name'] }} .col-md-3 - button.js-form-save.btn.btn-success.pull-right(style="margin-top: 15px;") + button.js-form-save.btn.btn-outline-success.pull-right(style="margin-top: 15px;") | Save Changes .row @@ -46,7 +46,7 @@ form( | {% endif %} | {% endfor %} - button.js-form-save.btn.btn-success.pull-right + button.js-form-save.btn.btn-outline-success.pull-right | Save Changes diff --git a/src/templates/projects/home_images.pug b/src/templates/projects/home_images.pug index 7399d796..274a9dfc 100644 --- a/src/templates/projects/home_images.pug +++ b/src/templates/projects/home_images.pug @@ -80,7 +80,7 @@ section.nav-tabs__tab.active#tab-images Image Sharing requires a Blender Cloud subscription. .buttons - a.btn.btn-default.btn-outline.green(href="https://store.blender.org/product/membership/") + a.btn.btn-outline-primary(href="https://store.blender.org/product/membership/") | Join Now | {% endif %} | {% endblock %} diff --git a/src/templates/projects/home_layout.pug b/src/templates/projects/home_layout.pug index 733f6ae7..58111965 100644 --- a/src/templates/projects/home_layout.pug +++ b/src/templates/projects/home_layout.pug @@ -38,29 +38,35 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba section.dashboard-secondary section.announcement - img.header( - src="{{ url_for('static', filename='assets/img/blender_sync_header.jpg') }}") + a(href="https://cloud.blender.org/blog/introducing-blender-sync") + img.header( + src="{{ url_for('static', filename='assets/img/blender_sync_header.jpg') }}") .text | {% block side_announcement %} - .title - a(href="https://cloud.blender.org/blog/introducing-blender-sync") Blender Sync + h5 + a.text-muted(href="https://cloud.blender.org/blog/introducing-blender-sync") + | Blender Sync .lead - span. + p. Save your settings once. Use them anywhere. Carry your Blender configuration with you, use our free add-on to sync your keymaps and preferences. -
+ #[hr] Syncing is free for everyone. No subscription required. | {% endblock %} + | {% if show_addon_download_buttons %} - .buttons - a.btn.btn-default.btn-outline.orange( - href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip") - i.pi-download - | Download v{{ config.BLENDER_CLOUD_ADDON_VERSION }} - a.btn.btn-default.btn-outline.blue( - href="{{ learn_more_btn_url }}") - | Learn More + .row + .col-md-8 + a.btn.btn-block.btn-outline-success( + href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip") + i.pi-download + | Download v{{ config.BLENDER_CLOUD_ADDON_VERSION }} + .col-md-4 + a.btn.btn-link( + href="{{ learn_more_btn_url }}") + | Learn More + i.pi-angle-right | {% endif %} | {% endblock %} diff --git a/src/templates/projects/index_dashboard.pug b/src/templates/projects/index_dashboard.pug index bb198864..fcc50df2 100644 --- a/src/templates/projects/index_dashboard.pug +++ b/src/templates/projects/index_dashboard.pug @@ -59,15 +59,15 @@ style. | {% endif %} | {% if current_user.has_cap('subscriber') %} - li.create#project-create( + li#project-create( data-url="{{ url_for('projects.create') }}") a.btn.btn-success( href="{{ url_for('projects.create') }}") i.pi-plus | Create Project | {% elif current_user.has_cap('can-renew-subscription') %} - li.create - a.btn(href="/renew", target="_blank") + li + a.btn.btn-outline-success(href="/renew", target="_blank") i.pi-heart | Renew subscription to create a project | {% endif %} @@ -210,44 +210,52 @@ style. section.dashboard-secondary section.announcement - img.header( - src="{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}") + a(href="https://cloud.blender.org/blog/introducing-private-projects") + img.header( + src="{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}") .text - h5 Projects + h5 + a.text-muted(href="https://cloud.blender.org/blog/introducing-private-projects") + | Projects .lead - span. + p. Create and manage your own personal projects. Upload assets and collaborate with other Blender Cloud members. - .buttons - a.btn.btn-default.btn-outline.blue( + + a.btn.btn-link.btn-block( href="https://cloud.blender.org/blog/introducing-private-projects") | Learn More + i.pi-angle-right section.announcement.mt-3 a(href="https://cloud.blender.org/blog/introducing-blender-sync") - img.header( - src="{{ url_for('static', filename='assets/img/blender_sync_header.jpg') }}") + img.header( + src="{{ url_for('static', filename='assets/img/blender_sync_header.jpg') }}") .text h5 a.text-muted(href="https://cloud.blender.org/blog/introducing-blender-sync") | Textures Browser & Settings Sync .lead - span. + p. Get the official Blender Cloud add-on: + ul li Save your Blender settings online, use them anywhere li Browse over 800 textures & HDRIs within Blender li Share Screenshots & Renders directly to Blender Cloud - .buttons - a.btn.btn-default.btn-outline.orange( - href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip") - i.pi-download - | Download Add-on v {{ config.BLENDER_CLOUD_ADDON_VERSION }} - a.btn.btn-default.btn-outline.blue( - href="https://cloud.blender.org/blog/introducing-blender-sync") - | Learn More + .row + .col-md-8 + a.btn.btn-outline-success.btn-block( + href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip") + i.pi-download + | Download Add-on v {{ config.BLENDER_CLOUD_ADDON_VERSION }} + .col-md-4 + a.btn.btn-link( + href="https://cloud.blender.org/blog/introducing-blender-sync") + | Learn More + i.pi-angle-right | {% endblock %} diff --git a/src/templates/projects/view.pug b/src/templates/projects/view.pug index f4669381..8151fbe6 100644 --- a/src/templates/projects/view.pug +++ b/src/templates/projects/view.pug @@ -2,6 +2,7 @@ | {% from '_macros/_add_new_menu.html' import add_new_menu %} | {% block page_title %}{{ project.name }}{% endblock%} +| {% set title = 'project' %} | {% block og %} meta(property="og:type", content="website") @@ -78,15 +79,14 @@ script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotke | {% block css %} link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet") -link(href="{{ url_for('static_pillar', filename='assets/css/base.css') }}", rel="stylesheet") link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') }}", rel="stylesheet") | {% endblock %} | {% block body %} #project-container #project-side-container - #project_sidebar - ul.project-tabs + #project_sidebar.bg-white + ul.project-tabs.p-0 li.tabs-thumbnail(class="{% if project.picture_square %}image{% endif %}") a(href="{{url_for('projects.view', project_url=project.url)}}") #project-loading @@ -96,6 +96,7 @@ link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') } | {% else %} i.pi-home | {% endif %} + li.tabs-browse( title="Browse", data-toggle="tooltip", @@ -103,6 +104,7 @@ link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') } class="active") a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") i.pi-folder + | {% if not project.is_private %} | {% if current_user_is_subscriber %} li.tabs-search( @@ -135,29 +137,29 @@ link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') } #project_nav(class="{{ title }}") #project_nav-container | {% if title != 'about' %} - #project_nav-header - .project-title - a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") - | {{ project.name }} + #project_nav-header.bg-white + a.project-title.p-2.font-weight-bold.text-dark( + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | {{ project.name }} | {% block project_tree %} - #project_tree + #project_tree.bg-white | {% endblock project_tree %} | {% endif %} #project_context-container | {% if project.has_method('PUT') %} - #project_context-header + #project_context-header.bg-white span#status-bar ul.project-edit-tools.disabled - li.button-dropdown - a#item_add.dropdown-toggle.project-mode-view( - type="button", - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false") + li.dropdown + button#item_add.project-mode-view.btn.btn-sm.btn-outline-secondary.dropdown-toggle( + type="button", + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false") i.button-add-icon.pi-collection-plus | New... @@ -165,52 +167,52 @@ link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') } | {{ add_new_menu(project.node_types) }} li.button-edit - a#item_edit.project-mode-view( + a#item_edit.project-mode-view.btn.btn-sm.btn-outline-secondary.ml-2( href="javascript:void(0);", title="Edit", data-project_id="{{project._id}}") i.button-edit-icon.pi-edit | Edit Project - li.button-dropdown - a.dropdown-toggle.project-mode-view( + li.dropdown + button.dropdown-toggle.project-mode-view.btn.btn-sm.btn-outline-secondary.mx-2( type="button", data-toggle="dropdown", aria-haspopup="true", aria-expanded="false") - i.pi-more-vertical + i.pi-more-vertical.p-0 ul.dropdown-menu | {% if current_user.has_cap('admin') %} - li.button-featured + li.dropdown-item a#item_featured( href="javascript:void(0);", title="Feature on project's homepage", data-toggle="tooltip", data-placement="left") - i.button-featured-icon.pi-star + i.pi-star | Toggle Featured - li.button-toggle-public + li.dropdown-item a#item_toggle_public( href="javascript:void(0);", title="Make it accessible to anyone", data-toggle="tooltip", data-placement="left") i.pi-lock-open - | Toggle public + | Toggle Public | {% endif %} - li.button-toggle-projheader + li.dropdown-item a#item_toggle_projheader( href="javascript:void(0);", title="Feature as project's header", data-toggle="tooltip", data-placement="left") - i.button-featured-icon.pi-star + i.pi-star | Toggle Project Header video - li.button-move + li.dropdown-item.button-move a#item_move( href="javascript:void(0);", title="Move into a folder...", @@ -219,7 +221,7 @@ link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') } i.button-move-icon.pi-move | Move - li.button-delete + li.dropdown-item.button-delete a#item_delete( href="javascript:void(0);", title="Can be undone within a month", @@ -230,14 +232,14 @@ link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') } // Edit Mode li.button-cancel - a#item_cancel.project-mode-edit( + a#item_cancel.project-mode-edit.btn.btn-outline-secondary( href="javascript:void(0);", title="Cancel changes") i.button-cancel-icon.pi-cancel | Cancel li.button-save - a#item_save.project-mode-edit( + a#item_save.project-mode-edit.btn.btn-outline-success.mx-2( href="javascript:void(0);", title="Save changes") i.button-save-icon.pi-check @@ -272,9 +274,7 @@ link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') } | {% endblock %} -| {% block footer_navigation %}{% endblock %} -| {% block footer %}{% endblock %} - +| {% block footer_container %}{% endblock %} | {% block footer_scripts_pre %} diff --git a/src/templates/projects/view_embed.pug b/src/templates/projects/view_embed.pug index ffa2ac30..e14a2999 100644 --- a/src/templates/projects/view_embed.pug +++ b/src/templates/projects/view_embed.pug @@ -22,9 +22,8 @@ | {% endif %} section.node-details-container.project - .node-details-title - h1 - a(href="{{ url_for( 'projects.view', project_url=project.url) }}") {{ project.name }} + h2.px-3.pt-3 + a(href="{{ url_for( 'projects.view', project_url=project.url) }}") {{ project.name }} | {% if project.description %} .node-details-description diff --git a/src/templates/stats.pug b/src/templates/stats.pug index 2ae14414..e43d1974 100644 --- a/src/templates/stats.pug +++ b/src/templates/stats.pug @@ -49,7 +49,7 @@ h3. Now only $10 per month - .btn.btn-default + .btn.btn-outline-primary | Join the Cloud hr diff --git a/src/templates/users/edit_embed_base.pug b/src/templates/users/edit_embed_base.pug index 52743002..aa66e199 100644 --- a/src/templates/users/edit_embed_base.pug +++ b/src/templates/users/edit_embed_base.pug @@ -72,9 +72,9 @@ | none | {% endif %} - a.btn.btn-default(href="javascript:update_from_bid()") Update from Blender ID + a.btn.btn-outline-secondary(href="javascript:update_from_bid()") Update from Blender ID - input#submit_edit_user.btn.btn-default( + input#submit_edit_user.btn.btn-outline-success( data-user-id="{{user.user_id}}", type="submit" value="Submit") diff --git a/src/templates/users/login.pug b/src/templates/users/login.pug index f01f2868..e0c91e1b 100644 --- a/src/templates/users/login.pug +++ b/src/templates/users/login.pug @@ -18,11 +18,11 @@ .buttons .login-button-container //a.forgot(href="https://blender.org/id/reset") forgot your password? - button.btn.btn-success.btn-block.button-login(type="submit") + button.btn.btn-outline-success.btn-block.button-login(type="submit") i.pi-log-in | Login - //a.btn.btn-default.button-register(href="https://blender.org/id/register", target="_blank") + //a.btn.btn-outline-secondary.button-register(href="https://blender.org/id/register", target="_blank") // i.pi-star-outline // | Create Account diff --git a/src/templates/users/settings/base.pug b/src/templates/users/settings/base.pug index 5b6ad086..5cf350b3 100644 --- a/src/templates/users/settings/base.pug +++ b/src/templates/users/settings/base.pug @@ -3,7 +3,7 @@ //- can provide overrides. | {% block body %} .container - #settings + #settings.d-flex.py-4.flex-xs-column #settings-sidebar | {% block settings_sidebar %} .settings-header diff --git a/src/templates/users/settings/profile.pug b/src/templates/users/settings/profile.pug index ce9c5627..427727dd 100644 --- a/src/templates/users/settings/profile.pug +++ b/src/templates/users/settings/profile.pug @@ -28,7 +28,7 @@ span {{ _("Change Gravatar") }} .buttons - button.btn.btn-default.button-submit(type='submit') + button.btn.btn-outline-success.button-submit(type='submit') i.pi-check | {{ _("Save Changes") }} | {% endblock %} diff --git a/src/templates/users/tasks.pug b/src/templates/users/tasks.pug index 3110ffba..96c76ec9 100644 --- a/src/templates/users/tasks.pug +++ b/src/templates/users/tasks.pug @@ -137,7 +137,7 @@ script(). $('td', row).eq(4).html(render_timing(data.timing)); $('td', row).eq(5).html(render_status_label(data, data.name)); - var view_tag = ' View'; + var view_tag = ' View'; $('td', row).eq(6).html(view_tag); } }); From 9c2ded79dd7670174a065c91274675366e6871b7 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Fri, 31 Aug 2018 19:32:17 +0200 Subject: [PATCH 024/148] CSS: Cleanup and simplification Mainly to rely more on bootstrap styling --- src/styles/_comments.sass | 9 + src/styles/_config.sass | 2 +- src/styles/_organizations.sass | 3 - src/styles/_project.sass | 384 +++++---------------------- src/styles/_user.sass | 7 - src/styles/_utils.sass | 21 +- src/styles/blog.sass | 23 +- src/styles/components/_base.sass | 6 +- src/styles/components/_dropdown.sass | 1 + src/styles/components/_footer.sass | 19 +- src/styles/components/_inputs.sass | 16 -- src/styles/components/_navbar.sass | 128 ++++----- src/styles/components/_search.sass | 21 +- src/styles/plugins/_jstree.sass | 10 +- src/styles/theatre.sass | 3 +- 15 files changed, 177 insertions(+), 476 deletions(-) diff --git a/src/styles/_comments.sass b/src/styles/_comments.sass index 0c24e0f4..812aca8d 100644 --- a/src/styles/_comments.sass +++ b/src/styles/_comments.sass @@ -453,12 +453,17 @@ $comments-width-max: 710px transition: background-color 150ms ease-in-out, color 150ms ease-in-out width: 100px + // The actual button for submitting the comment. button.comment-action-submit + align-items: center background: transparent border: none border-top-left-radius: 0 border-bottom-left-radius: 0 color: $color-success + cursor: pointer + display: flex + justify-content: center flex-direction: column height: 100% position: relative @@ -466,8 +471,12 @@ $comments-width-max: 710px white-space: nowrap width: 100% + &:hover + background: rgba($color-success, .1) + &:focus background: lighten($color-success, 10%) + color: $white &.submitting color: $color-info diff --git a/src/styles/_config.sass b/src/styles/_config.sass index 04c0831e..f0f1f1f7 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -132,7 +132,7 @@ $btn-border-radius: $border-radius $primary: $color-primary -$body-bg: $color-background +$body-bg: $white $body-color: $color-text $color-background-nav: #fff diff --git a/src/styles/_organizations.sass b/src/styles/_organizations.sass index 583a8250..f3b82eb0 100644 --- a/src/styles/_organizations.sass +++ b/src/styles/_organizations.sass @@ -5,9 +5,6 @@ body.organizations li.result padding: 10px 20px - li.create - margin-left: auto - .dashboard-secondary .box diff --git a/src/styles/_project.sass b/src/styles/_project.sass index 2a71724c..822380ca 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -1,4 +1,3 @@ -$project-sidebar-background: lighten($color-background, 5%) $node-latest-thumbnail-size: 160px body.open-projects, @@ -68,11 +67,6 @@ body.blog width: 100% ul.project-tabs - background-color: $color-background-nav - margin: 0 - padding: 0 - list-style: none - position: fixed width: $project-sidebar-width top: $project_header-height @@ -85,16 +79,9 @@ body.blog width: 100% li - display: flex - align-items: center - justify-content: center width: $project-sidebar-width height: $project-sidebar-width position: relative - color: white - background-color: $color-background-nav - border-left: 2px solid transparent - transition: all 100ms ease-in-out +media-xs border-bottom: 2px solid transparent @@ -109,77 +96,41 @@ body.blog border-top: thin solid transparent &:hover - background-color: $color-background-nav-light + background-color: $primary cursor: pointer - +media-xs - border-bottom: 2px solid $color-background-nav-light - +media-sm - border-bottom: 2px solid $color-background-nav-light - +media-md - border-left: 2px solid $color-background-nav-light - +media-lg - border-left: 2px solid $color-background-nav-light + a + color: $white a - width: $project-sidebar-width - height: $project-sidebar-width - display: flex align-items: center + display: flex justify-content: center - color: $color-text-light-primary + height: $project-sidebar-width + width: $project-sidebar-width i font-size: 1.1em &.active - background-color: $color-background-nav-light - - // The tiny triangle - &:after - +media-xs - border-top-color: $project-sidebar-background - border-right-color: transparent - left: 50% - right: initial - transform: translateX(-50%) - top: 0 - - border: 7px solid transparent - border-right-color: $project-sidebar-background - content: '' - display: block - position: absolute - right: 0 - top: 50% - transform: translateY(-50%) + background-color: $primary + color: $white a - color: white + color: $white &.tabs-thumbnail - height: $project-sidebar-width - border-bottom: 2px solid $color-background-nav-light - img - width: $project-sidebar-width height: $project-sidebar-width - - &.image - border-left: none + width: $project-sidebar-width #project-loading - position: absolute - width: $project-sidebar-width - height: $project-sidebar-width - display: flex align-items: center + display: flex + height: $project-sidebar-width justify-content: center - i - position: relative - top: -1px - font-size: 1em - color: white + width: $project-sidebar-width + #search-container #project_sidebar ul.project-tabs li.tabs-thumbnail background-color: $color-background-nav-dark @@ -199,7 +150,6 @@ body.blog +media-xs width: initial - background-color: $project-sidebar-background display: block left: 0 position: relative @@ -221,10 +171,6 @@ body.blog #project_context-header align-items: center - background-color: white - background-color: $color-background-light - border-bottom: thin solid lighten($color-text-dark-hint, 10%) - box-shadow: none color: $color-text-dark-secondary display: flex height: $project_header-height @@ -232,42 +178,24 @@ body.blog position: fixed top: $project_header-height transition: box-shadow 250ms ease-in-out - width: auto z-index: $z-index-base + 3 &.is-offset box-shadow: 0 0 25px rgba(black, .2) #project_nav-header - z-index: $z-index-base + 3 + left: 0 position: absolute top: 0 - left: 0 - height: $project_header-height - min-height: $project_header-height width: 100% - background-color: $color-background-light - border-bottom: thin solid $color-background-dark + z-index: $z-index-base + 3 /* Name of the project */ .project-title - width: 100% - max-width: 100% + +text-overflow-ellipsis height: 100% - transition: background-color 150ms ease-in-out - - a - display: block - width: 100% - color: $color-text-dark - padding: 15px - font-size: 1.1em - +text-overflow-ellipsis - - &:hover, &:active - color: $color-primary - text-decoration: none - outline: none + max-width: 100% + width: 100% span#status-bar position: absolute @@ -278,7 +206,6 @@ span#status-bar color: $color-text-dark opacity: 0 z-index: 1 - background-color: $color-background-light font-weight: 400 white-space: nowrap transition: all 250ms ease-in-out @@ -293,7 +220,7 @@ span#status-bar &.info color: $color-info &.error - color: $color-danger + color: $danger &.warning color: $color-warning &.success @@ -320,71 +247,29 @@ span#project-edit-title display: none ul.project-edit-tools - display: flex align-items: center - height: 100% - padding: 0 - margin: - top: 0 - left: auto - right: 5px - bottom: 0 - color: $color-text-dark-hint - + display: flex list-style-type: none - font-size: .9em + margin: 0 0 0 auto + padding: 0 +media-xs width: 100% margin: 0 auto justify-content: space-around - /* Bottons at the end of a form, like Save Changes */ - &.bottom - +clearfix - padding: 20px 0 25px 0 - border-top: thin solid $color-text-dark-hint - justify-content: flex-end - width: 100% - height: initial - li - margin: 0 - padding: 0 - position: relative - float: left - user-select: none + a, button + padding: $dropdown-item-padding-y ($dropdown-item-padding-x / 2) - a - min-width: 110px - user-select: none - - &:focus, &:active - text-decoration: none - outline: none + i + padding-right: 10px &.button-save - - &.disabled - border-bottom: none - - a - color: white - border-color: darken($color-success, 10%) - - a - border-color: $color-success - background-color: $color-success - color: white - margin-right: 10px - - &:hover - background-color: lighten($color-success, 5%) - &.field-error a - background-color: $color-danger - border-color: $color-danger + background-color: $danger + border-color: $danger color: white &.saving @@ -411,31 +296,6 @@ ul.project-edit-tools &.button-edit min-width: 80px - - &.button-dropdown - min-width: 50px - cursor: pointer - i - margin: auto - padding: 0 - - &.button-add-icon - margin-right: 5px - - a.dropdown-toggle - display: inline-block - min-width: auto - - &.open - a.dropdown-toggle - color: $color-text-light - background-color: $color-primary - border-color: darken($color-primary, 5%) - - li.button-delete:hover - a - color: $color-danger - &.featured a color: $color-warning @@ -445,132 +305,44 @@ ul.project-edit-tools background-color: rgba($color-warning, .1) &.disabled - cursor: not-allowed - border-bottom: thin solid $color-text-dark-hint + +disabled-stripes a - pointer-events: none - background: repeating-linear-gradient(-45deg, lighten($color-text-dark-hint, 15%), lighten($color-text-dark-hint, 15%) 10px, lighten($color-text-dark-hint, 5%) 10px, lighten($color-text-dark-hint, 5%) 20px) - border-color: darken($color-text-dark-hint, 5%) - opacity: .6 - color: $color-text-dark + +disabled-stripes - &.button-dropdown.disabled - border-bottom: none - - a - margin: 5px - padding: 5px 15px - color: $color-text-dark-primary - border-radius: 3px - border: thin solid $color-text-dark-primary - text-align: center - text-transform: uppercase - transition: background-color 150ms ease-in-out - background-color: $color-background-light - - i - margin-right: 5px - - &:hover - background-color: $color-background-active-dark - border-color: darken($color-background-active-dark, 5%) - color: $color-text-light - text-decoration: none - - &:focus, &:active - text-decoration: none - outline: none - - - &#item_save, - &#item_cancel - i - font-size: 1em - - &#item_add, - &#item_edit, - &#item_move, - &#item_featured + &.dropdown li - i - margin-right: 10px + padding: 0 - &#item_delete - li i - margin-right: 0 - // &:not(:last-of-type) - // border-right: thin solid darken($color-background, 50%) + a + color: $body-color + display: block + padding: $dropdown-item-padding-y $dropdown-item-padding-x + padding-left: 15px + user-select: none - /* ul.project-edit-tools */ + &:hover + color: $primary + text-decoration: none - /* Extra asset tools in dropdown */ - ul.dropdown-menu - width: auto - min-width: 180px - padding: 0 - margin: 0 - top: 44px - left: initial - right: 10px - bottom: initial - border: thin solid $color-text-dark-hint - border-bottom-left-radius: 3px - border-bottom-right-radius: 3px - border-top-left-radius: 0 - border-top-right-radius: 0 - background-color: white - color: $color-text-dark-primary - box-shadow: 1px 1px 25px rgba(black, .2) - - li - padding: 0 - clear: both - display: flex - align-items: center - width: 100% - - a - margin: 0 - padding: 10px 15px - width: 100% - border: 0 - font-size: .9em - width: 100% - text-align: left - - &:hover - color: $color-primary - background-color: transparent - &:active, &:focus - color: $color-primary - background-color: transparent - - i - display: inline-block - margin: 0 15px 0 0 - - &.icon-group:after - content: '\e80d' - &.icon-group_texture:after, - &.icon-group_hdri:after - content: '\e80b' - font-size: 1.1em - &.icon-asset:after - content: '\e825' - &.icon-page:after - content: '\e824' - &.icon-texture:after - content: '\e80a' - &.icon-hdri:after - content: '\f019' + /* Icons per asset type. */ + i + &.icon-group:after + content: '\e80d' + &.icon-group_texture:after, + &.icon-group_hdri:after + content: '\e80b' + font-size: 1.1em + &.icon-asset:after + content: '\e825' + &.icon-page:after + content: '\e824' + &.icon-texture:after + content: '\e80a' + &.icon-hdri:after + content: '\f019' /* // Extra asset tools in dropdown */ - - &.open - button - box-shadow: none - /* // Edit Asset buttons */ #project-loading @@ -661,7 +433,6 @@ ul.project_nav-edit-list #node-container flex: 1 - color: $color-text-dark background-color: white /* For error messages (403) and other overlaid text*/ @@ -684,8 +455,6 @@ ul.project_nav-edit-list /* Project context on the right of the navigation */ /* Contains #project_context */ #project_context-container - background-color: $color-background-nav - iframe#server_error width: 100% min-height: 800px @@ -697,11 +466,10 @@ ul.project_nav-edit-list +media-xs margin-top: 0 - background-color: $project-sidebar-background border-right: thin solid $color-background - margin-top: $project_header-height - overflow-y: auto - padding: 0 0 5px 0// some padding on top/bottom of jstree + margin-top: $project_header-height //so it's right below the project title. + overflow-y: auto // show vertical scrollbars when needed. + padding: 0 0 5px 0 // some padding on top/bottom of jstree. position: relative &.edit @@ -820,7 +588,7 @@ $node-preview-max-height-lg: 700px margin-left: auto &.pending - color: $color-danger + color: $danger section.node-preview.texture overflow: hidden @@ -1069,13 +837,6 @@ section.node-preview-forbidden span display: block - a - color: $color-text-light - - &.btn - border-color: white - color: white - hr opacity: .5 @@ -1122,9 +883,6 @@ section.node-preview.group padding: 0 margin: 0 - &.project - .node-details-title - padding: 10px 20px 0 20px .node-details-description +node-details-description @@ -1158,7 +916,7 @@ section.node-preview.group padding-left: 0 &.status-pending - color: $color-danger + color: $danger &.public color: $color-success @@ -1348,7 +1106,7 @@ section.node-details-container opacity: 1 .error-node-type-not-found - color: $color-danger + color: $danger clear: both a.learn-more @@ -2093,15 +1851,15 @@ section.node-children text-transform: capitalize &.error - color: $color-danger + color: $danger background-color: $color-background-light padding: 10px 15px - border: thin solid lighten($color-danger, 10%) - border-top: 2px solid $color-danger + border: thin solid lighten($danger, 10%) + border-top: 2px solid $danger border-bottom-left-radius: 3px border-bottom-right-radius: 3px label - color: $color-danger + color: $danger font-weight: 500 &.file @@ -2168,7 +1926,7 @@ section.node-children outline: none &.field-error - border-color: $color-danger + border-color: $danger .md-preview-loading position: absolute @@ -2415,7 +2173,7 @@ section.node-children .cancel +button($color-warning, 3px) .delete - +button($color-danger, 3px) + +button($danger, 3px) .toggle margin: 0 20px @@ -2426,7 +2184,7 @@ section.node-children &.cancel +button($color-warning, 3px) &.delete - +button($color-danger, 3px) + +button($danger, 3px) &.create +button($color-success, 3px) diff --git a/src/styles/_user.sass b/src/styles/_user.sass index a201494b..09929fb2 100644 --- a/src/styles/_user.sass +++ b/src/styles/_user.sass @@ -68,13 +68,6 @@ background-color: lighten($provider-color-google, 7%) #settings - +media-xs - flex-direction: column - - align-items: stretch - display: flex - margin: 25px auto - #settings-sidebar +media-xs width: 100% diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index f68216a0..3bdf374c 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -82,6 +82,15 @@ text-shadow: none +=disabled-stripes + color: $color-text-dark + cursor: not-allowed + background: repeating-linear-gradient(-45deg, lighten($color-text-dark-hint, 15%), lighten($color-text-dark-hint, 15%) 10px, lighten($color-text-dark-hint, 5%) 10px, lighten($color-text-dark-hint, 5%) 20px) + border-color: darken($color-text-dark-hint, 5%) + pointer-events: none + opacity: .6 + + @mixin overlay($from-color, $from-percentage, $to-color, $to-percentage) position: absolute top: 0 @@ -121,23 +130,17 @@ transform: translate(-50%, -50%) =input-generic - padding: 5px 5px 5px 0 + // padding: 5px 5px 5px 0 color: $color-text-dark - box-shadow: none - border: thin solid transparent - border-radius: 0 - border-bottom-color: $color-background-dark background-color: transparent - transition: border-color 150ms ease-in-out, box-shadow 150ms ease-in-out &:hover border-bottom-color: $color-background &:focus outline: 0 - border: thin solid transparent - border-bottom-color: $color-primary - box-shadow: 0 1px 0 0 $color-primary + border-color: $primary + box-shadow: none =label-generic color: $color-text-dark-primary diff --git a/src/styles/blog.sass b/src/styles/blog.sass index e7460a5c..550d1edc 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -94,9 +94,6 @@ +media-xs flex-direction: column padding-top: 0 - display: flex - padding: - bottom: 15px video max-width: 100% @@ -278,14 +275,7 @@ width: 100% .blog_index-item - +media-lg - max-width: 780px - +media-md - max-width: 780px - +media-sm - max-width: 780px - - margin: 15px auto + max-width: 780px &:hover .item-info a @@ -317,13 +307,6 @@ +media-lg min-height: 250px - .item-title - color: $color-text-dark - display: block - font-size: 1.8em - - padding: 10px 25px 10px - ul.meta +list-meta font-size: .9em @@ -585,7 +568,7 @@ margin: 0 auto padding: 15px 0 margin: 0 auto - border-bottom: thin solid $color-background + border-bottom: thin solid $color-background-dark &:last-child border-bottom: none @@ -616,7 +599,7 @@ position: absolute top: 20px border-radius: 3px - background-color: $color-background + background-color: $color-background-dark overflow: hidden img diff --git a/src/styles/components/_base.sass b/src/styles/components/_base.sass index 51aa3ef4..b4a8fa0c 100644 --- a/src/styles/components/_base.sass +++ b/src/styles/components/_base.sass @@ -22,12 +22,8 @@ body &.box +container-box -.container-page - background-color: white - .page-content - position: relative - flex: 1 + background-color: $white .container-box +container-box diff --git a/src/styles/components/_dropdown.sass b/src/styles/components/_dropdown.sass index dc322071..f03e38df 100644 --- a/src/styles/components/_dropdown.sass +++ b/src/styles/components/_dropdown.sass @@ -1,6 +1,7 @@ // Global, we want all menus to look like this. .dropdown-menu box-shadow: $dropdown-box-shadow + top: 95% // So there is less gap between the dropdown and the item. > li > a diff --git a/src/styles/components/_footer.sass b/src/styles/components/_footer.sass index 9af3ba97..46c2b510 100644 --- a/src/styles/components/_footer.sass +++ b/src/styles/components/_footer.sass @@ -1,15 +1,24 @@ /* FOOTER */ -#footer-container +.footer-wrapper + background-color: $color-background position: relative - .row - +media-xs - margin: 0 + &:after + background-color: $color-background + bottom: 0 + content: '' + position: fixed + left: 0 + right: 0 + top: 0 + pointer-events: none + z-index: -1 /* Footer Navigation */ footer font-size: .75em padding: 0 0 10px 0 + a color: $color-text-dark-primary @@ -58,7 +67,7 @@ footer transform: scale(1) -#footer-navigation +.footer-navigation font-size: .85em margin-bottom: 5px color: lighten($color-text, 30%) diff --git a/src/styles/components/_inputs.sass b/src/styles/components/_inputs.sass index 11dcd2f7..2690a549 100644 --- a/src/styles/components/_inputs.sass +++ b/src/styles/components/_inputs.sass @@ -24,23 +24,7 @@ textarea resize: vertical button, .btn - +button($color-text-dark-secondary, $color-background, false) - - &-success - +button($color-success, $color-success, false) - &-warning - +button($color-warning, $color-warning, false) - &-danger - +button($color-danger, $color-danger, false) - &-info - +button($color-info, $color-info, false) - - &.disabled - +button($color-background-dark, $color-background, false) - background-color: $color-background-light !important - border-color: $color-background-dark !important - color: $color-text !important opacity: .5 !important pointer-events: none !important text-shadow: none !important diff --git a/src/styles/components/_navbar.sass b/src/styles/components/_navbar.sass index f801ece6..ee13a1d5 100644 --- a/src/styles/components/_navbar.sass +++ b/src/styles/components/_navbar.sass @@ -1,43 +1,8 @@ // Navigation. -.navbar-overlay - +media-lg - display: block - bottom: 0 - display: none - left: 0 - height: 100% - position: absolute - right: 0 - top: 0 - transition: background-color 350ms ease-in-out - width: 100% - z-index: 0 - - &.is-active - background-color: $color-background-nav - text-shadow: none - -.navbar-brand - padding-left: 10px - color: $color-text - width: 120px // Blender Cloud logo width - - &:hover - color: $color-text-dark-primary - - -nav.navbar - .navbar-collapse - > ul > li > .navbar-item - padding: $navbar-nav-link-padding-x - height: $nav-link-height - .navbar, nav.sidebar - align-items: center - background-color: $color-background-nav border: none - display: flex + color: $color-text-dark-secondary padding: 0 z-index: $z-index-base + 5 /* Flowplayer seems to take up to 11, project container is 12 */ @@ -53,30 +18,26 @@ nav.sidebar .navbar-item align-items: center - color: $color-text display: flex user-select: none - transition: color 150ms ease-in-out, box-shadow 100ms ease-in-out + color: inherit +media-sm padding-left: 10px padding-right: 10px &:hover, &:focus - color: $color-primary + color: $primary background-color: transparent - box-shadow: inset 0 -3px 0 $color-primary + box-shadow: inset 0 -3px 0 $primary text-decoration: none &:focus - box-shadow: inset 0 -3px 0 $color-primary + box-shadow: inset 0 -3px 0 $primary &.active - color: $color-primary - box-shadow: inset 0 -3px 0 lighten($color-secondary, 10%) - - &:hover - box-shadow: inset 0 -3px 0 lighten($color-secondary, 20%) + color: $primary + box-shadow: inset 0 -3px 0 $primary .pi-angle-down position: relative @@ -86,11 +47,6 @@ nav.sidebar user-select: none position: relative - &.nav-item-sign-in - i - padding-right: 6px - font-size: 1.1em - img.gravatar border-radius: 999em height: 32px @@ -130,11 +86,6 @@ nav.sidebar .dropdown min-width: 60px // navbar avatar size - // Removes angle-down icon from bootstrap, - // since we use a nicer chevron. - a:after - display: none - span.fa-stack position: absolute top: 50% @@ -176,9 +127,51 @@ nav.sidebar font-size: .9em -nav.sidebar - ul li.nav-item-sign-in a.navbar-item - font-size: .8em +// Secondary navigation for +.nav-secondary + align-items: center + box-shadow: 0 2px 0 0 $color-background + + .nav-link + color: $color-text + transition: box-shadow 150ms ease-in-out + + &:hover, + &.active + box-shadow: 0 2px 0 0 $primary + + +.navbar-overlay + +media-lg + display: block + bottom: 0 + display: none + left: 0 + height: 100% + position: absolute + right: 0 + top: 0 + transition: background-color 350ms ease-in-out + width: 100% + z-index: 0 + + &.is-active + background-color: $color-background-nav + text-shadow: none + +.navbar-brand + padding-left: 10px + color: inherit + width: 120px // Blender Cloud logo width + + &:hover + color: $primary + +nav.navbar + .navbar-collapse + > ul > li > .navbar-item + padding: $navbar-nav-link-padding-x + height: $nav-link-height .navbar-backdrop-container @@ -206,20 +199,3 @@ nav.sidebar .navbar+.page-content padding-top: $nav-link-height - -// Secondary navigation for -.nav-secondary - align-items: center - box-shadow: 0 2px 0 0 $color-background - - .nav-link - color: $color-text - transition: box-shadow 150ms ease-in-out - - &:hover, - &.active - box-shadow: 0 2px 0 0 $color-primary - - .nav-title - font-weight: bold - padding-right: 20px diff --git a/src/styles/components/_search.sass b/src/styles/components/_search.sass index 929ecb8d..62470e6f 100644 --- a/src/styles/components/_search.sass +++ b/src/styles/components/_search.sass @@ -35,30 +35,25 @@ padding: 0 margin: 0 - .search-icon position: absolute - color: white top: 4px left: 10px cursor: pointer &:after - opacity: 0 + @extend .tooltip-inner + content: 'Use advanced search...' - font-style: normal - background: darken($color-background-nav, 5%) - color: $color-text-light-primary - box-shadow: 1px 1px 3px rgba(black, .4) - padding: 3px 10px font-size: .85em - border-radius: 3px - top: 30px + font-style: normal left: -10px - width: 150px - position: absolute - transition: top 150ms ease-in-out, opacity 150ms ease-in-out + opacity: 0 pointer-events: none + position: absolute + top: 30px + transition: top 150ms ease-in-out, opacity 150ms ease-in-out + width: 150px &:hover &:after diff --git a/src/styles/plugins/_jstree.sass b/src/styles/plugins/_jstree.sass index b485e69b..a31a343f 100644 --- a/src/styles/plugins/_jstree.sass +++ b/src/styles/plugins/_jstree.sass @@ -1,9 +1,8 @@ /* jsTree overrides */ - $tree-color-text: $color-text-dark-primary -$tree-color-highlight: hsl(hue($color-background-active), 40%, 50%) -$tree-color-highlight-background: hsl(hue($color-background-active), 40%, 50%) -$tree-color-highlight-background-text: white +$tree-color-highlight: $color-primary-accent +$tree-color-highlight-background: $color-primary-accent +$tree-color-highlight-background-text: $white .jstree-default /* list item */ @@ -104,8 +103,7 @@ $tree-color-highlight-background-text: white padding-left: 28px padding-right: 10px text-overflow: ellipsis - transition: none - transition: color 50ms ease-in-out, background-color 100ms ease-in-out + transition: background-color 100ms ease-in-out white-space: nowrap width: 100% diff --git a/src/styles/theatre.sass b/src/styles/theatre.sass index abb97a3a..371af62e 100644 --- a/src/styles/theatre.sass +++ b/src/styles/theatre.sass @@ -7,8 +7,7 @@ $color-theatre-background-dark: darken($color-theatre-background, 5%) $theatre-width: 350px -body.theatre, -body.theatre .container-page +body.theatre background-color: $color-theatre-background nav.navbar +media-lg From 2698be3e12f4d28bd29e730e64c1de2dc2906b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 30 Aug 2018 18:30:38 +0200 Subject: [PATCH 025/148] Saving & restoring video watching progress Video progress updates: - Mark as 'done' when 90% or more is watched. - Keep 'done' flag when re-watching. The video progress is stored on three events, whichever comes first: - Every 30 seconds of video. - Every 10% of the video. - Every pause/stop/navigation to another page. - When we detect the video is looping. --- pillar/api/eve_settings.py | 29 +++ pillar/api/users/routes.py | 129 +++++++++- pillar/tests/__init__.py | 2 +- src/scripts/video_plugins.js | 182 +++++++++++++ .../nodes/custom/asset/video/view_embed.pug | 8 + src/templates/projects/view.pug | 1 + tests/test_api/test_user_video_progress.py | 243 ++++++++++++++++++ 7 files changed, 590 insertions(+), 4 deletions(-) create mode 100644 src/scripts/video_plugins.js create mode 100644 tests/test_api/test_user_video_progress.py diff --git a/pillar/api/eve_settings.py b/pillar/api/eve_settings.py index 2c5dc52e..0fcb9200 100644 --- a/pillar/api/eve_settings.py +++ b/pillar/api/eve_settings.py @@ -123,6 +123,35 @@ users_schema = { 'allow_unknown': True, }, + # Node-specific information for this user. + 'nodes': { + 'type': 'dict', + 'schema': { + # Per watched video info about where the user left off, both in time and in percent. + 'view_progress': { + 'type': 'dict', + # Keyed by Node ID of the video asset. MongoDB doesn't support using + # ObjectIds as key, so we cast them to string instead. + 'keyschema': {'type': 'string'}, + 'valueschema': { + 'type': 'dict', + 'schema': { + 'progress_in_sec': {'type': 'float', 'min': 0}, + 'progress_in_percent': {'type': 'integer', 'min': 0, 'max': 100}, + + # When the progress was last updated, so we can limit this history to + # the last-watched N videos if we want, or show stuff in chrono order. + 'last_watched': {'type': 'datetime'}, + + # True means progress_in_percent = 100, for easy querying + 'done': {'type': 'boolean', 'default': False}, + }, + }, + }, + + }, + }, + # Properties defined by extensions. Extensions should use their name (see the # PillarExtension.name property) as the key, and are free to use whatever they want as value, # but we suggest a dict for future extendability. diff --git a/pillar/api/users/routes.py b/pillar/api/users/routes.py index 7f0e79c2..d4e1d690 100644 --- a/pillar/api/users/routes.py +++ b/pillar/api/users/routes.py @@ -1,9 +1,11 @@ import logging from eve.methods.get import get -from flask import Blueprint +from flask import Blueprint, request +import werkzeug.exceptions as wz_exceptions -from pillar.api.utils import jsonify +from pillar import current_app +from pillar.api import utils from pillar.api.utils.authorization import require_login from pillar.auth import current_user @@ -15,7 +17,128 @@ blueprint_api = Blueprint('users_api', __name__) @require_login() def my_info(): eve_resp, _, _, status, _ = get('users', {'_id': current_user.user_id}) - resp = jsonify(eve_resp['_items'][0], status=status) + resp = utils.jsonify(eve_resp['_items'][0], status=status) return resp +@blueprint_api.route('/video//progress') +@require_login() +def get_video_progress(video_id: str): + """Return video progress information. + + Either a `204 No Content` is returned (no information stored), + or a `200 Ok` with JSON from Eve's 'users' schema, from the key + video.view_progress.. + """ + + # Validation of the video ID; raises a BadRequest when it's not an ObjectID. + # This isn't strictly necessary, but it makes this function behave symmetrical + # to the set_video_progress() function. + utils.str2id(video_id) + + users_coll = current_app.db('users') + user_doc = users_coll.find_one(current_user.user_id, projection={'nodes.view_progress': True}) + try: + progress = user_doc['nodes']['view_progress'][video_id] + except KeyError: + return '', 204 + if not progress: + return '', 204 + + return utils.jsonify(progress) + + +@blueprint_api.route('/video//progress', methods=['POST']) +@require_login() +def set_video_progress(video_id: str): + """Save progress information about a certain video. + + Expected parameters: + - progress_in_sec: float number of seconds + - progress_in_perc: integer percentage of video watched (interval [0-100]) + """ + my_log = log.getChild('set_video_progress') + my_log.debug('Setting video progress for user %r video %r', current_user.user_id, video_id) + + # Constructing this response requires an active app, and thus can't be done on module load. + no_video_response = utils.jsonify({'_message': 'No such video'}, status=404) + + try: + progress_in_sec = float(request.form['progress_in_sec']) + progress_in_perc = int(request.form['progress_in_perc']) + except KeyError as ex: + my_log.debug('Missing POST field in request: %s', ex) + raise wz_exceptions.BadRequest(f'missing a form field') + except ValueError as ex: + my_log.debug('Invalid value for POST field in request: %s', ex) + raise wz_exceptions.BadRequest(f'Invalid value for field: {ex}') + + users_coll = current_app.db('users') + nodes_coll = current_app.db('nodes') + + # First check whether this is actually an existing video + video_oid = utils.str2id(video_id) + video_doc = nodes_coll.find_one(video_oid, projection={ + 'node_type': True, + 'properties.content_type': True, + 'properties.file': True, + }) + if not video_doc: + my_log.debug('Node %r not found, unable to set progress for user %r', + video_oid, current_user.user_id) + return no_video_response + + try: + is_video = (video_doc['node_type'] == 'asset' + and video_doc['properties']['content_type'] == 'video') + except KeyError: + is_video = False + + if not is_video: + my_log.info('Node %r is not a video, unable to set progress for user %r', + video_oid, current_user.user_id) + # There is no video found at this URL, so act as if it doesn't even exist. + return no_video_response + + # Compute the progress + percent = min(100, max(0, progress_in_perc)) + progress = { + 'progress_in_sec': progress_in_sec, + 'progress_in_percent': percent, + 'last_watched': utils.utcnow(), + } + + # After watching a certain percentage of the video, we consider it 'done' + # + # Total Credit start Total Credit Percent + # HH:MM:SS HH:MM:SS sec sec of duration + # Sintel 00:14:48 00:12:24 888 744 83.78% + # Tears of Steel 00:12:14 00:09:49 734 589 80.25% + # Cosmos Laundro 00:12:10 00:10:05 730 605 82.88% + # Agent 327 00:03:51 00:03:26 231 206 89.18% + # Caminandes 3 00:02:30 00:02:18 150 138 92.00% + # Glass Half 00:03:13 00:02:52 193 172 89.12% + # Big Buck Bunny 00:09:56 00:08:11 596 491 82.38% + # Elephant’s Drea 00:10:54 00:09:25 654 565 86.39% + # + # Median 85.09% + # Average 85.75% + # + # For training videos marking at done at 85% of the video may be a bit + # early, since those probably won't have (long) credits. This is why we + # stick to 90% here. + if percent >= 90: + progress['done'] = True + + # Setting each property individually prevents us from overwriting any + # existing {done: true} fields. + updates = {f'nodes.view_progress.{video_id}.{k}': v + for k, v in progress.items()} + result = users_coll.update_one({'_id': current_user.user_id}, + {'$set': updates}) + + if result.matched_count == 0: + my_log.error('Current user %r could not be updated', current_user.user_id) + raise wz_exceptions.InternalServerError('Unable to find logged-in user') + + return '', 204 diff --git a/pillar/tests/__init__.py b/pillar/tests/__init__.py index 1baae624..61914561 100644 --- a/pillar/tests/__init__.py +++ b/pillar/tests/__init__.py @@ -391,7 +391,7 @@ class AbstractPillarTest(TestMinimal): return user_id - def create_node(self, node_doc): + def create_node(self, node_doc) -> ObjectId: """Creates a node, returning its ObjectId. """ with self.app.test_request_context(): diff --git a/src/scripts/video_plugins.js b/src/scripts/video_plugins.js new file mode 100644 index 00000000..cd0c20a3 --- /dev/null +++ b/src/scripts/video_plugins.js @@ -0,0 +1,182 @@ +/* Video.JS plugin for keeping track of user's viewing progress. + Also registers the analytics plugin. + +Progress is reported after a number of seconds or a percentage +of the duration of the video, whichever comes first. + +Example usage: + +videojs(videoPlayerElement, options).ready(function() { + let report_url = '{{ url_for("users_api.set_video_progress", video_id=node._id) }}'; + this.progressPlugin({'report_url': report_url}); +}); + +*/ + +// Report after progressing this many seconds video-time. +let PROGRESS_REPORT_INTERVAL_SEC = 30; + +// Report after progressing this percentage of the entire video (scale 0-100). +let PROGRESS_REPORT_INTERVAL_PERC = 10; + +// Don't report within this many milliseconds of wall-clock time of the previous report. +let PROGRESS_RELAXING_TIME_MSEC = 500; + + +var Plugin = videojs.getPlugin('plugin'); +var VideoProgressPlugin = videojs.extend(Plugin, { + constructor: function(player, options) { + Plugin.call(this, player, options); + + this.last_wallclock_time_ms = 0; + this.last_inspected_progress_in_sec = 0; + this.last_reported_progress_in_sec = 0; + this.last_reported_progress_in_perc = 0; + this.report_url = options.report_url; + this.fetch_progress_url = options.fetch_progress_url; + this.reported_error = false; + this.reported_looping = false; + + if (typeof this.report_url === 'undefined' || !this.report_url) { + /* If we can't report anything, don't bother registering event handlers. */ + videojs.log('VideoProgressPlugin: no report_url option given. Not storing video progress.'); + } else { + /* Those events will have 'this' bound to the player, + * which is why we explicitly re-bind to 'this''. */ + player.on('timeupdate', this.on_timeupdate.bind(this)); + player.on('pause', this.on_pause.bind(this)); + } + + if (typeof this.fetch_progress_url === 'undefined' || !this.fetch_progress_url) { + /* If we can't report anything, don't bother registering event handlers. */ + videojs.log('VideoProgressPlugin: no fetch_progress_url option given. Not restoring video progress.'); + } else { + this.resume_playback(); + } + }, + + resume_playback: function() { + let on_done = function(progress, status, xhr) { + /* 'progress' is an object like: + {"progress_in_sec": 3, + "progress_in_percent": 51, + "last_watched": "Fri, 31 Aug 2018 13:53:06 GMT", + "done": true} + */ + switch (xhr.status) { + case 204: return; // no info found. + case 200: + /* Don't do anything when the progress is at 100%. + * Moving the current time to the end makes no sense then. */ + if (progress.progress_in_percent >= 100) return; + + /* Set the 'last reported' props before manipulating the + * player, so that the manipulation doesn't trigger more + * API calls to remember what we just restored. */ + this.last_reported_progress_in_sec = progress.progress_in_sec; + this.last_reported_progress_in_perc = progress.progress_in_perc; + + console.log("Continuing playback at ", progress.progress_in_percent, "% from", progress.last_watched); + this.player.currentTime(progress.progress_in_sec); + this.player.play(); + return; + default: + console.log("Unknown code", xhr.status, "getting video progress information."); + } + }; + + $.get(this.fetch_progress_url) + .fail(function(error) { + console.log("Unable to fetch video progress information:", xhrErrorResponseMessage(error)); + }) + .done(on_done.bind(this)); + }, + + /* Pausing playback should report the progress. + * This function is also called when playback stops at the end of the video, + * so it's important to report in this case; otherwise progress will never + * reach 100%. */ + on_pause: function(event) { + this.inspect_progress(true); + }, + + on_timeupdate: function() { + this.inspect_progress(false); + }, + + inspect_progress: function(force_report) { + // Don't report seeking when paused, only report actual playback. + if (this.player.paused()) return; + + let now_in_ms = new Date().getTime(); + if (!force_report && now_in_ms - this.last_wallclock_time_ms < PROGRESS_RELAXING_TIME_MSEC) { + // We're trying too fast, don't bother doing any other calculation. + // console.log('skipping, already reported', now_in_ms - this.last_wallclock_time_ms, 'ms ago.'); + return; + } + + let progress_in_sec = this.player.currentTime(); + let duration_in_sec = this.player.duration(); + + /* Instead of reporting the current time, report reaching the end + * of the video. This ensures that it's properly marked as 'done'. */ + if (!this.reported_looping) { + let margin = 1.25 * PROGRESS_RELAXING_TIME_MSEC / 1000.0; + let is_looping = progress_in_sec == 0 && duration_in_sec - this.last_inspected_progress_in_sec < margin; + this.last_inspected_progress_in_sec = progress_in_sec; + if (is_looping) { + this.reported_looping = true; + this.report(this.player.duration(), 100, now_in_ms); + return; + } + } + + if (Math.abs(progress_in_sec - this.last_reported_progress_in_sec) < 0.01) { + // Already reported this, don't bother doing it again. + return; + } + let progress_in_perc = 100 * progress_in_sec / duration_in_sec; + let diff_sec = progress_in_sec - this.last_reported_progress_in_sec; + let diff_perc = progress_in_perc - this.last_reported_progress_in_perc; + + if (!force_report + && Math.abs(diff_perc) < PROGRESS_REPORT_INTERVAL_PERC + && Math.abs(diff_sec) < PROGRESS_REPORT_INTERVAL_SEC) { + return; + } + + this.report(progress_in_sec, progress_in_perc, now_in_ms); + }, + + report: function(progress_in_sec, progress_in_perc, now_in_ms) { + /* Store when we tried, not when we succeeded. This function can be + * called every 15-250 milliseconds, so we don't want to retry with + * that frequency. */ + this.last_wallclock_time_ms = now_in_ms; + + let on_fail = function(error) { + /* Don't show (as in: a toastr popup) the error to the user, + * as it doesn't impact their ability to play the video. + * Also show the error only once, instead of spamming. */ + if (this.reported_error) return; + + let msg = xhrErrorResponseMessage(error); + console.log('Unable to report viewing progress:', msg); + this.reported_error = true; + }; + let on_done = function() { + this.last_reported_progress_in_sec = progress_in_sec; + this.last_reported_progress_in_perc = progress_in_perc; + }; + + $.post(this.report_url, { + progress_in_sec: progress_in_sec, + progress_in_perc: Math.round(progress_in_perc), + }) + .fail(on_fail.bind(this)) + .done(on_done.bind(this)); + }, +}); + +// Register our watch-progress-bookkeeping plugin. +videojs.registerPlugin('progressPlugin', VideoProgressPlugin); diff --git a/src/templates/nodes/custom/asset/video/view_embed.pug b/src/templates/nodes/custom/asset/video/view_embed.pug index a6ab2860..55c938c0 100644 --- a/src/templates/nodes/custom/asset/video/view_embed.pug +++ b/src/templates/nodes/custom/asset/video/view_embed.pug @@ -67,6 +67,14 @@ script(type="text/javascript"). }); this.hotkeys(); + {% if current_user.is_authenticated %} + let fetch_progress_url = '{{ url_for("users_api.get_video_progress", video_id=node._id) }}'; + let report_url = '{{ url_for("users_api.set_video_progress", video_id=node._id) }}'; + this.progressPlugin({ + 'report_url': report_url, + 'fetch_progress_url': fetch_progress_url, + }); + {% endif %} }); function addVideoPlayerButton(data) { diff --git a/src/templates/projects/view.pug b/src/templates/projects/view.pug index 6626636a..a216bc20 100644 --- a/src/templates/projects/view.pug +++ b/src/templates/projects/view.pug @@ -74,6 +74,7 @@ link(rel="amphtml", href="{{ url_for('nodes.view', node_id=node._id, _external=T script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}") +script(src="{{ url_for('static_pillar', filename='assets/js/video_plugins.min.js') }}") | {% endblock %} | {% block css %} diff --git a/tests/test_api/test_user_video_progress.py b/tests/test_api/test_user_video_progress.py new file mode 100644 index 00000000..b5e9af68 --- /dev/null +++ b/tests/test_api/test_user_video_progress.py @@ -0,0 +1,243 @@ +from unittest import mock + +import bson +from eve import RFC1123_DATE_FORMAT +import flask + +from pillar.tests import AbstractPillarTest + + +class AbstractVideoProgressTest(AbstractPillarTest): + def setUp(self, **kwargs): + super().setUp(**kwargs) + + self.pid, _ = self.ensure_project_exists() + + self.admin_uid = self.create_user(24 * 'a', roles={'admin'}) + self.uid = self.create_user(24 * 'b', roles={'subscriber'}) + + from pillar.api.utils import utcnow + self.fake_now = utcnow() + self.fake_now_str = self.fake_now.strftime(RFC1123_DATE_FORMAT) + + def create_video_node(self) -> bson.ObjectId: + return self.create_node({ + 'description': '', + 'node_type': 'asset', + 'user': self.admin_uid, + 'properties': { + 'status': 'published', + 'content_type': 'video', + 'file': bson.ObjectId()}, + 'name': 'Image test', + 'project': self.pid, + }) + + def set_progress(self, + progress_in_sec: float = 413.0, + progress_in_perc: int = 65, + expected_status: int = 204) -> None: + with self.login_as(self.uid): + url = flask.url_for('users_api.set_video_progress', video_id=str(self.video_id)) + self.post(url, + data={'progress_in_sec': progress_in_sec, + 'progress_in_perc': progress_in_perc}, + expected_status=expected_status) + + def get_progress(self, expected_status: int = 200) -> dict: + with self.login_as(self.uid): + url = flask.url_for('users_api.get_video_progress', video_id=str(self.video_id)) + progress = self.get(url, expected_status=expected_status) + return progress.json + + def create_video_and_set_progress(self, progress_in_sec=413.0, progress_in_perc=65): + self.video_id = self.create_video_node() + + # Check that we can get the progress after setting it. + with mock.patch('pillar.api.utils.utcnow') as utcnow: + utcnow.return_value = self.fake_now + self.set_progress(progress_in_sec, progress_in_perc) + + +class HappyFlowVideoProgressTest(AbstractVideoProgressTest): + + def test_video_progress_known_video(self): + self.create_video_and_set_progress() + progress = self.get_progress() + + expected_progress = { + 'progress_in_sec': 413.0, + 'progress_in_percent': 65, + 'last_watched': self.fake_now, + } + self.assertEqual({**expected_progress, 'last_watched': self.fake_now_str}, + progress) + + # Check that the database has been updated correctly. + self.db_user = self.fetch_user_from_db(self.uid) + self.assertEqual({str(self.video_id): expected_progress}, + self.db_user['nodes']['view_progress']) + + def test_user_adheres_to_schema(self): + from pillar.api.utils import remove_private_keys + # This check is necessary because the API code uses direct MongoDB manipulation, + # which means that the user can end up not matching the Cerberus schema. + self.create_video_and_set_progress() + db_user = self.fetch_user_from_db(self.uid) + + r, _, _, status = self.app.put_internal( + 'users', + payload=remove_private_keys(db_user), + _id=db_user['_id']) + self.assertEqual(200, status, r) + + def test_video_progress_is_private(self): + self.create_video_and_set_progress() + + with self.login_as(self.uid): + resp = self.get(f'/api/users/{self.uid}') + self.assertIn('nodes', resp.json) + + other_uid = self.create_user(24 * 'c', roles={'subscriber'}) + with self.login_as(other_uid): + resp = self.get(f'/api/users/{self.uid}') + self.assertIn('username', resp.json) # just to be sure this is a real user response + self.assertNotIn('nodes', resp.json) + + def test_done_at_100_percent(self): + self.create_video_and_set_progress(630, 100) + progress = self.get_progress() + self.assertEqual({'progress_in_sec': 630.0, + 'progress_in_percent': 100, + 'last_watched': self.fake_now_str, + 'done': True}, + progress) + + def test_done_at_95_percent(self): + self.create_video_and_set_progress(599, 95) + progress = self.get_progress() + self.assertEqual({'progress_in_sec': 599.0, + 'progress_in_percent': 95, + 'last_watched': self.fake_now_str, + 'done': True}, + progress) + + def test_rewatch_after_done(self): + from pillar.api.utils import utcnow + + self.create_video_and_set_progress(630, 100) + + # Re-watching should keep the 'done' key. + another_fake_now = utcnow() + with mock.patch('pillar.api.utils.utcnow') as mock_utcnow: + mock_utcnow.return_value = another_fake_now + self.set_progress(444, 70) + + progress = self.get_progress() + self.assertEqual({'progress_in_sec': 444, + 'progress_in_percent': 70, + 'done': True, + 'last_watched': another_fake_now.strftime(RFC1123_DATE_FORMAT)}, + progress) + + def test_inconsistent_progress(self): + # Send a percentage that's incorrect. It should just be copied. + self.create_video_and_set_progress(413.557, 30) + progress = self.get_progress() + + expected_progress = { + 'progress_in_sec': 413.557, + 'progress_in_percent': 30, + 'last_watched': self.fake_now, + } + self.assertEqual({**expected_progress, 'last_watched': self.fake_now_str}, + progress) + + # Check that the database has been updated correctly. + self.db_user = self.fetch_user_from_db(self.uid) + self.assertEqual({str(self.video_id): expected_progress}, + self.db_user['nodes']['view_progress']) + + +class UnhappyFlowVideoProgressTest(AbstractVideoProgressTest): + def test_get_video_progress_invalid_video_id(self): + with self.login_as(self.uid): + url = flask.url_for('users_api.get_video_progress', video_id='jemoeder') + self.get(url, expected_status=400) + + def test_get_video_progress_unknown_video(self): + with self.login_as(self.uid): + url = flask.url_for('users_api.get_video_progress', video_id=24 * 'f') + self.get(url, expected_status=204) + + def test_set_video_progress_unknown_video(self): + with self.login_as(self.uid): + url = flask.url_for('users_api.set_video_progress', video_id=24 * 'f') + self.post(url, + data={'progress_in_sec': 16, 'progress_in_perc': 10}, + expected_status=404) + + def test_set_video_progress_invalid_video_id(self): + with self.login_as(self.uid): + url = flask.url_for('users_api.set_video_progress', video_id='jemoeder') + self.post(url, + data={'progress_in_sec': 16, 'progress_in_perc': 10}, + expected_status=400) + + def test_get_video_empty_dict(self): + self.video_id = bson.ObjectId(24 * 'f') + with self.app.app_context(): + users_coll = self.app.db('users') + # The progress dict for that video is there, but empty. + users_coll.update_one( + {'_id': self.uid}, + {'$set': {f'nodes.view_progress.{self.video_id}': {}}}) + + progress = self.get_progress(expected_status=204) + self.assertIsNone(progress) + + def test_missing_post_field(self): + with self.login_as(self.uid): + url = flask.url_for('users_api.set_video_progress', video_id=24 * 'f') + self.post(url, data={'progress_in_ms': 1000}, expected_status=400) + + def test_nonint_progress(self): + with self.login_as(self.uid): + url = flask.url_for('users_api.set_video_progress', video_id=24 * 'f') + self.post(url, data={'progress_in_sec': 'je moeder'}, expected_status=400) + + def test_asset_is_valid_but_not_video(self): + self.video_id = self.create_node({ + 'description': '', + 'node_type': 'asset', + 'user': self.admin_uid, + 'properties': { + 'status': 'published', + 'content_type': 'image', # instead of video + 'file': bson.ObjectId()}, + 'name': 'Image test', + 'project': self.pid, + }) + + with mock.patch('pillar.api.utils.utcnow') as utcnow: + utcnow.return_value = self.fake_now + self.set_progress(expected_status=404) + self.get_progress(expected_status=204) + + def test_asset_malformed(self): + self.video_id = self.create_node({ + 'description': '', + 'node_type': 'asset', + 'user': self.admin_uid, + 'properties': { + 'status': 'published', + # Note the lack of a 'content_type' key. + 'file': bson.ObjectId()}, + 'name': 'Missing content_type test', + 'project': self.pid, + }) + + with mock.patch('pillar.api.utils.utcnow') as utcnow: + utcnow.return_value = self.fake_now + self.set_progress(expected_status=404) + self.get_progress(expected_status=204) From f29e01c78ef3756d55a04b7e9e52cd7b158c9c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 31 Aug 2018 18:16:47 +0200 Subject: [PATCH 026/148] Video player: remember volume in local storage --- src/scripts/video_plugins.js | 20 +++++++++++++++++++ .../nodes/custom/asset/video/view_embed.pug | 1 + 2 files changed, 21 insertions(+) diff --git a/src/scripts/video_plugins.js b/src/scripts/video_plugins.js index cd0c20a3..d19c1ed5 100644 --- a/src/scripts/video_plugins.js +++ b/src/scripts/video_plugins.js @@ -178,5 +178,25 @@ var VideoProgressPlugin = videojs.extend(Plugin, { }, }); +var RememberVolumePlugin = videojs.extend(Plugin, { + constructor: function(player, options) { + Plugin.call(this, player, options); + player.on('volumechange', this.on_volumechange.bind(this)); + this.restore_volume(); + }, + + restore_volume: function() { + let volume_str = localStorage.getItem('video-player-volume'); + if (volume_str == null) return; + this.player.volume(1.0 * volume_str); + }, + + on_volumechange: function(event) { + localStorage.setItem('video-player-volume', this.player.volume()); + }, +}); + + // Register our watch-progress-bookkeeping plugin. videojs.registerPlugin('progressPlugin', VideoProgressPlugin); +videojs.registerPlugin('rememberVolumePlugin', RememberVolumePlugin); diff --git a/src/templates/nodes/custom/asset/video/view_embed.pug b/src/templates/nodes/custom/asset/video/view_embed.pug index 55c938c0..6a4206fc 100644 --- a/src/templates/nodes/custom/asset/video/view_embed.pug +++ b/src/templates/nodes/custom/asset/video/view_embed.pug @@ -66,6 +66,7 @@ script(type="text/javascript"). 'eventsToTrack' : ['start', 'error', 'percentsPlayed'] }); this.hotkeys(); + this.rememberVolumePlugin(); {% if current_user.is_authenticated %} let fetch_progress_url = '{{ url_for("users_api.get_video_progress", video_id=node._id) }}'; From 0fcafddbd15a653ee56abd00fe9d49f5288b47e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 5 Sep 2018 14:54:08 +0200 Subject: [PATCH 027/148] Added unit test for creating comments We had an issue creating comments, so I wrote a test for it. The test succeeds on a new project, so the problem lies with the older projects. In the end it was the comment node type that still had `{'coerce': 'markdown'}`. --- ...test_comments_edit.py => test_comments.py} | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) rename tests/test_web/{test_comments_edit.py => test_comments.py} (57%) diff --git a/tests/test_web/test_comments_edit.py b/tests/test_web/test_comments.py similarity index 57% rename from tests/test_web/test_comments_edit.py rename to tests/test_web/test_comments.py index 2917c0a7..5d658518 100644 --- a/tests/test_web/test_comments_edit.py +++ b/tests/test_web/test_comments.py @@ -1,11 +1,51 @@ import json from bson import ObjectId +import flask from pillar.tests import AbstractPillarTest from pillar.tests import common_test_data as ctd +class CommentTest(AbstractPillarTest): + def setUp(self, **kwargs): + super().setUp(**kwargs) + + self.pid, self.project = self.ensure_project_exists() + self.owner_uid = self.create_user(24 * 'a', + groups=[ctd.EXAMPLE_ADMIN_GROUP_ID], + token='admin-token') + + # Create a node people can comment on. + self.node_id = self.create_node({ + '_id': ObjectId('572761099837730efe8e120d'), + 'description': 'This is an asset without file', + 'node_type': 'asset', + 'user': self.owner_uid, + 'properties': { + 'status': 'published', + 'content_type': 'image', + }, + 'name': 'Image test', + 'project': self.pid, + }) + + self.user_uid = self.create_user(24 * 'b', groups=[ctd.EXAMPLE_ADMIN_GROUP_ID], + token='user-token') + + def test_write_comment(self): + with self.login_as(self.user_uid): + comment_url = flask.url_for('nodes.comments_create') + self.post( + comment_url, + data={ + 'content': 'je möder lives at [home](https://cloud.blender.org/)', + 'parent_id': str(self.node_id), + }, + expected_status=201, + ) + + class CommentEditTest(AbstractPillarTest): def setUp(self, **kwargs): super().setUp(**kwargs) From bc16bb6e562b3c6ba4bcb8402910e76f6ab5f2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 5 Sep 2018 14:54:30 +0200 Subject: [PATCH 028/148] Send the request URL to Sentry Also removed some dead code. --- pillar/sentry_extra.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pillar/sentry_extra.py b/pillar/sentry_extra.py index c78f8770..0753d5a7 100644 --- a/pillar/sentry_extra.py +++ b/pillar/sentry_extra.py @@ -1,3 +1,5 @@ +import flask +import raven.breadcrumbs from raven.contrib.flask import Sentry from .auth import current_user @@ -14,16 +16,14 @@ class PillarSentry(Sentry): def init_app(self, app, *args, **kwargs): super().init_app(app, *args, **kwargs) - # We perform authentication of the user while handling the request, - # so Sentry calls get_user_info() too early. + flask.request_started.connect(self.__add_sentry_breadcrumbs, self) - def get_user_context_again(self, ): - from flask import request - - try: - self.client.user_context(self.get_user_info(request)) - except Exception as e: - self.client.logger.exception(str(e)) + def __add_sentry_breadcrumbs(self, sender, **extra): + raven.breadcrumbs.record( + message='Request started', + category='http', + data={'url': flask.request.url} + ) def get_user_info(self, request): user_info = super().get_user_info(request) From 2777c3708527616968321cda4897ea7ab0008c2d Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:05:45 +0200 Subject: [PATCH 029/148] Style videoplayer. --- src/styles/plugins/_videoplayer.sass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/plugins/_videoplayer.sass b/src/styles/plugins/_videoplayer.sass index d1a25058..94a7e82f 100644 --- a/src/styles/plugins/_videoplayer.sass +++ b/src/styles/plugins/_videoplayer.sass @@ -1,5 +1,5 @@ $videoplayer-controls-color: white -$videoplayer-background-color: $color-background-nav +$videoplayer-background-color: $black .video-js .vjs-big-play-button:before, .vjs-control:before, .vjs-modal-dialog From 278eebd235d5dca04f5621d3b64e7044b52481b7 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:06:14 +0200 Subject: [PATCH 030/148] Style jsTree --- src/styles/plugins/_jstree.sass | 54 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/styles/plugins/_jstree.sass b/src/styles/plugins/_jstree.sass index a31a343f..133f2c6d 100644 --- a/src/styles/plugins/_jstree.sass +++ b/src/styles/plugins/_jstree.sass @@ -1,8 +1,8 @@ /* jsTree overrides */ $tree-color-text: $color-text-dark-primary $tree-color-highlight: $color-primary-accent -$tree-color-highlight-background: $color-primary-accent -$tree-color-highlight-background-text: $white +$tree-color-highlight-background: $white +$tree-color-highlight-background-text: $primary .jstree-default /* list item */ @@ -62,48 +62,48 @@ $tree-color-highlight-background-text: $white &.jstree-open /* Text of children for an open tree (like a folder) */ .jstree-children > .jstree-node - padding-left: 15px !important + padding-left: 16px !important .jstree-icon:empty left: 20px !important // Tweaks for specific icons &.pi-file-archive - left: 22px !important + left: 25px !important &.pi-folder - left: 21px !important + left: 20px !important font-size: .9em !important - &.pi-film-thick - left: 22px !important + &.pi-splay + left: 20px !important font-size: .85em !important .jstree-anchor - box-shadow: inset 1px 0 0 0 rgba($tree-color-text, .2) + // box-shadow: inset 1px 0 0 0 $color-background /* Closed Folder */ // &.jstree-closed &.jstree-open .jstree-icon.jstree-ocl, &.jstree-closed .jstree-icon.jstree-ocl + float: left + min-width: 30px + opacity: 0 position: absolute z-index: 1 - opacity: 0 - min-width: 30px - float: left /* The text of the last level item */ .jstree-anchor +media-xs - width: 98% padding: 0 !important + width: 98% border: none + font-size: 13px height: inherit - line-height: 26px + line-height: 24px overflow: hidden padding-left: 28px padding-right: 10px text-overflow: ellipsis - transition: background-color 100ms ease-in-out white-space: nowrap width: 100% @@ -111,7 +111,7 @@ $tree-color-highlight-background-text: $white &:after content: '\e83a' !important font-family: 'pillar-font' - color: white + color: $tree-color-highlight-background-text display: none position: absolute right: 7px @@ -119,31 +119,31 @@ $tree-color-highlight-background-text: $white // Icon, not selected .jstree-icon - color: $color-text-dark-secondary - font-size: 95% !important + color: $tree-color-text margin: 0 !important /* Selected item */ &.jstree-clicked background-color: $tree-color-highlight-background !important - color: white !important + color: $tree-color-highlight-background-text !important + font-weight: bold &:after display: block - color: white !important + color: $tree-color-highlight-background-text !important .jstree-ocl, .jstree-icon - color: white + color: $tree-color-highlight-background-text /* hover an active item */ &.jstree-hovered background-color: lighten($tree-color-highlight-background, 10%) !important box-shadow: none - color: white !important + color: $tree-color-highlight-background-text !important &.jstree-hovered .jstree-icon - color: white !important + color: $tree-color-highlight-background-text !important .jstree-hovered background-color: rgba($tree-color-highlight, .1) !important @@ -182,8 +182,8 @@ $tree-color-highlight-background-text: $white position: absolute &:empty - line-height: 26px - left: 5px + line-height: 24px + left: 3px &.is_subscriber .jstree-node @@ -267,7 +267,7 @@ $tree-color-highlight-background-text: $white .jstree-default .jstree-node.jstree-closed .jstree-icon.jstree-ocl + .jstree-anchor, .jstree-default .jstree-node.jstree-open .jstree-icon.jstree-ocl + .jstree-anchor - padding-left: 28px !important + padding-left: 24px !important /* hovered text */ .jstree-default .jstree-hovered, @@ -278,11 +278,11 @@ $tree-color-highlight-background-text: $white a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked background-color: rgba($tree-color-highlight-background, .8) !important - color: white !important + color: $tree-color-highlight-background-text !important a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked background-color: rgba($tree-color-highlight-background, .8) !important - color: white !important + color: $tree-color-highlight-background-text !important i.jstree-icon.jstree-ocl color: rgba($tree-color-text, .5) !important From 1821bb6b7dabe4adb3fa919d7ebf498593269fac Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:11:10 +0200 Subject: [PATCH 031/148] CSS general cleanup and minor style tweaks --- src/styles/_config.sass | 2 +- src/styles/_organizations.sass | 13 ---------- src/styles/_project-dashboard.sass | 6 ----- src/styles/_project.sass | 35 +++++++++------------------ src/styles/_utils.sass | 8 ++++++ src/styles/components/_jumbotron.sass | 4 +-- src/styles/components/_navbar.sass | 16 ++++++------ 7 files changed, 31 insertions(+), 53 deletions(-) diff --git a/src/styles/_config.sass b/src/styles/_config.sass index f0f1f1f7..38fe2bd1 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -100,7 +100,7 @@ $sidebar-width: 50px !default /* Project specifics */ $project_nav-width: 250px !default -$project-sidebar-width: 50px !default +$project-sidebar-width: 40px !default $project_header-height: 37px !default $project_footer-height: 30px !default diff --git a/src/styles/_organizations.sass b/src/styles/_organizations.sass index f3b82eb0..b5af8501 100644 --- a/src/styles/_organizations.sass +++ b/src/styles/_organizations.sass @@ -1,17 +1,4 @@ body.organizations - ul#sub-nav-tabs__list - align-items: center - display: flex - - li.result - padding: 10px 20px - - .dashboard-secondary - .box - +container-box - padding: 10px 20px - margin: 0 - #item-details .organization label diff --git a/src/styles/_project-dashboard.sass b/src/styles/_project-dashboard.sass index f63b1b50..c11b6d9c 100644 --- a/src/styles/_project-dashboard.sass +++ b/src/styles/_project-dashboard.sass @@ -1,22 +1,16 @@ - .dashboard-container section#home, section#projects - background-color: $color-background border-bottom-left-radius: 3px border-bottom-right-radius: 3px nav#sub-nav-tabs.home, nav#sub-nav-tabs.projects - background-color: white border-bottom: thin solid $color-background-dark li.nav-tabs__list-tab padding: 15px 20px 10px 20px - section#home - background-color: $color-background-dark - nav.nav-tabs__tab display: none background-color: $color-background-light diff --git a/src/styles/_project.sass b/src/styles/_project.sass index 822380ca..bd1a80d6 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -40,10 +40,6 @@ body.blog width: 100% left: 0 -#project_context-header - span#status-bar - text-align: left - #project_nav-container +media-lg width: $project_nav-width * 1.33 @@ -62,6 +58,7 @@ body.blog #project_sidebar width: $project-sidebar-width z-index: $z-index-base + 6 + box-shadow: inset -1px 0 0 0 $color-background +media-xs width: 100% @@ -95,30 +92,21 @@ body.blog &:first-child border-top: thin solid transparent - &:hover - background-color: $primary + &:hover, + &.active cursor: pointer a - color: $white + color: $primary a align-items: center + color: $color-text display: flex justify-content: center height: $project-sidebar-width width: $project-sidebar-width - i - font-size: 1.1em - - &.active - background-color: $primary - color: $white - - a - color: $white - &.tabs-thumbnail img height: $project-sidebar-width @@ -176,13 +164,16 @@ body.blog height: $project_header-height min-height: $project_header-height position: fixed - top: $project_header-height + top: $project_header-height + 1 transition: box-shadow 250ms ease-in-out z-index: $z-index-base + 3 &.is-offset box-shadow: 0 0 25px rgba(black, .2) + span#status-bar + text-align: left + #project_nav-header left: 0 position: absolute @@ -432,8 +423,8 @@ ul.project_nav-edit-list padding-top: $project_header-height #node-container - flex: 1 background-color: white + flex: 1 /* For error messages (403) and other overlaid text*/ #node-overlay @@ -466,8 +457,7 @@ ul.project_nav-edit-list +media-xs margin-top: 0 - border-right: thin solid $color-background - margin-top: $project_header-height //so it's right below the project title. + // margin-top: $project_header-height //so it's right below the project title. overflow-y: auto // show vertical scrollbars when needed. padding: 0 0 5px 0 // some padding on top/bottom of jstree. position: relative @@ -816,6 +806,7 @@ section.node-preview-forbidden justify-content: center min-height: 400px position: relative + overflow: hidden img height: 130% @@ -1243,8 +1234,6 @@ a.learn-more section.node-children - background-color: white - &.group, &.storage flex: 1 padding: 10px 0 25px 20px diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index 3bdf374c..be5af072 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -666,3 +666,11 @@ .user-select-none user-select: none + +// Bootstrap has .img-fluid, a class to limit the width of an image to 100%. +// .imgs-fluid below is to be applied on a parent container when we can't add +// classes to the images themselves. e.g. the blog. +.imgs-fluid + img + // Just re-use Bootstrap's mixin here. + +img-fluid diff --git a/src/styles/components/_jumbotron.sass b/src/styles/components/_jumbotron.sass index 985ed80d..ffa1a735 100644 --- a/src/styles/components/_jumbotron.sass +++ b/src/styles/components/_jumbotron.sass @@ -2,8 +2,8 @@ .jumbotron background-size: cover border-radius: 0 - padding-top: 12em - padding-bottom: 12em + padding-top: 10em + padding-bottom: 10em // Black-transparent gradient from left to right to better read the overlay text. &.jumbotron-overlay diff --git a/src/styles/components/_navbar.sass b/src/styles/components/_navbar.sass index ee13a1d5..a339ccf9 100644 --- a/src/styles/components/_navbar.sass +++ b/src/styles/components/_navbar.sass @@ -1,4 +1,8 @@ // Navigation. + +.navbar + box-shadow: inset 0 -2px $color-background + .navbar, nav.sidebar border: none @@ -39,10 +43,6 @@ nav.sidebar color: $primary box-shadow: inset 0 -3px 0 $primary - .pi-angle-down - position: relative - left: 5px - li user-select: none position: relative @@ -130,15 +130,16 @@ nav.sidebar // Secondary navigation for .nav-secondary align-items: center - box-shadow: 0 2px 0 0 $color-background + box-shadow: inset 0 -2px 0 0 $color-background .nav-link color: $color-text + cursor: pointer transition: box-shadow 150ms ease-in-out &:hover, &.active - box-shadow: 0 2px 0 0 $primary + box-shadow: inset 0 -2px 0 0 $primary .navbar-overlay @@ -160,9 +161,8 @@ nav.sidebar text-shadow: none .navbar-brand - padding-left: 10px color: inherit - width: 120px // Blender Cloud logo width + padding-left: 4px &:hover color: $primary From b0d8da821f9b406d4d6d94367b755f2637f0789c Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:11:18 +0200 Subject: [PATCH 032/148] CSS: Blog cleanup --- src/styles/blog.sass | 300 +++++++++++-------------------------------- 1 file changed, 77 insertions(+), 223 deletions(-) diff --git a/src/styles/blog.sass b/src/styles/blog.sass index 550d1edc..f18e665b 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -87,9 +87,6 @@ @import components/checkbox @import components/overlay -.container-fluid.blog - padding: 0 - #blog_container +media-xs flex-direction: column @@ -244,6 +241,56 @@ #blog_post-edit-container padding: 25px +.blog_index-item + .item-picture + position: relative + width: 100% + max-height: 350px + min-height: 200px + height: auto + overflow: hidden + border-top-left-radius: 3px + border-top-right-radius: 3px + +clearfix + + img + +position-center-translate + width: 100% + border-top-left-radius: 3px + border-top-right-radius: 3px + + +media-xs + min-height: 150px + +media-sm + min-height: 150px + +media-md + min-height: 250px + +media-lg + min-height: 250px + + .item-content + +node-details-description + + +media-xs + padding: + left: 0 + right: 0 + + img + display: block + margin: 0 auto + + .item-meta + color: $color-text-dark-secondary + padding: + left: 25px + right: 25px + + +media-xs + padding: + left: 10px + right: 10px + #blog_index-container, #blog_post-create-container, #blog_post-edit-container @@ -262,117 +309,11 @@ +media-lg width: 100% - .blog_index-header - border-top-left-radius: 3px - border-top-right-radius: 3px - display: block - overflow: hidden - position: relative - text-align: center - width: 100% - - img - width: 100% - - .blog_index-item - max-width: 780px - - &:hover - .item-info a - color: $color-primary - - .item-picture - position: relative - width: 100% - max-height: 350px - min-height: 200px - height: auto - overflow: hidden - border-top-left-radius: 3px - border-top-right-radius: 3px - +clearfix - - img - +position-center-translate - width: 100% - border-top-left-radius: 3px - border-top-right-radius: 3px - - +media-xs - min-height: 150px - +media-sm - min-height: 150px - +media-md - min-height: 250px - +media-lg - min-height: 250px - - ul.meta - +list-meta - font-size: .9em - padding: 0px 25px 5px - - .item-content - +node-details-description - font-size: 1.3em - padding: 15px 25px 25px - - +media-xs - padding: - left: 0 - right: 0 - - img - display: block - margin: 0 auto - - .item-meta - color: $color-text-dark-secondary - padding: - left: 25px - right: 25px - - +media-xs - padding: - left: 10px - right: 10px - - .button-create, - .button-edit - +button($color-success, 3px, true) .item-picture+.button-back+.button-edit right: 20px top: 20px - .comments-container - padding: - left: 20px - right: 20px - max-width: 680px - margin: 0 auto - - +media-lg - padding: - left: 0 - right: 0 - - .comment-reply-container - background-color: transparent - - .comment-reply-field - textarea, .comment-reply-meta - background-color: $color-background-light - - &.filled - .comment-reply-meta - background-color: $color-success - - .comment-reply-form - +media-xs - padding: - left: 0 - #blog_post-edit-form padding: 0 @@ -432,12 +373,6 @@ +media-lg width: 25% - .button-create - display: block - width: 100% - +button($color-success, 6px) - margin: 0 - .button-back +button($color-info, 6px, true) display: block @@ -536,104 +471,6 @@ text-decoration: none color: $color-primary - -#blog_container - &.cloud-blog - #blog_index-container, - #blog_post-create-container, - #blog_post-edit-container - width: 100% - padding: 25px 30px 20px 30px - - #blog_index-container+#blog_index-sidebar - display: none - - #blog_index-container, - &.cloud-blog #blog_index-container - +media-sm - width: 100% - - +media-xs - width: 100% - - padding: 0 0 50px 0 - margin: 0 auto - - .blog_index-item - +media-xs - width: 100% - padding: 10px 25px - - &.list - margin: 0 auto - padding: 15px 0 - margin: 0 auto - border-bottom: thin solid $color-background-dark - - &:last-child - border-bottom: none - - +media-xs - width: 100% - padding: 15px 10px - margin: 0 - - a.item-title - padding: - top: 0 - bottom: 5px - font: - size: 1.6em - weight: 400 - - .item-info - color: $color-text-dark-secondary - font-size: .9em - padding: - left: 25px - right: 25px - - .item-header - width: 50px - height: 50px - position: absolute - top: 20px - border-radius: 3px - background-color: $color-background-dark - overflow: hidden - - img - +position-center-translate - width: 100% - - i - +position-center-translate - font-size: 1.2em - color: $color-text-dark-hint - - &.nothumb - border-radius: 50% - - a.item-title, .item-info - padding-left: 70px - -#blog_index-container - .blog_index-item - position: relative - - +media-xs - padding: 25px 0 20px 0 - - &.list - padding: 15px 10px - margin: 0 - - +media-xs - width: 100% - padding: 15px 10px - margin: 0 - - .blog-archive-navigation +media-xs font-size: 1em @@ -663,16 +500,7 @@ color: $color-text-dark-secondary pointer-events: none -.blog-action - display: flex - padding: 10px - position: absolute - right: 0 - top: 0 - z-index: 1 - - -// Specific tweaks for blogs in the context of a project +// Specific tweaks for blogs in the context of a project. #project_context .blog_index-item +media-xs @@ -697,3 +525,29 @@ .blog-archive-navigation margin-left: 35px + +// Used on the blog. +.comments-compact + .comments-list + border: none + padding: 0 0 15px 0 + + .comments-container + max-width: 680px + margin: 0 auto + + .comment-reply-container + background-color: transparent + + .comment-reply-field + textarea, .comment-reply-meta + background-color: $color-background-light + + &.filled + .comment-reply-meta + background-color: $color-success + + .comment-reply-form + +media-xs + padding: + left: 0 From 4546469d37bd913c642ecd4cff3798453ffd8b3b Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:19:00 +0200 Subject: [PATCH 033/148] Pug: Move blog macros to blender-cloud --- src/templates/nodes/custom/blog/_macros.pug | 142 -------------------- 1 file changed, 142 deletions(-) delete mode 100644 src/templates/nodes/custom/blog/_macros.pug diff --git a/src/templates/nodes/custom/blog/_macros.pug b/src/templates/nodes/custom/blog/_macros.pug deleted file mode 100644 index 8d9791f9..00000000 --- a/src/templates/nodes/custom/blog/_macros.pug +++ /dev/null @@ -1,142 +0,0 @@ -//- ******************************************************* -// -| {% import 'projects/_macros.html' as projectmacros %} -| {% macro render_blog_post(node, project=None, pages=None) %} -| {% if node.picture %} -a.blog_index-header(href="{{ node.url }}") - img(src="{{ node.picture.thumbnail('h', api=api) }}") -| {% endif %} -| {% if project and project._id != config.MAIN_PROJECT_ID %} -| {{ projectmacros.render_secondary_navigation(project, pages=pages) }} -| {% endif %} -.blog_index-item.mx-auto - h2.pt-4.px-4 - a.text-muted(href="{{ node.url }}") - | {{ node.name }} - ul.meta - | {% if node.project.name %} - li {{ node.project.name }} - | {% endif %} - | {% if node.user.full_name%} - li.who - | by {{ node.user.full_name }} - | {% endif %} - li.when - a(href="{{ node.url }}", - title="Updated {{ node._updated | pretty_date }}") - | {{ node._created | pretty_date }} - li - a(href="{{ node.url }}#comments") - | comment - - - .item-content - | {{ node.properties | markdowned('content') }} - -| {% endmacro %} - -//- ******************************************************* -// -| {% macro render_blog_list_item(node) %} -.blog_index-item.list - - | {% if node.picture %} - a.item-header(href="{{ node.url }}") - img.image(src="{{ node.picture.thumbnail('s', api=api) }}") - | {% else %} - a.item-header.nothumb(href="{{ node.url }}") - i.pi-document-text - | {% endif %} - - a.item-title.text-muted( - href="{{ node.url }}") - | {{node.name}} - - .item-info. - #[span(title="{{node._created}}") {{node._created | pretty_date }}] - {% if node._created != node._updated %} - #[span(title="{{node._updated}}") (updated {{node._updated | pretty_date }})] - {% endif %} - {% if node.properties.category %} · {{node.properties.category}}{% endif %} - · {{node.user.full_name}} - {% if node.properties.status != 'published' %} · {{ node.properties.status}} {% endif %} - -| {% endmacro %} - - -//- ******************************************************* -// -| {% macro render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %} -| {% if can_create_blog_posts %} -.blog-action - a.btn.btn-outline-primary.button-create(href="{{url_for('nodes.posts_create', project_id=project._id)}}") - i.pi-plus - | Create New Post -| {% endif %} - -| {% if posts %} -| {{ render_blog_post(posts[0], project=project, pages=pages) }} - -| {% for node in posts[1:] %} -| {% if loop.first %} -.blog-archive-navigation - span Blasts from the past -| {% endif %} -| {{ render_blog_list_item(node) }} -| {% endfor %} - -| {% if more_posts_available %} -.blog-archive-navigation - a(href="{{ project.blog_archive_url }}") - | {{posts_meta.total - posts|length}} more blog posts over here - i.pi-angle-right -| {% endif %} - -| {% else %} - -.blog_index-item - .item-content No posts... yet! - -| {% endif %} {# posts #} -| {% endmacro %} - - -//- Macro for rendering the navigation buttons for prev/next pages -// -| {% macro render_archive_pagination(project) %} -.blog-archive-navigation - | {% if project.blog_archive_prev %} - a.archive-nav-button( - href="{{ project.blog_archive_prev }}", rel="prev") - i.pi-angle-left - | Previous page - | {% else %} - span.archive-nav-button - i.pi-angle-left - | Previous page - | {% endif %} - - a.archive-nav-button( - href="{{ url_for('main.project_blog', project_url=project.url) }}") - | Blog Index - - | {% if project.blog_archive_next %} - a.archive-nav-button( - href="{{ project.blog_archive_next }}", rel="next") - | Next page - i.pi-angle-right - | {% else %} - span.archive-nav-button - | Next page - i.pi-angle-right - | {% endif %} - -| {% endmacro %} - -| {% macro render_archive(project, posts, posts_meta) %} - -| {{ render_archive_pagination(project) }} - -| {% for node in posts %} -| {{ render_blog_list_item(node) }} -| {% endfor %} - -| {{ render_archive_pagination(project) }} - -| {% endmacro %} From d347534fead50eed2bda75f1e974616e1aa4f33e Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:19:28 +0200 Subject: [PATCH 034/148] Pug: Move navigation macro to blender-cloud --- src/templates/_macros/_navigation.pug | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/templates/_macros/_navigation.pug diff --git a/src/templates/_macros/_navigation.pug b/src/templates/_macros/_navigation.pug deleted file mode 100644 index fd08e94a..00000000 --- a/src/templates/_macros/_navigation.pug +++ /dev/null @@ -1,26 +0,0 @@ -| {% macro navigation_tabs(title) %} - -nav#nav-tabs - ul#nav-tabs__list - li.nav-tabs__list-tab( - class="{% if title == 'homepage' %}active{% endif %}") - a(href="{{ url_for('main.homepage') }}") Activity - - li.nav-tabs__list-tab( - class="{% if title == 'home' %}active{% endif %}") - a(href="{{ url_for('projects.home_project') }}") Home - - li.nav-tabs__list-tab( - class="{% if title == 'dashboard' %}active{% endif %}") - a(href="{{ url_for('projects.index') }}") My Projects - - | {% if current_user.has_organizations() %} - li.nav-tabs__list-tab( - class="{% if title == 'organizations' %}active{% endif %}") - a( - href="{{ url_for('pillar.web.organizations.index') }}", - title="My Organizations") - | My Organizations - | {% endif %} - -| {% endmacro %} From 1500e2029153458f5d018c61400395cc195ccc4a Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:42:37 +0200 Subject: [PATCH 035/148] Blog: cleanup of layout and style Simpler markup reusing bootstrap 4 classes. --- src/styles/components/_jumbotron.sass | 3 +++ .../nodes/custom/blog/archive_main_project.pug | 9 +++------ src/templates/nodes/custom/blog/index.pug | 4 +--- .../nodes/custom/blog/index_main_project.pug | 7 ++----- src/templates/nodes/custom/post/create.pug | 2 +- src/templates/nodes/custom/post/view_embed.pug | 18 ++++-------------- .../nodes/custom/post/view_main_project.pug | 3 +-- 7 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/styles/components/_jumbotron.sass b/src/styles/components/_jumbotron.sass index ffa1a735..88dd15ac 100644 --- a/src/styles/components/_jumbotron.sass +++ b/src/styles/components/_jumbotron.sass @@ -23,3 +23,6 @@ h2, p text-shadow: 1px 1px rgba(black, .2), 1px 1px 25px rgba(black, .5) + + &:hover + text-decoration: none diff --git a/src/templates/nodes/custom/blog/archive_main_project.pug b/src/templates/nodes/custom/blog/archive_main_project.pug index 6ef9952a..bdbb6805 100644 --- a/src/templates/nodes/custom/blog/archive_main_project.pug +++ b/src/templates/nodes/custom/blog/archive_main_project.pug @@ -2,11 +2,8 @@ | {% import 'nodes/custom/blog/_macros.html' as blogmacros %} | {% block body %} -.container-fluid - #blog_container.cloud-blog - #blog_index-container - .blog_index-header - h3 Blog Archive +.container + h3 Blog Archive - | {{ blogmacros.render_archive(project, posts, posts_meta) }} + | {{ blogmacros.render_archive(project, posts, posts_meta) }} | {% endblock body %} diff --git a/src/templates/nodes/custom/blog/index.pug b/src/templates/nodes/custom/blog/index.pug index 6d502a1b..d5381689 100644 --- a/src/templates/nodes/custom/blog/index.pug +++ b/src/templates/nodes/custom/blog/index.pug @@ -9,9 +9,7 @@ link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel= | {% endblock %} | {% block project_context %} -#blog_container - #blog_index-container.expand-image-links - | {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta) }} +| {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta) }} | {% endblock %} | {% block project_tree %} diff --git a/src/templates/nodes/custom/blog/index_main_project.pug b/src/templates/nodes/custom/blog/index_main_project.pug index 64b239fc..6f809bff 100644 --- a/src/templates/nodes/custom/blog/index_main_project.pug +++ b/src/templates/nodes/custom/blog/index_main_project.pug @@ -10,10 +10,7 @@ link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css') | {% endblock css %} | {% block body %} -.container-fluid.blog - #blog_container.cloud-blog - #blog_index-container.expand-image-links - | {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=pages) }} +| {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=pages) }} | {% endblock %} | {% block footer_scripts %} @@ -25,7 +22,7 @@ script. /* Expand images when their link points to a jpg/png/gif */ /* TODO: De-duplicate code from view post */ var page_overlay = document.getElementById('page-overlay'); - $('.blog_index-item .item-content a img').on('click', function(e){ + $('.item-content a img').on('click', function(e){ e.preventDefault(); var href = $(this).parent().attr('href'); diff --git a/src/templates/nodes/custom/post/create.pug b/src/templates/nodes/custom/post/create.pug index 10414e29..6af4c7a1 100644 --- a/src/templates/nodes/custom/post/create.pug +++ b/src/templates/nodes/custom/post/create.pug @@ -68,7 +68,7 @@ | {% endif %} | {% endfor %} - input.btn.btn-outline-secondary.button-create(type='submit', value='Create {{ node_type.name }}') + input.btn.btn-outline-secondary(type='submit', value='Create {{ node_type.name }}') a.btn.btn-link.button-back(href="{{ url_for('projects.view', project_url=project.url) }}blog") | Back to Blog diff --git a/src/templates/nodes/custom/post/view_embed.pug b/src/templates/nodes/custom/post/view_embed.pug index 4924162e..d5e1e38f 100644 --- a/src/templates/nodes/custom/post/view_embed.pug +++ b/src/templates/nodes/custom/post/view_embed.pug @@ -1,19 +1,9 @@ | {% import 'nodes/custom/blog/_macros.html' as blogmacros %} -#blog_container(class="{% if project and project._id == config.MAIN_PROJECT_ID %}cloud-blog{% endif %}") +| {{ blogmacros.render_blog_post(node, project=project) }} - #blog_index-container.expand-image-links - .blog-action - | {% if node.has_method('PUT') %} - a.btn.btn-outline-secondary.button-edit(href="{{url_for('nodes.edit', node_id=node._id)}}") - i.pi-edit - | Edit Post - | {% endif %} - - | {{ blogmacros.render_blog_post(node, project=project) }} - - #comments-embed - .comments-list-loading - i.pi-spin +#comments-embed.comments-compact + .comments-list-loading + i.pi-spin include ../_scripts diff --git a/src/templates/nodes/custom/post/view_main_project.pug b/src/templates/nodes/custom/post/view_main_project.pug index c8c48071..26ece6ec 100644 --- a/src/templates/nodes/custom/post/view_main_project.pug +++ b/src/templates/nodes/custom/post/view_main_project.pug @@ -26,8 +26,7 @@ link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css') | {% set title = 'blog' %} | {% block body %} -.container-fluid.blog - | {% include 'nodes/custom/post/view_embed.html' %} +| {% include 'nodes/custom/post/view_embed.html' %} | {% endblock %} From 6b1a5e24e8cfdfb7e2dafad7313823e9df5204ca Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Thu, 6 Sep 2018 12:46:33 +0200 Subject: [PATCH 036/148] Pug: Use templates from blender-cloud Affects the following templates: /projects/view.pug /projects/index_dashboard.pug /organizations/index.pug A lot of this layout is hardcoded for blender-cloud anyway. Eventually Pillar should have its own templates to use as starting point for building other Pillar apps. This should be built using the minimal amount of code possible and rely on styling possible via Bootstrap. --- src/templates/organizations/index.pug | 208 +------ src/templates/projects/index_dashboard.pug | 348 +---------- src/templates/projects/view.pug | 638 +-------------------- 3 files changed, 12 insertions(+), 1182 deletions(-) diff --git a/src/templates/organizations/index.pug b/src/templates/organizations/index.pug index 53c222c4..d61e9b3b 100644 --- a/src/templates/organizations/index.pug +++ b/src/templates/organizations/index.pug @@ -1,210 +1,10 @@ | {% extends 'layout.html' %} -| {% from '_macros/_navigation.html' import navigation_tabs %} - | {% set title = 'organizations' %} | {% block page_title %}Organizations{% endblock %} -| {% block og %} -meta(property="og:title", content="Dashboard") -meta(name="twitter:title", content="Blender Cloud") - -meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}") -meta(property="og:type", content="website") - -meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") -meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") -| {% endblock %} - | {% block body %} -.dashboard-container - section.dashboard-main - | {{ navigation_tabs(title) }} - - section#projects - - | {% if can_create_organization %} - nav#sub-nav-tabs.projects - ul#sub-nav-tabs__list - li.result#create_organization_result_panel - li.create - button.btn.btn-outline-success(onclick='createNewOrganization(this)') - i.pi-plus - | Create Organization - | {% endif %} - - nav.nav-tabs__tab.active#own_projects - ul.projects__list - | {% if organizations %} - | {% for organization in organizations['_items'] %} - | {% set link_url = url_for('pillar.web.organizations.view_embed', organization_id=organization._id) %} - li.projects__list-item( - data-url="{{ link_url }}", - id="organization-{{ organization._id }}") - a.projects__list-thumbnail( - href="{{ link_url }}") - i.pi-users - .projects__list-details - a.title(href="{{ link_url }}") - | {{ organization.name }} - - ul.meta - li(title="Members") - | {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }} - | {% if (organization.unknown_members|count) != 0 %} - | ({{ organization.unknown_members|hide_none|count }} pending) - | {% endif %} - li(title="Seats") - | {{ organization.seat_count }} Seat{{ organization.seat_count|pluralize }} - - | {% endfor %} - | {% else %} - li.projects__list-item - a.projects__list-thumbnail - i.pi-blender-cloud - .projects__list-details - span Create an Organization to get started! - | {% endif %} - - section.dashboard-secondary - section.box - #item-details - -| {% endblock %} - - -| {% block footer_scripts %} -script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}") -script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.autocomplete-0.22.0.min.js') }}", async=true) - -script. - - /* Returns a more-or-less reasonable message given an error response object. */ - function xhrErrorResponseMessage(err) { - if (typeof err.responseJSON == 'undefined') - return err.statusText; - - if (typeof err.responseJSON._error != 'undefined' && typeof err.responseJSON._error.message != 'undefined') - return err.responseJSON._error.message; - - if (typeof err.responseJSON._message != 'undefined') - return err.responseJSON._message - - return err.statusText; - } - - /** - * Open an organization in the #item-details div. - */ - function item_open(item_id, pushState) - { - if (item_id === undefined ) { - throw new ReferenceError("item_open(" + item_id + ") called."); - } - - // Style elements starting with item_type and dash, e.g. "#job-uuid" - var clean_classes = 'active processing'; - var current_item = $('#organization-' + item_id); - - $('[id^="organization-"]').removeClass(clean_classes); - current_item - .removeClass(clean_classes) - .addClass('processing'); - - var item_url = '/o/' + item_id; - - $.get(item_url, function(item_data) { - $('#item-details').html(item_data); - - current_item - .removeClass(clean_classes) - .addClass('active'); - - }).fail(function(xhr) { - if (console) { - console.log('Error fetching organization', item_id, 'from', item_url); - console.log('XHR:', xhr); - } - - current_item.removeClass(clean_classes); - toastr.error('Failed to open organization'); - - if (xhr.status) { - $('#item-details').html(xhr.responseText); - } else { - $('#item-details').html('

Opening ' + item_type + ' failed. There possibly was ' + - 'an error connecting to the server. Please check your network connection and ' + - 'try again.

'); - } - }); - - // Determine whether we should push the new state or not. - pushState = (typeof pushState !== 'undefined') ? pushState : true; - if (!pushState) return; - - // Push the correct URL onto the history. - var push_state = {itemId: item_id}; - - window.history.pushState( - push_state, - 'Organization: ' + item_id, - item_url - ); - } - - $('li.projects__list-item').click(function(e){ - url = $(this).data('url'); - if (typeof url === 'undefined') return; - - window.location.href = url; - if (console) console.log(url); - - $(this).addClass('active'); - $(this).find('.projects__list-thumbnail i') - .removeAttr('class') - .addClass('pi-spin spin'); - }); - - - {% if open_organization_id %} - $(function() { item_open('{{ open_organization_id }}', false); }); - {% endif %} - - {% if can_create_organization %} - function createNewOrganization(button) { - $(button) - .attr('disabled', 'disabled') - .fadeTo(200, 0.1); - $('#create_organization_result_panel').html(''); - - // TODO: create a form to get the initial info from the user. - $.post( - '{{ url_for('pillar.web.organizations.create_new') }}', - { - name: 'New Organization', - seat_count: 1, - } - ) - .done(function(result) { - var $p = $('

').text('organization created, reloading list.') - $('#create_organization_result_panel').html($p); - - window.location.href = result.location; - }) - .fail(function(err) { - var msg = xhrErrorResponseMessage(err); - $('#create_organization_result_panel').html('Error creating organization: ' + msg); - - $(button) - .fadeTo(1000, 1.0) - .queue(function() { - $(this) - .removeAttr('disabled') - .dequeue() - ; - }) - }) - ; - return false; - } - {% endif %} +.p-5.text-center + h2 Organizations Index + .lead. + See Blender Cloud template for reference. | {% endblock %} diff --git a/src/templates/projects/index_dashboard.pug b/src/templates/projects/index_dashboard.pug index fcc50df2..ca6d7f22 100644 --- a/src/templates/projects/index_dashboard.pug +++ b/src/templates/projects/index_dashboard.pug @@ -1,356 +1,16 @@ | {% extends 'layout.html' %} -| {% from '_macros/_navigation.html' import navigation_tabs %} - | {% set title = 'dashboard' %} -| {% block og %} -meta(property="og:title", content="Dashboard") -meta(name="twitter:title", content="Blender Cloud") - -meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}") -meta(property="og:type", content="website") - -meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") -meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") -| {% endblock %} - | {% block page_title %} | {{current_user.full_name}} | {% endblock %} -| {% block css %} -| {{ super() }} -style. - .deleted-projects-toggle { - z-index: 10; - position: absolute; - right: 0; - font-size: 20px; - padding: 3px; - text-shadow: 0 0 2px white; - } - .deleted-projects-toggle .show-deleted { - color: #aaa; - } - .deleted-projects-toggle .hide-deleted { - color: #bbb; - } -| {% endblock %} - | {% block body %} -.dashboard-container - section.dashboard-main - | {{ navigation_tabs(title) }} - - section#projects - - nav#sub-nav-tabs.projects - ul#sub-nav-tabs__list - li.nav-tabs__list-tab.active(data-tab-toggle='own_projects') - | Own Projects - | {% if projects_user|length != 0 %} - span ({{ projects_user|length }}) - | {% endif %} - - li.nav-tabs__list-tab(data-tab-toggle='shared') - | Shared with me - | {% if projects_shared|length != 0 %} - span ({{ projects_shared|length }}) - | {% endif %} - - | {% if current_user.has_cap('subscriber') %} - li#project-create( - data-url="{{ url_for('projects.create') }}") - a.btn.btn-success( - href="{{ url_for('projects.create') }}") - i.pi-plus - | Create Project - | {% elif current_user.has_cap('can-renew-subscription') %} - li - a.btn.btn-outline-success(href="/renew", target="_blank") - i.pi-heart - | Renew subscription to create a project - | {% endif %} - - nav.nav-tabs__tab.active#own_projects - .deleted-projects-toggle - | {% if show_deleted_projects %} - a.hide-deleted(href="{{ request.base_url }}", title='Hide deleted projects') - i.pi-trash - | {% else %} - a.show-deleted(href="{{ request.base_url }}?deleted=1", title='Show deleted projects') - i.pi-trash - | {% endif %} - - ul.projects__list - | {% for project in projects_deleted %} - li.projects__list-item.deleted - span.projects__list-thumbnail - | {% if project.picture_square %} - img(src="{{ project.picture_square.thumbnail('s', api=api) }}") - | {% else %} - i.pi-blender-cloud - | {% endif %} - .projects__list-details - span.title {{ project.name }} - ul.meta - li.status.deleted Deleted - li.edit - a(href="javascript:undelete_project('{{ project._id }}')") Restore project - | {% else %} - | {% if show_deleted_projects %} - li.projects__list-item.deleted You have no recenly deleted projects. Deleted projects can be restored within a month after deletion. - | {% endif %} - | {% endfor %} - - | {% for project in projects_user %} - li.projects__list-item( - data-url="{{ url_for('projects.view', project_url=project.url) }}") - a.projects__list-thumbnail( - href="{{ url_for('projects.view', project_url=project.url) }}") - | {% if project.picture_square %} - img(src="{{ project.picture_square.thumbnail('s', api=api) }}") - | {% else %} - i.pi-blender-cloud - | {% endif %} - .projects__list-details - a.title(href="{{ url_for('projects.view', project_url=project.url) }}") - | {{ project.name }} - - ul.meta - li.status( - class="{{ project.is_private | yesno('private,public,') }}", - title="{{ project.is_private | yesno('Private Project,Public Project,') }}") - | {{ project.is_private | yesno('Private,Public,') }} - li.when(title="{{ project._created }}") {{ project._created | pretty_date }} - li.edit - a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit - | {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %} - li.pending Not Published - | {% endif %} - | {% else %} - | {% if current_user.has_cap('subscriber') %} - li.projects__list-item(data-url="{{ url_for('projects.create') }}") - a.projects__list-thumbnail - i.pi-plus - .projects__list-details - a.title(href="{{ url_for('projects.create') }}") - | Create a project to get started! - | {% elif current_user.has_cap('can-renew-subscription') %} - li.projects__list-item(data-url="https://store.blender.org/renew-my-subscription.php") - a.projects__list-thumbnail - i.pi-plus - .projects__list-details - a.title(href="https://store.blender.org/renew-my-subscription.php") - | Renew your Blender Cloud subscription to create your own projects! - | {% else %} - li.projects__list-item(data-url="/join") - a.projects__list-thumbnail - i.pi-plus - .projects__list-details - a.title(href="/join") - | Join Blender Cloud to create your own projects! - | {% endif %} - | {% endfor %} - - section.nav-tabs__tab#shared(style='display: none') - ul.projects__list - | {% if projects_shared %} - | {% for project in projects_shared %} - li.projects__list-item( - data-url="{{ url_for('projects.view', project_url=project.url) }}") - a.projects__list-thumbnail( - href="{{ url_for('projects.view', project_url=project.url) }}") - | {% if project.picture_square %} - img(src="{{ project.picture_square.thumbnail('s', api=api) }}") - | {% else %} - i.pi-blender-cloud - | {% endif %} - .projects__list-details - a.title(href="{{ url_for('projects.view', project_url=project.url) }}") - | {{ project.name }} - - ul.meta - li.status( - class="{{ project.is_private | yesno('private,public,') }}", - title="{{ project.is_private | yesno('Private Project,Public Project,') }}") - | {{ project.is_private | yesno('Private,Public,') }} - li.when {{ project._created | pretty_date }} - li.who by {{ project.user.full_name }} - li.edit - a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit - | {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %} - li.pending Not Published - | {% endif %} - - li.leave - span.user-remove-prompt - | Leave Project - - span.user-remove - | Are you sure? - span.user-remove-confirm( - user-id="{{ current_user.objectid }}", - project-url="{{url_for('projects.sharing', project_url=project.url)}}") - i.pi-check - | Yes, leave - span.user-remove-cancel - i.pi-cancel - | No, cancel - - | {% endfor %} - | {% else %} - li.projects__list-item - a.projects__list-thumbnail - i.pi-heart-broken - .projects__list-details - .title - | No projects shared with you... yet! - | {% endif %} - - section.dashboard-secondary - section.announcement - a(href="https://cloud.blender.org/blog/introducing-private-projects") - img.header( - src="{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}") - .text - h5 - a.text-muted(href="https://cloud.blender.org/blog/introducing-private-projects") - | Projects - .lead - p. - Create and manage your own personal projects. - Upload assets and collaborate with other Blender Cloud members. - - a.btn.btn-link.btn-block( - href="https://cloud.blender.org/blog/introducing-private-projects") - | Learn More - i.pi-angle-right - - section.announcement.mt-3 - a(href="https://cloud.blender.org/blog/introducing-blender-sync") - img.header( - src="{{ url_for('static', filename='assets/img/blender_sync_header.jpg') }}") - .text - h5 - a.text-muted(href="https://cloud.blender.org/blog/introducing-blender-sync") - | Textures Browser & Settings Sync - - .lead - p. - Get the official Blender Cloud add-on: - - ul - li Save your Blender settings online, use them anywhere - li Browse over 800 textures & HDRIs within Blender - li Share Screenshots & Renders directly to Blender Cloud - - .row - .col-md-8 - a.btn.btn-outline-success.btn-block( - href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip") - i.pi-download - | Download Add-on v {{ config.BLENDER_CLOUD_ADDON_VERSION }} - .col-md-4 - a.btn.btn-link( - href="https://cloud.blender.org/blog/introducing-blender-sync") - | Learn More - i.pi-angle-right - +.p-5.text-center + h2 Index Dashboard + p.lead. + See Blender Cloud template for reference. | {% endblock %} - | {% block footer_scripts %} -script. - $(document).ready(function() { - - $('li.projects__list-item').click(function(e){ - url = $(this).data('url'); - if (typeof url === 'undefined') return; - - window.location.href = url; - if (console) console.log(url); - - $(this).addClass('active'); - $(this).find('.projects__list-thumbnail i') - .removeAttr('class') - .addClass('pi-spin spin'); - }); - - // Tabs behavior - var $nav_tabs_list = $('#sub-nav-tabs__list'); - var $nav_tabs = $nav_tabs_list.find('li.nav-tabs__list-tab'); - $nav_tabs.on('click', function(e){ - e.preventDefault(); - - $nav_tabs.removeClass('active'); - $(this).addClass('active'); - - $('.nav-tabs__tab').hide(); - $('#' + $(this).attr('data-tab-toggle')).show(); - }); - - // Create project - $('#project-create').on('click', function(e){ - e.preventDefault(); - - $(this).addClass('disabled'); - $('a', this).html(' Creating project...'); - - window.location.href = $(this).data('url'); - }); - - // Leave project - var $projects_list = $('ul.projects__list'); - $projects_list.find('span.user-remove-prompt').on('click', function(e){ - e.stopPropagation(); - e.preventDefault(); - - $(this).next().show(); - $(this).hide(); - }); - - $projects_list.find('span.user-remove-cancel').on('click', function(e){ - e.stopPropagation(); - e.preventDefault(); - - $(this).parent().prev().show(); - $(this).parent().hide(); - }); - - $projects_list.find('span.user-remove-confirm').on('click', function(e){ - e.stopPropagation(); - e.preventDefault(); - var parent = $(this).closest('.projects__list-item'); - - function removeUser(userId, projectUrl){ - $.post(projectUrl, {user_id: userId, action: 'remove'}) - .done(function (data) { - parent.remove(); - }); - } - - removeUser($(this).attr('user-id'), $(this).attr('project-url')); - }); - - hopToTop(); // Display jump to top button - }); - - - var patch_url = '{{ url_for('projects.patch.patch_project', project_id='PROJECTID') }}'; - function undelete_project(project_id) { - console.log('undeleting project', project_id); - $.ajax({ - url: patch_url.replace('PROJECTID', project_id), - method: 'PATCH', - data: JSON.stringify({'op': 'undelete'}), - contentType: 'application/json' - }) - .done(function(data, textStatus, jqXHR) { - location.href = jqXHR.getResponseHeader('Location'); - }) - .fail(function(err) { - toastr.error(xhrErrorResponseMessage(err), 'Undeletion failed'); - }) - } | {% endblock %} diff --git a/src/templates/projects/view.pug b/src/templates/projects/view.pug index 8151fbe6..d0b1aeb1 100644 --- a/src/templates/projects/view.pug +++ b/src/templates/projects/view.pug @@ -1,641 +1,11 @@ | {% extends 'layout.html' %} -| {% from '_macros/_add_new_menu.html' import add_new_menu %} | {% block page_title %}{{ project.name }}{% endblock%} | {% set title = 'project' %} -| {% block og %} -meta(property="og:type", content="website") - -| {% if og_picture %} -meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}") -meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}") -| {% elif node and node.picture %} -meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}") -meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}") -| {% elif project.picture_header %} -meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}") -meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}") -| {% endif %} - -| {% if show_project %} -meta(property="og:title", content="{{ project.name }} - Blender Cloud") -meta(name="twitter:title", content="{{ project.name }} - Blender Cloud") -meta(property="og:description", content="{{ project.summary }}") -meta(name="twitter:description", content="{{ project.summary }}") - -meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}") -| {% else %} - -| {% if node %} -meta(property="og:title", content="{{ node.name }} - Blender Cloud") -meta(name="twitter:title", content="{{ node.name }} on Blender Cloud") - -| {% if node.node_type == 'post' %} - -| {% if node.properties.content %} -meta(property="og:description", content="{{ node.properties.content | truncate(180) }}") -meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}") -| {% else %} -meta(property="og:description", content="Blender Cloud, your source for open content and training") -meta(name="twitter:description", content="Blender Cloud, your source for open content and training") -| {% endif %} - -| {% else %} - -| {% if node.description %} -meta(property="og:description", content="{{ node.description | truncate(180) }}") -meta(name="twitter:description", content="{{ node.description | truncate(180) }}") -| {% else %} -meta(property="og:description", content="Blender Cloud, your source for open content and training") -meta(name="twitter:description", content="Blender Cloud, your source for open content and training") -| {% endif %} - -| {% endif %} - -meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}") -| {% else %} -meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud") -meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud") -meta(property="og:description", content="{{ project.summary }}") -meta(name="twitter:description", content="{{ project.summary }}") - -meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}") -| {% endif %} - -| {% endif %} -| {% endblock %} - -| {% block head %} -link(href="{{ url_for('static_pillar', filename='assets/jstree/themes/default/style.min.css') }}", rel="stylesheet") -| {% if node %} -link(rel="amphtml", href="{{ url_for('nodes.view', node_id=node._id, _external=True, format='amp') }}") -| {% endif %} - -script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}") -script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}") -script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}") -| {% endblock %} - -| {% block css %} -link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet") -link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') }}", rel="stylesheet") -| {% endblock %} - | {% block body %} -#project-container - #project-side-container - #project_sidebar.bg-white - ul.project-tabs.p-0 - li.tabs-thumbnail(class="{% if project.picture_square %}image{% endif %}") - a(href="{{url_for('projects.view', project_url=project.url)}}") - #project-loading - i.pi-spin - | {% if project.picture_square %} - img(src="{{ project.picture_square.thumbnail('b', api=api) }}") - | {% else %} - i.pi-home - | {% endif %} - - li.tabs-browse( - title="Browse", - data-toggle="tooltip", - data-placement="right", - class="active") - a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") - i.pi-folder - - | {% if not project.is_private %} - | {% if current_user_is_subscriber %} - li.tabs-search( - title="Search", - data-toggle="tooltip", - data-placement="right") - a(href="{{ url_for('projects.search', project_url=project.url, _external=True)}} ") - i.pi-search - | {% else %} - li.tabs-search( - title="Search (subscribers only)", - data-toggle="tooltip", - data-placement="right") - a(href="{{ url_for('cloud.join') }}") - i.pi-search - | {% endif %} - | {% endif %} - | {{ extension_sidebar_links }} - - | {% if project.has_method('PUT') %} - li( - title="Edit Project", - data-toggle="tooltip", - data-placement="right") - a(href="{{ url_for('projects.edit', project_url=project.url) }}") - i.pi-cog - | {% endif %} - - - #project_nav(class="{{ title }}") - #project_nav-container - | {% if title != 'about' %} - #project_nav-header.bg-white - a.project-title.p-2.font-weight-bold.text-dark( - href="{{url_for('projects.view', project_url=project.url, _external=True)}}") - | {{ project.name }} - - | {% block project_tree %} - #project_tree.bg-white - | {% endblock project_tree %} - | {% endif %} - - - #project_context-container - | {% if project.has_method('PUT') %} - #project_context-header.bg-white - span#status-bar - - ul.project-edit-tools.disabled - li.dropdown - button#item_add.project-mode-view.btn.btn-sm.btn-outline-secondary.dropdown-toggle( - type="button", - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false") - i.button-add-icon.pi-collection-plus - | New... - - ul.dropdown-menu.add_new-menu - | {{ add_new_menu(project.node_types) }} - - li.button-edit - a#item_edit.project-mode-view.btn.btn-sm.btn-outline-secondary.ml-2( - href="javascript:void(0);", - title="Edit", - data-project_id="{{project._id}}") - i.button-edit-icon.pi-edit - | Edit Project - - li.dropdown - button.dropdown-toggle.project-mode-view.btn.btn-sm.btn-outline-secondary.mx-2( - type="button", - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false") - i.pi-more-vertical.p-0 - - ul.dropdown-menu - | {% if current_user.has_cap('admin') %} - li.dropdown-item - a#item_featured( - href="javascript:void(0);", - title="Feature on project's homepage", - data-toggle="tooltip", - data-placement="left") - i.pi-star - | Toggle Featured - - li.dropdown-item - a#item_toggle_public( - href="javascript:void(0);", - title="Make it accessible to anyone", - data-toggle="tooltip", - data-placement="left") - i.pi-lock-open - | Toggle Public - | {% endif %} - - li.dropdown-item - a#item_toggle_projheader( - href="javascript:void(0);", - title="Feature as project's header", - data-toggle="tooltip", - data-placement="left") - i.pi-star - | Toggle Project Header video - - li.dropdown-item.button-move - a#item_move( - href="javascript:void(0);", - title="Move into a folder...", - data-toggle="tooltip", - data-placement="left") - i.button-move-icon.pi-move - | Move - - li.dropdown-item.button-delete - a#item_delete( - href="javascript:void(0);", - title="Can be undone within a month", - data-toggle="tooltip", - data-placement="left") - i.pi-trash - | Delete Project - - // Edit Mode - li.button-cancel - a#item_cancel.project-mode-edit.btn.btn-outline-secondary( - href="javascript:void(0);", - title="Cancel changes") - i.button-cancel-icon.pi-cancel - | Cancel - - li.button-save - a#item_save.project-mode-edit.btn.btn-outline-success.mx-2( - href="javascript:void(0);", - title="Save changes") - i.button-save-icon.pi-check - | Save Changes - - | {% endif %} - - | {% set utm_source = request.args.get('utm_source') %} - | {% if config.UTM_LINKS and utm_source in config.UTM_LINKS %} - #utm_container - a(href="{{config.UTM_LINKS[utm_source]['link']}}") - img(src="{{config.UTM_LINKS[utm_source]['image']}}", alt="gift", class="img-responsive") - | {% endif %} - #project_context - | {% block project_context %} - | {% if show_project %} - | {% include "projects/view_embed.html" %} - | {% endif %} - | {% endblock project_context %} - - #overlay-mode-move-container - .overlay-container - .title - i.pi-angle-left - | Select the folder where you want to move it - .buttons - button#item_move_accept.move.disabled - | Select a Folder - button#item_move_cancel.cancel - i.pi-cancel - | Cancel - +.p-5.text-center + h2 Project View + .lead. + See Blender Cloud for reference. | {% endblock %} - -| {% block footer_container %}{% endblock %} - -| {% block footer_scripts_pre %} - -| {% if project.has_method('PUT') %} -| {# JS containing the Edit, Add, Featured, and Move functions #} -script(type="text/javascript", src="{{ url_for('static_pillar', filename='assets/js/project-edit.min.js') }}") -| {% endif %} - -script. - function updateToggleProjHeaderMenuItem() { - var $toggle_projheader = $('#item_toggle_projheader'); - - if (ProjectUtils.isProject()) { - $toggle_projheader.hide(); - return; - } - if (ProjectUtils.nodeType() == 'asset') { - $toggle_projheader.show(); - } else { - $toggle_projheader.hide(); - } - } - $(updateToggleProjHeaderMenuItem); - - // Function to update the interface on loadNodeContent, and edit/saving assets - function updateUi(nodeId, mode) { - - if (mode === 'view') { - $('.project-mode-view').displayAs('inline-block'); - $('.project-mode-edit').hide(); - - $("#node-edit-form").unbind("submit"); - $("#item_save").unbind("click"); - $("#item_cancel").unbind("click"); - } else if (mode === 'edit') { - $('.project-mode-view').hide(); - $('.project-mode-edit').displayAs('inline-block'); - } else { - if (console) console.log('Invalid mode:', mode); - } - - // Prevent flicker by scrolling to top. - $("#project_context-container").scrollTop(0); - - // Enable specific items under the Add New dropdown - if (ProjectUtils.nodeType() === 'group') { - addMenuEnable(['asset', 'group']); - - } else if (ProjectUtils.nodeType() === 'group_texture') { - addMenuEnable(['group_texture', 'texture']); - - } else if (ProjectUtils.nodeType() === 'group_hdri') { - addMenuEnable(['group_hdri', 'hdri']); - - } else if (!ProjectUtils.isProject()) { - addMenuEnable(false); - } - - updateToggleProjHeaderMenuItem(); - - // Set the page title on the document - var page_title = $('#node-title').text() + " - {{ project.name }} — Blender Cloud"; - DocumentTitleAPI.set_page_title(page_title); - - // TODO: Maybe remove this, now it's also in loadNodeContent(), but double-check - // it's done like that in all users of updateUi(). - $('#project-loading').removeAttr('class'); - } -| {% endblock %} - -| {% block footer_scripts %} -script(src="{{ url_for('static_pillar', filename='assets/jstree/jstree.min.js') }}") - -script. - {% if show_project %} - ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''}); - {% else %} - {% if node %} - ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: false, nodeId: '{{node._id}}'}); - {% endif %} - {% endif %} - - var projectTree = document.getElementById('project_tree'); - - var urlNodeMove = "{{url_for('projects.move_node')}}"; - var urlNodeFeature = "{{url_for('projects.add_featured_node')}}"; - var urlNodeDelete = "{{url_for('projects.delete_node')}}"; - var urlNodeTogglePublic = "{{url_for('projects.toggle_node_public')}}"; - var urlNodeToggleProjHeader = "{{url_for('projects.toggle_node_project_header')}}"; - var urlProjectDelete = "{{url_for('projects.delete')}}"; - var urlProjectEdit = "{{url_for('projects.edit', project_url=project.url)}}"; - - - function loadNodeContent(url, nodeId) { - $('#project-loading').addClass('active'); - $.get(url, function(dataHtml) { - // Update the DOM injecting the generate HTML into the page - $('#project_context').html(dataHtml); - }) - .done(function(){ - updateUi(nodeId, 'view'); - }) - .fail(function(dataResponse) { - $('#project_context').html($('' + src = f'https://www.youtube.com/embed/{youtube_id}?rel=0' + html = f'

' \ + f'' \ + f'
' return html From baf27fa560a9750f34e519a5b214ec15e6e1f975 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 02:04:14 +0200 Subject: [PATCH 111/148] Blog: Fix and css cleanup --- src/styles/blog.sass | 28 ----------------------- src/templates/nodes/custom/blog/index.pug | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/styles/blog.sass b/src/styles/blog.sass index 24babb31..585af2ed 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -430,34 +430,6 @@ text-decoration: none color: $color-primary -.blog-archive-navigation - +media-xs - font-size: 1em - max-width: initial - - border-bottom: thin solid $color-background-dark - display: flex - font: - size: 1.2em - weight: 300 - margin: 0 auto - max-width: 780px - text-align: center - +text-overflow-ellipsis - - &:last-child - border: none - - a, span - +media-xs - padding: 10px - - flex: 1 - padding: 25px 15px - - span - color: $color-text-dark-secondary - pointer-events: none // Specific tweaks for blogs in the context of a project. #project_context diff --git a/src/templates/nodes/custom/blog/index.pug b/src/templates/nodes/custom/blog/index.pug index 8805b268..4df4c9a2 100644 --- a/src/templates/nodes/custom/blog/index.pug +++ b/src/templates/nodes/custom/blog/index.pug @@ -11,7 +11,7 @@ | {% endblock navigation_tabs %} | {% block body %} -| {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=pages) }} +| {{ blogmacros.render_blog_index(node, project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=pages) }} | {% endblock %} | {% block footer_scripts %} From bb483e72aa7787fab583bcce9bb46aaa4ccfb8bb Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 03:05:34 +0200 Subject: [PATCH 112/148] CSS cleanup (blog, comments) --- src/styles/_apps_base.sass | 9 ++ src/styles/_comments.sass | 1 + src/styles/_project.sass | 3 - src/styles/blog.sass | 218 +------------------------------------ 4 files changed, 11 insertions(+), 220 deletions(-) diff --git a/src/styles/_apps_base.sass b/src/styles/_apps_base.sass index 4969adb8..8a1d4d14 100644 --- a/src/styles/_apps_base.sass +++ b/src/styles/_apps_base.sass @@ -455,3 +455,12 @@ $loader-bar-height: 2px .progress-bar background-color: $primary background-image: linear-gradient(to right, $primary-accent, $primary) + +.node-details-description + +node-details-description + + @include media-breakpoint-up(lg) + max-width: map-get($grid-breakpoints, "md") + + @include media-breakpoint-up(xl) + max-width: map-get($grid-breakpoints, "lg") diff --git a/src/styles/_comments.sass b/src/styles/_comments.sass index 81efb976..2e76c3d7 100644 --- a/src/styles/_comments.sass +++ b/src/styles/_comments.sass @@ -3,6 +3,7 @@ $comments-width-max: 710px .comments-container max-width: $comments-width-max position: relative + width: 100% #comments-reload text-align: center diff --git a/src/styles/_project.sass b/src/styles/_project.sass index 123c42a2..8ff08ada 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -651,9 +651,6 @@ section.node-details-container width: 100% max-width: 100% -.node-details-description - +node-details-description - .node-details-meta > ul align-items: center diff --git a/src/styles/blog.sass b/src/styles/blog.sass index 585af2ed..957d40e6 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -49,13 +49,6 @@ @import _comments @import _notifications -#blog_container - +media-xs - flex-direction: column - padding-top: 0 - - video - max-width: 100% #blog_post-edit-form padding: 20px @@ -128,7 +121,6 @@ margin-bottom: 15px border-top: thin solid $color-text-dark-hint - .form-group.description, .form-group.summary, .form-group.content @@ -196,64 +188,10 @@ color: transparent -#blog_post-create-container, -#blog_post-edit-container - padding: 25px - -.blog_index-item - .item-picture - position: relative - width: 100% - max-height: 350px - min-height: 200px - height: auto - overflow: hidden - border-top-left-radius: 3px - border-top-right-radius: 3px - +clearfix - - img - +position-center-translate - width: 100% - border-top-left-radius: 3px - border-top-right-radius: 3px - - +media-xs - min-height: 150px - +media-sm - min-height: 150px - +media-md - min-height: 250px - +media-lg - min-height: 250px - - .item-content - +node-details-description - - +media-xs - padding: - left: 0 - right: 0 - - img - display: block - margin: 0 auto - - .item-meta - color: $color-text-dark-secondary - padding: - left: 25px - right: 25px - - +media-xs - padding: - left: 10px - right: 10px - -#blog_index-container, #blog_post-create-container, #blog_post-edit-container +container-box + padding: 25px width: 75% +media-xs @@ -268,11 +206,6 @@ +media-lg width: 100% - - .item-picture+.button-back+.button-edit - right: 20px - top: 20px - #blog_post-edit-form padding: 0 @@ -307,152 +240,3 @@ .form-upload-file-meta width: initial - -#blog_post-edit-title - padding: 0 - color: $color-text - font: - size: 1.8em - weight: 300 - margin: 0 20px 15px 0 - -#blog_index-sidebar - width: 25% - padding: 0 15px - - +media-xs - width: 100% - clear: both - display: block - margin-top: 25px - +media-sm - width: 40% - +media-md - width: 30% - +media-lg - width: 25% - - .button-back - +button($color-info, 6px, true) - display: block - width: 100% - margin: 15px 0 0 0 - - #blog_post-edit-form - .form-group - .form-control - background-color: white - - .blog_index-sidebar, - .blog_project-sidebar - +container-box - background-color: lighten($color-background, 5%) - padding: 20px - - .blog_project-card - position: relative - width: 100% - border-radius: 3px - overflow: hidden - background-color: white - color: lighten($color-text, 10%) - box-shadow: 0 0 30px rgba(black, .2) - - margin: - top: 0 - bottom: 15px - left: auto - right: auto - - - a.item-header - position: relative - width: 100% - height: 100px - display: block - background-size: 100% 100% - - overflow: hidden - - .overlay - z-index: 1 - width: 100% - height: 100px - @include overlay(transparent, 0%, white, 100%) - - - img.background - width: 100% - transform: scale(1.4) - - .card-thumbnail - position: absolute - z-index: 2 - height: 90px - width: 90px - display: block - top: 35px - left: 50% - transform: translateX(-50%) - background-color: white - border-radius: 3px - overflow: hidden - - &:hover - img.thumb - opacity: .9 - - img.thumb - width: 100% - border-radius: 3px - transition: opacity 150ms ease-in-out - +position-center-translate - - .item-info - padding: 10px 20px - background-color: white - border-bottom-left-radius: 3px - border-bottom-right-radius: 3px - - a.item-title - display: inline-block - width: 100% - padding: 30px 0 15px 0 - color: $color-text-dark - text-align: center - font: - size: 1.6em - weight: 300 - - transition: color 150ms ease-in-out - - &:hover - text-decoration: none - color: $color-primary - - -// Specific tweaks for blogs in the context of a project. -#project_context - .blog_index-item - +media-xs - margin-left: 0 - padding: 0 - margin-left: 10px - - &.list - margin-left: 35px !important - - .item-title, - .item-info - +media-xs - padding-left: 0 - padding-left: 25px - - #blog_container - .comments-container - +media-sm - margin-left: 10px - margin-left: 30px - - .blog-archive-navigation - margin-left: 35px From fbc7c0fce79ab309940e8d08a5f4548cdfba85d6 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 03:38:27 +0200 Subject: [PATCH 113/148] CSS: media breakpoints from Bootstrap and added a couple more for super big screens --- src/styles/_config.sass | 4 +++- src/styles/_utils.sass | 14 +++++++++++--- src/styles/blog.sass | 1 - 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/styles/_config.sass b/src/styles/_config.sass index a0d693f1..fb98d5da 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -158,4 +158,6 @@ $tooltip-opacity: 1 $nav-link-height: 37px $navbar-padding-x: 0 -$navbar-padding-y: 0 \ No newline at end of file +$navbar-padding-y: 0 + +$grid-breakpoints: (xs: 0,sm: 576px,md: 768px,lg: 1100px,xl: 1500px, xxl: 1800px) diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index 329b7038..790ab1f4 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -171,17 +171,25 @@ /* Small but wide: phablets, iPads ** Menu is collapsed, columns stack, no brand */ =media-sm - @media (min-width: #{$screen-tablet}) and (max-width: #{$screen-desktop - 1px}) + @include media-breakpoint-up(sm) @content /* Tablets portrait. ** Menu is expanded, but columns stack, brand is shown */ =media-md - @media (min-width: #{$screen-desktop}) + @include media-breakpoint-up(md) @content =media-lg - @media (min-width: #{$screen-lg-desktop}) + @include media-breakpoint-up(lg) + @content + +=media-xl + @include media-breakpoint-up(xl) + @content + +=media-xxl + @include media-breakpoint-up(xxl) @content =media-print diff --git a/src/styles/blog.sass b/src/styles/blog.sass index 957d40e6..5165732f 100644 --- a/src/styles/blog.sass +++ b/src/styles/blog.sass @@ -49,7 +49,6 @@ @import _comments @import _notifications - #blog_post-edit-form padding: 20px From 4e5a53a19ba9cb4b0b657ff073df4c294be411b8 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 03:42:48 +0200 Subject: [PATCH 114/148] Option to limit card-deck to a maximum N columns Only 3 supported for now --- src/styles/components/_card.sass | 24 +++++++++++++++++++----- src/templates/mixins/components.pug | 4 ++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/styles/components/_card.sass b/src/styles/components/_card.sass index 1d338685..1a219793 100644 --- a/src/styles/components/_card.sass +++ b/src/styles/components/_card.sass @@ -4,23 +4,37 @@ @extend .row .card - @extend .col-md-3 - +media-xs + @extend .col-md-4 + + +media-sm flex: 1 0 50% max-width: 50% - +media-sm + +media-md flex: 1 0 33% max-width: 33% - +media-md + +media-lg + flex: 1 0 33% + max-width: 33% + + +media-xl flex: 1 0 25% max-width: 25% - +media-lg + +media-xxl flex: 1 0 20% max-width: 20% + &.card-3-columns .card + +media-xl + flex: 1 0 33% + max-width: 33% + + +media-xxl + flex: 1 0 33% + max-width: 33% + &.card-deck-vertical @extend .flex-column flex-wrap: initial diff --git a/src/templates/mixins/components.pug b/src/templates/mixins/components.pug index 8213b0a3..cecdeecc 100644 --- a/src/templates/mixins/components.pug +++ b/src/templates/mixins/components.pug @@ -48,8 +48,8 @@ mixin nav-secondary-link() a.nav-link&attributes(attributes) block -mixin card-deck() - .card-deck.card-padless.card-deck-responsive()&attributes(attributes) +mixin card-deck(max_columns) + .card-deck.card-padless.card-deck-responsive(class="card-" + max_columns + "-columns")&attributes(attributes) if block block else From 9624f6bd761948f5ca17be324dbbd8f59b442b1a Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 04:05:37 +0200 Subject: [PATCH 115/148] Style pages --- .../nodes/custom/comment/_macros.pug | 2 +- .../nodes/custom/page/view_embed.pug | 36 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/templates/nodes/custom/comment/_macros.pug b/src/templates/nodes/custom/comment/_macros.pug index 70c04929..fc2601ad 100644 --- a/src/templates/nodes/custom/comment/_macros.pug +++ b/src/templates/nodes/custom/comment/_macros.pug @@ -5,7 +5,7 @@ class="{% if is_reply %}is-reply{% else %}is-first{% endif %}") .comment-avatar - img(src="{{ comment._user.email | gravatar }}") + img(src="{{ comment._user.email | gravatar }}", alt="{{ comment._user.full_name }}") .comment-content .comment-body diff --git a/src/templates/nodes/custom/page/view_embed.pug b/src/templates/nodes/custom/page/view_embed.pug index b0cb38e2..0b55ef42 100644 --- a/src/templates/nodes/custom/page/view_embed.pug +++ b/src/templates/nodes/custom/page/view_embed.pug @@ -1,26 +1,28 @@ | {% extends 'projects/landing.html' %} +include ../../../mixins/components | {% block body %} -| {% if node.picture %} -header - img.header(src="{{ node.picture.thumbnail('h', api=api) }}") -| {% endif %} +.expand-image-links.imgs-fluid + | {% if node.picture %} + +jumbotron( + "{{ node.name }}", + "{{ node._created | pretty_date }}{% if node.user.full_name %} · {{ node.user.full_name }}{% endif %}", + "{{ node.picture.thumbnail('h', api=api) }}", + "{{ node.url }}") + | {% endif %} -#node-container - #node-overlay +.container.pb-5 + .row + .col-8.mx-auto + h2.pt-5.text-center {{node.name}} - .node-details-container.page.expand-image-links.imgs-fluid + | {% if node.description %} + .node-details-description + | {{ node | markdowned('description') }} + | {% endif %} - h2.pt-3.text-center {{node.name}} - - hr - - | {% if node.description %} - | {{ node | markdowned('description') }} - | {% endif %} - - small.text-muted - span(title="created {{ node._created | pretty_date }}") Updated {{ node._updated | pretty_date }} + small.text-muted + span(title="created {{ node._created | pretty_date }}") Updated {{ node._updated | pretty_date }} include ../_scripts From e56b3ec61feddd578e4f0b67eff164736763a488 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 04:27:24 +0200 Subject: [PATCH 116/148] Use Pillar's built-in markdown when editing projects/creating posts. --- src/templates/nodes/custom/post/create.pug | 55 ++++++++++------------ src/templates/projects/edit.pug | 53 ++++++++++----------- 2 files changed, 52 insertions(+), 56 deletions(-) diff --git a/src/templates/nodes/custom/post/create.pug b/src/templates/nodes/custom/post/create.pug index 6af4c7a1..83b3987d 100644 --- a/src/templates/nodes/custom/post/create.pug +++ b/src/templates/nodes/custom/post/create.pug @@ -122,41 +122,38 @@ script(type="text/javascript"). .toLowerCase(); }; - var convert = new Markdown.getSanitizingConverter().makeHtml; - /* Build the markdown preview when typing in textarea */ $(function() { + var $contentField = $('.form-group.description textarea'), + $contentPreview = $('
').insertAfter($contentField); - var $textarea = $('.form-group.content textarea'), - $loader = $('
').insertAfter($textarea), - $preview = $('
').insertAfter($loader); + function parseDescriptionContent(content) { - $loader.hide(); + $.ajax({ + url: "{{ url_for('nodes.preview_markdown')}}", + type: 'post', + data: {content: content}, + headers: {"X-CSRFToken": csrf_token}, + headers: {}, + dataType: 'json' + }) + .done(function (data) { + $contentPreview.html(data.content); + }) + .fail(function (err) { + toastr.error(xhrErrorResponseMessage(err), 'Parsing failed'); + }); + } - // Delay function to not start converting heavy posts immediately - var delay = (function(){ - var timer = 0; - return function(callback, ms){ - clearTimeout (timer); - timer = setTimeout(callback, ms); - }; - })(); + var options = { + callback: parseDescriptionContent, + wait: 750, + highlight: false, + allowSubmit: false, + captureLength: 2 + } - $textarea.keyup(function() { - /* If there's an iframe (YouTube embed), delay markdown convert 1.5s */ - if (/iframe/i.test($textarea.val())) { - $loader.show(); - - delay(function(){ - // Convert markdown - $preview.html(convert($textarea.val())); - $loader.hide(); - }, 1500 ); - } else { - // Convert markdown - $preview.html(convert($textarea.val())); - }; - }).trigger('keyup'); + $contentField.typeWatch(options); }); $(function() { diff --git a/src/templates/projects/edit.pug b/src/templates/projects/edit.pug index 3cb9fa56..6d6cabb5 100644 --- a/src/templates/projects/edit.pug +++ b/src/templates/projects/edit.pug @@ -122,7 +122,6 @@ script(type="text/javascript"). $('.project-mode-edit').displayAs('inline-block'); ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''}); - var convert = new Markdown.getSanitizingConverter().makeHtml; $('.button-save').on('click', function(e){ e.preventDefault(); @@ -136,36 +135,36 @@ script(type="text/javascript"). /* Build the markdown preview when typing in textarea */ $(function() { - var $textarea = $('.form-group.description textarea'), - $loader = $('
').insertAfter($textarea), - $preview = $('
').insertAfter($loader); + var $contentField = $('.form-group.description textarea'), + $contentPreview = $('
').insertAfter($contentField); - $loader.hide(); + function parseDescriptionContent(content) { - // Delay function to not start converting heavy posts immediately - var delay = (function(){ - var timer = 0; - return function(callback, ms){ - clearTimeout (timer); - timer = setTimeout(callback, ms); - }; - })(); + $.ajax({ + url: "{{ url_for('nodes.preview_markdown')}}", + type: 'post', + data: {content: content}, + headers: {"X-CSRFToken": csrf_token}, + headers: {}, + dataType: 'json' + }) + .done(function (data) { + $contentPreview.html(data.content); + }) + .fail(function (err) { + toastr.error(xhrErrorResponseMessage(err), 'Parsing failed'); + }); + } - $textarea.keyup(function() { - /* If there's an iframe (YouTube embed), delay markdown convert 1.5s */ - if (/iframe/i.test($textarea.val())) { - $loader.show(); + var options = { + callback: parseDescriptionContent, + wait: 750, + highlight: false, + allowSubmit: false, + captureLength: 2 + } - delay(function(){ - // Convert markdown - $preview.html(convert($textarea.val())); - $loader.hide(); - }, 1500 ); - } else { - // Convert markdown - $preview.html(convert($textarea.val())); - }; - }).trigger('keyup'); + $contentField.typeWatch(options); $('input, textarea').keypress(function () { // Unused: save status of the page as 'edited' From a5bc36b1cf2d0186245d86fb59d682acee28537a Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 04:28:11 +0200 Subject: [PATCH 117/148] Jumbotron overlay is now optional. Just add the jumbotron-overlay class, or jumbotron-overlay-gradient --- src/styles/components/_jumbotron.sass | 35 +++++++++++++------ src/templates/mixins/components.pug | 4 +-- .../nodes/custom/page/view_embed.pug | 2 +- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/styles/components/_jumbotron.sass b/src/styles/components/_jumbotron.sass index 5b364e1c..438809f3 100644 --- a/src/styles/components/_jumbotron.sass +++ b/src/styles/components/_jumbotron.sass @@ -7,22 +7,35 @@ margin-bottom: 0 padding-top: 10em padding-bottom: 10em + position: relative + + &:after + background-color: rgba(black, .6) + bottom: 0 + content: '' + display: none + left: 0 + position: absolute + right: 0 + top: 0 + visibility: hidden // Black-transparent gradient from left to right to better read the overlay text. &.jumbotron-overlay - position: relative - - &:after - background-image: linear-gradient(45deg, rgba(black, .5) 25%, transparent 50%) - bottom: 0 - content: '' - left: 0 - position: absolute - right: 0 - top: 0 - * z-index: 1 + &:after + display: block + visibility: visible + + &.jumbotron-overlay-gradient + * + z-index: 1 + &:after + background-color: transparent + background-image: linear-gradient(45deg, rgba(black, .5) 25%, transparent 50%) + display: block + visibility: visible h2, p text-shadow: 1px 1px rgba(black, .2), 1px 1px 25px rgba(black, .5) diff --git a/src/templates/mixins/components.pug b/src/templates/mixins/components.pug index cecdeecc..98ee8eab 100644 --- a/src/templates/mixins/components.pug +++ b/src/templates/mixins/components.pug @@ -6,7 +6,7 @@ // #} mixin jumbotron(title, text, image, url) if url - a.jumbotron.jumbotron-overlay.text-white( + a.jumbotron.text-white( style='background-image: url(' + image + ');', href=url)&attributes(attributes) .container @@ -19,7 +19,7 @@ mixin jumbotron(title, text, image, url) .lead =text else - .jumbotron.jumbotron-overlay.text-white(style='background-image: url(' + image + ');')&attributes(attributes) + .jumbotron.text-white(style='background-image: url(' + image + ');')&attributes(attributes) .container .row .col-md-9 diff --git a/src/templates/nodes/custom/page/view_embed.pug b/src/templates/nodes/custom/page/view_embed.pug index 0b55ef42..298a4ecd 100644 --- a/src/templates/nodes/custom/page/view_embed.pug +++ b/src/templates/nodes/custom/page/view_embed.pug @@ -14,7 +14,7 @@ include ../../../mixins/components .container.pb-5 .row .col-8.mx-auto - h2.pt-5.text-center {{node.name}} + h2.pt-5.pb-3.text-center {{node.name}} | {% if node.description %} .node-details-description From 6648f8d07427999d8174375f4a841629c52a56cf Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 05:02:16 +0200 Subject: [PATCH 118/148] Minor style adjustments --- src/styles/_project.sass | 4 ++-- src/styles/components/_jumbotron.sass | 2 +- src/styles/components/_navbar.sass | 3 --- src/templates/nodes/view_base.pug | 22 ++++++++++------------ src/templates/projects/edit.pug | 2 +- src/templates/projects/edit_layout.pug | 2 +- 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/styles/_project.sass b/src/styles/_project.sass index 8ff08ada..644f360a 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -1775,7 +1775,7 @@ a.learn-more box-shadow: 0 5px 35px rgba(black, .2) color: $color-text-dark-primary position: absolute - top: 0 + top: -$project_header-height left: 0 right: 0 width: 80% @@ -1803,7 +1803,7 @@ a.learn-more &.visible visibility: visible opacity: 1 - top: $project_header-height + top: 0 .overlay-container .title diff --git a/src/styles/components/_jumbotron.sass b/src/styles/components/_jumbotron.sass index 438809f3..7775bb8d 100644 --- a/src/styles/components/_jumbotron.sass +++ b/src/styles/components/_jumbotron.sass @@ -10,7 +10,7 @@ position: relative &:after - background-color: rgba(black, .6) + background-color: rgba(black, .5) bottom: 0 content: '' display: none diff --git a/src/styles/components/_navbar.sass b/src/styles/components/_navbar.sass index b21c57a7..a52df028 100644 --- a/src/styles/components/_navbar.sass +++ b/src/styles/components/_navbar.sass @@ -57,8 +57,6 @@ +position-center-translate .dropdown - min-width: 50px // navbar avatar size - .navbar-item &:hover box-shadow: none // Remove the blue underline usually on navbar, from dropdown items. @@ -195,7 +193,6 @@ $nav-secondary-bar-size: -2px &.nav-see-more color: $primary - font-size: $font-size-xxs i, span +active-gradient diff --git a/src/templates/nodes/view_base.pug b/src/templates/nodes/view_base.pug index 67535f96..63820389 100644 --- a/src/templates/nodes/view_base.pug +++ b/src/templates/nodes/view_base.pug @@ -27,10 +27,10 @@ | {# DETAILS #} - section.node-details-meta.px-4.py-2 + section.node-details-meta.pl-4.pr-2.py-2.border-bottom ul.list-unstyled.m-0 | {% if node.properties.license_type %} - li + li.px-2 a.node-details-license( href="https://creativecommons.org/licenses/", target="_blank", @@ -41,15 +41,15 @@ | {% endif %} | {% if node.has_method('PUT') and (node.properties.status != 'published') %} - li(class="status-{{ node.properties.status }}") + li.px-2(class="status-{{ node.properties.status }}") | {{ node.properties.status | undertitle }} | {% endif %} - li(title="Author") + li.px-2(title="Author") | {{ node.user.full_name }} | {{ node.user.badges.html|safe }} - li( + li.px-2( title="Created {{ node._created }} (updated {{ node._updated | pretty_date_time }})") | {{ node._created | pretty_date }} @@ -60,14 +60,12 @@ | Shared | {% endif %} - - - li.left-side + li.ml-auto | {% if node.file %} - li(title="File size") + li.px-2(title="File size") | {{ node.file.length | filesizeformat }} - li.js-type(title="File format") + li.px-2.js-type(title="File format") | {{ node.file.content_type }} | {% endif %} @@ -95,11 +93,11 @@ | {% endblock node_download %} | {% elif current_user.has_cap('can-renew-subscription') %} - a.btn.btn-success( + a.btn.btn-outline-primary( title="Renew your subscription to download", target="_blank", href="/renew") - i.pi-heart + i.pi-heart.pr-2 | Renew Subscription | {% elif current_user.is_authenticated %} diff --git a/src/templates/projects/edit.pug b/src/templates/projects/edit.pug index 6d6cabb5..032c9590 100644 --- a/src/templates/projects/edit.pug +++ b/src/templates/projects/edit.pug @@ -29,7 +29,7 @@ .container-fluid .row .col-md-12 - h5.pl-2.mb-0 Project Overview + h5.pl-2.mb-0.pt-3 Project Overview #node-edit-container form( diff --git a/src/templates/projects/edit_layout.pug b/src/templates/projects/edit_layout.pug index 41a79517..a2a63229 100644 --- a/src/templates/projects/edit_layout.pug +++ b/src/templates/projects/edit_layout.pug @@ -45,7 +45,7 @@ include ../mixins/components #project_nav #project_nav-container // TODO - make list a macro - #project_tree.edit.bg-white + #project_tree.edit.bg-light +nav-secondary()(class="nav-secondary-vertical") +nav-secondary-link( class="{% if title == 'edit' %}active{% endif %}", From 85e5cb4f71212383f4d4dc813b3dfdd6d07a1ed0 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 05:02:52 +0200 Subject: [PATCH 119/148] Projects: Only display category for public projects --- src/templates/projects/_macros.pug | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/templates/projects/_macros.pug b/src/templates/projects/_macros.pug index 61d1d702..bd879bb1 100644 --- a/src/templates/projects/_macros.pug +++ b/src/templates/projects/_macros.pug @@ -14,11 +14,13 @@ include ../mixins/components +nav-secondary() | {% if project.url != 'blender-cloud' %} + | {% if not project.is_private %} li.text-capitalize a.nav-link.text-muted.px-0(href="{{ category_url }}") span {{ project.category }} li.px-1 i.pi-angle-right + | {% endif %} +nav-secondary-link( class="px-1 font-weight-bold", From 842ddaeab091ee7ef2aa63d991800a745a86565f Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 06:29:06 +0200 Subject: [PATCH 120/148] Assets: Display similar assets based on tags Experimental. --- src/styles/components/_card.sass | 1 + src/templates/nodes/view_base.pug | 31 +++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/styles/components/_card.sass b/src/styles/components/_card.sass index 1a219793..1902a78b 100644 --- a/src/styles/components/_card.sass +++ b/src/styles/components/_card.sass @@ -43,6 +43,7 @@ @extend .w-100 @extend .flex-row flex: initial + flex-wrap: wrap max-width: 100% .card-img-top diff --git a/src/templates/nodes/view_base.pug b/src/templates/nodes/view_base.pug index 63820389..08770d97 100644 --- a/src/templates/nodes/view_base.pug +++ b/src/templates/nodes/view_base.pug @@ -25,7 +25,6 @@ | {{ node | markdowned('description') }} | {% endif %} - | {# DETAILS #} section.node-details-meta.pl-4.pr-2.py-2.border-bottom ul.list-unstyled.m-0 @@ -114,12 +113,32 @@ | {% endif %} | {% endblock node_details %} +.container-fluid + .row + | {% block node_comments %} + .col-md-8.col-sm-12 + #comments-embed + .comments-list-loading + i.pi-spin + | {% endblock node_comments %} - | {% block node_comments %} - #comments-embed - .comments-list-loading - i.pi-spin - | {% endblock node_comments %} + | {% if node.properties.tags %} + .col-md-4.d-none.d-lg-block + script(src="{{ url_for('static_cloud', filename='assets/js/tagged_assets.min.js') }}") + script. + $(function() { + $('.js-asset-list').loadTaggedAssets(4, 0); + }) + .tagged-similar.p-3 + h6 Similar assets + | {% for tag in node.properties.tags[:3] %} + | {% if loop.index < 4 %} + .card-deck.card-padless.card-deck-vertical.mx-0( + class="js-asset-list", + data-asset-tag="{{ tag }}") + | {% endif %} + | {% endfor %} + | {% endif %} | {% include 'nodes/custom/_scripts.html' %} From 77e3c476f0873de06879e1b811ce98c2add2cc99 Mon Sep 17 00:00:00 2001 From: Francesco Siddi Date: Sun, 16 Sep 2018 13:04:12 +0200 Subject: [PATCH 121/148] Move node hooks into own file --- pillar/api/nodes/__init__.py | 355 ++--------------------------------- pillar/api/nodes/hooks.py | 325 ++++++++++++++++++++++++++++++++ 2 files changed, 344 insertions(+), 336 deletions(-) create mode 100644 pillar/api/nodes/hooks.py diff --git a/pillar/api/nodes/__init__.py b/pillar/api/nodes/__init__.py index 6e89a7e4..a5dfacc5 100644 --- a/pillar/api/nodes/__init__.py +++ b/pillar/api/nodes/__init__.py @@ -1,18 +1,12 @@ import base64 -import functools import logging -import typing -import urllib.parse import pymongo.errors import werkzeug.exceptions as wz_exceptions -from bson import ObjectId from flask import current_app, Blueprint, request -import pillar.markdown -from pillar.api.activities import activity_subscribe, activity_object_add -from pillar.api.node_types import PILLAR_NAMED_NODE_TYPES -from pillar.api.file_storage_backends.gcs import update_file_name +from pillar.api.nodes import hooks +from pillar.api.nodes.hooks import short_link_info from pillar.api.utils import str2id, jsonify from pillar.api.utils.authorization import check_permissions, require_login @@ -21,40 +15,6 @@ blueprint = Blueprint('nodes_api', __name__) ROLES_FOR_SHARING = {'subscriber', 'demo'} -def only_for_node_type_decorator(*required_node_type_names): - """Returns a decorator that checks its first argument's node type. - - If the node type is not of the required node type, returns None, - otherwise calls the wrapped function. - - >>> deco = only_for_node_type_decorator('comment') - >>> @deco - ... def handle_comment(node): pass - - >>> deco = only_for_node_type_decorator('comment', 'post') - >>> @deco - ... def handle_comment_or_post(node): pass - - """ - - # Convert to a set for efficient 'x in required_node_type_names' queries. - required_node_type_names = set(required_node_type_names) - - def only_for_node_type(wrapped): - @functools.wraps(wrapped) - def wrapper(node, *args, **kwargs): - if node.get('node_type') not in required_node_type_names: - return - - return wrapped(node, *args, **kwargs) - - return wrapper - - only_for_node_type.__doc__ = "Decorator, immediately returns when " \ - "the first argument is not of type %s." % required_node_type_names - return only_for_node_type - - @blueprint.route('//share', methods=['GET', 'POST']) @require_login(require_roles=ROLES_FOR_SHARING) def share_node(node_id): @@ -226,283 +186,6 @@ def create_short_code(node) -> str: return short_code -def short_link_info(short_code): - """Returns the short link info in a dict.""" - - short_link = urllib.parse.urljoin( - current_app.config['SHORT_LINK_BASE_URL'], short_code) - - return { - 'short_code': short_code, - 'short_link': short_link, - } - - -def before_replacing_node(item, original): - check_permissions('nodes', original, 'PUT') - update_file_name(item) - - -def after_replacing_node(item, original): - """Push an update to the Algolia index when a node item is updated. If the - project is private, prevent public indexing. - """ - - from pillar.celery import search_index_tasks as index - - projects_collection = current_app.data.driver.db['projects'] - project = projects_collection.find_one({'_id': item['project']}) - if project.get('is_private', False): - # Skip index updating and return - return - - status = item['properties'].get('status', 'unpublished') - node_id = str(item['_id']) - - if status == 'published': - index.node_save.delay(node_id) - else: - index.node_delete.delay(node_id) - - -def before_inserting_nodes(items): - """Before inserting a node in the collection we check if the user is allowed - and we append the project id to it. - """ - from pillar.auth import current_user - - nodes_collection = current_app.data.driver.db['nodes'] - - def find_parent_project(node): - """Recursive function that finds the ultimate parent of a node.""" - if node and 'parent' in node: - parent = nodes_collection.find_one({'_id': node['parent']}) - return find_parent_project(parent) - if node: - return node - else: - return None - - for item in items: - check_permissions('nodes', item, 'POST') - if 'parent' in item and 'project' not in item: - parent = nodes_collection.find_one({'_id': item['parent']}) - project = find_parent_project(parent) - if project: - item['project'] = project['_id'] - - # Default the 'user' property to the current user. - item.setdefault('user', current_user.user_id) - - -def after_inserting_nodes(items): - for item in items: - # Skip subscriptions for first level items (since the context is not a - # node, but a project). - # TODO: support should be added for mixed context - if 'parent' not in item: - return - context_object_id = item['parent'] - if item['node_type'] == 'comment': - nodes_collection = current_app.data.driver.db['nodes'] - parent = nodes_collection.find_one({'_id': item['parent']}) - # Always subscribe to the parent node - activity_subscribe(item['user'], 'node', item['parent']) - if parent['node_type'] == 'comment': - # If the parent is a comment, we provide its own parent as - # context. We do this in order to point the user to an asset - # or group when viewing the notification. - verb = 'replied' - context_object_id = parent['parent'] - # Subscribe to the parent of the parent comment (post or group) - activity_subscribe(item['user'], 'node', parent['parent']) - else: - activity_subscribe(item['user'], 'node', item['_id']) - verb = 'commented' - elif item['node_type'] in PILLAR_NAMED_NODE_TYPES: - verb = 'posted' - activity_subscribe(item['user'], 'node', item['_id']) - else: - # Don't automatically create activities for non-Pillar node types, - # as we don't know what would be a suitable verb (among other things). - continue - - activity_object_add( - item['user'], - verb, - 'node', - item['_id'], - 'node', - context_object_id - ) - - -def deduct_content_type(node_doc, original=None): - """Deduct the content type from the attached file, if any.""" - - if node_doc['node_type'] != 'asset': - log.debug('deduct_content_type: called on node type %r, ignoring', node_doc['node_type']) - return - - node_id = node_doc.get('_id') - try: - file_id = ObjectId(node_doc['properties']['file']) - except KeyError: - if node_id is None: - # Creation of a file-less node is allowed, but updates aren't. - return - log.warning('deduct_content_type: Asset without properties.file, rejecting.') - raise wz_exceptions.UnprocessableEntity('Missing file property for asset node') - - files = current_app.data.driver.db['files'] - file_doc = files.find_one({'_id': file_id}, - {'content_type': 1}) - if not file_doc: - log.warning('deduct_content_type: Node %s refers to non-existing file %s, rejecting.', - node_id, file_id) - raise wz_exceptions.UnprocessableEntity('File property refers to non-existing file') - - # Guess the node content type from the file content type - file_type = file_doc['content_type'] - if file_type.startswith('video/'): - content_type = 'video' - elif file_type.startswith('image/'): - content_type = 'image' - else: - content_type = 'file' - - node_doc['properties']['content_type'] = content_type - - -def nodes_deduct_content_type(nodes): - for node in nodes: - deduct_content_type(node) - - -def before_returning_node(node): - # Run validation process, since GET on nodes entry point is public - check_permissions('nodes', node, 'GET', append_allowed_methods=True) - - # Embed short_link_info if the node has a short_code. - short_code = node.get('short_code') - if short_code: - node['short_link'] = short_link_info(short_code)['short_link'] - - -def before_returning_nodes(nodes): - for node in nodes['_items']: - before_returning_node(node) - - -def node_set_default_picture(node, original=None): - """Uses the image of an image asset or colour map of texture node as picture.""" - - if node.get('picture'): - log.debug('Node %s already has a picture, not overriding', node.get('_id')) - return - - node_type = node.get('node_type') - props = node.get('properties', {}) - content = props.get('content_type') - - if node_type == 'asset' and content == 'image': - image_file_id = props.get('file') - elif node_type == 'texture': - # Find the colour map, defaulting to the first image map available. - image_file_id = None - for image in props.get('files', []): - if image_file_id is None or image.get('map_type') == 'color': - image_file_id = image.get('file') - else: - log.debug('Not setting default picture on node type %s content type %s', - node_type, content) - return - - if image_file_id is None: - log.debug('Nothing to set the picture to.') - return - - log.debug('Setting default picture for node %s to %s', node.get('_id'), image_file_id) - node['picture'] = image_file_id - - -def nodes_set_default_picture(nodes): - for node in nodes: - node_set_default_picture(node) - - -def before_deleting_node(node: dict): - check_permissions('nodes', node, 'DELETE') - - -def after_deleting_node(item): - from pillar.celery import search_index_tasks as index - index.node_delete.delay(str(item['_id'])) - - -only_for_textures = only_for_node_type_decorator('texture') - - -@only_for_textures -def texture_sort_files(node, original=None): - """Sort files alphabetically by map type, with colour map first.""" - - try: - files = node['properties']['files'] - except KeyError: - return - - # Sort the map types alphabetically, ensuring 'color' comes first. - as_dict = {f['map_type']: f for f in files} - types = sorted(as_dict.keys(), key=lambda k: '\0' if k == 'color' else k) - node['properties']['files'] = [as_dict[map_type] for map_type in types] - - -def textures_sort_files(nodes): - for node in nodes: - texture_sort_files(node) - - -def parse_markdown(node, original=None): - import copy - - projects_collection = current_app.data.driver.db['projects'] - project = projects_collection.find_one({'_id': node['project']}, {'node_types': 1}) - # Query node type directly using the key - node_type = next(nt for nt in project['node_types'] - if nt['name'] == node['node_type']) - - # Create a copy to not overwrite the actual schema. - schema = copy.deepcopy(current_app.config['DOMAIN']['nodes']['schema']) - schema['properties'] = node_type['dyn_schema'] - - def find_markdown_fields(schema, node): - """Find and process all makrdown validated fields.""" - for k, v in schema.items(): - if not isinstance(v, dict): - continue - - if v.get('validator') == 'markdown': - # If there is a match with the validator: markdown pair, assign the sibling - # property (following the naming convention __html) - # the processed value. - if k in node: - html = pillar.markdown.markdown(node[k]) - field_name = pillar.markdown.cache_field_name(k) - node[field_name] = html - if isinstance(node, dict) and k in node: - find_markdown_fields(v, node[k]) - - find_markdown_fields(schema, node) - - return 'ok' - - -def parse_markdowns(items): - for item in items: - parse_markdown(item) - - def setup_app(app, url_prefix): global _tagged @@ -512,26 +195,26 @@ def setup_app(app, url_prefix): from . import patch patch.setup_app(app, url_prefix=url_prefix) - app.on_fetched_item_nodes += before_returning_node - app.on_fetched_resource_nodes += before_returning_nodes + app.on_fetched_item_nodes += hooks.before_returning_node + app.on_fetched_resource_nodes += hooks.before_returning_nodes - app.on_replace_nodes += before_replacing_node - app.on_replace_nodes += parse_markdown - app.on_replace_nodes += texture_sort_files - app.on_replace_nodes += deduct_content_type - app.on_replace_nodes += node_set_default_picture - app.on_replaced_nodes += after_replacing_node + app.on_replace_nodes += hooks.before_replacing_node + app.on_replace_nodes += hooks.parse_markdown + app.on_replace_nodes += hooks.texture_sort_files + app.on_replace_nodes += hooks.deduct_content_type + app.on_replace_nodes += hooks.node_set_default_picture + app.on_replaced_nodes += hooks.after_replacing_node - app.on_insert_nodes += before_inserting_nodes - app.on_insert_nodes += parse_markdowns - app.on_insert_nodes += nodes_deduct_content_type - app.on_insert_nodes += nodes_set_default_picture - app.on_insert_nodes += textures_sort_files - app.on_inserted_nodes += after_inserting_nodes + app.on_insert_nodes += hooks.before_inserting_nodes + app.on_insert_nodes += hooks.parse_markdowns + app.on_insert_nodes += hooks.nodes_deduct_content_type + app.on_insert_nodes += hooks.nodes_set_default_picture + app.on_insert_nodes += hooks.textures_sort_files + app.on_inserted_nodes += hooks.after_inserting_nodes - app.on_update_nodes += texture_sort_files + app.on_update_nodes += hooks.texture_sort_files - app.on_delete_item_nodes += before_deleting_node - app.on_deleted_item_nodes += after_deleting_node + app.on_delete_item_nodes += hooks.before_deleting_node + app.on_deleted_item_nodes += hooks.after_deleting_node app.register_api_blueprint(blueprint, url_prefix=url_prefix) diff --git a/pillar/api/nodes/hooks.py b/pillar/api/nodes/hooks.py new file mode 100644 index 00000000..d179fa81 --- /dev/null +++ b/pillar/api/nodes/hooks.py @@ -0,0 +1,325 @@ +import functools +import logging +import urllib.parse +from bson import ObjectId +from flask import current_app +from werkzeug import exceptions as wz_exceptions + +import pillar.markdown +from pillar.api.activities import activity_subscribe, activity_object_add +from pillar.api.file_storage_backends.gcs import update_file_name +from pillar.api.node_types import PILLAR_NAMED_NODE_TYPES +from pillar.api.utils.authorization import check_permissions + +log = logging.getLogger(__name__) + + +def before_returning_node(node): + # Run validation process, since GET on nodes entry point is public + check_permissions('nodes', node, 'GET', append_allowed_methods=True) + + # Embed short_link_info if the node has a short_code. + short_code = node.get('short_code') + if short_code: + node['short_link'] = short_link_info(short_code)['short_link'] + + +def before_returning_nodes(nodes): + for node in nodes['_items']: + before_returning_node(node) + + +def only_for_node_type_decorator(*required_node_type_names): + """Returns a decorator that checks its first argument's node type. + + If the node type is not of the required node type, returns None, + otherwise calls the wrapped function. + + >>> deco = only_for_node_type_decorator('comment') + >>> @deco + ... def handle_comment(node): pass + + >>> deco = only_for_node_type_decorator('comment', 'post') + >>> @deco + ... def handle_comment_or_post(node): pass + + """ + + # Convert to a set for efficient 'x in required_node_type_names' queries. + required_node_type_names = set(required_node_type_names) + + def only_for_node_type(wrapped): + @functools.wraps(wrapped) + def wrapper(node, *args, **kwargs): + if node.get('node_type') not in required_node_type_names: + return + + return wrapped(node, *args, **kwargs) + + return wrapper + + only_for_node_type.__doc__ = "Decorator, immediately returns when " \ + "the first argument is not of type %s." % required_node_type_names + return only_for_node_type + + +def before_replacing_node(item, original): + check_permissions('nodes', original, 'PUT') + update_file_name(item) + + +def after_replacing_node(item, original): + """Push an update to the Algolia index when a node item is updated. If the + project is private, prevent public indexing. + """ + + from pillar.celery import search_index_tasks as index + + projects_collection = current_app.data.driver.db['projects'] + project = projects_collection.find_one({'_id': item['project']}) + if project.get('is_private', False): + # Skip index updating and return + return + + status = item['properties'].get('status', 'unpublished') + node_id = str(item['_id']) + + if status == 'published': + index.node_save.delay(node_id) + else: + index.node_delete.delay(node_id) + + +def before_inserting_nodes(items): + """Before inserting a node in the collection we check if the user is allowed + and we append the project id to it. + """ + from pillar.auth import current_user + + nodes_collection = current_app.data.driver.db['nodes'] + + def find_parent_project(node): + """Recursive function that finds the ultimate parent of a node.""" + if node and 'parent' in node: + parent = nodes_collection.find_one({'_id': node['parent']}) + return find_parent_project(parent) + if node: + return node + else: + return None + + for item in items: + check_permissions('nodes', item, 'POST') + if 'parent' in item and 'project' not in item: + parent = nodes_collection.find_one({'_id': item['parent']}) + project = find_parent_project(parent) + if project: + item['project'] = project['_id'] + + # Default the 'user' property to the current user. + item.setdefault('user', current_user.user_id) + + +def after_inserting_nodes(items): + for item in items: + # Skip subscriptions for first level items (since the context is not a + # node, but a project). + # TODO: support should be added for mixed context + if 'parent' not in item: + return + context_object_id = item['parent'] + if item['node_type'] == 'comment': + nodes_collection = current_app.data.driver.db['nodes'] + parent = nodes_collection.find_one({'_id': item['parent']}) + # Always subscribe to the parent node + activity_subscribe(item['user'], 'node', item['parent']) + if parent['node_type'] == 'comment': + # If the parent is a comment, we provide its own parent as + # context. We do this in order to point the user to an asset + # or group when viewing the notification. + verb = 'replied' + context_object_id = parent['parent'] + # Subscribe to the parent of the parent comment (post or group) + activity_subscribe(item['user'], 'node', parent['parent']) + else: + activity_subscribe(item['user'], 'node', item['_id']) + verb = 'commented' + elif item['node_type'] in PILLAR_NAMED_NODE_TYPES: + verb = 'posted' + activity_subscribe(item['user'], 'node', item['_id']) + else: + # Don't automatically create activities for non-Pillar node types, + # as we don't know what would be a suitable verb (among other things). + continue + + activity_object_add( + item['user'], + verb, + 'node', + item['_id'], + 'node', + context_object_id + ) + + +def deduct_content_type(node_doc, original=None): + """Deduct the content type from the attached file, if any.""" + + if node_doc['node_type'] != 'asset': + log.debug('deduct_content_type: called on node type %r, ignoring', node_doc['node_type']) + return + + node_id = node_doc.get('_id') + try: + file_id = ObjectId(node_doc['properties']['file']) + except KeyError: + if node_id is None: + # Creation of a file-less node is allowed, but updates aren't. + return + log.warning('deduct_content_type: Asset without properties.file, rejecting.') + raise wz_exceptions.UnprocessableEntity('Missing file property for asset node') + + files = current_app.data.driver.db['files'] + file_doc = files.find_one({'_id': file_id}, + {'content_type': 1}) + if not file_doc: + log.warning('deduct_content_type: Node %s refers to non-existing file %s, rejecting.', + node_id, file_id) + raise wz_exceptions.UnprocessableEntity('File property refers to non-existing file') + + # Guess the node content type from the file content type + file_type = file_doc['content_type'] + if file_type.startswith('video/'): + content_type = 'video' + elif file_type.startswith('image/'): + content_type = 'image' + else: + content_type = 'file' + + node_doc['properties']['content_type'] = content_type + + +def nodes_deduct_content_type(nodes): + for node in nodes: + deduct_content_type(node) + + +def node_set_default_picture(node, original=None): + """Uses the image of an image asset or colour map of texture node as picture.""" + + if node.get('picture'): + log.debug('Node %s already has a picture, not overriding', node.get('_id')) + return + + node_type = node.get('node_type') + props = node.get('properties', {}) + content = props.get('content_type') + + if node_type == 'asset' and content == 'image': + image_file_id = props.get('file') + elif node_type == 'texture': + # Find the colour map, defaulting to the first image map available. + image_file_id = None + for image in props.get('files', []): + if image_file_id is None or image.get('map_type') == 'color': + image_file_id = image.get('file') + else: + log.debug('Not setting default picture on node type %s content type %s', + node_type, content) + return + + if image_file_id is None: + log.debug('Nothing to set the picture to.') + return + + log.debug('Setting default picture for node %s to %s', node.get('_id'), image_file_id) + node['picture'] = image_file_id + + +def nodes_set_default_picture(nodes): + for node in nodes: + node_set_default_picture(node) + + +def before_deleting_node(node: dict): + check_permissions('nodes', node, 'DELETE') + + +def after_deleting_node(item): + from pillar.celery import search_index_tasks as index + index.node_delete.delay(str(item['_id'])) + + +only_for_textures = only_for_node_type_decorator('texture') + + +@only_for_textures +def texture_sort_files(node, original=None): + """Sort files alphabetically by map type, with colour map first.""" + + try: + files = node['properties']['files'] + except KeyError: + return + + # Sort the map types alphabetically, ensuring 'color' comes first. + as_dict = {f['map_type']: f for f in files} + types = sorted(as_dict.keys(), key=lambda k: '\0' if k == 'color' else k) + node['properties']['files'] = [as_dict[map_type] for map_type in types] + + +def textures_sort_files(nodes): + for node in nodes: + texture_sort_files(node) + + +def parse_markdown(node, original=None): + import copy + + projects_collection = current_app.data.driver.db['projects'] + project = projects_collection.find_one({'_id': node['project']}, {'node_types': 1}) + # Query node type directly using the key + node_type = next(nt for nt in project['node_types'] + if nt['name'] == node['node_type']) + + # Create a copy to not overwrite the actual schema. + schema = copy.deepcopy(current_app.config['DOMAIN']['nodes']['schema']) + schema['properties'] = node_type['dyn_schema'] + + def find_markdown_fields(schema, node): + """Find and process all makrdown validated fields.""" + for k, v in schema.items(): + if not isinstance(v, dict): + continue + + if v.get('validator') == 'markdown': + # If there is a match with the validator: markdown pair, assign the sibling + # property (following the naming convention __html) + # the processed value. + if k in node: + html = pillar.markdown.markdown(node[k]) + field_name = pillar.markdown.cache_field_name(k) + node[field_name] = html + if isinstance(node, dict) and k in node: + find_markdown_fields(v, node[k]) + + find_markdown_fields(schema, node) + + return 'ok' + + +def parse_markdowns(items): + for item in items: + parse_markdown(item) + + +def short_link_info(short_code): + """Returns the short link info in a dict.""" + + short_link = urllib.parse.urljoin( + current_app.config['SHORT_LINK_BASE_URL'], short_code) + + return { + 'short_code': short_code, + 'short_link': short_link, + } From 8753a12dee07cee5f296460c49888ef527d560ee Mon Sep 17 00:00:00 2001 From: Francesco Siddi Date: Sun, 16 Sep 2018 22:04:22 +0200 Subject: [PATCH 122/148] Tweak unit test to support new embed code --- tests/test_shortcodes.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/test_shortcodes.py b/tests/test_shortcodes.py index 2e90cf28..d402ae7a 100644 --- a/tests/test_shortcodes.py +++ b/tests/test_shortcodes.py @@ -58,9 +58,11 @@ class YouTubeTest(AbstractPillarTest): from pillar.shortcodes import render self.assertEqual( - '', + 'allow="autoplay; encrypted-media" allowfullscreen>' + '
', render('{youtube ABCDEF}') ) @@ -68,9 +70,11 @@ class YouTubeTest(AbstractPillarTest): from pillar.shortcodes import render self.assertEqual( - '', + 'allow="autoplay; encrypted-media" allowfullscreen>' + '
', render('{youtube http://youtube.com/embed/ABCDEF}') ) @@ -78,9 +82,11 @@ class YouTubeTest(AbstractPillarTest): from pillar.shortcodes import render self.assertEqual( - '', + 'allow="autoplay; encrypted-media" allowfullscreen>' + '
', render('{youtube https://youtu.be/NwVGvcIrNWA}') ) @@ -88,9 +94,11 @@ class YouTubeTest(AbstractPillarTest): from pillar.shortcodes import render self.assertEqual( - '', + 'allow="autoplay; encrypted-media" allowfullscreen>' + '
', render('{youtube "https://www.youtube.com/watch?v=NwVGvcIrNWA"}') ) @@ -98,9 +106,11 @@ class YouTubeTest(AbstractPillarTest): from pillar.shortcodes import render self.assertEqual( - '', + 'allow="autoplay; encrypted-media" allowfullscreen>' + '', render('{youtube "https://www.youtube.com/watch?v=NwVGvcIrNWA" width=5 height="3"}') ) From def52944bf915be1426304a27aad86c07d699427 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Sun, 16 Sep 2018 23:55:06 +0200 Subject: [PATCH 123/148] CSS tweaks for embeds, videos and iframe --- src/styles/_utils.sass | 47 ++++++++------------------- src/styles/components/_jumbotron.sass | 5 ++- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index 790ab1f4..191a0f63 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -130,7 +130,6 @@ transform: translate(-50%, -50%) =input-generic - // padding: 5px 5px 5px 0 color: $color-text-dark background-color: transparent @@ -361,16 +360,15 @@ =node-details-description +clearfix - color: darken($color-text-dark, 5%) - font: - weight: 300 - size: 1.2em - + color: $color-text + font-size: 1.33em word-break: break-word +media-xs font-size: 1.1em + /* Style links without a class. Usually regular + * links in a comment or node description. */ a:not([class]) color: $color-text-dark-primary text-decoration: underline @@ -383,11 +381,6 @@ line-height: 1.5em word-wrap: break-word - h1, h2, h3, h4, h5, h6 - padding: - top: 20px - right: 20px - blockquote background-color: lighten($color-background-light, 5%) box-shadow: inset 5px 0 0 $color-background @@ -408,10 +401,10 @@ img, p img, ul li img + @extend .d-block + @extend .mx-auto + @extend .my-3 max-width: 100% - padding: - bottom: 25px - top: 25px &.emoji display: inline-block @@ -424,25 +417,13 @@ font-size: 1.5em /* e.g. YouTube embed */ - iframe - height: auto - margin: 15px auto + iframe, video max-width: 100% - min-height: 500px - width: 100% + @extend .mx-auto - +media-sm - iframe - min-height: 314px - +media-xs - iframe - min-height: 314px - - iframe[src^="https://www.youtube"] - +media-xs - iframe - min-height: 420px - min-height: 500px + .embed-responsive, + video + @extend .my-3 iframe[src^="https://w.soundcloud"] min-height: auto @@ -570,9 +551,7 @@ /* Bootstrap's img-responsive class */ =img-responsive - display: block - max-width: 100% - height: auto + @extend .img-fluid /* Set the color for a specified property * 1: $property: e.g. background-color diff --git a/src/styles/components/_jumbotron.sass b/src/styles/components/_jumbotron.sass index 7775bb8d..4c84a02a 100644 --- a/src/styles/components/_jumbotron.sass +++ b/src/styles/components/_jumbotron.sass @@ -3,7 +3,10 @@ @extend .d-flex @extend .mb-0 @extend .rounded-0 - background-size: cover + background: + position: center + repeat: no-repeat + size: cover margin-bottom: 0 padding-top: 10em padding-bottom: 10em From 567247f3fd3798c26854e15f41d942e137eee0ad Mon Sep 17 00:00:00 2001 From: Francesco Siddi Date: Mon, 17 Sep 2018 09:09:27 +0200 Subject: [PATCH 124/148] Rename hooks.py to eve_hooks.py Follow naming convention started in Attract and Flamenco. --- pillar/api/nodes/__init__.py | 39 ++++++++++----------- pillar/api/nodes/{hooks.py => eve_hooks.py} | 0 2 files changed, 19 insertions(+), 20 deletions(-) rename pillar/api/nodes/{hooks.py => eve_hooks.py} (100%) diff --git a/pillar/api/nodes/__init__.py b/pillar/api/nodes/__init__.py index a5dfacc5..67cf5d0a 100644 --- a/pillar/api/nodes/__init__.py +++ b/pillar/api/nodes/__init__.py @@ -5,8 +5,7 @@ import pymongo.errors import werkzeug.exceptions as wz_exceptions from flask import current_app, Blueprint, request -from pillar.api.nodes import hooks -from pillar.api.nodes.hooks import short_link_info +from pillar.api.nodes import eve_hooks from pillar.api.utils import str2id, jsonify from pillar.api.utils.authorization import check_permissions, require_login @@ -47,7 +46,7 @@ def share_node(node_id): else: return '', 204 - return jsonify(short_link_info(short_code), status=status) + return jsonify(eve_hooks.short_link_info(short_code), status=status) @blueprint.route('/tagged/') @@ -195,26 +194,26 @@ def setup_app(app, url_prefix): from . import patch patch.setup_app(app, url_prefix=url_prefix) - app.on_fetched_item_nodes += hooks.before_returning_node - app.on_fetched_resource_nodes += hooks.before_returning_nodes + app.on_fetched_item_nodes += eve_hooks.before_returning_node + app.on_fetched_resource_nodes += eve_hooks.before_returning_nodes - app.on_replace_nodes += hooks.before_replacing_node - app.on_replace_nodes += hooks.parse_markdown - app.on_replace_nodes += hooks.texture_sort_files - app.on_replace_nodes += hooks.deduct_content_type - app.on_replace_nodes += hooks.node_set_default_picture - app.on_replaced_nodes += hooks.after_replacing_node + app.on_replace_nodes += eve_hooks.before_replacing_node + app.on_replace_nodes += eve_hooks.parse_markdown + app.on_replace_nodes += eve_hooks.texture_sort_files + app.on_replace_nodes += eve_hooks.deduct_content_type + app.on_replace_nodes += eve_hooks.node_set_default_picture + app.on_replaced_nodes += eve_hooks.after_replacing_node - app.on_insert_nodes += hooks.before_inserting_nodes - app.on_insert_nodes += hooks.parse_markdowns - app.on_insert_nodes += hooks.nodes_deduct_content_type - app.on_insert_nodes += hooks.nodes_set_default_picture - app.on_insert_nodes += hooks.textures_sort_files - app.on_inserted_nodes += hooks.after_inserting_nodes + app.on_insert_nodes += eve_hooks.before_inserting_nodes + app.on_insert_nodes += eve_hooks.parse_markdowns + app.on_insert_nodes += eve_hooks.nodes_deduct_content_type + app.on_insert_nodes += eve_hooks.nodes_set_default_picture + app.on_insert_nodes += eve_hooks.textures_sort_files + app.on_inserted_nodes += eve_hooks.after_inserting_nodes - app.on_update_nodes += hooks.texture_sort_files + app.on_update_nodes += eve_hooks.texture_sort_files - app.on_delete_item_nodes += hooks.before_deleting_node - app.on_deleted_item_nodes += hooks.after_deleting_node + app.on_delete_item_nodes += eve_hooks.before_deleting_node + app.on_deleted_item_nodes += eve_hooks.after_deleting_node app.register_api_blueprint(blueprint, url_prefix=url_prefix) diff --git a/pillar/api/nodes/hooks.py b/pillar/api/nodes/eve_hooks.py similarity index 100% rename from pillar/api/nodes/hooks.py rename to pillar/api/nodes/eve_hooks.py From 2935b442d8cc6d675da05b35942b943d6bbd8dab Mon Sep 17 00:00:00 2001 From: Francesco Siddi Date: Mon, 17 Sep 2018 09:14:11 +0200 Subject: [PATCH 125/148] Remove outdated remarkdown_comments management command --- pillar/cli/maintenance.py | 44 --------------------------------------- 1 file changed, 44 deletions(-) diff --git a/pillar/cli/maintenance.py b/pillar/cli/maintenance.py index 7e01b19e..a88a489e 100644 --- a/pillar/cli/maintenance.py +++ b/pillar/cli/maintenance.py @@ -559,50 +559,6 @@ def replace_pillar_node_type_schemas(project_url=None, all_projects=False, missi projects_changed, projects_seen) -@manager_maintenance.command -def remarkdown_comments(): - """Retranslates all Markdown to HTML for all comment nodes. - """ - - from pillar.api.nodes import convert_markdown - - nodes_collection = current_app.db()['nodes'] - comments = nodes_collection.find({'node_type': 'comment'}, - projection={'properties.content': 1, - 'node_type': 1}) - - updated = identical = skipped = errors = 0 - for node in comments: - convert_markdown(node) - node_id = node['_id'] - - try: - content_html = node['properties']['content_html'] - except KeyError: - log.warning('Node %s has no content_html', node_id) - skipped += 1 - continue - - result = nodes_collection.update_one( - {'_id': node_id}, - {'$set': {'properties.content_html': content_html}} - ) - if result.matched_count != 1: - log.error('Unable to update node %s', node_id) - errors += 1 - continue - - if result.modified_count: - updated += 1 - else: - identical += 1 - - log.info('updated : %i', updated) - log.info('identical: %i', identical) - log.info('skipped : %i', skipped) - log.info('errors : %i', errors) - - @manager_maintenance.option('-p', '--project', dest='proj_url', nargs='?', help='Project URL') @manager_maintenance.option('-a', '--all', dest='all_projects', action='store_true', default=False, From d4fd6b5cda51cfa9746a297c0ab98395e74217be Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 12:52:35 +0200 Subject: [PATCH 126/148] Asset Listing: display author name (when available) --- src/templates/_macros/_asset_list_item.pug | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/templates/_macros/_asset_list_item.pug b/src/templates/_macros/_asset_list_item.pug index a657172b..607cf99e 100644 --- a/src/templates/_macros/_asset_list_item.pug +++ b/src/templates/_macros/_asset_list_item.pug @@ -19,8 +19,9 @@ a.card.asset.card-image-fade.pr-0.mx-0.mb-2( .card-title.mb-1.font-weight-bold | {{ asset.name }} - ul.card-text.list-unstyled.d-flex.text-black-50.mt-auto - li.pr-2 {{ node_type | undertitle }} + ul.card-text.list-unstyled.d-flex.text-black-50.mt-auto.mb-0 + li.pr-2.font-weight-bold {{ node_type | undertitle }} + li.pr-2 {{ asset.user.full_name }} li {{ asset._created | pretty_date }} | {% if asset.properties.content_type == 'video' %} From 04e51a9d3ffc6531cdb657719e21672176361efb Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 12:53:25 +0200 Subject: [PATCH 127/148] CSS: Break to large size a bit earlier --- src/styles/_config.sass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/_config.sass b/src/styles/_config.sass index fb98d5da..6548955e 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -160,4 +160,4 @@ $nav-link-height: 37px $navbar-padding-x: 0 $navbar-padding-y: 0 -$grid-breakpoints: (xs: 0,sm: 576px,md: 768px,lg: 1100px,xl: 1500px, xxl: 1800px) +$grid-breakpoints: (xs: 0,sm: 576px,md: 768px,lg: 1060px,xl: 1500px, xxl: 1800px) From 02a7014bf497c5b04b352b6795362a86d774f59f Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 12:54:07 +0200 Subject: [PATCH 128/148] Cleanup and title-underline utility --- src/styles/_utils.sass | 16 +++++++++++++++- src/templates/_macros/_add_new_menu.pug | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index 191a0f63..aa2fc22f 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -361,7 +361,7 @@ =node-details-description +clearfix color: $color-text - font-size: 1.33em + font-size: 1.25em word-break: break-word +media-xs @@ -671,3 +671,17 @@ &:before +text-gradient($primary-accent, $primary) + +.title-underline + padding-bottom: 5px + position: relative + margin-bottom: 20px + + &:before + background-color: $primary + content: ' ' + display: block + height: 2px + top: 125% + position: absolute + width: 50px diff --git a/src/templates/_macros/_add_new_menu.pug b/src/templates/_macros/_add_new_menu.pug index 7dd9e433..84a8a9b6 100644 --- a/src/templates/_macros/_add_new_menu.pug +++ b/src/templates/_macros/_add_new_menu.pug @@ -6,7 +6,7 @@ | {% if node_type_name == 'group' %} | {% set node_type_name = 'folder' %} | {% endif %} -li(class="button-{{ node_type['name'] }}") +li a.dropdown-item( class="item_add_node", href="#", From cede3e75dbe4e5dcb200117788e746f0c4429143 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 13:47:03 +0200 Subject: [PATCH 129/148] Remove more Markdown references --- src/scripts/tutti/2_comments.js | 9 ----- src/templates/nodes/edit_embed.pug | 56 ++++++++++++++---------------- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/scripts/tutti/2_comments.js b/src/scripts/tutti/2_comments.js index d53fcaab..40d9340a 100644 --- a/src/scripts/tutti/2_comments.js +++ b/src/scripts/tutti/2_comments.js @@ -40,11 +40,6 @@ $(document).on('click','body .comment-action-reply',function(e){ parentDiv.after(commentForm); // document.getElementById('comment_field').focus(); $(commentField).focus(); - - // Convert Markdown - var convert = new Markdown.getSanitizingConverter().makeHtml; - var preview = $('.comment-reply-preview-md'); - preview.html(convert($(commentField).val())); $('.comment-reply-field').addClass('filled'); }); @@ -59,10 +54,6 @@ $(document).on('click','body .comment-action-cancel',function(e){ delete commentField.dataset.originalParentId; $(commentField).val(''); - // Convert Markdown - var convert = new Markdown.getSanitizingConverter().makeHtml; - var preview = $('.comment-reply-preview-md'); - preview.html(convert($(commentField).val())); $('.comment-reply-field').removeClass('filled'); $('.comment-container').removeClass('is-replying'); diff --git a/src/templates/nodes/edit_embed.pug b/src/templates/nodes/edit_embed.pug index c94d2f56..6f06c00a 100644 --- a/src/templates/nodes/edit_embed.pug +++ b/src/templates/nodes/edit_embed.pug @@ -101,41 +101,37 @@ script(type="text/javascript"). var page_title = 'Edit: {{ node.name }} - {{ project.name }} — Blender Cloud'; DocumentTitleAPI.set_page_title(page_title); - /* Build the markdown preview when typing in textarea */ - var convert = new Markdown.getSanitizingConverter(); - Markdown.Extra.init(convert); - convert = convert.makeHtml; + var $contentField = $('.form-group.description textarea'), + $contentPreview = $('
').insertAfter($contentField); - var $textarea = $('.form-group.description textarea'), - $loader = $('
').insertAfter($textarea), - $preview = $('
').insertAfter($loader); + function parseDescriptionContent(content) { - $loader.hide(); + $.ajax({ + url: "{{ url_for('nodes.preview_markdown')}}", + type: 'post', + data: {content: content}, + headers: {"X-CSRFToken": csrf_token}, + headers: {}, + dataType: 'json' + }) + .done(function (data) { + $contentPreview.html(data.content); + }) + .fail(function (err) { + toastr.error(xhrErrorResponseMessage(err), 'Parsing failed'); + }); + } - // Delay function to not start converting heavy posts immediately - var delay = (function(){ - var timer = 0; - return function(callback, ms){ - clearTimeout (timer); - timer = setTimeout(callback, ms); - }; - })(); + var options = { + callback: parseDescriptionContent, + wait: 750, + highlight: false, + allowSubmit: false, + captureLength: 2 + } - $textarea.keyup(function() { - /* If there's an iframe (YouTube embed), delay markdown convert 1.5s */ - if (/iframe/i.test($textarea.val())) { - $loader.show(); + $contentField.typeWatch(options); - delay(function(){ - // Convert markdown - $preview.html(convert($textarea.val())); - $loader.hide(); - }, 1500 ); - } else { - // Convert markdown - $preview.html(convert($textarea.val())); - } - }).trigger('keyup'); $('input, textarea').keypress(function () { // Unused: save status of the page as 'edited' From 77f855be3eef587b1e7973af217f59edfbc54e0b Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 14:23:53 +0200 Subject: [PATCH 130/148] Remove jQuery Montage No longer used since we list assets with a macro. --- .../assets/js/vendor/jquery.montage.min.js | 1 - src/templates/projects/view_embed.pug | 42 +------------------ 2 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 pillar/web/static/assets/js/vendor/jquery.montage.min.js diff --git a/pillar/web/static/assets/js/vendor/jquery.montage.min.js b/pillar/web/static/assets/js/vendor/jquery.montage.min.js deleted file mode 100644 index 6fbad789..00000000 --- a/pillar/web/static/assets/js/vendor/jquery.montage.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(window,$,undefined){Array.max=function(array){return Math.max.apply(Math,array)};Array.min=function(array){return Math.min.apply(Math,array)};var $event=$.event,resizeTimeout;$event.special.smartresize={setup:function(){$(this).bind("resize",$event.special.smartresize.handler)},teardown:function(){$(this).unbind("resize",$event.special.smartresize.handler)},handler:function(event,execAsap){var context=this,args=arguments;event.type="smartresize";if(resizeTimeout){clearTimeout(resizeTimeout)}resizeTimeout=setTimeout(function(){jQuery.event.handle.apply(context,args)},execAsap==="execAsap"?0:50)}};$.fn.smartresize=function(fn){return fn?this.bind("smartresize",fn):this.trigger("smartresize",["execAsap"])};$.fn.imagesLoaded=function(callback){var $images=this.find('img'),len=$images.length,_this=this,blank='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';function triggerCallback(){callback.call(_this,$images)}function imgLoaded(){if(--len<=0&&this.src!==blank){setTimeout(triggerCallback);$images.unbind('load error',imgLoaded)}}if(!len){triggerCallback()}$images.bind('load error',imgLoaded).each(function(){if(this.complete||this.complete===undefined){var src=this.src;this.src=blank;this.src=src}});return this};$.Montage=function(options,element){this.element=$(element).show();this.cache={};this.heights=new Array();this._create(options)};$.Montage.defaults={liquid:true,margin:1,minw:70,minh:20,maxh:250,alternateHeight:false,alternateHeightRange:{min:100,max:300},fixedHeight:null,minsize:false,fillLastRow:false};$.Montage.prototype={_getImageWidth:function($img,h){var i_w=$img.width(),i_h=$img.height(),r_i=i_h/i_w;return Math.ceil(h/r_i)},_getImageHeight:function($img,w){var i_w=$img.width(),i_h=$img.height(),r_i=i_h/i_w;return Math.ceil(r_i*w)},_chooseHeight:function(){if(this.options.minsize){return Array.min(this.heights)}var result={},max=0,res,val,min;for(var i=0,total=this.heights.length;ithis.options.maxh)continue;result[val]=inc;if(inc>=max){max=inc;res=val}}for(var i in result){if(result[i]===max){val=i;min=min||val;if(minval)min=val;if(min===null)min=val}}if(min===undefined)min=this.heights[0];res=min;return res},_stretchImage:function($img){var prevWrapper_w=$img.parent().width(),new_w=prevWrapper_w+this.cache.space_w_left,crop={x:new_w,y:this.theHeight};var new_image_w=$img.width()+this.cache.space_w_left,new_image_h=this._getImageHeight($img,new_image_w);this._cropImage($img,new_image_w,new_image_h,crop);this.cache.space_w_left=this.cache.container_w;if(this.options.alternateHeight)this.theHeight=Math.floor(Math.random()*(this.options.alternateHeightRange.max-this.options.alternateHeightRange.min+1)+this.options.alternateHeightRange.min)},_updatePrevImage:function($nextimg){var $prevImage=this.element.find('img.montage:last');this._stretchImage($prevImage);this._insertImage($nextimg)},_insertImage:function($img){var new_w=this._getImageWidth($img,this.theHeight);if(this.options.minsize&&!this.options.alternateHeight){if(this.cache.space_w_left<=this.options.margin*2){this._updatePrevImage($img)}else{if(new_w>this.cache.space_w_left){var crop={x:this.cache.space_w_left,y:this.theHeight};this._cropImage($img,new_w,this.theHeight,crop);this.cache.space_w_left=this.cache.container_w;$img.addClass('montage')}else{var crop={x:new_w,y:this.theHeight};this._cropImage($img,new_w,this.theHeight,crop);this.cache.space_w_left-=new_w;$img.addClass('montage')}}}else{if(new_wthis.cache.space_w_left){this._updatePrevImage($img)}else{var new_w=this.options.minw,new_h=this._getImageHeight($img,new_w),crop={x:new_w,y:this.theHeight};this._cropImage($img,new_w,new_h,crop);this.cache.space_w_left-=new_w;$img.addClass('montage')}}else{if(new_w>this.cache.space_w_left&&this.cache.space_w_leftthis.cache.space_w_left&&this.cache.space_w_left>=this.options.minw){var crop={x:this.cache.space_w_left,y:this.theHeight};this._cropImage($img,new_w,this.theHeight,crop);this.cache.space_w_left=this.cache.container_w;if(this.options.alternateHeight)this.theHeight=Math.floor(Math.random()*(this.options.alternateHeightRange.max-this.options.alternateHeightRange.min+1)+this.options.alternateHeightRange.min);$img.addClass('montage')}else{var crop={x:new_w,y:this.theHeight};this._cropImage($img,new_w,this.theHeight,crop);this.cache.space_w_left-=new_w;$img.addClass('montage')}}}},_cropImage:function($img,w,h,cropParam){var dec=this.options.margin*2;var $wrapper=$img.parent('a');this._resizeImage($img,w,h);$img.css({left:-(w-cropParam.x)/2+'px',top:-(h-cropParam.y)/2+'px'});$wrapper.addClass('am-wrapper').css({width:cropParam.x-dec+'px',height:cropParam.y+'px',margin:this.options.margin})},_resizeImage:function($img,w,h){$img.css({width:w+'px',height:h+'px'})},_reload:function(){var new_el_w=this.element.width();if(new_el_w!==this.cache.container_w){this.element.hide();this.cache.container_w=new_el_w;this.cache.space_w_left=new_el_w;var instance=this;instance.$imgs.removeClass('montage').each(function(i){instance._insertImage($(this))});if(instance.options.fillLastRow&&instance.cache.space_w_left!==instance.cache.container_w){instance._stretchImage(instance.$imgs.eq(instance.totalImages-1))}instance.element.show()}},_create:function(options){this.options=$.extend(true,{},$.Montage.defaults,options);var instance=this,el_w=instance.element.width();instance.$imgs=instance.element.find('img');instance.totalImages=instance.$imgs.length;if(instance.options.liquid)$('html').css('overflow-y','scroll');if(!instance.options.fixedHeight){instance.$imgs.each(function(i){var $img=$(this),img_w=$img.width();if(img_w').on('load', function() { - ++cnt; - if( cnt === totalImgs ) { - $imgs.show(); - $container.montage({ - fillLastRow : true, - alternateHeight : true, - alternateHeightRange : { - min : 180, - max : 240 - }, - margin : 3 - }); - } - }).attr('src',$img.attr('src')); - $img.parent().removeClass('hidden'); - }); - } - - $(function() { - montage(); - - $(".node-updates-list-item.asset, .node-updates-list-item.group") - .unbind('click') - .click(function(e) { - e.preventDefault(); - displayNode($(this).data('node_id')); - }); - }); - -| {% endblock %} +| {% endblock body %} From 1c42e8fd07a1ab3be95fc55c5338581786333ca2 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 14:26:37 +0200 Subject: [PATCH 131/148] Nodes View: Remove unnecessary containers #node-container and #node-overlay were not used. --- src/templates/nodes/view_base.pug | 194 +++++++++++++++--------------- 1 file changed, 96 insertions(+), 98 deletions(-) diff --git a/src/templates/nodes/view_base.pug b/src/templates/nodes/view_base.pug index 08770d97..3144026b 100644 --- a/src/templates/nodes/view_base.pug +++ b/src/templates/nodes/view_base.pug @@ -1,118 +1,116 @@ | {% block body %} -#node-container - #node-overlay - | {% block node_preview %} - | {% if node.picture %} - | {% if current_user.has_cap('subscriber') or node.permissions.world %} - section.node-preview.image.js-node-preview-image - img.node-preview-thumbnail(src="{{ node.picture.thumbnail('l', api=api) }}") - | {% else %} - | {% include 'nodes/custom/_node_preview_forbidden.html' %} +| {% block node_preview %} +| {% if node.picture %} +| {% if current_user.has_cap('subscriber') or node.permissions.world %} +section.node-preview.image.js-node-preview-image + img.node-preview-thumbnail(src="{{ node.picture.thumbnail('l', api=api) }}") +| {% else %} +| {% include 'nodes/custom/_node_preview_forbidden.html' %} +| {% endif %} +| {% endif %} +| {% endblock node_preview %} + +| {% block node_details %} + +| {# NAME #} +section.px-4 + h4.pt-4.mb-3 {{node.name}} + + | {# DESCRIPTION #} + | {% if node.description %} + .node-details-description + | {{ node | markdowned('description') }} | {% endif %} - | {% endif %} - | {% endblock node_preview %} - | {% block node_details %} - - | {# NAME #} - section.px-4 - h4.pt-4.mb-3 {{node.name}} - - | {# DESCRIPTION #} - | {% if node.description %} - .node-details-description - | {{ node | markdowned('description') }} +| {# DETAILS #} +section.node-details-meta.pl-4.pr-2.py-2.border-bottom + ul.list-unstyled.m-0 + | {% if node.properties.license_type %} + li.px-2 + a.node-details-license( + href="https://creativecommons.org/licenses/", + target="_blank", + title="{{ node.properties.license_type }} {% if node.properties.license_notes %}{{ node.properties.license_notes }}{% endif %}", + data-toggle="tooltip", + data-placement="top") + i(class="pi-license-{{ node.properties.license_type }}") | {% endif %} - | {# DETAILS #} - section.node-details-meta.pl-4.pr-2.py-2.border-bottom - ul.list-unstyled.m-0 - | {% if node.properties.license_type %} - li.px-2 - a.node-details-license( - href="https://creativecommons.org/licenses/", - target="_blank", - title="{{ node.properties.license_type }} {% if node.properties.license_notes %}{{ node.properties.license_notes }}{% endif %}", - data-toggle="tooltip", - data-placement="top") - i(class="pi-license-{{ node.properties.license_type }}") - | {% endif %} + | {% if node.has_method('PUT') and (node.properties.status != 'published') %} + li.px-2(class="status-{{ node.properties.status }}") + | {{ node.properties.status | undertitle }} + | {% endif %} - | {% if node.has_method('PUT') and (node.properties.status != 'published') %} - li.px-2(class="status-{{ node.properties.status }}") - | {{ node.properties.status | undertitle }} - | {% endif %} + li.px-2(title="Author") + | {{ node.user.full_name }} + | {{ node.user.badges.html|safe }} - li.px-2(title="Author") - | {{ node.user.full_name }} - | {{ node.user.badges.html|safe }} + li.px-2( + title="Created {{ node._created }} (updated {{ node._updated | pretty_date_time }})") + | {{ node._created | pretty_date }} - li.px-2( - title="Created {{ node._created }} (updated {{ node._updated | pretty_date_time }})") - | {{ node._created | pretty_date }} + | {% if node.short_link %} + li.shared + a(href="{{ node.short_link }}") + i.pi-share + | Shared + | {% endif %} - | {% if node.short_link %} - li.shared - a(href="{{ node.short_link }}") - i.pi-share - | Shared - | {% endif %} + li.ml-auto - li.ml-auto + | {% if node.file %} + li.px-2(title="File size") + | {{ node.file.length | filesizeformat }} + li.px-2.js-type(title="File format") + | {{ node.file.content_type }} + | {% endif %} - | {% if node.file %} - li.px-2(title="File size") - | {{ node.file.length | filesizeformat }} - li.px-2.js-type(title="File format") - | {{ node.file.content_type }} - | {% endif %} + | {% if node.permissions.world %} + li.public( + data-toggle="tooltip", + data-placement="bottom", + title="Anybody can download. Share it!") + i.pi-lock-open + span Public + | {% endif %} - | {% if node.permissions.world %} - li.public( - data-toggle="tooltip", - data-placement="bottom", - title="Anybody can download. Share it!") - i.pi-lock-open - span Public - | {% endif %} + | {% block node_details_meta_extra %}{% endblock %} - | {% block node_details_meta_extra %}{% endblock %} - - li.download - | {% if (current_user.has_cap('subscriber') or node.permissions.world) and (node.file or node.properties.files) %} - | {% block node_download %} - a( - title="Download {{ node.properties.content_type | undertitle }}", - href="{{ node.file.link }}", - download="{{ node.file.filename }}") - button.btn.btn-sm.btn-outline-primary.px-3(type="button") - i.pi-download.pr-2 - | Download - | {% endblock node_download %} - - | {% elif current_user.has_cap('can-renew-subscription') %} - a.btn.btn-outline-primary( - title="Renew your subscription to download", - target="_blank", - href="/renew") - i.pi-heart.pr-2 - | Renew Subscription - - | {% elif current_user.is_authenticated %} - .btn.disabled - i.pi-lock + li.download + | {% if (current_user.has_cap('subscriber') or node.permissions.world) and (node.file or node.properties.files) %} + | {% block node_download %} + a( + title="Download {{ node.properties.content_type | undertitle }}", + href="{{ node.file.link }}", + download="{{ node.file.filename }}") + button.btn.btn-sm.btn-outline-primary.px-3(type="button") + i.pi-download.pr-2 | Download + | {% endblock node_download %} - | {% else %} - a.btn( - title="Login to download {{ node.properties.content_type | undertitle }}", - href="{{ url_for('users.login') }}") - i.pi-lock - | Download - | {% endif %} + | {% elif current_user.has_cap('can-renew-subscription') %} + a.btn.btn-outline-primary( + title="Renew your subscription to download", + target="_blank", + href="/renew") + i.pi-heart.pr-2 + | Renew Subscription - | {% endblock node_details %} + | {% elif current_user.is_authenticated %} + .btn.disabled + i.pi-lock + | Download + + | {% else %} + a.btn( + title="Login to download {{ node.properties.content_type | undertitle }}", + href="{{ url_for('users.login') }}") + i.pi-lock + | Download + | {% endif %} + +| {% endblock node_details %} .container-fluid .row | {% block node_comments %} From 7fccf02e6832ce3620e00429d542da1acee3a905 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 15:00:55 +0200 Subject: [PATCH 132/148] Posts: Pass navigation_links Otherwise pages wont show up when looking at a project blog --- pillar/web/nodes/custom/posts.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pillar/web/nodes/custom/posts.py b/pillar/web/nodes/custom/posts.py index 44025b8c..a27c983b 100644 --- a/pillar/web/nodes/custom/posts.py +++ b/pillar/web/nodes/custom/posts.py @@ -19,6 +19,7 @@ from pillar.web.nodes.routes import url_for_node from pillar.web.nodes.forms import get_node_form import pillar.web.nodes.attachments from pillar.web.projects.routes import project_update_nodes_list +from pillar.web.projects.routes import project_navigation_links log = logging.getLogger(__name__) @@ -124,6 +125,7 @@ def posts_view(project_id=None, project_url=None, url=None, *, archive=False, pa title=title, node_type_post=project.get_node_type('post'), can_create_blog_posts=can_create_blog_posts, + navigation_links=navigation_links, pages=pages._items, api=api) From caee114d48ce73568e1946ede5441eb13fd20f55 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 15:01:23 +0200 Subject: [PATCH 133/148] Posts: Remove unused title and pages --- pillar/web/nodes/custom/posts.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pillar/web/nodes/custom/posts.py b/pillar/web/nodes/custom/posts.py index a27c983b..f19a3aff 100644 --- a/pillar/web/nodes/custom/posts.py +++ b/pillar/web/nodes/custom/posts.py @@ -108,11 +108,7 @@ def posts_view(project_id=None, project_url=None, url=None, *, archive=False, pa else: project.blog_archive_prev = None - title = 'blog_main' if is_main_project else 'blog' - - pages = Node.all({ - 'where': {'project': project._id, 'node_type': 'page'}, - 'projection': {'name': 1}}, api=api) + navigation_links = project_navigation_links(project, api) return render_template( template_path, @@ -122,11 +118,9 @@ def posts_view(project_id=None, project_url=None, url=None, *, archive=False, pa posts_meta=pmeta, more_posts_available=pmeta['total'] > pmeta['max_results'], project=project, - title=title, node_type_post=project.get_node_type('post'), can_create_blog_posts=can_create_blog_posts, navigation_links=navigation_links, - pages=pages._items, api=api) From 00c4ec874167f91d793902e3aa3e28dfefd606c3 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 15:01:57 +0200 Subject: [PATCH 134/148] Navigation Links: Pass the slug So we can style the items by comparing it to the page 'title'. --- pillar/web/projects/routes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pillar/web/projects/routes.py b/pillar/web/projects/routes.py index eb2cbaf8..53e98852 100644 --- a/pillar/web/projects/routes.py +++ b/pillar/web/projects/routes.py @@ -330,7 +330,7 @@ def project_navigation_links(project, api) -> list: }, api=api) if blog: - links.append({'url': finders.find_url_for_node(blog), 'label': blog.name}) + links.append({'url': finders.find_url_for_node(blog), 'label': blog.name, 'slug': 'blog'}) # Fetch pages pages = Node.all({ @@ -343,8 +343,7 @@ def project_navigation_links(project, api) -> list: # Process the results and append the links to the list for p in pages._items: - - links.append({'url': finders.find_url_for_node(p), 'label': p.name}) + links.append({'url': finders.find_url_for_node(p), 'label': p.name, 'slug': p.properties.url}) return links From 601b94e23abbe96f2e293fcc04ab2d46c819bf66 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 15:02:24 +0200 Subject: [PATCH 135/148] Pages: Set title from page properties url --- src/templates/nodes/custom/page/view_embed.pug | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/templates/nodes/custom/page/view_embed.pug b/src/templates/nodes/custom/page/view_embed.pug index 298a4ecd..61376099 100644 --- a/src/templates/nodes/custom/page/view_embed.pug +++ b/src/templates/nodes/custom/page/view_embed.pug @@ -1,6 +1,8 @@ | {% extends 'projects/landing.html' %} include ../../../mixins/components +| {% set title = node.properties.url %} + | {% block body %} .expand-image-links.imgs-fluid | {% if node.picture %} @@ -14,7 +16,7 @@ include ../../../mixins/components .container.pb-5 .row .col-8.mx-auto - h2.pt-5.pb-3.text-center {{node.name}} + h2.pt-5.pb-3.text-center {{ node.name }} | {% if node.description %} .node-details-description From 0aeae2cabd3510d5a012b395955f40f65af756b8 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 15:02:54 +0200 Subject: [PATCH 136/148] Navigation: Highlight current page in the navbar --- src/templates/projects/_macros.pug | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/templates/projects/_macros.pug b/src/templates/projects/_macros.pug index bd879bb1..3223743b 100644 --- a/src/templates/projects/_macros.pug +++ b/src/templates/projects/_macros.pug @@ -29,8 +29,10 @@ include ../mixins/components | {% endif %} | {% for link in navigation_links %} - +nav-secondary-link(href="{{ link['url'] }}") - | {{ link['label'] }} + +nav-secondary-link( + href="{{ link['url'] }}", + class="{% if link['slug'] == title %}active{% endif %}") + span {{ link['label'] }} | {% endfor %} | {% if project.nodes_featured %} From e40ba69872859b4725a19586648e1ee2c2f9ea4c Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 17:07:10 +0200 Subject: [PATCH 137/148] Project style adjustments. --- src/styles/_apps_base.sass | 2 +- src/styles/_config.sass | 5 +- src/styles/_project.sass | 105 ++++++++++------------------- src/styles/_utils.sass | 2 + src/styles/components/_navbar.sass | 18 +++-- 5 files changed, 53 insertions(+), 79 deletions(-) diff --git a/src/styles/_apps_base.sass b/src/styles/_apps_base.sass index 8a1d4d14..4d3979c7 100644 --- a/src/styles/_apps_base.sass +++ b/src/styles/_apps_base.sass @@ -403,7 +403,7 @@ nav.sidebar $loader-bar-width: 100px $loader-bar-height: 2px .loader-bar - bottom: 0 + bottom: -$loader-bar-height content: '' display: none height: 0 diff --git a/src/styles/_config.sass b/src/styles/_config.sass index 6548955e..17ce9671 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -101,7 +101,8 @@ $screen-md-max: $screen-lg-min - 1 !default $sidebar-width: 40px !default /* Project specifics */ -$project_nav-width: 275px !default +$project_nav-width: 250px !default +$project_nav-width-xl: $project_nav-width * 1.4 !default $project_nav-width-lg: $project_nav-width * 1.2 !default $project_nav-width-md: $project_nav-width $project_nav-width-sm: $project_nav-width * 0.8 !default @@ -160,4 +161,6 @@ $nav-link-height: 37px $navbar-padding-x: 0 $navbar-padding-y: 0 +$btn-padding-y-sm: 0.1rem + $grid-breakpoints: (xs: 0,sm: 576px,md: 768px,lg: 1060px,xl: 1500px, xxl: 1800px) diff --git a/src/styles/_project.sass b/src/styles/_project.sass index 644f360a..a7638703 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -1,10 +1,17 @@ $node-latest-thumbnail-size: 160px -body.open-projects, -body.courses, -body.workshops - #project-container - +container-behavior +/* Dark navbar when browsing a project. */ +body.project + nav.navbar + @extend .bg-dark + box-shadow: 0 2px $gray-700 + + .nav-link, .text-muted + color: $gray-400 !important + + .nav-main .nav-link + @extend .text-dark + #project-container display: flex @@ -26,22 +33,26 @@ body.workshops #project_nav, +#project_tree, #project_nav-container - +media-lg - width: $project_nav-width-lg - +media-sm - width: $project_nav-width-sm +media-xs width: $project_nav-width-xs + +media-sm + width: $project_nav-width-sm + +media-md + width: $project_nav-width-md + +media-lg + width: $project_nav-width-lg + +media-xl + width: $project_nav-width-xl width: $project_nav-width #project_nav-container +media-xs display: block - width: 100% - position: relative height: initial !important + position: relative position: fixed z-index: $z-index-base + 5 @@ -94,36 +105,11 @@ body.workshops width: $project-sidebar-width -#search-container #project_sidebar ul.project-tabs li.tabs-thumbnail - background-color: $color-background-nav-dark - &:hover - background-color: $color-background-nav-light - #project-nav, #project_context-container flex: 1 -/* Container for navigation on the left */ -#project_nav - +media-lg - width: $project_nav-width-lg - +media-sm - width: $project_nav-width-sm - +media-xs - width: $project_nav-width-xs - - display: block - left: 0 - position: relative - visibility: visible - width: $project_nav-width - - &.about - display: none - visibility: hidden - - /* Header with name and node edit tools */ #project_context-header right: 0 @@ -494,40 +480,28 @@ $node-preview-max-height-lg: 700px text-transform: uppercase section.node-preview - +media-md - max-height: $node-preview-max-height-md - +media-lg - max-height: $node-preview-max-height-lg - - align-items: center background-color: black color: $color-text-light-primary - // display: flex - justify-content: center - max-height: 500px - // min-height: 200px - // overflow: hidden + flex-shrink: 0 // prevents content/comments to make preview dissappear + max-height: calc((9 / 16) * 133vh) + min-height: 200px + overflow: hidden iframe width: 100% img - display: block - max-height: $node-preview-max-height-lg + @extend .d-block + @extend .mx-auto max-width: 100% object-fit: scale-down + flex: 1 +media-xs width: 100% - +media-md - max-height: $node-preview-max-height-md - +media-lg - max-height: $node-preview-max-height-lg - &.image cursor: zoom-in - display: flex overflow: hidden &.video @@ -583,19 +557,10 @@ section.node-preview color: $color-warning margin-right: 10px - &.project - background-color: black - width: 100% - - img - max-height: 800px - max-width: 100% - object-fit: cover - width: 100% section.node-preview-forbidden align-items: center - background-color: $color-background-nav + background-color: $primary color: $color-text-light cursor: default display: flex @@ -1033,7 +998,7 @@ a.learn-more width: auto height: auto background: black - box-shadow: 1px 1px 1px rgba(black, .5), 2px 2px 15px rgba(black, .5) + box-shadow: 1px 1px 1px rgba(black, .5), 2px 2px 25px rgba(black, .25) visibility: hidden display: none @@ -1070,7 +1035,7 @@ a.learn-more top: 0 left: 0 right: 0 - @include overlay(rgba($color-background-nav, .9), 0%, transparent, 25%) + @include overlay(rgba(black, .9), 0%, transparent, 25%) +text-overflow-ellipsis @@ -1290,13 +1255,13 @@ a.learn-more .list-node-children-item-thumbnail height: $list-node-children-item-width - background-color: $color-background-nav + background-color: black &:hover opacity: 1 img - opacity: .15 + opacity: .5 .list-node-children-item-name opacity: 1 @@ -2021,5 +1986,3 @@ a.learn-more padding: 5px 35px text-align: center -.ribbon - +ribbon diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index aa2fc22f..e12b3a9f 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -512,6 +512,8 @@ margin: 1px 0 padding: 3px 50px +.ribbon + +ribbon @mixin text-background($text-color, $background-color, $roundness, $padding) border-radius: $roundness diff --git a/src/styles/components/_navbar.sass b/src/styles/components/_navbar.sass index a52df028..c5492a91 100644 --- a/src/styles/components/_navbar.sass +++ b/src/styles/components/_navbar.sass @@ -1,6 +1,6 @@ /* Top level navigation bar. */ .navbar - box-shadow: inset 0 -2px $color-background + box-shadow: 0 2px $color-background .nav border: none @@ -119,22 +119,22 @@ &:focus box-shadow: inset 0 -3px 0 $primary - &.active - color: $primary - box-shadow: inset 0 -3px 0 $primary - /* Secondary navigation. */ $nav-secondary-bar-size: -2px .nav-secondary align-items: center - box-shadow: inset 0 $nav-secondary-bar-size 0 0 $color-background .nav-link color: $color-text cursor: pointer + margin-bottom: 2px transition: color 150ms ease-in-out + span + position: relative + top: 2px + &:after background-color: transparent bottom: 0 @@ -155,6 +155,7 @@ $nav-secondary-bar-size: -2px background-image: linear-gradient(to right, $primary-accent 70%, $primary) height: 2px width: 100% + bottom: -2px span +active-gradient @@ -162,6 +163,9 @@ $nav-secondary-bar-size: -2px i color: $primary-accent + .nav-link.active + font-weight: bold + &.nav-secondary-vertical align-items: flex-start flex-direction: column @@ -186,6 +190,8 @@ $nav-secondary-bar-size: -2px // Big navigation dropdown. .nav-main + min-width: initial + .nav-secondary .nav-link @extend .pr-5 From 57f5836829c79f2a31d4715b121e77e6d50dae5b Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 17:08:46 +0200 Subject: [PATCH 138/148] Cleanup and replace custom styles with bootstrap classes. --- src/templates/menus/user_base.pug | 2 +- .../nodes/custom/group_texture/view_embed.pug | 4 +- .../nodes/custom/texture/view_embed.pug | 72 ++++++++----------- src/templates/nodes/view_base.pug | 10 ++- src/templates/projects/home_images.pug | 2 + src/templates/projects/view_embed.pug | 5 +- 6 files changed, 45 insertions(+), 50 deletions(-) diff --git a/src/templates/menus/user_base.pug b/src/templates/menus/user_base.pug index 8be5bab3..f2229773 100644 --- a/src/templates/menus/user_base.pug +++ b/src/templates/menus/user_base.pug @@ -36,7 +36,7 @@ li.dropdown | {% else %} -li.pt-1.pr-1 +li.pr-1 a.btn.btn-sm.btn-outline-primary.px-3( href="{{ url_for('users.login') }}") | Log In diff --git a/src/templates/nodes/custom/group_texture/view_embed.pug b/src/templates/nodes/custom/group_texture/view_embed.pug index 6316a746..777ef2dc 100644 --- a/src/templates/nodes/custom/group_texture/view_embed.pug +++ b/src/templates/nodes/custom/group_texture/view_embed.pug @@ -15,7 +15,7 @@ span.texture-info-files {{ children|length }} item{% if children|length != 1 %}s{% endif %} | {% endif %} - section.node-children.group.texture + section.node-children.group.texture.px-3 | {% for child in children %} a.list-node-children-container( @@ -49,7 +49,7 @@ | {% if child.permissions.world %} .list-node-children-item-ribbon - span free + span FREE | {% endif %} | {% if child.node_type == 'texture' %} diff --git a/src/templates/nodes/custom/texture/view_embed.pug b/src/templates/nodes/custom/texture/view_embed.pug index a5f6931b..1712d77b 100644 --- a/src/templates/nodes/custom/texture/view_embed.pug +++ b/src/templates/nodes/custom/texture/view_embed.pug @@ -1,10 +1,9 @@ | {% block body %} #node-container.texture - #node-overlay - - .texture-title#node-title - | {{node.name}} + section.px-4 + h4.pt-4.mb-3(class="js-texture-title") + | {{node.name}} | {% if node.properties.license_type %} | {% if node.properties.license_notes %} @@ -24,15 +23,6 @@ | {% endif %} | {% endif %} - | {% if node.permissions.world %} - .texture-license.public( - data-toggle="tooltip", - data-placement="bottom", - title="Anybody can download. Share it!") - i.pi-lock-open - span Public - | {% endif %} - ul.node-row.texture-info | {% if node.properties.files %} li @@ -44,9 +34,20 @@ i.pi-puzzle | {% if not node.properties.is_tileable %}Not {% endif %}Seamless + li.ml-auto + + | {% if node.permissions.world %} + li.text-success( + data-toggle="tooltip", + data-placement="left", + title="Anybody can download. Share it!") + i.pi-lock-open + span Public + | {% endif %} + | {# Display publishing status only to editors #} | {% if node.has_method('PUT') %} - li.status( + li( class="{{ node.properties.status }}", title="Status") | Status: #[strong {{ node.properties.status | undertitle }}] @@ -68,37 +69,32 @@ section.node-details-container.texture - .node-details-header - .node-title {{map_type}} + .px-3.d-flex.flex-column.h-100 + h5 {{ map_type }} - .node-details-attributes - span.sizes - span.x - | Width: - strong {{ f.file.width }} - span.y - | Height: - strong {{ f.file.height }} - span.length - | {{ f.file.length | filesizeformat }} - span.content_type - | {{ f.file.content_type }} + .d-flex.flex-column.text-black-50.h-100 + span + | #[strong(title='Width') {{ f.file.width }}] x #[strong(title='Height') {{ f.file.height }}] - .node-details-meta - ul.node-details-meta-list + span.mt-auto {{ f.file.length | filesizeformat }} + span.text-uppercase.pt-1 + | {{ f.file.content_type }} + + ul.list-unstyled.mt-auto.pt-2 li.node-details-meta-list-item.texture.download | {% if f.file.link %} - a(href="{{ f.file.link }}",, + a(href="{{ f.file.link }}", title="Download texture", download="{{ f.file.filename }}") - button.btn.btn-outline-secondary(type="button") + button.btn.btn-sm.btn-outline-primary.px-3.btn-block(type="button") i.pi-download | Download | {% else %} - button.btn.btn-outline-secondary.disabled.sorry(type="button") + button.btn.btn-sm.btn-outline-primary.px-3.btn-block.disabled(type="button") i.pi-lock | Download | {% endif %} + | {% else %} section.node-row section.node-details-container.texture @@ -116,9 +112,9 @@ script. // Generate GA pageview ga('send', 'pageview', location.pathname); - var str = $('.texture-title').text(); + var str = $('.js-texture-title').text(); var to_replace = /_color|_bump|_specular|_normal|_translucency|_emission|_alpha|_tileable|.jpg|.png/g; - $('.texture-title').text(str.replace(to_replace,'').replace(/_/g,' ')); + $('.js-texture-title').text(str.replace(to_replace,'').replace(/_/g,' ')); $('.node-preview-thumbnail').each(function(i){ $(this).closest('.node-preview').css({'height' : $(this).width() / $(this).data('aspect_ratio')}); @@ -177,12 +173,6 @@ script. }); - $('.sorry').click(function() { - $.get('/403', function(data) { - $('#node-overlay').html(data).show().addClass('active'); - }) - }); - $('#node-overlay').click(function(){ $(this).removeClass('active').hide().html(); }); diff --git a/src/templates/nodes/view_base.pug b/src/templates/nodes/view_base.pug index 3144026b..31938d33 100644 --- a/src/templates/nodes/view_base.pug +++ b/src/templates/nodes/view_base.pug @@ -3,8 +3,11 @@ | {% block node_preview %} | {% if node.picture %} | {% if current_user.has_cap('subscriber') or node.permissions.world %} -section.node-preview.image.js-node-preview-image - img.node-preview-thumbnail(src="{{ node.picture.thumbnail('l', api=api) }}") +section.node-preview.d-flex.justify-content-center( + class="js-node-preview-image") + img.node-preview-thumbnail( + src="{{ node.picture.thumbnail('l', api=api) }}", + alt="{{ node.name }}") | {% else %} | {% include 'nodes/custom/_node_preview_forbidden.html' %} | {% endif %} @@ -120,7 +123,8 @@ section.node-details-meta.pl-4.pr-2.py-2.border-bottom i.pi-spin | {% endblock node_comments %} - | {% if node.properties.tags %} + | {# Check if tags is defined and there is _actually_ a tag at least #} + | {% if node.properties.tags and node.properties.tags[0] %} .col-md-4.d-none.d-lg-block script(src="{{ url_for('static_cloud', filename='assets/js/tagged_assets.min.js') }}") script. diff --git a/src/templates/projects/home_images.pug b/src/templates/projects/home_images.pug index 274a9dfc..e3130e32 100644 --- a/src/templates/projects/home_images.pug +++ b/src/templates/projects/home_images.pug @@ -1,4 +1,6 @@ | {% extends 'projects/home_layout.html' %} +| {% set title = 'images' %} + | {% set subtab = 'images' %} | {% set learn_more_btn_url = '/blog/introducing-image-sharing' %} | {% block currenttab %} diff --git a/src/templates/projects/view_embed.pug b/src/templates/projects/view_embed.pug index 6c441828..0a4e2beb 100644 --- a/src/templates/projects/view_embed.pug +++ b/src/templates/projects/view_embed.pug @@ -2,7 +2,7 @@ include ../mixins/components | {% block body %} -section.node-preview.project +section.node-preview | {% if header_video_file %} .embed-responsive.embed-responsive-16by9 video#videoplayer.video-js.vjs-fluid.embed-responsive-item( @@ -19,8 +19,7 @@ section.node-preview.project To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video | {% elif project.picture_header %} - a(href="{{ url_for( 'projects.view', project_url=project.url) }}") - img.header(src="{{ project.picture_header.thumbnail('l', api=api) }}") + img.node-preview-thumbnail(src="{{ project.picture_header.thumbnail('l', api=api) }}") | {% endif %} section.px-4 From 13b606df453eb504dd2c74d919f56a74231a38f8 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 18:16:42 +0200 Subject: [PATCH 139/148] CSS cleanup and use classes for styling --- src/styles/_comments.sass | 2 +- src/styles/plugins/_jstree.sass | 64 ------------------- .../nodes/custom/asset/video/view_embed.pug | 2 +- src/templates/nodes/view_base.pug | 6 +- 4 files changed, 5 insertions(+), 69 deletions(-) diff --git a/src/styles/_comments.sass b/src/styles/_comments.sass index 2e76c3d7..927c3700 100644 --- a/src/styles/_comments.sass +++ b/src/styles/_comments.sass @@ -32,7 +32,7 @@ $comments-width-max: 710px .comment-reply-container display: flex position: relative - padding: 15px 0 20px 0 + padding: 15px 0 transition: background-color 150ms ease-in-out, padding 150ms ease-in-out, margin 150ms ease-in-out &.comment-linked diff --git a/src/styles/plugins/_jstree.sass b/src/styles/plugins/_jstree.sass index 853213b8..df410fd9 100644 --- a/src/styles/plugins/_jstree.sass +++ b/src/styles/plugins/_jstree.sass @@ -123,7 +123,6 @@ $tree-color-highlight-background-text: $primary /* Selected item */ &.jstree-clicked - background-color: $tree-color-highlight-background !important color: $tree-color-highlight-background-text !important font-weight: bold @@ -137,16 +136,12 @@ $tree-color-highlight-background-text: $primary /* hover an active item */ &.jstree-hovered - background-color: lighten($tree-color-highlight-background, 10%) !important box-shadow: none color: $tree-color-highlight-background-text !important &.jstree-hovered .jstree-icon color: $tree-color-highlight-background-text !important - .jstree-hovered - background-color: rgba($tree-color-highlight, .1) !important - .jstree-leaf .jstree-clicked width: 100% !important @@ -193,63 +188,6 @@ $tree-color-highlight-background-text: $primary &:after display: none !important - &.blog - .jstree-anchor - padding: 6px 6px 6px 12px - - &:hover - color: $tree-color-highlight - - &.post - border-bottom: thin solid $color-background-dark - - &.jstree-clicked - &.post - background-color: transparent !important - - &:after - top: 8px - color: $tree-color-highlight !important - - .tree-item-info - color: $color-text - - .tree-item-title - color: $tree-color-highlight - - .tree-item - line-height: initial - padding-right: 10px - - &-title - font-size: 1.2em - overflow: initial - text-overflow: initial - white-space: normal - - &-info - color: $color-text-dark-secondary - display: block - font-size: .8em - padding: 5px - - &-thumbnail - align-items: center - background-color: $color-background - border-radius: 3px - color: $color-text-dark-secondary - display: flex - float: left - height: 70px - justify-content: center - margin: 0 10px 0 -5px - width: 70px - - img - height: 70px - width: 70px - - .jstree-loading padding: 5px color: $color-text-light-secondary @@ -276,11 +214,9 @@ $tree-color-highlight-background-text: $primary a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked - background-color: rgba($tree-color-highlight-background, .8) !important color: $tree-color-highlight-background-text !important a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked - background-color: rgba($tree-color-highlight-background, .8) !important color: $tree-color-highlight-background-text !important i.jstree-icon.jstree-ocl diff --git a/src/templates/nodes/custom/asset/video/view_embed.pug b/src/templates/nodes/custom/asset/video/view_embed.pug index 6ba9d44d..d3499eda 100644 --- a/src/templates/nodes/custom/asset/video/view_embed.pug +++ b/src/templates/nodes/custom/asset/video/view_embed.pug @@ -23,7 +23,7 @@ section.node-preview.video | {% block node_download %} | {% if node.file_variations %} -button.btn.btn-sm.btn-outline-primary.dropdown-toggle.px-3( +button.btn.btn-outline-primary.dropdown-toggle.px-3( type="button", data-toggle="dropdown", aria-haspopup="true", diff --git a/src/templates/nodes/view_base.pug b/src/templates/nodes/view_base.pug index 31938d33..b737759b 100644 --- a/src/templates/nodes/view_base.pug +++ b/src/templates/nodes/view_base.pug @@ -45,7 +45,7 @@ section.node-details-meta.pl-4.pr-2.py-2.border-bottom | {{ node.properties.status | undertitle }} | {% endif %} - li.px-2(title="Author") + li.pr-2(title="Author") | {{ node.user.full_name }} | {{ node.user.badges.html|safe }} @@ -87,7 +87,7 @@ section.node-details-meta.pl-4.pr-2.py-2.border-bottom title="Download {{ node.properties.content_type | undertitle }}", href="{{ node.file.link }}", download="{{ node.file.filename }}") - button.btn.btn-sm.btn-outline-primary.px-3(type="button") + button.btn.btn-outline-primary.px-3(type="button") i.pi-download.pr-2 | Download | {% endblock node_download %} @@ -118,7 +118,7 @@ section.node-details-meta.pl-4.pr-2.py-2.border-bottom .row | {% block node_comments %} .col-md-8.col-sm-12 - #comments-embed + #comments-embed.p-2 .comments-list-loading i.pi-spin | {% endblock node_comments %} From 008d9b8880751b4f541c71c56f20eba9a757732e Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 18:35:04 +0200 Subject: [PATCH 140/148] Comments: padding --- src/styles/_comments.sass | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/styles/_comments.sass b/src/styles/_comments.sass index 927c3700..71dd697c 100644 --- a/src/styles/_comments.sass +++ b/src/styles/_comments.sass @@ -196,8 +196,6 @@ $comments-width-max: 710px cursor: pointer font-family: 'pillar-font' height: 25px - position: relative - top: 4px width: 16px .comment-action-rating.up @@ -286,10 +284,11 @@ $comments-width-max: 710px color: $color-success &.is-replying - box-shadow: inset 5px 0 0 $color-primary + box-shadow: -5px 0 0 $color-primary + @extend .pl-3 &.is-replying+.comment-reply-container - box-shadow: inset 5px 0 0 $color-primary + box-shadow: -5px 0 0 $color-primary margin-left: 0 padding-left: 55px From fe86f76617c80b54ccffebb2bb1d4ece7f63ac5f Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Mon, 17 Sep 2018 19:04:42 +0200 Subject: [PATCH 141/148] Search: styling --- src/styles/_project.sass | 3 +- src/styles/_search.sass | 47 +++++++------------- src/templates/nodes/search.pug | 81 ++++++++++++++++------------------ 3 files changed, 58 insertions(+), 73 deletions(-) diff --git a/src/styles/_project.sass b/src/styles/_project.sass index a7638703..f85d9e16 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -58,9 +58,10 @@ body.project z-index: $z-index-base + 5 #project_sidebar + box-shadow: inset -1px 0 0 0 $color-background + flex-shrink: 0 width: $project-sidebar-width z-index: $z-index-base + 6 - box-shadow: inset -1px 0 0 0 $color-background +media-xs width: 100% diff --git a/src/styles/_search.sass b/src/styles/_search.sass index bc40c738..1332b731 100644 --- a/src/styles/_search.sass +++ b/src/styles/_search.sass @@ -93,6 +93,17 @@ $search-hit-width_grid: 100px background-color: lighten($color-background, 5%) .search-list + width: 50% + + .embed-responsive + width: 100px + min-width: 100px + + .card-deck.card-deck-vertical + .card + flex-wrap: initial + +.search-settings width: 30% .card-deck.card-deck-vertical @@ -119,8 +130,14 @@ $search-hit-width_grid: 100px .search-details width: 70% + .container-fluid .col-md-8 + flex: 1 + max-width: 100% + width: 100% + #search-details position: relative + #search-hit-container position: absolute // for scrollbars overflow-y: auto @@ -289,25 +306,11 @@ $search-hit-width_grid: 100px button width: 100% -#project_sidebar+#search-sidebar, -#project_sidebar+#search-sidebar+#search-container - padding-left: $sidebar-width - .search-project li.project display: none #search-sidebar - .card - margin-bottom: 10px - border-radius: 3px - border: none - background-color: white - box-shadow: 1px 1px 0 rgba(black, .1) - - a - text-decoration: none - .toggleRefine display: block padding-left: 7px @@ -346,22 +349,6 @@ $search-hit-width_grid: 100px position: relative left: -7px font-size: .9em - .facet_count - color: $color-text-dark-secondary - - .card-title - position: relative - &:after - content: '\e83b' - font-family: 'pillar-font' - position: absolute - right: 0 - color: $color-text-dark-primary - - .collapsed - .card-title:after - content: '\e838' - .search-list-stats color: $color-text-dark-hint diff --git a/src/templates/nodes/search.pug b/src/templates/nodes/search.pug index e9fbeae9..bf112ae7 100644 --- a/src/templates/nodes/search.pug +++ b/src/templates/nodes/search.pug @@ -5,7 +5,7 @@ include ../mixins/components | {% block page_title %}Search{% if project %} {{ project.name }}{% endif %}{% endblock %} | {% block head %} -script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}") +script(src="{{ url_for('static_pillar', filename='assets/js/vendor/video.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}") | {% endblock %} @@ -31,27 +31,26 @@ script. document.body.dataset["projectId"] = "{{project._id}}"; | {% endif %} -| {% if project %} -#project_sidebar.bg-white - ul.project-tabs.p-0 - li.tabs-browse( - title="Browse", - data-toggle="tooltip", - data-placement="right") - a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") - i.pi-folder - - li.tabs-search.active( - title="Search", - data-toggle="tooltip", - data-placement="right") - a(href="{{url_for('projects.search', project_url=project.url, _external=True)}}") - i.pi-search -| {% endif %} - - #search-container.d-flex(class="{% if project %}search-project{% endif %}") - .search-list + | {% if project %} + #project_sidebar.bg-white + ul.project-tabs.p-0 + li.tabs-browse( + title="Browse", + data-toggle="tooltip", + data-placement="right") + a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + i.pi-folder + + li.tabs-search.active( + title="Search", + data-toggle="tooltip", + data-placement="right") + a(href="{{url_for('projects.search', project_url=project.url, _external=True)}}") + i.pi-search + | {% endif %} + + .search-settings#search-sidebar.bg-light input.search-field.p-2.bg-white( type="text", name="q", @@ -62,13 +61,12 @@ script. placeholder="Search by Title, Type...") #pagination.mt-3 - - //- #accordion.panel-group.accordion(role="tablist", aria-multiselectable="true") #facets - #stats.search-list-stats - +card-deck()(id='hits', class="h-100 m-0 pt-3 pr-2 card-deck-vertical") + .border-left.search-list + + +card-deck(0)(id='hits', class="m-0 px-2 card-deck-vertical") #search-details.border-left.search-details #search-error @@ -78,22 +76,19 @@ script. | {% raw %} // Facet template script(type="text/template", id="facet-template") - .card - a(data-toggle='collapse', data-parent='#accordion', href='#filter_{{ facet }}', aria-expanded='true', aria-controls='filter_{{ facet }}') - .card-header(role='tab') - .card-title {{ title }} - .collapse.show(id='filter_{{ facet }}', role='tabpanel', aria-labelledby='headingOne') - .card-body - | {{#values}} - a.facet_link.toggleRefine( - class='{{#refined}}refined{{/refined}}', - data-facet='{{ facet }}', - data-value='{{ value }}', - href='#') - span - | {{ label }} - small.facet_count.pull-right {{ count }} - | {{/values}} + .card.border-0.p-0.m-2 + .card-body.p-3.m-0 + h6.text-muted {{ title }} + | {{#values}} + a.facet_link.toggleRefine( + class='{{#refined}}refined{{/refined}}', + data-facet='{{ facet }}', + data-value='{{ value }}', + href='#') + span + | {{ label }} + small.text-black-50.float-right {{ count }} + | {{/values}} // Hit template @@ -177,6 +172,8 @@ script. $(this).addClass('active'); }); + projectBrowseTypeList(); + // Remove focus from search input so that the click event // bound to .search-hit can be fired on the first click. $('#search-list').hover(function(){ @@ -210,5 +207,5 @@ script. | {% endblock %} -| {% block footer_navigation %}{% endblock %} +| {% block footer_container %}{% endblock %} | {% block footer %}{% endblock %} From 3e1273d56c011ecb1e120740a15c6b5c95c9cdde Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 18 Sep 2018 12:49:06 +0200 Subject: [PATCH 142/148] CSS: zoom-in cursor utility --- src/styles/_project.sass | 6 +----- src/styles/_utils.sass | 3 +++ src/templates/nodes/view_base.pug | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/styles/_project.sass b/src/styles/_project.sass index f85d9e16..e47dba44 100644 --- a/src/styles/_project.sass +++ b/src/styles/_project.sass @@ -485,8 +485,8 @@ section.node-preview color: $color-text-light-primary flex-shrink: 0 // prevents content/comments to make preview dissappear max-height: calc((9 / 16) * 133vh) - min-height: 200px overflow: hidden + min-height: 200px iframe width: 100% @@ -501,10 +501,6 @@ section.node-preview +media-xs width: 100% - &.image - cursor: zoom-in - overflow: hidden - &.video background-color: black position: relative diff --git a/src/styles/_utils.sass b/src/styles/_utils.sass index e12b3a9f..68060f3e 100644 --- a/src/styles/_utils.sass +++ b/src/styles/_utils.sass @@ -645,6 +645,9 @@ .cursor-pointer cursor: pointer +.cursor-zoom-in + cursor: zoom-in + .user-select-none user-select: none diff --git a/src/templates/nodes/view_base.pug b/src/templates/nodes/view_base.pug index b737759b..8d9c85c1 100644 --- a/src/templates/nodes/view_base.pug +++ b/src/templates/nodes/view_base.pug @@ -3,7 +3,7 @@ | {% block node_preview %} | {% if node.picture %} | {% if current_user.has_cap('subscriber') or node.permissions.world %} -section.node-preview.d-flex.justify-content-center( +section.node-preview.d-flex.justify-content-center.cursor-zoom-in( class="js-node-preview-image") img.node-preview-thumbnail( src="{{ node.picture.thumbnail('l', api=api) }}", From 45672565e941266356249d027ba3a7cdd652c3b3 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 18 Sep 2018 12:53:34 +0200 Subject: [PATCH 143/148] Card style fixes --- src/styles/components/_card.sass | 17 +++++++++++++---- src/templates/_macros/_asset_list_item.pug | 15 ++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/styles/components/_card.sass b/src/styles/components/_card.sass index 1902a78b..2b671fad 100644 --- a/src/styles/components/_card.sass +++ b/src/styles/components/_card.sass @@ -27,10 +27,6 @@ max-width: 20% &.card-3-columns .card - +media-xl - flex: 1 0 33% - max-width: 33% - +media-xxl flex: 1 0 33% max-width: 33% @@ -42,6 +38,8 @@ .card @extend .w-100 @extend .flex-row + @extend .p-0 + flex: initial flex-wrap: wrap max-width: 100% @@ -53,8 +51,19 @@ @extend .mr-2 max-width: 120px + &.asset + &.free + &:after + +ribbon + content: 'FREE' + left: -40px + right: initial + transform: rotate(-45deg) + padding: 1px 45px + .card-body @extend .overflow-hidden + flex-basis: 0 .card-padless .card diff --git a/src/templates/_macros/_asset_list_item.pug b/src/templates/_macros/_asset_list_item.pug index 607cf99e..fc8c07b0 100644 --- a/src/templates/_macros/_asset_list_item.pug +++ b/src/templates/_macros/_asset_list_item.pug @@ -15,14 +15,15 @@ a.card.asset.card-image-fade.pr-0.mx-0.mb-2( i(class="pi-{{ node_type }}") | {% endif %} - .card-body.py-2.d-flex.flex-column - .card-title.mb-1.font-weight-bold - | {{ asset.name }} + .card-body.py-2.d-flex.flex-column.text-truncate + .card-title.mb-1.font-weight-bold.text-truncate + | {{ asset.name | hide_none }} - ul.card-text.list-unstyled.d-flex.text-black-50.mt-auto.mb-0 - li.pr-2.font-weight-bold {{ node_type | undertitle }} - li.pr-2 {{ asset.user.full_name }} - li {{ asset._created | pretty_date }} + ul.card-text.list-unstyled.d-flex.text-black-50.mt-auto.mb-0.text-truncate + li.pr-2.font-weight-bold {{ node_type | undertitle | hide_none }} + li.pr-2.text-truncate {{ asset.project.name | hide_none }} + li.pr-2.text-truncate {{ asset.user.full_name | hide_none }} + li.text-truncate {{ asset._created | pretty_date | hide_none }} | {% if asset.properties.content_type == 'video' %} From 9a0da126e60a8d3d7bea55066de73eae3eadc82e Mon Sep 17 00:00:00 2001 From: Francesco Siddi Date: Tue, 18 Sep 2018 15:14:27 +0200 Subject: [PATCH 144/148] Fix failing tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Failure was due to a new ‘slug’ key in the link dict. --- tests/test_web/test_projects.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_web/test_projects.py b/tests/test_web/test_projects.py index a428c4f1..59487baf 100644 --- a/tests/test_web/test_projects.py +++ b/tests/test_web/test_projects.py @@ -40,7 +40,7 @@ class ProjectTest(AbstractPillarTest): navigation_links = project_navigation_links(project, api=api) # We expect only one link to be in the list - links = [{'url': '/p/default-project/about', 'label': 'About'}] + links = [{'url': '/p/default-project/about', 'slug': 'about', 'label': 'About'}] self.assertListEqual(links, navigation_links) def test_project_navigation_links_pages_and_blog(self): @@ -89,9 +89,10 @@ class ProjectTest(AbstractPillarTest): project = find_project_or_404(self.project['url'], api=api) navigation_links = project_navigation_links(project, api=api) expected_list = [ - {'url': '/blog/', 'label': 'Blog'}, # Blog is the first element of the list (since it's added first) - {'url': '/p/default-project/about', 'label': 'About'}, - {'url': '/p/default-project/awards', 'label': 'Awards'}] + # Blog is the first element of the list (since it's added first) + {'url': '/blog/', 'slug': 'blog', 'label': 'Blog'}, + {'url': '/p/default-project/about', 'slug': 'about', 'label': 'About'}, + {'url': '/p/default-project/awards', 'slug': 'awards', 'label': 'Awards'}] self.assertListEqual(expected_list, navigation_links) From 93720e226c3d4882658d056c3bf0670764ea37f5 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 18 Sep 2018 15:23:38 +0200 Subject: [PATCH 145/148] Badges: don't display them just yet --- src/templates/nodes/custom/comment/_macros.pug | 4 +++- src/templates/users/settings/profile.pug | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/templates/nodes/custom/comment/_macros.pug b/src/templates/nodes/custom/comment/_macros.pug index fc2601ad..7c8e473f 100644 --- a/src/templates/nodes/custom/comment/_macros.pug +++ b/src/templates/nodes/custom/comment/_macros.pug @@ -10,13 +10,15 @@ .comment-content .comment-body p.comment-author {{ comment._user.full_name }} + | {# //- TODO(Pablo): due to the broad styling done on the .comment-content class the //- styling for the badges that I put in _project.sass isn't applied properly here. | {{ comment._user.badges_html|safe }} + | #} span {{comment.properties | markdowned('content') }} - // TODO: Markdown preview when editing + | {# TODO(Pablo): Markdown preview when editing #} .comment-meta .comment-rating( diff --git a/src/templates/users/settings/profile.pug b/src/templates/users/settings/profile.pug index 7a6f1d8c..ad4aec8a 100644 --- a/src/templates/users/settings/profile.pug +++ b/src/templates/users/settings/profile.pug @@ -39,12 +39,15 @@ style. .form-group | {{ _("Change your full name, email, and password at") }} #[a(href="https://www.blender.org/id/settings/profile",target='_blank') Blender ID]. + | {# | {% if current_user.badges_html %} .form-group p Your Blender ID badges: | {{ current_user.badges_html|safe }} p.hint-text Note that updates to these badges may take a few minutes to be visible here. | {% endif %} + | #} + .py-3 a(href="https://gravatar.com/") img.rounded-circle(src="{{ current_user.gravatar }}") From 46bdd4f51c7cd558403aef3a4db7bdcd592fc8a6 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 18 Sep 2018 15:24:19 +0200 Subject: [PATCH 146/148] Pages: Don't show date and page title It's already in the jumbotron --- src/templates/nodes/custom/page/view_embed.pug | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/templates/nodes/custom/page/view_embed.pug b/src/templates/nodes/custom/page/view_embed.pug index 61376099..f8cbcaea 100644 --- a/src/templates/nodes/custom/page/view_embed.pug +++ b/src/templates/nodes/custom/page/view_embed.pug @@ -8,7 +8,7 @@ include ../../../mixins/components | {% if node.picture %} +jumbotron( "{{ node.name }}", - "{{ node._created | pretty_date }}{% if node.user.full_name %} · {{ node.user.full_name }}{% endif %}", + null, "{{ node.picture.thumbnail('h', api=api) }}", "{{ node.url }}") | {% endif %} @@ -16,10 +16,8 @@ include ../../../mixins/components .container.pb-5 .row .col-8.mx-auto - h2.pt-5.pb-3.text-center {{ node.name }} - | {% if node.description %} - .node-details-description + .node-details-description.pt-5 | {{ node | markdowned('description') }} | {% endif %} From b1d97e723f277cc559cb2415d385272d2f2d59a8 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 18 Sep 2018 15:24:40 +0200 Subject: [PATCH 147/148] Cards: Smaller ribbon for vertical aligned cards --- src/styles/components/_card.sass | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/styles/components/_card.sass b/src/styles/components/_card.sass index 2b671fad..a84736da 100644 --- a/src/styles/components/_card.sass +++ b/src/styles/components/_card.sass @@ -56,10 +56,11 @@ &:after +ribbon content: 'FREE' + font-size: .6rem left: -40px + padding: 1px 45px right: initial transform: rotate(-45deg) - padding: 1px 45px .card-body @extend .overflow-hidden From 749c3dbd58a67c4098bc8859e21f35c53aaa9992 Mon Sep 17 00:00:00 2001 From: Pablo Vazquez Date: Tue, 18 Sep 2018 15:25:02 +0200 Subject: [PATCH 148/148] Gulp: Add bootstrap's collapse and alert js to tutti --- gulpfile.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 5ce8230d..4d1b37f1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -90,8 +90,10 @@ gulp.task('scripts_concat_tutti', function() { source.popper + 'dist/umd/popper.min.js', source.bootstrap + 'js/dist/index.js', source.bootstrap + 'js/dist/util.js', - source.bootstrap + 'js/dist/tooltip.js', + source.bootstrap + 'js/dist/alert.js', + source.bootstrap + 'js/dist/collapse.js', source.bootstrap + 'js/dist/dropdown.js', + source.bootstrap + 'js/dist/tooltip.js', 'src/scripts/tutti/**/*.js' ];