├── .gitignore ├── LICENSE ├── Procfile ├── Procfile.windows ├── README.md ├── apps ├── __init__.py ├── accounts │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_elementaluser_banned.py │ │ ├── 0003_elementaluser_can_share_projects.py │ │ ├── 0004_auto_20150725_1206.py │ │ ├── 0005_elementaluser_deleted.py │ │ ├── 0006_elementaluser_ip.py │ │ ├── 0007_elementaluser_about_me.py │ │ ├── 0008_elementaluser_working_on.py │ │ ├── 0009_auto_20160312_1331.py │ │ └── __init__.py │ ├── mixins.py │ ├── models.py │ ├── static │ │ └── js │ │ │ └── profile.js │ ├── templates │ │ ├── login.html │ │ ├── profile.html │ │ ├── register.html │ │ └── user_settings.html │ ├── templatetags │ │ ├── __init__.py │ │ └── addcss.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── core │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── static │ │ └── js │ │ │ ├── components │ │ │ └── newsfeed.js │ │ │ └── vendor │ │ │ └── xml2json.js │ ├── templates │ │ ├── banned.html │ │ ├── base.html │ │ ├── includes │ │ │ ├── footer.html │ │ │ └── navbar.html │ │ ├── index.html │ │ └── terms_of_service.html │ ├── templatetags │ │ ├── __init__.py │ │ └── footer.py │ ├── tests.py │ └── views.py ├── galleries │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_usertogallery.py │ │ └── __init__.py │ └── models.py ├── projects │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_remove_project_shared.py │ │ ├── 0003_project_shared.py │ │ ├── 0004_project_data.py │ │ ├── 0005_auto_20150902_1930.py │ │ ├── 0006_project_deleted.py │ │ ├── 0007_project_thumbnail.py │ │ ├── 0008_project_updated.py │ │ ├── 0009_project_created.py │ │ ├── 0010_project_featured.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ ├── css │ │ │ ├── editor │ │ │ │ ├── blocks.scss │ │ │ │ ├── helpers.scss │ │ │ │ ├── menus.scss │ │ │ │ ├── style.css │ │ │ │ └── style.scss │ │ │ └── my-stuff.css │ │ ├── img │ │ │ ├── diagonalTexture.png │ │ │ ├── nav-search-glass.png │ │ │ ├── trash_closed.png │ │ │ └── trash_open.png │ │ └── js │ │ │ ├── components │ │ │ └── projectSettings.html │ │ │ ├── editor │ │ │ ├── block2json.js │ │ │ ├── blockAttributes.js │ │ │ ├── blockFilter.js │ │ │ ├── blocks.js │ │ │ ├── cssAttributes.js │ │ │ ├── fileManagement.js │ │ │ ├── files.js │ │ │ ├── script.js │ │ │ ├── siteIntegrations.js │ │ │ └── vendor │ │ │ │ ├── cssjson.js │ │ │ │ ├── filesaver.min.js │ │ │ │ ├── html2canvas.js │ │ │ │ ├── htmljson.js │ │ │ │ ├── htmlparser.js │ │ │ │ ├── interact.min.js │ │ │ │ └── jszip.min.js │ │ │ └── vendor │ │ │ ├── axios.min.js │ │ │ ├── html2canvas.js │ │ │ └── riot_compiler.js │ ├── templates │ │ ├── editor.html │ │ ├── my-things-mockup.html │ │ ├── my_projects.html │ │ └── proof-of-concept.html │ ├── tests.py │ ├── urls.py │ └── views.py └── rest │ ├── __init__.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── circle.yml ├── manage.py ├── requirements.txt ├── runtime.txt ├── settings.py ├── static ├── favicon.ico ├── footer.css ├── footer.scss ├── grid.css ├── grid.scss ├── helpers.css ├── helpers.scss ├── homepage.css ├── homepage.scss ├── nav.css ├── nav.scss ├── resources │ ├── elemental--base.png │ ├── front-about-code.png │ ├── front-about-learn.png │ ├── front-about-share.png │ ├── navbarimg1.png │ └── navbarimg2.png ├── style.css ├── style.scss ├── typography.css ├── typography.scss ├── variables.css └── variables.scss ├── urls.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | staticfiles 4 | .env 5 | db.sqlite3 6 | *.db 7 | .sass-cache/ 8 | *.map 9 | *.bat -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 ElementalCode 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn wsgi --log-file - 2 | -------------------------------------------------------------------------------- /Procfile.windows: -------------------------------------------------------------------------------- 1 | web: python manage.py runserver 0.0.0.0:5000 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://cloud.githubusercontent.com/assets/5458180/8791470/4f758e90-2f29-11e5-8f29-98886c74cb52.png) 2 | 3 | [![Stories in Ready](https://badge.waffle.io/ElementalCode/Elemental.png?label=ready&title=Ready)](https://waffle.io/ElementalCode/Elemental) 4 | 5 | [![Join the chat at https://gitter.im/ElementalCode/Elemental](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ElementalCode/Elemental) 6 | 7 | **Editor Demo:** http://elementalcode.herokuapp.com/projects/editor 8 | **Website Demo:** http://elementalcode.herokuapp.com/ 9 | 10 | A drag and drop block-based coding environment for front-end web technologies. 11 | 12 | Scratch Forums topic: https://scratch.mit.edu/discuss/topic/134554/ 13 | 14 | ## Project Goal 15 | 16 | The goal of Elemental is to act as a bridge between [Scratch](http://scratch.mit.edu/) and front-end web technology. Any and all design decisions will focus on teaching how to write clean, easy-to-read, valid front-end code. There should be as little opportunity for error as possible, thus bridging the syntax gap. Users of Elemental should not be expected to continue using Elemental once they become advanced users. Instead, they should be encouraged to convert to text-based code, for a quicker, more powerful, and more extensible coding experience. 17 | 18 | ## Developers 19 | 20 | ### PullJosh 21 | does things 22 | 23 | ### Firedrake969 24 | other people's things 25 | 26 | ### BookOwl 27 | neat things 28 | 29 | ### TheInitializer 30 | cool things 31 | 32 | ### an-OK-squirrel 33 | no things 34 | 35 | ======= 36 | 37 | ### Dev Notes 38 | If you're an Elemental Dev, be sure to read the [Dev Notes](https://github.com/ElementalCode/Elemental/wiki/Dev-Notes) to get up to speed on the rules and regulations for creating with the ElementalCode team. Happy coding! :) 39 | -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/__init__.py -------------------------------------------------------------------------------- /apps/accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/accounts/__init__.py -------------------------------------------------------------------------------- /apps/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.db.models import Q 3 | from django.contrib.auth.models import Group as AuthGroups 4 | 5 | from .models import ElementalUser 6 | 7 | 8 | class ElementalUserAdmin(admin.ModelAdmin): 9 | search_fields = ('username', ) 10 | list_filter = ('groups', ) 11 | 12 | readonly_fields = ('ip', ) 13 | 14 | superuser_fieldsets = ( 15 | (None, { 16 | 'fields': ( 17 | 'password', 'last_login', 'is_superuser', 18 | 'groups', 'user_permissions', 'email', 19 | 'is_staff', 'is_active', 'date_joined', 20 | 'username', 21 | 22 | 'banned', 'trusted', 'deleted', 23 | 'ip', 24 | ) 25 | }), 26 | ) 27 | 28 | admin_fieldsets = ( 29 | (None, { 30 | 'fields': ( 31 | 'password', 'last_login', 'groups', 32 | 'user_permissions', 'email', 33 | 'is_staff', 'is_active', 'date_joined', 34 | 'username', 35 | 36 | 'banned', 'trusted', 'deleted', 37 | 'ip', 38 | ) 39 | }), 40 | ) 41 | 42 | mod_fieldsets = ( 43 | (None, { 44 | 'fields': ( 45 | 'password', 'last_login', 46 | 'email', 'is_active', 47 | 'date_joined', 'username', 48 | 49 | 'banned', 'trusted', 'deleted', 50 | ) 51 | }), 52 | ) 53 | 54 | def get_fieldsets(self, request, obj=None): 55 | if not request.user.is_superuser: 56 | auth_group = request.user.groups.all()[0].name 57 | if request.user.is_superuser: 58 | return self.superuser_fieldsets 59 | elif auth_group == 'admin': 60 | return self.admin_fieldsets 61 | elif auth_group == 'moderator': 62 | return self.mod_fieldsets 63 | 64 | def get_queryset(self, request): 65 | qs = super(ElementalUserAdmin, self).get_queryset(request) 66 | if not request.user.is_superuser and len(request.user.groups.all()) > 0: 67 | auth_group = request.user.groups.all()[0].name 68 | else: 69 | return qs 70 | if auth_group == 'admin': 71 | return qs.filter(~Q(groups__name='admin'), ~Q(is_superuser=True)) 72 | elif auth_group == 'moderator': 73 | return qs.filter(~Q(groups__name='moderator'), ~Q(groups__name='admin'), ~Q(is_superuser=True)) 74 | return qs 75 | 76 | def formfield_for_manytomany(self, db_field, request, **kwargs): 77 | auth_groups = request.user.groups.all() 78 | for group in auth_groups: 79 | if db_field.name == 'groups': 80 | if group.name == 'admin': 81 | groups = [] 82 | groups.append('moderator') 83 | kwargs["queryset"] = AuthGroups.objects.filter( 84 | name__in=groups) 85 | return super(ElementalUserAdmin, self).formfield_for_manytomany( 86 | db_field, request, **kwargs) 87 | 88 | def get_actions(self, request): 89 | actions = super(ElementalUserAdmin, self).get_actions(request) 90 | if not request.user.is_superuser: 91 | del actions['delete_selected'] 92 | return actions 93 | 94 | def has_delete_permission(self, request, obj=None): 95 | if request.user.is_superuser: 96 | return True 97 | return False 98 | 99 | 100 | admin.site.register(ElementalUser, ElementalUserAdmin) -------------------------------------------------------------------------------- /apps/accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth import authenticate 3 | from django.contrib.auth.models import Group as AuthGroup 4 | from django.contrib.auth.forms import UserCreationForm 5 | 6 | from captcha.fields import ReCaptchaField 7 | 8 | from .models import ElementalUser 9 | 10 | 11 | class UserSettingsForm(forms.Form): 12 | current_password = forms.CharField(max_length=255, widget=forms.PasswordInput) 13 | email = forms.CharField(max_length=255, required=False) 14 | password1 = forms.CharField(max_length=255, widget=forms.PasswordInput, required=False) 15 | password2 = forms.CharField(max_length=255, widget=forms.PasswordInput, required=False) 16 | 17 | 18 | def __init__(self, *args, **kwargs): 19 | self.user = kwargs.pop('user', None) 20 | super(UserSettingsForm, self).__init__(*args, **kwargs) 21 | self.fields['email'].initial = self.user.email 22 | 23 | def clean_current_password(self): 24 | current_password = self.cleaned_data.get('current_password') 25 | user = authenticate(username=self.user.username, password=current_password) 26 | if user is None: 27 | raise forms.ValidationError("Incorrect password!") 28 | return current_password 29 | 30 | def clean(self): 31 | cleaned_data = super(UserSettingsForm, self).clean() 32 | pass1 = cleaned_data.get("password1") 33 | pass2 = cleaned_data.get("password2") 34 | if pass1 != pass2 and pass1 != '' and pass2 != '': 35 | raise forms.ValidationError("Passwords don't match!") 36 | 37 | class LoginForm(forms.Form): 38 | username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class':'form-input', 'placeholder': 'Username'})) 39 | password = forms.CharField(label="Password", widget=forms.PasswordInput(attrs={'class':'form-input', 'placeholder': 'Password'})) 40 | 41 | 42 | class SignupForm(UserCreationForm): 43 | username = forms.CharField(required=True, widget=forms.TextInput(attrs={'class': 'form-input', 'placeholder': 'Username'})) 44 | password1 = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'class': 'form-input', 'placeholder': 'Password'})) 45 | password2 = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'class': 'form-input', 'placeholder': 'Confirm Password'})) 46 | captcha = ReCaptchaField() 47 | 48 | class Meta: 49 | model = ElementalUser 50 | fields = ("username", "password1", "password2") 51 | 52 | def save(self, commit=True): 53 | user = super(SignupForm, self).save(commit=False) 54 | user.username = self.cleaned_data["username"] 55 | if commit: 56 | user.save() 57 | return user -------------------------------------------------------------------------------- /apps/accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.utils.timezone 6 | import django.core.validators 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('auth', '0006_require_contenttypes_0002'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ElementalUser', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 20 | ('password', models.CharField(max_length=128, verbose_name='password')), 21 | ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), 22 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 23 | ('username', models.CharField(unique=True, max_length=20, validators=[django.core.validators.RegexValidator(b'^[0-9a-zA-Z]*$', message=b'Only alphanumeric characters are allowed.')])), 24 | ('email', models.EmailField(max_length=254, verbose_name='email address', blank=True)), 25 | ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), 26 | ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), 27 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 28 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 29 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 30 | ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups')), 31 | ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')), 32 | ], 33 | options={ 34 | 'verbose_name': 'user', 35 | 'verbose_name_plural': 'users', 36 | }, 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0002_elementaluser_banned.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='elementaluser', 16 | name='banned', 17 | field=models.BooleanField(default=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0003_elementaluser_can_share_projects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0002_elementaluser_banned'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='elementaluser', 16 | name='can_share_projects', 17 | field=models.BooleanField(default=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0004_auto_20150725_1206.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.core.validators 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0003_elementaluser_can_share_projects'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='elementaluser', 17 | name='username', 18 | field=models.CharField(unique=True, max_length=20, validators=[django.core.validators.RegexValidator(b'^[a-zA-Z0-9-_]+$', message=b'Only alphanumeric characters are allowed.')]), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0005_elementaluser_deleted.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0004_auto_20150725_1206'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='elementaluser', 16 | name='deleted', 17 | field=models.BooleanField(default=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0006_elementaluser_ip.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0005_elementaluser_deleted'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='elementaluser', 16 | name='ip', 17 | field=models.GenericIPAddressField(null=True, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0007_elementaluser_about_me.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0006_elementaluser_ip'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='elementaluser', 16 | name='about_me', 17 | field=models.TextField(null=True, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0008_elementaluser_working_on.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0007_elementaluser_about_me'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='elementaluser', 16 | name='working_on', 17 | field=models.TextField(null=True, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/accounts/migrations/0009_auto_20160312_1331.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0008_elementaluser_working_on'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RenameField( 15 | model_name='elementaluser', 16 | old_name='can_share_projects', 17 | new_name='trusted', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /apps/accounts/mixins.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.shortcuts import redirect 3 | 4 | 5 | class UnbannedUserMixin(object): 6 | def dispatch(self, request, *args, **kwargs): 7 | if request.user.is_authenticated(): 8 | if request.user.banned: 9 | return redirect(reverse('ban-page')) 10 | return super(UnbannedUserMixin, self).dispatch(request, *args, **kwargs) 11 | 12 | class LoggedInRequiredMixin(object): 13 | def dispatch(self, request, *args, **kwargs): 14 | if not request.user.is_authenticated(): 15 | return redirect(reverse('index')) 16 | return super(LoggedInRequiredMixin, self).dispatch(request, *args, **kwargs) -------------------------------------------------------------------------------- /apps/accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.utils.http import urlquote 4 | from django.utils.translation import ugettext_lazy as _ 5 | from django.core.mail import send_mail 6 | from django.core.validators import RegexValidator 7 | from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager 8 | from django.contrib.auth.hashers import is_password_usable 9 | 10 | 11 | class ElementalUserManager(BaseUserManager): 12 | 13 | def _create_user(self, username, password, 14 | is_staff, is_superuser, **extra_fields): 15 | now = timezone.now() 16 | if not username: 17 | raise ValueError('The given username must be set') 18 | user = self.model(username=username, 19 | is_staff=is_staff, is_active=True, 20 | is_superuser=is_superuser, last_login=now, 21 | date_joined=now, **extra_fields) 22 | user.set_password(password) 23 | user.save(using=self._db) 24 | return user 25 | 26 | def create_user(self, username, password=None, **extra_fields): 27 | return self._create_user(username, password, False, False, 28 | **extra_fields) 29 | 30 | def create_superuser(self, username, password, **extra_fields): 31 | return self._create_user(username, password, True, True, 32 | **extra_fields) 33 | 34 | 35 | class ElementalUser(AbstractBaseUser, PermissionsMixin): 36 | alphanumeric = RegexValidator(r'^[a-zA-Z0-9-_]+$', message='Only alphanumeric characters are allowed.') 37 | 38 | username = models.CharField(unique=True, max_length=20, validators=[alphanumeric]) 39 | 40 | banned = models.BooleanField(default=False) 41 | trusted = models.BooleanField(default=False) 42 | deleted = models.BooleanField(default=False) 43 | ip = models.GenericIPAddressField(blank=True, null=True) 44 | 45 | email = models.EmailField(_('email address'), max_length=254, blank=True) 46 | first_name = models.CharField(_('first name'), max_length=30, blank=True) 47 | last_name = models.CharField(_('last name'), max_length=30, blank=True) 48 | is_staff = models.BooleanField(_('staff status'), default=False, 49 | help_text=_('Designates whether the user can log into this admin ' 50 | 'site.')) 51 | is_active = models.BooleanField(_('active'), default=True, 52 | help_text=_('Designates whether this user should be treated as ' 53 | 'active. Unselect this instead of deleting accounts.')) 54 | date_joined = models.DateTimeField(_('date joined'), default=timezone.now) 55 | 56 | about_me = models.TextField(blank=True, null=True) 57 | working_on = models.TextField(blank=True, null=True) 58 | 59 | objects = ElementalUserManager() 60 | 61 | USERNAME_FIELD = 'username' 62 | REQUIRED_FIELDS = [] 63 | 64 | class Meta: 65 | verbose_name = _('user') 66 | verbose_name_plural = _('users') 67 | 68 | def save(self, *args, **kwargs): 69 | if not is_password_usable(self.password): 70 | self.set_password(self.password) 71 | super(ElementalUser, self).save(*args, **kwargs) 72 | if len(self.groups.all()) > 0: 73 | auth_group = self.groups.all()[0].name 74 | else: 75 | auth_group = None 76 | allowed_groups = ('admin', 'moderator', ) 77 | if self.is_superuser or auth_group in allowed_groups: 78 | self.trusted = True 79 | return super(ElementalUser, self).save(*args, **kwargs) 80 | 81 | def set_ip(self, request): 82 | x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') 83 | if x_forwarded_for: 84 | ip = x_forwarded_for.split(',')[0] 85 | else: 86 | ip = request.META.get('REMOTE_ADDR') 87 | self.ip = ip 88 | self.save() 89 | 90 | def get_full_name(self): 91 | """ 92 | Returns the first_name plus the last_name, with a space in between. 93 | """ 94 | full_name = '%s %s' % (self.first_name, self.last_name) 95 | return full_name.strip() 96 | 97 | def get_short_name(self): 98 | "Returns the short name for the user." 99 | return self.first_name 100 | 101 | def email_user(self, subject, message, from_email=None): 102 | """ 103 | Sends an email to this User. 104 | """ 105 | send_mail(subject, message, from_email, [self.email]) -------------------------------------------------------------------------------- /apps/accounts/static/js/profile.js: -------------------------------------------------------------------------------- 1 | // ABOUT ME/WIWO STUFF 2 | var aboutMe = document.getElementById('aboutme'); 3 | var wiwo = document.getElementById('workingon'); 4 | 5 | function editProfileText(mode) { 6 | var request; 7 | var reqstr = '/rest/users/user/' + USERID; 8 | var config = { 9 | headers: { 10 | "X-CSRFToken": CSRF_TOKEN 11 | } 12 | }; 13 | if (mode == 'About Me') { 14 | request = axios.patch(reqstr, { 15 | about_me: aboutMe.textContent 16 | }, config); 17 | } else if (mode == 'What I\'m Working On') { 18 | request = axios.patch(reqstr, { 19 | working_on: wiwo.textContent 20 | }, config); 21 | } 22 | if (request) { 23 | request.then(function(data) { 24 | console.log(data); 25 | }).catch(function(err) { 26 | console.log(err); 27 | }); 28 | } 29 | } 30 | 31 | aboutMe.addEventListener('blur', function() { 32 | editProfileText('About Me'); 33 | }); 34 | 35 | wiwo.addEventListener('blur', function() { 36 | editProfileText('What I\'m Working On'); 37 | }); 38 | 39 | 40 | // LAYOUT STUFF 41 | var FavoritesCount = 1; 42 | var FollowingCount = 1; 43 | var FollowersCount = 1; 44 | 45 | function get_first(name) { return document.getElementsByClassName(name)[0]; } 46 | 47 | function set_width(object, count, emptymessage, imgsize) { 48 | if (count > 4) { 49 | object.style.width = ((imgsize + 18) * count) + "px"; 50 | } else if (count > 0) { 51 | object.style.width = "710px"; 52 | } else { 53 | object.parentNode.innerHTML = "
" + emptymessage.replace('%user', USERNAME) + "
"; 54 | } 55 | } 56 | 57 | var project_carousel = get_first("userpage-shared-section-carousel"); 58 | var favorite_carousel = get_first("userpage-favorite-section-carousel"); 59 | var following_carousel = get_first("userpage-following-section-carousel"); 60 | var followers_carousel = get_first("userpage-followers-section-carousel"); 61 | 62 | set_width(project_carousel, ProjectsCount, "%user has not shared any creations yet.", 160) 63 | set_width(favorite_carousel, FavoritesCount, "%user has not favorited any creations yet.", 160) 64 | set_width(following_carousel, FollowingCount, "%user is not following anyone yet.", 120) 65 | set_width(followers_carousel, FollowersCount, "Nobody is following %user yet.", 120) -------------------------------------------------------------------------------- /apps/accounts/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block title %}Login | {% endblock %} 5 | {% block content %} 6 |
7 |
8 |

Log In

9 |
10 | {% csrf_token %} 11 | {{ form.username }} 12 | {{ form.password }} 13 | 14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /apps/accounts/templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block morescripts %} 5 | 6 | 7 | 13 | {% endblock %} 14 | 15 | {% block content %} 16 |
17 |
18 |
19 |

{{ user.username }}

20 |
User
21 |
22 |
23 |
24 |

About Me

25 |
26 | {% if user.trusted %} 27 | {{ user.about_me }} 28 | {% endif %} 29 |
30 |

What I'm Working On

31 |
32 | {% if user.trusted %} 33 | {{ user.working_on }} 34 | {% endif %} 35 |
36 |
37 |
38 |
39 |
40 |

Featured Creation

