aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore11
-rw-r--r--README.md40
-rw-r--r--apps/accounts/__init__.py1
-rw-r--r--apps/accounts/admin.py13
-rw-r--r--apps/accounts/apps.py8
-rw-r--r--apps/accounts/forms.py19
-rw-r--r--apps/accounts/migrations/0001_initial.py55
-rw-r--r--apps/accounts/migrations/__init__.py0
-rw-r--r--apps/accounts/models.py20
-rw-r--r--apps/accounts/signals.py12
-rw-r--r--apps/accounts/tests/__init__.py0
-rw-r--r--apps/accounts/tests/test_models.py13
-rw-r--r--apps/accounts/tests/test_views.py23
-rw-r--r--apps/accounts/urls.py12
-rw-r--r--apps/accounts/views.py32
-rw-r--r--apps/notes/__init__.py0
-rw-r--r--apps/notes/forms.py17
-rw-r--r--apps/notes/migrations/0001_initial.py38
-rw-r--r--apps/notes/migrations/0002_auto_20181111_1825.py31
-rw-r--r--apps/notes/migrations/0003_note_folder.py19
-rw-r--r--apps/notes/migrations/__init__.py0
-rw-r--r--apps/notes/models.py38
-rw-r--r--apps/notes/serializers.py18
-rw-r--r--apps/notes/tests/__init__.py1
-rw-r--r--apps/notes/tests/test_models.py30
-rw-r--r--apps/notes/tests/test_views.py57
-rw-r--r--apps/notes/urls.py18
-rw-r--r--apps/notes/views.py41
-rw-r--r--apps/pages/__init__.py0
-rw-r--r--apps/pages/admin.py32
-rw-r--r--apps/pages/migrations/0001_initial.py26
-rw-r--r--apps/pages/migrations/__init__.py0
-rw-r--r--apps/pages/models.py41
-rw-r--r--apps/pages/tests/__init__.py0
-rw-r--r--apps/pages/tests/test_models.py20
-rw-r--r--apps/pages/tests/test_views.py29
-rw-r--r--apps/pages/views.py11
-rw-r--r--apps/utils/__init__.py0
-rw-r--r--apps/utils/next_prev.py80
-rw-r--r--apps/utils/static/autocomplete.js10
-rw-r--r--apps/utils/static/choices.css2
-rw-r--r--apps/utils/static/choices.min.js5
-rw-r--r--apps/utils/static/image-loader.js47
-rw-r--r--apps/utils/static/next-prev-links.js60
-rw-r--r--apps/utils/urls.py12
-rw-r--r--apps/utils/util.py84
-rw-r--r--apps/utils/views.py69
-rw-r--r--apps/utils/widgets.py144
-rw-r--r--config/__init__.py0
-rw-r--r--config/base_urls.py34
-rwxr-xr-xconfig/djadmin.sh10
-rw-r--r--config/requirements.txt12
-rw-r--r--config/settings.py167
-rw-r--r--config/wsgi.py16
-rw-r--r--design/config.rb12
-rw-r--r--design/sass/_fonts.scss10
-rw-r--r--design/sass/_footer.scss24
-rw-r--r--design/sass/_forms.scss85
-rw-r--r--design/sass/_global.scss196
-rw-r--r--design/sass/_header.scss55
-rw-r--r--design/sass/_mixins.scss136
-rw-r--r--design/sass/_queries.scss29
-rw-r--r--design/sass/screenv1.scss7
-rw-r--r--design/templates/accounts/profile.html54
-rw-r--r--design/templates/base.html56
-rw-r--r--design/templates/django_registration/activation_complete.html8
-rw-r--r--design/templates/django_registration/activation_email_body.txt5
-rw-r--r--design/templates/django_registration/activation_email_subject.txt1
-rw-r--r--design/templates/django_registration/registration_complete.html7
-rw-r--r--design/templates/django_registration/registration_form.html17
-rw-r--r--design/templates/notes/create.html18
-rw-r--r--design/templates/notes/note_list.html9
-rw-r--r--design/templates/pages/page.html7
-rw-r--r--design/templates/registration/login.html35
-rw-r--r--functional_tests.py12
-rwxr-xr-xmanage.py17
76 files changed, 2278 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..69f3431
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*.log
+Vagrantfile
+.vagrant
+.env
+venv/
+__pycache__
+.coverage
+htmlcov/
+.sass-cache/
+media/
+static/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..872c983
--- /dev/null
+++ b/README.md
@@ -0,0 +1,40 @@
+# Project purpose
+
+Create, edit and view notes
+
+## Internal Apps
+### Accounts
+* [User](https://git.luxagraf.net/luxagraf/writer/src/branch/master/apps/accounts/models.py)
+ * custom User model subclassing AbstractUser as per [Django best practices](https://docs.djangoproject.com/en/dev/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project)
+* [UserProfile](https://git.luxagraf.net/luxagraf/writer/src/branch/master/apps/accounts/models.py)
+ * adds profile to the user class with:
+ * profile photo
+ * website
+ * bio
+ * location
+
+
+## External dependencies
+
+#### [Coverage](https://github.com/nedbat/coveragepy)
+* Because I hate keeping track of what needs tests
+
+#### [Python Decouple](https://pypi.org/project/python-decouple/)
+* Decouple helps you to organize your settings so that you can change parameters without having to redeploy your app.
+* Allows committing settings.py by keeping not-committed settings in one place (.env)
+
+#### [Django Taggit](https://github.com/alex/django-taggit)
+* Simplest way to get robust tagging capabilities
+
+#### [Django Storages](https://github.com/jschneier/django-storages)
+* Simplest way to make all static assets go to Amazon S3
+* Simplifies migrating server setups by keep static assets off the server
+
+#### [Django Registration](https://github.com/ubernostrum/django-registration/)
+* Simplest way to handle all aspects of registration/confirmation (written by James Bennett)
+
+#### [Django Pwned Passwords](https://github.com/ubernostrum/pwned-passwords-django)
+* Because we should inform people when their passwords are compromised (written by James Bennett)
+
+#### [Django-Extensions](https://github.com/django-extensions/django-extensions)
+* Because I'd go crazy without it. shell_plus is a godsend
diff --git a/apps/accounts/__init__.py b/apps/accounts/__init__.py
new file mode 100644
index 0000000..9332741
--- /dev/null
+++ b/apps/accounts/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'accounts.apps.AccountAppConfig'
diff --git a/apps/accounts/admin.py b/apps/accounts/admin.py
new file mode 100644
index 0000000..25f873b
--- /dev/null
+++ b/apps/accounts/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from .models import User, UserProfile
+
+
+@admin.register(User)
+class UserAdmin(admin.ModelAdmin):
+ pass
+
+
+@admin.register(UserProfile)
+class UserProfileAdmin(admin.ModelAdmin):
+ pass
diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py
new file mode 100644
index 0000000..9a7d5ba
--- /dev/null
+++ b/apps/accounts/apps.py
@@ -0,0 +1,8 @@
+from django.apps import AppConfig
+
+
+class AccountAppConfig(AppConfig):
+ name = 'accounts'
+
+ def ready(self):
+ import accounts.signals
diff --git a/apps/accounts/forms.py b/apps/accounts/forms.py
new file mode 100644
index 0000000..d53e754
--- /dev/null
+++ b/apps/accounts/forms.py
@@ -0,0 +1,19 @@
+from django import forms
+from django_registration.forms import RegistrationForm
+
+from .models import User, UserProfile
+
+
+class UserForm(RegistrationForm):
+ class Meta(RegistrationForm.Meta):
+ model = User
+
+
+class ProfileForm(forms.ModelForm):
+ class Meta:
+ model = UserProfile
+ fields = '__all__'
+
+ def __init__(self, *args, **kwargs):
+ self.user = kwargs.pop("user", None)
+ super(ProfileForm, self).__init__(*args, **kwargs)
diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py
new file mode 100644
index 0000000..0e2775e
--- /dev/null
+++ b/apps/accounts/migrations/0001_initial.py
@@ -0,0 +1,55 @@
+# Generated by Django 2.1.2 on 2018-11-02 19:01
+
+from django.conf import settings
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('auth', '0009_alter_user_last_name_max_length'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('password', models.CharField(max_length=128, verbose_name='password')),
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
+ ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
+ ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
+ ('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')),
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
+ ],
+ options={
+ 'ordering': ['-date_joined'],
+ },
+ managers=[
+ ('objects', django.contrib.auth.models.UserManager()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='UserProfile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('photo', models.ImageField(blank=True, null=True, upload_to='profile')),
+ ('website', models.CharField(blank=True, default='', max_length=300, null=True)),
+ ('location', models.CharField(blank=True, default='', max_length=300, null=True)),
+ ('bio', models.TextField(blank=True, default='', null=True)),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/apps/accounts/migrations/__init__.py b/apps/accounts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/accounts/migrations/__init__.py
diff --git a/apps/accounts/models.py b/apps/accounts/models.py
new file mode 100644
index 0000000..a930081
--- /dev/null
+++ b/apps/accounts/models.py
@@ -0,0 +1,20 @@
+from django.db import models
+from django.contrib.auth.models import AbstractUser
+
+
+class User(AbstractUser):
+ pass
+
+ class Meta:
+ ordering = ['-date_joined']
+
+
+class UserProfile(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
+ photo = models.ImageField(upload_to='profile', null=True, blank=True)
+ website = models.CharField(max_length=300, null=True, blank=True, default='')
+ location = models.CharField(max_length=300, null=True, blank=True, default='')
+ bio = models.TextField(null=True, blank=True, default='')
+
+ def __str__(self):
+ return self.user.username
diff --git a/apps/accounts/signals.py b/apps/accounts/signals.py
new file mode 100644
index 0000000..837a7ed
--- /dev/null
+++ b/apps/accounts/signals.py
@@ -0,0 +1,12 @@
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+
+from .models import User, UserProfile
+
+
+@receiver(post_save, sender=User)
+def create_profile(sender, update_fields, created, instance, **kwargs):
+ """ creates a blank profile when a new user signs up """
+ if created:
+ user_profile = UserProfile.objects.create(user=instance)
+ user_profile.save()
diff --git a/apps/accounts/tests/__init__.py b/apps/accounts/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/accounts/tests/__init__.py
diff --git a/apps/accounts/tests/test_models.py b/apps/accounts/tests/test_models.py
new file mode 100644
index 0000000..f9b33f6
--- /dev/null
+++ b/apps/accounts/tests/test_models.py
@@ -0,0 +1,13 @@
+from django.test import TestCase
+from mixer.backend.django import mixer
+
+from accounts.models import User, UserProfile
+
+
+class UserProfileModelTest(TestCase):
+
+ def test_string_representation(self):
+ user = mixer.blend(User, username='test')
+ user.save()
+ profile = UserProfile.objects.get(user=user)
+ self.assertEqual(str(profile), str(user.username))
diff --git a/apps/accounts/tests/test_views.py b/apps/accounts/tests/test_views.py
new file mode 100644
index 0000000..39dcb31
--- /dev/null
+++ b/apps/accounts/tests/test_views.py
@@ -0,0 +1,23 @@
+from django.test import Client
+from django.test import RequestFactory, TestCase
+from mixer.backend.django import mixer
+
+from accounts.models import User
+from accounts.views import ProfileView
+
+
+class ProfileViewTest(TestCase):
+ def setUp(self):
+ # Every test needs access to the request factory.
+ self.factory = RequestFactory()
+ self.user = mixer.blend(User, username='tpynchon', password="gravity")
+
+ def test_profile_view(self):
+ request = self.factory.get('/settings/')
+ request.user = self.user
+ response = ProfileView.as_view()(request)
+ self.assertEqual(response.status_code, 200)
+ response.render()
+ html = response.content.decode('utf8')
+ self.assertTrue(html.startswith('<!DOCTYPE html>'))
+ self.assertIn('<h1>Account Settings</h1>', html)
diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py
new file mode 100644
index 0000000..d9ee327
--- /dev/null
+++ b/apps/accounts/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path
+
+from . import views
+
+
+urlpatterns = [
+ path(
+ r'',
+ views.SettingsListView.as_view(),
+ name="settings"
+ ),
+]
diff --git a/apps/accounts/views.py b/apps/accounts/views.py
new file mode 100644
index 0000000..e51c60e
--- /dev/null
+++ b/apps/accounts/views.py
@@ -0,0 +1,32 @@
+from django.views.generic import UpdateView, DetailView
+from django.utils.decorators import method_decorator
+from django.contrib.auth.decorators import login_required
+
+from .models import UserProfile
+from .forms import ProfileForm
+
+
+@method_decorator(login_required, name='dispatch')
+class UpdateViewWithUser(UpdateView):
+
+ def get_form_kwargs(self, **kwargs):
+ kwargs = super().get_form_kwargs(**kwargs)
+ kwargs.update({'user': self.request.user})
+ return kwargs
+
+
+class ProfileView(UpdateViewWithUser):
+ model = UserProfile
+ form_class = ProfileForm
+ template_name = "accounts/profile.html"
+
+ def get_object(self):
+ return self.request.user.userprofile
+
+
+class SettingsListView(DetailView):
+ model = UserProfile
+ template_name = "accounts/profile.html"
+
+ def get_object(self):
+ return self.request.user.userprofile
diff --git a/apps/notes/__init__.py b/apps/notes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/notes/__init__.py
diff --git a/apps/notes/forms.py b/apps/notes/forms.py
new file mode 100644
index 0000000..6a27ec9
--- /dev/null
+++ b/apps/notes/forms.py
@@ -0,0 +1,17 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from .models import Note
+
+
+class NoteForm(forms.ModelForm):
+ class Meta:
+ model = Note
+ fields = ['title', 'body_markdown', 'url', 'tags']
+ labels = {
+ "body_markdown": _("Note"),
+ }
+
+ def __init__(self, *args, **kwargs):
+ self.user = kwargs.pop("user", None)
+ super(NoteForm, self).__init__(*args, **kwargs)
diff --git a/apps/notes/migrations/0001_initial.py b/apps/notes/migrations/0001_initial.py
new file mode 100644
index 0000000..3f04eb7
--- /dev/null
+++ b/apps/notes/migrations/0001_initial.py
@@ -0,0 +1,38 @@
+# Generated by Django 2.1.2 on 2018-11-11 18:01
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import taggit.managers
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('taggit', '0002_auto_20150616_2121'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Folder',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=250)),
+ ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Note',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=250)),
+ ('body_markdown', models.TextField(null=True)),
+ ('url', models.CharField(max_length=250)),
+ ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('tags', taggit.managers.TaggableManager(blank=True, help_text='Tags', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
+ ],
+ ),
+ ]
diff --git a/apps/notes/migrations/0002_auto_20181111_1825.py b/apps/notes/migrations/0002_auto_20181111_1825.py
new file mode 100644
index 0000000..a7bc1c7
--- /dev/null
+++ b/apps/notes/migrations/0002_auto_20181111_1825.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.1.2 on 2018-11-11 18:25
+
+import datetime
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('notes', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='note',
+ name='date_created',
+ field=models.DateTimeField(blank=True, default=datetime.datetime(2018, 11, 11, 18, 25, 29, 645561), editable=False),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='note',
+ name='date_updated',
+ field=models.DateTimeField(blank=True, default=datetime.datetime(2018, 11, 11, 18, 25, 42, 867666), editable=False),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='note',
+ name='slug',
+ field=models.SlugField(blank=True, unique=True),
+ ),
+ ]
diff --git a/apps/notes/migrations/0003_note_folder.py b/apps/notes/migrations/0003_note_folder.py
new file mode 100644
index 0000000..d548429
--- /dev/null
+++ b/apps/notes/migrations/0003_note_folder.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.1.2 on 2018-11-14 15:41
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('notes', '0002_auto_20181111_1825'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='note',
+ name='folder',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='notes.Folder'),
+ ),
+ ]
diff --git a/apps/notes/migrations/__init__.py b/apps/notes/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/notes/migrations/__init__.py
diff --git a/apps/notes/models.py b/apps/notes/models.py
new file mode 100644
index 0000000..aeb3bb3
--- /dev/null
+++ b/apps/notes/models.py
@@ -0,0 +1,38 @@
+from django.db import models
+from django.utils import timezone
+from django.template.defaultfilters import slugify
+
+from taggit.managers import TaggableManager
+
+from django.conf import settings
+
+
+class Folder(models.Model):
+ created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
+ name = models.CharField(max_length=250)
+
+ def __str__(self):
+ return self.name
+
+
+class Note(models.Model):
+ created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
+ date_created = models.DateTimeField(blank=True, editable=False)
+ date_updated = models.DateTimeField(blank=True, editable=False)
+ title = models.CharField(max_length=250)
+ body_markdown = models.TextField(null=True)
+ url = models.CharField(max_length=250)
+ slug = models.SlugField(unique=True, blank=True)
+ folder = models.ForeignKey(Folder, null=True, on_delete=models.SET_NULL)
+ tags = TaggableManager(blank=True, help_text='Tags')
+
+ def __str__(self):
+ return self.title
+
+ def save(self, **kwargs):
+ # On save, update timestamps (users updated through admin.py)
+ if not self.id:
+ self.date_created = timezone.now()
+ self.slug = slugify(self.title)[:50]
+ self.date_updated = timezone.now()
+ super(Note, self).save()
diff --git a/apps/notes/serializers.py b/apps/notes/serializers.py
new file mode 100644
index 0000000..d6c7392
--- /dev/null
+++ b/apps/notes/serializers.py
@@ -0,0 +1,18 @@
+from rest_framework import serializers
+from taggit_serializer.serializers import TagListSerializerField, TaggitSerializer
+
+from .models import Note, Folder
+
+
+class NoteSerializer(TaggitSerializer, serializers.HyperlinkedModelSerializer):
+ tags = TagListSerializerField()
+
+ class Meta:
+ model = Note
+ fields = ('title', 'body_markdown', 'url', 'folder', 'tags', 'date_created')
+
+
+class FolderSerializer(serializers.HyperlinkedModelSerializer):
+ class Meta:
+ model = Folder
+ fields = ('name')
diff --git a/apps/notes/tests/__init__.py b/apps/notes/tests/__init__.py
new file mode 100644
index 0000000..1d7ccb7
--- /dev/null
+++ b/apps/notes/tests/__init__.py
@@ -0,0 +1 @@
+from notes.tests import test_models, test_views
diff --git a/apps/notes/tests/test_models.py b/apps/notes/tests/test_models.py
new file mode 100644
index 0000000..e6cdb6a
--- /dev/null
+++ b/apps/notes/tests/test_models.py
@@ -0,0 +1,30 @@
+from django.test import TestCase
+from mixer.backend.django import mixer
+
+from notes.models import Note, Folder
+from accounts.models import User
+
+
+class FolderModelTest(TestCase):
+
+ def test_string_representation(self):
+ user = mixer.blend(User, username='tpynchon')
+ folder = Folder(created_by=user, name="My Folder")
+ self.assertEqual(str(folder), "My Folder")
+
+
+class NoteModelTest(TestCase):
+
+ def test_string_representation(self):
+ user = mixer.blend(User, username='test')
+ note = Note(
+ created_by=user,
+ title="test note",
+ body_markdown="the body of the note",
+ url="https://luxagraf.net/",
+ tags="mine,cool site"
+ )
+ self.assertEqual(str(note), "test note")
+ self.assertEqual(str(note.body_markdown), "the body of the note")
+ self.assertEqual(str(note.url), "https://luxagraf.net/")
+ self.assertEqual(str(note.tags), "mine,cool site")
diff --git a/apps/notes/tests/test_views.py b/apps/notes/tests/test_views.py
new file mode 100644
index 0000000..3f21b0b
--- /dev/null
+++ b/apps/notes/tests/test_views.py
@@ -0,0 +1,57 @@
+import json
+from django.test import Client
+from django.test import RequestFactory, TestCase
+
+from rest_framework.test import force_authenticate
+from rest_framework.test import APIRequestFactory
+from mixer.backend.django import mixer
+
+from accounts.models import User
+from notes.models import Note
+from notes.views import NoteListView, NoteViewSet
+
+
+class StoriesViewTest(TestCase):
+ def setUp(self):
+ # Every test needs access to the request factory.
+ self.factory = RequestFactory()
+ # test API with rest framework request factory.
+ self.apifactory = APIRequestFactory()
+ self.user = mixer.blend(User, username='tpynchon', password="gravity")
+ self.note = Note.objects.create(
+ created_by=self.user,
+ title="test note",
+ body_markdown="the body of the note",
+ url="https://luxagraf.net/",
+ )
+ self.note.tags.add("mine,cool site")
+ self.note.save()
+
+ def test_list_view(self):
+ request = self.factory.get('/%s/notes/' % (self.user.username))
+ request.user = self.user
+ response = NoteListView.as_view()(request)
+ self.assertEqual(response.status_code, 200)
+ response.render()
+
+ def test_api_list(self):
+ # Make an authenticated request to the view...
+ request = self.factory.get('/api/notes/')
+ force_authenticate(request, user=self.user)
+ response = NoteViewSet.as_view({'get': 'list'})(request)
+ self.assertEqual(response.status_code, 200)
+ response.render()
+ api_data = json.loads(response.content.decode('utf8'))[0]
+ self.assertEqual(api_data['title'], 'test note')
+ self.assertEqual(api_data['tags'], ['mine,cool site'])
+
+ def test_api_(self):
+ # Make an authenticated request to the view...
+ request = self.factory.get('/api/notes/')
+ force_authenticate(request, user=self.user)
+ response = NoteViewSet.as_view({'get': 'list'})(request)
+ self.assertEqual(response.status_code, 200)
+ response.render()
+ api_data = json.loads(response.content.decode('utf8'))[0]
+ self.assertEqual(api_data['title'], 'test note')
+ self.assertEqual(api_data['tags'], ['mine,cool site'])
diff --git a/apps/notes/urls.py b/apps/notes/urls.py
new file mode 100644
index 0000000..76bdeb1
--- /dev/null
+++ b/apps/notes/urls.py
@@ -0,0 +1,18 @@
+from django.urls import path
+
+from . import views
+
+app_name = "notes"
+
+urlpatterns = [
+ path(
+ r'create/',
+ views.NoteCreateView.as_view(),
+ name="note-create"
+ ),
+ path(
+ r'',
+ views.NoteListView.as_view(),
+ name="note"
+ ),
+]
diff --git a/apps/notes/views.py b/apps/notes/views.py
new file mode 100644
index 0000000..ddb72ed
--- /dev/null
+++ b/apps/notes/views.py
@@ -0,0 +1,41 @@
+from django.views.generic import CreateView, ListView, UpdateView, DeleteView
+from django.views.generic.detail import DetailView
+from django.utils.decorators import method_decorator
+from django.contrib.auth.decorators import login_required
+
+from rest_framework import viewsets
+from .serializers import NoteSerializer, FolderSerializer
+from .models import Note
+from .forms import NoteForm
+
+
+@method_decorator(login_required, name='dispatch')
+class LoggedInCreateViewWithUser(CreateView):
+
+ def get_form_kwargs(self, **kwargs):
+ kwargs = super().get_form_kwargs(**kwargs)
+ kwargs.update({'user': self.request.user})
+ return kwargs
+
+
+class NoteListView(ListView):
+ model = Note
+
+ def get_queryset(self):
+ return Note.objects.filter(created_by=self.request.user)
+
+
+class NoteCreateView(LoggedInCreateViewWithUser):
+ model = Note
+ form_class = NoteForm
+ template_name = "notes/create.html"
+
+
+class NoteViewSet(viewsets.ModelViewSet):
+ """
+ API endpoint that allows users to be viewed or edited.
+ """
+ serializer_class = NoteSerializer
+
+ def get_queryset(self):
+ return Note.objects.filter(created_by=self.request.user).order_by('-date_created')
diff --git a/apps/pages/__init__.py b/apps/pages/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/pages/__init__.py
diff --git a/apps/pages/admin.py b/apps/pages/admin.py
new file mode 100644
index 0000000..706d690
--- /dev/null
+++ b/apps/pages/admin.py
@@ -0,0 +1,32 @@
+from django.contrib import admin
+
+from django import forms
+
+from pages.models import Page
+
+
+class PageEntryForm(forms.ModelForm):
+ class Meta:
+ model = Page
+ fields = '__all__'
+ widgets = {
+ 'body_markdown': forms.Textarea(attrs={'rows': 50, 'cols': 100}),
+ }
+
+
+@admin.register(Page)
+class PageAdmin(admin.ModelAdmin):
+ form = PageEntryForm
+ list_display = ('title', 'slug', 'path')
+ search_fields = ['title', 'body_markdown']
+ prepopulated_fields = {"slug": ('title',)}
+ fieldsets = (
+ ('Page', {
+ 'fields': ('title', 'body_markdown', ('slug', 'path', )),
+ 'classes': ('show', 'extrapretty', 'wide')
+ }),
+ ('Metadata', {
+ 'classes': ('collapse closed',),
+ 'fields': ('meta_description',),
+ })
+ )
diff --git a/apps/pages/migrations/0001_initial.py b/apps/pages/migrations/0001_initial.py
new file mode 100644
index 0000000..49e763f
--- /dev/null
+++ b/apps/pages/migrations/0001_initial.py
@@ -0,0 +1,26 @@
+# Generated by Django 2.1.2 on 2018-11-11 21:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Page',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=200)),
+ ('slug', models.SlugField()),
+ ('body_html', models.TextField(blank=True)),
+ ('body_markdown', models.TextField()),
+ ('meta_description', models.CharField(blank=True, max_length=256, null=True)),
+ ('path', models.CharField(blank=True, max_length=200, null=True)),
+ ],
+ ),
+ ]
diff --git a/apps/pages/migrations/__init__.py b/apps/pages/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/pages/migrations/__init__.py
diff --git a/apps/pages/models.py b/apps/pages/models.py
new file mode 100644
index 0000000..0c663cb
--- /dev/null
+++ b/apps/pages/models.py
@@ -0,0 +1,41 @@
+from django.db import models
+from django.template.defaultfilters import slugify
+from django.contrib.sitemaps import Sitemap
+
+from utils.util import markdown_to_html, render_images
+
+
+class Page(models.Model):
+ title = models.CharField(max_length=200)
+ slug = models.SlugField()
+ body_html = models.TextField(blank=True)
+ body_markdown = models.TextField()
+ meta_description = models.CharField(max_length=256, null=True, blank=True)
+ path = models.CharField(max_length=200, null=True, blank=True)
+
+ def __str__(self):
+ return self.title
+
+ def get_absolute_url(self):
+ if self.path:
+ return "/%s/%s" % (self.path, self.slug)
+ else:
+ return "/%s" % (self.slug)
+
+ def save(self):
+ # run markdown
+ md = render_images(self.body_markdown)
+ self.body_html = markdown_to_html(md)
+ if not self.id:
+ # self.date_created = timezone.now()
+ self.slug = slugify(self.title)[:50]
+ super(Page, self).save()
+
+
+class PageSitemap(Sitemap):
+ changefreq = "never"
+ priority = 1.0
+ protocol = "https"
+
+ def items(self):
+ return Page.objects.all()
diff --git a/apps/pages/tests/__init__.py b/apps/pages/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/pages/tests/__init__.py
diff --git a/apps/pages/tests/test_models.py b/apps/pages/tests/test_models.py
new file mode 100644
index 0000000..f8de5c7
--- /dev/null
+++ b/apps/pages/tests/test_models.py
@@ -0,0 +1,20 @@
+from django.test import TestCase
+
+from pages.models import Page
+
+
+class PageModelTest(TestCase):
+
+ def test_string_representation(self):
+ page = Page(
+ title="Test Page",
+ meta_description="The meta desc",
+ body_markdown="the body of the page",
+ )
+ page.save()
+ self.assertEqual(str(page), "Test Page")
+ self.assertEqual(str(page.slug), "test-page")
+ self.assertEqual(str(page.body_markdown), "the body of the page")
+ self.assertEqual(str(page.body_html), "<p>the body of the page</p>")
+ self.assertEqual(str(page.meta_description), "The meta desc")
+ self.assertEqual(page.path, None)
diff --git a/apps/pages/tests/test_views.py b/apps/pages/tests/test_views.py
new file mode 100644
index 0000000..42a91e4
--- /dev/null
+++ b/apps/pages/tests/test_views.py
@@ -0,0 +1,29 @@
+from django.test import RequestFactory, TestCase
+from mixer.backend.django import mixer
+
+from accounts.models import User
+from pages.models import Page
+from pages.views import PageDetailView
+
+
+class PageViewTest(TestCase):
+ def setUp(self):
+ # Every test needs access to the request factory.
+ self.factory = RequestFactory()
+ self.page = Page(
+ title="Test Page",
+ meta_description="The meta desc",
+ body_markdown="the body of the page",
+ )
+ self.page.save()
+
+ def test_non_existent_page(self):
+ """A non-existent staticflatpage raises a 404."""
+ response = self.client.get('/no_such_page/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_detail_view(self):
+ response = self.client.get(self.page.get_absolute_url())
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
+ print(response.content)
diff --git a/apps/pages/views.py b/apps/pages/views.py
new file mode 100644
index 0000000..617ce04
--- /dev/null
+++ b/apps/pages/views.py
@@ -0,0 +1,11 @@
+from django.views.generic.detail import DetailView
+from pages.models import Page
+
+
+class PageDetailView(DetailView):
+ model = Page
+ slug_field = "slug"
+
+ def get_template_names(self):
+ obj = self.get_object()
+ return ["pages/%s.html" % obj.slug, 'pages/page.html']
diff --git a/apps/utils/__init__.py b/apps/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/utils/__init__.py
diff --git a/apps/utils/next_prev.py b/apps/utils/next_prev.py
new file mode 100644
index 0000000..766add1
--- /dev/null
+++ b/apps/utils/next_prev.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# from https://github.com/gregplaysguitar/django-next-prev/blob/master/next_prev.py
+
+from functools import partial
+
+from django.db import models
+
+if not locals().get('reduce'):
+ from functools import reduce
+
+__version__ = '1.0.1'
+VERSION = tuple(map(int, __version__.split('.')))
+
+
+def get_model_attr(instance, attr):
+ """Example usage: get_model_attr(instance, 'category__slug')"""
+ for field in attr.split('__'):
+ instance = getattr(instance, field)
+ return instance
+
+
+def next_or_prev_in_order(instance, qs=None, prev=False, loop=False):
+ """Get the next (or previous with prev=True) item for instance, from the
+ given queryset (which is assumed to contain instance) respecting
+ queryset ordering. If loop is True, return the first/last item when the
+ end/start is reached. """
+
+ if not qs:
+ qs = instance.__class__.objects.all()
+
+ if prev:
+ qs = qs.reverse()
+ lookup = 'lt'
+ else:
+ lookup = 'gt'
+
+ q_list = []
+ prev_fields = []
+
+ if qs.query.extra_order_by:
+ ordering = qs.query.extra_order_by
+ elif qs.query.order_by:
+ ordering = qs.query.order_by
+ elif qs.query.get_meta().ordering:
+ ordering = qs.query.get_meta().ordering
+ else:
+ ordering = []
+
+ ordering = list(ordering)
+
+ # if the ordering doesn't contain pk, append it and reorder the queryset
+ # to ensure consistency
+ if 'pk' not in ordering and '-pk' not in ordering:
+ ordering.append('pk')
+ qs = qs.order_by(*ordering)
+
+ for field in ordering:
+ if field[0] == '-':
+ this_lookup = (lookup == 'gt' and 'lt' or 'gt')
+ field = field[1:]
+ else:
+ this_lookup = lookup
+ q_kwargs = dict([(f, get_model_attr(instance, f))
+ for f in prev_fields])
+ key = "%s__%s" % (field, this_lookup)
+ q_kwargs[key] = get_model_attr(instance, field)
+ q_list.append(models.Q(**q_kwargs))
+ prev_fields.append(field)
+ try:
+ return qs.filter(reduce(models.Q.__or__, q_list))[0]
+ except IndexError:
+ length = qs.count()
+ if loop and length > 1:
+ # queryset is reversed above if prev
+ return qs[0]
+ return None
+
+
+next_in_order = partial(next_or_prev_in_order, prev=False)
+prev_in_order = partial(next_or_prev_in_order, prev=True)
diff --git a/apps/utils/static/autocomplete.js b/apps/utils/static/autocomplete.js
new file mode 100644
index 0000000..ad0c70d
--- /dev/null
+++ b/apps/utils/static/autocomplete.js
@@ -0,0 +1,10 @@
+function autoCompleteItems() {
+var item = document.getElementById('id_ap');
+var singlePresetOpts = new Choices(item, {
+ searchPlaceholderValue: 'Search for Animal',
+ placeholder: true,
+});
+}
+document.addEventListener("DOMContentLoaded", function(event) {
+ autoCompleteItems();
+});
diff --git a/apps/utils/static/choices.css b/apps/utils/static/choices.css
new file mode 100644
index 0000000..2c1e7f1
--- /dev/null
+++ b/apps/utils/static/choices.css
@@ -0,0 +1,2 @@
+.choices{position:relative;margin-bottom:24px;font-size:16px; min-width:400px;}
+.choices:focus{outline:none}.choices:last-child{margin-bottom:0}.choices.is-disabled .choices__inner,.choices.is-disabled .choices__input{background-color:#eaeaea;cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.choices.is-disabled .choices__item{cursor:not-allowed}.choices[data-type*=select-one]{cursor:pointer}.choices[data-type*=select-one] .choices__inner{padding-bottom:7.5px}.choices[data-type*=select-one] .choices__input{display:block;width:100%;padding:10px;border-bottom:1px solid #ddd;background-color:#fff;margin:0}.choices[data-type*=select-one] .choices__button{background-image:url(../../icons/cross-inverse.svg);padding:0;background-size:8px;position:absolute;top:50%;right:0;margin-top:-10px;margin-right:25px;height:20px;width:20px;border-radius:10em;opacity:.5}.choices[data-type*=select-one] .choices__button:focus,.choices[data-type*=select-one] .choices__button:hover{opacity:1}.choices[data-type*=select-one] .choices__button:focus{box-shadow:0 0 0 2px #00bcd4}.choices[data-type*=select-one]:after{content:"";height:0;width:0;border-style:solid;border-color:#333 transparent transparent transparent;border-width:5px;position:absolute;right:11.5px;top:50%;margin-top:-2.5px;pointer-events:none}.choices[data-type*=select-one].is-open:after{border-color:transparent transparent #333 transparent;margin-top:-7.5px}.choices[data-type*=select-one][dir=rtl]:after{left:11.5px;right:auto}.choices[data-type*=select-one][dir=rtl] .choices__button{right:auto;left:0;margin-left:25px;margin-right:0}.choices[data-type*=select-multiple] .choices__inner,.choices[data-type*=text] .choices__inner{cursor:text}.choices[data-type*=select-multiple] .choices__button,.choices[data-type*=text] .choices__button{position:relative;display:inline-block;margin:0 -4px 0 8px;padding-left:16px;border-left:1px solid #008fa1;background-image:url(../../icons/cross.svg);background-size:8px;width:8px;line-height:1;opacity:.75}.choices[data-type*=select-multiple] .choices__button:focus,.choices[data-type*=select-multiple] .choices__button:hover,.choices[data-type*=text] .choices__button:focus,.choices[data-type*=text] .choices__button:hover{opacity:1}.choices__inner{display:inline-block;vertical-align:top;width:100%;background-color:#f9f9f9;padding:7.5px 7.5px 3.75px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;min-height:44px;overflow:hidden}.is-focused .choices__inner,.is-open .choices__inner{border-color:#b7b7b7}.is-open .choices__inner{border-radius:2.5px 2.5px 0 0}.is-flipped.is-open .choices__inner{border-radius:0 0 2.5px 2.5px}.choices__list{margin:0;padding-left:0;list-style:none}.choices__list--single{display:inline-block;padding:4px 16px 4px 4px;width:100%}[dir=rtl] .choices__list--single{padding-right:4px;padding-left:16px}.choices__list--single .choices__item{width:100%}.choices__list--multiple{display:inline}.choices__list--multiple .choices__item{display:inline-block;vertical-align:middle;border-radius:20px;padding:4px 10px;font-size:12px;font-weight:500;margin-right:3.75px;margin-bottom:3.75px;background-color:#00bcd4;border:1px solid #00a5bb;color:#fff;word-break:break-all}.choices__list--multiple .choices__item[data-deletable]{padding-right:5px}[dir=rtl] .choices__list--multiple .choices__item{margin-right:0;margin-left:3.75px}.choices__list--multiple .choices__item.is-highlighted{background-color:#00a5bb;border:1px solid #008fa1}.is-disabled .choices__list--multiple .choices__item{background-color:#aaa;border:1px solid #919191}.choices__list--dropdown{display:none;z-index:1;position:absolute;width:100%;background-color:#fff;border:1px solid #ddd;top:100%;margin-top:-1px;border-bottom-left-radius:2.5px;border-bottom-right-radius:2.5px;overflow:hidden;word-break:break-all}.choices__list--dropdown.is-active{display:block}.is-open .choices__list--dropdown{border-color:#b7b7b7}.is-flipped .choices__list--dropdown{top:auto;bottom:100%;margin-top:0;margin-bottom:-1px;border-radius:.25rem .25rem 0 0}.choices__list--dropdown .choices__list{position:relative;max-height:300px;overflow:auto;-webkit-overflow-scrolling:touch;will-change:scroll-position}.choices__list--dropdown .choices__item{position:relative;padding:10px;font-size:14px}[dir=rtl] .choices__list--dropdown .choices__item{text-align:right}@media (min-width:640px){.choices__list--dropdown .choices__item--selectable{padding-right:100px}.choices__list--dropdown .choices__item--selectable:after{content:attr(data-select-text);font-size:12px;opacity:0;position:absolute;right:10px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}[dir=rtl] .choices__list--dropdown .choices__item--selectable{text-align:right;padding-left:100px;padding-right:10px}[dir=rtl] .choices__list--dropdown .choices__item--selectable:after{right:auto;left:10px}}.choices__list--dropdown .choices__item--selectable.is-highlighted{background-color:#f2f2f2}.choices__list--dropdown .choices__item--selectable.is-highlighted:after{opacity:.5}.choices__item{cursor:default}.choices__item--selectable{cursor:pointer}.choices__item--disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:.5}.choices__heading{font-weight:600;font-size:12px;padding:10px;border-bottom:1px solid #f7f7f7;color:gray}.choices__button{text-indent:-9999px;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;background-color:transparent;background-repeat:no-repeat;background-position:center;cursor:pointer}.choices__button:focus{outline:none}.choices__input{display:inline-block;vertical-align:baseline;background-color:#f9f9f9;font-size:14px;margin-bottom:5px;border:0;border-radius:0;max-width:100%;padding:4px 0 4px 2px}.choices__input:focus{outline:0}[dir=rtl] .choices__input{padding-right:2px;padding-left:0}.choices__placeholder{opacity:.5}
diff --git a/apps/utils/static/choices.min.js b/apps/utils/static/choices.min.js
new file mode 100644
index 0000000..197f18e
--- /dev/null
+++ b/apps/utils/static/choices.min.js
@@ -0,0 +1,5 @@
+/*! choices.js v3.0.3 | (c) 2018 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Choices=t():e.Choices=t()}(this,function(){return function(e){function t(n){if(i[n])return i[n].exports;var s=i[n]={exports:{},id:n,loaded:!1};return e[n].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var i={};return t.m=e,t.c=i,t.p="/assets/scripts/dist/",t(0)}([function(e,t,i){e.exports=i(1)},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function s(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function o(e){if(Array.isArray(e)){for(var t=0,i=Array(e.length);t<e.length;t++)i[t]=e[t];return i}return Array.from(e)}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var a=function(){function e(e,t){for(var i=0;i<t.length;i++){var n=t[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,i,n){return i&&e(t.prototype,i),n&&e(t,n),t}}(),c=i(2),l=n(c),h=i(3),u=n(h),d=i(4),f=n(d),p=i(31),v=i(32);i(33);var m=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"[data-choice]",i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(r(this,e),(0,v.isType)("String",t)){var n=document.querySelectorAll(t);if(n.length>1)for(var s=1;s<n.length;s++){var o=n[s];new e(o,i)}}var a={silent:!1,items:[],choices:[],renderChoiceLimit:-1,maxItemCount:-1,addItems:!0,removeItems:!0,removeItemButton:!1,editItems:!1,duplicateItems:!0,delimiter:",",paste:!0,searchEnabled:!0,searchChoices:!0,searchFloor:1,searchResultLimit:4,searchFields:["label","value"],position:"auto",resetScrollPosition:!0,regexFilter:null,shouldSort:!0,shouldSortItems:!1,sortFilter:v.sortByAlpha,placeholder:!0,placeholderValue:null,searchPlaceholderValue:null,prependValue:null,appendValue:null,renderSelectedChoices:"auto",loadingText:"Loading...",noResultsText:"No results found",noChoicesText:"No choices to choose from",itemSelectText:"Press to select",addItemText:function(e){return'Press Enter to add <b>"'+e+'"</b>'},maxItemText:function(e){return"Only "+e+" values can be added."},uniqueItemText:"Only unique values can be added.",classNames:{containerOuter:"choices",containerInner:"choices__inner",input:"choices__input",inputCloned:"choices__input--cloned",list:"choices__list",listItems:"choices__list--multiple",listSingle:"choices__list--single",listDropdown:"choices__list--dropdown",item:"choices__item",itemSelectable:"choices__item--selectable",itemDisabled:"choices__item--disabled",itemChoice:"choices__item--choice",placeholder:"choices__placeholder",group:"choices__group",groupHeading:"choices__heading",button:"choices__button",activeState:"is-active",focusState:"is-focused",openState:"is-open",disabledState:"is-disabled",highlightedState:"is-highlighted",hiddenState:"is-hidden",flippedState:"is-flipped",loadingState:"is-loading",noResults:"has-no-results",noChoices:"has-no-choices"},fuseOptions:{include:"score"},callbackOnInit:null,callbackOnCreateTemplates:null};if(this.idNames={itemChoice:"item-choice"},this.config=(0,v.extend)(a,i),"auto"!==this.config.renderSelectedChoices&&"always"!==this.config.renderSelectedChoices&&(this.config.silent||console.warn("renderSelectedChoices: Possible values are 'auto' and 'always'. Falling back to 'auto'."),this.config.renderSelectedChoices="auto"),this.store=new f.default(this.render),this.initialised=!1,this.currentState={},this.prevState={},this.currentValue="",this.element=t,this.passedElement=(0,v.isType)("String",t)?document.querySelector(t):t,!this.passedElement)return void(this.config.silent||console.error("Passed element not found"));this.isTextElement="text"===this.passedElement.type,this.isSelectOneElement="select-one"===this.passedElement.type,this.isSelectMultipleElement="select-multiple"===this.passedElement.type,this.isSelectElement=this.isSelectOneElement||this.isSelectMultipleElement,this.isValidElementType=this.isTextElement||this.isSelectElement,this.isIe11=!(!navigator.userAgent.match(/Trident/)||!navigator.userAgent.match(/rv[ :]11/)),this.isScrollingOnIe=!1,this.config.shouldSortItems===!0&&this.isSelectOneElement&&(this.config.silent||console.warn("shouldSortElements: Type of passed element is 'select-one', falling back to false.")),this.highlightPosition=0,this.canSearch=this.config.searchEnabled,this.placeholder=!1,this.isSelectOneElement||(this.placeholder=!!this.config.placeholder&&(this.config.placeholderValue||this.passedElement.getAttribute("placeholder"))),this.presetChoices=this.config.choices,this.presetItems=this.config.items,this.passedElement.value&&(this.presetItems=this.presetItems.concat(this.passedElement.value.split(this.config.delimiter))),this.baseId=(0,v.generateId)(this.passedElement,"choices-"),this.render=this.render.bind(this),this._onFocus=this._onFocus.bind(this),this._onBlur=this._onBlur.bind(this),this._onKeyUp=this._onKeyUp.bind(this),this._onKeyDown=this._onKeyDown.bind(this),this._onClick=this._onClick.bind(this),this._onTouchMove=this._onTouchMove.bind(this),this._onTouchEnd=this._onTouchEnd.bind(this),this._onMouseDown=this._onMouseDown.bind(this),this._onMouseOver=this._onMouseOver.bind(this),this._onPaste=this._onPaste.bind(this),this._onInput=this._onInput.bind(this),this.wasTap=!0;var c="classList"in document.documentElement;c||this.config.silent||console.error("Choices: Your browser doesn't support Choices");var l=(0,v.isElement)(this.passedElement)&&this.isValidElementType;if(l){if("active"===this.passedElement.getAttribute("data-choice"))return;this.init()}else this.config.silent||console.error("Incompatible input passed")}return a(e,[{key:"init",value:function(){if(this.initialised!==!0){var e=this.config.callbackOnInit;this.initialised=!0,this._createTemplates(),this._createInput(),this.store.subscribe(this.render),this.render(),this._addEventListeners(),e&&(0,v.isType)("Function",e)&&e.call(this)}}},{key:"destroy",value:function(){if(this.initialised!==!1){this._removeEventListeners(),this.passedElement.classList.remove(this.config.classNames.input,this.config.classNames.hiddenState),this.passedElement.removeAttribute("tabindex");var e=this.passedElement.getAttribute("data-choice-orig-style");Boolean(e)?(this.passedElement.removeAttribute("data-choice-orig-style"),this.passedElement.setAttribute("style",e)):this.passedElement.removeAttribute("style"),this.passedElement.removeAttribute("aria-hidden"),this.passedElement.removeAttribute("data-choice"),this.passedElement.value=this.passedElement.value,this.containerOuter.parentNode.insertBefore(this.passedElement,this.containerOuter),this.containerOuter.parentNode.removeChild(this.containerOuter),this.clearStore(),this.config.templates=null,this.initialised=!1}}},{key:"renderGroups",value:function(e,t,i){var n=this,s=i||document.createDocumentFragment(),o=this.config.sortFilter;return this.config.shouldSort&&e.sort(o),e.forEach(function(e){var i=t.filter(function(t){return n.isSelectOneElement?t.groupId===e.id:t.groupId===e.id&&!t.selected});if(i.length>=1){var o=n._getTemplate("choiceGroup",e);s.appendChild(o),n.renderChoices(i,s,!0)}}),s}},{key:"renderChoices",value:function(e,t){var i=this,n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],s=t||document.createDocumentFragment(),r=this.config,a=r.renderSelectedChoices,c=r.searchResultLimit,l=r.renderChoiceLimit,h=this.isSearching?v.sortByScore:this.config.sortFilter,u=function(e){var t="auto"!==a||(i.isSelectOneElement||!e.selected);if(t){var n=i._getTemplate("choice",e);s.appendChild(n)}},d=e;"auto"!==a||this.isSelectOneElement||(d=e.filter(function(e){return!e.selected}));var f=d.reduce(function(e,t){return t.placeholder?e.placeholderChoices.push(t):e.normalChoices.push(t),e},{placeholderChoices:[],normalChoices:[]}),p=f.placeholderChoices,m=f.normalChoices;(this.config.shouldSort||this.isSearching)&&m.sort(h);var g=d.length,y=[].concat(o(p),o(m));this.isSearching?g=c:l>0&&!n&&(g=l);for(var b=0;b<g;b++)y[b]&&u(y[b]);return s}},{key:"renderItems",value:function(e){var t=this,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=i||document.createDocumentFragment();if(this.config.shouldSortItems&&!this.isSelectOneElement&&e.sort(this.config.sortFilter),this.isTextElement){var s=this.store.getItemsReducedToValues(e),o=s.join(this.config.delimiter);this.passedElement.setAttribute("value",o),this.passedElement.value=o}else{var r=document.createDocumentFragment();e.forEach(function(e){var i=t._getTemplate("option",e);r.appendChild(i)}),this.passedElement.innerHTML="",this.passedElement.appendChild(r)}return e.forEach(function(e){var i=t._getTemplate("item",e);n.appendChild(i)}),n}},{key:"render",value:function(){if(!this.store.isLoading()&&(this.currentState=this.store.getState(),this.currentState!==this.prevState)){if((this.currentState.choices!==this.prevState.choices||this.currentState.groups!==this.prevState.groups||this.currentState.items!==this.prevState.items)&&this.isSelectElement){var e=this.store.getGroupsFilteredByActive(),t=this.store.getChoicesFilteredByActive(),i=document.createDocumentFragment();this.choiceList.innerHTML="",this.config.resetScrollPosition&&(this.choiceList.scrollTop=0),e.length>=1&&this.isSearching!==!0?i=this.renderGroups(e,t,i):t.length>=1&&(i=this.renderChoices(t,i));var n=this.store.getItemsFilteredByActive(),s=this._canAddItem(n,this.input.value);if(i.childNodes&&i.childNodes.length>0)s.response?(this.choiceList.appendChild(i),this._highlightChoice()):this.choiceList.appendChild(this._getTemplate("notice",s.notice));else{var o=void 0,r=void 0;this.isSearching?(r=(0,v.isType)("Function",this.config.noResultsText)?this.config.noResultsText():this.config.noResultsText,o=this._getTemplate("notice",r,"no-results")):(r=(0,v.isType)("Function",this.config.noChoicesText)?this.config.noChoicesText():this.config.noChoicesText,o=this._getTemplate("notice",r,"no-choices")),this.choiceList.appendChild(o)}}if(this.currentState.items!==this.prevState.items){var a=this.store.getItemsFilteredByActive();if(this.itemList.innerHTML="",a&&a){var c=this.renderItems(a);c.childNodes&&this.itemList.appendChild(c)}}this.prevState=this.currentState}}},{key:"highlightItem",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(!e)return this;var i=e.id,n=e.groupId,s=n>=0?this.store.getGroupById(n):null;return this.store.dispatch((0,p.highlightItem)(i,!0)),t&&(s&&s.value?(0,v.triggerEvent)(this.passedElement,"highlightItem",{id:i,value:e.value,label:e.label,groupValue:s.value}):(0,v.triggerEvent)(this.passedElement,"highlightItem",{id:i,value:e.value,label:e.label})),this}},{key:"unhighlightItem",value:function(e){if(!e)return this;var t=e.id,i=e.groupId,n=i>=0?this.store.getGroupById(i):null;return this.store.dispatch((0,p.highlightItem)(t,!1)),n&&n.value?(0,v.triggerEvent)(this.passedElement,"unhighlightItem",{id:t,value:e.value,label:e.label,groupValue:n.value}):(0,v.triggerEvent)(this.passedElement,"unhighlightItem",{id:t,value:e.value,label:e.label}),this}},{key:"highlightAll",value:function(){var e=this,t=this.store.getItems();return t.forEach(function(t){e.highlightItem(t)}),this}},{key:"unhighlightAll",value:function(){var e=this,t=this.store.getItems();return t.forEach(function(t){e.unhighlightItem(t)}),this}},{key:"removeItemsByValue",value:function(e){var t=this;if(!e||!(0,v.isType)("String",e))return this;var i=this.store.getItemsFilteredByActive();return i.forEach(function(i){i.value===e&&t._removeItem(i)}),this}},{key:"removeActiveItems",value:function(e){var t=this,i=this.store.getItemsFilteredByActive();return i.forEach(function(i){i.active&&e!==i.id&&t._removeItem(i)}),this}},{key:"removeHighlightedItems",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],i=this.store.getItemsFilteredByActive();return i.forEach(function(i){i.highlighted&&i.active&&(e._removeItem(i),t&&e._triggerChange(i.value))}),this}},{key:"showDropdown",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=document.body,i=document.documentElement,n=Math.max(t.scrollHeight,t.offsetHeight,i.clientHeight,i.scrollHeight,i.offsetHeight);this.containerOuter.classList.add(this.config.classNames.openState),this.containerOuter.setAttribute("aria-expanded","true"),this.dropdown.classList.add(this.config.classNames.activeState),this.dropdown.setAttribute("aria-expanded","true");var s=this.dropdown.getBoundingClientRect(),o=Math.ceil(s.top+window.scrollY+this.dropdown.offsetHeight),r=!1;return"auto"===this.config.position?r=o>=n:"top"===this.config.position&&(r=!0),r&&this.containerOuter.classList.add(this.config.classNames.flippedState),e&&this.canSearch&&document.activeElement!==this.input&&this.input.focus(),(0,v.triggerEvent)(this.passedElement,"showDropdown",{}),this}},{key:"hideDropdown",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=this.containerOuter.classList.contains(this.config.classNames.flippedState);return this.containerOuter.classList.remove(this.config.classNames.openState),this.containerOuter.setAttribute("aria-expanded","false"),this.dropdown.classList.remove(this.config.classNames.activeState),this.dropdown.setAttribute("aria-expanded","false"),t&&this.containerOuter.classList.remove(this.config.classNames.flippedState),e&&this.canSearch&&document.activeElement===this.input&&this.input.blur(),(0,v.triggerEvent)(this.passedElement,"hideDropdown",{}),this}},{key:"toggleDropdown",value:function(){var e=this.dropdown.classList.contains(this.config.classNames.activeState);return e?this.hideDropdown():this.showDropdown(!0),this}},{key:"getValue",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],i=this.store.getItemsFilteredByActive(),n=[];return i.forEach(function(i){e.isTextElement?n.push(t?i.value:i):i.active&&n.push(t?i.value:i)}),this.isSelectOneElement?n[0]:n}},{key:"setValue",value:function(e){var t=this;if(this.initialised===!0){var i=[].concat(o(e)),n=function(e){var i=(0,v.getType)(e);if("Object"===i){if(!e.value)return;t.isTextElement?t._addItem(e.value,e.label,e.id,void 0,e.customProperties,e.placeholder):t._addChoice(e.value,e.label,!0,!1,-1,e.customProperties,e.placeholder)}else"String"===i&&(t.isTextElement?t._addItem(e):t._addChoice(e,e,!0,!1,-1,null))};i.length>1?i.forEach(function(e){n(e)}):n(i[0])}return this}},{key:"setValueByChoice",value:function(e){var t=this;if(!this.isTextElement){var i=this.store.getChoices(),n=(0,v.isType)("Array",e)?e:[e];n.forEach(function(e){var n=i.find(function(t){return t.value===e});n?n.selected?t.config.silent||console.warn("Attempting to select choice already selected"):t._addItem(n.value,n.label,n.id,n.groupId,n.customProperties,n.placeholder,n.keyCode):t.config.silent||console.warn("Attempting to select choice that does not exist")})}return this}},{key:"setChoices",value:function(e,t,i){var n=this,s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(this.initialised===!0&&this.isSelectElement){if(!(0,v.isType)("Array",e)||!t)return this;s&&this._clearChoices(),this._setLoading(!0),e&&e.length&&(this.containerOuter.classList.remove(this.config.classNames.loadingState),e.forEach(function(e){e.choices?n._addGroup(e,e.id||null,t,i):n._addChoice(e[t],e[i],e.selected,e.disabled,void 0,e.customProperties,e.placeholder)})),this._setLoading(!1)}return this}},{key:"clearStore",value:function(){return this.store.dispatch((0,p.clearAll)()),this}},{key:"clearInput",value:function(){return this.input.value&&(this.input.value=""),this.isSelectOneElement||this._setInputWidth(),!this.isTextElement&&this.config.searchEnabled&&(this.isSearching=!1,this.store.dispatch((0,p.activateChoices)(!0))),this}},{key:"enable",value:function(){if(this.initialised){this.passedElement.disabled=!1;var e=this.containerOuter.classList.contains(this.config.classNames.disabledState);e&&(this._addEventListeners(),this.passedElement.removeAttribute("disabled"),this.input.removeAttribute("disabled"),this.containerOuter.classList.remove(this.config.classNames.disabledState),this.containerOuter.removeAttribute("aria-disabled"),this.isSelectOneElement&&this.containerOuter.setAttribute("tabindex","0"))}return this}},{key:"disable",value:function(){if(this.initialised){this.passedElement.disabled=!0;var e=!this.containerOuter.classList.contains(this.config.classNames.disabledState);e&&(this._removeEventListeners(),this.passedElement.setAttribute("disabled",""),this.input.setAttribute("disabled",""),this.containerOuter.classList.add(this.config.classNames.disabledState),this.containerOuter.setAttribute("aria-disabled","true"),this.isSelectOneElement&&this.containerOuter.setAttribute("tabindex","-1"))}return this}},{key:"ajax",value:function(e){var t=this;return this.initialised===!0&&this.isSelectElement&&(requestAnimationFrame(function(){t._handleLoadingState(!0)}),e(this._ajaxCallback())),this}},{key:"_triggerChange",value:function(e){e&&(0,v.triggerEvent)(this.passedElement,"change",{value:e})}},{key:"_handleButtonAction",value:function(e,t){if(e&&t&&this.config.removeItems&&this.config.removeItemButton){var i=t.parentNode.getAttribute("data-id"),n=e.find(function(e){return e.id===parseInt(i,10)});this._removeItem(n),this._triggerChange(n.value),this.isSelectOneElement&&this._selectPlaceholderChoice()}}},{key:"_selectPlaceholderChoice",value:function(){var e=this.store.getPlaceholderChoice();e&&(this._addItem(e.value,e.label,e.id,e.groupId,null,e.placeholder),this._triggerChange(e.value))}},{key:"_handleItemAction",value:function(e,t){var i=this,n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(e&&t&&this.config.removeItems&&!this.isSelectOneElement){var s=t.getAttribute("data-id");e.forEach(function(e){e.id!==parseInt(s,10)||e.highlighted?n||e.highlighted&&i.unhighlightItem(e):i.highlightItem(e)}),document.activeElement!==this.input&&this.input.focus()}}},{key:"_handleChoiceAction",value:function(e,t){if(e&&t){var i=t.getAttribute("data-id"),n=this.store.getChoiceById(i),s=e[0]&&e[0].keyCode?e[0].keyCode:null,o=this.dropdown.classList.contains(this.config.classNames.activeState);if(n.keyCode=s,(0,v.triggerEvent)(this.passedElement,"choice",{choice:n}),n&&!n.selected&&!n.disabled){var r=this._canAddItem(e,n.value);r.response&&(this._addItem(n.value,n.label,n.id,n.groupId,n.customProperties,n.placeholder,n.keyCode),this._triggerChange(n.value))}this.clearInput(),o&&this.isSelectOneElement&&(this.hideDropdown(),this.containerOuter.focus())}}},{key:"_handleBackspace",value:function(e){if(this.config.removeItems&&e){var t=e[e.length-1],i=e.some(function(e){return e.highlighted});this.config.editItems&&!i&&t?(this.input.value=t.value,this._setInputWidth(),this._removeItem(t),this._triggerChange(t.value)):(i||this.highlightItem(t,!1),this.removeHighlightedItems(!0))}}},{key:"_canAddItem",value:function(e,t){var i=!0,n=(0,v.isType)("Function",this.config.addItemText)?this.config.addItemText(t):this.config.addItemText;(this.isSelectMultipleElement||this.isTextElement)&&this.config.maxItemCount>0&&this.config.maxItemCount<=e.length&&(i=!1,n=(0,v.isType)("Function",this.config.maxItemText)?this.config.maxItemText(this.config.maxItemCount):this.config.maxItemText),this.isTextElement&&this.config.addItems&&i&&this.config.regexFilter&&(i=this._regexFilter(t));var s=!e.some(function(e){return(0,v.isType)("String",t)?e.value===t.trim():e.value===t});return s||this.config.duplicateItems||this.isSelectOneElement||!i||(i=!1,n=(0,v.isType)("Function",this.config.uniqueItemText)?this.config.uniqueItemText(t):this.config.uniqueItemText),{response:i,notice:n}}},{key:"_handleLoadingState",value:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],t=this.itemList.querySelector("."+this.config.classNames.placeholder);e?(this.containerOuter.classList.add(this.config.classNames.loadingState),this.containerOuter.setAttribute("aria-busy","true"),this.isSelectOneElement?t?t.innerHTML=this.config.loadingText:(t=this._getTemplate("placeholder",this.config.loadingText),this.itemList.appendChild(t)):this.input.placeholder=this.config.loadingText):(this.containerOuter.classList.remove(this.config.classNames.loadingState),this.isSelectOneElement?t.innerHTML=this.placeholder||"":this.input.placeholder=this.placeholder||"")}},{key:"_ajaxCallback",value:function(){var e=this;return function(t,i,n){if(t&&i){var s=(0,v.isType)("Object",t)?[t]:t;s&&(0,v.isType)("Array",s)&&s.length?(e._handleLoadingState(!1),e._setLoading(!0),s.forEach(function(t){if(t.choices){var s=t.id||null;e._addGroup(t,s,i,n)}else e._addChoice(t[i],t[n],t.selected,t.disabled,void 0,t.customProperties,t.placeholder)}),e._setLoading(!1),e.isSelectOneElement&&e._selectPlaceholderChoice()):e._handleLoadingState(!1),e.containerOuter.removeAttribute("aria-busy")}}}},{key:"_searchChoices",value:function(e){var t=(0,v.isType)("String",e)?e.trim():e,i=(0,v.isType)("String",this.currentValue)?this.currentValue.trim():this.currentValue;if(t.length>=1&&t!==i+" "){var n=this.store.getSearchableChoices(),s=t,o=(0,v.isType)("Array",this.config.searchFields)?this.config.searchFields:[this.config.searchFields],r=Object.assign(this.config.fuseOptions,{keys:o}),a=new l.default(n,r),c=a.search(s);return this.currentValue=t,this.highlightPosition=0,this.isSearching=!0,this.store.dispatch((0,p.filterChoices)(c)),c.length}return 0}},{key:"_handleSearch",value:function(e){if(e){var t=this.store.getChoices(),i=t.some(function(e){return!e.active});if(this.input===document.activeElement)if(e&&e.length>=this.config.searchFloor){var n=0;this.config.searchChoices&&(n=this._searchChoices(e)),(0,v.triggerEvent)(this.passedElement,"search",{value:e,resultCount:n})}else i&&(this.isSearching=!1,this.store.dispatch((0,p.activateChoices)(!0)))}}},{key:"_addEventListeners",value:function(){document.addEventListener("keyup",this._onKeyUp),document.addEventListener("keydown",this._onKeyDown),document.addEventListener("click",this._onClick),document.addEventListener("touchmove",this._onTouchMove),document.addEventListener("touchend",this._onTouchEnd),document.addEventListener("mousedown",this._onMouseDown),document.addEventListener("mouseover",this._onMouseOver),this.isSelectOneElement&&(this.containerOuter.addEventListener("focus",this._onFocus),this.containerOuter.addEventListener("blur",this._onBlur)),this.input.addEventListener("input",this._onInput),this.input.addEventListener("paste",this._onPaste),this.input.addEventListener("focus",this._onFocus),this.input.addEventListener("blur",this._onBlur)}},{key:"_removeEventListeners",value:function(){document.removeEventListener("keyup",this._onKeyUp),document.removeEventListener("keydown",this._onKeyDown),document.removeEventListener("click",this._onClick),document.removeEventListener("touchmove",this._onTouchMove),document.removeEventListener("touchend",this._onTouchEnd),document.removeEventListener("mousedown",this._onMouseDown),document.removeEventListener("mouseover",this._onMouseOver),this.isSelectOneElement&&(this.containerOuter.removeEventListener("focus",this._onFocus),this.containerOuter.removeEventListener("blur",this._onBlur)),this.input.removeEventListener("input",this._onInput),this.input.removeEventListener("paste",this._onPaste),this.input.removeEventListener("focus",this._onFocus),this.input.removeEventListener("blur",this._onBlur)}},{key:"_setInputWidth",value:function(){this.placeholder?this.input.value&&this.input.value.length>=this.placeholder.length/1.25&&(this.input.style.width=(0,v.getWidthOfInput)(this.input)):this.input.style.width=(0,v.getWidthOfInput)(this.input)}},{key:"_onKeyDown",value:function(e){var t,i=this;if(e.target===this.input||this.containerOuter.contains(e.target)){var n=e.target,o=this.store.getItemsFilteredByActive(),r=this.input===document.activeElement,a=this.dropdown.classList.contains(this.config.classNames.activeState),c=this.itemList&&this.itemList.children,l=String.fromCharCode(e.keyCode),h=46,u=8,d=13,f=65,p=27,m=38,g=40,y=33,b=34,E=e.ctrlKey||e.metaKey;this.isTextElement||!/[a-zA-Z0-9-_ ]/.test(l)||a||this.showDropdown(!0),this.canSearch=this.config.searchEnabled;var _=function(){E&&c&&(i.canSearch=!1,i.config.removeItems&&!i.input.value&&i.input===document.activeElement&&i.highlightAll())},S=function(){if(i.isTextElement&&n.value){var t=i.input.value,s=i._canAddItem(o,t);s.response&&(a&&i.hideDropdown(),i._addItem(t),i._triggerChange(t),i.clearInput())}if(n.hasAttribute("data-button")&&(i._handleButtonAction(o,n),e.preventDefault()),a){e.preventDefault();var r=i.dropdown.querySelector("."+i.config.classNames.highlightedState);r&&(o[0]&&(o[0].keyCode=d),i._handleChoiceAction(o,r))}else i.isSelectOneElement&&(a||(i.showDropdown(!0),e.preventDefault()))},I=function(){a&&(i.toggleDropdown(),i.containerOuter.focus())},w=function(){if(a||i.isSelectOneElement){a||i.showDropdown(!0),i.canSearch=!1;var t=e.keyCode===g||e.keyCode===b?1:-1,n=e.metaKey||e.keyCode===b||e.keyCode===y,s=void 0;if(n)s=t>0?Array.from(i.dropdown.querySelectorAll("[data-choice-selectable]")).pop():i.dropdown.querySelector("[data-choice-selectable]");else{var o=i.dropdown.querySelector("."+i.config.classNames.highlightedState);s=o?(0,v.getAdjacentEl)(o,"[data-choice-selectable]",t):i.dropdown.querySelector("[data-choice-selectable]")}s&&((0,v.isScrolledIntoView)(s,i.choiceList,t)||i._scrollToChoice(s,t),i._highlightChoice(s)),e.preventDefault()}},T=function(){!r||e.target.value||i.isSelectOneElement||(i._handleBackspace(o),e.preventDefault())},C=(t={},s(t,f,_),s(t,d,S),s(t,p,I),s(t,m,w),s(t,y,w),s(t,g,w),s(t,b,w),s(t,u,T),s(t,h,T),t);C[e.keyCode]&&C[e.keyCode]()}}},{key:"_onKeyUp",value:function(e){if(e.target===this.input){var t=this.input.value,i=this.store.getItemsFilteredByActive(),n=this._canAddItem(i,t);if(this.isTextElement){var s=this.dropdown.classList.contains(this.config.classNames.activeState);if(t){if(n.notice){var o=this._getTemplate("notice",n.notice);this.dropdown.innerHTML=o.outerHTML}n.response===!0?s||this.showDropdown():!n.notice&&s&&this.hideDropdown()}else s&&this.hideDropdown()}else{var r=46,a=8;e.keyCode!==r&&e.keyCode!==a||e.target.value?this.canSearch&&n.response&&this._handleSearch(this.input.value):!this.isTextElement&&this.isSearching&&(this.isSearching=!1,this.store.dispatch((0,p.activateChoices)(!0)))}this.canSearch=this.config.searchEnabled}}},{key:"_onInput",value:function(){this.isSelectOneElement||this._setInputWidth()}},{key:"_onTouchMove",value:function(){this.wasTap===!0&&(this.wasTap=!1)}},{key:"_onTouchEnd",value:function(e){var t=e.target||e.touches[0].target,i=this.dropdown.classList.contains(this.config.classNames.activeState);this.wasTap===!0&&this.containerOuter.contains(t)&&(t!==this.containerOuter&&t!==this.containerInner||this.isSelectOneElement||(this.isTextElement?document.activeElement!==this.input&&this.input.focus():i||this.showDropdown(!0)),e.stopPropagation()),this.wasTap=!0}},{key:"_onMouseDown",value:function(e){var t=e.target;if(t===this.choiceList&&this.isIe11&&(this.isScrollingOnIe=!0),this.containerOuter.contains(t)&&t!==this.input){var i=void 0,n=this.store.getItemsFilteredByActive(),s=e.shiftKey;(i=(0,v.findAncestorByAttrName)(t,"data-button"))?this._handleButtonAction(n,i):(i=(0,v.findAncestorByAttrName)(t,"data-item"))?this._handleItemAction(n,i,s):(i=(0,v.findAncestorByAttrName)(t,"data-choice"))&&this._handleChoiceAction(n,i),e.preventDefault()}}},{key:"_onClick",value:function(e){var t=e.target,i=this.dropdown.classList.contains(this.config.classNames.activeState),n=this.store.getItemsFilteredByActive();if(this.containerOuter.contains(t))t.hasAttribute("data-button")&&this._handleButtonAction(n,t),i?this.isSelectOneElement&&t!==this.input&&!this.dropdown.contains(t)&&this.hideDropdown(!0):this.isTextElement?document.activeElement!==this.input&&this.input.focus():this.canSearch?this.showDropdown(!0):(this.showDropdown(),this.containerOuter.focus());else{var s=n.some(function(e){return e.highlighted});s&&this.unhighlightAll(),this.containerOuter.classList.remove(this.config.classNames.focusState),i&&this.hideDropdown()}}},{key:"_onMouseOver",value:function(e){(e.target===this.dropdown||this.dropdown.contains(e.target))&&e.target.hasAttribute("data-choice")&&this._highlightChoice(e.target)}},{key:"_onPaste",value:function(e){e.target!==this.input||this.config.paste||e.preventDefault()}},{key:"_onFocus",value:function(e){var t=this,i=e.target;if(this.containerOuter.contains(i)){var n=this.dropdown.classList.contains(this.config.classNames.activeState),s={text:function(){i===t.input&&t.containerOuter.classList.add(t.config.classNames.focusState)},"select-one":function(){t.containerOuter.classList.add(t.config.classNames.focusState),i===t.input&&(n||t.showDropdown())},"select-multiple":function(){i===t.input&&(t.containerOuter.classList.add(t.config.classNames.focusState),n||t.showDropdown(!0))}};s[this.passedElement.type]()}}},{key:"_onBlur",value:function(e){var t=this,i=e.target;if(this.containerOuter.contains(i)&&!this.isScrollingOnIe){var n=this.store.getItemsFilteredByActive(),s=this.dropdown.classList.contains(this.config.classNames.activeState),o=n.some(function(e){return e.highlighted}),r={text:function(){i===t.input&&(t.containerOuter.classList.remove(t.config.classNames.focusState),o&&t.unhighlightAll(),s&&t.hideDropdown())},"select-one":function(){t.containerOuter.classList.remove(t.config.classNames.focusState),i===t.containerOuter&&s&&!t.canSearch&&t.hideDropdown(),i===t.input&&s&&t.hideDropdown()},"select-multiple":function(){i===t.input&&(t.containerOuter.classList.remove(t.config.classNames.focusState),s&&t.hideDropdown(),o&&t.unhighlightAll())}};r[this.passedElement.type]()}else this.isScrollingOnIe=!1,this.input.focus()}},{key:"_regexFilter",value:function(e){if(!e)return!1;var t=this.config.regexFilter,i=new RegExp(t.source,"i");return i.test(e)}},{key:"_scrollToChoice",value:function(e,t){var i=this;if(e){var n=this.choiceList.offsetHeight,s=e.offsetHeight,o=e.offsetTop+s,r=this.choiceList.scrollTop+n,a=t>0?this.choiceList.scrollTop+o-r:e.offsetTop,c=function e(){var n=4,s=i.choiceList.scrollTop,o=!1,r=void 0,c=void 0;t>0?(r=(a-s)/n,c=r>1?r:1,i.choiceList.scrollTop=s+c,s<a&&(o=!0)):(r=(s-a)/n,c=r>1?r:1,i.choiceList.scrollTop=s-c,s>a&&(o=!0)),o&&requestAnimationFrame(function(i){e(i,a,t)})};requestAnimationFrame(function(e){c(e,a,t)})}}},{key:"_highlightChoice",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,i=Array.from(this.dropdown.querySelectorAll("[data-choice-selectable]")),n=t;if(i&&i.length){var s=Array.from(this.dropdown.querySelectorAll("."+this.config.classNames.highlightedState));s.forEach(function(t){t.classList.remove(e.config.classNames.highlightedState),t.setAttribute("aria-selected","false")}),n?this.highlightPosition=i.indexOf(n):(n=i.length>this.highlightPosition?i[this.highlightPosition]:i[i.length-1],n||(n=i[0])),n.classList.add(this.config.classNames.highlightedState),n.setAttribute("aria-selected","true"),this.containerOuter.setAttribute("aria-activedescendant",n.id)}}},{key:"_addItem",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:-1,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:-1,s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,o=arguments.length>5&&void 0!==arguments[5]&&arguments[5],r=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null,a=(0,v.isType)("String",e)?e.trim():e,c=r,l=this.store.getItems(),h=t||a,u=parseInt(i,10)||-1,d=n>=0?this.store.getGroupById(n):null,f=l?l.length+1:1;return this.config.prependValue&&(a=this.config.prependValue+a.toString()),this.config.appendValue&&(a+=this.config.appendValue.toString()),this.store.dispatch((0,p.addItem)(a,h,f,u,n,s,o,c)),
+this.isSelectOneElement&&this.removeActiveItems(f),d&&d.value?(0,v.triggerEvent)(this.passedElement,"addItem",{id:f,value:a,label:h,groupValue:d.value,keyCode:c}):(0,v.triggerEvent)(this.passedElement,"addItem",{id:f,value:a,label:h,keyCode:c}),this}},{key:"_removeItem",value:function(e){if(!e||!(0,v.isType)("Object",e))return this;var t=e.id,i=e.value,n=e.label,s=e.choiceId,o=e.groupId,r=o>=0?this.store.getGroupById(o):null;return this.store.dispatch((0,p.removeItem)(t,s)),r&&r.value?(0,v.triggerEvent)(this.passedElement,"removeItem",{id:t,value:i,label:n,groupValue:r.value}):(0,v.triggerEvent)(this.passedElement,"removeItem",{id:t,value:i,label:n}),this}},{key:"_addChoice",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=arguments.length>3&&void 0!==arguments[3]&&arguments[3],s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:-1,o=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,r=arguments.length>6&&void 0!==arguments[6]&&arguments[6],a=arguments.length>7&&void 0!==arguments[7]?arguments[7]:null;if("undefined"!=typeof e&&null!==e){var c=this.store.getChoices(),l=t||e,h=c?c.length+1:1,u=this.baseId+"-"+this.idNames.itemChoice+"-"+h;this.store.dispatch((0,p.addChoice)(e,l,h,s,n,u,o,r,a)),i&&this._addItem(e,l,h,void 0,o,r,a)}}},{key:"_clearChoices",value:function(){this.store.dispatch((0,p.clearChoices)())}},{key:"_addGroup",value:function(e,t){var i=this,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"value",s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"label",o=(0,v.isType)("Object",e)?e.choices:Array.from(e.getElementsByTagName("OPTION")),r=t?t:Math.floor((new Date).valueOf()*Math.random()),a=!!e.disabled&&e.disabled;o?(this.store.dispatch((0,p.addGroup)(e.label,r,!0,a)),o.forEach(function(e){var t=e.disabled||e.parentNode&&e.parentNode.disabled;i._addChoice(e[n],(0,v.isType)("Object",e)?e[s]:e.innerHTML,e.selected,t,r,e.customProperties,e.placeholder)})):this.store.dispatch((0,p.addGroup)(e.label,e.id,!1,e.disabled))}},{key:"_getTemplate",value:function(e){if(!e)return null;for(var t=this.config.templates,i=arguments.length,n=Array(i>1?i-1:0),s=1;s<i;s++)n[s-1]=arguments[s];return t[e].apply(t,n)}},{key:"_createTemplates",value:function(){var e=this,t=this.config.classNames,i={containerOuter:function(i){return(0,v.strToEl)('\n <div\n class="'+t.containerOuter+'"\n '+(e.isSelectElement?e.config.searchEnabled?'role="combobox" aria-autocomplete="list"':'role="listbox"':"")+'\n data-type="'+e.passedElement.type+'"\n '+(e.isSelectOneElement?'tabindex="0"':"")+'\n aria-haspopup="true"\n aria-expanded="false"\n dir="'+i+'"\n >\n </div>\n ')},containerInner:function(){return(0,v.strToEl)('\n <div class="'+t.containerInner+'"></div>\n ')},itemList:function(){var i,n=(0,u.default)(t.list,(i={},s(i,t.listSingle,e.isSelectOneElement),s(i,t.listItems,!e.isSelectOneElement),i));return(0,v.strToEl)('\n <div class="'+n+'"></div>\n ')},placeholder:function(e){return(0,v.strToEl)('\n <div class="'+t.placeholder+'">\n '+e+"\n </div>\n ")},item:function(i){var n,o=(0,u.default)(t.item,(n={},s(n,t.highlightedState,i.highlighted),s(n,t.itemSelectable,!i.highlighted),s(n,t.placeholder,i.placeholder),n));if(e.config.removeItemButton){var r;return o=(0,u.default)(t.item,(r={},s(r,t.highlightedState,i.highlighted),s(r,t.itemSelectable,!i.disabled),s(r,t.placeholder,i.placeholder),r)),(0,v.strToEl)('\n <div\n class="'+o+'"\n data-item\n data-id="'+i.id+'"\n data-value="'+i.value+'"\n data-deletable\n '+(i.active?'aria-selected="true"':"")+"\n "+(i.disabled?'aria-disabled="true"':"")+"\n >\n "+i.label+'<!--\n --><button\n type="button"\n class="'+t.button+'"\n data-button\n aria-label="Remove item: \''+i.value+"'\"\n >\n Remove item\n </button>\n </div>\n ")}return(0,v.strToEl)('\n <div\n class="'+o+'"\n data-item\n data-id="'+i.id+'"\n data-value="'+i.value+'"\n '+(i.active?'aria-selected="true"':"")+"\n "+(i.disabled?'aria-disabled="true"':"")+"\n >\n "+i.label+"\n </div>\n ")},choiceList:function(){return(0,v.strToEl)('\n <div\n class="'+t.list+'"\n dir="ltr"\n role="listbox"\n '+(e.isSelectOneElement?"":'aria-multiselectable="true"')+"\n >\n </div>\n ")},choiceGroup:function(e){var i=(0,u.default)(t.group,s({},t.itemDisabled,e.disabled));return(0,v.strToEl)('\n <div\n class="'+i+'"\n data-group\n data-id="'+e.id+'"\n data-value="'+e.value+'"\n role="group"\n '+(e.disabled?'aria-disabled="true"':"")+'\n >\n <div class="'+t.groupHeading+'">'+e.value+"</div>\n </div>\n ")},choice:function(i){var n,o=(0,u.default)(t.item,t.itemChoice,(n={},s(n,t.itemDisabled,i.disabled),s(n,t.itemSelectable,!i.disabled),s(n,t.placeholder,i.placeholder),n));return(0,v.strToEl)('\n <div\n class="'+o+'"\n data-select-text="'+e.config.itemSelectText+'"\n data-choice\n data-id="'+i.id+'"\n data-value="'+i.value+'"\n '+(i.disabled?'data-choice-disabled aria-disabled="true"':"data-choice-selectable")+'\n id="'+i.elementId+'"\n '+(i.groupId>0?'role="treeitem"':'role="option"')+"\n >\n "+i.label+"\n </div>\n ")},input:function(){var e=(0,u.default)(t.input,t.inputCloned);return(0,v.strToEl)('\n <input\n type="text"\n class="'+e+'"\n autocomplete="off"\n autocapitalize="off"\n spellcheck="false"\n role="textbox"\n aria-autocomplete="list"\n >\n ')},dropdown:function(){var e=(0,u.default)(t.list,t.listDropdown);return(0,v.strToEl)('\n <div\n class="'+e+'"\n aria-expanded="false"\n >\n </div>\n ')},notice:function(e){var i,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",o=(0,u.default)(t.item,t.itemChoice,(i={},s(i,t.noResults,"no-results"===n),s(i,t.noChoices,"no-choices"===n),i));return(0,v.strToEl)('\n <div class="'+o+'">\n '+e+"\n </div>\n ")},option:function(e){return(0,v.strToEl)('\n <option value="'+e.value+'" selected>'+e.label+"</option>\n ")}},n=this.config.callbackOnCreateTemplates,o={};n&&(0,v.isType)("Function",n)&&(o=n.call(this,v.strToEl)),this.config.templates=(0,v.extend)(i,o)}},{key:"_setLoading",value:function(e){this.store.dispatch((0,p.setIsLoading)(e))}},{key:"_createInput",value:function(){var e=this,t=this.passedElement.getAttribute("dir")||"ltr",i=this._getTemplate("containerOuter",t),n=this._getTemplate("containerInner"),s=this._getTemplate("itemList"),o=this._getTemplate("choiceList"),r=this._getTemplate("input"),a=this._getTemplate("dropdown");this.containerOuter=i,this.containerInner=n,this.input=r,this.choiceList=o,this.itemList=s,this.dropdown=a,this.passedElement.classList.add(this.config.classNames.input,this.config.classNames.hiddenState),this.passedElement.tabIndex="-1";var c=this.passedElement.getAttribute("style");if(Boolean(c)&&this.passedElement.setAttribute("data-choice-orig-style",c),this.passedElement.setAttribute("style","display:none;"),this.passedElement.setAttribute("aria-hidden","true"),this.passedElement.setAttribute("data-choice","active"),(0,v.wrap)(this.passedElement,n),(0,v.wrap)(n,i),this.isSelectOneElement?r.placeholder=this.config.searchPlaceholderValue||"":this.placeholder&&(r.placeholder=this.placeholder,r.style.width=(0,v.getWidthOfInput)(r)),this.config.addItems||this.disable(),i.appendChild(n),i.appendChild(a),n.appendChild(s),this.isTextElement||a.appendChild(o),this.isSelectMultipleElement||this.isTextElement?n.appendChild(r):this.canSearch&&a.insertBefore(r,a.firstChild),this.isSelectElement){var l=Array.from(this.passedElement.getElementsByTagName("OPTGROUP"));if(this.highlightPosition=0,this.isSearching=!1,this._setLoading(!0),l&&l.length)l.forEach(function(t){e._addGroup(t,t.id||null)});else{var h=Array.from(this.passedElement.options),u=this.config.sortFilter,d=this.presetChoices;h.forEach(function(e){d.push({value:e.value,label:e.innerHTML,selected:e.selected,disabled:e.disabled||e.parentNode.disabled,placeholder:e.hasAttribute("placeholder")})}),this.config.shouldSort&&d.sort(u);var f=d.some(function(e){return e.selected});d.forEach(function(t,i){if(e.isSelectOneElement){var n=f||!f&&i>0;e._addChoice(t.value,t.label,!n||t.selected,!!n&&t.disabled,void 0,t.customProperties,t.placeholder)}else e._addChoice(t.value,t.label,t.selected,t.disabled,void 0,t.customProperties,t.placeholder)})}this._setLoading(!1)}else this.isTextElement&&this.presetItems.forEach(function(t){var i=(0,v.getType)(t);if("Object"===i){if(!t.value)return;e._addItem(t.value,t.label,t.id,void 0,t.customProperties,t.placeholder)}else"String"===i&&e._addItem(t)})}}]),e}();e.exports=m},function(e,t,i){!function(t){"use strict";function i(){console.log.apply(console,arguments)}function n(e,t){var i;this.list=e,this.options=t=t||{};for(i in a)a.hasOwnProperty(i)&&("boolean"==typeof a[i]?this.options[i]=i in t?t[i]:a[i]:this.options[i]=t[i]||a[i])}function s(e,t,i){var n,r,a,c,l,h;if(t){if(a=t.indexOf("."),a!==-1?(n=t.slice(0,a),r=t.slice(a+1)):n=t,c=e[n],null!==c&&void 0!==c)if(r||"string"!=typeof c&&"number"!=typeof c)if(o(c))for(l=0,h=c.length;l<h;l++)s(c[l],r,i);else r&&s(c,r,i);else i.push(c)}else i.push(e);return i}function o(e){return"[object Array]"===Object.prototype.toString.call(e)}function r(e,t){t=t||{},this.options=t,this.options.location=t.location||r.defaultOptions.location,this.options.distance="distance"in t?t.distance:r.defaultOptions.distance,this.options.threshold="threshold"in t?t.threshold:r.defaultOptions.threshold,this.options.maxPatternLength=t.maxPatternLength||r.defaultOptions.maxPatternLength,this.pattern=t.caseSensitive?e:e.toLowerCase(),this.patternLen=e.length,this.patternLen<=this.options.maxPatternLength&&(this.matchmask=1<<this.patternLen-1,this.patternAlphabet=this._calculatePatternAlphabet())}var a={id:null,caseSensitive:!1,include:[],shouldSort:!0,searchFn:r,sortFn:function(e,t){return e.score-t.score},getFn:s,keys:[],verbose:!1,tokenize:!1,matchAllTokens:!1,tokenSeparator:/ +/g,minMatchCharLength:1,findAllMatches:!1};n.VERSION="2.7.3",n.prototype.set=function(e){return this.list=e,e},n.prototype.search=function(e){this.options.verbose&&i("\nSearch term:",e,"\n"),this.pattern=e,this.results=[],this.resultMap={},this._keyMap=null,this._prepareSearchers(),this._startSearch(),this._computeScore(),this._sort();var t=this._format();return t},n.prototype._prepareSearchers=function(){var e=this.options,t=this.pattern,i=e.searchFn,n=t.split(e.tokenSeparator),s=0,o=n.length;if(this.options.tokenize)for(this.tokenSearchers=[];s<o;s++)this.tokenSearchers.push(new i(n[s],e));this.fullSeacher=new i(t,e)},n.prototype._startSearch=function(){var e,t,i,n,s=this.options,o=s.getFn,r=this.list,a=r.length,c=this.options.keys,l=c.length,h=null;if("string"==typeof r[0])for(i=0;i<a;i++)this._analyze("",r[i],i,i);else for(this._keyMap={},i=0;i<a;i++)for(h=r[i],n=0;n<l;n++){if(e=c[n],"string"!=typeof e){if(t=1-e.weight||1,this._keyMap[e.name]={weight:t},e.weight<=0||e.weight>1)throw new Error("Key weight has to be > 0 and <= 1");e=e.name}else this._keyMap[e]={weight:1};this._analyze(e,o(h,e,[]),h,i)}},n.prototype._analyze=function(e,t,n,s){var r,a,c,l,h,u,d,f,p,v,m,g,y,b,E,_=this.options,S=!1;if(void 0!==t&&null!==t){a=[];var I=0;if("string"==typeof t){if(r=t.split(_.tokenSeparator),_.verbose&&i("---------\nKey:",e),this.options.tokenize){for(b=0;b<this.tokenSearchers.length;b++){for(f=this.tokenSearchers[b],_.verbose&&i("Pattern:",f.pattern),p=[],g=!1,E=0;E<r.length;E++){v=r[E],m=f.search(v);var w={};m.isMatch?(w[v]=m.score,S=!0,g=!0,a.push(m.score)):(w[v]=1,this.options.matchAllTokens||a.push(1)),p.push(w)}g&&I++,_.verbose&&i("Token scores:",p)}for(l=a[0],u=a.length,b=1;b<u;b++)l+=a[b];l/=u,_.verbose&&i("Token score average:",l)}d=this.fullSeacher.search(t),_.verbose&&i("Full text score:",d.score),h=d.score,void 0!==l&&(h=(h+l)/2),_.verbose&&i("Score average:",h),y=!this.options.tokenize||!this.options.matchAllTokens||I>=this.tokenSearchers.length,_.verbose&&i("Check Matches",y),(S||d.isMatch)&&y&&(c=this.resultMap[s],c?c.output.push({key:e,score:h,matchedIndices:d.matchedIndices}):(this.resultMap[s]={item:n,output:[{key:e,score:h,matchedIndices:d.matchedIndices}]},this.results.push(this.resultMap[s])))}else if(o(t))for(b=0;b<t.length;b++)this._analyze(e,t[b],n,s)}},n.prototype._computeScore=function(){var e,t,n,s,o,r,a,c,l,h=this._keyMap,u=this.results;for(this.options.verbose&&i("\n\nComputing score:\n"),e=0;e<u.length;e++){for(n=0,s=u[e].output,o=s.length,c=1,t=0;t<o;t++)r=s[t].score,a=h?h[s[t].key].weight:1,l=r*a,1!==a?c=Math.min(c,l):(n+=l,s[t].nScore=l);1===c?u[e].score=n/o:u[e].score=c,this.options.verbose&&i(u[e])}},n.prototype._sort=function(){var e=this.options;e.shouldSort&&(e.verbose&&i("\n\nSorting...."),this.results.sort(e.sortFn))},n.prototype._format=function(){var e,t,n,s,o=this.options,r=o.getFn,a=[],c=this.results,l=o.include;for(o.verbose&&i("\n\nOutput:\n\n",c),n=o.id?function(e){c[e].item=r(c[e].item,o.id,[])[0]}:function(){},s=function(e){var t,i,n,s,o,r=c[e];if(l.length>0){if(t={item:r.item},l.indexOf("matches")!==-1)for(n=r.output,t.matches=[],i=0;i<n.length;i++)s=n[i],o={indices:s.matchedIndices},s.key&&(o.key=s.key),t.matches.push(o);l.indexOf("score")!==-1&&(t.score=c[e].score)}else t=r.item;return t},e=0,t=c.length;e<t;e++)n(e),a.push(s(e));return a},r.defaultOptions={location:0,distance:100,threshold:.6,maxPatternLength:32},r.prototype._calculatePatternAlphabet=function(){var e={},t=0;for(t=0;t<this.patternLen;t++)e[this.pattern.charAt(t)]=0;for(t=0;t<this.patternLen;t++)e[this.pattern.charAt(t)]|=1<<this.pattern.length-t-1;return e},r.prototype._bitapScore=function(e,t){var i=e/this.patternLen,n=Math.abs(this.options.location-t);return this.options.distance?i+n/this.options.distance:n?1:i},r.prototype.search=function(e){var t,i,n,s,o,r,a,c,l,h,u,d,f,p,v,m,g,y,b,E,_,S,I,w=this.options;if(e=w.caseSensitive?e:e.toLowerCase(),this.pattern===e)return{isMatch:!0,score:0,matchedIndices:[[0,e.length-1]]};if(this.patternLen>w.maxPatternLength){if(y=e.match(new RegExp(this.pattern.replace(w.tokenSeparator,"|"))),b=!!y)for(_=[],t=0,S=y.length;t<S;t++)I=y[t],_.push([e.indexOf(I),I.length-1]);return{isMatch:b,score:b?.5:1,matchedIndices:_}}for(s=w.findAllMatches,o=w.location,n=e.length,r=w.threshold,a=e.indexOf(this.pattern,o),E=[],t=0;t<n;t++)E[t]=0;for(a!=-1&&(r=Math.min(this._bitapScore(0,a),r),a=e.lastIndexOf(this.pattern,o+this.patternLen),a!=-1&&(r=Math.min(this._bitapScore(0,a),r))),a=-1,m=1,g=[],h=this.patternLen+n,t=0;t<this.patternLen;t++){for(c=0,l=h;c<l;)this._bitapScore(t,o+l)<=r?c=l:h=l,l=Math.floor((h-c)/2+c);for(h=l,u=Math.max(1,o-l+1),d=s?n:Math.min(o+l,n)+this.patternLen,f=Array(d+2),f[d+1]=(1<<t)-1,i=d;i>=u;i--)if(v=this.patternAlphabet[e.charAt(i-1)],v&&(E[i-1]=1),f[i]=(f[i+1]<<1|1)&v,0!==t&&(f[i]|=(p[i+1]|p[i])<<1|1|p[i+1]),f[i]&this.matchmask&&(m=this._bitapScore(t,i-1),m<=r)){if(r=m,a=i-1,g.push(a),a<=o)break;u=Math.max(1,2*o-a)}if(this._bitapScore(t+1,o)>r)break;p=f}return _=this._getMatchedIndices(E),{isMatch:a>=0,score:0===m?.001:m,matchedIndices:_}},r.prototype._getMatchedIndices=function(e){for(var t,i=[],n=-1,s=-1,o=0,r=e.length;o<r;o++)t=e[o],t&&n===-1?n=o:t||n===-1||(s=o-1,s-n+1>=this.options.minMatchCharLength&&i.push([n,s]),n=-1);return e[o-1]&&o-1-n+1>=this.options.minMatchCharLength&&i.push([n,o-1]),i},e.exports=n}(this)},function(e,t,i){var n,s;!function(){"use strict";function i(){for(var e=[],t=0;t<arguments.length;t++){var n=arguments[t];if(n){var s=typeof n;if("string"===s||"number"===s)e.push(n);else if(Array.isArray(n))e.push(i.apply(null,n));else if("object"===s)for(var r in n)o.call(n,r)&&n[r]&&e.push(r)}}return e.join(" ")}var o={}.hasOwnProperty;"undefined"!=typeof e&&e.exports?e.exports=i:(n=[],s=function(){return i}.apply(t,n),!(void 0!==s&&(e.exports=s)))}()},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function s(e){if(Array.isArray(e)){for(var t=0,i=Array(e.length);t<e.length;t++)i[t]=e[t];return i}return Array.from(e)}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var i=0;i<t.length;i++){var n=t[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,i,n){return i&&e(t.prototype,i),n&&e(t,n),t}}(),a=i(5),c=i(26),l=n(c),h=function(){function e(){o(this,e),this.store=(0,a.createStore)(l.default,window.devToolsExtension?window.devToolsExtension():void 0)}return r(e,[{key:"getState",value:function(){return this.store.getState()}},{key:"dispatch",value:function(e){this.store.dispatch(e)}},{key:"subscribe",value:function(e){this.store.subscribe(e)}},{key:"isLoading",value:function(){var e=this.store.getState();return e.general.loading}},{key:"getItems",value:function(){var e=this.store.getState();return e.items}},{key:"getItemsFilteredByActive",value:function(){var e=this.getItems(),t=e.filter(function(e){return e.active===!0},[]);return t}},{key:"getItemsReducedToValues",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.getItems(),t=e.reduce(function(e,t){return e.push(t.value),e},[]);return t}},{key:"getChoices",value:function(){var e=this.store.getState();return e.choices}},{key:"getChoicesFilteredByActive",value:function(){var e=this.getChoices(),t=e.filter(function(e){return e.active===!0});return t}},{key:"getChoicesFilteredBySelectable",value:function(){var e=this.getChoices(),t=e.filter(function(e){return e.disabled!==!0});return t}},{key:"getSearchableChoices",value:function(){var e=this.getChoicesFilteredBySelectable();return e.filter(function(e){return e.placeholder!==!0})}},{key:"getChoiceById",value:function(e){if(e){var t=this.getChoicesFilteredByActive(),i=t.find(function(t){return t.id===parseInt(e,10)});return i}return!1}},{key:"getGroups",value:function(){var e=this.store.getState();return e.groups}},{key:"getGroupsFilteredByActive",value:function(){var e=this.getGroups(),t=this.getChoices(),i=e.filter(function(e){var i=e.active===!0&&e.disabled===!1,n=t.some(function(e){return e.active===!0&&e.disabled===!1});return i&&n},[]);return i}},{key:"getGroupById",value:function(e){var t=this.getGroups(),i=t.find(function(t){return t.id===e});return i}},{key:"getPlaceholderChoice",value:function(){var e=this.getChoices(),t=[].concat(s(e)).reverse().find(function(e){return e.placeholder===!0});return t}}]),e}();t.default=h,e.exports=h},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0,t.compose=t.applyMiddleware=t.bindActionCreators=t.combineReducers=t.createStore=void 0;var s=i(6),o=n(s),r=i(21),a=n(r),c=i(23),l=n(c),h=i(24),u=n(h),d=i(25),f=n(d),p=i(22);n(p);t.createStore=o.default,t.combineReducers=a.default,t.bindActionCreators=l.default,t.applyMiddleware=u.default,t.compose=f.default},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function s(e,t,i){function n(){g===m&&(g=m.slice())}function o(){return v}function a(e){if("function"!=typeof e)throw new Error("Expected listener to be a function.");var t=!0;return n(),g.push(e),function(){if(t){t=!1,n();var i=g.indexOf(e);g.splice(i,1)}}}function h(e){if(!(0,r.default)(e))throw new Error("Actions must be plain objects. Use custom middleware for async actions.");if("undefined"==typeof e.type)throw new Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(y)throw new Error("Reducers may not dispatch actions.");try{y=!0,v=p(v,e)}finally{y=!1}for(var t=m=g,i=0;i<t.length;i++){var n=t[i];n()}return e}function u(e){if("function"!=typeof e)throw new Error("Expected the nextReducer to be a function.");p=e,h({type:l.INIT})}function d(){var e,t=a;return e={subscribe:function(e){function i(){e.next&&e.next(o())}if("object"!=typeof e)throw new TypeError("Expected the observer to be an object.");i();var n=t(i);return{unsubscribe:n}}},e[c.default]=function(){return this},e}var f;if("function"==typeof t&&"undefined"==typeof i&&(i=t,t=void 0),"undefined"!=typeof i){if("function"!=typeof i)throw new Error("Expected the enhancer to be a function.");return i(s)(e,t)}if("function"!=typeof e)throw new Error("Expected the reducer to be a function.");var p=e,v=t,m=[],g=m,y=!1;return h({type:l.INIT}),f={dispatch:h,subscribe:a,getState:o,replaceReducer:u},f[c.default]=d,f}t.__esModule=!0,t.ActionTypes=void 0,t.default=s;var o=i(7),r=n(o),a=i(17),c=n(a),l=t.ActionTypes={INIT:"@@redux/INIT"}},function(e,t,i){function n(e){if(!r(e)||s(e)!=a)return!1;var t=o(e);if(null===t)return!0;var i=u.call(t,"constructor")&&t.constructor;return"function"==typeof i&&i instanceof i&&h.call(i)==d}var s=i(8),o=i(14),r=i(16),a="[object Object]",c=Function.prototype,l=Object.prototype,h=c.toString,u=l.hasOwnProperty,d=h.call(Object);e.exports=n},function(e,t,i){function n(e){return null==e?void 0===e?c:a:l&&l in Object(e)?o(e):r(e)}var s=i(9),o=i(12),r=i(13),a="[object Null]",c="[object Undefined]",l=s?s.toStringTag:void 0;e.exports=n},function(e,t,i){var n=i(10),s=n.Symbol;e.exports=s},function(e,t,i){var n=i(11),s="object"==typeof self&&self&&self.Object===Object&&self,o=n||s||Function("return this")();e.exports=o},function(e,t){(function(t){var i="object"==typeof t&&t&&t.Object===Object&&t;e.exports=i}).call(t,function(){return this}())},function(e,t,i){function n(e){var t=r.call(e,c),i=e[c];try{e[c]=void 0;var n=!0}catch(e){}var s=a.call(e);return n&&(t?e[c]=i:delete e[c]),s}var s=i(9),o=Object.prototype,r=o.hasOwnProperty,a=o.toString,c=s?s.toStringTag:void 0;e.exports=n},function(e,t){function i(e){return s.call(e)}var n=Object.prototype,s=n.toString;e.exports=i},function(e,t,i){var n=i(15),s=n(Object.getPrototypeOf,Object);e.exports=s},function(e,t){function i(e,t){return function(i){return e(t(i))}}e.exports=i},function(e,t){function i(e){return null!=e&&"object"==typeof e}e.exports=i},function(e,t,i){e.exports=i(18)},function(e,t,i){(function(e,n){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o,r=i(20),a=s(r);o="undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof e?e:n;var c=(0,a.default)(o);t.default=c}).call(t,function(){return this}(),i(19)(e))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children=[],e.webpackPolyfill=1),e}},function(e,t){"use strict";function i(e){var t,i=e.Symbol;return"function"==typeof i?i.observable?t=i.observable:(t=i("observable"),i.observable=t):t="@@observable",t}Object.defineProperty(t,"__esModule",{value:!0}),t.default=i},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function s(e,t){var i=t&&t.type,n=i&&'"'+i.toString()+'"'||"an action";return"Given action "+n+', reducer "'+e+'" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.'}function o(e){Object.keys(e).forEach(function(t){var i=e[t],n=i(void 0,{type:a.ActionTypes.INIT});if("undefined"==typeof n)throw new Error('Reducer "'+t+"\" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.");var s="@@redux/PROBE_UNKNOWN_ACTION_"+Math.random().toString(36).substring(7).split("").join(".");if("undefined"==typeof i(void 0,{type:s}))throw new Error('Reducer "'+t+'" returned undefined when probed with a random type. '+("Don't try to handle "+a.ActionTypes.INIT+' or other actions in "redux/*" ')+"namespace. They are considered private. Instead, you must return the current state for any unknown actions, unless it is undefined, in which case you must return the initial state, regardless of the action type. The initial state may not be undefined, but can be null.")})}function r(e){for(var t=Object.keys(e),i={},n=0;n<t.length;n++){var r=t[n];"function"==typeof e[r]&&(i[r]=e[r])}var a=Object.keys(i),c=void 0;try{o(i)}catch(e){c=e}return function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1];if(c)throw c;for(var n=!1,o={},r=0;r<a.length;r++){var l=a[r],h=i[l],u=e[l],d=h(u,t);if("undefined"==typeof d){var f=s(l,t);throw new Error(f)}o[l]=d,n=n||d!==u}return n?o:e}}t.__esModule=!0,t.default=r;var a=i(6),c=i(7),l=(n(c),i(22));n(l)},function(e,t){"use strict";function i(e){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(e);try{throw new Error(e)}catch(e){}}t.__esModule=!0,t.default=i},function(e,t){"use strict";function i(e,t){return function(){return t(e.apply(void 0,arguments))}}function n(e,t){if("function"==typeof e)return i(e,t);if("object"!=typeof e||null===e)throw new Error("bindActionCreators expected an object or a function, instead received "+(null===e?"null":typeof e)+'. Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');for(var n=Object.keys(e),s={},o=0;o<n.length;o++){var r=n[o],a=e[r];"function"==typeof a&&(s[r]=i(a,t))}return s}t.__esModule=!0,t.default=n},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function s(){for(var e=arguments.length,t=Array(e),i=0;i<e;i++)t[i]=arguments[i];return function(e){return function(i,n,s){var r=e(i,n,s),c=r.dispatch,l=[],h={getState:r.getState,dispatch:function(e){return c(e)}};return l=t.map(function(e){return e(h)}),c=a.default.apply(void 0,l)(r.dispatch),o({},r,{dispatch:c})}}}t.__esModule=!0;var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var i=arguments[t];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n])}return e};t.default=s;var r=i(25),a=n(r)},function(e,t){"use strict";function i(){for(var e=arguments.length,t=Array(e),i=0;i<e;i++)t[i]=arguments[i];return 0===t.length?function(e){return e}:1===t.length?t[0]:t.reduce(function(e,t){return function(){return e(t.apply(void 0,arguments))}})}t.__esModule=!0,t.default=i},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(5),o=i(27),r=n(o),a=i(28),c=n(a),l=i(29),h=n(l),u=i(30),d=n(u),f=(0,s.combineReducers)({items:r.default,groups:c.default,choices:h.default,general:d.default}),p=function(e,t){var i=e;return"CLEAR_ALL"===t.type&&(i=void 0),f(i,t)};t.default=p},function(e,t){"use strict";function i(e){if(Array.isArray(e)){for(var t=0,i=Array(e.length);t<e.length;t++)i[t]=e[t];return i}return Array.from(e)}Object.defineProperty(t,"__esModule",{value:!0});var n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1];switch(t.type){case"ADD_ITEM":var n=[].concat(i(e),[{id:t.id,choiceId:t.choiceId,groupId:t.groupId,value:t.value,label:t.label,active:!0,highlighted:!1,customProperties:t.customProperties,placeholder:t.placeholder||!1,keyCode:null}]);return n.map(function(e){return e.highlighted&&(e.highlighted=!1),e});case"REMOVE_ITEM":return e.map(function(e){return e.id===t.id&&(e.active=!1),e});case"HIGHLIGHT_ITEM":return e.map(function(e){return e.id===t.id&&(e.highlighted=t.highlighted),e});default:return e}};t.default=n},function(e,t){"use strict";function i(e){if(Array.isArray(e)){for(var t=0,i=Array(e.length);t<e.length;t++)i[t]=e[t];return i}return Array.from(e)}Object.defineProperty(t,"__esModule",{value:!0});var n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1];switch(t.type){case"ADD_GROUP":return[].concat(i(e),[{id:t.id,value:t.value,active:t.active,disabled:t.disabled}]);case"CLEAR_CHOICES":return e.groups=[];default:return e}};t.default=n},function(e,t){"use strict";function i(e){if(Array.isArray(e)){for(var t=0,i=Array(e.length);t<e.length;t++)i[t]=e[t];return i}return Array.from(e)}Object.defineProperty(t,"__esModule",{value:!0});var n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1];switch(t.type){case"ADD_CHOICE":return[].concat(i(e),[{id:t.id,elementId:t.elementId,groupId:t.groupId,value:t.value,label:t.label||t.value,disabled:t.disabled||!1,selected:!1,active:!0,score:9999,customProperties:t.customProperties,placeholder:t.placeholder||!1,keyCode:null}]);case"ADD_ITEM":var n=e;return t.activateOptions&&(n=e.map(function(e){return e.active=t.active,e})),t.choiceId>-1&&(n=e.map(function(e){return e.id===parseInt(t.choiceId,10)&&(e.selected=!0),e})),n;case"REMOVE_ITEM":return t.choiceId>-1?e.map(function(e){return e.id===parseInt(t.choiceId,10)&&(e.selected=!1),e}):e;case"FILTER_CHOICES":var s=t.results,o=e.map(function(e){return e.active=s.some(function(t){return t.item.id===e.id&&(e.score=t.score,!0)}),e});return o;case"ACTIVATE_CHOICES":return e.map(function(e){return e.active=t.active,e});case"CLEAR_CHOICES":return e.choices=[];default:return e}};t.default=n},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{loading:!1},t=arguments[1];switch(t.type){case"LOADING":return{loading:t.isLoading};default:return e}};t.default=i},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.addItem=function(e,t,i,n,s,o,r,a){return{type:"ADD_ITEM",value:e,label:t,id:i,choiceId:n,groupId:s,customProperties:o,placeholder:r,keyCode:a}},t.removeItem=function(e,t){return{type:"REMOVE_ITEM",id:e,choiceId:t}},t.highlightItem=function(e,t){return{type:"HIGHLIGHT_ITEM",id:e,highlighted:t}},t.addChoice=function(e,t,i,n,s,o,r,a,c){return{type:"ADD_CHOICE",value:e,label:t,id:i,groupId:n,disabled:s,elementId:o,customProperties:r,placeholder:a,keyCode:c}},t.filterChoices=function(e){return{type:"FILTER_CHOICES",results:e}},t.activateChoices=function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];return{type:"ACTIVATE_CHOICES",active:e}},t.clearChoices=function(){return{type:"CLEAR_CHOICES"}},t.addGroup=function(e,t,i,n){return{type:"ADD_GROUP",value:e,id:t,active:i,disabled:n}},t.clearAll=function(){return{type:"CLEAR_ALL"}},t.setIsLoading=function(e){return{type:"LOADING",isLoading:e}}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n=(t.capitalise=function(e){return e.replace(/\w\S*/g,function(e){return e.charAt(0).toUpperCase()+e.substr(1).toLowerCase()})},t.generateChars=function(e){for(var t="",i=0;i<e;i++){var n=a(0,36);t+=n.toString(36)}return t}),s=(t.generateId=function(e,t){var i=e.id||e.name&&e.name+"-"+n(2)||n(4);return i=i.replace(/(:|\.|\[|\]|,)/g,""),i=t+i},t.getType=function(e){return Object.prototype.toString.call(e).slice(8,-1)}),o=t.isType=function(e,t){var i=s(t);
+return void 0!==t&&null!==t&&i===e},r=(t.isNode=function(e){return"object"===("undefined"==typeof Node?"undefined":i(Node))?e instanceof Node:e&&"object"===("undefined"==typeof e?"undefined":i(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},t.isElement=function(e){return"object"===("undefined"==typeof HTMLElement?"undefined":i(HTMLElement))?e instanceof HTMLElement:e&&"object"===("undefined"==typeof e?"undefined":i(e))&&null!==e&&1===e.nodeType&&"string"==typeof e.nodeName},t.extend=function e(){for(var t={},i=arguments.length,n=function(i){for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(o("Object",i[n])?t[n]=e(!0,t[n],i[n]):t[n]=i[n])},s=0;s<i;s++){var r=arguments[s];o("Object",r)&&n(r)}return t},t.whichTransitionEvent=function(){var e,t=document.createElement("fakeelement"),i={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(e in i)if(void 0!==t.style[e])return i[e]},t.whichAnimationEvent=function(){var e,t=document.createElement("fakeelement"),i={animation:"animationend",OAnimation:"oAnimationEnd",MozAnimation:"animationend",WebkitAnimation:"webkitAnimationEnd"};for(e in i)if(void 0!==t.style[e])return i[e]}),a=(t.getParentsUntil=function(e,t,i){for(var n=[];e&&e!==document;e=e.parentNode){if(t){var s=t.charAt(0);if("."===s&&e.classList.contains(t.substr(1)))break;if("#"===s&&e.id===t.substr(1))break;if("["===s&&e.hasAttribute(t.substr(1,t.length-1)))break;if(e.tagName.toLowerCase()===t)break}if(i){var o=i.charAt(0);"."===o&&e.classList.contains(i.substr(1))&&n.push(e),"#"===o&&e.id===i.substr(1)&&n.push(e),"["===o&&e.hasAttribute(i.substr(1,i.length-1))&&n.push(e),e.tagName.toLowerCase()===i&&n.push(e)}else n.push(e)}return 0===n.length?null:n},t.wrap=function(e,t){return t=t||document.createElement("div"),e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.appendChild(e)},t.getSiblings=function(e){for(var t=[],i=e.parentNode.firstChild;i;i=i.nextSibling)1===i.nodeType&&i!==e&&t.push(i);return t},t.findAncestor=function(e,t){for(;(e=e.parentElement)&&!e.classList.contains(t););return e},t.findAncestorByAttrName=function(e,t){for(var i=e;i;){if(i.hasAttribute(t))return i;i=i.parentElement}return null},t.debounce=function(e,t,i){var n;return function(){var s=this,o=arguments,r=function(){n=null,i||e.apply(s,o)},a=i&&!n;clearTimeout(n),n=setTimeout(r,t),a&&e.apply(s,o)}},t.getElemDistance=function(e){var t=0;if(e.offsetParent)do t+=e.offsetTop,e=e.offsetParent;while(e);return t>=0?t:0},t.getElementOffset=function(e,t){var i=t;return i>1&&(i=1),i>0&&(i=0),Math.max(e.offsetHeight*i)},t.getAdjacentEl=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;if(e&&t){var n=e.parentNode.parentNode,s=Array.from(n.querySelectorAll(t)),o=s.indexOf(e),r=i>0?1:-1;return s[o+r]}},t.getScrollPosition=function(e){return"bottom"===e?Math.max((window.scrollY||window.pageYOffset)+(window.innerHeight||document.documentElement.clientHeight)):window.scrollY||window.pageYOffset},t.isInView=function(e,t,i){return this.getScrollPosition(t)>this.getElemDistance(e)+this.getElementOffset(e,i)},t.isScrolledIntoView=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;if(e){var n=void 0;return n=i>0?t.scrollTop+t.offsetHeight>=e.offsetTop+e.offsetHeight:e.offsetTop>=t.scrollTop}},t.stripHTML=function(e){var t=document.createElement("DIV");return t.innerHTML=e,t.textContent||t.innerText||""},t.addAnimation=function(e,t){var i=r(),n=function n(){e.classList.remove(t),e.removeEventListener(i,n,!1)};e.classList.add(t),e.addEventListener(i,n,!1)},t.getRandomNumber=function(e,t){return Math.floor(Math.random()*(t-e)+e)}),c=t.strToEl=function(){var e=document.createElement("div");return function(t){var i=t.trim(),n=void 0;for(e.innerHTML=i,n=e.children[0];e.firstChild;)e.removeChild(e.firstChild);return n}}();t.getWidthOfInput=function(e){var t=e.value||e.placeholder,i=e.offsetWidth;if(t){var n=c("<span>"+t+"</span>");if(n.style.position="absolute",n.style.padding="0",n.style.top="-9999px",n.style.left="-9999px",n.style.width="auto",n.style.whiteSpace="pre",document.body.contains(e)&&window.getComputedStyle){var s=window.getComputedStyle(e);s&&(n.style.fontSize=s.fontSize,n.style.fontFamily=s.fontFamily,n.style.fontWeight=s.fontWeight,n.style.fontStyle=s.fontStyle,n.style.letterSpacing=s.letterSpacing,n.style.textTransform=s.textTransform,n.style.padding=s.padding)}document.body.appendChild(n),t&&n.offsetWidth!==e.offsetWidth&&(i=n.offsetWidth+4),document.body.removeChild(n)}return i+"px"},t.sortByAlpha=function(e,t){var i=(e.label||e.value).toLowerCase(),n=(t.label||t.value).toLowerCase();return i<n?-1:i>n?1:0},t.sortByScore=function(e,t){return e.score-t.score},t.triggerEvent=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,n=new CustomEvent(t,{detail:i,bubbles:!0,cancelable:!0});return e.dispatchEvent(n)}},function(e,t){"use strict";!function(){function e(e,t){t=t||{bubbles:!1,cancelable:!1,detail:void 0};var i=document.createEvent("CustomEvent");return i.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),i}Array.from||(Array.from=function(){var e=Object.prototype.toString,t=function(t){return"function"==typeof t||"[object Function]"===e.call(t)},i=function(e){var t=Number(e);return isNaN(t)?0:0!==t&&isFinite(t)?(t>0?1:-1)*Math.floor(Math.abs(t)):t},n=Math.pow(2,53)-1,s=function(e){var t=i(e);return Math.min(Math.max(t,0),n)};return function(e){var i=this,n=Object(e);if(null==e)throw new TypeError("Array.from requires an array-like object - not null or undefined");var o,r=arguments.length>1?arguments[1]:void 0;if("undefined"!=typeof r){if(!t(r))throw new TypeError("Array.from: when provided, the second argument must be a function");arguments.length>2&&(o=arguments[2])}for(var a,c=s(n.length),l=t(i)?Object(new i(c)):new Array(c),h=0;h<c;)a=n[h],r?l[h]="undefined"==typeof o?r(a,h):r.call(o,a,h):l[h]=a,h+=1;return l.length=c,l}}()),Array.prototype.find||(Array.prototype.find=function(e){if(null==this)throw new TypeError("Array.prototype.find called on null or undefined");if("function"!=typeof e)throw new TypeError("predicate must be a function");for(var t,i=Object(this),n=i.length>>>0,s=arguments[1],o=0;o<n;o++)if(t=i[o],e.call(s,t,o,i))return t}),e.prototype=window.Event.prototype,window.CustomEvent=e}()}])});
+//# sourceMappingURL=choices.min.js.map
diff --git a/apps/utils/static/image-loader.js b/apps/utils/static/image-loader.js
new file mode 100644
index 0000000..121c3be
--- /dev/null
+++ b/apps/utils/static/image-loader.js
@@ -0,0 +1,47 @@
+function add_images(){
+ var el = document.getElementById("id_body_markdown");
+ if (el){
+ var iframe = '<iframe frameborder="0" style="border: #dddddd 1px solid;margin-left: 20px;width:330px; height:720px;" src="/luximages/insert/?textarea='+el.id+'"></iframe>';
+ el.insertAdjacentHTML('afterend', iframe);
+ }
+
+ var featured_image = document.getElementById("id_featured_image")
+
+ if (featured_image) {
+ featured_image.querySelectorAll('li').forEach(function(element) {
+ var cur = element.dataset.imageid;
+ var loop = Number(element.dataset.loopcounter);
+ if (cur != "") {
+ if (loop <= 100) {
+ console.log(loop);
+ var request = new XMLHttpRequest();
+ request.open('GET', '/photos/luximage/data/admin/tn/'+cur+'/', true);
+ request.onload = function() {
+ if (request.status >= 200 && request.status < 400) {
+ var data = JSON.parse(request.responseText);
+ var el = element.getElementsByTagName('label')[0];
+ url = "url('"+data['url']+"');";
+ //console.log(url);
+ el.style.backgroundImage = 'url('+data["url"]+')';
+
+ //console.log(el.style);
+ } else {
+ console.log("server error", request.statusText);
+ }
+ };
+ request.onerror = function() {
+ console.log("error on request");
+ };
+ request.send();
+ }
+ }
+ });
+ }
+
+}
+document.addEventListener("DOMContentLoaded", function(event) {
+ add_images();
+ md = document.forms["entry_form"].elements["body_markdown"];
+ md.style.maxHeight = "300rem";
+ md.style.maxWidth = "300rem";
+});
diff --git a/apps/utils/static/next-prev-links.js b/apps/utils/static/next-prev-links.js
new file mode 100644
index 0000000..dddc879
--- /dev/null
+++ b/apps/utils/static/next-prev-links.js
@@ -0,0 +1,60 @@
+function build_next_prev() {
+
+ var url = window.location.href
+ var cur = Number(url.split('/')[6]);
+ var app = url.split('/')[4];
+ var model = url.split('/')[5];
+ if (cur) {
+ var style = document.createElement('style');
+ style.type = 'text/css';
+ style.innerHTML = '.np-container {padding-left: 0;} .prev, .next {display: inline-block; margin-right: .5em;} .prev:after { content: "|"; margin-left:.5em;} .prev a:before {content: "\u00AB"; margin-right: 3px;} .next a:after{content: "\u00BB"; margin-left: 3px;}';
+ document.getElementsByTagName('head')[0].appendChild(style);
+
+ json_url = '/admin/data/'+app+'/'+model+'/'+cur+'/';
+ //console.log(json_url);
+
+ var container = document.createElement("ul");
+ var next_li = document.createElement("li");
+ var next_link = document.createElement("a");
+ var prev_li = document.createElement("li");
+ var prev_link = document.createElement("a");
+ prev_li.className = "prev";
+ next_li.className = "next";
+ container.className = "np-container";
+ next_link.textContent = "Next";
+ prev_link.textContent = "Prev";
+
+ var request = new XMLHttpRequest();
+ request.open('GET', json_url, true);
+ request.onload = function() {
+ if (request.status >= 200 && request.status < 400) {
+ var data = JSON.parse(request.responseText);
+ next_link.href = data['next'];
+ prev_link.href = data['prev'];
+ if (data['next'] != '') {
+ next_li.appendChild(next_link);
+ }
+ if (data['prev']) {
+ prev_li.appendChild(prev_link);
+ }
+ } else {
+ console.log("server error");
+ }
+ };
+ request.onerror = function() {
+ console.log("error on request");
+ };
+ request.send();
+ container.appendChild(prev_li);
+ container.appendChild(next_li);
+ //console.log(container);
+ Array.from(document.getElementsByClassName('object-tools')).forEach(function(item) {
+ item.parentNode.insertBefore(container, item.nextSibling);
+ })
+ } else {
+ return;
+ }
+};
+document.addEventListener("DOMContentLoaded", function(event) {
+ build_next_prev();
+});
diff --git a/apps/utils/urls.py b/apps/utils/urls.py
new file mode 100644
index 0000000..7c37c5d
--- /dev/null
+++ b/apps/utils/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path
+
+from . import views
+
+
+urlpatterns = [
+ path(
+ r'<str:app>/<str:model>/<int:pk>/',
+ views.nav_json,
+ name="admin_links"
+ ),
+]
diff --git a/apps/utils/util.py b/apps/utils/util.py
new file mode 100644
index 0000000..0c089ee
--- /dev/null
+++ b/apps/utils/util.py
@@ -0,0 +1,84 @@
+import re
+from django.apps import apps
+from django.template.loader import render_to_string
+from bs4 import BeautifulSoup
+import markdown
+
+
+def markdown_to_html(txt):
+ md = markdown.Markdown(
+ extensions=[
+ 'markdown.extensions.fenced_code',
+ 'markdown.extensions.codehilite',
+ 'markdown.extensions.attr_list',
+ 'footnotes',
+ 'extra'
+ ],
+ extension_configs={
+ 'markdown.extensions.codehilite': {
+ 'css_class': 'highlight',
+ 'linenums': False
+ },
+ },
+ output_format='html5',
+ safe_mode=False
+ )
+ return md.convert(txt)
+
+
+def parse_image(s):
+ soup = BeautifulSoup(s.group(), "lxml")
+ for img in soup.find_all('img'):
+ cl = img['class']
+ image_id = img['id'].split("image-")[1]
+ i = apps.get_model('photos', 'LuxImage').objects.get(pk=image_id)
+ caption = False
+ exif = False
+ cluster_class = None
+ is_cluster = False
+ extra = None
+ if cl[0] == 'cluster':
+ css_class = cl[0]
+ is_cluster = True
+ cluster_class = cl[1]
+ try:
+ if cl[2] == 'caption':
+ caption = True
+ elif cl[2] == 'exif':
+ exif = True
+ else:
+ extra = cl[2]
+
+ if len(cl) > 3:
+ if cl[3] == 'exif':
+ exif = True
+ except:
+ pass
+ elif cl[0] != 'cluster' and len(cl) > 1:
+ css_class = cl[0]
+ if cl[1] == 'caption':
+ caption = True
+ if cl[1] == 'exif':
+ exif = True
+ elif cl[0] != 'cluster' and len(cl) > 2:
+ css_class = cl[0]
+ if cl[1] == 'caption':
+ caption = True
+ if cl[2] == 'exif':
+ exif = True
+ print('caption'+str(caption))
+ else:
+ css_class = cl[0]
+ return render_to_string("lib/img_%s.html" % css_class, {'image': i, 'caption': caption, 'exif': exif, 'is_cluster': is_cluster, 'cluster_class': cluster_class, 'extra': extra})
+
+
+def render_images(s):
+ s = re.sub('<img(.*)/>', parse_image, s)
+ return s
+
+
+def parse_video(s):
+ soup = BeautifulSoup(s, "lxml")
+ if soup.find('video'):
+ return True
+ return False
diff --git a/apps/utils/views.py b/apps/utils/views.py
new file mode 100644
index 0000000..dcb16a8
--- /dev/null
+++ b/apps/utils/views.py
@@ -0,0 +1,69 @@
+from itertools import chain
+import json
+from django.http import Http404, HttpResponse
+from django.views.generic import ListView
+from photos.models import LuxImage, LuxVideo
+from django.shortcuts import render_to_response
+from django.shortcuts import render
+from django.template import RequestContext
+
+class PaginatedListView(ListView):
+ """
+ handles my own pagination system
+ """
+ context_object_name = 'object_list'
+
+ def dispatch(self, request, *args, **kwargs):
+ path = request.path.split('/')[1:-1]
+ if path[-1] == self.kwargs['page']:
+ path = "/".join(t for t in path[:-1])
+ request.page_url = "/" + path + '/%d/'
+ else:
+ request.page_url = request.path + '%d/'
+ print(request.page_url)
+ request.page = int(self.kwargs['page'])
+ request.base_path = path
+ return super(PaginatedListView, self).dispatch(request, *args, **kwargs)
+
+
+def insert_image(request):
+ """
+ The view that handles the admin insert image/video feature
+ """
+ images = LuxImage.objects.all()[:80]
+ videos = LuxVideo.objects.all()[:10]
+ object_list = sorted(
+ chain(images, videos),
+ key=lambda instance: instance.pub_date,
+ reverse=True
+ )
+ return render(request, 'admin/insert_images.html', {'object_list': object_list, 'textarea_id': request.GET['textarea']})
+
+
+from taggit.models import Tag
+
+from dal import autocomplete
+
+class TagAutocomplete(autocomplete.Select2QuerySetView):
+ def get_queryset(self):
+ # Don't forget to filter out results depending on the visitor !
+ if not self.request.user.is_authenticated:
+ return Tag.objects.none()
+
+ qs = Tag.objects.all()
+
+ if self.q:
+ qs = qs.filter(name__istartswith=self.q)
+
+ return qs
+
+from django.apps import apps
+
+def nav_json(request, app, model, pk):
+ model = apps.get_model(app_label=app, model_name=model)
+ p = model.objects.get(pk=pk)
+ data = {}
+ data['prev'] = p.get_previous_admin_url
+ data['next'] = p.get_next_admin_url
+ data = json.dumps(data)
+ return HttpResponse(data)
diff --git a/apps/utils/widgets.py b/apps/utils/widgets.py
new file mode 100644
index 0000000..f4a7a4a
--- /dev/null
+++ b/apps/utils/widgets.py
@@ -0,0 +1,144 @@
+import os
+from django import forms
+from django.contrib import admin
+from django.contrib.admin.widgets import AdminFileWidget
+from django.contrib.gis.admin import OSMGeoAdmin
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext_lazy as _
+from django.template.loader import render_to_string
+from django.template import Context
+from django.forms.widgets import SelectMultiple
+from django.conf import settings
+
+import markdown
+
+from bs4 import BeautifulSoup
+from django.utils.module_loading import import_string
+
+
+class CustomSelectMultiple(SelectMultiple):
+ def render_options(self, choices, selected_choices):
+ if not selected_choices:
+ # there is CreatView and we have no selected choices - render all selected
+ render_option = self.render_option
+ else:
+ # there is UpdateView and we have selected choices - render as default
+ render_option = super(CustomSelectMultiple, self).render_option
+
+ selected_choices = set(force_text(v) for v in selected_choices)
+ output = []
+ for option_value, option_label in chain(self.choices, choices):
+ if isinstance(option_label, (list, tuple)):
+ output.append(format_html('<optgroup label="{0}">', force_text(option_value)))
+ for option in option_label:
+ output.append(render_option(selected_choices, *option))
+ output.append('</optgroup>')
+ else:
+
+ output.append(render_option(selected_choices, option_value, option_label))
+ return '\n'.join(output)
+
+ def render_option(self, selected_choices, option_value, option_label):
+ option_value = force_text(option_value)
+ selected_html = mark_safe(' selected="selected"')
+
+ return format_html('<option value="{0}"{1}>{2}</option>',
+ option_value,
+ selected_html,
+ force_text(option_label))
+
+
+class TagListFilter(admin.SimpleListFilter):
+ # Human-readable title which will be displayed in the
+ # right admin sidebar just above the filter options.
+ title = _('tag')
+
+ # Parameter for the filter that will be used in the URL query.
+ parameter_name = 'tag'
+
+ def lookups(self, request, model_admin):
+ """
+ Returns a list of tuples. The first element in each
+ tuple is the coded value for the option that will
+ appear in the URL query. The second element is the
+ human-readable name for the option that will appear
+ in the right sidebar.
+ """
+ tl = []
+ self.model_to_use = model_admin.model
+ for t in self.model_to_use.tags.all().order_by('name'):
+ tl += (t.name, t.name),
+ return tl
+
+ def queryset(self, request, queryset):
+ """
+ Returns the filtered queryset based on the value
+ provided in the query string and retrievable via
+ `self.value()`.
+ """
+ qs = self.model_to_use.objects.all()
+ try:
+ request.GET['tag']
+ return qs.filter(tags__name=self.value())
+ except:
+ return qs
+
+
+def thumbnail(image_path):
+ absolute_url = os.path.join(settings.IMAGES_URL, image_path[7:])
+ print(absolute_url)
+ return '<img style="max-width: 400px" src="%s" alt="%s" />' % (absolute_url, image_path)
+
+
+class ImageRadioSelect(forms.RadioSelect):
+ template_name = 'horizontal_select.html'
+
+
+class AdminImageWidget(AdminFileWidget):
+ """
+ A FileField Widget that displays an image instead of a file path
+ if the current file is an image.
+ """
+ def render(self, name, value, attrs=None):
+ output = []
+ file_name = str(value)
+ help_text = ''
+ if file_name:
+ file_path = '%s' % (file_name)
+ if attrs['id'] == 'id_thumbnail':
+ help_text = '160 wide'
+ if attrs['id'] == 'id_image':
+ help_text = '205px high'
+ output.append('<span>%s</span><a target="_blank" href="%s">%s</a>' % (help_text, file_path, thumbnail(file_name)))
+
+ output.append(super(AdminFileWidget, self).render(name, value, attrs))
+ return mark_safe(''.join(output))
+
+
+class LGEntryForm(forms.ModelForm):
+ class Meta:
+ widgets = {
+ 'body_markdown': forms.Textarea(attrs={'rows': 40, 'cols': 100}),
+ 'featured_image': ImageRadioSelect,
+ }
+
+
+class LGEntryFormSmall(forms.ModelForm):
+ class Meta:
+ widgets = {
+ 'body_markdown': forms.Textarea(attrs={'rows': 12, 'cols': 100}),
+ }
+
+
+class OLAdminBase(OSMGeoAdmin):
+ default_lon = -9285175
+ default_lat = 4025046
+ default_zoom = 15
+ units = True
+ scrollable = False
+ map_width = 700
+ map_height = 425
+ map_template = 'gis/admin/osm.html'
+ openlayers_url = '/static/admin/js/OpenLayers.js'
+
+
diff --git a/config/__init__.py b/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config/__init__.py
diff --git a/config/base_urls.py b/config/base_urls.py
new file mode 100644
index 0000000..bb2fa56
--- /dev/null
+++ b/config/base_urls.py
@@ -0,0 +1,34 @@
+from django.contrib import admin
+from django.urls import path
+from django.views.generic.base import RedirectView
+from django.conf.urls import include, url
+from django.conf.urls.static import static
+from django.conf import settings
+
+from django_registration.backends.activation.views import RegistrationView
+from rest_framework import routers
+
+from pages.views import PageDetailView
+from notes.views import NoteViewSet
+from accounts.forms import UserForm
+
+router = routers.DefaultRouter()
+router.register(r'notes', NoteViewSet, basename="notes")
+
+urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+urlpatterns += [
+ path('admin/', admin.site.urls),
+ # path(r'profile/', include('accounts.urls')),
+ path(r'notes/', include('notes.urls')),
+ path(r'accounts/', include('django_registration.backends.activation.urls')),
+ path(r'accounts/', include('django.contrib.auth.urls')),
+ path(r'register/', RegistrationView.as_view(form_class=UserForm), name='django_registration_register',),
+ path(r'settings/', include('accounts.urls')),
+ path(r'', include('django_registration.backends.activation.urls')),
+ path(r'', include('django.contrib.auth.urls')),
+ path(r'', include('notes.urls')),
+ path(r'<slug>', PageDetailView.as_view(), name="pages"),
+ path(r'<path>/<slug>/', PageDetailView.as_view(), name="pages"),
+ path(r'api/', include(router.urls)),
+ path(r'api-auth/', include('rest_framework.urls', namespace='rest_framework'))
+]
diff --git a/config/djadmin.sh b/config/djadmin.sh
new file mode 100755
index 0000000..24081d4
--- /dev/null
+++ b/config/djadmin.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+DIR="$( cd "$( dirname $( dirname "${BASH_SOURCE[0]}" ) )" && pwd )"
+PYTHONPATH=$PYTHONPATH:$DIR
+PYTHONPATH=$PYTHONPATH:"${DIR}/apps"
+PYTHONPATH=$PYTHONPATH:"${DIR}/apps/lib"
+PYTHONPATH=$PYTHONPATH:"${DIR}/venv/lib/python3.6/"
+export PYTHONPATH
+export DJANGO_SETTINGS_MODULE=config.settings
+ADMIN="${DIR}/venv/bin/django-admin.py"
+$ADMIN $@
diff --git a/config/requirements.txt b/config/requirements.txt
new file mode 100644
index 0000000..f174273
--- /dev/null
+++ b/config/requirements.txt
@@ -0,0 +1,12 @@
+coverage==4.5.1
+Django==2.1.2
+django-extensions==2.1.3
+django-registration==3.0
+django-storages==1.7.1
+django-taggit==0.23.0
+ipython==7.1.1
+ipython-genutils==0.2.0
+mixer==6.1.3
+Pillow==5.3.0
+psycopg2==2.7.5
+pwned-passwords-django==1.3.1
diff --git a/config/settings.py b/config/settings.py
new file mode 100644
index 0000000..ade8a84
--- /dev/null
+++ b/config/settings.py
@@ -0,0 +1,167 @@
+"""
+Django settings for project.
+
+Generated by 'django-admin startproject' using Django 2.1.2.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.1/ref/settings/
+"""
+
+import os
+from decouple import config, Csv
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+SECRET_KEY = config('SECRET_KEY')
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = config('DEBUG', cast=bool)
+
+AUTH_PROFILE_MODULE = "accounts.UserProfile"
+AUTH_USER_MODEL = "accounts.User"
+
+DEFAULT_FILE_STORAGE = config('DEFAULT_FILE_STORAGE')
+
+AWS_S3_OBJECT_PARAMETERS = {
+ 'CacheControl': 'max-age=86400',
+}
+
+ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())
+
+# Django registration settings
+ACCOUNT_ACTIVATION_DAYS = 3
+REGISTRATION_OPEN = True
+REGISTRATION_SALT = 'Astra inclinant, sed non obligant'
+
+# Django Rest Framework
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': (
+ 'rest_framework.permissions.IsAuthenticated',
+ )
+}
+
+# APPS
+# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
+DJANGO_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.sites',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+]
+THIRD_PARTY_APPS = [
+ 'taggit',
+ 'taggit_serializer',
+ 'django_extensions',
+ 'rest_framework',
+]
+LOCAL_APPS = [
+ 'utils',
+ 'pages',
+ 'accounts',
+ 'notes',
+]
+INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
+
+
+# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'config.base_urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [
+ os.path.join(BASE_DIR, 'templates'),
+ ],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'config.wsgi.application'
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql',
+ 'NAME': config('DB_NAME'),
+ 'USER': config('DB_USER'),
+ 'PASSWORD': config('DB_PASSWORD'),
+ 'HOST': config('DB_HOST'),
+ 'PORT': '',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'pwned_passwords_django.validators.PwnedPasswordsValidator',
+ 'OPTIONS': {
+ 'error_message': 'We hate to be the bearers of bad news, but that password is known to be compromised',
+ 'help_message': 'Your password can\'t be a commonly used password.',
+ }
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.1/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'America/Chicago'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.1/howto/static-files/
+
+STATIC_URL = '/static/'
+STATIC_ROOT = BASE_DIR+'/static/'
+MEDIA_URL = '/media/'
+MEDIA_ROOT = BASE_DIR+'/media/'
+
+EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
+EMAIL_FILE_PATH = os.path.join(BASE_DIR, "sent_emails")
diff --git a/config/wsgi.py b/config/wsgi.py
new file mode 100644
index 0000000..0c727f0
--- /dev/null
+++ b/config/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for myproj project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproj.settings')
+
+application = get_wsgi_application()
diff --git a/design/config.rb b/design/config.rb
new file mode 100644
index 0000000..16900a7
--- /dev/null
+++ b/design/config.rb
@@ -0,0 +1,12 @@
+project_type = :stand_alone
+# Set this to the root of your project when deployed:
+http_path = "/"
+css_dir = "../media"
+sass_dir = "sass"
+images_dir = "../media/img"
+javascripts_dir = "../media"
+#output_style = :compressed
+output_style = :compressed
+#output_style = (environment == :production) ? :compressed : :expanded
+# To enable relative paths to assets via compass helper functions. Uncomment:
+# relative_assets = true
diff --git a/design/sass/_fonts.scss b/design/sass/_fonts.scss
new file mode 100644
index 0000000..dfe632a
--- /dev/null
+++ b/design/sass/_fonts.scss
@@ -0,0 +1,10 @@
+@font-face {
+ font-family: 'carrois_gothicregular';
+ src: url('carroisgothic-regular-webfont.eot');
+ src: url('carroisgothic-regular-webfont.eot?#iefix') format('embedded-opentype'),
+ url('carroisgothic-regular-webfont.woff') format('woff'),
+ url('carroisgothic-regular-webfont.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+
+}
diff --git a/design/sass/_footer.scss b/design/sass/_footer.scss
new file mode 100644
index 0000000..e525c36
--- /dev/null
+++ b/design/sass/_footer.scss
@@ -0,0 +1,24 @@
+footer {
+ @include constrain_wide;
+ margin-top: 5em;
+ &:before {
+ @include faded_line_after;
+ margin-bottom: 1.2em;
+ }
+ nav {
+ text-align: center;
+ }
+ h4 {
+ @include fontsize(13);
+ font-family: helvetica,arial,sans-serif;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin: 0 0 10px;
+ }
+ p {
+ @include fontsize(14);
+ text-align: center;
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+ }
+}
diff --git a/design/sass/_forms.scss b/design/sass/_forms.scss
new file mode 100644
index 0000000..3dc8ee4
--- /dev/null
+++ b/design/sass/_forms.scss
@@ -0,0 +1,85 @@
+form fieldset {
+ margin: 1rem 0;
+ padding: 0;
+ border: none;
+ width: 100%;
+ position: relative;
+}
+form fieldset label {
+ position: absolute;
+ -webkit-transform: translate3d(10px, 100%);
+ -moz-transform: translate3d(10px, 100%);
+ -ms-transform: translate3d(10px, 100%);
+ -o-transform: translate3d(10px, 100%);
+ transform: translate3d(10px, 100%);
+ @include fontsize(14);
+ color: lighten($body_font_color, 25);
+ line-height: 14px;
+ top: .7rem;
+ left: .75rem;
+ z-index: 1;
+}
+input {
+ border: 1px solid #b3b3b3;
+ border-radius: 4px;
+ padding: 2.2rem 0 .75rem .75rem;
+ width: 99%;
+ @include fontsize(24);
+}
+form .error input {
+ border: 1px solid $link_color;
+}
+.helptext {
+ @include fontsize(14);
+ color: lighten($body_font_color, 25);
+}
+.errorlist {
+ color: $link_color;
+ list-style-type: none;
+ padding: 0 0 0 1rem;
+ position: relative;
+}
+table {
+ width: 100%;
+ border-collapse: collapse;
+ tr {
+ margin: 2rem 0;
+ }
+ td, th {
+ display: table-cell;
+ vertical-align: inherit;
+ margin: 0;
+ padding: 16px .25rem 14px;
+ border-bottom: 1px solid #b3b3b3;
+ }
+ th {
+ @include fontsize(13);
+ @include smcaps;
+ width: 200px;
+ }
+ td {
+ width: 200px;
+ }
+ &:after {
+ display: block;
+ @extend %clearfix;
+ }
+ .btn { float: right;}
+}
+.btn {
+ @include fontsize(15);
+ display: inline-block;
+ border-radius: 4px;
+ -webkit-appearance: none;
+ text-decoration: none;
+ cursor: pointer;
+ background: $link_color;
+ color: #fff !important;
+ border: 1px solid $link_color;
+ padding: 5px 9px;
+ white-space: nowrap;
+ &:hover {
+ background: $link_hover_color;
+ border: 1px solid $link_hover_color;
+ }
+}
diff --git a/design/sass/_global.scss b/design/sass/_global.scss
new file mode 100644
index 0000000..9a01c61
--- /dev/null
+++ b/design/sass/_global.scss
@@ -0,0 +1,196 @@
+body {
+ margin: 0 auto;
+ padding: 0;
+ overflow-x: hidden;
+ font:$body_p_font;
+ color: $body_font_color;
+ text-align: left;
+ background-color: transparent
+}
+ul {
+ padding: 0;
+}
+// eliminate touch delay on mobile safari
+a, button, input, select, textarea, label, summary {
+ touch-action: manipulation;
+}
+a {
+ color: $link_color;
+ -webkit-transition: all 0.1s ease;
+ -moz-transition: all 0.1s ease;
+ -ms-transition: all 0.1s ease;
+ transition: all 0.1s ease;
+ &:hover {
+ text-decoration: none;
+ color: $link_color;
+ }
+ &:visited {
+ color: $link_color;
+ }
+}
+p {
+ text-align: left;
+ @include fontsize(18);
+ @include breakpoint(alpha){
+ @include fontsize(20);
+ line-height: 1.5;
+ }
+ @include breakpoint(beta){
+ @include fontsize(22);
+ line-height: 1.6;
+ }
+}
+time {
+ @include smcaps;
+ @include fontsize(11);
+ display: block;
+ span {
+ @include fontsize(13);
+ }
+}
+abbr {
+ cursor: help;
+}
+pre {
+ text-align: left;
+ @include breakpoint(alpha){
+ @include fontsize(18);
+ line-height: 1.5;
+ }
+}
+object, embed, video {
+ max-width: 100%;
+ width: 100%;
+ height: auto;
+}
+blockquote {
+ @include fontsize(18);
+ display: block;
+ border-top: 4px solid lighten($body_font_light, 20);
+ border-bottom: 4px solid lighten($body_font_light, 20);
+ margin: 3rem 0;
+ position: relative;
+ text-align: left;
+ font-style: italic;
+ cite {
+ display: block;
+ text-align: right;
+ }
+ @include breakpoint(alpha){
+ @include fontsize(20);
+ line-height: 1.5;
+ }
+ @include breakpoint(beta){
+ @include fontsize(22);
+ line-height: 1.6;
+ }
+}
+blockquote:before {
+ @include fancy_sans;
+ @include fontsize(68);
+ content: '\201C';
+ position: absolute;
+ top: -1rem;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 3rem;
+ height: 2rem;
+ color: #666;
+ text-align: center;
+}
+hr {
+ border: 0;
+ height: 1px;
+ @include faded_line_after;
+ margin: 3rem 0;
+}
+img {
+ max-width: 100%;
+}
+figure {
+ margin: 0;
+}
+figcaption {
+ text-align: left;
+}
+figcaption, figcaption a {
+ @include fancy_sans;
+ @include fontsize(14);
+ text-align: left;
+ line-height: 1.9;
+ padding: .3rem .5rem .3rem 0;
+ color: #666;
+ border-bottom: 1px lighten($body_font_light, 20) solid;
+ margin-bottom: 1rem;
+}
+figcaption a:visited {
+ color: #666;
+}
+h1 {
+ font-family: $headline_font_serif;
+ @include fontsize(36);
+ font-weight: normal;
+}
+h2 {
+ font-family: $headline_font_serif;
+ @include fontsize(28);
+ font-weight: normal;
+ text-align: left;
+ @include breakpoint(gamma){
+ @include fontsize(30);
+ line-height: 1.6;
+ }
+}
+h3 {
+ @include fancy_sans;
+ @include fontsize(24);
+ font-weight: normal;
+ text-align: left;
+ @include breakpoint(gamma){
+ @include fontsize(28);
+ line-height: 1.4;
+ }
+}
+.wrapper {
+ @include constrain_wide;
+}
+//************** Universals ************************
+.hide {
+ display: none;
+}
+
+.strike {
+ text-decoration: line-through;
+}
+
+.yes {
+ background: green !important;
+ color: white;
+}
+
+.no {
+ background: red !important;
+ color: white;
+}
+
+.alert, .error {
+ color: red !important;
+}
+//************** other global classes ************************
+.sans {
+ @include generic_sans;
+}
+.bl {
+ @include smcaps;
+ @include fontsize(11);
+}
+.italic {
+ font-style: italic;
+}
+.small {
+ font-size: 90%;
+}
+.divide-after:after {
+ margin-bottom: 3em;
+ @include faded_line_after;
+}
diff --git a/design/sass/_header.scss b/design/sass/_header.scss
new file mode 100644
index 0000000..8b8c3da
--- /dev/null
+++ b/design/sass/_header.scss
@@ -0,0 +1,55 @@
+.head-wrapper {
+ height: 65px;
+ box-shadow: 0 -1px 0 #e7e2ee inset;
+ background: #fff;
+}
+header {
+ @include constrain_wide;
+ .left {
+ float: left;
+ a {
+ display: inline-block;
+ padding-left: 0;
+ }
+ }
+ .right {
+ float: right;
+ }
+}
+nav {
+ @include fancy-sans-bold;
+ letter-spacing: 1px;
+ margin: 0 0 10px;
+ ul {
+ list-style-type: none;
+ padding: 0;
+ li {
+ display: inline;
+ }
+ }
+ a {
+ @include fontsize(14);
+ text-decoration: none;
+ color: #444;
+ font-weight: normal;
+ padding: 6px;
+ &:visited {
+ color: #444;
+ }
+ &:hover {
+ color: $link_color;
+ }
+ }
+ .btn {
+ display: inline-block;
+ background: transparent;
+ color: $link_color !important;
+ border: 1px solid $link_color;
+ border-radius: 4px;
+ &:hover {
+ color: white !important;
+ border-color: $link_color;
+ background-color: $link_color;
+ }
+ }
+}
diff --git a/design/sass/_mixins.scss b/design/sass/_mixins.scss
new file mode 100644
index 0000000..1aeeb83
--- /dev/null
+++ b/design/sass/_mixins.scss
@@ -0,0 +1,136 @@
+$brown: #201a11;
+$brown: #222;
+$light: #ccc;
+$orange: #b53a04;
+$link_color: #FC5D2B;
+$link_hover_color: #BD3039;
+$headline_font_serif: Georgia, 'Times New Roman', serif;
+
+$body_p_font: normal 100% / 1.5 "proxima-nova",helvetica,arial,sans-serif;
+$body_font_color: #6a6a6a;
+$body_font_light: #b3aeae;
+
+$archive_p_line_height: 1.6;
+//$light;
+$narrow-beta-width: 640px;
+$narrow-max-width: 700px;
+$max_width: 1280px;
+
+@mixin smcaps {
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+@mixin plain_a {
+ border: none;
+ text-decoration: none;
+ color: $body_font_color;
+}
+
+@function calc-rem($size) {
+ $remsize: $size/16;
+ @return #{$remsize}rem;
+}
+
+@mixin fontsize($size) {
+ font-size: $size + px;
+ font-size: calc-rem($size);
+}
+
+@mixin generic-sans {
+ font-family: sans-serif;
+}
+@mixin fancy-sans {
+ font-family: "proxima-nova",helvetica,arial,sans-serif;
+}
+@mixin fancy-sans-bold {
+ font-family: "proxima-nova",helvetica,arial,sans-serif;
+ font-weight: 600;
+}
+@mixin fancy-serif {
+ font-family: Georgia, Palatino, serif;
+}
+%clearfix {
+ *zoom: 1;
+ &:before {
+ content: " ";
+ display: table;
+ }
+ &:after {
+ content: " ";
+ display: table;
+ clear: both;
+ }
+}
+
+@mixin transparent_class {
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)";
+ filter: alpha(opacity = 90);
+ opacity: 0.9;
+}
+
+@mixin faded_line_after {
+ display: block;
+ content: "";
+ margin-top: 3em;
+ height: 1px;
+ width: 100%;
+ background: -webkit-linear-gradient(left, rgba(0,0,0,0),rgba(0,0,0,0.1),rgba(0,0,0,0));
+ background: -moz-linear-gradient(left, rgba(0,0,0,0),rgba(0,0,0,0.1),rgba(0,0,0,0));
+ background: -o-linear-gradient(left, rgba(0,0,0,0),rgba(0,0,0,0.1),rgba(0,0,0,0));
+ background: linear-gradient(left, rgba(0,0,0,0),rgba(0,0,0,0.1),rgba(0,0,0,0));
+}
+
+//for overriding the above on dark pages:
+@mixin light_faded_line_after {
+ background: -webkit-linear-gradient(left, rgba(243,237,219,0),rgba(243,237,219,0.1),rgba(243,237,219,0));
+ background: -moz-linear-gradient(left, rgba(243,237,219,0),rgba(243,237,219,0.1),rgba(243,237,219,0));
+ background: -o-linear-gradient(left, rgba(243,237,219,0),rgba(243,237,219,0.1),rgba(243,237,219,0));
+ background: linear-gradient(left, rgba(243,237,219,0),rgba(243,237,219,0.1),rgba(243,237,219,0));
+}
+
+@mixin lighter_faded_line_after {
+ background: -webkit-linear-gradient(left, rgba(243,237,219,.1),rgba(243,237,219,0.3),rgba(243,237,219,.1));
+ background: -moz-linear-gradient(left, rgba(243,237,219,0),rgba(243,237,219,0.1),rgba(243,237,219,0));
+ background: -o-linear-gradient(left, rgba(243,237,219,0),rgba(243,237,219,0.1),rgba(243,237,219,0));
+ background: linear-gradient(left, rgba(243,237,219,0),rgba(243,237,219,0.1),rgba(243,237,219,0));
+}
+
+//generic constrain function
+@mixin constrain($size) {
+ max-width: $size;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+//set an element to centered, narrowish column width
+//used mostly on columns of text
+@mixin constrain_narrow() {
+ max-width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+ @include breakpoint(beta) {
+ max-width: $narrow-beta-width;
+ }
+ @include breakpoint(gamma) {
+ max-width: $narrow-max-width;
+ }
+}
+
+//set an element to centered, wideish column width
+@mixin constrain_wide() {
+ max-width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+ @include breakpoint(gamma) {
+ max-width: $breakpoint-gamma;
+ }
+ @include breakpoint(delta) {
+ max-width: $breakpoint-delta;
+ }
+ @include breakpoint(epsilon) {
+ max-width: $max_width;
+ }
+}
+
+
+
diff --git a/design/sass/_queries.scss b/design/sass/_queries.scss
new file mode 100644
index 0000000..3ea883f
--- /dev/null
+++ b/design/sass/_queries.scss
@@ -0,0 +1,29 @@
+$breakpoint-alpha: 728px; //728
+$breakpoint-beta: 824px; //784
+$breakpoint-gamma: 960px;
+$breakpoint-delta: 1170px;
+$breakpoint-epsilon: $max_width;
+
+@mixin breakpoint($point) {
+ @if $point == "alpha" {
+ @media screen and (min-width:$breakpoint-alpha ){ @content; }
+ }
+ @if $point == "alpha-2" {
+ @media screen and (min-width:$breakpoint-alpha-2 ){ @content; }
+ }
+ @else if $point == "beta" {
+ @media screen and (min-width: $breakpoint-beta) { @content; }
+ }
+ @else if $point == "beta-2" {
+ @media screen and (min-width: $breakpoint-beta-2) { @content; }
+ }
+ @else if $point == "gamma" {
+ @media screen and (min-width: $breakpoint-gamma) { @content; }
+ }
+ @else if $point == "delta" {
+ @media screen and (min-width: $breakpoint-delta) { @content; }
+ }
+ @else if $point == "epsilon" {
+ @media screen and (min-width: $breakpoint-epsilon) { @content; }
+ }
+}
diff --git a/design/sass/screenv1.scss b/design/sass/screenv1.scss
new file mode 100644
index 0000000..a9e582e
--- /dev/null
+++ b/design/sass/screenv1.scss
@@ -0,0 +1,7 @@
+@import "_fonts.scss";
+@import "_mixins.scss";
+@import "_queries.scss";
+@import "_global.scss";
+@import "_header.scss";
+@import "_footer.scss";
+@import "_forms.scss";
diff --git a/design/templates/accounts/profile.html b/design/templates/accounts/profile.html
new file mode 100644
index 0000000..c47e6c6
--- /dev/null
+++ b/design/templates/accounts/profile.html
@@ -0,0 +1,54 @@
+{% extends 'base.html' %}
+{% block content %}
+<main>
+<h1>Account Settings</h1>
+<table>
+ <tbody>
+ <tr>
+ <th scope="row">Username</th>
+ <td colspan="2">{{object.user.username}}<td>
+ </tr>
+ <tr>
+ <th scope="row">Email</th>
+ <td colspan="2">{{object.user.email}}<td>
+ <td class="text-right"><a href="/settings/change-email/"class="btn">Change</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Fullname</th>
+ <td colspan="2">{{object.user.first_name}} {{object.user.last_name}}<td>
+ <td class="text-right"><a href="/settings/change-profile/#display_name"class="btn">Change</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Plan</th>
+ <td colspan="2">{{object.plan}}<td>
+ <td class="text-right"><a href="/settings/change-plan/" class="btn">Change</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Credit Card</th>
+ <td colspan="2">{{object.credit_card}}<td>
+ <td class="text-right"><a href="/settings/change-profile/" class="btn">Change</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Bio</th>
+ <td colspan="2">{{object.bio}}<td>
+ <td class="text-right"><a href="/settings/change-bio/" class="btn">Change</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Photo</th>
+ <td colspan="2">{{object.photo}}<td>
+ <td class="text-right"><a href="/settings/change-photo/" class="btn">Change</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Website</th>
+ <td colspan="2">{{object.website}}<td>
+ <td class="text-right"><a href="/settings/change-website/" class="btn">Change</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Location</th>
+ <td colspan="2">{{object.location}}<td>
+ <td class="text-right"><a href="/settings/change-location/" class="btn">Change</a></td>
+ </tr>
+ </tbody>
+</table>
+</main>
+{% endblock %}
diff --git a/design/templates/base.html b/design/templates/base.html
new file mode 100644
index 0000000..45291d0
--- /dev/null
+++ b/design/templates/base.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!--[if lt IE 8]> <html class="lte8"> <![endif]-->
+<!--[if IE 8]> <html class="ie8 lte8"> <![endif]-->
+<!--[if IE 9]> <html class="ie9"> <![endif]-->
+<!--[if !IE]><!--> <html lang="en"><!--<![endif]-->
+<head>
+<title>Note</title>
+<meta name="viewport" content="width=device-width">
+<meta name="description" content="Note taking for writers">
+<meta property="og:description" content="Note taking for writers">
+<meta property="og:site_name" content="Notes">
+<meta property="og:image" content="">
+<link rel="stylesheet" href="/media/screenv1.css?{{now}}" type="text/css">
+<link rel="icon" type="image/png" href="">
+<link rel="manifest" href="/webmanifest.json">
+<link rel="apple-touch-icon" sizes="256x256" href="">
+</head>
+<body class="{% block bodyclass %}{% endblock %}">
+ <div class="head-wrapper">
+ <header>
+ <nav class="left">
+ <ul>
+ <li><a href="/" title="">Home</a></li>
+ </ul>
+ </nav>
+ <div class="logo"></div>
+ <nav class="right">
+ <ul>
+ <li><a href="{% url 'pages' slug='tour' %}" title="">Tour</a></li>
+ <li><a href="{% url 'pages' slug='howto' %}" title="">How to</a></li>
+ <li><a href="{% url 'logout' %}" title="">Log out</a></li>{% if request.user %}
+ <li><a href="{% url 'settings' %}" title="">Account</a></li>{% else %}
+ <li><a href="{% url 'login' %}" title="">Login</a></li>
+ <li><a href="{% url 'django_registration_register' %}" title="" class="btn">Get Started</a></li>{% endif %}
+ </ul>
+ </nav>
+ </header>
+ </div>
+ <div class="wrapper">
+{% block content %}
+{% endblock %}
+ </div>
+ <footer>
+ <p>&copy; Luxagraf Software. Problems or questions? Contact <a href="{% url 'pages' slug='terms-of-service' %}" title="">support@notes.tld</a>.</p>
+ <nav>
+ <ul>
+ <li><a href="{% url 'pages' slug='terms-of-service' %}" title="">Terms of Service</a></li>
+ <li><a href="{% url 'pages' slug='privacy' %}" title="">Privacy</a></li>
+ <li><a href="{% url 'pages' slug='about' %}" title="">About</a></li>
+ <li><a href="{% url 'pages' slug='faq' %}" title="">FAQ</a></li>
+ <li><a href="{% url 'pages' slug='resources' %}" title="">Resources</a></li>
+ <li><a href="{% url 'pages' slug='security' %}" title="">Security</a></li>
+ </ul>
+ </nav>
+ </footer>
+</body>
diff --git a/design/templates/django_registration/activation_complete.html b/design/templates/django_registration/activation_complete.html
new file mode 100644
index 0000000..914be51
--- /dev/null
+++ b/design/templates/django_registration/activation_complete.html
@@ -0,0 +1,8 @@
+
+{% extends 'base.html' %}
+{% block content %}
+<main>
+<h1>Your account is now active.</h1>
+<p><a href="{% url 'login' %}">Login</a> and get started! <?p>
+</main>
+{% endblock %}
diff --git a/design/templates/django_registration/activation_email_body.txt b/design/templates/django_registration/activation_email_body.txt
new file mode 100644
index 0000000..8b1e134
--- /dev/null
+++ b/design/templates/django_registration/activation_email_body.txt
@@ -0,0 +1,5 @@
+Thanks for signing up with {{site}}, just one more thing to do, click this link to confirm your email:
+
+<a href="{{scheme}}://{{site}}/activate/{{activation_key}}">{{scheme}}://{{site}}/register/{{activation_key}}</a>
+
+
diff --git a/design/templates/django_registration/activation_email_subject.txt b/design/templates/django_registration/activation_email_subject.txt
new file mode 100644
index 0000000..ea275d2
--- /dev/null
+++ b/design/templates/django_registration/activation_email_subject.txt
@@ -0,0 +1 @@
+Please Activate Your Account
diff --git a/design/templates/django_registration/registration_complete.html b/design/templates/django_registration/registration_complete.html
new file mode 100644
index 0000000..e9d0610
--- /dev/null
+++ b/design/templates/django_registration/registration_complete.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% block content %}
+<main>
+<h1>Thanks for signing up.</h1>
+<p>Please check your email for a link to confirm you new account.<?p>
+</main>
+{% endblock %}
diff --git a/design/templates/django_registration/registration_form.html b/design/templates/django_registration/registration_form.html
new file mode 100644
index 0000000..54e01c5
--- /dev/null
+++ b/design/templates/django_registration/registration_form.html
@@ -0,0 +1,17 @@
+{% extends 'base.html' %}
+{% block content %}
+<form action="" method="post">
+{% csrf_token %}
+{{ form.non_field_errors }}
+{% for field in form %}
+<fieldset {% if field.errors %}class="error"{%endif%}>
+{{field.label_tag}}
+{{field}}
+{% if field.label == "Password" %}<span class="helptext">Password should be 8 or more characters.</span>{% endif %}
+{% if field.errors %}{{field.errors}}{% endif %}
+</fieldset>
+{% endfor %}
+<p><input class="btn" value="submit" type="submit" /></p>
+</form>
+<p class="text-muted">Already have an account? <a href="{% url 'login' %}">Log in</a>.</p>
+{% endblock %}
diff --git a/design/templates/notes/create.html b/design/templates/notes/create.html
new file mode 100644
index 0000000..3bd765d
--- /dev/null
+++ b/design/templates/notes/create.html
@@ -0,0 +1,18 @@
+{% extends 'base.html' %}
+{% block content %}
+<main>
+ <h1>Create a new note</h1>
+<form action="" method="post">
+{% csrf_token %}
+{{ form.non_field_errors }}
+{% for field in form %}
+<fieldset {% if field.errors %}class="error"{%endif%}>
+{{field.label_tag}}
+{{field}}
+{% if field.errors %}{{field.errors}}{% endif %}
+</fieldset>
+{% endfor %}
+<p><input class="btn" value="submit" type="submit" /></p>
+</form>
+</main>
+{% endblock %}
diff --git a/design/templates/notes/note_list.html b/design/templates/notes/note_list.html
new file mode 100644
index 0000000..762d05a
--- /dev/null
+++ b/design/templates/notes/note_list.html
@@ -0,0 +1,9 @@
+{% extends 'base.html' %}
+{% block content %}
+<main>
+ <h1> Notes</h1>
+ <ul>{% for obj in object_list %}
+ <li>{{obj}}</li>
+ {% endfor %}</ul>
+</main>
+{% endblock %}
diff --git a/design/templates/pages/page.html b/design/templates/pages/page.html
new file mode 100644
index 0000000..3ac5795
--- /dev/null
+++ b/design/templates/pages/page.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% block content %}
+<main>
+ <h1>{{page.title}}</h1>
+ {{page.body_html|safe}}
+</main>
+{% endblock %}
diff --git a/design/templates/registration/login.html b/design/templates/registration/login.html
new file mode 100644
index 0000000..13b1c6c
--- /dev/null
+++ b/design/templates/registration/login.html
@@ -0,0 +1,35 @@
+{% extends 'base.html' %}
+{% block content %}
+{% if form.errors %}
+<p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+{% if next %}
+ {% if user.is_authenticated %}
+ <p>Your account doesn't have access to this page. To proceed,
+ please login with an account that has access.</p>
+ {% else %}
+ <p>Please login to see this page.</p>
+ {% endif %}
+{% endif %}
+
+<form method="post" action="{% url 'login' %}">
+{% csrf_token %}
+<table>
+<tr>
+ <td>{{ form.username.label_tag }}</td>
+ <td>{{ form.username }}</td>
+</tr>
+<tr>
+ <td>{{ form.password.label_tag }}</td>
+ <td>{{ form.password }}</td>
+</tr>
+</table>
+
+<input type="submit" value="login">
+<input type="hidden" name="next" value="{{ next }}">
+</form>
+
+{# Assumes you setup the password_reset view in your URLconf #}
+<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
+{% endblock %}
diff --git a/functional_tests.py b/functional_tests.py
new file mode 100644
index 0000000..9eedef4
--- /dev/null
+++ b/functional_tests.py
@@ -0,0 +1,12 @@
+from selenium import webdriver
+
+
+options = webdriver.ChromeOptions()
+options.add_argument('--headless')
+options.add_argument('--disable-gpu')
+browser = webdriver.Chrome("/usr/local/bin/chromedriver", chrome_options=options)
+browser.get('http://127.0.0.1:8000')
+assert 'Django' in browser.title
+browser.get('http://127.0.0.1:8000/admin/')
+assert 'Django' in browser.title
+
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..e9c4109
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+import os
+import sys
+d = os.path.dirname(os.path.abspath(__file__))
+sys.path.append(d+"/apps")
+sys.path.append(d+"/config")
+if __name__ == '__main__':
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)