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 %}
+
+
+
+ {% for book in books %}
+ {% include "clipping_manager/partials/book_list_item.html" with book=book %}
+ {% endfor %}
+
+
+ {% 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 %}
+
+
\ 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";