Integrate the Cabinets app into the core.
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
@@ -15,6 +15,7 @@ the user links
|
||||
- Tags are alphabetically ordered by label (GitLab #342).
|
||||
- Stop loading theme fonts from the web (GitLab #343).
|
||||
- Add support for attaching multiple tags (GitLab #307).
|
||||
- Integrate the Cabinets app.
|
||||
|
||||
2.1.10 (2017-02-13)
|
||||
===================
|
||||
|
||||
@@ -61,6 +61,7 @@ resolved to '/api/documents/<pk>/pages/<page_pk>/pages'.
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- The Cabinets app integration.
|
||||
- Add "Check now" button to interval sources.
|
||||
- Remove the installation app
|
||||
- Add support for page search
|
||||
|
||||
3
mayan/apps/cabinets/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
default_app_config = 'cabinets.apps.CabinetsApp'
|
||||
13
mayan/apps/cabinets/admin.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Cabinet
|
||||
|
||||
from mptt.admin import MPTTModelAdmin
|
||||
|
||||
|
||||
@admin.register(Cabinet)
|
||||
class CabinetAdmin(MPTTModelAdmin):
|
||||
filter_horizontal = ('documents',)
|
||||
list_display = ('label',)
|
||||
220
mayan/apps/cabinets/api_views.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from documents.models import Document
|
||||
from documents.permissions import permission_document_view
|
||||
from rest_api.filters import MayanObjectPermissionsFilter
|
||||
from rest_api.permissions import MayanPermission
|
||||
|
||||
from .models import Cabinet
|
||||
from .permissions import (
|
||||
permission_cabinet_add_document, permission_cabinet_create,
|
||||
permission_cabinet_delete, permission_cabinet_edit,
|
||||
permission_cabinet_remove_document, permission_cabinet_view
|
||||
)
|
||||
from .serializers import (
|
||||
CabinetDocumentSerializer, CabinetSerializer, NewCabinetDocumentSerializer,
|
||||
WritableCabinetSerializer
|
||||
)
|
||||
|
||||
|
||||
class APIDocumentCabinetListView(generics.ListAPIView):
|
||||
"""
|
||||
Returns a list of all the cabinets to which a document belongs.
|
||||
"""
|
||||
|
||||
serializer_class = CabinetSerializer
|
||||
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {'GET': (permission_cabinet_view,)}
|
||||
|
||||
def get_queryset(self):
|
||||
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=permission_document_view, user=self.request.user,
|
||||
obj=document
|
||||
)
|
||||
|
||||
queryset = document.document_cabinets().all()
|
||||
return queryset
|
||||
|
||||
|
||||
class APICabinetListView(generics.ListCreateAPIView):
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {'GET': (permission_cabinet_view,)}
|
||||
mayan_view_permissions = {'POST': (permission_cabinet_create,)}
|
||||
permission_classes = (MayanPermission,)
|
||||
queryset = Cabinet.objects.all()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return CabinetSerializer
|
||||
elif self.request.method == 'POST':
|
||||
return WritableCabinetSerializer
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Returns a list of all the cabinets.
|
||||
"""
|
||||
return super(APICabinetListView, self).get(*args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
"""
|
||||
Create a new cabinet.
|
||||
"""
|
||||
return super(APICabinetListView, self).post(*args, **kwargs)
|
||||
|
||||
|
||||
class APICabinetView(generics.RetrieveUpdateDestroyAPIView):
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {
|
||||
'GET': (permission_cabinet_view,),
|
||||
'PUT': (permission_cabinet_edit,),
|
||||
'PATCH': (permission_cabinet_edit,),
|
||||
'DELETE': (permission_cabinet_delete,)
|
||||
}
|
||||
permission_classes = (MayanPermission,)
|
||||
queryset = Cabinet.objects.all()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Delete the selected cabinet.
|
||||
"""
|
||||
return super(APICabinetView, self).delete(*args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Returns the details of the selected cabinet.
|
||||
"""
|
||||
return super(APICabinetView, self).get(*args, **kwargs)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return CabinetSerializer
|
||||
else:
|
||||
return WritableCabinetSerializer
|
||||
|
||||
def patch(self, *args, **kwargs):
|
||||
"""
|
||||
Edit the selected cabinet.
|
||||
"""
|
||||
return super(APICabinetView, self).patch(*args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
"""
|
||||
Edit the selected cabinet.
|
||||
"""
|
||||
return super(APICabinetView, self).put(*args, **kwargs)
|
||||
|
||||
|
||||
class APICabinetDocumentListView(generics.ListCreateAPIView):
|
||||
"""
|
||||
Returns a list of all the documents contained in a particular cabinet.
|
||||
"""
|
||||
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {
|
||||
'GET': (permission_cabinet_view,),
|
||||
'POST': (permission_cabinet_add_document,)
|
||||
}
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return CabinetDocumentSerializer
|
||||
elif self.request.method == 'POST':
|
||||
return NewCabinetDocumentSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""
|
||||
Extra context provided to the serializer class.
|
||||
"""
|
||||
return {
|
||||
'cabinet': self.get_cabinet(),
|
||||
'format': self.format_kwarg,
|
||||
'request': self.request,
|
||||
'view': self
|
||||
}
|
||||
|
||||
def get_cabinet(self):
|
||||
return get_object_or_404(Cabinet, pk=self.kwargs['pk'])
|
||||
|
||||
def get_queryset(self):
|
||||
cabinet = self.get_cabinet()
|
||||
|
||||
return AccessControlList.objects.filter_by_access(
|
||||
permission_document_view, self.request.user,
|
||||
queryset=cabinet.documents.all()
|
||||
)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(cabinet=self.get_cabinet())
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Add a document to the selected cabinet.
|
||||
"""
|
||||
return super(APICabinetDocumentListView, self).post(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class APICabinetDocumentView(generics.RetrieveDestroyAPIView):
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
lookup_url_kwarg = 'document_pk'
|
||||
mayan_object_permissions = {
|
||||
'GET': (permission_cabinet_view,),
|
||||
'DELETE': (permission_cabinet_remove_document,)
|
||||
}
|
||||
serializer_class = CabinetDocumentSerializer
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""
|
||||
Remove a document from the selected cabinet.
|
||||
"""
|
||||
|
||||
return super(APICabinetDocumentView, self).delete(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Returns the details of the selected cabinet document.
|
||||
"""
|
||||
|
||||
return super(APICabinetDocumentView, self).get(*args, **kwargs)
|
||||
|
||||
def get_cabinet(self):
|
||||
return get_object_or_404(Cabinet, pk=self.kwargs['pk'])
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_cabinet().documents.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""
|
||||
Extra context provided to the serializer class.
|
||||
"""
|
||||
return {
|
||||
'cabinet': self.get_cabinet(),
|
||||
'format': self.format_kwarg,
|
||||
'request': self.request,
|
||||
'view': self
|
||||
}
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
self.get_cabinet().documents.remove(instance)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=permission_document_view, user=self.request.user,
|
||||
obj=instance
|
||||
)
|
||||
|
||||
serializer = self.get_serializer(instance)
|
||||
return Response(serializer.data)
|
||||
106
mayan/apps/cabinets/apps.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from acls import ModelPermission
|
||||
from acls.permissions import permission_acl_edit, permission_acl_view
|
||||
from common import (
|
||||
MayanAppConfig, menu_facet, menu_main, menu_multi_item, menu_object,
|
||||
menu_sidebar, menu_secondary
|
||||
)
|
||||
from rest_api.classes import APIEndPoint
|
||||
|
||||
from .links import (
|
||||
link_cabinet_list, link_document_cabinet_list,
|
||||
link_document_cabinet_remove, link_cabinet_add_document,
|
||||
link_cabinet_add_multiple_documents, link_cabinet_child_add,
|
||||
link_cabinet_create, link_cabinet_delete, link_cabinet_edit,
|
||||
link_cabinet_view, link_custom_acl_list,
|
||||
link_multiple_document_cabinet_remove
|
||||
)
|
||||
from .menus import menu_cabinets
|
||||
from .permissions import (
|
||||
permission_cabinet_add_document, permission_cabinet_delete,
|
||||
permission_cabinet_edit, permission_cabinet_remove_document,
|
||||
permission_cabinet_view
|
||||
)
|
||||
|
||||
|
||||
class CabinetsApp(MayanAppConfig):
|
||||
name = 'cabinets'
|
||||
test = True
|
||||
verbose_name = _('Cabinets')
|
||||
|
||||
def ready(self):
|
||||
super(CabinetsApp, self).ready()
|
||||
|
||||
Document = apps.get_model(
|
||||
app_label='documents', model_name='Document'
|
||||
)
|
||||
|
||||
DocumentCabinet = self.get_model('DocumentCabinet')
|
||||
Cabinet = self.get_model('Cabinet')
|
||||
|
||||
APIEndPoint(app=self, version_string='1')
|
||||
|
||||
Document.add_to_class(
|
||||
'document_cabinets',
|
||||
lambda document: DocumentCabinet.objects.filter(documents=document)
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=Document, permissions=(
|
||||
permission_cabinet_add_document,
|
||||
permission_cabinet_remove_document
|
||||
)
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=Cabinet, permissions=(
|
||||
permission_acl_edit, permission_acl_view,
|
||||
permission_cabinet_delete, permission_cabinet_edit,
|
||||
permission_cabinet_view
|
||||
)
|
||||
)
|
||||
|
||||
menu_facet.bind_links(
|
||||
links=(link_document_cabinet_list,), sources=(Document,)
|
||||
)
|
||||
|
||||
menu_cabinets.bind_links(
|
||||
links=(
|
||||
link_cabinet_list, link_cabinet_create
|
||||
)
|
||||
)
|
||||
|
||||
menu_main.bind_links(links=(menu_cabinets,), position=98)
|
||||
|
||||
menu_multi_item.bind_links(
|
||||
links=(
|
||||
link_cabinet_add_multiple_documents,
|
||||
link_multiple_document_cabinet_remove
|
||||
), sources=(Document,)
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_cabinet_view,
|
||||
), sources=(DocumentCabinet, )
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_cabinet_view, link_cabinet_edit,
|
||||
link_custom_acl_list, link_cabinet_delete
|
||||
), sources=(Cabinet,)
|
||||
)
|
||||
menu_sidebar.bind_links(
|
||||
links=(link_cabinet_child_add,), sources=(Cabinet,)
|
||||
)
|
||||
menu_sidebar.bind_links(
|
||||
links=(link_cabinet_add_document, link_document_cabinet_remove),
|
||||
sources=(
|
||||
'cabinets:document_cabinet_list',
|
||||
'cabinets:cabinet_add_document',
|
||||
'cabinets:document_cabinet_remove'
|
||||
)
|
||||
)
|
||||
33
mayan/apps/cabinets/forms.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from acls.models import AccessControlList
|
||||
|
||||
from .models import Cabinet
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CabinetListForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
help_text = kwargs.pop('help_text', None)
|
||||
permission = kwargs.pop('permission', None)
|
||||
queryset = kwargs.pop('queryset', Cabinet.objects.all())
|
||||
user = kwargs.pop('user', None)
|
||||
|
||||
logger.debug('user: %s', user)
|
||||
super(CabinetListForm, self).__init__(*args, **kwargs)
|
||||
|
||||
queryset = AccessControlList.objects.filter_by_access(
|
||||
permission=permission, user=user, queryset=queryset
|
||||
)
|
||||
|
||||
self.fields['cabinets'] = forms.ModelMultipleChoiceField(
|
||||
label=_('Cabinets'), help_text=help_text,
|
||||
queryset=queryset, required=False,
|
||||
widget=forms.SelectMultiple(attrs={'class': 'select2'})
|
||||
)
|
||||
76
mayan/apps/cabinets/links.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import copy
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from acls.links import link_acl_list
|
||||
from documents.permissions import permission_document_view
|
||||
from navigation import Link
|
||||
|
||||
from .permissions import (
|
||||
permission_cabinet_add_document, permission_cabinet_create,
|
||||
permission_cabinet_delete, permission_cabinet_edit,
|
||||
permission_cabinet_view, permission_cabinet_remove_document
|
||||
)
|
||||
|
||||
# Document links
|
||||
|
||||
link_document_cabinet_list = Link(
|
||||
icon='fa fa-columns', permissions=(permission_document_view,),
|
||||
text=_('Cabinets'), view='cabinets:document_cabinet_list',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_document_cabinet_remove = Link(
|
||||
args='resolved_object.pk',
|
||||
permissions=(permission_cabinet_remove_document,),
|
||||
text=_('Remove from cabinets'), view='cabinets:document_cabinet_remove'
|
||||
)
|
||||
link_cabinet_add_document = Link(
|
||||
permissions=(permission_cabinet_add_document,),
|
||||
text=_('Add to a cabinets'), view='cabinets:cabinet_add_document',
|
||||
args='object.pk'
|
||||
)
|
||||
link_cabinet_add_multiple_documents = Link(
|
||||
text=_('Add to cabinets'), view='cabinets:cabinet_add_multiple_documents'
|
||||
)
|
||||
link_multiple_document_cabinet_remove = Link(
|
||||
text=_('Remove from cabinets'),
|
||||
view='cabinets:multiple_document_cabinet_remove'
|
||||
)
|
||||
|
||||
# Cabinet links
|
||||
|
||||
|
||||
def cabinet_is_root(context):
|
||||
return context[
|
||||
'resolved_object'
|
||||
].is_root_node()
|
||||
|
||||
|
||||
link_custom_acl_list = copy.copy(link_acl_list)
|
||||
link_custom_acl_list.condition = cabinet_is_root
|
||||
|
||||
link_cabinet_child_add = Link(
|
||||
permissions=(permission_cabinet_create,), text=_('Add new level'),
|
||||
view='cabinets:cabinet_child_add', args='object.pk'
|
||||
)
|
||||
link_cabinet_create = Link(
|
||||
icon='fa fa-plus', permissions=(permission_cabinet_create,),
|
||||
text=_('Create cabinet'), view='cabinets:cabinet_create'
|
||||
)
|
||||
link_cabinet_delete = Link(
|
||||
permissions=(permission_cabinet_delete,), tags='dangerous',
|
||||
text=_('Delete'), view='cabinets:cabinet_delete', args='object.pk'
|
||||
)
|
||||
link_cabinet_edit = Link(
|
||||
permissions=(permission_cabinet_edit,), text=_('Edit'),
|
||||
view='cabinets:cabinet_edit', args='object.pk'
|
||||
)
|
||||
link_cabinet_list = Link(
|
||||
icon='fa fa-columns', text=_('All'), view='cabinets:cabinet_list'
|
||||
)
|
||||
link_cabinet_view = Link(
|
||||
permissions=(permission_cabinet_view,), text=_('Details'),
|
||||
view='cabinets:cabinet_view', args='object.pk'
|
||||
)
|
||||
9
mayan/apps/cabinets/menus.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from navigation import Menu
|
||||
|
||||
menu_cabinets = Menu(
|
||||
icon='fa fa-columns', label=_('Cabinets'), name='cabinets menu'
|
||||
)
|
||||
88
mayan/apps/cabinets/migrations/0001_initial.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-01-24 07:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('documents', '0034_auto_20160509_2321'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Cabinet',
|
||||
fields=[
|
||||
(
|
||||
'id', models.AutoField(
|
||||
auto_created=True, primary_key=True, serialize=False,
|
||||
verbose_name='ID'
|
||||
)
|
||||
),
|
||||
(
|
||||
'label', models.CharField(
|
||||
max_length=128, verbose_name='Label'
|
||||
)
|
||||
),
|
||||
(
|
||||
'lft', models.PositiveIntegerField(
|
||||
db_index=True, editable=False
|
||||
)
|
||||
),
|
||||
(
|
||||
'rght', models.PositiveIntegerField(
|
||||
db_index=True, editable=False
|
||||
)
|
||||
),
|
||||
(
|
||||
'tree_id', models.PositiveIntegerField(
|
||||
db_index=True, editable=False
|
||||
)
|
||||
),
|
||||
(
|
||||
'level', models.PositiveIntegerField(
|
||||
db_index=True, editable=False
|
||||
)
|
||||
),
|
||||
(
|
||||
'documents', models.ManyToManyField(
|
||||
blank=True, related_name='cabinets',
|
||||
to='documents.Document', verbose_name='Documents'
|
||||
)
|
||||
),
|
||||
(
|
||||
'parent', mptt.fields.TreeForeignKey(
|
||||
blank=True, null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='children', to='cabinets.Cabinet'
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ('parent__label', 'label'),
|
||||
'verbose_name': 'Cabinet',
|
||||
'verbose_name_plural': 'Cabinets',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DocumentCabinet',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Document cabinet',
|
||||
'proxy': True,
|
||||
'verbose_name_plural': 'Document cabinets',
|
||||
},
|
||||
bases=('cabinets.cabinet',),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='cabinet',
|
||||
unique_together=set([('parent', 'label')]),
|
||||
),
|
||||
]
|
||||
0
mayan/apps/cabinets/migrations/__init__.py
Normal file
87
mayan/apps/cabinets/models.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models, transaction
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mptt.fields import TreeForeignKey
|
||||
from mptt.models import MPTTModel
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from documents.models import Document
|
||||
from documents.permissions import permission_document_view
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Cabinet(MPTTModel):
|
||||
parent = TreeForeignKey(
|
||||
'self', blank=True, db_index=True, null=True, related_name='children'
|
||||
)
|
||||
label = models.CharField(max_length=128, verbose_name=_('Label'))
|
||||
documents = models.ManyToManyField(
|
||||
Document, blank=True, related_name='cabinets',
|
||||
verbose_name=_('Documents')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('parent__label', 'label')
|
||||
# unique_together doesn't work if there is a FK
|
||||
# https://code.djangoproject.com/ticket/1751
|
||||
unique_together = ('parent', 'label')
|
||||
verbose_name = _('Cabinet')
|
||||
verbose_name_plural = _('Cabinets')
|
||||
|
||||
def __str__(self):
|
||||
return self.get_full_path()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('cabinets:cabinet_view', args=(self.pk,))
|
||||
|
||||
def get_document_count(self, user):
|
||||
return self.get_documents_queryset(user=user).count()
|
||||
|
||||
def get_documents_queryset(self, user):
|
||||
return AccessControlList.objects.filter_by_access(
|
||||
permission_document_view, user, queryset=self.documents
|
||||
)
|
||||
|
||||
def get_full_path(self):
|
||||
result = []
|
||||
for node in self.get_ancestors(include_self=True):
|
||||
result.append(node.label)
|
||||
|
||||
return ' / '.join(result)
|
||||
|
||||
def validate_unique(self, exclude=None):
|
||||
# Explicit validation of uniqueness of parent+label as the provided
|
||||
# unique_together check in Meta is not working for all 100% cases
|
||||
# when there is a FK in the unique_together tuple
|
||||
# https://code.djangoproject.com/ticket/1751
|
||||
|
||||
with transaction.atomic():
|
||||
if Cabinet.objects.select_for_update().filter(parent=self.parent, label=self.label).exists():
|
||||
params = {
|
||||
'model_name': _('Cabinet'),
|
||||
'field_labels': _('Parent and Label')
|
||||
}
|
||||
raise ValidationError(
|
||||
{
|
||||
NON_FIELD_ERRORS: [
|
||||
ValidationError(
|
||||
message=_(
|
||||
'%(model_name)s with this %(field_labels)s already '
|
||||
'exists.'
|
||||
), code='unique_together', params=params,
|
||||
)
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DocumentCabinet(Cabinet):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = _('Document cabinet')
|
||||
verbose_name_plural = _('Document cabinets')
|
||||
28
mayan/apps/cabinets/permissions.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('cabinets', _('Cabinets'))
|
||||
|
||||
# Translators: this refers to the permission that will allow users to add
|
||||
# documents to cabinets.
|
||||
permission_cabinet_add_document = namespace.add_permission(
|
||||
name='cabinet_add_document', label=_('Add documents to cabinets')
|
||||
)
|
||||
permission_cabinet_create = namespace.add_permission(
|
||||
name='cabinet_create', label=_('Create cabinets')
|
||||
)
|
||||
permission_cabinet_delete = namespace.add_permission(
|
||||
name='cabinet_delete', label=_('Delete cabinets')
|
||||
)
|
||||
permission_cabinet_edit = namespace.add_permission(
|
||||
name='cabinet_edit', label=_('Edit cabinets')
|
||||
)
|
||||
permission_cabinet_remove_document = namespace.add_permission(
|
||||
name='cabinet_remove_document', label=_('Remove documents from cabinets')
|
||||
)
|
||||
permission_cabinet_view = namespace.add_permission(
|
||||
name='cabinet_view', label=_('View cabinets')
|
||||
)
|
||||
194
mayan/apps/cabinets/serializers.py
Normal file
@@ -0,0 +1,194 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
from rest_framework_recursive.fields import RecursiveField
|
||||
|
||||
from documents.models import Document
|
||||
from documents.serializers import DocumentSerializer
|
||||
|
||||
from .models import Cabinet
|
||||
|
||||
|
||||
class CabinetSerializer(serializers.ModelSerializer):
|
||||
children = RecursiveField(
|
||||
help_text=_('List of children cabinets.'), many=True, read_only=True
|
||||
)
|
||||
documents_count = serializers.SerializerMethodField(
|
||||
help_text=_('Number of documents on this cabinet level.')
|
||||
)
|
||||
full_path = serializers.SerializerMethodField(
|
||||
help_text=_(
|
||||
'The name of this cabinet level appended to the names of its '
|
||||
'ancestors.'
|
||||
)
|
||||
)
|
||||
documents_url = serializers.HyperlinkedIdentityField(
|
||||
help_text=_(
|
||||
'URL of the API endpoint showing the list documents inside this '
|
||||
'cabinet.'
|
||||
), view_name='rest_api:cabinet-document-list'
|
||||
)
|
||||
parent_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
extra_kwargs = {
|
||||
'url': {'view_name': 'rest_api:cabinet-detail'},
|
||||
}
|
||||
fields = (
|
||||
'children', 'documents_count', 'documents_url', 'full_path', 'id',
|
||||
'label', 'parent', 'parent_url', 'url'
|
||||
)
|
||||
model = Cabinet
|
||||
|
||||
def get_documents_count(self, obj):
|
||||
return obj.get_document_count(user=self.context['request'].user)
|
||||
|
||||
def get_full_path(self, obj):
|
||||
return obj.get_full_path()
|
||||
|
||||
def get_parent_url(self, obj):
|
||||
if obj.parent:
|
||||
return reverse(
|
||||
'rest_api:cabinet-detail', args=(obj.parent.pk,),
|
||||
format=self.context['format'],
|
||||
request=self.context.get('request')
|
||||
)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class WritableCabinetSerializer(serializers.ModelSerializer):
|
||||
documents_pk_list = serializers.CharField(
|
||||
help_text=_(
|
||||
'Comma separated list of document primary keys to add to this '
|
||||
'cabinet.'
|
||||
), required=False
|
||||
)
|
||||
|
||||
# This is here because parent is optional in the model but the serializer
|
||||
# sets it as required.
|
||||
parent = serializers.PrimaryKeyRelatedField(
|
||||
allow_null=True, queryset=Cabinet.objects.all(), required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ('documents_pk_list', 'label', 'id', 'parent')
|
||||
model = Cabinet
|
||||
|
||||
def _add_documents(self, documents_pk_list, instance):
|
||||
instance.documents.add(
|
||||
*Document.objects.filter(pk__in=documents_pk_list.split(','))
|
||||
)
|
||||
|
||||
def create(self, validated_data):
|
||||
documents_pk_list = validated_data.pop('documents_pk_list', '')
|
||||
|
||||
instance = super(WritableCabinetSerializer, self).create(validated_data)
|
||||
|
||||
if documents_pk_list:
|
||||
self._add_documents(
|
||||
documents_pk_list=documents_pk_list, instance=instance
|
||||
)
|
||||
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
documents_pk_list = validated_data.pop('documents_pk_list', '')
|
||||
|
||||
instance = super(WritableCabinetSerializer, self).update(
|
||||
instance, validated_data
|
||||
)
|
||||
|
||||
if documents_pk_list:
|
||||
instance.documents.clear()
|
||||
self._add_documents(
|
||||
documents_pk_list=documents_pk_list, instance=instance
|
||||
)
|
||||
|
||||
return instance
|
||||
|
||||
def run_validation(self, data=None):
|
||||
# Copy data into a new dictionary since data is an immutable type
|
||||
result = data.copy()
|
||||
|
||||
# Add None parent to keep validation from failing.
|
||||
# This is here because parent is optional in the model but the serializer
|
||||
# sets it as required.
|
||||
result.setdefault('parent')
|
||||
|
||||
data = super(WritableCabinetSerializer, self).run_validation(result)
|
||||
|
||||
# Explicit validation of uniqueness of parent+label as the provided
|
||||
# unique_together check in Meta is not working for all 100% cases
|
||||
# when there is a FK in the unique_together tuple
|
||||
# https://code.djangoproject.com/ticket/1751
|
||||
with transaction.atomic():
|
||||
if Cabinet.objects.select_for_update().filter(parent=data['parent'], label=data['label']).exists():
|
||||
params = {
|
||||
'model_name': _('Cabinet'),
|
||||
'field_labels': _('Parent and Label')
|
||||
}
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [
|
||||
_(
|
||||
'%(model_name)s with this %(field_labels)s '
|
||||
'already exists.'
|
||||
) % params
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class CabinetDocumentSerializer(DocumentSerializer):
|
||||
cabinet_document_url = serializers.SerializerMethodField(
|
||||
help_text=_(
|
||||
'API URL pointing to a document in relation to the cabinet '
|
||||
'storing it. This URL is different than the canonical document '
|
||||
'URL.'
|
||||
)
|
||||
)
|
||||
|
||||
class Meta(DocumentSerializer.Meta):
|
||||
fields = DocumentSerializer.Meta.fields + ('cabinet_document_url',)
|
||||
read_only_fields = DocumentSerializer.Meta.fields
|
||||
|
||||
def get_cabinet_document_url(self, instance):
|
||||
return reverse(
|
||||
'rest_api:cabinet-document', args=(
|
||||
self.context['cabinet'].pk, instance.pk
|
||||
), request=self.context['request'], format=self.context['format']
|
||||
)
|
||||
|
||||
|
||||
class NewCabinetDocumentSerializer(serializers.Serializer):
|
||||
documents_pk_list = serializers.CharField(
|
||||
help_text=_(
|
||||
'Comma separated list of document primary keys to add to this '
|
||||
'cabinet.'
|
||||
)
|
||||
)
|
||||
|
||||
def _add_documents(self, documents_pk_list, instance):
|
||||
instance.documents.add(
|
||||
*Document.objects.filter(pk__in=documents_pk_list.split(','))
|
||||
)
|
||||
|
||||
def create(self, validated_data):
|
||||
documents_pk_list = validated_data['documents_pk_list']
|
||||
|
||||
if documents_pk_list:
|
||||
self._add_documents(
|
||||
documents_pk_list=documents_pk_list,
|
||||
instance=validated_data['cabinet']
|
||||
)
|
||||
|
||||
return {'documents_pk_list': documents_pk_list}
|
||||
13
mayan/apps/cabinets/static/cabinets/packages/jstree/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/debug
|
||||
/jstree.sublime-project
|
||||
/jstree.sublime-workspace
|
||||
/bower_components
|
||||
/node_modules
|
||||
/site
|
||||
/nuget
|
||||
/demo/filebrowser/data/root
|
||||
/npm.txt
|
||||
/libs
|
||||
/docs
|
||||
/dist/libs
|
||||
/.vscode
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2014 Ivan Bozhanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
658
mayan/apps/cabinets/static/cabinets/packages/jstree/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "jstree",
|
||||
"version": "3.3.3",
|
||||
"main" : [
|
||||
"./dist/jstree.js",
|
||||
"./dist/themes/default/style.css"
|
||||
],
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"docs",
|
||||
"demo",
|
||||
"libs",
|
||||
"node_modules",
|
||||
"test",
|
||||
"libs",
|
||||
"jstree.jquery.json",
|
||||
"gruntfile.js",
|
||||
"package.json",
|
||||
"bower.json",
|
||||
"component.json",
|
||||
"LICENCE-MIT",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": ">=1.9.1"
|
||||
},
|
||||
"keywords": [
|
||||
"ui",
|
||||
"tree",
|
||||
"jstree"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "jstree",
|
||||
"repo": "vakata/jstree",
|
||||
"description": "jsTree is jquery plugin, that provides interactive trees.",
|
||||
"version": "3.3.3",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"ui",
|
||||
"tree",
|
||||
"jstree"
|
||||
],
|
||||
"scripts": [
|
||||
"dist/jstree.js",
|
||||
"dist/jstree.min.js"
|
||||
],
|
||||
"images": [
|
||||
"dist/themes/default/32px.png",
|
||||
"dist/themes/default/40px.png",
|
||||
"dist/themes/default/throbber.gif"
|
||||
],
|
||||
"styles": [
|
||||
"dist/themes/default/style.css",
|
||||
"dist/themes/default/style.min.css"
|
||||
],
|
||||
"dependencies": {
|
||||
"components/jquery": ">=1.9.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "vakata/jstree",
|
||||
"description": "jsTree is jquery plugin, that provides interactive trees.",
|
||||
"type": "component",
|
||||
"homepage": "http://jstree.com",
|
||||
"license": "MIT",
|
||||
"support": {
|
||||
"issues": "https://github.com/vakata/jstree/issues",
|
||||
"forum": "https://groups.google.com/forum/#!forum/jstree",
|
||||
"source": "https://github.com/vakata/jstree"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ivan Bozhanov",
|
||||
"email": "jstree@jstree.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"components/jquery": ">=1.9.1"
|
||||
},
|
||||
"suggest": {
|
||||
"robloach/component-installer": "Allows installation of Components via Composer"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"scripts": [
|
||||
"dist/jstree.js"
|
||||
],
|
||||
"styles": [
|
||||
"dist/themes/default/style.css"
|
||||
],
|
||||
"images": [
|
||||
"dist/themes/default/32px.png",
|
||||
"dist/themes/default/40px.png",
|
||||
"dist/themes/default/throbber.gif"
|
||||
],
|
||||
"files": [
|
||||
"dist/jstree.min.js",
|
||||
"dist/themes/default/style.min.css",
|
||||
"dist/themes/default/32px.png",
|
||||
"dist/themes/default/40px.png",
|
||||
"dist/themes/default/throbber.gif"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>jstree basic demos</title>
|
||||
<style>
|
||||
html { margin:0; padding:0; font-size:62.5%; }
|
||||
body { max-width:800px; min-width:300px; margin:0 auto; padding:20px 10px; font-size:14px; font-size:1.4em; }
|
||||
h1 { font-size:1.8em; }
|
||||
.demo { overflow:auto; border:1px solid silver; min-height:100px; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="./../../dist/themes/default/style.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>HTML demo</h1>
|
||||
<div id="html" class="demo">
|
||||
<ul>
|
||||
<li data-jstree='{ "opened" : true }'>Root node
|
||||
<ul>
|
||||
<li data-jstree='{ "selected" : true }'>Child node 1</li>
|
||||
<li>Child node 2</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h1>Inline data demo</h1>
|
||||
<div id="data" class="demo"></div>
|
||||
|
||||
<h1>Data format demo</h1>
|
||||
<div id="frmt" class="demo"></div>
|
||||
|
||||
<h1>AJAX demo</h1>
|
||||
<div id="ajax" class="demo"></div>
|
||||
|
||||
<h1>Lazy loading demo</h1>
|
||||
<div id="lazy" class="demo"></div>
|
||||
|
||||
<h1>Callback function data demo</h1>
|
||||
<div id="clbk" class="demo"></div>
|
||||
|
||||
<h1>Interaction and events demo</h1>
|
||||
<button id="evts_button">select node with id 1</button> <em>either click the button or a node in the tree</em>
|
||||
<div id="evts" class="demo"></div>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
|
||||
<script src="./../../dist/jstree.min.js"></script>
|
||||
|
||||
<script>
|
||||
// html demo
|
||||
$('#html').jstree();
|
||||
|
||||
// inline data demo
|
||||
$('#data').jstree({
|
||||
'core' : {
|
||||
'data' : [
|
||||
{ "text" : "Root node", "children" : [
|
||||
{ "text" : "Child node 1" },
|
||||
{ "text" : "Child node 2" }
|
||||
]}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// data format demo
|
||||
$('#frmt').jstree({
|
||||
'core' : {
|
||||
'data' : [
|
||||
{
|
||||
"text" : "Root node",
|
||||
"state" : { "opened" : true },
|
||||
"children" : [
|
||||
{
|
||||
"text" : "Child node 1",
|
||||
"state" : { "selected" : true },
|
||||
"icon" : "jstree-file"
|
||||
},
|
||||
{ "text" : "Child node 2", "state" : { "disabled" : true } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// ajax demo
|
||||
$('#ajax').jstree({
|
||||
'core' : {
|
||||
'data' : {
|
||||
"url" : "./root.json",
|
||||
"dataType" : "json" // needed only if you do not supply JSON headers
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// lazy demo
|
||||
$('#lazy').jstree({
|
||||
'core' : {
|
||||
'data' : {
|
||||
"url" : "//www.jstree.com/fiddle/?lazy",
|
||||
"data" : function (node) {
|
||||
return { "id" : node.id };
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// data from callback
|
||||
$('#clbk').jstree({
|
||||
'core' : {
|
||||
'data' : function (node, cb) {
|
||||
if(node.id === "#") {
|
||||
cb([{"text" : "Root", "id" : "1", "children" : true}]);
|
||||
}
|
||||
else {
|
||||
cb(["Child"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// interaction and events
|
||||
$('#evts_button').on("click", function () {
|
||||
var instance = $('#evts').jstree(true);
|
||||
instance.deselect_all();
|
||||
instance.select_node('1');
|
||||
});
|
||||
$('#evts')
|
||||
.on("changed.jstree", function (e, data) {
|
||||
if(data.selected.length) {
|
||||
alert('The selected node is: ' + data.instance.get_node(data.selected[0]).text);
|
||||
}
|
||||
})
|
||||
.jstree({
|
||||
'core' : {
|
||||
'multiple' : false,
|
||||
'data' : [
|
||||
{ "text" : "Root node", "children" : [
|
||||
{ "text" : "Child node 1", "id" : 1 },
|
||||
{ "text" : "Child node 2" }
|
||||
]}
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
[{"id":1,"text":"Root node","children":[{"id":2,"text":"Child node 1"},{"id":3,"text":"Child node 2"}]}]
|
||||
@@ -0,0 +1 @@
|
||||
deny from all
|
||||
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
ini_set('open_basedir', dirname(__FILE__) . DIRECTORY_SEPARATOR);
|
||||
|
||||
class fs
|
||||
{
|
||||
protected $base = null;
|
||||
|
||||
protected function real($path) {
|
||||
$temp = realpath($path);
|
||||
if(!$temp) { throw new Exception('Path does not exist: ' . $path); }
|
||||
if($this->base && strlen($this->base)) {
|
||||
if(strpos($temp, $this->base) !== 0) { throw new Exception('Path is not inside base ('.$this->base.'): ' . $temp); }
|
||||
}
|
||||
return $temp;
|
||||
}
|
||||
protected function path($id) {
|
||||
$id = str_replace('/', DIRECTORY_SEPARATOR, $id);
|
||||
$id = trim($id, DIRECTORY_SEPARATOR);
|
||||
$id = $this->real($this->base . DIRECTORY_SEPARATOR . $id);
|
||||
return $id;
|
||||
}
|
||||
protected function id($path) {
|
||||
$path = $this->real($path);
|
||||
$path = substr($path, strlen($this->base));
|
||||
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
|
||||
$path = trim($path, '/');
|
||||
return strlen($path) ? $path : '/';
|
||||
}
|
||||
|
||||
public function __construct($base) {
|
||||
$this->base = $this->real($base);
|
||||
if(!$this->base) { throw new Exception('Base directory does not exist'); }
|
||||
}
|
||||
public function lst($id, $with_root = false) {
|
||||
$dir = $this->path($id);
|
||||
$lst = @scandir($dir);
|
||||
if(!$lst) { throw new Exception('Could not list path: ' . $dir); }
|
||||
$res = array();
|
||||
foreach($lst as $item) {
|
||||
if($item == '.' || $item == '..' || $item === null) { continue; }
|
||||
$tmp = preg_match('([^ a-zа-я-_0-9.]+)ui', $item);
|
||||
if($tmp === false || $tmp === 1) { continue; }
|
||||
if(is_dir($dir . DIRECTORY_SEPARATOR . $item)) {
|
||||
$res[] = array('text' => $item, 'children' => true, 'id' => $this->id($dir . DIRECTORY_SEPARATOR . $item), 'icon' => 'folder');
|
||||
}
|
||||
else {
|
||||
$res[] = array('text' => $item, 'children' => false, 'id' => $this->id($dir . DIRECTORY_SEPARATOR . $item), 'type' => 'file', 'icon' => 'file file-'.substr($item, strrpos($item,'.') + 1));
|
||||
}
|
||||
}
|
||||
if($with_root && $this->id($dir) === '/') {
|
||||
$res = array(array('text' => basename($this->base), 'children' => $res, 'id' => '/', 'icon'=>'folder', 'state' => array('opened' => true, 'disabled' => true)));
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
public function data($id) {
|
||||
if(strpos($id, ":")) {
|
||||
$id = array_map(array($this, 'id'), explode(':', $id));
|
||||
return array('type'=>'multiple', 'content'=> 'Multiple selected: ' . implode(' ', $id));
|
||||
}
|
||||
$dir = $this->path($id);
|
||||
if(is_dir($dir)) {
|
||||
return array('type'=>'folder', 'content'=> $id);
|
||||
}
|
||||
if(is_file($dir)) {
|
||||
$ext = strpos($dir, '.') !== FALSE ? substr($dir, strrpos($dir, '.') + 1) : '';
|
||||
$dat = array('type' => $ext, 'content' => '');
|
||||
switch($ext) {
|
||||
case 'txt':
|
||||
case 'text':
|
||||
case 'md':
|
||||
case 'js':
|
||||
case 'json':
|
||||
case 'css':
|
||||
case 'html':
|
||||
case 'htm':
|
||||
case 'xml':
|
||||
case 'c':
|
||||
case 'cpp':
|
||||
case 'h':
|
||||
case 'sql':
|
||||
case 'log':
|
||||
case 'py':
|
||||
case 'rb':
|
||||
case 'htaccess':
|
||||
case 'php':
|
||||
$dat['content'] = file_get_contents($dir);
|
||||
break;
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'gif':
|
||||
case 'png':
|
||||
case 'bmp':
|
||||
$dat['content'] = 'data:'.finfo_file(finfo_open(FILEINFO_MIME_TYPE), $dir).';base64,'.base64_encode(file_get_contents($dir));
|
||||
break;
|
||||
default:
|
||||
$dat['content'] = 'File not recognized: '.$this->id($dir);
|
||||
break;
|
||||
}
|
||||
return $dat;
|
||||
}
|
||||
throw new Exception('Not a valid selection: ' . $dir);
|
||||
}
|
||||
public function create($id, $name, $mkdir = false) {
|
||||
$dir = $this->path($id);
|
||||
if(preg_match('([^ a-zа-я-_0-9.]+)ui', $name) || !strlen($name)) {
|
||||
throw new Exception('Invalid name: ' . $name);
|
||||
}
|
||||
if($mkdir) {
|
||||
mkdir($dir . DIRECTORY_SEPARATOR . $name);
|
||||
}
|
||||
else {
|
||||
file_put_contents($dir . DIRECTORY_SEPARATOR . $name, '');
|
||||
}
|
||||
return array('id' => $this->id($dir . DIRECTORY_SEPARATOR . $name));
|
||||
}
|
||||
public function rename($id, $name) {
|
||||
$dir = $this->path($id);
|
||||
if($dir === $this->base) {
|
||||
throw new Exception('Cannot rename root');
|
||||
}
|
||||
if(preg_match('([^ a-zа-я-_0-9.]+)ui', $name) || !strlen($name)) {
|
||||
throw new Exception('Invalid name: ' . $name);
|
||||
}
|
||||
$new = explode(DIRECTORY_SEPARATOR, $dir);
|
||||
array_pop($new);
|
||||
array_push($new, $name);
|
||||
$new = implode(DIRECTORY_SEPARATOR, $new);
|
||||
if($dir !== $new) {
|
||||
if(is_file($new) || is_dir($new)) { throw new Exception('Path already exists: ' . $new); }
|
||||
rename($dir, $new);
|
||||
}
|
||||
return array('id' => $this->id($new));
|
||||
}
|
||||
public function remove($id) {
|
||||
$dir = $this->path($id);
|
||||
if($dir === $this->base) {
|
||||
throw new Exception('Cannot remove root');
|
||||
}
|
||||
if(is_dir($dir)) {
|
||||
foreach(array_diff(scandir($dir), array(".", "..")) as $f) {
|
||||
$this->remove($this->id($dir . DIRECTORY_SEPARATOR . $f));
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
if(is_file($dir)) {
|
||||
unlink($dir);
|
||||
}
|
||||
return array('status' => 'OK');
|
||||
}
|
||||
public function move($id, $par) {
|
||||
$dir = $this->path($id);
|
||||
$par = $this->path($par);
|
||||
$new = explode(DIRECTORY_SEPARATOR, $dir);
|
||||
$new = array_pop($new);
|
||||
$new = $par . DIRECTORY_SEPARATOR . $new;
|
||||
rename($dir, $new);
|
||||
return array('id' => $this->id($new));
|
||||
}
|
||||
public function copy($id, $par) {
|
||||
$dir = $this->path($id);
|
||||
$par = $this->path($par);
|
||||
$new = explode(DIRECTORY_SEPARATOR, $dir);
|
||||
$new = array_pop($new);
|
||||
$new = $par . DIRECTORY_SEPARATOR . $new;
|
||||
if(is_file($new) || is_dir($new)) { throw new Exception('Path already exists: ' . $new); }
|
||||
|
||||
if(is_dir($dir)) {
|
||||
mkdir($new);
|
||||
foreach(array_diff(scandir($dir), array(".", "..")) as $f) {
|
||||
$this->copy($this->id($dir . DIRECTORY_SEPARATOR . $f), $this->id($new));
|
||||
}
|
||||
}
|
||||
if(is_file($dir)) {
|
||||
copy($dir, $new);
|
||||
}
|
||||
return array('id' => $this->id($new));
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($_GET['operation'])) {
|
||||
$fs = new fs(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'root' . DIRECTORY_SEPARATOR);
|
||||
try {
|
||||
$rslt = null;
|
||||
switch($_GET['operation']) {
|
||||
case 'get_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : '/';
|
||||
$rslt = $fs->lst($node, (isset($_GET['id']) && $_GET['id'] === '#'));
|
||||
break;
|
||||
case "get_content":
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : '/';
|
||||
$rslt = $fs->data($node);
|
||||
break;
|
||||
case 'create_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : '/';
|
||||
$rslt = $fs->create($node, isset($_GET['text']) ? $_GET['text'] : '', (!isset($_GET['type']) || $_GET['type'] !== 'file'));
|
||||
break;
|
||||
case 'rename_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : '/';
|
||||
$rslt = $fs->rename($node, isset($_GET['text']) ? $_GET['text'] : '');
|
||||
break;
|
||||
case 'delete_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : '/';
|
||||
$rslt = $fs->remove($node);
|
||||
break;
|
||||
case 'move_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : '/';
|
||||
$parn = isset($_GET['parent']) && $_GET['parent'] !== '#' ? $_GET['parent'] : '/';
|
||||
$rslt = $fs->move($node, $parn);
|
||||
break;
|
||||
case 'copy_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : '/';
|
||||
$parn = isset($_GET['parent']) && $_GET['parent'] !== '#' ? $_GET['parent'] : '/';
|
||||
$rslt = $fs->copy($node, $parn);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Unsupported operation: ' . $_GET['operation']);
|
||||
break;
|
||||
}
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($rslt);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
header($_SERVER["SERVER_PROTOCOL"] . ' 500 Server Error');
|
||||
header('Status: 500 Server Error');
|
||||
echo $e->getMessage();
|
||||
}
|
||||
die();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Title</title>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="stylesheet" href="./../../dist/themes/default/style.min.css" />
|
||||
<style>
|
||||
html, body { background:#ebebeb; font-size:10px; font-family:Verdana; margin:0; padding:0; }
|
||||
#container { min-width:320px; margin:0px auto 0 auto; background:white; border-radius:0px; padding:0px; overflow:hidden; }
|
||||
#tree { float:left; min-width:319px; border-right:1px solid silver; overflow:auto; padding:0px 0; }
|
||||
#data { margin-left:320px; }
|
||||
#data textarea { margin:0; padding:0; height:100%; width:100%; border:0; background:white; display:block; line-height:18px; resize:none; }
|
||||
#data, #code { font: normal normal normal 12px/18px 'Consolas', monospace !important; }
|
||||
|
||||
#tree .folder { background:url('./file_sprite.png') right bottom no-repeat; }
|
||||
#tree .file { background:url('./file_sprite.png') 0 0 no-repeat; }
|
||||
#tree .file-pdf { background-position: -32px 0 }
|
||||
#tree .file-as { background-position: -36px 0 }
|
||||
#tree .file-c { background-position: -72px -0px }
|
||||
#tree .file-iso { background-position: -108px -0px }
|
||||
#tree .file-htm, #tree .file-html, #tree .file-xml, #tree .file-xsl { background-position: -126px -0px }
|
||||
#tree .file-cf { background-position: -162px -0px }
|
||||
#tree .file-cpp { background-position: -216px -0px }
|
||||
#tree .file-cs { background-position: -236px -0px }
|
||||
#tree .file-sql { background-position: -272px -0px }
|
||||
#tree .file-xls, #tree .file-xlsx { background-position: -362px -0px }
|
||||
#tree .file-h { background-position: -488px -0px }
|
||||
#tree .file-crt, #tree .file-pem, #tree .file-cer { background-position: -452px -18px }
|
||||
#tree .file-php { background-position: -108px -18px }
|
||||
#tree .file-jpg, #tree .file-jpeg, #tree .file-png, #tree .file-gif, #tree .file-bmp { background-position: -126px -18px }
|
||||
#tree .file-ppt, #tree .file-pptx { background-position: -144px -18px }
|
||||
#tree .file-rb { background-position: -180px -18px }
|
||||
#tree .file-text, #tree .file-txt, #tree .file-md, #tree .file-log, #tree .file-htaccess { background-position: -254px -18px }
|
||||
#tree .file-doc, #tree .file-docx { background-position: -362px -18px }
|
||||
#tree .file-zip, #tree .file-gz, #tree .file-tar, #tree .file-rar { background-position: -416px -18px }
|
||||
#tree .file-js { background-position: -434px -18px }
|
||||
#tree .file-css { background-position: -144px -0px }
|
||||
#tree .file-fla { background-position: -398px -0px }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" role="main">
|
||||
<div id="tree"></div>
|
||||
<div id="data">
|
||||
<div class="content code" style="display:none;"><textarea id="code" readonly="readonly"></textarea></div>
|
||||
<div class="content folder" style="display:none;"></div>
|
||||
<div class="content image" style="display:none; position:relative;"><img src="" alt="" style="display:block; position:absolute; left:50%; top:50%; padding:0; max-height:90%; max-width:90%;" /></div>
|
||||
<div class="content default" style="text-align:center;">Select a file from the tree.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
|
||||
<script src="./../../dist/jstree.min.js"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$(window).resize(function () {
|
||||
var h = Math.max($(window).height() - 0, 420);
|
||||
$('#container, #data, #tree, #data .content').height(h).filter('.default').css('lineHeight', h + 'px');
|
||||
}).resize();
|
||||
|
||||
$('#tree')
|
||||
.jstree({
|
||||
'core' : {
|
||||
'data' : {
|
||||
'url' : '?operation=get_node',
|
||||
'data' : function (node) {
|
||||
return { 'id' : node.id };
|
||||
}
|
||||
},
|
||||
'check_callback' : function(o, n, p, i, m) {
|
||||
if(m && m.dnd && m.pos !== 'i') { return false; }
|
||||
if(o === "move_node" || o === "copy_node") {
|
||||
if(this.get_node(n).parent === this.get_node(p).id) { return false; }
|
||||
}
|
||||
return true;
|
||||
},
|
||||
'force_text' : true,
|
||||
'themes' : {
|
||||
'responsive' : false,
|
||||
'variant' : 'small',
|
||||
'stripes' : true
|
||||
}
|
||||
},
|
||||
'sort' : function(a, b) {
|
||||
return this.get_type(a) === this.get_type(b) ? (this.get_text(a) > this.get_text(b) ? 1 : -1) : (this.get_type(a) >= this.get_type(b) ? 1 : -1);
|
||||
},
|
||||
'contextmenu' : {
|
||||
'items' : function(node) {
|
||||
var tmp = $.jstree.defaults.contextmenu.items();
|
||||
delete tmp.create.action;
|
||||
tmp.create.label = "New";
|
||||
tmp.create.submenu = {
|
||||
"create_folder" : {
|
||||
"separator_after" : true,
|
||||
"label" : "Folder",
|
||||
"action" : function (data) {
|
||||
var inst = $.jstree.reference(data.reference),
|
||||
obj = inst.get_node(data.reference);
|
||||
inst.create_node(obj, { type : "default" }, "last", function (new_node) {
|
||||
setTimeout(function () { inst.edit(new_node); },0);
|
||||
});
|
||||
}
|
||||
},
|
||||
"create_file" : {
|
||||
"label" : "File",
|
||||
"action" : function (data) {
|
||||
var inst = $.jstree.reference(data.reference),
|
||||
obj = inst.get_node(data.reference);
|
||||
inst.create_node(obj, { type : "file" }, "last", function (new_node) {
|
||||
setTimeout(function () { inst.edit(new_node); },0);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
if(this.get_type(node) === "file") {
|
||||
delete tmp.create;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
},
|
||||
'types' : {
|
||||
'default' : { 'icon' : 'folder' },
|
||||
'file' : { 'valid_children' : [], 'icon' : 'file' }
|
||||
},
|
||||
'unique' : {
|
||||
'duplicate' : function (name, counter) {
|
||||
return name + ' ' + counter;
|
||||
}
|
||||
},
|
||||
'plugins' : ['state','dnd','sort','types','contextmenu','unique']
|
||||
})
|
||||
.on('delete_node.jstree', function (e, data) {
|
||||
$.get('?operation=delete_node', { 'id' : data.node.id })
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('create_node.jstree', function (e, data) {
|
||||
$.get('?operation=create_node', { 'type' : data.node.type, 'id' : data.node.parent, 'text' : data.node.text })
|
||||
.done(function (d) {
|
||||
data.instance.set_id(data.node, d.id);
|
||||
})
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('rename_node.jstree', function (e, data) {
|
||||
$.get('?operation=rename_node', { 'id' : data.node.id, 'text' : data.text })
|
||||
.done(function (d) {
|
||||
data.instance.set_id(data.node, d.id);
|
||||
})
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('move_node.jstree', function (e, data) {
|
||||
$.get('?operation=move_node', { 'id' : data.node.id, 'parent' : data.parent })
|
||||
.done(function (d) {
|
||||
//data.instance.load_node(data.parent);
|
||||
data.instance.refresh();
|
||||
})
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('copy_node.jstree', function (e, data) {
|
||||
$.get('?operation=copy_node', { 'id' : data.original.id, 'parent' : data.parent })
|
||||
.done(function (d) {
|
||||
//data.instance.load_node(data.parent);
|
||||
data.instance.refresh();
|
||||
})
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('changed.jstree', function (e, data) {
|
||||
if(data && data.selected && data.selected.length) {
|
||||
$.get('?operation=get_content&id=' + data.selected.join(':'), function (d) {
|
||||
if(d && typeof d.type !== 'undefined') {
|
||||
$('#data .content').hide();
|
||||
switch(d.type) {
|
||||
case 'text':
|
||||
case 'txt':
|
||||
case 'md':
|
||||
case 'htaccess':
|
||||
case 'log':
|
||||
case 'sql':
|
||||
case 'php':
|
||||
case 'js':
|
||||
case 'json':
|
||||
case 'css':
|
||||
case 'html':
|
||||
$('#data .code').show();
|
||||
$('#code').val(d.content);
|
||||
break;
|
||||
case 'png':
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'bmp':
|
||||
case 'gif':
|
||||
$('#data .image img').one('load', function () { $(this).css({'marginTop':'-' + $(this).height()/2 + 'px','marginLeft':'-' + $(this).width()/2 + 'px'}); }).attr('src',d.content);
|
||||
$('#data .image').show();
|
||||
break;
|
||||
default:
|
||||
$('#data .default').html(d.content).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#data .content').hide();
|
||||
$('#data .default').html('Select a file from the tree.').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,91 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 4.0.1
|
||||
-- http://www.phpmyadmin.net
|
||||
--
|
||||
-- Host: 127.0.0.1
|
||||
-- Generation Time: Apr 15, 2014 at 05:14 PM
|
||||
-- Server version: 5.5.27
|
||||
-- PHP Version: 5.4.7
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
--
|
||||
-- Database: `test`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `tree_data`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `tree_data` (
|
||||
`id` int(10) unsigned NOT NULL,
|
||||
`nm` varchar(255) CHARACTER SET utf8 NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Dumping data for table `tree_data`
|
||||
--
|
||||
|
||||
INSERT INTO `tree_data` (`id`, `nm`) VALUES
|
||||
(1, 'root'),
|
||||
(1063, 'Node 12'),
|
||||
(1064, 'Node 2'),
|
||||
(1065, 'Node 3'),
|
||||
(1066, 'Node 4'),
|
||||
(1067, 'Node 5'),
|
||||
(1068, 'Node 6'),
|
||||
(1069, 'Node 7'),
|
||||
(1070, 'Node 8'),
|
||||
(1071, 'Node 9'),
|
||||
(1072, 'Node 9'),
|
||||
(1073, 'Node 9'),
|
||||
(1074, 'Node 9'),
|
||||
(1075, 'Node 7'),
|
||||
(1076, 'Node 8'),
|
||||
(1077, 'Node 9'),
|
||||
(1078, 'Node 9'),
|
||||
(1079, 'Node 9');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `tree_struct`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `tree_struct` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`lft` int(10) unsigned NOT NULL,
|
||||
`rgt` int(10) unsigned NOT NULL,
|
||||
`lvl` int(10) unsigned NOT NULL,
|
||||
`pid` int(10) unsigned NOT NULL,
|
||||
`pos` int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1083 ;
|
||||
|
||||
--
|
||||
-- Dumping data for table `tree_struct`
|
||||
--
|
||||
|
||||
INSERT INTO `tree_struct` (`id`, `lft`, `rgt`, `lvl`, `pid`, `pos`) VALUES
|
||||
(1, 1, 36, 0, 0, 0),
|
||||
(1063, 2, 31, 1, 1, 0),
|
||||
(1064, 3, 30, 2, 1063, 0),
|
||||
(1065, 4, 29, 3, 1064, 0),
|
||||
(1066, 5, 28, 4, 1065, 0),
|
||||
(1067, 6, 19, 5, 1066, 0),
|
||||
(1068, 7, 18, 6, 1067, 0),
|
||||
(1069, 8, 17, 7, 1068, 0),
|
||||
(1070, 9, 16, 8, 1069, 0),
|
||||
(1071, 12, 13, 9, 1070, 1),
|
||||
(1072, 14, 15, 9, 1070, 2),
|
||||
(1073, 10, 11, 9, 1070, 0),
|
||||
(1074, 32, 35, 1, 1, 1),
|
||||
(1075, 20, 27, 5, 1066, 1),
|
||||
(1076, 21, 26, 6, 1075, 0),
|
||||
(1077, 24, 25, 7, 1076, 1),
|
||||
(1078, 33, 34, 2, 1074, 0),
|
||||
(1079, 22, 23, 7, 1076, 0);
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
require_once(dirname(__FILE__) . '/class.db.php');
|
||||
require_once(dirname(__FILE__) . '/class.tree.php');
|
||||
|
||||
if(isset($_GET['operation'])) {
|
||||
$fs = new tree(db::get('mysqli://root@127.0.0.1/test'), array('structure_table' => 'tree_struct', 'data_table' => 'tree_data', 'data' => array('nm')));
|
||||
try {
|
||||
$rslt = null;
|
||||
switch($_GET['operation']) {
|
||||
case 'analyze':
|
||||
var_dump($fs->analyze(true));
|
||||
die();
|
||||
break;
|
||||
case 'get_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
|
||||
$temp = $fs->get_children($node);
|
||||
$rslt = array();
|
||||
foreach($temp as $v) {
|
||||
$rslt[] = array('id' => $v['id'], 'text' => $v['nm'], 'children' => ($v['rgt'] - $v['lft'] > 1));
|
||||
}
|
||||
break;
|
||||
case "get_content":
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? $_GET['id'] : 0;
|
||||
$node = explode(':', $node);
|
||||
if(count($node) > 1) {
|
||||
$rslt = array('content' => 'Multiple selected');
|
||||
}
|
||||
else {
|
||||
$temp = $fs->get_node((int)$node[0], array('with_path' => true));
|
||||
$rslt = array('content' => 'Selected: /' . implode('/',array_map(function ($v) { return $v['nm']; }, $temp['path'])). '/'.$temp['nm']);
|
||||
}
|
||||
break;
|
||||
case 'create_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
|
||||
$temp = $fs->mk($node, isset($_GET['position']) ? (int)$_GET['position'] : 0, array('nm' => isset($_GET['text']) ? $_GET['text'] : 'New node'));
|
||||
$rslt = array('id' => $temp);
|
||||
break;
|
||||
case 'rename_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
|
||||
$rslt = $fs->rn($node, array('nm' => isset($_GET['text']) ? $_GET['text'] : 'Renamed node'));
|
||||
break;
|
||||
case 'delete_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
|
||||
$rslt = $fs->rm($node);
|
||||
break;
|
||||
case 'move_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
|
||||
$parn = isset($_GET['parent']) && $_GET['parent'] !== '#' ? (int)$_GET['parent'] : 0;
|
||||
$rslt = $fs->mv($node, $parn, isset($_GET['position']) ? (int)$_GET['position'] : 0);
|
||||
break;
|
||||
case 'copy_node':
|
||||
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
|
||||
$parn = isset($_GET['parent']) && $_GET['parent'] !== '#' ? (int)$_GET['parent'] : 0;
|
||||
$rslt = $fs->cp($node, $parn, isset($_GET['position']) ? (int)$_GET['position'] : 0);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Unsupported operation: ' . $_GET['operation']);
|
||||
break;
|
||||
}
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($rslt);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
header($_SERVER["SERVER_PROTOCOL"] . ' 500 Server Error');
|
||||
header('Status: 500 Server Error');
|
||||
echo $e->getMessage();
|
||||
}
|
||||
die();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Title</title>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="stylesheet" href="./../../dist/themes/default/style.min.css" />
|
||||
<style>
|
||||
html, body { background:#ebebeb; font-size:10px; font-family:Verdana; margin:0; padding:0; }
|
||||
#container { min-width:320px; margin:0px auto 0 auto; background:white; border-radius:0px; padding:0px; overflow:hidden; }
|
||||
#tree { float:left; min-width:319px; border-right:1px solid silver; overflow:auto; padding:0px 0; }
|
||||
#data { margin-left:320px; }
|
||||
#data textarea { margin:0; padding:0; height:100%; width:100%; border:0; background:white; display:block; line-height:18px; }
|
||||
#data, #code { font: normal normal normal 12px/18px 'Consolas', monospace !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" role="main">
|
||||
<div id="tree"></div>
|
||||
<div id="data">
|
||||
<div class="content code" style="display:none;"><textarea id="code" readonly="readonly"></textarea></div>
|
||||
<div class="content folder" style="display:none;"></div>
|
||||
<div class="content image" style="display:none; position:relative;"><img src="" alt="" style="display:block; position:absolute; left:50%; top:50%; padding:0; max-height:90%; max-width:90%;" /></div>
|
||||
<div class="content default" style="text-align:center;">Select a node from the tree.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
|
||||
<script src="./../../dist/jstree.min.js"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$(window).resize(function () {
|
||||
var h = Math.max($(window).height() - 0, 420);
|
||||
$('#container, #data, #tree, #data .content').height(h).filter('.default').css('lineHeight', h + 'px');
|
||||
}).resize();
|
||||
|
||||
$('#tree')
|
||||
.jstree({
|
||||
'core' : {
|
||||
'data' : {
|
||||
'url' : '?operation=get_node',
|
||||
'data' : function (node) {
|
||||
return { 'id' : node.id };
|
||||
}
|
||||
},
|
||||
'check_callback' : true,
|
||||
'themes' : {
|
||||
'responsive' : false
|
||||
}
|
||||
},
|
||||
'force_text' : true,
|
||||
'plugins' : ['state','dnd','contextmenu','wholerow']
|
||||
})
|
||||
.on('delete_node.jstree', function (e, data) {
|
||||
$.get('?operation=delete_node', { 'id' : data.node.id })
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('create_node.jstree', function (e, data) {
|
||||
$.get('?operation=create_node', { 'id' : data.node.parent, 'position' : data.position, 'text' : data.node.text })
|
||||
.done(function (d) {
|
||||
data.instance.set_id(data.node, d.id);
|
||||
})
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('rename_node.jstree', function (e, data) {
|
||||
$.get('?operation=rename_node', { 'id' : data.node.id, 'text' : data.text })
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('move_node.jstree', function (e, data) {
|
||||
$.get('?operation=move_node', { 'id' : data.node.id, 'parent' : data.parent, 'position' : data.position })
|
||||
.fail(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('copy_node.jstree', function (e, data) {
|
||||
$.get('?operation=copy_node', { 'id' : data.original.id, 'parent' : data.parent, 'position' : data.position })
|
||||
.always(function () {
|
||||
data.instance.refresh();
|
||||
});
|
||||
})
|
||||
.on('changed.jstree', function (e, data) {
|
||||
if(data && data.selected && data.selected.length) {
|
||||
$.get('?operation=get_content&id=' + data.selected.join(':'), function (d) {
|
||||
$('#data .default').text(d.content).show();
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#data .content').hide();
|
||||
$('#data .default').text('Select a file from the tree.').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8305
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/jstree.js
vendored
Normal file
6
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/jstree.min.js
vendored
Normal file
BIN
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default-dark/32px.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default-dark/40px.png
vendored
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
1146
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default-dark/style.css
vendored
Normal file
1
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default-dark/style.min.css
vendored
Normal file
BIN
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default-dark/throbber.gif
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default/32px.png
vendored
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default/40px.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
1102
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default/style.css
vendored
Normal file
1
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default/style.min.css
vendored
Normal file
BIN
mayan/apps/cabinets/static/cabinets/packages/jstree/dist/themes/default/throbber.gif
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
241
mayan/apps/cabinets/static/cabinets/packages/jstree/gruntfile.js
Normal file
@@ -0,0 +1,241 @@
|
||||
/*global module:false, require:false, __dirname:false*/
|
||||
|
||||
module.exports = function(grunt) {
|
||||
grunt.util.linefeed = "\n";
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
concat: {
|
||||
options : {
|
||||
separator : "\n"
|
||||
},
|
||||
dist: {
|
||||
src: ['src/<%= pkg.name %>.js', 'src/<%= pkg.name %>.*.js', 'src/vakata-jstree.js'],
|
||||
dest: 'dist/<%= pkg.name %>.js'
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
libs : {
|
||||
files : [
|
||||
{ expand: true, cwd : 'libs/', src: ['*'], dest: 'dist/libs/' }
|
||||
]
|
||||
},
|
||||
docs : {
|
||||
files : [
|
||||
{ expand: true, cwd : 'dist/', src: ['**/*'], dest: 'docs/assets/dist/' }
|
||||
]
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
options: {
|
||||
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> - (<%= _.pluck(pkg.licenses, "type").join(", ") %>) */\n',
|
||||
preserveComments: false,
|
||||
//sourceMap: "dist/jstree.min.map",
|
||||
//sourceMappingURL: "jstree.min.map",
|
||||
report: "min",
|
||||
beautify: {
|
||||
ascii_only: true
|
||||
},
|
||||
compress: {
|
||||
hoist_funs: false,
|
||||
loops: false,
|
||||
unused: false
|
||||
}
|
||||
},
|
||||
dist: {
|
||||
src: ['<%= concat.dist.dest %>'],
|
||||
dest: 'dist/<%= pkg.name %>.min.js'
|
||||
}
|
||||
},
|
||||
qunit: {
|
||||
files: ['test/unit/**/*.html']
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
'curly' : true,
|
||||
'eqeqeq' : true,
|
||||
'latedef' : true,
|
||||
'newcap' : true,
|
||||
'noarg' : true,
|
||||
'sub' : true,
|
||||
'undef' : true,
|
||||
'boss' : true,
|
||||
'eqnull' : true,
|
||||
'browser' : true,
|
||||
'trailing' : true,
|
||||
'globals' : {
|
||||
'console' : true,
|
||||
'jQuery' : true,
|
||||
'browser' : true,
|
||||
'XSLTProcessor' : true,
|
||||
'ActiveXObject' : true
|
||||
}
|
||||
},
|
||||
beforeconcat: ['src/<%= pkg.name %>.js', 'src/<%= pkg.name %>.*.js'],
|
||||
afterconcat: ['dist/<%= pkg.name %>.js']
|
||||
},
|
||||
dox: {
|
||||
files: {
|
||||
src: ['src/*.js'],
|
||||
dest: 'docs'
|
||||
}
|
||||
},
|
||||
amd : {
|
||||
files: {
|
||||
src: ['dist/jstree.js'],
|
||||
dest: 'dist/jstree.js'
|
||||
}
|
||||
},
|
||||
less: {
|
||||
production: {
|
||||
options : {
|
||||
cleancss : true,
|
||||
compress : true
|
||||
},
|
||||
files: {
|
||||
"dist/themes/default/style.min.css" : "src/themes/default/style.less",
|
||||
"dist/themes/default-dark/style.min.css" : "src/themes/default-dark/style.less"
|
||||
}
|
||||
},
|
||||
development: {
|
||||
files: {
|
||||
"src/themes/default/style.css" : "src/themes/default/style.less",
|
||||
"dist/themes/default/style.css" : "src/themes/default/style.less",
|
||||
"src/themes/default-dark/style.css" : "src/themes/default-dark/style.less",
|
||||
"dist/themes/default-dark/style.css" : "src/themes/default-dark/style.less"
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
js : {
|
||||
files: ['src/**/*.js'],
|
||||
tasks: ['js'],
|
||||
options : {
|
||||
atBegin : true
|
||||
}
|
||||
},
|
||||
css : {
|
||||
files: ['src/**/*.less','src/**/*.png','src/**/*.gif'],
|
||||
tasks: ['css'],
|
||||
options : {
|
||||
atBegin : true
|
||||
}
|
||||
},
|
||||
},
|
||||
resemble: {
|
||||
options: {
|
||||
screenshotRoot: 'test/visual/screenshots/',
|
||||
url: 'http://127.0.0.1/jstree/test/visual/',
|
||||
gm: false
|
||||
},
|
||||
desktop: {
|
||||
options: {
|
||||
width: 1280,
|
||||
},
|
||||
src: ['desktop'],
|
||||
dest: 'desktop',
|
||||
},
|
||||
mobile: {
|
||||
options: {
|
||||
width: 360,
|
||||
},
|
||||
src: ['mobile'],
|
||||
dest: 'mobile'
|
||||
}
|
||||
},
|
||||
imagemin: {
|
||||
dynamic: {
|
||||
options: { // Target options
|
||||
optimizationLevel: 7,
|
||||
pngquant : true
|
||||
},
|
||||
files: [{
|
||||
expand: true, // Enable dynamic expansion
|
||||
cwd: 'src/themes/default/', // Src matches are relative to this path
|
||||
src: ['**/*.{png,jpg,gif}'], // Actual patterns to match
|
||||
dest: 'dist/themes/default/' // Destination path prefix
|
||||
},{
|
||||
expand: true, // Enable dynamic expansion
|
||||
cwd: 'src/themes/default-dark/', // Src matches are relative to this path
|
||||
src: ['**/*.{png,jpg,gif}'], // Actual patterns to match
|
||||
dest: 'dist/themes/default-dark/' // Destination path prefix
|
||||
}]
|
||||
}
|
||||
},
|
||||
replace: {
|
||||
files: {
|
||||
src: ['dist/*.js', 'bower.json', 'component.json', 'jstree.jquery.json'],
|
||||
overwrite: true,
|
||||
replacements: [
|
||||
{
|
||||
from: '{{VERSION}}',
|
||||
to: "<%= pkg.version %>"
|
||||
},
|
||||
{
|
||||
from: /"version": "[^"]+"/g,
|
||||
to: "\"version\": \"<%= pkg.version %>\""
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-less');
|
||||
grunt.loadNpmTasks('grunt-contrib-qunit');
|
||||
grunt.loadNpmTasks('grunt-resemble-cli');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-imagemin');
|
||||
grunt.loadNpmTasks('grunt-text-replace');
|
||||
|
||||
grunt.registerMultiTask('amd', 'Clean up AMD', function () {
|
||||
var s, d;
|
||||
this.files.forEach(function (f) {
|
||||
s = f.src;
|
||||
d = f.dest;
|
||||
});
|
||||
grunt.file.copy(s, d, {
|
||||
process: function (contents) {
|
||||
contents = contents.replace(/\s*if\(\$\.jstree\.plugins\.[a-z]+\)\s*\{\s*return;\s*\}/ig, '');
|
||||
contents = contents.replace(/\/\*globals[^\/]+\//ig, '');
|
||||
//contents = contents.replace(/\(function \(factory[\s\S]*?undefined/mig, '(function ($, undefined');
|
||||
//contents = contents.replace(/\}\)\);/g, '}(jQuery));');
|
||||
contents = contents.replace(/\(function \(factory[\s\S]*?undefined\s*\)[^\n]+/mig, '');
|
||||
contents = contents.replace(/\}\)\);/g, '');
|
||||
contents = contents.replace(/\s*("|')use strict("|');/g, '');
|
||||
contents = contents.replace(/\s*return \$\.fn\.jstree;/g, '');
|
||||
return grunt.file.read('src/intro.js') + contents + grunt.file.read('src/outro.js');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
grunt.registerMultiTask('dox', 'Generate dox output ', function() {
|
||||
var exec = require('child_process').exec,
|
||||
path = require('path'),
|
||||
done = this.async(),
|
||||
doxPath = path.resolve(__dirname),
|
||||
formatter = [doxPath, 'node_modules', '.bin', 'dox'].join(path.sep);
|
||||
exec(formatter + ' < "dist/jstree.js" > "docs/jstree.json"', {maxBuffer: 5000*1024}, function(error, stout, sterr){
|
||||
if (error) {
|
||||
grunt.log.error(formatter);
|
||||
grunt.log.error("WARN: "+ error);
|
||||
}
|
||||
if (!error) {
|
||||
grunt.log.writeln('dist/jstree.js doxxed.');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
grunt.util.linefeed = "\n";
|
||||
|
||||
// Default task.
|
||||
grunt.registerTask('default', ['jshint:beforeconcat','concat','amd','jshint:afterconcat','copy:libs','uglify','less','imagemin','replace','copy:docs','qunit','resemble','dox']);
|
||||
grunt.registerTask('js', ['concat','amd','uglify']);
|
||||
grunt.registerTask('css', ['copy','less']);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "jstree",
|
||||
"title": "jsTree",
|
||||
"description": "Tree view for jQuery",
|
||||
"version": "3.3.3",
|
||||
"homepage": "http://jstree.com",
|
||||
"keywords": [
|
||||
"ui",
|
||||
"tree",
|
||||
"jstree"
|
||||
],
|
||||
"author": {
|
||||
"name": "Ivan Bozhanov",
|
||||
"email": "jstree@jstree.com",
|
||||
"url": "http://vakata.com"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/vakata/jstree/blob/master/LICENSE-MIT"
|
||||
}
|
||||
],
|
||||
"bugs": "https://github.com/vakata/jstree/issues",
|
||||
"demo": "http://jstree.com/demo",
|
||||
"dependencies": {
|
||||
"jquery": ">=1.9.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "jstree",
|
||||
"title": "jsTree",
|
||||
"description": "jQuery tree plugin",
|
||||
"version": "3.3.3",
|
||||
"homepage": "http://jstree.com",
|
||||
"main": "./dist/jstree.js",
|
||||
"author": {
|
||||
"name": "Ivan Bozhanov",
|
||||
"email": "jstree@jstree.com",
|
||||
"url": "http://vakata.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/vakata/jstree.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/vakata/jstree/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/vakata/jstree/blob/master/LICENSE-MIT"
|
||||
}
|
||||
],
|
||||
"keywords": [],
|
||||
"devDependencies": {
|
||||
"dox": "~0.4.4",
|
||||
"grunt": "~0.4.0",
|
||||
"grunt-contrib-concat": "*",
|
||||
"grunt-contrib-copy": "*",
|
||||
"grunt-contrib-imagemin": "~0.4.0",
|
||||
"grunt-contrib-jshint": "*",
|
||||
"grunt-contrib-less": "~0.8.2",
|
||||
"grunt-contrib-qunit": "~v0.3.0",
|
||||
"grunt-contrib-uglify": "*",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-phantomcss-gitdiff": "0.0.7",
|
||||
"grunt-resemble-cli": "0.0.8",
|
||||
"grunt-text-replace": "~0.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"jquery": ">=1.9.1"
|
||||
},
|
||||
"npmName": "jstree",
|
||||
"npmFileMap": [
|
||||
{
|
||||
"basePath": "/dist/",
|
||||
"files": [
|
||||
"jstree.min.js",
|
||||
"themes/**/*.png",
|
||||
"themes/**/*.gif",
|
||||
"themes/**/*.min.css"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*globals jQuery, define, module, exports, require, window, document, postMessage */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['jquery'], factory);
|
||||
}
|
||||
else if(typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = factory(require('jquery'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($, undefined) {
|
||||
"use strict";
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* ### Changed plugin
|
||||
*
|
||||
* This plugin adds more information to the `changed.jstree` event. The new data is contained in the `changed` event data property, and contains a lists of `selected` and `deselected` nodes.
|
||||
*/
|
||||
/*globals jQuery, define, exports, require, document */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.changed', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.changed) { return; }
|
||||
|
||||
$.jstree.plugins.changed = function (options, parent) {
|
||||
var last = [];
|
||||
this.trigger = function (ev, data) {
|
||||
var i, j;
|
||||
if(!data) {
|
||||
data = {};
|
||||
}
|
||||
if(ev.replace('.jstree','') === 'changed') {
|
||||
data.changed = { selected : [], deselected : [] };
|
||||
var tmp = {};
|
||||
for(i = 0, j = last.length; i < j; i++) {
|
||||
tmp[last[i]] = 1;
|
||||
}
|
||||
for(i = 0, j = data.selected.length; i < j; i++) {
|
||||
if(!tmp[data.selected[i]]) {
|
||||
data.changed.selected.push(data.selected[i]);
|
||||
}
|
||||
else {
|
||||
tmp[data.selected[i]] = 2;
|
||||
}
|
||||
}
|
||||
for(i = 0, j = last.length; i < j; i++) {
|
||||
if(tmp[last[i]] === 1) {
|
||||
data.changed.deselected.push(last[i]);
|
||||
}
|
||||
}
|
||||
last = data.selected.slice();
|
||||
}
|
||||
/**
|
||||
* triggered when selection changes (the "changed" plugin enhances the original event with more data)
|
||||
* @event
|
||||
* @name changed.jstree
|
||||
* @param {Object} node
|
||||
* @param {Object} action the action that caused the selection to change
|
||||
* @param {Array} selected the current selection
|
||||
* @param {Object} changed an object containing two properties `selected` and `deselected` - both arrays of node IDs, which were selected or deselected since the last changed event
|
||||
* @param {Object} event the event (if any) that triggered this changed event
|
||||
* @plugin changed
|
||||
*/
|
||||
parent.trigger.call(this, ev, data);
|
||||
};
|
||||
this.refresh = function (skip_loading, forget_state) {
|
||||
last = [];
|
||||
return parent.refresh.apply(this, arguments);
|
||||
};
|
||||
};
|
||||
}));
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* ### Conditionalselect plugin
|
||||
*
|
||||
* This plugin allows defining a callback to allow or deny node selection by user input (activate node method).
|
||||
*/
|
||||
/*globals jQuery, define, exports, require, document */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.conditionalselect', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.conditionalselect) { return; }
|
||||
|
||||
/**
|
||||
* a callback (function) which is invoked in the instance's scope and receives two arguments - the node and the event that triggered the `activate_node` call. Returning false prevents working with the node, returning true allows invoking activate_node. Defaults to returning `true`.
|
||||
* @name $.jstree.defaults.checkbox.visible
|
||||
* @plugin checkbox
|
||||
*/
|
||||
$.jstree.defaults.conditionalselect = function () { return true; };
|
||||
$.jstree.plugins.conditionalselect = function (options, parent) {
|
||||
// own function
|
||||
this.activate_node = function (obj, e) {
|
||||
if(this.settings.conditionalselect.call(this, this.get_node(obj), e)) {
|
||||
parent.activate_node.call(this, obj, e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
}));
|
||||
4810
mayan/apps/cabinets/static/cabinets/packages/jstree/src/jstree.js
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* ### Massload plugin
|
||||
*
|
||||
* Adds massload functionality to jsTree, so that multiple nodes can be loaded in a single request (only useful with lazy loading).
|
||||
*/
|
||||
/*globals jQuery, define, exports, require, document */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.massload', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.massload) { return; }
|
||||
|
||||
/**
|
||||
* massload configuration
|
||||
*
|
||||
* It is possible to set this to a standard jQuery-like AJAX config.
|
||||
* In addition to the standard jQuery ajax options here you can supply functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node IDs need to be loaded, the return value of those functions will be used.
|
||||
*
|
||||
* You can also set this to a function, that function will receive the node IDs being loaded as argument and a second param which is a function (callback) which should be called with the result.
|
||||
*
|
||||
* Both the AJAX and the function approach rely on the same return value - an object where the keys are the node IDs, and the value is the children of that node as an array.
|
||||
*
|
||||
* {
|
||||
* "id1" : [{ "text" : "Child of ID1", "id" : "c1" }, { "text" : "Another child of ID1", "id" : "c2" }],
|
||||
* "id2" : [{ "text" : "Child of ID2", "id" : "c3" }]
|
||||
* }
|
||||
*
|
||||
* @name $.jstree.defaults.massload
|
||||
* @plugin massload
|
||||
*/
|
||||
$.jstree.defaults.massload = null;
|
||||
$.jstree.plugins.massload = function (options, parent) {
|
||||
this.init = function (el, options) {
|
||||
this._data.massload = {};
|
||||
parent.init.call(this, el, options);
|
||||
};
|
||||
this._load_nodes = function (nodes, callback, is_callback, force_reload) {
|
||||
var s = this.settings.massload,
|
||||
nodesString = JSON.stringify(nodes),
|
||||
toLoad = [],
|
||||
m = this._model.data,
|
||||
i, j, dom;
|
||||
if (!is_callback) {
|
||||
for(i = 0, j = nodes.length; i < j; i++) {
|
||||
if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) {
|
||||
toLoad.push(nodes[i]);
|
||||
dom = this.get_node(nodes[i], true);
|
||||
if (dom && dom.length) {
|
||||
dom.addClass("jstree-loading").attr('aria-busy',true);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._data.massload = {};
|
||||
if (toLoad.length) {
|
||||
if($.isFunction(s)) {
|
||||
return s.call(this, toLoad, $.proxy(function (data) {
|
||||
var i, j;
|
||||
if(data) {
|
||||
for(i in data) {
|
||||
if(data.hasOwnProperty(i)) {
|
||||
this._data.massload[i] = data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(i = 0, j = nodes.length; i < j; i++) {
|
||||
dom = this.get_node(nodes[i], true);
|
||||
if (dom && dom.length) {
|
||||
dom.removeClass("jstree-loading").attr('aria-busy',false);
|
||||
}
|
||||
}
|
||||
parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
|
||||
}, this));
|
||||
}
|
||||
if(typeof s === 'object' && s && s.url) {
|
||||
s = $.extend(true, {}, s);
|
||||
if($.isFunction(s.url)) {
|
||||
s.url = s.url.call(this, toLoad);
|
||||
}
|
||||
if($.isFunction(s.data)) {
|
||||
s.data = s.data.call(this, toLoad);
|
||||
}
|
||||
return $.ajax(s)
|
||||
.done($.proxy(function (data,t,x) {
|
||||
var i, j;
|
||||
if(data) {
|
||||
for(i in data) {
|
||||
if(data.hasOwnProperty(i)) {
|
||||
this._data.massload[i] = data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(i = 0, j = nodes.length; i < j; i++) {
|
||||
dom = this.get_node(nodes[i], true);
|
||||
if (dom && dom.length) {
|
||||
dom.removeClass("jstree-loading").attr('aria-busy',false);
|
||||
}
|
||||
}
|
||||
parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
|
||||
}, this))
|
||||
.fail($.proxy(function (f) {
|
||||
parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
|
||||
}, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
|
||||
};
|
||||
this._load_node = function (obj, callback) {
|
||||
var data = this._data.massload[obj.id],
|
||||
rslt = null, dom;
|
||||
if(data) {
|
||||
rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data'](
|
||||
obj,
|
||||
typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data,
|
||||
function (status) { callback.call(this, status); }
|
||||
);
|
||||
dom = this.get_node(obj.id, true);
|
||||
if (dom && dom.length) {
|
||||
dom.removeClass("jstree-loading").attr('aria-busy',false);
|
||||
}
|
||||
delete this._data.massload[obj.id];
|
||||
return rslt;
|
||||
}
|
||||
return parent._load_node.call(this, obj, callback);
|
||||
};
|
||||
};
|
||||
}));
|
||||
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* ### Search plugin
|
||||
*
|
||||
* Adds search functionality to jsTree.
|
||||
*/
|
||||
/*globals jQuery, define, exports, require, document */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.search', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.search) { return; }
|
||||
|
||||
/**
|
||||
* stores all defaults for the search plugin
|
||||
* @name $.jstree.defaults.search
|
||||
* @plugin search
|
||||
*/
|
||||
$.jstree.defaults.search = {
|
||||
/**
|
||||
* a jQuery-like AJAX config, which jstree uses if a server should be queried for results.
|
||||
*
|
||||
* A `str` (which is the search string) parameter will be added with the request, an optional `inside` parameter will be added if the search is limited to a node id. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed.
|
||||
* Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to
|
||||
* @name $.jstree.defaults.search.ajax
|
||||
* @plugin search
|
||||
*/
|
||||
ajax : false,
|
||||
/**
|
||||
* Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`.
|
||||
* @name $.jstree.defaults.search.fuzzy
|
||||
* @plugin search
|
||||
*/
|
||||
fuzzy : false,
|
||||
/**
|
||||
* Indicates if the search should be case sensitive. Default is `false`.
|
||||
* @name $.jstree.defaults.search.case_sensitive
|
||||
* @plugin search
|
||||
*/
|
||||
case_sensitive : false,
|
||||
/**
|
||||
* Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers).
|
||||
* This setting can be changed at runtime when calling the search method. Default is `false`.
|
||||
* @name $.jstree.defaults.search.show_only_matches
|
||||
* @plugin search
|
||||
*/
|
||||
show_only_matches : false,
|
||||
/**
|
||||
* Indicates if the children of matched element are shown (when show_only_matches is true)
|
||||
* This setting can be changed at runtime when calling the search method. Default is `false`.
|
||||
* @name $.jstree.defaults.search.show_only_matches_children
|
||||
* @plugin search
|
||||
*/
|
||||
show_only_matches_children : false,
|
||||
/**
|
||||
* Indicates if all nodes opened to reveal the search result, should be closed when the search is cleared or a new search is performed. Default is `true`.
|
||||
* @name $.jstree.defaults.search.close_opened_onclear
|
||||
* @plugin search
|
||||
*/
|
||||
close_opened_onclear : true,
|
||||
/**
|
||||
* Indicates if only leaf nodes should be included in search results. Default is `false`.
|
||||
* @name $.jstree.defaults.search.search_leaves_only
|
||||
* @plugin search
|
||||
*/
|
||||
search_leaves_only : false,
|
||||
/**
|
||||
* If set to a function it wil be called in the instance's scope with two arguments - search string and node (where node will be every node in the structure, so use with caution).
|
||||
* If the function returns a truthy value the node will be considered a match (it might not be displayed if search_only_leaves is set to true and the node is not a leaf). Default is `false`.
|
||||
* @name $.jstree.defaults.search.search_callback
|
||||
* @plugin search
|
||||
*/
|
||||
search_callback : false
|
||||
};
|
||||
|
||||
$.jstree.plugins.search = function (options, parent) {
|
||||
this.bind = function () {
|
||||
parent.bind.call(this);
|
||||
|
||||
this._data.search.str = "";
|
||||
this._data.search.dom = $();
|
||||
this._data.search.res = [];
|
||||
this._data.search.opn = [];
|
||||
this._data.search.som = false;
|
||||
this._data.search.smc = false;
|
||||
this._data.search.hdn = [];
|
||||
|
||||
this.element
|
||||
.on("search.jstree", $.proxy(function (e, data) {
|
||||
if(this._data.search.som && data.res.length) {
|
||||
var m = this._model.data, i, j, p = [], k, l;
|
||||
for(i = 0, j = data.res.length; i < j; i++) {
|
||||
if(m[data.res[i]] && !m[data.res[i]].state.hidden) {
|
||||
p.push(data.res[i]);
|
||||
p = p.concat(m[data.res[i]].parents);
|
||||
if(this._data.search.smc) {
|
||||
for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) {
|
||||
if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) {
|
||||
p.push(m[data.res[i]].children_d[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root);
|
||||
this._data.search.hdn = this.hide_all(true);
|
||||
this.show_node(p, true);
|
||||
this.redraw(true);
|
||||
}
|
||||
}, this))
|
||||
.on("clear_search.jstree", $.proxy(function (e, data) {
|
||||
if(this._data.search.som && data.res.length) {
|
||||
this.show_node(this._data.search.hdn, true);
|
||||
this.redraw(true);
|
||||
}
|
||||
}, this));
|
||||
};
|
||||
/**
|
||||
* used to search the tree nodes for a given string
|
||||
* @name search(str [, skip_async])
|
||||
* @param {String} str the search string
|
||||
* @param {Boolean} skip_async if set to true server will not be queried even if configured
|
||||
* @param {Boolean} show_only_matches if set to true only matching nodes will be shown (keep in mind this can be very slow on large trees or old browsers)
|
||||
* @param {mixed} inside an optional node to whose children to limit the search
|
||||
* @param {Boolean} append if set to true the results of this search are appended to the previous search
|
||||
* @plugin search
|
||||
* @trigger search.jstree
|
||||
*/
|
||||
this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) {
|
||||
if(str === false || $.trim(str.toString()) === "") {
|
||||
return this.clear_search();
|
||||
}
|
||||
inside = this.get_node(inside);
|
||||
inside = inside && inside.id ? inside.id : null;
|
||||
str = str.toString();
|
||||
var s = this.settings.search,
|
||||
a = s.ajax ? s.ajax : false,
|
||||
m = this._model.data,
|
||||
f = null,
|
||||
r = [],
|
||||
p = [], i, j;
|
||||
if(this._data.search.res.length && !append) {
|
||||
this.clear_search();
|
||||
}
|
||||
if(show_only_matches === undefined) {
|
||||
show_only_matches = s.show_only_matches;
|
||||
}
|
||||
if(show_only_matches_children === undefined) {
|
||||
show_only_matches_children = s.show_only_matches_children;
|
||||
}
|
||||
if(!skip_async && a !== false) {
|
||||
if($.isFunction(a)) {
|
||||
return a.call(this, str, $.proxy(function (d) {
|
||||
if(d && d.d) { d = d.d; }
|
||||
this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
|
||||
this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
|
||||
});
|
||||
}, this), inside);
|
||||
}
|
||||
else {
|
||||
a = $.extend({}, a);
|
||||
if(!a.data) { a.data = {}; }
|
||||
a.data.str = str;
|
||||
if(inside) {
|
||||
a.data.inside = inside;
|
||||
}
|
||||
if (this._data.search.lastRequest) {
|
||||
this._data.search.lastRequest.abort();
|
||||
}
|
||||
this._data.search.lastRequest = $.ajax(a)
|
||||
.fail($.proxy(function () {
|
||||
this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
|
||||
this.settings.core.error.call(this, this._data.core.last_error);
|
||||
}, this))
|
||||
.done($.proxy(function (d) {
|
||||
if(d && d.d) { d = d.d; }
|
||||
this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
|
||||
this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
|
||||
});
|
||||
}, this));
|
||||
return this._data.search.lastRequest;
|
||||
}
|
||||
}
|
||||
if(!append) {
|
||||
this._data.search.str = str;
|
||||
this._data.search.dom = $();
|
||||
this._data.search.res = [];
|
||||
this._data.search.opn = [];
|
||||
this._data.search.som = show_only_matches;
|
||||
this._data.search.smc = show_only_matches_children;
|
||||
}
|
||||
|
||||
f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
|
||||
$.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) {
|
||||
var v = m[i];
|
||||
if(v.text && !v.state.hidden && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) {
|
||||
r.push(i);
|
||||
p = p.concat(v.parents);
|
||||
}
|
||||
});
|
||||
if(r.length) {
|
||||
p = $.vakata.array_unique(p);
|
||||
for(i = 0, j = p.length; i < j; i++) {
|
||||
if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) {
|
||||
this._data.search.opn.push(p[i]);
|
||||
}
|
||||
}
|
||||
if(!append) {
|
||||
this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #')));
|
||||
this._data.search.res = r;
|
||||
}
|
||||
else {
|
||||
this._data.search.dom = this._data.search.dom.add($(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #'))));
|
||||
this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r));
|
||||
}
|
||||
this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
|
||||
}
|
||||
/**
|
||||
* triggered after search is complete
|
||||
* @event
|
||||
* @name search.jstree
|
||||
* @param {jQuery} nodes a jQuery collection of matching nodes
|
||||
* @param {String} str the search string
|
||||
* @param {Array} res a collection of objects represeing the matching nodes
|
||||
* @plugin search
|
||||
*/
|
||||
this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches });
|
||||
};
|
||||
/**
|
||||
* used to clear the last search (removes classes and shows all nodes if filtering is on)
|
||||
* @name clear_search()
|
||||
* @plugin search
|
||||
* @trigger clear_search.jstree
|
||||
*/
|
||||
this.clear_search = function () {
|
||||
if(this.settings.search.close_opened_onclear) {
|
||||
this.close_node(this._data.search.opn, 0);
|
||||
}
|
||||
/**
|
||||
* triggered after search is complete
|
||||
* @event
|
||||
* @name clear_search.jstree
|
||||
* @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search)
|
||||
* @param {String} str the search string (the last search string)
|
||||
* @param {Array} res a collection of objects represeing the matching nodes (the result from the last search)
|
||||
* @plugin search
|
||||
*/
|
||||
this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res });
|
||||
if(this._data.search.res.length) {
|
||||
this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) {
|
||||
return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&');
|
||||
}).join(', #')));
|
||||
this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search");
|
||||
}
|
||||
this._data.search.str = "";
|
||||
this._data.search.res = [];
|
||||
this._data.search.opn = [];
|
||||
this._data.search.dom = $();
|
||||
};
|
||||
|
||||
this.redraw_node = function(obj, deep, callback, force_render) {
|
||||
obj = parent.redraw_node.apply(this, arguments);
|
||||
if(obj) {
|
||||
if($.inArray(obj.id, this._data.search.res) !== -1) {
|
||||
var i, j, tmp = null;
|
||||
for(i = 0, j = obj.childNodes.length; i < j; i++) {
|
||||
if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
|
||||
tmp = obj.childNodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(tmp) {
|
||||
tmp.className += ' jstree-search';
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
};
|
||||
|
||||
// helpers
|
||||
(function ($) {
|
||||
// from http://kiro.me/projects/fuse.html
|
||||
$.vakata.search = function(pattern, txt, options) {
|
||||
options = options || {};
|
||||
options = $.extend({}, $.vakata.search.defaults, options);
|
||||
if(options.fuzzy !== false) {
|
||||
options.fuzzy = true;
|
||||
}
|
||||
pattern = options.caseSensitive ? pattern : pattern.toLowerCase();
|
||||
var MATCH_LOCATION = options.location,
|
||||
MATCH_DISTANCE = options.distance,
|
||||
MATCH_THRESHOLD = options.threshold,
|
||||
patternLen = pattern.length,
|
||||
matchmask, pattern_alphabet, match_bitapScore, search;
|
||||
if(patternLen > 32) {
|
||||
options.fuzzy = false;
|
||||
}
|
||||
if(options.fuzzy) {
|
||||
matchmask = 1 << (patternLen - 1);
|
||||
pattern_alphabet = (function () {
|
||||
var mask = {},
|
||||
i = 0;
|
||||
for (i = 0; i < patternLen; i++) {
|
||||
mask[pattern.charAt(i)] = 0;
|
||||
}
|
||||
for (i = 0; i < patternLen; i++) {
|
||||
mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1);
|
||||
}
|
||||
return mask;
|
||||
}());
|
||||
match_bitapScore = function (e, x) {
|
||||
var accuracy = e / patternLen,
|
||||
proximity = Math.abs(MATCH_LOCATION - x);
|
||||
if(!MATCH_DISTANCE) {
|
||||
return proximity ? 1.0 : accuracy;
|
||||
}
|
||||
return accuracy + (proximity / MATCH_DISTANCE);
|
||||
};
|
||||
}
|
||||
search = function (text) {
|
||||
text = options.caseSensitive ? text : text.toLowerCase();
|
||||
if(pattern === text || text.indexOf(pattern) !== -1) {
|
||||
return {
|
||||
isMatch: true,
|
||||
score: 0
|
||||
};
|
||||
}
|
||||
if(!options.fuzzy) {
|
||||
return {
|
||||
isMatch: false,
|
||||
score: 1
|
||||
};
|
||||
}
|
||||
var i, j,
|
||||
textLen = text.length,
|
||||
scoreThreshold = MATCH_THRESHOLD,
|
||||
bestLoc = text.indexOf(pattern, MATCH_LOCATION),
|
||||
binMin, binMid,
|
||||
binMax = patternLen + textLen,
|
||||
lastRd, start, finish, rd, charMatch,
|
||||
score = 1,
|
||||
locations = [];
|
||||
if (bestLoc !== -1) {
|
||||
scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
|
||||
bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
|
||||
if (bestLoc !== -1) {
|
||||
scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
|
||||
}
|
||||
}
|
||||
bestLoc = -1;
|
||||
for (i = 0; i < patternLen; i++) {
|
||||
binMin = 0;
|
||||
binMid = binMax;
|
||||
while (binMin < binMid) {
|
||||
if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
|
||||
binMin = binMid;
|
||||
} else {
|
||||
binMax = binMid;
|
||||
}
|
||||
binMid = Math.floor((binMax - binMin) / 2 + binMin);
|
||||
}
|
||||
binMax = binMid;
|
||||
start = Math.max(1, MATCH_LOCATION - binMid + 1);
|
||||
finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
|
||||
rd = new Array(finish + 2);
|
||||
rd[finish + 1] = (1 << i) - 1;
|
||||
for (j = finish; j >= start; j--) {
|
||||
charMatch = pattern_alphabet[text.charAt(j - 1)];
|
||||
if (i === 0) {
|
||||
rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
|
||||
} else {
|
||||
rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
|
||||
}
|
||||
if (rd[j] & matchmask) {
|
||||
score = match_bitapScore(i, j - 1);
|
||||
if (score <= scoreThreshold) {
|
||||
scoreThreshold = score;
|
||||
bestLoc = j - 1;
|
||||
locations.push(bestLoc);
|
||||
if (bestLoc > MATCH_LOCATION) {
|
||||
start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
|
||||
break;
|
||||
}
|
||||
lastRd = rd;
|
||||
}
|
||||
return {
|
||||
isMatch: bestLoc >= 0,
|
||||
score: score
|
||||
};
|
||||
};
|
||||
return txt === true ? { 'search' : search } : search(txt);
|
||||
};
|
||||
$.vakata.search.defaults = {
|
||||
location : 0,
|
||||
distance : 100,
|
||||
threshold : 0.6,
|
||||
fuzzy : false,
|
||||
caseSensitive : false
|
||||
};
|
||||
}($));
|
||||
|
||||
// include the search plugin by default
|
||||
// $.jstree.defaults.plugins.push("search");
|
||||
}));
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* ### Sort plugin
|
||||
*
|
||||
* Automatically sorts all siblings in the tree according to a sorting function.
|
||||
*/
|
||||
/*globals jQuery, define, exports, require */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.sort', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.sort) { return; }
|
||||
|
||||
/**
|
||||
* the settings function used to sort the nodes.
|
||||
* It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`.
|
||||
* @name $.jstree.defaults.sort
|
||||
* @plugin sort
|
||||
*/
|
||||
$.jstree.defaults.sort = function (a, b) {
|
||||
//return this.get_type(a) === this.get_type(b) ? (this.get_text(a) > this.get_text(b) ? 1 : -1) : this.get_type(a) >= this.get_type(b);
|
||||
return this.get_text(a) > this.get_text(b) ? 1 : -1;
|
||||
};
|
||||
$.jstree.plugins.sort = function (options, parent) {
|
||||
this.bind = function () {
|
||||
parent.bind.call(this);
|
||||
this.element
|
||||
.on("model.jstree", $.proxy(function (e, data) {
|
||||
this.sort(data.parent, true);
|
||||
}, this))
|
||||
.on("rename_node.jstree create_node.jstree", $.proxy(function (e, data) {
|
||||
this.sort(data.parent || data.node.parent, false);
|
||||
this.redraw_node(data.parent || data.node.parent, true);
|
||||
}, this))
|
||||
.on("move_node.jstree copy_node.jstree", $.proxy(function (e, data) {
|
||||
this.sort(data.parent, false);
|
||||
this.redraw_node(data.parent, true);
|
||||
}, this));
|
||||
};
|
||||
/**
|
||||
* used to sort a node's children
|
||||
* @private
|
||||
* @name sort(obj [, deep])
|
||||
* @param {mixed} obj the node
|
||||
* @param {Boolean} deep if set to `true` nodes are sorted recursively.
|
||||
* @plugin sort
|
||||
* @trigger search.jstree
|
||||
*/
|
||||
this.sort = function (obj, deep) {
|
||||
var i, j;
|
||||
obj = this.get_node(obj);
|
||||
if(obj && obj.children && obj.children.length) {
|
||||
obj.children.sort($.proxy(this.settings.sort, this));
|
||||
if(deep) {
|
||||
for(i = 0, j = obj.children_d.length; i < j; i++) {
|
||||
this.sort(obj.children_d[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// include the sort plugin by default
|
||||
// $.jstree.defaults.plugins.push("sort");
|
||||
}));
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* ### State plugin
|
||||
*
|
||||
* Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc)
|
||||
*/
|
||||
/*globals jQuery, define, exports, require */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.state', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.state) { return; }
|
||||
|
||||
var to = false;
|
||||
/**
|
||||
* stores all defaults for the state plugin
|
||||
* @name $.jstree.defaults.state
|
||||
* @plugin state
|
||||
*/
|
||||
$.jstree.defaults.state = {
|
||||
/**
|
||||
* A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`.
|
||||
* @name $.jstree.defaults.state.key
|
||||
* @plugin state
|
||||
*/
|
||||
key : 'jstree',
|
||||
/**
|
||||
* A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`.
|
||||
* @name $.jstree.defaults.state.events
|
||||
* @plugin state
|
||||
*/
|
||||
events : 'changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree',
|
||||
/**
|
||||
* Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire.
|
||||
* @name $.jstree.defaults.state.ttl
|
||||
* @plugin state
|
||||
*/
|
||||
ttl : false,
|
||||
/**
|
||||
* A function that will be executed prior to restoring state with one argument - the state object. Can be used to clear unwanted parts of the state.
|
||||
* @name $.jstree.defaults.state.filter
|
||||
* @plugin state
|
||||
*/
|
||||
filter : false
|
||||
};
|
||||
$.jstree.plugins.state = function (options, parent) {
|
||||
this.bind = function () {
|
||||
parent.bind.call(this);
|
||||
var bind = $.proxy(function () {
|
||||
this.element.on(this.settings.state.events, $.proxy(function () {
|
||||
if(to) { clearTimeout(to); }
|
||||
to = setTimeout($.proxy(function () { this.save_state(); }, this), 100);
|
||||
}, this));
|
||||
/**
|
||||
* triggered when the state plugin is finished restoring the state (and immediately after ready if there is no state to restore).
|
||||
* @event
|
||||
* @name state_ready.jstree
|
||||
* @plugin state
|
||||
*/
|
||||
this.trigger('state_ready');
|
||||
}, this);
|
||||
this.element
|
||||
.on("ready.jstree", $.proxy(function (e, data) {
|
||||
this.element.one("restore_state.jstree", bind);
|
||||
if(!this.restore_state()) { bind(); }
|
||||
}, this));
|
||||
};
|
||||
/**
|
||||
* save the state
|
||||
* @name save_state()
|
||||
* @plugin state
|
||||
*/
|
||||
this.save_state = function () {
|
||||
var st = { 'state' : this.get_state(), 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) };
|
||||
$.vakata.storage.set(this.settings.state.key, JSON.stringify(st));
|
||||
};
|
||||
/**
|
||||
* restore the state from the user's computer
|
||||
* @name restore_state()
|
||||
* @plugin state
|
||||
*/
|
||||
this.restore_state = function () {
|
||||
var k = $.vakata.storage.get(this.settings.state.key);
|
||||
if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } }
|
||||
if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; }
|
||||
if(!!k && k.state) { k = k.state; }
|
||||
if(!!k && $.isFunction(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); }
|
||||
if(!!k) {
|
||||
this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); });
|
||||
this.set_state(k);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* clear the state on the user's computer
|
||||
* @name clear_state()
|
||||
* @plugin state
|
||||
*/
|
||||
this.clear_state = function () {
|
||||
return $.vakata.storage.del(this.settings.state.key);
|
||||
};
|
||||
};
|
||||
|
||||
(function ($, undefined) {
|
||||
$.vakata.storage = {
|
||||
// simply specifying the functions in FF throws an error
|
||||
set : function (key, val) { return window.localStorage.setItem(key, val); },
|
||||
get : function (key) { return window.localStorage.getItem(key); },
|
||||
del : function (key) { return window.localStorage.removeItem(key); }
|
||||
};
|
||||
}($));
|
||||
|
||||
// include the state plugin by default
|
||||
// $.jstree.defaults.plugins.push("state");
|
||||
}));
|
||||
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* ### Types plugin
|
||||
*
|
||||
* Makes it possible to add predefined types for groups of nodes, which make it possible to easily control nesting rules and icon for each group.
|
||||
*/
|
||||
/*globals jQuery, define, exports, require */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.types', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.types) { return; }
|
||||
|
||||
/**
|
||||
* An object storing all types as key value pairs, where the key is the type name and the value is an object that could contain following keys (all optional).
|
||||
*
|
||||
* * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
|
||||
* * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited.
|
||||
* * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits.
|
||||
* * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme.
|
||||
* * `li_attr` an object of values which will be used to add HTML attributes on the resulting LI DOM node (merged with the node's own data)
|
||||
* * `a_attr` an object of values which will be used to add HTML attributes on the resulting A DOM node (merged with the node's own data)
|
||||
*
|
||||
* There are two predefined types:
|
||||
*
|
||||
* * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
|
||||
* * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
|
||||
*
|
||||
* @name $.jstree.defaults.types
|
||||
* @plugin types
|
||||
*/
|
||||
$.jstree.defaults.types = {
|
||||
'default' : {}
|
||||
};
|
||||
$.jstree.defaults.types[$.jstree.root] = {};
|
||||
|
||||
$.jstree.plugins.types = function (options, parent) {
|
||||
this.init = function (el, options) {
|
||||
var i, j;
|
||||
if(options && options.types && options.types['default']) {
|
||||
for(i in options.types) {
|
||||
if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) {
|
||||
for(j in options.types['default']) {
|
||||
if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
|
||||
options.types[i][j] = options.types['default'][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parent.init.call(this, el, options);
|
||||
this._model.data[$.jstree.root].type = $.jstree.root;
|
||||
};
|
||||
this.refresh = function (skip_loading, forget_state) {
|
||||
parent.refresh.call(this, skip_loading, forget_state);
|
||||
this._model.data[$.jstree.root].type = $.jstree.root;
|
||||
};
|
||||
this.bind = function () {
|
||||
this.element
|
||||
.on('model.jstree', $.proxy(function (e, data) {
|
||||
var m = this._model.data,
|
||||
dpc = data.nodes,
|
||||
t = this.settings.types,
|
||||
i, j, c = 'default', k;
|
||||
for(i = 0, j = dpc.length; i < j; i++) {
|
||||
c = 'default';
|
||||
if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
|
||||
c = m[dpc[i]].original.type;
|
||||
}
|
||||
if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
|
||||
c = m[dpc[i]].data.jstree.type;
|
||||
}
|
||||
m[dpc[i]].type = c;
|
||||
if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
|
||||
m[dpc[i]].icon = t[c].icon;
|
||||
}
|
||||
if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') {
|
||||
for (k in t[c].li_attr) {
|
||||
if (t[c].li_attr.hasOwnProperty(k)) {
|
||||
if (k === 'id') {
|
||||
continue;
|
||||
}
|
||||
else if (m[dpc[i]].li_attr[k] === undefined) {
|
||||
m[dpc[i]].li_attr[k] = t[c].li_attr[k];
|
||||
}
|
||||
else if (k === 'class') {
|
||||
m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') {
|
||||
for (k in t[c].a_attr) {
|
||||
if (t[c].a_attr.hasOwnProperty(k)) {
|
||||
if (k === 'id') {
|
||||
continue;
|
||||
}
|
||||
else if (m[dpc[i]].a_attr[k] === undefined) {
|
||||
m[dpc[i]].a_attr[k] = t[c].a_attr[k];
|
||||
}
|
||||
else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') {
|
||||
m[dpc[i]].a_attr['href'] = t[c].a_attr['href'];
|
||||
}
|
||||
else if (k === 'class') {
|
||||
m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m[$.jstree.root].type = $.jstree.root;
|
||||
}, this));
|
||||
parent.bind.call(this);
|
||||
};
|
||||
this.get_json = function (obj, options, flat) {
|
||||
var i, j,
|
||||
m = this._model.data,
|
||||
opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
|
||||
tmp = parent.get_json.call(this, obj, opt, flat);
|
||||
if(tmp === false) { return false; }
|
||||
if($.isArray(tmp)) {
|
||||
for(i = 0, j = tmp.length; i < j; i++) {
|
||||
tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default";
|
||||
if(options && options.no_id) {
|
||||
delete tmp[i].id;
|
||||
if(tmp[i].li_attr && tmp[i].li_attr.id) {
|
||||
delete tmp[i].li_attr.id;
|
||||
}
|
||||
if(tmp[i].a_attr && tmp[i].a_attr.id) {
|
||||
delete tmp[i].a_attr.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
|
||||
if(options && options.no_id) {
|
||||
tmp = this._delete_ids(tmp);
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
};
|
||||
this._delete_ids = function (tmp) {
|
||||
if($.isArray(tmp)) {
|
||||
for(var i = 0, j = tmp.length; i < j; i++) {
|
||||
tmp[i] = this._delete_ids(tmp[i]);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
delete tmp.id;
|
||||
if(tmp.li_attr && tmp.li_attr.id) {
|
||||
delete tmp.li_attr.id;
|
||||
}
|
||||
if(tmp.a_attr && tmp.a_attr.id) {
|
||||
delete tmp.a_attr.id;
|
||||
}
|
||||
if(tmp.children && $.isArray(tmp.children)) {
|
||||
tmp.children = this._delete_ids(tmp.children);
|
||||
}
|
||||
return tmp;
|
||||
};
|
||||
this.check = function (chk, obj, par, pos, more) {
|
||||
if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
|
||||
obj = obj && obj.id ? obj : this.get_node(obj);
|
||||
par = par && par.id ? par : this.get_node(par);
|
||||
var m = obj && obj.id ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j;
|
||||
m = m && m._model && m._model.data ? m._model.data : null;
|
||||
switch(chk) {
|
||||
case "create_node":
|
||||
case "move_node":
|
||||
case "copy_node":
|
||||
if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
|
||||
tmp = this.get_rules(par);
|
||||
if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
|
||||
this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_01', 'reason' : 'max_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
|
||||
return false;
|
||||
}
|
||||
if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) {
|
||||
this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_02', 'reason' : 'valid_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
|
||||
return false;
|
||||
}
|
||||
if(m && obj.children_d && obj.parents) {
|
||||
d = 0;
|
||||
for(i = 0, j = obj.children_d.length; i < j; i++) {
|
||||
d = Math.max(d, m[obj.children_d[i]].parents.length);
|
||||
}
|
||||
d = d - obj.parents.length + 1;
|
||||
}
|
||||
if(d <= 0 || d === undefined) { d = 1; }
|
||||
do {
|
||||
if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
|
||||
this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_03', 'reason' : 'max_depth prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
|
||||
return false;
|
||||
}
|
||||
par = this.get_node(par.parent);
|
||||
tmp = this.get_rules(par);
|
||||
d++;
|
||||
} while(par);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* used to retrieve the type settings object for a node
|
||||
* @name get_rules(obj)
|
||||
* @param {mixed} obj the node to find the rules for
|
||||
* @return {Object}
|
||||
* @plugin types
|
||||
*/
|
||||
this.get_rules = function (obj) {
|
||||
obj = this.get_node(obj);
|
||||
if(!obj) { return false; }
|
||||
var tmp = this.get_type(obj, true);
|
||||
if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
|
||||
if(tmp.max_children === undefined) { tmp.max_children = -1; }
|
||||
if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
|
||||
return tmp;
|
||||
};
|
||||
/**
|
||||
* used to retrieve the type string or settings object for a node
|
||||
* @name get_type(obj [, rules])
|
||||
* @param {mixed} obj the node to find the rules for
|
||||
* @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
|
||||
* @return {String|Object}
|
||||
* @plugin types
|
||||
*/
|
||||
this.get_type = function (obj, rules) {
|
||||
obj = this.get_node(obj);
|
||||
return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
|
||||
};
|
||||
/**
|
||||
* used to change a node's type
|
||||
* @name set_type(obj, type)
|
||||
* @param {mixed} obj the node to change
|
||||
* @param {String} type the new type
|
||||
* @plugin types
|
||||
*/
|
||||
this.set_type = function (obj, type) {
|
||||
var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a;
|
||||
if($.isArray(obj)) {
|
||||
obj = obj.slice();
|
||||
for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
|
||||
this.set_type(obj[t1], type);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
t = this.settings.types;
|
||||
obj = this.get_node(obj);
|
||||
if(!t[type] || !obj) { return false; }
|
||||
d = this.get_node(obj, true);
|
||||
if (d && d.length) {
|
||||
a = d.children('.jstree-anchor');
|
||||
}
|
||||
old_type = obj.type;
|
||||
old_icon = this.get_icon(obj);
|
||||
obj.type = type;
|
||||
if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
|
||||
this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
|
||||
}
|
||||
|
||||
// remove old type props
|
||||
if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') {
|
||||
for (k in t[old_type].li_attr) {
|
||||
if (t[old_type].li_attr.hasOwnProperty(k)) {
|
||||
if (k === 'id') {
|
||||
continue;
|
||||
}
|
||||
else if (k === 'class') {
|
||||
m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], '');
|
||||
if (d) { d.removeClass(t[old_type].li_attr[k]); }
|
||||
}
|
||||
else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) {
|
||||
m[obj.id].li_attr[k] = null;
|
||||
if (d) { d.removeAttr(k); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') {
|
||||
for (k in t[old_type].a_attr) {
|
||||
if (t[old_type].a_attr.hasOwnProperty(k)) {
|
||||
if (k === 'id') {
|
||||
continue;
|
||||
}
|
||||
else if (k === 'class') {
|
||||
m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], '');
|
||||
if (a) { a.removeClass(t[old_type].a_attr[k]); }
|
||||
}
|
||||
else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) {
|
||||
if (k === 'href') {
|
||||
m[obj.id].a_attr[k] = '#';
|
||||
if (a) { a.attr('href', '#'); }
|
||||
}
|
||||
else {
|
||||
delete m[obj.id].a_attr[k];
|
||||
if (a) { a.removeAttr(k); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add new props
|
||||
if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') {
|
||||
for (k in t[type].li_attr) {
|
||||
if (t[type].li_attr.hasOwnProperty(k)) {
|
||||
if (k === 'id') {
|
||||
continue;
|
||||
}
|
||||
else if (m[obj.id].li_attr[k] === undefined) {
|
||||
m[obj.id].li_attr[k] = t[type].li_attr[k];
|
||||
if (d) {
|
||||
if (k === 'class') {
|
||||
d.addClass(t[type].li_attr[k]);
|
||||
}
|
||||
else {
|
||||
d.attr(k, t[type].li_attr[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (k === 'class') {
|
||||
m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class'];
|
||||
if (d) { d.addClass(t[type].li_attr[k]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') {
|
||||
for (k in t[type].a_attr) {
|
||||
if (t[type].a_attr.hasOwnProperty(k)) {
|
||||
if (k === 'id') {
|
||||
continue;
|
||||
}
|
||||
else if (m[obj.id].a_attr[k] === undefined) {
|
||||
m[obj.id].a_attr[k] = t[type].a_attr[k];
|
||||
if (a) {
|
||||
if (k === 'class') {
|
||||
a.addClass(t[type].a_attr[k]);
|
||||
}
|
||||
else {
|
||||
a.attr(k, t[type].a_attr[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (k === 'href' && m[obj.id].a_attr[k] === '#') {
|
||||
m[obj.id].a_attr['href'] = t[type].a_attr['href'];
|
||||
if (a) { a.attr('href', t[type].a_attr['href']); }
|
||||
}
|
||||
else if (k === 'class') {
|
||||
m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class'];
|
||||
if (a) { a.addClass(t[type].a_attr[k]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
// include the types plugin by default
|
||||
// $.jstree.defaults.plugins.push("types");
|
||||
}));
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* ### Unique plugin
|
||||
*
|
||||
* Enforces that no nodes with the same name can coexist as siblings.
|
||||
*/
|
||||
/*globals jQuery, define, exports, require */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.unique', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.unique) { return; }
|
||||
|
||||
/**
|
||||
* stores all defaults for the unique plugin
|
||||
* @name $.jstree.defaults.unique
|
||||
* @plugin unique
|
||||
*/
|
||||
$.jstree.defaults.unique = {
|
||||
/**
|
||||
* Indicates if the comparison should be case sensitive. Default is `false`.
|
||||
* @name $.jstree.defaults.unique.case_sensitive
|
||||
* @plugin unique
|
||||
*/
|
||||
case_sensitive : false,
|
||||
/**
|
||||
* A callback executed in the instance's scope when a new node is created and the name is already taken, the two arguments are the conflicting name and the counter. The default will produce results like `New node (2)`.
|
||||
* @name $.jstree.defaults.unique.duplicate
|
||||
* @plugin unique
|
||||
*/
|
||||
duplicate : function (name, counter) {
|
||||
return name + ' (' + counter + ')';
|
||||
}
|
||||
};
|
||||
|
||||
$.jstree.plugins.unique = function (options, parent) {
|
||||
this.check = function (chk, obj, par, pos, more) {
|
||||
if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
|
||||
obj = obj && obj.id ? obj : this.get_node(obj);
|
||||
par = par && par.id ? par : this.get_node(par);
|
||||
if(!par || !par.children) { return true; }
|
||||
var n = chk === "rename_node" ? pos : obj.text,
|
||||
c = [],
|
||||
s = this.settings.unique.case_sensitive,
|
||||
m = this._model.data, i, j;
|
||||
for(i = 0, j = par.children.length; i < j; i++) {
|
||||
c.push(s ? m[par.children[i]].text : m[par.children[i]].text.toLowerCase());
|
||||
}
|
||||
if(!s) { n = n.toLowerCase(); }
|
||||
switch(chk) {
|
||||
case "delete_node":
|
||||
return true;
|
||||
case "rename_node":
|
||||
i = ($.inArray(n, c) === -1 || (obj.text && obj.text[ s ? 'toString' : 'toLowerCase']() === n));
|
||||
if(!i) {
|
||||
this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_01', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
|
||||
}
|
||||
return i;
|
||||
case "create_node":
|
||||
i = ($.inArray(n, c) === -1);
|
||||
if(!i) {
|
||||
this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_04', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
|
||||
}
|
||||
return i;
|
||||
case "copy_node":
|
||||
i = ($.inArray(n, c) === -1);
|
||||
if(!i) {
|
||||
this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_02', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
|
||||
}
|
||||
return i;
|
||||
case "move_node":
|
||||
i = ( (obj.parent === par.id && (!more || !more.is_multi)) || $.inArray(n, c) === -1);
|
||||
if(!i) {
|
||||
this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_03', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this.create_node = function (par, node, pos, callback, is_loaded) {
|
||||
if(!node || node.text === undefined) {
|
||||
if(par === null) {
|
||||
par = $.jstree.root;
|
||||
}
|
||||
par = this.get_node(par);
|
||||
if(!par) {
|
||||
return parent.create_node.call(this, par, node, pos, callback, is_loaded);
|
||||
}
|
||||
pos = pos === undefined ? "last" : pos;
|
||||
if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
|
||||
return parent.create_node.call(this, par, node, pos, callback, is_loaded);
|
||||
}
|
||||
if(!node) { node = {}; }
|
||||
var tmp, n, dpc, i, j, m = this._model.data, s = this.settings.unique.case_sensitive, cb = this.settings.unique.duplicate;
|
||||
n = tmp = this.get_string('New node');
|
||||
dpc = [];
|
||||
for(i = 0, j = par.children.length; i < j; i++) {
|
||||
dpc.push(s ? m[par.children[i]].text : m[par.children[i]].text.toLowerCase());
|
||||
}
|
||||
i = 1;
|
||||
while($.inArray(s ? n : n.toLowerCase(), dpc) !== -1) {
|
||||
n = cb.call(this, tmp, (++i)).toString();
|
||||
}
|
||||
node.text = n;
|
||||
}
|
||||
return parent.create_node.call(this, par, node, pos, callback, is_loaded);
|
||||
};
|
||||
};
|
||||
|
||||
// include the unique plugin by default
|
||||
// $.jstree.defaults.plugins.push("unique");
|
||||
}));
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* ### Wholerow plugin
|
||||
*
|
||||
* Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers.
|
||||
*/
|
||||
/*globals jQuery, define, exports, require */
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.wholerow', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery, jQuery.jstree);
|
||||
}
|
||||
}(function ($, jstree, undefined) {
|
||||
"use strict";
|
||||
|
||||
if($.jstree.plugins.wholerow) { return; }
|
||||
|
||||
var div = document.createElement('DIV');
|
||||
div.setAttribute('unselectable','on');
|
||||
div.setAttribute('role','presentation');
|
||||
div.className = 'jstree-wholerow';
|
||||
div.innerHTML = ' ';
|
||||
$.jstree.plugins.wholerow = function (options, parent) {
|
||||
this.bind = function () {
|
||||
parent.bind.call(this);
|
||||
|
||||
this.element
|
||||
.on('ready.jstree set_state.jstree', $.proxy(function () {
|
||||
this.hide_dots();
|
||||
}, this))
|
||||
.on("init.jstree loading.jstree ready.jstree", $.proxy(function () {
|
||||
//div.style.height = this._data.core.li_height + 'px';
|
||||
this.get_container_ul().addClass('jstree-wholerow-ul');
|
||||
}, this))
|
||||
.on("deselect_all.jstree", $.proxy(function (e, data) {
|
||||
this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
|
||||
}, this))
|
||||
.on("changed.jstree", $.proxy(function (e, data) {
|
||||
this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
|
||||
var tmp = false, i, j;
|
||||
for(i = 0, j = data.selected.length; i < j; i++) {
|
||||
tmp = this.get_node(data.selected[i], true);
|
||||
if(tmp && tmp.length) {
|
||||
tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
|
||||
}
|
||||
}
|
||||
}, this))
|
||||
.on("open_node.jstree", $.proxy(function (e, data) {
|
||||
this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
|
||||
}, this))
|
||||
.on("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) {
|
||||
if(e.type === "hover_node" && this.is_disabled(data.node)) { return; }
|
||||
this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
|
||||
}, this))
|
||||
.on("contextmenu.jstree", ".jstree-wholerow", $.proxy(function (e) {
|
||||
if (this._data.contextmenu) {
|
||||
e.preventDefault();
|
||||
var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
|
||||
$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp);
|
||||
}
|
||||
}, this))
|
||||
/*!
|
||||
.on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) {
|
||||
if(e.target === e.currentTarget) {
|
||||
var a = $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor");
|
||||
e.target = a[0];
|
||||
a.trigger(e);
|
||||
}
|
||||
})
|
||||
*/
|
||||
.on("click.jstree", ".jstree-wholerow", function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
|
||||
$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
|
||||
})
|
||||
.on("dblclick.jstree", ".jstree-wholerow", function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
|
||||
$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
|
||||
})
|
||||
.on("click.jstree", ".jstree-leaf > .jstree-ocl", $.proxy(function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
|
||||
$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
|
||||
}, this))
|
||||
.on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", $.proxy(function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
if(!this.is_disabled(e.currentTarget)) {
|
||||
this.hover_node(e.currentTarget);
|
||||
}
|
||||
return false;
|
||||
}, this))
|
||||
.on("mouseleave.jstree", ".jstree-node", $.proxy(function (e) {
|
||||
this.dehover_node(e.currentTarget);
|
||||
}, this));
|
||||
};
|
||||
this.teardown = function () {
|
||||
if(this.settings.wholerow) {
|
||||
this.element.find(".jstree-wholerow").remove();
|
||||
}
|
||||
parent.teardown.call(this);
|
||||
};
|
||||
this.redraw_node = function(obj, deep, callback, force_render) {
|
||||
obj = parent.redraw_node.apply(this, arguments);
|
||||
if(obj) {
|
||||
var tmp = div.cloneNode(true);
|
||||
//tmp.style.height = this._data.core.li_height + 'px';
|
||||
if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; }
|
||||
if(this._data.core.focused && this._data.core.focused === obj.id) { tmp.className += ' jstree-wholerow-hovered'; }
|
||||
obj.insertBefore(tmp, obj.childNodes[0]);
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
};
|
||||
// include the wholerow plugin by default
|
||||
// $.jstree.defaults.plugins.push("wholerow");
|
||||
}));
|
||||
601
mayan/apps/cabinets/static/cabinets/packages/jstree/src/misc.js
Normal file
@@ -0,0 +1 @@
|
||||
}));
|
||||
@@ -0,0 +1,93 @@
|
||||
/*global jQuery */
|
||||
// wrap in IIFE and pass jQuery as $
|
||||
(function ($, undefined) {
|
||||
"use strict";
|
||||
|
||||
// some private plugin stuff if needed
|
||||
var private_var = null;
|
||||
|
||||
// extending the defaults
|
||||
$.jstree.defaults.sample = {
|
||||
sample_option : 'sample_val'
|
||||
};
|
||||
|
||||
// the actual plugin code
|
||||
$.jstree.plugins.sample = function (options, parent) {
|
||||
// own function
|
||||
this.sample_function = function (arg) {
|
||||
// you can chain this method if needed and available
|
||||
if(parent.sample_function) { parent.sample_function.call(this, arg); }
|
||||
};
|
||||
|
||||
// *SPECIAL* FUNCTIONS
|
||||
this.init = function (el, options) {
|
||||
// do not forget parent
|
||||
parent.init.call(this, el, options);
|
||||
};
|
||||
// bind events if needed
|
||||
this.bind = function () {
|
||||
// call parent function first
|
||||
parent.bind.call(this);
|
||||
// do(stuff);
|
||||
};
|
||||
// unbind events if needed (all in jquery namespace are taken care of by the core)
|
||||
this.unbind = function () {
|
||||
// do(stuff);
|
||||
// call parent function last
|
||||
parent.unbind.call(this);
|
||||
};
|
||||
this.teardown = function () {
|
||||
// do not forget parent
|
||||
parent.teardown.call(this);
|
||||
};
|
||||
// state management - get and restore
|
||||
this.get_state = function () {
|
||||
// always get state from parent first
|
||||
var state = parent.get_state.call(this);
|
||||
// add own stuff to state
|
||||
state.sample = { 'var' : 'val' };
|
||||
return state;
|
||||
};
|
||||
this.set_state = function (state, callback) {
|
||||
// only process your part if parent returns true
|
||||
// there will be multiple times with false
|
||||
if(parent.set_state.call(this, state, callback)) {
|
||||
// check the key you set above
|
||||
if(state.sample) {
|
||||
// do(stuff); // like calling this.sample_function(state.sample.var);
|
||||
// remove your part of the state, call again and RETURN FALSE, the next cycle will be TRUE
|
||||
delete state.sample;
|
||||
this.set_state(state, callback);
|
||||
return false;
|
||||
}
|
||||
// return true if your state is gone (cleared in the previous step)
|
||||
return true;
|
||||
}
|
||||
// parent was false - return false too
|
||||
return false;
|
||||
};
|
||||
// node transportation
|
||||
this.get_json = function (obj, options, flat) {
|
||||
// get the node from the parent
|
||||
var tmp = parent.get_json.call(this, obj, options, flat), i, j;
|
||||
if($.isArray(tmp)) {
|
||||
for(i = 0, j = tmp.length; i < j; i++) {
|
||||
tmp[i].sample = 'value';
|
||||
}
|
||||
}
|
||||
else {
|
||||
tmp.sample = 'value';
|
||||
}
|
||||
// return the original / modified node
|
||||
return tmp;
|
||||
};
|
||||
};
|
||||
|
||||
// attach to document ready if needed
|
||||
$(function () {
|
||||
// do(stuff);
|
||||
});
|
||||
|
||||
// you can include the sample plugin in all instances by default
|
||||
$.jstree.defaults.plugins.push("sample");
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,88 @@
|
||||
// base jstree
|
||||
.jstree-node, .jstree-children, .jstree-container-ul { display:block; margin:0; padding:0; list-style-type:none; list-style-image:none; }
|
||||
.jstree-node { white-space:nowrap; }
|
||||
.jstree-anchor { display:inline-block; color:black; white-space:nowrap; padding:0 4px 0 1px; margin:0; vertical-align:top; }
|
||||
.jstree-anchor:focus { outline:0; }
|
||||
.jstree-anchor, .jstree-anchor:link, .jstree-anchor:visited, .jstree-anchor:hover, .jstree-anchor:active { text-decoration:none; color:inherit; }
|
||||
.jstree-icon { display:inline-block; text-decoration:none; margin:0; padding:0; vertical-align:top; text-align:center; }
|
||||
.jstree-icon:empty { display:inline-block; text-decoration:none; margin:0; padding:0; vertical-align:top; text-align:center; }
|
||||
.jstree-ocl { cursor:pointer; }
|
||||
.jstree-leaf > .jstree-ocl { cursor:default; }
|
||||
.jstree .jstree-open > .jstree-children { display:block; }
|
||||
.jstree .jstree-closed > .jstree-children,
|
||||
.jstree .jstree-leaf > .jstree-children { display:none; }
|
||||
.jstree-anchor > .jstree-themeicon { margin-right:2px; }
|
||||
.jstree-no-icons .jstree-themeicon,
|
||||
.jstree-anchor > .jstree-themeicon-hidden { display:none; }
|
||||
.jstree-hidden, .jstree-node.jstree-hidden { display:none; }
|
||||
|
||||
// base jstree rtl
|
||||
.jstree-rtl {
|
||||
.jstree-anchor { padding:0 1px 0 4px; }
|
||||
.jstree-anchor > .jstree-themeicon { margin-left:2px; margin-right:0; }
|
||||
.jstree-node { margin-left:0; }
|
||||
.jstree-container-ul > .jstree-node { margin-right:0; }
|
||||
}
|
||||
|
||||
// base jstree wholerow
|
||||
.jstree-wholerow-ul {
|
||||
position:relative;
|
||||
display:inline-block;
|
||||
min-width:100%;
|
||||
.jstree-leaf > .jstree-ocl { cursor:pointer; }
|
||||
.jstree-anchor, .jstree-icon { position:relative; }
|
||||
.jstree-wholerow { width:100%; cursor:pointer; position:absolute; left:0; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none; }
|
||||
}
|
||||
|
||||
// base contextmenu
|
||||
.vakata-context {
|
||||
display:none;
|
||||
&, ul { margin:0; padding:2px; position:absolute; background:#f5f5f5; border:1px solid #979797; box-shadow:2px 2px 2px #999999; }
|
||||
ul { list-style:none; left:100%; margin-top:-2.7em; margin-left:-4px; }
|
||||
.vakata-context-right ul { left:auto; right:100%; margin-left:auto; margin-right:-4px; }
|
||||
li {
|
||||
list-style:none;
|
||||
> a {
|
||||
display:block; padding:0 2em 0 2em; text-decoration:none; width:auto; color:black; white-space:nowrap; line-height:2.4em; text-shadow:1px 1px 0 white; border-radius:1px;
|
||||
&:hover { position:relative; background-color:#e8eff7; box-shadow:0 0 2px #0a6aa1; }
|
||||
&.vakata-context-parent { background-image:url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw=="); background-position:right center; background-repeat:no-repeat; }
|
||||
}
|
||||
> a:focus { outline:0; }
|
||||
}
|
||||
.vakata-context-hover > a { position:relative; background-color:#e8eff7; box-shadow:0 0 2px #0a6aa1; }
|
||||
.vakata-context-separator {
|
||||
> a, > a:hover { background:white; border:0; border-top:1px solid #e2e3e3; height:1px; min-height:1px; max-height:1px; padding:0; margin:0 0 0 2.4em; border-left:1px solid #e0e0e0; text-shadow:0 0 0 transparent; box-shadow:0 0 0 transparent; border-radius:0; }
|
||||
}
|
||||
.vakata-contextmenu-disabled {
|
||||
a, a:hover { color:silver; background-color:transparent; border:0; box-shadow:0 0 0; }
|
||||
}
|
||||
li > a {
|
||||
> i { text-decoration:none; display:inline-block; width:2.4em; height:2.4em; background:transparent; margin:0 0 0 -2em; vertical-align:top; text-align:center; line-height:2.4em; }
|
||||
> i:empty { width:2.4em; line-height:2.4em; }
|
||||
.vakata-contextmenu-sep { display:inline-block; width:1px; height:2.4em; background:white; margin:0 0.5em 0 0; border-left:1px solid #e2e3e3; }
|
||||
}
|
||||
.vakata-contextmenu-shortcut { font-size:0.8em; color:silver; opacity:0.5; display:none; }
|
||||
}
|
||||
.vakata-context-rtl {
|
||||
ul { left:auto; right:100%; margin-left:auto; margin-right:-4px; }
|
||||
li > a.vakata-context-parent { background-image:url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7"); background-position:left center; background-repeat:no-repeat; }
|
||||
.vakata-context-separator > a { margin:0 2.4em 0 0; border-left:0; border-right:1px solid #e2e3e3;}
|
||||
.vakata-context-left ul { right:auto; left:100%; margin-left:-4px; margin-right:auto; }
|
||||
li > a {
|
||||
> i { margin:0 -2em 0 0; }
|
||||
.vakata-contextmenu-sep { margin:0 0 0 0.5em; border-left-color:white; background:#e2e3e3; }
|
||||
}
|
||||
}
|
||||
|
||||
// base drag'n'drop
|
||||
#jstree-marker { position: absolute; top:0; left:0; margin:-5px 0 0 0; padding:0; border-right:0; border-top:5px solid transparent; border-bottom:5px solid transparent; border-left:5px solid; width:0; height:0; font-size:0; line-height:0; }
|
||||
#jstree-dnd {
|
||||
line-height:16px;
|
||||
margin:0;
|
||||
padding:4px;
|
||||
.jstree-icon,
|
||||
.jstree-copy { display:inline-block; text-decoration:none; margin:0 2px 0 0; padding:0; width:16px; height:16px; }
|
||||
.jstree-ok { background:green; }
|
||||
.jstree-er { background:red; }
|
||||
.jstree-copy { margin:0 2px 0 2px; }
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,50 @@
|
||||
/* jsTree default dark theme */
|
||||
@theme-name: default-dark;
|
||||
@hovered-bg-color: #555;
|
||||
@hovered-shadow-color: #555;
|
||||
@disabled-color: #666666;
|
||||
@disabled-bg-color: #333333;
|
||||
@clicked-bg-color: #5fa2db;
|
||||
@clicked-shadow-color: #666666;
|
||||
@clicked-gradient-color-1: #5fa2db;
|
||||
@clicked-gradient-color-2: #5fa2db;
|
||||
@search-result-color: #ffffff;
|
||||
@mobile-wholerow-bg-color: #333333;
|
||||
@mobile-wholerow-shadow: #111111;
|
||||
@mobile-wholerow-bordert: #666;
|
||||
@mobile-wholerow-borderb: #000;
|
||||
@responsive: true;
|
||||
@image-path: "";
|
||||
@base-height: 40px;
|
||||
|
||||
@import "../mixins.less";
|
||||
@import "../base.less";
|
||||
@import "../main.less";
|
||||
|
||||
.jstree-@{theme-name} {
|
||||
background:#333;
|
||||
.jstree-anchor { color:#999; text-shadow:1px 1px 0 rgba(0,0,0,0.5); }
|
||||
.jstree-clicked, .jstree-checked { color:white; }
|
||||
.jstree-hovered { color:white; }
|
||||
#jstree-marker& {
|
||||
border-left-color:#999;
|
||||
background:transparent;
|
||||
}
|
||||
.jstree-anchor > .jstree-icon { opacity:0.75; }
|
||||
.jstree-clicked > .jstree-icon,
|
||||
.jstree-hovered > .jstree-icon,
|
||||
.jstree-checked > .jstree-icon { opacity:1; }
|
||||
}
|
||||
// theme variants
|
||||
.jstree-@{theme-name} {
|
||||
&.jstree-rtl .jstree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); }
|
||||
&.jstree-rtl .jstree-last { background:transparent; }
|
||||
}
|
||||
.jstree-@{theme-name}-small {
|
||||
&.jstree-rtl .jstree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg=="); }
|
||||
&.jstree-rtl .jstree-last { background:transparent; }
|
||||
}
|
||||
.jstree-@{theme-name}-large {
|
||||
&.jstree-rtl .jstree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg=="); }
|
||||
&.jstree-rtl .jstree-last { background:transparent; }
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,22 @@
|
||||
/* jsTree default theme */
|
||||
@theme-name: default;
|
||||
@hovered-bg-color: #e7f4f9;
|
||||
@hovered-shadow-color: #cccccc;
|
||||
@disabled-color: #666666;
|
||||
@disabled-bg-color: #efefef;
|
||||
@clicked-bg-color: #beebff;
|
||||
@clicked-shadow-color: #999999;
|
||||
@clicked-gradient-color-1: #beebff;
|
||||
@clicked-gradient-color-2: #a8e4ff;
|
||||
@search-result-color: #8b0000;
|
||||
@mobile-wholerow-bg-color: #ebebeb;
|
||||
@mobile-wholerow-shadow: #666666;
|
||||
@mobile-wholerow-bordert: rgba(255,255,255,0.7);
|
||||
@mobile-wholerow-borderb: rgba(64,64,64,0.2);
|
||||
@responsive: true;
|
||||
@image-path: "";
|
||||
@base-height: 40px;
|
||||
|
||||
@import "../mixins.less";
|
||||
@import "../base.less";
|
||||
@import "../main.less";
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,77 @@
|
||||
.jstree-@{theme-name} {
|
||||
.jstree-node,
|
||||
.jstree-icon { background-repeat:no-repeat; background-color:transparent; }
|
||||
.jstree-anchor,
|
||||
.jstree-animated,
|
||||
.jstree-wholerow { transition:background-color 0.15s, box-shadow 0.15s; }
|
||||
.jstree-hovered { background:@hovered-bg-color; border-radius:2px; box-shadow:inset 0 0 1px @hovered-shadow-color; }
|
||||
.jstree-context { background:@hovered-bg-color; border-radius:2px; box-shadow:inset 0 0 1px @hovered-shadow-color; }
|
||||
.jstree-clicked { background:@clicked-bg-color; border-radius:2px; box-shadow:inset 0 0 1px @clicked-shadow-color; }
|
||||
.jstree-no-icons .jstree-anchor > .jstree-themeicon { display:none; }
|
||||
.jstree-disabled {
|
||||
background:transparent; color:@disabled-color;
|
||||
&.jstree-hovered { background:transparent; box-shadow:none; }
|
||||
&.jstree-clicked { background:@disabled-bg-color; }
|
||||
> .jstree-icon { opacity:0.8; filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale"); /* Firefox 10+ */ filter: gray; /* IE6-9 */ -webkit-filter: grayscale(100%); /* Chrome 19+ & Safari 6+ */ }
|
||||
}
|
||||
// search
|
||||
.jstree-search { font-style:italic; color:@search-result-color; font-weight:bold; }
|
||||
// checkboxes
|
||||
.jstree-no-checkboxes .jstree-checkbox { display:none !important; }
|
||||
&.jstree-checkbox-no-clicked {
|
||||
.jstree-clicked {
|
||||
background:transparent;
|
||||
box-shadow:none;
|
||||
&.jstree-hovered { background:@hovered-bg-color; }
|
||||
}
|
||||
> .jstree-wholerow-ul .jstree-wholerow-clicked {
|
||||
background:transparent;
|
||||
&.jstree-wholerow-hovered { background:@hovered-bg-color; }
|
||||
}
|
||||
}
|
||||
// stripes
|
||||
> .jstree-striped { min-width:100%; display:inline-block; background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat; }
|
||||
// wholerow
|
||||
> .jstree-wholerow-ul .jstree-hovered,
|
||||
> .jstree-wholerow-ul .jstree-clicked { background:transparent; box-shadow:none; border-radius:0; }
|
||||
.jstree-wholerow { -moz-box-sizing:border-box; -webkit-box-sizing:border-box; box-sizing:border-box; }
|
||||
.jstree-wholerow-hovered { background:@hovered-bg-color; }
|
||||
.jstree-wholerow-clicked { .gradient(@clicked-gradient-color-1, @clicked-gradient-color-2); }
|
||||
}
|
||||
|
||||
// theme variants
|
||||
.jstree-@{theme-name} {
|
||||
.jstree-theme(24px, "@{image-path}32px.png", 32px);
|
||||
&.jstree-rtl .jstree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); }
|
||||
&.jstree-rtl .jstree-last { background:transparent; }
|
||||
}
|
||||
.jstree-@{theme-name}-small {
|
||||
.jstree-theme(18px, "@{image-path}32px.png", 32px);
|
||||
&.jstree-rtl .jstree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg=="); }
|
||||
&.jstree-rtl .jstree-last { background:transparent; }
|
||||
}
|
||||
.jstree-@{theme-name}-large {
|
||||
.jstree-theme(32px, "@{image-path}32px.png", 32px);
|
||||
&.jstree-rtl .jstree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg=="); }
|
||||
&.jstree-rtl .jstree-last { background:transparent; }
|
||||
}
|
||||
|
||||
// mobile theme attempt
|
||||
@media (max-width: 768px) {
|
||||
#jstree-dnd.jstree-dnd-responsive when (@responsive = true) {
|
||||
line-height:@base-height; font-weight:bold; font-size:1.1em; text-shadow:1px 1px white;
|
||||
> i { background:transparent; width:@base-height; height:@base-height; }
|
||||
> .jstree-ok { background-image:url("@{image-path}@{base-height}.png"); background-position:0 -(@base-height * 5); background-size:(@base-height * 3) (@base-height * 6); }
|
||||
> .jstree-er { background-image:url("@{image-path}@{base-height}.png"); background-position:-(@base-height * 1) -(@base-height * 5); background-size:(@base-height * 3) (@base-height * 6); }
|
||||
}
|
||||
#jstree-marker.jstree-dnd-responsive when (@responsive = true) {
|
||||
border-left-width:10px;
|
||||
border-top-width:10px;
|
||||
border-bottom-width:10px;
|
||||
margin-top:-10px;
|
||||
}
|
||||
}
|
||||
|
||||
.jstree-@{theme-name}-responsive when (@responsive = true) {
|
||||
@import "responsive.less";
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
.gradient (@color1; @color2) {
|
||||
background:@color1;
|
||||
background: -webkit-linear-gradient(top, @color1 0%,@color2 100%);
|
||||
background: linear-gradient(to bottom, @color1 0%,@color2 100%);
|
||||
}
|
||||
|
||||
.jstree-theme (@base-height, @image, @image-height) {
|
||||
@correction: (@image-height - @base-height) / 2;
|
||||
|
||||
.jstree-node { min-height:@base-height; line-height:@base-height; margin-left:@base-height; min-width:@base-height; }
|
||||
.jstree-anchor { line-height:@base-height; height:@base-height; }
|
||||
.jstree-icon { width:@base-height; height:@base-height; line-height:@base-height; }
|
||||
.jstree-icon:empty { width:@base-height; height:@base-height; line-height:@base-height; }
|
||||
&.jstree-rtl .jstree-node { margin-right:@base-height; }
|
||||
.jstree-wholerow { height:@base-height; }
|
||||
|
||||
.jstree-node,
|
||||
.jstree-icon { background-image:url("@{image}"); }
|
||||
.jstree-node { background-position:-(@image-height * 9 + @correction) -@correction; background-repeat:repeat-y; }
|
||||
.jstree-last { background:transparent; }
|
||||
|
||||
.jstree-open > .jstree-ocl { background-position:-(@image-height * 4 + @correction) -@correction; }
|
||||
.jstree-closed > .jstree-ocl { background-position:-(@image-height * 3 + @correction) -@correction; }
|
||||
.jstree-leaf > .jstree-ocl { background-position:-(@image-height * 2 + @correction) -@correction; }
|
||||
|
||||
.jstree-themeicon { background-position:-(@image-height * 8 + @correction) -@correction; }
|
||||
|
||||
> .jstree-no-dots {
|
||||
.jstree-node,
|
||||
.jstree-leaf > .jstree-ocl { background:transparent; }
|
||||
.jstree-open > .jstree-ocl { background-position:-(@image-height * 1 + @correction) -@correction; }
|
||||
.jstree-closed > .jstree-ocl { background-position:-@correction -@correction; }
|
||||
}
|
||||
|
||||
.jstree-disabled {
|
||||
background:transparent;
|
||||
&.jstree-hovered {
|
||||
background:transparent;
|
||||
}
|
||||
&.jstree-clicked {
|
||||
background:#efefef;
|
||||
}
|
||||
}
|
||||
|
||||
.jstree-checkbox {
|
||||
background-position:-(@image-height * 5 + @correction) -@correction;
|
||||
&:hover { background-position:-(@image-height * 5 + @correction) -(@image-height * 1 + @correction); }
|
||||
}
|
||||
|
||||
&.jstree-checkbox-selection .jstree-clicked, .jstree-checked {
|
||||
> .jstree-checkbox {
|
||||
background-position:-(@image-height * 7 + @correction) -@correction;
|
||||
&:hover { background-position:-(@image-height * 7 + @correction) -(@image-height * 1 + @correction); }
|
||||
}
|
||||
}
|
||||
.jstree-anchor {
|
||||
> .jstree-undetermined {
|
||||
background-position:-(@image-height * 6 + @correction) -@correction;
|
||||
&:hover {
|
||||
background-position:-(@image-height * 6 + @correction) -(@image-height * 1 + @correction);
|
||||
}
|
||||
}
|
||||
}
|
||||
.jstree-checkbox-disabled { opacity:0.8; filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale"); /* Firefox 10+ */ filter: gray; /* IE6-9 */ -webkit-filter: grayscale(100%); /* Chrome 19+ & Safari 6+ */ }
|
||||
|
||||
> .jstree-striped { background-size:auto (@base-height * 2); }
|
||||
|
||||
&.jstree-rtl {
|
||||
.jstree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); background-position: 100% 1px; background-repeat:repeat-y; }
|
||||
.jstree-last { background:transparent; }
|
||||
.jstree-open > .jstree-ocl { background-position:-(@image-height * 4 + @correction) -(@image-height * 1 + @correction); }
|
||||
.jstree-closed > .jstree-ocl { background-position:-(@image-height * 3 + @correction) -(@image-height * 1 + @correction); }
|
||||
.jstree-leaf > .jstree-ocl { background-position:-(@image-height * 2 + @correction) -(@image-height * 1 + @correction); }
|
||||
> .jstree-no-dots {
|
||||
.jstree-node,
|
||||
.jstree-leaf > .jstree-ocl { background:transparent; }
|
||||
.jstree-open > .jstree-ocl { background-position:-(@image-height * 1 + @correction) -(@image-height * 1 + @correction); }
|
||||
.jstree-closed > .jstree-ocl { background-position:-@correction -(@image-height * 1 + @correction); }
|
||||
}
|
||||
}
|
||||
.jstree-themeicon-custom { background-color:transparent; background-image:none; background-position:0 0; }
|
||||
|
||||
> .jstree-container-ul .jstree-loading > .jstree-ocl { background:url("@{image-path}throbber.gif") center center no-repeat; }
|
||||
|
||||
.jstree-file { background:url("@{image}") -(@image-height * 3 + @correction) -(@image-height * 2 + @correction) no-repeat; }
|
||||
.jstree-folder { background:url("@{image}") -(@image-height * 8 + @correction) -(@correction) no-repeat; }
|
||||
|
||||
> .jstree-container-ul > .jstree-node { margin-left:0; margin-right:0; }
|
||||
|
||||
// drag'n'drop
|
||||
#jstree-dnd& {
|
||||
line-height:@base-height; padding:0 4px;
|
||||
.jstree-ok,
|
||||
.jstree-er { background-image:url("@{image-path}32px.png"); background-repeat:no-repeat; background-color:transparent; }
|
||||
i { background:transparent; width:@base-height; height:@base-height; line-height:@base-height; }
|
||||
.jstree-ok { background-position: -(@correction) -(@image-height * 2 + @correction); }
|
||||
.jstree-er { background-position: -(@image-height * 1 + @correction) -(@image-height * 2 + @correction); }
|
||||
}
|
||||
|
||||
// ellipsis
|
||||
.jstree-ellipsis { overflow: hidden; }
|
||||
// base height + PADDINGS!
|
||||
.jstree-ellipsis .jstree-anchor { width: calc(100% ~"-" (@base-height + 5px)); text-overflow: ellipsis; overflow: hidden; }
|
||||
.jstree-ellipsis.jstree-no-icons .jstree-anchor { width: calc(100% ~"-" 5px); }
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
@media (max-width: 768px) {
|
||||
// background image
|
||||
.jstree-icon { background-image:url("@{image-path}@{base-height}.png"); }
|
||||
|
||||
.jstree-node,
|
||||
.jstree-leaf > .jstree-ocl { background:transparent; }
|
||||
|
||||
.jstree-node { min-height:@base-height; line-height:@base-height; margin-left:@base-height; min-width:@base-height; white-space:nowrap; }
|
||||
.jstree-anchor { line-height:@base-height; height:@base-height; }
|
||||
.jstree-icon, .jstree-icon:empty { width:@base-height; height:@base-height; line-height:@base-height; }
|
||||
|
||||
> .jstree-container-ul > .jstree-node { margin-left:0; }
|
||||
&.jstree-rtl .jstree-node { margin-left:0; margin-right:@base-height; background:transparent; }
|
||||
&.jstree-rtl .jstree-container-ul > .jstree-node { margin-right:0; }
|
||||
|
||||
.jstree-ocl,
|
||||
.jstree-themeicon,
|
||||
.jstree-checkbox { background-size:(@base-height * 3) (@base-height * 6); }
|
||||
.jstree-leaf > .jstree-ocl,
|
||||
&.jstree-rtl .jstree-leaf > .jstree-ocl { background:transparent; }
|
||||
.jstree-open > .jstree-ocl { background-position:0 0px !important; }
|
||||
.jstree-closed > .jstree-ocl { background-position:0 -(@base-height * 1) !important; }
|
||||
&.jstree-rtl .jstree-closed > .jstree-ocl { background-position:-(@base-height * 1) 0px !important; }
|
||||
|
||||
.jstree-themeicon { background-position:-(@base-height * 1) -(@base-height * 1); }
|
||||
|
||||
.jstree-checkbox, .jstree-checkbox:hover { background-position:-(@base-height * 1) -(@base-height * 2); }
|
||||
&.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
|
||||
&.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
|
||||
.jstree-checked > .jstree-checkbox,
|
||||
.jstree-checked > .jstree-checkbox:hover { background-position:0 -(@base-height * 2); }
|
||||
.jstree-anchor > .jstree-undetermined, .jstree-anchor > .jstree-undetermined:hover { background-position:0 -(@base-height * 3); }
|
||||
|
||||
.jstree-anchor { font-weight:bold; font-size:1.1em; text-shadow:1px 1px white; }
|
||||
|
||||
> .jstree-striped { background:transparent; }
|
||||
.jstree-wholerow { border-top:1px solid @mobile-wholerow-bordert; border-bottom:1px solid @mobile-wholerow-borderb; background:@mobile-wholerow-bg-color; height:@base-height; }
|
||||
.jstree-wholerow-hovered { background:@hovered-bg-color; }
|
||||
.jstree-wholerow-clicked { background:@clicked-bg-color; }
|
||||
|
||||
// thanks to PHOTONUI
|
||||
.jstree-children .jstree-last > .jstree-wholerow { box-shadow: inset 0 -6px 3px -5px @mobile-wholerow-shadow; }
|
||||
.jstree-children .jstree-open > .jstree-wholerow { box-shadow: inset 0 6px 3px -5px @mobile-wholerow-shadow; border-top:0; }
|
||||
.jstree-children .jstree-open + .jstree-open { box-shadow:none; }
|
||||
|
||||
// experiment
|
||||
.jstree-node,
|
||||
.jstree-icon,
|
||||
.jstree-node > .jstree-ocl,
|
||||
.jstree-themeicon,
|
||||
.jstree-checkbox { background-image:url("@{image-path}@{base-height}.png"); background-size:(@base-height * 3) (@base-height * 6); }
|
||||
|
||||
.jstree-node { background-position:-(@base-height * 2) 0; background-repeat:repeat-y; }
|
||||
.jstree-last { background:transparent; }
|
||||
.jstree-leaf > .jstree-ocl { background-position:-(@base-height * 1) -(@base-height * 3); }
|
||||
.jstree-last > .jstree-ocl { background-position:-(@base-height * 1) -(@base-height * 4); }
|
||||
/*
|
||||
.jstree-open > .jstree-ocl,
|
||||
.jstree-closed > .jstree-ocl { border-radius:20px; background-color:white; }
|
||||
*/
|
||||
|
||||
.jstree-themeicon-custom { background-color:transparent; background-image:none; background-position:0 0; }
|
||||
.jstree-file { background:url("@{image-path}@{base-height}.png") 0 -(@base-height * 4) no-repeat; background-size:(@base-height * 3) (@base-height * 6); }
|
||||
.jstree-folder { background:url("@{image-path}@{base-height}.png") -(@base-height * 1) -(@base-height * 1) no-repeat; background-size:(@base-height * 3) (@base-height * 6); }
|
||||
|
||||
> .jstree-container-ul > .jstree-node { margin-left:0; margin-right:0; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
(function (factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('jstree.checkbox', ['jquery','jstree'], factory);
|
||||
}
|
||||
else if(typeof exports === 'object') {
|
||||
factory(require('jquery'), require('jstree'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($, undefined) {
|
||||
"use strict";
|
||||
if(document.registerElement && Object && Object.create) {
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
proto.createdCallback = function () {
|
||||
var c = { core : {}, plugins : [] }, i;
|
||||
for(i in $.jstree.plugins) {
|
||||
if($.jstree.plugins.hasOwnProperty(i) && this.attributes[i]) {
|
||||
c.plugins.push(i);
|
||||
if(this.getAttribute(i) && JSON.parse(this.getAttribute(i))) {
|
||||
c[i] = JSON.parse(this.getAttribute(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
for(i in $.jstree.defaults.core) {
|
||||
if($.jstree.defaults.core.hasOwnProperty(i) && this.attributes[i]) {
|
||||
c.core[i] = JSON.parse(this.getAttribute(i)) || this.getAttribute(i);
|
||||
}
|
||||
}
|
||||
$(this).jstree(c);
|
||||
};
|
||||
// proto.attributeChangedCallback = function (name, previous, value) { };
|
||||
try {
|
||||
document.registerElement("vakata-jstree", { prototype: proto });
|
||||
} catch(ignore) { }
|
||||
}
|
||||
}));
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Basic Test Suite</title>
|
||||
<!-- Load local QUnit. -->
|
||||
<link rel="stylesheet" href="libs/qunit.css" media="screen">
|
||||
<script src="libs/qunit.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture">this had better work.</div>
|
||||
<!-- Load local lib and tests. -->
|
||||
<script src="test.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* QUnit v1.12.0 - A JavaScript Unit Testing Framework
|
||||
*
|
||||
* http://qunitjs.com
|
||||
*
|
||||
* Copyright 2012 jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*/
|
||||
|
||||
/** Font Family and Sizes */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
||||
#qunit-tests { font-size: smaller; }
|
||||
|
||||
|
||||
/** Resets */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/** Header */
|
||||
|
||||
#qunit-header {
|
||||
padding: 0.5em 0 0.5em 1em;
|
||||
|
||||
color: #8699a4;
|
||||
background-color: #0d3349;
|
||||
|
||||
font-size: 1.5em;
|
||||
line-height: 1em;
|
||||
font-weight: normal;
|
||||
|
||||
border-radius: 5px 5px 0 0;
|
||||
-moz-border-radius: 5px 5px 0 0;
|
||||
-webkit-border-top-right-radius: 5px;
|
||||
-webkit-border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
#qunit-header a {
|
||||
text-decoration: none;
|
||||
color: #c2ccd1;
|
||||
}
|
||||
|
||||
#qunit-header a:hover,
|
||||
#qunit-header a:focus {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar label {
|
||||
display: inline-block;
|
||||
padding: 0 .5em 0 .1em;
|
||||
}
|
||||
|
||||
#qunit-banner {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar {
|
||||
padding: 0.5em 0 0.5em 2em;
|
||||
color: #5E740B;
|
||||
background-color: #eee;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#qunit-userAgent {
|
||||
padding: 0.5em 0 0.5em 2.5em;
|
||||
background-color: #2b81af;
|
||||
color: #fff;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-container {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/** Tests: Pass/Fail */
|
||||
|
||||
#qunit-tests {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests li {
|
||||
padding: 0.4em 0.5em 0.4em 2.5em;
|
||||
border-bottom: 1px solid #fff;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests li strong {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#qunit-tests li a {
|
||||
padding: 0.5em;
|
||||
color: #c2ccd1;
|
||||
text-decoration: none;
|
||||
}
|
||||
#qunit-tests li a:hover,
|
||||
#qunit-tests li a:focus {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#qunit-tests li .runtime {
|
||||
float: right;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.qunit-assert-list {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
}
|
||||
|
||||
.qunit-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests table {
|
||||
border-collapse: collapse;
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
#qunit-tests th {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
padding: 0 .5em 0 0;
|
||||
}
|
||||
|
||||
#qunit-tests td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#qunit-tests pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#qunit-tests del {
|
||||
background-color: #e0f2be;
|
||||
color: #374e0c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-tests ins {
|
||||
background-color: #ffcaca;
|
||||
color: #500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*** Test Counts */
|
||||
|
||||
#qunit-tests b.counts { color: black; }
|
||||
#qunit-tests b.passed { color: #5E740B; }
|
||||
#qunit-tests b.failed { color: #710909; }
|
||||
|
||||
#qunit-tests li li {
|
||||
padding: 5px;
|
||||
background-color: #fff;
|
||||
border-bottom: none;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/*** Passing Styles */
|
||||
|
||||
#qunit-tests li li.pass {
|
||||
color: #3c510c;
|
||||
background-color: #fff;
|
||||
border-left: 10px solid #C6E746;
|
||||
}
|
||||
|
||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
||||
#qunit-tests .pass .test-name { color: #366097; }
|
||||
|
||||
#qunit-tests .pass .test-actual,
|
||||
#qunit-tests .pass .test-expected { color: #999999; }
|
||||
|
||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
||||
|
||||
/*** Failing Styles */
|
||||
|
||||
#qunit-tests li li.fail {
|
||||
color: #710909;
|
||||
background-color: #fff;
|
||||
border-left: 10px solid #EE5757;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#qunit-tests > li:last-child {
|
||||
border-radius: 0 0 5px 5px;
|
||||
-moz-border-radius: 0 0 5px 5px;
|
||||
-webkit-border-bottom-right-radius: 5px;
|
||||
-webkit-border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
||||
#qunit-tests .fail .test-name,
|
||||
#qunit-tests .fail .module-name { color: #000000; }
|
||||
|
||||
#qunit-tests .fail .test-actual { color: #EE5757; }
|
||||
#qunit-tests .fail .test-expected { color: green; }
|
||||
|
||||
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
||||
|
||||
|
||||
/** Result */
|
||||
|
||||
#qunit-testresult {
|
||||
padding: 0.5em 0.5em 0.5em 2.5em;
|
||||
|
||||
color: #2b81af;
|
||||
background-color: #D2E0E6;
|
||||
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
#qunit-testresult .module-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/** Fixture */
|
||||
|
||||
#qunit-fixture {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
test('basic test', function() {
|
||||
expect(1);
|
||||
ok(true, 'this had better work.');
|
||||
});
|
||||
|
||||
|
||||
test('can access the DOM', function() {
|
||||
expect(1);
|
||||
var fixture = document.getElementById('qunit-fixture');
|
||||
equal(fixture.innerText || fixture.textContent, 'this had better work.', 'should be able to access the DOM.');
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Light theme visual tests</title>
|
||||
<link rel="stylesheet" href="./../../../dist/themes/default/style.min.css">
|
||||
<link rel="stylesheet" href="./../../../dist/themes/default-dark/style.min.css">
|
||||
<style>.tree { border:1px solid black; padding:10px; width:300px; margin:20px; float:left; min-height:200px; }</style>
|
||||
</head>
|
||||
<body style="background:white;">
|
||||
<div class="tree" id="empty"></div>
|
||||
<div class="tree" id="core"><ul><li>asdf</li></ul></div>
|
||||
<div class="tree" id="tree">
|
||||
<ul>
|
||||
<li>Node 01
|
||||
<ul>
|
||||
<li>Node</li>
|
||||
<li>Node</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Node 02</li>
|
||||
<li data-jstree='{"opened" : true}'>Node 03
|
||||
<ul>
|
||||
<li>Node</li>
|
||||
<li>Node</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Node 04</li>
|
||||
<li>Node 05</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tree" id="full"><ul><li data-jstree='{ "selected" : true, "type" : "file" }'>full</li><li>asdf</li></ul></div>
|
||||
<div class="tree" id="dark"><ul><li data-jstree='{ "selected" : true, "type" : "file"}'>full</li><li>asdf</li></ul></div>
|
||||
|
||||
<script src="./../../../dist/libs/jquery.js"></script>
|
||||
<script src="./../../../dist/jstree.min.js"></script>
|
||||
<script>
|
||||
$('#empty').jstree();
|
||||
$('#tree, #core').jstree();
|
||||
$('#full').jstree({ plugins : ["checkbox","sort","types","wholerow"], "types" : { "file" : { "icon" : "jstree-file" } } });
|
||||
$('#dark').jstree({ plugins : ["checkbox","sort","types","wholerow"], "core" : { "themes" : { "name" : "default-dark" } }, "types" : { "file" : { "icon" : "jstree-file" } } });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mobile theme visual tests</title>
|
||||
<link rel="stylesheet" href="./../../../dist/themes/default/style.min.css">
|
||||
<link rel="stylesheet" href="./../../../dist/themes/default-dark/style.min.css">
|
||||
<style>.tree { border:1px solid black; padding:10px; width:300px; margin:20px; float:left; min-height:200px; }</style>
|
||||
</head>
|
||||
<body style="background:white;">
|
||||
<div class="tree" id="tree">
|
||||
<ul>
|
||||
<li>Node 01
|
||||
<ul>
|
||||
<li>Node</li>
|
||||
<li>Node</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Node 02</li>
|
||||
<li data-jstree='{"opened" : true}'>Node 03
|
||||
<ul>
|
||||
<li>Node</li>
|
||||
<li>Node</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Node 04</li>
|
||||
<li>Node 05</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tree" id="full"><ul><li data-jstree='{ "selected" : true, "type" : "file" }'>full</li><li>asdf</li></ul></div>
|
||||
<div class="tree" id="dark"><ul><li data-jstree='{ "selected" : true, "type" : "file"}'>full</li><li>asdf</li></ul></div>
|
||||
|
||||
<script src="./../../../dist/libs/jquery.js"></script>
|
||||
<script src="./../../../dist/jstree.min.js"></script>
|
||||
<script>
|
||||
$.jstree.defaults.core.themes.responsive = true;
|
||||
$('#tree').jstree();
|
||||
$('#full').jstree({ plugins : ["checkbox","sort","types","wholerow"], "types" : { "file" : { "icon" : "jstree-file" } } });
|
||||
$('#dark').jstree({ plugins : ["checkbox","sort","types","wholerow"], "core" : { "themes" : { "name" : "default-dark" } }, "types" : { "file" : { "icon" : "jstree-file" } } });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
57
mayan/apps/cabinets/templates/cabinets/cabinet_details.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{% extends 'appearance/base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% load navigation_tags %}
|
||||
|
||||
{% block title %}{% include 'appearance/calculate_form_title.html' %}{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" href="{% static 'cabinets/packages/jstree/dist/themes/default/style.min.css' %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if title %}
|
||||
<h3>{{ title }}</h3>
|
||||
<hr>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-md-4">
|
||||
<h4>{% trans 'Navigation:' %}</h4>
|
||||
<div class="jstree"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-8">
|
||||
{% with document_list as object_list %}
|
||||
{% include 'appearance/generic_list_subtemplate.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'cabinets/packages/jstree/dist/jstree.min.js' %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
var jstreeElement = $('.jstree');
|
||||
jstreeElement
|
||||
.on("select_node.jstree", function (e, data) {
|
||||
if(data.selected.length) {
|
||||
window.location.href=data.instance.get_node(data.selected[0]).data.href;
|
||||
}
|
||||
})
|
||||
.jstree({
|
||||
'core' : {
|
||||
'data' : [
|
||||
{{ jstree_data|safe }}
|
||||
|
||||
],
|
||||
'themes' : {
|
||||
'responsive' : true,
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
0
mayan/apps/cabinets/tests/__init__.py
Normal file
5
mayan/apps/cabinets/tests/literals.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
TEST_CABINET_LABEL = 'test cabinet label'
|
||||
TEST_CABINET_EDITED_LABEL = 'test cabinet edited label'
|
||||
|
||||
241
mayan/apps/cabinets/tests/test_api.py
Normal file
@@ -0,0 +1,241 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from documents.models import DocumentType
|
||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||
from user_management.tests.literals import (
|
||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
|
||||
)
|
||||
|
||||
from ..models import Cabinet
|
||||
|
||||
from .literals import TEST_CABINET_EDITED_LABEL, TEST_CABINET_LABEL
|
||||
|
||||
|
||||
@override_settings(OCR_AUTO_OCR=False)
|
||||
class CabinetAPITestCase(APITestCase):
|
||||
"""
|
||||
Test the cabinet API endpoints
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(CabinetAPITestCase, self).setUp()
|
||||
|
||||
self.admin_user = get_user_model().objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
|
||||
self.client.login(
|
||||
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
|
||||
self.document_type = DocumentType.objects.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document = self.document_type.new_document(
|
||||
file_object=file_object,
|
||||
)
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document_2 = self.document_type.new_document(
|
||||
file_object=file_object,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.document_type.delete()
|
||||
super(CabinetAPITestCase, self).tearDown()
|
||||
|
||||
def test_cabinet_create(self):
|
||||
response = self.client.post(
|
||||
reverse('rest_api:cabinet-list'), {'label': TEST_CABINET_LABEL}
|
||||
)
|
||||
|
||||
cabinet = Cabinet.objects.first()
|
||||
|
||||
self.assertEqual(response.data['id'], cabinet.pk)
|
||||
self.assertEqual(response.data['label'], TEST_CABINET_LABEL)
|
||||
|
||||
self.assertEqual(Cabinet.objects.count(), 1)
|
||||
self.assertEqual(cabinet.label, TEST_CABINET_LABEL)
|
||||
|
||||
def test_cabinet_create_with_single_document(self):
|
||||
response = self.client.post(
|
||||
reverse('rest_api:cabinet-list'), {
|
||||
'label': TEST_CABINET_LABEL, 'documents_pk_list': '{}'.format(
|
||||
self.document.pk
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
cabinet = Cabinet.objects.first()
|
||||
|
||||
self.assertEqual(response.data['id'], cabinet.pk)
|
||||
self.assertEqual(response.data['label'], TEST_CABINET_LABEL)
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), (repr(self.document),)
|
||||
)
|
||||
self.assertEqual(cabinet.label, TEST_CABINET_LABEL)
|
||||
|
||||
def test_cabinet_create_with_multiple_documents(self):
|
||||
response = self.client.post(
|
||||
reverse('rest_api:cabinet-list'), {
|
||||
'label': TEST_CABINET_LABEL,
|
||||
'documents_pk_list': '{},{}'.format(
|
||||
self.document.pk, self.document_2.pk
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
cabinet = Cabinet.objects.first()
|
||||
|
||||
self.assertEqual(response.data['id'], cabinet.pk)
|
||||
self.assertEqual(response.data['label'], TEST_CABINET_LABEL)
|
||||
|
||||
self.assertEqual(Cabinet.objects.count(), 1)
|
||||
|
||||
self.assertEqual(cabinet.label, TEST_CABINET_LABEL)
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), map(
|
||||
repr, (self.document, self.document_2)
|
||||
)
|
||||
)
|
||||
|
||||
def test_cabinet_document_delete(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
self.client.delete(
|
||||
reverse(
|
||||
'rest_api:cabinet-document',
|
||||
args=(cabinet.pk, self.document.pk)
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(cabinet.documents.count(), 0)
|
||||
|
||||
def test_cabinet_document_detail(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'rest_api:cabinet-document',
|
||||
args=(cabinet.pk, self.document.pk)
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(response.data['uuid'], force_text(self.document.uuid))
|
||||
|
||||
def test_cabinet_document_list(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('rest_api:cabinet-document-list', args=(cabinet.pk,))
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
response.data['results'][0]['uuid'], force_text(self.document.uuid)
|
||||
)
|
||||
|
||||
def test_cabinet_delete(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.client.delete(
|
||||
reverse('rest_api:cabinet-detail', args=(cabinet.pk,))
|
||||
)
|
||||
|
||||
self.assertEqual(Cabinet.objects.count(), 0)
|
||||
|
||||
def test_cabinet_edit_via_patch(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.client.patch(
|
||||
reverse('rest_api:cabinet-detail', args=(cabinet.pk,)),
|
||||
{'label': TEST_CABINET_EDITED_LABEL}
|
||||
)
|
||||
|
||||
cabinet.refresh_from_db()
|
||||
|
||||
self.assertEqual(cabinet.label, TEST_CABINET_EDITED_LABEL)
|
||||
|
||||
def test_cabinet_edit_via_put(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.client.put(
|
||||
reverse('rest_api:cabinet-detail', args=(cabinet.pk,)),
|
||||
{'label': TEST_CABINET_EDITED_LABEL}
|
||||
)
|
||||
|
||||
cabinet.refresh_from_db()
|
||||
|
||||
self.assertEqual(cabinet.label, TEST_CABINET_EDITED_LABEL)
|
||||
|
||||
def test_cabinet_add_document(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.client.post(
|
||||
reverse('rest_api:cabinet-document-list', args=(cabinet.pk,)), {
|
||||
'documents_pk_list': '{}'.format(self.document.pk)
|
||||
}
|
||||
)
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), (repr(self.document),)
|
||||
)
|
||||
|
||||
def test_cabinet_add_multiple_documents(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.client.post(
|
||||
reverse('rest_api:cabinet-document-list', args=(cabinet.pk,)), {
|
||||
'documents_pk_list': '{},{}'.format(
|
||||
self.document.pk, self.document_2.pk
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), map(
|
||||
repr, (self.document, self.document_2)
|
||||
)
|
||||
)
|
||||
|
||||
def test_cabinet_list_view(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
Cabinet.objects.create(
|
||||
label=TEST_CABINET_LABEL, parent=cabinet
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('rest_api:cabinet-list')
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['results'][0]['label'], cabinet.label)
|
||||
|
||||
def test_cabinet_remove_document(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
self.client.delete(
|
||||
reverse(
|
||||
'rest_api:cabinet-document', args=(
|
||||
cabinet.pk, self.document.pk
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(cabinet.documents.count(), 0)
|
||||
82
mayan/apps/cabinets/tests/test_models.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import override_settings
|
||||
|
||||
from common.tests import BaseTestCase
|
||||
from documents.models import DocumentType
|
||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||
|
||||
from ..models import Cabinet
|
||||
|
||||
from .literals import TEST_CABINET_LABEL
|
||||
|
||||
|
||||
@override_settings(OCR_AUTO_OCR=False)
|
||||
class CabinetTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super(CabinetTestCase, self).setUp()
|
||||
self.document_type = DocumentType.objects.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document = self.document_type.new_document(
|
||||
file_object=file_object
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.document_type.delete()
|
||||
super(CabinetTestCase, self).tearDown()
|
||||
|
||||
def test_cabinet_creation(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.assertEqual(Cabinet.objects.all().count(), 1)
|
||||
self.assertQuerysetEqual(Cabinet.objects.all(), (repr(cabinet),))
|
||||
|
||||
def test_cabinet_duplicate_creation(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
cabinet_2 = Cabinet(label=TEST_CABINET_LABEL)
|
||||
cabinet_2.validate_unique()
|
||||
cabinet_2.save()
|
||||
|
||||
self.assertEqual(Cabinet.objects.all().count(), 1)
|
||||
self.assertQuerysetEqual(Cabinet.objects.all(), (repr(cabinet),))
|
||||
|
||||
def test_inner_cabinet_creation(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
inner_cabinet = Cabinet.objects.create(
|
||||
parent=cabinet, label=TEST_CABINET_LABEL
|
||||
)
|
||||
|
||||
self.assertEqual(Cabinet.objects.all().count(), 2)
|
||||
self.assertQuerysetEqual(
|
||||
Cabinet.objects.all(), map(repr, (cabinet, inner_cabinet))
|
||||
)
|
||||
|
||||
def test_addition_of_documents(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
self.assertEqual(cabinet.documents.count(), 1)
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), (repr(self.document),)
|
||||
)
|
||||
|
||||
def test_addition_and_deletion_of_documents(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
self.assertEqual(cabinet.documents.count(), 1)
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), (repr(self.document),)
|
||||
)
|
||||
|
||||
cabinet.documents.remove(self.document)
|
||||
|
||||
self.assertEqual(cabinet.documents.count(), 0)
|
||||
self.assertQuerysetEqual(cabinet.documents.all(), ())
|
||||
207
mayan/apps/cabinets/tests/test_views.py
Normal file
@@ -0,0 +1,207 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from documents.permissions import permission_document_view
|
||||
from documents.tests.test_views import GenericDocumentViewTestCase
|
||||
|
||||
from ..models import Cabinet
|
||||
from ..permissions import (
|
||||
permission_cabinet_add_document, permission_cabinet_create,
|
||||
permission_cabinet_delete, permission_cabinet_edit,
|
||||
permission_cabinet_remove_document, permission_cabinet_view
|
||||
)
|
||||
from .literals import TEST_CABINET_LABEL, TEST_CABINET_EDITED_LABEL
|
||||
|
||||
|
||||
class CabinetViewTestCase(GenericDocumentViewTestCase):
|
||||
def setUp(self):
|
||||
super(CabinetViewTestCase, self).setUp()
|
||||
self.login_user()
|
||||
|
||||
def _create_cabinet(self, label):
|
||||
return self.post(
|
||||
'cabinets:cabinet_create', data={
|
||||
'label': TEST_CABINET_LABEL
|
||||
}
|
||||
)
|
||||
|
||||
def test_cabinet_create_view_no_permission(self):
|
||||
response = self._create_cabinet(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.assertEquals(response.status_code, 403)
|
||||
self.assertEqual(Cabinet.objects.count(), 0)
|
||||
|
||||
def test_cabinet_create_view_with_permission(self):
|
||||
self.grant(permission=permission_cabinet_create)
|
||||
|
||||
response = self._create_cabinet(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(Cabinet.objects.count(), 1)
|
||||
self.assertEqual(Cabinet.objects.first().label, TEST_CABINET_LABEL)
|
||||
|
||||
def test_cabinet_create_duplicate_view_with_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
self.grant(permission=permission_cabinet_create)
|
||||
response = self._create_cabinet(label=TEST_CABINET_LABEL)
|
||||
|
||||
# HTTP 200 with error message
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(Cabinet.objects.count(), 1)
|
||||
self.assertEqual(Cabinet.objects.first().pk, cabinet.pk)
|
||||
|
||||
def _delete_cabinet(self, cabinet):
|
||||
return self.post('cabinets:cabinet_delete', args=(cabinet.pk,))
|
||||
|
||||
def test_cabinet_delete_view_no_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
response = self._delete_cabinet(cabinet=cabinet)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertEqual(Cabinet.objects.count(), 1)
|
||||
|
||||
def test_cabinet_delete_view_with_permission(self):
|
||||
self.grant(permission=permission_cabinet_delete)
|
||||
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
response = self._delete_cabinet(cabinet=cabinet)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(Cabinet.objects.count(), 0)
|
||||
|
||||
def _edit_cabinet(self, cabinet, label):
|
||||
return self.post(
|
||||
'cabinets:cabinet_edit', args=(cabinet.pk,), data={
|
||||
'label': label
|
||||
}
|
||||
)
|
||||
|
||||
def test_cabinet_edit_view_no_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
response = self._edit_cabinet(
|
||||
cabinet=cabinet, label=TEST_CABINET_EDITED_LABEL
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
cabinet.refresh_from_db()
|
||||
self.assertEqual(cabinet.label, TEST_CABINET_LABEL)
|
||||
|
||||
def test_cabinet_edit_view_with_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.grant(permission=permission_cabinet_edit)
|
||||
|
||||
response = self._edit_cabinet(
|
||||
cabinet=cabinet, label=TEST_CABINET_EDITED_LABEL
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
cabinet.refresh_from_db()
|
||||
self.assertEqual(cabinet.label, TEST_CABINET_EDITED_LABEL)
|
||||
|
||||
def _add_document_to_cabinet(self, cabinet):
|
||||
return self.post(
|
||||
'cabinets:cabinet_add_document', args=(self.document.pk,), data={
|
||||
'cabinets': cabinet.pk
|
||||
}
|
||||
)
|
||||
|
||||
def test_cabinet_add_document_view_no_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.grant(permission=permission_cabinet_view)
|
||||
|
||||
response = self._add_document_to_cabinet(cabinet=cabinet)
|
||||
|
||||
self.assertContains(
|
||||
response, text='Select a valid choice.', status_code=200
|
||||
)
|
||||
cabinet.refresh_from_db()
|
||||
self.assertEqual(cabinet.documents.count(), 0)
|
||||
|
||||
def test_cabinet_add_document_view_with_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.grant(permission=permission_cabinet_view)
|
||||
self.grant(permission=permission_cabinet_add_document)
|
||||
self.grant(permission=permission_document_view)
|
||||
|
||||
response = self._add_document_to_cabinet(cabinet=cabinet)
|
||||
|
||||
cabinet.refresh_from_db()
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(cabinet.documents.count(), 1)
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), (repr(self.document),)
|
||||
)
|
||||
|
||||
def _add_multiple_documents_to_cabinet(self, cabinet):
|
||||
return self.post(
|
||||
'cabinets:cabinet_add_multiple_documents', data={
|
||||
'id_list': (self.document.pk,), 'cabinets': cabinet.pk
|
||||
}
|
||||
)
|
||||
|
||||
def test_cabinet_add_multiple_documents_view_no_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.grant(permission=permission_cabinet_view)
|
||||
|
||||
response = self._add_multiple_documents_to_cabinet(cabinet=cabinet)
|
||||
|
||||
self.assertContains(
|
||||
response, text='Select a valid choice', status_code=200
|
||||
)
|
||||
cabinet.refresh_from_db()
|
||||
self.assertEqual(cabinet.documents.count(), 0)
|
||||
|
||||
def test_cabinet_add_multiple_documents_view_with_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
self.grant(permission=permission_cabinet_view)
|
||||
self.grant(permission=permission_cabinet_add_document)
|
||||
|
||||
response = self._add_multiple_documents_to_cabinet(cabinet=cabinet)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
cabinet.refresh_from_db()
|
||||
self.assertEqual(cabinet.documents.count(), 1)
|
||||
self.assertQuerysetEqual(
|
||||
cabinet.documents.all(), (repr(self.document),)
|
||||
)
|
||||
|
||||
def _remove_document_from_cabinet(self, cabinet):
|
||||
return self.post(
|
||||
'cabinets:document_cabinet_remove', args=(self.document.pk,),
|
||||
data={
|
||||
'cabinets': (cabinet.pk,),
|
||||
}
|
||||
)
|
||||
|
||||
def test_cabinet_remove_document_view_no_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
response = self._remove_document_from_cabinet(cabinet=cabinet)
|
||||
|
||||
self.assertContains(
|
||||
response, text='Select a valid choice', status_code=200
|
||||
)
|
||||
|
||||
cabinet.refresh_from_db()
|
||||
self.assertEqual(cabinet.documents.count(), 1)
|
||||
|
||||
def test_cabinet_remove_document_view_with_permission(self):
|
||||
cabinet = Cabinet.objects.create(label=TEST_CABINET_LABEL)
|
||||
|
||||
cabinet.documents.add(self.document)
|
||||
|
||||
self.grant(permission=permission_cabinet_remove_document)
|
||||
|
||||
response = self._remove_document_from_cabinet(cabinet=cabinet)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
cabinet.refresh_from_db()
|
||||
self.assertEqual(cabinet.documents.count(), 0)
|
||||
73
mayan/apps/cabinets/urls.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .api_views import (
|
||||
APIDocumentCabinetListView, APICabinetDocumentListView,
|
||||
APICabinetDocumentView, APICabinetListView, APICabinetView
|
||||
)
|
||||
from .views import (
|
||||
DocumentAddToCabinetView, DocumentCabinetListView,
|
||||
DocumentRemoveFromCabinetView, CabinetChildAddView, CabinetCreateView,
|
||||
CabinetDeleteView, CabinetDetailView, CabinetEditView, CabinetListView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^list/$', CabinetListView.as_view(), name='cabinet_list'),
|
||||
url(
|
||||
r'^(?P<pk>\d+)/child/add/$', CabinetChildAddView.as_view(),
|
||||
name='cabinet_child_add'
|
||||
),
|
||||
url(r'^create/$', CabinetCreateView.as_view(), name='cabinet_create'),
|
||||
url(
|
||||
r'^(?P<pk>\d+)/edit/$', CabinetEditView.as_view(), name='cabinet_edit'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>\d+)/delete/$', CabinetDeleteView.as_view(),
|
||||
name='cabinet_delete'
|
||||
),
|
||||
url(r'^(?P<pk>\d+)/$', CabinetDetailView.as_view(), name='cabinet_view'),
|
||||
|
||||
url(
|
||||
r'^document/(?P<pk>\d+)/cabinet/add/$',
|
||||
DocumentAddToCabinetView.as_view(), name='cabinet_add_document'
|
||||
),
|
||||
url(
|
||||
r'^document/multiple/cabinet/add/$',
|
||||
DocumentAddToCabinetView.as_view(),
|
||||
name='cabinet_add_multiple_documents'
|
||||
),
|
||||
url(
|
||||
r'^document/(?P<pk>\d+)/cabinet/remove/$',
|
||||
DocumentRemoveFromCabinetView.as_view(), name='document_cabinet_remove'
|
||||
),
|
||||
url(
|
||||
r'^document/multiple/cabinet/remove/$',
|
||||
DocumentRemoveFromCabinetView.as_view(),
|
||||
name='multiple_document_cabinet_remove'
|
||||
),
|
||||
url(
|
||||
r'^document/(?P<pk>\d+)/cabinet/list/$',
|
||||
DocumentCabinetListView.as_view(), name='document_cabinet_list'
|
||||
),
|
||||
]
|
||||
|
||||
api_urls = [
|
||||
url(
|
||||
r'^cabinets/(?P<pk>[0-9]+)/documents/(?P<document_pk>[0-9]+)/$',
|
||||
APICabinetDocumentView.as_view(), name='cabinet-document'
|
||||
),
|
||||
url(
|
||||
r'^cabinets/(?P<pk>[0-9]+)/documents/$',
|
||||
APICabinetDocumentListView.as_view(), name='cabinet-document-list'
|
||||
),
|
||||
url(
|
||||
r'^cabinets/(?P<pk>[0-9]+)/$', APICabinetView.as_view(),
|
||||
name='cabinet-detail'
|
||||
),
|
||||
url(r'^cabinets/$', APICabinetListView.as_view(), name='cabinet-list'),
|
||||
url(
|
||||
r'^documents/(?P<pk>[0-9]+)/cabinets/$',
|
||||
APIDocumentCabinetListView.as_view(), name='document-cabinet-list'
|
||||
),
|
||||
]
|
||||
348
mayan/apps/cabinets/views.py
Normal file
@@ -0,0 +1,348 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext_lazy as _, ungettext
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from common.views import (
|
||||
MultipleObjectFormActionView, SingleObjectCreateView,
|
||||
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView,
|
||||
TemplateView
|
||||
)
|
||||
from documents.permissions import permission_document_view
|
||||
from documents.models import Document
|
||||
|
||||
from .forms import CabinetListForm
|
||||
from .models import Cabinet
|
||||
from .permissions import (
|
||||
permission_cabinet_add_document, permission_cabinet_create,
|
||||
permission_cabinet_delete, permission_cabinet_edit,
|
||||
permission_cabinet_view, permission_cabinet_remove_document
|
||||
)
|
||||
from .widgets import jstree_data
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CabinetCreateView(SingleObjectCreateView):
|
||||
fields = ('label',)
|
||||
model = Cabinet
|
||||
view_permission = permission_cabinet_create
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'title': _('Create cabinet'),
|
||||
}
|
||||
|
||||
|
||||
class CabinetChildAddView(SingleObjectCreateView):
|
||||
fields = ('label',)
|
||||
model = Cabinet
|
||||
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
If the form is valid, save the associated model.
|
||||
"""
|
||||
self.object = form.save(commit=False)
|
||||
self.object.parent = self.get_object()
|
||||
self.object.save()
|
||||
|
||||
return super(CabinetChildAddView, self).form_valid(form)
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
cabinet = super(CabinetChildAddView, self).get_object(*args, **kwargs)
|
||||
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=permission_cabinet_edit, user=self.request.user,
|
||||
obj=cabinet.get_root()
|
||||
)
|
||||
|
||||
return cabinet
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'title': _(
|
||||
'Add new level to: %s'
|
||||
) % self.get_object().get_full_path(),
|
||||
}
|
||||
|
||||
|
||||
class CabinetDeleteView(SingleObjectDeleteView):
|
||||
model = Cabinet
|
||||
object_permission = permission_cabinet_delete
|
||||
post_action_redirect = reverse_lazy('cabinets:cabinet_list')
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'object': self.get_object(),
|
||||
'title': _('Delete the cabinet: %s?') % self.get_object(),
|
||||
}
|
||||
|
||||
|
||||
class CabinetDetailView(TemplateView):
|
||||
template_name = 'cabinets/cabinet_details.html'
|
||||
|
||||
def get_document_queryset(self):
|
||||
queryset = AccessControlList.objects.filter_by_access(
|
||||
permission=permission_document_view, user=self.request.user,
|
||||
queryset=self.get_object().documents.all()
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super(CabinetDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
cabinet = self.get_object()
|
||||
|
||||
data.update(
|
||||
{
|
||||
'jstree_data': '\n'.join(
|
||||
jstree_data(node=cabinet.get_root(), selected_node=cabinet)
|
||||
),
|
||||
'document_list': self.get_document_queryset(),
|
||||
'hide_links': True,
|
||||
'object': cabinet,
|
||||
'title': _('Details of cabinet: %s') % cabinet.get_full_path(),
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def get_object(self):
|
||||
cabinet = get_object_or_404(Cabinet, pk=self.kwargs['pk'])
|
||||
|
||||
if cabinet.is_root_node():
|
||||
permission_object = cabinet
|
||||
else:
|
||||
permission_object = cabinet.get_root()
|
||||
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=permission_cabinet_view, user=self.request.user,
|
||||
obj=permission_object
|
||||
)
|
||||
|
||||
return cabinet
|
||||
|
||||
|
||||
class CabinetEditView(SingleObjectEditView):
|
||||
fields = ('label',)
|
||||
model = Cabinet
|
||||
object_permission = permission_cabinet_edit
|
||||
post_action_redirect = reverse_lazy('cabinets:cabinet_list')
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'object': self.get_object(),
|
||||
'title': _('Edit cabinet: %s') % self.get_object(),
|
||||
}
|
||||
|
||||
|
||||
class CabinetListView(SingleObjectListView):
|
||||
model = Cabinet
|
||||
object_permission = permission_cabinet_view
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_link': True,
|
||||
'title': _('Cabinets'),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return Cabinet.objects.root_nodes()
|
||||
|
||||
|
||||
class DocumentCabinetListView(CabinetListView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=permission_document_view, user=request.user,
|
||||
obj=self.document
|
||||
)
|
||||
|
||||
return super(DocumentCabinetListView, self).dispatch(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_link': True,
|
||||
'object': self.document,
|
||||
'title': _('Cabinets containing document: %s') % self.document,
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return self.document.document_cabinets().all()
|
||||
|
||||
|
||||
class DocumentAddToCabinetView(MultipleObjectFormActionView):
|
||||
form_class = CabinetListForm
|
||||
model = Document
|
||||
success_message = _(
|
||||
'Add to cabinet request performed on %(count)d document'
|
||||
)
|
||||
success_message_plural = _(
|
||||
'Add to cabinet request performed on %(count)d documents'
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
queryset = self.get_queryset()
|
||||
|
||||
result = {
|
||||
'submit_label': _('Add'),
|
||||
'title': ungettext(
|
||||
'Add document to cabinets',
|
||||
'Add documents to cabinets',
|
||||
queryset.count()
|
||||
)
|
||||
}
|
||||
|
||||
if queryset.count() == 1:
|
||||
result.update(
|
||||
{
|
||||
'object': queryset.first(),
|
||||
'title': _(
|
||||
'Add document "%s" to cabinets'
|
||||
) % queryset.first()
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_form_extra_kwargs(self):
|
||||
queryset = self.get_queryset()
|
||||
result = {
|
||||
'help_text': _(
|
||||
'Cabinets to which the selected documents will be added.'
|
||||
),
|
||||
'permission': permission_cabinet_add_document,
|
||||
'user': self.request.user
|
||||
}
|
||||
|
||||
if queryset.count() == 1:
|
||||
result.update(
|
||||
{
|
||||
'queryset': Cabinet.objects.exclude(
|
||||
pk__in=queryset.first().cabinets.all()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def object_action(self, form, instance):
|
||||
cabinet_membership = instance.cabinets.all()
|
||||
|
||||
for cabinet in form.cleaned_data['cabinets']:
|
||||
AccessControlList.objects.check_access(
|
||||
obj=cabinet, permissions=permission_cabinet_add_document,
|
||||
user=self.request.user
|
||||
)
|
||||
if cabinet in cabinet_membership:
|
||||
messages.warning(
|
||||
self.request, _(
|
||||
'Document: %(document)s is already in '
|
||||
'cabinet: %(cabinet)s.'
|
||||
) % {
|
||||
'document': instance, 'cabinet': cabinet
|
||||
}
|
||||
)
|
||||
else:
|
||||
cabinet.documents.add(instance)
|
||||
messages.success(
|
||||
self.request, _(
|
||||
'Document: %(document)s added to cabinet: '
|
||||
'%(cabinet)s successfully.'
|
||||
) % {
|
||||
'document': instance, 'cabinet': cabinet
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class DocumentRemoveFromCabinetView(MultipleObjectFormActionView):
|
||||
form_class = CabinetListForm
|
||||
model = Document
|
||||
success_message = _(
|
||||
'Remove from cabinet request performed on %(count)d document'
|
||||
)
|
||||
success_message_plural = _(
|
||||
'Remove from cabinet request performed on %(count)d documents'
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
queryset = self.get_queryset()
|
||||
|
||||
result = {
|
||||
'submit_label': _('Remove'),
|
||||
'title': ungettext(
|
||||
'Remove document from cabinets',
|
||||
'Remove documents from cabinets',
|
||||
queryset.count()
|
||||
)
|
||||
}
|
||||
|
||||
if queryset.count() == 1:
|
||||
result.update(
|
||||
{
|
||||
'object': queryset.first(),
|
||||
'title': _(
|
||||
'Remove document "%s" to cabinets'
|
||||
) % queryset.first()
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_form_extra_kwargs(self):
|
||||
queryset = self.get_queryset()
|
||||
result = {
|
||||
'help_text': _(
|
||||
'Cabinets from which the selected documents will be removed.'
|
||||
),
|
||||
'permission': permission_cabinet_remove_document,
|
||||
'user': self.request.user
|
||||
}
|
||||
|
||||
if queryset.count() == 1:
|
||||
result.update(
|
||||
{
|
||||
'queryset': queryset.first().cabinets.all()
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def object_action(self, form, instance):
|
||||
cabinet_membership = instance.cabinets.all()
|
||||
|
||||
for cabinet in form.cleaned_data['cabinets']:
|
||||
AccessControlList.objects.check_access(
|
||||
obj=cabinet, permissions=permission_cabinet_remove_document,
|
||||
user=self.request.user
|
||||
)
|
||||
|
||||
if cabinet not in cabinet_membership:
|
||||
messages.warning(
|
||||
self.request, _(
|
||||
'Document: %(document)s is not in cabinet: '
|
||||
'%(cabinet)s.'
|
||||
) % {
|
||||
'document': instance, 'cabinet': cabinet
|
||||
}
|
||||
)
|
||||
else:
|
||||
cabinet.documents.remove(instance)
|
||||
messages.success(
|
||||
self.request, _(
|
||||
'Document: %(document)s removed from cabinet: '
|
||||
'%(cabinet)s.'
|
||||
) % {
|
||||
'document': instance, 'cabinet': cabinet
|
||||
}
|
||||
)
|
||||
28
mayan/apps/cabinets/widgets.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
def jstree_data(node, selected_node):
|
||||
result = []
|
||||
result.append('{')
|
||||
result.append('"text": "{}",'.format(node.label))
|
||||
result.append(
|
||||
'"state": {{ "opened": true, "selected": {} }},'.format(
|
||||
'true' if node == selected_node else 'false'
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
'"data": {{ "href": "{}" }},'.format(node.get_absolute_url())
|
||||
)
|
||||
|
||||
children = node.get_children().order_by('label',)
|
||||
|
||||
if children:
|
||||
result.append('"children" : [')
|
||||
for child in children:
|
||||
result.extend(jstree_data(node=child, selected_node=selected_node))
|
||||
|
||||
result.append(']')
|
||||
|
||||
result.append('},')
|
||||
|
||||
return result
|
||||
@@ -79,6 +79,7 @@ INSTALLED_APPS = (
|
||||
'smart_settings',
|
||||
'user_management',
|
||||
# Mayan EDMS
|
||||
'cabinets',
|
||||
'checkouts',
|
||||
'document_comments',
|
||||
'document_indexing',
|
||||
|
||||