41 | 42 |
43 | Project 44 |
45 |
46 |
47 |
48 |
49 | 70 | 89 | 108 | 127 | {% endblock %} 128 | 129 | {% block extra_scripts %} 130 | 131 | {% endblock %} 132 | -------------------------------------------------------------------------------- /apps/accounts/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load addcss %} 3 | 4 | {% block title %}Register | {% endblock %} 5 | {% block content %} 6 |
7 |
8 |

Sign Up

9 |
10 | {% csrf_token %} 11 | {{ form.username }} 12 | {{ form.password1 }} 13 | {{ form.password2 }} 14 | {{ form.captcha|addcss:'centered' }} 15 | 16 |
17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /apps/accounts/templates/user_settings.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 | {% csrf_token %} 6 | {{ form.as_p }} 7 | 8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /apps/accounts/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/accounts/templatetags/__init__.py -------------------------------------------------------------------------------- /apps/accounts/templatetags/addcss.py: -------------------------------------------------------------------------------- 1 | from django.template import Library 2 | register = Library() 3 | 4 | @register.filter(name='addcss') 5 | def addcss(value, arg): 6 | value.field.widget.attrs['class'] = arg 7 | return value -------------------------------------------------------------------------------- /apps/accounts/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.urlresolvers import reverse 4 | from django.conf import settings 5 | from django.test import TestCase, override_settings, Client, RequestFactory 6 | 7 | from .models import ElementalUser 8 | 9 | class AccountTestCases(TestCase): 10 | 11 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 12 | def setUp(self): 13 | self.client = Client() 14 | os.environ['RECAPTCHA_TESTING'] = 'True' 15 | 16 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 17 | def tearDown(self): 18 | del os.environ['RECAPTCHA_TESTING'] 19 | 20 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 21 | def test_user_registration(self): 22 | data = { 23 | 'username': 'testguy2', 24 | 'password1': 'supersecurepassword', 25 | 'password2': 'supersecurepassword', 26 | 'g-recaptcha-response': 'PASSED' 27 | } 28 | response = self.client.post(reverse('accounts:register'), data) 29 | self.assertEqual(response.status_code, 302) 30 | try: 31 | user = ElementalUser.objects.get(username='testguy2') 32 | except: 33 | user = None 34 | self.assertIsNotNone(user) 35 | 36 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 37 | def test_user_registration_failures(self): 38 | data = { 39 | 'username': 'testguy2', 40 | 'password1': 'supersecurepassword', 41 | 'password2': 'supersecurepssword', 42 | 'g-recaptcha-response': 'PASSED' 43 | } 44 | response = self.client.post(reverse('accounts:register'), data) 45 | self.assertEqual(response.status_code, 200) 46 | try: 47 | user = ElementalUser.objects.get(username='testguy2') 48 | except: 49 | user = None 50 | self.assertIsNone(user) 51 | 52 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 53 | def test_user_registration_fail_captcha(self): 54 | data = { 55 | 'username': 'testguy8', 56 | 'password1': 'supersecurepassword', 57 | 'password2': 'supersecurepassword', 58 | } 59 | response = self.client.post(reverse('accounts:register'), data) 60 | self.assertEqual(response.status_code, 200) 61 | try: 62 | user = ElementalUser.objects.get(username='testguy8') 63 | except: 64 | user = None 65 | self.assertIsNone(user) 66 | 67 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 68 | def test_user_change_password(self): 69 | u = ElementalUser( 70 | username='testguy3', 71 | password='supersecurepassword' 72 | ) 73 | u.save() 74 | self.client.login(username='testguy3', password='supersecurepassword') 75 | 76 | data = { 77 | 'current_password': 'supersecurepassword', 78 | 'password1': 'insecurepassword', 79 | 'password2': 'insecurepassword' 80 | } 81 | response = self.client.post(reverse('accounts:user-settings'), data) 82 | self.assertEqual(response.status_code, 302) 83 | 84 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 85 | def test_user_change_password_fail(self): 86 | u = ElementalUser( 87 | username='testguy3', 88 | password='supersecurepassword' 89 | ) 90 | u.save() 91 | self.client.login(username='testguy3', password='supersecurepassword') 92 | 93 | data = { 94 | 'current_password': 'supersecurepassword', 95 | 'password1': 'insecurepassword', 96 | 'password2': 'insecurepassworddiff' 97 | } 98 | response = self.client.post(reverse('accounts:user-settings'), data) 99 | self.assertEqual(response.status_code, 200) 100 | 101 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 102 | def test_user_change_email(self): 103 | u = ElementalUser( 104 | username='testguy3', 105 | password='supersecurepassword' 106 | ) 107 | u.save() 108 | self.client.login(username='testguy3', password='supersecurepassword') 109 | 110 | data = { 111 | 'current_password': 'supersecurepassword', 112 | 'email': 'z@a.com' 113 | } 114 | response = self.client.post(reverse('accounts:user-settings'), data) 115 | self.assertEqual(response.status_code, 302) 116 | try: 117 | user = ElementalUser.objects.get(email='z@a.com') 118 | except: 119 | user = None 120 | self.assertIsNotNone(user) 121 | 122 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 123 | def test_user_change_email_fail(self): 124 | u = ElementalUser( 125 | username='testguy3', 126 | password='supersecurepassword' 127 | ) 128 | u.save() 129 | self.client.login(username='testguy3', password='supersecurepassword') 130 | 131 | data = { 132 | 'current_password': 'supersecurepasswordoops', 133 | 'email': 'z@a.com' 134 | } 135 | response = self.client.post(reverse('accounts:user-settings'), data) 136 | self.assertEqual(response.status_code, 200) 137 | try: 138 | user = ElementalUser.objects.get(email='z@a.com') 139 | except: 140 | user = None 141 | self.assertIsNone(user) -------------------------------------------------------------------------------- /apps/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | from views import ProfileView, UserSettings, SignUp, Logout, Login 7 | 8 | urlpatterns = [ 9 | url(r'^(?i)settings/?$', UserSettings.as_view(), name='user-settings'), 10 | url(r'^(?i)register/?$', SignUp.as_view(), name='register'), 11 | url(r'^(?i)logout/?$', Logout.as_view(), name='logout'), 12 | url(r'^(?i)login/?$', Login.as_view(), name="login"), 13 | ] -------------------------------------------------------------------------------- /apps/accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib import messages 3 | from django.db.models import Q 4 | from django.contrib.auth import authenticate, login, logout 5 | from django.contrib.auth.models import Group as AuthGroup 6 | from django.core import signing 7 | from django.core.mail import send_mail 8 | from django.core.urlresolvers import reverse, reverse_lazy 9 | from django.http import HttpResponse 10 | from django.shortcuts import render, redirect 11 | from django.template import loader 12 | from django.views.generic import TemplateView, ListView 13 | from django.views.generic.base import View 14 | from django.views.generic.edit import (FormView, UpdateView, CreateView, 15 | DeleteView) 16 | from .forms import LoginForm, SignupForm 17 | from apps.accounts.models import ElementalUser 18 | from apps.accounts.mixins import UnbannedUserMixin 19 | 20 | 21 | from .models import ElementalUser 22 | from apps.projects.models import Project 23 | 24 | from .forms import UserSettingsForm 25 | 26 | from .mixins import UnbannedUserMixin, LoggedInRequiredMixin 27 | 28 | class ProfileView(UnbannedUserMixin, TemplateView): 29 | template_name = 'profile.html' 30 | 31 | def get_context_data(self, **kwargs): 32 | context = super(ProfileView, self).get_context_data(**kwargs) 33 | context['user'] = ElementalUser.objects.get(username__iexact=self.kwargs['username']) 34 | if not self.request.user.is_anonymous() and not self.request.user.is_superuser and len(self.request.user.groups.all()[0].name) > 0: 35 | auth_group = request.user.groups.all()[0].name 36 | 37 | # just a little failsafe in case of broken things... 38 | allowed_groups = ('admin', 'moderator', ) 39 | if context['user'].trusted or self.request.user.is_superuser or auth_group in allowed_groups: 40 | context['projects'] = Project.objects.filter(user=context['user'], shared=True).order_by('-updated') 41 | else: 42 | context['projects'] = Project.objects.none() 43 | 44 | print context['projects'] 45 | 46 | return context 47 | 48 | class UserSettings(LoggedInRequiredMixin, UnbannedUserMixin, FormView): 49 | template_name = 'user_settings.html' 50 | form_class = UserSettingsForm 51 | success_url = reverse_lazy('accounts:user-settings') 52 | 53 | def get_form_kwargs(self): 54 | kwargs = super(UserSettings, self).get_form_kwargs() 55 | kwargs['user'] = self.request.user 56 | return kwargs 57 | 58 | def form_valid(self, form): 59 | self.request.user.email = form.cleaned_data.get('email') 60 | self.request.user.save() 61 | if form.cleaned_data.get('password1') != '': 62 | self.request.user.password = form.cleaned_data.get('password1') 63 | self.request.user.save() 64 | return super(UserSettings, self).form_valid(form) 65 | 66 | class SignUp(FormView): 67 | template_name = 'register.html' 68 | form_class = SignupForm 69 | success_url = '/' 70 | 71 | def get(self, request): 72 | if request.user.is_authenticated(): 73 | return redirect(reverse('index')) 74 | return super(SignUp, self).get(request) 75 | 76 | def form_valid(self, form): 77 | super(SignUp, self).form_valid(form) 78 | form.save() 79 | user = authenticate(username=form.cleaned_data.get('username'), 80 | password=form.cleaned_data.get('password1')) 81 | login(self.request, user) 82 | user.set_ip(self.request) 83 | return redirect(reverse('index')) 84 | 85 | 86 | class Logout(View): 87 | def get(self, request, *args, **kwargs): 88 | logout(request) 89 | return redirect('/') 90 | 91 | class Login(UnbannedUserMixin, FormView): 92 | template_name = 'login.html' 93 | form_class = LoginForm 94 | success_url = reverse_lazy('index') 95 | 96 | def get(self, request): 97 | if request.user.is_authenticated(): 98 | return redirect(reverse('index')) 99 | return super(Login, self).get(request) 100 | 101 | def form_valid(self, form): 102 | super(Login, self).form_valid(form) 103 | username = form.cleaned_data['username'] 104 | password = form.cleaned_data['password'] 105 | user = authenticate(username=username, password=password) 106 | 107 | if user is not None: 108 | if user.is_active: 109 | login(self.request, user) 110 | user.set_ip(self.request) 111 | if not user.banned: 112 | return super(Login, self).form_valid(form) 113 | else: 114 | return redirect(reverse('ban-page')) 115 | else: 116 | form.errors['non_field_errors'] = ['Your account is not active.'] 117 | return render(self.request, 'index.html', 118 | {'form': form}) 119 | else: 120 | form.errors['non_field_errors'] = ['Invalid login'] 121 | return render(self.request, 'login.html', 122 | {'form': form}) -------------------------------------------------------------------------------- /apps/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/core/__init__.py -------------------------------------------------------------------------------- /apps/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/core/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.db import transaction 3 | from django.core.exceptions import ValidationError 4 | from django.contrib.auth.forms import UserCreationForm 5 | 6 | from apps.accounts.models import ElementalUser 7 | 8 | 9 | class LoginForm(forms.Form): 10 | username = forms.CharField(label='username') 11 | password = forms.CharField(label="Password", 12 | widget=forms.PasswordInput) 13 | 14 | 15 | class SignupForm(UserCreationForm): 16 | username = forms.CharField(required=True) 17 | 18 | class Meta: 19 | model = ElementalUser 20 | fields = ("username", "password1", "password2") 21 | 22 | def save(self, commit=True): 23 | user = super(SignupForm, self).save(commit=False) 24 | user.username = self.cleaned_data["username"] 25 | if commit: 26 | user.save() 27 | return user -------------------------------------------------------------------------------- /apps/core/static/js/components/newsfeed.js: -------------------------------------------------------------------------------- 1 | decodeHTML = function(str) { 2 | var map = {"gt":">" /* , … */}; 3 | return str.replace('&','&').replace(/&(#(?:x[0-9a-f]+|\d+)|[a-z]+);?/gi, function($0, $1) { 4 | if ($1[0] === "#") { 5 | return String.fromCharCode($1[1].toLowerCase() === "x" ? parseInt($1.substr(2), 16) : parseInt($1.substr(1), 10)); 6 | } else { 7 | return map.hasOwnProperty($1) ? map[$1] : $0; 8 | } 9 | }); 10 | }; 11 | 12 | document.addEventListener("DOMContentLoaded", function(event) { 13 | axios.get('https://elementalcode.github.io/blog/feed.xml').then(function(data) 14 | { 15 | var parser = new DOMParser(); 16 | var newsFeed = parser.parseFromString(data.data,"text/xml"); 17 | var posts = newsFeed.getElementsByTagName('entry'); 18 | posts_meta = []; 19 | for (x in posts) { 20 | if ((x > 4) || (typeof(posts[x]) !== "object")) { 21 | break; 22 | } 23 | var post = document.createElement('div'); 24 | post.className = 'news-post push-tiny--right'; 25 | if (x == 0) { 26 | post.className = post.className + ' first-news-post' 27 | } 28 | var title = document.createElement('a'); 29 | title.href = posts[x].getElementsByTagName('id')[0].innerHTML 30 | title.innerHTML = decodeHTML(posts[x].getElementsByTagName('title')[0].innerHTML); 31 | var desc = document.createElement('div'); 32 | var text = document.createElement('span'); 33 | text.className = 'smalltext'; 34 | text.innerHTML = decodeHTML(posts[x].getElementsByTagName('summary')[0].innerHTML); 35 | desc.appendChild(text); 36 | post.appendChild(title); 37 | post.appendChild(desc) 38 | document.getElementsByTagName('newsfeed')[0].appendChild(post) 39 | } 40 | } 41 | ) 42 | }); -------------------------------------------------------------------------------- /apps/core/static/js/vendor/xml2json.js: -------------------------------------------------------------------------------- 1 | /* This work is licensed under Creative Commons GNU LGPL License. 2 | 3 | License: http://creativecommons.org/licenses/LGPL/2.1/ 4 | Version: 0.9 5 | Author: Stefan Goessner/2006 6 | Web: http://goessner.net/ 7 | */ 8 | function xml2json(xml) { 9 | var obj = {}; 10 | if (xml.nodeType == 1) { 11 | if (xml.attributes.length > 0) { 12 | obj["@attributes"] = {}; 13 | for (var j = 0; j < xml.attributes.length; j++) { 14 | var attribute = xml.attributes.item(j); 15 | obj["@attributes"][attribute.nodeName] = attribute.nodeValue; 16 | } 17 | } 18 | } else if (xml.nodeType == 3) { 19 | obj = xml.nodeValue; 20 | } 21 | if (xml.hasChildNodes()) { 22 | for (var i = 0; i < xml.childNodes.length; i++) { 23 | var item = xml.childNodes.item(i); 24 | var nodeName = item.nodeName; 25 | if (typeof (obj[nodeName]) == "undefined") { 26 | obj[nodeName] = xml2json(item); 27 | } else { 28 | if (typeof (obj[nodeName].push) == "undefined") { 29 | var old = obj[nodeName]; 30 | obj[nodeName] = []; 31 | obj[nodeName].push(old); 32 | } 33 | obj[nodeName].push(xml2json(item)); 34 | } 35 | } 36 | } 37 | return obj; 38 | } -------------------------------------------------------------------------------- /apps/core/templates/banned.html: -------------------------------------------------------------------------------- 1 | YOU HAVE BEEN BANNED 2 | MUAHAHA -------------------------------------------------------------------------------- /apps/core/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | {% block title %}{% endblock %}ElementalCode 6 | 7 | 8 | 9 | {% block morescripts %}{% endblock morescripts %} 10 | 11 | 12 |
13 | {% include "includes/navbar.html" %} 14 |
15 | {% block content %}{% endblock %} 16 |
17 |
18 |
19 | {% include "includes/footer.html" %} 20 | 21 | {% block extra_scripts %}{% endblock extra_scripts %} 22 | 23 | 24 | -------------------------------------------------------------------------------- /apps/core/templates/includes/footer.html: -------------------------------------------------------------------------------- 1 | {% load footer %} 2 | 11 | -------------------------------------------------------------------------------- /apps/core/templates/includes/navbar.html: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /apps/core/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block morescripts %} 5 | 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 | {% if request.user.is_authenticated %} 12 | {% else %} 13 |
14 |
15 |

Learn to develop on the web with an easy drag-and-drop interface.

