Update note, model, page
This commit is contained in:
47
src/myapp/acl/README.md
Normal file
47
src/myapp/acl/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
Удаление неиспользуемых ACL
|
||||
|
||||
В файле specialistoff/acl/acl.py
|
||||
|
||||
```
|
||||
# import json
|
||||
# from pathlib import Path
|
||||
|
||||
...
|
||||
|
||||
# data = {}
|
||||
# acl_file = Path(app.config['DIR_DATA'] + "/acl.json")
|
||||
# if acl_file.exists():
|
||||
# with open(acl_file, 'r') as fd:
|
||||
# data = json.load(fd)
|
||||
# if view_func.__module__ not in data:
|
||||
# data[view_func.__module__] = []
|
||||
# if view_func.__name__ not in data[view_func.__module__]:
|
||||
# data[view_func.__module__].append(view_func.__name__)
|
||||
# with open(acl_file, 'w') as fd:
|
||||
# json.dump(data, fd)
|
||||
```
|
||||
|
||||
```
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from specialistoff import app, lib, models
|
||||
|
||||
data = {}
|
||||
acl_file = Path(app.config['DIR_DATA'] + "/acl.json")
|
||||
if acl_file.exists():
|
||||
with open(acl_file, 'r') as fd:
|
||||
data = json.load(fd)
|
||||
|
||||
|
||||
for item in data:
|
||||
resources = models.db_session.query(
|
||||
models.ACLResource
|
||||
).filter(
|
||||
models.ACLResource.modulename == item
|
||||
).filter(
|
||||
~models.ACLResource.funcname.in_(data[item])
|
||||
)
|
||||
if resources.count() > 0:
|
||||
print(resources.all())
|
||||
```
|
||||
6
src/myapp/acl/__init__.py
Normal file
6
src/myapp/acl/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
from .acl import ACL
|
||||
|
||||
__all__ = ['ACL']
|
||||
83
src/myapp/acl/acl.py
Normal file
83
src/myapp/acl/acl.py
Normal file
@@ -0,0 +1,83 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
# import json
|
||||
# from pathlib import Path
|
||||
|
||||
from flask import abort
|
||||
from functools import wraps
|
||||
from .. import app, lib, models
|
||||
|
||||
|
||||
class ACL:
|
||||
"""Декоратор для управления доступом
|
||||
"""
|
||||
def __init__(self, view_func):
|
||||
# data = {}
|
||||
# acl_file = Path(app.config['DIR_DATA'] + "/acl.json")
|
||||
# if acl_file.exists():
|
||||
# with open(acl_file, 'r') as fd:
|
||||
# data = json.load(fd)
|
||||
# if view_func.__module__ not in data:
|
||||
# data[view_func.__module__] = []
|
||||
# if view_func.__name__ not in data[view_func.__module__]:
|
||||
# data[view_func.__module__].append(view_func.__name__)
|
||||
# with open(acl_file, 'w') as fd:
|
||||
# json.dump(data, fd)
|
||||
funcname = view_func.__name__
|
||||
modulename = view_func.__module__
|
||||
resource = models.db_session.query(
|
||||
models.ACLResource
|
||||
).filter(
|
||||
models.ACLResource.funcname == funcname,
|
||||
models.ACLResource.modulename == modulename
|
||||
).first()
|
||||
if resource is None:
|
||||
resource = models.ACLResource(
|
||||
modulename=modulename,
|
||||
funcname=funcname
|
||||
)
|
||||
models.db_session.add(resource)
|
||||
models.db_session.commit()
|
||||
self.view_func = view_func
|
||||
wraps(view_func)(self)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Текущий объект, на который проверяем наличие прав
|
||||
"""
|
||||
resource = models.db_session.query(
|
||||
models.ACLResource.id
|
||||
).filter(
|
||||
models.ACLResource.funcname == self.view_func.__name__,
|
||||
models.ACLResource.modulename == self.view_func.__module__
|
||||
).first()
|
||||
|
||||
rule = models.db_session.query(
|
||||
models.ACLUserRule.permission
|
||||
).filter(
|
||||
models.ACLUserRule.user_id == lib.get_user().id,
|
||||
models.ACLUserRule.resource_id == resource.id
|
||||
).first()
|
||||
if rule is None:
|
||||
roles = models.db_session.query(
|
||||
models.ACLUserRole.role_id
|
||||
).filter(
|
||||
models.ACLUserRole.user_id == lib.get_user().id
|
||||
)
|
||||
rolerule = models.db_session.query(
|
||||
models.ACLRoleRule
|
||||
).filter(
|
||||
models.ACLRoleRule.role_id.in_(roles),
|
||||
models.ACLRoleRule.resource_id == resource.id
|
||||
).first()
|
||||
if rolerule is None:
|
||||
if app.config['DEFAULT_PERMISSION'] == 'deny':
|
||||
abort(403)
|
||||
elif rolerule.permission == 'deny':
|
||||
abort(403)
|
||||
elif rule.permission == 'deny':
|
||||
abort(403)
|
||||
# maybe do something before the view_func call
|
||||
response = self.view_func(*args, **kwargs)
|
||||
# maybe do something after the view_func call
|
||||
return response
|
||||
@@ -32,15 +32,19 @@ from .users import User # noqa F401
|
||||
# Метки
|
||||
from .tag import Tag # noqa F401
|
||||
|
||||
# Статьи
|
||||
from .page.common import Page # noqa F401
|
||||
from .page.favorite import PageFavorite # noqa F401
|
||||
from .page.tag import PageTag # noqa F401
|
||||
|
||||
# Заметки
|
||||
from .note.common import Note # noqa F401
|
||||
from .note.favorite import NoteFavorite # noqa F401
|
||||
from .note.tag import NoteTag # noqa F401
|
||||
from .note.trash import NoteTrash # noqa F401
|
||||
from .note.tree import NoteTree # noqa F401
|
||||
|
||||
# Статьи
|
||||
from .page.common import Page # noqa F401
|
||||
from .page.favorite import PageFavorite # noqa F401
|
||||
from .page.tag import PageTag # noqa F401
|
||||
from .page.trash import PageTrash # noqa F401
|
||||
from .page.tree import PageTree # noqa F401
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
|
||||
@@ -28,6 +28,21 @@ class Note(Base):
|
||||
uselist=False
|
||||
)
|
||||
tags = relationship("NoteTag", primaryjoin="Note.id==NoteTag.note_id")
|
||||
# Родитель
|
||||
parents = relationship(
|
||||
"NoteTree",
|
||||
primaryjoin="Note.id == NoteTree.child_id",
|
||||
)
|
||||
# Дочерние узлы
|
||||
nodes = relationship(
|
||||
"NoteTree",
|
||||
primaryjoin="Note.id == NoteTree.parent_id",
|
||||
)
|
||||
trash = relationship(
|
||||
"NoteTrash",
|
||||
primaryjoin="Note.id == NoteTrash.note_id",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, user, title):
|
||||
assert type(user).__name__ == 'User', 'Не передан объект User'
|
||||
|
||||
42
src/myapp/models/note/trash.py
Normal file
42
src/myapp/models/note/trash.py
Normal file
@@ -0,0 +1,42 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import Column, Integer, ForeignKey, DateTime, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .. import Base
|
||||
|
||||
|
||||
class NoteTrash(Base):
|
||||
"""Корзина для страниц справки
|
||||
"""
|
||||
__tablename__ = "note_trash"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
# ID пользователя
|
||||
user_id = Column(Integer, ForeignKey('user.id'), index=True)
|
||||
# ID страницы
|
||||
note_id = Column(Integer, ForeignKey('note.id'), index=True)
|
||||
# Дата удаления
|
||||
created = Column(DateTime)
|
||||
|
||||
# Связи
|
||||
user = relationship(
|
||||
"User",
|
||||
primaryjoin="NoteTrash.user_id == User.id",
|
||||
uselist=False
|
||||
)
|
||||
note = relationship(
|
||||
"Note",
|
||||
primaryjoin="NoteTrash.note_id == Note.id",
|
||||
back_populates="trash",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, user, note):
|
||||
assert type(user).__name__ == 'User', 'Не передан объект User'
|
||||
assert type(note).__name__ == 'Note', 'Не передан объект Note'
|
||||
self.user_id = user.id
|
||||
self.note_id = note.id
|
||||
self.created = datetime.datetime.now()
|
||||
49
src/myapp/models/note/tree.py
Normal file
49
src/myapp/models/note/tree.py
Normal file
@@ -0,0 +1,49 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import Table, Column, Boolean, Integer, ForeignKey, String, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .. import Base
|
||||
|
||||
|
||||
class NoteTree(Base):
|
||||
"""Справка по сервису
|
||||
"""
|
||||
__tablename__ = "note_tree"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
parent_id = Column(Integer, ForeignKey('note.id'), index=True) # ссылку на предка (ancestor)
|
||||
child_id = Column(Integer, ForeignKey('note.id'), index=True) # ссылку на потомка (descendant)
|
||||
level = Column(Integer, default=0) # Уровень относительно родителя
|
||||
|
||||
# Связи
|
||||
# Родитель
|
||||
parent = relationship(
|
||||
"Note",
|
||||
primaryjoin="Note.id == NoteTree.parent_id",
|
||||
uselist=False
|
||||
)
|
||||
# Дочерний узел
|
||||
child = relationship(
|
||||
"Note",
|
||||
primaryjoin="Note.id == NoteTree.child_id",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, parent, child):
|
||||
assert type(parent).__name__ == 'Note', 'Не передан объект Note'
|
||||
assert type(child).__name__ == 'Note', 'Не передан объект Note'
|
||||
self.parent_id = parent.id
|
||||
self.child_id = child.id
|
||||
|
||||
def __repr__(self):
|
||||
return "<NoteTree({id}, '{parent}', '{child}')>".format(
|
||||
id=self.id,
|
||||
parent=self.parent,
|
||||
child=self.child
|
||||
)
|
||||
|
||||
def __json__(self):
|
||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||
@@ -28,6 +28,21 @@ class Page(Base):
|
||||
uselist=False
|
||||
)
|
||||
tags = relationship("PageTag", primaryjoin="Page.id==PageTag.page_id")
|
||||
# Родитель
|
||||
parents = relationship(
|
||||
"PageTree",
|
||||
primaryjoin="Page.id == PageTree.child_id",
|
||||
)
|
||||
# Дочерние узлы
|
||||
nodes = relationship(
|
||||
"PageTree",
|
||||
primaryjoin="Page.id == PageTree.parent_id",
|
||||
)
|
||||
trash = relationship(
|
||||
"PageTrash",
|
||||
primaryjoin="Page.id == PageTrash.page_id",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, user, title):
|
||||
assert type(user).__name__ == 'User', 'Не передан объект User'
|
||||
|
||||
42
src/myapp/models/page/trash.py
Normal file
42
src/myapp/models/page/trash.py
Normal file
@@ -0,0 +1,42 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import Column, Integer, ForeignKey, DateTime, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .. import Base
|
||||
|
||||
|
||||
class PageTrash(Base):
|
||||
"""Корзина для страниц справки
|
||||
"""
|
||||
__tablename__ = "page_trash"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
# ID пользователя
|
||||
user_id = Column(Integer, ForeignKey('user.id'), index=True)
|
||||
# ID страницы
|
||||
page_id = Column(Integer, ForeignKey('page.id'), index=True)
|
||||
# Дата удаления
|
||||
created = Column(DateTime)
|
||||
|
||||
# Связи
|
||||
user = relationship(
|
||||
"User",
|
||||
primaryjoin="PageTrash.user_id == User.id",
|
||||
uselist=False
|
||||
)
|
||||
page = relationship(
|
||||
"Page",
|
||||
primaryjoin="PageTrash.page_id == Page.id",
|
||||
back_populates="trash",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, user, page):
|
||||
assert type(user).__name__ == 'User', 'Не передан объект User'
|
||||
assert type(page).__name__ == 'Page', 'Не передан объект Page'
|
||||
self.user_id = user.id
|
||||
self.page_id = page.id
|
||||
self.created = datetime.datetime.now()
|
||||
49
src/myapp/models/page/tree.py
Normal file
49
src/myapp/models/page/tree.py
Normal file
@@ -0,0 +1,49 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import Table, Column, Boolean, Integer, ForeignKey, String, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .. import Base
|
||||
|
||||
|
||||
class PageTree(Base):
|
||||
"""Справка по сервису
|
||||
"""
|
||||
__tablename__ = "page_tree"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
parent_id = Column(Integer, ForeignKey('page.id'), index=True) # ссылку на предка (ancestor)
|
||||
child_id = Column(Integer, ForeignKey('page.id'), index=True) # ссылку на потомка (descendant)
|
||||
level = Column(Integer, default=0) # Уровень относительно родителя
|
||||
|
||||
# Связи
|
||||
# Родитель
|
||||
parent = relationship(
|
||||
"Page",
|
||||
primaryjoin="Page.id == PageTree.parent_id",
|
||||
uselist=False
|
||||
)
|
||||
# Дочерний узел
|
||||
child = relationship(
|
||||
"Page",
|
||||
primaryjoin="Page.id == PageTree.child_id",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, parent, child):
|
||||
assert type(parent).__name__ == 'Page', 'Не передан объект Page'
|
||||
assert type(child).__name__ == 'Page', 'Не передан объект Page'
|
||||
self.parent_id = parent.id
|
||||
self.child_id = child.id
|
||||
|
||||
def __repr__(self):
|
||||
return "<PageTree({id}, '{parent}', '{child}')>".format(
|
||||
id=self.id,
|
||||
parent=self.parent,
|
||||
child=self.child
|
||||
)
|
||||
|
||||
def __json__(self):
|
||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||
@@ -34,6 +34,7 @@ jsonrpc = JSONRPC()
|
||||
|
||||
from . import ( # noqa F401
|
||||
login,
|
||||
favorite,
|
||||
note,
|
||||
profile,
|
||||
page,
|
||||
|
||||
7
src/myapp/ns_api/favorite/__init__.py
Normal file
7
src/myapp/ns_api/favorite/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
from . import (
|
||||
note,
|
||||
page
|
||||
)
|
||||
110
src/myapp/ns_api/favorite/note.py
Normal file
110
src/myapp/ns_api/favorite/note.py
Normal file
@@ -0,0 +1,110 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
from .. import jsonrpc, login_required
|
||||
from ... import acl, app, lib, models
|
||||
from ...mutations.note import note_as_dict
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.note.add')
|
||||
@login_required
|
||||
def favorite_note_add(id: int) -> bool:
|
||||
"""Добавление статьи в избранное
|
||||
"""
|
||||
note = models.db_session.query(
|
||||
models.Note
|
||||
).filter(
|
||||
models.Note.id == id
|
||||
).first()
|
||||
if note is None:
|
||||
raise ValueError
|
||||
exist = models.db_session.query(
|
||||
models.NoteFavorite
|
||||
).filter(
|
||||
models.NoteFavorite.note_id == id,
|
||||
models.NoteFavorite.user_id == lib.get_user().id
|
||||
).first()
|
||||
if exist:
|
||||
raise ValueError
|
||||
|
||||
new_favorite_note = models.NoteFavorite(
|
||||
lib.get_user(),
|
||||
note
|
||||
)
|
||||
models.db_session.add(new_favorite_note)
|
||||
models.db_session.commit()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.note.delete')
|
||||
@login_required
|
||||
def favorite_note_delete(id: int) -> bool:
|
||||
"""Удаление статьи из избранного
|
||||
"""
|
||||
exist = models.db_session.query(
|
||||
models.NoteFavorite
|
||||
).filter(
|
||||
models.NoteFavorite.note_id == id,
|
||||
models.NoteFavorite.user_id == lib.get_user().id
|
||||
).first()
|
||||
if exist is None:
|
||||
raise ValueError
|
||||
models.db_session.delete(exist)
|
||||
models.db_session.commit()
|
||||
return True
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.notes')
|
||||
@login_required
|
||||
def favorite_notes(
|
||||
page: int = 1,
|
||||
order_by: dict = {'field': 'title', 'order': 'asc'},
|
||||
fields: list = ['id', 'title']
|
||||
) -> list:
|
||||
indexes = models.db_session.query(
|
||||
models.NoteFavorite.note_id
|
||||
).filter(
|
||||
models.NoteFavorite.user_id == lib.get_user().id
|
||||
)
|
||||
notes = models.db_session.query(
|
||||
models.Note
|
||||
).filter(
|
||||
models.Note.id.in_(indexes)
|
||||
)
|
||||
|
||||
# Сортировка
|
||||
if order_by['field'] not in ['created', 'id', 'title', 'updated']:
|
||||
raise ValueError
|
||||
if order_by['order'] not in ['asc', 'desc']:
|
||||
raise ValueError
|
||||
field = getattr(models.Note, order_by['field'])
|
||||
order = getattr(field, order_by['order'])
|
||||
notes = notes.order_by(
|
||||
order()
|
||||
)
|
||||
|
||||
notes = lib.getpage(
|
||||
notes,
|
||||
page,
|
||||
app.config['ITEMS_ON_PAGE']
|
||||
).all()
|
||||
|
||||
result = []
|
||||
for note in notes:
|
||||
newRow = note_as_dict(note, fields)
|
||||
result.append(newRow)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.notes.count')
|
||||
@login_required
|
||||
def favorite_notes_count() -> int:
|
||||
result = models.db_session.query(
|
||||
models.NoteFavorite
|
||||
).filter(
|
||||
models.NoteFavorite.user_id == lib.get_user().id
|
||||
).count()
|
||||
|
||||
return result
|
||||
110
src/myapp/ns_api/favorite/page.py
Normal file
110
src/myapp/ns_api/favorite/page.py
Normal file
@@ -0,0 +1,110 @@
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
from .. import jsonrpc, login_required
|
||||
from ... import acl, app, lib, models
|
||||
from ...mutations.page import page_as_dict
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.page.add')
|
||||
@login_required
|
||||
def favorite_page_add(id: int) -> str:
|
||||
"""Добавление статьи в избранное
|
||||
"""
|
||||
page = models.db_session.query(
|
||||
models.Page
|
||||
).filter(
|
||||
models.Page.id == id
|
||||
).first()
|
||||
if page is None:
|
||||
raise ValueError
|
||||
exist = models.db_session.query(
|
||||
models.PageFavorite
|
||||
).filter(
|
||||
models.PageFavorite.page_id == id,
|
||||
models.PageFavorite.user_id == lib.get_user().id
|
||||
).first()
|
||||
if exist:
|
||||
raise ValueError
|
||||
|
||||
new_favorite_page = models.PageFavorite(
|
||||
lib.get_user(),
|
||||
page
|
||||
)
|
||||
models.db_session.add(new_favorite_page)
|
||||
models.db_session.commit()
|
||||
|
||||
return 'ok'
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.page.delete')
|
||||
@login_required
|
||||
def favorite_page_delete(id: int) -> str:
|
||||
"""Удаление статьи из избранного
|
||||
"""
|
||||
exist = models.db_session.query(
|
||||
models.PageFavorite
|
||||
).filter(
|
||||
models.PageFavorite.page_id == id,
|
||||
models.PageFavorite.user_id == lib.get_user().id
|
||||
).first()
|
||||
if exist is None:
|
||||
raise ValueError
|
||||
models.db_session.delete(exist)
|
||||
models.db_session.commit()
|
||||
return 'ok'
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.pages')
|
||||
@login_required
|
||||
def favorite_pages(
|
||||
page: int = 1,
|
||||
order_by: dict = {'field': 'title', 'order': 'asc'},
|
||||
fields: list = ['id', 'title']
|
||||
) -> list:
|
||||
indexes = models.db_session.query(
|
||||
models.PageFavorite.page_id
|
||||
).filter(
|
||||
models.PageFavorite.user_id == lib.get_user().id
|
||||
)
|
||||
pages = models.db_session.query(
|
||||
models.Page
|
||||
).filter(
|
||||
models.Page.id.in_(indexes)
|
||||
)
|
||||
|
||||
# Сортировка
|
||||
if order_by['field'] not in ['created', 'id', 'title', 'updated']:
|
||||
raise ValueError
|
||||
if order_by['order'] not in ['asc', 'desc']:
|
||||
raise ValueError
|
||||
field = getattr(models.Page, order_by['field'])
|
||||
order = getattr(field, order_by['order'])
|
||||
pages = pages.order_by(
|
||||
order()
|
||||
)
|
||||
|
||||
pages = lib.getpage(
|
||||
pages,
|
||||
page,
|
||||
app.config['ITEMS_ON_PAGE']
|
||||
).all()
|
||||
|
||||
result = []
|
||||
for page in pages:
|
||||
newRow = page_as_dict(page, fields)
|
||||
result.append(newRow)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@jsonrpc.method('favorite.pages.count')
|
||||
@login_required
|
||||
def favorite_pages_count() -> int:
|
||||
result = models.db_session.query(
|
||||
models.PageFavorite
|
||||
).filter(
|
||||
models.PageFavorite.user_id == lib.get_user().id
|
||||
).count()
|
||||
|
||||
return result
|
||||
@@ -3,10 +3,14 @@ __email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
from . import jsonrpc, login_required
|
||||
from .. import app, lib, models
|
||||
from ..mutations.note import note_as_dict
|
||||
|
||||
|
||||
@jsonrpc.method('note')
|
||||
def note_id(id: int) -> dict:
|
||||
def note_id(
|
||||
id: int,
|
||||
fields: list[str] = ['id', 'title']
|
||||
) -> dict:
|
||||
"""Заметка
|
||||
"""
|
||||
note = models.db_session.query(
|
||||
@@ -17,11 +21,7 @@ def note_id(id: int) -> dict:
|
||||
if note is None:
|
||||
raise ValueError
|
||||
|
||||
result = note.as_dict()
|
||||
result['user'] = note.user.as_dict()
|
||||
result['tags'] = []
|
||||
for tagLink in note.tags:
|
||||
result['tags'].append(tagLink.tag.as_dict())
|
||||
result = note_as_dict(note, fields)
|
||||
return result
|
||||
|
||||
|
||||
@@ -91,14 +91,30 @@ def note_update(id: int, title: str, body: str) -> dict:
|
||||
|
||||
|
||||
@jsonrpc.method('notes')
|
||||
def notes_list(page: int) -> list:
|
||||
def notes_list(
|
||||
page: int = 1,
|
||||
order_by: dict = {'field': 'title', 'order': 'asc'},
|
||||
fields: list = ['id', 'title']
|
||||
) -> list:
|
||||
"""Список заметок
|
||||
"""
|
||||
notes = models.db_session.query(
|
||||
models.Note
|
||||
).order_by(
|
||||
models.Note.title.asc()
|
||||
).filter(
|
||||
models.Note.user_id==lib.get_user().id
|
||||
)
|
||||
|
||||
# Сортировка
|
||||
if order_by['field'] not in ['created', 'id', 'title', 'status', 'updated']:
|
||||
raise ValueError
|
||||
if order_by['order'] not in ['asc', 'desc']:
|
||||
raise ValueError
|
||||
field = getattr(models.Note, order_by['field'])
|
||||
order = getattr(field, order_by['order'])
|
||||
notes = notes.order_by(
|
||||
order()
|
||||
)
|
||||
|
||||
notes = lib.getpage(
|
||||
notes,
|
||||
page,
|
||||
@@ -107,11 +123,7 @@ def notes_list(page: int) -> list:
|
||||
|
||||
result = []
|
||||
for note in notes:
|
||||
newRow = note.as_dict()
|
||||
newRow['user'] = note.user.as_dict()
|
||||
newRow['tags'] = []
|
||||
for tagLink in note.tags:
|
||||
newRow['tags'].append(tagLink.tag.as_dict())
|
||||
newRow = note_as_dict(note, fields)
|
||||
result.append(newRow)
|
||||
return result
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% include '/components/backtotop.js' %}
|
||||
{% include '/components/filter.js' %}
|
||||
{% include '/components/footer.js' %}
|
||||
{% include '/components/info.js' %}
|
||||
{% include '/components/order_by.js' %}
|
||||
{% include '/components/pagination.js' %}
|
||||
|
||||
39
src/myapp/templates/components/info.js
Normal file
39
src/myapp/templates/components/info.js
Normal file
@@ -0,0 +1,39 @@
|
||||
function ComponentInfo() {
|
||||
let data = {
|
||||
item: null,
|
||||
}
|
||||
return {
|
||||
oninit: function(vnode) {
|
||||
console.log('ComponentInfo.oninit');
|
||||
for (let key in vnode.attrs){
|
||||
data[key] = vnode.attrs[key];
|
||||
};
|
||||
},
|
||||
onbeforeupdate: function(vnode) {
|
||||
console.log('ComponentInfo.onbeforeupdate');
|
||||
for (let key in vnode.attrs){
|
||||
data[key] = vnode.attrs[key];
|
||||
};
|
||||
},
|
||||
view: function(vnode) {
|
||||
console.log('ComponentInfo.view');
|
||||
let result = [];
|
||||
if (data.item!=null) {
|
||||
result.push(
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col text-muted py-2'},
|
||||
m('small', [
|
||||
m('i', {class: 'fa fa-user me-1'}),
|
||||
m(m.route.Link, {class: 'me-2', href: `/user/${data.item.user.id}`}, data.item.user.name),
|
||||
{tag: "<", children: `Создано: ${data.item.created}`},
|
||||
{tag: "<", children: ' '},
|
||||
{tag: "<", children: `Обновлено: ${data.item.updated}`},
|
||||
])
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
{% include '/private/components/favorite.js' %}
|
||||
{% include '/private/components/menu-general.js' %}
|
||||
{% include '/private/components/notes.js' %}
|
||||
{% include '/private/components/pages.js' %}
|
||||
{% include '/private/components/tags.js' %}
|
||||
{% include '/private/components/users.js' %}
|
||||
|
||||
@@ -30,8 +30,10 @@ function MenuGeneral() {
|
||||
m(m.route.Link, { class: 'btn btn-outline-secondary btn-lg border-0', href: '/tags'}, 'Метки'),
|
||||
m(m.route.Link, { class: 'btn btn-outline-secondary btn-lg border-0', href: '/users'}, 'Пользователи'),
|
||||
m('div', { class: 'btn-group btn-group-lg float-end'},
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary', href: '/profile'}, m('i', {class: 'fa fa-user'})),
|
||||
m('button', {class: "btn btn-outline-danger", onclick: logout}, m('i', {class: "fa fa-sign-out"})),
|
||||
m(m.route.Link, { class: 'btn btn-outline-primary', href: '/notes' }, m('i', { class: 'fa fa-sticky-note-o' })),
|
||||
m(m.route.Link, { class: 'btn btn-outline-warning', href: '/favorite' }, m('i', { class: 'fa fa-star' })),
|
||||
m(m.route.Link, { class: 'btn btn-outline-secondary', href: '/profile'}, m('i', {class: 'fa fa-user'})),
|
||||
m('button', {class: 'btn btn-outline-danger', onclick: logout}, m('i', {class: 'fa fa-sign-out'})),
|
||||
)
|
||||
])
|
||||
)
|
||||
|
||||
57
src/myapp/templates/private/components/notes.js
Normal file
57
src/myapp/templates/private/components/notes.js
Normal file
@@ -0,0 +1,57 @@
|
||||
function ComponentNotes() {
|
||||
let data = {
|
||||
notes: null,
|
||||
};
|
||||
function note_render(note, noteIdx) {
|
||||
let odd = '';
|
||||
if (noteIdx % 2) {
|
||||
odd = ' bg-light'
|
||||
};
|
||||
let tags = note.tags.map(
|
||||
function(tag, tagIdx) {
|
||||
return [
|
||||
m('i', {class: "fa fa-tag"}),
|
||||
{tag: '<', children: ' '},
|
||||
m(m.route.Link, {class: "font-monospace text-decoration-none", href: `/tag/${tag.id}`}, tag.name),
|
||||
{tag: '<', children: ' '},
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
return m('div', {class: 'row'},
|
||||
m('div', {class: "col py-2" + odd}, [
|
||||
m(m.route.Link, {class: "text-decoration-none", href: `/note/${note.id}`}, m.trust(note.title)),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col text-muted'},
|
||||
m('small', [...tags])
|
||||
)
|
||||
)
|
||||
])
|
||||
)
|
||||
};
|
||||
function notes_render() {
|
||||
return data.notes.map(note_render);
|
||||
};
|
||||
return {
|
||||
oninit: function(vnode) {
|
||||
console.log('ComponentNotes.oninit');
|
||||
for (let key in vnode.attrs){
|
||||
data[key] = vnode.attrs[key];
|
||||
};
|
||||
},
|
||||
onupdate: function(vnode) {
|
||||
console.log('ComponentNotes.onupdate');
|
||||
for (let key in vnode.attrs){
|
||||
data[key] = vnode.attrs[key];
|
||||
};
|
||||
},
|
||||
view: function() {
|
||||
console.log('ComponentNotes.view');
|
||||
if (data.notes!=null) {
|
||||
let result = [];
|
||||
result.push(notes_render());
|
||||
return result;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
356
src/myapp/templates/private/components/tags.js
Normal file
356
src/myapp/templates/private/components/tags.js
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
Пример
|
||||
|
||||
result.push(m(ComponentTags, {resource: data.file, typeOf: 'file'}));
|
||||
*/
|
||||
function ComponentTags(arguments) {
|
||||
let data = {
|
||||
resource: null,
|
||||
typeOf: null,
|
||||
newtag: '',
|
||||
groups: {},
|
||||
tags_all: [],
|
||||
panels: {
|
||||
standart: {
|
||||
visible: false
|
||||
},
|
||||
new: {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
for (let key in arguments){
|
||||
data[key] = arguments[key];
|
||||
};
|
||||
function tag_add() {
|
||||
/* Добавить тег к ресурсу */
|
||||
let newtag = data.newtag.trim().toLowerCase();
|
||||
data.newtag = newtag;
|
||||
if (data.newtag.length<2) {
|
||||
return;
|
||||
}
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'tag.exist',
|
||||
"params": {
|
||||
"name": data.newtag
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
tag_add_to_node( response['result'] );
|
||||
} else if ('error' in response) {
|
||||
console.log(response);
|
||||
data.panels.new.visible = true;
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
function(error) {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
};
|
||||
function tag_new_add() {
|
||||
/* Добавление нового тега */
|
||||
if (data.newtag.length<2) {
|
||||
return;
|
||||
}
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'tag.add',
|
||||
"params": {
|
||||
"name": data.newtag
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
tags_get();
|
||||
tag_add_to_node( response['result'] );
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
function tag_remove(tag) {
|
||||
/* Удаление тега из ресурса */
|
||||
console.log(tag_includes(tag));
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": `tag.${data.typeOf}.delete`,
|
||||
"params": {
|
||||
"tag": tag.id,
|
||||
"id": data.resource.id
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
data.resource.tags = arrayRemove(data.resource.tags, tag);
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
function tags_get() {
|
||||
/* Получить список тегов */
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: [
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'tags.groups',
|
||||
"params": {},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'tags',
|
||||
"params": {},
|
||||
"id": get_id()
|
||||
}
|
||||
]
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response[0]) {
|
||||
data.groups = response[0]['result'];
|
||||
}
|
||||
if ('result' in response[1]) {
|
||||
data.tags_all = response[1]['result'];
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
function tags_filtered() {
|
||||
let result = data.tags_all.filter(filterTag);
|
||||
return result;
|
||||
};
|
||||
function tag_includes(tag) {
|
||||
let result = data.resource.tags.find(function(element, index, array) {
|
||||
return element.id===tag.id;
|
||||
}, tag);
|
||||
return result;
|
||||
};
|
||||
function filterTag(tag) {
|
||||
let value = data.newtag;
|
||||
if ( value.length<2 ) {
|
||||
return false;
|
||||
}
|
||||
if ( tag.name.toLowerCase().includes(value.toLowerCase()) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
function tag_add_to_node(tag) {
|
||||
/* Добавление тега к ресурсу */
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": `tag.${data.typeOf}.add`,
|
||||
"params": {
|
||||
"id": data.resource.id,
|
||||
"tag": tag.id
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
data.resource.tags.push(response['result']);
|
||||
sortedTags();
|
||||
data.panels.new.visible = false;
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
function sortedTags() {
|
||||
if (data.resource.tags === undefined) {return [];}
|
||||
data.resource.tags.sort(
|
||||
function(a, b) {
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
};
|
||||
function newtag_clear() {
|
||||
/* Очистка поля нового тега */
|
||||
data.newtag = '';
|
||||
data.panels.new.visible = false;
|
||||
};
|
||||
function form_submit_tag_add(e) {
|
||||
e.preventDefault();
|
||||
tag_add();
|
||||
};
|
||||
function form_submit_cancelNewTag(e) {
|
||||
e.preventDefault();
|
||||
newtag_clear();
|
||||
};
|
||||
function groups_render() {
|
||||
let result = [];
|
||||
Object.keys(data.groups).forEach(
|
||||
function(group, groupIdx) {
|
||||
if (groupIdx % 2) {
|
||||
result.push(m('a', {class: 'btn btn-outline-secondary font-monospace btn-lg me-2 mb-2', href: `#tag${groupIdx}`}, group));
|
||||
} else {
|
||||
result.push(m('a', {class: 'btn btn-outline-primary font-monospace btn-lg me-2 mb-2', href: `#tag${groupIdx}`}, group));
|
||||
};
|
||||
}
|
||||
);
|
||||
return result;
|
||||
};
|
||||
function groups_tags_render() {
|
||||
let result = [];
|
||||
Object.keys(data.groups).forEach(
|
||||
function(group, groupIdx) {
|
||||
let tags = data.groups[group].map(
|
||||
function(tag, tagIdx) {
|
||||
if (tag_includes(tag)) {
|
||||
return m('button', {type: 'button', class: 'btn btn-primary font-monospace me-2 mb-1', onclick: function() {tag_remove(tag)}}, tag.name);
|
||||
} else {
|
||||
return m('button', {type: 'button', class: 'btn btn-outline-secondary font-monospace me-2 mb-1', onclick: function() {tag_add_to_node(tag)}}, tag.name);
|
||||
}
|
||||
}
|
||||
);
|
||||
result.push(m('div', {class: 'row mt-3'},
|
||||
m('div', {class: 'col pe-0'}, [
|
||||
m('a', {name: `tag${groupIdx}`}),
|
||||
m('a', {class: 'btn btn-outline-danger me-2 mb-1', href: '#tags'}, group),
|
||||
[...tags]
|
||||
])
|
||||
));
|
||||
}
|
||||
);
|
||||
return result;
|
||||
};
|
||||
function tags_render() {
|
||||
let result = data.resource.tags.map(
|
||||
function(tag) {
|
||||
return m('div', {class: 'btn-group mb-1 me-2'}, [
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary font-monospace', href: `/tag/${tag.id}`}, tag.name),
|
||||
m('button', {class: 'btn btn-outline-danger', type: 'button', onclick: function() {tag_remove(tag)}}, m('i', {class: 'fa fa-remove'})),
|
||||
]);
|
||||
}
|
||||
);
|
||||
return result;
|
||||
};
|
||||
function button_add_render() {
|
||||
if (data.panels.standart.visible) {
|
||||
return m('button', {class: 'btn btn-outline-danger mb-1 me-2', type: 'button', onclick: function() {panel_show(data.panels.standart)}}, m('i', {class: 'fa fa-minus'}));
|
||||
} else {
|
||||
return m('button', {class: 'btn btn-outline-success mb-1 me-2', type: 'button', onclick: function() {panel_show(data.panels.standart)}}, m('i', {class: 'fa fa-plus'}));
|
||||
}
|
||||
};
|
||||
return {
|
||||
data: data,
|
||||
oninit: function(vnode) {
|
||||
console.log('ComponentTags.oninit');
|
||||
for (let key in vnode.attrs){
|
||||
data[key] = vnode.attrs[key];
|
||||
};
|
||||
tags_get();
|
||||
},
|
||||
onbeforeupdate: function(vnode) {
|
||||
console.log('ComponentTags.onbeforeupdate');
|
||||
for (let key in vnode.attrs){
|
||||
data[key] = vnode.attrs[key];
|
||||
};
|
||||
// tags_get();
|
||||
},
|
||||
view: function(vnode) {
|
||||
console.log('ComponentTags.view');
|
||||
let result = []
|
||||
if (data.resource) {
|
||||
result.push(
|
||||
m('div', {class: 'row mb-3'},
|
||||
m('div', {class: 'col'}, [
|
||||
m('a', {name: "tags"}),
|
||||
m('div', {class: 'btn mb-1'}, m('i', {class: 'fa fa-tags'})),
|
||||
button_add_render(),
|
||||
tags_render()
|
||||
])
|
||||
)
|
||||
);
|
||||
if (data.panels.standart.visible) {
|
||||
result.push(...[
|
||||
m('div', {class: 'row mb-3'},
|
||||
m('div', {class: 'col'}, [
|
||||
m('form', {onsubmit: form_submit_tag_add},
|
||||
m('div', {class: 'input-group'}, [
|
||||
m('button', {class: 'btn btn-outline-danger', type: 'button', onclick: newtag_clear}, m('i', {class: 'fa fa-remove'})),
|
||||
m('input', {class: 'form-control', oninput: function(e) {data.newtag = e.target.value}, onkeyup: function(e) { if (e.keyCode==13) { tag_add() }}, value: data.newtag}),
|
||||
m('button', {class: 'btn btn-outline-success', type: 'submit'}, m('i', {class: 'fa fa-save'})),
|
||||
])
|
||||
)
|
||||
])
|
||||
),
|
||||
m('div', {class: 'row mb-3'},
|
||||
m('div', {class: 'col py-2'},
|
||||
(function() {
|
||||
let result = tags_filtered().map(
|
||||
function(tag) {
|
||||
if (tag_includes(tag)) {
|
||||
return m('button', {class: 'btn btn-primary font-monospace me-2 mb-1', type: 'button', onclick: function() { tag_remove(tag) }}, tag.name);
|
||||
} else {
|
||||
return m('button', {class: 'btn btn-outline-secondary font-monospace me-2 mb-1', type: 'button', onclick: function() { tag_add_to_node(tag) }}, tag.name);
|
||||
}
|
||||
}
|
||||
);
|
||||
return result;
|
||||
})()
|
||||
)
|
||||
),
|
||||
m('div', {class: 'row mb-3'},
|
||||
m('div', {class: 'col py-2'},
|
||||
groups_render()
|
||||
)
|
||||
)
|
||||
]);
|
||||
if (data.panels.new.visible) {
|
||||
result.push(
|
||||
m('form', {onsubmit: form_submit_cancelNewTag},
|
||||
m('div', {class: 'row mb-3'},
|
||||
m('div', {class: 'col py-2'}, [
|
||||
m('label', 'Добавить новый тег?'),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2'},
|
||||
m('div', {class: 'input-group'}, [
|
||||
m('button', {class: 'btn btn-outline-danger', type: 'submit'}, m('i', {class: 'fa fa-close'})),
|
||||
m('input', {class: 'form-control', oninput: function (e) {data.newtag = e.target.value}, value: data.newtag}),
|
||||
m('button', {class: 'btn btn-outline-success', type: 'button', onclick: tag_new_add}, 'Добавить')
|
||||
])
|
||||
)
|
||||
)
|
||||
])
|
||||
)
|
||||
),
|
||||
);
|
||||
};
|
||||
result.push(
|
||||
groups_tags_render()
|
||||
);
|
||||
};
|
||||
};
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
110
src/myapp/templates/private/domains/favorite/favorite.js
Normal file
110
src/myapp/templates/private/domains/favorite/favorite.js
Normal file
@@ -0,0 +1,110 @@
|
||||
function Favorite() {
|
||||
function breadcrumbs_render() {
|
||||
let result = m('ul', {class: 'breadcrumb mt-2'}, [
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/'}, m('i', {class: 'fa fa-home'}))),
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/favorite'}, 'Избранное')),
|
||||
m('li', {class: 'breadcrumb-item active'}, 'Избранные вопросы'),
|
||||
]);
|
||||
return result;
|
||||
};
|
||||
return {
|
||||
view: function(vnode) {
|
||||
console.log('Favorite.view');
|
||||
let result = [];
|
||||
result.push([
|
||||
breadcrumbs_render(),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col h1 py-2'}, [
|
||||
m(m.route.Link, {class: "btn btn-outline-secondary btn-lg me-2", href: '/profile', title: "Вернуться"}, m('i', {class: "fa fa-chevron-left"})),
|
||||
'Избранное'
|
||||
])
|
||||
),
|
||||
m('hr'),
|
||||
m(MenuFavorite),
|
||||
breadcrumbs_render(),
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
/*
|
||||
<h3>
|
||||
<a class="btn btn-outline-secondary" href="/profile"><i class="fa fa-chevron-left"></i></a>
|
||||
Избранное</h3>
|
||||
{# include 'favorite_menu.html' #}
|
||||
<hr />
|
||||
|
||||
<div class='row'>
|
||||
<div class="col-md-4 py-2">
|
||||
<a class="btn btn-outline-secondary btn-lg w-100" href="/favorite/questions">Вопросы <span class="badge bg-secondary">{ questions }</span></a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 py-2">
|
||||
<a class="btn btn-outline-secondary btn-lg w-100" href="/favorite/orders">Заказы <span class="badge bg-secondary">{ orders }</span></a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 py-2">
|
||||
<a class="btn btn-outline-secondary btn-lg w-100" href="/favorite/pages">Статьи <span class="badge bg-secondary">{ pages }</span></a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 py-2">
|
||||
<a class="btn btn-outline-secondary btn-lg w-100" href="/favorite/resumes">Резюме <span class="badge bg-secondary">{ resumes }</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Object.assign(root.data, {
|
||||
questions: 0,
|
||||
orders: 0,
|
||||
pages: 0,
|
||||
resumes: 0,
|
||||
menuitem: null,
|
||||
});
|
||||
root.created = function() {
|
||||
let vm = this;
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: [
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.questions.count',
|
||||
"params": {},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.orders.count',
|
||||
"params": {},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.pages.count',
|
||||
"params": {},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.resumes.count',
|
||||
"params": {},
|
||||
"id": get_id()
|
||||
},
|
||||
]
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response[0]) {
|
||||
vm.questions = response[0]['result'];
|
||||
}
|
||||
if ('result' in response[1]) {
|
||||
vm.orders = response[1]['result'];
|
||||
}
|
||||
if ('result' in response[2]) {
|
||||
vm.pages = response[2]['result'];
|
||||
}
|
||||
if ('result' in response[3]) {
|
||||
vm.resumes = response[3]['result'];
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
*/
|
||||
15
src/myapp/templates/private/domains/favorite/inc.j2
Normal file
15
src/myapp/templates/private/domains/favorite/inc.j2
Normal file
@@ -0,0 +1,15 @@
|
||||
{% include '/private/domains/favorite/favorite.js' %}
|
||||
{% include '/private/domains/favorite/menu.js' %}
|
||||
{% include '/private/domains/favorite/notes.js' %}
|
||||
{% include '/private/domains/favorite/pages.js' %}
|
||||
|
||||
Object.assign(
|
||||
routes,
|
||||
{
|
||||
"/favorite": layout_decorator(Favorite),
|
||||
"/favorite/notes": layout_decorator(FavoriteNotes),
|
||||
"/favorite/notes/:page": layout_decorator(FavoriteNotes),
|
||||
"/favorite/pages": layout_decorator(FavoritePages),
|
||||
"/favorite/pages/:page": layout_decorator(FavoritePages),
|
||||
}
|
||||
);
|
||||
45
src/myapp/templates/private/domains/favorite/menu.js
Normal file
45
src/myapp/templates/private/domains/favorite/menu.js
Normal file
@@ -0,0 +1,45 @@
|
||||
function MenuFavorite() {
|
||||
let data = {
|
||||
menuitem: null,
|
||||
}
|
||||
function button_common() {
|
||||
if (data.menuitem===null) {
|
||||
return {tag: '<', children: '<div class="btn btn-primary me-2"><i class="fa fa-bars"></i></div>'};
|
||||
} else {
|
||||
return m(m.route.Link, {class: "btn btn-outline-secondary me-2 text-decoration-none", href: '/favorite', title: 'Избранное'}, m('i', {class: 'fa fa-bars'}))
|
||||
}
|
||||
};
|
||||
function button_pages() {
|
||||
if (data.menuitem==='pages') {
|
||||
return {tag: '<', children: '<div class="btn btn-primary me-2">Статьи</div>'};
|
||||
} else {
|
||||
return m(m.route.Link, {class: "btn btn-outline-secondary me-2 text-decoration-none", href: '/favorite/pages'}, 'Статьи')
|
||||
}
|
||||
};
|
||||
function button_notes() {
|
||||
if (data.menuitem === 'notes') {
|
||||
return { tag: '<', children: '<div class="btn btn-primary me-2">Заметки</div>' };
|
||||
} else {
|
||||
return m(m.route.Link, { class: "btn btn-outline-secondary me-2 text-decoration-none", href: '/favorite/notes' }, 'Заметки')
|
||||
}
|
||||
};
|
||||
return {
|
||||
oninit: function(vnode) {
|
||||
console.log('MenuFavorite.oninit');
|
||||
for (let key in vnode.attrs){
|
||||
console.log(key);
|
||||
data[key] = vnode.attrs[key];
|
||||
};
|
||||
},
|
||||
view: function() {
|
||||
console.log('MenuFavorite.view');
|
||||
return m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2'}, [
|
||||
button_common(),
|
||||
button_notes(),
|
||||
button_pages(),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
120
src/myapp/templates/private/domains/favorite/notes.js
Normal file
120
src/myapp/templates/private/domains/favorite/notes.js
Normal file
@@ -0,0 +1,120 @@
|
||||
function FavoriteNotes() {
|
||||
let data = {
|
||||
filter: PanelFilter(),
|
||||
order_by: PanelOrderBy({
|
||||
field: 'updated',
|
||||
fields: [
|
||||
{value: 'id', text: 'ID'},
|
||||
{value: 'title', text: 'заголовку'},
|
||||
{value: 'created', text: 'дате создания'},
|
||||
{value: 'updated', text: 'дате обновления'}
|
||||
],
|
||||
clickHandler: notes_get,
|
||||
order: 'asc',
|
||||
}),
|
||||
notes: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 0,
|
||||
prefix_url: '/favorite/notes'
|
||||
},
|
||||
};
|
||||
function breadcrumbs_render() {
|
||||
let result = m('ul', {class: 'breadcrumb mt-2'}, [
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/'}, m('i', {class: 'fa fa-home'}))),
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/favorite'}, 'Избранное')),
|
||||
m('li', {class: 'breadcrumb-item active'}, 'Избранные заметки'),
|
||||
]);
|
||||
return result;
|
||||
};
|
||||
function notes_get() {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: [
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.notes',
|
||||
"params": {
|
||||
"page": data.pagination.page,
|
||||
"order_by": data.order_by.value,
|
||||
"fields": ["id", "title", "tags", "created", "updated", "user"]
|
||||
},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.notes.count',
|
||||
"id": get_id()
|
||||
}
|
||||
]
|
||||
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response[0]) {
|
||||
data.notes = response[0]['result'];
|
||||
}
|
||||
if ('result' in response[1]) {
|
||||
data.pagination.size = response[1]['result'];
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
return {
|
||||
oninit: function(vnode) {
|
||||
console.log('FavoriteNotes.oninit');
|
||||
document.title = `Избранные статьи - ${SETTINGS.TITLE}`;
|
||||
if (vnode.attrs.page!==undefined) {
|
||||
data.pagination.page = Number(vnode.attrs.page);
|
||||
};
|
||||
notes_get();
|
||||
},
|
||||
onbeforeupdate: function(vnode) {
|
||||
console.log('FavoriteNotes.onbeforeupdate');
|
||||
if (vnode.attrs.page!==undefined) {
|
||||
if (data.pagination.page != Number(vnode.attrs.page)) {
|
||||
data.pagination.page = Number(vnode.attrs.page);
|
||||
notes_get();
|
||||
};
|
||||
} else {
|
||||
if (data.pagination.page != 1) {
|
||||
data.pagination.page = 1;
|
||||
notes_get();
|
||||
};
|
||||
}
|
||||
},
|
||||
view: function(vnode) {
|
||||
console.log('FavoriteNotes.view');
|
||||
result = [];
|
||||
result.push(
|
||||
breadcrumbs_render(),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col h1 py-2'}, [
|
||||
m('div', {class: "btn-group btn-group-lg me-2"}, [
|
||||
m(m.route.Link, {class: "btn btn-outline-secondary", href: '/favorite', title: "Вернуться"}, m('i', {class: "fa fa-chevron-left"})),
|
||||
m('button', {type: "button", class: "btn btn-outline-secondary", onclick: function() { panel_show(data.filter.data) }},
|
||||
m('i', {class: "fa fa-filter"})
|
||||
),
|
||||
m('button', {type: "button", class: "btn btn-outline-secondary", onclick: function() { panel_show(data.order_by.data) }},
|
||||
m('i', {class: "fa fa-sort-alpha-asc"})
|
||||
)
|
||||
]),
|
||||
`Избранные заметки`
|
||||
])
|
||||
),
|
||||
m('hr'),
|
||||
m(MenuFavorite, {menuitem: 'notes'}),
|
||||
);
|
||||
|
||||
result.push(m(data.filter));
|
||||
result.push(m(data.order_by));
|
||||
result.push(m(Pagination, data.pagination));
|
||||
if (data.notes.length>0) {
|
||||
result.push(m(ComponentNotes, {notes: data.notes}));
|
||||
result.push(m(Pagination, data.pagination));
|
||||
};
|
||||
result.push(breadcrumbs_render());
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
120
src/myapp/templates/private/domains/favorite/pages.js
Normal file
120
src/myapp/templates/private/domains/favorite/pages.js
Normal file
@@ -0,0 +1,120 @@
|
||||
function FavoritePages() {
|
||||
let data = {
|
||||
filter: PanelFilter(),
|
||||
order_by: PanelOrderBy({
|
||||
field: 'updated',
|
||||
fields: [
|
||||
{value: 'id', text: 'ID'},
|
||||
{value: 'title', text: 'заголовку'},
|
||||
{value: 'created', text: 'дате создания'},
|
||||
{value: 'updated', text: 'дате обновления'}
|
||||
],
|
||||
clickHandler: pages_get,
|
||||
order: 'asc',
|
||||
}),
|
||||
pages: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 0,
|
||||
prefix_url: '/favorite/pages'
|
||||
},
|
||||
};
|
||||
function breadcrumbs_render() {
|
||||
let result = m('ul', {class: 'breadcrumb mt-2'}, [
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/'}, m('i', {class: 'fa fa-home'}))),
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/favorite'}, 'Избранное')),
|
||||
m('li', {class: 'breadcrumb-item active'}, 'Избранные статьи'),
|
||||
]);
|
||||
return result;
|
||||
};
|
||||
function pages_get() {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: [
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.pages',
|
||||
"params": {
|
||||
"page": data.pagination.page,
|
||||
"order_by": data.order_by.value,
|
||||
"fields": ["id", "title", "tags", "created", "updated", "user"]
|
||||
},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'favorite.pages.count',
|
||||
"id": get_id()
|
||||
}
|
||||
]
|
||||
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response[0]) {
|
||||
data.pages = response[0]['result'];
|
||||
}
|
||||
if ('result' in response[1]) {
|
||||
data.pagination.size = response[1]['result'];
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
return {
|
||||
oninit: function(vnode) {
|
||||
console.log('FavoritePages.oninit');
|
||||
document.title = `Избранные статьи - ${SETTINGS.TITLE}`;
|
||||
if (vnode.attrs.page!==undefined) {
|
||||
data.pagination.page = Number(vnode.attrs.page);
|
||||
};
|
||||
pages_get();
|
||||
},
|
||||
onbeforeupdate: function(vnode) {
|
||||
console.log('FavoritePages.onbeforeupdate');
|
||||
if (vnode.attrs.page!==undefined) {
|
||||
if (data.pagination.page != Number(vnode.attrs.page)) {
|
||||
data.pagination.page = Number(vnode.attrs.page);
|
||||
pages_get();
|
||||
};
|
||||
} else {
|
||||
if (data.pagination.page != 1) {
|
||||
data.pagination.page = 1;
|
||||
pages_get();
|
||||
};
|
||||
}
|
||||
},
|
||||
view: function(vnode) {
|
||||
console.log('FavoritePages.view');
|
||||
result = [];
|
||||
result.push(
|
||||
breadcrumbs_render(),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col h1 py-2'}, [
|
||||
m('div', {class: "btn-group btn-group-lg me-2"}, [
|
||||
m(m.route.Link, {class: "btn btn-outline-secondary", href: '/favorite', title: "Вернуться"}, m('i', {class: "fa fa-chevron-left"})),
|
||||
m('button', {type: "button", class: "btn btn-outline-secondary", onclick: function() { panel_show(data.filter.data) }},
|
||||
m('i', {class: "fa fa-filter"})
|
||||
),
|
||||
m('button', {type: "button", class: "btn btn-outline-secondary", onclick: function() { panel_show(data.order_by.data) }},
|
||||
m('i', {class: "fa fa-sort-alpha-asc"})
|
||||
)
|
||||
]),
|
||||
`Избранные статьи`
|
||||
])
|
||||
),
|
||||
m('hr'),
|
||||
m(MenuFavorite, {menuitem: 'pages'}),
|
||||
);
|
||||
|
||||
result.push(m(data.filter));
|
||||
result.push(m(data.order_by));
|
||||
result.push(m(Pagination, data.pagination));
|
||||
if (data.pages.length>0) {
|
||||
result.push(m(ComponentPages, {pages: data.pages}));
|
||||
result.push(m(Pagination, data.pagination));
|
||||
};
|
||||
result.push(breadcrumbs_render());
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
{% include '/private/domains/favorite/inc.j2' %}
|
||||
{% include '/private/domains/note/inc.j2' %}
|
||||
{% include '/private/domains/page/inc.j2' %}
|
||||
{% include '/private/domains/profile/inc.j2' %}
|
||||
{% include '/private/domains/tag/inc.j2' %}
|
||||
|
||||
103
src/myapp/templates/private/domains/note/add.js
Normal file
103
src/myapp/templates/private/domains/note/add.js
Normal file
@@ -0,0 +1,103 @@
|
||||
function NoteAdd() {
|
||||
let data = {
|
||||
uuid: get_id(),
|
||||
note: {
|
||||
title: '',
|
||||
body: '',
|
||||
},
|
||||
editor: null,
|
||||
};
|
||||
function breadcrumbs_render() {
|
||||
let result = m('ul', {class: 'breadcrumb mt-2'}, [
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/'}, m('i', {class: 'fa fa-home'}))),
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/notes'}, 'Список заметок')),
|
||||
m('li', {class: 'breadcrumb-item active'}, 'Новая заметка'),
|
||||
]);
|
||||
return result;
|
||||
};
|
||||
function form_submit(e) {
|
||||
e.preventDefault();
|
||||
note_add();
|
||||
};
|
||||
function note_add() {
|
||||
if (data.note.title.length<2) {
|
||||
return;
|
||||
};
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note.add',
|
||||
"params": data.note,
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
m.route.set(`/note/${response['result'].id}`);
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
function editor_events(ed) {
|
||||
ed.on('change', function (e) {
|
||||
data.note.body = ed.getContent();
|
||||
});
|
||||
};
|
||||
return {
|
||||
oncreate(vnode) {
|
||||
console.log('NoteAdd.oncreate');
|
||||
if (data.editor==null) {
|
||||
tinymce_config = tinymce_config_init();
|
||||
tinymce_config.selector = `#note_body_${data.uuid}`;
|
||||
tinymce_config.setup = editor_events;
|
||||
tinymce.init(tinymce_config).then(
|
||||
function (editors) {
|
||||
data.editor = editors[0];
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
onbeforeremove: function(vnode) {
|
||||
console.log('NoteAdd.onbeforeremove');
|
||||
if (data.editor!=null) {
|
||||
data.editor.remove();
|
||||
data.editor = null;
|
||||
};
|
||||
},
|
||||
view: function(vnode) {
|
||||
console.log('NoteAdd.view');
|
||||
result = [];
|
||||
result.push(breadcrumbs_render());
|
||||
result.push([
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col h1 py-2'}, [
|
||||
m(m.route.Link, {class: "btn btn-outline-secondary btn-lg me-2", href: "/notes", title: "Список заметок"}, m('i', {class: 'fa fa-chevron-left'})),
|
||||
'Новая заметка'
|
||||
])
|
||||
),
|
||||
m('hr')
|
||||
]);
|
||||
result.push(
|
||||
m('form', {onsubmit: form_submit}, [
|
||||
m('div', {class: 'mb-2'}, [
|
||||
m('label', {class: 'form-label'}, 'Заголовок'),
|
||||
m('input', {class: 'form-control', type: 'text', oninput: function (e) {data.note.title = e.target.value}, value: data.note.title}, 'Заголовок'),
|
||||
]),
|
||||
m('div', {class: 'mb-2'}, [
|
||||
m('label', {class: 'form-label'}, 'Текст'),
|
||||
m('textarea', { class: 'form-control', cols: '40', rows: '8', id: `note_body_${data.uuid}`, oninput: function (e) {data.note.body = e.target.value}, value: data.note.body})
|
||||
]),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2'}, [
|
||||
m('button', {class: 'btn btn-outline-success btn-lg float-end', type: 'submit'}, [m('i', {class: 'fa fa-save me-2'}), 'Сохранить']),
|
||||
])
|
||||
),
|
||||
])
|
||||
);
|
||||
result.push(breadcrumbs_render());
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
170
src/myapp/templates/private/domains/note/edit.js
Normal file
170
src/myapp/templates/private/domains/note/edit.js
Normal file
@@ -0,0 +1,170 @@
|
||||
function NoteEdit() {
|
||||
let data = {
|
||||
uuid: get_id(),
|
||||
note: null,
|
||||
editor: null,
|
||||
};
|
||||
function breadcrumbs_render() {
|
||||
let result = m('ul', {class: 'breadcrumb mt-2'}, [
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/'}, m('i', {class: 'fa fa-home'}))),
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/notes'}, 'Список статей')),
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: `/note/${data.note.id}`}, data.note.title)),
|
||||
m('li', {class: 'breadcrumb-item active'}, 'Редактирование страницы'),
|
||||
]);
|
||||
return result;
|
||||
};
|
||||
function note_get(id) {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note',
|
||||
"params": {
|
||||
"id": id,
|
||||
"fields": ["id", "title", "body"]
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
data.note = response['result'];
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
function apply() {
|
||||
/* Сохранить */
|
||||
if (data.note.title.length<2) {
|
||||
return;
|
||||
};
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "note.update",
|
||||
"params": {
|
||||
"id": data.note.id,
|
||||
"title": data.note.title,
|
||||
"body": data.note.body
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
function apply_and_close() {
|
||||
/* Сохранить */
|
||||
if (data.note.title.length<2) {
|
||||
return;
|
||||
};
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "note.update",
|
||||
"params": {
|
||||
"id": data.note.id,
|
||||
"title": data.note.title,
|
||||
"body": data.note.body
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
m.route.set(`/note/${data.note.id}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
function form_submit(e) {
|
||||
e.preventDefault();
|
||||
apply_and_close();
|
||||
};
|
||||
function editor_events(ed) {
|
||||
ed.on('change', function (e) {
|
||||
data.note.body = ed.getContent();
|
||||
});
|
||||
};
|
||||
return {
|
||||
oncreate(vnode) {
|
||||
console.log('NoteEdit.oncreate');
|
||||
if (data.note!=null) {
|
||||
console.log(data.note);
|
||||
if (data.editor==null) {
|
||||
tinymce_config = tinymce_config_init();
|
||||
tinymce_config.selector = `#note_body_${data.uuid}`;
|
||||
tinymce_config.setup = editor_events;
|
||||
tinymce.init(tinymce_config).then(
|
||||
function (editors) {
|
||||
data.editor = editors[0];
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
oninit: function(vnode) {
|
||||
console.log('NoteEdit.oninit');
|
||||
note_get(vnode.attrs.id);
|
||||
},
|
||||
onupdate: function(vnode) {
|
||||
console.log('NoteEdit.onupdate');
|
||||
if (data.note.id.toString()!==vnode.attrs.id) {
|
||||
note_get(vnode.attrs.id);
|
||||
};
|
||||
if (data.note!=null) {
|
||||
if (data.editor==null) {
|
||||
tinymce_config = tinymce_config_init();
|
||||
tinymce_config.selector = `#note_body_${data.uuid}`;
|
||||
tinymce_config.setup = editor_events;
|
||||
tinymce.init(tinymce_config).then(
|
||||
function (editors) {
|
||||
data.editor = editors[0];
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
},
|
||||
view: function(vnode) {
|
||||
let result = [];
|
||||
if (data.note!=null) {
|
||||
result.push(
|
||||
breadcrumbs_render(),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col h1 py-2'},
|
||||
m(m.route.Link, {class: "btn btn-outline-secondary btn-lg me-2", href: `/note/${data.note.id}`, title: data.note.title}, m('i', {class: 'fa fa-chevron-left'})),
|
||||
'Редактирование страницы',
|
||||
)
|
||||
),
|
||||
m('hr'),
|
||||
m('form', {onsubmit: form_submit}, [
|
||||
m('div', {class: 'mb-2'}, [
|
||||
m('label', {class: 'form-label'}, 'Заголовок'),
|
||||
m('input', {class: 'form-control', type: 'text', oninput: function (e) {data.note.title = e.target.value}, value: data.note.title}, 'Заголовок'),
|
||||
]),
|
||||
m('div', {class: 'mb-2'}, [
|
||||
m('label', {class: 'form-label'}, 'Текст'),
|
||||
m('textarea', { class: 'form-control', cols: '40', rows: '8', id: `note_body_${data.uuid}`, oninput: function (e) {data.note.body = e.target.value}, value: data.note.body})
|
||||
]),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2'}, [
|
||||
m('button', { class: 'btn btn-outline-success btn-lg float-end', type: 'button', onclick: apply }, [m('i', { class: 'fa fa-save me-2'}), 'Сохранить']),
|
||||
m('button', {class: 'btn btn-outline-success btn-lg float-end', type: 'submit'}, [m('i', {class: 'fa fa-save me-2'}), 'Сохранить и закрыть']),
|
||||
])
|
||||
),
|
||||
])
|
||||
);
|
||||
result.push(breadcrumbs_render());
|
||||
};
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
17
src/myapp/templates/private/domains/note/inc.j2
Normal file
17
src/myapp/templates/private/domains/note/inc.j2
Normal file
@@ -0,0 +1,17 @@
|
||||
{% include '/private/domains/note/add.js' %}
|
||||
{% include '/private/domains/note/edit.js' %}
|
||||
{% include '/private/domains/note/note.js' %}
|
||||
{% include '/private/domains/note/notes.js' %}
|
||||
{% include '/private/domains/note/source.js' %}
|
||||
|
||||
Object.assign(
|
||||
routes,
|
||||
{
|
||||
"/note/add": layout_decorator(NoteAdd),
|
||||
"/note/:id/edit": layout_decorator(NoteEdit),
|
||||
"/note/:id/source": layout_decorator(NoteSource),
|
||||
"/note/:id": layout_decorator(Note),
|
||||
"/notes": layout_decorator(Notes),
|
||||
"/notes/:page": layout_decorator(Notes),
|
||||
}
|
||||
);
|
||||
220
src/myapp/templates/private/domains/note/note.js
Normal file
220
src/myapp/templates/private/domains/note/note.js
Normal file
@@ -0,0 +1,220 @@
|
||||
function Note() {
|
||||
let data = {
|
||||
note: null,
|
||||
panels: {
|
||||
standart: {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
};
|
||||
function breadcrumbs_render() {
|
||||
let result = m('ul', {class: 'breadcrumb mt-2'}, [
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/'}, m('i', {class: 'fa fa-home'}))),
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/notes'}, 'Список заметок')),
|
||||
m('li', {class: 'breadcrumb-item active'}, data.note.title),
|
||||
]);
|
||||
return result;
|
||||
};
|
||||
function settings_save(name) {
|
||||
if (name==='status') {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note.status',
|
||||
"params": {
|
||||
"id": data.note.id,
|
||||
"status": data.note.status
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
data.note = response['result'];
|
||||
}
|
||||
}
|
||||
);
|
||||
} else if (name==='typeOf') {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note.settings.update',
|
||||
"params": {
|
||||
"id": data.note.id,
|
||||
"name": name,
|
||||
"typeOf": "string",
|
||||
"value": value
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
data.note = response['result'];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
function note_delete() {
|
||||
/* Удалить статью в корзину */
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note.delete',
|
||||
"params": {
|
||||
"id": data.note.id
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
m.route.set('/notes');
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
function note_get(id) {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: [
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note',
|
||||
"params": {
|
||||
"id": id,
|
||||
"fields": ["id", "title", "body", "created", "updated", "parent_id", "tags", "user", "nodes"]
|
||||
},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note.nodes',
|
||||
"params": {
|
||||
"id": id
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
]
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response[0]) {
|
||||
data.note = response[0]['result'];
|
||||
document.title = `${data.note.title} - ${SETTINGS.TITLE}`;
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
function button_back_render() {
|
||||
if (data.note.parent_id===null) {
|
||||
return m(m.route.Link, {class: "btn btn-outline-secondary", href: "/notes", title: "Список статей"}, m('i', {class: 'fa fa-chevron-left'}))
|
||||
} else {
|
||||
return m(m.route.Link, {class: "btn btn-outline-secondary", href: `/note/${data.note.parent_id}`, title: "Список статей"}, m('i', {class: 'fa fa-chevron-left'}))
|
||||
}
|
||||
};
|
||||
function button_draft_render() {
|
||||
if (data.note.status==='draft') {
|
||||
return m('span', {class: 'small text-muted'}, 'черновик')
|
||||
}
|
||||
};
|
||||
function button_trash_render() {
|
||||
if (data.note.trash) {
|
||||
return m('button', {class: 'btn btn-outline-success', onclick: note_recovery}, [m('i', {class: 'fa fa-plus'}), ' Восстановить из корзины'])
|
||||
} else {
|
||||
return m('button', {class: 'btn btn-outline-warning', onclick: note_delete}, [m('i', {class: 'fa fa-trash-o'}), ' В корзину'])
|
||||
}
|
||||
};
|
||||
function panels_standart_render() {
|
||||
if (data.panels.standart.visible) {
|
||||
return m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2'}, [
|
||||
m(ComponentFavorite, {resource: data.note, name: 'note'}),
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary', href: `/note/${data.note.id}/edit`}, [m('i', {class: 'fa fa-edit'}), ' Редактировать']),
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary', href: `/note/${data.note.id}/moving`}, [m('i', {class: 'fa fa-arrows'}), ' Перемещение']),
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary', href: `/note/${data.note.id}/history`}, [m('i', {class: 'fa fa-history'}), ' История изменений']),
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary', href: `/note/${data.note.id}/source`}, [m('i', {class: 'fa fa-code'}), ' Исходный код']),
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary', href: `/note/${data.note.id}/print`}, [m('i', {class: 'fa fa-print'}), ' Печать']),
|
||||
m(m.route.Link, {class: 'btn btn-outline-secondary', href: `/note/${data.note.id}/recovery`}, [m('i', {class: 'fa fa-print'}), ' Печать']),
|
||||
button_trash_render(),
|
||||
m('h3', 'Свойства'),
|
||||
m('div', {class: 'row py-2'},
|
||||
m('div', {class: 'col-md-4'}, m('label', 'Статус')),
|
||||
m('div', {class: 'col-md-8'},
|
||||
m('div', {class: 'input-group'}, [
|
||||
m('button', {class: 'btn btn-outline-warning', type: 'button'}, m('i', {class: 'fa fa-rotate-left'})),
|
||||
m('select', {class: 'form-select', onchange: function(e) { data.note.status = e.target.value; }, value: data.note.status}, [
|
||||
m('option', {value: 'draft'}, 'Черновик'),
|
||||
m('option', {value: 'published'}, 'Опубликовано')
|
||||
]),
|
||||
m('button', {class: 'btn btn-outline-success', type: 'button', onclick: function() { settings_save('status') }}, m('i', {class: 'fa fa-save'}))
|
||||
])
|
||||
)
|
||||
)
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
return {
|
||||
oninit: function(vnode) {
|
||||
console.log('Note.oninit');
|
||||
note_get(Number(vnode.attrs.id));
|
||||
},
|
||||
onbeforeupdate: function(vnode) {
|
||||
console.log('Note.onbeforeupdate');
|
||||
if (data.note != null) {
|
||||
if (data.note.id!==Number(vnode.attrs.id)) {
|
||||
note_get(Number(vnode.attrs.id));
|
||||
}
|
||||
}
|
||||
},
|
||||
view: function(vnode) {
|
||||
console.log('Note.view');
|
||||
result = [];
|
||||
if (data.note!=null) {
|
||||
result.push(
|
||||
breadcrumbs_render(),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col h1 py-2'},
|
||||
m(m.route.Link, {class: 'btn btn-outline-success float-end', href: `/note/${data.note.id}/add`}, m('i', {class: 'fa fa-plus'})),
|
||||
m('div', {class: 'btn-group btn-group-lg me-2'}, [
|
||||
button_back_render(),
|
||||
m('button', {type: 'button', class: 'btn btn-outline-secondary', onclick: function() {panel_show(data.panels.standart)}}, m('i', {class: 'fa fa-cog'})),
|
||||
]),
|
||||
data.note.title,
|
||||
button_draft_render(),
|
||||
)
|
||||
),
|
||||
panels_standart_render(),
|
||||
m('hr'),
|
||||
m(ComponentInfo, {item: data.note}),
|
||||
m(ComponentTags, {resource: data.note, typeOf: 'note'}),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col'}, m.trust(data.note.body))
|
||||
),
|
||||
);
|
||||
if (data.note.nodes.length>0) {
|
||||
let nodes = data.note.nodes.map(
|
||||
function(subnote, subnoteIdx) {
|
||||
return m('li',
|
||||
m(m.route.Link, {href: `/note/${subnote.id}`}, subnote.title)
|
||||
);
|
||||
}
|
||||
);
|
||||
result.push(
|
||||
m('ul', [...nodes])
|
||||
);
|
||||
}
|
||||
result.push(breadcrumbs_render());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
112
src/myapp/templates/private/domains/note/notes.js
Normal file
112
src/myapp/templates/private/domains/note/notes.js
Normal file
@@ -0,0 +1,112 @@
|
||||
function Notes() {
|
||||
let data = {
|
||||
filter: PanelFilter(),
|
||||
order_by: PanelOrderBy({
|
||||
field: 'title',
|
||||
fields: [
|
||||
{value: 'id', text: 'ID'},
|
||||
{value: 'title', text: 'заголовку'},
|
||||
{value: 'created', text: 'дате создания'},
|
||||
{value: 'updated', text: 'дате обновления'}
|
||||
],
|
||||
clickHandler: notes_get,
|
||||
order: 'asc',
|
||||
}),
|
||||
notes: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 0,
|
||||
prefix_url: '/notes'
|
||||
},
|
||||
}
|
||||
function breadcrumbs_render() {
|
||||
return m('ul', {class: 'breadcrumb mt-2'}, [
|
||||
m('li', {class: 'breadcrumb-item'}, m(m.route.Link, {href: '/'}, m('i', {class: 'fa fa-home'}))),
|
||||
m('li', {class: 'breadcrumb-item active'}, 'Список заметок')
|
||||
]);
|
||||
};
|
||||
function notes_get() {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: [
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'notes',
|
||||
"params": {
|
||||
"page": data.pagination.page,
|
||||
"order_by": data.order_by.value,
|
||||
"fields": ["id", "title", "tags", "created", "updated"]
|
||||
},
|
||||
"id": get_id()
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'notes.count',
|
||||
"params": {},
|
||||
"id": get_id()
|
||||
}
|
||||
]
|
||||
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response[0]) {
|
||||
data.notes = response[0]['result'];
|
||||
}
|
||||
if ('result' in response[1]) {
|
||||
data.pagination.size = response[1]['result'];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return {
|
||||
oninit: function(vnode) {
|
||||
console.log('Notes.oninit');
|
||||
document.title = `Список статей - ${SETTINGS.TITLE}`;
|
||||
if (vnode.attrs.page!==undefined) {
|
||||
data.pagination.page = Number(vnode.attrs.page);
|
||||
};
|
||||
notes_get();
|
||||
},
|
||||
onbeforeupdate: function(vnode) {
|
||||
console.log('Notes.onbeforeupdate');
|
||||
if (vnode.attrs.page!==undefined) {
|
||||
if (data.pagination.page.toString() != vnode.attrs.page) {
|
||||
data.pagination.page = Number(vnode.attrs.page);
|
||||
notes_get();
|
||||
}
|
||||
};
|
||||
},
|
||||
view: function(vnode) {
|
||||
console.log('Notes.view');
|
||||
result = [];
|
||||
result.push([
|
||||
breadcrumbs_render(),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2 h1'}, [
|
||||
m(m.route.Link, {class: 'btn btn-outline-success btn-lg float-end', href: '/note/add'}, m('i', {class: 'fa fa-plus'})),
|
||||
m('div', {class: "btn-group btn-group-lg me-2"}, [
|
||||
m('button', {type: "button", class: "btn btn-outline-secondary", onclick: function() { panel_show(data.filter.data) }},
|
||||
m('i', {class: "fa fa-filter"})
|
||||
),
|
||||
m('button', {type: "button", class: "btn btn-outline-secondary", onclick: function() { panel_show(data.order_by.data) }},
|
||||
m('i', {class: "fa fa-sort-alpha-asc"})
|
||||
)
|
||||
]),
|
||||
'Мои заметки',
|
||||
])
|
||||
),
|
||||
m('hr')
|
||||
]);
|
||||
result.push(m(data.filter));
|
||||
result.push(m(data.order_by));
|
||||
result.push(m(Pagination, data.pagination));
|
||||
if (data.notes.length>0) {
|
||||
result.push(m(ComponentNotes, {notes: data.notes}));
|
||||
result.push(m(Pagination, data.pagination));
|
||||
};
|
||||
result.push(breadcrumbs_render());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
130
src/myapp/templates/private/domains/note/source.js
Normal file
130
src/myapp/templates/private/domains/note/source.js
Normal file
@@ -0,0 +1,130 @@
|
||||
function NoteSource() {
|
||||
let data = {
|
||||
note: null,
|
||||
editor: null,
|
||||
}
|
||||
function note_get(id) {
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note',
|
||||
"params": {
|
||||
"id": id
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
data.note = response['result'];
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
function note_update() {
|
||||
data.note.body = data.editor.getValue();
|
||||
m.request({
|
||||
url: '/api',
|
||||
method: "POST",
|
||||
body: {
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'note.update',
|
||||
"params": {
|
||||
"id": data.note.id,
|
||||
"title": data.note.title,
|
||||
"body": data.note.body
|
||||
},
|
||||
"id": get_id()
|
||||
}
|
||||
}).then(
|
||||
function(response) {
|
||||
if ('result' in response) {
|
||||
m.route.set(`/note/${response['result'].id}`);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
function editor_init() {
|
||||
let vCode = document.getElementById('note_body');
|
||||
const {EditorState} = CM["@codemirror/state"];
|
||||
const {basicSetup, EditorView} = CM["codemirror"];
|
||||
|
||||
data.editor = {};
|
||||
data.editor.state = EditorState.create({
|
||||
doc: data.note.body,
|
||||
extensions: [
|
||||
basicSetup,
|
||||
EditorView.updateListener.of(function(e) {
|
||||
data.note.body = e.state.doc.toString();
|
||||
}),
|
||||
EditorView.lineWrapping
|
||||
]
|
||||
});
|
||||
data.editor.view = new EditorView({
|
||||
lineWrapping: true,
|
||||
state: data.editor.state,
|
||||
parent: vCode
|
||||
});
|
||||
};
|
||||
function editor_remove() {
|
||||
data.editor = null;
|
||||
}
|
||||
return {
|
||||
oncreate(vnode) {
|
||||
console.log(vnode);
|
||||
if (data.note!=null) {
|
||||
console.log(data.note);
|
||||
editor_init();
|
||||
}
|
||||
},
|
||||
onbeforeremove: function(vnode) {
|
||||
editor_remove();
|
||||
},
|
||||
oninit: function(vnode) {
|
||||
document.title = `Исходный код статьи - ${SETTINGS.TITLE}`;
|
||||
note_get(vnode.attrs.id);
|
||||
},
|
||||
onupdate(vnode) {
|
||||
console.log(vnode);
|
||||
if (data.note!=null) {
|
||||
if (data.editor==null) {
|
||||
console.log(data.note);
|
||||
editor_init();
|
||||
}
|
||||
}
|
||||
},
|
||||
view: function(vnode) {
|
||||
let result = [];
|
||||
if (data.note!=null) {
|
||||
result.push([
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col h1 py-1'}, [
|
||||
m(m.route.Link, {class: "btn btn-outline-secondary btn-lg", href: `/note/${data.note.id}`, title: data.note.title}, m('i', {class: 'fa fa-chevron-left'})),
|
||||
{tag: '<', children: ' '},
|
||||
'Исходный код статьи',
|
||||
m('hr'),
|
||||
])
|
||||
),
|
||||
m(NoteMenu, {menuitem: 'source', note: data.note}),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2'}, [
|
||||
m('button', {class: 'btn btn-outline-success float-end', type: 'button', onclick: note_update}, [m('i', {class: 'fa fa-save'}), ' Сохранить']),
|
||||
])
|
||||
),
|
||||
m('div', {class: 'form-group mb-2'}, [
|
||||
m('label', {class: 'form-label'}, 'Текст'),
|
||||
m('div', {id: 'note_body', oninput: function (e) {data.note.body = e.target.value}, value: data.note.body})
|
||||
]),
|
||||
m('div', {class: 'row'},
|
||||
m('div', {class: 'col py-2'}, [
|
||||
m('button', {class: 'btn btn-outline-success float-end', type: 'button', onclick: note_update}, [m('i', {class: 'fa fa-save'}), ' Сохранить']),
|
||||
])
|
||||
),
|
||||
]);
|
||||
};
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user