Merge branch 'develop' into kostia/feature/crypto-wallet-connect

This commit is contained in:
Kostiantyn Stoliarskyi 2023-05-04 14:34:43 +03:00
commit f563090b22
48 changed files with 38024 additions and 946 deletions

View File

@ -23,6 +23,7 @@ def create_app(environment="development"):
user_blueprint,
book_blueprint,
home_blueprint,
section_blueprint,
)
from app.models import (
User,
@ -50,6 +51,7 @@ def create_app(environment="development"):
app.register_blueprint(user_blueprint)
app.register_blueprint(book_blueprint)
app.register_blueprint(home_blueprint)
app.register_blueprint(section_blueprint)
# Set up flask login.
@login_manager.user_loader

View File

@ -1,2 +1,3 @@
# flake8: noqa F401
from .pagination import create_pagination
from .breadcrumbs import create_breadcrumbs

View File

@ -0,0 +1,131 @@
from flask import url_for
from flask_login import current_user
from app import models as m, db
from app import schema as s
def create_breadcrumbs(
book_id: int,
collection_path: tuple[int],
section_id: int = 0,
interpretation_id: int = 0,
) -> list[s.BreadCrumb]:
"""
How breadcrumbs look like:
Book List -> Book Name -> Top Level Collection -> SubCollection -> Section -> Interpretation
- If i am not owner of a book
John's books -> Book Name -> Top Level Collection -> SubCollection -> Section -> Interpretation
- If i am owner
My Books -> Book Title -> Part I -> Chapter X -> Paragraph 1.7 -> By John
"""
crumples: list[s.BreadCrumb] = []
book: m.Book = db.session.get(m.Book, book_id)
if current_user.is_authenticated and book.user_id == current_user.id:
# My Book
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.MyBookList,
url=url_for("book.my_books"),
label="My Books",
)
]
else:
# Not mine book
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.AuthorBookList,
url="#",
label=book.owner.username + "'s books",
)
]
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Collection,
url=url_for("book.collection_view", book_id=book_id),
label=book.label,
)
]
for collection_id in collection_path:
if collection_id is None:
continue
collection: m.Collection = db.session.get(m.Collection, collection_id)
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Collection,
url=url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
),
label=collection.label,
)
]
if section_id and collection_path:
section: m.Section = db.session.get(m.Section, section_id)
if len(collection_path) == 2:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Section,
url=url_for(
"book.section_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[-1],
),
label=section.label,
)
]
else:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Section,
url=url_for(
"book.section_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[0],
),
label=section.label,
)
]
if interpretation_id:
interpretation: m.Interpretation = db.session.get(
m.Interpretation, interpretation_id
)
if len(collection_path) == 2:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Interpretation,
url=url_for(
"book.interpretation_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[-1],
section_id=section_id,
),
label=interpretation.label,
)
]
else:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Interpretation,
url=url_for(
"book.interpretation_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[0],
section_id=section_id,
),
label=interpretation.label,
)
]
return crumples

View File

@ -10,3 +10,4 @@ from .contributor import (
EditContributorRoleForm,
)
from .collection import CreateCollectionForm, EditCollectionForm
from .section import CreateSectionForm, EditSectionForm

View File

@ -1,8 +1,40 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms import StringField, SubmitField, ValidationError
from wtforms.validators import DataRequired, Length
from app import models as m, db
from app.logger import log
class CreateBookForm(FlaskForm):
class BaseBookForm(FlaskForm):
label = StringField("Label", [DataRequired(), Length(6, 256)])
class CreateBookForm(BaseBookForm):
submit = SubmitField("Add new book")
class EditBookForm(BaseBookForm):
book_id = StringField("User ID", [DataRequired()])
submit = SubmitField("Edit book")
def validate_book_id(self, field):
book_id = field.data
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
raise ValidationError("Book not found")
def validate_label(self, field):
label = field.data
book_id = self.book_id.data
existing_book: m.Book = m.Book.query.filter_by(
is_deleted=False,
label=label,
).first()
if existing_book:
log(
log.WARNING, "Book with label [%s] already exists: [%s]", label, book_id
)
raise ValidationError("Book label must be unique!")

68
app/forms/section.py Normal file
View File

@ -0,0 +1,68 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, ValidationError
from wtforms.validators import DataRequired, Length
from app import models as m, db
from app.logger import log
class BaseSectionForm(FlaskForm):
label = StringField("Label", [DataRequired(), Length(3, 256)])
about = StringField("About")
class CreateSectionForm(BaseSectionForm):
collection_id = StringField("Collection ID", [DataRequired()])
submit = SubmitField("Create")
def validate_collection_id(self, field):
collection_id = field.data
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.sub_collections:
log(log.WARNING, "Collection [%s] it not leaf", collection)
raise ValidationError("You can't create section for this collection")
def validate_label(self, field):
label = field.data
collection_id = self.collection_id.data
section: m.Section = m.Section.query.filter_by(
is_deleted=False, label=label, collection_id=collection_id
).first()
if section:
log(
log.WARNING,
"Section with label [%s] already exists: [%s]",
label,
section,
)
raise ValidationError("Section label must be unique!")
class EditSectionForm(BaseSectionForm):
section_id = StringField("Section ID", [DataRequired()])
submit = SubmitField("Edit")
def validate_label(self, field):
label = field.data
section_id = self.section_id.data
collection_id = db.session.get(m.Section, section_id).collection_id
section: m.Section = (
m.Section.query.filter_by(
is_deleted=False, label=label, collection_id=collection_id
)
.filter(m.Section.id != section_id)
.first()
)
if section:
log(
log.WARNING,
"Section with label [%s] already exists: [%s]",
label,
section,
)
raise ValidationError("Section label must be unique!")

View File

@ -19,3 +19,7 @@ class Book(BaseModel):
def __repr__(self):
return f"<{self.id}: {self.label}>"
@property
def last_version(self):
return self.versions[-1]

View File

@ -25,3 +25,15 @@ class Collection(BaseModel):
def __repr__(self):
return f"<{self.id}: {self.label}>"
@property
def active_sections(self):
return [section for section in self.sections if not section.is_deleted]
@property
def sub_collections(self):
return [
sub_collection
for sub_collection in self.children
if not sub_collection.is_deleted
]

View File

@ -32,5 +32,20 @@ class Section(BaseModel):
path += self.label
return path
@property
def book_id(self):
_book_id = self.version.book_id
return _book_id
@property
def sub_collection_id(self):
parent = self.collection
grand_parent = parent.parent
if grand_parent.is_root:
_sub_collection_id = parent.id
else:
_sub_collection_id = grand_parent.id
return _sub_collection_id
def __repr__(self):
return f"<{self.id}: {self.label}>"

View File

@ -1,3 +1,4 @@
# flake8: noqa F401
from .pagination import Pagination
from .user import User
from .breadcrumbs import BreadCrumbType, BreadCrumb

21
app/schema/breadcrumbs.py Normal file
View File

@ -0,0 +1,21 @@
import enum
from pydantic import BaseModel
class BreadCrumbType(enum.StrEnum):
"""Bread Crumb Type"""
MyBookList = "MyBookList"
AuthorBookList = "AuthorBookList"
Collection = "Collection"
Section = "Section"
Interpretation = "Interpretation"
class BreadCrumb(BaseModel):
"""Bread Crumb for navigation"""
label: str
url: str
type: BreadCrumbType

View File