16 |
17 | 18 | 20 |
Intuitive, block based editor
21 | 23 |
Instant updates as you design
24 | 26 |
Share your pages online with one click
27 | 29 | 30 |
Full HTML and CSS features
31 |
32 |
33 |
34 |
35 | Start creating!
no account required
36 |
or
37 | Register for Free
to save to the cloud
38 |
39 |
40 | {% endif %} 41 | 42 | 73 | 104 | 135 | {% endblock %} 136 | 137 | {% block extra_scripts %} 138 | {% endblock %} 139 | -------------------------------------------------------------------------------- /apps/core/templates/terms_of_service.html: -------------------------------------------------------------------------------- 1 | Basically, be good, and anything you do is your fault. Not ours. 2 | Here goes. 3 | 4 | By using ElementalCode (the "Service") and its hosting site elementalcode.herokuapp.com (the "Site"), you agree to the following terms. ElementalCode reserves the right to block and/or terminate your account without notice from the Site if the end user violates any of the listed terms. 5 | 6 | You may not use the Service for anything illegal under United States and European Union law, including hosting or sharing illegal or illegally obtained files. 7 | You may not save anything that may be considered NSFW (such as, but not limited to, sexually explicit content and vulgar language). 8 | 9 | You agree to defend, indemnify and hold harmless ElementalCode from and against any and all claims, damages, obligations, losses, liabilities, costs or debt, and expenses (including but not limited to attorney's fees), resulting from or arising out of a) your use and access of the Service, by you or any person using your account and password; b) a breach of these Terms, or c) Content posted on the Service. 10 | 11 | In no event shall ElementalCode be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use the Service; (ii) any conduct or content of any third party on the Service; (iii) any content obtained from the Service; and (iv) unauthorized access, use or alteration of your transmissions or content, whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose. 12 | 13 | Your use of the Service is at your sole risk. The Service is provided on an "AS IS" and "AS AVAILABLE" basis. The Service is provided without warranties of any kind, whether express or implied, including, but not limited to, implied warranties of merchantability, fitness for a particular purpose, non-infringement or course of performance. 14 | 15 | ElementalCode does not warrant that a) the Service will function uninterrupted, secure or available at any particular time or location; b) any errors or defects will be corrected; c) the Service is free of viruses or other harmful components; or d) the results of using the Service will meet your requirements. 16 | 17 | We reserve the right to update these Terms of Service at any time without notification. -------------------------------------------------------------------------------- /apps/core/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/core/templatetags/__init__.py -------------------------------------------------------------------------------- /apps/core/templatetags/footer.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from django import template 4 | register = template.Library() 5 | 6 | thingies = [ 7 | ";", 8 | "return 5;", 9 | "<div><font>Elemental: Never be like me again!</span>;", 10 | "please roll again", 11 | "I love useless footers", 12 | "I'm lonely!", 13 | "'); DROP TABLE footers; --", 14 | "This footer is the only good thing that ever came out of super-squirrel, but don't tell him that", 15 | "how long have you been refreshing the page?", 16 | "yes, this is completely pointless", 17 | "thank mr skeltal", 18 | "Good ol' C-x M-c M-butterfly..." 19 | ] 20 | 21 | @register.simple_tag 22 | def footer(): 23 | return random.choice(thingies) -------------------------------------------------------------------------------- /apps/core/tests.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.conf import settings 3 | from django.test import TestCase, override_settings, Client, RequestFactory 4 | 5 | class CoreTestCases(TestCase): 6 | 7 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 8 | def setUp(self): 9 | self.client = Client() -------------------------------------------------------------------------------- /apps/core/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib import messages 3 | from django.db.models import Q 4 | from django.contrib.auth import authenticate, login, logout 5 | from django.contrib.auth.models import Group as AuthGroup 6 | from django.core import signing 7 | from django.core.mail import send_mail 8 | from django.core.urlresolvers import reverse, reverse_lazy 9 | from django.http import HttpResponse 10 | from django.shortcuts import render, redirect 11 | from django.template import loader 12 | from django.views.generic import TemplateView, ListView 13 | from django.views.generic.base import View 14 | from django.views.generic.edit import (FormView, UpdateView, CreateView, 15 | DeleteView) 16 | 17 | from .forms import LoginForm, SignupForm 18 | from apps.accounts.models import ElementalUser 19 | from apps.accounts.mixins import UnbannedUserMixin 20 | from apps.projects.models import Project 21 | 22 | 23 | class Index(UnbannedUserMixin, FormView): 24 | template_name = 'index.html' 25 | form_class = LoginForm 26 | success_url = reverse_lazy('index') 27 | 28 | def get_context_data(self, **kwargs): 29 | context = super(Index, self).get_context_data(**kwargs) 30 | context['featured'] = Project.objects.exclude(featured__isnull=True).order_by('-featured')[:5] 31 | return context 32 | 33 | def form_valid(self, form): 34 | super(Index, self).form_valid(form) 35 | username = form.cleaned_data['username'] 36 | password = form.cleaned_data['password'] 37 | user = authenticate(username=username, password=password) 38 | 39 | if user is not None: 40 | if user.is_active: 41 | login(self.request, user) 42 | user.set_ip(self.request) 43 | if not user.banned: 44 | return super(Index, self).form_valid(form) 45 | else: 46 | return redirect(reverse('ban-page')) 47 | else: 48 | form.errors['non_field_errors'] = ['Your account is not active.'] 49 | return render(self.request, 'index.html', 50 | {'form': form}) 51 | else: 52 | form.errors['non_field_errors'] = ['Invalid login'] 53 | return render(self.request, 'index.html', 54 | {'form': form}) 55 | 56 | 57 | class BanPage(TemplateView): 58 | template_name = 'banned.html' 59 | 60 | def get(self, request): 61 | if request.user and not request.user.banned: # if user is logged in and is not banned 62 | return redirect(reverse('index')) 63 | else: # user logged and banned 64 | return render(request, 'banned.html') 65 | 66 | 67 | class TermsOfService(TemplateView): 68 | template_name = 'terms_of_service.html' 69 | 70 | 71 | class SignUp(FormView): 72 | template_name = 'register.html' 73 | form_class = SignupForm 74 | success_url = '/' 75 | 76 | def get(self, request): 77 | if request.user.is_authenticated(): 78 | return redirect(reverse('index')) 79 | return super(SignUp, self).get(request) 80 | 81 | def form_valid(self, form): 82 | super(SignUp, self).form_valid(form) 83 | form.save() 84 | user = authenticate(username=form.cleaned_data.get('username'), 85 | password=form.cleaned_data.get('password1')) 86 | login(self.request, user) 87 | return redirect(reverse('index')) 88 | 89 | 90 | class Logout(View): 91 | def get(self, request, *args, **kwargs): 92 | logout(request) 93 | return redirect('/') -------------------------------------------------------------------------------- /apps/galleries/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/galleries/__init__.py -------------------------------------------------------------------------------- /apps/galleries/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Gallery 3 | 4 | admin.site.register(Gallery) -------------------------------------------------------------------------------- /apps/galleries/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('projects', '0007_project_thumbnail'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Gallery', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('name', models.CharField(max_length=25)), 19 | ], 20 | ), 21 | migrations.CreateModel( 22 | name='ProjectToGallery', 23 | fields=[ 24 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 25 | ('gallery', models.ForeignKey(to='galleries.Gallery')), 26 | ('project', models.ForeignKey(to='projects.Project')), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /apps/galleries/migrations/0002_usertogallery.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('galleries', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='UserToGallery', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 20 | ('gallery', models.ForeignKey(to='galleries.Gallery')), 21 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /apps/galleries/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/galleries/migrations/__init__.py -------------------------------------------------------------------------------- /apps/galleries/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from apps.projects.models import Project 4 | from apps.accounts.models import ElementalUser 5 | 6 | class Gallery(models.Model): 7 | name = models.CharField(max_length=25) 8 | 9 | 10 | class ProjectToGallery(models.Model): 11 | project = models.ForeignKey(Project) 12 | gallery = models.ForeignKey('Gallery') 13 | 14 | def __unicode__(self): 15 | return '{0} is in {1}'.format(self.project.name, self.gallery.name) 16 | 17 | 18 | class UserToGallery(models.Model): 19 | user = models.ForeignKey(ElementalUser) 20 | gallery = models.ForeignKey('Gallery') 21 | STATUS_CHOICES = ( 22 | ('owner', 'Owner'), 23 | ('admin', 'Admin'), 24 | ('member', 'Member'), 25 | ) 26 | 27 | def __unicode__(self): 28 | return '{0} is a(n) {1} of {2}'.format(self.user.username, self.status, self.gallery.name) -------------------------------------------------------------------------------- /apps/projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/projects/__init__.py -------------------------------------------------------------------------------- /apps/projects/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Project 3 | 4 | class ProjectAdmin(admin.ModelAdmin): 5 | readonly_fields = ('created', 'updated', ) 6 | 7 | admin.site.register(Project, ProjectAdmin) -------------------------------------------------------------------------------- /apps/projects/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Project', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('name', models.CharField(max_length=50, verbose_name='name', blank=True)), 20 | ('shared', models.BooleanField(default=False)), 21 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /apps/projects/migrations/0002_remove_project_shared.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('projects', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='project', 16 | name='shared', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /apps/projects/migrations/0003_project_shared.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('projects', '0002_remove_project_shared'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='project', 16 | name='shared', 17 | field=models.BooleanField(default=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/projects/migrations/0004_project_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('projects', '0003_project_shared'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='project', 16 | name='data', 17 | field=models.TextField(default=''), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /apps/projects/migrations/0005_auto_20150902_1930.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('projects', '0004_project_data'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='project', 16 | name='name', 17 | field=models.CharField(max_length=50, verbose_name='name'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/projects/migrations/0006_project_deleted.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('projects', '0005_auto_20150902_1930'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='project', 16 | name='deleted', 17 | field=models.BooleanField(default=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/projects/migrations/0007_project_thumbnail.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('projects', '0006_project_deleted'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='project', 16 | name='thumbnail', 17 | field=models.TextField(default=''), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /apps/projects/migrations/0008_project_updated.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('projects', '0007_project_thumbnail'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='project', 17 | name='updated', 18 | field=models.DateTimeField(default=datetime.datetime(2016, 3, 4, 13, 43, 39, 736000), auto_now=True), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /apps/projects/migrations/0009_project_created.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('projects', '0008_project_updated'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='project', 17 | name='created', 18 | field=models.DateTimeField(default=datetime.datetime(2016, 3, 4, 13, 47, 16, 703000), auto_now_add=True), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /apps/projects/migrations/0010_project_featured.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-05-01 21:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('projects', '0009_project_created'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='project', 17 | name='featured', 18 | field=models.DateField(blank=True, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /apps/projects/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/projects/migrations/__init__.py -------------------------------------------------------------------------------- /apps/projects/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from apps.accounts.models import ElementalUser 4 | 5 | class Project(models.Model): 6 | name = models.CharField('name', max_length=50) 7 | user = models.ForeignKey(ElementalUser) 8 | shared = models.BooleanField(default=False) 9 | data = models.TextField() 10 | deleted = models.BooleanField(default=False) 11 | thumbnail = models.TextField() 12 | updated = models.DateTimeField(auto_now=True) 13 | created = models.DateTimeField(auto_now_add=True) 14 | featured = models.DateField(blank=True, null=True) 15 | 16 | def __unicode__(self): 17 | return self.name 18 | 19 | def save(self, *args, **kwargs): 20 | if not self.user.trusted: 21 | self.shared = False 22 | super(Project, self).save(*args, **kwargs) -------------------------------------------------------------------------------- /apps/projects/static/css/editor/blocks.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: gray; 3 | } 4 | 5 | .scriptingArea, 6 | .draggy, 7 | .blockArea { 8 | font-family:'Source Code Pro'; 9 | color:white; 10 | white-space: nowrap; 11 | /* Don't let text to "wrap" to a new line (blocks would look ugly that way) */ 12 | -webkit-user-select: none; /* Chrome all / Safari all */ 13 | -moz-user-select: none; /* Firefox all */ 14 | -ms-user-select: none; /* IE 10+ */ 15 | user-select: none; /* Likely future */ 16 | 17 | cursor: default; 18 | } 19 | .draggy { 20 | position:absolute; 21 | z-index: 999; 22 | padding: 0; 23 | margin: 0; 24 | } 25 | .script-input { 26 | font-family:'Source Code Pro'; 27 | padding:0 5px; 28 | min-width: 25px; 29 | min-height: 20px; 30 | border-radius:999px; 31 | border:none; 32 | outline:none; 33 | background-color:white; 34 | color: black; 35 | display: inline-block; 36 | text-align: center; 37 | cursor: text; 38 | z-index: 100; 39 | } 40 | .hat { 41 | display:table; 42 | /* Using "block" would result in 100% width, and "inline-block" would result in everything on same line */ 43 | background:#C6E44D; 44 | padding:5px 10px; 45 | position:relative; 46 | border-radius:0px 5px 5px 0px; 47 | } 48 | .hat:after { 49 | background:#C6E44D; 50 | display:block; 51 | content:' '; 52 | width:80%; 53 | height:100%; 54 | position:absolute; 55 | top:-45%; 56 | left:0px; 57 | border-radius:50%; 58 | z-index:-1; 59 | } 60 | .stack { 61 | display:table; 62 | background:#914DE4; 63 | padding:5px 10px; 64 | position:relative; 65 | border-radius:5px; 66 | /*border-left: 1px solid #A9A9A9;*/ 67 | 68 | /* Replacing borders with box-shadows allows curving around border-radius */ 69 | box-shadow: -1px 0px 0px #A9A9A9; 70 | } 71 | 72 | .scriptingArea { 73 | * + .stack, 74 | * + .c-wrapper > .c-header { 75 | border-top-left-radius: 0px; 76 | } 77 | .stack:not(:last-child), 78 | .c-wrapper:not(:first-child) > .c-header, 79 | .c-wrapper:not(:last-child) > .c-footer { // not 100% sure if this works 100% of the time 80 | border-bottom-left-radius: 0px; 81 | } 82 | } 83 | 84 | .c-wrapper { 85 | padding:0px; 86 | display:table; 87 | /*border-left: 1px solid #A9A9A9;*/ 88 | } 89 | .c-header, 90 | .c-footer { 91 | display:table; 92 | background:#E44D4D; 93 | padding:5px 10px; 94 | position:relative; 95 | border-radius:5px; 96 | min-width:40px; 97 | min-height:18px; 98 | } 99 | .c-header { 100 | border-bottom-left-radius:0px; 101 | } 102 | .c-footer { 103 | border-top-left-radius:0px; 104 | margin-bottom:1px; 105 | } 106 | .c-content { 107 | background:#E44D4D; 108 | padding:6px 0px 6px 25px; 109 | margin:-6px 0px -6px 0px; 110 | width:4px; 111 | overflow-x: visible; 112 | min-height: 30px; 113 | position:relative; /* For z-index (appear on top of .c-header, which in turn puts it on top of the extra border-bottom when attaching a block) */ 114 | box-shadow: -1px 0px 0px #A9A9A9; 115 | } 116 | .c-header { 117 | box-shadow: -1px -1px 0px #A9A9A9; 118 | } 119 | .c-footer { 120 | box-shadow: -1px 1px 0px #A9A9A9; 121 | } 122 | .drop-area { 123 | border-bottom: 5px solid white; 124 | } 125 | .c-wrapper.zebra > li, .c-wrapper.zebra > ul { 126 | background:#F45D5D; 127 | } 128 | 129 | .remove-attr { 130 | display:inline-block; 131 | width:0px; 132 | height:0px; 133 | border:7px solid transparent; 134 | border-right-color:black; 135 | margin-left:-4px; 136 | margin-right:2px; 137 | cursor:pointer; 138 | } 139 | .add-attr { 140 | display:inline-block; 141 | width:0px; 142 | height:0px; 143 | border:7px solid transparent; 144 | border-left-color:black; 145 | margin-left:2px; 146 | margin-right:-4px; 147 | cursor:pointer; 148 | } 149 | 150 | .attr-holder { 151 | background-color: orange; 152 | border-radius: 10px; 153 | box-sizing: border-box; 154 | display: inline-block; 155 | padding: 3px; 156 | margin-right: 6px; 157 | .attr-dropdown, 158 | .attr-input { 159 | border-radius: 10px; 160 | min-width: 30px; 161 | display: inline-block; 162 | margin:0px 3px; 163 | padding: 0px 4px; 164 | outline:none; 165 | } 166 | .attr-input { 167 | background-color: white; 168 | color: black; 169 | } 170 | .attr-dropdown { 171 | background-color: white; 172 | color:black; 173 | &:empty { 174 | content: ' '; 175 | } 176 | cursor:text; 177 | } 178 | } -------------------------------------------------------------------------------- /apps/projects/static/css/editor/helpers.scss: -------------------------------------------------------------------------------- 1 | .fake-hidden { 2 | position: absolute; 3 | top: -9999px; 4 | left: -9999px; 5 | opacity: 0; 6 | } 7 | .hidden { 8 | display: none; 9 | } -------------------------------------------------------------------------------- /apps/projects/static/css/editor/menus.scss: -------------------------------------------------------------------------------- 1 | // All the stuff for custom context menus... 2 | 3 | .context-menu { 4 | position: absolute; 5 | font-family: 'Source Sans Pro', sans-serif; 6 | z-index: 1000; 7 | background-color: white; 8 | box-sizing: border-box; 9 | border: 3px solid gray; 10 | border-radius: 10px; 11 | display: none; 12 | min-width: 150px; 13 | .menu-item { 14 | padding: 5px; 15 | cursor: pointer; 16 | &:hover { 17 | color: white; 18 | background-color: gray; 19 | } 20 | } 21 | } 22 | 23 | .attributeDropdown { 24 | position: absolute; 25 | border-radius: 10px; 26 | background-color: #63B9D6; 27 | z-index: 100; 28 | overflow:hidden; 29 | 30 | width: 200px; 31 | height: 200px; 32 | } 33 | .searchContainer { 34 | padding:3px 0px; 35 | text-align:center; 36 | } 37 | .inlineSearch { 38 | display:block; 39 | margin:3px auto; 40 | border:none; 41 | border-radius:999px; 42 | padding:4px; 43 | text-indent: 24px; 44 | background-image: url(../img/nav-search-glass.png); 45 | background-repeat: no-repeat; 46 | background-position: 8px 4px; 47 | outline:none; 48 | width:calc(100% - 22px); 49 | } 50 | .scroll-up, .scroll-down { 51 | background:#399CBD; 52 | height:20px; 53 | text-align:center; 54 | } 55 | .scroll-up:hover, .scroll-down:hover { 56 | background:#1B7D9E; 57 | } 58 | .scroll-down { 59 | position:absolute; 60 | bottom:0px; 61 | left:0px; 62 | width:100%; 63 | } 64 | .upArrow { 65 | display:inline-block; 66 | border:9px solid transparent; 67 | border-bottom-color:white; 68 | position:relative; 69 | bottom:3px; 70 | } 71 | .downArrow { 72 | display:inline-block; 73 | border:9px solid transparent; 74 | border-top-color:white; 75 | position:relative; 76 | top:5px; 77 | } 78 | .attribute-results { 79 | list-style:none; 80 | padding:0; 81 | margin:0; 82 | color:white; 83 | font-family: 'Source Sans Pro', sans-serif; 84 | } 85 | .attribute-results li { 86 | padding:2px 8px; 87 | cursor:pointer; 88 | } 89 | .attribute-results li:hover { 90 | background:rgba(255,255,255,0.2); 91 | } -------------------------------------------------------------------------------- /apps/projects/static/css/editor/style.scss: -------------------------------------------------------------------------------- 1 | $nav_grad_top: #42BA92; 2 | $nav_grad_bottom: #27ae60; 3 | $borders_color: #252E35; 4 | $right_background: #444C54; 5 | $right_transparent: rgba(68,76,84, 0.6); 6 | $right_texture: "/static/img/diagonalTexture.png"; 7 | $top_bar_text_shadow: rgba(0,0,0,0.5); 8 | $menu_drop_back: #fff; 9 | $menu_drop_hover: #58B2C8; 10 | $menu_drop_text: #333; 11 | $search_icon_url: "/static/img/nav-search-glass.png"; 12 | 13 | @import "blocks"; 14 | @import "menus"; 15 | @import "helpers"; 16 | 17 | @import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700); 18 | 19 | body { 20 | margin: 0px; 21 | overflow: hidden; 22 | } 23 | 24 | html, body { 25 | height: 100%; 26 | } 27 | 28 | .scriptingArea { 29 | height: calc(100% - 200px - 12px); 30 | min-width: 100%; 31 | overflow: auto; 32 | position: relative; 33 | 34 | //for some reason this is cutting off the hat block ::after element... 35 | background: linear-gradient($right_transparent, $right_transparent), url($right_texture); 36 | z-index: 1; 37 | } 38 | 39 | .topBar { 40 | /* Monster Gradient: */ 41 | background: $nav_grad_top; 42 | //border-bottom: 1px solid $borders_color; 43 | color: #efefef; 44 | font-family: 'Source Sans Pro', sans-serif; 45 | padding: 15px 24px; 46 | position:relative; 47 | z-index:999; 48 | } 49 | 50 | .left { 51 | display: inline-block; 52 | } 53 | 54 | .logo { 55 | color: white; 56 | font-size: 20px; 57 | font-weight: bold; 58 | //text-shadow: 0px 1px 1px $top_bar_text_shadow; 59 | vertical-align: middle; 60 | } 61 | 62 | .menus { 63 | display: inline-block; 64 | margin-left: 30px; 65 | vertical-align: middle; 66 | } 67 | 68 | .menu { 69 | display: inline-block; 70 | margin-right: 20px; 71 | position: relative; 72 | top: 1px; 73 | } 74 | .menuTitle:hover + .menuContents, .menuContents:hover { 75 | display: block; 76 | } 77 | 78 | .menuTitle { 79 | cursor: default; 80 | position: relative; 81 | z-index: 999; 82 | } 83 | 84 | .menuContents { 85 | background: $menu_drop_back; 86 | border-radius: 3px; 87 | display: none; 88 | left: 0px; 89 | list-style: none; 90 | margin: 0px; 91 | padding: 0px; 92 | position: absolute; 93 | top: 30px; 94 | width: 145px; 95 | z-index: 5; 96 | border:1px solid $borders_color; 97 | } 98 | 99 | .menuContents:before { 100 | background: $menu_drop_back; 101 | content: ' '; 102 | display: block; 103 | width: 12px; 104 | height: 12px; 105 | left: 5px; 106 | position: absolute; 107 | top: -7px; 108 | z-index: -8; 109 | border:1px solid $borders_color; 110 | border-bottom-color:transparent; 111 | border-right-color:transparent; 112 | 113 | -ms-transform: rotate(45deg); /* IE 9 */ 114 | -webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */ 115 | transform: rotate(45deg); 116 | } 117 | 118 | .menuContents:after { 119 | content: ' '; 120 | display: block; 121 | height: 130%; 122 | height: calc(100% + 40px); 123 | left: -10px; 124 | position: absolute; 125 | top: -30px; 126 | width: 120%; 127 | /* For older browsers */ 128 | width: calc(100% + 20px); 129 | z-index: -1; 130 | } 131 | 132 | .menuContents li { 133 | color: $menu_drop_text; 134 | cursor: pointer; 135 | padding: 10px 20px; 136 | } 137 | 138 | .menuContents li:first-of-type { 139 | border-top-left-radius: 2px; 140 | border-top-right-radius: 2px; 141 | } 142 | 143 | .menuContents li:last-of-type { 144 | border-bottom-left-radius: 2px; 145 | border-bottom-right-radius: 2px; 146 | } 147 | 148 | .menuContents li:hover { 149 | background: $menu_drop_hover; 150 | } 151 | 152 | .mainArea { 153 | align-content: stretch; 154 | display: flex; 155 | height: calc(100% - 45px); 156 | } 157 | 158 | .leftSide { 159 | border-right: 4px solid $borders_color; 160 | box-sizing: border-box; 161 | flex: 0 0 400px; 162 | height: 100%; 163 | max-width: 100%; 164 | overflow-x: hidden; 165 | .pagePreview { 166 | height: calc(100% - 200px); 167 | .previewBody { 168 | background: white; 169 | height: 100%; 170 | width: 100%; 171 | border: none; 172 | } 173 | } 174 | .filePane { 175 | height: 200px; 176 | background-color: white; 177 | box-sizing: border-box; 178 | border-top: 5px solid #BFBFBF; 179 | padding: 5px; 180 | overflow-y: scroll; 181 | .file, 182 | .add-file { 183 | width: 100px; 184 | height: 100px; 185 | border: 4px solid #BFBFBF; 186 | border-radius: 3px; 187 | display: inline-block; 188 | text-align: center; 189 | margin: 0px; 190 | margin-right: 10px; 191 | margin-bottom: 10px; 192 | } 193 | .file { 194 | &-name { 195 | position: relative; 196 | top: 50%; 197 | transform: translateY(-50%); 198 | font-family: 'Source Sans Pro', sans-serif; 199 | } 200 | &.selected { 201 | border-color:#989898; 202 | } 203 | } 204 | } 205 | } 206 | 207 | .rightSide { 208 | flex-grow: 1; 209 | flex-shrink: 1; 210 | height: 100%; 211 | width: 100%; 212 | 213 | .upperArea { 214 | border-bottom: 2px solid $borders_color; 215 | display: flex; 216 | height: 200px; 217 | width: 100%; 218 | } 219 | } 220 | 221 | .upperArea .paletteSelect { 222 | background: $right_background; 223 | border-right: 2px solid $borders_color; 224 | flex: 0 0 250px; 225 | overflow-y: auto; 226 | padding: 20px; 227 | } 228 | 229 | .upperArea .allBlocks { 230 | background: $right_background; 231 | height: 100%; 232 | overflow-x: hidden; 233 | overflow-y: auto; 234 | position: relative; 235 | /* For child positioning (absolute) */ 236 | width: 100%; 237 | } 238 | 239 | .paletteColumn { 240 | width: 83px; 241 | margin: 0px 15px; 242 | padding: 0px; 243 | text-align: left; 244 | display: inline-block; 245 | position: relative; 246 | left: -3px; 247 | 248 | .paletteOptionWrap:last-of-type { 249 | margin-bottom: 0px; 250 | } 251 | } 252 | 253 | .paletteOptionWrap { 254 | display: inline-block; 255 | border: 1px solid #505E77; 256 | /* Massive gradient code: */ 257 | background: rgb(255,255,255); 258 | border-radius: 999px; 259 | padding: 3px 12px; 260 | font-family: 'Source Sans Pro', sans-serif; 261 | cursor: pointer; 262 | width: 100%; 263 | margin-bottom: 15px; 264 | position: relative; 265 | 266 | input[type=radio] { 267 | display: none; 268 | } 269 | } 270 | 271 | .paletteColorIndicator { 272 | display: block; 273 | width: 20px; 274 | height: 20px; 275 | background: rgb(222,42,51); 276 | position: absolute; 277 | top: 3px; 278 | right: 4px; 279 | border-radius: 999px; 280 | } 281 | 282 | .paletteColorIndicator:after { 283 | content: ' '; 284 | //display: block; 285 | display:none; 286 | width: 20px; 287 | height: 26px; 288 | background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,0.12) 100%); 289 | /* FF3.6+ */ 290 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,0.12))); 291 | /* Chrome,Safari4+ */ 292 | background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,0.12) 100%); 293 | /* Chrome10+,Safari5.1+ */ 294 | background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,0.12) 100%); 295 | /* Opera 11.10+ */ 296 | background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,0.12) 100%); 297 | /* IE10+ */ 298 | background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,0.12) 100%); 299 | /* W3C */ 300 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#1f000000',GradientType=0 ); 301 | /* IE6-9 */ 302 | position: absolute; 303 | top: 0px; 304 | right: 0px; 305 | border-radius: 0px 999px 999px 0px; 306 | border: 1px solid rgba(0,0,0,0.15); 307 | border-left-color: transparent; 308 | } 309 | 310 | .paletteOptionWrap:hover { 311 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0); 312 | } 313 | 314 | #searchBar { 315 | position: absolute; 316 | top: 0px; 317 | right: 57px; 318 | border-radius: 0px 0px 5px 5px; 319 | border: 1px solid $borders_color; 320 | border-top: none; 321 | outline: none; 322 | padding: 8px 10px 8px 26px; 323 | background-image: url($search_icon_url); 324 | background-repeat: no-repeat; 325 | background-position: 8px 9px; 326 | cursor: pointer; 327 | width: 0px; 328 | padding-right: 4px; 329 | opacity: 0.7; 330 | transition: width 0.2s, 331 | padding 0.2s; 332 | } 333 | 334 | #searchBar:hover, #searchBar:focus { 335 | opacity: 1; 336 | } 337 | 338 | #searchBar:focus { 339 | cursor: auto; 340 | width: 170px; 341 | } 342 | 343 | .infoText { 344 | color:#ddd; 345 | font-family:'Source Code Pro', sans-serif; 346 | } 347 | 348 | .upperArea .allBlocks .blockArea { 349 | /* 350 | display: flex; 351 | flex-direction: column; 352 | flex-wrap: wrap; 353 | */ 354 | height: 100%; 355 | height: calc(100% - 64px); 356 | overflow-x: hidden; 357 | overflow-y: auto; 358 | justify-content: flex-start; 359 | padding: 32px; 360 | .stack, .c-wrapper { 361 | margin:6px 0px; 362 | } 363 | } 364 | 365 | .trashCan { 366 | display: none; 367 | position: fixed; 368 | top: 56px; 369 | left: calc(130px + 50%); 370 | width: 140px; 371 | height: 180px; 372 | z-index: 800; 373 | background-size: 100% auto; 374 | background-image: url("/static/img/trash_closed.png"); 375 | cursor: default; 376 | &:hover, 377 | &.hovering { 378 | background-image: url("/static/img/trash_open.png"); 379 | } 380 | &.showing { 381 | display: block; 382 | } 383 | } 384 | 385 | .trashCan2 { 386 | display: block; 387 | position: fixed; 388 | bottom: 10px; 389 | right: 20px; 390 | width: 70px; 391 | height: 90px; 392 | z-index: 800; 393 | background-size: 100% auto; 394 | background-image: url("/static/img/trash_closed.png"); 395 | cursor: default; 396 | &:hover, 397 | &.hovering { 398 | background-image: url("/static/img/trash_open.png"); 399 | } 400 | } 401 | 402 | @media (max-width:450px) { 403 | .advanceWrap { 404 | display: none; 405 | } 406 | } -------------------------------------------------------------------------------- /apps/projects/static/css/my-stuff.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Raleway:400,700,900,300); 2 | 3 | .creation-box { 4 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.7); 5 | margin: 10px; 6 | padding: 10px; 7 | width: 360px; 8 | height: 240px; 9 | display: inline-block; 10 | background-image:url("https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Magnificent_CME_Erupts_on_the_Sun_-_August_31.jpg/800px-Magnificent_CME_Erupts_on_the_Sun_-_August_31.jpg"); 11 | background-size: cover; 12 | 13 | font-family: Raleway; 14 | } 15 | 16 | .creation-box input[name="creation-title"] { 17 | width: 340px; 18 | padding: 5px; 19 | margin: auto; 20 | border: none; 21 | box-shadow: 0 0 20px #000; 22 | border-radius: 5px; 23 | transition: box-shadow 0.5s ease; 24 | 25 | font-family: Raleway; 26 | } 27 | 28 | .creation-box input[type='text']:focus { 29 | box-shadow: 0 0 20px #fff, 0 0 10px #fff; 30 | } 31 | 32 | .creation-box button, .share-box { 33 | background-color: #fff; 34 | border: none; 35 | border-radius: 3px; 36 | width: 90px; 37 | margin: 5px; 38 | transition: background-color 0.3s ease, color 0.3s ease; 39 | font-size: 1em; 40 | box-shadow: 0 0 10px #000; 41 | 42 | font-family: Raleway; 43 | } 44 | 45 | /* delete button */ 46 | .creation-box button { 47 | margin-top: 150px; 48 | 49 | font-family: Raleway; 50 | } 51 | 52 | .creation-box button:hover { 53 | color: #fff; 54 | background-color: #D33439; 55 | } 56 | 57 | /* share checkbox */ 58 | .share-box { 59 | font-family: Raleway; 60 | } 61 | .share-box:hover { 62 | color: #fff; 63 | background-color: #017CC2; 64 | } -------------------------------------------------------------------------------- /apps/projects/static/img/diagonalTexture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/projects/static/img/diagonalTexture.png -------------------------------------------------------------------------------- /apps/projects/static/img/nav-search-glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/projects/static/img/nav-search-glass.png -------------------------------------------------------------------------------- /apps/projects/static/img/trash_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/projects/static/img/trash_closed.png -------------------------------------------------------------------------------- /apps/projects/static/img/trash_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/projects/static/img/trash_open.png -------------------------------------------------------------------------------- /apps/projects/static/js/components/projectSettings.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 |
12 |
13 | 14 | 17 |
18 |
19 | 73 |
-------------------------------------------------------------------------------- /apps/projects/static/js/editor/block2json.js: -------------------------------------------------------------------------------- 1 | //probably should put this in a local function on prod... fine for now tho 2 | 3 | if (!String.prototype.startsWith) { // sweet polyfill 4 | String.prototype.startsWith = function(searchString, position) { 5 | position = position || 0; 6 | return this.indexOf(searchString, position) === position; 7 | }; 8 | } 9 | 10 | function toArr(nl) { 11 | return Array.prototype.slice.call(nl); 12 | } 13 | 14 | function includesArrItem(str, arr) { 15 | var includes = false; 16 | for (var i = 0; i < arr.length; i++) { 17 | if (str.indexOf(arr[i]) > -1) { 18 | includes = true; 19 | break; 20 | } 21 | } 22 | return includes; 23 | } 24 | 25 | function getChildElem(parent, cName) { 26 | var child = null; 27 | for (var i = 0; i < parent.childNodes.length; i++) { 28 | if (parent.childNodes[i].className == cName) { 29 | child = parent.childNodes[i]; 30 | break; 31 | } 32 | } 33 | return child; 34 | } 35 | 36 | function getElType(node) { 37 | var classList = node.className.split(' '); 38 | var type = null; 39 | for (var i = 0; i < classList.length; i++) { 40 | if (classList[i].startsWith('e-')) { 41 | type = classList[i].substr(2, classList[i].length - 1); 42 | break; 43 | } 44 | } 45 | return type; 46 | } 47 | 48 | function getAttrNames(classes) { 49 | var classList = classes.split(' '); 50 | var names = []; 51 | for (var i = 0; i < classList.length; i++) { 52 | if (attrNames.indexOf(classList[i]) > -1) { 53 | names.push(classList[i]); 54 | } 55 | } 56 | return names; 57 | } 58 | 59 | function getSingleAttrs(element) { 60 | // get attributes from element 61 | var attrs = {}; 62 | var attrElems = toArr(element.children); 63 | for (var i = 0; i < attrElems.length; i++) { 64 | var attrHolder = attrElems[i]; 65 | if (attrHolder.classList && attrHolder.classList.contains('attr-holder')) { 66 | var attrName = attrHolder.children[0].textContent; 67 | var attrValue = attrHolder.children[1].textContent; 68 | attrs[encodeEntities(attrName)] = encodeEntities(attrValue); 69 | } 70 | } 71 | return attrs; 72 | } 73 | 74 | function getWrapperAttrs(element) { 75 | element = element.children[0]; 76 | var attrs = {}; 77 | var attrElems = toArr(element.children); 78 | for (var i = 0; i < attrElems.length; i++) { 79 | var attrHolder = attrElems[i]; 80 | if (attrHolder.classList && attrHolder.classList.contains('attr-holder')) { 81 | var attrName = attrHolder.children[0].textContent; 82 | var attrValue = attrHolder.children[1].textContent; 83 | attrs[encodeEntities(attrName)] = encodeEntities(attrValue); 84 | } 85 | } 86 | return attrs; 87 | } 88 | 89 | function encodeEntities(str) { 90 | return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); 91 | } 92 | 93 | function getText(elem) { 94 | var text = ''; 95 | var childNodes = toArr(elem.children); 96 | for (var i = 0; i < childNodes.length; i++) { 97 | if (childNodes[i].classList.contains(textInput)) { 98 | text += childNodes[i].children[0].textContent; 99 | } 100 | } 101 | return text; 102 | } 103 | 104 | function getInlineText(elem) { 105 | var text = ''; 106 | var childNodes = toArr(elem.children); 107 | for (var i = 0; i < childNodes.length; i++) { 108 | if (childNodes[i].classList.contains(textInput)) { 109 | text += childNodes[i].textContent; 110 | } 111 | } 112 | return text; 113 | } 114 | 115 | function traverseTree(parentNode) { 116 | parentNode = parentNode.children[1]; 117 | var directChildren = toArr(parentNode.children); 118 | var pushedArr = []; 119 | for (var i = 0; i < directChildren.length; i++) { 120 | if (includesArrItem(directChildren[i].className, stackElements)) { //things like imgs 121 | var elType = getElType(directChildren[i]); 122 | if (elType == 'text') { 123 | elType = ''; 124 | } 125 | if (elType == 'CSS') { 126 | var cssFileName = getSingleAttrs(directChildren[i]).href; 127 | if (fileData.hasOwnProperty(cssFileName)) { 128 | var cssText = CSSJSON.toCSS({ 129 | children: fileData[cssFileName], 130 | attributes: {} 131 | }); 132 | pushedArr.push({ 133 | tag: 'style', 134 | attr: getSingleAttrs(directChildren[i]), 135 | text: cssText 136 | }); 137 | } else { 138 | pushedArr.push({ 139 | tag: 'style', 140 | attr: getSingleAttrs(directChildren[i]), 141 | text: '' 142 | }); 143 | } 144 | } else { 145 | pushedArr.push({ 146 | tag: elType, 147 | attr: elType ? getSingleAttrs(directChildren[i]) : {}, 148 | text: encodeEntities(getInlineText(directChildren[i])) 149 | }); 150 | } 151 | } else if (includesArrItem(directChildren[i].className, wrapperElements)) { // things that can nest things - ie most elements 152 | var elType = getElType(directChildren[i]); 153 | pushedArr.push({ 154 | tag: elType, 155 | attr: getWrapperAttrs(directChildren[i]), 156 | child: traverseTree(directChildren[i]), 157 | }); 158 | } 159 | } 160 | return pushedArr; //recursively get children of blocks 161 | } 162 | 163 | function getCSSAttributes(children) { 164 | var attributes = {}; 165 | for (var i = 0; i < children.length; i++) { 166 | var child = children[i]; 167 | var attrName = encodeEntities(child.children[0].textContent); 168 | var attrValue = encodeEntities(child.children[1].textContent); 169 | attributes[attrName] = attrValue; 170 | } 171 | return attributes; 172 | } 173 | 174 | function setFrameContent(ext) { 175 | ext = ext || currentFile.split('.').pop(); 176 | var script = document.getElementsByClassName('script')[0].cloneNode(true); //should only be one... 177 | var previewElement = document.getElementsByClassName('previewBody')[0]; 178 | 179 | var directChildren = toArr(script.children); 180 | directChildren.shift(); 181 | 182 | if (ext == 'css') { 183 | var jsonFormat = {}; 184 | for (var i = 0; i < directChildren.length; i++) { 185 | //this should be easier than HTML because it's merely a list of selectors 186 | var child = directChildren[i]; 187 | // check to make sure it's a selector block 188 | if (child.classList.contains('e-selector')) { 189 | var selector = child.children[0].children[0].textContent; 190 | jsonFormat[selector] = {}; 191 | jsonFormat[selector].attributes = getCSSAttributes(child.children[1].children); 192 | // console.log(child.children[1].children); 193 | } 194 | } 195 | fileData[currentFile] = jsonFormat; 196 | } else if (ext == 'html') { 197 | 198 | var jsonFormat = { 199 | tag: 'body', 200 | attr: {}, 201 | child: [], 202 | }; 203 | var blocks = []; 204 | 205 | for (var i = 0; i < directChildren.length; i++) { 206 | if (includesArrItem(directChildren[i].className, stackElements)) { //things like imgs 207 | var elType = getElType(directChildren[i]); 208 | if (elType == 'text') { 209 | elType = ''; 210 | } 211 | if (elType == 'CSS') { 212 | console.log(getSingleAttrs(directChildren[i])); 213 | } else { 214 | blocks.push({ 215 | tag: elType, 216 | attr: elType ? getSingleAttrs(directChildren[i]) : {}, 217 | text: encodeEntities(getInlineText(directChildren[i])) 218 | }); 219 | } 220 | } else if (includesArrItem(directChildren[i].className, wrapperElements)) { // things that can nest things - ie most elements 221 | var elType = getElType(directChildren[i]); 222 | blocks.push({ 223 | tag: elType, 224 | child: traverseTree(directChildren[i]), 225 | }); 226 | } 227 | } 228 | 229 | if (blocks[0]) { 230 | jsonFormat = blocks[0]; 231 | } 232 | 233 | fileData[currentFile] = jsonFormat; 234 | 235 | var parsedHtml = json2html(jsonFormat); 236 | 237 | var previewWindow = previewElement; 238 | previewWindow = (previewWindow.contentWindow) ? previewWindow.contentWindow : (previewWindow.contentDocument.document) ? previewWindow.contentDocument.document : previewWindow.contentDocument; 239 | previewWindow.document.open(); 240 | previewWindow.document.write(parsedHtml); 241 | previewWindow.document.close(); 242 | } else { 243 | throw 'this should never be thrown though'; 244 | } 245 | } 246 | 247 | setFrameContent(); 248 | 249 | // example: 250 | // 251 | // var json = { 252 | // tag: 'body', 253 | // attr: { 254 | // id: '1', 255 | // class: ['foo'] 256 | // }, 257 | // child: [{ 258 | // tag: 'h2', 259 | // text: 'sample text with inline tag' 260 | // },{ 261 | // tag: 'pre', 262 | // attr: { 263 | // id: 'demo', 264 | // class: ['foo', 'bar'] 265 | // } 266 | // },{ 267 | // tag: 'pre', 268 | // attr: { 269 | // id: 'output', 270 | // class: ['goo'] 271 | // } 272 | // },{ 273 | // tag: 'input', 274 | // attr: { 275 | // id: 'execute', 276 | // type: 'button', 277 | // value: 'execute' 278 | // } 279 | // }] 280 | // }; -------------------------------------------------------------------------------- /apps/projects/static/js/editor/blockAttributes.js: -------------------------------------------------------------------------------- 1 | var ATTRIBUTE_MENU = document.getElementById('blockAttributeDropdown'); 2 | var ATTRIBUTE_SEARCH = document.getElementById('propSearch'); 3 | var ATTRIBUTE_RESULTS = document.getElementById('attributeResults'); 4 | 5 | var CLICKED_ATTR; 6 | 7 | SCRIPTING_AREA.addEventListener('click', function(ev) { 8 | var el = ev.target; 9 | 10 | // Check if click was on rightward arrow 11 | if (ev.target.classList.contains('add-attr')) { 12 | 13 | // If so, add an attribute block 14 | var newAttrString = [ 15 | '', 16 | ' ', 17 | '=', 18 | '', 19 | '' 20 | ].join(''); 21 | var newAttr = stringToHtml(newAttrString); 22 | el.parentNode.parentNode.insertBefore(newAttr, el.parentNode); 23 | 24 | // Check if click was on the leftward arrow 25 | } else if (ev.target.classList.contains('remove-attr')) { 26 | 27 | // If so, remove the last attribute block 28 | var prev = el.parentNode.previousElementSibling; 29 | if (prev) { 30 | prev.parentNode.removeChild(prev); 31 | } 32 | } 33 | 34 | // Check if click was on the first input of an attribute block 35 | if (ev.target.classList.contains('attr-dropdown')) { 36 | 37 | // If so, display the searchable dropdown used for attributes 38 | ATTRIBUTE_MENU.classList.remove('hidden'); 39 | 40 | // Position dropdown based on input location 41 | ATTRIBUTE_MENU.style.top = getOffset(el).top + el.offsetHeight + 'px'; 42 | ATTRIBUTE_MENU.style.left = getOffset(el).left + 'px'; 43 | CLICKED_ATTR = ev.target; 44 | 45 | ATTRIBUTE_SEARCH.focus(); // Give focus to search input so user can type without clicking 46 | } else { 47 | 48 | // If click was not in one of the previously specified places, hide the dropdown (won't do anything if it was already hidden) 49 | ATTRIBUTE_MENU.classList.add('hidden'); 50 | CLICKED_ATTR = null; 51 | } 52 | }); 53 | 54 | // uses array called attrNames... 55 | 56 | function attrSearch(ev) { 57 | var searchString = ev.target.value; 58 | var validAttrs = attrNames.filter(function(attr) { 59 | return attr.indexOf(searchString) > -1; 60 | }); 61 | var newHtml = []; 62 | for (var i = 0; i < validAttrs.length; i++) { 63 | var attrName = validAttrs[i]; 64 | newHtml.push('
  • ' + attrName + '
  • '); 65 | } 66 | newHtml = newHtml.join(''); 67 | ATTRIBUTE_RESULTS.innerHTML = newHtml; 68 | } 69 | 70 | ATTRIBUTE_SEARCH.addEventListener('keyup', attrSearch); 71 | ATTRIBUTE_SEARCH.addEventListener('paste', attrSearch); 72 | 73 | ATTRIBUTE_RESULTS.addEventListener('click', function(ev) { 74 | var attr = ev.target.textContent; 75 | CLICKED_ATTR.textContent = attr; 76 | }); 77 | 78 | // initialize the stuff in the menu 79 | var attrString = ''; 80 | for (var i = 0; i < attrNames.length; i++) { 81 | attrString += '
  • ' + attrNames[i] + '
  • '; 82 | } 83 | ATTRIBUTE_RESULTS.innerHTML = attrString; -------------------------------------------------------------------------------- /apps/projects/static/js/editor/cssAttributes.js: -------------------------------------------------------------------------------- 1 | var CSS_ATTRIBUTE_MENU = document.getElementById('cssAttributeDropdown'); 2 | var CSS_ATTRIBUTE_SEARCH = document.getElementById('cssPropSearch'); 3 | var CSS_ATTRIBUTE_RESULTS = document.getElementById('cssAttributeResults'); 4 | 5 | var CSS_CLICKED_ATTR; 6 | 7 | SCRIPTING_AREA.addEventListener('click', function(ev) { 8 | var el = ev.target; 9 | 10 | // Check if click was on the first input of an attribute 11 | if (ev.target.classList.contains('css-attr-dropdown')) { 12 | 13 | // If so, display the searchable dropdown used for attributes 14 | CSS_ATTRIBUTE_MENU.classList.remove('hidden'); 15 | 16 | // Position dropdown based on input location 17 | CSS_ATTRIBUTE_MENU.style.top = getOffset(el).top + el.offsetHeight + 'px'; 18 | CSS_ATTRIBUTE_MENU.style.left = getOffset(el).left + 'px'; 19 | CSS_CLICKED_ATTR = ev.target; 20 | 21 | ATTRIBUTE_SEARCH.focus(); // Give focus to search input so user can type without clicking 22 | } else { 23 | 24 | // If click was not in one of the previously specified places, hide the dropdown (won't do anything if it was already hidden) 25 | CSS_ATTRIBUTE_MENU.classList.add('hidden'); 26 | CSS_CLICKED_ATTR = null; 27 | } 28 | }); 29 | 30 | // uses array called cssAttrNames... 31 | 32 | function attrSearch(ev) { 33 | var searchString = ev.target.value; 34 | var validAttrs = cssAttrNames.filter(function(attr) { 35 | return attr.indexOf(searchString) > -1; 36 | }); 37 | var newHtml = []; 38 | for (var i = 0; i < validAttrs.length; i++) { 39 | var attrName = validAttrs[i]; 40 | newHtml.push('
  • ' + attrName + '
  • '); 41 | } 42 | newHtml = newHtml.join(''); 43 | CSS_ATTRIBUTE_RESULTS.innerHTML = newHtml; 44 | } 45 | 46 | CSS_ATTRIBUTE_SEARCH.addEventListener('keyup', attrSearch); 47 | CSS_ATTRIBUTE_SEARCH.addEventListener('paste', attrSearch); 48 | 49 | CSS_ATTRIBUTE_RESULTS.addEventListener('click', function(ev) { 50 | var attr = ev.target.textContent; 51 | CSS_CLICKED_ATTR.textContent = attr; 52 | }); 53 | 54 | // initialize the stuff in the menu 55 | var cssAttrString = ''; 56 | for (var i = 0; i < cssAttrNames.length; i++) { 57 | cssAttrString += '
  • ' + cssAttrNames[i] + '
  • '; 58 | } 59 | CSS_ATTRIBUTE_RESULTS.innerHTML = cssAttrString; -------------------------------------------------------------------------------- /apps/projects/static/js/editor/fileManagement.js: -------------------------------------------------------------------------------- 1 | document.getElementById('downloadButton').addEventListener('click', function() { 2 | var zip = new JSZip(); 3 | var jsonFiles = zip.folder('.elem'); 4 | 5 | for (fileName in fileData) { 6 | zip.file(fileName, json2html(fileData[fileName])); 7 | // jsonFiles.file(fileName.split('.')[0] + '.json', JSON.stringify(fileData[fileName])); 8 | } 9 | 10 | jsonFiles.file('project.json', JSON.stringify(fileData)); 11 | 12 | var content = zip.generate({type: 'blob'}); 13 | saveAs(content, 'project.zip'); 14 | }); 15 | 16 | document.getElementById('openFileButton').addEventListener('click', function() { 17 | document.getElementById('projectOpen').click(); 18 | }); 19 | 20 | document.getElementById('projectOpen').addEventListener('change', function() { 21 | var opening = this.files[0]; 22 | 23 | // var zip = new JSZip(); 24 | // zip.load(this.files[0]); 25 | 26 | var reader = new FileReader(); 27 | reader.onload = function(e) { 28 | var unzip = new JSZip(e.target.result); 29 | var newJson = JSON.parse(unzip.files['.elem/project.json'].asText()); 30 | 31 | // first, set the json to the new json 32 | fileData = newJson; 33 | 34 | // then, set the file selectors 35 | var newHtml = ''; 36 | var first = true; 37 | for (file in newJson) { 38 | newHtml += '
    ' + file + '
    '; 39 | if (first) { 40 | first = false; 41 | currentFile = file; 42 | } 43 | } 44 | newHtml += '
    +
    '; 45 | $('.filePane')[0].innerHTML = newHtml; 46 | 47 | // finally, re-render 48 | loadFile(currentFile); 49 | } 50 | reader.readAsArrayBuffer(this.files[0]); 51 | 52 | document.getElementById('projectOpen').value = null; // clear value 53 | }); -------------------------------------------------------------------------------- /apps/projects/static/js/editor/script.js: -------------------------------------------------------------------------------- 1 | interact('.leftSide') 2 | .resizable({ 3 | edges: { left: false, right: true, bottom: false, top: false } 4 | }) 5 | .on('resizemove', function (event) { 6 | var target = event.target, 7 | x = (parseFloat(target.getAttribute('data-x')) || 0), 8 | y = (parseFloat(target.getAttribute('data-y')) || 0); 9 | 10 | // don't mess with iframe 11 | document.querySelector('.previewBody').style['pointer-events'] = 'none'; 12 | 13 | // update the element's style 14 | //target.style.width = event.rect.width + 'px'; 15 | target.style.flexBasis = event.rect.width + 'px'; 16 | target.style.height = event.rect.height + 'px'; 17 | 18 | // translate when resizing from top or left edges 19 | x += event.deltaRect.left; 20 | y += event.deltaRect.top; 21 | 22 | target.style.webkitTransform = target.style.transform = 23 | 'translate(' + x + 'px,' + y + 'px)'; 24 | 25 | target.setAttribute('data-x', x); 26 | target.setAttribute('data-y', y); 27 | }).on('mouseup', function(event) { 28 | document.querySelector('.previewBody').style['pointer-events'] = ''; 29 | }); 30 | 31 | interact('.filePane') 32 | .resizable({ 33 | edges: { left: false, right: false, bottom: false, top: true } 34 | }) 35 | .on('resizemove', function (event) { 36 | var target = event.target, 37 | x = (parseFloat(target.getAttribute('data-x')) || 0), 38 | y = (parseFloat(target.getAttribute('data-y')) || 0); 39 | 40 | // don't mess with iframe 41 | document.querySelector('.previewBody').style['pointer-events'] = 'none'; 42 | 43 | // update the element's style 44 | target.style.width = event.rect.width + 'px'; 45 | // target.style.flexBasis = event.rect.width + 'px'; 46 | target.style.height = event.rect.height + 'px'; 47 | target.parentNode.children[0].style.height = 'calc(100% - ' + target.style.height + ')'; 48 | 49 | // translate when resizing from top or left edges 50 | // x += event.deltaRect.left; 51 | // y += event.deltaRect.top; 52 | 53 | // target.style.webkitTransform = target.style.transform = 54 | // 'translate(' + x + 'px,' + y + 'px)'; 55 | 56 | target.setAttribute('data-x', x); 57 | target.setAttribute('data-y', y); 58 | }).on('mouseup', function(event) { 59 | document.querySelector('.previewBody').style['pointer-events'] = ''; 60 | }); 61 | 62 | window.onload = function() { 63 | blockFilterOnload(); 64 | // attrOnload(); 65 | } -------------------------------------------------------------------------------- /apps/projects/static/js/editor/siteIntegrations.js: -------------------------------------------------------------------------------- 1 | // alert('hi'); 2 | 3 | var SAVE_BUTTON = $('#shareMenu'); 4 | 5 | SAVE_BUTTON.addEventListener('click', function(ev) { 6 | if (ev.target.matches('#saveButton')) { 7 | var request; 8 | var config = { 9 | headers: { 10 | "X-CSRFToken": CSRF_TOKEN 11 | } 12 | }; 13 | 14 | var previewWindow = document.getElementsByClassName('previewBody')[0]; 15 | previewWindow.style.height = '360px'; 16 | previewWindow.style.width = '480px'; 17 | html2canvas(previewWindow.contentWindow.document.body).then(function(canvas) { 18 | previewWindow.style.height = ''; 19 | previewWindow.style.width = ''; 20 | document.body.appendChild(canvas); 21 | thumbnail = canvas.toDataURL(); 22 | console.log(thumbnail); 23 | if (NEW_PROJECT) { 24 | request = axios.post('/rest/projects/project/', { 25 | data: JSON.stringify(fileData), 26 | name: 'untitled', 27 | thumbnail: thumbnail, 28 | }, config); 29 | } else { 30 | request = axios.patch('/rest/projects/project/' + P_ID, { 31 | data: JSON.stringify(fileData), 32 | thumbnail: thumbnail, 33 | }, config); 34 | } 35 | request.then(function(data) { 36 | if (NEW_PROJECT) { 37 | console.log('created'); 38 | P_ID = data.data.id; 39 | NEW_PROJECT = false; 40 | } else { 41 | console.log('updated'); 42 | } 43 | }).catch(function(err) { 44 | console.log(err); 45 | }); 46 | }); 47 | } 48 | }); -------------------------------------------------------------------------------- /apps/projects/static/js/editor/vendor/cssjson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS-JSON Converter for JavaScript 3 | * Converts CSS to JSON and back. 4 | * Version 2.1 5 | * 6 | * Released under the MIT license. 7 | * 8 | * Copyright (c) 2013 Aram Kocharyan, http://aramk.com/ 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 11 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 12 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 13 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 19 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | var CSSJSON = new function () { 26 | 27 | var base = this; 28 | 29 | base.init = function () { 30 | // String functions 31 | String.prototype.trim = function () { 32 | return this.replace(/^\s+|\s+$/g, ''); 33 | }; 34 | 35 | String.prototype.repeat = function (n) { 36 | return new Array(1 + n).join(this); 37 | }; 38 | }; 39 | base.init(); 40 | 41 | var selX = /([^\s\;\{\}][^\;\{\}]*)\{/g; 42 | var endX = /\}/g; 43 | var lineX = /([^\;\{\}]*)\;/g; 44 | var commentX = /\/\*[\s\S]*?\*\//g; 45 | var lineAttrX = /([^\:]+):([^\;]*);/; 46 | 47 | // This is used, a concatenation of all above. We use alternation to 48 | // capture. 49 | var altX = /(\/\*[\s\S]*?\*\/)|([^\s\;\{\}][^\;\{\}]*(?=\{))|(\})|([^\;\{\}]+\;(?!\s*\*\/))/gmi; 50 | 51 | // Capture groups 52 | var capComment = 1; 53 | var capSelector = 2; 54 | var capEnd = 3; 55 | var capAttr = 4; 56 | 57 | var isEmpty = function (x) { 58 | return typeof x == 'undefined' || x.length == 0 || x == null; 59 | }; 60 | 61 | /** 62 | * Input is css string and current pos, returns JSON object 63 | * 64 | * @param cssString 65 | * The CSS string. 66 | * @param args 67 | * An optional argument object. ordered: Whether order of 68 | * comments and other nodes should be kept in the output. This 69 | * will return an object where all the keys are numbers and the 70 | * values are objects containing "name" and "value" keys for each 71 | * node. comments: Whether to capture comments. split: Whether to 72 | * split each comma separated list of selectors. 73 | */ 74 | base.toJSON = function (cssString, args) { 75 | var node = { 76 | children: {}, 77 | attributes: {} 78 | }; 79 | var match = null; 80 | var count = 0; 81 | 82 | if (typeof args == 'undefined') { 83 | var args = { 84 | ordered: false, 85 | comments: false, 86 | stripComments: false, 87 | split: false 88 | }; 89 | } 90 | if (args.stripComments) { 91 | args.comments = false; 92 | cssString = cssString.replace(commentX, ''); 93 | } 94 | 95 | while ((match = altX.exec(cssString)) != null) { 96 | if (!isEmpty(match[capComment]) && args.comments) { 97 | // Comment 98 | var add = match[capComment].trim(); 99 | node[count++] = add; 100 | } else if (!isEmpty(match[capSelector])) { 101 | // New node, we recurse 102 | var name = match[capSelector].trim(); 103 | // This will return when we encounter a closing brace 104 | var newNode = base.toJSON(cssString, args); 105 | if (args.ordered) { 106 | var obj = {}; 107 | obj['name'] = name; 108 | obj['value'] = newNode; 109 | // Since we must use key as index to keep order and not 110 | // name, this will differentiate between a Rule Node and an 111 | // Attribute, since both contain a name and value pair. 112 | obj['type'] = 'rule'; 113 | node[count++] = obj; 114 | } else { 115 | if (args.split) { 116 | var bits = name.split(','); 117 | } else { 118 | var bits = [name]; 119 | } 120 | for (i in bits) { 121 | var sel = bits[i].trim(); 122 | if (sel in node.children) { 123 | for (var att in newNode.attributes) { 124 | node.children[sel].attributes[att] = newNode.attributes[att]; 125 | } 126 | } else { 127 | node.children[sel] = newNode; 128 | } 129 | } 130 | } 131 | } else if (!isEmpty(match[capEnd])) { 132 | // Node has finished 133 | return node; 134 | } else if (!isEmpty(match[capAttr])) { 135 | var line = match[capAttr].trim(); 136 | var attr = lineAttrX.exec(line); 137 | if (attr) { 138 | // Attribute 139 | var name = attr[1].trim(); 140 | var value = attr[2].trim(); 141 | if (args.ordered) { 142 | var obj = {}; 143 | obj['name'] = name; 144 | obj['value'] = value; 145 | obj['type'] = 'attr'; 146 | node[count++] = obj; 147 | } else { 148 | if (name in node.attributes) { 149 | var currVal = node.attributes[name]; 150 | if (!(currVal instanceof Array)) { 151 | node.attributes[name] = [currVal]; 152 | } 153 | node.attributes[name].push(value); 154 | } else { 155 | node.attributes[name] = value; 156 | } 157 | } 158 | } else { 159 | // Semicolon terminated line 160 | node[count++] = line; 161 | } 162 | } 163 | } 164 | 165 | return node; 166 | }; 167 | 168 | /** 169 | * @param node 170 | * A JSON node. 171 | * @param depth 172 | * The depth of the current node; used for indentation and 173 | * optional. 174 | * @param breaks 175 | * Whether to add line breaks in the output. 176 | */ 177 | base.toCSS = function (node, depth, breaks) { 178 | var cssString = ''; 179 | if (typeof depth == 'undefined') { 180 | depth = 0; 181 | } 182 | if (typeof breaks == 'undefined') { 183 | breaks = false; 184 | } 185 | if (node.attributes) { 186 | for (i in node.attributes) { 187 | var att = node.attributes[i]; 188 | if (att instanceof Array) { 189 | for (var j = 0; j < att.length; j++) { 190 | cssString += strAttr(i, att[j], depth); 191 | } 192 | } else { 193 | cssString += strAttr(i, att, depth); 194 | } 195 | } 196 | } 197 | if (node.children) { 198 | var first = true; 199 | for (i in node.children) { 200 | if (breaks && !first) { 201 | cssString += '\n'; 202 | } else { 203 | first = false; 204 | } 205 | cssString += strNode(i, node.children[i], depth); 206 | } 207 | } 208 | return cssString; 209 | }; 210 | 211 | // Helpers 212 | 213 | var strAttr = function (name, value, depth) { 214 | return '\t'.repeat(depth) + name + ': ' + value + ';\n'; 215 | }; 216 | 217 | var strNode = function (name, value, depth) { 218 | var cssString = '\t'.repeat(depth) + name + ' {\n'; 219 | cssString += base.toCSS(value, depth + 1); 220 | cssString += '\t'.repeat(depth) + '}\n'; 221 | return cssString; 222 | }; 223 | 224 | }; -------------------------------------------------------------------------------- /apps/projects/static/js/editor/vendor/filesaver.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 2 | var saveAs=saveAs||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=e.webkitRequestFileSystem,c=e.requestFileSystem||a||e.mozRequestFileSystem,u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},f="application/octet-stream",s=0,d=500,l=function(t){var o=function(){"string"==typeof t?n().revokeObjectURL(t):t.remove()};e.chrome?o():setTimeout(o,d)},v=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(i){u(i)}}},p=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob(["",e],{type:e.type}):e},w=function(t,u,d){d||(t=p(t));var w,y,m,S=this,h=t.type,O=!1,R=function(){v(S,"writestart progress write writeend".split(" "))},b=function(){if((O||!w)&&(w=n().createObjectURL(t)),y)y.location.href=w;else{var o=e.open(w,"_blank");void 0==o&&"undefined"!=typeof safari&&(e.location.href=w)}S.readyState=S.DONE,R(),l(w)},g=function(e){return function(){return S.readyState!==S.DONE?e.apply(this,arguments):void 0}},E={create:!0,exclusive:!1};return S.readyState=S.INIT,u||(u="download"),r?(w=n().createObjectURL(t),o.href=w,o.download=u,void setTimeout(function(){i(o),R(),l(w),S.readyState=S.DONE})):(e.chrome&&h&&h!==f&&(m=t.slice||t.webkitSlice,t=m.call(t,0,t.size,f),O=!0),a&&"download"!==u&&(u+=".download"),(h===f||a)&&(y=e),c?(s+=t.size,void c(e.TEMPORARY,s,g(function(e){e.root.getDirectory("saved",E,g(function(e){var n=function(){e.getFile(u,E,g(function(e){e.createWriter(g(function(n){n.onwriteend=function(t){y.location.href=e.toURL(),S.readyState=S.DONE,v(S,"writeend",t),l(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&b()},"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=S["on"+e]}),n.write(t),S.abort=function(){n.abort(),S.readyState=S.DONE},S.readyState=S.WRITING}),b)}),b)};e.getFile(u,{create:!1},g(function(e){e.remove(),n()}),g(function(e){e.code===e.NOT_FOUND_ERR?n():b()}))}),b)}),b)):void b())},y=w.prototype,m=function(e,t,n){return new w(e,t,n)};return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t,n){return n||(e=p(e)),navigator.msSaveOrOpenBlob(e,t||"download")}:(y.abort=function(){var e=this;e.readyState=e.DONE,v(e,"abort")},y.readyState=y.INIT=0,y.WRITING=1,y.DONE=2,y.error=y.onwritestart=y.onprogress=y.onwrite=y.onabort=y.onerror=y.onwriteend=null,m)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!=define.amd&&define([],function(){return saveAs}); -------------------------------------------------------------------------------- /apps/projects/static/js/editor/vendor/htmljson.js: -------------------------------------------------------------------------------- 1 | function parseHtml(html) { 2 | var results = ''; 3 | HTMLParser(html, { 4 | start: function(tag, attrs, unary) { 5 | results += '<' + tag; 6 | for (var i = 0; i < attrs.length; i++) { 7 | results += ' ' + attrs[i].name + '="' + attrs[i].escaped + '"'; 8 | } 9 | results += (unary ? '/' : '') + '>'; 10 | }, 11 | end: function(tag) { 12 | results += ''; 13 | }, 14 | chars: function(text) { 15 | results += text; 16 | }, 17 | comment: function(text) { 18 | results += ''; 19 | } 20 | }); 21 | return results; 22 | } 23 | 24 | function makeMap(str) { 25 | var obj = {}, items = str.split(','); 26 | for (var i = 0; i < items.length; i++) { 27 | obj[items[i]] = true; 28 | } 29 | return obj; 30 | } 31 | 32 | function html2json(html) { 33 | var inline = {}; 34 | 35 | html = html.replace(//, ''); 36 | 37 | var bufArray = []; 38 | var results = {}; 39 | var inlineBuf = []; 40 | bufArray.last = function() { 41 | return this[ this.length - 1]; 42 | }; 43 | HTMLParser(html, { 44 | start: function(tag, attrs, unary) { 45 | if (inline[tag]) { 46 | // inline tag is melted into text 47 | // because syntacs higlight became dirty 48 | // if support it. 49 | // 'hoge tag fuga' 50 | var attributes = ''; 51 | for (var i = 0; i < attrs.length; i++) { 52 | attributes += ' ' + attrs[i].name + '="' + attrs[i].value + '"'; 53 | } 54 | inlineBuf.push('<' + tag + attributes + '>'); 55 | } else { 56 | var buf = {}; // buffer for single tag 57 | buf.tag = tag; 58 | if (attrs.length !== 0) { 59 | var attr = {}; 60 | for (var i = 0; i < attrs.length; i++) { 61 | var attr_name = attrs[i].name; 62 | var attr_value = attrs[i].value; 63 | if (attr_name === 'class') { 64 | attr_value = attr_value.split(' '); 65 | } 66 | attr[attr_name] = attr_value; 67 | } 68 | buf['attr'] = attr; 69 | } 70 | if (unary) { 71 | // if this tag don't has end tag 72 | // like 73 | // add last parents 74 | var last = bufArray.last(); 75 | if (!(last.child instanceof Array)) { 76 | last.child = []; 77 | } 78 | last.child.push(buf); 79 | } else { 80 | bufArray.push(buf); 81 | } 82 | } 83 | }, 84 | end: function(tag) { 85 | if (inline[tag]) { 86 | // if end of inline tag 87 | // inlineBuf is now 'tag' 88 | // melt into last node text 89 | var last = bufArray.last(); 90 | inlineBuf.push(''); 91 | // inlineBuf became 'tag' 92 | if (!last.text) last.text = ''; 93 | last.text += inlineBuf.join(''); 94 | // clear inlineBuf 95 | inlineBuf = []; 96 | } else { 97 | // if block tag 98 | var buf = bufArray.pop(); 99 | if (bufArray.length === 0) { 100 | return results = buf; 101 | } 102 | var last = bufArray.last(); 103 | if (!(last.child instanceof Array)) { 104 | last.child = []; 105 | } 106 | last.child.push(buf); 107 | } 108 | }, 109 | chars: function(text) { 110 | if (inlineBuf.length !== 0) { 111 | // if inlineBuf exists 112 | // this cace inlineBuf is maybe like this 113 | // 'hoge tag' 114 | // so append to last 115 | inlineBuf.push(text); 116 | } else { 117 | var last = bufArray.last(); 118 | if (last) { 119 | if (!last.text) { 120 | last.text = ''; 121 | } 122 | // last.text += text; 123 | if (last.child) { 124 | last.child.push({'tag': '', 'text': text}); 125 | } else { 126 | last.child = []; 127 | last.child.push({'tag': '', 'text': text}); 128 | } 129 | } 130 | } 131 | }, 132 | comment: function(text) { 133 | // results += ""; 134 | } 135 | }); 136 | return results; 137 | } 138 | 139 | function json2html(json) { 140 | var html = ''; 141 | var tag = json.tag; 142 | var text = json.text; 143 | var children = json.child; 144 | var buf = []; 145 | 146 | // Empty Elements - HTML 4.01 147 | var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed'); 148 | 149 | var buildAttr = function(attr) { 150 | for (var k in attr) { 151 | buf.push(' ' + k + '="'); 152 | if (attr[k] instanceof Array) { 153 | buf.push(attr[k].join(' ')); 154 | } else { 155 | buf.push(attr[k]); 156 | } 157 | buf.push('"'); 158 | } 159 | } 160 | 161 | if (tag) { 162 | buf.push('<'); 163 | buf.push(tag); 164 | json.attr ? buf.push(buildAttr(json.attr)) : null; 165 | if (empty[tag]) buf.push('/'); 166 | buf.push('>'); 167 | } 168 | text ? buf.push(text) : null; 169 | if (children) { 170 | for (var j = 0; j < children.length; j++) { 171 | buf.push(json2html(children[j])); 172 | } 173 | } 174 | if (!empty[tag] && tag) { 175 | buf.push(''); 176 | } 177 | return buf.join(''); 178 | } -------------------------------------------------------------------------------- /apps/projects/static/js/editor/vendor/htmlparser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 Parser By Sam Blowes 3 | * 4 | * Designed for HTML5 documents 5 | * 6 | * Original code by John Resig (ejohn.org) 7 | * http://ejohn.org/blog/pure-javascript-html-parser/ 8 | * Original code by Erik Arvidsson, Mozilla Public License 9 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 10 | * 11 | * // Use like so: 12 | * HTMLParser(htmlString, { 13 | * start: function(tag, attrs, unary) {}, 14 | * end: function(tag) {}, 15 | * chars: function(text) {}, 16 | * comment: function(text) {} 17 | * }); 18 | * 19 | * // or to get an XML string: 20 | * HTMLtoXML(htmlString); 21 | * 22 | * // or to get an XML DOM Document 23 | * HTMLtoDOM(htmlString); 24 | * 25 | * // or to inject into an existing document/DOM node 26 | * HTMLtoDOM(htmlString, document); 27 | * HTMLtoDOM(htmlString, document.body); 28 | * 29 | */ 30 | 31 | (function () { 32 | 33 | // Regular Expressions for parsing tags and attributes 34 | var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, 35 | endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, 36 | attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; 37 | 38 | // Empty Elements - HTML 5 39 | var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"); 40 | 41 | // Block Elements - HTML 5 42 | var block = makeMap("address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"); 43 | 44 | // Inline Elements - HTML 5 45 | var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); 46 | 47 | // Elements that you can, intentionally, leave open 48 | // (and which close themselves) 49 | var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); 50 | 51 | // Attributes that have their values filled in disabled="disabled" 52 | var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); 53 | 54 | // Special Elements (can contain anything) 55 | var special = makeMap("script,style"); 56 | 57 | var HTMLParser = this.HTMLParser = function (html, handler) { 58 | var index, chars, match, stack = [], last = html; 59 | stack.last = function () { 60 | return this[this.length - 1]; 61 | }; 62 | 63 | while (html) { 64 | chars = true; 65 | 66 | // Make sure we're not in a script or style element 67 | if (!stack.last() || !special[stack.last()]) { 68 | 69 | // Comment 70 | if (html.indexOf(""); 72 | 73 | if (index >= 0) { 74 | if (handler.comment) 75 | handler.comment(html.substring(4, index)); 76 | html = html.substring(index + 3); 77 | chars = false; 78 | } 79 | 80 | // end tag 81 | } else if (html.indexOf("]*>"), function (all, text) { 113 | text = text.replace(/|/g, "$1$2"); 114 | if (handler.chars) 115 | handler.chars(text); 116 | 117 | return ""; 118 | }); 119 | 120 | parseEndTag("", stack.last()); 121 | } 122 | 123 | if (html == last) 124 | throw "Parse Error: " + html; 125 | last = html; 126 | } 127 | 128 | // Clean up any remaining tags 129 | parseEndTag(); 130 | 131 | function parseStartTag(tag, tagName, rest, unary) { 132 | tagName = tagName.toLowerCase(); 133 | 134 | if (block[tagName]) { 135 | while (stack.last() && inline[stack.last()]) { 136 | parseEndTag("", stack.last()); 137 | } 138 | } 139 | 140 | if (closeSelf[tagName] && stack.last() == tagName) { 141 | parseEndTag("", tagName); 142 | } 143 | 144 | unary = empty[tagName] || !!unary; 145 | 146 | if (!unary) 147 | stack.push(tagName); 148 | 149 | if (handler.start) { 150 | var attrs = []; 151 | 152 | rest.replace(attr, function (match, name) { 153 | var value = arguments[2] ? arguments[2] : 154 | arguments[3] ? arguments[3] : 155 | arguments[4] ? arguments[4] : 156 | fillAttrs[name] ? name : ""; 157 | 158 | attrs.push({ 159 | name: name, 160 | value: value, 161 | escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //" 162 | }); 163 | }); 164 | 165 | if (handler.start) 166 | handler.start(tagName, attrs, unary); 167 | } 168 | } 169 | 170 | function parseEndTag(tag, tagName) { 171 | // If no tag name is provided, clean shop 172 | if (!tagName) 173 | var pos = 0; 174 | 175 | // Find the closest opened tag of the same type 176 | else 177 | for (var pos = stack.length - 1; pos >= 0; pos--) 178 | if (stack[pos] == tagName) 179 | break; 180 | 181 | if (pos >= 0) { 182 | // Close all the open elements, up the stack 183 | for (var i = stack.length - 1; i >= pos; i--) 184 | if (handler.end) 185 | handler.end(stack[i]); 186 | 187 | // Remove the open elements from the stack 188 | stack.length = pos; 189 | } 190 | } 191 | }; 192 | 193 | this.HTMLtoXML = function (html) { 194 | var results = ""; 195 | 196 | HTMLParser(html, { 197 | start: function (tag, attrs, unary) { 198 | results += "<" + tag; 199 | 200 | for (var i = 0; i < attrs.length; i++) 201 | results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; 202 | results += ">"; 203 | }, 204 | end: function (tag) { 205 | results += ""; 206 | }, 207 | chars: function (text) { 208 | results += text; 209 | }, 210 | comment: function (text) { 211 | results += ""; 212 | } 213 | }); 214 | 215 | return results; 216 | }; 217 | 218 | this.HTMLtoDOM = function (html, doc) { 219 | // There can be only one of these elements 220 | var one = makeMap("html,head,body,title"); 221 | 222 | // Enforce a structure for the document 223 | var structure = { 224 | link: "head", 225 | base: "head" 226 | }; 227 | 228 | if (!doc) { 229 | if (typeof DOMDocument != "undefined") 230 | doc = new DOMDocument(); 231 | else if (typeof document != "undefined" && document.implementation && document.implementation.createDocument) 232 | doc = document.implementation.createDocument("", "", null); 233 | else if (typeof ActiveX != "undefined") 234 | doc = new ActiveXObject("Msxml.DOMDocument"); 235 | 236 | } else 237 | doc = doc.ownerDocument || 238 | doc.getOwnerDocument && doc.getOwnerDocument() || 239 | doc; 240 | 241 | var elems = [], 242 | documentElement = doc.documentElement || 243 | doc.getDocumentElement && doc.getDocumentElement(); 244 | 245 | // If we're dealing with an empty document then we 246 | // need to pre-populate it with the HTML document structure 247 | if (!documentElement && doc.createElement) (function () { 248 | var html = doc.createElement("html"); 249 | var head = doc.createElement("head"); 250 | head.appendChild(doc.createElement("title")); 251 | html.appendChild(head); 252 | html.appendChild(doc.createElement("body")); 253 | doc.appendChild(html); 254 | })(); 255 | 256 | // Find all the unique elements 257 | if (doc.getElementsByTagName) 258 | for (var i in one) 259 | one[i] = doc.getElementsByTagName(i)[0]; 260 | 261 | // If we're working with a document, inject contents into 262 | // the body element 263 | var curParentNode = one.body; 264 | 265 | HTMLParser(html, { 266 | start: function (tagName, attrs, unary) { 267 | // If it's a pre-built element, then we can ignore 268 | // its construction 269 | if (one[tagName]) { 270 | curParentNode = one[tagName]; 271 | if (!unary) { 272 | elems.push(curParentNode); 273 | } 274 | return; 275 | } 276 | 277 | var elem = doc.createElement(tagName); 278 | 279 | for (var attr in attrs) 280 | elem.setAttribute(attrs[attr].name, attrs[attr].value); 281 | 282 | if (structure[tagName] && typeof one[structure[tagName]] != "boolean") 283 | one[structure[tagName]].appendChild(elem); 284 | 285 | else if (curParentNode && curParentNode.appendChild) 286 | curParentNode.appendChild(elem); 287 | 288 | if (!unary) { 289 | elems.push(elem); 290 | curParentNode = elem; 291 | } 292 | }, 293 | end: function (tag) { 294 | elems.length -= 1; 295 | 296 | // Init the new parentNode 297 | curParentNode = elems[elems.length - 1]; 298 | }, 299 | chars: function (text) { 300 | curParentNode.appendChild(doc.createTextNode(text)); 301 | }, 302 | comment: function (text) { 303 | // create comment node 304 | } 305 | }); 306 | 307 | return doc; 308 | }; 309 | 310 | function makeMap(str) { 311 | var obj = {}, items = str.split(","); 312 | for (var i = 0; i < items.length; i++) 313 | obj[items[i]] = true; 314 | return obj; 315 | } 316 | })(); -------------------------------------------------------------------------------- /apps/projects/templates/my-things-mockup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 61 | 62 | 63 | 64 | 65 |
    66 |
    67 | 68 | 71 |
    72 |
    73 |
    74 | 75 | 78 |
    79 |
    80 |
    81 | 82 | 85 |
    86 |
    87 |
    88 | 89 | 92 |
    93 |
    94 |
    95 | 96 | 99 |
    100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /apps/projects/templates/my_projects.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block morescripts %} 5 | 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 | {% for p in projects %} 12 | 17 | 18 | {% endfor %} 19 | {% endblock %} 20 | 21 | {% block extra_scripts %} 22 | 23 | 27 | {% endblock %} -------------------------------------------------------------------------------- /apps/projects/templates/proof-of-concept.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block morescripts %} 5 | 6 | 7 | 8 | 99 | {% endblock %} 100 | 101 | {% block content %} 102 | POC pages 103 | 104 | {% endblock %} -------------------------------------------------------------------------------- /apps/projects/tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.core.urlresolvers import reverse 4 | from django.conf import settings 5 | from django.test import TestCase, override_settings, Client, RequestFactory 6 | 7 | from apps.accounts.models import ElementalUser 8 | from .models import Project 9 | 10 | class ProjectTestCases(TestCase): 11 | 12 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 13 | def setUp(self): 14 | self.client = Client() 15 | 16 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 17 | def test_create_project(self): 18 | u = ElementalUser(password='password', 19 | username='username', 20 | trusted=True) 21 | u.save() 22 | self.client.login(username='username', password='password') 23 | data = { 24 | 'data': 'fake_testing_data_create', 25 | 'name': 'my_project', 26 | 'thumbnail': 'thumb', 27 | } 28 | response = self.client.post(reverse('api:project-create'), data) 29 | self.assertEqual(response.status_code, 201) 30 | try: 31 | project = Project.objects.get(data='fake_testing_data_create') 32 | except: 33 | project = None 34 | self.assertIsNotNone(project) 35 | 36 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 37 | def test_create_project_fail(self): 38 | u = ElementalUser(password='password', 39 | username='username') 40 | u.save() 41 | self.client.login(username='username', password='password') 42 | data = { 43 | 'data': 'fake_testing_data_create_fail', 44 | 'name': 'my_project', 45 | 'thumbnail': 'thumb', 46 | } 47 | response = self.client.post(reverse('api:project-create'), data) 48 | self.assertEqual(response.status_code, 403) 49 | try: 50 | project = Project.objects.get(data='fake_testing_data_create_fail') 51 | except: 52 | project = None 53 | self.assertIsNone(project) 54 | 55 | @override_settings(AUTH_USER_MODEL=settings.AUTH_USER_MODEL) 56 | def test_update_project(self): 57 | u = ElementalUser(password='password', 58 | username='username') 59 | u.save() 60 | self.client.login(username='username', password='password') 61 | project = Project(name='name', 62 | user=u, 63 | data='fake_data') 64 | project.save() 65 | data = { 66 | "data": "new_data" 67 | } 68 | data = json.dumps(data) 69 | response = self.client.patch(reverse('api:project-data', args=[project.id]), data, content_type='application/json') 70 | try: 71 | project = Project.objects.get(data='new_data') 72 | except: 73 | project = None 74 | self.assertIsNotNone(project) -------------------------------------------------------------------------------- /apps/projects/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.views.generic import TemplateView 3 | 4 | from .views import ProjectCreate, ProjectEdit, MyProjects 5 | 6 | urlpatterns = [ 7 | url(r'^(?i)editor/?$', ProjectCreate.as_view(), name='create-project'), 8 | url(r'^(?i)project/(?P\d+)/?$', ProjectEdit.as_view(), name='edit-project'), 9 | url(r'^(?i)mystuff/?$', MyProjects.as_view(), name='my-projects'), 10 | # url(r'^(?i)proof-of-concept/?$', TemplateView.as_view(template_name='proof-of-concept.html')), 11 | ] -------------------------------------------------------------------------------- /apps/projects/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib import messages 3 | from django.db.models import Q 4 | from django.contrib.auth import authenticate, login, logout 5 | from django.contrib.auth.models import Group as AuthGroup 6 | from django.core import signing 7 | from django.core.mail import send_mail 8 | from django.core.urlresolvers import reverse, reverse_lazy 9 | from django.http import HttpResponse, Http404 10 | from django.shortcuts import render, redirect 11 | from django.views.generic import TemplateView, ListView 12 | from django.views.generic.base import View 13 | from django.views.generic.edit import (FormView, UpdateView, CreateView, 14 | DeleteView) 15 | 16 | from apps.accounts.mixins import UnbannedUserMixin, LoggedInRequiredMixin 17 | from .models import Project 18 | 19 | 20 | class ProjectCreate(UnbannedUserMixin, TemplateView): 21 | template_name = 'editor.html' 22 | 23 | def get_context_data(self, **kwargs): 24 | context = super(ProjectCreate, self).get_context_data(**kwargs) 25 | context['new_project'] = True 26 | return context 27 | 28 | 29 | class MyProjects(UnbannedUserMixin, LoggedInRequiredMixin, ListView): 30 | template_name = 'my_projects.html' 31 | model = Project 32 | paginate_by = 25 33 | context_object_name = 'projects' 34 | 35 | def get_queryset(self): # add filtering later? 36 | return Project.objects.filter(user=self.request.user, deleted=False).order_by('-updated') 37 | 38 | class ProjectEdit(UnbannedUserMixin, TemplateView): 39 | template_name = 'editor.html' 40 | 41 | def dispatch(self, request, *args, **kwargs): 42 | project = Project.objects.get(id=self.kwargs['pk']) 43 | if project.user != request.user: 44 | if not project.user.trusted or not project.shared or project.deleted: 45 | return redirect('/') # should be 404... 46 | return super(ProjectEdit, self).dispatch(request) 47 | 48 | def get_context_data(self, **kwargs): 49 | context = super(ProjectEdit, self).get_context_data(**kwargs) 50 | context['project'] = Project.objects.get(id=self.kwargs['pk']) 51 | return context -------------------------------------------------------------------------------- /apps/rest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/apps/rest/__init__.py -------------------------------------------------------------------------------- /apps/rest/serializers.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import PermissionDenied 2 | 3 | from rest_framework import routers, serializers, viewsets 4 | 5 | from apps.projects.models import Project 6 | from apps.accounts.models import ElementalUser 7 | 8 | 9 | # class UserSpecificUpdate(serializers.ModelSerializer): 10 | 11 | # def update(self, instance, validated_data): 12 | # if self.context['request'].user == instance.user: 13 | # super(UserSerializer, self).update( 14 | 15 | 16 | class UserSerializer(serializers.ModelSerializer): 17 | 18 | class Meta: 19 | model = ElementalUser 20 | fields = ('id', 'username', 'date_joined', 'about_me', 'working_on', ) 21 | read_only_fields = ('username', 'date_joined', ) 22 | 23 | def update(self, instance, validated_data): 24 | get = validated_data.get 25 | if self.context['request'].user == instance: 26 | # need to find a better way... 27 | instance.about_me = get('about_me', instance.about_me) 28 | instance.working_on = get('working_on', instance.working_on) 29 | instance.save() 30 | return instance 31 | raise PermissionDenied 32 | 33 | 34 | class ProjectSerializer(serializers.ModelSerializer): 35 | username = serializers.SerializerMethodField() 36 | 37 | class Meta: 38 | model = Project 39 | fields = ('username', 'shared', 'id', 'data', 'name', 'thumbnail', ) 40 | 41 | def get_username(self, obj): 42 | return obj.user.username 43 | 44 | def update(self, instance, validated_data): 45 | get = validated_data.get 46 | if self.context['request'].user == instance.user: 47 | # need to find a better way... 48 | instance.data = get('data', instance.data) 49 | instance.name = get('name', instance.name) 50 | instance.shared = get('shared', instance.shared) 51 | instance.thumbnail = get('thumbnail', instance.thumbnail) 52 | instance.save() 53 | return instance 54 | raise PermissionDenied 55 | 56 | 57 | class ProjectCreateSerializer(serializers.ModelSerializer): 58 | 59 | def create(self, kwargs): 60 | if self.context['request'].user.trusted: 61 | kwargs['user'] = self.context['request'].user 62 | return Project.objects.create(**kwargs) 63 | raise PermissionDenied 64 | 65 | class Meta: 66 | model = Project 67 | fields = ('data', 'name', 'id', 'thumbnail', ) -------------------------------------------------------------------------------- /apps/rest/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | from .views import ProjectDetail, UserDetail, ProjectCreate 4 | 5 | urlpatterns = [ 6 | # nothing 7 | url(r'^(?i)users/user/(?P\d+)/?$', UserDetail.as_view(), name='user-data'), 8 | url(r'^(?i)projects/project/(?P\d+)/?$', ProjectDetail.as_view(), name='project-data'), 9 | url(r'^(?i)projects/project/?$', ProjectCreate.as_view(), name='project-create'), 10 | ] -------------------------------------------------------------------------------- /apps/rest/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, HttpResponseForbidden 2 | 3 | from rest_framework import generics, status 4 | import json 5 | 6 | from django.http import HttpResponse 7 | from django.shortcuts import render, redirect 8 | from django.views.generic import TemplateView, ListView 9 | from django.views.generic.base import View 10 | 11 | from rest_framework.response import Response 12 | from rest_framework.authentication import SessionAuthentication, TokenAuthentication 13 | from rest_framework.permissions import IsAuthenticated 14 | 15 | from apps.projects.models import Project 16 | from apps.accounts.models import ElementalUser 17 | from apps.accounts.mixins import LoggedInRequiredMixin 18 | from .serializers import ProjectSerializer, ProjectCreateSerializer, UserSerializer 19 | 20 | 21 | class UserDetail(SessionAuthentication, generics.RetrieveUpdateAPIView): 22 | queryset = ElementalUser.objects.all() 23 | serializer_class = UserSerializer 24 | authentication_classes = (SessionAuthentication, ) 25 | permission_classes = (IsAuthenticated,) 26 | 27 | 28 | class ProjectDetail(SessionAuthentication, generics.RetrieveUpdateDestroyAPIView): 29 | queryset = Project.objects.all() 30 | serializer_class = ProjectSerializer 31 | authentication_classes = (SessionAuthentication, ) 32 | permission_classes = (IsAuthenticated,) 33 | 34 | def get_object(self): 35 | return Project.objects.get( 36 | id=self.kwargs['pk']) 37 | 38 | def delete(self, request, pk): 39 | p = Project.objects.get( 40 | id=self.kwargs['pk']) 41 | p.deleted = True 42 | p.save() 43 | return HttpResponse('200 OK') 44 | 45 | class ProjectCreate(SessionAuthentication, TokenAuthentication, generics.CreateAPIView): 46 | queryset = Project.objects.all() 47 | serializer_class = ProjectCreateSerializer 48 | authentication_classes = (SessionAuthentication, TokenAuthentication, ) 49 | permission_classes = (IsAuthenticated, ) 50 | 51 | 52 | # class ProjectCreate(LoggedInRequiredMixin, View): # required: data, name 53 | 54 | # def post(self, request, *args, **kwargs): 55 | # print request.POST 56 | 57 | # def is_json(myjson): 58 | # try: 59 | # json_object = json.loads(myjson) 60 | # except ValueError, e: 61 | # return False 62 | # return True 63 | 64 | # project = Project.objects.get(id=kwargs['pk']) 65 | # get = request.POST.get 66 | 67 | # if is_json(get('project_data')) and get('name'): 68 | # p = Project.objects.create( 69 | # data=get('project_data'), 70 | # name=get('name'), 71 | # user=request.user 72 | # ) 73 | # return HttpResponse(p.id) 74 | 75 | # return HttpResponse('error') -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | deployment: 2 | production: 3 | branch: master 4 | commands: 5 | - git push git@heroku.com:elementalcode.git $CIRCLE_SHA1:master 6 | - heroku run python manage.py migrate --app elementalcode 7 | 8 | notify: 9 | webhooks: 10 | - url: https://webhooks.gitter.im/e/588b954a8e591bb172ea -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dj-database-url==0.3.0 2 | Django==1.9.4 3 | djangorestframework==3.3.2 4 | gunicorn==19.3.0 5 | psycopg2==2.6 6 | SQLAlchemy==1.0.4 7 | whitenoise==1.0.6 8 | django-flat-theme==0.9.5 9 | django-recaptcha==1.0.5 10 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-2.7.9 2 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for gettingstarted project, on Heroku. Fore more info, see: 3 | https://github.com/heroku/heroku-django-template 4 | 5 | For more information on this file, see 6 | https://docs.djangoproject.com/en/1.8/topics/settings/ 7 | 8 | For the full list of settings and their values, see 9 | https://docs.djangoproject.com/en/1.8/ref/settings/ 10 | """ 11 | 12 | import os 13 | import dj_database_url 14 | 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 18 | 19 | AUTH_USER_MODEL = 'accounts.ElementalUser' 20 | 21 | AUTHENTICATION_BACKENDS = ( 22 | 'django.contrib.auth.backends.ModelBackend', 23 | ) 24 | 25 | 26 | # Quick-start development settings - unsuitable for production 27 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 28 | 29 | # SECURITY WARNING: keep the secret key used in production secret! 30 | if os.environ.get('PRODUCTION'): 31 | SECRET_KEY = os.environ['SECRET_KEY'] 32 | else: 33 | SECRET_KEY = 'i+acxn5(akgsn!sr4^qgf(^m&*@+g1@u^t@=8s@axc41ml*f=s' 34 | 35 | # SECURITY WARNING: don't run with debug turned on in production! 36 | if os.environ.get('PRODUCTION'): 37 | DEBUG = False 38 | else: 39 | DEBUG = True 40 | 41 | if os.environ.get('PRODUCTION'): 42 | SECURE_SSL_REDIRECT = True 43 | CSRF_COOKIE_SECURE = True 44 | SESSION_COOKIE_SECURE = True 45 | 46 | # reCAPTCHA stuff 47 | 48 | RECAPTCHA_PUBLIC_KEY = '6LdjuxoTAAAAAPVA-bMQ0zExBH4IKlc2ogoDyGLD' 49 | RECAPTCHA_PRIVATE_KEY = os.environ.get('ELEMENTAL_RECAPTCHA_KEY') 50 | NOCAPTCHA = True 51 | 52 | # Application definition 53 | 54 | INSTALLED_APPS = ( 55 | 'flat', 56 | 'django.contrib.admin', 57 | 'django.contrib.auth', 58 | 'django.contrib.contenttypes', 59 | 'django.contrib.sessions', 60 | 'django.contrib.messages', 61 | 'django.contrib.staticfiles', 62 | 'rest_framework', 63 | 'apps.core', 64 | 'apps.accounts', 65 | 'apps.projects', 66 | 'apps.galleries', 67 | 'captcha', 68 | ) 69 | 70 | # RESTful API stuff 71 | 72 | REST_FRAMEWORK = { 73 | 'DEFAULT_PERMISSION_CLASSES': [ 74 | 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' 75 | ] 76 | } 77 | 78 | MIDDLEWARE_CLASSES = ( 79 | 'django.contrib.sessions.middleware.SessionMiddleware', 80 | 'django.middleware.common.CommonMiddleware', 81 | 'django.middleware.csrf.CsrfViewMiddleware', 82 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 83 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 84 | 'django.contrib.messages.middleware.MessageMiddleware', 85 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 86 | 'django.middleware.security.SecurityMiddleware', 87 | ) 88 | 89 | ROOT_URLCONF = 'urls' 90 | 91 | TEMPLATES = [ 92 | { 93 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 94 | 'DIRS': [], 95 | 'APP_DIRS': True, 96 | 'OPTIONS': { 97 | 'context_processors': [ 98 | 'django.template.context_processors.debug', 99 | 'django.template.context_processors.request', 100 | 'django.contrib.auth.context_processors.auth', 101 | 'django.contrib.messages.context_processors.messages', 102 | ], 103 | 'debug': DEBUG 104 | }, 105 | }, 106 | ] 107 | 108 | WSGI_APPLICATION = 'wsgi.application' 109 | 110 | 111 | # Database 112 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 113 | 114 | DATABASES = { 115 | 'default': { 116 | 'ENGINE': 'django.db.backends.sqlite3', 117 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 118 | } 119 | } 120 | 121 | # Internationalization 122 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 123 | 124 | LANGUAGE_CODE = 'en-us' 125 | TIME_ZONE = 'UTC' 126 | USE_I18N = True 127 | USE_L10N = True 128 | USE_TZ = True 129 | 130 | 131 | # Parse database configuration from $DATABASE_URL 132 | DATABASES['default'] = dj_database_url.config(default='sqlite://db/sqlite3.db') # comment this out when you make a migration! 133 | 134 | # Enable Connection Pooling (if desired) 135 | # DATABASES['default']['ENGINE'] = 'django_postgrespool' 136 | 137 | # Honor the 'X-Forwarded-Proto' header for request.is_secure() 138 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 139 | 140 | # Allow all host headers 141 | ALLOWED_HOSTS = ['*'] 142 | 143 | # Static files (CSS, JavaScript, Images) 144 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 145 | 146 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 147 | STATIC_ROOT = 'staticfiles' 148 | STATIC_URL = '/static/' 149 | 150 | STATICFILES_DIRS = ( 151 | os.path.join(BASE_DIR, 'static'), 152 | ) 153 | 154 | # Simplified static file serving. 155 | # https://warehouse.python.org/project/whitenoise/ 156 | STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' 157 | 158 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/static/favicon.ico -------------------------------------------------------------------------------- /static/footer.css: -------------------------------------------------------------------------------- 1 | #footer { 2 | font-family: 'Source Sans Pro', sans-serif; 3 | position: relative; 4 | padding: 10px 0; } 5 | 6 | #hidden_semicolon { 7 | position: absolute; 8 | left: -15px; 9 | top: 50%; 10 | margin-top: -12px; 11 | font-weight: bold; 12 | color: rgba(0, 0, 0, 0.2); 13 | cursor: default; 14 | -webkit-touch-callout: none; 15 | -webkit-user-select: none; 16 | -moz-user-select: none; 17 | -ms-user-select: none; 18 | user-select: none; } 19 | 20 | .footer-link { 21 | margin-right: 4px; 22 | color: #252E35; 23 | text-decoration: none; 24 | border-bottom: 1px dotted #252E35; } 25 | .footer-link:hover { 26 | border-bottom: 1px solid #252E35; } 27 | 28 | /* 29 | .wrapper { 30 | min-height: 100%; 31 | height: auto !important; 32 | height: 100%; 33 | margin-bottom: $footer-height * -1; 34 | } 35 | 36 | .push, 37 | .footerContainer { 38 | height: $footer-height; 39 | } 40 | 41 | .footerContainer { 42 | margin: 0; 43 | padding: 0; 44 | .footer { 45 | margin: 0; 46 | padding: 0; 47 | display: flex; 48 | height: $footer-height; 49 | div { 50 | display: inline-block; 51 | flex-grow: 1; 52 | flex-basis: 0; 53 | text-align: center; 54 | .footerLink { 55 | color: white; 56 | text-decoration: none; 57 | font-family: 'Raleway', sans-serif; 58 | display: block; 59 | width: 100%; 60 | height: $footer-height; 61 | line-height: $footer-height; 62 | flex-grow: 1; 63 | position: relative; 64 | transition: background-color 0.1s ease-in-out; 65 | &:hover { 66 | background-color: #13171B; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | */ 73 | 74 | /*# sourceMappingURL=footer.css.map */ 75 | -------------------------------------------------------------------------------- /static/footer.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | #footer { 4 | font-family:'Source Sans Pro', sans-serif; 5 | position:relative; 6 | padding:10px 0; 7 | } 8 | #hidden_semicolon { 9 | position:absolute; 10 | left:-15px; 11 | top:50%; 12 | margin-top:-12px; 13 | font-weight:bold; 14 | color:rgba(0,0,0,0.2); 15 | cursor:default; 16 | -webkit-touch-callout: none; 17 | -webkit-user-select: none; 18 | -moz-user-select: none; 19 | -ms-user-select: none; 20 | user-select: none; 21 | } 22 | .footer-link { 23 | margin-right:4px; 24 | color:#252E35; 25 | text-decoration:none; 26 | border-bottom:1px dotted #252E35; 27 | &:hover { 28 | border-bottom:1px solid #252E35; 29 | } 30 | } 31 | 32 | /* 33 | .wrapper { 34 | min-height: 100%; 35 | height: auto !important; 36 | height: 100%; 37 | margin-bottom: $footer-height * -1; 38 | } 39 | 40 | .push, 41 | .footerContainer { 42 | height: $footer-height; 43 | } 44 | 45 | .footerContainer { 46 | margin: 0; 47 | padding: 0; 48 | .footer { 49 | margin: 0; 50 | padding: 0; 51 | display: flex; 52 | height: $footer-height; 53 | div { 54 | display: inline-block; 55 | flex-grow: 1; 56 | flex-basis: 0; 57 | text-align: center; 58 | .footerLink { 59 | color: white; 60 | text-decoration: none; 61 | font-family: 'Raleway', sans-serif; 62 | display: block; 63 | width: 100%; 64 | height: $footer-height; 65 | line-height: $footer-height; 66 | flex-grow: 1; 67 | position: relative; 68 | transition: background-color 0.1s ease-in-out; 69 | &:hover { 70 | background-color: #13171B; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | */ 77 | -------------------------------------------------------------------------------- /static/grid.css: -------------------------------------------------------------------------------- 1 | .grid .half { 2 | width: 50%; } 3 | .grid .one-third { 4 | width: 33%; } 5 | .grid .two-third { 6 | width: 66%; } 7 | .grid .one-fourth { 8 | width: 25%; } 9 | .grid .three-fourth { 10 | width: 75%; } 11 | .grid .one-fifth { 12 | width: 20%; } 13 | 14 | /*# sourceMappingURL=grid.css.map */ 15 | -------------------------------------------------------------------------------- /static/grid.scss: -------------------------------------------------------------------------------- 1 | .grid { 2 | .half { 3 | width: 50%; 4 | } 5 | .one-third { 6 | width: 33%; 7 | } 8 | .two-third { 9 | width: 66%; 10 | } 11 | .one-fourth { 12 | width: 25%; 13 | } 14 | .three-fourth { 15 | width: 75%; 16 | } 17 | .one-fifth { 18 | width: 20%; 19 | } 20 | } -------------------------------------------------------------------------------- /static/helpers.css: -------------------------------------------------------------------------------- 1 | .vertically-center-single { 2 | position: relative; 3 | top: 50%; 4 | transform: translateY(-50%); } 5 | 6 | .pull--left { 7 | float: left !important; } 8 | 9 | .pull--right { 10 | float: right !important; } 11 | 12 | .pad { 13 | padding: 35px !important; } 14 | 15 | .pad--top { 16 | padding-top: 35px !important; } 17 | 18 | .pad--right { 19 | padding-right: 35px !important; } 20 | 21 | .pad--bottom { 22 | padding-bottom: 35px !important; } 23 | 24 | .pad--left { 25 | padding-left: 35px !important; } 26 | 27 | .pad--ends { 28 | padding-top: 35px !important; 29 | padding-bottom: 35px !important; } 30 | 31 | .pad--sides { 32 | padding-right: 35px !important; 33 | padding-left: 35px !important; } 34 | 35 | .pad-double { 36 | padding: 70px !important; } 37 | 38 | .pad-double--top { 39 | padding-top: 70px !important; } 40 | 41 | .pad-double--right { 42 | padding-right: 70px !important; } 43 | 44 | .pad-double--bottom { 45 | padding-bottom: 70px !important; } 46 | 47 | .pad-double--left { 48 | padding-left: 70px !important; } 49 | 50 | .pad-double--ends { 51 | padding-top: 70px !important; 52 | padding-bottom: 70px !important; } 53 | 54 | .pad-double--sides { 55 | padding-right: 70px !important; 56 | padding-left: 70px !important; } 57 | 58 | .pad-half { 59 | padding: 17.5px !important; } 60 | 61 | .pad-half--top { 62 | padding-top: 17.5px !important; } 63 | 64 | .pad-half--right { 65 | padding-right: 17.5px !important; } 66 | 67 | .pad-half--bottom { 68 | padding-bottom: 17.5px !important; } 69 | 70 | .pad-half--left { 71 | padding-left: 17.5px !important; } 72 | 73 | .pad-half--ends { 74 | padding-top: 17.5px !important; 75 | padding-bottom: 17.5px !important; } 76 | 77 | .pad-half--sides { 78 | padding-right: 17.5px !important; 79 | padding-left: 17.5px !important; } 80 | 81 | .push-double { 82 | margin: 70px !important; } 83 | 84 | .push-double--top { 85 | margin-top: 70px !important; } 86 | 87 | .push-double--right { 88 | margin-right: 70px !important; } 89 | 90 | .push-double--bottom { 91 | margin-bottom: 70px !important; } 92 | 93 | .push-double--left { 94 | margin-left: 70px !important; } 95 | 96 | .push-double--ends { 97 | margin-top: 70px !important; 98 | margin-bottom: 70px !important; } 99 | 100 | .push-double--sides { 101 | margin-right: 70px !important; 102 | margin-left: 70px !important; } 103 | 104 | .push { 105 | margin: 35px !important; } 106 | 107 | .push--top { 108 | margin-top: 35px !important; } 109 | 110 | .push--right { 111 | margin-right: 35px !important; } 112 | 113 | .push--bottom { 114 | margin-bottom: 35px !important; } 115 | 116 | .push--left { 117 | margin-left: 35px !important; } 118 | 119 | .push--ends { 120 | margin-top: 35px !important; 121 | margin-bottom: 35px !important; } 122 | 123 | .push--sides { 124 | margin-right: 35px !important; 125 | margin-left: 35px !important; } 126 | 127 | .push-half { 128 | margin: 17.5px !important; } 129 | 130 | .push-half--top { 131 | margin-top: 17.5px !important; } 132 | 133 | .push-half--right { 134 | margin-right: 17.5px !important; } 135 | 136 | .push-half--bottom { 137 | margin-bottom: 17.5px !important; } 138 | 139 | .push-half--left { 140 | margin-left: 17.5px !important; } 141 | 142 | .push-half--ends { 143 | margin-top: 17.5px !important; 144 | margin-bottom: 17.5px !important; } 145 | 146 | .push-half--sides { 147 | margin-right: 17.5px !important; 148 | margin-left: 17.5px !important; } 149 | 150 | .push-part { 151 | margin: 15px !important; } 152 | 153 | .push-part--top { 154 | margin-top: 15px !important; } 155 | 156 | .push-part--right { 157 | margin-right: 15px !important; } 158 | 159 | .push-part--bottom { 160 | margin-bottom: 15px !important; } 161 | 162 | .push-part--left { 163 | margin-left: 15px !important; } 164 | 165 | .push-part--ends { 166 | margin-top: 15px !important; 167 | margin-bottom: 15px !important; } 168 | 169 | .push-part--sides { 170 | margin-right: 15px !important; 171 | margin-left: 15px !important; } 172 | 173 | .push-tiny { 174 | margin: 5px !important; } 175 | 176 | .push-tiny--top { 177 | margin-top: 5px !important; } 178 | 179 | .push-tiny--right { 180 | margin-right: 5px !important; } 181 | 182 | .push-tiny--bottom { 183 | margin-bottom: 5px !important; } 184 | 185 | .push-tiny--left { 186 | margin-left: 5px !important; } 187 | 188 | .push-tiny--ends { 189 | margin-top: 5px !important; 190 | margin-bottom: 5px !important; } 191 | 192 | .push-tiny--sides { 193 | margin-right: 5px !important; 194 | margin-left: 5px !important; } 195 | 196 | .flush { 197 | margin: 0 !important; } 198 | 199 | .flush--top { 200 | margin-top: 0 !important; } 201 | 202 | .flush--right { 203 | margin-right: 0 !important; } 204 | 205 | .flush--bottom { 206 | margin-bottom: 0 !important; } 207 | 208 | .flush--left { 209 | margin-left: 0 !important; } 210 | 211 | .flush--ends { 212 | margin-top: 0 !important; 213 | margin-bottom: 0 !important; } 214 | 215 | .flush--sides { 216 | margin-right: 0 !important; 217 | margin-left: 0 !important; } 218 | 219 | .complete-flush { 220 | margin: 0 !important; 221 | padding: 0 !important; } 222 | 223 | .complete-flush--top { 224 | margin-top: 0 !important; 225 | padding-top: 0 !important; } 226 | 227 | .complete-flush--right { 228 | margin-right: 0 !important; 229 | padding-right: 0 !important; } 230 | 231 | .complete-flush--bottom { 232 | margin-bottom: 0 !important; 233 | padding-bottom: 0 !important; } 234 | 235 | .complete-flush--left { 236 | margin-left: 0 !important; 237 | padding-left: 0 !important; } 238 | 239 | .complete-flush--ends { 240 | margin-top: 0 !important; 241 | margin-bottom: 0 !important; 242 | padding-bottom: 0 !important; 243 | padding-top: 0 !important; } 244 | 245 | .complete-flush--sides { 246 | margin-right: 0 !important; 247 | margin-left: 0 !important; 248 | padding-left: 0 !important; 249 | padding-right: 0 !important; } 250 | 251 | .float-right { 252 | float: right !important; } 253 | 254 | .float-left { 255 | float: left !important; } 256 | 257 | .full--width { 258 | width: 100% !important; } 259 | 260 | .half { 261 | width: 50%; } 262 | 263 | .one-third { 264 | width: 33%; } 265 | 266 | .two-third { 267 | width: 66%; } 268 | 269 | .one-fourth { 270 | width: 25%; } 271 | 272 | .three-fourth { 273 | width: 75%; } 274 | 275 | .one-fifth { 276 | width: 20%; } 277 | 278 | .inline-block { 279 | display: inline-block; } 280 | 281 | .block { 282 | display: block; } 283 | 284 | .bg-seafoam { 285 | background-color: #42BA92; 286 | /*If the BG is seafoam you should probably have white text :P*/ 287 | color: white; } 288 | 289 | .raleway { 290 | font-family: Raleway, sans-serif; } 291 | 292 | .opensans { 293 | font-family: "Open Sans", sans-serif; } 294 | 295 | /*# sourceMappingURL=helpers.css.map */ 296 | -------------------------------------------------------------------------------- /static/helpers.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // VERTICAL AND HORIZONTAL ALIGNMENT 4 | .vertically-center-single { 5 | position: relative; 6 | top: 50%; 7 | transform: translateY(-50%); 8 | } 9 | 10 | // FLOATS 11 | .pull--left { float: left!important; } 12 | .pull--right { float: right!important; } 13 | 14 | // PADDING AND MARGIN 15 | .pad { padding: $base-spacing-unit!important; } 16 | .pad--top { padding-top: $base-spacing-unit!important; } 17 | .pad--right { padding-right: $base-spacing-unit!important; } 18 | .pad--bottom { padding-bottom:$base-spacing-unit!important; } 19 | .pad--left { padding-left: $base-spacing-unit!important; } 20 | .pad--ends { padding-top: $base-spacing-unit!important; padding-bottom:$base-spacing-unit!important; } 21 | .pad--sides { padding-right: $base-spacing-unit!important; padding-left: $base-spacing-unit!important; } 22 | 23 | .pad-double { padding: $base-spacing-unit * 2!important; } 24 | .pad-double--top { padding-top: $base-spacing-unit * 2!important; } 25 | .pad-double--right { padding-right: $base-spacing-unit * 2!important; } 26 | .pad-double--bottom { padding-bottom:$base-spacing-unit * 2!important; } 27 | .pad-double--left { padding-left: $base-spacing-unit * 2!important; } 28 | .pad-double--ends { padding-top: $base-spacing-unit * 2!important; padding-bottom:$base-spacing-unit * 2!important; } 29 | .pad-double--sides { padding-right: $base-spacing-unit * 2!important; padding-left: $base-spacing-unit * 2!important; } 30 | 31 | .pad-half { padding: $base-spacing-unit / 2!important; } 32 | .pad-half--top { padding-top: $base-spacing-unit / 2!important; } 33 | .pad-half--right { padding-right: $base-spacing-unit / 2!important; } 34 | .pad-half--bottom { padding-bottom:$base-spacing-unit / 2!important; } 35 | .pad-half--left { padding-left: $base-spacing-unit / 2!important; } 36 | .pad-half--ends { padding-top: $base-spacing-unit / 2!important; padding-bottom:$base-spacing-unit / 2!important; } 37 | .pad-half--sides { padding-right: $base-spacing-unit / 2!important; padding-left: $base-spacing-unit / 2!important; } 38 | 39 | .push-double { margin: $base-spacing-unit * 2!important; } 40 | .push-double--top { margin-top: $base-spacing-unit * 2!important; } 41 | .push-double--right { margin-right: $base-spacing-unit * 2!important; } 42 | .push-double--bottom { margin-bottom:$base-spacing-unit * 2!important; } 43 | .push-double--left { margin-left: $base-spacing-unit * 2!important; } 44 | .push-double--ends { margin-top: $base-spacing-unit * 2!important; margin-bottom:$base-spacing-unit * 2!important; } 45 | .push-double--sides { margin-right: $base-spacing-unit * 2!important; margin-left: $base-spacing-unit * 2!important; } 46 | 47 | .push { margin: $base-spacing-unit!important; } 48 | .push--top { margin-top: $base-spacing-unit!important; } 49 | .push--right { margin-right: $base-spacing-unit!important; } 50 | .push--bottom { margin-bottom:$base-spacing-unit!important; } 51 | .push--left { margin-left: $base-spacing-unit!important; } 52 | .push--ends { margin-top: $base-spacing-unit!important; margin-bottom:$base-spacing-unit!important; } 53 | .push--sides { margin-right: $base-spacing-unit!important; margin-left: $base-spacing-unit!important; } 54 | 55 | .push-half { margin: $base-spacing-unit / 2!important; } 56 | .push-half--top { margin-top: $base-spacing-unit / 2!important; } 57 | .push-half--right { margin-right: $base-spacing-unit / 2!important; } 58 | .push-half--bottom { margin-bottom:$base-spacing-unit / 2!important; } 59 | .push-half--left { margin-left: $base-spacing-unit / 2!important; } 60 | .push-half--ends { margin-top: $base-spacing-unit / 2!important; margin-bottom:$base-spacing-unit / 2!important; } 61 | .push-half--sides { margin-right: $base-spacing-unit / 2!important; margin-left: $base-spacing-unit / 2!important; } 62 | 63 | .push-part { margin: 15px!important; } 64 | .push-part--top { margin-top: 15px!important; } 65 | .push-part--right { margin-right: 15px!important; } 66 | .push-part--bottom { margin-bottom:15px!important; } 67 | .push-part--left { margin-left: 15px!important; } 68 | .push-part--ends { margin-top: 15px!important; margin-bottom:15px!important; } 69 | .push-part--sides { margin-right: 15px!important; margin-left: 15px!important; } 70 | 71 | .push-tiny { margin: 5px!important; } 72 | .push-tiny--top { margin-top: 5px!important; } 73 | .push-tiny--right { margin-right: 5px!important; } 74 | .push-tiny--bottom { margin-bottom:5px!important; } 75 | .push-tiny--left { margin-left: 5px!important; } 76 | .push-tiny--ends { margin-top: 5px!important; margin-bottom:5px!important; } 77 | .push-tiny--sides { margin-right: 5px!important; margin-left: 5px!important; } 78 | 79 | .flush { margin: 0!important; } 80 | .flush--top { margin-top: 0!important; } 81 | .flush--right { margin-right: 0!important; } 82 | .flush--bottom { margin-bottom:0!important; } 83 | .flush--left { margin-left: 0!important; } 84 | .flush--ends { margin-top: 0!important; margin-bottom:0!important; } 85 | .flush--sides { margin-right: 0!important; margin-left: 0!important; } 86 | 87 | .complete-flush { margin: 0!important; padding: 0!important; } 88 | .complete-flush--top { margin-top: 0!important; padding-top: 0!important; } 89 | .complete-flush--right { margin-right: 0!important; padding-right: 0!important; } 90 | .complete-flush--bottom { margin-bottom:0!important; padding-bottom: 0!important; } 91 | .complete-flush--left { margin-left: 0!important; padding-left: 0!important; } 92 | .complete-flush--ends { margin-top: 0!important; margin-bottom:0!important; padding-bottom: 0!important; padding-top: 0!important; } 93 | .complete-flush--sides { margin-right: 0!important; margin-left: 0!important; padding-left: 0!important; padding-right: 0!important; } 94 | 95 | // FLOATS 96 | .float-right { float: right !important; } 97 | .float-left { float: left !important; } 98 | 99 | // SIZES 100 | .full--width { width: 100% !important; } 101 | 102 | .half { width: 50%; } 103 | .one-third { width: 33%; } 104 | .two-third { width: 66%; } 105 | .one-fourth { width: 25%; } 106 | .three-fourth { width: 75%; } 107 | .one-fifth { width: 20%; } 108 | 109 | .inline-block { display: inline-block; } 110 | 111 | .block { display: block; } 112 | 113 | .bg-seafoam { 114 | background-color: #42BA92; 115 | /*If the BG is seafoam you should probably have white text :P*/ 116 | color: white; 117 | } 118 | 119 | .raleway { 120 | font-family: Raleway, sans-serif; 121 | } 122 | 123 | .opensans { 124 | font-family: "Open Sans", sans-serif; 125 | } -------------------------------------------------------------------------------- /static/homepage.css: -------------------------------------------------------------------------------- 1 | .centered, .carousel { 2 | width: 90%; 3 | max-width: 900px; 4 | margin: 0 auto; } 5 | 6 | /* == Logged out user things == */ 7 | .logged-out-header .logged-out-header-inner { 8 | width: 40%; } 9 | .logged-out-header .header-text { 10 | color: white; 11 | text-align: center; 12 | font-family: 'Source Sans Pro', sans-serif; 13 | font-weight: bold; 14 | font-size: 44px; 15 | text-shadow: 0 2px 2px rgba(37, 46, 53, 0.6); 16 | margin: 0 auto 30px auto; 17 | padding-top: 20px; } 18 | 19 | .showcase { 20 | height: 40.8%; 21 | margin: 0 auto; 22 | position: relative; 23 | /* for the abs-positioned image */ 24 | cursor: default; 25 | -webkit-touch-callout: none; 26 | /* iOS Safari */ 27 | -webkit-user-select: none; 28 | /* Chrome/Safari/Opera */ 29 | -khtml-user-select: none; 30 | /* Konqueror */ 31 | -moz-user-select: none; 32 | /* Firefox */ 33 | -ms-user-select: none; 34 | /* IE/Edge */ 35 | user-select: none; 36 | /* non-prefixed version, currently 37 | not supported by any browser */ } 38 | .showcase .showcase-img { 39 | width: 100%; 40 | border-radius: 5px; 41 | box-shadow: 0 1px 8px -1px #252E35; } 42 | .showcase .showcase-callout { 43 | z-index: 8888; 44 | color: white; 45 | position: absolute; 46 | height: 35px; 47 | box-sizing: border-box; 48 | font-family: 'Source Sans Pro', sans-serif; 49 | font-weight: bold; 50 | font-size: 16px; 51 | letter-spacing: 1px; 52 | white-space: nowrap; 53 | padding: 8px 30px; 54 | border-radius: 500px; 55 | background-color: #252E35; } 56 | .showcase .showcase-callout:after { 57 | content: ""; 58 | display: block; 59 | width: 0px; 60 | height: 0px; 61 | border-left: 10px solid #252E35; 62 | border-top: 10px solid #252E35; 63 | border-right: 10px solid transparent; 64 | border-bottom: 10px solid transparent; 65 | transform: translate(20px, -37px) rotate(45deg); 66 | position: relative; 67 | top: 1px; } 68 | .showcase .showcase-callout.arrowplacement2 { 69 | transform: translate(-90%, 0); } 70 | .showcase .showcase-callout.arrowplacement2:after { 71 | transform: translate(240px, -37px) rotate(45deg); } 72 | 73 | .showcase-pocket { 74 | display: block; 75 | background: #444C54; 76 | height: 34px; 77 | position: relative; 78 | z-index: 5555; 79 | top: -30px; 80 | box-shadow: 0 -17px 20px -13px rgba(68, 76, 84, 0.9), 0 -5px 4px -5px rgba(0, 0, 0, 0.5); 81 | width: calc(40% + 100px); 82 | margin: 0 auto; } 83 | 84 | .cta-wrapper { 85 | text-align: center; } 86 | 87 | .cta-button { 88 | display: inline-block; 89 | color: white; 90 | text-decoration: none; 91 | font-family: 'Source Sans Pro', sans-serif; 92 | font-size: 20px; 93 | font-weight: bold; 94 | padding: 18px 38px; 95 | background: #42BA92; 96 | border-radius: 4px; 97 | line-height: 1; } 98 | .cta-button span { 99 | font-size: 16px; 100 | color: rgba(37, 46, 53, 0.85); 101 | font-weight: normal; 102 | /*&:before { 103 | content:' '; 104 | display:block; 105 | width:24px; 106 | margin-left:-12px; 107 | height:0; 108 | border-top:1px solid rgba(37,46,53,0.2); 109 | 110 | position:absolute; 111 | top:-1px; 112 | left:50%; 113 | }*/ } 114 | 115 | .or-separator { 116 | display: inline-block; 117 | color: white; 118 | font-family: 'Source Sans Pro', sans-serif; 119 | position: relative; 120 | bottom: 12px; 121 | margin: 0 20px; 122 | cursor: default; } 123 | .or-separator:before { 124 | content: " "; 125 | display: block; 126 | width: 0; 127 | height: 8px; 128 | border-right: 1px solid rgba(255, 255, 255, 0.3); 129 | position: relative; 130 | left: 7px; } 131 | .or-separator:after { 132 | content: " "; 133 | display: block; 134 | width: 0; 135 | height: 8px; 136 | border-right: 1px solid rgba(255, 255, 255, 0.3); 137 | position: relative; 138 | left: 7px; 139 | top: 4px; } 140 | 141 | /* == Responsibly responsive == */ 142 | @media screen and (max-width: 1634px) { 143 | .logged-out-header .header-text { 144 | font-size: 2.65vw; } } 145 | @media screen and (max-width: 850px) { 146 | .showcase, .showcase-pocket { 147 | display: none; } 148 | 149 | .logged-out-header .logged-out-header-inner { 150 | width: 80%; } 151 | 152 | .logged-out-header .header-text { 153 | font-size: 5.05vw; } } 154 | @media screen and (max-width: 570px) { 155 | .cta-button { 156 | display: block; 157 | margin: 10px 0; } 158 | 159 | .or-separator { 160 | display: none; } } 161 | /* == Carousels == */ 162 | .carousel-wrapper { 163 | padding: 30px 0; } 164 | .carousel-wrapper.gray { 165 | background: #ddd; } 166 | 167 | .carousel { 168 | font-family: 'Source Sans Pro', sans-serif; } 169 | .carousel .carousel-title { 170 | font-size: 42px; 171 | margin: 0; 172 | color: #42BA92; 173 | line-height: 0.8; 174 | margin-bottom: 10px; } 175 | .carousel .carousel-content { 176 | display: flex; 177 | width: 100%; 178 | border-radius: 4px; 179 | overflow: hidden; } 180 | .carousel .carousel-content .carousel-link { 181 | margin-bottom: -4px; 182 | text-decoration: none; 183 | flex-shrink: 1; 184 | flex-grow: 1; 185 | flex-basis: 0; 186 | position: relative; } 187 | .carousel .carousel-content .carousel-image { 188 | width: 100%; } 189 | .carousel .carousel-content .carousel-info { 190 | width: 100%; 191 | height: 0; 192 | padding-bottom: 100%; 193 | position: absolute; 194 | top: 0; 195 | left: 0; 196 | background: -moz-linear-gradient(top, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.5) 100%); 197 | background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.5) 100%); 198 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.5) 100%); 199 | opacity: 0; 200 | overflow: hidden; 201 | transition: 0.4s; } 202 | .carousel .carousel-content .carousel-info:hover { 203 | opacity: 1; } 204 | .carousel .carousel-content .carousel-header { 205 | font-size: 20px; 206 | max-width: 100%; 207 | position: absolute; 208 | top: 15px; 209 | left: 15px; 210 | margin: 0; 211 | margin-right: 15px; 212 | line-height: 1; 213 | opacity: 1; 214 | color: white; } 215 | 216 | /* putting my css down here, easier to edit (also I don't do scss) */ 217 | .showcase-callout { 218 | -webkit-transition: opacity .4s; 219 | transition: opacity .4s; 220 | opacity: 0; } 221 | 222 | /*.showcase-callout:hover { 223 | opacity: .8; 224 | }*/ 225 | .bordered-link { 226 | -webkit-transition: background .5s, color .2s; 227 | transition: background .5s, color .2s; } 228 | 229 | .unbordered-link { 230 | transition: background .5s, color .2s; 231 | padding: 6px 14px; 232 | border-radius: 4px; 233 | color: white; } 234 | 235 | .unbordered-link:hover { 236 | background: white; 237 | color: #444C54 !important; } 238 | 239 | .showcase-trigger { 240 | border-radius: 10px; 241 | width: 15px; 242 | height: 15px; 243 | background-color: #0074D9; 244 | z-index: 8888; 245 | position: absolute; 246 | opacity: .8; 247 | box-shadow: 0px 0px 5px #0074D9; 248 | transition: opacity ease-out 0.2s, width ease-out 0.2s, height ease-out 0.2s, transform ease-out 0.2s; 249 | transform: translate(0, 0); } 250 | 251 | .showcase-trigger:hover + .showcase-callout { 252 | opacity: 1; } 253 | 254 | .showcase-trigger::after { 255 | animation: pulsate 3s 1s ease-out infinite; 256 | background: transparent; 257 | border-radius: 10px; 258 | width: 15px; 259 | height: 15px; 260 | position: absolute; 261 | left: 0; 262 | top: 0; 263 | box-sizing: border-box; 264 | border: 2px solid #0074D9; 265 | position: absolute; 266 | display: block; 267 | content: ""; 268 | opacity: 0; } 269 | 270 | .showcase-trigger:hover { 271 | opacity: 0.3; 272 | width: 20px; 273 | height: 20px; 274 | border-radius: 100%; 275 | transform: translate(-3.5px, -3.5px); 276 | box-shadow: none; } 277 | 278 | .showcase-trigger:hover::after { 279 | animation: none; 280 | display: none; } 281 | 282 | /* transition things */ 283 | @-webkit-keyframes pulsate { 284 | 0% { 285 | -webkit-transform: scale(1); 286 | opacity: 0.8; } 287 | 30% { 288 | -webkit-transform: scale(2); 289 | opacity: 0; } 290 | 100% { 291 | -webkit-transform: scale(2); 292 | opacity: 0; } } 293 | @-moz-keyframes pulsate { 294 | 0% { 295 | -moz-transform: scale(1); 296 | opacity: 0.8; } 297 | 30% { 298 | -moz-transform: scale(2); 299 | opacity: 0; } 300 | 100% { 301 | -moz-transform: scale(2); 302 | opacity: 0; } } 303 | @keyframes pulsate { 304 | 0% { 305 | -webkit-transform: scale(1); 306 | -moz-transform: scale(1); 307 | -ms-transform: scale(1); 308 | -o-transform: scale(1); 309 | transform: scale(1); 310 | opacity: 0.8; } 311 | 30% { 312 | -webkit-transform: scale(2); 313 | -moz-transform: scale(2); 314 | -ms-transform: scale(2); 315 | -o-transform: scale(2); 316 | transform: scale(2); 317 | opacity: 0; } 318 | 100% { 319 | -webkit-transform: scale(2); 320 | -moz-transform: scale(2); 321 | -ms-transform: scale(2); 322 | -o-transform: scale(2); 323 | transform: scale(2); 324 | opacity: 0; } } 325 | 326 | /*# sourceMappingURL=homepage.css.map */ 327 | -------------------------------------------------------------------------------- /static/homepage.scss: -------------------------------------------------------------------------------- 1 | // TODO: Make media quieries for really small screens (< 850px width) 2 | 3 | .centered { 4 | width:90%; 5 | max-width:900px; 6 | margin:0 auto; 7 | } 8 | 9 | /* == Logged out user things == */ 10 | // Is it possible to only serve these when the user is logged out? 11 | .logged-out-header { 12 | 13 | .logged-out-header-inner { 14 | width:40%; 15 | } 16 | .header-text { 17 | color:white; 18 | text-align:center; 19 | font-family:'Source Sans Pro', sans-serif; 20 | font-weight: bold; 21 | font-size:44px; 22 | text-shadow:0 2px 2px rgba(37,46,53, 0.6); 23 | margin:0 auto 30px auto; 24 | padding-top:20px; 25 | } 26 | } 27 | .showcase { 28 | height: 40.8%; 29 | margin: 0 auto; 30 | position: relative; /* for the abs-positioned image */ 31 | cursor:default; 32 | -webkit-touch-callout: none; /* iOS Safari */ 33 | -webkit-user-select: none; /* Chrome/Safari/Opera */ 34 | -khtml-user-select: none; /* Konqueror */ 35 | -moz-user-select: none; /* Firefox */ 36 | -ms-user-select: none; /* IE/Edge */ 37 | user-select: none; /* non-prefixed version, currently 38 | not supported by any browser */ 39 | 40 | .showcase-img { 41 | width: 100%; 42 | 43 | border-radius: 5px; 44 | box-shadow: 0 1px 8px -1px #252E35; 45 | } 46 | 47 | .showcase-callout { 48 | z-index: 8888; 49 | color: white; 50 | position: absolute; 51 | height: 35px; 52 | box-sizing: border-box; 53 | font-family:'Source Sans Pro', sans-serif; 54 | font-weight: bold; 55 | font-size:16px; 56 | letter-spacing: 1px; 57 | white-space: nowrap; 58 | padding: 8px 30px; 59 | border-radius: 500px; 60 | background-color: #252E35; 61 | 62 | &:after { 63 | content:""; 64 | display: block; 65 | width: 0px; 66 | height: 0px; 67 | border-left: 10px solid #252E35; 68 | border-top: 10px solid #252E35; 69 | border-right: 10px solid transparent; 70 | border-bottom: 10px solid transparent; 71 | transform: translate(20px,-37px) rotate(45deg); 72 | position:relative; 73 | top:1px; 74 | } 75 | &.arrowplacement2 { 76 | transform:translate(-90%, 0); 77 | } 78 | &.arrowplacement2:after { 79 | transform: translate(240px,-37px) rotate(45deg); 80 | } 81 | } 82 | } 83 | .showcase-pocket { 84 | display:block; 85 | background:#444C54; 86 | height:34px; 87 | position:relative; 88 | z-index:5555; 89 | top:-30px; 90 | box-shadow: 0 -17px 20px -13px rgba(68,76,84,0.9), 0 -5px 4px -5px rgba(0,0,0,0.5); 91 | width:calc(40% + 100px); 92 | margin:0 auto; 93 | } 94 | .cta-wrapper { 95 | text-align:center; 96 | } 97 | .cta-button { 98 | display:inline-block; 99 | color:white; 100 | text-decoration:none; 101 | font-family:'Source Sans Pro', sans-serif; 102 | font-size:20px; 103 | font-weight:bold; 104 | padding:18px 38px; 105 | background:#42BA92; 106 | border-radius:4px; 107 | line-height:1; 108 | 109 | span { 110 | font-size:16px; 111 | color:rgba(37,46,53,0.85); 112 | font-weight:normal; 113 | 114 | /*&:before { 115 | content:' '; 116 | display:block; 117 | width:24px; 118 | margin-left:-12px; 119 | height:0; 120 | border-top:1px solid rgba(37,46,53,0.2); 121 | 122 | position:absolute; 123 | top:-1px; 124 | left:50%; 125 | }*/ 126 | } 127 | } 128 | .or-separator { 129 | display:inline-block; 130 | color:white; 131 | font-family:'Source Sans Pro', sans-serif; 132 | position:relative; 133 | bottom:12px; 134 | margin:0 20px; 135 | cursor:default; 136 | 137 | &:before { 138 | content:" "; 139 | display:block; 140 | width:0; 141 | height:8px; 142 | border-right:1px solid rgba(255,255,255,0.3); 143 | position:relative; 144 | left:7px; 145 | } 146 | &:after { 147 | content:" "; 148 | display:block; 149 | width:0; 150 | height:8px; 151 | border-right:1px solid rgba(255,255,255,0.3); 152 | position:relative; 153 | left:7px; 154 | top:4px; 155 | } 156 | } 157 | 158 | /* == Responsibly responsive == */ 159 | @media screen and (max-width:1634px) { 160 | .logged-out-header .header-text { 161 | font-size:2.65vw; 162 | } 163 | } 164 | @media screen and (max-width:850px) { 165 | .showcase, .showcase-pocket { 166 | display:none; 167 | } 168 | .logged-out-header .logged-out-header-inner { 169 | width:80%; 170 | } 171 | .logged-out-header .header-text { 172 | font-size:5.05vw; 173 | } 174 | } 175 | @media screen and (max-width:570px) { 176 | .cta-button { 177 | display:block; 178 | 179 | margin:10px 0; 180 | } 181 | .or-separator { 182 | display:none; 183 | } 184 | } 185 | /* == Carousels == */ 186 | .carousel-wrapper { 187 | padding:30px 0; 188 | 189 | &.gray { 190 | background:#ddd; 191 | } 192 | } 193 | .carousel { 194 | @extend .centered; 195 | font-family:'Source Sans Pro', sans-serif; 196 | 197 | .carousel-title { 198 | font-size:42px; 199 | margin:0; 200 | color:#42BA92; 201 | line-height:0.8; 202 | margin-bottom:10px; 203 | } 204 | 205 | .carousel-content { 206 | display:flex; 207 | width:100%; 208 | border-radius:4px; 209 | overflow:hidden; 210 | 211 | .carousel-link { 212 | margin-bottom:-4px; // Account for inline-block spacing struggles 213 | text-decoration:none; 214 | flex-shrink:1; 215 | flex-grow:1; 216 | flex-basis:0; 217 | 218 | position:relative; 219 | } 220 | .carousel-image { 221 | width:100%; 222 | } 223 | .carousel-info { 224 | width:100%; 225 | height:0; 226 | padding-bottom:100%; // Doctors hate this one weird trick! 227 | position:absolute; 228 | top:0; 229 | left:0; 230 | background: -moz-linear-gradient(top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.5) 100%); 231 | background: -webkit-linear-gradient(top, rgba(0,0,0,0.75) 0%,rgba(0,0,0,0.5) 100%); 232 | background: linear-gradient(to bottom, rgba(0,0,0,0.75) 0%,rgba(0,0,0,0.5) 100%); 233 | opacity:0; 234 | overflow:hidden; 235 | 236 | transition:0.4s; 237 | 238 | &:hover { 239 | opacity:1; 240 | } 241 | } 242 | .carousel-header { 243 | font-size:20px; 244 | max-width:100%; 245 | position:absolute; 246 | top:15px; 247 | left:15px; 248 | margin:0; 249 | margin-right:15px; 250 | line-height:1; 251 | opacity:1; 252 | color:white; 253 | } 254 | } 255 | } 256 | 257 | /* putting my css down here, easier to edit (also I don't do scss) */ 258 | .showcase-callout { 259 | -webkit-transition: opacity .4s; 260 | transition: opacity .4s; 261 | opacity: 0; 262 | } 263 | 264 | /*.showcase-callout:hover { 265 | opacity: .8; 266 | }*/ 267 | 268 | .bordered-link { 269 | -webkit-transition: background .5s, color .2s; 270 | transition: background .5s, color .2s; 271 | } 272 | 273 | .unbordered-link { 274 | transition: background .5s, color .2s; 275 | padding: 6px 14px; 276 | border-radius: 4px; 277 | color: white; 278 | } 279 | 280 | .unbordered-link:hover { 281 | background: white; 282 | color: #444C54 !important; 283 | } 284 | 285 | .showcase-trigger { 286 | border-radius: 10px; 287 | width: 15px; 288 | height: 15px; 289 | background-color: #0074D9; 290 | z-index: 8888; 291 | position: absolute; 292 | opacity: .8; 293 | box-shadow: 0px 0px 5px #0074D9; 294 | transition: opacity ease-out 0.2s, width ease-out 0.2s, height ease-out 0.2s, transform ease-out 0.2s; 295 | transform: translate(0, 0); 296 | } 297 | 298 | .showcase-trigger:hover + .showcase-callout { 299 | opacity: 1; 300 | } 301 | .showcase-trigger::after { 302 | animation: pulsate 3s 1s ease-out infinite; 303 | background: transparent; 304 | border-radius: 10px; 305 | width: 15px; 306 | height: 15px; 307 | position: absolute; 308 | left: 0; 309 | top: 0; 310 | box-sizing: border-box; 311 | border: 2px solid #0074D9; 312 | position: absolute; 313 | display: block; 314 | content: ""; 315 | opacity: 0; 316 | } 317 | 318 | .showcase-trigger:hover { 319 | opacity: 0.3; 320 | width: 20px; 321 | height: 20px; 322 | border-radius: 100%; 323 | transform:translate(-3.5px, -3.5px); 324 | box-shadow: none; 325 | } 326 | 327 | .showcase-trigger:hover::after { 328 | animation: none; 329 | display: none; 330 | } 331 | 332 | 333 | 334 | /* transition things */ 335 | @-webkit-keyframes pulsate { 336 | 0% { 337 | -webkit-transform:scale(1); 338 | opacity:0.8 339 | } 340 | 30% { 341 | -webkit-transform:scale(2); 342 | opacity:0 343 | } 344 | 100% { 345 | -webkit-transform:scale(2); 346 | opacity:0 347 | } 348 | } 349 | @-moz-keyframes pulsate { 350 | 0% { 351 | -moz-transform:scale(1); 352 | opacity:0.8 353 | } 354 | 30% { 355 | -moz-transform:scale(2); 356 | opacity:0 357 | } 358 | 100% { 359 | -moz-transform:scale(2); 360 | opacity:0 361 | } 362 | } 363 | @keyframes pulsate { 364 | 0% { 365 | -webkit-transform:scale(1); 366 | -moz-transform:scale(1); 367 | -ms-transform:scale(1); 368 | -o-transform:scale(1); 369 | transform:scale(1); 370 | opacity:0.8 371 | } 372 | 30% { 373 | -webkit-transform:scale(2); 374 | -moz-transform:scale(2); 375 | -ms-transform:scale(2); 376 | -o-transform:scale(2); 377 | transform:scale(2); 378 | opacity:0 379 | } 380 | 100% { 381 | -webkit-transform:scale(2); 382 | -moz-transform:scale(2); 383 | -ms-transform:scale(2); 384 | -o-transform:scale(2); 385 | transform:scale(2); 386 | opacity:0 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /static/nav.css: -------------------------------------------------------------------------------- 1 | #nav-wrapper { 2 | width: 100%; 3 | background: #444C54; 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | box-shadow: 0 1px 5px 0px #252E35; 8 | z-index: 9999; } 9 | 10 | #site-nav { 11 | min-height: 80px; 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | cursor: default; 16 | border-bottom: 0; } 17 | #site-nav a { 18 | color: white; 19 | text-decoration: none; 20 | font-family: 'Source Sans Pro', sans-serif; 21 | font-size: 18px; 22 | margin: 0 8px; 23 | display: inline-table; 24 | vertical-align: middle; } 25 | #site-nav a.bordered-link { 26 | border: 2px solid white; 27 | padding: 6px 14px; 28 | border-radius: 4px; } 29 | #site-nav a.bordered-link:hover { 30 | background: white; 31 | color: #444C54; } 32 | #site-nav a.green-link { 33 | font-weight: bold; 34 | color: #42BA92; } 35 | 36 | /*# sourceMappingURL=nav.css.map */ 37 | -------------------------------------------------------------------------------- /static/nav.scss: -------------------------------------------------------------------------------- 1 | #nav-wrapper { 2 | width:100%; 3 | background:#444C54; 4 | position:fixed; 5 | top:0; 6 | left:0; 7 | box-shadow:0 1px 5px 0px #252E35; 8 | z-index:9999; 9 | } 10 | #site-nav { 11 | min-height:80px; 12 | display:flex; 13 | justify-content:space-between; 14 | align-items:center; 15 | cursor:default; 16 | border-bottom: 0; 17 | 18 | a { 19 | color:white; 20 | text-decoration:none; 21 | font-family:'Source Sans Pro', sans-serif; 22 | font-size:18px; 23 | margin:0 8px; 24 | display:inline-table; 25 | vertical-align:middle; 26 | 27 | &.bordered-link { 28 | border:2px solid white; 29 | padding:6px 14px; 30 | border-radius:4px; 31 | &:hover { 32 | background:white; 33 | color:#444C54; 34 | } 35 | } 36 | &.green-link { 37 | font-weight:bold; 38 | color:#42BA92; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /static/resources/elemental--base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/static/resources/elemental--base.png -------------------------------------------------------------------------------- /static/resources/front-about-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/static/resources/front-about-code.png -------------------------------------------------------------------------------- /static/resources/front-about-learn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/static/resources/front-about-learn.png -------------------------------------------------------------------------------- /static/resources/front-about-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/static/resources/front-about-share.png -------------------------------------------------------------------------------- /static/resources/navbarimg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/static/resources/navbarimg1.png -------------------------------------------------------------------------------- /static/resources/navbarimg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementalCode/elemental/27999b021e507c5bdd01425f3e5584b6c3bb6054/static/resources/navbarimg2.png -------------------------------------------------------------------------------- /static/typography.css: -------------------------------------------------------------------------------- 1 | span.smalltext { 2 | font-size: 12px; } 3 | 4 | .text--right { 5 | text-align: right !important; } 6 | 7 | .text--left { 8 | text-align: left !important; } 9 | 10 | /*# sourceMappingURL=typography.css.map */ 11 | -------------------------------------------------------------------------------- /static/typography.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | span { 4 | &.smalltext { 5 | font-size: 12px; 6 | } 7 | } 8 | 9 | .text--right { text-align: right!important; } 10 | .text--left { text-align: left!important; } -------------------------------------------------------------------------------- /static/variables.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /*# sourceMappingURL=variables.css.map */ 4 | -------------------------------------------------------------------------------- /static/variables.scss: -------------------------------------------------------------------------------- 1 | $footer-height: 130px; 2 | 3 | $base-spacing-unit: 35px; 4 | 5 | $colors: ( 6 | seafoam: #42BA92, 7 | seafoam-dark: #319E7A, 8 | coral: #ff7f50, 9 | ); 10 | 11 | // colors 12 | @function color($color-name) { 13 | @return map-get($colors, $color-name); 14 | } -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.conf.urls import include, url 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | from apps.core.views import (Index, SignUp, Logout, TermsOfService, 8 | BanPage) 9 | from apps.accounts.views import ProfileView, UserSettings 10 | 11 | def favicon(request): 12 | f = open('static/favicon.ico', 'rb') 13 | x = f.read() 14 | f.close() 15 | return HttpResponse(x) 16 | 17 | urlpatterns = [ 18 | 19 | # Index views 20 | url(r'^$', Index.as_view(), name='index'), 21 | url(r'^(?i)admin/?', include(admin.site.urls)), 22 | url(r'^(?i)terms-of-service/?$', TermsOfService.as_view(), name='terms'), 23 | url(r'^(?i)banned/?$', BanPage.as_view(), name='ban-page'), 24 | 25 | # Stuff to possibly move aside 26 | url(r'^(?i)users/(?P\w+)/?$', ProfileView.as_view(), name='profile'), 27 | 28 | # Namespaces 29 | url(r'^(?i)projects/', include('apps.projects.urls', namespace='projects')), 30 | url(r'^(?i)accounts/', include('apps.accounts.urls', namespace='accounts')), 31 | 32 | # REST stuff 33 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), 34 | 35 | url(r'^(?i)rest/', include('apps.rest.urls', namespace='api')), 36 | url(r'^favicon.ico/?$', favicon) 37 | ] 38 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for gettingstarted project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | from whitenoise.django import DjangoWhiteNoise 15 | 16 | application = get_wsgi_application() 17 | application = DjangoWhiteNoise(application) --------------------------------------------------------------------------------