diff --git a/clipping_manager/templates/clipping_manager/books.html b/clipping_manager/templates/clipping_manager/books.html new file mode 100644 index 0000000..6950c97 --- /dev/null +++ b/clipping_manager/templates/clipping_manager/books.html @@ -0,0 +1,54 @@ +{% extends "content.html" %} +{% load i18n %} +{% load staticfiles sekizai_tags %} + +{% block content %} +
+
+
+ + + + + +
+ +
+
+
+ + +
+
+
+ + + {% addtoblock "js" %} + + + {% endaddtoblock %} + +{% endblock %} \ No newline at end of file diff --git a/clipping_manager/templates/clipping_manager/dashboard.html b/clipping_manager/templates/clipping_manager/dashboard.html index 9d38254..2698cd4 100644 --- a/clipping_manager/templates/clipping_manager/dashboard.html +++ b/clipping_manager/templates/clipping_manager/dashboard.html @@ -21,6 +21,9 @@

Manage Clippings

Browse Your Clippings + + Browse Your Books +

You can import clippings from your Amazon Kindle or a simple text file.

diff --git a/clipping_manager/templates/clipping_manager/partials/book_list_item.html b/clipping_manager/templates/clipping_manager/partials/book_list_item.html new file mode 100644 index 0000000..08248ae --- /dev/null +++ b/clipping_manager/templates/clipping_manager/partials/book_list_item.html @@ -0,0 +1,20 @@ +{% load urlparams %} + +
+ +
+
+ plain book cover +
+
+ {{book.title|default:_("unknown")}} +
+
+ {{book.author_name|default:_("unknown")}} +
+
+ {{book.clippings_count}} +
+
+
+
\ No newline at end of file diff --git a/clipping_manager/templates/clipping_manager/partials/clipping_list_item.html b/clipping_manager/templates/clipping_manager/partials/clipping_list_item.html index 61fdf34..22c67a9 100644 --- a/clipping_manager/templates/clipping_manager/partials/clipping_list_item.html +++ b/clipping_manager/templates/clipping_manager/partials/clipping_list_item.html @@ -1,4 +1,4 @@
-

{{ clipping.content }}

+

{{ clipping.content }}