@ -0,0 +1,863 @@
/*!
* Quill Editor v1.3.6
* https://quilljs.com/
* Copyright (c) 2014, Jason Chen
* Copyright (c) 2013, salesforce.com
*/
.ql-container {
box-sizing: border-box;
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
/* height: 100%; */
margin: 0px;
position: relative;
}
.ql-container.ql-disabled .ql-tooltip {
visibility: hidden;
}
.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
pointer-events: none;
}
.ql-clipboard {
left: -100000px;
height: 1px;
overflow-y: hidden;
position: absolute;
top: 50%;
}
.ql-clipboard p {
margin: 0;
padding: 0;
}
.ql-editor {
box-sizing: border-box;
line-height: 1.42;
height: 100%;
outline: none;
overflow-y: auto;
padding: 12px 15px;
tab-size: 4;
-moz-tab-size: 4;
text-align: left;
word-wrap: break-word;
}
.ql-editor > * {
cursor: text;
}
.ql-editor p,
.ql-editor ol,
.ql-editor ul,
.ql-editor pre,
.ql-editor blockquote,
.ql-editor h1,
.ql-editor h2,
.ql-editor h3,
.ql-editor h4,
.ql-editor h5,
.ql-editor h6 {
margin: 0;
padding: 0;
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol,
.ql-editor ul {
padding-left: 1.5em;
}
.ql-editor ol > li,
.ql-editor ul > li {
list-style-type: none;
}
.ql-editor ul > li::before {
content: '\2022';
}
.ql-editor ul[data-checked='true'],
.ql-editor ul[data-checked='false'] {
pointer-events: none;
}
.ql-editor ul[data-checked='true'] > li *,
.ql-editor ul[data-checked='false'] > li * {
pointer-events: all;
}
.ql-editor ul[data-checked='true'] > li::before,
.ql-editor ul[data-checked='false'] > li::before {
color: #777;
cursor: pointer;
pointer-events: all;
}
.ql-editor ul[data-checked='true'] > li::before {
content: '\2611';
}
.ql-editor ul[data-checked='false'] > li::before {
content: '\2610';
}
.ql-editor li::before {
display: inline-block;
white-space: nowrap;
width: 1.2em;
}
.ql-editor li:not(.ql-direction-rtl)::before {
margin-left: -1.5em;
margin-right: 0.3em;
text-align: right;
}
.ql-editor li.ql-direction-rtl::before {
margin-left: 0.3em;
margin-right: -1.5em;
}
.ql-editor ol li:not(.ql-direction-rtl),
.ql-editor ul li:not(.ql-direction-rtl) {
padding-left: 1.5em;
}
.ql-editor ol li.ql-direction-rtl,
.ql-editor ul li.ql-direction-rtl {
padding-right: 1.5em;
}
.ql-editor ol li {
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
counter-increment: list-0;
}
.ql-editor ol li:before {
content: counter(list-0, decimal) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-increment: list-1;
}
.ql-editor ol li.ql-indent-1:before {
content: counter(list-1, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-2 {
counter-increment: list-2;
}
.ql-editor ol li.ql-indent-2:before {
content: counter(list-2, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-2 {
counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-3 {
counter-increment: list-3;
}
.ql-editor ol li.ql-indent-3:before {
content: counter(list-3, decimal) '. ';
}
.ql-editor ol li.ql-indent-3 {
counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-4 {
counter-increment: list-4;
}
.ql-editor ol li.ql-indent-4:before {
content: counter(list-4, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-4 {
counter-reset: list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-5 {
counter-increment: list-5;
}
.ql-editor ol li.ql-indent-5:before {
content: counter(list-5, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-5 {
counter-reset: list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-6 {
counter-increment: list-6;
}
.ql-editor ol li.ql-indent-6:before {
content: counter(list-6, decimal) '. ';
}
.ql-editor ol li.ql-indent-6 {
counter-reset: list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-7 {
counter-increment: list-7;
}
.ql-editor ol li.ql-indent-7:before {
content: counter(list-7, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-7 {
counter-reset: list-8 list-9;
}
.ql-editor ol li.ql-indent-8 {
counter-increment: list-8;
}
.ql-editor ol li.ql-indent-8:before {
content: counter(list-8, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-8 {
counter-reset: list-9;
}
.ql-editor ol li.ql-indent-9 {
counter-increment: list-9;
}
.ql-editor ol li.ql-indent-9:before {
content: counter(list-9, decimal) '. ';
}
.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
padding-left: 3em;
}
.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
padding-left: 4.5em;
}
.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 3em;
}
.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 4.5em;
}
.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
padding-left: 6em;
}
.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
padding-left: 7.5em;
}
.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 6em;
}
.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 7.5em;
}
.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
padding-left: 9em;
}
.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
padding-left: 10.5em;
}
.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 9em;
}
.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 10.5em;
}
.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
padding-left: 12em;
}
.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
padding-left: 13.5em;
}
.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 12em;
}
.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 13.5em;
}
.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
padding-left: 15em;
}
.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
padding-left: 16.5em;
}
.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 15em;
}
.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 16.5em;
}
.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
padding-left: 18em;
}
.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
padding-left: 19.5em;
}
.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 18em;
}
.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 19.5em;
}
.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
padding-left: 21em;
}
.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
padding-left: 22.5em;
}
.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 21em;
}
.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 22.5em;
}
.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
padding-left: 24em;
}
.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
padding-left: 25.5em;
}
.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 24em;
}
.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 25.5em;
}
.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
padding-left: 27em;
}
.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
padding-left: 28.5em;
}
.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 27em;
}
.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 28.5em;
}
.ql-editor .ql-video {
display: block;
max-width: 100%;
}
.ql-editor .ql-video.ql-align-center {
margin: 0 auto;
}
.ql-editor .ql-video.ql-align-right {
margin: 0 0 0 auto;
}
.ql-editor .ql-bg-black {
background-color: #000;
}
.ql-editor .ql-bg-red {
background-color: #e60000;
}
.ql-editor .ql-bg-orange {
background-color: #f90;
}
.ql-editor .ql-bg-yellow {
background-color: #ff0;
}
.ql-editor .ql-bg-green {
background-color: #008a00;
}
.ql-editor .ql-bg-purple {
background-color: #93f;
}
.ql-editor .ql-color-white {
color: #fff;
}
.ql-editor .ql-color-red {
color: #e60000;
}
.ql-editor .ql-color-orange {
color: #f90;
}
.ql-editor .ql-color-yellow {
color: #ff0;
}
.ql-editor .ql-color-green {
color: #008a00;
}
.ql-editor .ql-color-purple {
color: #93f;
}
.ql-editor .ql-font-serif {
font-family: Georgia, Times New Roman, serif;
}
.ql-editor .ql-font-monospace {
font-family: Monaco, Courier New, monospace;
}
.ql-editor .ql-size-small {
font-size: 0.75em;
}
.ql-editor .ql-size-large {
font-size: 1.5em;
}
.ql-editor .ql-size-huge {
font-size: 2.5em;
}
.ql-editor .ql-direction-rtl {
direction: rtl;
text-align: inherit;
}
.ql-editor .ql-align-center {
text-align: center;
}
.ql-editor .ql-align-justify {
text-align: justify;
}
.ql-editor .ql-align-right {
text-align: right;
}
.ql-editor.ql-blank::before {
color: rgba(0, 0, 0, 0.6);
content: attr(data-placeholder);
font-style: italic;
left: 15px;
pointer-events: none;
position: absolute;
right: 15px;
}
.ql-snow.ql-toolbar:after,
.ql-snow .ql-toolbar:after {
clear: both;
content: '';
display: table;
}
.ql-snow.ql-toolbar button,
.ql-snow .ql-toolbar button {
background: none;
border: none;
cursor: pointer;
display: inline-block;
float: left;
height: 24px;
padding: 3px 5px;
width: 28px;
}
.ql-snow.ql-toolbar button svg,
.ql-snow .ql-toolbar button svg {
float: left;
height: 100%;
}
.ql-snow.ql-toolbar button:active:hover,
.ql-snow .ql-toolbar button:active:hover {
outline: none;
}
.ql-snow.ql-toolbar input.ql-image[type='file'],
.ql-snow .ql-toolbar input.ql-image[type='file'] {
display: none;
}
@media (pointer: coarse) {
.ql-snow.ql-toolbar button:hover:not(.ql-active),
.ql-snow .ql-toolbar button:hover:not(.ql-active) {
color: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
stroke: #444;
}
}
.ql-snow {
box-sizing: border-box;
}
.ql-snow * {
box-sizing: border-box;
}
.ql-snow .ql-hidden {
display: none;
}
.ql-snow .ql-out-bottom,
.ql-snow .ql-out-top {
visibility: hidden;
}
.ql-snow .ql-tooltip {
position: absolute;
transform: translateY(10px);
}
.ql-snow .ql-tooltip a {
cursor: pointer;
text-decoration: none;
}
.ql-snow .ql-tooltip.ql-flip {
transform: translateY(-10px);
}
.ql-snow .ql-formats {
display: inline-block;
vertical-align: middle;
}
.ql-snow .ql-formats:after {
clear: both;
content: '';
display: table;
}
.ql-snow .ql-stroke {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
.ql-snow .ql-stroke-miter {
fill: none;
stroke: #444;
stroke-miterlimit: 10;
stroke-width: 2;
}
.ql-snow .ql-fill,
.ql-snow .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow .ql-empty {
fill: none;
}
.ql-snow .ql-even {
fill-rule: evenodd;
}
.ql-snow .ql-thin,
.ql-snow .ql-stroke.ql-thin {
stroke-width: 1;
}
.ql-snow .ql-transparent {
opacity: 0.4;
}
.ql-snow .ql-direction svg:last-child {
display: none;
}
.ql-snow .ql-direction.ql-active svg:last-child {
display: inline;
}
.ql-snow .ql-direction.ql-active svg:first-child {
display: none;
}
.ql-snow .ql-editor h1 {
font-size: 2em;
}
.ql-snow .ql-editor h2 {
font-size: 1.5em;
}
.ql-snow .ql-editor h3 {
font-size: 1.17em;
}
.ql-snow .ql-editor h4 {
font-size: 1em;
}
.ql-snow .ql-editor h5 {
font-size: 0.83em;
}
.ql-snow .ql-editor h6 {
font-size: 0.67em;
}
.ql-snow .ql-editor a {
text-decoration: underline;
}
.ql-snow .ql-editor blockquote {
border-left: 4px solid #ccc;
margin-bottom: 5px;
margin-top: 5px;
padding-left: 16px;
}
.ql-snow .ql-editor code,
.ql-snow .ql-editor pre {
background-color: #f0f0f0;
border-radius: 3px;
}
.ql-snow .ql-editor pre {
white-space: pre-wrap;
margin-bottom: 5px;
margin-top: 5px;
padding: 5px 10px;
}
.ql-snow .ql-editor code {
font-size: 85%;
padding: 2px 4px;
}
.ql-snow .ql-editor pre.ql-syntax {
background-color: #23241f;
color: #f8f8f2;
overflow: visible;
}
.ql-snow .ql-editor img {
max-width: 100%;
}
.ql-snow .ql-picker {
display: inline-block;
float: left;
font-size: 14px;
font-weight: 500;
height: 24px;
position: relative;
vertical-align: middle;
}
.ql-snow .ql-picker-label {
cursor: pointer;
display: inline-block;
height: 100%;
padding-left: 8px;
padding-right: 2px;
position: relative;
width: 100%;
}
.ql-snow .ql-picker-label::before {
display: inline-block;
line-height: 22px;
}
.ql-snow .ql-picker-options {
display: none;
min-width: 100%;
position: absolute;
white-space: nowrap;
}
.ql-snow .ql-picker-options .ql-picker-item {
cursor: pointer;
display: block;
padding: 5px 8px;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label {
z-index: 2;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
fill: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
stroke: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-options {
display: block;
margin-top: -1px;
top: 100%;
z-index: 1;
}
.ql-snow .ql-color-picker,
.ql-snow .ql-icon-picker {
width: 28px;
}
.ql-snow .ql-color-picker .ql-picker-label,
.ql-snow .ql-icon-picker .ql-picker-label {
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-label svg,
.ql-snow .ql-icon-picker .ql-picker-label svg {
right: 4px;
}
.ql-snow .ql-icon-picker .ql-picker-options {
padding: 4px 0px;
}
.ql-snow .ql-icon-picker .ql-picker-item {
height: 24px;
width: 24px;
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-options {
padding: 3px 5px;
width: 152px;
}
.ql-snow .ql-color-picker .ql-picker-item {
border: 1px solid transparent;
float: left;
height: 16px;
margin: 2px;
padding: 0px;
width: 16px;
}
.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
position: absolute;
margin-top: -9px;
right: 0;
top: 50%;
width: 18px;
}
.ql-snow
.ql-picker.ql-header
.ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow
.ql-picker.ql-font
.ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow
.ql-picker.ql-size
.ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow
.ql-picker.ql-header
.ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow
.ql-picker.ql-font
.ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow
.ql-picker.ql-size
.ql-picker-item[data-label]:not([data-label=''])::before {
content: attr(data-label);
}
.ql-snow .ql-picker.ql-header {
width: 98px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
content: 'Heading 1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: 'Heading 2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: 'Heading 3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: 'Heading 4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: 'Heading 5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: 'Heading 6';
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
font-size: 2em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
font-size: 1.5em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
font-size: 1.17em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
font-size: 1em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
font-size: 0.83em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
font-size: 0.67em;
}
.ql-snow .ql-picker.ql-font {
width: 108px;
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: 'Sans Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
content: 'Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
content: 'Monospace';
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
font-family: Georgia, Times New Roman, serif;
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
font-family: Monaco, Courier New, monospace;
}
.ql-snow .ql-picker.ql-size {
width: 98px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
content: 'Small';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
content: 'Large';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
content: 'Huge';
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
font-size: 32px;
}
.ql-snow .ql-color-picker.ql-background .ql-picker-item {
background-color: #fff;
}
.ql-snow .ql-color-picker.ql-color .ql-picker-item {
background-color: #000;
}
.ql-toolbar.ql-snow {
border: 1px solid #ccc;
box-sizing: border-box;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
padding: 8px;
}
.ql-toolbar.ql-snow .ql-formats {
margin-right: 15px;
}
.ql-toolbar.ql-snow .ql-picker-label {
border: 1px solid transparent;
}
.ql-toolbar.ql-snow .ql-picker-options {
border: 1px solid transparent;
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 8px;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {
border-color: #000;
}
.ql-toolbar.ql-snow + .ql-container.ql-snow {
border-top: 0px;
}
.ql-snow .ql-tooltip {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: 0px 0px 5px #ddd;
color: #444;
padding: 5px 12px;
white-space: nowrap;
}
.ql-snow .ql-tooltip::before {
content: 'Visit URL:';
line-height: 26px;
margin-right: 8px;
}
.ql-snow .ql-tooltip input[type='text'] {
display: none;
border: 1px solid #ccc;
font-size: 13px;
height: 26px;
margin: 0px;
padding: 3px 5px;
width: 170px;
}
.ql-snow .ql-tooltip a.ql-preview {
display: inline-block;
max-width: 200px;
overflow-x: hidden;
text-overflow: ellipsis;
vertical-align: top;
}
.ql-snow .ql-tooltip a.ql-action::after {
border-right: 1px solid #ccc;
content: 'Edit';
margin-left: 16px;
padding-right: 8px;
}
.ql-snow .ql-tooltip a.ql-remove::before {
content: 'Remove';
margin-left: 8px;
}
.ql-snow .ql-tooltip a {
line-height: 26px;
}
.ql-snow .ql-tooltip.ql-editing a.ql-preview,
.ql-snow .ql-tooltip.ql-editing a.ql-remove {
display: none;
}
.ql-snow .ql-tooltip.ql-editing input[type='text'] {
display: inline-block;
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: 'Save';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode='link']::before {
content: 'Enter link:';
}
.ql-snow .ql-tooltip[data-mode='formula']::before {
content: 'Enter formula:';
}
.ql-snow .ql-tooltip[data-mode='video']::before {
content: 'Enter video:';
}
.ql-container.ql-snow {
border: 1px solid #ccc;
}

File diff suppressed because it is too large Load Diff

1
app/static/js/initQuill.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function initQuill(): void;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
export declare function initQuillValueToTextArea(): void;

1
app/static/js/src/contributors.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function initContributors(): void;

1
app/static/js/src/initQuill.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function initQuill(): void;

1
app/static/js/src/main.d.ts vendored Normal file
View File

@ -0,0 +1 @@
import './styles.css';

1
app/static/js/src/quill.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function initQuill(): void;

View File

@ -0,0 +1 @@
export declare function initQuillValueToTextArea(): void;

View File

@ -13,6 +13,8 @@
<!-- styles -->
<!-- prettier-ignore -->
<script src="{{ url_for('static', filename='js/main.js') }}" type="text/javascript"></script>
<!-- prettier-ignore -->
<link href="{{ url_for('static', filename='css/quill.snow.css') }}" rel="stylesheet" />
<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
@ -91,8 +93,6 @@
{% endblock %}
<!-- scripts -->
<!-- prettier-ignore -->
{% block scripts %}
{% endblock %}
{% block scripts %} {% endblock %}
</body>
</html>

View File

@ -22,9 +22,6 @@
<button id="search-btn" class="cursor-pointer select-none text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Search</button>
</div>
</div>
<div class="col-span-6 sm:col-span-3 overflow-x-auto shadow-md sm:rounded-lg" id="search-results">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<tbody id="search-results-tbody">
@ -38,9 +35,7 @@
</tbody>
</table>
</div>
</div>
<div>
<label for="role" class="mb-2 block text-sm font-medium text-gray-900 dark:text-white">Select an option</label >
<select id="role" name="role" class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">

View File

@ -0,0 +1,43 @@
<!-- Add collection modal -->
<!-- prettier-ignore-->
<div id="add-section-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form
{% if sub_collection %}
action="{{ url_for('book.section_create', book_id=book.id, collection_id=collection.id, sub_collection_id=sub_collection.id) }}"
{% else %}
action="{{ url_for('book.section_create', book_id=book.id, collection_id=collection.id) }}"
{% endif %}
method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
{{ form_hidden_tag() }}
<input type="hidden" name="collection_id" id="collection_id" value="{{sub_collection.id if sub_collection else collection.id}}" />
<input type="hidden" name="about" id="about" />
<!-- Modal header -->
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Add Section </h3>
<button id="modalAddCloseButton" data-modal-hide="add-section-modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
</div>
<!-- Modal body -->
<div class="p-6 space-y-6">
<div class="grid gap-6">
<div class="col-span-6 sm:col-span-3">
<label for="label" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" >Label</label >
<input type="text" name="label" id="label" class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Collection label" required />
</div>
</div>
</div>
<div class="p-6 pt-0 space-y-6">
<div class="w-full max-w-6xl mx-auto rounded-xl bg-gray-50 dark:bg-gray-600 shadow-lg text-white-900">
<div class="overflow-hidden rounded-md bg-gray-50 [&>*]:dark:bg-gray-600 text-black [&>*]:!border-none [&>*]:!stroke-black dark:text-white dark:[&>*]:!stroke-white">
<div id="editor" class="dark:text-white h-80"></div>
</div>
</div>
</div>
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button name="submit" type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Create</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,21 @@
<!-- prettier-ignore -->
<nav class="flex mt-5 mb-3" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 md:space-x-3 ml-5">
{% for breadcrumb in breadcrumbs %}
<li class="inline-flex items-center">
<a href="{{ breadcrumb.url }}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white">
<!-- prettier-ignore -->
<!--svg for all types of breadcrumb-->
{% if breadcrumb.type == "MyBookList" or breadcrumb.type == "AuthorBookList" %}
<svg aria-hidden="true" class="flex-shrink-0 w-4 h-4 mr-2 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex-shrink-0 w-4 h-4 mr-2 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> </svg>
{% endif %}
<!-- prettier-ignore -->
{{ breadcrumb.label }}
</a>
<svg aria-hidden="true" class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path> </svg>
</li>
{% endfor %}
</ol>
</nav>

View File

@ -18,13 +18,8 @@
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4"> {{ book.owner.username }} </h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">{{ book.label }}</h1>
</div>
</div>
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">Collections page</h1>
<!-- prettier-ignore -->
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
@ -43,16 +38,11 @@
</ul>
</div>
<div id="myTabContent">
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="files"
role="tabpanel"
aria-labelledby="files-tab">
<dl
class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="files" role="tabpanel" aria-labelledby="files-tab">
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
{% for collection in book.versions[-1].children_collections if not collection.is_root and not collection.is_deleted %}
<!-- prettier-ignore -->
<a href="{{url_for('book.sub_collection_view',book_id=book.id,collection_id=collection.id)}}" >
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
@ -74,7 +64,6 @@
</div>
</dl>
</a >
{% endfor %}
</dl>
</div>

View File

@ -0,0 +1,26 @@
<!-- prettier-ignore-->
<div id="delete-collection-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form
{% if sub_collection %}
action="{{ url_for('book.section_delete', book_id=book.id, collection_id=collection.id, sub_collection_id=sub_collection.id, section_id=section.id) }}"
{% else %}
action="{{ url_for('book.section_delete', book_id=book.id, collection_id=collection.id, section_id=section.id) }}"
{% endif %}
method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
{{ form_hidden_tag() }}
<!-- Modal header -->
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Delete Section </h3>
<button id="modalAddCloseButton" data-modal-hide="delete-collection-modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
</div>
<!-- Modal body -->
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button name="submit" type="submit" class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">Confirm Deletion</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,45 @@
<!-- prettier-ignore-->
<div id="edit-section-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form
{% if sub_collection %}
action="{{ url_for('book.section_edit', book_id=book.id, collection_id=collection.id, sub_collection_id=sub_collection.id, section_id=section.id) }}"
{% else %}
action="{{ url_for('book.section_edit', book_id=book.id, collection_id=collection.id, section_id=section.id) }}"
{% endif %}
method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
{{ form_hidden_tag() }}
<input type="hidden" name="section_id" id="section_id" value="{{section.id}}" />
<input type="hidden" name="about" id="about" />
<!-- Modal header -->
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Edit Section </h3>
<button id="modalAddCloseButton" data-modal-hide="edit-section-modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
</div>
<!-- Modal body -->
<div class="p-6 space-y-6">
<div class="grid gap-6">
<div class="col-span-6 sm:col-span-3">
<label for="label" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" >Label</label >
<input value="{{section.label}}" type="text" name="label" id="label" class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Collection label" required />
</div>
</div>
</div>
<div class="p-6 pt-0 space-y-6">
<div class="w-full max-w-6xl mx-auto rounded-xl bg-gray-50 dark:bg-gray-600 shadow-lg text-white-900">
<div class="overflow-hidden rounded-md bg-gray-50 [&>*]:dark:bg-gray-600 text-black [&>*]:!border-none [&>*]:!stroke-black dark:text-white dark:[&>*]:!stroke-white">
<div id="editor" class="dark:text-white h-80">
{{ section.about|safe }}
</div>
</div>
</div>
</div>
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button name="submit" type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>
</div>
</form>
</div>
</div>

View File

@ -15,14 +15,10 @@
{% for book in books %}
<!-- prettier-ignore -->
<dl
class=" bg-white dark:bg-gray-900 max-w-full p-5 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<a
class="flex flex-col pb-2"
href="{{url_for('book.collection_view',book_id=book.id)}}">
<dl class=" bg-white dark:bg-gray-900 max-w-full p-5 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<a class="flex flex-col pb-2" href="{{url_for('book.collection_view',book_id=book.id)}}">
<dt class="mb-2">{{book.owner.username}}/{{book.label}}</dt>
<dd
class="flex flex-col md:flex-row text-lg font-semibold text-gray-500 md:text-lg dark:text-gray-400">
<dd class="flex flex-col md:flex-row text-lg font-semibold text-gray-500 md:text-lg dark:text-gray-400">
{% if book.versions %}
<p>
Last updated on {{book.versions[-1].updated_at.strftime('%B %d, %Y')}}
@ -30,48 +26,15 @@
{% endif %}
<div class="flex ml-auto align-center justify-center space-x-3">
<span class="space-x-0.5 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 22 22"
stroke-width="1"
stroke="currentColor"
class="w-4 h-4 inline-flex mr-1">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" /> </svg>
<p>{{ book.stars|length }}</p>
</span>
<span class="space-x-0.5 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 22 22"
stroke-width="1"
stroke="currentColor"
class="w-4 h-4 inline-flex mr-1">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /> </svg>
<p>55</p>
</span>
<span class="space-x-0.5 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 22 22"
stroke-width="1"
stroke="currentColor"
class="w-4 h-4 inline-flex mr-1">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> </svg>
<p>55</p>
</span>
</div>
@ -79,6 +42,60 @@
</a>
</dl>
{% endfor %}
<!-- prettier-ignore -->
{% if page.pages > 1 %}
<div class="container content-center mt-3 flex bg-white dark:bg-gray-800">
<nav aria-label="Page navigation example" class="mx-auto">
<ul class="inline-flex items-center -space-x-px">
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page=1&q={{page.query}}" class="block px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<span class="sr-only">First</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M15.79 14.77a.75.75 0 01-1.06.02l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 111.04 1.08L11.832 10l3.938 3.71a.75.75 0 01.02 1.06zm-6 0a.75.75 0 01-1.06.02l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 111.04 1.08L5.832 10l3.938 3.71a.75.75 0 01.02 1.06z" clip-rule="evenodd" /> </svg>
</a>
</li>
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{page.page-1 if page.page > 1 else 1}}&q={{page.query}}" class="block px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<span class="sr-only">Previous</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
<!-- prettier-ignore -->
{% for p in page.pages_for_links %}
<li>
<!-- prettier-ignore -->
{% if p == page.page %}
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{p}}&q={{page.query}}" aria-current="page" class="z-10 px-3 py-2 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white">{{p}}</a>
{% else %}
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{p}}&q={{page.query}}" class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">{{p}}</a>
{% endif %}
</li>
{% endfor %}
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{page.page+1 if page.page < page.pages else page.pages}}&q={{page.query}}" class="block px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<!-- prettier-ignore -->
<span class="sr-only">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{page.pages}}&q={{page.query}}" class="block px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<!-- prettier-ignore -->
<span class="sr-only">Last</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M10.21 14.77a.75.75 0 01.02-1.06L14.168 10 10.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M4.21 14.77a.75.75 0 01.02-1.06L8.168 10 4.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
</ul>
</nav>
</div>
{% endif %}
</div>
{% include 'user/add.html' %}
<!-- prettier-ignore -->

