48 Commits

Author SHA1 Message Date
0baf5b38c3 Project view: dim title link 2018-09-06 12:52:54 +02:00
858a75af8d Pug: Move project home templates to blender-cloud
These are super hard-coded to the Cloud anyway.
2018-09-06 12:51:58 +02:00
6b1a5e24e8 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.
2018-09-06 12:46:33 +02:00
1500e20291 Blog: cleanup of layout and style
Simpler markup reusing bootstrap 4 classes.
2018-09-06 12:42:37 +02:00
d347534fea Pug: Move navigation macro to blender-cloud 2018-09-06 12:19:28 +02:00
4546469d37 Pug: Move blog macros to blender-cloud 2018-09-06 12:19:00 +02:00
b0d8da821f CSS: Blog cleanup 2018-09-06 12:11:18 +02:00
1821bb6b7d CSS general cleanup and minor style tweaks 2018-09-06 12:11:10 +02:00
278eebd235 Style jsTree 2018-09-06 12:06:14 +02:00
2777c37085 Style videoplayer. 2018-09-06 12:05:45 +02:00
9c2ded79dd CSS: Cleanup and simplification
Mainly to rely more on bootstrap styling
2018-08-31 19:32:17 +02:00
b4acfb89fa Layout: use bootstrap classes 2018-08-31 19:31:36 +02:00
33bd2c5880 Sass: Import modules on top level 2018-08-31 14:26:42 +02:00
76338b4568 Sass config: Bootstrap overrides 2018-08-31 14:24:25 +02:00
7405e198eb Use .displayAs() instead of .show()
Needed for CSS display to be set as inline-block instead of show()'s inline.
2018-08-31 14:23:23 +02:00
2332bc0960 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'.
2018-08-31 14:20:59 +02:00
ac3a599bb6 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.
2018-08-31 14:19:09 +02:00
814275fc95 Gulp: only chmod when running --production 2018-08-31 14:17:39 +02:00
46b0d6d663 Upgrade npm dependencies
Change gulp-uglify for gulp-uglify-es which has support for ES6.

New dependencies:
* boostrap
* jquery
* popper.js (required by bootstrap)
2018-08-29 16:30:17 +02:00
40f79af49d Tooltips: Cleanup 2018-08-28 15:54:14 +02:00
84608500b9 CSS: Split dropdown styling 2018-08-28 15:53:47 +02:00
819300f954 Navbar cleanup 2018-08-28 15:52:56 +02:00
b569829343 General cleanup 2018-08-28 15:52:50 +02:00
c35fb6202b render_secondary_navigation: Bootstrap 4 tweaks 2018-08-28 15:51:56 +02:00
d0ff519980 Font Pillar: Aliases for CC license icons
Also comments about updating the font from fontello.com
2018-08-27 17:03:13 +02:00
6ff4ee8fa1 Minor Dashboard style tweaks 2018-08-27 17:02:36 +02:00
b5535a8773 CSS: New primary color and navbar height 2018-08-27 17:02:07 +02:00
2ded541955 CSS Cleanup: remove font-body specifics 2018-08-27 17:01:43 +02:00
3965061bde 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.
2018-08-27 17:01:08 +02:00
5238e2c26d Pillar Font: Use variable for path 2018-08-22 19:57:22 +02:00
466adabbb0 Added unit tests for IP range validation 2018-07-13 13:50:01 +02:00
5fb40eb32b Simple unittests for Cerberus validation 2018-07-13 11:42:31 +02:00
9f380751f5 Support for capabilities check in any shortcode
Use the @capcheck decorator on any shortcode that should support
this. Currently used by iframe and youtube.
2018-07-11 12:32:00 +02:00
49075cbc60 Local development server uses http, not https 2018-06-23 01:25:35 +02:00
81848c2c44 Introducing package-lock.json 2018-06-22 19:38:49 +02:00
9ee7b742ab Make more consistent use of BLENDER_ID_ENDPOINT
Now BLENDER_ID_ENDPOINT is used for the Blender ID OAuth config,
and it's directly accessed when building requests for Blender ID token
validation (without using utility functions).
2018-06-22 19:38:27 +02:00
58c33074c3 Fix unittest for jinja.do_markdown
We were passing invalid html to do_markdown, which was returning a valid
version, by closing the <script> tag.
2018-06-22 17:10:38 +02:00
756427b34e Link Markdown Cheatsheet to CommonMark help 2018-06-10 10:03:56 +02:00
7e06212cd5 CSS: Tweaks to pre/code 2018-06-10 09:41:26 +02:00
ef3912b647 CSS: Fix for emojis on lists 2018-06-10 09:01:44 +02:00
151484dee3 Support parsing of bare links in Markdown text 2018-06-08 19:35:14 +02:00
bec1f209ba Update bleach library from 1.4.3 to 2.1.3 2018-06-08 19:34:39 +02:00
0e14bdd09f Introduce rating functions
These hotness and confidence calculation algorithms come from Reddit
and have been tweaked based on our experience on the Dillo project.
2018-06-03 02:09:20 +02:00
ce6df542cc Add ratings_embedded_schema to node_types
Ratings, like attachments, are a common feature in node_types.
By adding this schema definition, we reduce code duplication.
No functional changes are introduced introduced in this commit.
2018-05-11 01:32:39 +02:00
530302b74f Fix deprecation warning, rename Form to FlaskForm
Starting with flask_wtform version 1.0, Form will be dropped in favor
of FlaskForm.
2018-05-09 22:50:26 +02:00
1bfb6cd2f6 Use high-res image for page and blog headers 2018-05-07 15:26:42 +02:00
53b6210531 Remove unneeded file opening
The statement has been moved to the Docker file of blender-cloud,
where we actually append a generated STATIC_FILE_HASH.
2018-04-21 18:09:42 +02:00
aeaa03ed80 Handle embedded featured nodes to get node_id 2018-04-16 17:30:02 +02:00
101 changed files with 7674 additions and 3594 deletions

View File

@@ -12,7 +12,7 @@ var pug = require('gulp-pug');
var rename = require('gulp-rename'); var rename = require('gulp-rename');
var sass = require('gulp-sass'); var sass = require('gulp-sass');
var sourcemaps = require('gulp-sourcemaps'); var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify'); var uglify = require('gulp-uglify-es').default;
var enabled = { var enabled = {
uglify: argv.production, uglify: argv.production,
@@ -21,6 +21,7 @@ var enabled = {
prettyPug: !argv.production, prettyPug: !argv.production,
cachify: !argv.production, cachify: !argv.production,
cleanup: argv.production, cleanup: argv.production,
chmod: argv.production,
}; };
var destination = { var destination = {
@@ -29,6 +30,10 @@ var destination = {
js: 'pillar/web/static/assets/js', js: 'pillar/web/static/assets/js',
} }
var source = {
bootstrap: 'node_modules/bootstrap/',
popper: 'node_modules/popper.js/'
}
/* CSS */ /* CSS */
gulp.task('styles', function() { gulp.task('styles', function() {
@@ -67,7 +72,7 @@ gulp.task('scripts', function() {
.pipe(gulpif(enabled.uglify, uglify())) .pipe(gulpif(enabled.uglify, uglify()))
.pipe(rename({suffix: '.min'})) .pipe(rename({suffix: '.min'}))
.pipe(gulpif(enabled.maps, sourcemaps.write("."))) .pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(chmod(644)) .pipe(gulpif(enabled.chmod, chmod(644)))
.pipe(gulp.dest(destination.js)) .pipe(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload())); .pipe(gulpif(argv.livereload, livereload()));
}); });
@@ -82,7 +87,7 @@ gulp.task('scripts_concat_tutti', function() {
.pipe(concat("tutti.min.js")) .pipe(concat("tutti.min.js"))
.pipe(gulpif(enabled.uglify, uglify())) .pipe(gulpif(enabled.uglify, uglify()))
.pipe(gulpif(enabled.maps, sourcemaps.write("."))) .pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(chmod(644)) .pipe(gulpif(enabled.chmod, chmod(644)))
.pipe(gulp.dest(destination.js)) .pipe(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload())); .pipe(gulpif(argv.livereload, livereload()));
}); });
@@ -94,7 +99,30 @@ gulp.task('scripts_concat_markdown', function() {
.pipe(concat("markdown.min.js")) .pipe(concat("markdown.min.js"))
.pipe(gulpif(enabled.uglify, uglify())) .pipe(gulpif(enabled.uglify, uglify()))
.pipe(gulpif(enabled.maps, sourcemaps.write("."))) .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()));
});
// 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(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload())); .pipe(gulpif(argv.livereload, livereload()));
}); });
@@ -137,4 +165,5 @@ gulp.task('default', tasks.concat([
'scripts', 'scripts',
'scripts_concat_tutti', 'scripts_concat_tutti',
'scripts_concat_markdown', 'scripts_concat_markdown',
'scripts_concat_bootstrap',
])); ]));

5452
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,23 +4,28 @@
"author": "Blender Institute", "author": "Blender Institute",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/armadillica/pillar.git" "url": "git://git.blender.org/pillar.git"
}, },
"devDependencies": { "devDependencies": {
"gulp": "~3.9.1", "gulp": "^3.9.1",
"gulp-autoprefixer": "~2.3.1", "gulp-autoprefixer": "^6.0.0",
"gulp-cached": "~1.1.0", "gulp-cached": "^1.1.1",
"gulp-chmod": "~1.3.0", "gulp-chmod": "^2.0.0",
"gulp-concat": "~2.6.0", "gulp-concat": "^2.6.1",
"gulp-if": "^2.0.1", "gulp-if": "^2.0.2",
"gulp-git": "~2.4.2", "gulp-git": "^2.8.0",
"gulp-livereload": "~3.8.1", "gulp-livereload": "^4.0.0",
"gulp-plumber": "~1.1.0", "gulp-plumber": "^1.2.0",
"gulp-pug": "~3.2.0", "gulp-pug": "^4.0.1",
"gulp-rename": "~1.2.2", "gulp-rename": "^1.4.0",
"gulp-sass": "~2.3.1", "gulp-sass": "^4.0.1",
"gulp-sourcemaps": "~1.6.0", "gulp-sourcemaps": "^2.6.4",
"gulp-uglify": "~1.5.3", "gulp-uglify-es": "^1.0.4",
"minimist": "^1.2.0" "minimist": "^1.2.0"
},
"dependencies": {
"bootstrap": "^4.1.3",
"jquery": "^3.3.1",
"popper.js": "^1.14.4"
} }
} }

View File

@@ -184,7 +184,6 @@ class PillarServer(BlinkerCompatibleEve):
if not self.config.get('STATIC_FILE_HASH'): if not self.config.get('STATIC_FILE_HASH'):
self.log.warning('STATIC_FILE_HASH is empty, generating random one') self.log.warning('STATIC_FILE_HASH is empty, generating random one')
f = open('/data/git/blender-cloud/config_local.py', 'a')
h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8] h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8]
self.config['STATIC_FILE_HASH'] = h self.config['STATIC_FILE_HASH'] = h

View File

@@ -47,13 +47,6 @@ def store_subclient_token():
'subclient_user_id': str(db_user['_id'])}), status 'subclient_user_id': str(db_user['_id'])}), status
def blender_id_endpoint():
"""Gets the endpoint for the authentication API. If the env variable
is defined, it's possible to override the (default) production address.
"""
return current_app.config['BLENDER_ID_ENDPOINT'].rstrip('/')
def validate_create_user(blender_id_user_id, token, oauth_subclient_id): def validate_create_user(blender_id_user_id, token, oauth_subclient_id):
"""Validates a user against Blender ID, creating the user in our database. """Validates a user against Blender ID, creating the user in our database.
@@ -121,13 +114,13 @@ def validate_token(user_id, token, oauth_subclient_id):
# We only want to accept Blender Cloud tokens. # We only want to accept Blender Cloud tokens.
payload['client_id'] = current_app.config['OAUTH_CREDENTIALS']['blender-id']['id'] payload['client_id'] = current_app.config['OAUTH_CREDENTIALS']['blender-id']['id']
url = '{0}/u/validate_token'.format(blender_id_endpoint()) url = '{0}/u/validate_token'.format(current_app.config['BLENDER_ID_ENDPOINT'])
log.debug('POSTing to %r', url) log.debug('POSTing to %r', url)
# Retry a few times when POSTing to BlenderID fails. # Retry a few times when POSTing to BlenderID fails.
# Source: http://stackoverflow.com/a/15431343/875379 # Source: http://stackoverflow.com/a/15431343/875379
s = requests.Session() s = requests.Session()
s.mount(blender_id_endpoint(), HTTPAdapter(max_retries=5)) s.mount(current_app.config['BLENDER_ID_ENDPOINT'], HTTPAdapter(max_retries=5))
# POST to Blender ID, handling errors as negative verification results. # POST to Blender ID, handling errors as negative verification results.
try: try:
@@ -225,7 +218,7 @@ def fetch_blenderid_user() -> dict:
my_log = log.getChild('fetch_blenderid_user') my_log = log.getChild('fetch_blenderid_user')
bid_url = '%s/api/user' % blender_id_endpoint() bid_url = '%s/api/user' % current_app.config['BLENDER_ID_ENDPOINT']
my_log.debug('Fetching user info from %s', bid_url) my_log.debug('Fetching user info from %s', bid_url)
credentials = current_app.config['OAUTH_CREDENTIALS']['blender-id'] credentials = current_app.config['OAUTH_CREDENTIALS']['blender-id']
@@ -270,7 +263,7 @@ def setup_app(app, url_prefix):
def switch_user_url(next_url: str) -> str: def switch_user_url(next_url: str) -> str:
from urllib.parse import quote from urllib.parse import quote
base_url = '%s/switch' % blender_id_endpoint() base_url = '%s/switch' % current_app.config['BLENDER_ID_ENDPOINT']
if next_url: if next_url:
return '%s?next=%s' % (base_url, quote(next_url)) return '%s?next=%s' % (base_url, quote(next_url))
return base_url return base_url

View File

