diff --git a/HISTORY.rst b/HISTORY.rst index 53fb58576c..3282232228 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -21,8 +21,16 @@ document (document stub that has no document version). - Add support for client side caching of document page images. The time the images are cached is controlled by the new setting - DOCUMENTS_PAGE_IMAGE_CACHE_TIME which defaults to 3600 seconds (1 hour). + DOCUMENTS_PAGE_IMAGE_CACHE_TIME which defaults to 31556926 seconds (1 year). - The document quick label selection field now uses a select2 widget. +- Include querystring when force reload of a bare template view. +- Speed up document image fade in reveal. +- Use reseteable timer to ensure more document panels heights are matched. +- Rewrote Mayan's Javascript suite MayanApp into ECMAScript2015. +- Remove use is waitForJQuery. +- Remove code statistics from the documentation. +- Remove the pending work chapter. This is now available in the Wiki: + wiki.mayan-edms.com 3.0.1 (2018-07-08) ================= diff --git a/docs/topics/code_statistics.rst b/docs/topics/code_statistics.rst deleted file mode 100644 index dfe990721f..0000000000 --- a/docs/topics/code_statistics.rst +++ /dev/null @@ -1,662 +0,0 @@ -Code statistics ---------------- - -As of Wed Mar 14 19:31:49 2018 (commit f3ba6ee2cca99acbbb2403d69cc3f922b0f2b0a2 - -App name: acls - - Views - module: mayan/apps/acls/views.py - class based views: 4 - function based views: 0 - - API Views - module: mayan/apps/acls/api_views.py - class based views: 4 - function based views: 0 - - Tests - module: mayan/apps/acls/tests/test_models.py - tests: 8 - module: mayan/apps/acls/tests/test_actions.py - tests: 2 - module: mayan/apps/acls/tests/test_links.py - tests: 4 - module: mayan/apps/acls/tests/test_api.py - tests: 9 - module: mayan/apps/acls/tests/test_views.py - tests: 8 - - -App name: appearance - - Views - No views - - API Views - No API views - - Tests - No tests - - -App name: authentication - - Views - module: mayan/apps/authentication/views.py - class based views: 0 - function based views: 7 - - API Views - No API views - - Tests - module: mayan/apps/authentication/tests/test_views.py - tests: 10 - - -App name: cabinets - - Views - module: mayan/apps/cabinets/views.py - class based views: 9 - function based views: 0 - - API Views - module: mayan/apps/cabinets/api_views.py - class based views: 5 - function based views: 0 - - Tests - module: mayan/apps/cabinets/tests/test_models.py - tests: 5 - module: mayan/apps/cabinets/tests/test_events.py - tests: 2 - module: mayan/apps/cabinets/tests/test_api.py - tests: 13 - module: mayan/apps/cabinets/tests/test_views.py - tests: 15 - - -App name: checkouts - - Views - module: mayan/apps/checkouts/views.py - class based views: 4 - function based views: 0 - - API Views - module: mayan/apps/checkouts/api_views.py - class based views: 2 - function based views: 0 - - Tests - module: mayan/apps/checkouts/tests/test_models.py - tests: 10 - module: mayan/apps/checkouts/tests/test_api.py - tests: 10 - module: mayan/apps/checkouts/tests/test_views.py - tests: 7 - - -App name: common - - Views - module: mayan/apps/common/views.py - class based views: 17 - function based views: 1 - - API Views - module: mayan/apps/common/api_views.py - class based views: 1 - function based views: 0 - - Tests - module: mayan/apps/common/tests/test_api.py - tests: 1 - module: mayan/apps/common/tests/test_commands.py - tests: 1 - module: mayan/apps/common/tests/test_views.py - tests: 3 - - -App name: converter - - Views - module: mayan/apps/converter/views.py - class based views: 4 - function based views: 0 - - API Views - No API views - - Tests - module: mayan/apps/converter/tests/test_classes.py - tests: 6 - module: mayan/apps/converter/tests/test_views.py - tests: 6 - - -App name: django_gpg - - Views - module: mayan/apps/django_gpg/views.py - class based views: 9 - function based views: 0 - - API Views - module: mayan/apps/django_gpg/api_views.py - class based views: 2 - function based views: 0 - - Tests - module: mayan/apps/django_gpg/tests/test_models.py - tests: 15 - module: mayan/apps/django_gpg/tests/test_api.py - tests: 6 - module: mayan/apps/django_gpg/tests/test_views.py - tests: 4 - - -App name: document_comments - - Views - module: mayan/apps/document_comments/views.py - class based views: 3 - function based views: 0 - - API Views - module: mayan/apps/document_comments/api_views.py - class based views: 2 - function based views: 0 - - Tests - module: mayan/apps/document_comments/tests/test_api.py - tests: 8 - - -App name: document_indexing - - Views - module: mayan/apps/document_indexing/views.py - class based views: 13 - function based views: 0 - - API Views - module: mayan/apps/document_indexing/api_views.py - class based views: 6 - function based views: 0 - - Tests - module: mayan/apps/document_indexing/tests/test_models.py - tests: 4 - module: mayan/apps/document_indexing/tests/test_views.py - tests: 8 - - -App name: document_parsing - - Views - module: mayan/apps/document_parsing/views.py - class based views: 6 - function based views: 0 - - API Views - module: mayan/apps/document_parsing/api_views.py - class based views: 1 - function based views: 0 - - Tests - module: mayan/apps/document_parsing/tests/test_parsers.py - tests: 1 - module: mayan/apps/document_parsing/tests/test_events.py - tests: 2 - module: mayan/apps/document_parsing/tests/test_api.py - tests: 2 - module: mayan/apps/document_parsing/tests/test_views.py - tests: 4 - - -App name: document_signatures - - Views - module: mayan/apps/document_signatures/views.py - class based views: 8 - function based views: 0 - - API Views - No API views - - Tests - module: mayan/apps/document_signatures/tests/test_models.py - tests: 13 - module: mayan/apps/document_signatures/tests/test_links.py - tests: 4 - module: mayan/apps/document_signatures/tests/test_views.py - tests: 12 - - -App name: document_states - - Views - module: mayan/apps/document_states/views.py - class based views: 29 - function based views: 0 - - API Views - module: mayan/apps/document_states/api_views.py - class based views: 12 - function based views: 0 - - Tests - module: mayan/apps/document_states/tests/test_models.py - tests: 3 - module: mayan/apps/document_states/tests/test_actions.py - tests: 0 - module: mayan/apps/document_states/tests/test_api.py - tests: 66 - module: mayan/apps/document_states/tests/test_views.py - tests: 33 - - -App name: documents - - Views - No views - - API Views - module: mayan/apps/documents/api_views.py - class based views: 16 - function based views: 0 - - Tests - module: mayan/apps/documents/tests/test_document_type_views.py - tests: 12 - module: mayan/apps/documents/tests/test_duplicated_document_views.py - tests: 4 - module: mayan/apps/documents/tests/test_document_views.py - tests: 31 - module: mayan/apps/documents/tests/test_utils.py - tests: 1 - module: mayan/apps/documents/tests/test_models.py - tests: 12 - module: mayan/apps/documents/tests/test_events.py - tests: 4 - module: mayan/apps/documents/tests/test_widgets.py - tests: 1 - module: mayan/apps/documents/tests/test_links.py - tests: 6 - module: mayan/apps/documents/tests/test_search.py - tests: 4 - module: mayan/apps/documents/tests/test_api.py - tests: 39 - module: mayan/apps/documents/tests/test_deleted_document_views.py - tests: 8 - module: mayan/apps/documents/tests/test_document_version_views.py - tests: 4 - module: mayan/apps/documents/tests/test_document_page_views.py - tests: 2 - - -App name: dynamic_search - - Views - module: mayan/apps/dynamic_search/views.py - class based views: 4 - function based views: 0 - - API Views - module: mayan/apps/dynamic_search/api_views.py - class based views: 3 - function based views: 0 - - Tests - module: mayan/apps/dynamic_search/tests/test_models.py - tests: 4 - module: mayan/apps/dynamic_search/tests/test_api.py - tests: 3 - module: mayan/apps/dynamic_search/tests/test_views.py - tests: 1 - - -App name: events - - Views - module: mayan/apps/events/views.py - class based views: 9 - function based views: 0 - - API Views - module: mayan/apps/events/api_views.py - class based views: 7 - function based views: 0 - - Tests - module: mayan/apps/events/tests/test_api.py - tests: 1 - module: mayan/apps/events/tests/test_views.py - tests: 2 - - -App name: linking - - Views - module: mayan/apps/linking/views.py - class based views: 11 - function based views: 0 - - API Views - module: mayan/apps/linking/api_views.py - class based views: 7 - function based views: 0 - - Tests - module: mayan/apps/linking/tests/test_models.py - tests: 1 - module: mayan/apps/linking/tests/test_api.py - tests: 34 - module: mayan/apps/linking/tests/test_views.py - tests: 8 - - -App name: lock_manager - - Views - No views - - API Views - No API views - - Tests - module: mayan/apps/lock_manager/tests/test_backends.py - tests: 6 - - -App name: mailer - - Views - module: mayan/apps/mailer/views.py - class based views: 10 - function based views: 0 - - API Views - No API views - - Tests - module: mayan/apps/mailer/tests/test_models.py - tests: 5 - module: mayan/apps/mailer/tests/test_views.py - tests: 12 - - -App name: mayan_statistics - - Views - module: mayan/apps/mayan_statistics/views.py - class based views: 4 - function based views: 0 - - API Views - No API views - - Tests - module: mayan/apps/mayan_statistics/tests/test_views.py - tests: 4 - - -App name: metadata - - Views - module: mayan/apps/metadata/views.py - class based views: 9 - function based views: 0 - - API Views - module: mayan/apps/metadata/api_views.py - class based views: 6 - function based views: 0 - - Tests - module: mayan/apps/metadata/tests/test_models.py - tests: 14 - module: mayan/apps/metadata/tests/test_wizard_steps.py - tests: 1 - module: mayan/apps/metadata/tests/test_api.py - tests: 35 - module: mayan/apps/metadata/tests/test_views.py - tests: 8 - - -App name: mimetype - - Views - No views - - API Views - No API views - - Tests - No tests - - -App name: mirroring - - Views - No views - - API Views - No API views - - Tests - No tests - - -App name: motd - - Views - module: mayan/apps/motd/views.py - class based views: 4 - function based views: 0 - - API Views - module: mayan/apps/motd/api_views.py - class based views: 2 - function based views: 0 - - Tests - module: mayan/apps/motd/tests/test_models.py - tests: 4 - module: mayan/apps/motd/tests/test_api.py - tests: 10 - - -App name: navigation - - Views - No views - - API Views - No API views - - Tests - module: mayan/apps/navigation/tests/test_classes.py - tests: 9 - - -App name: ocr - - Views - module: mayan/apps/ocr/views.py - class based views: 8 - function based views: 0 - - API Views - module: mayan/apps/ocr/api_views.py - class based views: 3 - function based views: 0 - - Tests - module: mayan/apps/ocr/tests/test_models.py - tests: 2 - module: mayan/apps/ocr/tests/test_events.py - tests: 2 - module: mayan/apps/ocr/tests/test_api.py - tests: 6 - module: mayan/apps/ocr/tests/test_views.py - tests: 4 - - -App name: permissions - - Views - module: mayan/apps/permissions/views.py - class based views: 7 - function based views: 0 - - API Views - module: mayan/apps/permissions/api_views.py - class based views: 3 - function based views: 0 - - Tests - module: mayan/apps/permissions/tests/test_models.py - tests: 2 - module: mayan/apps/permissions/tests/test_api.py - tests: 17 - module: mayan/apps/permissions/tests/test_views.py - tests: 3 - - -App name: rest_api - - Views - No views - - API Views - module: mayan/apps/rest_api/api_views.py - class based views: 1 - function based views: 0 - - Tests - - -App name: smart_settings - - Views - module: mayan/apps/smart_settings/views.py - class based views: 2 - function based views: 0 - - API Views - No API views - - Tests - module: mayan/apps/smart_settings/tests/test_classes.py - tests: 1 - module: mayan/apps/smart_settings/tests/test_view_permissions.py - tests: 2 - - -App name: sources - - Views - module: mayan/apps/sources/views.py - class based views: 10 - function based views: 0 - - API Views - module: mayan/apps/sources/api_views.py - class based views: 4 - function based views: 0 - - Tests - module: mayan/apps/sources/tests/test_models.py - tests: 3 - module: mayan/apps/sources/tests/test_classes.py - tests: 1 - module: mayan/apps/sources/tests/test_views.py - tests: 15 - - -App name: storage - - Views - No views - - API Views - No API views - - Tests - No tests - - -App name: tags - - Views - module: mayan/apps/tags/views.py - class based views: 8 - function based views: 0 - - API Views - module: mayan/apps/tags/api_views.py - class based views: 5 - function based views: 0 - - Tests - module: mayan/apps/tags/tests/test_models.py - tests: 1 - module: mayan/apps/tags/tests/test_actions.py - tests: 2 - module: mayan/apps/tags/tests/test_wizard_steps.py - tests: 1 - module: mayan/apps/tags/tests/test_api.py - tests: 28 - module: mayan/apps/tags/tests/test_views.py - tests: 18 - - -App name: task_manager - - Views - module: mayan/apps/task_manager/views.py - class based views: 4 - function based views: 0 - - API Views - No API views - - Tests - module: mayan/apps/task_manager/tests/test_views.py - tests: 8 - - -App name: user_management - - Views - module: mayan/apps/user_management/views.py - class based views: 11 - function based views: 0 - - API Views - module: mayan/apps/user_management/api_views.py - class based views: 6 - function based views: 0 - - Tests - module: mayan/apps/user_management/tests/test_api.py - tests: 31 - module: mayan/apps/user_management/tests/test_views.py - tests: 10 - -Totals: - Tests: 753 - Function based API views: 0 - Function based views: 8 - Apps: 34 - Class based API views: 98 - Class based views: 207 - -These are the defined views and tests defined. Actual executable views or -tests could be higher if subclassed. - -Actual test executed: 757 as lock manager app subclasses its 6 tests once diff --git a/docs/topics/index.rst b/docs/topics/index.rst index c937e20d94..df0c8cf2b2 100644 --- a/docs/topics/index.rst +++ b/docs/topics/index.rst @@ -25,6 +25,4 @@ Introductions to all the key parts of Mayan EDMS you'll need to know: file_storage backups upload_wizard - pending_work - code_statistics docker diff --git a/docs/topics/pending_work.rst b/docs/topics/pending_work.rst deleted file mode 100644 index e47d81ebcd..0000000000 --- a/docs/topics/pending_work.rst +++ /dev/null @@ -1,312 +0,0 @@ -============ -Pending work -============ - -Release blockers ----------------- - -These are errors or issues that are blocking a release. - -- None - -Pending work ------------- - -These are tasks that need to be completed but are missing a dependency or -a design decision. As more information is added to each, they should be -converted into a MERC. - -API -~~~ -- User API edit view: Should not be able to add of remove groups without - corresponding group access. -- User group list API get & post views: Should adding a group to an user - via the API return 201 or 200. Currently returns 201. -- Consistent API return code for delete views without access. Some views - return 403 other return 404. -- Update API docstrings. The upgrade to the latest DRF broke all formatting. -- Make views smaller. Much as much as possible to serializers. -- Switch to ViewSets. -- Add API filtering. Example unread notifications. - - -Documents -~~~~~~~~~ -- Navigating to the interactive document page image is not triggering - the document view event. - - -Events -~~~~~~ -- New event: document emailed. - - -Forms processing -~~~~~~~~~~~~~~~~ -- Remove usage of self.cleaned_data. Use self.clean_data instead. - - -Permissions -~~~~~~~~~~~ -- Permission should be reciprocal. Example: To be able to add a tag to a - document, the user must hold the tag add permission for the document - and for the tag to be added. To be able to enable a metadata type to a - document type, the user must hold the metadata add permissions for the - metadata type and for the document type. -- Edit type permissions should only grant the ability to edit the properties - of an object. To modify its relationship with other objects a reciprocal - permission check should be instead. - - -Search -~~~~~~ -- Rename SearchModel.pk to id - - -Sources -~~~~~~~ -- Add ACLs support to sources. -- Provide error message/feedback when scanning from a remote scanner fails. -- Require a permission for document types to avoid a user that has the workflow - creation permission to attach a workflow to a document type they don't - control. -- Research making APIWorkflowDocumentTypeList a subclass of documents.api_views.APIDocumentTypeList -- A POST request to APIWorkflowDocumentTypeList should require some permission - on the document type part to avoid adding non controlled document types - to a new workflow. -- To transition a workflow, the transition permission is only needed for the - workflow. Make it necesary to have the same permission for the document - of document type. -- To view the transition log, the workflow view permission is only needed for the - document. Make it necesary to have the same permission for the workflow or - for the transition and the states. -- Render date time of scanned documents using SANE to a better output - (like document versions). - - -Testing -~~~~~~~ -- Add document test mixin that creates documents types and documents - (to be used in dynamic_search.test_api). -- Update all API tests using self.client to just self. and the HTTP method. -- Add test for searches for each app that uses search. - - -UI - Frontend -~~~~~~~~~~~~~ -- Fix menu not collapsing at the same width of nav parent. -- Move direct CSS style from code into base.css. grep 'style' * -R. Style code in: - - * appearance/templates/appearance/generic_list_items_subtemplate.html - * appearance/templates/appearance/base.html - * appearance/templates/appearance/generic_list_subtemplate.html - * appearance/templates/navigation/generic_link_instance.html - -- Check if location is found in partial and remove it. Avoid circular loading. -- Add location to history after a form submit redirect. - - -UI -~~ -- Shift click select to seletect multiple documents. -- During the document upload wizard and the option to double click to - select document type and submit the form. The purpose is to speed up - the step with less mouse travel since this is a common screen. -- Add metadata to the Menu class to allow UI code to decide where and how - to display each menu. -- Reduce the facet buttons paddings. -- Make facet action button smaller. Reduce margin-bottom. -- Remove previous from the views.py. It is not longer used by the form's cancel button. - - -Workflows -~~~~~~~~~ -- Workflow trigger filters. Example: {{ document.document_type.name = 'invoice' }} - or same UI as the smart links app. Will allow restricting the firing of workflow - actions by an user defined filter criteria. -- Require a permission for document types to avoid a user that has the workflow - creation permission to attach a workflow to a document type they don't - control. -- Research making APIWorkflowDocumentTypeList a subclass of - documents.api_views.APIDocumentTypeList -- A POST request to APIWorkflowDocumentTypeList should require some permission - on the document type part to avoid adding non controlled document types - to a new workflow. -- To transition a workflow, the transition permission is only needed for the - workflow. Make it necesary to have the same permission for the document - of document type. -- To view the transition log, the workflow view permission is only needed for - the document. Make it necesary to have the same permission for the workflow or - for the transition and the states. - - -New features ------------- - - -API -~~~ -- Add converter API -- Document signatures API -- Smart settings API -- Use REST_API's 'mayan_permission_attribute_check' to simplify API views access checking and filtering. -- DRF filtering using the URL query. - - -Converter -~~~~~~~~~ -- New zoom transformation. Resample, not just bigger final size but do - a resize * zoom multiple before. Produces a bigger image or higher - quality than the original. - - -Caching -~~~~~~~ -- Size limited caching. A new model in the common app will keep track - of all cache files. A manager method will be provided that will - return the cache files in other of age to be deleted. - - -Distribution -~~~~~~~~~~~~ -- Python based Javascript package manager. Each app specifies what - library and version needs. The common app (or a new app) will add all - the JS loading lines automatically so that compress can detect them. - - -Metadata -~~~~~~~~ -- Metadata lookup memory. Add a select2 style widget that will query a - new metadata API endpoint that will return all used values so far. -- Metadata validation_choices and parser_choices as static model methods -- Metadata.api as Metadata.utils and manager - - -Search -~~~~~~ -- Add support for highlighting the search results in pages. - - -Settings -~~~~~~~~ -- Database based settings. - - -Sources -~~~~~~~ -- UI improvement for staging folders files selection. GitLab issue. - - -UI -~~ -- Upgrade to Bootstrap 4. -- Upgrade to Flatly 4. -- Better workflow transition UI. Instead of a dropdown show all the - available transitions as buttons. - - -Workflows -~~~~~~~~~ -- Workflow trigger filters. Example: {{ document.document_type.name = 'invoice' }} or same - UI as the smart links app. Will allow restricting the firing of workflow - actions by an user defined filter criteria. -- New workflow action: send email. Subject and content are templates. - - -Other -~~~~~ -- Python based Javascript package manager. Each app specifies what - library and version needs. The common app (or a new app) will add all - the JS loading lines automatically so that compress can detect them. -- When moving documents to the trash update the message to "submitted" - and not "moved" or "deleted" since this is handled by a task queue - and is not immediate and doesn't delete the document. -- When emptying the trash update the message to "submitted" - since this is handled by a task queue and is not immediate. -- New app that allows creating user document filters. Will provide the - same service as the document filters class. Interface can be made - using the template language or the same UI as the smart links. -- Allow add queue metadata that can be exported via a management command. - This will allow creating supervisor templates without all the worker - entries being hardcoded. -- Delete .gitignore files from copied packages. Include .gitignore files - keep compiled or distributable files from being included in the main - repository. Temporary measure until a Javascript library manager is - added. -- Automatically capture license information from installed Javascript - libraries. -- Automatically capture license information from installed Python - packages. -- Update celery to 4.1.0 -- Update to use the new class based views in the authentication app. - password_change(), password_change_done(), password_reset(), - password_reset_done(), password_reset_confirm(), and password_reset_complete() - function-based views are deprecated in favor of new class-based views - PasswordChangeView, PasswordChangeDoneView, PasswordResetView, - PasswordResetDoneView, PasswordResetConfirmView, and PasswordResetCompleteView. -- django.utils.translation.string_concat() is deprecated in favor of - django.utils.text.format_lazy(). string_concat(strings) can be - replaced by format_lazy('{}' x len(strings), strings). - Found in converter/classes.py and metadata/forms.py. -- Fix warnings in preparation for Django 2.0. -- Update all tempfile.mkstemp() to tempfile.mkstemp(dir=setting_temporary_directory.value) -- Get rid of common.utils.get_descriptor only used by common.utils.copyfile -- Update common.utils.copyfile to use only file objects. -- Change metadata label column from CharField to Label -- Start testing to Python 3 compatibility. -- Unify all RelationshipForms into a common class. -- Add test for event subscription view. -- Repeated templates: password_reset_confirm.html and password_reset_form.html -- Remove unused text=get_notification_count from events.links -- Reduce number of languages so dropzone view starts faster. -- In DocumentComment model, rename comment field to text. -- class MultipleInstanceActionMixin is deprecated. Replace views using this with - MultipleObjectFormActionView or MultipleObjectConfirmActionView -- Subscribe to tag events -- Subscribe to workflow events -- Subscribe to workflow state events -- Link to go from event notification to view. Example document workflow notification to document workflow views. -- Find replacement for ``python-gnupg``. Unstable & inconsistent API. -- New app that allows creating user document filters. Will provide the - same service as the document filters class. Interface can be made - using the template language or the same UI as the smart links. -- Allow add queue metadata that can be exported via a management command. - This will allow creating supervisor templates without all the worker - entries being hardcoded. -- Automatically capture license information from installed Javascript - libraries. -- Automatically capture license information from installed Python - packages. -- Finish and merge improved compressed file branch. -- Improve and merge PCL support branch. -- Swtich to self hosted documentation. -- Unify error logs in a common model. Fields: Datetime, namespace, - message, content type, object id. -- Export documents as PDF. Each document image is used to create a PDF - dinamycally. -- Document splitting. Only for PDF files first. A document versions - relationship between the documents has to be designed. -- Manually linking documents. -- Migrate settings/base.py to Django's 1.11 format. -- Rename model methods to use ``get_`` or ``do_`` -- Hunt TODO -- Hunt FIXME -- Convert SETTING_GPG_BACKEND into a setting option similar to converter and converter options. -- Reorganize modelForms Meta class and methods. -- Periodic messages, like notifications. Using an AJAX worker. -- Find replacement for ``python-gnupg``. Unstable & inconsistent API. -- Google docs integration. Upload document from Google Drive. -- Get ``dumpdata`` and ``loaddata`` working flawlessly. Will allow for easier backups, restores and database backend migrations. -- Add generic list ordering. ``django.views.generic.list.MultipleObjectMixin`` (https://docs.djangoproject.com/en/1.8/ref/class-based-views/mixins-multiple-object/#django.views.generic.list.MultipleObjectMixin) now supports an ``ordering`` parameter. -- Add support to convert any document to PDF. https://gitlab.mister-muffin.de/josch/img2pdf -- Add support for combining documents. -- Add support for splitting documents. -- Add new document source to get documents from an URL. -- Add support for metadata mapping files. CSV file containing filename to metadata values mapping, useful for bulk upload and migrations. -- Add support for registering widgets to the home screen. -- Merge mimetype and converter apps. -- Metadata widgets (Date, time, timedate). -- Datatime widget: https://github.com/smalot/bootstrap-datetimepicker -- Add events for document signing app (uploaded detached signateure, signed document, deleted signature) -- A configurable conversion process. Being able to invoke different binaries for file conversion, as opposed to the current libreoffice only solution. -- A tool in the admin interface to mass (re)convert the files (basically the page count function, but then applied on all documents). diff --git a/mayan/apps/appearance/static/appearance/js/base.js b/mayan/apps/appearance/static/appearance/js/base.js index 72f0978ecc..244ac0298a 100644 --- a/mayan/apps/appearance/static/appearance/js/base.js +++ b/mayan/apps/appearance/static/appearance/js/base.js @@ -1,26 +1,12 @@ 'use strict'; -var app = new App(); +// Make it globally available. Used by event.links + +var MayanAppClass = MayanApp; + var partialNavigation = new PartialNavigation({ initialURL: initialURL, + disabledAnchorClasses: ['disabled'], excludeAnchorClasses: ['fancybox', 'new_window', 'non-ajax'], - formBeforeSerializeCallbacks: [App.MultiObjectFormProcess], + formBeforeSerializeCallbacks: [MayanApp.MultiObjectFormProcess], }); - -jQuery(document).ready(function() { - app.setupAutoSubmit(); - app.setupFullHeightResizing(); - app.setupItemsSelector(); - app.setupNavbarCollapse(); - app.setupNewWindowAnchor(); - app.setupAJAXperiodicWorkers(); - partialNavigation.initialize(); -}); - -var afterBaseLoad = function () { - MayanImage.intialize(); - app.doToastrMessages(); - app.resizeFullHeight(); - app.setupSelect2(); - app.setupScrollView(); -} diff --git a/mayan/apps/appearance/static/appearance/js/mayan_app.js b/mayan/apps/appearance/static/appearance/js/mayan_app.js index efcdd0dc38..7dd052e46e 100644 --- a/mayan/apps/appearance/static/appearance/js/mayan_app.js +++ b/mayan/apps/appearance/static/appearance/js/mayan_app.js @@ -1,284 +1,296 @@ 'use strict'; -var App = function (parameters) { - var self = this; +class MayanApp { + constructor (parameters) { + var self = this; - parameters = parameters || {} + parameters = parameters || {} - this.window = $(window); -} + this.window = $(window); + } -// Class methods and variables + // Class methods and variables -App.mayanNotificationBadge = function (options, data) { - // Callback to add the notifications count inside a badge markup - var notifications = data[options.attributeName]; + static mayanNotificationBadge (options, data) { + // Callback to add the notifications count inside a badge markup + var notifications = data[options.attributeName]; - if (notifications > 0) { - // Save the original link text before adding the initial badge markup - if (!options.element.data('mn-saved-text')) { - options.element.data('mn-saved-text', options.element.html()); - } + if (notifications > 0) { + // Save the original link text before adding the initial badge markup + if (!options.element.data('mn-saved-text')) { + options.element.data('mn-saved-text', options.element.html()); + } - options.element.html( - options.element.data('mn-saved-text') + ' ' + notifications + '' - ); - } else { - if (options.element.data('mn-saved-text')) { - // If there is a saved original link text, restore it options.element.html( - options.element.data('mn-saved-text') + options.element.data('mn-saved-text') + ' ' + notifications + '' ); + } else { + if (options.element.data('mn-saved-text')) { + // If there is a saved original link text, restore it + options.element.html( + options.element.data('mn-saved-text') + ); + } } } -} -App.MultiObjectFormProcess = function ($form, options) { - /* - * ajaxForm callback to add the external item checkboxes to the - * submitted form - */ + static MultiObjectFormProcess ($form, options) { + /* + * ajaxForm callback to add the external item checkboxes to the + * submitted form + */ - if ($form.hasClass('form-multi-object-action')) { - // Turn form data into an object - var formArray = $form.serializeArray().reduce(function (obj, item) { - obj[item.name] = item.value; - return obj; - }, {}); + if ($form.hasClass('form-multi-object-action')) { + // Turn form data into an object + var formArray = $form.serializeArray().reduce(function (obj, item) { + obj[item.name] = item.value; + return obj; + }, {}); - // Add all checked checkboxes to the form data - $('.form-multi-object-action-checkbox:checked').each(function() { + // Add all checked checkboxes to the form data + $('.form-multi-object-action-checkbox:checked').each(function() { + var $this = $(this); + formArray[$this.attr('name')] = $this.attr('value'); + }); + + // Set the form data as the data to send + options.data = formArray; + } + } + + static tagSelectionTemplate (tag, container) { + var $tag = $( + ' ' + tag.text + '' + ); + container[0].style.background = tag.element.dataset.color; + return $tag; + } + + static tagResultTemplate (tag) { + if (!tag.element) { return ''; } + var $tag = $( + ' ' + tag.text + '' + ); + return $tag; + } + + // Instance methods + + AJAXperiodicWorker (options) { + var app = this; + + $.ajax({ + complete: function() { + if (!options.app) { + // Preserve the app reference between consecutive calls + options.app = app; + } + setTimeout(options.app.AJAXperiodicWorker, options.interval, options); + }, + success: function(data) { + if (options.callback) { + // Conver the callback string to an actual function + var callbackFunction = window; + + $.each(options.callback.split('.'), function (index, value) { + callbackFunction = callbackFunction[value] + }); + + callbackFunction(options, data); + } else { + options.element.text(data[options.attributeName]); + } + }, + url: options.APIURL + }); + } + + doToastrMessages () { + toastr.options = { + 'closeButton': true, + 'debug': false, + 'newestOnTop': true, + 'positionClass': 'toast-top-right', + 'preventDuplicates': false, + 'onclick': null, + 'showDuration': '300', + 'hideDuration': '1000', + 'timeOut': '5000', + 'extendedTimeOut': '1000', + 'showEasing': 'swing', + 'hideEasing': 'linear', + 'showMethod': 'fadeIn', + 'hideMethod': 'fadeOut' + } + + // Add invisible bootstrap messages to copy the styles to toastr.js + + $('body').append('\ + \ + \ + \ + \ + '); + + // Copy the bootstrap style from the sample alerts to toaster.js via + // dynamic document style tag + + $('head').append('\ + \ + '); + + $.each(DjangoMessages, function (index, value) { + var options = {}; + + if (value.tags === 'error') { + // Error messages persist + options['timeOut'] = 0; + } + if (value.tags === 'warning') { + // Error messages persist + options['timeOut'] = 10000; + } + + toastr[value.tags](value.message, '', options); + }); + } + + initialize () { + this.setupAJAXperiodicWorkers(); + this.setupAutoSubmit(); + this.setupFullHeightResizing(); + this.setupItemsSelector(); + this.setupNavbarCollapse(); + this.setupNewWindowAnchor(); + partialNavigation.initialize(); + } + + setupAJAXperiodicWorkers () { + var app = this; + + $('a[data-apw-url]').each(function() { var $this = $(this); - formArray[$this.attr('name')] = $this.attr('value'); + + app.AJAXperiodicWorker({ + attributeName: $this.data('apw-attribute'), + APIURL: $this.data('apw-url'), + callback: $this.data('apw-callback'), + element: $this, + interval: $this.data('apw-interval'), + }); }); - - // Set the form data as the data to send - options.data = formArray; - } -} - -App.tagSelectionTemplate = function (tag, container) { - var $tag = $( - ' ' + tag.text + '' - ); - container[0].style.background = tag.element.dataset.color; - return $tag; -} - -App.tagResultTemplate = function (tag) { - if (!tag.element) { return ''; } - var $tag = $( - ' ' + tag.text + '' - ); - return $tag; -} - -// Instance methods - -App.prototype.AJAXperiodicWorker = function (options) { - var app = this; - - $.ajax({ - complete: function() { - if (!options.app) { - // Preserve the app reference between consecutive calls - options.app = app; - } - setTimeout(options.app.AJAXperiodicWorker, options.interval, options); - }, - success: function(data) { - if (options.callback) { - // Conver the callback string to an actual function - var callbackFunction = window; - - $.each(options.callback.split('.'), function (index, value) { - callbackFunction = callbackFunction[value] - }); - - callbackFunction(options, data); - } else { - options.element.text(data[options.attributeName]); - } - }, - url: options.APIURL - }); -} - -App.prototype.doToastrMessages = function () { - toastr.options = { - 'closeButton': true, - 'debug': false, - 'newestOnTop': true, - 'positionClass': 'toast-top-right', - 'preventDuplicates': false, - 'onclick': null, - 'showDuration': '300', - 'hideDuration': '1000', - 'timeOut': '5000', - 'extendedTimeOut': '1000', - 'showEasing': 'swing', - 'hideEasing': 'linear', - 'showMethod': 'fadeIn', - 'hideMethod': 'fadeOut' } - // Add invisible bootstrap messages to copy the styles to toastr.js - - $('body').append('\ - \ - \ - \ - \ - '); - - // Copy the bootstrap style from the sample alerts to toaster.js via - // dynamic document style tag - - $('head').append('\ - \ - '); - - $.each(DjangoMessages, function (index, value) { - var options = {}; - - if (value.tags === 'error') { - // Error messages persist - options['timeOut'] = 0; - } - if (value.tags === 'warning') { - // Error messages persist - options['timeOut'] = 10000; - } - - toastr[value.tags](value.message, '', options); - }); -} - -App.prototype.setupAJAXperiodicWorkers = function () { - var app = this; - - $('a[data-apw-url]').each(function() { - var $this = $(this); - - app.AJAXperiodicWorker({ - attributeName: $this.data('apw-attribute'), - APIURL: $this.data('apw-url'), - callback: $this.data('apw-callback'), - element: $this, - interval: $this.data('apw-interval'), + setupAutoSubmit () { + $('body').on('change', '.select-auto-submit', function () { + if ($(this).val()) { + $(this.form).trigger('submit'); + } }); - }); -} + } -App.prototype.setupAutoSubmit = function () { - $('body').on('change', '.select-auto-submit', function () { - if ($(this).val()) { - $(this.form).trigger('submit'); - } - }); -} + setupFullHeightResizing () { + var self = this; -App.prototype.setupNavbarCollapse = function () { - $(document).keyup(function(e) { - if (e.keyCode === 27) { - $('.navbar-collapse').collapse('hide'); - } - }); + this.resizeFullHeight(); - $('body').on('click', 'a', function (event) { - if (!$(this).hasAnyClass(['dropdown-toggle'])) { - $('.navbar-collapse').collapse('hide'); - } - }); -} + this.window.resize(function() { + self.resizeFullHeight(); + }); + } -App.prototype.setupNewWindowAnchor = function () { - $('body').on('click', 'a.new_window', function (event) { - event.preventDefault(); - var newWindow = window.open($(this).attr('href'), '_blank'); - newWindow.focus(); - }); -} + setupItemsSelector () { + var app = this; + app.lastChecked = null; -App.prototype.setupScrollView = function () { - $('.scrollable').scrollview(); -} - -App.prototype.setupItemsSelector = function () { - var app = this; - app.lastChecked = null; - - $('body').on('click', '.check-all', function (event) { - var checked = $(event.target).prop('checked'); - var $checkBoxes = $('.check-all-slave'); - - $checkBoxes.prop('checked', checked); - $checkBoxes.trigger('change'); - }); - - $('body').on('click', '.check-all-slave', function(e) { - if(!app.lastChecked) { - app.lastChecked = this; - return; - } - if(e.shiftKey) { + $('body').on('click', '.check-all', function (event) { + var checked = $(event.target).prop('checked'); var $checkBoxes = $('.check-all-slave'); - var start = $checkBoxes.index(this); - var end = $checkBoxes.index(app.lastChecked); + $checkBoxes.prop('checked', checked); + $checkBoxes.trigger('change'); + }); - $checkBoxes.slice( - Math.min(start,end), Math.max(start,end) + 1 - ).prop('checked', app.lastChecked.checked).trigger('change'); - } - app.lastChecked = this; - }) -} - -App.prototype.setupSelect2 = function () { - $('.select2').select2({ - dropdownAutoWidth: true, - width: '100%' - }); - - $('.select2-tags').select2({ - templateSelection: App.tagSelectionTemplate, - templateResult: App.tagResultTemplate, - width: '100%' - }); -} - -App.prototype.setupFullHeightResizing = function () { - var self = this; - - this.resizeFullHeight(); - - this.window.resize(function() { - self.resizeFullHeight(); - }); -} - -App.prototype.resizeFullHeight = function () { - $('.full-height').height(this.window.height() - $('.full-height').data('height-difference')); + $('body').on('click', '.check-all-slave', function(e) { + if(!app.lastChecked) { + app.lastChecked = this; + return; + } + if(e.shiftKey) { + var $checkBoxes = $('.check-all-slave'); + + var start = $checkBoxes.index(this); + var end = $checkBoxes.index(app.lastChecked); + + $checkBoxes.slice( + Math.min(start,end), Math.max(start,end) + 1 + ).prop('checked', app.lastChecked.checked).trigger('change'); + } + app.lastChecked = this; + }) + } + + setupNavbarCollapse () { + $(document).keyup(function(e) { + if (e.keyCode === 27) { + $('.navbar-collapse').collapse('hide'); + } + }); + + $('body').on('click', 'a', function (event) { + if (!$(this).hasAnyClass(['dropdown-toggle'])) { + $('.navbar-collapse').collapse('hide'); + } + }); + } + + setupNewWindowAnchor () { + $('body').on('click', 'a.new_window', function (event) { + event.preventDefault(); + var newWindow = window.open($(this).attr('href'), '_blank'); + newWindow.focus(); + }); + } + + setupScrollView () { + $('.scrollable').scrollview(); + } + + setupSelect2 () { + $('.select2').select2({ + dropdownAutoWidth: true, + width: '100%' + }); + + $('.select2-tags').select2({ + templateSelection: MayanApp.tagSelectionTemplate, + templateResult: MayanApp.tagResultTemplate, + width: '100%' + }); + } + + resizeFullHeight () { + $('.full-height').height(this.window.height() - $('.full-height').data('height-difference')); + } } diff --git a/mayan/apps/appearance/static/appearance/js/mayan_image.js b/mayan/apps/appearance/static/appearance/js/mayan_image.js index 1d9103364e..d8db8a9290 100644 --- a/mayan/apps/appearance/static/appearance/js/mayan_image.js +++ b/mayan/apps/appearance/static/appearance/js/mayan_image.js @@ -1,71 +1,80 @@ 'use strict'; -var MayanImage = function (options) { - this.element = options.element; - this.load(); -} +class MayanImage { + constructor (options) { + this.element = options.element; + this.load(); + } -MayanImage.intialize = function () { - var app = this; + static intialize () { + var app = this; - this.fancybox = $().fancybox({ - animationDuration : 400, - buttons : [ - 'fullScreen', - 'close', - ], - selector: 'a.fancybox', - afterShow: function (instance, current) { - $('a.a-caption').on('click', function(event) { - instance.close(true); - }); - }, - infobar: true, + this.fancybox = $().fancybox({ + animationDuration : 300, + buttons : [ + 'fullScreen', + 'close', + ], + selector: 'a.fancybox', + afterShow: function (instance, current) { + $('a.a-caption').on('click', function(event) { + instance.close(true); + }); + }, + infobar: true, - }); + }); - $('img.lazy-load').lazyload({ - appear: function(elements_left, settings) { - new MayanImage({element: $(this)}); - }, - threshold: 400, - }); + $('img.lazy-load').lazyload({ + appear: function(elements_left, settings) { + new MayanImage({element: $(this)}); + }, + threshold: 400, + }); - $('img.lazy-load-carousel').lazyload({ - appear: function(elements_left, settings) { - new MayanImage({element: $(this)}); - }, - container: $('#carousel-container'), - threshold: 2000, - }); + $('img.lazy-load-carousel').lazyload({ + appear: function(elements_left, settings) { + new MayanImage({element: $(this)}); + }, + container: $('#carousel-container'), + threshold: 2000, + }); - $('.lazy-load').on('load', function() { - $(this).hide(); - $(this).fadeIn(); - $(this).siblings('.spinner-container').remove(); - $(this).removeClass('lazy-load pull-left'); - }); + $('.lazy-load').on('load', function() { + $(this).hide(); + $(this).fadeIn(300); + $(this).siblings('.spinner-container').remove(); + $(this).removeClass('lazy-load pull-left'); + }); - $('.lazy-load-carousel').on('load', function() { - $(this).hide(); - $(this).fadeIn(); - $(this).siblings('.spinner-container').remove(); - $(this).removeClass('lazy-load-carousel pull-left'); - }); + $('.lazy-load-carousel').on('load', function() { + $(this).hide(); + $(this).fadeIn(300); + $(this).siblings('.spinner-container').remove(); + $(this).removeClass('lazy-load-carousel pull-left'); + }); + } + + static timerFunction () { + $.fn.matchHeight._maintainScroll = true; + $.fn.matchHeight._update(); + } + + load () { + var self = this; + var container = this.element.parent().parent().parent(); + + this.element.on('error', (function(event) { + container.html(MayanImage.templateInvalidDocument); + })); + + this.element.attr('src', this.element.attr('data-url')); + $.fn.matchHeight._maintainScroll = true; + + clearTimeout(MayanImage.timer); + MayanImage.timer = setTimeout(MayanImage.timerFunction, 100); + }; } MayanImage.templateInvalidDocument = $('#template-invalid-document').html(); - - -MayanImage.prototype.load = function () { - var self = this; - var container = this.element.parent().parent().parent(); - - this.element.on('error', (function(event) { - container.html(MayanImage.templateInvalidDocument); - })); - - this.element.attr('src', this.element.attr('data-url')); - $.fn.matchHeight._update(); - $.fn.matchHeight._maintainScroll = true; -}; +MayanImage.timer = setTimeout(null); diff --git a/mayan/apps/appearance/static/appearance/js/partial_navigation.js b/mayan/apps/appearance/static/appearance/js/partial_navigation.js index b1590a0869..55e55811c9 100644 --- a/mayan/apps/appearance/static/appearance/js/partial_navigation.js +++ b/mayan/apps/appearance/static/appearance/js/partial_navigation.js @@ -13,274 +13,291 @@ $.fn.hasAnyClass = function() { return false; } -var PartialNavigation = function (parameters) { - parameters = parameters || {}; +class PartialNavigation { + constructor (parameters) { + parameters = parameters || {}; - // lastLocation - used as the AJAX referer - this.lastLocation = null; + // lastLocation - used as the AJAX referer + this.lastLocation = null; - // initialURL - the URL to send users when trying to access the / URL - this.initialURL = parameters.initialURL || null; + // initialURL - the URL to send users when trying to access the / URL + this.initialURL = parameters.initialURL || null; - // excludeAnchorClasses - Anchors with any of these classes will not be processes as AJAX anchors - this.excludeAnchorClasses = parameters.excludeAnchorClasses || []; + // disabledAnchorClasses - Anchors with any of these classes will not be + // processes as AJAX anchors and their events nulled + this.disabledAnchorClasses = parameters.disabledAnchorClasses || []; - // formBeforeSerializeCallbacks - Callbacks to execute before submitting an ajaxForm - this.formBeforeSerializeCallbacks = parameters.formBeforeSerializeCallbacks || []; + // excludeAnchorClasses - Anchors with any of these classes will not be + // processes as AJAX anchors + this.excludeAnchorClasses = parameters.excludeAnchorClasses || []; - if (!this.initialURL) { - alert('Need to setup initialURL'); - } -} + // formBeforeSerializeCallbacks - Callbacks to execute before submitting an ajaxForm + this.formBeforeSerializeCallbacks = parameters.formBeforeSerializeCallbacks || []; -PartialNavigation.prototype.initialize = function () { - this.setupAjaxAnchors(); - this.setupAjaxNavigation(); - this.setupAjaxForm(); -} - -PartialNavigation.prototype.filterLocation = function (newLocation) { - /* - * Method to validate new locations - */ - var uri = new URI(newLocation); - var currentLocation = new URI(location); - - if (uri.path() === '') { - // href with no path remain in the same location - // We strip the same location query and use the new href's one - uri.path( - new URI(currentLocation.fragment()).path() - ) - return uri.toString(); - } - - if (uri.path() === '/') { - // Root URL is not allowed - return this.initialURL; - } - - return newLocation; -} - -PartialNavigation.prototype.loadAjaxContent = function (url) { - /* - * Method to load and display partial backend views to the main - * view port. - */ - var app = this; - - url = this.filterLocation(url); - $.ajax({ - async: true, - mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file - url: url, - type: 'GET', - success: function (data, textStatus, response){ - if (response.status == 278) { - // Handle redirects - var newLocation = response.getResponseHeader('Location'); - - app.setLocation(newLocation); - app.lastLocation = newLocation; - } else { - app.lastLocation = url; - if (response.getResponseHeader('Content-Disposition')) { - window.location = this.url; - } else { - $('#ajax-content').html(data); - } - } - }, - error: function (jqXHR, textStatus, errorThrown){ - app.processAjaxRequestError(jqXHR); - }, - dataType: 'html', - }); -} - -PartialNavigation.prototype.onAnchorClick = function ($this, event) { - /* - * Anchor click event manager. We intercept all click events and - * route them to load the content via AJAX instead. - */ - var url; - - if ($this.hasAnyClass(this.excludeAnchorClasses)) { - return true; - } - - url = $this.attr('href'); - if (url === undefined) { - return true; - } - - if (url.indexOf('javascript:;') > -1) { - // Ignore links meant to execute javascript on click. - return true; - } - - if (url === '#') { - // Ignore links with hash at the. - return true; - } - - event.preventDefault(); - - if (event.ctrlKey) { - window.open(url); - return false; - } - - if (!($this.hasClass('disabled') || $this.parent().hasClass('disabled'))) { - this.setLocation(url); - } -} - -PartialNavigation.prototype.processAjaxRequestError = function (jqXHR) { - /* - * Method to process an AJAX request and make it presentable to the - * user. - */ - - if (djangoDEBUG) { - $('#ajax-content').html('
' + jqXHR.responseText + '
'); - } else { - if (jqXHR.status == 0) { - $('#modal-server-error .modal-body').html($('#template-error').html()); - $('#modal-server-error').modal('show') - } else { - $('#ajax-content').html(jqXHR.responseText); - } - } -} - -PartialNavigation.prototype.setLocation = function (newLocation, pushState) { - /* - * Method to update the browsers history and trigger a page update. - */ - - // Validate the new location first. - newLocation = this.filterLocation(newLocation); - - if (typeof pushState === 'undefined') { - // Check if we should just load the content or load the content - // and update the history. - pushState = true; - } - - var currentLocation = new URI(location); - currentLocation.fragment(newLocation); - - if (pushState) { - history.pushState({}, '', currentLocation); - } - this.loadAjaxContent(newLocation); -} - -PartialNavigation.prototype.setupAjaxAnchors = function () { - /* - * Setup the new click event handler. - */ - var app = this; - $('body').on('click', 'a', function (event) { - app.onAnchorClick($(this), event); - }); -} - -PartialNavigation.prototype.setupAjaxForm = function () { - /* - * Method to setup the handling of form in an AJAX way. - */ - var app = this; - var lastAjaxFormData = {}; - - $('form').ajaxForm({ - async: true, - beforeSerialize: function($form, options) { - // Manage any callback registered to preprocess the form. - $.each(app.formBeforeSerializeCallbacks, function (index, value) { - value($form, options); - }); - }, - beforeSubmit: function(arr, $form, options) { - var uri = new URI(location); - var uriFragment = uri.fragment(); - var url = $form.attr('action') || uriFragment; - - options.url = url; - lastAjaxFormData.url = url + '?' + decodeURIComponent($form.serialize()); - - if ($form.attr('target') == '_blank') { - // If the form has a target attribute we emulate it by - // opening a new window and passing the form serialized - // data as the query. - window.open( - $form.attr('action') + '?' + decodeURIComponent($form.serialize()) - ); - - return false; - } - }, - dataType: 'html', - delegation: true, - error: function(jqXHR, textStatus, errorThrown){ - app.processAjaxRequestError(jqXHR); - }, - mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file - success: function(data, textStatus, request){ - if (request.status == 278) { - // Handle redirects after submitting the form - var newLocation = request.getResponseHeader('Location'); - var uri = new URI(newLocation); - var uriFragment = uri.fragment(); - var currentUri = new URI(window.location.hash); - var currentUriFragment = currentUri.fragment(); - var url = uriFragment || currentUriFragment; - - app.setLocation(newLocation); - } else { - var currentUri = new URI(window.location.hash); - currentUri.fragment(lastAjaxFormData.url); - history.pushState({}, '', currentUri); - $('#ajax-content').html(data); - } + if (!this.initialURL) { + alert('Need to setup initialURL'); } - }); -} + } -PartialNavigation.prototype.setupAjaxNavigation = function () { - /* - * Setup the navigation method using the hash of the location. - * Also handles the back button event and loads via AJAX any - * URL in the location when the app first launches. Registers - * a callback to send an emulated HTTP_REFERER so that the backends - * code will still work without change. - */ - var app = this; + initialize () { + this.setupAjaxAnchors(); + this.setupAjaxNavigation(); + this.setupAjaxForm(); + } - // Load ajax content when the hash changes - if (window.history && window.history.pushState) { - $(window).on('popstate', function() { - var uri = new URI(location); - var uriFragment = uri.fragment(); - app.setLocation(uriFragment, false); + filterLocation (newLocation) { + /* + * Method to validate new locations + */ + var uri = new URI(newLocation); + var currentLocation = new URI(location); + + if (uri.path() === '') { + // href with no path remain in the same location + // We strip the same location query and use the new href's one + uri.path( + new URI(currentLocation.fragment()).path() + ) + return uri.toString(); + } + + if (uri.path() === '/') { + // Root URL is not allowed + return this.initialURL; + } + + return newLocation; + } + + loadAjaxContent (url) { + /* + * Method to load and display partial backend views to the main + * view port. + */ + var app = this; + + url = this.filterLocation(url); + $.ajax({ + async: true, + mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file + url: url, + type: 'GET', + success: function (data, textStatus, response){ + if (response.status == 278) { + // Handle redirects + var newLocation = response.getResponseHeader('Location'); + + app.setLocation(newLocation); + app.lastLocation = newLocation; + } else { + app.lastLocation = url; + if (response.getResponseHeader('Content-Disposition')) { + window.location = this.url; + } else { + $('#ajax-content').html(data); + } + } + }, + error: function (jqXHR, textStatus, errorThrown){ + app.processAjaxRequestError(jqXHR); + }, + dataType: 'html', }); } - // Load any initial address in the URL of the browser - if (window.location.hash) { - var uri = new URI(window.location.hash); - var uriFragment = uri.fragment(); - this.setLocation(uriFragment); - } else { - this.setLocation('/'); + onAnchorClick ($this, event) { + /* + * Anchor click event manager. We intercept all click events and + * route them to load the content via AJAX instead. + */ + var url; + + if ($this.hasAnyClass(this.excludeAnchorClasses)) { + return true; + } + + if ($this.hasAnyClass(this.disabledAnchorClasses)) { + event.preventDefault(); + return false; + } + + if ($this.parents().hasAnyClass(this.disabledAnchorClasses)) { + event.preventDefault(); + return false; + } + + url = $this.attr('href'); + if (url === undefined) { + return true; + } + + if (url.indexOf('javascript:;') > -1) { + // Ignore links meant to execute javascript on click. + return true; + } + + if (url === '#') { + // Ignore links with hash at the. + return true; + } + + event.preventDefault(); + + if (event.ctrlKey) { + window.open(url); + return false; + } + + if (!($this.hasClass('disabled') || $this.parent().hasClass('disabled'))) { + this.setLocation(url); + } } - $.ajaxSetup({ - beforeSend: function (jqXHR, settings) { - // Emulate the HTTP_REFERER. - jqXHR.setRequestHeader('X-Alt-Referer', app.lastLocation); - }, - }); + processAjaxRequestError (jqXHR) { + /* + * Method to process an AJAX request and make it presentable to the + * user. + */ + + if (djangoDEBUG) { + $('#ajax-content').html('
' + jqXHR.responseText + '
'); + } else { + if (jqXHR.status == 0) { + $('#modal-server-error .modal-body').html($('#template-error').html()); + $('#modal-server-error').modal('show') + } else { + $('#ajax-content').html(jqXHR.responseText); + } + } + } + + setLocation (newLocation, pushState) { + /* + * Method to update the browsers history and trigger a page update. + */ + + // Validate the new location first. + newLocation = this.filterLocation(newLocation); + + if (typeof pushState === 'undefined') { + // Check if we should just load the content or load the content + // and update the history. + pushState = true; + } + + var currentLocation = new URI(location); + currentLocation.fragment(newLocation); + + if (pushState) { + history.pushState({}, '', currentLocation); + } + this.loadAjaxContent(newLocation); + } + + setupAjaxAnchors () { + /* + * Setup the new click event handler. + */ + var app = this; + $('body').on('click', 'a', function (event) { + app.onAnchorClick($(this), event); + }); + } + + setupAjaxForm () { + /* + * Method to setup the handling of form in an AJAX way. + */ + var app = this; + var lastAjaxFormData = {}; + + $('form').ajaxForm({ + async: true, + beforeSerialize: function($form, options) { + // Manage any callback registered to preprocess the form. + $.each(app.formBeforeSerializeCallbacks, function (index, value) { + value($form, options); + }); + }, + beforeSubmit: function(arr, $form, options) { + var uri = new URI(location); + var uriFragment = uri.fragment(); + var url = $form.attr('action') || uriFragment; + + options.url = url; + lastAjaxFormData.url = url + '?' + decodeURIComponent($form.serialize()); + + if ($form.attr('target') == '_blank') { + // If the form has a target attribute we emulate it by + // opening a new window and passing the form serialized + // data as the query. + window.open( + $form.attr('action') + '?' + decodeURIComponent($form.serialize()) + ); + + return false; + } + }, + dataType: 'html', + delegation: true, + error: function(jqXHR, textStatus, errorThrown){ + app.processAjaxRequestError(jqXHR); + }, + mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file + success: function(data, textStatus, request){ + if (request.status == 278) { + // Handle redirects after submitting the form + var newLocation = request.getResponseHeader('Location'); + var uri = new URI(newLocation); + var uriFragment = uri.fragment(); + var currentUri = new URI(window.location.hash); + var currentUriFragment = currentUri.fragment(); + var url = uriFragment || currentUriFragment; + + app.setLocation(newLocation); + } else { + var currentUri = new URI(window.location.hash); + currentUri.fragment(lastAjaxFormData.url); + history.pushState({}, '', currentUri); + $('#ajax-content').html(data); + } + } + }); + } + + setupAjaxNavigation () { + /* + * Setup the navigation method using the hash of the location. + * Also handles the back button event and loads via AJAX any + * URL in the location when the app first launches. Registers + * a callback to send an emulated HTTP_REFERER so that the backends + * code will still work without change. + */ + var app = this; + + // Load ajax content when the hash changes + if (window.history && window.history.pushState) { + $(window).on('popstate', function() { + var uri = new URI(location); + var uriFragment = uri.fragment(); + app.setLocation(uriFragment, false); + }); + } + + // Load any initial address in the URL of the browser + if (window.location.hash) { + var uri = new URI(window.location.hash); + var uriFragment = uri.fragment(); + this.setLocation(uriFragment); + } else { + this.setLocation('/'); + } + + $.ajaxSetup({ + beforeSend: function (jqXHR, settings) { + // Emulate the HTTP_REFERER. + jqXHR.setRequestHeader('X-Alt-Referer', app.lastLocation); + }, + }); + } } diff --git a/mayan/apps/appearance/templates/appearance/base.html b/mayan/apps/appearance/templates/appearance/base.html index 03924406b5..677f8c7758 100644 --- a/mayan/apps/appearance/templates/appearance/base.html +++ b/mayan/apps/appearance/templates/appearance/base.html @@ -11,7 +11,7 @@ * current location's path as the new hash */ document.write(' diff --git a/mayan/apps/appearance/templates/appearance/base_plain.html b/mayan/apps/appearance/templates/appearance/base_plain.html index 6c33c4fd4a..a2a9a8b82f 100644 --- a/mayan/apps/appearance/templates/appearance/base_plain.html +++ b/mayan/apps/appearance/templates/appearance/base_plain.html @@ -31,16 +31,6 @@ if (currentHash.length) { window.location = currentHash.substring(1); } - - function waitForJQuery(func) { - if (window.jQuery) { - func(); - } else { - setTimeout(function() { - waitForJQuery(func) - }, 50); - } - } diff --git a/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html b/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html index 063ffe7111..93bf83dbec 100644 --- a/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html +++ b/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html @@ -4,18 +4,6 @@ {% load common_tags %} {% load navigation_tags %} - -

@@ -53,12 +41,11 @@
{% endif %} -
+
{% for object in object_list %}
-
+ + diff --git a/mayan/apps/appearance/templates/appearance/root.html b/mayan/apps/appearance/templates/appearance/root.html index 8eff3b1735..f94aa88285 100644 --- a/mayan/apps/appearance/templates/appearance/root.html +++ b/mayan/apps/appearance/templates/appearance/root.html @@ -27,17 +27,6 @@ {% block stylesheets %}{% endblock %} - {% if appearance_type == 'plain' %} @@ -152,15 +141,28 @@ - - + {% endspaceless %} diff --git a/mayan/apps/documents/icons.py b/mayan/apps/documents/icons.py index 99d43e2683..396a95b9dc 100644 --- a/mayan/apps/documents/icons.py +++ b/mayan/apps/documents/icons.py @@ -7,10 +7,10 @@ icon_dashboard_document_types = Icon( driver_name='fontawesome', symbol='book' ) icon_dashboard_documents_in_trash = Icon( - driver_name='fontawesome', symbol='book' + driver_name='fontawesome', symbol='trash' ) icon_dashboard_pages_per_month = Icon( - driver_name='fontawesome', symbol='trash' + driver_name='fontawesome', symbol='copy' ) icon_dashboard_new_documents_this_month = Icon( driver_name='fontawesome', symbol='calendar' diff --git a/mayan/apps/documents/links.py b/mayan/apps/documents/links.py index 37e3007e4a..2feaf19dc4 100644 --- a/mayan/apps/documents/links.py +++ b/mayan/apps/documents/links.py @@ -152,7 +152,7 @@ link_document_multiple_document_type_edit = Link( view='documents:document_multiple_document_type_edit' ) link_document_multiple_download = Link( - text=_('Download'), view='documents:document_multiple_download_form' + text=_('Advanced download'), view='documents:document_multiple_download_form' ) link_document_multiple_update_page_count = Link( text=_('Recalculate page count'), diff --git a/mayan/apps/documents/settings.py b/mayan/apps/documents/settings.py index 70b849a313..3bee9448ea 100644 --- a/mayan/apps/documents/settings.py +++ b/mayan/apps/documents/settings.py @@ -32,7 +32,7 @@ setting_display_width = namespace.add_setting( global_name='DOCUMENTS_DISPLAY_WIDTH', default='3600' ) settings_document_page_image_cache_time = namespace.add_setting( - global_name='DOCUMENTS_PAGE_IMAGE_CACHE_TIME', default='3600' + global_name='DOCUMENTS_PAGE_IMAGE_CACHE_TIME', default='31556926' ) setting_documentimagecache_storage = namespace.add_setting( global_name='DOCUMENTS_CACHE_STORAGE_BACKEND', diff --git a/mayan/apps/documents/templates/documents/document_print.html b/mayan/apps/documents/templates/documents/document_print.html index 95880bd679..65dc7c5a07 100644 --- a/mayan/apps/documents/templates/documents/document_print.html +++ b/mayan/apps/documents/templates/documents/document_print.html @@ -1,7 +1,7 @@ -{% load documents_tags %} - {% extends 'appearance/base_plain.html' %} +{% load documents_tags %} + {% block title %}{{ title }}{% endblock title %} {% block content_plain %} diff --git a/mayan/apps/documents/templates/documents/forms/widgets/document_page_image.html b/mayan/apps/documents/templates/documents/forms/widgets/document_page_image.html index 619da51a44..4874a1ec9e 100644 --- a/mayan/apps/documents/templates/documents/forms/widgets/document_page_image.html +++ b/mayan/apps/documents/templates/documents/forms/widgets/document_page_image.html @@ -1,16 +1,16 @@ {% load documents_tags %} -
-
+ -
+ -
+ diff --git a/mayan/apps/events/links.py b/mayan/apps/events/links.py index 6551483324..2dd2e0eb36 100644 --- a/mayan/apps/events/links.py +++ b/mayan/apps/events/links.py @@ -80,7 +80,7 @@ link_user_notifications_list = Link( html_data={ 'apw-attribute': 'count', 'apw-interval': '5000', 'apw-url': '/api/notifications/?read=False', - 'apw-callback': 'App.mayanNotificationBadge' + 'apw-callback': 'MayanAppClass.mayanNotificationBadge' }, icon_class=icon_user_notifications_list, text='', view='events:user_notifications_list' ) diff --git a/mayan/apps/metadata/static/metadata/js/metadata_form.js b/mayan/apps/metadata/static/metadata/js/metadata_form.js index a39726fac8..8bbdc29912 100644 --- a/mayan/apps/metadata/static/metadata/js/metadata_form.js +++ b/mayan/apps/metadata/static/metadata/js/metadata_form.js @@ -1,11 +1,9 @@ 'use strict'; -waitForJQuery(function() { - jQuery(document).ready(function() { - $('.metadata-value').on('input', function(event) { - // Check the checkbox next to a metadata value input when there is - // data entry in the value's input. - $(event.target).parents('tr').find(':checkbox').prop('checked', true); - }); +jQuery(document).ready(function() { + $('.metadata-value').on('input', function(event) { + // Check the checkbox next to a metadata value input when there is + // data entry in the value's input. + $(event.target).parents('tr').find(':checkbox').prop('checked', true); }); }); diff --git a/mayan/apps/navigation/classes.py b/mayan/apps/navigation/classes.py index 0f735aff3a..7f11cc2b65 100644 --- a/mayan/apps/navigation/classes.py +++ b/mayan/apps/navigation/classes.py @@ -143,8 +143,8 @@ class Menu(object): result = [] try: - request = Variable('request').resolve(context) - except VariableDoesNotExist: + request = context.request + except AttributeError: # There is no request variable, most probable a 500 in a test view # Don't return any resolved links then. logger.warning('No request variable, aborting menu resolution') @@ -286,7 +286,7 @@ class Link(object): app_label='acls', model_name='AccessControlList' ) - request = Variable('request').resolve(context) + request = context.request current_path = request.META['PATH_INFO'] current_view = resolve(current_path).view_name diff --git a/setup.py b/setup.py index 4ae12cca73..b42264263e 100644 --- a/setup.py +++ b/setup.py @@ -62,8 +62,6 @@ PyYAML==3.13 celery==3.1.24 django-activity-stream==0.6.5 django-autoadmin==1.1.1 -#django-celery==3.2.1 - Use fork below until patch https://github.com/celery/django-celery/pull/552 is accepted. -https://github.com/mayan-edms/django-celery/zipball/master#egg=django-celery django-colorful==1.2 django-cors-headers==2.2.0 django-downloadview==1.9 @@ -128,6 +126,9 @@ setup( 'Topic :: Communications :: File Sharing', ], description='Free Open Source Electronic Document Management System', + dependency_links=[ + "https://github.com/mayan-edms/django-celery/zipball/master#egg=django-celery" + ], include_package_data=True, install_requires=install_requires, license='Apache 2.0',