View File

@ -1,32 +1,58 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% include 'book/delete_section_modal.html' %}
{% include 'book/edit_section_modal.html' %}
<!-- show delete section btn on rightside bar -->
{% set show_delete_section = True %}
{% set show_edit_section = True %}
<!-- prettier-ignore -->
{% block right_sidebar %}
{% include 'book/right_sidebar.html' %}
{% endblock %}
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">
Interpretations page
</h1>
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4">
{{ book.owner.username }}/{{ book.label }}
</h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">
{{collection.label}} {% if sub_collection %}/ {{sub_collection.label}} {% endif %} /{{section.label}}
</h1>
</div>
</div>
<!-- prettier-ignore -->
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
<li class="mr-2" role="presentation">
<button class="flex items-center space-x-2 p-4 border-b-2 rounded-t-lg" id="interpretation-tab" data-tabs-target="#interpretation" type="button" role="tab" aria-controls="interpretation" aria-selected="false">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
<span>Interpretations</span>
</button>
</li>
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="about-tab" data-tabs-target="#section-about" type="button" role="tab" aria-controls="about" aria-selected="false">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" /> </svg>
<span>About</span>
</button>
</li>
<li class="mr-2" role="presentation">
<button class="flex items-center space-x-2 p-4 border-b-2 rounded-t-lg" id="interpretation-tab" data-tabs-target="#interpretation" type="button" role="tab" aria-controls="interpretation" aria-selected="false">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
<span>Interpretations</span>
</button>
</li>
</ul>
</div>
</div>
<div id="myTabContent">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="section-about" role="tabpanel" aria-labelledby="about-tab">
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<div class="flex flex-col w-full">
<!-- prettier-ignore -->
<dt class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<div class="ql-snow">
<div class="ql-editor">
{{ section.about|safe }}
</div>
</div>
</dt>
</div>
</dl>
</div>
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="interpretation" role="tabpanel" aria-labelledby="interpretation-tab">
<!-- prettier-ignore -->
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
@ -37,38 +63,33 @@
{% else %}
<a href="{{url_for('book.interpretation_view', book_id=book.id, collection_id=collection.id, section_id=section.id)}}" >
{% endif %}
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<div class="flex flex-col pb-3 p-3 w-full">
<!-- prettier-ignore -->
<dt class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<div>
<h1>{{ section.label }}</h1>
<p>{{ interpretation.text }}</p>
</div>
<div class="flex ml-auto align-center justify-center space-x-3">
<span class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /></svg>
<p>55</p>
</span>
<span class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
<p>55</p>
</span>
</div>
</dt>
</div>
</dl>
</a >
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<div class="flex flex-col pb-3 p-3 w-full">
<!-- prettier-ignore -->
<dt class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<div>
<h1>{{ section.label }}</h1>
<p>{{ interpretation.text }}</p>
</div>
<div class="flex ml-auto align-center justify-center space-x-3">
<span class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /></svg>
<p>55</p>
</span>
<span class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
<p>55</p>
</span>
</div>
</dt>
</div>
</dl>
</a>
{% endfor %}
</dl>
</div>
</div>
<!-- prettier-ignore -->
{% endblock %}
<!-- prettier-ignore -->
{% block scripts %}
{% endblock %}
</div>
<!-- prettier-ignore -->
{% endblock %}