@@ -40,6 +40,51 @@ attachments_embedded_schema = {
}, },
} }
# TODO (fsiddi) reference this schema in all node_types that allow ratings
ratings_embedded_schema = {
'type': 'dict',
# Total count of positive ratings (updated at every rating action)
'schema': {
'positive': {
'type': 'integer',
},
# Total count of negative ratings (updated at every rating action)
'negative': {
'type': 'integer',
},
# Collection of ratings, keyed by user
'ratings': {
'type': 'list',
'schema': {
'type': 'dict',
'schema': {
'user': {
'type': 'objectid',
'data_relation': {
'resource': 'users',
'field': '_id',
'embeddable': False
}
},
'is_positive': {
'type': 'boolean'
},
# Weight of the rating based on user rep and the context.
# Currently we have the following weights:
# - 1 auto null
# - 2 manual null
# - 3 auto valid
# - 4 manual valid
'weight': {
'type': 'integer'
}
}
}
},
'hot': {'type': 'float'},
},
}
# Import after defining the common embedded schemas, to prevent dependency cycles. # Import after defining the common embedded schemas, to prevent dependency cycles.
from pillar.api.node_types.asset import node_type_asset from pillar.api.node_types.asset import node_type_asset
from pillar.api.node_types.blog import node_type_blog from pillar.api.node_types.blog import node_type_blog

View File

