mirror of
https://github.com/logos-co/open-law.git
synced 2025-01-09 22:35:50 +00:00
Merge branch 'develop' into svyat/feat/user_permissions
This commit is contained in:
commit
1554015e31
@ -22,7 +22,6 @@ def create_app(environment="development"):
|
|||||||
user_blueprint,
|
user_blueprint,
|
||||||
book_blueprint,
|
book_blueprint,
|
||||||
home_blueprint,
|
home_blueprint,
|
||||||
section_blueprint,
|
|
||||||
vote_blueprint,
|
vote_blueprint,
|
||||||
approve_blueprint,
|
approve_blueprint,
|
||||||
star_blueprint,
|
star_blueprint,
|
||||||
@ -55,7 +54,6 @@ def create_app(environment="development"):
|
|||||||
app.register_blueprint(user_blueprint)
|
app.register_blueprint(user_blueprint)
|
||||||
app.register_blueprint(book_blueprint)
|
app.register_blueprint(book_blueprint)
|
||||||
app.register_blueprint(home_blueprint)
|
app.register_blueprint(home_blueprint)
|
||||||
app.register_blueprint(section_blueprint)
|
|
||||||
app.register_blueprint(vote_blueprint)
|
app.register_blueprint(vote_blueprint)
|
||||||
app.register_blueprint(approve_blueprint)
|
app.register_blueprint(approve_blueprint)
|
||||||
app.register_blueprint(star_blueprint)
|
app.register_blueprint(star_blueprint)
|
||||||
|
@ -39,7 +39,7 @@ def create_breadcrumbs(
|
|||||||
crumples += [
|
crumples += [
|
||||||
s.BreadCrumb(
|
s.BreadCrumb(
|
||||||
type=s.BreadCrumbType.AuthorBookList,
|
type=s.BreadCrumbType.AuthorBookList,
|
||||||
url="#",
|
url="",
|
||||||
label=book.owner.username + "'s books",
|
label=book.owner.username + "'s books",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -60,11 +60,7 @@ def create_breadcrumbs(
|
|||||||
crumples += [
|
crumples += [
|
||||||
s.BreadCrumb(
|
s.BreadCrumb(
|
||||||
type=s.BreadCrumbType.Collection,
|
type=s.BreadCrumbType.Collection,
|
||||||
url=url_for(
|
url="",
|
||||||
"book.sub_collection_view",
|
|
||||||
book_id=book_id,
|
|
||||||
collection_id=collection_id,
|
|
||||||
),
|
|
||||||
label=collection.label,
|
label=collection.label,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -72,12 +68,7 @@ def create_breadcrumbs(
|
|||||||
crumples += [
|
crumples += [
|
||||||
s.BreadCrumb(
|
s.BreadCrumb(
|
||||||
type=s.BreadCrumbType.Section,
|
type=s.BreadCrumbType.Section,
|
||||||
url=url_for(
|
url="",
|
||||||
"book.section_view",
|
|
||||||
book_id=book_id,
|
|
||||||
collection_id=collection_path[0],
|
|
||||||
sub_collection_id=collection_path[-1],
|
|
||||||
),
|
|
||||||
label=collection.label,
|
label=collection.label,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -28,6 +28,8 @@ def delete_nested_version_entities(book_version: m.BookVersion):
|
|||||||
|
|
||||||
|
|
||||||
def delete_nested_collection_entities(collection: m.Collection):
|
def delete_nested_collection_entities(collection: m.Collection):
|
||||||
|
for sub_collection in collection.children:
|
||||||
|
delete_nested_collection_entities(sub_collection)
|
||||||
for section in collection.sections:
|
for section in collection.sections:
|
||||||
section: m.Section
|
section: m.Section
|
||||||
section.is_deleted = True
|
section.is_deleted = True
|
||||||
|
@ -25,7 +25,7 @@ def set_book_tags(book: m.Book, tags: str):
|
|||||||
for book_tag in book_tags:
|
for book_tag in book_tags:
|
||||||
db.session.delete(book_tag)
|
db.session.delete(book_tag)
|
||||||
|
|
||||||
tags_names = [tag.title() for tag in tags.split(",") if len(tag)]
|
tags_names = [tag.lower() for tag in tags.split(",") if len(tag)]
|
||||||
|
|
||||||
for tag_name in tags_names:
|
for tag_name in tags_names:
|
||||||
try:
|
try:
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField
|
from wtforms import StringField, SubmitField, ValidationError
|
||||||
from wtforms.validators import DataRequired
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
from app.controllers import clean_html
|
||||||
|
from app.logger import log
|
||||||
|
|
||||||
|
|
||||||
class BaseInterpretationForm(FlaskForm):
|
class BaseInterpretationForm(FlaskForm):
|
||||||
about = StringField("About")
|
about = StringField("About")
|
||||||
@ -12,6 +15,14 @@ class CreateInterpretationForm(BaseInterpretationForm):
|
|||||||
section_id = StringField("Interpretation ID", [DataRequired()])
|
section_id = StringField("Interpretation ID", [DataRequired()])
|
||||||
submit = SubmitField("Create")
|
submit = SubmitField("Create")
|
||||||
|
|
||||||
|
def validate_text(self, field):
|
||||||
|
text = clean_html(field.data)
|
||||||
|
text = text.replace(" ", "")
|
||||||
|
text = text.strip()
|
||||||
|
if len(text) < 1:
|
||||||
|
log(log.WARNING, "Can't submit empty interpretation")
|
||||||
|
raise ValidationError("You can't create interpretation with no text")
|
||||||
|
|
||||||
|
|
||||||
class EditInterpretationForm(BaseInterpretationForm):
|
class EditInterpretationForm(BaseInterpretationForm):
|
||||||
interpretation_id = StringField("Interpretation ID", [DataRequired()])
|
interpretation_id = StringField("Interpretation ID", [DataRequired()])
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import base64
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import (
|
from wtforms import (
|
||||||
StringField,
|
StringField,
|
||||||
@ -58,7 +60,7 @@ class NewUserForm(FlaskForm):
|
|||||||
|
|
||||||
class EditUserForm(FlaskForm):
|
class EditUserForm(FlaskForm):
|
||||||
name = StringField("Name", [DataRequired()])
|
name = StringField("Name", [DataRequired()])
|
||||||
avatar_img = FileField("Avatar file (max 200x200px)")
|
avatar_img = FileField("Avatar file (max 1mb, formats: jpg,jpeg,png)")
|
||||||
submit = SubmitField("Save")
|
submit = SubmitField("Save")
|
||||||
|
|
||||||
def validate_username(self, field):
|
def validate_username(self, field):
|
||||||
@ -69,6 +71,16 @@ class EditUserForm(FlaskForm):
|
|||||||
):
|
):
|
||||||
raise ValidationError("This username is taken.")
|
raise ValidationError("This username is taken.")
|
||||||
|
|
||||||
|
def validate_avatar_img(self, field):
|
||||||
|
if field.data:
|
||||||
|
img_data = field.data.read()
|
||||||
|
img_data = base64.b64encode(img_data)
|
||||||
|
img_data = img_data.decode("utf-8")
|
||||||
|
field.data = img_data
|
||||||
|
size = len(img_data) / 1000000
|
||||||
|
if size > 1:
|
||||||
|
raise ValidationError("Avatar file size too large")
|
||||||
|
|
||||||
|
|
||||||
class ReactivateUserForm(FlaskForm):
|
class ReactivateUserForm(FlaskForm):
|
||||||
submit = SubmitField("Save")
|
submit = SubmitField("Save")
|
||||||
|
@ -36,4 +36,8 @@ class BookVersion(BaseModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def children_collections(self):
|
def children_collections(self):
|
||||||
return self.root_collection.children
|
return [
|
||||||
|
collection
|
||||||
|
for collection in self.root_collection.children
|
||||||
|
if not collection.is_deleted
|
||||||
|
]
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,7 +1,10 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% block body %}
|
|
||||||
|
|
||||||
|
{% block title %}Login{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
<!-- component -->
|
<!-- component -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-900 h-screen pt-20">
|
<div class="bg-gray-50 dark:bg-gray-900 h-screen pt-20">
|
||||||
<section>
|
<section>
|
||||||
|
@ -2,13 +2,21 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>{{ config.APP_NAME }}</title>
|
|
||||||
|
<title>{% block title %}{{ config.APP_NAME }}{% endblock %}</title>
|
||||||
|
|
||||||
<!-- meta -->
|
<!-- meta -->
|
||||||
<meta name="description" content="OpenLaw Flask App" />
|
<meta name="description" content="OpenLaw Flask App" />
|
||||||
<meta name="author" content="Simple2B" />
|
<meta name="author" content="Simple2B" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
|
||||||
{% block meta %}{% endblock %}
|
{% block meta %}{% endblock %}
|
||||||
|
<!-- favicon -->
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/x-icon"
|
||||||
|
href="{{ url_for('static', filename='img/logo.svg') }}"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- styles -->
|
<!-- styles -->
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
@ -35,7 +43,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-white dark:bg-gray-800">
|
<body class="bg-white dark:bg-gray-800" >
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% include 'header.html' %}
|
{% include 'header.html' %}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<div class="grid gap-6">
|
<div class="grid gap-6">
|
||||||
<div class="col-span-6 sm:col-span-3">
|
<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 >
|
<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 />
|
<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="Section label" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
|
{% block title %}Books{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="md:mr-64 relative overflow-x-auto shadow-md sm:rounded-lg mt-1">
|
<div class="md:mr-64 relative overflow-x-auto shadow-md sm:rounded-lg mt-1">
|
||||||
<!-- prettier-ignore -->
|
<!-- 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">
|
<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">
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
{% include 'book/delete_section_modal.html' %}
|
{% include 'book/delete_section_modal.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% block title %}{{book.label[:32]}}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="flex overflow-hidden">
|
<div class="flex overflow-hidden">
|
||||||
@ -29,7 +31,7 @@
|
|||||||
<div class="flex text-black dark:text-white">
|
<div class="flex text-black dark:text-white">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div>
|
<div>
|
||||||
{% if not book.versions[-1].children_collections %}
|
{% if not book.versions[-1].children_collections and current_user.is_authenticated %}
|
||||||
<button type="button" data-modal-target="add-collection-modal" data-modal-toggle="add-collection-modal" ><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 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> </button>
|
<button type="button" data-modal-target="add-collection-modal" data-modal-toggle="add-collection-modal" ><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 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> </button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ url_for("book.settings", book_id=book.id) }}" type="button" class="ml-2" >
|
<a href="{{ url_for("book.settings", book_id=book.id) }}" type="button" class="ml-2" >
|
||||||
@ -262,7 +264,7 @@
|
|||||||
|
|
||||||
<div class="gap-1 flex flex-wrap">
|
<div class="gap-1 flex flex-wrap">
|
||||||
{% for tag in book.tags %}
|
{% for tag in book.tags %}
|
||||||
<div class="cursor-pointer multiple-input-word bg-sky-300 hover:bg-sky-400 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white rounded text-center py-1/2 px-2">{{tag.name}}</div>
|
<a href="{{url_for('search.tag_search_interpretations',tag_name=tag.name)}}"><div class="cursor-pointer multiple-input-word bg-sky-300 hover:bg-sky-400 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white rounded text-center py-1/2 px-2">{{tag.name}}</div></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% set selected_tab='favorite_books' %}
|
{% set selected_tab='favorite_books' %}
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
|
{% block title %}Favorite Books{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="md:mr-64 pt-1 relative overflow-x-auto shadow-md sm:rounded-lg mt-1 h-box w-box flex">
|
class="md:mr-64 pt-1 relative overflow-x-auto shadow-md sm:rounded-lg mt-1 h-box w-box flex">
|
||||||
{% if not current_user.is_authenticated %}
|
{% if not current_user.is_authenticated %}
|
||||||
|
@ -2,22 +2,16 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{% include 'book/delete_section_modal.html' %}
|
|
||||||
{% include 'book/edit_section_modal.html' %}
|
|
||||||
{% include 'book/approve_interpretation_modal.html' %}
|
{% include 'book/approve_interpretation_modal.html' %}
|
||||||
<!-- prettier-ignore -->
|
|
||||||
{% set show_delete_section = True %}
|
|
||||||
<!-- prettier-ignore -->
|
|
||||||
{% set show_edit_section = True %}
|
|
||||||
{% block right_sidebar %}
|
{% block right_sidebar %}
|
||||||
{% include 'book/right_sidebar.html' %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% block title %}{{section.label}}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'book/breadcrumbs_navigation.html'%}
|
{% include 'book/breadcrumbs_navigation.html'%}
|
||||||
<div class="overflow-x-auto shadow-md sm:rounded-lg md:mr-64">
|
<div class="overflow-x-auto shadow-md sm:rounded-lg">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div class="fixed z-30 w-full top-44 pt-6 bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
<div class="fixed z-30 w-full top-44 pt-6 bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% set selected_tab='my_contributions' %}
|
{% set selected_tab='my_contributions' %}
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
|
{% block title %}My Contributions{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="md:mr-64 pt-1 relative overflow-x-auto shadow-md sm:rounded-lg mt-1 h-box w-box flex">
|
class="md:mr-64 pt-1 relative overflow-x-auto shadow-md sm:rounded-lg mt-1 h-box w-box flex">
|
||||||
{% if not current_user.is_authenticated %}
|
{% if not current_user.is_authenticated %}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% set selected_tab='my_library' %}
|
{% set selected_tab='my_library' %}
|
||||||
|
|
||||||
|
{% block title %}My Library{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
@ -110,7 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
|
@ -4,21 +4,17 @@
|
|||||||
|
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{% include 'book/delete_interpretation_modal.html' %}
|
|
||||||
{% include 'book/delete_comment_modal.html' %}
|
{% include 'book/delete_comment_modal.html' %}
|
||||||
{% include 'book/edit_comment_modal.html' %}
|
{% include 'book/edit_comment_modal.html' %}
|
||||||
{% include 'book/edit_interpretation_modal.html' %}
|
|
||||||
{% set show_edit_interpretation = True %}
|
|
||||||
{% set show_delete_interpretation = True %}
|
|
||||||
{% block right_sidebar %}
|
{% block right_sidebar %}
|
||||||
{% include 'book/right_sidebar.html' %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% block title %}{{ section.label[:32] }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'book/breadcrumbs_navigation.html'%}
|
{% include 'book/breadcrumbs_navigation.html'%}
|
||||||
<div class="shadow-md mt-5 md:mr-64 h-auto overflow-x-hidden">
|
<div class="shadow-md mt-5 h-auto overflow-x-hidden">
|
||||||
<div class="ql-snow mt-20">
|
<div class="ql-snow mt-20">
|
||||||
<h1 class="text-l font-extrabold dark:text-white ml-4 truncate">
|
<h1 class="text-l font-extrabold dark:text-white ml-4 truncate">
|
||||||
{{ section.label }}
|
{{ section.label }}
|
||||||
@ -207,7 +203,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="p-5 m-3">
|
<div class="p-5 m-3">
|
||||||
{% for child in comment.children %}
|
{% for child in comment.children %}
|
||||||
<div class="p-5 mb-2 flex justify-between items-end bg-slate-600 rounded-lg">
|
<div class="p-5 mb-2 flex justify-between items-end bg-slate-100 dark:bg-slate-600 rounded-lg">
|
||||||
<div class="ql-snow">
|
<div class="ql-snow">
|
||||||
<div class="inline-block mb-4 ql-editor-readonly !p-0">
|
<div class="inline-block mb-4 ql-editor-readonly !p-0">
|
||||||
{{display_tags(child.text)|safe}}
|
{{display_tags(child.text)|safe}}
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
{% include 'book/delete_book_modal.html' %}
|
{% include 'book/delete_book_modal.html' %}
|
||||||
{% include 'book/access_level_modal.html' %}
|
{% include 'book/access_level_modal.html' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}Book Settings{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Hide right_sidebar -->
|
<!-- Hide right_sidebar -->
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Statistics{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
{% include 'book/add_section_modal.html' %}
|
{% include 'book/add_section_modal.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% block title %}{{book.label[:32]}}{% endblock %}
|
||||||
|
|
||||||
{% block right_sidebar %}
|
{% block right_sidebar %}
|
||||||
{% include 'book/right_sidebar.html' %}
|
{% include 'book/right_sidebar.html' %}
|
||||||
|
119
app/templates/search/quick_search_window.html
Normal file
119
app/templates/search/quick_search_window.html
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<!-- prettier-ignore -->
|
||||||
|
<div id="quickSearchModal" tabindex="-1" aria-hidden="true" class="absolute w-96 top-14 left-[250px] z-50 hidden h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<div class="relative w-full max-w-2xl max-h-full">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
|
<!-- Modal header -->
|
||||||
|
<button type="button" class="hidden text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-hide="quickSearchModal"> <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>
|
||||||
|
<span class="sr-only">Close modal</span>
|
||||||
|
</button>
|
||||||
|
<!-- Modal body -->
|
||||||
|
<div class="p-1 space-y-1">
|
||||||
|
<div id="emptyQuickSearchDiv" class="hidden h-32">
|
||||||
|
<table>
|
||||||
|
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
|
||||||
|
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
Nothing found
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="quickSearchBlock-interpretations">
|
||||||
|
<table>
|
||||||
|
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
|
||||||
|
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
Interpretations
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="interpretationsText-0">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<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 mr-2"> <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>
|
||||||
|
<a href="" id="interpretationsText-0"></a></th>
|
||||||
|
</tr>
|
||||||
|
<tr class="interpretationsText-1">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<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 mr-2"> <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>
|
||||||
|
<a href="" id="interpretationsText-1"></a></th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="quickSearchBlock-books">
|
||||||
|
<table>
|
||||||
|
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
|
||||||
|
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
Books
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="booksText-0">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <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>
|
||||||
|
<a href="" id="booksText-0"></a></th>
|
||||||
|
</tr>
|
||||||
|
<tr class="booksText-1">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <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>
|
||||||
|
<a href="" id="booksText-1"></a></th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="quickSearchBlock-users">
|
||||||
|
<table>
|
||||||
|
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
|
||||||
|
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
Users
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="usersText-0">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <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>
|
||||||
|
<a href="" id="usersText-0"></a></th>
|
||||||
|
</tr>
|
||||||
|
<tr class="usersText-1">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <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>
|
||||||
|
<a href="" id="usersText-1"></a></th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="quickSearchBlock-tags" class="hidden">
|
||||||
|
<table>
|
||||||
|
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
|
||||||
|
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
Tags
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="tagsText-0">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" /> </svg>
|
||||||
|
<a href="" id="tagsText-0"></a></th>
|
||||||
|
</tr>
|
||||||
|
<tr class="tagsText-1">
|
||||||
|
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" /> </svg>
|
||||||
|
<a href="" id="tagsText-1"></a></th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Modal footer -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,5 +1,8 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Search results{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
@ -10,9 +13,9 @@
|
|||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
||||||
<li class="mr-2">
|
<li class="mr-2">
|
||||||
<a href="{{url_for('search.search_interpretations',q=query)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
<a href="{{url_for('search.search_interpretations',q=query)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
||||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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 aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
|
||||||
Interpretations
|
Interpretations
|
||||||
</a>
|
</a>
|
@ -1,5 +1,8 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Search results{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
@ -10,9 +13,9 @@
|
|||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
||||||
<li class="mr-2">
|
<li class="mr-2">
|
||||||
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
|
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
|
||||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-500"> <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 aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-500"> <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>
|
||||||
Interpretations
|
Interpretations
|
||||||
</a>
|
</a>
|
@ -1,5 +1,8 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Search results{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
@ -10,9 +13,9 @@
|
|||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
||||||
<li class="mr-2">
|
<li class="mr-2">
|
||||||
<a href="{{url_for('search.search_interpretations',q=query)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
<a href="{{url_for('search.search_interpretations',q=query)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
||||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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 aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
|
||||||
Interpretations
|
Interpretations
|
||||||
</a>
|
</a>
|
@ -1,5 +1,8 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Search results{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
@ -10,9 +13,9 @@
|
|||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
||||||
<li class="mr-2">
|
<li class="mr-2">
|
||||||
<a href="{{url_for('search.search_interpretations',q=query)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
<a href="{{url_for('search.search_interpretations',q=query)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
||||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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 aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
|
||||||
Interpretations
|
Interpretations
|
||||||
</a>
|
</a>
|
||||||
@ -53,7 +56,7 @@
|
|||||||
<div class="pl-3">
|
<div class="pl-3">
|
||||||
<div class="text-base font-semibold">{{user.username}}</div>
|
<div class="text-base font-semibold">{{user.username}}</div>
|
||||||
<div class="font-normal text-gray-500">{{user.wallet_id}}</div>
|
<div class="font-normal text-gray-500">{{user.wallet_id}}</div>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
<a href="{{ url_for('user.profile',user_id=user.id) }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Go to {{user.username}}'s library</a>
|
<a href="{{ url_for('user.profile',user_id=user.id) }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Go to {{user.username}}'s library</a>
|
@ -1,18 +1,21 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Tag search results{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Search results</h1>
|
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Tag search results</h1>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Search result for {{tag_name}} </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400">Tag search result for {{tag_name}} </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
||||||
<li class="mr-2">
|
<li class="mr-2">
|
||||||
<a href="{{url_for('search.tag_search_interpretations',tag_name=tag_name)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
<a href="{{url_for('search.tag_search_interpretations',tag_name=tag_name)}}" class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 group" >
|
||||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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 aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
|
||||||
Interpretations
|
Interpretations
|
||||||
</a>
|
</a>
|
@ -1,18 +1,21 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Tag search results{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Search results</h1>
|
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Tag search results</h1>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Search result for {{tag_name}} </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Tag search result for {{tag_name}} </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
|
||||||
<li class="mr-2">
|
<li class="mr-2">
|
||||||
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
|
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
|
||||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-500"> <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 aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-500"> <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>
|
||||||
Interpretations
|
Interpretations
|
||||||
</a>
|
</a>
|
@ -7,8 +7,9 @@
|
|||||||
<svg class="w-5 h-5 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 fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path></svg>
|
<svg class="w-5 h-5 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 fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path></svg>
|
||||||
</div>
|
</div>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<input id="mainSearchInput" required minlength="1" name="search_query" {% if search_query %}value={{ search_query }}{% endif %} type="text" class="block p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg w-80 bg-gray-50 focus:ring-blue-500 focus:border-blue-500 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">
|
<input required minlength="1" autocomplete="off" name="search_query" {% if search_query %}value={{ search_query }}{% endif %} type="text" id="mainSearchInput" class="block p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg w-80 bg-gray-50 focus:ring-blue-500 focus:border-blue-500 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" />
|
||||||
</div>
|
</div>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<button type="button" id="global-search-button" class="md:flex px-3 py-2 text-xs text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 ml-2 font-medium rounded-lg text-center inline-flex items-center mr-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"><svg class="w-5 h-5 text-white-500" 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 ml-0 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75l-2.489-2.489m0 0a3.375 3.375 0 10-4.773-4.773 3.375 3.375 0 004.774 4.774zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>Search</button>
|
<button type="submit" id="global-search-button" class="md:flex px-3 py-2 text-xs text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 ml-2 font-medium rounded-lg text-center inline-flex items-center mr-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"><svg class="w-5 h-5 text-white-500" 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 ml-0 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75l-2.489-2.489m0 0a3.375 3.375 0 10-4.773-4.773 3.375 3.375 0 004.774 4.774zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>Search</button>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'search/quick_search_window.html' %}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
|
{% block title %}Sections{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="md:mr-64 relative overflow-x-auto shadow-md sm:rounded-lg mt-1">
|
<div class="md:mr-64 relative overflow-x-auto shadow-md sm:rounded-lg mt-1">
|
||||||
<!-- prettier-ignore -->
|
<!-- 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">
|
<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">
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% include 'user/delete_profile_modal.html' %}
|
{% include 'user/delete_profile_modal.html' %}
|
||||||
|
|
||||||
|
{% block title %}Edit Profile{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- component -->
|
<!-- component -->
|
||||||
<section>
|
<section>
|
||||||
@ -35,19 +38,24 @@
|
|||||||
<div class="flex items-center mb-3">
|
<div class="flex items-center mb-3">
|
||||||
<div>
|
<div>
|
||||||
{% if current_user.avatar_img %}
|
{% if current_user.avatar_img %}
|
||||||
<img class=" w-14 h-14 rounded-full mr-3" src="data:image/jpeg;base64,{{ current_user.avatar_img }}" alt="user avatar">
|
<img class="w-14 h-14 rounded-full mr-3" src="data:image/jpeg;base64,{{ current_user.avatar_img }}" alt="user avatar">
|
||||||
{% else %}
|
{% else %}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 56 56" stroke-width="1.5" stroke="currentColor" class="w-14 h-14"> <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>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-14 h-14 mr-2"> <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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
{{form.avatar_img(type='file', 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', id="avatar_img",
|
{{form.avatar_img(type='file', 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', id="avatar_img", accept=".jpg,.png,.jpeg",
|
||||||
value=current_user.avatar_img if current_user.avatar_img else "")}}</div>
|
value=current_user.avatar_img if current_user.avatar_img else "")}}</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 sm:w-auto dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save changes</button>
|
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 sm:w-auto dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save changes</button>
|
||||||
</form>
|
</form>
|
||||||
<button type="button" data-modal-target="delete_profile_modal" data-modal-toggle="delete_profile_modal" class="text-red-700 hover:text-white border border-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 mr-2 mb-2 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600 dark:focus:ring-red-900">Delete you profile</button>
|
<div class="flex">
|
||||||
|
<form action="{{ url_for('user.delete_avatar') }}" class="mb-0" method="POST">
|
||||||
|
<button type="submit" class="text-red-700 hover:text-white border border-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 mr-2 mb-2 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600 dark:focus:ring-red-900">Delete Avatar</button>
|
||||||
|
</form>
|
||||||
|
<button type="button" data-modal-target="delete_profile_modal" data-modal-toggle="delete_profile_modal" class="text-red-700 hover:text-white border border-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 mr-2 mb-2 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600 dark:focus:ring-red-900">Delete you profile</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{user.username.strip() + "'s profile" or user.wallet_id}}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
|
||||||
{% if user.is_deleted %}
|
{% if user.is_deleted %}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% include 'user/delete_profile_modal.html' %}
|
{% include 'user/delete_profile_modal.html' %}
|
||||||
|
|
||||||
|
{% block title %}Reactivate Profile{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- component -->
|
<!-- component -->
|
||||||
<section>
|
<section>
|
||||||
|
@ -4,7 +4,6 @@ from .main import main_blueprint
|
|||||||
from .user import bp as user_blueprint
|
from .user import bp as user_blueprint
|
||||||
from .book import bp as book_blueprint
|
from .book import bp as book_blueprint
|
||||||
from .home import bp as home_blueprint
|
from .home import bp as home_blueprint
|
||||||
from .section import bp as section_blueprint
|
|
||||||
from .vote import bp as vote_blueprint
|
from .vote import bp as vote_blueprint
|
||||||
from .approve import bp as approve_blueprint
|
from .approve import bp as approve_blueprint
|
||||||
from .star import bp as star_blueprint
|
from .star import bp as star_blueprint
|
||||||
|
@ -3,7 +3,6 @@ from flask import (
|
|||||||
flash,
|
flash,
|
||||||
redirect,
|
redirect,
|
||||||
url_for,
|
url_for,
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_, or_
|
||||||
@ -29,18 +28,19 @@ from .bp import bp
|
|||||||
|
|
||||||
@bp.route("/all", methods=["GET"])
|
@bp.route("/all", methods=["GET"])
|
||||||
def get_all():
|
def get_all():
|
||||||
q = request.args.get("q", type=str, default=None)
|
log(log.INFO, "Create query for books")
|
||||||
books: m.Book = m.Book.query.order_by(m.Book.id)
|
books: m.Book = m.Book.query.filter(m.Book.is_deleted is not False).order_by(
|
||||||
if q:
|
m.Book.id
|
||||||
books = books.filter(m.Book.label.like(f"{q}"))
|
)
|
||||||
|
log(log.INFO, "Create pagination for books")
|
||||||
|
|
||||||
pagination = create_pagination(total=books.count())
|
pagination = create_pagination(total=books.count())
|
||||||
|
log(log.INFO, "Returning data for front end")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"book/all.html",
|
"book/all.html",
|
||||||
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
||||||
page=pagination,
|
page=pagination,
|
||||||
search_query=q,
|
|
||||||
all_books=True,
|
all_books=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,15 +48,22 @@ def get_all():
|
|||||||
@bp.route("/my_library", methods=["GET"])
|
@bp.route("/my_library", methods=["GET"])
|
||||||
def my_library():
|
def my_library():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
|
log(log.INFO, "Create query for my_library page for books")
|
||||||
|
|
||||||
books: m.Book = m.Book.query.order_by(m.Book.id)
|
books: m.Book = m.Book.query.order_by(m.Book.id)
|
||||||
books = books.filter_by(user_id=current_user.id, is_deleted=False)
|
books = books.filter_by(user_id=current_user.id, is_deleted=False)
|
||||||
|
log(log.INFO, "Create pagination for books")
|
||||||
|
|
||||||
pagination = create_pagination(total=books.count())
|
pagination = create_pagination(total=books.count())
|
||||||
|
log(log.INFO, "Returns data for front end")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"book/my_library.html",
|
"book/my_library.html",
|
||||||
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
||||||
page=pagination,
|
page=pagination,
|
||||||
)
|
)
|
||||||
|
log(log.INFO, "Returns data for front end is user is anonym")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"book/my_library.html",
|
"book/my_library.html",
|
||||||
books=[],
|
books=[],
|
||||||
@ -77,9 +84,8 @@ def create():
|
|||||||
root_collection = m.Collection(
|
root_collection = m.Collection(
|
||||||
label="Root Collection", version_id=version.id, is_root=True
|
label="Root Collection", version_id=version.id, is_root=True
|
||||||
).save()
|
).save()
|
||||||
tags = form.tags.data
|
tags = form.tags.data or ""
|
||||||
if tags:
|
set_book_tags(book, tags)
|
||||||
set_book_tags(book, tags)
|
|
||||||
|
|
||||||
# access groups
|
# access groups
|
||||||
editor_access_group = create_editor_group(book_id=book.id)
|
editor_access_group = create_editor_group(book_id=book.id)
|
||||||
@ -113,9 +119,8 @@ def edit(book_id: int):
|
|||||||
book: m.Book = db.session.get(m.Book, book_id)
|
book: m.Book = db.session.get(m.Book, book_id)
|
||||||
label = form.label.data
|
label = form.label.data
|
||||||
about = form.about.data
|
about = form.about.data
|
||||||
tags = form.tags.data
|
tags = form.tags.data or ""
|
||||||
if tags:
|
set_book_tags(book, tags)
|
||||||
set_book_tags(book, tags)
|
|
||||||
|
|
||||||
book.label = label
|
book.label = label
|
||||||
book.about = about
|
book.about = about
|
||||||
@ -163,6 +168,8 @@ def statistic_view(book_id: int):
|
|||||||
@bp.route("/favorite_books", methods=["GET"])
|
@bp.route("/favorite_books", methods=["GET"])
|
||||||
def favorite_books():
|
def favorite_books():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
|
log(log.INFO, "Creating query for books")
|
||||||
|
|
||||||
books = (
|
books = (
|
||||||
db.session.query(
|
db.session.query(
|
||||||
m.Book,
|
m.Book,
|
||||||
@ -178,7 +185,10 @@ def favorite_books():
|
|||||||
)
|
)
|
||||||
|
|
||||||
books = books.filter_by(is_deleted=False)
|
books = books.filter_by(is_deleted=False)
|
||||||
|
log(log.INFO, "Creating pagination for books")
|
||||||
|
|
||||||
pagination = create_pagination(total=books.count())
|
pagination = create_pagination(total=books.count())
|
||||||
|
log(log.INFO, "Returns data for front end")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"book/favorite_books.html",
|
"book/favorite_books.html",
|
||||||
@ -190,34 +200,40 @@ def favorite_books():
|
|||||||
|
|
||||||
@bp.route("/my_contributions", methods=["GET"])
|
@bp.route("/my_contributions", methods=["GET"])
|
||||||
def my_contributions():
|
def my_contributions():
|
||||||
interpretations = (
|
if current_user.is_authenticated:
|
||||||
db.session.query(
|
log(log.INFO, "Creating query for interpretations")
|
||||||
m.Interpretation,
|
|
||||||
)
|
interpretations = (
|
||||||
.filter(
|
db.session.query(
|
||||||
or_(
|
m.Interpretation,
|
||||||
and_(
|
|
||||||
m.Interpretation.id == m.Comment.interpretation_id,
|
|
||||||
m.Comment.user_id == current_user.id,
|
|
||||||
m.Comment.is_deleted.is_(False),
|
|
||||||
m.Interpretation.is_deleted.is_(False),
|
|
||||||
),
|
|
||||||
and_(
|
|
||||||
m.Interpretation.user_id == current_user.id,
|
|
||||||
m.Interpretation.is_deleted.is_(False),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
.filter(
|
||||||
|
or_(
|
||||||
|
and_(
|
||||||
|
m.Interpretation.id == m.Comment.interpretation_id,
|
||||||
|
m.Comment.user_id == current_user.id,
|
||||||
|
m.Comment.is_deleted.is_(False),
|
||||||
|
m.Interpretation.is_deleted.is_(False),
|
||||||
|
),
|
||||||
|
and_(
|
||||||
|
m.Interpretation.user_id == current_user.id,
|
||||||
|
m.Interpretation.is_deleted.is_(False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.group_by(m.Interpretation.id)
|
||||||
|
.order_by(m.Interpretation.created_at.desc())
|
||||||
)
|
)
|
||||||
.group_by(m.Interpretation.id)
|
log(log.INFO, "Creating pagination for interpretations")
|
||||||
.order_by(m.Interpretation.created_at.desc())
|
|
||||||
)
|
|
||||||
|
|
||||||
pagination = create_pagination(total=interpretations.count())
|
pagination = create_pagination(total=interpretations.count())
|
||||||
|
log(log.INFO, "Returns data for front end")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"book/my_contributions.html",
|
"book/my_contributions.html",
|
||||||
interpretations=interpretations.paginate(
|
interpretations=interpretations.paginate(
|
||||||
page=pagination.page, per_page=pagination.per_page
|
page=pagination.page, per_page=pagination.per_page
|
||||||
),
|
),
|
||||||
page=pagination,
|
page=pagination,
|
||||||
)
|
)
|
||||||
|
return render_template("book/my_contributions.html", interpretations=[])
|
||||||
|
@ -102,8 +102,7 @@ def create_comment(
|
|||||||
comment.save()
|
comment.save()
|
||||||
|
|
||||||
tags = current_app.config["TAG_REGEX"].findall(text)
|
tags = current_app.config["TAG_REGEX"].findall(text)
|
||||||
if tags:
|
set_comment_tags(comment, tags)
|
||||||
set_comment_tags(comment, tags)
|
|
||||||
|
|
||||||
flash("Success!", "success")
|
flash("Success!", "success")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
@ -198,8 +197,7 @@ def comment_edit(
|
|||||||
log(log.INFO, "Edit comment [%s]", comment)
|
log(log.INFO, "Edit comment [%s]", comment)
|
||||||
|
|
||||||
tags = current_app.config["TAG_REGEX"].findall(text)
|
tags = current_app.config["TAG_REGEX"].findall(text)
|
||||||
if tags:
|
set_comment_tags(comment, tags)
|
||||||
set_comment_tags(comment, tags)
|
|
||||||
|
|
||||||
comment.save()
|
comment.save()
|
||||||
|
|
||||||
|
@ -143,8 +143,7 @@ def interpretation_create(
|
|||||||
# -------------
|
# -------------
|
||||||
|
|
||||||
tags = current_app.config["TAG_REGEX"].findall(text)
|
tags = current_app.config["TAG_REGEX"].findall(text)
|
||||||
if tags:
|
set_interpretation_tags(interpretation, tags)
|
||||||
set_interpretation_tags(interpretation, tags)
|
|
||||||
|
|
||||||
flash("Success!", "success")
|
flash("Success!", "success")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
@ -201,8 +200,7 @@ def interpretation_edit(
|
|||||||
interpretation.plain_text = plain_text
|
interpretation.plain_text = plain_text
|
||||||
interpretation.text = text
|
interpretation.text = text
|
||||||
tags = current_app.config["TAG_REGEX"].findall(text)
|
tags = current_app.config["TAG_REGEX"].findall(text)
|
||||||
if tags:
|
set_interpretation_tags(interpretation, tags)
|
||||||
set_interpretation_tags(interpretation, tags)
|
|
||||||
|
|
||||||
log(log.INFO, "Edit interpretation [%s]", interpretation.id)
|
log(log.INFO, "Edit interpretation [%s]", interpretation.id)
|
||||||
interpretation.save()
|
interpretation.save()
|
||||||
|
@ -4,15 +4,21 @@ from flask import (
|
|||||||
)
|
)
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from app import models as m, db
|
from app import models as m, db
|
||||||
|
from app.logger import log
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("home", __name__, url_prefix="/home")
|
bp = Blueprint("home", __name__, url_prefix="/home")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/", methods=["GET"])
|
@bp.route("/", methods=["GET"])
|
||||||
def get_all():
|
def get_all():
|
||||||
|
log(log.INFO, "Create query for home page for books")
|
||||||
|
|
||||||
books: m.Book = (
|
books: m.Book = (
|
||||||
m.Book.query.filter_by(is_deleted=False).order_by(m.Book.id).limit(5)
|
m.Book.query.filter_by(is_deleted=False).order_by(m.Book.id).limit(5)
|
||||||
).all()
|
).all()
|
||||||
|
log(log.INFO, "Create query for home page for interpretations")
|
||||||
|
|
||||||
interpretations = (
|
interpretations = (
|
||||||
db.session.query(
|
db.session.query(
|
||||||
m.Interpretation,
|
m.Interpretation,
|
||||||
@ -34,6 +40,7 @@ def get_all():
|
|||||||
.limit(5)
|
.limit(5)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
log(log.INFO, "Returning data to front end")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"home/index.html",
|
"home/index.html",
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
from flask import Blueprint, render_template, request
|
from flask import Blueprint, render_template, request, jsonify, url_for
|
||||||
from sqlalchemy import func, and_, or_
|
from sqlalchemy import func, and_, or_
|
||||||
|
|
||||||
from app import models as m, db
|
from app import models as m, db
|
||||||
from app.controllers import create_pagination
|
from app.controllers import create_pagination
|
||||||
|
from app.controllers.build_qa_url_using_interpretation import (
|
||||||
|
build_qa_url_using_interpretation,
|
||||||
|
)
|
||||||
|
from app.logger import log
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("search", __name__)
|
bp = Blueprint("search", __name__)
|
||||||
@ -11,14 +15,18 @@ bp = Blueprint("search", __name__)
|
|||||||
@bp.route("/search_interpretations", methods=["GET"])
|
@bp.route("/search_interpretations", methods=["GET"])
|
||||||
def search_interpretations():
|
def search_interpretations():
|
||||||
q = request.args.get("q", type=str, default="").lower()
|
q = request.args.get("q", type=str, default="").lower()
|
||||||
|
log(log.INFO, "Starting to build query for interpretations")
|
||||||
interpretations = m.Interpretation.query.order_by(m.Interpretation.id).filter(
|
interpretations = m.Interpretation.query.order_by(m.Interpretation.id).filter(
|
||||||
(func.lower(m.Interpretation.plain_text).like(f"%{q}%"))
|
(func.lower(m.Interpretation.plain_text).like(f"%{q}%"))
|
||||||
)
|
)
|
||||||
|
log(log.INFO, "Get count of interpretations")
|
||||||
count = interpretations.count()
|
count = interpretations.count()
|
||||||
|
log(log.INFO, "Creating pagination")
|
||||||
pagination = create_pagination(total=interpretations.count())
|
pagination = create_pagination(total=interpretations.count())
|
||||||
|
log(log.INFO, "Returning data to front")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"searchResultsInterpretations.html",
|
"search/search_results_interpretations.html",
|
||||||
query=q,
|
query=q,
|
||||||
interpretations=interpretations.paginate(
|
interpretations=interpretations.paginate(
|
||||||
page=pagination.page, per_page=pagination.per_page
|
page=pagination.page, per_page=pagination.per_page
|
||||||
@ -30,49 +38,47 @@ def search_interpretations():
|
|||||||
|
|
||||||
@bp.route("/search_books", methods=["GET"])
|
@bp.route("/search_books", methods=["GET"])
|
||||||
def search_books():
|
def search_books():
|
||||||
q = request.args.get("q", type=str, default="")
|
q = request.args.get("q", type=str, default="").lower()
|
||||||
|
log(log.INFO, "Starting to build query for books")
|
||||||
|
|
||||||
books = (
|
books = (
|
||||||
db.session.query(m.Book)
|
m.Book.query.join(m.BookVersion, m.BookVersion.book_id == m.Book.id)
|
||||||
|
.join(m.Collection, m.BookVersion.id == m.Collection.version_id, full=True)
|
||||||
|
.join(m.Section, m.BookVersion.id == m.Section.version_id, full=True)
|
||||||
|
.join(m.Interpretation, m.Interpretation.section_id == m.Section.id, full=True)
|
||||||
.filter(
|
.filter(
|
||||||
or_(
|
or_(
|
||||||
and_(
|
and_(
|
||||||
func.lower(m.Book.label).like(f"%{q}%"),
|
func.lower(m.Book.label).like(f"%{q}%"),
|
||||||
m.Book.is_deleted == False, # noqa: E712
|
|
||||||
),
|
),
|
||||||
and_(
|
and_(
|
||||||
func.lower(m.Collection.label).like(f"%{q}%"),
|
func.lower(m.Collection.label).like(f"%{q}%"),
|
||||||
m.Collection.is_deleted == False, # noqa: E712
|
m.Collection.is_deleted == False, # noqa: E712
|
||||||
m.Collection.is_root == False, # noqa: E712
|
m.Collection.is_root == False, # noqa: E712
|
||||||
m.BookVersion.id == m.Collection.version_id,
|
|
||||||
m.Book.id == m.BookVersion.book_id,
|
|
||||||
m.Book.is_deleted == False, # noqa: E712
|
|
||||||
),
|
),
|
||||||
and_(
|
and_(
|
||||||
func.lower(m.Section.label).like(f"%{q}%"),
|
func.lower(m.Section.label).like(f"%{q}%"),
|
||||||
m.Section.is_deleted == False, # noqa: E712
|
m.Section.is_deleted == False, # noqa: E712
|
||||||
m.BookVersion.id == m.Section.version_id,
|
|
||||||
m.Book.id == m.BookVersion.book_id,
|
|
||||||
m.Book.is_deleted == False, # noqa: E712
|
|
||||||
),
|
),
|
||||||
and_(
|
and_(
|
||||||
func.lower(m.Interpretation.plain_text).like(f"%{q}%"),
|
func.lower(m.Interpretation.plain_text).like(f"%{q}%"),
|
||||||
m.Interpretation.is_deleted == False, # noqa: E712
|
m.Interpretation.is_deleted == False, # noqa: E712
|
||||||
m.Interpretation.section_id == m.Section.id,
|
|
||||||
m.Section.is_deleted == False, # noqa: E712
|
m.Section.is_deleted == False, # noqa: E712
|
||||||
m.BookVersion.id == m.Section.version_id,
|
|
||||||
m.Book.id == m.BookVersion.book_id,
|
|
||||||
m.Book.is_deleted == False, # noqa: E712
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
m.Book.is_deleted == False, # noqa: E712
|
||||||
)
|
)
|
||||||
.order_by(m.Book.created_at.asc())
|
|
||||||
.group_by(m.Book.id)
|
.group_by(m.Book.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log(log.INFO, "Get count of books")
|
||||||
count = books.count()
|
count = books.count()
|
||||||
|
log(log.INFO, "Creating pagination")
|
||||||
pagination = create_pagination(total=books.count())
|
pagination = create_pagination(total=books.count())
|
||||||
|
log(log.INFO, "Returning data to front")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"searchResultsBooks.html",
|
"search/search_results_books.html",
|
||||||
query=q,
|
query=q,
|
||||||
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
||||||
page=pagination,
|
page=pagination,
|
||||||
@ -82,7 +88,8 @@ def search_books():
|
|||||||
|
|
||||||
@bp.route("/search_users", methods=["GET"])
|
@bp.route("/search_users", methods=["GET"])
|
||||||
def search_users():
|
def search_users():
|
||||||
q = request.args.get("q", type=str, default="")
|
q = request.args.get("q", type=str, default="").lower()
|
||||||
|
log(log.INFO, "Starting to build query for users")
|
||||||
users = (
|
users = (
|
||||||
m.User.query.order_by(m.User.id)
|
m.User.query.order_by(m.User.id)
|
||||||
.filter(
|
.filter(
|
||||||
@ -94,12 +101,16 @@ def search_users():
|
|||||||
.order_by(m.User.id.asc())
|
.order_by(m.User.id.asc())
|
||||||
.group_by(m.User.id)
|
.group_by(m.User.id)
|
||||||
)
|
)
|
||||||
|
log(log.INFO, "Get count of users")
|
||||||
|
|
||||||
count = users.count()
|
count = users.count()
|
||||||
|
log(log.INFO, "Creating pagination")
|
||||||
|
|
||||||
pagination = create_pagination(total=users.count())
|
pagination = create_pagination(total=users.count())
|
||||||
|
log(log.INFO, "Returning data to front")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"searchResultsUsers.html",
|
"search/search_results_users.html",
|
||||||
query=q,
|
query=q,
|
||||||
users=users.paginate(page=pagination.page, per_page=pagination.per_page),
|
users=users.paginate(page=pagination.page, per_page=pagination.per_page),
|
||||||
page=pagination,
|
page=pagination,
|
||||||
@ -109,13 +120,20 @@ def search_users():
|
|||||||
|
|
||||||
@bp.route("/search_tags", methods=["GET"])
|
@bp.route("/search_tags", methods=["GET"])
|
||||||
def search_tags():
|
def search_tags():
|
||||||
q = request.args.get("q", type=str, default="")
|
q = request.args.get("q", type=str, default="").lower()
|
||||||
|
log(log.INFO, "Starting to build query for tags")
|
||||||
|
|
||||||
tags = m.Tag.query.order_by(m.Tag.id).filter(func.lower(m.Tag.name).like(f"%{q}%"))
|
tags = m.Tag.query.order_by(m.Tag.id).filter(func.lower(m.Tag.name).like(f"%{q}%"))
|
||||||
|
log(log.INFO, "Get count of tags")
|
||||||
|
|
||||||
count = tags.count()
|
count = tags.count()
|
||||||
|
log(log.INFO, "Creating pagination")
|
||||||
|
|
||||||
pagination = create_pagination(total=tags.count())
|
pagination = create_pagination(total=tags.count())
|
||||||
|
log(log.INFO, "Returning data to front")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"searchResultsTags.html",
|
"search/search_results_tags.html",
|
||||||
query=q,
|
query=q,
|
||||||
tags=tags.paginate(page=pagination.page, per_page=pagination.per_page),
|
tags=tags.paginate(page=pagination.page, per_page=pagination.per_page),
|
||||||
page=pagination,
|
page=pagination,
|
||||||
@ -125,7 +143,9 @@ def search_tags():
|
|||||||
|
|
||||||
@bp.route("/tag_search_interpretations", methods=["GET"])
|
@bp.route("/tag_search_interpretations", methods=["GET"])
|
||||||
def tag_search_interpretations():
|
def tag_search_interpretations():
|
||||||
tag_name = request.args.get("tag_name", type=str, default="")
|
tag_name = request.args.get("tag_name", type=str, default="").lower()
|
||||||
|
log(log.INFO, "Starting to build query for interpretations")
|
||||||
|
|
||||||
interpretations = (
|
interpretations = (
|
||||||
db.session.query(m.Interpretation)
|
db.session.query(m.Interpretation)
|
||||||
.filter(
|
.filter(
|
||||||
@ -139,11 +159,13 @@ def tag_search_interpretations():
|
|||||||
.order_by(m.Interpretation.created_at.asc())
|
.order_by(m.Interpretation.created_at.asc())
|
||||||
.group_by(m.Interpretation.id)
|
.group_by(m.Interpretation.id)
|
||||||
)
|
)
|
||||||
|
log(log.INFO, "Creating pagination")
|
||||||
|
|
||||||
pagination = create_pagination(total=interpretations.count())
|
pagination = create_pagination(total=interpretations.count())
|
||||||
|
log(log.INFO, "Returning data to front")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"tagSearchResultsInterpretations.html",
|
"search/tag_search_results_interpretations.html",
|
||||||
tag_name=tag_name,
|
tag_name=tag_name,
|
||||||
interpretations=interpretations.paginate(
|
interpretations=interpretations.paginate(
|
||||||
page=pagination.page, per_page=pagination.per_page
|
page=pagination.page, per_page=pagination.per_page
|
||||||
@ -155,7 +177,9 @@ def tag_search_interpretations():
|
|||||||
|
|
||||||
@bp.route("/tag_search_books", methods=["GET"])
|
@bp.route("/tag_search_books", methods=["GET"])
|
||||||
def tag_search_books():
|
def tag_search_books():
|
||||||
tag_name = request.args.get("tag_name", type=str, default="")
|
tag_name = request.args.get("tag_name", type=str, default="").lower()
|
||||||
|
log(log.INFO, "Starting to build query for books")
|
||||||
|
|
||||||
books = (
|
books = (
|
||||||
db.session.query(m.Book)
|
db.session.query(m.Book)
|
||||||
.filter(
|
.filter(
|
||||||
@ -169,13 +193,89 @@ def tag_search_books():
|
|||||||
.order_by(m.Book.created_at.asc())
|
.order_by(m.Book.created_at.asc())
|
||||||
.group_by(m.Book.id)
|
.group_by(m.Book.id)
|
||||||
)
|
)
|
||||||
|
log(log.INFO, "Creating pagination")
|
||||||
|
|
||||||
pagination = create_pagination(total=books.count())
|
pagination = create_pagination(total=books.count())
|
||||||
|
log(log.INFO, "Returning data to front")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"tagSearchResultsBooks.html",
|
"search/tag_search_results_books.html",
|
||||||
tag_name=tag_name,
|
tag_name=tag_name,
|
||||||
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
|
||||||
page=pagination,
|
page=pagination,
|
||||||
count=books.count(),
|
count=books.count(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/quick_search", methods=["GET"])
|
||||||
|
def quick_search():
|
||||||
|
search_query = request.args.get("search_query", type=str, default="").lower()
|
||||||
|
log(log.INFO, "Starting to build query for interpretations")
|
||||||
|
|
||||||
|
interpretations = (
|
||||||
|
m.Interpretation.query.order_by(m.Interpretation.id)
|
||||||
|
.filter(
|
||||||
|
(func.lower(m.Interpretation.plain_text).like(f"%{search_query}%")),
|
||||||
|
m.Interpretation.is_deleted == False, # noqa: E712,
|
||||||
|
)
|
||||||
|
.limit(2)
|
||||||
|
)
|
||||||
|
interpretations_res = []
|
||||||
|
for interpretation in interpretations:
|
||||||
|
url_for_interpretation = build_qa_url_using_interpretation(interpretation)
|
||||||
|
interpretations_res.append(
|
||||||
|
{"label": interpretation.section.label, "url": url_for_interpretation}
|
||||||
|
)
|
||||||
|
log(log.INFO, "Starting to build query for books")
|
||||||
|
|
||||||
|
books = (
|
||||||
|
m.Book.query.order_by(m.Book.id)
|
||||||
|
.filter(
|
||||||
|
(func.lower(m.Book.label).like(f"%{search_query}%")),
|
||||||
|
m.Book.is_deleted == False, # noqa: E712,
|
||||||
|
)
|
||||||
|
.limit(2)
|
||||||
|
)
|
||||||
|
books_res = []
|
||||||
|
for book in books:
|
||||||
|
url_for_book = url_for("book.collection_view", book_id=book.id)
|
||||||
|
books_res.append({"label": book.label, "url": url_for_book})
|
||||||
|
log(log.INFO, "Starting to build query for users")
|
||||||
|
|
||||||
|
users = (
|
||||||
|
m.User.query.order_by(m.User.id)
|
||||||
|
.filter(
|
||||||
|
or_(
|
||||||
|
func.lower(m.User.username).like(f"%{search_query}%"),
|
||||||
|
func.lower(m.User.wallet_id).like(f"%{search_query}%"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by(m.User.id.asc())
|
||||||
|
.group_by(m.User.id)
|
||||||
|
.limit(2)
|
||||||
|
)
|
||||||
|
users_res = []
|
||||||
|
for user in users:
|
||||||
|
url_for_user = url_for("user.profile", user_id=user.id)
|
||||||
|
users_res.append({"label": user.username, "url": url_for_user})
|
||||||
|
log(log.INFO, "Starting to build query for tags")
|
||||||
|
|
||||||
|
tags = (
|
||||||
|
m.Tag.query.order_by(m.Tag.id)
|
||||||
|
.filter(func.lower(m.Tag.name).like(f"%{search_query}%"))
|
||||||
|
.limit(2)
|
||||||
|
)
|
||||||
|
tags_res = []
|
||||||
|
for tag in tags:
|
||||||
|
url_for_tag = url_for("search.tag_search_interpretations", tag_name=tag.name)
|
||||||
|
tags_res.append({"label": tag.name, "url": url_for_tag})
|
||||||
|
log(log.INFO, "Returning data to front")
|
||||||
|
|
||||||
|
return jsonify(
|
||||||
|
{
|
||||||
|
"interpretations": interpretations_res,
|
||||||
|
"books": books_res,
|
||||||
|
"users": users_res,
|
||||||
|
"tags": tags_res,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
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,
|
|
||||||
)
|
|
@ -1,5 +1,3 @@
|
|||||||
import base64
|
|
||||||
|
|
||||||
from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
|
from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
|
||||||
from flask_login import login_required, current_user, logout_user
|
from flask_login import login_required, current_user, logout_user
|
||||||
from app.controllers import create_pagination
|
from app.controllers import create_pagination
|
||||||
@ -40,9 +38,9 @@ def edit_profile():
|
|||||||
user: m.User = current_user
|
user: m.User = current_user
|
||||||
user.username = form.name.data
|
user.username = form.name.data
|
||||||
if form.avatar_img.data:
|
if form.avatar_img.data:
|
||||||
img_data = form.avatar_img.data.read()
|
current_user.avatar_img = (
|
||||||
img_data = base64.b64encode(img_data)
|
form.avatar_img.data
|
||||||
current_user.avatar_img = img_data.decode("utf-8")
|
) # form.avatar_img.data is changed in form validator
|
||||||
user.is_activated = True
|
user.is_activated = True
|
||||||
user.save()
|
user.save()
|
||||||
return redirect(url_for("main.index"))
|
return redirect(url_for("main.index"))
|
||||||
@ -58,6 +56,17 @@ def edit_profile():
|
|||||||
return render_template("user/edit_profile.html", form=form)
|
return render_template("user/edit_profile.html", form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/delete_avatar", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def delete_avatar():
|
||||||
|
user: m.User = current_user
|
||||||
|
current_user.avatar_img = None
|
||||||
|
log(log.ERROR, "Delete user [%s] avatar", user)
|
||||||
|
current_user.save()
|
||||||
|
|
||||||
|
return redirect(url_for("user.edit_profile"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/<int:user_id>/profile")
|
@bp.route("/<int:user_id>/profile")
|
||||||
def profile(user_id: int):
|
def profile(user_id: int):
|
||||||
user: m.User = db.session.get(m.User, user_id)
|
user: m.User = db.session.get(m.User, user_id)
|
||||||
@ -72,22 +81,6 @@ def profile(user_id: int):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/create", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def create():
|
|
||||||
form = f.NewUserForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
user = m.User(
|
|
||||||
username=form.username.data,
|
|
||||||
password=form.password.data,
|
|
||||||
activated=form.activated.data,
|
|
||||||
)
|
|
||||||
log(log.INFO, "Form submitted. User: [%s]", user)
|
|
||||||
flash("User added!", "success")
|
|
||||||
user.save()
|
|
||||||
return redirect(url_for("user.get_all"))
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/profile_delete", methods=["POST"])
|
@bp.route("/profile_delete", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def profile_delete():
|
def profile_delete():
|
||||||
|
@ -14,17 +14,17 @@ config = context.config
|
|||||||
# Interpret the config file for Python logging.
|
# Interpret the config file for Python logging.
|
||||||
# This line sets up loggers basically.
|
# This line sets up loggers basically.
|
||||||
fileConfig(config.config_file_name)
|
fileConfig(config.config_file_name)
|
||||||
logger = logging.getLogger('alembic.env')
|
logger = logging.getLogger("alembic.env")
|
||||||
|
|
||||||
# add your model's MetaData object here
|
# add your model's MetaData object here
|
||||||
# for 'autogenerate' support
|
# for 'autogenerate' support
|
||||||
# from myapp import mymodel
|
# from myapp import mymodel
|
||||||
# target_metadata = mymodel.Base.metadata
|
# target_metadata = mymodel.Base.metadata
|
||||||
config.set_main_option(
|
config.set_main_option(
|
||||||
'sqlalchemy.url',
|
"sqlalchemy.url",
|
||||||
str(current_app.extensions['migrate'].db.get_engine().url).replace(
|
str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"),
|
||||||
'%', '%%'))
|
)
|
||||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
target_metadata = current_app.extensions["migrate"].db.metadata
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
# other values from the config, defined by the needs of env.py,
|
||||||
# can be acquired:
|
# can be acquired:
|
||||||
@ -45,9 +45,7 @@ def run_migrations_offline():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
url = config.get_main_option("sqlalchemy.url")
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
context.configure(
|
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
|
||||||
url=url, target_metadata=target_metadata, literal_binds=True
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
@ -65,20 +63,20 @@ def run_migrations_online():
|
|||||||
# when there are no changes to the schema
|
# when there are no changes to the schema
|
||||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
def process_revision_directives(context, revision, directives):
|
def process_revision_directives(context, revision, directives):
|
||||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
if getattr(config.cmd_opts, "autogenerate", False):
|
||||||
script = directives[0]
|
script = directives[0]
|
||||||
if script.upgrade_ops.is_empty():
|
if script.upgrade_ops.is_empty():
|
||||||
directives[:] = []
|
directives[:] = []
|
||||||
logger.info('No changes in schema detected.')
|
logger.info("No changes in schema detected.")
|
||||||
|
|
||||||
connectable = current_app.extensions['migrate'].db.get_engine()
|
connectable = current_app.extensions["migrate"].db.get_engine()
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
with connectable.connect() as connection:
|
||||||
context.configure(
|
context.configure(
|
||||||
connection=connection,
|
connection=connection,
|
||||||
target_metadata=target_metadata,
|
target_metadata=target_metadata,
|
||||||
process_revision_directives=process_revision_directives,
|
process_revision_directives=process_revision_directives,
|
||||||
**current_app.extensions['migrate'].configure_args
|
**current_app.extensions["migrate"].configure_args
|
||||||
)
|
)
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
|
@ -10,23 +10,23 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '1dfa1f2c208f'
|
revision = "1dfa1f2c208f"
|
||||||
down_revision = '2ec60080de3b'
|
down_revision = "2ec60080de3b"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('comments', schema=None) as batch_op:
|
with op.batch_alter_table("comments", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('edited', sa.Boolean(), nullable=True))
|
batch_op.add_column(sa.Column("edited", sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('comments', schema=None) as batch_op:
|
with op.batch_alter_table("comments", schema=None) as batch_op:
|
||||||
batch_op.drop_column('edited')
|
batch_op.drop_column("edited")
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -10,29 +10,33 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '2ec60080de3b'
|
revision = "2ec60080de3b"
|
||||||
down_revision = '4ce4bacc7c06'
|
down_revision = "4ce4bacc7c06"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.alter_column('wallet_id',
|
batch_op.alter_column(
|
||||||
existing_type=sa.VARCHAR(length=256),
|
"wallet_id",
|
||||||
type_=sa.String(length=64),
|
existing_type=sa.VARCHAR(length=256),
|
||||||
existing_nullable=True)
|
type_=sa.String(length=64),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.alter_column('wallet_id',
|
batch_op.alter_column(
|
||||||
existing_type=sa.String(length=64),
|
"wallet_id",
|
||||||
type_=sa.VARCHAR(length=256),
|
existing_type=sa.String(length=64),
|
||||||
existing_nullable=True)
|
type_=sa.VARCHAR(length=256),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -10,29 +10,29 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '377fc0b7e4bb'
|
revision = "377fc0b7e4bb"
|
||||||
down_revision = 'a1345b416f81'
|
down_revision = "a1345b416f81"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('is_activated', sa.Boolean(), nullable=True))
|
batch_op.add_column(sa.Column("is_activated", sa.Boolean(), nullable=True))
|
||||||
batch_op.alter_column('username',
|
batch_op.alter_column(
|
||||||
existing_type=sa.VARCHAR(length=60),
|
"username", existing_type=sa.VARCHAR(length=60), nullable=True
|
||||||
nullable=True)
|
)
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.alter_column('username',
|
batch_op.alter_column(
|
||||||
existing_type=sa.VARCHAR(length=60),
|
"username", existing_type=sa.VARCHAR(length=60), nullable=False
|
||||||
nullable=False)
|
)
|
||||||
batch_op.drop_column('is_activated')
|
batch_op.drop_column("is_activated")
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -10,45 +10,57 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '4ce4bacc7c06'
|
revision = "4ce4bacc7c06"
|
||||||
down_revision = '377fc0b7e4bb'
|
down_revision = "377fc0b7e4bb"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.alter_column('username',
|
batch_op.alter_column(
|
||||||
existing_type=sa.VARCHAR(length=60),
|
"username",
|
||||||
type_=sa.String(length=64),
|
existing_type=sa.VARCHAR(length=60),
|
||||||
existing_nullable=True)
|
type_=sa.String(length=64),
|
||||||
batch_op.alter_column('password_hash',
|
existing_nullable=True,
|
||||||
existing_type=sa.VARCHAR(length=255),
|
)
|
||||||
type_=sa.String(length=256),
|
batch_op.alter_column(
|
||||||
existing_nullable=True)
|
"password_hash",
|
||||||
batch_op.alter_column('wallet_id',
|
existing_type=sa.VARCHAR(length=255),
|
||||||
existing_type=sa.VARCHAR(length=255),
|
type_=sa.String(length=256),
|
||||||
type_=sa.String(length=256),
|
existing_nullable=True,
|
||||||
existing_nullable=True)
|
)
|
||||||
|
batch_op.alter_column(
|
||||||
|
"wallet_id",
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
type_=sa.String(length=256),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.alter_column('wallet_id',
|
batch_op.alter_column(
|
||||||
existing_type=sa.String(length=256),
|
"wallet_id",
|
||||||
type_=sa.VARCHAR(length=255),
|
existing_type=sa.String(length=256),
|
||||||
existing_nullable=True)
|
type_=sa.VARCHAR(length=255),
|
||||||
batch_op.alter_column('password_hash',
|
existing_nullable=True,
|
||||||
existing_type=sa.String(length=256),
|
)
|
||||||
type_=sa.VARCHAR(length=255),
|
batch_op.alter_column(
|
||||||
existing_nullable=True)
|
"password_hash",
|
||||||
batch_op.alter_column('username',
|
existing_type=sa.String(length=256),
|
||||||
existing_type=sa.String(length=64),
|
type_=sa.VARCHAR(length=255),
|
||||||
type_=sa.VARCHAR(length=60),
|
existing_nullable=True,
|
||||||
existing_nullable=True)
|
)
|
||||||
|
batch_op.alter_column(
|
||||||
|
"username",
|
||||||
|
existing_type=sa.String(length=64),
|
||||||
|
type_=sa.VARCHAR(length=60),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -10,31 +10,38 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '5df1fabbee7d'
|
revision = "5df1fabbee7d"
|
||||||
down_revision = '1dfa1f2c208f'
|
down_revision = "1dfa1f2c208f"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('comments', schema=None) as batch_op:
|
with op.batch_alter_table("comments", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('approved', sa.Boolean(), nullable=True))
|
batch_op.add_column(sa.Column("approved", sa.Boolean(), nullable=True))
|
||||||
batch_op.drop_column('included_with_interpretation')
|
batch_op.drop_column("included_with_interpretation")
|
||||||
|
|
||||||
with op.batch_alter_table('interpretations', schema=None) as batch_op:
|
with op.batch_alter_table("interpretations", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('approved', sa.Boolean(), nullable=True))
|
batch_op.add_column(sa.Column("approved", sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('interpretations', schema=None) as batch_op:
|
with op.batch_alter_table("interpretations", schema=None) as batch_op:
|
||||||
batch_op.drop_column('approved')
|
batch_op.drop_column("approved")
|
||||||
|
|
||||||
with op.batch_alter_table('comments', schema=None) as batch_op:
|
with op.batch_alter_table("comments", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('included_with_interpretation', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
batch_op.add_column(
|
||||||
batch_op.drop_column('approved')
|
sa.Column(
|
||||||
|
"included_with_interpretation",
|
||||||
|
sa.BOOLEAN(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
batch_op.drop_column("approved")
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -10,28 +10,35 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '7baa732e01c6'
|
revision = "7baa732e01c6"
|
||||||
down_revision = '0961578f302a'
|
down_revision = "0961578f302a"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('section_tags',
|
op.create_table(
|
||||||
sa.Column('tag_id', sa.Integer(), nullable=True),
|
"section_tags",
|
||||||
sa.Column('section_id', sa.Integer(), nullable=True),
|
sa.Column("tag_id", sa.Integer(), nullable=True),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column("section_id", sa.Integer(), nullable=True),
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column('is_deleted', sa.Boolean(), nullable=True),
|
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['section_id'], ['sections.id'], ),
|
sa.Column("is_deleted", sa.Boolean(), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], ),
|
sa.ForeignKeyConstraint(
|
||||||
sa.PrimaryKeyConstraint('id')
|
["section_id"],
|
||||||
|
["sections.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["tag_id"],
|
||||||
|
["tags.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
)
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.drop_table('section_tags')
|
op.drop_table("section_tags")
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -10,29 +10,35 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '883298018384'
|
revision = "883298018384"
|
||||||
down_revision = '5df1fabbee7d'
|
down_revision = "5df1fabbee7d"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('interpretations', schema=None) as batch_op:
|
with op.batch_alter_table("interpretations", schema=None) as batch_op:
|
||||||
batch_op.drop_column('label')
|
batch_op.drop_column("label")
|
||||||
|
|
||||||
with op.batch_alter_table('sections', schema=None) as batch_op:
|
with op.batch_alter_table("sections", schema=None) as batch_op:
|
||||||
batch_op.drop_column('about')
|
batch_op.drop_column("about")
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('sections', schema=None) as batch_op:
|
with op.batch_alter_table("sections", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('about', sa.TEXT(), autoincrement=False, nullable=True))
|
batch_op.add_column(
|
||||||
|
sa.Column("about", sa.TEXT(), autoincrement=False, nullable=True)
|
||||||
|
)
|
||||||
|
|
||||||
with op.batch_alter_table('interpretations', schema=None) as batch_op:
|
with op.batch_alter_table("interpretations", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('label', sa.VARCHAR(length=256), autoincrement=False, nullable=False))
|
batch_op.add_column(
|
||||||
|
sa.Column(
|
||||||
|
"label", sa.VARCHAR(length=256), autoincrement=False, nullable=False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -10,23 +10,23 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'a1345b416f81'
|
revision = "a1345b416f81"
|
||||||
down_revision = '067a10a531d7'
|
down_revision = "067a10a531d7"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('avatar_img', sa.Text(), nullable=True))
|
batch_op.add_column(sa.Column("avatar_img", sa.Text(), nullable=True))
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('users', schema=None) as batch_op:
|
with op.batch_alter_table("users", schema=None) as batch_op:
|
||||||
batch_op.drop_column('avatar_img')
|
batch_op.drop_column("avatar_img")
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -13,8 +13,10 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"ethers": "5.5.3",
|
"ethers": "5.5.3",
|
||||||
"flowbite": "^1.6.4",
|
"flowbite": "^1.6.4",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
"siwe": "1.0.0",
|
"siwe": "1.0.0",
|
||||||
"tailwindcss": "^3.2.7"
|
"tailwindcss": "^3.2.7"
|
||||||
},
|
},
|
||||||
|
@ -46,6 +46,10 @@ export function addSection() {
|
|||||||
`${collectionId}/${subCollectionId}/create_section`,
|
`${collectionId}/${subCollectionId}/create_section`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (newActionPath.includes('/0')) {
|
||||||
|
console.log('ALERT');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addSectionForm.setAttribute('action', `${newActionPath}`);
|
addSectionForm.setAttribute('action', `${newActionPath}`);
|
||||||
sectionModal.show();
|
sectionModal.show();
|
||||||
|
@ -24,6 +24,7 @@ import {scroll} from './scroll';
|
|||||||
import {initCheckBoxTree} from './checkBoxTree';
|
import {initCheckBoxTree} from './checkBoxTree';
|
||||||
import {initPermissions} from './permissions';
|
import {initPermissions} from './permissions';
|
||||||
import {copyLink} from './copyLink';
|
import {copyLink} from './copyLink';
|
||||||
|
import {quickSearch} from './quickSearch';
|
||||||
import {flash} from './flash';
|
import {flash} from './flash';
|
||||||
import {slashSearch} from './slashSearch';
|
import {slashSearch} from './slashSearch';
|
||||||
|
|
||||||
@ -53,5 +54,6 @@ scroll();
|
|||||||
initCheckBoxTree();
|
initCheckBoxTree();
|
||||||
initPermissions();
|
initPermissions();
|
||||||
copyLink();
|
copyLink();
|
||||||
|
quickSearch();
|
||||||
flash();
|
flash();
|
||||||
slashSearch();
|
slashSearch();
|
||||||
|
@ -63,7 +63,7 @@ const multipleInputJs = () => {
|
|||||||
tagsToSubmitInput.value = addedWords.join();
|
tagsToSubmitInput.value = addedWords.join();
|
||||||
|
|
||||||
multipleInput.addEventListener('input', () => {
|
multipleInput.addEventListener('input', () => {
|
||||||
let inputValue = multipleInput.value.trim();
|
let inputValue = multipleInput.value.trim().toLowerCase();
|
||||||
if (inputValue.length > 32) {
|
if (inputValue.length > 32) {
|
||||||
multipleInput.value = inputValue.slice(0, 32);
|
multipleInput.value = inputValue.slice(0, 32);
|
||||||
return;
|
return;
|
||||||
|
92
src/quickSearch.ts
Normal file
92
src/quickSearch.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import {Modal} from 'flowbite';
|
||||||
|
import type {ModalOptions, ModalInterface} from 'flowbite';
|
||||||
|
import debounce = require('lodash.debounce');
|
||||||
|
|
||||||
|
const currentSearchInput: HTMLInputElement =
|
||||||
|
document.querySelector('#mainSearchInput');
|
||||||
|
const searchDiv: HTMLElement = document.querySelector('#quickSearchModal');
|
||||||
|
|
||||||
|
const modalOptions: ModalOptions = {
|
||||||
|
closable: true,
|
||||||
|
backdrop: 'static',
|
||||||
|
onHide: () => {},
|
||||||
|
onShow: () => {},
|
||||||
|
onToggle: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const quickSearchModal: ModalInterface = new Modal(searchDiv, modalOptions);
|
||||||
|
|
||||||
|
export function quickSearch() {
|
||||||
|
if (currentSearchInput && searchDiv) {
|
||||||
|
currentSearchInput.addEventListener('input', debounce(onInputChange, 500));
|
||||||
|
currentSearchInput.addEventListener('keypress', async e => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const urlParams = new URLSearchParams({
|
||||||
|
q: currentSearchInput.value.toLowerCase(),
|
||||||
|
});
|
||||||
|
const res = await fetch('/search_interpretations?' + urlParams);
|
||||||
|
if (res.status === 200) {
|
||||||
|
window.location.replace(res.url);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInputChange = async (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (currentSearchInput.value.length > 0) {
|
||||||
|
const urlParams = new URLSearchParams({
|
||||||
|
search_query: currentSearchInput.value.toLowerCase(),
|
||||||
|
});
|
||||||
|
const res = await fetch('/quick_search?' + urlParams);
|
||||||
|
const json = await res.json();
|
||||||
|
if (res.status === 200) {
|
||||||
|
let emptyEntityArr = [];
|
||||||
|
|
||||||
|
for (const entity in json) {
|
||||||
|
// iterate over json from back end
|
||||||
|
const el: HTMLDivElement = document.querySelector(
|
||||||
|
`#quickSearchBlock-${entity}`,
|
||||||
|
);
|
||||||
|
const secondUnusedLink = document.querySelector(`.${entity}Text-1`);
|
||||||
|
const emptySearchDiv = document.querySelector('#emptyQuickSearchDiv');
|
||||||
|
if (secondUnusedLink) {
|
||||||
|
secondUnusedLink.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
if (json[entity].length < 1) {
|
||||||
|
emptyEntityArr.push(entity);
|
||||||
|
if (el) {
|
||||||
|
el.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json[entity].length == 1) {
|
||||||
|
if (secondUnusedLink) {
|
||||||
|
secondUnusedLink.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (emptyEntityArr.length === 4) {
|
||||||
|
emptySearchDiv.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const obj in json[entity]) {
|
||||||
|
// iterate over every entity in json
|
||||||
|
el.classList.remove('hidden');
|
||||||
|
emptySearchDiv.classList.add('hidden');
|
||||||
|
const link = document.querySelector(`#${entity}Text-${obj}`);
|
||||||
|
// taking needed html element for markup
|
||||||
|
if (link) {
|
||||||
|
// setting needed values to element
|
||||||
|
link.textContent = json[entity][obj].label;
|
||||||
|
link.setAttribute('href', json[entity][obj].url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quickSearchModal.show();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -23,7 +23,7 @@ def test_create_tags_on_book_create_and_edit(client: FlaskClient):
|
|||||||
book = m.Book.query.filter_by(label=BOOK_NAME).first()
|
book = m.Book.query.filter_by(label=BOOK_NAME).first()
|
||||||
assert book.tags
|
assert book.tags
|
||||||
|
|
||||||
splitted_tags = [tag.title() for tag in tags.split(",")]
|
splitted_tags = [tag.lower() for tag in tags.split(",")]
|
||||||
assert len(book.tags) == 3
|
assert len(book.tags) == 3
|
||||||
for tag in book.tags:
|
for tag in book.tags:
|
||||||
tag: m.Tag
|
tag: m.Tag
|
||||||
@ -42,7 +42,7 @@ def test_create_tags_on_book_create_and_edit(client: FlaskClient):
|
|||||||
|
|
||||||
book: m.Book = m.Book.query.first()
|
book: m.Book = m.Book.query.first()
|
||||||
|
|
||||||
splitted_tags = [tag.title() for tag in tags.split(",")]
|
splitted_tags = [tag.lower() for tag in tags.split(",")]
|
||||||
assert len(book.tags) == 3
|
assert len(book.tags) == 3
|
||||||
for tag in book.tags:
|
for tag in book.tags:
|
||||||
tag: m.Tag
|
tag: m.Tag
|
||||||
|
@ -133,6 +133,15 @@ def test_profile(client):
|
|||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert str.encode(new_name) in res.data
|
assert str.encode(new_name) in res.data
|
||||||
|
|
||||||
|
# delete_avatar
|
||||||
|
assert user.avatar_img
|
||||||
|
res = client.post(
|
||||||
|
"/user/delete_avatar",
|
||||||
|
follow_redirects=True,
|
||||||
|
)
|
||||||
|
assert res
|
||||||
|
assert not user.avatar_img
|
||||||
|
|
||||||
# delete_profile
|
# delete_profile
|
||||||
res = client.post(
|
res = client.post(
|
||||||
"/user/profile_delete",
|
"/user/profile_delete",
|
||||||
|
17
yarn.lock
17
yarn.lock
@ -1113,6 +1113,18 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||||
|
|
||||||
|
"@types/lodash.debounce@^4.0.7":
|
||||||
|
version "4.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f"
|
||||||
|
integrity sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "*"
|
||||||
|
|
||||||
|
"@types/lodash@*":
|
||||||
|
version "4.14.194"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76"
|
||||||
|
integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==
|
||||||
|
|
||||||
"@types/mime@*":
|
"@types/mime@*":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
||||||
@ -2606,6 +2618,11 @@ locate-path@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^4.1.0"
|
p-locate "^4.1.0"
|
||||||
|
|
||||||
|
lodash.debounce@^4.0.8:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||||
|
|
||||||
lru-cache@^6.0.0:
|
lru-cache@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user