View File

@ -22,6 +22,15 @@
</li>
{% endif %}
{% if show_create_section %}
<li>
<!-- prettier-ignore -->
<button type="button" data-modal-target="add-section-modal" data-modal-toggle="add-section-modal" class="space-x-3 text-white ml-2 w-11/12 bg-emerald-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-emerald-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-emerald-600 dark:hover:bg-emerald-700 dark:focus:ring-emerald-800">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> </svg>
<p>New Section</p>
</button>
</li>
{% endif %}
{% if show_edit_collection %}
<li>
@ -33,6 +42,16 @@
</li>
{% endif %}
{% if show_edit_section %}
<li>
<!-- prettier-ignore -->
<button type="button" data-modal-target="edit-section-modal" data-modal-toggle="edit-section-modal" class="space-x-3 text-white ml-2 w-11/12 bg-teal-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-teal-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-teal-600 dark:hover:bg-teal-700 dark:focus:ring-teal-800">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /> </svg>
<p>Edit Section</p>
</button>
</li>
{% endif %}
{% if show_delete_collection %}
<li>
<!-- prettier-ignore -->
@ -42,6 +61,16 @@
</button>
</li>
{% endif %}
{% if show_delete_section %}
<li>
<!-- prettier-ignore -->
<button type="button" data-modal-target="collection-collection-modal" data-modal-toggle="delete-collection-modal" class="space-x-3 text-white ml-2 w-11/12 bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /> </svg>
<p>Delete Section</p>
</button>
</li>
{% endif %}
</ul>
</div>
</aside>