@@ -0,0 +1,87 @@
# These functions come from Reddit
# https://github.com/reddit/reddit/blob/master/r2/r2/lib/db/_sorts.pyx
# Additional resources
# http://www.redditblog.com/2009/10/reddits-new-comment-sorting-system.html
# http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
# http://amix.dk/blog/post/19588
from datetime import datetime, timezone
from math import log
from math import sqrt
epoch = datetime(1970, 1, 1, 0, 0, 0, 0, timezone.utc)
def epoch_seconds(date):
"""Returns the number of seconds from the epoch to date."""
td = date - epoch
return td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000)
def score(ups, downs):
return ups - downs
def hot(ups, downs, date):
"""The hot formula. Reddit's hot ranking uses the logarithm function to
weight the first votes higher than the rest.
The first 10 upvotes have the same weight as the next 100 upvotes which
have the same weight as the next 1000, etc.
Dillo authors: we modified the formula to give more weight to negative
votes when an entry is controversial.
TODO: make this function more dynamic so that different defaults can be
specified depending on the item that is being rated.
"""
s = score(ups, downs)
order = log(max(abs(s), 1), 10)
sign = 1 if s > 0 else -1 if s < 0 else 0
seconds = epoch_seconds(date) - 1134028003
base_hot = round(sign * order + seconds / 45000, 7)
if downs > 1:
rating_delta = 100 * (downs - ups) / downs
if rating_delta < 25:
# The post is controversial
return base_hot
base_hot = base_hot - (downs * 6)
return base_hot
def _confidence(ups, downs):
n = ups + downs
if n == 0:
return 0
z = 1.0 #1.0 = 85%, 1.6 = 95%
phat = float(ups) / n
return sqrt(phat+z*z/(2*n)-z*((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
def confidence(ups, downs):
if ups + downs == 0:
return 0
else:
return _confidence(ups, downs)
def update_hot(document):
"""Update the hotness of a document given its current ratings.
We expect the document to implement the ratings_embedded_schema in
a 'ratings' property.
"""
dt = document['_created']
dt = dt.replace(tzinfo=timezone.utc)
document['properties']['ratings']['hot'] = hot(
document['properties']['ratings']['positive'],
document['properties']['ratings']['negative'],
dt,
)

View File

@@ -131,16 +131,15 @@ class BlenderIdSignIn(OAuthSignIn):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
base_url = current_app.config['OAUTH_CREDENTIALS']['blender-id'].get( base_url = current_app.config['BLENDER_ID_ENDPOINT']
'base_url', 'https://www.blender.org/id/')
self.service = OAuth2Service( self.service = OAuth2Service(
name='blender-id', name='blender-id',
client_id=self.consumer_id, client_id=self.consumer_id,
client_secret=self.consumer_secret, client_secret=self.consumer_secret,
authorize_url='%soauth/authorize' % base_url, authorize_url='%s/oauth/authorize' % base_url,
access_token_url='%soauth/token' % base_url, access_token_url='%s/oauth/token' % base_url,
base_url='%sapi/' % base_url base_url='%s/api/' % base_url
) )
def authorize(self): def authorize(self):

View File

@@ -32,7 +32,7 @@ SECRET_KEY = ''
AUTH_TOKEN_HMAC_KEY = b'' AUTH_TOKEN_HMAC_KEY = b''
# Authentication settings # Authentication settings
BLENDER_ID_ENDPOINT = 'http://blender-id:8000/' BLENDER_ID_ENDPOINT = 'http://id.local:8000'
CDN_USE_URL_SIGNING = True CDN_USE_URL_SIGNING = True
CDN_SERVICE_DOMAIN_PROTOCOL = 'https' CDN_SERVICE_DOMAIN_PROTOCOL = 'https'
@@ -124,9 +124,8 @@ BLENDER_ID_USER_INFO_TOKEN = '-set-in-config-local-'
# Example entry: # Example entry:
# OAUTH_CREDENTIALS = { # OAUTH_CREDENTIALS = {
# 'blender-id': { # 'blender-id': {
# 'id': 'CLOUD-OF-SNOWFLAKES-43', # 'id': 'CLOUD-OF-SNOWFLAKES-42',
# 'secret': 'thesecret', # 'secret': 'thesecret',
# 'base_url': 'http://blender-id:8000/'
# } # }
# } # }
# OAuth providers are defined in pillar.auth.oauth # OAuth providers are defined in pillar.auth.oauth

View File

@@ -45,11 +45,15 @@ ALLOWED_STYLES = [
def markdown(s: str) -> str: def markdown(s: str) -> str:
commented_shortcodes = shortcodes.comment_shortcodes(s) commented_shortcodes = shortcodes.comment_shortcodes(s)
tainted_html = CommonMark.commonmark(commented_shortcodes) tainted_html = CommonMark.commonmark(commented_shortcodes)
safe_html = bleach.clean(tainted_html,
tags=ALLOWED_TAGS, # Create a Cleaner that supports parsing of bare links (see filters).
cleaner = bleach.Cleaner(tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES, attributes=ALLOWED_ATTRIBUTES,
styles=ALLOWED_STYLES, styles=ALLOWED_STYLES,
strip_comments=False) strip_comments=False,
filters=[bleach.linkifier.LinkifyFilter])
safe_html = cleaner.clean(tainted_html)
return safe_html return safe_html

View File

@@ -33,18 +33,57 @@ log = logging.getLogger(__name__)
def shortcode(name: str): def shortcode(name: str):
"""Class decorator for shortcodes.""" """Class decorator for shortcodes."""
def decorator(cls): def decorator(decorated):
assert hasattr(cls, '__call__'), '@shortcode should be used on callables.' assert hasattr(decorated, '__call__'), '@shortcode should be used on callables.'
if isinstance(cls, type): if isinstance(decorated, type):
instance = cls() as_callable = decorated()
else: else:
instance = cls as_callable = decorated
shortcodes.register(name)(instance) shortcodes.register(name)(as_callable)
return cls return decorated
return decorator return decorator
class capcheck:
"""Decorator for shortcodes.
On call, check for capabilities before calling the function. If the user does not
have a capability, display a message insdead of the content.
kwargs:
- 'cap': Capability required for viewing.
- 'nocap': Optional, text shown when the user does not have this capability.
- others: Passed to the decorated shortcode.
"""
def __init__(self, decorated):
assert hasattr(decorated, '__call__'), '@capcheck should be used on callables.'
if isinstance(decorated, type):
as_callable = decorated()
else:
as_callable = decorated
self.decorated = as_callable
def __call__(self,
context: typing.Any,
content: str,
pargs: typing.List[str],
kwargs: typing.Dict[str, str]) -> str:
from pillar.auth import current_user
cap = kwargs.pop('cap', '')
if cap:
nocap = kwargs.pop('nocap', '')
if not current_user.has_cap(cap):
if not nocap:
return ''
html = html_module.escape(nocap)
return f'<p class="shortcode nocap">{html}</p>'
return self.decorated(context, content, pargs, kwargs)
@shortcode('test') @shortcode('test')
class Test: class Test:
def __call__(self, def __call__(self,
@@ -68,6 +107,7 @@ class Test:
@shortcode('youtube') @shortcode('youtube')
@capcheck
class YouTube: class YouTube:
log = log.getChild('YouTube') log = log.getChild('YouTube')
@@ -129,6 +169,7 @@ class YouTube:
@shortcode('iframe') @shortcode('iframe')
@capcheck
def iframe(context: typing.Any, def iframe(context: typing.Any,
content: str, content: str,
pargs: typing.List[str], pargs: typing.List[str],
@@ -140,16 +181,6 @@ def iframe(context: typing.Any,
- others: Turned into attributes for the iframe element. - others: Turned into attributes for the iframe element.
""" """
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pillar.auth import current_user
cap = kwargs.pop('cap', '')
if cap:
nocap = kwargs.pop('nocap', '')
if not current_user.has_cap(cap):
if not nocap:
return ''
html = html_module.escape(nocap)
return f'<p class="shortcode nocap">{html}</p>'
kwargs['class'] = f'shortcode {kwargs.get("class", "")}'.strip() kwargs['class'] = f'shortcode {kwargs.get("class", "")}'.strip()
element = ET.Element('iframe', kwargs) element = ET.Element('iframe', kwargs)

View File

@@ -1,6 +1,6 @@
"""Flask configuration file for unit testing.""" """Flask configuration file for unit testing."""
BLENDER_ID_ENDPOINT = 'http://127.0.0.1:8001' # nonexistant server, no trailing slash! BLENDER_ID_ENDPOINT = 'http://id.local:8001' # Non existant server
SERVER_NAME = 'localhost' SERVER_NAME = 'localhost'
PILLAR_SERVER_ENDPOINT = 'http://localhost/api/' PILLAR_SERVER_ENDPOINT = 'http://localhost/api/'
@@ -26,7 +26,6 @@ OAUTH_CREDENTIALS = {
'blender-id': { 'blender-id': {
'id': 'blender-id-app-id', 'id': 'blender-id-app-id',
'secret': 'blender-idsecret', 'secret': 'blender-idsecret',
'base_url': 'http://blender-id:8000/'
}, },
'facebook': { 'facebook': {
'id': 'fb-app-id', 'id': 'fb-app-id',

View File

@@ -4,7 +4,7 @@ from datetime import datetime
from datetime import date from datetime import date
import pillarsdk import pillarsdk
from flask import current_app from flask import current_app
from flask_wtf import Form from flask_wtf import FlaskForm
from wtforms import StringField from wtforms import StringField
from wtforms import DateField from wtforms import DateField
from wtforms import SelectField from wtforms import SelectField
@@ -110,7 +110,7 @@ def get_node_form(node_type):
:param node_type: Describes the node type via dyn_schema, form_schema and :param node_type: Describes the node type via dyn_schema, form_schema and
parent parent
""" """
class ProceduralForm(Form): class ProceduralForm(FlaskForm):
pass pass
parent_prop = node_type['parent'] parent_prop = node_type['parent']

View File

@@ -1,4 +1,4 @@
from flask_wtf import Form from flask_wtf import FlaskForm
from wtforms import StringField from wtforms import StringField
from wtforms import BooleanField from wtforms import BooleanField
from wtforms import HiddenField from wtforms import HiddenField
@@ -12,7 +12,7 @@ from pillar.web import system_util
from pillar.web.utils.forms import FileSelectField, JSONRequired from pillar.web.utils.forms import FileSelectField, JSONRequired
class ProjectForm(Form): class ProjectForm(FlaskForm):
project_id = HiddenField('project_id', validators=[DataRequired()]) project_id = HiddenField('project_id', validators=[DataRequired()])
name = StringField('Name', validators=[DataRequired()]) name = StringField('Name', validators=[DataRequired()])
url = StringField('Url', validators=[DataRequired()]) url = StringField('Url', validators=[DataRequired()])
@@ -32,7 +32,7 @@ class ProjectForm(Form):
picture_square = FileSelectField('Picture square', file_format='image') picture_square = FileSelectField('Picture square', file_format='image')
def validate(self): def validate(self):
rv = Form.validate(self) rv = FlaskForm.validate(self)
if not rv: if not rv:
return False return False
@@ -54,7 +54,7 @@ class ProjectForm(Form):
return True return True
class NodeTypeForm(Form): class NodeTypeForm(FlaskForm):
project_id = HiddenField('project_id', validators=[DataRequired()]) project_id = HiddenField('project_id', validators=[DataRequired()])
name = StringField('Name', validators=[DataRequired()]) name = StringField('Name', validators=[DataRequired()])
parent = StringField('Parent') parent = StringField('Parent')

View File

@@ -12,14 +12,6 @@ from pillar.sdk import FlaskInternalApi
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def blender_id_endpoint():
"""Gets the endpoint for the authentication API. If the env variable
is defined, it's possible to override the (default) production address.
"""
return os.environ.get('BLENDER_ID_ENDPOINT',
"https://www.blender.org/id").rstrip('/')
def pillar_server_endpoint(): def pillar_server_endpoint():
"""Gets the endpoint for the authentication API. If the env variable """Gets the endpoint for the authentication API. If the env variable
is defined, we will use the one from the config object. is defined, we will use the one from the config object.

View File

@@ -1,5 +1,5 @@
from flask_login import current_user from flask_login import current_user
from flask_wtf import Form from flask_wtf import FlaskForm
from pillar.web import system_util from pillar.web import system_util
from pillarsdk.users import User from pillarsdk.users import User
@@ -14,7 +14,7 @@ from wtforms.validators import Regexp
import wtforms.validators as wtvalid import wtforms.validators as wtvalid
class UserLoginForm(Form): class UserLoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()]) username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()]) password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me') remember_me = BooleanField('Remember Me')
@@ -23,7 +23,7 @@ class UserLoginForm(Form):
super(UserLoginForm, self).__init__(csrf_enabled=False, *args, **kwargs) super(UserLoginForm, self).__init__(csrf_enabled=False, *args, **kwargs)
class UserProfileForm(Form): class UserProfileForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length( username = StringField('Username', validators=[DataRequired(), Length(
min=3, max=128, message="Min. 3, max. 128 chars please"), Regexp( min=3, max=128, message="Min. 3, max. 128 chars please"), Regexp(
r'^[\w.@+-]+$', message="Please do not use spaces")]) r'^[\w.@+-]+$', message="Please do not use spaces")])
@@ -52,7 +52,7 @@ class UserProfileForm(Form):
return True return True
class UserSettingsEmailsForm(Form): class UserSettingsEmailsForm(FlaskForm):
choices = [ choices = [
(1, 'Keep me updated with Blender Cloud news.'), (1, 'Keep me updated with Blender Cloud news.'),
(0, 'Do not mail me news update.')] (0, 'Do not mail me news update.')]
@@ -74,7 +74,7 @@ class RolesField(SelectMultipleField):
return current_app.user_roles return current_app.user_roles
class UserEditForm(Form): class UserEditForm(FlaskForm):
roles = RolesField('Roles') roles = RolesField('Roles')
email = StringField( email = StringField(
validators=[wtvalid.DataRequired(), wtvalid.Email()], validators=[wtvalid.DataRequired(), wtvalid.Email()],

View File

@@ -5,7 +5,7 @@ attrs==16.2.0
algoliasearch==1.12.0 algoliasearch==1.12.0
bcrypt==3.1.3 bcrypt==3.1.3
blinker==1.4 blinker==1.4
bleach==1.4.3 bleach==2.1.3
celery[redis]==4.0.2 celery[redis]==4.0.2
CommonMark==0.7.2 CommonMark==0.7.2
elasticsearch==6.1.1 elasticsearch==6.1.1
@@ -40,7 +40,7 @@ Flask-PyMongo==0.4.1
-e git+https://github.com/armadillica/cerberus.git@sybren-0.9#egg=Cerberus -e git+https://github.com/armadillica/cerberus.git@sybren-0.9#egg=Cerberus
Events==0.2.2 Events==0.2.2
future==0.15.2 future==0.15.2
html5lib==0.9999999 html5lib==0.99999999
googleapis-common-protos==1.1.0 googleapis-common-protos==1.1.0
itsdangerous==0.24 itsdangerous==0.24
Jinja2==2.9.6 Jinja2==2.9.6

View File

@@ -64,4 +64,13 @@
return this; 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)); }(jQuery));

View File

@@ -453,12 +453,17 @@ $comments-width-max: 710px
transition: background-color 150ms ease-in-out, color 150ms ease-in-out transition: background-color 150ms ease-in-out, color 150ms ease-in-out
width: 100px width: 100px
// The actual button for submitting the comment.
button.comment-action-submit button.comment-action-submit
align-items: center
background: transparent background: transparent
border: none border: none
border-top-left-radius: 0 border-top-left-radius: 0
border-bottom-left-radius: 0 border-bottom-left-radius: 0
color: $color-success color: $color-success
cursor: pointer
display: flex
justify-content: center
flex-direction: column flex-direction: column
height: 100% height: 100%
position: relative position: relative
@@ -466,8 +471,12 @@ $comments-width-max: 710px
white-space: nowrap white-space: nowrap
width: 100% width: 100%
&:hover
background: rgba($color-success, .1)
&:focus &:focus
background: lighten($color-success, 10%) background: lighten($color-success, 10%)
color: $white
&.submitting &.submitting
color: $color-info color: $color-info

View File

@@ -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-secondary: rgba($color-text-light, .54) !default
$color-text-light-hint: rgba($color-text-light, .38) !default $color-text-light-hint: rgba($color-text-light, .38) !default
$color-primary: #68B3C8 !default $color-primary: #009eff !default
$color-primary-light: hsl(hue($color-primary), 30%, 90%) !default $color-primary-light: hsl(hue($color-primary), 30%, 90%) !default
$color-primary-dark: hsl(hue($color-primary), 80%, 30%) !default $color-primary-dark: hsl(hue($color-primary), 80%, 30%) !default
$color-primary-accent: hsl(hue($color-primary), 100%, 50%) !default $color-primary-accent: hsl(hue($color-primary), 100%, 50%) !default
@@ -100,13 +100,10 @@ $sidebar-width: 50px !default
/* Project specifics */ /* Project specifics */
$project_nav-width: 250px !default $project_nav-width: 250px !default
$project-sidebar-width: 50px !default $project-sidebar-width: 40px !default
$project_header-height: 50px !default $project_header-height: 37px !default
$project_footer-height: 30px !default $project_footer-height: 30px !default
$navbar-height: 50px !default
$navbar-backdrop-height: 600px !default
$node-type-asset_image: #e87d86 !default $node-type-asset_image: #e87d86 !default
$node-type-asset_file: #CC91C7 !default $node-type-asset_file: #CC91C7 !default
$node-type-asset_video: #7dc5e8 !default $node-type-asset_video: #7dc5e8 !default
@@ -125,3 +122,29 @@ $z-index-base: 13 !default
@media (min-width: $screen-lg-min) @media (min-width: $screen-lg-min)
width: 1270px width: 1270px
// Bootstrap overrides.
$enable-caret: false
$border-radius: .2rem
$btn-border-radius: $border-radius
$primary: $color-primary
$body-bg: $white
$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
$tooltip-opacity: 1

View File

@@ -60,14 +60,13 @@
#node-overlay #node-overlay
#error-container #error-container
position: fixed
top: $navbar-height
align-items: flex-start align-items: flex-start
position: fixed
top: $nav-link-height
#error-box #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-left-radius: 0
border-top-right-radius: 0 border-top-right-radius: 0
box-shadow: 0 0 25px rgba(black, .1), 0 0 50px rgba(black, .1)
position: relative position: relative
width: 100% width: 100%

View File

@@ -9,7 +9,6 @@
color: $color-primary color: $color-primary
cursor: pointer cursor: pointer
float: right float: right
font-family: $font-body
height: initial height: initial
margin: 0 margin: 0
padding: 8px 10px 0 10px padding: 8px 10px 0 10px

View File

@@ -1,20 +1,4 @@
body.organizations body.organizations
ul#sub-nav-tabs__list
align-items: center
display: flex
li.result
padding: 10px 20px
li.create
margin-left: auto
.dashboard-secondary
.box
+container-box
padding: 10px 20px
margin: 0
#item-details #item-details
.organization .organization
label label

View File

@@ -409,7 +409,6 @@ a.page-card-cta
display: block display: block
+position-center-translate +position-center-translate
+media-xs +media-xs
display: none display: none
+media-sm +media-sm
@@ -419,9 +418,6 @@ a.page-card-cta
+media-lg +media-lg
width: 100% width: 100%
.services.navbar-backdrop-overlay
background: rgba(black, .5)
.services .services
.page-card-side .page-card-side
max-width: 500px max-width: 500px

View File

@@ -1,22 +1,16 @@
.dashboard-container .dashboard-container
section#home, section#home,
section#projects section#projects
background-color: $color-background
border-bottom-left-radius: 3px border-bottom-left-radius: 3px
border-bottom-right-radius: 3px border-bottom-right-radius: 3px
nav#sub-nav-tabs.home, nav#sub-nav-tabs.home,
nav#sub-nav-tabs.projects nav#sub-nav-tabs.projects
background-color: white
border-bottom: thin solid $color-background-dark border-bottom: thin solid $color-background-dark
li.nav-tabs__list-tab li.nav-tabs__list-tab
padding: 15px 20px 10px 20px padding: 15px 20px 10px 20px
section#home
background-color: $color-background-dark
nav.nav-tabs__tab nav.nav-tabs__tab
display: none display: none
background-color: $color-background-light background-color: $color-background-light
@@ -287,9 +281,8 @@
flex-direction: column flex-direction: column
.title .title
font-size: 1.2em
padding-bottom: 2px
color: $color-text-dark-primary color: $color-text-dark-primary
padding-bottom: 2px
ul.meta ul.meta
font-size: .9em font-size: .9em

View File

@@ -92,10 +92,6 @@ ul.sharing-users-list
&:hover &:hover
color: lighten($color-danger, 10%) color: lighten($color-danger, 10%)
.sharing-users-intro,
.sharing-users-info
h4
font-family: $font-body
.sharing-users-info .sharing-users-info
padding-left: 15px padding-left: 15px

View File

@@ -1,4 +1,3 @@
$project-sidebar-background: lighten($color-background, 5%)
$node-latest-thumbnail-size: 160px $node-latest-thumbnail-size: 160px
body.open-projects, body.open-projects,
@@ -41,10 +40,6 @@ body.blog
width: 100% width: 100%
left: 0 left: 0
#project_context-header
span#status-bar
text-align: left
#project_nav-container #project_nav-container
+media-lg +media-lg
width: $project_nav-width * 1.33 width: $project_nav-width * 1.33
@@ -63,16 +58,12 @@ body.blog
#project_sidebar #project_sidebar
width: $project-sidebar-width width: $project-sidebar-width
z-index: $z-index-base + 6 z-index: $z-index-base + 6
box-shadow: inset -1px 0 0 0 $color-background
+media-xs +media-xs
width: 100% width: 100%
ul.project-tabs ul.project-tabs
background-color: $color-background-nav
margin: 0
padding: 0
list-style: none
position: fixed position: fixed
width: $project-sidebar-width width: $project-sidebar-width
top: $project_header-height top: $project_header-height
@@ -85,16 +76,9 @@ body.blog
width: 100% width: 100%
li li
display: flex
align-items: center
justify-content: center
width: $project-sidebar-width width: $project-sidebar-width
height: $project-sidebar-width height: $project-sidebar-width
position: relative position: relative
color: white
background-color: $color-background-nav
border-left: 2px solid transparent
transition: all 100ms ease-in-out
+media-xs +media-xs
border-bottom: 2px solid transparent border-bottom: 2px solid transparent
@@ -108,78 +92,33 @@ body.blog
&:first-child &:first-child
border-top: thin solid transparent border-top: thin solid transparent
&:hover &:hover,
background-color: $color-background-nav-light &.active
cursor: pointer cursor: pointer
+media-xs a
border-bottom: 2px solid $color-background-nav-light color: $primary
+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 a
width: $project-sidebar-width
height: $project-sidebar-width
display: flex
align-items: center align-items: center
color: $color-text
display: flex
justify-content: center 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%)
a
color: white
&.tabs-thumbnail &.tabs-thumbnail
height: $project-sidebar-width
border-bottom: 2px solid $color-background-nav-light
img img
width: $project-sidebar-width
height: $project-sidebar-width height: $project-sidebar-width
width: $project-sidebar-width
&.image
border-left: none
#project-loading #project-loading
position: absolute
width: $project-sidebar-width
height: $project-sidebar-width
display: flex
align-items: center align-items: center
display: flex
height: $project-sidebar-width
justify-content: center justify-content: center
i width: $project-sidebar-width
position: relative
top: -1px
font-size: 1em
color: white
#search-container #project_sidebar ul.project-tabs li.tabs-thumbnail #search-container #project_sidebar ul.project-tabs li.tabs-thumbnail
background-color: $color-background-nav-dark background-color: $color-background-nav-dark
@@ -199,7 +138,6 @@ body.blog
+media-xs +media-xs
width: initial width: initial
background-color: $project-sidebar-background
display: block display: block
left: 0 left: 0
position: relative position: relative
@@ -221,53 +159,34 @@ body.blog
#project_context-header #project_context-header
align-items: center 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 color: $color-text-dark-secondary
display: flex display: flex
height: $project_header-height height: $project_header-height
min-height: $project_header-height min-height: $project_header-height
position: fixed position: fixed
top: $project_header-height top: $project_header-height + 1
transition: box-shadow 250ms ease-in-out transition: box-shadow 250ms ease-in-out
width: auto
z-index: $z-index-base + 3 z-index: $z-index-base + 3
&.is-offset &.is-offset
box-shadow: 0 0 25px rgba(black, .2) box-shadow: 0 0 25px rgba(black, .2)
span#status-bar
text-align: left
#project_nav-header #project_nav-header
z-index: $z-index-base + 3 left: 0
position: absolute position: absolute
top: 0 top: 0
left: 0
height: $project_header-height
min-height: $project_header-height
width: 100% width: 100%
background-color: $color-background-light z-index: $z-index-base + 3
border-bottom: thin solid $color-background-dark
/* Name of the project */ /* Name of the project */
.project-title .project-title
width: 100%
max-width: 100%
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 +text-overflow-ellipsis
height: 100%
&:hover, &:active max-width: 100%
color: $color-primary width: 100%
text-decoration: none
outline: none
span#status-bar span#status-bar
position: absolute position: absolute
@@ -278,7 +197,6 @@ span#status-bar
color: $color-text-dark color: $color-text-dark
opacity: 0 opacity: 0
z-index: 1 z-index: 1
background-color: $color-background-light
font-weight: 400 font-weight: 400
white-space: nowrap white-space: nowrap
transition: all 250ms ease-in-out transition: all 250ms ease-in-out
@@ -293,7 +211,7 @@ span#status-bar
&.info &.info
color: $color-info color: $color-info
&.error &.error
color: $color-danger color: $danger
&.warning &.warning
color: $color-warning color: $color-warning
&.success &.success
@@ -320,71 +238,29 @@ span#project-edit-title
display: none display: none
ul.project-edit-tools ul.project-edit-tools
display: flex
align-items: center align-items: center
height: 100% display: flex
padding: 0
margin:
top: 0
left: auto
right: 5px
bottom: 0
color: $color-text-dark-hint
list-style-type: none list-style-type: none
font-size: .9em margin: 0 0 0 auto
padding: 0
+media-xs +media-xs
width: 100% width: 100%
margin: 0 auto margin: 0 auto
justify-content: space-around 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 li
margin: 0 a, button
padding: 0 padding: $dropdown-item-padding-y ($dropdown-item-padding-x / 2)
position: relative
float: left
user-select: none
a i
min-width: 110px padding-right: 10px
user-select: none
&:focus, &:active
text-decoration: none
outline: none
&.button-save &.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 &.field-error
a a
background-color: $color-danger background-color: $danger
border-color: $color-danger border-color: $danger
color: white color: white
&.saving &.saving
@@ -411,31 +287,6 @@ ul.project-edit-tools
&.button-edit &.button-edit
min-width: 80px 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 &.featured
a a
color: $color-warning color: $color-warning
@@ -445,111 +296,28 @@ ul.project-edit-tools
background-color: rgba($color-warning, .1) background-color: rgba($color-warning, .1)
&.disabled &.disabled
cursor: not-allowed +disabled-stripes
border-bottom: thin solid $color-text-dark-hint
a a
pointer-events: none +disabled-stripes
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
&.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
li
i
margin-right: 10px
&#item_delete
li i
margin-right: 0
// &:not(:last-of-type)
// border-right: thin solid darken($color-background, 50%)
/* ul.project-edit-tools */
/* 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)
&.dropdown
li li
padding: 0 padding: 0
clear: both
display: flex
align-items: center
width: 100%
a a
margin: 0 color: $body-color
padding: 10px 15px display: block
width: 100% padding: $dropdown-item-padding-y $dropdown-item-padding-x
border: 0 padding-left: 15px
font-size: .9em user-select: none
width: 100%
text-align: left
&:hover &:hover
color: $color-primary color: $primary
background-color: transparent text-decoration: none
&:active, &:focus
color: $color-primary
background-color: transparent
/* Icons per asset type. */
i i
display: inline-block
margin: 0 15px 0 0
&.icon-group:after &.icon-group:after
content: '\e80d' content: '\e80d'
&.icon-group_texture:after, &.icon-group_texture:after,
@@ -566,11 +334,6 @@ ul.project-edit-tools
content: '\f019' content: '\f019'
/* // Extra asset tools in dropdown */ /* // Extra asset tools in dropdown */
&.open
button
box-shadow: none
/* // Edit Asset buttons */ /* // Edit Asset buttons */
#project-loading #project-loading
@@ -607,9 +370,6 @@ ul.project_nav-edit-list
li li
background-color: $color-background background-color: $color-background
border-bottom: 1px solid $color-background-dark border-bottom: 1px solid $color-background-dark
font:
weight: 400
family: $font-body
color: $color-text-dark color: $color-text-dark
position: relative position: relative
@@ -663,9 +423,8 @@ ul.project_nav-edit-list
padding-top: $project_header-height padding-top: $project_header-height
#node-container #node-container
flex: 1
color: $color-text-dark
background-color: white background-color: white
flex: 1
/* For error messages (403) and other overlaid text*/ /* For error messages (403) and other overlaid text*/
#node-overlay #node-overlay
@@ -687,8 +446,6 @@ ul.project_nav-edit-list
/* Project context on the right of the navigation */ /* Project context on the right of the navigation */
/* Contains #project_context */ /* Contains #project_context */
#project_context-container #project_context-container
background-color: $color-background-nav
iframe#server_error iframe#server_error
width: 100% width: 100%
min-height: 800px min-height: 800px
@@ -700,11 +457,9 @@ ul.project_nav-edit-list
+media-xs +media-xs
margin-top: 0 margin-top: 0
background-color: $project-sidebar-background // margin-top: $project_header-height //so it's right below the project title.
border-right: thin solid $color-background overflow-y: auto // show vertical scrollbars when needed.
margin-top: $project_header-height padding: 0 0 5px 0 // some padding on top/bottom of jstree.
overflow-y: auto
padding: 0 0 5px 0// some padding on top/bottom of jstree
position: relative position: relative
&.edit &.edit
@@ -713,9 +468,7 @@ ul.project_nav-edit-list
/* Node Context */ /* Node Context */
=project-node-title =project-node-title
font: font-size: 1.5em
family: $font-body
size: 1.5em
color: $color-text-dark-primary color: $color-text-dark-primary
$node-preview-max-height-sm: 300px $node-preview-max-height-sm: 300px
@@ -825,7 +578,7 @@ $node-preview-max-height-lg: 700px
margin-left: auto margin-left: auto
&.pending &.pending
color: $color-danger color: $danger
section.node-preview.texture section.node-preview.texture
overflow: hidden overflow: hidden
@@ -897,7 +650,6 @@ $node-preview-max-height-lg: 700px
.node-title .node-title
color: $color-text-dark-primary color: $color-text-dark-primary
font: font:
family: $font-body
weight: 500 weight: 500
size: 1.5em size: 1.5em
+text-overflow-ellipsis +text-overflow-ellipsis
@@ -1054,6 +806,7 @@ section.node-preview-forbidden
justify-content: center justify-content: center
min-height: 400px min-height: 400px
position: relative position: relative
overflow: hidden
img img
height: 130% height: 130%
@@ -1075,13 +828,6 @@ section.node-preview-forbidden
span span
display: block display: block
a
color: $color-text-light
&.btn
border-color: white
color: white
hr hr
opacity: .5 opacity: .5
@@ -1128,9 +874,6 @@ section.node-preview.group
padding: 0 padding: 0
margin: 0 margin: 0
&.project
.node-details-title
padding: 10px 20px 0 20px
.node-details-description .node-details-description
+node-details-description +node-details-description
@@ -1164,7 +907,7 @@ section.node-preview.group
padding-left: 0 padding-left: 0
&.status-pending &.status-pending
color: $color-danger color: $danger
&.public &.public
color: $color-success color: $color-success
@@ -1354,7 +1097,7 @@ section.node-details-container
opacity: 1 opacity: 1
.error-node-type-not-found .error-node-type-not-found
color: $color-danger color: $danger
clear: both clear: both
a.learn-more a.learn-more
@@ -1491,8 +1234,6 @@ a.learn-more
section.node-children section.node-children
background-color: white
&.group, &.storage &.group, &.storage
flex: 1 flex: 1
padding: 10px 0 25px 20px padding: 10px 0 25px 20px
@@ -1911,7 +1652,6 @@ section.node-children
.node_index-collection-name .node_index-collection-name
font: font:
family: $font-body
size: 4em size: 4em
weight: 600 weight: 600
margin-bottom: -5px margin-bottom: -5px
@@ -2034,7 +1774,6 @@ section.node-children
color: $color-text-dark color: $color-text-dark
font: font:
size: 1.5em size: 1.5em
family: $font-body
weight: 500 weight: 500
text-decoration: none text-decoration: none
@@ -2101,15 +1840,15 @@ section.node-children
text-transform: capitalize text-transform: capitalize
&.error &.error
color: $color-danger color: $danger
background-color: $color-background-light background-color: $color-background-light
padding: 10px 15px padding: 10px 15px
border: thin solid lighten($color-danger, 10%) border: thin solid lighten($danger, 10%)
border-top: 2px solid $color-danger border-top: 2px solid $danger
border-bottom-left-radius: 3px border-bottom-left-radius: 3px
border-bottom-right-radius: 3px border-bottom-right-radius: 3px
label label
color: $color-danger color: $danger
font-weight: 500 font-weight: 500
&.file &.file
@@ -2176,7 +1915,7 @@ section.node-children
outline: none outline: none
&.field-error &.field-error
border-color: $color-danger border-color: $danger
.md-preview-loading .md-preview-loading
position: absolute position: absolute
@@ -2423,7 +2162,7 @@ section.node-children
.cancel .cancel
+button($color-warning, 3px) +button($color-warning, 3px)
.delete .delete
+button($color-danger, 3px) +button($danger, 3px)
.toggle .toggle
margin: 0 20px margin: 0 20px
@@ -2434,7 +2173,7 @@ section.node-children
&.cancel &.cancel
+button($color-warning, 3px) +button($color-warning, 3px)
&.delete &.delete
+button($color-danger, 3px) +button($danger, 3px)
&.create &.create
+button($color-success, 3px) +button($color-success, 3px)

View File

@@ -29,7 +29,6 @@ $search-hit-width_grid: 100px
font: font:
size: .9em size: .9em
weight: 400 weight: 400
family: $font-body
style: initial style: initial
width: 100% width: 100%
+text-overflow-ellipsis +text-overflow-ellipsis
@@ -416,9 +415,7 @@ $search-hit-width_grid: 100px
&.texture &.texture
.texture-title .texture-title
font: font-size: 2em
size: 2em
family: $font-body
padding: 15px 10px 10px 15px padding: 15px 10px 10px 15px
.node-row .node-row
background: white background: white

View File

@@ -68,13 +68,6 @@
background-color: lighten($provider-color-google, 7%) background-color: lighten($provider-color-google, 7%)
#settings #settings
+media-xs
flex-direction: column
align-items: stretch
display: flex
margin: 25px auto
#settings-sidebar #settings-sidebar
+media-xs +media-xs
width: 100% width: 100%

View File

@@ -26,7 +26,6 @@
display: inline-flex display: inline-flex
align-items: center align-items: center
justify-content: center justify-content: center
font-family: $font-body
padding: 5px 12px padding: 5px 12px
border-radius: $roundness border-radius: $roundness
@@ -83,6 +82,15 @@
text-shadow: none 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) @mixin overlay($from-color, $from-percentage, $to-color, $to-percentage)
position: absolute position: absolute
top: 0 top: 0
@@ -122,24 +130,17 @@
transform: translate(-50%, -50%) transform: translate(-50%, -50%)
=input-generic =input-generic
padding: 5px 5px 5px 0 // padding: 5px 5px 5px 0
color: $color-text-dark color: $color-text-dark
box-shadow: none
font-family: $font-body
border: thin solid transparent
border-radius: 0
border-bottom-color: $color-background-dark
background-color: transparent background-color: transparent
transition: border-color 150ms ease-in-out, box-shadow 150ms ease-in-out
&:hover &:hover
border-bottom-color: $color-background border-bottom-color: $color-background
&:focus &:focus
outline: 0 outline: 0
border: thin solid transparent border-color: $primary
border-bottom-color: $color-primary box-shadow: none
box-shadow: 0 1px 0 0 $color-primary
=label-generic =label-generic
color: $color-text-dark-primary color: $color-text-dark-primary
@@ -354,7 +355,6 @@
+clearfix +clearfix
color: darken($color-text-dark, 5%) color: darken($color-text-dark, 5%)
font: font:
family: $font-body
weight: 300 weight: 300
size: 1.2em size: 1.2em
@@ -405,6 +405,10 @@
bottom: 25px bottom: 25px
top: 25px top: 25px
&.emoji
display: inline-block
padding: initial
h2 h2
margin-bottom: 15px margin-bottom: 15px
@@ -443,11 +447,12 @@
li li
margin-bottom: 7px margin-bottom: 7px
img img
display: block display: block
padding: padding:
top: 25px
bottom: 10px bottom: 10px
top: 25px
ul, ul li ul ul, ul li ul
margin-top: 15px margin-top: 15px
@@ -455,10 +460,13 @@
code, kbd, pre, samp code, kbd, pre, samp
background-color: rgba($color-primary, .05) background-color: rgba($color-primary, .05)
color: $color-primary color: darken($color-primary, 15%)
font-size: inherit font-size: inherit
white-space: pre-line white-space: pre-line
code
background-color: transparent
kbd kbd
border: border:
color: rgba($color-primary, .33) color: rgba($color-primary, .33)
@@ -652,3 +660,17 @@
transition: all 1s ease-out transition: all 1s ease-out
img img
transition: all 1s ease-out transition: all 1s ease-out
.cursor-pointer
cursor: pointer
.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

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,96 @@
@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 _config
@import _utils @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 _comments @import _comments
@import _error @import _error
@import _search @import _search
.container-fluid.blog @import components/base
padding: 0 @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
#blog_container #blog_container
+media-xs +media-xs
flex-direction: column flex-direction: column
padding-top: 0 padding-top: 0
display: flex
padding:
bottom: 15px
video video
max-width: 100% max-width: 100%
@@ -26,7 +101,6 @@
.form-group .form-group
position: relative position: relative
margin: 0 auto 30px auto margin: 0 auto 30px auto
font-family: $font-body
input, textarea, select input, textarea, select
+input-generic +input-generic
@@ -167,50 +241,7 @@
#blog_post-edit-container #blog_post-edit-container
padding: 25px padding: 25px
#blog_index-container, .blog_index-item
#blog_post-create-container,
#blog_post-edit-container
+container-box
width: 75%
+media-xs
border-radius: 0
width: 100%
clear: both
display: block
+media-sm
width: 100%
+media-md
width: 100%
+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
+media-lg
max-width: 780px
+media-md
max-width: 780px
+media-sm
max-width: 780px
margin: 15px auto
&:hover
.item-info a
color: $color-primary
.item-picture .item-picture
position: relative position: relative
width: 100% width: 100%
@@ -237,24 +268,8 @@
+media-lg +media-lg
min-height: 250px min-height: 250px
.item-title
color: $color-text-dark
display: block
font:
family: $font-body
size: 1.8em
padding: 10px 25px 10px
ul.meta
+list-meta
font-size: .9em
padding: 0px 25px 5px
.item-content .item-content
+node-details-description +node-details-description
font-size: 1.3em
padding: 15px 25px 25px
+media-xs +media-xs
padding: padding:
@@ -276,42 +291,29 @@
left: 10px left: 10px
right: 10px right: 10px
.button-create, #blog_index-container,
.button-edit #blog_post-create-container,
+button($color-success, 3px, true) #blog_post-edit-container
+container-box
width: 75%
+media-xs
border-radius: 0
width: 100%
clear: both
display: block
+media-sm
width: 100%
+media-md
width: 100%
+media-lg
width: 100%
.item-picture+.button-back+.button-edit .item-picture+.button-back+.button-edit
right: 20px right: 20px
top: 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 #blog_post-edit-form
padding: 0 padding: 0
@@ -371,12 +373,6 @@
+media-lg +media-lg
width: 25% width: 25%
.button-create
display: block
width: 100%
+button($color-success, 6px)
margin: 0
.button-back .button-back
+button($color-info, 6px, true) +button($color-info, 6px, true)
display: block display: block
@@ -475,105 +471,6 @@
text-decoration: none text-decoration: none
color: $color-primary 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
&: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
family: $font-body
.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
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 .blog-archive-navigation
+media-xs +media-xs
font-size: 1em font-size: 1em
@@ -603,16 +500,7 @@
color: $color-text-dark-secondary color: $color-text-dark-secondary
pointer-events: none pointer-events: none
.blog-action // Specific tweaks for blogs in the context of a project.
display: flex
padding: 10px
position: absolute
right: 0
top: 0
z-index: 1
// Specific tweaks for blogs in the context of a project
#project_context #project_context
.blog_index-item .blog_index-item
+media-xs +media-xs
@@ -637,3 +525,29 @@
.blog-archive-navigation .blog-archive-navigation
margin-left: 35px 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

View File

@@ -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

View File

@@ -0,0 +1,29 @@
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
.page-content
background-color: $white
.container-box
+container-box

View File

@@ -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

View File

@@ -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: 15px 0
.card-fade
img
opacity: .8
transition: opacity ease-in-out 150ms
&:hover
img
opacity: 1

View File

@@ -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

View File

@@ -0,0 +1,13 @@
// 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
padding: $dropdown-item-padding-y
// Open dropdown on mouse hover dropdowns in the navbar.
nav .dropdown:hover
ul.dropdown-menu
display: block

View File

@@ -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

View File

@@ -0,0 +1,117 @@
/* FOOTER */
.footer-wrapper
background-color: $color-background
position: relative
&: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
&: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

View File

@@ -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

View File

@@ -0,0 +1,38 @@
/* 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
&.disabled
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

View File

@@ -0,0 +1,28 @@
// Mainly overrides bootstrap jumbotron settings
.jumbotron
background-size: cover
border-radius: 0
padding-top: 10em
padding-bottom: 10em
// 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)
&:hover
text-decoration: none

View File

@@ -0,0 +1,201 @@
// Navigation.
.navbar
box-shadow: inset 0 -2px $color-background
.navbar,
nav.sidebar
border: none
color: $color-text-dark-secondary
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%
.navbar-item
align-items: center
display: flex
user-select: none
color: inherit
+media-sm
padding-left: 10px
padding-right: 10px
&:hover, &:focus
color: $primary
background-color: transparent
box-shadow: inset 0 -3px 0 $primary
text-decoration: none
&:focus
box-shadow: inset 0 -3px 0 $primary
&.active
color: $primary
box-shadow: inset 0 -3px 0 $primary
li
user-select: none
position: relative
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
min-width: 60px // navbar avatar size
span.fa-stack
position: absolute
top: 50%
left: 50%
transform: translate(-50%, -50%)
ul.dropdown-menu
li
a
white-space: nowrap
&:hover
box-shadow: none // removes underline
&.subitem // e.g. "Not Sintel? Log out"
font-size: .8em
padding-top: 0
text-transform: initial
i
width: 30px
&.subscription-status
&.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
// Secondary navigation for
.nav-secondary
align-items: center
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: inset 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
color: inherit
padding-left: 4px
&:hover
color: $primary
nav.navbar
.navbar-collapse
> ul > li > .navbar-item
padding: $navbar-nav-link-padding-x
height: $nav-link-height
.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
.nav-tabs .dropdown-menu, .nav-pills .dropdown-menu
margin-top: 0
.navbar+.page-content
padding-top: $nav-link-height

View File

@@ -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

View File

@@ -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%)

View File

@@ -0,0 +1,87 @@
#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
top: 4px
left: 10px
cursor: pointer
&:after
@extend .tooltip-inner
content: 'Use advanced search...'
font-size: .85em
font-style: normal
left: -10px
opacity: 0
pointer-events: none
position: absolute
top: 30px
transition: top 150ms ease-in-out, opacity 150ms ease-in-out
width: 150px
&: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)

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
.tooltip
transition: none
.tooltip-inner
white-space: nowrap

View File

@@ -1,7 +1,74 @@
/* 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-face
font-family: 'pillar-font' font-family: 'pillar-font'
src: url('../font/pillar-font.eot?55726379') src: url('#{$pillar-font-path}/pillar-font.woff?55726379') format("woff"), url('#{$pillar-font-path}/pillar-font.woff2?55726379') format("woff2")
src: url('../font/pillar-font.eot?55726379#iefix') format("embedded-opentype"), url('../font/pillar-font.woff2?55726379') format("woff2"), url('../font/pillar-font.woff?55726379') format("woff")
font-weight: normal font-weight: normal
font-style: normal font-style: normal

View File

@@ -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 _config
@import _utils @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 */ /* Generic styles (comments, notifications, etc) come from base.css */
@import _notifications
@import _comments
@import _project @import _project
@import _project-sharing @import _project-sharing
@import _project-dashboard @import _project-dashboard
@import _user @import _user
@import _search
@import _organizations @import _organizations
@import _search
/* services, about, etc */ /* services, about, etc */
@import _pages @import _pages

View File

@@ -1,9 +1,8 @@
/* jsTree overrides */ /* jsTree overrides */
$tree-color-text: $color-text-dark-primary $tree-color-text: $color-text-dark-primary
$tree-color-highlight: hsl(hue($color-background-active), 40%, 50%) $tree-color-highlight: $color-primary-accent
$tree-color-highlight-background: hsl(hue($color-background-active), 40%, 50%) $tree-color-highlight-background: $white
$tree-color-highlight-background-text: white $tree-color-highlight-background-text: $primary
.jstree-default .jstree-default
/* list item */ /* list item */
@@ -63,49 +62,48 @@ $tree-color-highlight-background-text: white
&.jstree-open &.jstree-open
/* Text of children for an open tree (like a folder) */ /* Text of children for an open tree (like a folder) */
.jstree-children > .jstree-node .jstree-children > .jstree-node
padding-left: 15px !important padding-left: 16px !important
.jstree-icon:empty .jstree-icon:empty
left: 20px !important left: 20px !important
// Tweaks for specific icons // Tweaks for specific icons
&.pi-file-archive &.pi-file-archive
left: 22px !important left: 25px !important
&.pi-folder &.pi-folder
left: 21px !important left: 20px !important
font-size: .9em !important font-size: .9em !important
&.pi-film-thick &.pi-splay
left: 22px !important left: 20px !important
font-size: .85em !important font-size: .85em !important
.jstree-anchor .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 */ /* Closed Folder */
// &.jstree-closed // &.jstree-closed
&.jstree-open .jstree-icon.jstree-ocl, &.jstree-open .jstree-icon.jstree-ocl,
&.jstree-closed .jstree-icon.jstree-ocl &.jstree-closed .jstree-icon.jstree-ocl
float: left
min-width: 30px
opacity: 0
position: absolute position: absolute
z-index: 1 z-index: 1
opacity: 0
min-width: 30px
float: left
/* The text of the last level item */ /* The text of the last level item */
.jstree-anchor .jstree-anchor
+media-xs +media-xs
width: 98%
padding: 0 !important padding: 0 !important
width: 98%
border: none border: none
font-size: 13px
height: inherit height: inherit
line-height: 26px line-height: 24px
overflow: hidden overflow: hidden
padding-left: 28px padding-left: 28px
padding-right: 10px padding-right: 10px
text-overflow: ellipsis text-overflow: ellipsis
transition: none
transition: color 50ms ease-in-out, background-color 100ms ease-in-out
white-space: nowrap white-space: nowrap
width: 100% width: 100%
@@ -113,7 +111,7 @@ $tree-color-highlight-background-text: white
&:after &:after
content: '\e83a' !important content: '\e83a' !important
font-family: 'pillar-font' font-family: 'pillar-font'
color: white color: $tree-color-highlight-background-text
display: none display: none
position: absolute position: absolute
right: 7px right: 7px
@@ -121,31 +119,31 @@ $tree-color-highlight-background-text: white
// Icon, not selected // Icon, not selected
.jstree-icon .jstree-icon
color: $color-text-dark-secondary color: $tree-color-text
font-size: 95% !important
margin: 0 !important margin: 0 !important
/* Selected item */ /* Selected item */
&.jstree-clicked &.jstree-clicked
background-color: $tree-color-highlight-background !important background-color: $tree-color-highlight-background !important
color: white !important color: $tree-color-highlight-background-text !important
font-weight: bold
&:after &:after
display: block display: block
color: white !important color: $tree-color-highlight-background-text !important
.jstree-ocl, .jstree-ocl,
.jstree-icon .jstree-icon
color: white color: $tree-color-highlight-background-text
/* hover an active item */ /* hover an active item */
&.jstree-hovered &.jstree-hovered
background-color: lighten($tree-color-highlight-background, 10%) !important background-color: lighten($tree-color-highlight-background, 10%) !important
box-shadow: none box-shadow: none
color: white !important color: $tree-color-highlight-background-text !important
&.jstree-hovered .jstree-icon &.jstree-hovered .jstree-icon
color: white !important color: $tree-color-highlight-background-text !important
.jstree-hovered .jstree-hovered
background-color: rgba($tree-color-highlight, .1) !important background-color: rgba($tree-color-highlight, .1) !important
@@ -184,8 +182,8 @@ $tree-color-highlight-background-text: white
position: absolute position: absolute
&:empty &:empty
line-height: 26px line-height: 24px
left: 5px left: 3px
&.is_subscriber &.is_subscriber
.jstree-node .jstree-node
@@ -269,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-closed .jstree-icon.jstree-ocl + .jstree-anchor,
.jstree-default .jstree-node.jstree-open .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 */ /* hovered text */
.jstree-default .jstree-hovered, .jstree-default .jstree-hovered,
@@ -280,11 +278,11 @@ $tree-color-highlight-background-text: white
a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked a.jstree-anchor.jstree-clicked+ul li a.jstree-anchor.jstree-clicked
background-color: rgba($tree-color-highlight-background, .8) !important 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 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 background-color: rgba($tree-color-highlight-background, .8) !important
color: white !important color: $tree-color-highlight-background-text !important
i.jstree-icon.jstree-ocl i.jstree-icon.jstree-ocl
color: rgba($tree-color-text, .5) !important color: rgba($tree-color-text, .5) !important

View File

@@ -1,5 +1,5 @@
$videoplayer-controls-color: white $videoplayer-controls-color: white
$videoplayer-background-color: $color-background-nav $videoplayer-background-color: $black
.video-js .video-js
.vjs-big-play-button:before, .vjs-control:before, .vjs-modal-dialog .vjs-big-play-button:before, .vjs-control:before, .vjs-modal-dialog

View File

@@ -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 _config
@import _utils @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 _comments
@import _project @import _project
@import _project-sharing @import _project-sharing
@import _project-dashboard @import _project-dashboard

View File

@@ -7,8 +7,7 @@ $color-theatre-background-dark: darken($color-theatre-background, 5%)
$theatre-width: 350px $theatre-width: 350px
body.theatre, body.theatre
body.theatre .container-page
background-color: $color-theatre-background background-color: $color-theatre-background
nav.navbar nav.navbar
+media-lg +media-lg
@@ -26,6 +25,7 @@ body.theatre .container-page
display: flex display: flex
align-items: center align-items: center
justify-content: center justify-content: center
.page-body .page-body
height: 100% height: 100%
width: 100% width: 100%

View File

@@ -6,7 +6,7 @@
| {% if node_type_name == 'group' %} | {% if node_type_name == 'group' %}
| {% set node_type_name = 'folder' %} | {% set node_type_name = 'folder' %}
| {% endif %} | {% endif %}
li(class="button-{{ node_type['name'] }}") li.dropdown-item(class="button-{{ node_type['name'] }}")
a.item_add_node( a.item_add_node(
href="#", href="#",
title="{{ node_type['description'] }}", title="{{ node_type['description'] }}",
@@ -19,7 +19,8 @@ li(class="button-{{ node_type['name'] }}")
| {% elif node_type_name == 'group_hdri' %} | {% elif node_type_name == 'group_hdri' %}
| HDRi Folder | HDRi Folder
| {% else %} | {% else %}
| {{ node_type_name }} span.text-capitalize
|{{ node_type_name }}
| {% endif %} | {% endif %}
| {% endif %} | {% endif %}
| {% endfor %} | {% endfor %}

View File

@@ -28,15 +28,15 @@
span Add files... span Add files...
input(type='file', name='file', multiple='') input(type='file', name='file', multiple='')
button.btn.btn-primary.start(type='submit') button.btn.btn-outline-primary.start(type='submit')
i.pi-upload 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 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 i.pi-trash
span Delete span Delete

View File

@@ -23,7 +23,7 @@ script#template-upload(type="text/x-tmpl").
</button> </button>
{% } %} {% } %}
{% if (!i) { %} {% if (!i) { %}
<button class="btn btn-warning cancel"> <button class="btn btn-outline-secondary cancel">
<i class="ion-close-round"></i> <i class="ion-close-round"></i>
<span>Cancel</span> <span>Cancel</span>
</button> </button>
@@ -61,7 +61,7 @@ script#template-download(type="text/x-tmpl").
</td> </td>
<td> <td>
{% if (file.deleteUrl) { %} {% if (file.deleteUrl) { %}
<button class="btn btn-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"{% if (file.deleteWithCredentials) { %} data-xhr-fields='{"withCredentials":true}'{% } %}> <button class="btn btn-outline-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"{% if (file.deleteWithCredentials) { %} data-xhr-fields='{"withCredentials":true}'{% } %}>
<i class="ion-trash-b"></i> <i class="ion-trash-b"></i>
<span>Delete</span> <span>Delete</span>
</button> </button>
@@ -71,7 +71,7 @@ script#template-download(type="text/x-tmpl").
Create Create
</div> </div>
{% } else { %} {% } else { %}
<button class="btn btn-warning cancel"> <button class="btn btn-outline-secondary cancel">
<i class="ion-close-round"></i> <i class="ion-close-round"></i>
<span>Cancel</span> <span>Cancel</span>
</button> </button>

View File

@@ -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 %}

View File

@@ -9,5 +9,5 @@
.modal-body .modal-body
| ... | ...
.modal-footer .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 button.btn.btn-primary(type='button') Save changes

View File

@@ -66,7 +66,6 @@ html(lang="en")
| {% if not title %}{% set title="default" %}{% endif %} | {% if not title %}{% set title="default" %}{% endif %}
body(class="{{ title }}") body(class="{{ title }}")
.container-page
.page-content .page-content
.page-body .page-body
| {% block body %}{% endblock %} | {% block body %}{% endblock %}

View File

@@ -1,7 +1,7 @@
| {% block menu_body %} | {% block menu_body %}
| {% if current_user.is_authenticated %} | {% if current_user.is_authenticated %}
li(class="dropdown") li.dropdown
| {% block menu_avatar %} | {% block menu_avatar %}
a.navbar-item.dropdown-toggle(href="#", data-toggle="dropdown", title="{{ current_user.email }}") a.navbar-item.dropdown-toggle(href="#", data-toggle="dropdown", title="{{ current_user.email }}")
img.gravatar( img.gravatar(
@@ -9,42 +9,38 @@ li(class="dropdown")
alt="Avatar") alt="Avatar")
| {% endblock menu_avatar %} | {% endblock menu_avatar %}
ul.dropdown-menu ul.dropdown-menu.dropdown-menu-right
| {% if not current_user.has_role('protected') %} | {% if not current_user.has_role('protected') %}
| {% block menu_list %} | {% block menu_list %}
li li
a.navbar-item( a.navbar-item(
href="{{ url_for('projects.home_project') }}" href="{{ url_for('projects.home_project') }}"
title="Home") title="Home")
i.pi-home | #[i.pi-home] Home
| Home
li li
a.navbar-item( a.navbar-item(
href="{{ url_for('projects.index') }}" href="{{ url_for('projects.index') }}"
title="My Projects") title="My Projects")
i.pi-star | #[i.pi-star] My Projects
| My Projects
| {% if current_user.has_organizations() %} | {% if current_user.has_organizations() %}
li li
a.navbar-item( a.navbar-item(
href="{{ url_for('pillar.web.organizations.index') }}" href="{{ url_for('pillar.web.organizations.index') }}"
title="My Organizations") title="My Organizations")
i.pi-users | #[i.pi-users] My Organizations
| My Organizations
| {% endif %} | {% endif %}
li li
a.navbar-item( a.navbar-item(
href="{{ url_for('settings.profile') }}" href="{{ url_for('settings.profile') }}"
title="Settings") title="Settings")
i.pi-cog | #[i.pi-cog] Settings
| Settings
| {% endblock menu_list %} | {% endblock menu_list %}
li.divider(role="separator") li.dropdown-divider(role="separator")
| {% endif %} | {% endif %}
li li
@@ -59,8 +55,9 @@ li(class="dropdown")
| {% else %} | {% else %}
li.nav-item-sign-in li.pt-1.pr-1
a.navbar-item(href="{{ url_for('users.login') }}") a.btn.btn-sm.btn-outline-primary.px-3(
| Log in href="{{ url_for('users.login') }}")
| Log In
| {% endif %} | {% endif %}
| {% endblock menu_body %} | {% endblock menu_body %}

View File

@@ -5,21 +5,21 @@ section.node-preview-forbidden
div div
p Available to Blender Cloud subscribers p Available to Blender Cloud subscribers
hr hr.bg-white
| {% if current_user.has_cap('can-renew-subscription') %} | {% if current_user.has_cap('can-renew-subscription') %}
p p
small You have a subscription, it just needs to be renewed. 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 | #[i.pi-heart] Renew Subscription
| {% else %} | {% else %}
p p
small Support Blender and get awesome stuff! 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 | #[i.pi-heart] Get a Subscription
| {% endif %} | {% endif %}
| {% if current_user.is_anonymous %} | {% if current_user.is_anonymous %}
p(style="margin-top: 15px") p(style="margin-top: 15px")
small 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 %} | {% endif %}

View File

@@ -61,7 +61,7 @@ script(type="text/javascript").
} }
{% if node.has_method('PUT') %} {% if node.has_method('PUT') %}
$('.project-mode-view').show(); $('.project-mode-view').displayAs('inline-block');
{% else %} {% else %}
$('.project-mode-view').hide(); $('.project-mode-view').hide();
{% endif %} {% endif %}

View File

@@ -23,7 +23,7 @@ section.node-preview.video
| {% block node_download %} | {% block node_download %}
| {% if node.file_variations %} | {% if node.file_variations %}
button.btn.btn-default.dropdown-toggle( button.btn.btn-outline-secondary.dropdown-toggle(
type="button", type="button",
data-toggle="dropdown", data-toggle="dropdown",
aria-haspopup="true", aria-haspopup="true",

View File

@@ -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('l', api=api) }}")
| {% endif %}
| {% 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 }}
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(
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-default.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 %}

View File

@@ -2,10 +2,7 @@
| {% import 'nodes/custom/blog/_macros.html' as blogmacros %} | {% import 'nodes/custom/blog/_macros.html' as blogmacros %}
| {% block body %} | {% block body %}
.container-fluid .container
#blog_container.cloud-blog
#blog_index-container
.blog_index-header
h3 Blog Archive h3 Blog Archive
| {{ blogmacros.render_archive(project, posts, posts_meta) }} | {{ blogmacros.render_archive(project, posts, posts_meta) }}

View File

@@ -9,9 +9,7 @@ link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel=
| {% endblock %} | {% endblock %}
| {% block project_context %} | {% block project_context %}
#blog_container | {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta) }}
#blog_index-container.expand-image-links
| {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta) }}
| {% endblock %} | {% endblock %}
| {% block project_tree %} | {% block project_tree %}

View File

@@ -10,10 +10,7 @@ link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css')
| {% endblock css %} | {% endblock css %}
| {% block body %} | {% block body %}
.container-fluid.blog | {{ blogmacros.render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=pages) }}
#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) }}
| {% endblock %} | {% endblock %}
| {% block footer_scripts %} | {% block footer_scripts %}
@@ -25,7 +22,7 @@ script.
/* Expand images when their link points to a jpg/png/gif */ /* Expand images when their link points to a jpg/png/gif */
/* TODO: De-duplicate code from view post */ /* TODO: De-duplicate code from view post */
var page_overlay = document.getElementById('page-overlay'); 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(); e.preventDefault();
var href = $(this).parent().attr('href'); var href = $(this).parent().attr('href');

View File

@@ -32,14 +32,13 @@
.comment-reply-preview-md .comment-reply-preview-md
.comment-reply-info .comment-reply-info
.comment-action-cancel( .comment-action-cancel(
type="button",
title="{{ _('cancel') }}") title="{{ _('cancel') }}")
span {{ _('cancel') }} span {{ _('cancel') }}
a( a(
title="{{ _('Handy guide of Markdown syntax') }}", title="{{ _('Handy guide of Markdown syntax') }}",
target="_blank", target="_blank",
href="https://guides.github.com/features/mastering-markdown/") href="http://commonmark.org/help/")
span {{ _('markdown cheatsheet') }} span {{ _('markdown cheatsheet') }}
| {% endblock can_post_comment %} | {% endblock can_post_comment %}

