mirror of https://github.com/logos-co/open-law.git
Merge branch 'develop' into svyat/feat/user_menu_on_header
This commit is contained in:
commit
dcbabf8fbc
|
@ -1,2 +1,3 @@
|
|||
# flake8: noqa F401
|
||||
from .pagination import create_pagination
|
||||
from .breadcrumbs import create_breadcrumbs
|
||||
|
|
|
@ -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
|
|
@ -1,9 +1,7 @@
|
|||
# flake8: noqa F401
|
||||
from .auth import LoginForm
|
||||
from .user import UserForm, NewUserForm
|
||||
from .book import (
|
||||
CreateBookForm,
|
||||
)
|
||||
from .book import CreateBookForm, EditBookForm
|
||||
from .contributor import (
|
||||
AddContributorForm,
|
||||
DeleteContributorForm,
|
||||
|
|
|
@ -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!")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# flake8: noqa F401
|
||||
from .pagination import Pagination
|
||||
from .user import User
|
||||
from .breadcrumbs import BreadCrumbType, BreadCrumb
|
||||
|
|
|
@ -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
|
File diff suppressed because one or more lines are too long
|
@ -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">
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -2,19 +2,11 @@
|
|||
{% extends 'base.html' %}
|
||||
{% 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}}/{{sub_collection.label}}/{{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">
|
||||
|
@ -32,7 +24,11 @@
|
|||
<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 interpretation in section.interpretations %}
|
||||
<a href="{{url_for('book.interpretation_view',book_id=book.id,collection_id=collection.id,sub_collection_id=sub_collection.id, section_id=section.id)}}" >
|
||||
{% if sub_collection %}
|
||||
<a href="{{url_for('book.interpretation_view', book_id=book.id, collection_id=collection.id, sub_collection_id=sub_collection.id, section_id=section.id)}}" >
|
||||
{% 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 -->
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% if book.owner.id == current_user.id %}
|
||||
<!-- show edit collection btn on rightside bar -->
|
||||
{% set show_edit_collection = True %}
|
||||
|
@ -19,24 +18,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}}/{{sub_collection.label}}</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,18 +42,18 @@
|
|||
</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 sub_collection.sections %}
|
||||
{% for section in sections %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<a href="{{url_for('book.interpretation_view',book_id=book.id,collection_id=collection.id,sub_collection_id=sub_collection.id, section_id=section.id)}}">
|
||||
{% if sub_collection %}
|
||||
<a href="{{url_for('book.interpretation_view', book_id=book.id, collection_id=collection.id, sub_collection_id=sub_collection.id, section_id=section.id)}}">
|
||||
{% 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">
|
||||
<dt class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
|
||||
|
@ -68,7 +64,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>
|
||||
|
@ -78,18 +73,11 @@
|
|||
</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">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
This is about of {{sub_collection.label}}
|
||||
</p>
|
||||
<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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,24 +22,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 +48,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 %}
|
||||
|
||||
<!-- 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 +74,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 -->
|
||||
|
|
|
@ -65,11 +65,11 @@
|
|||
</div>
|
||||
<!-- prettier-ignore -->
|
||||
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="all-sections" role="tabpanel" aria-labelledby="all-sections-tab">
|
||||
{% for section in sections %}
|
||||
<!-- prettier-ignore -->
|
||||
<h1 class="text-l font-extrabold dark:text-white my-2">{{section.path}}</h1>
|
||||
{% for section in sections %}
|
||||
<!-- prettier-ignore -->
|
||||
<h1 class="text-l font-extrabold dark:text-white my-2">{{section.path}}</h1>
|
||||
{% endfor %}
|
||||
<a type="button" href="{{ url_for('section.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 %}
|
||||
|
|
|
@ -35,17 +35,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>
|
||||
|
|
|
@ -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,81 +76,75 @@ def create():
|
|||
return redirect(url_for("book.my_books"))
|
||||
|
||||
|
||||
@bp.route("/<int:book_id>", methods=["GET"])
|
||||
@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>", methods=["GET"])
|
||||
@bp.route("/<int:book_id>/<int:collection_id>/subcollections", methods=["GET"])
|
||||
def sub_collection_view(book_id: int, collection_id: int):
|
||||
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))
|
||||
breadcrumbs = create_breadcrumbs(book_id=book_id, collection_path=(collection.id,))
|
||||
if collection.is_leaf:
|
||||
return render_template(
|
||||
"book/section_view.html",
|
||||
book=book,
|
||||
collection=collection,
|
||||
sub_collection=collection,
|
||||
)
|
||||
else:
|
||||
return render_template(
|
||||
"book/sub_collection_view.html", book=book, collection=collection
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/<int:book_id>/<int:collection_id>/<int:sub_collection_id>", methods=["GET"])
|
||||
def section_view(book_id: int, collection_id: int, sub_collection_id: int):
|
||||
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: 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("Sub_collection not found", "danger")
|
||||
return redirect(
|
||||
url_for(
|
||||
"book.sub_collection_view", book_id=book_id, collection_id=collection_id
|
||||
)
|
||||
url_for("book.section_view", book_id=book.id, collection_id=collection.id)
|
||||
)
|
||||
else:
|
||||
return render_template(
|
||||
"book/section_view.html",
|
||||
"book/sub_collection_view.html",
|
||||
book=book,
|
||||
collection=collection,
|
||||
sub_collection=sub_collection,
|
||||
breadcrumbs=breadcrumbs,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/<int:book_id>/<int:collection_id>/sections", methods=["GET"])
|
||||
@bp.route(
|
||||
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/<int:section_id>",
|
||||
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/sections",
|
||||
methods=["GET"],
|
||||
)
|
||||
def interpretation_view(
|
||||
book_id: int, collection_id: int, sub_collection_id: int, section_id: int
|
||||
def section_view(
|
||||
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:
|
||||
|
@ -164,15 +158,81 @@ def interpretation_view(
|
|||
flash("Collection not found", "danger")
|
||||
return redirect(url_for("book.collection_view", book_id=book_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("Sub_collection not found", "danger")
|
||||
return redirect(
|
||||
url_for(
|
||||
"book.sub_collection_view", book_id=book_id, collection_id=collection_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("Sub_collection not found", "danger")
|
||||
return redirect(
|
||||
url_for(
|
||||
"book.sub_collection_view",
|
||||
book_id=book_id,
|
||||
collection_id=collection_id,
|
||||
)
|
||||
)
|
||||
|
||||
if sub_collection:
|
||||
sections = sub_collection.sections
|
||||
else:
|
||||
sections = collection.sections
|
||||
|
||||
breadcrumbs = create_breadcrumbs(
|
||||
book_id=book_id,
|
||||
collection_path=(
|
||||
collection_id,
|
||||
sub_collection_id,
|
||||
),
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"book/section_view.html",
|
||||
book=book,
|
||||
collection=collection,
|
||||
sections=sections,
|
||||
sub_collection=sub_collection,
|
||||
breadcrumbs=breadcrumbs,
|
||||
)
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/<int:book_id>/<int:collection_id>/<int:section_id>/interpretations",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/<int:section_id>/interpretations",
|
||||
methods=["GET"],
|
||||
)
|
||||
def interpretation_view(
|
||||
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.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))
|
||||
|
||||
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("Sub_collection not found", "danger")
|
||||
return redirect(
|
||||
url_for(
|
||||
"book.sub_collection_view",
|
||||
book_id=book_id,
|
||||
collection_id=collection_id,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
section: m.Section = db.session.get(m.Section, section_id)
|
||||
if not section:
|
||||
|
@ -187,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,
|
||||
sub_collection=sub_collection if sub_collection_id else None,
|
||||
section=section,
|
||||
breadcrumbs=breadcrumbs,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue