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 .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.common import Note # noqa F401
|
||||||
from .note.favorite import NoteFavorite # noqa F401
|
from .note.favorite import NoteFavorite # noqa F401
|
||||||
from .note.tag import NoteTag # 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)
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,21 @@ class Note(Base):
|
|||||||
uselist=False
|
uselist=False
|
||||||
)
|
)
|
||||||
tags = relationship("NoteTag", primaryjoin="Note.id==NoteTag.note_id")
|
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):
|
def __init__(self, user, title):
|
||||||
assert type(user).__name__ == 'User', 'Не передан объект User'
|
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
|
uselist=False
|
||||||
)
|
)
|
||||||
tags = relationship("PageTag", primaryjoin="Page.id==PageTag.page_id")
|
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):
|
def __init__(self, user, title):
|
||||||
assert type(user).__name__ == 'User', 'Не передан объект User'
|
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
|
from . import ( # noqa F401
|
||||||
login,
|
login,
|
||||||
|
favorite,
|
||||||
note,
|
note,
|
||||||
profile,
|
profile,
|
||||||
page,
|
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 jsonrpc, login_required
|
||||||
from .. import app, lib, models
|
from .. import app, lib, models
|
||||||
|
from ..mutations.note import note_as_dict
|
||||||
|
|
||||||
|
|
||||||
@jsonrpc.method('note')
|
@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(
|
note = models.db_session.query(
|
||||||
@@ -17,11 +21,7 @@ def note_id(id: int) -> dict:
|
|||||||
if note is None:
|
if note is None:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
result = note.as_dict()
|
result = note_as_dict(note, fields)
|
||||||
result['user'] = note.user.as_dict()
|
|
||||||
result['tags'] = []
|
|
||||||
for tagLink in note.tags:
|
|
||||||
result['tags'].append(tagLink.tag.as_dict())
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -91,14 +91,30 @@ def note_update(id: int, title: str, body: str) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@jsonrpc.method('notes')
|
@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(
|
notes = models.db_session.query(
|
||||||
models.Note
|
models.Note
|
||||||
).order_by(
|
).filter(
|
||||||
models.Note.title.asc()
|
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 = lib.getpage(
|
||||||
notes,
|
notes,
|
||||||
page,
|
page,
|
||||||
@@ -107,11 +123,7 @@ def notes_list(page: int) -> list:
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
for note in notes:
|
for note in notes:
|
||||||
newRow = note.as_dict()
|
newRow = note_as_dict(note, fields)
|
||||||
newRow['user'] = note.user.as_dict()
|
|
||||||
newRow['tags'] = []
|
|
||||||
for tagLink in note.tags:
|
|
||||||
newRow['tags'].append(tagLink.tag.as_dict())
|
|
||||||
result.append(newRow)
|
result.append(newRow)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{% include '/components/backtotop.js' %}
|
{% include '/components/backtotop.js' %}
|
||||||
{% include '/components/filter.js' %}
|
{% include '/components/filter.js' %}
|
||||||
{% include '/components/footer.js' %}
|
{% include '/components/footer.js' %}
|
||||||
|
{% include '/components/info.js' %}
|
||||||
{% include '/components/order_by.js' %}
|
{% include '/components/order_by.js' %}
|
||||||
{% include '/components/pagination.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/favorite.js' %}
|
||||||
{% include '/private/components/menu-general.js' %}
|
{% include '/private/components/menu-general.js' %}
|
||||||
|
{% include '/private/components/notes.js' %}
|
||||||
{% include '/private/components/pages.js' %}
|
{% include '/private/components/pages.js' %}
|
||||||
|
{% include '/private/components/tags.js' %}
|
||||||
{% include '/private/components/users.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: '/tags'}, 'Метки'),
|
||||||
m(m.route.Link, { class: 'btn btn-outline-secondary btn-lg border-0', href: '/users'}, 'Пользователи'),
|
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('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(m.route.Link, { class: 'btn btn-outline-primary', href: '/notes' }, m('i', { class: 'fa fa-sticky-note-o' })),
|
||||||
m('button', {class: "btn btn-outline-danger", onclick: logout}, m('i', {class: "fa fa-sign-out"})),
|
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/page/inc.j2' %}
|
||||||
{% include '/private/domains/profile/inc.j2' %}
|
{% include '/private/domains/profile/inc.j2' %}
|
||||||
{% include '/private/domains/tag/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