View File

@@ -28,7 +28,7 @@ li.node-details-meta-list-item
| {% block node_download %} | {% block node_download %}
| {% if node.properties.files %} | {% if node.properties.files %}
button.btn.btn-default.dropdown-toggle( button.btn.btn-outline-secondary.dropdown-toggle(
title="Download HDRI", title="Download HDRI",
type="button", type="button",
data-toggle="dropdown", data-toggle="dropdown",

View File

@@ -3,7 +3,7 @@
| {% block body %} | {% block body %}
| {% if node.picture %} | {% if node.picture %}
header header
img.header(src="{{ node.picture.thumbnail('l', api=api) }}") img.header(src="{{ node.picture.thumbnail('h', api=api) }}")
| {% endif %} | {% endif %}
| {% block navbar_secondary %} | {% block navbar_secondary %}
| {{ super() }} | {{ super() }}

View File

@@ -68,9 +68,9 @@
| {% endif %} | {% endif %}
| {% endfor %} | {% endfor %}
input.btn.btn-default.button-create(type='submit', value='Create {{ node_type.name }}') input.btn.btn-outline-secondary(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 | Back to Blog
#blog_post-create-container #blog_post-create-container

View File

@@ -1,18 +1,8 @@
| {% import 'nodes/custom/blog/_macros.html' as blogmacros %} | {% 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 #comments-embed.comments-compact
.blog-action
| {% if node.has_method('PUT') %}
a.btn.btn-default.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 .comments-list-loading
i.pi-spin i.pi-spin

View File

@@ -26,8 +26,7 @@ link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css')
| {% set title = 'blog' %} | {% set title = 'blog' %}
| {% block body %} | {% block body %}
.container-fluid.blog | {% include 'nodes/custom/post/view_embed.html' %}
| {% include 'nodes/custom/post/view_embed.html' %}
| {% endblock %} | {% endblock %}

View File

@@ -91,11 +91,11 @@
a(href="{{ f.file.link }}",, a(href="{{ f.file.link }}",,
title="Download texture", title="Download texture",
download="{{ f.file.filename }}") download="{{ f.file.filename }}")
button.btn.btn-default(type="button") button.btn.btn-outline-secondary(type="button")
i.pi-download i.pi-download
| Download | Download
| {% else %} | {% else %}
button.btn.btn-default.disabled.sorry(type="button") button.btn.btn-outline-secondary.disabled.sorry(type="button")
i.pi-lock i.pi-lock
| Download | Download
| {% endif %} | {% endif %}

View File

@@ -38,18 +38,21 @@
| {% elif field.type == 'HiddenField' %} | {% elif field.type == 'HiddenField' %}
| {{ field }} | {{ field }}
| {% elif field.name == 'attachments' %} | {% elif field.name == 'attachments' %}
hr
#attachments-actions #attachments-actions
.btn.btn-info#attachments-action-add .btn.btn-info#attachments-action-add
i.pi-plus i.pi-plus
| Add New Attachment | 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 | 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]). | (#[a(href='https://pillarframework.org/shortcodes/#attachments', target='_blank') help]).
| This shortcode is placed on your copy-paste buffer by clicking "Copy to clipboard". | This shortcode is placed on your copy-paste buffer by clicking "Copy to clipboard".
| {{ render_field(field, field.name) }} | {{ render_field(field, field.name) }}
hr
| {% elif field.name == 'files' %} | {% elif field.name == 'files' %}
#files-actions #files-actions
@@ -66,20 +69,23 @@
| {% endfor %} | {% endfor %}
ul.project-edit-tools.bottom hr
ul.project-edit-tools.justify-content-end.h-auto
li.button-cancel 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);", href="javascript:void(0);",
title="Cancel changes") title="Cancel changes")
i.button-cancel-icon.pi-cancel i.button-cancel-icon.pi-cancel
| Cancel | Cancel
li.button-save 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);", href="javascript:void(0);",
title="Save changes") title="Save changes")
i.button-save-icon.pi-check i.button-save-icon.pi-check
| Save Changes | 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.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.iframe-transport.min.js') }}")
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.fileupload.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.fileupload.min.js') }}")