View File

@ -1,15 +1,17 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% if book.owner.id == current_user.id %}
<!-- show edit collection btn on rightside bar -->
{% set show_edit_collection = True %}
<!-- show delete collection btn on rightside bar -->
{% set show_delete_collection = True %}
{% set show_create_section = True %}
{% set show_create_collection = not collection.sub_collections and not collection.is_leaf %}
<!-- prettier-ignore -->
{% include 'book/edit_collection_modal.html' %}
{% include 'book/delete_collection_modal.html' %}
{% include 'book/add_section_modal.html' %}
{% include 'book/add_collection_modal.html' %}
{% endif %}
<!-- prettier-ignore -->
@ -19,24 +21,21 @@
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4"> {{ book.owner.username }}/{{ book.label }} </h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">{{collection.label}}{% if sub_collection %}/ {{sub_collection.label}} {% endif %} </h1>
</div>
</div>
<!-- prettier-ignore -->
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">Sections page</h1>
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<!-- prettier-ignore -->
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 rounded-t-lg" id="files-tab" data-tabs-target="#files" type="button" role="tab" aria-controls="files" aria-selected="false">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
<span>Files</span>
</button>
</li>
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="about-tab" data-tabs-target="#about" type="button" role="tab" aria-controls="about" aria-selected="false">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" /> </svg>
<span>About</span>
@ -46,13 +45,9 @@
</div>
<div id="myTabContent">
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="files"
role="tabpanel"
aria-labelledby="files-tab">
<dl
class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="files" role="tabpanel" aria-labelledby="files-tab">
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
{% for section in sections %}
@ -72,7 +67,6 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /></svg>
<p>55</p>
</span>
<span class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
<p>55</p>
@ -82,15 +76,10 @@
</div>
</dl>
</a>
{% endfor %}
</dl>
</div>
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="about"
role="tabpanel"
aria-labelledby="about-tab">
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="about" role="tabpanel" aria-labelledby="about-tab">
<p class="text-sm text-gray-500 dark:text-gray-400">This is about</p>
</div>
</div>

View File

@ -7,6 +7,24 @@
<!-- prettier-ignore -->
{% block right_sidebar %} {% endblock %}
<div class="p-5">
<div class="flex justify-between ml-4 mb-3">
<h1 class="text-2xl font-extrabold dark:text-white">Settings</h1>
</div>
<form action="{{ url_for('book.edit', book_id=book.id) }}" method="post" class="mb-0 flex flex-col space-y-2 w-1/2">
{{ form_hidden_tag() }}
<input value="{{book.id}}" type="text" name="book_id" id="book_id" class="hidden" placeholder="Book id" required>
<div>
<label for="label" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Label</label>
<input value="{{book.label}}" type="text" name="label" id="label" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="My Book" required>
</div>
<div>
<button type="submit" class="text-center text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>
</div>
</form>
</div>
<div class="p-5">
<div class="flex justify-between ml-4 mb-2">
<h1 class="text-2xl font-extrabold dark:text-white">Contributors</h1>

View File

@ -2,17 +2,16 @@
{% extends 'base.html' %}
{% if book.owner.id == current_user.id %}
<!-- show edit collection btn on rightside bar -->
{% set show_edit_collection = True %}
<!-- show delete collection btn on rightside bar -->
{% set show_delete_collection = True %}
<!-- show create collection btn on rightside bar -->
{% set show_create_collection = True %}
{% set show_create_section = not collection.sub_collections or collection.is_leaf or not collection.children %}
<!-- prettier-ignore -->
{% include 'book/edit_collection_modal.html' %}
{% include 'book/delete_collection_modal.html' %}
{% include 'book/add_collection_modal.html' %}
{% include 'book/add_section_modal.html' %}
{% endif %}
@ -22,24 +21,23 @@
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4"> {{ book.owner.username }}/{{ book.label }} </h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">{{collection.label}}</h1>
</div>
</div>
<!-- prettier-ignore -->
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">
Sub collections page
</h1>
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<!-- prettier-ignore -->
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 rounded-t-lg" id="files-tab" data-tabs-target="#files" type="button" role="tab" aria-controls="files" aria-selected="false">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
<span>Files</span>
</button>
</li>
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="about-tab" data-tabs-target="#about" type="button" role="tab" aria-controls="about" aria-selected="false">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" /> </svg>
<span>About</span>
@ -49,22 +47,17 @@
</div>
<div id="myTabContent">
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="files"
role="tabpanel"
aria-labelledby="files-tab">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="files" role="tabpanel" aria-labelledby="files-tab">
<!-- prettier-ignore -->
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
{% for sub_collection in collection.children %}
{% for sub_collection in collection.sub_collections %}
<!-- prettier-ignore -->
<a href="{{url_for('book.section_view',book_id=book.id,collection_id=collection.id,sub_collection_id=sub_collection.id)}}">
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<div class="flex flex-col pb-3 p-3 w-full">
<dt
class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<dt class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<p>{{ sub_collection.label }}</p>
<div class="ml-auto">
<!-- prettier-ignore -->
@ -80,17 +73,13 @@
{% endfor %}
</dl>
</div>
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="about"
role="tabpanel"
aria-labelledby="about-tab">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="about" role="tabpanel" aria-labelledby="about-tab">
<p class="text-sm text-gray-500 dark:text-gray-400">
This is about of {{collection.about}}
</p>
</div>
</div>
<!-- prettier-ignore -->
{% endblock %}
<!-- prettier-ignore -->

View File

@ -2,25 +2,56 @@
class="fixed top-0 z-50 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<div class="px-3 py-3 lg:px-5 lg:pl-3">
<div class="flex items-center justify-between">
<div class="flex w-full items-center md:justify-start justify-between">
<div class="flex w-full items-center justify-between">
<!-- prettier-ignore -->
<button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
<span class="sr-only">Open sidebar</span>
<!-- prettier-ignore -->
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path> </svg>
</button>
<button data-drawer-target="logo-right-sidebar" data-drawer-toggle="logo-right-sidebar" aria-controls="logo-right-sidebar" type="button" class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
<span class="sr-only">Open filters</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" /> </svg>
</button>
<!-- prettier-ignore -->
<a href="/" class="flex ml-2 md:mr-24"> <img src="{{url_for('static',filename='img/logo.svg')}}" class="h-8 mr-3" alt="OpenLaw Logo" /> <span class="hidden md:inline-block self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white" >OpenLaw</span > </a>
<a href="/" class="flex ml-2 md:mr-24"> <img src="{{url_for('static',filename='img/logo.svg')}}" class="h-8 md:mr-3" alt="OpenLaw Logo" /> <span class="hidden md:inline-block self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white" >OpenLaw</span > </a>
<button
data-drawer-target="logo-right-sidebar"
data-drawer-toggle="logo-right-sidebar"
aria-controls="logo-right-sidebar"
type="button"
class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
<span class="sr-only">Open filters</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
</svg>
</button>
{% include 'searchbar.html' %}
<button id="theme-toggle" type="button" class="md:ml-auto text-gray-500 dark:text-gray-400 hover:bg-gray-100 mr-2 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5">
<button
id="theme-toggle"
type="button"
class="md:block hidden md:ml-auto text-gray-500 dark:text-gray-400 hover:bg-gray-100 md:mr-2 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5">
<!-- prettier-ignore -->
<svg id="theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path> </svg>
<svg id="theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" fill-rule="evenodd" clip-rule="evenodd"></path> </svg>
<svg
id="theme-toggle-light-icon"
class="hidden w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"></path>
</svg>
</button>
<!-- prettier-ignore -->
@ -28,79 +59,104 @@
<button id="connectWalletBtn" type="button" class="text-white bg-gradient-to-r from-cyan-500 to-blue-500 hover:bg-gradient-to-bl focus:ring-4 focus:outline-none focus:ring-cyan-300 dark:focus:ring-cyan-800 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2">Connect wallet</button>
{% endif %}
{% if current_user.is_authenticated %}
<div class="flex items-center ml-3">
<!-- prettier-ignore -->
<button id="dropdownNotificationButton" data-dropdown-toggle="dropdownNotification" class="inline-flex items-center text-sm font-medium text-center text-gray-500 hover:text-gray-900 focus:outline-none dark:hover:text-white dark:text-gray-400" type="button">
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z"></path> </svg>
<div class="relative flex">
<div
class="relative inline-flex w-3 h-3 bg-red-500 border-2 border-white rounded-full -top-2 right-3 dark:border-gray-900"></div>
</div>
</button>
<!-- Dropdown menu -->
<!--Those notification for now with dummy data-->
<!-- prettier-ignore -->
<div id="dropdownNotification" class="z-20 hidden w-full max-w-sm bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-800 dark:divide-gray-700" aria-labelledby="dropdownNotificationButton">
<div class="block px-4 py-2 font-medium text-center text-gray-700 rounded-t-lg bg-gray-50 dark:bg-gray-800 dark:text-white"> Notifications </div>
<div class="divide-y divide-gray-100 dark:divide-gray-700">
<a
href="#"
class="flex px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="flex-shrink-0">
<img
class="rounded-full w-11 h-11"
src="/docs/images/people/profile-picture-1.jpg"
alt="Jese image" />
<div
class="absolute flex items-center justify-center w-5 h-5 ml-6 -mt-5 bg-blue-600 border border-white rounded-full dark:border-gray-800">
<svg
class="w-3 h-3 text-white"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8.707 7.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l2-2a1 1 0 00-1.414-1.414L11 7.586V3a1 1 0 10-2 0v4.586l-.293-.293z"></path>
<path
d="M3 5a2 2 0 012-2h1a1 1 0 010 2H5v7h2l1 2h4l1-2h2V5h-1a1 1 0 110-2h1a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V5z"></path>
</svg>
</div>
</div>
<div class="w-full pl-3">
<div class="text-gray-500 text-sm mb-1.5 dark:text-gray-400">
New message from
<span class="font-semibold text-gray-900 dark:text-white"
>Jese Leos</span
>: "Hey, what's up? All set for the presentation?"
</div>
<div class="text-xs text-blue-600 dark:text-blue-500">
a few moments ago
</div>
</div>
</a>
<a
href="#"
class="block py-2 text-sm font-medium text-center text-gray-900 rounded-b-lg bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-white">
<div class="inline-flex items-center">
<svg
class="w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path>
<path
fill-rule="evenodd"
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
clip-rule="evenodd"></path>
</svg>
View all
<div class="flex">
<div class="items-center md:ml-3 hidden md:flex">
<!-- prettier-ignore -->
<button id="dropdownNotificationButton" data-dropdown-toggle="dropdownNotification" class="inline-flex items-center text-sm font-medium text-center text-gray-500 hover:text-gray-900 focus:outline-none dark:hover:text-white dark:text-gray-400" type="button">
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z"></path> </svg>
<div class="relative flex">
<div class="relative inline-flex w-3 h-3 bg-red-500 border-2 border-white rounded-full -top-2 right-3 dark:border-gray-900"></div>
</div>
</button>
</div>
<div class="items-center md:ml-3 hidden md:flex">
<div>
<!-- prettier-ignore -->
<button type="button" class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 md:mr-2 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5" aria-expanded="false" data-dropdown-toggle="dropdown-user" >
<span class="sr-only">Open user menu</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" /> </svg>
<!-- prettier-ignore -->
</button>
</div>
<!-- prettier-ignore -->
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:bg-gray-700 dark:divide-gray-600" id="dropdown-user" >
<div class="px-4 py-3" role="none">
<p class="text-center text-sm text-gray-900 dark:text-white" role="none"> {{current_user.username}} </p>
</div>
<ul class="py-1" role="none">
<li>
<a href="{{ url_for('auth.logout') }}" class="block px-4 py-2 text-sm dark:hover:bg-gray-600 dark:text-white" role="menuitem" >Sign out</a >
</li>
</ul>
</div>
</a>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% if current_user.is_authenticated %}
<!-- Dropdown menu -->
<!--Those notification for now with dummy data-->
<!-- prettier-ignore -->
<div id="dropdownNotification" class="shadow-md z-20 hidden w-screen bg-white divide-y divide-gray-100 rounded-lg dark:bg-gray-800 dark:divide-gray-700 border border-gray-600 dark:shadow-gray-600" aria-labelledby="dropdownNotificationButton">
<div class="block px-4 py-2 font-medium text-center text-gray-700 rounded-t-lg bg-gray-50 dark:bg-gray-800 dark:text-white"> Notifications </div>
<div class="divide-y divide-gray-100 dark:divide-gray-700">
<a
href="#"
class="flex px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="flex-shrink-0">
<img
class="rounded-full w-11 h-11"
src="/docs/images/people/profile-picture-1.jpg"
alt="Jese image" />
<div
class="absolute flex items-center justify-center w-5 h-5 ml-6 -mt-5 bg-blue-600 border border-white rounded-full dark:border-gray-800">
<svg
class="w-3 h-3 text-white"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8.707 7.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l2-2a1 1 0 00-1.414-1.414L11 7.586V3a1 1 0 10-2 0v4.586l-.293-.293z"></path>
<path
d="M3 5a2 2 0 012-2h1a1 1 0 010 2H5v7h2l1 2h4l1-2h2V5h-1a1 1 0 110-2h1a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V5z"></path>
</svg>
</div>
</div>
<div class="w-full pl-3">
<div class="text-gray-500 text-sm mb-1.5 dark:text-gray-400">
New message from
<span class="font-semibold text-gray-900 dark:text-white"
>Jese Leos</span
>: "Hey, what's up? All set for the presentation?"
</div>
<div class="text-xs text-blue-600 dark:text-blue-500">
a few moments ago
</div>
</div>
</a>
<a
href="#"
class="block py-2 text-sm font-medium text-center text-gray-900 rounded-b-lg bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-white">
<div class="inline-flex items-center">
<svg
class="w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path>
<path
fill-rule="evenodd"
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
clip-rule="evenodd"></path>
</svg>
View all
</div>
</a>
</div>
{% endif %}
</nav>