— {{ clipping.book.title }}
\ No newline at end of file diff --git a/clipping_manager/templatetags/__init__.py b/clipping_manager/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clipping_manager/templatetags/urlparams.py b/clipping_manager/templatetags/urlparams.py new file mode 100644 index 0000000..e75d0bc --- /dev/null +++ b/clipping_manager/templatetags/urlparams.py @@ -0,0 +1,11 @@ +from django import template +from urllib.parse import urlencode + +register = template.Library() + +@register.simple_tag +def urlparams(*_, **kwargs): + safe_args = {k: v for k, v in kwargs.items() if v is not None} + if safe_args: + return '?{}'.format(urlencode(safe_args)) + return '' \ No newline at end of file diff --git a/clipping_manager/urls.py b/clipping_manager/urls.py index b991cc8..1b3a826 100644 --- a/clipping_manager/urls.py +++ b/clipping_manager/urls.py @@ -4,11 +4,12 @@ from clipping_manager.views import UploadMyClippingsFileView, RandomClippingView, RandomClippingFullscreenView, \ DashboardView, AdminStatisticsView, EmailDeliveryView, DailyEmailDeliveryView, BiweeklyEmailDeliveryView, \ - WeeklyEmailDeliveryView, ClippingsBrowseView, UploadTextFileClippingsView, PersonalStatisticsView + WeeklyEmailDeliveryView, ClippingsBrowseView, UploadTextFileClippingsView, PersonalStatisticsView, BooksView urlpatterns = [ url(r'^$', login_required(DashboardView.as_view()), name='dashboard'), url(r'^browse/$', login_required(ClippingsBrowseView.as_view()), name='browse'), + url(r'^books/$', login_required(BooksView.as_view()), name='books'), url(r'^upload/$', login_required(UploadMyClippingsFileView.as_view()), name='upload'), url(r'^upload-plaintext/$', login_required(UploadTextFileClippingsView.as_view()), name='upload-plaintext'), url(r'^email-delivery/$', login_required(EmailDeliveryView.as_view()), name='email-delivery'), diff --git a/clipping_manager/views.py b/clipping_manager/views.py index 6f9e102..5f03350 100644 --- a/clipping_manager/views.py +++ b/clipping_manager/views.py @@ -55,6 +55,16 @@ def get_queryset(self): return self.filter.qs.distinct() +class BooksView(ListView): + template_name = 'clipping_manager/books.html' + context_object_name = 'books' + model = Book + + def get_queryset(self): + return Book.objects.for_user(self.request.user) \ + .annotate(clippings_count = Count("clippings")) + + class UploadMyClippingsFileView(FormView): form_class = UploadKindleClippingsForm template_name = 'clipping_manager/upload_kindle_clippings_file.html' diff --git a/static/css/main.css b/static/css/main.css index 04df703..ef2eee4 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -10,6 +10,10 @@ a:hover { background: #ffc600; } +.hidden { + display: none; +} + .new-tab-container { background-image: url("/static/img/background_1.jpg"); background-size: cover; @@ -177,4 +181,170 @@ a:hover { .pagination .page-item:not(.disabled) .page-link:hover { color: #7cda24; } + +.gallery-view { + display: -ms-grid; + display: grid; + -ms-grid-columns: (190px)[auto-fill]; + grid-template-columns: repeat(auto-fill, 190px); + grid-auto-rows: 230px; + -ms-flex-pack: distribute; + justify-content: space-around; + -webkit-column-gap: 10px; + column-gap: 10px; + row-gap: 30px; +} + +.gallery-view a { + color: black; + text-decoration: none; +} + +.gallery-view .list-header { + display: none; +} + +.gallery-view .view-element { + height: 100%; + padding: 0 5px; + border: 0.1px solid #e7e7e7; + display: -ms-grid; + display: grid; + -ms-grid-rows: 7fr 1fr 1fr; + grid-template-rows: 7fr 1fr 1fr; + -webkit-transition: border-color 0.2s cubic-bezier(0, 0.03, 0.23, 0.94), -webkit-transform 0.2s cubic-bezier(0, 0.03, 0.23, 0.94); + transition: border-color 0.2s cubic-bezier(0, 0.03, 0.23, 0.94), -webkit-transform 0.2s cubic-bezier(0, 0.03, 0.23, 0.94); + transition: border-color 0.2s cubic-bezier(0, 0.03, 0.23, 0.94), transform 0.2s cubic-bezier(0, 0.03, 0.23, 0.94); + transition: border-color 0.2s cubic-bezier(0, 0.03, 0.23, 0.94), transform 0.2s cubic-bezier(0, 0.03, 0.23, 0.94), -webkit-transform 0.2s cubic-bezier(0, 0.03, 0.23, 0.94); + -webkit-box-shadow: 0 5px 10px #0000001a; + box-shadow: 0 5px 10px #0000001a; +} + +.gallery-view .view-element:hover { + border-color: #ffc800; + -webkit-filter: blur(0px); + filter: blur(0px); + -webkit-transform: scale(1.07); + transform: scale(1.07); +} + +.gallery-view .view-element .book-cover { + display: -ms-grid; + display: grid; +} + +.gallery-view .view-element .book-cover img { + -ms-grid-column-align: center; + justify-self: center; + -ms-flex-item-align: center; + -ms-grid-row-align: center; + align-self: center; + max-height: 150px; +} + +.gallery-view .view-element .book-title { + -ms-flex-item-align: center; + -ms-grid-row-align: center; + align-self: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.8rem; + font-weight: bold; +} + +.gallery-view .view-element .book-author { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.7rem; +} + +.gallery-view .view-element .book-clippings-count { + display: none; +} + +.list-view { + border: 1px solid #aba6a6; + display: -ms-grid; + display: grid; + grid-auto-rows: minmax(60px, auto); + -webkit-box-shadow: 0 5px 10px #0000001a; + box-shadow: 0 5px 10px #0000001a; +} + +.list-view a { + color: black; + text-decoration: none; +} + +.list-view .list-header { + background: #e9e9e9; + height: 100%; + padding: 5px; + border-bottom: 1px solid #aba6a6; + display: -ms-grid; + display: grid; + -ms-grid-columns: 50px 6fr 3fr 1fr; + grid-template-columns: 50px 6fr 3fr 1fr; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + justify-items: center; + font-size: 0.95rem; + font-weight: 500; +} + +.list-view .list-header .header-title { + -ms-grid-column-align: start; + justify-self: start; +} + +.list-view .view-element { + height: 100%; + padding: 5px; + border-bottom: 1px solid #e7e7e7; + display: -ms-grid; + display: grid; + -ms-grid-columns: 50px 6fr 3fr 1fr; + grid-template-columns: 50px 6fr 3fr 1fr; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + justify-items: center; + font-weight: 370; + -webkit-transition: backgroud 0.1s cubic-bezier(0, 0.03, 0.23, 0.94); + transition: backgroud 0.1s cubic-bezier(0, 0.03, 0.23, 0.94); +} + +.list-view .view-element:hover { + background: #fbfbfb; +} + +.list-view .view-element .book-cover img { + height: 30px; +} + +.list-view .view-element .book-title { + -ms-grid-column-align: start; + justify-self: start; + font-weight: 380; +} + +@media only screen and (max-width: 768px) { + .list-view .list-header { + -ms-grid-columns: 50px 8fr 2fr; + grid-template-columns: 50px 8fr 2fr; + } + .list-view .list-header .header-clippings { + display: none; + } + .list-view .view-element { + -ms-grid-columns: 50px 8fr 2fr; + grid-template-columns: 50px 8fr 2fr; + } + .list-view .view-element .book-author { + display: none; + } +} /*# sourceMappingURL=main.css.map */ \ No newline at end of file diff --git a/static/js/components.js b/static/js/components.js new file mode 100644 index 0000000..02778f8 --- /dev/null +++ b/static/js/components.js @@ -0,0 +1,49 @@ +// ------- books.html ------- +const booksView = { + // Hides books which doesn't match the search bar value + searchBook() { + const searchInput = document.querySelector('#search-book'); + const books = document.querySelectorAll('.js-book-element'); + + function displayMatches() { + const regex = new RegExp(this.value, 'gi'); + + books.forEach(book => { + const title = book.querySelector('.js-book-title').textContent; + const author = book.querySelector('.js-book-author').textContent; + + if (title.match(regex) || author.match(regex)) { + book.classList.remove('hidden'); + } + else { + book.classList.add('hidden'); + } + }) + } + + searchInput.addEventListener('keyup', displayMatches); + }, + // Switches between the list and the gallery book view + controls buttons' colors + switchView() { + const galleryBtn = document.querySelector('#btn-gallery'); + const listBtn = document.querySelector('#btn-list'); + const bookView = document.querySelector('#book-view'); + + function viewControl() { + // Swap buttons' colors + Array.from(this.parentElement.children).forEach(btn => { + btn.classList.remove('btn-dark'); + btn.classList.add('btn-light'); + }) + + this.classList.remove('btn-light'); + this.classList.add('btn-dark'); + + // Switch the book view + bookView.className = this.dataset.view; + } + + galleryBtn.addEventListener('click', viewControl); + listBtn.addEventListener('click', viewControl); + } +} diff --git a/static/js/helper-functions.js b/static/js/helper-functions.js index d259121..701f0c6 100644 --- a/static/js/helper-functions.js +++ b/static/js/helper-functions.js @@ -1,10 +1,11 @@ +// ------- browse.html ------- // Highlights the searched phrase ('Content contains') in clippings function highlightClippings(wordToHighlight) { // If empty -> stop the function if (!wordToHighlight) return; // Get all clippings' text - const clippings = document.querySelectorAll('.clipping-content > em'); + const clippings = document.querySelectorAll('.js-clipping-content > em'); // Highlighs searched-for phrase in each clipping (case-insensitive) clippings.forEach((clipping) => { diff --git a/static/scss/_basics.scss b/static/scss/_basics.scss index 29cabb2..da2ea05 100644 --- a/static/scss/_basics.scss +++ b/static/scss/_basics.scss @@ -9,4 +9,8 @@ a { .highlight { background: #ffc600; +} + +.hidden { + display: none; } \ No newline at end of file diff --git a/static/scss/_mixins.scss b/static/scss/_mixins.scss index f3ad494..aeaecd4 100644 --- a/static/scss/_mixins.scss +++ b/static/scss/_mixins.scss @@ -21,4 +21,28 @@ .key {min-width: $width} .value {max-width: calc(100% - #{$width} - 3%)} +} + +@mixin plain-link { + color: black; + text-decoration: none; +} + +@mixin ellipsis-overflow { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@mixin list-grid($border-color, $col-widths) { + height: 100%; + padding: 5px; + + border-bottom: 1px solid $border-color; + + display: grid; + grid-template-columns: $col-widths; + + align-items: center; + justify-items: center; } \ No newline at end of file diff --git a/static/scss/_variables.scss b/static/scss/_variables.scss index d4056e1..a07dccf 100644 --- a/static/scss/_variables.scss +++ b/static/scss/_variables.scss @@ -4,7 +4,13 @@ $color-tertiary: #ffc800; $color-gray-1: #6f6f6f; +$backgroud-gray: #e9e9e9; +$backgroud-light-gray: #fbfbfb; + $border-radius-1: 0.25rem; +$border-gray: #aba6a6; +$border-light-gray: #e7e7e7; + // Media Queries widths $phone: 640px; diff --git a/static/scss/components/_books-gallery-view.scss b/static/scss/components/_books-gallery-view.scss new file mode 100644 index 0000000..fbecda6 --- /dev/null +++ b/static/scss/components/_books-gallery-view.scss @@ -0,0 +1,67 @@ +@import "../variables"; +@import "../mixins"; + +.gallery-view { + display: grid; + grid-template-columns: repeat(auto-fill, 190px); + grid-auto-rows: 230px; + justify-content: space-around; + column-gap: 10px; + row-gap: 30px; + + a { + @include plain-link; + } + + .list-header { + display: none; + } + + .view-element { + height: 100%; + padding: 0 5px; + border: 0.1px solid $border-light-gray; + + display: grid; + grid-template-rows: 7fr 1fr 1fr; + + transition: border-color 0.2s cubic-bezier(0, 0.03, 0.23, 0.94), + transform 0.2s cubic-bezier(0, 0.03, 0.23, 0.94); + + @include shadow-1; + + &:hover { + border-color: $color-tertiary; + filter: blur(0px); + transform: scale(1.07); + } + + + .book-cover { + display: grid; + + img { + justify-self: center; + align-self: center; + max-height: 150px; + } + } + + .book-title { + align-self: center; + @include ellipsis-overflow; + font-size: 0.8rem; + font-weight: bold; + + } + + .book-author { + @include ellipsis-overflow; + font-size: 0.7rem; + } + + .book-clippings-count { + display: none; + } + } +} diff --git a/static/scss/components/_books-list-view.scss b/static/scss/components/_books-list-view.scss new file mode 100644 index 0000000..063b83d --- /dev/null +++ b/static/scss/components/_books-list-view.scss @@ -0,0 +1,72 @@ +@import "../variables"; +@import "../mixins"; + +.list-view { + border: 1px solid $border-gray; + + display: grid; + grid-auto-rows: minmax(60px, auto); + + @include shadow-1; + + a { + @include plain-link; + } + + .list-header { + background: $backgroud-gray; + + @include list-grid($border-gray, 50px 6fr 3fr 1fr); + + font-size: 0.95rem; + font-weight: 500; + + .header-title { + justify-self: start; + } + } + + .view-element { + @include list-grid($border-light-gray, 50px 6fr 3fr 1fr); + + font-weight: 370; + + transition: backgroud 0.1s cubic-bezier(0, 0.03, 0.23, 0.94); + + &:hover { + background: $backgroud-light-gray; + } + + .book-cover { + img { + height: 30px; + } + } + + .book-title { + justify-self: start; + font-weight: 380; + } + } +} + +// Hide author for < tablet screens +.list-view { + @media only screen and (max-width: $tablet) { + .list-header { + grid-template-columns: 50px 8fr 2fr; + + .header-clippings { + display: none; + } + } + + .view-element { + grid-template-columns: 50px 8fr 2fr; + + .book-author { + display: none; + } + } + } +} \ No newline at end of file diff --git a/static/scss/main.scss b/static/scss/main.scss index 4190b54..114b7e7 100644 --- a/static/scss/main.scss +++ b/static/scss/main.scss @@ -6,3 +6,5 @@ @import "components/key-value"; @import "components/navigation"; @import "components/pagination"; +@import "components/books-gallery-view"; +@import "components/books-list-view";