View File

@@ -1,210 +1,10 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
| {% set title = 'organizations' %} | {% set title = 'organizations' %}
| {% block page_title %}Organizations{% endblock %} | {% 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 %} | {% block body %}
.dashboard-container .p-5.text-center
section.dashboard-main h2 Organizations Index
| {{ navigation_tabs(title) }} .lead.
See Blender Cloud template for reference.
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-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('<p class="text-danger">Opening ' + item_type + ' failed. There possibly was ' +
'an error connecting to the server. Please check your network connection and ' +
'try again.</p>');
}
});
// 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 = $('<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 %}
| {% endblock %} | {% endblock %}

View File

@@ -75,7 +75,7 @@
script $('#admin-picker').hide(); script $('#admin-picker').hide();
.input-group .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 i.pi-check
| Save Changes | Save Changes
| {% else %} | {% else %}

View File

@@ -1,8 +1,7 @@
| {% macro render_secondary_navigation(project, pages=None) %} | {% macro render_secondary_navigation(project, pages=None) %}
nav.navbar-secondary nav.navbar-secondary
.navbar-container
nav.collapse.navbar-collapse nav.collapse.navbar-collapse
ul.nav.navbar-nav.navbar-right ul.navbar-nav.navbar-right
li li
a.navbar-item( a.navbar-item(
href="{{ url_for('projects.view', project_url=project.url) }}", href="{{ url_for('projects.view', project_url=project.url) }}",
@@ -26,9 +25,15 @@ nav.navbar-secondary
| {% endfor %} | {% endfor %}
| {% endif %} | {% endif %}
| {% if project.nodes_featured %} | {% 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 li
a.navbar-item( a.navbar-item(
href="{{ url_for('projects.view_node', project_url=project.url, node_id=project.nodes_featured[0]) }}", href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}",
title="Explore {{ project.name }}", title="Explore {{ project.name }}",
class="{% if category == 'blog' %}active{% endif %}") class="{% if category == 'blog' %}active{% endif %}")
span Explore span Explore

View File

@@ -7,17 +7,16 @@ span#project-edit-title
| Edit Project | Edit Project
ul.project-edit-tools ul.project-edit-tools
// Edit Mode // Edit Mode
li.button-cancel 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)}}", href="{{url_for('projects.view', project_url=project.url, _external=True)}}",
title="Cancel changes") title="Cancel changes")
i.button-cancel-icon.pi-back i.button-cancel-icon.pi-angle-left
| Go to Project | Go to Project
li.button-save li.button-save
a#item_save.project-mode-edit( a#item_save.project-mode-edit.btn.btn-sm.btn-outline-success.mx-2(
href="#", href="#",
title="Save changes") title="Save changes")
i.button-save-icon.pi-check i.button-save-icon.pi-check
@@ -80,16 +79,18 @@ ul.project-edit-tools
| {% endfor %} | {% endfor %}
ul.project-edit-tools.bottom hr
ul.project-edit-tools.justify-content-end.h-auto
li.button-cancel 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)}}", href="{{url_for('projects.view', project_url=project.url, _external=True)}}",
title="Cancel changes") title="Cancel changes")
i.button-cancel-icon.pi-back i.button-cancel-icon.pi-angle-left
| Go to Project | Go to Project
li.button-save li.button-save
a#item_save.project-mode-edit( a#item_save.project-mode-edit.btn.btn-outline-success.ml-2(
href="#", href="#",
title="Save changes") title="Save changes")
i.button-save-icon.pi-check i.button-save-icon.pi-check
@@ -97,6 +98,8 @@ ul.project-edit-tools
| {% endblock %} | {% endblock %}
| {% block footer_container %}{% endblock %}
| {% block footer_scripts %} | {% 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.ui.widget.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.iframe-transport.min.js') }}") script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.iframe-transport.min.js') }}")
@@ -105,7 +108,8 @@ script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets
script(type="text/javascript"). script(type="text/javascript").
$('.project-mode-edit').show(); // Show edit mode buttons (save, cancel).
$('.project-mode-edit').displayAs('inline-block');
ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''}); ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''});
var convert = new Markdown.getSanitizingConverter().makeHtml; var convert = new Markdown.getSanitizingConverter().makeHtml;