View File

@ -69,7 +69,7 @@
<!-- prettier-ignore -->
<h1 class="text-l font-extrabold dark:text-white my-2">{{section.path}}</h1>
{% endfor %}
<a type="button" href="{{ url_for('book.get_all') }}" class="mt-4 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"> Explore all sections... <svg aria-hidden="true" class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> </a>
<a type="button" href="#" class="mt-4 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"> Explore all sections... <svg aria-hidden="true" class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> </a>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,125 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block content %}
<div class="md:mr-64 relative overflow-x-auto shadow-md sm:rounded-lg mt-1">
<!-- prettier-ignore -->
<div class="p-5 flex border-b-2 border-gray-200 border-solid dark:border-gray-700 text-gray-900 dark:text-white dark:divide-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8"> <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0120.25 6v12A2.25 2.25 0 0118 20.25H6A2.25 2.25 0 013.75 18V6A2.25 2.25 0 016 3.75h1.5m9 0h-9" /> </svg>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">Sections</h1>
</div>
<dl
class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
{% for section in sections %}
<!-- prettier-ignore -->
<a
href="{{url_for('book.interpretation_view',book_id=section.book_id,collection_id=section.collection_id,sub_collection_id=section.sub_collection_id, section_id=section.id)}}">
<dl
class="bg-white dark:bg-gray-900 max-w-full p-3 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<div class="flex flex-col pb-3 p-3 w-full">
<dt
class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<!-- prettier-ignore -->
<p>{{ section.path }}</p>
<div class="flex ml-auto align-center justify-center space-x-3">
<span class="space-x-0.5 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 22 22"
stroke-width="1"
stroke="currentColor"
class="w-4 h-4 inline-flex mr-1">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
</svg>
<p>55</p>
</span>
<span class="space-x-0.5 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 22 22"
stroke-width="1"
stroke="currentColor"
class="w-4 h-4 inline-flex mr-1">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
<p>55</p>
</span>
</div>
</dt>
</div>
</dl>
</a>
{% endfor %}
</dl>
<!-- prettier-ignore -->
{% if page.pages > 1 %}
<div class="container content-center mt-3 flex bg-white dark:bg-gray-800">
<nav aria-label="Page navigation example" class="mx-auto">
<ul class="inline-flex items-center -space-x-px">
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('section.get_all') }}?page=1&q={{page.query}}" class="block px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<span class="sr-only">First</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M15.79 14.77a.75.75 0 01-1.06.02l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 111.04 1.08L11.832 10l3.938 3.71a.75.75 0 01.02 1.06zm-6 0a.75.75 0 01-1.06.02l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 111.04 1.08L5.832 10l3.938 3.71a.75.75 0 01.02 1.06z" clip-rule="evenodd" /> </svg>
</a>
</li>
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('section.get_all') }}?page={{page.page-1 if page.page > 1 else 1}}&q={{page.query}}" class="block px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<span class="sr-only">Previous</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
<!-- prettier-ignore -->
{% for p in page.pages_for_links %}
<li>
<!-- prettier-ignore -->
{% if p == page.page %}
<!-- prettier-ignore -->
<a href="{{ url_for('section.get_all') }}?page={{p}}&q={{page.query}}" aria-current="page" class="z-10 px-3 py-2 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white">{{p}}</a>
{% else %}
<!-- prettier-ignore -->
<a href="{{ url_for('section.get_all') }}?page={{p}}&q={{page.query}}" class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">{{p}}</a>
{% endif %}
</li>
{% endfor %}
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('section.get_all') }}?page={{page.page+1 if page.page < page.pages else page.pages}}&q={{page.query}}" class="block px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<!-- prettier-ignore -->
<span class="sr-only">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('section.get_all') }}?page={{page.pages}}&q={{page.query}}" class="block px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<!-- prettier-ignore -->
<span class="sr-only">Last</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M10.21 14.77a.75.75 0 01.02-1.06L14.168 10 10.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M4.21 14.77a.75.75 0 01.02-1.06L8.168 10 4.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
</ul>
</nav>
</div>
{% endif %}
</div>
<!-- prettier-ignore -->
{% endblock %}
<!-- prettier-ignore -->
{% block scripts %}
{% endblock %}

View File

@ -3,7 +3,27 @@
class="fixed top-0 left-0 z-40 w-64 h-screen pt-28 transition-transform -translate-x-full bg-white border-r border-gray-200 md:translate-x-0 dark:bg-gray-800 dark:border-gray-700"
aria-label="Sidebar">
<div class="h-full px-3 pb-4 overflow-y-auto bg-white dark:bg-gray-800">
<!-- prettier-ignore -->
<ul class="space-y-2 font-medium">
<li class="md:hidden">
<!-- prettier-ignore -->
<span class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white bg-gray-200 dark:bg-gray-700" >
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" /> </svg>
<p class="ml-2">{{ current_user.username }}</p>
</span>
</li>
<li>
<li class="md:hidden" id="dropdownNotificationButton" data-dropdown-toggle="dropdownNotification" >
<!-- prettier-ignore -->
<span class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" >
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z"></path> </svg>
<div class="relative flex">
<div
class="relative inline-flex w-3 h-3 bg-red-500 border-2 border-white rounded-full -top-2 right-3 dark:border-gray-900"></div>
</div>
<p class="ml-2">Notifications</p>
</span>
</li>
<li>
<a
href="{{ url_for('home.get_all') }}"
@ -35,17 +55,8 @@
<a
href="{{ url_for('user.get_all') }}"
class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">
<svg
aria-hidden="true"
class="flex-shrink-0 w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"></path>
</svg>
<!-- prettier-ignore -->
<svg aria-hidden="true" class="flex-shrink-0 w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path> </svg>
<span class="flex-1 ml-3 whitespace-nowrap">Users</span>
</a>
</li>
@ -67,6 +78,19 @@
</a>
</li>
<li class="md:hidden">
<div id="theme-toggle" class="absolute bottom-14 left-3 cursor-pointer flex items-center mt-2 p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">
<button type="button" class=" text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none rounded-lg text-sm">
<!-- prettier-ignore -->
<svg id="theme-toggle-dark-icon" class="hidden w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path> </svg>
<svg id="theme-toggle-light-icon" class="hidden w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" fill-rule="evenodd" clip-rule="evenodd"></path> </svg>
</button>
<span class="ml-3 dark:hidden">Dark Mode</span>
<span class="ml-3 dark:block hidden">Light Mode</span>
</div>
</li>
{% if current_user.is_authenticated %}
<li>
<a
href="{{ url_for('auth.logout') }}"
@ -85,6 +109,7 @@
<span class="flex-1 ml-3 whitespace-nowrap">Sign Out</span>
</a>
</li>
{% endif %}
</ul>
</div>
</aside>

