diff options
author | luxagraf <sng@luxagraf.net> | 2018-11-29 16:57:26 -0600 |
---|---|---|
committer | luxagraf <sng@luxagraf.net> | 2018-11-29 16:57:26 -0600 |
commit | 4974eb58480f413c67f5f6e8fac430186eda2b62 (patch) | |
tree | 7402ecba682dbda38db4b6db221cc8378a390977 | |
parent | 0c2a092e8d8ad33a1c306ee9efca0da96eb56415 (diff) |
uploading all recent changes ahead of sys upgrade
29 files changed, 950 insertions, 71 deletions
@@ -9,3 +9,6 @@ htmlcov/ .sass-cache/ media/ static/ +scripts/node_modules +scripts/package-lock.json +scripts/quill @@ -38,3 +38,6 @@ Create, edit and view notes #### [Django-Extensions](https://github.com/django-extensions/django-extensions) * Because I'd go crazy without it. shell_plus is a godsend + +## Art credits +<div>Icons made by <a href="https://www.freepik.com/" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> diff --git a/apps/accounts/models.py b/apps/accounts/models.py index 5ed7634..d000e3e 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -1,5 +1,8 @@ from django.db import models from django.contrib.auth.models import AbstractUser +from django.utils.functional import cached_property + +from notes.models import Notebook class User(AbstractUser): @@ -10,7 +13,7 @@ class User(AbstractUser): class UserProfile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') 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='') @@ -20,3 +23,7 @@ class UserProfile(models.Model): def __str__(self): return self.user.username + + @cached_property + def get_notebook_list(self): + return Notebook.objects.filter(owner=self.user).select_related()[:8] diff --git a/apps/accounts/views.py b/apps/accounts/views.py index e51c60e..d463522 100644 --- a/apps/accounts/views.py +++ b/apps/accounts/views.py @@ -21,7 +21,7 @@ class ProfileView(UpdateViewWithUser): template_name = "accounts/profile.html" def get_object(self): - return self.request.user.userprofile + return self.request.user.profile class SettingsListView(DetailView): @@ -29,4 +29,4 @@ class SettingsListView(DetailView): template_name = "accounts/profile.html" def get_object(self): - return self.request.user.userprofile + return self.request.user.profile diff --git a/apps/notes/forms.py b/apps/notes/forms.py index a7a4f8d..c1df176 100644 --- a/apps/notes/forms.py +++ b/apps/notes/forms.py @@ -1,7 +1,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ -from .models import Note +from .models import Note, Notebook class NoteForm(forms.ModelForm): @@ -15,3 +15,12 @@ class NoteForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) super(NoteForm, self).__init__(*args, **kwargs) + + +class NotebookForm(NoteForm): + class Meta: + model = Notebook + fields = ['name'] + labels = { + "name": _("Notebook Name"), + } diff --git a/apps/notes/models.py b/apps/notes/models.py index 83766a9..c0b23ce 100644 --- a/apps/notes/models.py +++ b/apps/notes/models.py @@ -33,6 +33,10 @@ class Notebook(models.Model): self.slug = unique_slug_generator(self) super(Notebook, self).save() + @cached_property + def get_absolute_url(self): + return reverse("notes:notebook-detail", kwargs={"user": self.owner.username, "slug": self.slug}) + class Note(models.Model): unique_id = models.UUIDField(default=uuid.uuid4, editable=False) diff --git a/apps/notes/serializers.py b/apps/notes/serializers.py index 2fbcc1f..daef52e 100644 --- a/apps/notes/serializers.py +++ b/apps/notes/serializers.py @@ -13,6 +13,8 @@ class NoteSerializer(TaggitSerializer, serializers.ModelSerializer): class NotebookSerializer(serializers.HyperlinkedModelSerializer): + json_absolute_url = serializers.URLField(source='get_absolute_url', read_only=True) + class Meta: model = Notebook - fields = ('name',) + fields = ('name', 'json_absolute_url') diff --git a/apps/notes/urls.py b/apps/notes/urls.py index ccfcc9e..a79c7cd 100644 --- a/apps/notes/urls.py +++ b/apps/notes/urls.py @@ -5,12 +5,18 @@ from .views import ( NoteCreateView, NoteListView, NoteListRedirectView, + NotebookListView, + NotebookDetailView, + NoteTagView, ) app_name = "notes" urlpatterns = [ path(r'create/', NoteCreateView.as_view(), name='note-create',), + path(r'<str:user>/tagged/<slug>', NoteTagView.as_view(), name='note-tag',), + path(r'<str:user>/notebooks/<slug>', NotebookDetailView.as_view(), name='notebook-detail',), + path(r'<str:user>/notebooks/', NotebookListView.as_view(), name='notebook-list',), path(r'<str:user>/<slug>', NoteDetailView.as_view(), name='note-detail',), path(r'<str:user>/', NoteListView.as_view(), name='note-list',), path(r'', NoteListRedirectView.as_view(), name='note-redirect',), diff --git a/apps/notes/views.py b/apps/notes/views.py index b971390..d885ad9 100644 --- a/apps/notes/views.py +++ b/apps/notes/views.py @@ -13,7 +13,7 @@ from rest_framework import permissions from .serializers import NoteSerializer, NotebookSerializer from .models import Note, Notebook -from .forms import NoteForm +from .forms import NoteForm, NotebookForm @method_decorator(login_required, name='dispatch') @@ -40,6 +40,35 @@ class NoteListView(LoggedInViewWithUser, ListView): return ['sell.html'] +class NoteTagView(LoggedInViewWithUser, ListView): + model = Note + template_name = 'notes/notes_list.html' + + def get_queryset(self): + ''' + This can generate a crazy amount of joins if there's a lot of tags + have to keep an eye on it. Would be better to do: + from django.db.models import Q + from functools import reduce + from operator import and_, or_ + #query = reduce(and_, (Q(tags__slug=t) for t in self.tag_list)) + # Note.objects.filter(query, owner=self.request.user) + But that doesn't work for some reason. + ''' + if not self.request.user.is_anonymous: + self.tag_list = [x.strip() for x in self.kwargs['slug'].split("+")] + qs = Note.objects.filter(owner=self.request.user) + for tag in self.tag_list: + qs = qs.filter(tags__slug=tag) + return qs + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['notes_list'] = Note.objects.filter(owner=self.request.user) + context['tags'] = self.tag_list + return context + + class NoteListRedirectView(RedirectView, LoggedInViewWithUser): def get_redirect_url(self, *args, **kwargs): @@ -75,6 +104,34 @@ class NoteCreateView(CreateView, LoggedInViewWithUser): return reverse_lazy('notes:note-detail', kwargs={'user': self.request.user.username, 'slug': self.object.slug}) +class NotebookListView(CreateView, LoggedInViewWithUser): + model = Notebook + form_class = NotebookForm + template_name = 'notes/notebook_create.html' + + def get_queryset(self): + if not self.request.user.is_anonymous: + return Notebook.objects.filter(owner=self.request.user) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['notebook_list'] = Notebook.objects.filter(owner=self.request.user) + context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() + return context + + +class NotebookDetailView(DetailView, LoggedInViewWithUser): + model = Notebook + + def get_object(self): + return get_object_or_404(self.get_queryset().select_related(), owner=self.request.user, slug=self.kwargs["slug"]) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() + return context + + class IsOwnerOrDeny(permissions.BasePermission): """ Custom permission to only allow owners to post to their endpoint @@ -118,3 +175,6 @@ class NotebookViewSet(viewsets.ModelViewSet): def perform_create(self, serializer): serializer.save(owner=self.request.user) + + def get_menu_data(self, serializer): + return Notebook.objects.filter(owner=self.request.user).order_by('-name')[:1] diff --git a/apps/utils/util.py b/apps/utils/util.py index 899b73f..415704c 100644 --- a/apps/utils/util.py +++ b/apps/utils/util.py @@ -95,7 +95,10 @@ def unique_slug_generator(instance, new_slug=None): if new_slug is not None: slug = new_slug else: - slug = slugify(instance.title) + try: + slug = slugify(instance.title) + except AttributeError: + slug = slugify(instance.name) Klass = instance.__class__ qs_exists = Klass.objects.filter(slug=slug).exists() if qs_exists: diff --git a/config/base_urls.py b/config/base_urls.py index 953ceae..c881679 100644 --- a/config/base_urls.py +++ b/config/base_urls.py @@ -15,10 +15,15 @@ from notes.views import ( NotebookViewSet, NoteListView, ) + +# redirect admin as per Adrian holovaty's site +ADMIN_URL = 'https://docs.djangoproject.com/en/dev/ref/contrib/admin/' + router = routers.DefaultRouter() -router.register(r'notes/notebook/', NotebookViewSet, basename="notebook-api") +router.register(r'notes/notebook', NotebookViewSet, basename="notebook-api") router.register(r'notes', NoteViewSet, basename="notes-api") -ADMIN_URL = 'https://docs.djangoproject.com/en/dev/ref/contrib/admin/' + + urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += [ path('admin/', RedirectView.as_view(url=ADMIN_URL)), @@ -31,11 +36,12 @@ urlpatterns += [ path(r'', include('django.contrib.auth.urls')), path(r'', NoteListView.as_view(), name='homepage',), path(r'notes/', include('notes.urls')), - path(r'api/', include(router.urls)), + path(r'api/v1/', include(router.urls)), path(r'<slug>', PageDetailView.as_view(), name="pages"), #path(r'<path>/<slug>/', PageDetailView.as_view(), name="pages"), path(r'api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] +''' if settings.DEBUG: import debug_toolbar urlpatterns = [ @@ -45,3 +51,4 @@ if settings.DEBUG: # url(r'^__debug__/', include(debug_toolbar.urls)), ] + urlpatterns +''' diff --git a/config/settings.py b/config/settings.py index b95c18d..da75f57 100644 --- a/config/settings.py +++ b/config/settings.py @@ -64,7 +64,7 @@ THIRD_PARTY_APPS = [ 'taggit_serializer', 'django_extensions', 'rest_framework', - 'debug_toolbar' + #'debug_toolbar' ] LOCAL_APPS = [ 'utils', @@ -80,7 +80,7 @@ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', + #'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', diff --git a/design/sass/_forms.scss b/design/sass/_forms.scss index d6b0931..5923e1f 100644 --- a/design/sass/_forms.scss +++ b/design/sass/_forms.scss @@ -101,6 +101,40 @@ table { color: $link_color !important; } } +.btn-hollow { + @include fontsize(17); + padding: 6px 8px; + border: none; //1px solid $body_font_light; + color: $link_color !important; + outline: $link_color !important; + background: white; + border: 1px solid $link_color; + &:hover { + background: white; + color: $link_color !important; + } +} +.btn-link { + @include fontsize(15); + padding: 0; + border: none; //1px solid $body_font_light; + color: lighten($body_font_color, 15) !important; + background: white; + border: none; + &:hover { + background: white; + color: $body_font_color !important; + } +} +.save { + color: white !important; + background: $link_color !important; + &:hover { + background: $link_color; + color: white !important; + } + +} .btn-accent { padding: 3px 5px; border: 1px solid $text_accent; diff --git a/design/sass/_global.scss b/design/sass/_global.scss index d394042..c242ace 100644 --- a/design/sass/_global.scss +++ b/design/sass/_global.scss @@ -152,7 +152,7 @@ h3 { } } .wrapper { - @include constrain_wide; + @include constrain(1440px); //margin-top: 5rem; } //************** Universals ************************ @@ -186,6 +186,21 @@ h3 { .left-margin-2 { margin-left: 2px; } +.right-padding-0 { + padding-right: 0 !important; +} +.center { + text-align: center; + margin-right: auto; + margin-left: auto; +} +.list-style-none { + list-style-type: none; + padding: 0; +} +.vertical li { + display: block; +} //************** other global classes ************************ .sans { @include generic_sans; diff --git a/design/sass/_header.scss b/design/sass/_header.scss index 7f98419..487f1fc 100644 --- a/design/sass/_header.scss +++ b/design/sass/_header.scss @@ -54,3 +54,47 @@ nav { } } } +.dropmenu { + outline: white; + position: absolute; + z-index: 10; + margin-top: 4px; + margin-left: 2rem; + background: #fff; + color: #60516E; + padding: 10px; + border-radius: 4px; + border: 1px solid rgba(0,0,0,0.05); + filter: drop-shadow(0 0px 4px rgba(0,0,0,0.08)); + &:after { + top: unset; + left: unset; + margin-left: unset; + border-top-color: unset; + content: ""; + position: absolute; + border: 10px solid transparent; + bottom: 100%; + left: 50%; + margin-left: -10px; + border-bottom-color: white; + } + a, a:visited { + color: lighten($body_font_color, 15); + } + a:hover { + color: $body_font_color; + } + .menu-divider { + border-top: 1px solid #e7e2ee; + padding: 8px 16px 0; + margin: 8px -10px 0; + } + li { + margin-top: 4px; + margin-bottom: 4px; + } +} +#user-menu { + margin-left: 4rem; +} diff --git a/design/sass/_notes.scss b/design/sass/_notes.scss index 09d234a..cd10e39 100644 --- a/design/sass/_notes.scss +++ b/design/sass/_notes.scss @@ -1,25 +1,163 @@ +main { + width: 100%; + display: flex ; + align-items: stretch ; + flex-flow: row wrap ; + margin: 0; +} +.svg-icon-arrow { + display: block; + z-index: 2; + width: 18px; + height: 18px; + &:hover { + -moz-transform: scaleX(-1); + -o-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + transform: scaleX(-1); + filter: FlipH; + -ms-filter: "FlipH"; + transition: 0.5s; + } +} +.note-list-container, .balance-container { + position: relative; + order: 1; + z-index: 1; + visibility: hidden; + //background: #fbfafa; + padding: 4px; + border-left: 1px #f9f9f9 solid; + max-width: 320px; + .list-note-preview { + li { + height: 4.5rem; + } + h4 { + @include fontsize(15); + } + } + .note-preview { + overflow: hidden; + white-space: nowrap; + } +} +.balance-container { + order: 3; + min-width: 320px; +} +.list-notebook { + +} +.icon-notebook { + display: inline-block; + content: " "; + width: 24px; + height: 24px; + background: url('/media/moleskine.svg'); +} +.list-note-preview { + padding: 0; + margin: 0; + list-style-type: none; + li { + @include fontsize(13); + min-height: 4.5rem; + box-shadow: 0 -1px 0 #e7e2ee inset; + margin: 0; + padding: 6px 12px; + &:hover { + background: #f1f1f1; + } + a { + color: lighten($body_font_color, 15); + text-decoration: none; + } + } + h4 { + @include fontsize(16); + margin: 0; + padding: 8px 0 8px 6px; + font-weight: normal; + overflow: hidden; + white-space: nowrap; + } + .note-preview { + padding-left: 6px; + margin-bottom: 16px; + margin-right: 6px; + } +} .note-title { - @include fontsize(22); + @include fontsize(26); } .note-header { @extend %clearfix; } -.note-header-float { - width: 30%; - float: right; +.note-header-right { text-align: right; + float: right; + width: 50%; } .note-time, .note-url { text-align: right; @include fancy-sans; + @include fontsize(12); + margin-bottom: 0; + text-transform: uppercase; +} +.note-url { + margin-top: 0; +} +.note-header { + margin-top: 1rem; + margin-bottom: 3rem; +} +.edit-btn-wrapper{ + text-align: right; +} +.note-header-left { + @include fancy-sans; @include fontsize(13); + float: left; + width: 50%; + .label { + text-transform: uppercase; + @include fontsize(11); + color: $body_font_light; + margin-right: 4px; + } + ul { + display: inline-block; + padding: 0; + margin-bottom: 0; + } + li { + margin-right: 4px; + display: inline-block; + } + a { + text-decoration: none; + color: darken($body_font_light, 5);; + &:hover { + color: darken($body_font_light, 25); + } + } +} +.notebook { + display: block; } .note-container { - @include constrain(80%); + max-width: 60%; + position: relative; + flex:1; + order: 2; + background: #fff; + z-index: 4; } #note-body { @include fancy-sans; - @include fontsize(15); + @include fontsize(14); } .inactive { .ql-editor { @@ -39,33 +177,94 @@ font-size:inherit; } } -.note-list-container { - max-width: 300px; - ul { - padding: 0; - list-style-type: none; - } - li { - @include fontsize(13); - height: 4.5rem; - box-shadow: 0 -1px 0 #e7e2ee inset; - margin: 0; - a { - color: lighten($body_font_color, 15); - text-decoration: none; - } - } - h4 { - @include fontsize(15); - margin: 0; - padding: 8px 0 8px 6px; - font-weight: normal; - overflow: hidden; - white-space: nowrap; - } - .note-preview { - padding-left: 6px; - overflow: hidden; - white-space: nowrap; - } +#user-menu, #notebooks-menu { + display: none; +} +.active { + display: block !important; +} + +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com> + +*/ + +.hljs, pre.ql-syntax { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #002b36 !important; + color: #839496; +} + +.hljs-comment, +.hljs-quote { + color: #586e75; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-string, +.hljs-meta .hljs-meta-string, +.hljs-literal, +.hljs-doctag, +.hljs-regexp { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-link { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-built_in, +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #073642; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; } diff --git a/design/templates/base.html b/design/templates/base.html index f14e5df..bb54717 100644 --- a/design/templates/base.html +++ b/design/templates/base.html @@ -20,8 +20,16 @@ <div class="head-wrapper"> <header> <nav class="left"> - <ul> - <li><a href="/" title="">Home</a></li> + <ul>{% if not request.user.is_anonymous %} + <li><a href="{%url 'notes:note-list' user.username %}">Notes</a></li> + <li><a id="notebook-menu-link" href="{%url 'notes:notebook-list' user.username %}" title="view notebooks">Notebooks</a> + <div id="notebooks-menu" class="dropmenu"> + <ul id="notebooks-menu-list" class="list-style-none vertical">{% for obj in user.profile.get_notebook_list %} + <li><a href="{{obj.get_absolute_url}}">{{obj}}</a></li> + {% endfor %}</ul> + </div> + </li>{%else%} + <li><a href="/" title="">Home</a></li>{%endif%} </ul> </nav> <div class="logo"></div> @@ -29,8 +37,23 @@ <ul> <li><a href="{% url 'pages' slug='tour' %}" title="">Tour</a></li> <li><a href="{% url 'pages' slug='howto' %}" title="">How to</a></li>{% if not request.user.is_anonymous %} - <li><a href="{% url 'settings' %}" title="">Account</a></li> - <li><a href="{% url 'logout' %}" title="">Log out</a></li>{% else %} + <li><a id="account-menu" href="{% url 'settings' %}" title="">Account</a> + <div id="user-menu" class="dropmenu" tabindex="-1"> + <ul id="user-menu-list" class="list-style-none vertical"> + <li><a href="/users/luxagraf/" class="">Your Notes</a></li> + <li><a href="/settings/" class="">Account settings</a></li> + <li><a href="/plans/" class="">Subscription plans</a></li> + </ul> + <ul class="menu-divider list-style-none"> + <li> + <form action="/logout/" method="post"> + <input type="hidden" name="a" value="3"> + <button type="submit" value="Log out" class="btn-link">Log out</button> + </form> + </li> + </ul> + </div> + </li>{% else %} <li><a href="{% url 'login' %}" title="" id="overlay-trigger" data-element="#js-overlay-content">Login</a></li> <li><a href="{% url 'django_registration_register' %}" title="" class="btn">Get Started</a></li>{% endif %} </ul> @@ -38,12 +61,12 @@ </header> </div> <div class="wrapper"> - <ul class="breadcrumb" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + {%comment%}<ul class="breadcrumb" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> <li> <a href="/" title="home" itemprop="url"><span itemprop="title">Home</span></a> </li> {% block breadcrumbs %}{% endblock %} - </ul> + </ul>{%endcomment%} {% block content %} {% endblock %} </div> @@ -65,9 +88,11 @@ <script async src="/media/js/package.min.js"></script> {% block jsinclude %}{%endblock%} <script> -// Waiting for the DOM to load + + document.addEventListener("DOMContentLoaded", function () { {% block jsdomready %}{%endblock%} + // Notebook menu }); </script> </body> diff --git a/design/templates/notes/notebook_create.html b/design/templates/notes/notebook_create.html new file mode 100644 index 0000000..8ffc94a --- /dev/null +++ b/design/templates/notes/notebook_create.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% block extrastyles %} +<link rel="stylesheet" href="/media/quill.snow.css" /> +{% endblock %} +{% block content %} +<main> + <article class="note-container"> + <form id="new-notebook-form" action="{% url 'notebook-api-list' %}" method="post"> +{% csrf_token %} +{{ form.non_field_errors }} +{% for field in form %} +<fieldset class="{% if field.errors %}error {%endif%}{% if field.name == 'body_qjson' or field.name == 'body_html' %}hide {%endif%}" id="fs-{{field.name}}" > +{{field.label_tag}} +{{field}} +{% if field.errors %}{{field.errors}}{% endif %} +</fieldset> +{% endfor %} +<p><input class="btn btn-inline" value="create" type="submit" /></p> +</form> + <ul>{% for object in notebook_list %} + <li> + <a href="{% url 'notes:notebook-detail' user.username object.slug %}"><i class="icon-notebook"> </i>{{object.name}}</a> + </li> + {%endfor%}</ul> + </article> + <aside class="note-list-container"> + <div class="svg-wrapper"><svg class="svg-icon-arrow"> + <svg viewBox="0 0 16 13" id="shape-double-arrow" width="100%" height="100%"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square"><g id="Showing-post-info" transform="translate(-297.000000, -105.000000)" stroke="currentColor"><g transform="translate(305.000000, 111.500000) scale(-1, 1) rotate(-180.000000) translate(-305.000000, -111.500000) translate(297.000000, 105.000000)"><path d="M2.20710678,6.5 L6.85355339,1.85355339 L7.20710678,1.5 L6.5,0.792893219 L6.14644661,1.14644661 L1.14644661,6.14644661 L1.05805826,6.23483496 L0.792893219,6.5 L1.14644661,6.85355339 L6.14644661,11.8535534 L6.5,12.2071068 L7.20710678,11.5 L6.85355339,11.1464466 L2.20710678,6.5 Z" id="Combined-Shape"></path><path d="M10.2071068,6.5 L14.8535534,1.85355339 L15.2071068,1.5 L14.5,0.792893219 L14.1464466,1.14644661 L9.14644661,6.14644661 L9.05805826,6.23483496 L8.79289322,6.5 L9.14644661,6.85355339 L14.1464466,11.8535534 L14.5,12.2071068 L15.2071068,11.5 L14.8535534,11.1464466 L10.2071068,6.5 Z" id="Combined-Shape"></path></g></g></g></svg> + </svg></div> + <div class=""> + <ul class="list-note-preview">{% for obj in notes_list %} + <li> + <a href="{% url 'notes:note-detail' user.username obj.slug %}"> + <h4>{{obj.title}}</h4> + <div class="note-preview">{{obj.body_text|truncatewords:12}}</div> + </a> + </li> + {% endfor %}</ul> + </div> + </aside> + <div class="balance-container"> + </div> +</main> +{% endblock %} + diff --git a/design/templates/notes/notebook_detail.html b/design/templates/notes/notebook_detail.html new file mode 100644 index 0000000..36873e1 --- /dev/null +++ b/design/templates/notes/notebook_detail.html @@ -0,0 +1,49 @@ +{% extends 'base.html' %} + +{% block content %} +<main> + <article class="note-container"> + {%comment%} + <form id="new-note-form" action="{% url 'notebook-api-list' %}" method="post"> + <label>Create a new notebook</label> + {% csrf_token %} + {{ form.non_field_errors }} + {% for field in form %} + <fieldset class="{% if field.errors %}error {%endif%}{% if field.name == 'body_qjson' or field.name == 'body_html' %}hide {%endif%}" id="fs-{{field.name}}" > + {{field.label_tag}} + {{field}} + {% if field.errors %}{{field.errors}}{% endif %} + </fieldset> + {% endfor %} + <p><input class="btn btn-inline" value="create" type="submit" /></p> + </form> + {%endcomment%} +{{object}} + <ul class="list-note-preview">{% for obj in object.note_set.all %} + <li> + <a href="{% url 'notes:note-detail' user.username obj.slug %}"> + <h4>{{obj.title}}</h4> + <div class="note-preview">{{obj.body_text|truncatewords:36}}</div> + </a> + </li> + {% endfor %}</ul> + </article> + <aside class="note-list-container"> + <div class="svg-wrapper"><svg class="svg-icon-arrow"> + <svg viewBox="0 0 16 13" id="shape-double-arrow" width="100%" height="100%"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square"><g id="Showing-post-info" transform="translate(-297.000000, -105.000000)" stroke="currentColor"><g transform="translate(305.000000, 111.500000) scale(-1, 1) rotate(-180.000000) translate(-305.000000, -111.500000) translate(297.000000, 105.000000)"><path d="M2.20710678,6.5 L6.85355339,1.85355339 L7.20710678,1.5 L6.5,0.792893219 L6.14644661,1.14644661 L1.14644661,6.14644661 L1.05805826,6.23483496 L0.792893219,6.5 L1.14644661,6.85355339 L6.14644661,11.8535534 L6.5,12.2071068 L7.20710678,11.5 L6.85355339,11.1464466 L2.20710678,6.5 Z" id="Combined-Shape"></path><path d="M10.2071068,6.5 L14.8535534,1.85355339 L15.2071068,1.5 L14.5,0.792893219 L14.1464466,1.14644661 L9.14644661,6.14644661 L9.05805826,6.23483496 L8.79289322,6.5 L9.14644661,6.85355339 L14.1464466,11.8535534 L14.5,12.2071068 L15.2071068,11.5 L14.8535534,11.1464466 L10.2071068,6.5 Z" id="Combined-Shape"></path></g></g></g></svg> + </svg></div> + <div class=""> + <ul class="list-note-preview">{% for obj in notes_list %} + <li> + <a href="{% url 'notes:note-detail' user.username obj.slug %}"> + <h4>{{obj.title}}</h4> + <div class="note-preview">{{obj.body_text|truncatewords:12}}</div> + </a> + </li> + {% endfor %}</ul> + </div> + </aside> + <div class="balance-container"> + </div> +</main> +{% endblock %} diff --git a/design/templates/notes/notes_detail.html b/design/templates/notes/notes_detail.html index bf46ab8..95583bd 100644 --- a/design/templates/notes/notes_detail.html +++ b/design/templates/notes/notes_detail.html @@ -10,12 +10,24 @@ <main> <article class="note-container"> <header class="note-header"> - <button class="hide btn btn-accent" id="edit-toggle-btn">Edit</button> - <div class="note-header-float"> + <div class="note-header-left"> + <span class="label">tags:</span> + <ul>{% for tag in object.tags.all %} + <li> + <a href="{% url 'notes:note-tag' user.username tag.slug %}">{{tag}}</a> + </li> + {%endfor%}</ul> + <div class="notebook"> + <span class="label">notebook:</span> + <a href="">{{object.notebook.name}}</a> + </div> + </div> + <div class="note-header-right"> <h2 class="note-time">{{object.date_created|date:"M d, Y"}}</h2> - {% if object.url %}<h3 class="note-url"><a class="btn btn-small btn-subtle" href="{{object.url}}">Source</a><a class="btn btn-small btn-subtle left-margin-2" href="object.cache">Archive</a></h3>{% endif %} + {% if object.url %}<h3 class="note-url"><a class="btn btn-small btn-subtle" href="{{object.url}}">Source</a><a class="btn btn-small btn-subtle right-padding-0 left-margin-2" href="object.cache">Archive</a></h3>{% endif %} </div> </header> + <div class="edit-btn-wrapper"><button class="hide btn btn-hollow" id="edit-toggle-btn">Edit</button></div> <h1 id="note-title" class="note-title">{{object.title}}</h1> <div id="q-container" class="inactive"><div id="note-body">{% if object.body_html %}{{object.body_html|safe}}{%else%}{{object.body_text}}{%endif%}</div></div> <form action="" method="post" id="note-edit-form">{% csrf_token %} @@ -26,8 +38,11 @@ </form> </article> <aside class="note-list-container"> + <div class="svg-wrapper"><svg class="svg-icon-arrow"> + <svg viewBox="0 0 16 13" id="shape-double-arrow" width="100%" height="100%"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square"><g id="Showing-post-info" transform="translate(-297.000000, -105.000000)" stroke="currentColor"><g transform="translate(305.000000, 111.500000) scale(-1, 1) rotate(-180.000000) translate(-305.000000, -111.500000) translate(297.000000, 105.000000)"><path d="M2.20710678,6.5 L6.85355339,1.85355339 L7.20710678,1.5 L6.5,0.792893219 L6.14644661,1.14644661 L1.14644661,6.14644661 L1.05805826,6.23483496 L0.792893219,6.5 L1.14644661,6.85355339 L6.14644661,11.8535534 L6.5,12.2071068 L7.20710678,11.5 L6.85355339,11.1464466 L2.20710678,6.5 Z" id="Combined-Shape"></path><path d="M10.2071068,6.5 L14.8535534,1.85355339 L15.2071068,1.5 L14.5,0.792893219 L14.1464466,1.14644661 L9.14644661,6.14644661 L9.05805826,6.23483496 L8.79289322,6.5 L9.14644661,6.85355339 L14.1464466,11.8535534 L14.5,12.2071068 L15.2071068,11.5 L14.8535534,11.1464466 L10.2071068,6.5 Z" id="Combined-Shape"></path></g></g></g></svg> + </svg></div> <div class=""> - <ul>{% for obj in notes_list %} + <ul class="list-note-preview">{% for obj in notes_list %} <li> <a href="{% url 'notes:note-detail' user.username obj.slug %}"> <h4>{{obj.title}}</h4> @@ -37,12 +52,24 @@ {% endfor %}</ul> </div> </aside> + <div class="balance-container"> + </div> </main> {% endblock %} {% block jsinclude %} <script src="/media/js/highlight.pack.js"></script> <script src="/media/js/quill.min.js"></script> +<script> +document.addEventListener('readystatechange', event => { + if (event.target.readyState === "interactive") { + //initLoader(); + } + else if (event.target.readyState === "complete") { + initQuill("#note-body"); + } +}); +</script> {% endblock %} <script> {% block jsdomready %} @@ -56,8 +83,8 @@ window.editing = false; window.quillchange = false; + hljs.initHighlightingOnLoad(); btn.classList.remove('hide'); - initQuill("#note-body"); note_html.setAttribute('name', 'body_html'); note_html.setAttribute('class', 'hide'); note_html.setAttribute('id', 'id_body_html'); @@ -68,7 +95,6 @@ form.appendChild(note_qjson); document.getElementById("btn-js-hide").classList.add("hide"); btn.addEventListener('click', function(){edit_note(this, title, qcontainer, window.quill, "{% url 'notes-api-detail' object.pk %}" )}, false) - {%endblock%} </script> diff --git a/design/templates/notes/notes_list.html b/design/templates/notes/notes_list.html index 8066369..b1c1700 100644 --- a/design/templates/notes/notes_list.html +++ b/design/templates/notes/notes_list.html @@ -1,9 +1,35 @@ {% extends 'base.html' %} + {% block content %} <main> - <h1> Notes</h1> - <ul>{% for obj in object_list %} - <li><a href="{% url 'notes:note-detail' user.username obj.slug %}">{{obj}}</a></li> - {% endfor %}</ul> + <article class="note-container"> + <h1>Notes {% if tags|length == 1%} tagged {% for tag in tags%}{{tag}}{%endfor%}{%endif%}</h1> + <div>Tagged with: {% for tag in tags%}<a href="{{tag|slugify}}">{{tag}}</a>{%endfor%}</div> + <ul class="list-note-preview">{% for obj in object_list %} + <li> + <a href="{% url 'notes:note-detail' user.username obj.slug %}"> + <h4>{{obj.title}}</h4> + <div class="note-preview">{{obj.body_text|truncatewords:36}}</div> + </a> + </li> + {% endfor %}</ul> + </article> + <aside class="note-list-container"> + <div class="svg-wrapper"><svg class="svg-icon-arrow"> + <svg viewBox="0 0 16 13" id="shape-double-arrow" width="100%" height="100%"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square"><g id="Showing-post-info" transform="translate(-297.000000, -105.000000)" stroke="currentColor"><g transform="translate(305.000000, 111.500000) scale(-1, 1) rotate(-180.000000) translate(-305.000000, -111.500000) translate(297.000000, 105.000000)"><path d="M2.20710678,6.5 L6.85355339,1.85355339 L7.20710678,1.5 L6.5,0.792893219 L6.14644661,1.14644661 L1.14644661,6.14644661 L1.05805826,6.23483496 L0.792893219,6.5 L1.14644661,6.85355339 L6.14644661,11.8535534 L6.5,12.2071068 L7.20710678,11.5 L6.85355339,11.1464466 L2.20710678,6.5 Z" id="Combined-Shape"></path><path d="M10.2071068,6.5 L14.8535534,1.85355339 L15.2071068,1.5 L14.5,0.792893219 L14.1464466,1.14644661 L9.14644661,6.14644661 L9.05805826,6.23483496 L8.79289322,6.5 L9.14644661,6.85355339 L14.1464466,11.8535534 L14.5,12.2071068 L15.2071068,11.5 L14.8535534,11.1464466 L10.2071068,6.5 Z" id="Combined-Shape"></path></g></g></g></svg> + </svg></div> + <div class=""> + <ul class="list-note-preview">{% for obj in notes_list %} + <li> + <a href="{% url 'notes:note-detail' user.username obj.slug %}"> + <h4>{{obj.title}}</h4> + <div class="note-preview">{{obj.body_text|truncatewords:12}}</div> + </a> + </li> + {% endfor %}</ul> + </div> + </aside> + <div class="balance-container"> + </div> </main> {% endblock %} diff --git a/design/templates/notes/notes_listold.html b/design/templates/notes/notes_listold.html new file mode 100644 index 0000000..8066369 --- /dev/null +++ b/design/templates/notes/notes_listold.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} +{% block content %} +<main> + <h1> Notes</h1> + <ul>{% for obj in object_list %} + <li><a href="{% url 'notes:note-detail' user.username obj.slug %}">{{obj}}</a></li> + {% endfor %}</ul> +</main> +{% endblock %} diff --git a/design/templates/sell.html b/design/templates/sell.html new file mode 100644 index 0000000..001823b --- /dev/null +++ b/design/templates/sell.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% block content %} +<main> + <h1> TK Notes</h1> +</main> +{% endblock %} diff --git a/scripts/gulpfile.js b/scripts/gulpfile.js new file mode 100644 index 0000000..67c5bf7 --- /dev/null +++ b/scripts/gulpfile.js @@ -0,0 +1,17 @@ +const { src, dest } = require('gulp'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const concat = require('gulp-concat'); +const babel = require('gulp-babel'); + +exports.default = function() { + return src('src/*.js') + .pipe(babel({ + presets: ['@babel/env'] + })) + .pipe(concat('package.js')) + .pipe(uglify()) + .pipe(dest('../media/js/')) + .pipe(rename({ extname: '.min.js' })) + .pipe(dest('../media/js/')); +} diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 0000000..778b5e6 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,20 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "javascript for aite", + "main": "common.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "luxagraf", + "license": "ISC", + "devDependencies": { + "@babel/core": "^7.1.6", + "@babel/preset-env": "^7.1.6", + "gulp": "^4.0.0", + "gulp-babel": "^8.0.0", + "gulp-concat": "^2.6.1", + "gulp-rename": "^1.4.0", + "gulp-uglify": "^3.0.1" + } +} diff --git a/scripts/src/js.cookie.js b/scripts/src/js.cookie.js new file mode 100644 index 0000000..9a0945e --- /dev/null +++ b/scripts/src/js.cookie.js @@ -0,0 +1,165 @@ +/*! + * JavaScript Cookie v2.2.0 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +;(function (factory) { + var registeredInModuleLoader = false; + if (typeof define === 'function' && define.amd) { + define(factory); + registeredInModuleLoader = true; + } + if (typeof exports === 'object') { + module.exports = factory(); + registeredInModuleLoader = true; + } + if (!registeredInModuleLoader) { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function () { + window.Cookies = OldCookies; + return api; + }; + } +}(function () { + function extend () { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[ i ]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function init (converter) { + function api (key, value, attributes) { + var result; + if (typeof document === 'undefined') { + return; + } + + // Write + + if (arguments.length > 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); + attributes.expires = expires; + } + + // We're using "expires" because "max-age" is not supported by IE + attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; + + try { + result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + if (!converter.write) { + value = encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + } else { + value = converter.write(value, key); + } + + key = encodeURIComponent(String(key)); + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); + key = key.replace(/[\(\)]/g, escape); + + var stringifiedAttributes = ''; + + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue; + } + stringifiedAttributes += '; ' + attributeName; + if (attributes[attributeName] === true) { + continue; + } + stringifiedAttributes += '=' + attributes[attributeName]; + } + return (document.cookie = key + '=' + value + stringifiedAttributes); + } + + // Read + + if (!key) { + result = {}; + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling "get()" + var cookies = document.cookie ? document.cookie.split('; ') : []; + var rdecode = /(%[0-9A-Z]{2})+/g; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + if (!this.json && cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + var name = parts[0].replace(rdecode, decodeURIComponent); + cookie = converter.read ? + converter.read(cookie, name) : converter(cookie, name) || + cookie.replace(rdecode, decodeURIComponent); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + if (key === name) { + result = cookie; + break; + } + + if (!key) { + result[name] = cookie; + } + } catch (e) {} + } + + return result; + } + + api.set = api; + api.get = function (key) { + return api.call(api, key); + }; + api.getJSON = function () { + return api.apply({ + json: true + }, [].slice.call(arguments)); + }; + api.defaults = {}; + + api.remove = function (key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.withConverter = init; + + return api; + } + + return init(function () {}); +})); diff --git a/scripts/src/main-nav.js b/scripts/src/main-nav.js new file mode 100644 index 0000000..fa6f25a --- /dev/null +++ b/scripts/src/main-nav.js @@ -0,0 +1,71 @@ +function hideOnClickOutsided(element, btn) { + // given a menu element and btn, hide the menu + // whenever the click is not on either the element + // or the btn the opened it + const outsideClickListener = event => { + if (!element.contains(event.target) && (event.target.id != btn)) { // or use: event.target.closest(selector) === null + if (isVisible(element)) { + element.classList.remove('active') + removeClickListener() + } + } + } + + const removeClickListener = () => { + document.removeEventListener('click', outsideClickListener) + } + document.addEventListener('click', outsideClickListener) +} +const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js + +//---------------------------------------- +//Initialize main menu bar with progressive enhancements +//---------------------------------------------- +// Account Menu +var account_a = document.getElementById("account-menu"); +var account_div = document.getElementById("user-menu"); +//Add button function +account_a.addEventListener('click', function(e){ + e.preventDefault(); + account_div.classList.toggle('active') + account_div.focus(); + hideOnClickOutsided(account_div, account_a.id); +}, false); + +// Notebooks Menu +var notebook_a = document.getElementById("notebook-menu-link"); +var notebook_div = document.getElementById("notebooks-menu"); +//Add button function +notebook_a.addEventListener('click', function(e){ + e.preventDefault(); + notebook_div.classList.toggle('active') + notebook_div.focus(); + hideOnClickOutsided(notebook_div, notebook_a.id); +}, false); + + +//function buildNotebookMenu () { +// var data = JSON.parse(this.responseText); +// var div = document.getElementById("notebooks-menu"); +// var ul = document.createElement("ul"); +// ul.classList.add("vertical","list-style-none"); +// div.appendChild(ul); +// for(var i in data) { +// var li = document.createElement("li"); +// var a = document.createElement("a"); +// a.setAttribute("href", data[i]['json_absolute_url']); +// a.innerHTML = data[i]['name']; +// li.append(a); +// ul.append(li); +// } +// var li = document.createElement("li"); +// var a = document.createElement("a"); +// a.setAttribute("href", '/user/{{user.username}}/notebooks/'); +// a.innerHTML = "View all" +// li.append(a); +// ul.append(li); +//} +// +// +//// getJSON("{%url 'notebook-api-list' %}", buildNotebookMenu); + diff --git a/scripts/overlay.js b/scripts/src/overlay.js index b40d911..b40d911 100644 --- a/scripts/overlay.js +++ b/scripts/src/overlay.js diff --git a/scripts/util.js b/scripts/src/util.js index 99a5ef3..3a4efc4 100644 --- a/scripts/util.js +++ b/scripts/src/util.js @@ -1,21 +1,43 @@ +function getJSON(url, callback) { + var request = new XMLHttpRequest(); + request.addEventListener("load", callback); + request.open('GET', url, true); + request.onload = function() { + if (request.status >= 200 && request.status < 400) { + //console.log(request.responseText); + } else { + console.log("server error"); + } + }; + request.onerror = function() { + console.log("error on request"); + }; + request.send(); +} + function edit_note(btn, title, qcontainer, quill, url){ - console.log(editing); - var formElement = document.querySelector("form"); - if (editing === false) { + var formElement = document.getElementById("note-edit-form"); + if (window.editing === false) { title.setAttribute("contenteditable", true); title.classList.add('highlight') qcontainer.classList.remove('inactive') quill.enable(true); btn.innerHTML = "Save" - editing = true; + btn.classList.add("save"); + window.editing = true; + window.titlecontents = title.innerHTML } else { - if (window.quillchange === true) { + if (window.quillchange === true || window.titlecontents != title.innerHTML) { var form_note_title = document.getElementById('id_title'); var note_html = document.getElementById('id_body_html'); + var note_text = document.getElementById('id_body_text'); var note_qjson = document.getElementById('id_body_qjson'); - form_note_title.value = title.innerHTML; + var new_title = document.getElementById('id_title'); + new_title.value = title.innerHTML; note_html.innerHTML = quill.root.innerHTML; + note_text.innerHTML = quill.getText(); note_qjson.innerHTML = JSON.stringify(quill.getContents()); + console.log(note_text); var request = new XMLHttpRequest(); request.open("PATCH", url); var csrftoken = Cookies.get('csrftoken'); @@ -39,6 +61,7 @@ function edit_note(btn, title, qcontainer, quill, url){ qcontainer.classList.add('inactive'); quill.enable(false); btn.innerHTML = "Edit" + btn.classList.remove("save"); document.body.focus(); editing = false; } |