View File

@@ -5,7 +5,7 @@
#project-container #project-container
#project-side-container #project-side-container
#project_sidebar #project_sidebar
ul.project-tabs ul.project-tabs.p-0
li.tabs-thumbnail( li.tabs-thumbnail(
title="About", title="About",
data-toggle="tooltip", data-toggle="tooltip",
@@ -51,13 +51,13 @@
#project_nav #project_nav
#project_nav-container #project_nav-container
#project_nav-header #project_nav-header.bg-white
.project-title a.project-title.p-2.font-weight-bold.text-dark(
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {{ project.name }} | {{ project.name }}
// TODO - make list a macro // TODO - make list a macro
#project_tree.edit #project_tree.edit.bg-white
ul.project_nav-edit-list ul.project_nav-edit-list
li(class="{% if title == 'edit' %}active{% endif %}") li(class="{% if title == 'edit' %}active{% endif %}")
a(href="{{ url_for('projects.edit', project_url=project.url) }}") a(href="{{ url_for('projects.edit', project_url=project.url) }}")
@@ -77,10 +77,9 @@
i(class="pi-{{ext.icon}}") i(class="pi-{{ext.icon}}")
| {{ext.name | title}} | {{ext.name | title}}
| {% endfor %} | {% endfor %}
.project_split(title="Toggle Navigation [T]")
#project_context-container #project_context-container
#project_context-header #project_context-header.bg-white
span#status-bar span#status-bar
| {% block project_context_header %} | {% block project_context_header %}
| {% endblock %} | {% endblock %}

View File

@@ -19,7 +19,7 @@ form(
.col-md-9 .col-md-9
h3 Editing: {{ node_type['name'] }} h3 Editing: {{ node_type['name'] }}
.col-md-3 .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 | Save Changes
.row .row
@@ -46,7 +46,7 @@ form(
| {% endif %} | {% endif %}
| {% endfor %} | {% endfor %}
button.js-form-save.btn.btn-success.pull-right button.js-form-save.btn.btn-outline-success.pull-right
| Save Changes | Save Changes

View File

@@ -80,7 +80,7 @@ section.nav-tabs__tab.active#tab-images
Image Sharing requires a Blender Cloud subscription. Image Sharing requires a Blender Cloud subscription.
.buttons .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 | Join Now
| {% endif %} | {% endif %}
| {% endblock %} | {% endblock %}

View File

@@ -1,43 +1,8 @@
| {% extends 'projects/home_layout.html' %} | {% extends 'projects/home_layout.html' %}
| {% set subtab = 'blender_sync' %}
| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %}
| {% block currenttab %} | {% block currenttab %}
section.nav-tabs__tab.active#tab-blender_sync .p-5.text-center
.tab_header-container h2 Home Index
.tab_header-intro( p.lead.
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/pattern_01.jpg')}})") See Blender Cloud for reference.
.tab_header-intro_text
h2 Connect Blender with the Cloud
p
| Save your Blender preferences once, load them anywhere.
<br/>
| Use the
=' '
a(href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip') Blender Cloud add-on
=' '
| to synchronise your settings from within Blender.
.tab_header-intro_icons
i.pi-blender
i.pi-heart-filled
i.pi-blender-cloud
| {% for version in synced_versions %}
.blender_sync-main
.blender_sync-main-header
h2.blender_sync-main-title
i.pi-blender
| Blender {{ version.version }}
.blender_sync-main-last
| Last synced on: {{ version.date|pretty_date }}
| {% else %}
.blender_sync-main.empty
.blender_sync-main-header
span.blender_sync-main-title
| No settings synced yet
<hr/>
a.download(
href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip')
| Download add-on
| {% endfor %}
| {% endblock %} | {% endblock %}

View File

@@ -1,77 +1,15 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
| {% set title = 'home' %} | {% set title = 'home' %}
| {% block og %}
meta(property="og:type", content="website")
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
meta(property="og:title", content="Blender Cloud - Home")
meta(name="twitter:title", content="Blender Cloud")
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 %} | {% block page_title %}
| {{current_user.full_name}} | {{current_user.full_name}}
| {% endblock %} | {% endblock %}
| {% block body %} | {% block body %}
.dashboard-container .p-5.text-center
section.dashboard-main h2 Home Layout
| {{ navigation_tabs(title) }} p.lead.
See Blender Cloud for reference.
section#projects | {% block currenttab %}{% endblock currenttab %}
nav#sub-nav-tabs.home
ul#sub-nav-tabs__list
li.nav-tabs__list-tab#subtab-blender_sync(data-tab-url="{{ url_for('projects.home_project')}}")
i.pi-blender
| Blender Sync
li.nav-tabs__list-tab#subtab-images(data-tab-url="{{ url_for('projects.home_project_shared_images')}}")
i.pi-picture
| Images
| {% block currenttab %}{% endblock %}
section.dashboard-secondary
section.announcement
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
.lead
span.
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 <small>v</small>{{ config.BLENDER_CLOUD_ADDON_VERSION }}
a.btn.btn-default.btn-outline.blue(
href="{{ learn_more_btn_url }}")
| Learn More
| {% endif %}
| {% endblock %}
| {% block footer_scripts %}
script.
$(document).ready(function () {
$('#subtab-{{ subtab }}').addClass('active');
var $nav_tabs = $('#sub-nav-tabs__list').find('li.nav-tabs__list-tab');
$nav_tabs.on('click', function (e) {
window.location = $(this).attr('data-tab-url');
});
});
| {% endblock %} | {% endblock %}

View File

@@ -1,347 +1,16 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
| {% set title = 'dashboard' %} | {% 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 %} | {% block page_title %}
| {{current_user.full_name}} | {{current_user.full_name}}
| {% endblock %} | {% 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 %} | {% block body %}
.dashboard-container .p-5.text-center
section.dashboard-main h2 Index Dashboard
| {{ navigation_tabs(title) }} p.lead.
See Blender Cloud template for reference.
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.create#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")
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
img.header(
src="{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}")
.text
.title Projects
.lead
span.
Create and manage your own personal projects.
Upload assets and collaborate with other Blender Cloud members.
.buttons
a.btn.btn-default.btn-outline.blue(
href="https://cloud.blender.org/blog/introducing-private-projects")
| Learn More
section.announcement
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
.lead
span.
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 <small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
a.btn.btn-default.btn-outline.blue(
href="https://cloud.blender.org/blog/introducing-blender-sync")
| Learn More
| {% endblock %} | {% endblock %}
| {% block footer_scripts %} | {% 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('<i class="pi-spin spin"></i> 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 %} | {% endblock %}

View File

@@ -1,641 +1,11 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_add_new_menu.html' import add_new_menu %}
| {% block page_title %}{{ project.name }}{% endblock%} | {% 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/base.css') }}", rel="stylesheet")
link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') }}", rel="stylesheet")
| {% endblock %}
| {% block body %} | {% block body %}
#project-container .p-5.text-center
#project-side-container h2 Project View
#project_sidebar .lead.
ul.project-tabs See Blender Cloud for reference.
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
.project-title
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {{ project.name }}
| {% block project_tree %}
#project_tree
| {% endblock project_tree %}
| {% endif %}
#project_context-container
| {% if project.has_method('PUT') %}
#project_context-header
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")
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(
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(
type="button",
data-toggle="dropdown",
aria-haspopup="true",
aria-expanded="false")
i.pi-more-vertical
ul.dropdown-menu
| {% if current_user.has_cap('admin') %}
li.button-featured
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
| Toggle Featured
li.button-toggle-public
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.button-toggle-projheader
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
| Toggle Project Header video
li.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.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(
href="javascript:void(0);",
title="Cancel changes")
i.button-cancel-icon.pi-cancel
| Cancel
li.button-save
a#item_save.project-mode-edit(
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 <strong>folder</strong> 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
| {% endblock %} | {% endblock %}
| {% block footer_navigation %}{% endblock %}
| {% block footer %}{% 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').show();
$('.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').show();
} 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($('<iframe id="server_error"/>'));
$('#server_error').attr('src', url);
})
.always(function(){
$('#project-loading').removeAttr('class');
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
});
}
function loadProjectContent(url) {
$('#project-loading').addClass('active');
$.get(url, function(dataHtml) {
// Update the DOM injecting the generated HTML into the page
$('#project_context').html(dataHtml);
})
.done(function() {
updateUi('', 'view');
addMenuEnable();
addMenuDisable(['texture']);
})
.fail(function(dataResponse) {
$('#project_context').html($('<iframe id="server_error"/>'));
$('#server_error').attr('src', url);
})
.always(function(){
$('#project-loading').removeAttr('class');
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
});
}
function displayStorage(storageNodeId, path) {
var url = '/nodes/' + storageNodeId + '/view?path=' + path;
loadNodeContent(url);
}
function displayNode(nodeId, pushState) {
// Remove the 'n_' suffix from the id
if (nodeId.substring(0, 2) == 'n_') {
nodeId = nodeId.substr(2);
}
var url = '/nodes/' + nodeId + '/view';
loadNodeContent(url, nodeId);
// 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 = {nodeId: nodeId, url: url};
var push_url = '{{url_for("projects.view", project_url=project.url)}}' + nodeId;
// console.log('Pushing state ', push_state, ' with URL ', push_url);
window.history.pushState(
push_state,
'Node ' + nodeId, // TODO: use sensible title
push_url
);
}
function redirectToNode(nodeId) {
var generic_url = '{{ url_for("projects.view_node", project_url=project.url, node_id="theNodeId") }}';
var node_url = generic_url.replace('theNodeId', nodeId);
// This makes the user skip the current page when using the 'back' button,
// i.e. it works as a proper redirect.
location.replace(node_url);
}
window.onpopstate = function(event) {
var state = event.state;
// console.log('State popped. location:', document.location, 'state:', state);
// Deselect any selected node. We'll select the visited node (if any) later on.
var jstreeAPI = $(projectTree).jstree(true);
jstreeAPI.deselect_all(true);
if (state == null) {
// Went back to the project.
displayProject();
return;
}
// Went back to a node.
loadNodeContent(state.url, state.nodeId);
// Annoying hack because jstreeAPI.select_node() can only suppress the
// changed.jstree event, and NOT the selected_node.jstree event.
projectTree.dataset.ignoreSelectNode = true;
jstreeAPI.select_node('n_' + state.nodeId, true);
delete projectTree.dataset.ignoreSelectNode;
};
function displayProject() {
var url = "{{url_for('projects.view', project_url=project.url, embed=1)}}";
loadProjectContent(url);
}
function getHashId() {
if (console)
console.log('getHashId() should not be used any more!');
}
/* Loaded once, on page load */
function loadContent() {
var nodeId = ProjectUtils.nodeId();
var isProject = ProjectUtils.isProject();
if (isProject) {
// No need to asynchronously load the project, as it's embedded by Jinja.
// displayProject() is still needed, though, when people use 'back' to go there.
if (location.hash) {
// 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-edit').hide();
} else {
displayNode(nodeId, false);
}
$(projectTree).jstree({
'core': {
'data': function (obj, callback) {
if(obj.id === '#') { //tree root
if (isProject) {
$.getJSON("{{url_for('projects.jstree', project_url=project.url)}}", function (jsonObject) {
callback.call(this, jsonObject['items']);
});
} else {
$.getJSON('/nodes/' + nodeId + '/jstree', function(jsonObject) {
callback.call(this, jsonObject['items']);
});
}
} else { //normal node
var childNodeId;
if (obj.original.type == 'group_storage') {
childNodeId = obj.original.storage_node;
$.getJSON('/nodes/' + childNodeId + '/jstree?children=1&path=' + obj.original.path, function(jsonObject) {
callback.call(this, jsonObject.children);
});
} else {
// Remove the 'n_' suffix from the id
childNodeId = obj.id.substring(2);
$.getJSON('/nodes/' + childNodeId + '/jstree?children=1', function(jsonObject) {
callback.call(this, jsonObject.children);
});
}
}
}
},
"types" : {
"#": {"valid_children": ["collection"]},
"chapter" : {"icon": "pi-folder"},
"group" : {"icon": "pi-folder"},
"group_texture" : {"icon": "pi-folder-texture"},
"group_hdri" : {"icon": "pi-folder-texture", "max_children": 0},
"group_storage" : {"icon": "pi-folder"},
"filesystem_node" : {"icon": "pi-folder"},
"file" : {"icon": "pi-file-archive", "max_children": 0},
"filesystem_file" : {"icon": "pi-document", "max_children": 0},
"image" : {"icon": "pi-image", "max_children": 0},
"hdri" : {"icon": "pi-globe", "max_children": 0},
"texture" : {"icon": "pi-texture", "max_children": 0},
"video" : {"icon": "pi-film-thick", "max_children": 0},
"blog" : {"icon": "pi-newspaper", "max_children": 0},
"page" : {"icon": "pi-document-text", "max_children": 0},
"default" : {"icon": "pi-document"}
},
"plugins": ["types",] //, "state", "sort"
});
var jstreeAPI = $(projectTree).jstree(true);
$(projectTree).on("select_node.jstree", function (e, data) {
var selectedNodeId = data.node.id.substr(2);
// Ignore events that can't be suppressed otherwise.
// This can be removed if jstreeAPI.select_node() allows suppressing
// the select_node.jstree event.
if (e.target.dataset.ignoreSelectNode === 'true') return;
if (typeof(data.node.original.path) === 'undefined') {
var movingMode = Cookies.getJSON('bcloud_moving_node');
// Check if we are in the process of moving a node
if (movingMode) {
// Allow moving nodes only inside of node_type group
if (data.node.original.type != 'group' || movingMode.node_id === selectedNodeId || movingMode.node_id === ProjectUtils.parentNodeId()) {
if (movingMode.node_type === 'texture') {
if (data.node.original.type === 'group_texture') {
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
} else {
$('#item_move_accept').html('Select a Texture Folder').addClass('disabled');
}
} else if (movingMode.node_type === 'hdri') {
if (data.node.original.type === 'group_hdri') {
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
} else {
$('#item_move_accept').html('Select an HDRi Folder').addClass('disabled');
}
} else {
$('#item_move_accept').html('Select a Folder').addClass('disabled');
}
} else {
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
}
}
// Check the type of node and act accordingly
if (data.node.original.custom_view) {
window.location = data.node.a_attr.href;
} else {
var currentNodeId = ProjectUtils.nodeId();
if (currentNodeId != selectedNodeId) {
displayNode(selectedNodeId);
}
jstreeAPI.open_node(data.node);
}
} else {
displayStorage(data.node.original.storage_node, data.node.original.path);
jstreeAPI.toggle_node(data.node);
}
});
};
{% if is_embedded_edit is not defined or is_embedded_edit %}
// Initialize the page if we are not directly editing a node (most of the time)
loadContent();
{% endif %}
var project_container = document.getElementById('project-container');
/* UI Stuff */
$(window).on("load resize",function(){
containerResizeY($(window).height());
if ($(window).width() > 480) {
project_container.style.height = (window.innerHeight - project_container.offsetTop) + "px";
}
});
{% if current_user_is_subscriber %}
$(projectTree).addClass('is_subscriber');
{% endif %}
| {% endblock %}
| {% block comment_scripts %} {% endblock%}

View File

@@ -22,9 +22,8 @@
| {% endif %} | {% endif %}
section.node-details-container.project section.node-details-container.project
.node-details-title h2.px-3.pt-3
h1 a.text-muted(href="{{ url_for( 'projects.view', project_url=project.url) }}") {{ project.name }}
a(href="{{ url_for( 'projects.view', project_url=project.url) }}") {{ project.name }}
| {% if project.description %} | {% if project.description %}
.node-details-description .node-details-description
@@ -127,5 +126,4 @@ script.
}); });
}); });
| {% endblock %} | {% endblock %}