View File

@ -4,3 +4,4 @@ from .main import main_blueprint
from .user import bp as user_blueprint
from .book import bp as book_blueprint
from .home import bp as home_blueprint
from .section import bp as section_blueprint

View File

@ -8,7 +8,7 @@ from flask import (
)
from flask_login import login_required, current_user
from app.controllers import create_pagination
from app.controllers import create_pagination, create_breadcrumbs
from app import models as m, db, forms as f
from app.logger import log
@ -76,15 +76,40 @@ def create():
return redirect(url_for("book.my_books"))
@bp.route("/<int:book_id>/edit", methods=["POST"])
@login_required
def edit(book_id: int):
form = f.EditBookForm()
if form.validate_on_submit():
book: m.Book = db.session.get(m.Book, book_id)
label = form.label.data
book.label = label
log(log.INFO, "Update Book: [%s]", book)
book.save()
flash("Success!", "success")
return redirect(url_for("book.collection_view", book_id=book_id))
else:
log(log.ERROR, "Book create errors: [%s]", form.errors)
for field, errors in form.errors.items():
field_label = form._fields[field].label.text
for error in errors:
flash(error.replace("Field", field_label), "danger")
return redirect(url_for("book.settings", book_id=book_id))
@bp.route("/<int:book_id>/collections", methods=["GET"])
def collection_view(book_id: int):
book = db.session.get(m.Book, book_id)
breadcrumbs = create_breadcrumbs(book_id=book_id, collection_path=())
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")
return redirect(url_for("book.my_books"))
else:
return render_template("book/collection_view.html", book=book)
return render_template(
"book/collection_view.html", book=book, breadcrumbs=breadcrumbs
)
@bp.route("/<int:book_id>/<int:collection_id>/subcollections", methods=["GET"])
@ -94,19 +119,22 @@ def sub_collection_view(book_id: int, collection_id: int):
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")
return redirect(url_for("book.my_books"))
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.is_deleted:
log(log.WARNING, "Collection with id [%s] not found", collection_id)
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
breadcrumbs = create_breadcrumbs(book_id=book_id, collection_path=(collection.id,))
if collection.is_leaf:
return redirect(
url_for("book.section_view", book_id=book.id, collection_id=collection.id)
)
else:
return render_template(
"book/sub_collection_view.html", book=book, collection=collection
"book/sub_collection_view.html",
book=book,
collection=collection,
breadcrumbs=breadcrumbs,
)
@ -145,9 +173,17 @@ def section_view(
)
if sub_collection:
sections = sub_collection.sections
sections = sub_collection.active_sections
else:
sections = collection.sections
sections = collection.active_sections
breadcrumbs = create_breadcrumbs(
book_id=book_id,
collection_path=(
collection_id,
sub_collection_id,
),
)
return render_template(
"book/section_view.html",
@ -155,6 +191,7 @@ def section_view(
collection=collection,
sections=sections,
sub_collection=sub_collection,
breadcrumbs=breadcrumbs,
)
@ -210,12 +247,21 @@ def interpretation_view(
)
)
else:
breadcrumbs = create_breadcrumbs(
book_id=book_id,
collection_path=(
collection_id,
sub_collection_id,
),
section_id=section_id,
)
return render_template(
"book/interpretation_view.html",
book=book,
collection=collection,
sub_collection=sub_collection if sub_collection_id else None,
section=section,
breadcrumbs=breadcrumbs,
)
@ -590,3 +636,231 @@ def collection_delete(
book_id=book_id,
)
)
###############
# Sections CRUD
###############
@bp.route("/<int:book_id>/<int:collection_id>/create_section", methods=["POST"])
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/create_section",
methods=["POST"],
)
@login_required
def section_create(
book_id: int, collection_id: int, sub_collection_id: int | None = None
):
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")
return redirect(url_for("book.my_books"))
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.is_deleted:
log(log.WARNING, "Collection with id [%s] not found", collection_id)
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
sub_collection = None
if sub_collection_id:
sub_collection: m.Collection = db.session.get(m.Collection, sub_collection_id)
if not sub_collection or sub_collection.is_deleted:
log(log.WARNING, "Sub_collection with id [%s] not found", sub_collection_id)
flash("Subcollection not found", "danger")
return redirect(
url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
)
)
redirect_url = url_for("book.collection_view", book_id=book_id)
if collection_id:
redirect_url = url_for(
"book.section_view",
book_id=book_id,
collection_id=collection_id,
sub_collection_id=sub_collection_id,
)
form = f.CreateSectionForm()
if form.validate_on_submit():
section: m.Section = m.Section(
label=form.label.data,
about=form.about.data,
collection_id=sub_collection_id or collection_id,
version_id=book.last_version.id,
)
if sub_collection:
sub_collection.is_leaf = True
else:
collection.is_leaf = True
log(log.INFO, "Create section [%s]. Collection: [%s]", section, collection_id)
section.save()
flash("Success!", "success")
return redirect(redirect_url)
else:
log(log.ERROR, "Section create errors: [%s]", form.errors)
for field, errors in form.errors.items():
field_label = form._fields[field].label.text
for error in errors:
flash(error.replace("Field", field_label), "danger")
return redirect(redirect_url)
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:section_id>/edit_section", methods=["POST"]
)
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/<int:section_id>/edit_section",
methods=["POST"],
)
@login_required
def section_edit(
book_id: int,
collection_id: int,
section_id: int,
sub_collection_id: int | None = None,
):
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.owner != current_user or book.is_deleted:
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
flash("You are not owner of this book!", "danger")
return redirect(url_for("book.my_books"))
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.is_deleted:
log(log.WARNING, "Collection with id [%s] not found", collection_id)
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
if sub_collection_id:
sub_collection: m.Collection = db.session.get(m.Collection, sub_collection_id)
if not sub_collection or sub_collection.is_deleted:
log(log.WARNING, "Sub_collection with id [%s] not found", sub_collection_id)
flash("SubCollection not found", "danger")
return redirect(
url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
)
)
redirect_url = url_for(
"book.interpretation_view",
book_id=book_id,
collection_id=collection_id,
sub_collection_id=sub_collection_id,
section_id=section_id,
)
section: m.Section = db.session.get(m.Section, section_id)
if not section or section.is_deleted:
log(log.WARNING, "Section with id [%s] not found", section_id)
flash("Section not found", "danger")
return redirect(redirect_url)
form = f.EditSectionForm()
if form.validate_on_submit():
label = form.label.data
if label:
section.label = label
about = form.about.data
if about:
section.about = about
log(log.INFO, "Edit section [%s]", section.id)
section.save()
flash("Success!", "success")
return redirect(redirect_url)
else:
log(log.ERROR, "Section edit errors: [%s]", form.errors)
for field, errors in form.errors.items():
field_label = form._fields[field].label.text
for error in errors:
flash(error.replace("Field", field_label), "danger")
return redirect(redirect_url)
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:section_id>/delete_section",
methods=["POST"],
)
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/<int:section_id>/delete_section",
methods=["POST"],
)
@login_required
def section_delete(
book_id: int,
collection_id: int,
section_id: int,
sub_collection_id: int | None = None,
):
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.owner != current_user:
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
flash("You are not owner of this book!", "danger")
return redirect(url_for("book.my_books"))
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.is_deleted:
log(log.WARNING, "Collection with id [%s] not found", collection_id)
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
collection_to_edit = collection
if sub_collection_id:
sub_collection: m.Collection = db.session.get(m.Collection, sub_collection_id)
if not sub_collection or sub_collection.is_deleted:
log(log.WARNING, "Sub_collection with id [%s] not found", sub_collection_id)
flash("SubCollection not found", "danger")
return redirect(
url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
)
)
collection_to_edit = sub_collection
redirect_url = url_for(
"book.section_view",
book_id=book_id,
collection_id=collection_id,
sub_collection_id=sub_collection_id,
)
section: m.Section = db.session.get(m.Section, section_id)
if not section or section.is_deleted:
log(log.WARNING, "Section with id [%s] not found", section_id)
flash("Section not found", "danger")
return redirect(redirect_url)
section.is_deleted = True
if not collection_to_edit.active_sections:
log(
log.INFO,
"Section [%s] has no active section. Set is_leaf = False",
section.id,
)
collection_to_edit.is_leaf = False
log(log.INFO, "Delete section [%s]", section.id)
section.save()
flash("Success!", "success")
return redirect(
url_for(
"book.collection_view",
book_id=book_id,
)
)

29
app/views/section.py Normal file
View File

@ -0,0 +1,29 @@
from flask import (
Blueprint,
render_template,
request,
)
from app.controllers import create_pagination
from app import models as m
bp = Blueprint("section", __name__, url_prefix="/section")
@bp.route("/all", methods=["GET"])
def get_all():
q = request.args.get("q", type=str, default=None)
section: m.Section = m.Section.query.order_by(m.Section.id)
if q:
section = section.filter(m.Section.label.like(f"{q}"))
pagination = create_pagination(total=section.count())
return render_template(
"section/all.html",
sections=section.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
search_query=q,
all_books=True,
)

View File

@ -3,7 +3,7 @@ const searchAndShowResults = async (
searchResultsTbody: any,
trExample: any,
userIdInput: any,
) => {
): Promise<undefined> => {
searchResultsTbody.innerHTML = '';
const bookId = userSearchbar.getAttribute('data-book-id');

20
src/initQuill.ts Normal file
View File

@ -0,0 +1,20 @@
const Quill = require('./quill');
export function initQuill() {
var toolbarOptions = [
['bold', 'italic', 'underline'],
[{list: 'ordered'}, {list: 'bullet'}],
[{header: [1, 2, 3, 4, 5, 6, false]}],
[{indent: '-1'}, {indent: '+1'}],
['clean'],
];
const editorElement = document.querySelector('#editor');
if (editorElement) {
var quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: toolbarOptions,
},
});
}
}

View File

@ -2,9 +2,13 @@ import './styles.css';
import {initBooks} from './books';
import {initContributors} from './contributors';
import {initWallet} from './wallet';
import {initQuill} from './initQuill';
import {initQuillValueToTextArea} from './quill_value_to_textarea';
document.addEventListener('DOMContentLoaded', () => {
initBooks();
initContributors();
initQuill();
initQuillValueToTextArea();
initWallet();
});

17398
src/quill.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
const quill_value_to_textarea = (): undefined => {
const aboutInput: HTMLButtonElement = document.querySelector('#about');
const qlEditor: HTMLButtonElement = document.querySelector('.ql-editor');
const editorContent = qlEditor.innerHTML;
aboutInput.value = editorContent;
return undefined;
};
export function initQuillValueToTextArea() {
const qlEditor: HTMLButtonElement = document.querySelector('.ql-editor');
if (!qlEditor) {
return;
}
qlEditor.addEventListener('DOMSubtreeModified', async e => {
quill_value_to_textarea();
});
}

View File

@ -5,7 +5,7 @@ from app import models as m, db
from tests.utils import login
def test_create_book(client: FlaskClient):
def test_create_edit_book(client: FlaskClient):
login(client)
BOOK_NAME = "Test Book"
@ -61,6 +61,44 @@ def test_create_book(client: FlaskClient):
assert book.versions
assert len(book.versions) == 1
response: Response = client.post(
"/book/999/edit",
data=dict(
book_id=999,
label="Book 1",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Book not found" in response.data
response: Response = client.post(
f"/book/{book.id}/edit",
data=dict(
book_id=book.id,
label=BOOK_NAME,
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Book label must be unique!" in response.data
response: Response = client.post(
f"/book/{book.id}/edit",
data=dict(
book_id=book.id,
label=BOOK_NAME + " EDITED",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
book = db.session.get(m.Book, book.id)
assert book.label != BOOK_NAME
def test_add_contributor(client: FlaskClient):
_, user = login(client)
@ -300,12 +338,12 @@ def test_crud_subcollection(client: FlaskClient, runner: FlaskCliRunner):
leaf_collection: m.Collection = m.Collection(
label="Test Leaf Collection #1 Label",
version_id=book.versions[-1].id,
version_id=book.last_version.id,
is_leaf=True,
parent_id=book.versions[-1].root_collection.id,
parent_id=book.last_version.root_collection.id,
).save()
collection: m.Collection = m.Collection(
label="Test Collection #1 Label", version_id=book.versions[-1].id
label="Test Collection #1 Label", version_id=book.last_version.id
).save()
response: Response = client.post(
@ -415,3 +453,248 @@ def test_crud_subcollection(client: FlaskClient, runner: FlaskCliRunner):
assert response.status_code == 200
assert b"Collection not found" in response.data
def test_crud_sections(client: FlaskClient, runner: FlaskCliRunner):
_, user = login(client)
user: m.User
# add dummmy data
runner.invoke(args=["db-populate"])
book: m.Book = db.session.get(m.Book, 1)
book.user_id = user.id
book.save()
leaf_collection: m.Collection = m.Collection(
label="Test Leaf Collection #1 Label",
version_id=book.last_version.id,
is_leaf=True,
parent_id=book.last_version.root_collection.id,
).save()
collection: m.Collection = m.Collection(
label="Test Collection #1 Label", version_id=book.last_version.id
).save()
sub_collection: m.Collection = m.Collection(
label="Test SubCollection #1 Label",
version_id=book.last_version.id,
parent_id=collection.id,
is_leaf=True,
).save()
leaf_collection.is_leaf = False
leaf_collection.save()
response: Response = client.post(
f"/book/{book.id}/{collection.id}/create_section",
data=dict(
collection_id=collection.id,
label="Test Section",
about="Test Section #1 About",
),
follow_redirects=True,
)
assert b"You can't create section for this collection" in response.data
leaf_collection.is_leaf = True
leaf_collection.save()
label_1 = "Test Section #1 Label"
response: Response = client.post(
f"/book/{book.id}/{leaf_collection.id}/create_section",
data=dict(
collection_id=leaf_collection.id,
label=label_1,
about="Test Section #1 About",
),
follow_redirects=True,
)
assert response.status_code == 200
section: m.Section = m.Section.query.filter_by(
label=label_1, collection_id=leaf_collection.id
).first()
assert section
assert section.collection_id == leaf_collection.id
assert section.version_id == book.last_version.id
assert not section.interpretations
response: Response = client.post(
f"/book/{book.id}/{leaf_collection.id}/create_section",
data=dict(
collection_id=leaf_collection.id,
label=label_1,
about="Test Section #1 About",
),
follow_redirects=True,
)
assert b"Section label must be unique!" in response.data
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{sub_collection.id}/create_section",
data=dict(
collection_id=sub_collection.id,
label=label_1,
about="Test Section #1 About",
),
follow_redirects=True,
)
assert response.status_code == 200
section: m.Section = m.Section.query.filter_by(
label=label_1, collection_id=sub_collection.id
).first()
assert section
assert section.collection_id == sub_collection.id
assert section.version_id == book.last_version.id
assert not section.interpretations
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{sub_collection.id}/create_section",
data=dict(
collection_id=sub_collection.id,
label=label_1,
about="Test Section #1 About",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Section label must be unique!" in response.data
response: Response = client.post(
f"/book/{book.id}/999/create_section",
data=dict(
collection_id=999,
label=label_1,
about="Test Section #1 About",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Collection not found" in response.data
response: Response = client.post(
f"/book/{book.id}/{collection.id}/999/create_section",
data=dict(collection_id=999, label=label_1, about="Test Section #1 About"),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Subcollection not found" in response.data
# edit
m.Section(
label="Test",
about="Test",
collection_id=leaf_collection.id,
version_id=book.last_version.id,
).save()
m.Section(
label="Test",
about="Test",
collection_id=sub_collection.id,
version_id=book.last_version.id,
).save()
section: m.Section = m.Section.query.filter_by(
label=label_1, collection_id=leaf_collection.id
).first()
response: Response = client.post(
f"/book/{book.id}/{leaf_collection.id}/{section.id}/edit_section",
data=dict(
section_id=section.id,
label="Test",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Section label must be unique!" in response.data
new_label = "Test Section #1 Label(edited)"
new_about = "Test Section #1 About(edited)"
response: Response = client.post(
f"/book/{book.id}/{leaf_collection.id}/{section.id}/edit_section",
data=dict(section_id=section.id, label=new_label, about=new_about),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
edited_section: m.Section = m.Section.query.filter_by(
label=new_label, about=new_about, id=section.id
).first()
assert edited_section
#
section_2: m.Section = m.Section.query.filter_by(
label=label_1, collection_id=sub_collection.id
).first()
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{sub_collection.id}/{section_2.id}/edit_section",
data=dict(
section_id=section_2.id,
label="Test",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Section label must be unique!" in response.data
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{sub_collection.id}/{section_2.id}/edit_section",
data=dict(section_id=section_2.id, label=new_label, about=new_about),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
edited_section: m.Section = m.Section.query.filter_by(
label=new_label, about=new_about, id=section_2.id
).first()
assert edited_section
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{sub_collection.id}/999/edit_section",
data=dict(section_id=section_2.id, label=new_label, about=new_about),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Section not found" in response.data
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{leaf_collection.id}/{section.id}/delete_section",
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
deleted_section: m.Section = db.session.get(m.Section, section.id)
assert deleted_section.is_deleted
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{sub_collection.id}/{section_2.id}/delete_section",
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
deleted_section: m.Section = db.session.get(m.Section, section_2.id)
assert deleted_section.is_deleted
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{sub_collection.id}/999/delete_section",
follow_redirects=True,
)
assert response.status_code == 200
assert b"Section not found" in response.data

18
tests/test_breadcrumbs.py Normal file
View File

@ -0,0 +1,18 @@
from flask.testing import FlaskCliRunner
from app.controllers import create_breadcrumbs
from app import models as m, db
def test_breadcrumbs(runner: FlaskCliRunner, app):
runner.invoke(args=["db-populate"])
with app.app_context(), app.test_request_context():
res = create_breadcrumbs(1, (1,), 1)
assert len(res) == 4
book: m.Book = db.session.get(m.Book, 1)
assert book
assert book.owner.username in res[0].label
assert res[1].label == book.label
with app.app_context(), app.test_request_context():
res = create_breadcrumbs(1, (), 1)
assert res
assert len(res) == 2