View File

@@ -49,7 +49,7 @@
h3. h3.
Now only &dollar;10 per month Now only &dollar;10 per month
.btn.btn-default .btn.btn-outline-primary
| Join the Cloud | Join the Cloud
hr hr

View File

@@ -72,9 +72,9 @@
| none | none
| {% endif %} | {% 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}}", data-user-id="{{user.user_id}}",
type="submit" value="Submit") type="submit" value="Submit")

View File

@@ -18,11 +18,11 @@
.buttons .buttons
.login-button-container .login-button-container
//a.forgot(href="https://blender.org/id/reset") forgot your password? //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 i.pi-log-in
| Login | 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 // i.pi-star-outline
// | Create Account // | Create Account

View File

@@ -3,7 +3,7 @@
//- can provide overrides. //- can provide overrides.
| {% block body %} | {% block body %}
.container .container
#settings #settings.d-flex.py-4.flex-xs-column
#settings-sidebar #settings-sidebar
| {% block settings_sidebar %} | {% block settings_sidebar %}
.settings-header .settings-header

View File

@@ -28,7 +28,7 @@
span {{ _("Change Gravatar") }} span {{ _("Change Gravatar") }}
.buttons .buttons
button.btn.btn-default.button-submit(type='submit') button.btn.btn-outline-success.button-submit(type='submit')
i.pi-check i.pi-check
| {{ _("Save Changes") }} | {{ _("Save Changes") }}
| {% endblock %} | {% endblock %}

View File

@@ -137,7 +137,7 @@ script().
$('td', row).eq(4).html(render_timing(data.timing)); $('td', row).eq(4).html(render_timing(data.timing));
$('td', row).eq(5).html(render_status_label(data, data.name)); $('td', row).eq(5).html(render_status_label(data, data.name));
var view_tag = '<span class="btn btn-default btn-xs load-shot-view" shot-view-url="' + data.url_edit + '"><i class="glyphicon glyphicon-edit"></i> View</span>'; var view_tag = '<span class="btn btn-outline-secondary btn-xs load-shot-view" shot-view-url="' + data.url_edit + '"><i class="glyphicon glyphicon-edit"></i> View</span>';
$('td', row).eq(6).html(view_tag); $('td', row).eq(6).html(view_tag);
} }
}); });

View File

@@ -0,0 +1,199 @@
"""Test that what we feed to Cerberus actually works.
This'll help us upgrade to new versions of Cerberus.
"""
import unittest
from pillar.tests import AbstractPillarTest
from bson import ObjectId
class CerberusCanaryTest(unittest.TestCase):
def _canary_test(self, validator):
groups_schema = {'name': {'type': 'string', 'required': True}}
# On error, validate_schema() raises ValidationError
validator.validate_schema(groups_schema)
# On error, validate() returns False
self.assertTrue(validator.validate({'name': 'je moeder'}, groups_schema))
self.assertFalse(validator.validate({'je moeder': 'op je hoofd'}, groups_schema))
def test_canary(self):
import cerberus
validator = cerberus.Validator()
self._canary_test(validator)
def test_our_validator_simple(self):
from pillar.api import custom_field_validation
validator = custom_field_validation.ValidateCustomFields()
self._canary_test(validator)
class ValidationTest(AbstractPillarTest):
def setUp(self):
super().setUp()
from pillar.api import custom_field_validation
self.validator = custom_field_validation.ValidateCustomFields()
self.user_id = ObjectId(8 * 'abc')
self.ensure_user_exists(self.user_id, 'Tést Üsâh')
def assertValid(self, document, schema):
with self.app.app_context():
is_valid = self.validator.validate(document, schema)
self.assertTrue(is_valid, f'errors: {self.validator.errors}')
def assertInvalid(self, document, schema):
with self.app.app_context():
is_valid = self.validator.validate(document, schema)
self.assertFalse(is_valid)
class ProjectValidationTest(ValidationTest):
def test_empty(self):
from pillar.api.eve_settings import projects_schema
self.assertInvalid({}, projects_schema)
def test_simple_project(self):
from pillar.api.eve_settings import projects_schema
project = {
'name': 'Té Ærhüs',
'user': self.user_id,
'category': 'assets',
'is_private': False,
'status': 'published',
}
self.assertValid(project, projects_schema)
def test_with_node_types(self):
from pillar.api.eve_settings import projects_schema
from pillar.api import node_types
project = {
'name': 'Té Ærhüs',
'user': self.user_id,
'category': 'assets',
'is_private': False,
'status': 'published',
'node_types': [node_types.node_type_asset,
node_types.node_type_comment]
}
self.assertValid(project, projects_schema)
class NodeValidationTest(ValidationTest):
def setUp(self):
super().setUp()
self.pid, self.project = self.ensure_project_exists()
def test_empty(self):
from pillar.api.eve_settings import nodes_schema
self.assertInvalid({}, nodes_schema)
def test_asset(self):
from pillar.api.eve_settings import nodes_schema
file_id, _ = self.ensure_file_exists()
node = {
'name': '"The Harmless Prototype™"',
'project': self.pid,
'node_type': 'asset',
'properties': {
'status': 'published',
'content_type': 'image',
'file': file_id,
},
'user': self.user_id,
'short_code': 'ABC333',
}
self.assertValid(node, nodes_schema)
def test_asset_invalid_properties(self):
from pillar.api.eve_settings import nodes_schema
file_id, _ = self.ensure_file_exists()
node = {
'name': '"The Harmless Prototype™"',
'project': self.pid,
'node_type': 'asset',
'properties': {
'status': 'invalid-status',
'content_type': 'image',
'file': file_id,
},
'user': self.user_id,
'short_code': 'ABC333',
}
self.assertInvalid(node, nodes_schema)
def test_comment(self):
from pillar.api.eve_settings import nodes_schema
file_id, _ = self.ensure_file_exists()
node = {
'name': '"The Harmless Prototype™"',
'project': self.pid,
'node_type': 'asset',
'properties': {
'status': 'published',
'content_type': 'image',
'file': file_id,
},
'user': self.user_id,
'short_code': 'ABC333',
}
node_id = self.create_node(node)
comment = {
'name': 'comment on some node',
'project': self.pid,
'node_type': 'comment',
'properties': {
'content': 'this is a comment',
'status': 'published',
},
'parent': node_id,
}
self.assertValid(comment, nodes_schema)
class IPRangeValidatorTest(ValidationTest):
schema = {'iprange': {'type': 'iprange', 'required': True}}
def assertValid(self, document, schema=None):
return super().assertValid(document, schema or self.schema)
def assertInvalid(self, document, schema=None):
return super().assertInvalid(document, schema or self.schema)
def test_ipv6(self):
self.assertValid({'iprange': '2a03:b0c0:0:1010::8fe:6ef1'})
self.assertValid({'iprange': '0:0:0:0:0:ffff:102:304'})
self.assertValid({'iprange': '2a03:b0c0:0:1010::8fe:6ef1/120'})
self.assertValid({'iprange': 'ff06::/8'})
self.assertValid({'iprange': '::/8'})
self.assertValid({'iprange': '::/1'})
self.assertValid({'iprange': '::1/128'})
self.assertValid({'iprange': '::'})
self.assertInvalid({'iprange': '::/0'})
self.assertInvalid({'iprange': 'barbled'})
def test_ipv4(self):
self.assertValid({'iprange': '1.2.3.4'})
self.assertValid({'iprange': '1.2.3.4/24'})
self.assertValid({'iprange': '127.0.0.0/8'})
self.assertInvalid({'iprange': '127.0.0.0/0'})
self.assertInvalid({'iprange': 'garbled'})

View File

@@ -12,7 +12,7 @@ class OAuthTests(AbstractPillarTest):
oauth_provider = OAuthSignIn.get_provider('blender-id') oauth_provider = OAuthSignIn.get_provider('blender-id')
self.assertIsInstance(oauth_provider, BlenderIdSignIn) self.assertIsInstance(oauth_provider, BlenderIdSignIn)
self.assertEqual(oauth_provider.service.base_url, 'http://blender-id:8000/api/') self.assertEqual(oauth_provider.service.base_url, 'http://id.local:8001/api/')
def test_provider_not_implemented(self): def test_provider_not_implemented(self):
from pillar.auth.oauth import OAuthSignIn, ProviderNotImplemented from pillar.auth.oauth import OAuthSignIn, ProviderNotImplemented
@@ -46,11 +46,11 @@ class OAuthTests(AbstractPillarTest):
def test_provider_callback_happy(self): def test_provider_callback_happy(self):
from pillar.auth.oauth import OAuthSignIn from pillar.auth.oauth import OAuthSignIn
responses.add(responses.POST, 'http://blender-id:8000/oauth/token', responses.add(responses.POST, 'http://id.local:8001/oauth/token',
json={'access_token': 'successful-token'}, json={'access_token': 'successful-token'},
status=200) status=200)
responses.add(responses.GET, 'http://blender-id:8000/api/user', responses.add(responses.GET, 'http://id.local:8001/api/user',
json={'id': '7', json={'id': '7',
'email': 'harry@blender.org'}, 'email': 'harry@blender.org'},
status=200) status=200)

View File

@@ -184,3 +184,31 @@ class NodeSetattrTest(unittest.TestCase):
node_setattr(node, 'b.complex', {None: 5}) node_setattr(node, 'b.complex', {None: 5})
self.assertEqual({'b': {'complex': {None: 5}}}, node) self.assertEqual({'b': {'complex': {None: 5}}}, node)
class TestRating(unittest.TestCase):
def test_hotness(self):
"""We expect the sorted values to reflect the original order in the
list.
"""
from datetime import datetime, timezone
from pillar.api.utils.rating import hot
t = datetime(2017, 2, 11, 0, 0, 0, 0, timezone.utc)
y = datetime(2017, 2, 10, 0, 0, 0, 0, timezone.utc)
w = datetime(2017, 2, 5, 0, 0, 0, 0, timezone.utc)
cases = [
(hot(1, 8, t), 'today super bad'),
(hot(0, 3, t), 'today slightly worse'),
(hot(0, 2, y), 'yesterday bad'),
(hot(0, 2, t), 'today bad'),
(hot(4, 4, w), 'last week controversial'),
(hot(7, 1, w), 'last week very good'),
(hot(5, 1, y), 'yesterday medium'),
(hot(5, 0, y), 'yesterday good'),
(hot(7, 1, y), 'yesterday very good'),
(hot(4, 4, t), 'today controversial'),
(hot(7, 1, t), 'today very good'),
]
sorted_by_hot = sorted(cases, key=lambda tup: tup[0])
for idx, t in enumerate(sorted_by_hot):
self.assertEqual(cases[idx][0], t[0])

View File

@@ -40,7 +40,7 @@ class DemoTest(unittest.TestCase):
self.assertEqual('<dl><dt>test</dt><dt>ü</dt><dd>é</dd></dl>', render('{test ü="é"}')) self.assertEqual('<dl><dt>test</dt><dt>ü</dt><dd>é</dd></dl>', render('{test ü="é"}'))
class YouTubeTest(unittest.TestCase): class YouTubeTest(AbstractPillarTest):
def test_missing(self): def test_missing(self):
from pillar.shortcodes import render from pillar.shortcodes import render
@@ -104,6 +104,19 @@ class YouTubeTest(unittest.TestCase):
render('{youtube "https://www.youtube.com/watch?v=NwVGvcIrNWA" width=5 height="3"}') render('{youtube "https://www.youtube.com/watch?v=NwVGvcIrNWA" width=5 height="3"}')
) )
def test_user_no_cap(self):
from pillar.shortcodes import render
with self.app.app_context():
# Anonymous user, so no subscriber capability.
self.assertEqual('', render('{youtube ABCDEF cap=subscriber}'))
self.assertEqual('', render('{youtube ABCDEF cap="subscriber"}'))
self.assertEqual(
'<p class="shortcode nocap">Aðeins áskrifendur hafa aðgang að þessu efni.</p>',
render('{youtube ABCDEF'
' cap="subscriber"'
' nocap="Aðeins áskrifendur hafa aðgang að þessu efni."}'))
class IFrameTest(AbstractPillarTest): class IFrameTest(AbstractPillarTest):
def test_missing_cap(self): def test_missing_cap(self):

Some files were not shown because too many files have changed in this diff Show More